root/usr/src/uts/intel/io/dktp/dcdev/dadk.c
/*
 * 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) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * Direct Attached Disk
 */

#include <sys/file.h>
#include <sys/scsi/scsi.h>
#include <sys/var.h>
#include <sys/proc.h>
#include <sys/dktp/cm.h>
#include <sys/vtoc.h>
#include <sys/dkio.h>
#include <sys/policy.h>
#include <sys/priv.h>

#include <sys/dktp/dadev.h>
#include <sys/dktp/fctypes.h>
#include <sys/dktp/flowctrl.h>
#include <sys/dktp/tgcom.h>
#include <sys/dktp/tgdk.h>
#include <sys/dktp/bbh.h>
#include <sys/dktp/dadkio.h>
#include <sys/dktp/dadk.h>
#include <sys/cdio.h>

/*
 * Local Function Prototypes
 */
static void dadk_restart(void *pktp);
static void dadk_pktcb(struct cmpkt *pktp);
static void dadk_iodone(struct buf *bp);
static void dadk_polldone(struct buf *bp);
static void dadk_setcap(struct dadk *dadkp);
static void dadk_create_errstats(struct dadk *dadkp, int instance);
static void dadk_destroy_errstats(struct dadk *dadkp);

static int dadk_chkerr(struct cmpkt *pktp);
static int dadk_ioprep(struct dadk *dadkp, struct cmpkt *pktp);
static int dadk_iosetup(struct dadk *dadkp, struct cmpkt *pktp);
static int dadk_ioretry(struct cmpkt *pktp, int action);

static struct cmpkt *dadk_pktprep(struct dadk *dadkp, struct cmpkt *in_pktp,
    struct buf *bp, void (*cb_func)(struct buf *), int (*func)(caddr_t),
    caddr_t arg);

static int  dadk_pkt(opaque_t com_data, struct buf *bp, int (*func)(caddr_t),
    caddr_t arg);
static void dadk_transport(opaque_t com_data, struct buf *bp);
static int dadk_ctl_ioctl(struct dadk *, uint32_t, uintptr_t, int);

struct tgcom_objops dadk_com_ops = {
        nodev,
        nodev,
        dadk_pkt,
        dadk_transport,
        0, 0
};

/*
 * architecture dependent allocation restrictions for dadk_iob_alloc(). For
 * x86, we'll set dma_attr_addr_hi to dadk_max_phys_addr and dma_attr_sgllen
 * to dadk_sgl_size during _init().
 */
#if defined(__sparc)
static ddi_dma_attr_t dadk_alloc_attr = {
        DMA_ATTR_V0,    /* version number */
        0x0,            /* lowest usable address */
        0xFFFFFFFFull,  /* high DMA address range */
        0xFFFFFFFFull,  /* DMA counter register */
        1,              /* DMA address alignment */
        1,              /* DMA burstsizes */
        1,              /* min effective DMA size */
        0xFFFFFFFFull,  /* max DMA xfer size */
        0xFFFFFFFFull,  /* segment boundary */
        1,              /* s/g list length */
        512,            /* granularity of device */
        0,              /* DMA transfer flags */
};
#elif defined(__x86)
static ddi_dma_attr_t dadk_alloc_attr = {
        DMA_ATTR_V0,    /* version number */
        0x0,            /* lowest usable address */
        0x0,            /* high DMA address range [set in _init()] */
        0xFFFFull,      /* DMA counter register */
        512,            /* DMA address alignment */
        1,              /* DMA burstsizes */
        1,              /* min effective DMA size */
        0xFFFFFFFFull,  /* max DMA xfer size */
        0xFFFFFFFFull,  /* segment boundary */
        0,              /* s/g list length [set in _init()] */
        512,            /* granularity of device */
        0,              /* DMA transfer flags */
};

uint64_t dadk_max_phys_addr = 0xFFFFFFFFull;
int dadk_sgl_size = 0xFF;
#endif

static int dadk_rmb_ioctl(struct dadk *dadkp, int cmd, intptr_t arg, int flags,
    int silent);
static void dadk_rmb_iodone(struct buf *bp);

static int dadk_dk_buf_setup(struct dadk *dadkp, opaque_t *cmdp,
    dev_t dev, enum uio_seg dataspace, int rw);
static void dadk_dk(struct dadk *dadkp, struct dadkio_rwcmd *scmdp,
    struct buf *bp);
static void dadkmin(struct buf *bp);
static int dadk_dk_strategy(struct buf *bp);
static void dadk_recorderr(struct cmpkt *pktp, struct dadkio_rwcmd *rwcmdp);

struct tgdk_objops dadk_ops = {
        dadk_init,
        dadk_free,
        dadk_probe,
        dadk_attach,
        dadk_open,
        dadk_close,
        dadk_ioctl,
        dadk_strategy,
        dadk_setgeom,
        dadk_getgeom,
        dadk_iob_alloc,
        dadk_iob_free,
        dadk_iob_htoc,
        dadk_iob_xfer,
        dadk_dump,
        dadk_getphygeom,
        dadk_set_bbhobj,
        dadk_check_media,
        dadk_inquiry,
        dadk_cleanup,
        0
};

/*
 * Local static data
 */

#ifdef  DADK_DEBUG
#define DENT    0x0001
#define DERR    0x0002
#define DIO     0x0004
#define DGEOM   0x0010
#define DSTATE  0x0020
static  int     dadk_debug = DGEOM;

#endif  /* DADK_DEBUG */

static int dadk_check_media_time = 3000000;     /* 3 Second State Check */
static int dadk_dk_maxphys = 0x80000;

static char     *dadk_cmds[] = {
        "\000Unknown",                  /* unknown              */
        "\001read sector",              /* DCMD_READ 1          */
        "\002write sector",             /* DCMD_WRITE 2         */
        "\003format track",             /* DCMD_FMTTRK 3        */
        "\004format whole drive",       /* DCMD_FMTDRV 4        */
        "\005recalibrate",              /* DCMD_RECAL  5        */
        "\006seek sector",              /* DCMD_SEEK   6        */
        "\007read verify",              /* DCMD_RDVER  7        */
        "\010read defect list",         /* DCMD_GETDEF 8        */
        "\011lock door",                /* DCMD_LOCK   9        */
        "\012unlock door",              /* DCMD_UNLOCK 10       */
        "\013start motor",              /* DCMD_START_MOTOR 11  */
        "\014stop motor",               /* DCMD_STOP_MOTOR 12   */
        "\015eject",                    /* DCMD_EJECT  13       */
        "\016update geometry",          /* DCMD_UPDATE_GEOM  14 */
        "\017get state",                /* DCMD_GET_STATE  15   */
        "\020cdrom pause",              /* DCMD_PAUSE  16       */
        "\021cdrom resume",             /* DCMD_RESUME  17      */
        "\022cdrom play track index",   /* DCMD_PLAYTRKIND  18  */
        "\023cdrom play msf",           /* DCMD_PLAYMSF  19     */
        "\024cdrom sub channel",        /* DCMD_SUBCHNL  20     */
        "\025cdrom read mode 1",        /* DCMD_READMODE1  21   */
        "\026cdrom read toc header",    /* DCMD_READTOCHDR  22  */
        "\027cdrom read toc entry",     /* DCMD_READTOCENT  23  */
        "\030cdrom read offset",        /* DCMD_READOFFSET  24  */
        "\031cdrom read mode 2",        /* DCMD_READMODE2  25   */
        "\032cdrom volume control",     /* DCMD_VOLCTRL  26     */
        "\033flush cache",              /* DCMD_FLUSH_CACHE  27 */
        NULL
};

static char *dadk_sense[] = {
        "\000Success",                  /* DERR_SUCCESS         */
        "\001address mark not found",   /* DERR_AMNF            */
        "\002track 0 not found",        /* DERR_TKONF           */
        "\003aborted command",          /* DERR_ABORT           */
        "\004write fault",              /* DERR_DWF             */
        "\005ID not found",             /* DERR_IDNF            */
        "\006drive busy",               /* DERR_BUSY            */
        "\007uncorrectable data error", /* DERR_UNC             */
        "\010bad block detected",       /* DERR_BBK             */
        "\011invalid command",          /* DERR_INVCDB          */
        "\012device hard error",        /* DERR_HARD            */
        "\013illegal length indicated", /* DERR_ILI             */
        "\014end of media",             /* DERR_EOM             */
        "\015media change requested",   /* DERR_MCR             */
        "\016recovered from error",     /* DERR_RECOVER         */
        "\017device not ready",         /* DERR_NOTREADY        */
        "\020medium error",             /* DERR_MEDIUM          */
        "\021hardware error",           /* DERR_HW              */
        "\022illegal request",          /* DERR_ILL             */
        "\023unit attention",           /* DERR_UNIT_ATTN       */
        "\024data protection",          /* DERR_DATA_PROT       */
        "\025miscompare",               /* DERR_MISCOMPARE      */
        "\026ICRC error during UDMA",   /* DERR_ICRC            */
        "\027reserved",                 /* DERR_RESV            */
        NULL
};

