diff options
Diffstat (limited to 'usr/src/uts/intel/io/dktp/disk/cmdk.c')
-rw-r--r-- | usr/src/uts/intel/io/dktp/disk/cmdk.c | 1995 |
1 files changed, 1995 insertions, 0 deletions
diff --git a/usr/src/uts/intel/io/dktp/disk/cmdk.c b/usr/src/uts/intel/io/dktp/disk/cmdk.c new file mode 100644 index 0000000000..5308314aca --- /dev/null +++ b/usr/src/uts/intel/io/dktp/disk/cmdk.c @@ -0,0 +1,1995 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/scsi/scsi.h> +#include <sys/dktp/cm.h> +#include <sys/dktp/quetypes.h> +#include <sys/dktp/queue.h> +#include <sys/dktp/fctypes.h> +#include <sys/dktp/flowctrl.h> +#include <sys/dktp/cmdev.h> +#include <sys/dkio.h> +#include <sys/dktp/tgdk.h> +#include <sys/dktp/dadk.h> +#include <sys/dktp/bbh.h> +#include <sys/dktp/altsctr.h> +#include <sys/dktp/cmdk.h> + +#include <sys/stat.h> +#include <sys/vtoc.h> +#include <sys/file.h> +#include <sys/dktp/dadkio.h> +#include <sys/aio_req.h> + +#include <sys/cmlb.h> + +/* + * Local Static Data + */ +#ifdef CMDK_DEBUG +#define DENT 0x0001 +#define DIO 0x0002 + +static int cmdk_debug = DIO; +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +/* + * NDKMAP is the base number for accessing the fdisk partitions. + * c?d?p0 --> cmdk@?,?:q + */ +#define PARTITION0_INDEX (NDKMAP + 0) + +#define DKTP_DATA (dkp->dk_tgobjp)->tg_data +#define DKTP_EXT (dkp->dk_tgobjp)->tg_ext + +static void *cmdk_state; + +/* + * the cmdk_attach_mutex protects cmdk_max_instance in multi-threaded + * attach situations + */ +static kmutex_t cmdk_attach_mutex; +static int cmdk_max_instance = 0; + +/* + * Panic dumpsys state + * There is only a single flag that is not mutex locked since + * the system is prevented from thread switching and cmdk_dump + * will only be called in a single threaded operation. + */ +static int cmdk_indump; + +/* + * Local Function Prototypes + */ +static int cmdk_create_obj(dev_info_t *dip, struct cmdk *dkp); +static void cmdk_destroy_obj(dev_info_t *dip, struct cmdk *dkp); +static void cmdkmin(struct buf *bp); +static int cmdkrw(dev_t dev, struct uio *uio, int flag); +static int cmdkarw(dev_t dev, struct aio_req *aio, int flag); + +/* + * Bad Block Handling Functions Prototypes + */ +static void cmdk_bbh_reopen(struct cmdk *dkp); +static opaque_t cmdk_bbh_gethandle(opaque_t bbh_data, struct buf *bp); +static bbh_cookie_t cmdk_bbh_htoc(opaque_t bbh_data, opaque_t handle); +static void cmdk_bbh_freehandle(opaque_t bbh_data, opaque_t handle); +static void cmdk_bbh_close(struct cmdk *dkp); +static void cmdk_bbh_setalts_idx(struct cmdk *dkp); +static int cmdk_bbh_bsearch(struct alts_ent *buf, int cnt, daddr32_t key); + +static struct bbh_objops cmdk_bbh_ops = { + nulldev, + nulldev, + cmdk_bbh_gethandle, + cmdk_bbh_htoc, + cmdk_bbh_freehandle, + 0, 0 +}; + +static int cmdkopen(dev_t *dev_p, int flag, int otyp, cred_t *credp); +static int cmdkclose(dev_t dev, int flag, int otyp, cred_t *credp); +static int cmdkstrategy(struct buf *bp); +static int cmdkdump(dev_t dev, caddr_t addr, daddr_t blkno, int nblk); +static int cmdkioctl(dev_t, int, intptr_t, int, cred_t *, int *); +static int cmdkread(dev_t dev, struct uio *uio, cred_t *credp); +static int cmdkwrite(dev_t dev, struct uio *uio, cred_t *credp); +static int cmdk_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, + int mod_flags, char *name, caddr_t valuep, int *lengthp); +static int cmdkaread(dev_t dev, struct aio_req *aio, cred_t *credp); +static int cmdkawrite(dev_t dev, struct aio_req *aio, cred_t *credp); + +/* + * Device driver ops vector + */ + +static struct cb_ops cmdk_cb_ops = { + cmdkopen, /* open */ + cmdkclose, /* close */ + cmdkstrategy, /* strategy */ + nodev, /* print */ + cmdkdump, /* dump */ + cmdkread, /* read */ + cmdkwrite, /* write */ + cmdkioctl, /* ioctl */ + nodev, /* devmap */ + nodev, /* mmap */ + nodev, /* segmap */ + nochpoll, /* poll */ + cmdk_prop_op, /* cb_prop_op */ + 0, /* streamtab */ + D_64BIT | D_MP | D_NEW, /* Driver comaptibility flag */ + CB_REV, /* cb_rev */ + cmdkaread, /* async read */ + cmdkawrite /* async write */ +}; + +static int cmdkinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, + void **result); +static int cmdkprobe(dev_info_t *dip); +static int cmdkattach(dev_info_t *dip, ddi_attach_cmd_t cmd); +static int cmdkdetach(dev_info_t *dip, ddi_detach_cmd_t cmd); + +struct dev_ops cmdk_ops = { + DEVO_REV, /* devo_rev, */ + 0, /* refcnt */ + cmdkinfo, /* info */ + nulldev, /* identify */ + cmdkprobe, /* probe */ + cmdkattach, /* attach */ + cmdkdetach, /* detach */ + nodev, /* reset */ + &cmdk_cb_ops, /* driver operations */ + (struct bus_ops *)0 /* bus operations */ +}; + +/* + * This is the loadable module wrapper. + */ +#include <sys/modctl.h> + +extern struct mod_ops mod_driverops; + +static struct modldrv modldrv = { + &mod_driverops, /* Type of module. This one is a driver */ + "Common Direct Access Disk %I%", + &cmdk_ops, /* driver ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, (void *)&modldrv, NULL +}; + +/* Function prototypes for cmlb callbacks */ + +static int cmdk_lb_rdwr(dev_info_t *dip, uchar_t cmd, void *bufaddr, + diskaddr_t start, size_t length); +static int cmdk_lb_getphygeom(dev_info_t *dip, cmlb_geom_t *phygeomp); +static int cmdk_lb_getvirtgeom(dev_info_t *dip, cmlb_geom_t *virtgeomp); +static int cmdk_lb_getcapacity(dev_info_t *dip, diskaddr_t *capp); +static int cmdk_lb_getattribute(dev_info_t *dip, tg_attribute_t *tgattribute); + +static void cmdk_devid_setup(struct cmdk *dkp); +static int cmdk_devid_modser(struct cmdk *dkp); +static int cmdk_get_modser(struct cmdk *dkp, int ioccmd, char *buf, int len); +static int cmdk_devid_fabricate(struct cmdk *dkp); +static int cmdk_devid_read(struct cmdk *dkp); + +static cmlb_tg_ops_t cmdk_lb_ops = { + TG_DK_OPS_VERSION_0, + cmdk_lb_rdwr, + cmdk_lb_getphygeom, + cmdk_lb_getvirtgeom, + cmdk_lb_getcapacity, + cmdk_lb_getattribute +}; + +int +_init(void) +{ + int rval; + + if (rval = ddi_soft_state_init(&cmdk_state, sizeof (struct cmdk), 7)) + return (rval); + + mutex_init(&cmdk_attach_mutex, NULL, MUTEX_DRIVER, NULL); + if ((rval = mod_install(&modlinkage)) != 0) { + mutex_destroy(&cmdk_attach_mutex); + ddi_soft_state_fini(&cmdk_state); + } + return (rval); +} + +int +_fini(void) +{ + return (EBUSY); + + /* + * This has been commented out until cmdk is a true + * unloadable module. Right now x86's are panicking on + * a diskless reconfig boot. + */ + +#if 0 /* bugid 1186679 */ + int rval; + + rval = mod_remove(&modlinkage); + if (rval != 0) + return (rval); + + mutex_destroy(&cmdk_attach_mutex); + ddi_soft_state_fini(&cmdk_state); + + return (0); +#endif +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +/* + * Autoconfiguration Routines + */ +static int +cmdkprobe(dev_info_t *dip) +{ + int instance; + int status; + struct cmdk *dkp; + + instance = ddi_get_instance(dip); + + if (ddi_get_soft_state(cmdk_state, instance)) + return (DDI_PROBE_PARTIAL); + + if ((ddi_soft_state_zalloc(cmdk_state, instance) != DDI_SUCCESS) || + ((dkp = ddi_get_soft_state(cmdk_state, instance)) == NULL)) + return (DDI_PROBE_PARTIAL); + + mutex_init(&dkp->dk_mutex, NULL, MUTEX_DRIVER, NULL); + rw_init(&dkp->dk_bbh_mutex, NULL, RW_DRIVER, NULL); + dkp->dk_dip = dip; + mutex_enter(&dkp->dk_mutex); + + dkp->dk_dev = makedevice(ddi_driver_major(dip), + ddi_get_instance(dip) << CMDK_UNITSHF); + + /* linkage to dadk and strategy */ + if (cmdk_create_obj(dip, dkp) != DDI_SUCCESS) { + mutex_exit(&dkp->dk_mutex); + mutex_destroy(&dkp->dk_mutex); + rw_destroy(&dkp->dk_bbh_mutex); + ddi_soft_state_free(cmdk_state, instance); + return (DDI_PROBE_PARTIAL); + } + + status = dadk_probe(DKTP_DATA, KM_NOSLEEP); + if (status != DDI_PROBE_SUCCESS) { + cmdk_destroy_obj(dip, dkp); /* dadk/strategy linkage */ + mutex_exit(&dkp->dk_mutex); + mutex_destroy(&dkp->dk_mutex); + rw_destroy(&dkp->dk_bbh_mutex); + ddi_soft_state_free(cmdk_state, instance); + return (status); + } + + mutex_exit(&dkp->dk_mutex); +#ifdef CMDK_DEBUG + if (cmdk_debug & DENT) + PRF("cmdkprobe: instance= %d name= `%s`\n", + instance, ddi_get_name_addr(dip)); +#endif + return (status); +} + +static int +cmdkattach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + int instance; + struct cmdk *dkp; + char *node_type; + + if (cmd != DDI_ATTACH) + return (DDI_FAILURE); + + instance = ddi_get_instance(dip); + if (!(dkp = ddi_get_soft_state(cmdk_state, instance))) + return (DDI_FAILURE); + + mutex_enter(&dkp->dk_mutex); + + /* dadk_attach is an empty function that only returns SUCCESS */ + (void) dadk_attach(DKTP_DATA); + + node_type = (DKTP_EXT->tg_nodetype); + + /* + * this open allows cmlb to read the device + * and determine the label types + * so that cmlb can create minor nodes for device + */ + + /* open the target disk */ + if (dadk_open(DKTP_DATA, 0) != DDI_SUCCESS) + goto fail2; + + /* mark as having opened target */ + dkp->dk_flag |= CMDK_TGDK_OPEN; + + cmlb_alloc_handle((cmlb_handle_t *)&dkp->dk_cmlbhandle); + + if (cmlb_attach(dip, + &cmdk_lb_ops, + DTYPE_DIRECT, /* device_type */ + 0, /* removable */ + node_type, + CMLB_CREATE_ALTSLICE_VTOC_16_DTYPE_DIRECT, /* alter_behaviour */ + dkp->dk_cmlbhandle) != 0) + goto fail1; + + /* Calling validate will create minor nodes according to disk label */ + (void) cmlb_validate(dkp->dk_cmlbhandle); + + /* set bbh (Bad Block Handling) */ + cmdk_bbh_reopen(dkp); + + /* setup devid string */ + cmdk_devid_setup(dkp); + + mutex_enter(&cmdk_attach_mutex); + if (instance > cmdk_max_instance) + cmdk_max_instance = instance; + mutex_exit(&cmdk_attach_mutex); + + mutex_exit(&dkp->dk_mutex); + + /* + * Add a zero-length attribute to tell the world we support + * kernel ioctls (for layered drivers) + */ + (void) ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP, + DDI_KERNEL_IOCTL, NULL, 0); + ddi_report_dev(dip); + + return (DDI_SUCCESS); + +fail1: + cmlb_free_handle(&dkp->dk_cmlbhandle); + (void) dadk_close(DKTP_DATA); +fail2: + cmdk_destroy_obj(dip, dkp); + rw_destroy(&dkp->dk_bbh_mutex); + mutex_exit(&dkp->dk_mutex); + mutex_destroy(&dkp->dk_mutex); + ddi_soft_state_free(cmdk_state, instance); + return (DDI_FAILURE); +} + + +static int +cmdkdetach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + struct cmdk *dkp; + int instance; + int max_instance; + + if (cmd != DDI_DETACH) { +#ifdef CMDK_DEBUG + if (cmdk_debug & DIO) { + PRF("cmdkdetach: cmd = %d unknown\n", cmd); + } +#endif + return (DDI_FAILURE); + } + + mutex_enter(&cmdk_attach_mutex); + max_instance = cmdk_max_instance; + mutex_exit(&cmdk_attach_mutex); + + /* check if any instance of driver is open */ + for (instance = 0; instance < max_instance; instance++) { + dkp = ddi_get_soft_state(cmdk_state, instance); + if (!dkp) + continue; + if (dkp->dk_flag & CMDK_OPEN) + return (DDI_FAILURE); + } + + instance = ddi_get_instance(dip); + if (!(dkp = ddi_get_soft_state(cmdk_state, instance))) + return (DDI_SUCCESS); + + mutex_enter(&dkp->dk_mutex); + + /* + * The cmdk_part_info call at the end of cmdkattach may have + * caused cmdk_reopen to do a TGDK_OPEN, make sure we close on + * detach for case when cmdkopen/cmdkclose never occurs. + */ + if (dkp->dk_flag & CMDK_TGDK_OPEN) { + dkp->dk_flag &= ~CMDK_TGDK_OPEN; + (void) dadk_close(DKTP_DATA); + } + + cmlb_detach(dkp->dk_cmlbhandle); + cmlb_free_handle(&dkp->dk_cmlbhandle); + ddi_prop_remove_all(dip); + + cmdk_destroy_obj(dip, dkp); /* dadk/strategy linkage */ + mutex_exit(&dkp->dk_mutex); + mutex_destroy(&dkp->dk_mutex); + rw_destroy(&dkp->dk_bbh_mutex); + ddi_soft_state_free(cmdk_state, instance); + + return (DDI_SUCCESS); +} + +static int +cmdkinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) +{ + dev_t dev = (dev_t)arg; + int instance; + struct cmdk *dkp; + +#ifdef lint + dip = dip; /* no one ever uses this */ +#endif +#ifdef CMDK_DEBUG + if (cmdk_debug & DENT) + PRF("cmdkinfo: call\n"); +#endif + instance = CMDKUNIT(dev); + + switch (infocmd) { + case DDI_INFO_DEVT2DEVINFO: + if (!(dkp = ddi_get_soft_state(cmdk_state, instance))) + return (DDI_FAILURE); + *result = (void *) dkp->dk_dip; + break; + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)(intptr_t)instance; + break; + default: + return (DDI_FAILURE); + } + return (DDI_SUCCESS); +} + +static int +cmdk_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int mod_flags, + char *name, caddr_t valuep, int *lengthp) +{ + struct cmdk *dkp; + diskaddr_t p_lblksrt; + diskaddr_t p_lblkcnt; + +#ifdef CMDK_DEBUG + if (cmdk_debug & DENT) + PRF("cmdk_prop_op: call\n"); +#endif + + dkp = ddi_get_soft_state(cmdk_state, ddi_get_instance(dip)); + + /* + * Our dynamic properties are all device specific and size oriented. + * Requests issued under conditions where size is valid are passed + * to ddi_prop_op_nblocks with the size information, otherwise the + * request is passed to ddi_prop_op. Size depends on valid label. + */ + if ((dev != DDI_DEV_T_ANY) && (dkp != NULL)) { + if (!cmlb_partinfo( + dkp->dk_cmlbhandle, + CMDKPART(dev), + &p_lblkcnt, + &p_lblksrt, + NULL, + NULL)) + return (ddi_prop_op_nblocks(dev, dip, + prop_op, mod_flags, + name, valuep, lengthp, + (uint64_t)p_lblkcnt)); + } + + return (ddi_prop_op(dev, dip, + prop_op, mod_flags, + name, valuep, lengthp)); +} + +/* + * dump routine + */ +static int +cmdkdump(dev_t dev, caddr_t addr, daddr_t blkno, int nblk) +{ + int instance; + struct cmdk *dkp; + diskaddr_t p_lblksrt; + diskaddr_t p_lblkcnt; + struct buf local; + struct buf *bp; + +#ifdef CMDK_DEBUG + if (cmdk_debug & DENT) + PRF("cmdkdump: call\n"); +#endif + instance = CMDKUNIT(dev); + if (!(dkp = ddi_get_soft_state(cmdk_state, instance)) || (blkno < 0)) + return (ENXIO); + + if (cmlb_partinfo( + dkp->dk_cmlbhandle, + CMDKPART(dev), + &p_lblkcnt, + &p_lblksrt, + NULL, + NULL)) { + return (ENXIO); + } + + if ((blkno+nblk) > p_lblkcnt) + return (EINVAL); + + cmdk_indump = 1; /* Tell disk targets we are panic dumpping */ + + bp = &local; + bzero(bp, sizeof (*bp)); + bp->b_flags = B_BUSY; + bp->b_un.b_addr = addr; + bp->b_bcount = nblk << SCTRSHFT; + SET_BP_SEC(bp, ((ulong_t)(p_lblksrt + blkno))); + + (void) dadk_dump(DKTP_DATA, bp); + return (bp->b_error); +} + +/* + * Copy in the dadkio_rwcmd according to the user's data model. If needed, + * convert it for our internal use. + */ +static int +rwcmd_copyin(struct dadkio_rwcmd *rwcmdp, caddr_t inaddr, int flag) +{ + switch (ddi_model_convert_from(flag)) { + case DDI_MODEL_ILP32: { + struct dadkio_rwcmd32 cmd32; + + if (ddi_copyin(inaddr, &cmd32, + sizeof (struct dadkio_rwcmd32), flag)) { + return (EFAULT); + } + + rwcmdp->cmd = cmd32.cmd; + rwcmdp->flags = cmd32.flags; + rwcmdp->blkaddr = (daddr_t)cmd32.blkaddr; + rwcmdp->buflen = cmd32.buflen; + rwcmdp->bufaddr = (caddr_t)(intptr_t)cmd32.bufaddr; + /* + * Note: we do not convert the 'status' field, + * as it should not contain valid data at this + * point. + */ + bzero(&rwcmdp->status, sizeof (rwcmdp->status)); + break; + } + case DDI_MODEL_NONE: { + if (ddi_copyin(inaddr, rwcmdp, + sizeof (struct dadkio_rwcmd), flag)) { + return (EFAULT); + } + } + } + return (0); +} + +/* + * If necessary, convert the internal rwcmdp and status to the appropriate + * data model and copy it out to the user. + */ +static int +rwcmd_copyout(struct dadkio_rwcmd *rwcmdp, caddr_t outaddr, int flag) +{ + switch (ddi_model_convert_from(flag)) { + case DDI_MODEL_ILP32: { + struct dadkio_rwcmd32 cmd32; + + cmd32.cmd = rwcmdp->cmd; + cmd32.flags = rwcmdp->flags; + cmd32.blkaddr = rwcmdp->blkaddr; + cmd32.buflen = rwcmdp->buflen; + ASSERT64(((uintptr_t)rwcmdp->bufaddr >> 32) == 0); + cmd32.bufaddr = (caddr32_t)(uintptr_t)rwcmdp->bufaddr; + + cmd32.status.status = rwcmdp->status.status; + cmd32.status.resid = rwcmdp->status.resid; + cmd32.status.failed_blk_is_valid = + rwcmdp->status.failed_blk_is_valid; + cmd32.status.failed_blk = rwcmdp->status.failed_blk; + cmd32.status.fru_code_is_valid = + rwcmdp->status.fru_code_is_valid; + cmd32.status.fru_code = rwcmdp->status.fru_code; + + bcopy(rwcmdp->status.add_error_info, + cmd32.status.add_error_info, DADKIO_ERROR_INFO_LEN); + + if (ddi_copyout(&cmd32, outaddr, + sizeof (struct dadkio_rwcmd32), flag)) + return (EFAULT); + break; + } + case DDI_MODEL_NONE: { + if (ddi_copyout(rwcmdp, outaddr, + sizeof (struct dadkio_rwcmd), flag)) + return (EFAULT); + } + } + return (0); +} + +/* + * ioctl routine + */ +static int +cmdkioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *credp, int *rvalp) +{ + int instance; + struct scsi_device *devp; + struct cmdk *dkp; + char data[NBPSCTR]; + + instance = CMDKUNIT(dev); + if (!(dkp = ddi_get_soft_state(cmdk_state, instance))) + return (ENXIO); + + bzero(data, sizeof (data)); + + switch (cmd) { + + case DKIOCGMEDIAINFO: { + struct dk_minfo media_info; + struct tgdk_geom phyg; + + /* dadk_getphygeom always returns success */ + (void) dadk_getphygeom(DKTP_DATA, &phyg); + + media_info.dki_lbsize = phyg.g_secsiz; + media_info.dki_capacity = phyg.g_cap; + media_info.dki_media_type = DK_FIXED_DISK; + + if (ddi_copyout(&media_info, (void *)arg, + sizeof (struct dk_minfo), flag)) { + return (EFAULT); + } else { + return (0); + } + } + + case DKIOCINFO: { + struct dk_cinfo *info = (struct dk_cinfo *)data; + + /* controller information */ + info->dki_ctype = (DKTP_EXT->tg_ctype); + info->dki_cnum = ddi_get_instance(ddi_get_parent(dkp->dk_dip)); + (void) strcpy(info->dki_cname, + ddi_get_name(ddi_get_parent(dkp->dk_dip))); + + /* Unit Information */ + info->dki_unit = ddi_get_instance(dkp->dk_dip); + devp = ddi_get_driver_private(dkp->dk_dip); + info->dki_slave = (CMDEV_TARG(devp)<<3) | CMDEV_LUN(devp); + (void) strcpy(info->dki_dname, ddi_driver_name(dkp->dk_dip)); + info->dki_flags = DKI_FMTVOL; + info->dki_partition = CMDKPART(dev); + + info->dki_maxtransfer = maxphys / DEV_BSIZE; + info->dki_addr = 1; + info->dki_space = 0; + info->dki_prio = 0; + info->dki_vec = 0; + + if (ddi_copyout(data, (void *)arg, sizeof (*info), flag)) + return (EFAULT); + else + return (0); + } + + case DKIOCSTATE: { + int state; + int rval; + diskaddr_t p_lblksrt; + diskaddr_t p_lblkcnt; + + if (ddi_copyin((void *)arg, &state, sizeof (int), flag)) + return (EFAULT); + + /* dadk_check_media blocks until state changes */ + if (rval = dadk_check_media(DKTP_DATA, &state)) + return (rval); + + if (state == DKIO_INSERTED) { + + if (cmlb_validate(dkp->dk_cmlbhandle) != 0) + return (ENXIO); + + if (cmlb_partinfo(dkp->dk_cmlbhandle, CMDKPART(dev), + &p_lblkcnt, &p_lblksrt, NULL, NULL)) + return (ENXIO); + + if (p_lblkcnt <= 0) + return (ENXIO); + } + + if (ddi_copyout(&state, (caddr_t)arg, sizeof (int), flag)) + return (EFAULT); + + return (0); + } + + /* + * is media removable? + */ + case DKIOCREMOVABLE: { + int i; + + i = (DKTP_EXT->tg_rmb) ? 1 : 0; + + if (ddi_copyout(&i, (caddr_t)arg, sizeof (int), flag)) + return (EFAULT); + + return (0); + } + + case DKIOCADDBAD: + /* + * This is not an update mechanism to add bad blocks + * to the bad block structures stored on disk. + * + * addbadsec(1M) will update the bad block data on disk + * and use this ioctl to force the driver to re-initialize + * the list of bad blocks in the driver. + */ + + /* start BBH */ + cmdk_bbh_reopen(dkp); + return (0); + + case DKIOCG_PHYGEOM: + case DKIOCG_VIRTGEOM: + case DKIOCGGEOM: + case DKIOCSGEOM: + case DKIOCGAPART: + case DKIOCSAPART: + case DKIOCGVTOC: + case DKIOCSVTOC: + case DKIOCPARTINFO: + case DKIOCGMBOOT: + case DKIOCSMBOOT: + case DKIOCGETEFI: + case DKIOCSETEFI: + case DKIOCPARTITION: + { + int rc; + + rc = cmlb_ioctl( + dkp->dk_cmlbhandle, + dev, + cmd, + arg, + flag, + credp, + rvalp); + if (cmd == DKIOCSVTOC) + cmdk_devid_setup(dkp); + return (rc); + } + + case DIOCTL_RWCMD: { + struct dadkio_rwcmd *rwcmdp; + int status; + + rwcmdp = kmem_alloc(sizeof (struct dadkio_rwcmd), KM_SLEEP); + + status = rwcmd_copyin(rwcmdp, (caddr_t)arg, flag); + + if (status == 0) { + bzero(&(rwcmdp->status), sizeof (struct dadkio_status)); + status = dadk_ioctl(DKTP_DATA, + dev, + cmd, + (uintptr_t)rwcmdp, + flag, + credp, + rvalp); + } + if (status == 0) + status = rwcmd_copyout(rwcmdp, (caddr_t)arg, flag); + + kmem_free(rwcmdp, sizeof (struct dadkio_rwcmd)); + return (status); + } + + default: + return (dadk_ioctl(DKTP_DATA, + dev, + cmd, + arg, + flag, + credp, + rvalp)); + } +} + +/*ARGSUSED1*/ +static int +cmdkclose(dev_t dev, int flag, int otyp, cred_t *credp) +{ + int part; + ulong_t partbit; + int instance; + struct cmdk *dkp; + int lastclose = 1; + int i; + + instance = CMDKUNIT(dev); + if (!(dkp = ddi_get_soft_state(cmdk_state, instance)) || + (otyp >= OTYPCNT)) + return (ENXIO); + + mutex_enter(&dkp->dk_mutex); + + /* check if device has been opened */ + if (!(dkp->dk_flag & CMDK_OPEN)) { + mutex_exit(&dkp->dk_mutex); + return (ENXIO); + } + + part = CMDKPART(dev); + partbit = 1 << part; + + /* account for close */ + if (otyp == OTYP_LYR) { + if (dkp->dk_open_lyr[part]) + dkp->dk_open_lyr[part]--; + } else + dkp->dk_open_reg[otyp] &= ~partbit; + dkp->dk_open_exl &= ~partbit; + + for (i = 0; i < CMDK_MAXPART; i++) + if (dkp->dk_open_lyr[i] != 0) { + lastclose = 0; + break; + } + + if (lastclose) + for (i = 0; i < OTYPCNT; i++) + if (dkp->dk_open_reg[i] != 0) { + lastclose = 0; + break; + } + + mutex_exit(&dkp->dk_mutex); + + if (lastclose) + cmlb_invalidate(dkp->dk_cmlbhandle); + + return (DDI_SUCCESS); +} + +/*ARGSUSED3*/ +static int +cmdkopen(dev_t *dev_p, int flag, int otyp, cred_t *credp) +{ + dev_t dev = *dev_p; + int part; + ulong_t partbit; + int instance; + struct cmdk *dkp; + diskaddr_t p_lblksrt; + diskaddr_t p_lblkcnt; + int i; + int nodelay; + + instance = CMDKUNIT(dev); + if (!(dkp = ddi_get_soft_state(cmdk_state, instance))) + return (ENXIO); + + if (otyp >= OTYPCNT) + return (EINVAL); + + part = CMDKPART(dev); + partbit = 1 << part; + nodelay = (flag & (FNDELAY | FNONBLOCK)); + + mutex_enter(&dkp->dk_mutex); + + if (cmlb_validate(dkp->dk_cmlbhandle) != 0) { + + /* fail if not doing non block open */ + if (!nodelay) { + mutex_exit(&dkp->dk_mutex); + return (ENXIO); + } + } else if (cmlb_partinfo(dkp->dk_cmlbhandle, part, &p_lblkcnt, + &p_lblksrt, NULL, NULL) == 0) { + + if (p_lblkcnt <= 0 && (!nodelay || otyp != OTYP_CHR)) { + mutex_exit(&dkp->dk_mutex); + return (ENXIO); + } + } else { + /* fail if not doing non block open */ + if (!nodelay) { + mutex_exit(&dkp->dk_mutex); + return (ENXIO); + } + } + + if ((DKTP_EXT->tg_rdonly) && (flag & FWRITE)) { + mutex_exit(&dkp->dk_mutex); + return (EROFS); + } + + /* check for part already opend exclusively */ + if (dkp->dk_open_exl & partbit) + goto excl_open_fail; + + /* check if we can establish exclusive open */ + if (flag & FEXCL) { + if (dkp->dk_open_lyr[part]) + goto excl_open_fail; + for (i = 0; i < OTYPCNT; i++) { + if (dkp->dk_open_reg[i] & partbit) + goto excl_open_fail; + } + } + + /* open will succeed, account for open */ + dkp->dk_flag |= CMDK_OPEN; + if (otyp == OTYP_LYR) + dkp->dk_open_lyr[part]++; + else + dkp->dk_open_reg[otyp] |= partbit; + if (flag & FEXCL) + dkp->dk_open_exl |= partbit; + + mutex_exit(&dkp->dk_mutex); + return (DDI_SUCCESS); + +excl_open_fail: + mutex_exit(&dkp->dk_mutex); + return (EBUSY); +} + +/* + * read routine + */ +/*ARGSUSED2*/ +static int +cmdkread(dev_t dev, struct uio *uio, cred_t *credp) +{ + return (cmdkrw(dev, uio, B_READ)); +} + +/* + * async read routine + */ +/*ARGSUSED2*/ +static int +cmdkaread(dev_t dev, struct aio_req *aio, cred_t *credp) +{ + return (cmdkarw(dev, aio, B_READ)); +} + +/* + * write routine + */ +/*ARGSUSED2*/ +static int +cmdkwrite(dev_t dev, struct uio *uio, cred_t *credp) +{ + return (cmdkrw(dev, uio, B_WRITE)); +} + +/* + * async write routine + */ +/*ARGSUSED2*/ +static int +cmdkawrite(dev_t dev, struct aio_req *aio, cred_t *credp) +{ + return (cmdkarw(dev, aio, B_WRITE)); +} + +static void +cmdkmin(struct buf *bp) +{ + if (bp->b_bcount > DK_MAXRECSIZE) + bp->b_bcount = DK_MAXRECSIZE; +} + +static int +cmdkrw(dev_t dev, struct uio *uio, int flag) +{ + return (physio(cmdkstrategy, (struct buf *)0, dev, flag, cmdkmin, uio)); +} + +static int +cmdkarw(dev_t dev, struct aio_req *aio, int flag) +{ + return (aphysio(cmdkstrategy, anocancel, dev, flag, cmdkmin, aio)); +} + +/* + * strategy routine + */ +static int +cmdkstrategy(struct buf *bp) +{ + int instance; + struct cmdk *dkp; + long d_cnt; + diskaddr_t p_lblksrt; + diskaddr_t p_lblkcnt; + + instance = CMDKUNIT(bp->b_edev); + if (cmdk_indump || !(dkp = ddi_get_soft_state(cmdk_state, instance)) || + (dkblock(bp) < 0)) { + bp->b_resid = bp->b_bcount; + SETBPERR(bp, ENXIO); + biodone(bp); + return (0); + } + + bp->b_flags &= ~(B_DONE|B_ERROR); + bp->b_resid = 0; + bp->av_back = NULL; + + /* + * only re-read the vtoc if necessary (force == FALSE) + */ + if (cmlb_partinfo( + dkp->dk_cmlbhandle, + CMDKPART(bp->b_edev), + &p_lblkcnt, + &p_lblksrt, + NULL, + NULL)) { + SETBPERR(bp, ENXIO); + } + + if ((bp->b_bcount & (NBPSCTR-1)) || (dkblock(bp) > p_lblkcnt)) + SETBPERR(bp, ENXIO); + + if ((bp->b_flags & B_ERROR) || (dkblock(bp) == p_lblkcnt)) { + bp->b_resid = bp->b_bcount; + biodone(bp); + return (0); + } + + d_cnt = bp->b_bcount >> SCTRSHFT; + if ((dkblock(bp) + d_cnt) > p_lblkcnt) { + bp->b_resid = ((dkblock(bp) + d_cnt) - p_lblkcnt) << SCTRSHFT; + bp->b_bcount -= bp->b_resid; + } + + SET_BP_SEC(bp, ((ulong_t)(p_lblksrt + dkblock(bp)))); + if (dadk_strategy(DKTP_DATA, bp) != DDI_SUCCESS) { + bp->b_resid += bp->b_bcount; + biodone(bp); + } + return (0); +} + +static int +cmdk_create_obj(dev_info_t *dip, struct cmdk *dkp) +{ + struct scsi_device *devp; + opaque_t queobjp = NULL; + opaque_t flcobjp = NULL; + char que_keyvalp[64]; + int que_keylen; + char flc_keyvalp[64]; + int flc_keylen; + + ASSERT(mutex_owned(&dkp->dk_mutex)); + + /* Create linkage to queueing routines based on property */ + que_keylen = sizeof (que_keyvalp); + if (ddi_prop_op(DDI_DEV_T_NONE, dip, PROP_LEN_AND_VAL_BUF, + DDI_PROP_CANSLEEP, "queue", que_keyvalp, &que_keylen) != + DDI_PROP_SUCCESS) { + cmn_err(CE_WARN, "cmdk_create_obj: queue property undefined"); + return (DDI_FAILURE); + } + que_keyvalp[que_keylen] = (char)0; + + if (strcmp(que_keyvalp, "qfifo") == 0) { + queobjp = (opaque_t)qfifo_create(); + } else if (strcmp(que_keyvalp, "qsort") == 0) { + queobjp = (opaque_t)qsort_create(); + } else { + return (DDI_FAILURE); + } + + /* Create linkage to dequeueing routines based on property */ + flc_keylen = sizeof (flc_keyvalp); + if (ddi_prop_op(DDI_DEV_T_NONE, dip, PROP_LEN_AND_VAL_BUF, + DDI_PROP_CANSLEEP, "flow_control", flc_keyvalp, &flc_keylen) != + DDI_PROP_SUCCESS) { + cmn_err(CE_WARN, + "cmdk_create_obj: flow-control property undefined"); + return (DDI_FAILURE); + } + + flc_keyvalp[flc_keylen] = (char)0; + + if (strcmp(flc_keyvalp, "dsngl") == 0) { + flcobjp = (opaque_t)dsngl_create(); + } else if (strcmp(flc_keyvalp, "dmult") == 0) { + flcobjp = (opaque_t)dmult_create(); + } else { + return (DDI_FAILURE); + } + + /* populate bbh_obj object stored in dkp */ + dkp->dk_bbh_obj.bbh_data = dkp; + dkp->dk_bbh_obj.bbh_ops = &cmdk_bbh_ops; + + /* create linkage to dadk */ + dkp->dk_tgobjp = (opaque_t)dadk_create(); + + devp = ddi_get_driver_private(dip); + (void) dadk_init(DKTP_DATA, devp, flcobjp, queobjp, &dkp->dk_bbh_obj, + NULL); + + return (DDI_SUCCESS); +} + +static void +cmdk_destroy_obj(dev_info_t *dip, struct cmdk *dkp) +{ + char que_keyvalp[64]; + int que_keylen; + char flc_keyvalp[64]; + int flc_keylen; + + ASSERT(mutex_owned(&dkp->dk_mutex)); + + (void) dadk_free((dkp->dk_tgobjp)); + dkp->dk_tgobjp = NULL; + + que_keylen = sizeof (que_keyvalp); + if (ddi_prop_op(DDI_DEV_T_NONE, dip, PROP_LEN_AND_VAL_BUF, + DDI_PROP_CANSLEEP, "queue", que_keyvalp, &que_keylen) != + DDI_PROP_SUCCESS) { + cmn_err(CE_WARN, "cmdk_destroy_obj: queue property undefined"); + return; + } + que_keyvalp[que_keylen] = (char)0; + + flc_keylen = sizeof (flc_keyvalp); + if (ddi_prop_op(DDI_DEV_T_NONE, dip, PROP_LEN_AND_VAL_BUF, + DDI_PROP_CANSLEEP, "flow_control", flc_keyvalp, &flc_keylen) != + DDI_PROP_SUCCESS) { + cmn_err(CE_WARN, + "cmdk_destroy_obj: flow-control property undefined"); + return; + } + flc_keyvalp[flc_keylen] = (char)0; +} + +static int +cmdk_lb_rdwr( + dev_info_t *dip, + uchar_t cmd, + void *bufaddr, + diskaddr_t start, + size_t count) +{ + struct cmdk *dkp; + opaque_t handle; + int rc = 0; + char *bufa; + + dkp = ddi_get_soft_state(cmdk_state, ddi_get_instance(dip)); + if (dkp == NULL) + return (ENXIO); + + if (cmd != TG_READ && cmd != TG_WRITE) + return (EINVAL); + + /* count must be multiple of 512 */ + count = (count + NBPSCTR - 1) & -NBPSCTR; + handle = dadk_iob_alloc(DKTP_DATA, start, count, KM_SLEEP); + if (!handle) + return (ENOMEM); + + if (cmd == TG_READ) { + bufa = dadk_iob_xfer(DKTP_DATA, handle, B_READ); + if (!bufa) + rc = EIO; + else + bcopy(bufa, bufaddr, count); + } else { + bufa = dadk_iob_htoc(DKTP_DATA, handle); + bcopy(bufaddr, bufa, count); + bufa = dadk_iob_xfer(DKTP_DATA, handle, B_WRITE); + if (!bufa) + rc = EIO; + } + (void) dadk_iob_free(DKTP_DATA, handle); + + return (rc); +} + +static int +cmdk_lb_getcapacity( + dev_info_t *dip, + diskaddr_t *capp) +{ + struct cmdk *dkp; + struct tgdk_geom phyg; + + dkp = ddi_get_soft_state(cmdk_state, ddi_get_instance(dip)); + if (dkp == NULL) + return (ENXIO); + + /* dadk_getphygeom always returns success */ + (void) dadk_getphygeom(DKTP_DATA, &phyg); + + *capp = phyg.g_cap; + + return (0); +} + +static int +cmdk_lb_getvirtgeom( + dev_info_t *dip, + cmlb_geom_t *virtgeomp) +{ + struct cmdk *dkp; + struct tgdk_geom phyg; + diskaddr_t capacity; + + dkp = ddi_get_soft_state(cmdk_state, ddi_get_instance(dip)); + if (dkp == NULL) + return (ENXIO); + + (void) dadk_getgeom(DKTP_DATA, &phyg); + capacity = phyg.g_cap; + + /* + * If the controller returned us something that doesn't + * really fit into an Int 13/function 8 geometry + * result, just fail the ioctl. See PSARC 1998/313. + */ + if (capacity < 0 || capacity >= 63 * 254 * 1024) + return (EINVAL); + + virtgeomp->g_capacity = capacity; + virtgeomp->g_nsect = 63; + virtgeomp->g_nhead = 254; + virtgeomp->g_ncyl = capacity / (63 * 254); + virtgeomp->g_acyl = 0; + virtgeomp->g_secsize = 512; + virtgeomp->g_intrlv = 1; + virtgeomp->g_rpm = 3600; + + return (0); +} + +static int +cmdk_lb_getphygeom( + dev_info_t *dip, + cmlb_geom_t *phygeomp) +{ + struct cmdk *dkp; + struct tgdk_geom phyg; + + dkp = ddi_get_soft_state(cmdk_state, ddi_get_instance(dip)); + if (dkp == NULL) + return (ENXIO); + + /* dadk_getphygeom always returns success */ + (void) dadk_getphygeom(DKTP_DATA, &phyg); + + phygeomp->g_capacity = phyg.g_cap; + phygeomp->g_nsect = phyg.g_sec; + phygeomp->g_nhead = phyg.g_head; + phygeomp->g_acyl = phyg.g_acyl; + phygeomp->g_ncyl = phyg.g_cyl; + phygeomp->g_secsize = phyg.g_secsiz; + phygeomp->g_intrlv = 1; + phygeomp->g_rpm = 3600; + + return (0); +} + +static int +cmdk_lb_getattribute( + dev_info_t *dip, + tg_attribute_t *tgattribute) +{ + struct cmdk *dkp; + + dkp = ddi_get_soft_state(cmdk_state, ddi_get_instance(dip)); + if (dkp == NULL) + return (ENXIO); + + if ((DKTP_EXT->tg_rdonly)) + tgattribute->media_is_writable = FALSE; + else + tgattribute->media_is_writable = TRUE; + + return (0); +} + +/* + * Create and register the devid. + * There are 4 different ways we can get a device id: + * 1. Already have one - nothing to do + * 2. Build one from the drive's model and serial numbers + * 3. Read one from the disk (first sector of last track) + * 4. Fabricate one and write it on the disk. + * If any of these succeeds, register the deviceid + */ +static void +cmdk_devid_setup(struct cmdk *dkp) +{ + int rc; + + /* Try options until one succeeds, or all have failed */ + + /* 1. All done if already registered */ + if (dkp->dk_devid != NULL) + return; + + /* 2. Build a devid from the model and serial number */ + rc = cmdk_devid_modser(dkp); + if (rc != DDI_SUCCESS) { + /* 3. Read devid from the disk, if present */ + rc = cmdk_devid_read(dkp); + + /* 4. otherwise make one up and write it on the disk */ + if (rc != DDI_SUCCESS) + rc = cmdk_devid_fabricate(dkp); + } + + /* If we managed to get a devid any of the above ways, register it */ + if (rc == DDI_SUCCESS) + (void) ddi_devid_register(dkp->dk_dip, dkp->dk_devid); + +} + +/* + * Build a devid from the model and serial number + * Return DDI_SUCCESS or DDI_FAILURE. + */ +static int +cmdk_devid_modser(struct cmdk *dkp) +{ + int rc = DDI_FAILURE; + char *hwid; + int modlen; + int serlen; + + /* + * device ID is a concatenation of model number, '=', serial number. + */ + hwid = kmem_alloc(CMDK_HWIDLEN, KM_SLEEP); + modlen = cmdk_get_modser(dkp, DIOCTL_GETMODEL, hwid, CMDK_HWIDLEN); + if (modlen == 0) { + rc = DDI_FAILURE; + goto err; + } + hwid[modlen++] = '='; + serlen = cmdk_get_modser(dkp, DIOCTL_GETSERIAL, + hwid + modlen, CMDK_HWIDLEN - modlen); + if (serlen == 0) { + rc = DDI_FAILURE; + goto err; + } + hwid[modlen + serlen] = 0; + + /* Initialize the device ID, trailing NULL not included */ + rc = ddi_devid_init(dkp->dk_dip, DEVID_ATA_SERIAL, modlen + serlen, + hwid, (ddi_devid_t *)&dkp->dk_devid); + if (rc != DDI_SUCCESS) { + rc = DDI_FAILURE; + goto err; + } + + rc = DDI_SUCCESS; + +err: + kmem_free(hwid, CMDK_HWIDLEN); + return (rc); +} + +static int +cmdk_get_modser(struct cmdk *dkp, int ioccmd, char *buf, int len) +{ + dadk_ioc_string_t strarg; + int rval; + char *s; + char ch; + boolean_t ret; + int i; + int tb; + + strarg.is_buf = buf; + strarg.is_size = len; + if (dadk_ioctl(DKTP_DATA, + dkp->dk_dev, + ioccmd, + (uintptr_t)&strarg, + FNATIVE | FKIOCTL, + NULL, + &rval) != 0) + return (0); + + /* + * valid model/serial string must contain a non-zero non-space + * trim trailing spaces/NULL + */ + ret = B_FALSE; + s = buf; + for (i = 0; i < strarg.is_size; i++) { + ch = *s++; + if (ch != ' ' && ch != '\0') + tb = i + 1; + if (ch != ' ' && ch != '\0' && ch != '0') + ret = B_TRUE; + } + + if (ret == B_FALSE) + return (0); + + return (tb); +} + +/* + * Read a devid from on the first block of the last track of + * the last cylinder. Make sure what we read is a valid devid. + * Return DDI_SUCCESS or DDI_FAILURE. + */ +static int +cmdk_devid_read(struct cmdk *dkp) +{ + diskaddr_t blk; + struct dk_devid *dkdevidp; + uint_t *ip; + int chksum; + int i, sz; + tgdk_iob_handle handle; + int rc = DDI_FAILURE; + + if (cmlb_get_devid_block(dkp->dk_cmlbhandle, &blk)) + goto err; + + /* read the devid */ + handle = dadk_iob_alloc(DKTP_DATA, blk, NBPSCTR, KM_SLEEP); + if (handle == NULL) + goto err; + + dkdevidp = (struct dk_devid *)dadk_iob_xfer(DKTP_DATA, handle, B_READ); + if (dkdevidp == NULL) + goto err; + + /* Validate the revision */ + if ((dkdevidp->dkd_rev_hi != DK_DEVID_REV_MSB) || + (dkdevidp->dkd_rev_lo != DK_DEVID_REV_LSB)) + goto err; + + /* Calculate the checksum */ + chksum = 0; + ip = (uint_t *)dkdevidp; + for (i = 0; i < ((NBPSCTR - sizeof (int))/sizeof (int)); i++) + chksum ^= ip[i]; + if (DKD_GETCHKSUM(dkdevidp) != chksum) + goto err; + + /* Validate the device id */ + if (ddi_devid_valid((ddi_devid_t)dkdevidp->dkd_devid) != DDI_SUCCESS) + goto err; + + /* keep a copy of the device id */ + sz = ddi_devid_sizeof((ddi_devid_t)dkdevidp->dkd_devid); + dkp->dk_devid = kmem_alloc(sz, KM_SLEEP); + bcopy(dkdevidp->dkd_devid, dkp->dk_devid, sz); + + rc = DDI_SUCCESS; + +err: + if (handle != NULL) + (void) dadk_iob_free(DKTP_DATA, handle); + return (rc); +} + +/* + * Create a devid and write it on the first block of the last track of + * the last cylinder. + * Return DDI_SUCCESS or DDI_FAILURE. + */ +static int +cmdk_devid_fabricate(struct cmdk *dkp) +{ + ddi_devid_t devid = NULL; /* devid made by ddi_devid_init */ + struct dk_devid *dkdevidp; /* devid struct stored on disk */ + diskaddr_t blk; + tgdk_iob_handle handle = NULL; + uint_t *ip, chksum; + int i; + int rc; + + rc = ddi_devid_init(dkp->dk_dip, DEVID_FAB, 0, NULL, &devid); + if (rc != DDI_SUCCESS) + goto err; + + if (cmlb_get_devid_block(dkp->dk_cmlbhandle, &blk)) { + /* no device id block address */ + return (DDI_FAILURE); + } + + handle = dadk_iob_alloc(DKTP_DATA, blk, NBPSCTR, KM_SLEEP); + if (!handle) + goto err; + + /* Locate the buffer */ + dkdevidp = (struct dk_devid *)dadk_iob_htoc(DKTP_DATA, handle); + + /* Fill in the revision */ + bzero(dkdevidp, NBPSCTR); + dkdevidp->dkd_rev_hi = DK_DEVID_REV_MSB; + dkdevidp->dkd_rev_lo = DK_DEVID_REV_LSB; + + /* Copy in the device id */ + i = ddi_devid_sizeof(devid); + if (i > DK_DEVID_SIZE) + goto err; + bcopy(devid, dkdevidp->dkd_devid, i); + + /* Calculate the chksum */ + chksum = 0; + ip = (uint_t *)dkdevidp; + for (i = 0; i < ((NBPSCTR - sizeof (int))/sizeof (int)); i++) + chksum ^= ip[i]; + + /* Fill in the checksum */ + DKD_FORMCHKSUM(chksum, dkdevidp); + + /* write the devid */ + (void) dadk_iob_xfer(DKTP_DATA, handle, B_WRITE); + + dkp->dk_devid = devid; + + rc = DDI_SUCCESS; + +err: + if (handle != NULL) + (void) dadk_iob_free(DKTP_DATA, handle); + + if (rc != DDI_SUCCESS && devid != NULL) + ddi_devid_free(devid); + + return (rc); +} + +static void +cmdk_bbh_free_alts(struct cmdk *dkp) +{ + if (dkp->dk_alts_hdl) { + (void) dadk_iob_free(DKTP_DATA, dkp->dk_alts_hdl); + kmem_free(dkp->dk_slc_cnt, + NDKMAP * (sizeof (uint32_t) + sizeof (struct alts_ent *))); + dkp->dk_alts_hdl = NULL; + } +} + +static void +cmdk_bbh_reopen(struct cmdk *dkp) +{ + tgdk_iob_handle handle = NULL; + diskaddr_t slcb, slcn, slce; + struct alts_parttbl *ap; + struct alts_ent *enttblp; + uint32_t altused; + uint32_t altbase; + uint32_t altlast; + int alts; + uint16_t vtoctag; + int i, j; + + /* find slice with V_ALTSCTR tag */ + for (alts = 0; alts < NDKMAP; alts++) { + if (cmlb_partinfo( + dkp->dk_cmlbhandle, + alts, + &slcn, + &slcb, + NULL, + &vtoctag)) { + goto empty; /* no partition table exists */ + } + + if (vtoctag == V_ALTSCTR && slcn > 1) + break; + } + if (alts >= NDKMAP) { + goto empty; /* no V_ALTSCTR slice defined */ + } + + /* read in ALTS label block */ + handle = dadk_iob_alloc(DKTP_DATA, slcb, NBPSCTR, KM_SLEEP); + if (!handle) { + goto empty; + } + + ap = (struct alts_parttbl *)dadk_iob_xfer(DKTP_DATA, handle, B_READ); + if (!ap || (ap->alts_sanity != ALTS_SANITY)) { + goto empty; + } + + altused = ap->alts_ent_used; /* number of BB entries */ + altbase = ap->alts_ent_base; /* blk offset from begin slice */ + altlast = ap->alts_ent_end; /* blk offset to last block */ + /* ((altused * sizeof (struct alts_ent) + NBPSCTR - 1) & ~NBPSCTR) */ + + if (altused == 0 || + altbase < 1 || + altbase > altlast || + altlast >= slcn) { + goto empty; + } + (void) dadk_iob_free(DKTP_DATA, handle); + + /* read in ALTS remapping table */ + handle = dadk_iob_alloc(DKTP_DATA, + slcb + altbase, + (altlast - altbase + 1) << SCTRSHFT, KM_SLEEP); + if (!handle) { + goto empty; + } + + enttblp = (struct alts_ent *)dadk_iob_xfer(DKTP_DATA, handle, B_READ); + if (!enttblp) { + goto empty; + } + + rw_enter(&dkp->dk_bbh_mutex, RW_WRITER); + + /* allocate space for dk_slc_cnt and dk_slc_ent tables */ + if (dkp->dk_slc_cnt == NULL) { + dkp->dk_slc_cnt = kmem_alloc(NDKMAP * + (sizeof (long) + sizeof (struct alts_ent *)), KM_SLEEP); + } + dkp->dk_slc_ent = (struct alts_ent **)(dkp->dk_slc_cnt + NDKMAP); + + /* free previous BB table (if any) */ + if (dkp->dk_alts_hdl) { + (void) dadk_iob_free(DKTP_DATA, dkp->dk_alts_hdl); + dkp->dk_alts_hdl = NULL; + dkp->dk_altused = 0; + } + + /* save linkage to new BB table */ + dkp->dk_alts_hdl = handle; + dkp->dk_altused = altused; + + /* + * build indexes to BB table by slice + * effectively we have + * struct alts_ent *enttblp[altused]; + * + * uint32_t dk_slc_cnt[NDKMAP]; + * struct alts_ent *dk_slc_ent[NDKMAP]; + */ + for (i = 0; i < NDKMAP; i++) { + if (cmlb_partinfo( + dkp->dk_cmlbhandle, + i, + &slcn, + &slcb, + NULL, + NULL)) { + goto empty1; + } + + dkp->dk_slc_cnt[i] = 0; + if (slcn == 0) + continue; /* slice is not allocated */ + + /* last block in slice */ + slce = slcb + slcn - 1; + + /* find first remap entry in after beginnning of slice */ + for (j = 0; j < altused; j++) { + if (enttblp[j].bad_start + enttblp[j].bad_end >= slcb) + break; + } + dkp->dk_slc_ent[i] = enttblp + j; + + /* count remap entrys until end of slice */ + for (; j < altused && enttblp[j].bad_start <= slce; j++) { + dkp->dk_slc_cnt[i] += 1; + } + } + + rw_exit(&dkp->dk_bbh_mutex); + return; + +empty: + rw_enter(&dkp->dk_bbh_mutex, RW_WRITER); +empty1: + if (handle && handle != dkp->dk_alts_hdl) + (void) dadk_iob_free(DKTP_DATA, handle); + + if (dkp->dk_alts_hdl) { + (void) dadk_iob_free(DKTP_DATA, dkp->dk_alts_hdl); + dkp->dk_alts_hdl = NULL; + } + + rw_exit(&dkp->dk_bbh_mutex); +} + +/*ARGSUSED*/ +static bbh_cookie_t +cmdk_bbh_htoc(opaque_t bbh_data, opaque_t handle) +{ + struct bbh_handle *hp; + bbh_cookie_t ckp; + + hp = (struct bbh_handle *)handle; + ckp = hp->h_cktab + hp->h_idx; + hp->h_idx++; + return (ckp); +} + +/*ARGSUSED*/ +static void +cmdk_bbh_freehandle(opaque_t bbh_data, opaque_t handle) +{ + struct bbh_handle *hp; + + hp = (struct bbh_handle *)handle; + kmem_free(handle, (sizeof (struct bbh_handle) + + (hp->h_totck * (sizeof (struct bbh_cookie))))); +} + + +/* + * cmdk_bbh_gethandle remaps the bad sectors to alternates. + * There are 7 different cases when the comparison is made + * between the bad sector cluster and the disk section. + * + * bad sector cluster gggggggggggbbbbbbbggggggggggg + * case 1: ddddd + * case 2: -d----- + * case 3: ddddd + * case 4: dddddddddddd + * case 5: ddddddd----- + * case 6: ---ddddddd + * case 7: ddddddd + * + * where: g = good sector, b = bad sector + * d = sector in disk section + * - = disk section may be extended to cover those disk area + */ + +static opaque_t +cmdk_bbh_gethandle(opaque_t bbh_data, struct buf *bp) +{ + struct cmdk *dkp = (struct cmdk *)bbh_data; + struct bbh_handle *hp; + struct bbh_cookie *ckp; + struct alts_ent *altp; + uint32_t alts_used; + uint32_t part = CMDKPART(bp->b_edev); + daddr32_t lastsec; + long d_count; + int i; + int idx; + int cnt; + + if (part >= V_NUMPAR) + return (NULL); + + /* + * This if statement is atomic and it will succeed + * if there are no bad blocks (almost always) + * + * so this if is performed outside of the rw_enter for speed + * and then repeated inside the rw_enter for safety + */ + if (!dkp->dk_alts_hdl) { + return (NULL); + } + + rw_enter(&dkp->dk_bbh_mutex, RW_READER); + + if (dkp->dk_alts_hdl == NULL) { + rw_exit(&dkp->dk_bbh_mutex); + return (NULL); + } + + alts_used = dkp->dk_slc_cnt[part]; + if (alts_used == 0) { + rw_exit(&dkp->dk_bbh_mutex); + return (NULL); + } + altp = dkp->dk_slc_ent[part]; + + /* + * binary search for the largest bad sector index in the alternate + * entry table which overlaps or larger than the starting d_sec + */ + i = cmdk_bbh_bsearch(altp, alts_used, GET_BP_SEC(bp)); + /* if starting sector is > the largest bad sector, return */ + if (i == -1) { + rw_exit(&dkp->dk_bbh_mutex); + return (NULL); + } + /* i is the starting index. Set altp to the starting entry addr */ + altp += i; + + d_count = bp->b_bcount >> SCTRSHFT; + lastsec = GET_BP_SEC(bp) + d_count - 1; + + /* calculate the number of bad sectors */ + for (idx = i, cnt = 0; idx < alts_used; idx++, altp++, cnt++) { + if (lastsec < altp->bad_start) + break; + } + + if (!cnt) { + rw_exit(&dkp->dk_bbh_mutex); + return (NULL); + } + + /* calculate the maximum number of reserved cookies */ + cnt <<= 1; + cnt++; + + /* allocate the handle */ + hp = (struct bbh_handle *)kmem_zalloc((sizeof (*hp) + + (cnt * sizeof (*ckp))), KM_SLEEP); + + hp->h_idx = 0; + hp->h_totck = cnt; + ckp = hp->h_cktab = (struct bbh_cookie *)(hp + 1); + ckp[0].ck_sector = GET_BP_SEC(bp); + ckp[0].ck_seclen = d_count; + + altp = dkp->dk_slc_ent[part]; + altp += i; + for (idx = 0; i < alts_used; i++, altp++) { + /* CASE 1: */ + if (lastsec < altp->bad_start) + break; + + /* CASE 3: */ + if (ckp[idx].ck_sector > altp->bad_end) + continue; + + /* CASE 2 and 7: */ + if ((ckp[idx].ck_sector >= altp->bad_start) && + (lastsec <= altp->bad_end)) { + ckp[idx].ck_sector = altp->good_start + + ckp[idx].ck_sector - altp->bad_start; + break; + } + + /* at least one bad sector in our section. break it. */ + /* CASE 5: */ + if ((lastsec >= altp->bad_start) && + (lastsec <= altp->bad_end)) { + ckp[idx+1].ck_seclen = lastsec - altp->bad_start + 1; + ckp[idx].ck_seclen -= ckp[idx+1].ck_seclen; + ckp[idx+1].ck_sector = altp->good_start; + break; + } + /* CASE 6: */ + if ((ckp[idx].ck_sector <= altp->bad_end) && + (ckp[idx].ck_sector >= altp->bad_start)) { + ckp[idx+1].ck_seclen = ckp[idx].ck_seclen; + ckp[idx].ck_seclen = altp->bad_end - + ckp[idx].ck_sector + 1; + ckp[idx+1].ck_seclen -= ckp[idx].ck_seclen; + ckp[idx].ck_sector = altp->good_start + + ckp[idx].ck_sector - altp->bad_start; + idx++; + ckp[idx].ck_sector = altp->bad_end + 1; + continue; /* check rest of section */ + } + + /* CASE 4: */ + ckp[idx].ck_seclen = altp->bad_start - ckp[idx].ck_sector; + ckp[idx+1].ck_sector = altp->good_start; + ckp[idx+1].ck_seclen = altp->bad_end - altp->bad_start + 1; + idx += 2; + ckp[idx].ck_sector = altp->bad_end + 1; + ckp[idx].ck_seclen = lastsec - altp->bad_end; + } + + rw_exit(&dkp->dk_bbh_mutex); + return ((opaque_t)hp); +} + +static int +cmdk_bbh_bsearch(struct alts_ent *buf, int cnt, daddr32_t key) +{ + int i; + int ind; + int interval; + int mystatus = -1; + + if (!cnt) + return (mystatus); + + ind = 1; /* compiler complains about possible uninitialized var */ + for (i = 1; i <= cnt; i <<= 1) + ind = i; + + for (interval = ind; interval; ) { + if ((key >= buf[ind-1].bad_start) && + (key <= buf[ind-1].bad_end)) { + return (ind-1); + } else { + interval >>= 1; + if (key < buf[ind-1].bad_start) { + /* record the largest bad sector index */ + mystatus = ind-1; + if (!interval) + break; + ind = ind - interval; + } else { + /* + * if key is larger than the last element + * then break + */ + if ((ind == cnt) || !interval) + break; + if ((ind+interval) <= cnt) + ind += interval; + } + } + } + return (mystatus); +} |