view usr/src/uts/intel/io/dktp/disk/cmdk.c @ 20600:a9ce21b31e86

10081 smatch indenting fixes for usr/src/uts Reviewed by: Toomas Soome <tsoome@me.com> Reviewed by: Peter Tribble <peter.tribble@gmail.com> Reviewed by: Andy Fiddaman <andy@omniosce.org> Approved by: Robert Mustacchi <rm@joyent.com>
author John Levon <john.levon@joyent.com>
date Mon, 14 Jan 2019 19:42:58 +0000
parents 4a287026aa7f
children 7616bf36dcb7
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 (c) 1992, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Copyright (c) 2018, Joyent, Inc.
 */

#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

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);

static void cmdk_setup_pm(dev_info_t *dip, struct cmdk *dkp);
static int cmdkresume(dev_info_t *dip);
static int cmdksuspend(dev_info_t *dip);
static int cmdkpower(dev_info_t *dip, int component, int level);

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 */
	cmdkpower,		/* power */
	ddi_quiesce_not_needed,	/* quiesce */
};

/*
 * This is the loadable module wrapper.
 */
#include <sys/modctl.h>

static struct modldrv modldrv = {
	&mod_driverops,		/* Type of module. This one is a driver */
	"Common Direct Access Disk",
	&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, void *tg_cookie);

static int cmdk_lb_getinfo(dev_info_t *dip, int cmd,  void *arg,
    void *tg_cookie);

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_1,
	cmdk_lb_rdwr,
	cmdk_lb_getinfo
};

static boolean_t
cmdk_isopen(struct cmdk *dkp, dev_t dev)
{
	int		part, otyp;
	ulong_t		partbit;

	ASSERT(MUTEX_HELD((&dkp->dk_mutex)));

	part = CMDKPART(dev);
	partbit = 1 << part;

	/* account for close */
	if (dkp->dk_open_lyr[part] != 0)
		return (B_TRUE);
	for (otyp = 0; otyp < OTYPCNT; otyp++)
		if (dkp->dk_open_reg[otyp] & partbit)
			return (B_TRUE);
	return (B_FALSE);
}

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);
}

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)
		return (DDI_PROBE_PARTIAL);

	if ((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;

	switch (cmd) {
	case DDI_ATTACH:
		break;
	case DDI_RESUME:
		return (cmdkresume(dip));
	default:
		return (DDI_FAILURE);
	}

	instance = ddi_get_instance(dip);
	if (!(dkp = ddi_get_soft_state(cmdk_state, instance)))
		return (DDI_FAILURE);

	dkp->dk_pm_level = CMDK_SPINDLE_UNINIT;
	mutex_init(&dkp->dk_mutex, NULL, MUTEX_DRIVER, NULL);

	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;

#ifdef _ILP32
	{
		struct  tgdk_geom phyg;
		(void) dadk_getphygeom(DKTP_DATA, &phyg);
		if ((phyg.g_cap - 1) > DK_MAX_BLOCKS) {
			(void) dadk_close(DKTP_DATA);
			goto fail2;
		}
	}
#endif


	/* 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 */
	    B_FALSE,			/* removable */
	    B_FALSE,			/* hot pluggable XXX */
	    node_type,
	    CMLB_CREATE_ALTSLICE_VTOC_16_DTYPE_DIRECT,	/* alter_behaviour */
	    dkp->dk_cmlbhandle,
	    0) != 0)
		goto fail1;

	/* Calling validate will create minor nodes according to disk label */
	(void) cmlb_validate(dkp->dk_cmlbhandle, 0, 0);

	/* 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);

	/*
	 * Initialize power management
	 */
	mutex_init(&dkp->dk_pm_mutex, NULL, MUTEX_DRIVER, NULL);
	cv_init(&dkp->dk_suspend_cv,   NULL, CV_DRIVER, NULL);
	cmdk_setup_pm(dip, dkp);

	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;

	switch (cmd) {
	case DDI_DETACH:
		/* return (DDI_FAILURE); */
		break;
	case DDI_SUSPEND:
		return (cmdksuspend(dip));
	default:
#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, 0);
	cmlb_free_handle(&dkp->dk_cmlbhandle);
	ddi_prop_remove_all(dip);

	cmdk_destroy_obj(dip, dkp);	/* dadk/strategy linkage  */

	/*
	 * free the devid structure if allocated before
	 */
	if (dkp->dk_devid) {
		ddi_devid_free(dkp->dk_devid);
		dkp->dk_devid = NULL;
	}

	mutex_exit(&dkp->dk_mutex);
	mutex_destroy(&dkp->dk_mutex);
	rw_destroy(&dkp->dk_bbh_mutex);
	mutex_destroy(&dkp->dk_pm_mutex);
	cv_destroy(&dkp->dk_suspend_cv);
	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);
}