static char *dadk_name = "Disk";

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

extern struct mod_ops mod_miscops;

static struct modlmisc modlmisc = {
        &mod_miscops,   /* Type of module */
        "Direct Attached Disk"
};

static struct modlinkage modlinkage = {
        MODREV_1, (void *)&modlmisc, NULL
};

int
_init(void)
{
#ifdef DADK_DEBUG
        if (dadk_debug & DENT)
                PRF("dadk_init: call\n");
#endif

#if defined(__x86)
        /* set the max physical address for iob allocs on x86 */
        dadk_alloc_attr.dma_attr_addr_hi = dadk_max_phys_addr;

        /*
         * set the sgllen for iob allocs on x86. If this is set less than
         * the number of pages the buffer will take (taking into account
         * alignment), it would force the allocator to try and allocate
         * contiguous pages.
         */
        dadk_alloc_attr.dma_attr_sgllen = dadk_sgl_size;
#endif

        return (mod_install(&modlinkage));
}

int
_fini(void)
{
#ifdef DADK_DEBUG
        if (dadk_debug & DENT)
                PRF("dadk_fini: call\n");
#endif

        return (mod_remove(&modlinkage));
}

int
_info(struct modinfo *modinfop)
{
        return (mod_info(&modlinkage, modinfop));
}

struct tgdk_obj *
dadk_create()
{
        struct tgdk_obj *dkobjp;
        struct dadk *dadkp;

        dkobjp = kmem_zalloc((sizeof (*dkobjp) + sizeof (*dadkp)), KM_NOSLEEP);
        if (!dkobjp)
                return (NULL);
        dadkp = (struct dadk *)(dkobjp+1);

        dkobjp->tg_ops  = (struct  tgdk_objops *)&dadk_ops;
        dkobjp->tg_data = (opaque_t)dadkp;
        dkobjp->tg_ext = &(dkobjp->tg_extblk);
        dadkp->dad_extp = &(dkobjp->tg_extblk);

#ifdef DADK_DEBUG
        if (dadk_debug & DENT)
                PRF("dadk_create: tgdkobjp= 0x%x dadkp= 0x%x\n", dkobjp, dadkp);
#endif
        return (dkobjp);
}

int
dadk_init(opaque_t objp, opaque_t devp, opaque_t flcobjp, opaque_t queobjp,
        opaque_t bbhobjp, void *lkarg)
{
        struct dadk *dadkp = (struct dadk *)objp;
        struct scsi_device *sdevp = (struct scsi_device *)devp;

        dadkp->dad_sd = devp;
        dadkp->dad_ctlobjp = (opaque_t)sdevp->sd_address.a_hba_tran;
        sdevp->sd_private = (caddr_t)dadkp;

        /* initialize the communication object */
        dadkp->dad_com.com_data = (opaque_t)dadkp;
        dadkp->dad_com.com_ops  = &dadk_com_ops;

        dadkp->dad_bbhobjp = bbhobjp;
        BBH_INIT(bbhobjp);

        dadkp->dad_flcobjp = flcobjp;
        mutex_init(&dadkp->dad_cmd_mutex, NULL, MUTEX_DRIVER, NULL);
        dadkp->dad_cmd_count = 0;
        return (FLC_INIT(flcobjp, &(dadkp->dad_com), queobjp, lkarg));
}

int
dadk_free(struct tgdk_obj *dkobjp)
{
        TGDK_CLEANUP(dkobjp);
        kmem_free(dkobjp, (sizeof (*dkobjp) + sizeof (struct dadk)));

        return (DDI_SUCCESS);
}

void
dadk_cleanup(struct tgdk_obj *dkobjp)
{
        struct dadk *dadkp;

        dadkp = (struct dadk *)(dkobjp->tg_data);
        if (dadkp->dad_sd)
                dadkp->dad_sd->sd_private = NULL;
        if (dadkp->dad_bbhobjp) {
                BBH_FREE(dadkp->dad_bbhobjp);
                dadkp->dad_bbhobjp = NULL;
        }
        if (dadkp->dad_flcobjp) {
                FLC_FREE(dadkp->dad_flcobjp);
                dadkp->dad_flcobjp = NULL;
        }
        mutex_destroy(&dadkp->dad_cmd_mutex);
}

/* ARGSUSED */
int
dadk_probe(opaque_t objp, int kmsflg)
{
        struct dadk *dadkp = (struct dadk *)objp;
        struct scsi_device *devp;
        char   name[80];

        devp = dadkp->dad_sd;
        if (!devp->sd_inq || (devp->sd_inq->inq_dtype == DTYPE_NOTPRESENT) ||
            (devp->sd_inq->inq_dtype == DTYPE_UNKNOWN)) {
                return (DDI_PROBE_FAILURE);
        }

        switch (devp->sd_inq->inq_dtype) {
                case DTYPE_DIRECT:
                        dadkp->dad_ctype = DKC_DIRECT;
                        dadkp->dad_extp->tg_nodetype = DDI_NT_BLOCK;
                        dadkp->dad_extp->tg_ctype = DKC_DIRECT;
                        break;
                case DTYPE_RODIRECT: /* eg cdrom */
                        dadkp->dad_ctype = DKC_CDROM;
                        dadkp->dad_extp->tg_rdonly = 1;
                        dadkp->dad_rdonly = 1;
                        dadkp->dad_cdrom = 1;
                        dadkp->dad_extp->tg_nodetype = DDI_NT_CD;
                        dadkp->dad_extp->tg_ctype = DKC_CDROM;
                        break;
                case DTYPE_WORM:
                case DTYPE_OPTICAL:
                default:
                        return (DDI_PROBE_FAILURE);
        }

        dadkp->dad_extp->tg_rmb = dadkp->dad_rmb = devp->sd_inq->inq_rmb;

        dadkp->dad_secshf = SCTRSHFT;
        dadkp->dad_blkshf = 0;

        /* display the device name */
        (void) strcpy(name, "Vendor '");
        gda_inqfill((caddr_t)devp->sd_inq->inq_vid, 8, &name[strlen(name)]);
        (void) strcat(name, "' Product '");
        gda_inqfill((caddr_t)devp->sd_inq->inq_pid, 16, &name[strlen(name)]);
        (void) strcat(name, "'");
        gda_log(devp->sd_dev, dadk_name, CE_NOTE, "!<%s>\n", name);

        return (DDI_PROBE_SUCCESS);
}


/* ARGSUSED */
int
dadk_attach(opaque_t objp)
{
        return (DDI_SUCCESS);
}

int
dadk_set_bbhobj(opaque_t objp, opaque_t bbhobjp)
{
        struct dadk *dadkp = (struct dadk *)objp;
        /* free the old bbh object */
        if (dadkp->dad_bbhobjp)
                BBH_FREE(dadkp->dad_bbhobjp);

        /* initialize the new bbh object */
        dadkp->dad_bbhobjp = bbhobjp;
        BBH_INIT(bbhobjp);

        return (DDI_SUCCESS);
}

/* ARGSUSED */
int
dadk_open(opaque_t objp, int flag)
{
        struct dadk *dadkp = (struct dadk *)objp;
        int error;
        int wce;

        if (!dadkp->dad_rmb) {
                if (dadkp->dad_phyg.g_cap) {
                        FLC_START_KSTAT(dadkp->dad_flcobjp, "disk",
                            ddi_get_instance(CTL_DIP_DEV(dadkp->dad_ctlobjp)));
                        return (DDI_SUCCESS);
                }
        } else {
                mutex_enter(&dadkp->dad_mutex);
                dadkp->dad_iostate = DKIO_NONE;
                cv_broadcast(&dadkp->dad_state_cv);
                mutex_exit(&dadkp->dad_mutex);

                if (dadk_rmb_ioctl(dadkp, DCMD_START_MOTOR, 0, 0,
                    DADK_SILENT) ||
                    dadk_rmb_ioctl(dadkp, DCMD_LOCK, 0, 0, DADK_SILENT) ||
                    dadk_rmb_ioctl(dadkp, DCMD_UPDATE_GEOM, 0, 0,
                    DADK_SILENT)) {
                        return (DDI_FAILURE);
                }

                mutex_enter(&dadkp->dad_mutex);
                dadkp->dad_iostate = DKIO_INSERTED;
                cv_broadcast(&dadkp->dad_state_cv);
                mutex_exit(&dadkp->dad_mutex);
        }

        /*
         * get write cache enable state
         * If there is an error, must assume that write cache
         * is enabled.
         * NOTE: Since there is currently no Solaris mechanism to
         * change the state of the Write Cache Enable feature,
         * this code just checks the value of the WCE bit
         * obtained at device init time.  If a mechanism
         * is added to the driver to change WCE, dad_wce
         * must be updated appropriately.
         */
        error = dadk_ctl_ioctl(dadkp, DIOCTL_GETWCE,
            (uintptr_t)&wce, FKIOCTL | FNATIVE);
        mutex_enter(&dadkp->dad_mutex);
        dadkp->dad_wce = (error != 0) || (wce != 0);
        mutex_exit(&dadkp->dad_mutex);

        /* logical disk geometry */
        (void) dadk_ctl_ioctl(dadkp, DIOCTL_GETGEOM,
            (uintptr_t)&dadkp->dad_logg, FKIOCTL | FNATIVE);
        if (dadkp->dad_logg.g_cap == 0)
                return (DDI_FAILURE);

        /* get physical disk geometry */
        (void) dadk_ctl_ioctl(dadkp, DIOCTL_GETPHYGEOM,
            (uintptr_t)&dadkp->dad_phyg, FKIOCTL | FNATIVE);
        if (dadkp->dad_phyg.g_cap == 0)
                return (DDI_FAILURE);

        dadk_setcap(dadkp);

        dadk_create_errstats(dadkp,
            ddi_get_instance(CTL_DIP_DEV(dadkp->dad_ctlobjp)));

        /* start profiling */
        FLC_START_KSTAT(dadkp->dad_flcobjp, "disk",
            ddi_get_instance(CTL_DIP_DEV(dadkp->dad_ctlobjp)));

        return (DDI_SUCCESS);
}

static void
dadk_setcap(struct dadk *dadkp)
{
        int      totsize;
        int      i;

        totsize = dadkp->dad_phyg.g_secsiz;

        if (totsize == 0) {
                if (dadkp->dad_cdrom) {
                        totsize = 2048;
                } else {
                        totsize = NBPSCTR;
                }
        } else {
                /* Round down sector size to multiple of 512B */
                totsize &= ~(NBPSCTR-1);
        }
        dadkp->dad_phyg.g_secsiz = totsize;

        /* set sec,block shift factor - (512->0, 1024->1, 2048->2, etc.) */
        totsize >>= SCTRSHFT;
        for (i = 0; totsize != 1; i++, totsize >>= 1)
                ;
        dadkp->dad_blkshf = i;
        dadkp->dad_secshf = i + SCTRSHFT;
}


static void
dadk_create_errstats(struct dadk *dadkp, int instance)
{
        dadk_errstats_t *dep;
        char kstatname[KSTAT_STRLEN];
        dadk_ioc_string_t dadk_ioc_string;

        if (dadkp->dad_errstats)
                return;

        (void) sprintf(kstatname, "cmdk%d,error", instance);
        dadkp->dad_errstats = kstat_create("cmdkerror", instance,
            kstatname, "device_error", KSTAT_TYPE_NAMED,
            sizeof (dadk_errstats_t) / sizeof (kstat_named_t),
            KSTAT_FLAG_PERSISTENT);

        if (!dadkp->dad_errstats)
                return;

        dep = (dadk_errstats_t *)dadkp->dad_errstats->ks_data;

        kstat_named_init(&dep->dadk_softerrs,
            "Soft Errors", KSTAT_DATA_UINT32);
        kstat_named_init(&dep->dadk_harderrs,
            "Hard Errors", KSTAT_DATA_UINT32);
        kstat_named_init(&dep->dadk_transerrs,
            "Transport Errors", KSTAT_DATA_UINT32);
        kstat_named_init(&dep->dadk_model,
            "Model", KSTAT_DATA_CHAR);
        kstat_named_init(&dep->dadk_revision,
            "Revision", KSTAT_DATA_CHAR);
        kstat_named_init(&dep->dadk_serial,
            "Serial No", KSTAT_DATA_CHAR);
        kstat_named_init(&dep->dadk_capacity,
            "Size", KSTAT_DATA_ULONGLONG);
        kstat_named_init(&dep->dadk_rq_media_err,
            "Media Error", KSTAT_DATA_UINT32);
        kstat_named_init(&dep->dadk_rq_ntrdy_err,
            "Device Not Ready", KSTAT_DATA_UINT32);
        kstat_named_init(&dep->dadk_rq_nodev_err,
            "No Device", KSTAT_DATA_UINT32);
        kstat_named_init(&dep->dadk_rq_recov_err,
            "Recoverable", KSTAT_DATA_UINT32);
        kstat_named_init(&dep->dadk_rq_illrq_err,
            "Illegal Request", KSTAT_DATA_UINT32);

        dadkp->dad_errstats->ks_private = dep;
        dadkp->dad_errstats->ks_update = nulldev;
        kstat_install(dadkp->dad_errstats);

        /* get model */
        dep->dadk_model.value.c[0] = 0;
        dadk_ioc_string.is_buf = &dep->dadk_model.value.c[0];
        dadk_ioc_string.is_size = sizeof (dep->dadk_model.value.c);
        (void) dadk_ctl_ioctl(dadkp, DIOCTL_GETMODEL,
            (uintptr_t)&dadk_ioc_string, FKIOCTL | FNATIVE);

        /* get serial */
        dep->dadk_serial.value.c[0] = 0;
        dadk_ioc_string.is_buf = &dep->dadk_serial.value.c[0];
        dadk_ioc_string.is_size = sizeof (dep->dadk_serial.value.c);
        (void) dadk_ctl_ioctl(dadkp, DIOCTL_GETSERIAL,
            (uintptr_t)&dadk_ioc_string, FKIOCTL | FNATIVE);

        /* Get revision */
        dep->dadk_revision.value.c[0] = 0;

        /* Get capacity */

        dep->dadk_capacity.value.ui64 =
            (uint64_t)dadkp->dad_logg.g_cap *
            (uint64_t)dadkp->dad_logg.g_secsiz;
}


int
dadk_close(opaque_t objp)
{
        struct dadk *dadkp = (struct dadk *)objp;

        if (dadkp->dad_rmb) {
                (void) dadk_rmb_ioctl(dadkp, DCMD_STOP_MOTOR, 0, 0,
                    DADK_SILENT);
                (void) dadk_rmb_ioctl(dadkp, DCMD_UNLOCK, 0, 0, DADK_SILENT);
        }
        FLC_STOP_KSTAT(dadkp->dad_flcobjp);

        dadk_destroy_errstats(dadkp);

        return (DDI_SUCCESS);
}

static void
dadk_destroy_errstats(struct dadk *dadkp)
{
        if (!dadkp->dad_errstats)
                return;

        kstat_delete(dadkp->dad_errstats);
        dadkp->dad_errstats = NULL;
}


int
dadk_strategy(opaque_t objp, struct buf *bp)
{
        struct dadk *dadkp = (struct dadk *)objp;

        if (dadkp->dad_rdonly && !(bp->b_flags & B_READ)) {
                bioerror(bp, EROFS);
                return (DDI_FAILURE);
        }

        if (bp->b_bcount & (dadkp->DAD_SECSIZ-1)) {
                bioerror(bp, ENXIO);
                return (DDI_FAILURE);
        }

        SET_BP_SEC(bp, (LBLK2SEC(GET_BP_SEC(bp), dadkp->dad_blkshf)));
        mutex_enter(&dadkp->dad_cmd_mutex);
        dadkp->dad_cmd_count++;
        mutex_exit(&dadkp->dad_cmd_mutex);
        FLC_ENQUE(dadkp->dad_flcobjp, bp);

        return (DDI_SUCCESS);
}