/*
 * Initialize the power management components
 */
static void
cmdk_setup_pm(dev_info_t *dip, struct cmdk *dkp)
{
	char *pm_comp[] = { "NAME=cmdk", "0=off", "1=on", NULL };

	/*
	 * Since the cmdk device does not the 'reg' property,
	 * cpr will not call its DDI_SUSPEND/DDI_RESUME entries.
	 * The following code is to tell cpr that this device
	 * DOES need to be suspended and resumed.
	 */
	(void) ddi_prop_update_string(DDI_DEV_T_NONE, dip,
	    "pm-hardware-state", "needs-suspend-resume");

	if (ddi_prop_update_string_array(DDI_DEV_T_NONE, dip,
	    "pm-components", pm_comp, 3) == DDI_PROP_SUCCESS) {
		if (pm_raise_power(dip, 0, CMDK_SPINDLE_ON) == DDI_SUCCESS) {
			mutex_enter(&dkp->dk_pm_mutex);
			dkp->dk_pm_level = CMDK_SPINDLE_ON;
			dkp->dk_pm_is_enabled = 1;
			mutex_exit(&dkp->dk_pm_mutex);
		} else {
			mutex_enter(&dkp->dk_pm_mutex);
			dkp->dk_pm_level = CMDK_SPINDLE_OFF;
			dkp->dk_pm_is_enabled = 0;
			mutex_exit(&dkp->dk_pm_mutex);
		}
	} else {
		mutex_enter(&dkp->dk_pm_mutex);
		dkp->dk_pm_level = CMDK_SPINDLE_UNINIT;
		dkp->dk_pm_is_enabled = 0;
		mutex_exit(&dkp->dk_pm_mutex);
	}
}

/*
 * suspend routine, it will be run when get the command
 * DDI_SUSPEND at detach(9E) from system power management
 */
static int
cmdksuspend(dev_info_t *dip)
{
	struct cmdk	*dkp;
	int		instance;
	clock_t		count = 0;

	instance = ddi_get_instance(dip);
	if (!(dkp = ddi_get_soft_state(cmdk_state, instance)))
		return (DDI_FAILURE);
	mutex_enter(&dkp->dk_mutex);
	if (dkp->dk_flag & CMDK_SUSPEND) {
		mutex_exit(&dkp->dk_mutex);
		return (DDI_SUCCESS);
	}
	dkp->dk_flag |= CMDK_SUSPEND;

	/* need to wait a while */
	while (dadk_getcmds(DKTP_DATA) != 0) {
		delay(drv_usectohz(1000000));
		if (count > 60) {
			dkp->dk_flag &= ~CMDK_SUSPEND;
			cv_broadcast(&dkp->dk_suspend_cv);
			mutex_exit(&dkp->dk_mutex);
			return (DDI_FAILURE);
		}
		count++;
	}
	mutex_exit(&dkp->dk_mutex);
	return (DDI_SUCCESS);
}

/*
 * resume routine, it will be run when get the command
 * DDI_RESUME at attach(9E) from system power management
 */
static int
cmdkresume(dev_info_t *dip)
{
	struct cmdk	*dkp;
	int		instance;

	instance = ddi_get_instance(dip);
	if (!(dkp = ddi_get_soft_state(cmdk_state, instance)))
		return (DDI_FAILURE);
	mutex_enter(&dkp->dk_mutex);
	if (!(dkp->dk_flag & CMDK_SUSPEND)) {
		mutex_exit(&dkp->dk_mutex);
		return (DDI_FAILURE);
	}
	dkp->dk_pm_level = CMDK_SPINDLE_ON;
	dkp->dk_flag &= ~CMDK_SUSPEND;
	cv_broadcast(&dkp->dk_suspend_cv);
	mutex_exit(&dkp->dk_mutex);
	return (DDI_SUCCESS);

}