int
dadk_dump(opaque_t objp, struct buf *bp)
{
        struct dadk *dadkp = (struct dadk *)objp;
        struct cmpkt *pktp;

        if (dadkp->dad_rdonly) {
                bioerror(bp, EROFS);
                return (DDI_FAILURE);
        }

        if (bp->b_bcount & (dadkp->DAD_SECSIZ-1)) {
                bioerror(bp, ENXIO);
                return (DDI_FAILURE);
        }

        SET_BP_SEC(bp, (LBLK2SEC(GET_BP_SEC(bp), dadkp->dad_blkshf)));

        pktp = dadk_pktprep(dadkp, NULL, bp, dadk_polldone, NULL, NULL);
        if (!pktp) {
                cmn_err(CE_WARN, "no resources for dumping");
                bioerror(bp, EIO);
                return (DDI_FAILURE);
        }
        pktp->cp_flags |= CPF_NOINTR;

        (void) dadk_ioprep(dadkp, pktp);
        dadk_transport(dadkp, bp);
        pktp->cp_byteleft -= pktp->cp_bytexfer;

        while (geterror(bp) == 0 && pktp->cp_byteleft != 0) {
                (void) dadk_iosetup(dadkp, pktp);
                dadk_transport(dadkp, bp);
                pktp->cp_byteleft -= pktp->cp_bytexfer;
        }

        if (pktp->cp_private)
                BBH_FREEHANDLE(dadkp->dad_bbhobjp, pktp->cp_private);
        gda_free(dadkp->dad_ctlobjp, pktp, NULL);
        return (DDI_SUCCESS);
}

/* ARGSUSED  */
int
dadk_ioctl(opaque_t objp, dev_t dev, int cmd, intptr_t arg, int flag,
        cred_t *cred_p, int *rval_p)
{
        struct dadk *dadkp = (struct dadk *)objp;

        switch (cmd) {
        case DKIOCGETDEF:
                {
                struct buf      *bp;
                int             err, head;
                unsigned char   *secbuf;
                STRUCT_DECL(defect_header, adh);

                STRUCT_INIT(adh, flag & FMODELS);

                /*
                 * copyin header ....
                 * yields head number and buffer address
                 */
                if (ddi_copyin((caddr_t)arg, STRUCT_BUF(adh), STRUCT_SIZE(adh),
                    flag))
                        return (EFAULT);
                head = STRUCT_FGET(adh, head);
                if (head < 0 || head >= dadkp->dad_phyg.g_head)
                        return (ENXIO);
                secbuf = kmem_zalloc(NBPSCTR, KM_SLEEP);
                if (!secbuf)
                        return (ENOMEM);
                bp = getrbuf(KM_SLEEP);
                if (!bp) {
                        kmem_free(secbuf, NBPSCTR);
                        return (ENOMEM);
                }

                bp->b_edev = dev;
                bp->b_dev  = cmpdev(dev);
                bp->b_flags = B_BUSY;
                bp->b_resid = 0;
                bp->b_bcount = NBPSCTR;
                bp->b_un.b_addr = (caddr_t)secbuf;
                bp->b_blkno = head; /* I had to put it somwhere! */
                bp->b_forw = (struct buf *)dadkp;
                bp->b_back = (struct buf *)DCMD_GETDEF;

                mutex_enter(&dadkp->dad_cmd_mutex);
                dadkp->dad_cmd_count++;
                mutex_exit(&dadkp->dad_cmd_mutex);
                FLC_ENQUE(dadkp->dad_flcobjp, bp);
                err = biowait(bp);
                if (!err) {
                        if (ddi_copyout((caddr_t)secbuf,
                            STRUCT_FGETP(adh, buffer), NBPSCTR, flag))
                                err = ENXIO;
                }
                kmem_free(secbuf, NBPSCTR);
                freerbuf(bp);
                return (err);
                }
        case DIOCTL_RWCMD:
                {
                struct dadkio_rwcmd *rwcmdp;
                int status, rw;

                /*
                 * copied in by cmdk and, if necessary, converted to the
                 * correct datamodel
                 */
                rwcmdp = (struct dadkio_rwcmd *)(intptr_t)arg;

                /*
                 * handle the complex cases here; we pass these
                 * through to the driver, which will queue them and
                 * handle the requests asynchronously.  The simpler
                 * cases ,which can return immediately, fail here, and
                 * the request reverts to the dadk_ioctl routine, while
                 *  will reroute them directly to the ata driver.
                 */
                switch (rwcmdp->cmd) {
                        case DADKIO_RWCMD_READ :
                                /*FALLTHROUGH*/
                        case DADKIO_RWCMD_WRITE:
                                rw = ((rwcmdp->cmd == DADKIO_RWCMD_WRITE) ?
                                    B_WRITE : B_READ);
                                status = dadk_dk_buf_setup(dadkp,
                                    (opaque_t)rwcmdp, dev, ((flag &FKIOCTL) ?
                                    UIO_SYSSPACE : UIO_USERSPACE), rw);
                                return (status);
                        default:
                                return (EINVAL);
                        }
                }
        case DKIOC_UPDATEFW:

                /*
                 * Require PRIV_ALL privilege to invoke DKIOC_UPDATEFW
                 * to protect the firmware update from malicious use
                 */
                if (PRIV_POLICY(cred_p, PRIV_ALL, B_FALSE, EPERM, NULL) != 0)
                        return (EPERM);
                else
                        return (dadk_ctl_ioctl(dadkp, cmd, arg, flag));

        case DKIOCFLUSHWRITECACHE:
                {
                        struct buf *bp;
                        int err = 0;
                        struct dk_callback *dkc = (struct dk_callback *)arg;
                        struct cmpkt *pktp;
                        int is_sync = 1;

                        mutex_enter(&dadkp->dad_mutex);
                        if (dadkp->dad_noflush || !  dadkp->dad_wce) {
                                err = dadkp->dad_noflush ? ENOTSUP : 0;
                                mutex_exit(&dadkp->dad_mutex);
                                /*
                                 * If a callback was requested: a
                                 * callback will always be done if the
                                 * caller saw the DKIOCFLUSHWRITECACHE
                                 * ioctl return 0, and never done if the
                                 * caller saw the ioctl return an error.
                                 */
                                if ((flag & FKIOCTL) && dkc != NULL &&
                                    dkc->dkc_callback != NULL) {
                                        (*dkc->dkc_callback)(dkc->dkc_cookie,
                                            err);
                                        /*
                                         * Did callback and reported error.
                                         * Since we did a callback, ioctl
                                         * should return 0.
                                         */
                                        err = 0;
                                }
                                return (err);
                        }
                        mutex_exit(&dadkp->dad_mutex);

                        bp = getrbuf(KM_SLEEP);

                        bp->b_edev = dev;
                        bp->b_dev  = cmpdev(dev);
                        bp->b_flags = B_BUSY;
                        bp->b_resid = 0;
                        bp->b_bcount = 0;
                        SET_BP_SEC(bp, 0);

                        if ((flag & FKIOCTL) && dkc != NULL &&
                            dkc->dkc_callback != NULL) {
                                struct dk_callback *dkc2 =
                                    (struct dk_callback *)kmem_zalloc(
                                    sizeof (struct dk_callback), KM_SLEEP);

                                bcopy(dkc, dkc2, sizeof (*dkc2));
                                bp->b_private = dkc2;
                                bp->b_iodone = dadk_flushdone;
                                is_sync = 0;
                        }

                        /*
                         * Setup command pkt
                         * dadk_pktprep() can't fail since DDI_DMA_SLEEP set
                         */
                        pktp = dadk_pktprep(dadkp, NULL, bp,
                            dadk_iodone, DDI_DMA_SLEEP, NULL);

                        pktp->cp_time = DADK_FLUSH_CACHE_TIME;

                        *((char *)(pktp->cp_cdbp)) = DCMD_FLUSH_CACHE;
                        pktp->cp_byteleft = 0;
                        pktp->cp_private = NULL;
                        pktp->cp_secleft = 0;
                        pktp->cp_srtsec = -1;
                        pktp->cp_bytexfer = 0;

                        CTL_IOSETUP(dadkp->dad_ctlobjp, pktp);

                        mutex_enter(&dadkp->dad_cmd_mutex);
                        dadkp->dad_cmd_count++;
                        mutex_exit(&dadkp->dad_cmd_mutex);
                        FLC_ENQUE(dadkp->dad_flcobjp, bp);

                        if (is_sync) {
                                err = biowait(bp);
                                freerbuf(bp);
                        }
                        return (err);
                }
        default:
                if (!dadkp->dad_rmb)
                        return (dadk_ctl_ioctl(dadkp, cmd, arg, flag));
        }

        switch (cmd) {
        case CDROMSTOP:
                return (dadk_rmb_ioctl(dadkp, DCMD_STOP_MOTOR, 0,
                    0, DADK_SILENT));
        case CDROMSTART:
                return (dadk_rmb_ioctl(dadkp, DCMD_START_MOTOR, 0,
                    0, DADK_SILENT));
        case DKIOCLOCK:
                return (dadk_rmb_ioctl(dadkp, DCMD_LOCK, 0, 0, DADK_SILENT));
        case DKIOCUNLOCK:
                return (dadk_rmb_ioctl(dadkp, DCMD_UNLOCK, 0, 0, DADK_SILENT));
        case DKIOCEJECT:
        case CDROMEJECT:
                {
                        int ret;

                        if (ret = dadk_rmb_ioctl(dadkp, DCMD_UNLOCK, 0, 0,
                            DADK_SILENT)) {
                                return (ret);
                        }
                        if (ret = dadk_rmb_ioctl(dadkp, DCMD_EJECT, 0, 0,
                            DADK_SILENT)) {
                                return (ret);
                        }
                        mutex_enter(&dadkp->dad_mutex);
                        dadkp->dad_iostate = DKIO_EJECTED;
                        cv_broadcast(&dadkp->dad_state_cv);
                        mutex_exit(&dadkp->dad_mutex);

                        return (0);

                }
        default:
                return (ENOTTY);
        /*
         * cdrom audio commands
         */
        case CDROMPAUSE:
                cmd = DCMD_PAUSE;
                break;
        case CDROMRESUME:
                cmd = DCMD_RESUME;
                break;
        case CDROMPLAYMSF:
                cmd = DCMD_PLAYMSF;
                break;
        case CDROMPLAYTRKIND:
                cmd = DCMD_PLAYTRKIND;
                break;
        case CDROMREADTOCHDR:
                cmd = DCMD_READTOCHDR;
                break;
        case CDROMREADTOCENTRY:
                cmd = DCMD_READTOCENT;
                break;
        case CDROMVOLCTRL:
                cmd = DCMD_VOLCTRL;
                break;
        case CDROMSUBCHNL:
                cmd = DCMD_SUBCHNL;
                break;
        case CDROMREADMODE2:
                cmd = DCMD_READMODE2;
                break;
        case CDROMREADMODE1:
                cmd = DCMD_READMODE1;
                break;
        case CDROMREADOFFSET:
                cmd = DCMD_READOFFSET;
                break;
        }
        return (dadk_rmb_ioctl(dadkp, cmd, arg, flag, 0));
}

int
dadk_flushdone(struct buf *bp)
{
        struct dk_callback *dkc = bp->b_private;

        ASSERT(dkc != NULL && dkc->dkc_callback != NULL);

        (*dkc->dkc_callback)(dkc->dkc_cookie, geterror(bp));

        kmem_free(dkc, sizeof (*dkc));
        freerbuf(bp);
        return (0);
}

int
dadk_getphygeom(opaque_t objp, struct tgdk_geom *dkgeom_p)
{
        struct dadk *dadkp = (struct dadk *)objp;

        bcopy((caddr_t)&dadkp->dad_phyg, (caddr_t)dkgeom_p,
            sizeof (struct tgdk_geom));
        return (DDI_SUCCESS);
}

int
dadk_getgeom(opaque_t objp, struct tgdk_geom *dkgeom_p)
{
        struct dadk *dadkp = (struct dadk *)objp;
        bcopy((caddr_t)&dadkp->dad_logg, (caddr_t)dkgeom_p,
            sizeof (struct tgdk_geom));
        return (DDI_SUCCESS);
}

int
dadk_setgeom(opaque_t objp, struct tgdk_geom *dkgeom_p)
{
        struct dadk *dadkp = (struct dadk *)objp;

        dadkp->dad_logg.g_cyl = dkgeom_p->g_cyl;
        dadkp->dad_logg.g_head = dkgeom_p->g_head;
        dadkp->dad_logg.g_sec = dkgeom_p->g_sec;
        dadkp->dad_logg.g_cap = dkgeom_p->g_cap;
        return (DDI_SUCCESS);
}


tgdk_iob_handle
dadk_iob_alloc(opaque_t objp, daddr_t blkno, ssize_t xfer, int kmsflg)
{
        struct dadk *dadkp = (struct dadk *)objp;
        struct buf *bp;
        struct tgdk_iob *iobp;
        size_t rlen;

        iobp = kmem_zalloc(sizeof (*iobp), kmsflg);
        if (iobp == NULL)
                return (NULL);
        if ((bp = getrbuf(kmsflg)) == NULL) {
                kmem_free(iobp, sizeof (*iobp));
                return (NULL);
        }

        iobp->b_psec  = LBLK2SEC(blkno, dadkp->dad_blkshf);
        iobp->b_pbyteoff = (blkno & ((1<<dadkp->dad_blkshf) - 1)) << SCTRSHFT;
        iobp->b_pbytecnt = ((iobp->b_pbyteoff + xfer + dadkp->DAD_SECSIZ - 1)
            >> dadkp->dad_secshf) << dadkp->dad_secshf;

        bp->b_un.b_addr = 0;
        /*
         * use i_ddi_mem_alloc() for now until we have an interface to allocate
         * memory for DMA which doesn't require a DMA handle.
         */
        if (i_ddi_mem_alloc((dadkp->dad_sd)->sd_dev, &dadk_alloc_attr,
            (size_t)iobp->b_pbytecnt, ((kmsflg == KM_SLEEP) ? 1 : 0), 0, NULL,
            &bp->b_un.b_addr, &rlen, NULL) != DDI_SUCCESS) {
                freerbuf(bp);
                kmem_free(iobp, sizeof (*iobp));
                return (NULL);
        }
        iobp->b_flag |= IOB_BPALLOC | IOB_BPBUFALLOC;
        iobp->b_bp = bp;
        iobp->b_lblk = blkno;
        iobp->b_xfer = xfer;
        iobp->b_lblk = blkno;
        iobp->b_xfer = xfer;
        return (iobp);
}

/* ARGSUSED */
int
dadk_iob_free(opaque_t objp, struct tgdk_iob *iobp)
{
        struct buf *bp;

        if (iobp) {
                if (iobp->b_bp && (iobp->b_flag & IOB_BPALLOC)) {
                        bp = iobp->b_bp;
                        if (bp->b_un.b_addr && (iobp->b_flag & IOB_BPBUFALLOC))
                                i_ddi_mem_free((caddr_t)bp->b_un.b_addr, NULL);
                        freerbuf(bp);
                }
                kmem_free(iobp, sizeof (*iobp));
        }
        return (DDI_SUCCESS);
}

/* ARGSUSED */
caddr_t
dadk_iob_htoc(opaque_t objp, struct tgdk_iob *iobp)
{
        return (iobp->b_bp->b_un.b_addr+iobp->b_pbyteoff);
}


caddr_t
dadk_iob_xfer(opaque_t objp, struct tgdk_iob *iobp, int rw)
{
        struct dadk     *dadkp = (struct dadk *)objp;
        struct buf      *bp;
        int             err;

        bp = iobp->b_bp;
        if (dadkp->dad_rdonly && !(rw & B_READ)) {
                bioerror(bp, EROFS);
                return (NULL);
        }

        bp->b_flags |= (B_BUSY | rw);
        bp->b_bcount = iobp->b_pbytecnt;
        SET_BP_SEC(bp, iobp->b_psec);
        bp->av_back = (struct buf *)0;
        bp->b_resid = 0;

        /* call flow control */
        mutex_enter(&dadkp->dad_cmd_mutex);
        dadkp->dad_cmd_count++;
        mutex_exit(&dadkp->dad_cmd_mutex);
        FLC_ENQUE(dadkp->dad_flcobjp, bp);
        err = biowait(bp);

        bp->b_bcount = iobp->b_xfer;
        bp->b_flags &= ~(B_DONE|B_BUSY);

        if (err)
                return (NULL);

        return (bp->b_un.b_addr+iobp->b_pbyteoff);
}