/*
 * power management entry point, it was used to
 * change power management component.
 * Actually, the real hard drive suspend/resume
 * was handled in ata, so this function is not
 * doing any real work other than verifying that
 * the disk is idle.
 */
static int
cmdkpower(dev_info_t *dip, int component, int level)
{
	struct cmdk	*dkp;
	int		instance;

	instance = ddi_get_instance(dip);
	if (!(dkp = ddi_get_soft_state(cmdk_state, instance)) ||
	    component != 0 || level > CMDK_SPINDLE_ON ||
	    level < CMDK_SPINDLE_OFF) {
		return (DDI_FAILURE);
	}

	mutex_enter(&dkp->dk_pm_mutex);
	if (dkp->dk_pm_is_enabled && dkp->dk_pm_level == level) {
		mutex_exit(&dkp->dk_pm_mutex);
		return (DDI_SUCCESS);
	}
	mutex_exit(&dkp->dk_pm_mutex);

	if ((level == CMDK_SPINDLE_OFF) &&
	    (dadk_getcmds(DKTP_DATA) != 0)) {
		return (DDI_FAILURE);
	}

	mutex_enter(&dkp->dk_pm_mutex);
	dkp->dk_pm_level = level;
	mutex_exit(&dkp->dk_pm_mutex);
	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;

#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));
	if (dkp == NULL)
		return (ddi_prop_op(dev, dip, prop_op, mod_flags,
		    name, valuep, lengthp));

	return (cmlb_prop_op(dkp->dk_cmlbhandle,
	    dev, dip, prop_op, mod_flags, name, valuep, lengthp,
	    CMDKPART(dev), NULL));
}

/*
 * 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,
	    0)) {
		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 = (blkaddr_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);

	mutex_enter(&dkp->dk_mutex);
	while (dkp->dk_flag & CMDK_SUSPEND) {
		cv_wait(&dkp->dk_suspend_cv, &dkp->dk_mutex);
	}
	mutex_exit(&dkp->dk_mutex);

	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, 0) != 0)
				return (ENXIO);

			if (cmlb_partinfo(dkp->dk_cmlbhandle, CMDKPART(dev),
			    &p_lblkcnt, &p_lblksrt, NULL, NULL, 0))
				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 DKIOCGEXTVTOC:
	case DKIOCSEXTVTOC:
	case DKIOCEXTPARTINFO:
	case DKIOCGMBOOT:
	case DKIOCSMBOOT:
	case DKIOCGETEFI:
	case DKIOCSETEFI:
	case DKIOCPARTITION:
	case DKIOCSETEXTPART:
	{
		int rc;

		rc = cmlb_ioctl(dkp->dk_cmlbhandle, dev, cmd, arg, flag,
		    credp, rvalp, 0);
		if (cmd == DKIOCSVTOC || cmd == DKIOCSEXTVTOC)
			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 */
	ASSERT(cmdk_isopen(dkp, dev));
	if (!(dkp->dk_flag & CMDK_OPEN)) {
		mutex_exit(&dkp->dk_mutex);
		return (ENXIO);
	}

	while (dkp->dk_flag & CMDK_SUSPEND) {
		cv_wait(&dkp->dk_suspend_cv, &dkp->dk_mutex);
	}

	part = CMDKPART(dev);
	partbit = 1 << part;

	/* account for close */
	if (otyp == OTYP_LYR) {
		ASSERT(dkp->dk_open_lyr[part] > 0);
		if (dkp->dk_open_lyr[part])
			dkp->dk_open_lyr[part]--;
	} else {
		ASSERT((dkp->dk_open_reg[otyp] & partbit) != 0);
		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, 0);

	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);

	mutex_enter(&dkp->dk_mutex);
	while (dkp->dk_flag & CMDK_SUSPEND) {
		cv_wait(&dkp->dk_suspend_cv, &dkp->dk_mutex);
	}
	mutex_exit(&dkp->dk_mutex);

	part = CMDKPART(dev);
	partbit = 1 << part;
	nodelay = (flag & (FNDELAY | FNONBLOCK));

	mutex_enter(&dkp->dk_mutex);

	if (cmlb_validate(dkp->dk_cmlbhandle, 0, 0) != 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) == 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)
{
	int 		instance;
	struct	cmdk	*dkp;

	instance = CMDKUNIT(dev);
	if (!(dkp = ddi_get_soft_state(cmdk_state, instance)))
		return (ENXIO);

	mutex_enter(&dkp->dk_mutex);
	while (dkp->dk_flag & CMDK_SUSPEND) {
		cv_wait(&dkp->dk_suspend_cv, &dkp->dk_mutex);
	}
	mutex_exit(&dkp->dk_mutex);

	return (physio(cmdkstrategy, (struct buf *)0, dev, flag, cmdkmin, uio));
}