static void
dadk_transport(opaque_t com_data, struct buf *bp)
{
        struct dadk *dadkp = (struct dadk *)com_data;

        if (CTL_TRANSPORT(dadkp->dad_ctlobjp, GDA_BP_PKT(bp)) ==
            CTL_SEND_SUCCESS)
                return;
        dadk_restart((void*)GDA_BP_PKT(bp));
}

static int
dadk_pkt(opaque_t com_data, struct buf *bp, int (*func)(caddr_t), caddr_t arg)
{
        struct cmpkt *pktp;
        struct dadk *dadkp = (struct dadk *)com_data;

        if (GDA_BP_PKT(bp))
                return (DDI_SUCCESS);

        pktp = dadk_pktprep(dadkp, NULL, bp, dadk_iodone, func, arg);
        if (!pktp)
                return (DDI_FAILURE);

        return (dadk_ioprep(dadkp, pktp));
}

/*
 * Read, Write preparation
 */
static int
dadk_ioprep(struct dadk *dadkp, struct cmpkt *pktp)
{
        struct buf *bp;

        bp = pktp->cp_bp;
        if (bp->b_forw == (struct buf *)dadkp)
                *((char *)(pktp->cp_cdbp)) = (char)(intptr_t)bp->b_back;

        else if (bp->b_flags & B_READ)
                *((char *)(pktp->cp_cdbp)) = DCMD_READ;
        else
                *((char *)(pktp->cp_cdbp)) = DCMD_WRITE;
        pktp->cp_byteleft = bp->b_bcount;

        /* setup the bad block list handle */
        pktp->cp_private = BBH_GETHANDLE(dadkp->dad_bbhobjp, bp);
        return (dadk_iosetup(dadkp, pktp));
}

static int
dadk_iosetup(struct dadk *dadkp, struct cmpkt *pktp)
{
        struct buf      *bp;
        bbh_cookie_t    bbhckp;
        int             seccnt;

        seccnt = pktp->cp_bytexfer >> dadkp->dad_secshf;
        pktp->cp_secleft -= seccnt;

        if (pktp->cp_secleft) {
                pktp->cp_srtsec += seccnt;
        } else {
                /* get the first cookie from the bad block list */
                if (!pktp->cp_private) {
                        bp = pktp->cp_bp;
                        pktp->cp_srtsec  = GET_BP_SEC(bp);
                        pktp->cp_secleft = (bp->b_bcount >> dadkp->dad_secshf);
                } else {
                        bbhckp = BBH_HTOC(dadkp->dad_bbhobjp,
                            pktp->cp_private);
                        pktp->cp_srtsec = BBH_GETCK_SECTOR(dadkp->dad_bbhobjp,
                            bbhckp);
                        pktp->cp_secleft = BBH_GETCK_SECLEN(dadkp->dad_bbhobjp,
                            bbhckp);
                }
        }

        pktp->cp_bytexfer = pktp->cp_secleft << dadkp->dad_secshf;

        if (CTL_IOSETUP(dadkp->dad_ctlobjp, pktp)) {
                return (DDI_SUCCESS);
        } else {
                return (DDI_FAILURE);
        }




}

static struct cmpkt *
dadk_pktprep(struct dadk *dadkp, struct cmpkt *in_pktp, struct buf *bp,
    void (*cb_func)(struct buf *), int (*func)(caddr_t), caddr_t arg)
{
        struct cmpkt *pktp;

        pktp = gda_pktprep(dadkp->dad_ctlobjp, in_pktp, (opaque_t)bp, func,
            arg);

        if (pktp) {
                pktp->cp_callback = dadk_pktcb;
                pktp->cp_time = DADK_IO_TIME;
                pktp->cp_flags = 0;
                pktp->cp_iodone = cb_func;
                pktp->cp_dev_private = (opaque_t)dadkp;

        }

        return (pktp);
}


static void
dadk_restart(void *vpktp)
{
        struct cmpkt *pktp = (struct cmpkt *)vpktp;

        if (dadk_ioretry(pktp, QUE_COMMAND) == JUST_RETURN)
                return;
        pktp->cp_iodone(pktp->cp_bp);
}

static int
dadk_ioretry(struct cmpkt *pktp, int action)
{
        struct buf *bp;
        struct dadk *dadkp = PKT2DADK(pktp);

        switch (action) {
        case QUE_COMMAND:
                if (pktp->cp_retry++ < DADK_RETRY_COUNT) {
                        CTL_IOSETUP(dadkp->dad_ctlobjp, pktp);
                        if (CTL_TRANSPORT(dadkp->dad_ctlobjp, pktp) ==
                            CTL_SEND_SUCCESS) {
                                return (JUST_RETURN);
                        }
                        gda_log(dadkp->dad_sd->sd_dev, dadk_name,
                            CE_WARN, "transport of command fails\n");
                } else
                        gda_log(dadkp->dad_sd->sd_dev,
                            dadk_name, CE_WARN,
                            "exceeds maximum number of retries\n");
                bioerror(pktp->cp_bp, ENXIO);
                /*FALLTHROUGH*/
        case COMMAND_DONE_ERROR:
                bp = pktp->cp_bp;
                bp->b_resid += pktp->cp_byteleft - pktp->cp_bytexfer +
                    pktp->cp_resid;
                if (geterror(bp) == 0) {
                        if ((*((char *)(pktp->cp_cdbp)) == DCMD_FLUSH_CACHE) &&
                            (pktp->cp_dev_private == (opaque_t)dadkp) &&
                            ((int)(*(char *)pktp->cp_scbp) == DERR_ABORT)) {
                                /*
                                 * Flag "unimplemented" responses for
                                 * DCMD_FLUSH_CACHE as ENOTSUP
                                 */
                                bioerror(bp, ENOTSUP);
                                mutex_enter(&dadkp->dad_mutex);
                                dadkp->dad_noflush = 1;
                                mutex_exit(&dadkp->dad_mutex);
                        } else {
                                bioerror(bp, EIO);
                        }
                }
                /*FALLTHROUGH*/
        case COMMAND_DONE:
        default:
                return (COMMAND_DONE);
        }
}


static void
dadk_pktcb(struct cmpkt *pktp)
{
        int action;
        struct dadkio_rwcmd *rwcmdp;

        rwcmdp = (struct dadkio_rwcmd *)pktp->cp_passthru;  /* ioctl packet */

        if (pktp->cp_reason == CPS_SUCCESS) {
                if (rwcmdp && (rwcmdp != (opaque_t)DADK_SILENT))
                        rwcmdp->status.status = DADKIO_STAT_NO_ERROR;
                pktp->cp_iodone(pktp->cp_bp);
                return;
        }

        if (rwcmdp && (rwcmdp != (opaque_t)DADK_SILENT)) {
                if (pktp->cp_reason == CPS_CHKERR)
                        dadk_recorderr(pktp, rwcmdp);
                dadk_iodone(pktp->cp_bp);
                return;
        }

        if (pktp->cp_reason == CPS_CHKERR)
                action = dadk_chkerr(pktp);
        else
                action = COMMAND_DONE_ERROR;

        if (action == JUST_RETURN)
                return;

        /*
         * If we are panicking don't retry the command
         * just fail it so we can go down completing all
         * of the buffers.
         */
        if (ddi_in_panic() && action == QUE_COMMAND)
                action = COMMAND_DONE_ERROR;

        if (action != COMMAND_DONE) {
                if ((dadk_ioretry(pktp, action)) == JUST_RETURN)
                        return;
        }
        pktp->cp_iodone(pktp->cp_bp);
}



static struct dadkio_derr dadk_errtab[] = {
        {COMMAND_DONE, GDA_INFORMATIONAL},      /*  0 DERR_SUCCESS      */
        {QUE_COMMAND, GDA_FATAL},               /*  1 DERR_AMNF         */
        {QUE_COMMAND, GDA_FATAL},               /*  2 DERR_TKONF        */
        {COMMAND_DONE_ERROR, GDA_INFORMATIONAL}, /* 3 DERR_ABORT        */
        {QUE_COMMAND, GDA_RETRYABLE},           /*  4 DERR_DWF          */
        {QUE_COMMAND, GDA_FATAL},               /*  5 DERR_IDNF         */
        {JUST_RETURN, GDA_INFORMATIONAL},       /*  6 DERR_BUSY         */
        {QUE_COMMAND, GDA_FATAL},               /*  7 DERR_UNC          */
        {QUE_COMMAND, GDA_RETRYABLE},           /*  8 DERR_BBK          */
        {COMMAND_DONE_ERROR, GDA_FATAL},        /*  9 DERR_INVCDB       */
        {COMMAND_DONE_ERROR, GDA_FATAL},        /* 10 DERR_HARD         */
        {COMMAND_DONE_ERROR, GDA_FATAL},        /* 11 DERR_ILI          */
        {COMMAND_DONE_ERROR, GDA_FATAL},        /* 12 DERR_EOM          */
        {COMMAND_DONE, GDA_INFORMATIONAL},      /* 13 DERR_MCR          */
        {COMMAND_DONE, GDA_INFORMATIONAL},      /* 14 DERR_RECOVER      */
        {COMMAND_DONE_ERROR, GDA_FATAL},        /* 15 DERR_NOTREADY     */
        {QUE_COMMAND, GDA_RETRYABLE},           /* 16 DERR_MEDIUM       */
        {COMMAND_DONE_ERROR, GDA_FATAL},        /* 17 DERR_HW           */
        {COMMAND_DONE, GDA_FATAL},              /* 18 DERR_ILL          */
        {COMMAND_DONE, GDA_FATAL},              /* 19 DERR_UNIT_ATTN    */
        {COMMAND_DONE_ERROR, GDA_FATAL},        /* 20 DERR_DATA_PROT    */
        {COMMAND_DONE_ERROR, GDA_FATAL},        /* 21 DERR_MISCOMPARE   */
        {QUE_COMMAND, GDA_RETRYABLE},           /* 22 DERR_ICRC         */
        {COMMAND_DONE_ERROR, GDA_FATAL},        /* 23 DERR_RESV         */
};

static int
dadk_chkerr(struct cmpkt *pktp)
{
        daddr_t err_blkno;
        struct dadk *dadkp = PKT2DADK(pktp);
        dadk_errstats_t *dep;
        int scb = *(char *)pktp->cp_scbp;

        if (scb == DERR_SUCCESS) {
                if (pktp->cp_retry != 0 && dadkp->dad_errstats != NULL) {
                        dep = (dadk_errstats_t *)
                            dadkp->dad_errstats->ks_data;
                        dep->dadk_rq_recov_err.value.ui32++;
                }
                return (COMMAND_DONE);
        }

        if (pktp->cp_retry) {
                err_blkno = pktp->cp_srtsec + ((pktp->cp_bytexfer -
                    pktp->cp_resid) >> dadkp->dad_secshf);
        } else
                err_blkno = -1;

        if (dadkp->dad_errstats != NULL) {
                dep = (dadk_errstats_t *)dadkp->dad_errstats->ks_data;

                switch (dadk_errtab[scb].d_severity) {
                        case GDA_RETRYABLE:
                                dep->dadk_softerrs.value.ui32++;
                                break;

                        case GDA_FATAL:
                                dep->dadk_harderrs.value.ui32++;
                                break;

                        default:
                                break;
                }

                switch (scb) {
                        case DERR_INVCDB:
                        case DERR_ILI:
                        case DERR_EOM:
                        case DERR_HW:
                        case DERR_ICRC:
                                dep->dadk_transerrs.value.ui32++;
                                break;

                        case DERR_AMNF:
                        case DERR_TKONF:
                        case DERR_DWF:
                        case DERR_BBK:
                        case DERR_UNC:
                        case DERR_HARD:
                        case DERR_MEDIUM:
                        case DERR_DATA_PROT:
                        case DERR_MISCOMP:
                                dep->dadk_rq_media_err.value.ui32++;
                                break;

                        case DERR_NOTREADY:
                                dep->dadk_rq_ntrdy_err.value.ui32++;
                                break;

                        case DERR_IDNF:
                        case DERR_UNIT_ATTN:
                                dep->dadk_rq_nodev_err.value.ui32++;
                                break;

                        case DERR_ILL:
                        case DERR_RESV:
                                dep->dadk_rq_illrq_err.value.ui32++;
                                break;

                        default:
                                break;
                }
        }

        /* if attempting to read a sector from a cdrom audio disk */
        if ((dadkp->dad_cdrom) &&
            (*((char *)(pktp->cp_cdbp)) == DCMD_READ) &&
            (scb == DERR_ILL)) {
                return (COMMAND_DONE);
        }
        if (pktp->cp_passthru == NULL) {
                gda_errmsg(dadkp->dad_sd, pktp, dadk_name,
                    dadk_errtab[scb].d_severity, pktp->cp_srtsec,
                    err_blkno, dadk_cmds, dadk_sense);
        }

        if (scb == DERR_BUSY) {
                (void) timeout(dadk_restart, (void *)pktp, DADK_BSY_TIMEOUT);
        }

        return (dadk_errtab[scb].d_action);
}

static void
dadk_recorderr(struct cmpkt *pktp, struct dadkio_rwcmd *rwcmdp)
{
        struct dadk *dadkp;
        int scb;

        dadkp = PKT2DADK(pktp);
        scb = (int)(*(char *)pktp->cp_scbp);


        rwcmdp->status.failed_blk = rwcmdp->blkaddr +
            ((pktp->cp_bytexfer - pktp->cp_resid) >> dadkp->dad_secshf);

        rwcmdp->status.resid = pktp->cp_bp->b_resid +
            pktp->cp_byteleft - pktp->cp_bytexfer + pktp->cp_resid;
        switch ((int)(* (char *)pktp->cp_scbp)) {
        case DERR_AMNF:
        case DERR_ABORT:
                rwcmdp->status.status = DADKIO_STAT_ILLEGAL_REQUEST;
                break;
        case DERR_DWF:
        case DERR_IDNF:
                rwcmdp->status.status = DADKIO_STAT_ILLEGAL_ADDRESS;
                break;
        case DERR_TKONF:
        case DERR_UNC:
        case DERR_BBK:
                rwcmdp->status.status = DADKIO_STAT_MEDIUM_ERROR;
                rwcmdp->status.failed_blk_is_valid = 1;
                rwcmdp->status.resid = 0;
                break;
        case DERR_BUSY:
                rwcmdp->status.status = DADKIO_STAT_NOT_READY;
                break;
        case DERR_INVCDB:
        case DERR_HARD:
                rwcmdp->status.status = DADKIO_STAT_HARDWARE_ERROR;
                break;
        case DERR_ICRC:
        default:
                rwcmdp->status.status = DADKIO_STAT_NOT_SUPPORTED;
        }

        if (rwcmdp->flags & DADKIO_FLAG_SILENT)
                return;
        gda_errmsg(dadkp->dad_sd, pktp, dadk_name, dadk_errtab[scb].d_severity,
            rwcmdp->blkaddr, rwcmdp->status.failed_blk,
            dadk_cmds, dadk_sense);
}

/*ARGSUSED*/
static void
dadk_polldone(struct buf *bp)
{
        struct cmpkt *pktp;
        struct dadk *dadkp;

        pktp  = GDA_BP_PKT(bp);
        dadkp = PKT2DADK(pktp);
        mutex_enter(&dadkp->dad_cmd_mutex);
        dadkp->dad_cmd_count--;
        mutex_exit(&dadkp->dad_cmd_mutex);
}

static void
dadk_iodone(struct buf *bp)
{
        struct cmpkt *pktp;
        struct dadk *dadkp;

        pktp  = GDA_BP_PKT(bp);
        dadkp = PKT2DADK(pktp);

        /* check for all iodone */
        pktp->cp_byteleft -= pktp->cp_bytexfer;
        if (geterror(bp) == 0 && pktp->cp_byteleft != 0) {
                pktp->cp_retry = 0;
                (void) dadk_iosetup(dadkp, pktp);


        /*      transport the next one */
                if (CTL_TRANSPORT(dadkp->dad_ctlobjp, pktp) == CTL_SEND_SUCCESS)
                        return;
                if ((dadk_ioretry(pktp, QUE_COMMAND)) == JUST_RETURN)
                        return;
        }

        /* start next one */
        FLC_DEQUE(dadkp->dad_flcobjp, bp);

        /* free pkt */
        if (pktp->cp_private)
                BBH_FREEHANDLE(dadkp->dad_bbhobjp, pktp->cp_private);
        gda_free(dadkp->dad_ctlobjp, pktp, NULL);
        mutex_enter(&dadkp->dad_cmd_mutex);
        dadkp->dad_cmd_count--;
        mutex_exit(&dadkp->dad_cmd_mutex);
        biodone(bp);
}