static int
cmdkarw(dev_t dev, struct aio_req *aio, int flag)
{
	int 		instance;
	struct	cmdk	*dkp;

	instance = CMDKUNIT(dev);
	if (!(dkp = ddi_get_soft_state(cmdk_state, instance)))
		return (ENXIO);

	mutex_enter(&dkp->dk_mutex);
	while (dkp->dk_flag & CMDK_SUSPEND) {
		cv_wait(&dkp->dk_suspend_cv, &dkp->dk_mutex);
	}
	mutex_exit(&dkp->dk_mutex);

	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);
	}

	mutex_enter(&dkp->dk_mutex);
	ASSERT(cmdk_isopen(dkp, bp->b_edev));
	while (dkp->dk_flag & CMDK_SUSPEND) {
		cv_wait(&dkp->dk_suspend_cv, &dkp->dk_mutex);
	}
	mutex_exit(&dkp->dk_mutex);

	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, 0)) {
		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;
}
/*ARGSUSED5*/
static int
cmdk_lb_rdwr(dev_info_t *dip, uchar_t cmd, void *bufaddr,
    diskaddr_t start, size_t count, void *tg_cookie)
{
	struct cmdk	*dkp;
	opaque_t	handle;
	int		rc = 0;
	char		*bufa;
	size_t		buflen;

	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);

	/* buflen must be multiple of 512 */
	buflen = (count + NBPSCTR - 1) & -NBPSCTR;
	handle = dadk_iob_alloc(DKTP_DATA, start, buflen, 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);
}

/*ARGSUSED3*/
static int
cmdk_lb_getinfo(dev_info_t *dip, int cmd, void *arg, void *tg_cookie)
{

	struct cmdk		*dkp;
	struct tgdk_geom	phyg;


	dkp = ddi_get_soft_state(cmdk_state, ddi_get_instance(dip));
	if (dkp == NULL)
		return (ENXIO);

	switch (cmd) {
	case TG_GETPHYGEOM: {
		cmlb_geom_t *phygeomp = (cmlb_geom_t *)arg;

		/* 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);
	}

	case TG_GETVIRTGEOM: {
		cmlb_geom_t *virtgeomp = (cmlb_geom_t *)arg;
		diskaddr_t		capacity;

		(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);
	}

	case TG_GETCAPACITY:
	case TG_GETBLOCKSIZE:
	{

		/* dadk_getphygeom always returns success */
		(void) dadk_getphygeom(DKTP_DATA, &phyg);
		if (cmd == TG_GETCAPACITY)
			*(diskaddr_t *)arg = phyg.g_cap;
		else
			*(uint32_t *)arg = (uint32_t)phyg.g_secsiz;

		return (0);
	}

	case TG_GETATTR: {
		tg_attribute_t *tgattribute = (tg_attribute_t *)arg;
		if ((DKTP_EXT->tg_rdonly))
			tgattribute->media_is_writable = FALSE;
		else
			tgattribute->media_is_writable = TRUE;
		tgattribute->media_is_rotational = TRUE;

		return (0);
	}

	default:
		return (ENOTTY);
	}
}





/*
 * 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, &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 = NULL;
	int		rc = DDI_FAILURE;

	if (cmlb_get_devid_block(dkp->dk_cmlbhandle, &blk, 0))
		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 = DDI_FAILURE;

	if (ddi_devid_init(dkp->dk_dip, DEVID_FAB, 0, NULL, &devid) !=
	    DDI_SUCCESS)
		goto err;

	if (cmlb_get_devid_block(dkp->dk_cmlbhandle, &blk, 0)) {
		/* no device id block address */
		goto err;
	}

	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,
		    0)) {
			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,
		    0)) {
			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);
}