int
dadk_check_media(opaque_t objp, int *state)
{
        struct dadk *dadkp = (struct dadk *)objp;

        if (!dadkp->dad_rmb) {
                return (ENXIO);
        }
#ifdef DADK_DEBUG
        if (dadk_debug & DSTATE)
                PRF("dadk_check_media: user state %x disk state %x\n",
                    *state, dadkp->dad_iostate);
#endif
        /*
         * If state already changed just return
         */
        if (*state != dadkp->dad_iostate) {
                *state = dadkp->dad_iostate;
                return (0);
        }

        /*
         * Startup polling on thread state
         */
        mutex_enter(&dadkp->dad_mutex);
        if (dadkp->dad_thread_cnt == 0) {
                /*
                 * One thread per removable dadk device
                 */
                (void) thread_create(NULL, 0, dadk_watch_thread, dadkp, 0, &p0,
                    TS_RUN, v.v_maxsyspri - 2);
        }
        dadkp->dad_thread_cnt++;

        /*
         * Wait for state to change
         */
        do {
                if (cv_wait_sig(&dadkp->dad_state_cv, &dadkp->dad_mutex) == 0) {
                        dadkp->dad_thread_cnt--;
                        mutex_exit(&dadkp->dad_mutex);
                        return (EINTR);
                }
        } while (*state == dadkp->dad_iostate);
        *state = dadkp->dad_iostate;
        dadkp->dad_thread_cnt--;
        mutex_exit(&dadkp->dad_mutex);
        return (0);
}


#define MEDIA_ACCESS_DELAY 2000000

static void
dadk_watch_thread(struct dadk *dadkp)
{
        enum dkio_state state;
        int interval;

        interval = drv_usectohz(dadk_check_media_time);

        do {
                if (dadk_rmb_ioctl(dadkp, DCMD_GET_STATE, (intptr_t)&state, 0,
                    DADK_SILENT)) {
                        /*
                         * Assume state remained the same
                         */
                        state = dadkp->dad_iostate;
                }

                /*
                 * now signal the waiting thread if this is *not* the
                 * specified state;
                 * delay the signal if the state is DKIO_INSERTED
                 * to allow the target to recover
                 */
                if (state != dadkp->dad_iostate) {

                        dadkp->dad_iostate = state;
                        if (state == DKIO_INSERTED) {
                                /*
                                 * delay the signal to give the drive a chance
                                 * to do what it apparently needs to do
                                 */
                                (void) timeout((void(*)(void *))cv_broadcast,
                                    (void *)&dadkp->dad_state_cv,
                                    drv_usectohz((clock_t)MEDIA_ACCESS_DELAY));
                        } else {
                                cv_broadcast(&dadkp->dad_state_cv);
                        }
                }
                delay(interval);
        } while (dadkp->dad_thread_cnt);
}

int
dadk_inquiry(opaque_t objp, opaque_t *inqpp)
{
        struct dadk *dadkp = (struct dadk *)objp;
        struct scsi_inquiry **sinqpp = (struct scsi_inquiry **)inqpp;

        if (dadkp && dadkp->dad_sd && dadkp->dad_sd->sd_inq) {
                *sinqpp = dadkp->dad_sd->sd_inq;
                return (DDI_SUCCESS);
        }

        return (DDI_FAILURE);
}

static int
dadk_rmb_ioctl(struct dadk *dadkp, int cmd, intptr_t arg, int flags, int silent)

{
        struct buf *bp;
        int err;
        struct cmpkt *pktp;

        if ((bp = getrbuf(KM_SLEEP)) == NULL) {
                return (ENOMEM);
        }
        pktp = dadk_pktprep(dadkp, NULL, bp, dadk_rmb_iodone, NULL, NULL);
        if (!pktp) {
                freerbuf(bp);
                return (ENOMEM);
        }
        bp->b_back  = (struct buf *)arg;
        bp->b_forw  = (struct buf *)dadkp->dad_flcobjp;
        pktp->cp_passthru = (opaque_t)(intptr_t)silent;

        err = dadk_ctl_ioctl(dadkp, cmd, (uintptr_t)pktp, flags);
        freerbuf(bp);
        gda_free(dadkp->dad_ctlobjp, pktp, NULL);
        return (err);


}

static void
dadk_rmb_iodone(struct buf *bp)
{
        struct cmpkt *pktp;
        struct dadk *dadkp;

        pktp  = GDA_BP_PKT(bp);
        dadkp = PKT2DADK(pktp);

        bp->b_flags &= ~(B_DONE|B_BUSY);

        /* Start next one */
        FLC_DEQUE(dadkp->dad_flcobjp, bp);

        mutex_enter(&dadkp->dad_cmd_mutex);
        dadkp->dad_cmd_count--;
        mutex_exit(&dadkp->dad_cmd_mutex);
        biodone(bp);
}

static int
dadk_dk_buf_setup(struct dadk *dadkp, opaque_t *cmdp, dev_t dev,
        enum uio_seg dataspace, int rw)
{
        struct dadkio_rwcmd *rwcmdp = (struct dadkio_rwcmd *)cmdp;
        struct buf      *bp;
        struct iovec    aiov;
        struct uio      auio;
        struct uio      *uio = &auio;
        int             status;

        bp = getrbuf(KM_SLEEP);

        bp->av_forw = bp->b_forw = (struct buf *)dadkp;
        bp->b_back  = (struct buf *)rwcmdp;     /* ioctl packet */

        bzero((caddr_t)&auio, sizeof (struct uio));
        bzero((caddr_t)&aiov, sizeof (struct iovec));
        aiov.iov_base = rwcmdp->bufaddr;
        aiov.iov_len = rwcmdp->buflen;
        uio->uio_iov = &aiov;

        uio->uio_iovcnt = 1;
        uio->uio_resid = rwcmdp->buflen;
        uio->uio_segflg = dataspace;

        /* Let physio do the rest... */
        status = physio(dadk_dk_strategy, bp, dev, rw, dadkmin, uio);

        freerbuf(bp);
        return (status);

}

/* Do not let a user gendisk request get too big or */
/* else we could use to many resources.             */

static void
dadkmin(struct buf *bp)
{
        if (bp->b_bcount > dadk_dk_maxphys)
                bp->b_bcount = dadk_dk_maxphys;
}

static int
dadk_dk_strategy(struct buf *bp)
{
        dadk_dk((struct dadk *)bp->av_forw, (struct dadkio_rwcmd *)bp->b_back,
            bp);
        return (0);
}

static void
dadk_dk(struct dadk *dadkp, struct dadkio_rwcmd *rwcmdp, struct buf *bp)
{
        struct  cmpkt *pktp;

        pktp = dadk_pktprep(dadkp, NULL, bp, dadk_iodone, NULL, NULL);
        if (!pktp) {
                bioerror(bp, ENOMEM);
                biodone(bp);
                return;
        }

        pktp->cp_passthru = rwcmdp;

        (void) dadk_ioprep(dadkp, pktp);

        mutex_enter(&dadkp->dad_cmd_mutex);
        dadkp->dad_cmd_count++;
        mutex_exit(&dadkp->dad_cmd_mutex);
        FLC_ENQUE(dadkp->dad_flcobjp, bp);
}

/*
 * There is no existing way to notify cmdk module
 * when the command completed, so add this function
 * to calculate how many on-going commands.
 */
int
dadk_getcmds(opaque_t objp)
{
        struct dadk *dadkp = (struct dadk *)objp;
        int count;

        mutex_enter(&dadkp->dad_cmd_mutex);
        count = dadkp->dad_cmd_count;
        mutex_exit(&dadkp->dad_cmd_mutex);
        return (count);
}

/*
 * this function was used to calc the cmd for CTL_IOCTL
 */
static int
dadk_ctl_ioctl(struct dadk *dadkp, uint32_t cmd, uintptr_t arg, int flag)
{
        int error;
        mutex_enter(&dadkp->dad_cmd_mutex);
        dadkp->dad_cmd_count++;
        mutex_exit(&dadkp->dad_cmd_mutex);
        error = CTL_IOCTL(dadkp->dad_ctlobjp, cmd, arg, flag);
        mutex_enter(&dadkp->dad_cmd_mutex);
        dadkp->dad_cmd_count--;
        mutex_exit(&dadkp->dad_cmd_mutex);
        return (error);
}