root/usr/src/uts/intel/io/fdc.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * Copyright 2012 Garrett D'Amore <garrett@damore.org>.  All rights reserved.
 */


/*
 * Floppy Disk Controller Driver
 *
 *   for the standard PC architecture using the Intel 8272A fdc.
 *   Note that motor control and drive select use a latch external
 *   to the fdc.
 *
 *   This driver is EISA capable, and uses DMA buffer chaining if available.
 *   If this driver is attached to the ISA bus nexus (or if the EISA bus driver
 *   does not support DMA buffer chaining), then the bus driver must ensure
 *   that dma mapping (breakup) and dma engine requests are properly degraded.
 */

/*
 * hack for bugid 1160621:
 * workaround compiler optimization bug by turning on DEBUG
 */
#ifndef DEBUG
#define DEBUG   1
#endif

#include <sys/param.h>
#include <sys/buf.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/open.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/cmn_err.h>
#include <sys/note.h>
#include <sys/debug.h>
#include <sys/kmem.h>
#include <sys/stat.h>

#include <sys/autoconf.h>
#include <sys/dkio.h>
#include <sys/vtoc.h>
#include <sys/kstat.h>

#include <sys/fdio.h>
#include <sys/fdc.h>
#include <sys/i8272A.h>
#include <sys/fd_debug.h>
#include <sys/promif.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

/*
 * bss (uninitialized data)
 */
static void *fdc_state_head;            /* opaque handle top of state structs */
static ddi_dma_attr_t fdc_dma_attr;
static ddi_device_acc_attr_t fdc_accattr = {DDI_DEVICE_ATTR_V0,
        DDI_STRUCTURE_LE_ACC, DDI_STRICTORDER_ACC};

/*
 * Local static data
 */
#define OURUN_TRIES     12
static uchar_t rwretry = 4;
static uchar_t skretry = 3;
static uchar_t configurecmd[4] = {FO_CNFG, 0, 0x0F, 0};
static uchar_t recalcmd[2] = {FO_RECAL, 0};
static uchar_t senseintcmd = FO_SINT;

/*
 * error handling
 *
 * for debugging, set rwretry and skretry = 1
 *              set fcerrlevel to 1
 *              set fcerrmask  to 224  or 644
 *
 * after debug, set rwretry to 4, skretry to 3, and fcerrlevel to 5
 * set fcerrmask to FDEM_ALL
 * or remove the define DEBUG
 */
static uint_t fcerrmask = FDEM_ALL;
static int fcerrlevel = 6;

#define KIOIP   KSTAT_INTR_PTR(fcp->c_intrstat)


static xlate_tbl_t drate_mfm[] = {
        {  250, 2},
        {  300, 1},
        {  417, 0},
        {  500, 0},
        { 1000, 3},
        {    0, 0}
};

static xlate_tbl_t sector_size[] = {
        {  256, 1},
        {  512, 2},
        { 1024, 3},
        {    0, 2}
};

static xlate_tbl_t motor_onbits[] = {
        {  0, 0x10},
        {  1, 0x20},
        {  2, 0x40},
        {  3, 0x80},
        {  0, 0x80}
};

static xlate_tbl_t step_rate[] = {
        {  10, 0xF0},           /* for 500K data rate */
        {  20, 0xE0},
        {  30, 0xD0},
        {  40, 0xC0},
        {  50, 0xB0},
        {  60, 0xA0},
        {  70, 0x90},
        {  80, 0x80},
        {  90, 0x70},
        { 100, 0x60},
        { 110, 0x50},
        { 120, 0x40},
        { 130, 0x30},
        { 140, 0x20},
        { 150, 0x10},
        { 160, 0x00},
        {   0, 0x00}
};

#ifdef notdef
static xlate_tbl_t head_unld[] = {
        {  16, 0x1},            /* for 500K data rate */
        {  32, 0x2},
        {  48, 0x3},
        {  64, 0x4},
        {  80, 0x5},
        {  96, 0x6},
        { 112, 0x7},
        { 128, 0x8},
        { 144, 0x9},
        { 160, 0xA},
        { 176, 0xB},
        { 192, 0xC},
        { 208, 0xD},
        { 224, 0xE},
        { 240, 0xF},
        { 256, 0x0},
        {   0, 0x0}
};
#endif

static struct fdcmdinfo {
        char *cmdname;          /* command name */
        uchar_t ncmdbytes;      /* number of bytes of command */
        uchar_t nrsltbytes;     /* number of bytes in result */
        uchar_t cmdtype;                /* characteristics */
} fdcmds[] = {
        "", 0, 0, 0,                    /* - */
        "", 0, 0, 0,                    /* - */
        "read_track", 9, 7, 1,          /* 2 */
        "specify", 3, 0, 3,             /* 3 */
        "sense_drv_status", 2, 1, 3,    /* 4 */
        "write", 9, 7, 1,               /* 5 */
        "read", 9, 7, 1,                /* 6 */
        "recalibrate", 2, 0, 2,         /* 7 */
        "sense_int_status", 1, 2, 3,    /* 8 */
        "write_del", 9, 7, 1,           /* 9 */
        "read_id", 2, 7, 2,             /* A */
        "", 0, 0, 0,                    /* - */
        "read_del", 9, 7, 1,            /* C */
        "format_track", 10, 7, 1,       /* D */
        "dump_reg", 1, 10, 4,           /* E */
        "seek", 3, 0, 2,                /* F */
        "version", 1, 1, 3,             /* 10 */
        "", 0, 0, 0,                    /* - */
        "perp_mode", 2, 0, 3,           /* 12 */
        "configure", 4, 0, 4,           /* 13 */
        /* relative seek */
};


static int
fdc_bus_ctl(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *, void *);
static int get_ioaddr(dev_info_t *dip, int *ioaddr);
static int get_unit(dev_info_t *dip, int *cntrl_num);

struct bus_ops fdc_bus_ops = {
        BUSO_REV,
        nullbusmap,
        0,      /* ddi_intrspec_t (*bus_get_intrspec)(); */
        0,      /* int  (*bus_add_intrspec)(); */
        0,      /* void (*bus_remove_intrspec)(); */
        i_ddi_map_fault,
        0,
        ddi_dma_allochdl,
        ddi_dma_freehdl,
        ddi_dma_bindhdl,
        ddi_dma_unbindhdl,
        ddi_dma_flush,
        ddi_dma_win,
        ddi_dma_mctl,
        fdc_bus_ctl,
        ddi_bus_prop_op,
};

static int fdc_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int fdc_probe(dev_info_t *);
static int fdc_attach(dev_info_t *, ddi_attach_cmd_t);
static int fdc_detach(dev_info_t *, ddi_detach_cmd_t);
static int fdc_quiesce(dev_info_t *);
static int fdc_enhance_probe(struct fdcntlr *fcp);

struct dev_ops  fdc_ops = {
        DEVO_REV,               /* devo_rev, */
        0,                      /* refcnt  */
        fdc_getinfo,            /* getinfo */
        nulldev,                /* identify */
        fdc_probe,              /* probe */
        fdc_attach,             /* attach */
        fdc_detach,             /* detach */
        nodev,                  /* reset */
        (struct cb_ops *)0,     /* driver operations */
        &fdc_bus_ops,           /* bus operations */
        NULL,                   /* power */
        fdc_quiesce,            /* quiesce */
};

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

extern struct mod_ops mod_driverops;

static struct modldrv modldrv = {
        &mod_driverops,         /* Type of module. This one is a driver */
        "Floppy Controller",    /* Name of the module. */
        &fdc_ops,               /* Driver ops vector */
};

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

int
_init(void)
{
        int retval;

        if ((retval = ddi_soft_state_init(&fdc_state_head,
            sizeof (struct fdcntlr) + NFDUN * sizeof (struct fcu_obj), 0)) != 0)
                return (retval);

        if ((retval = mod_install(&modlinkage)) != 0)
                ddi_soft_state_fini(&fdc_state_head);
        return (retval);
}

int
_fini(void)
{
        int retval;

        if ((retval = mod_remove(&modlinkage)) != 0)
                return (retval);
        ddi_soft_state_fini(&fdc_state_head);
        return (retval);
}

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


int fdc_abort(struct fcu_obj *);
int fdc_dkinfo(struct fcu_obj *, struct dk_cinfo *);
int fdc_select(struct fcu_obj *, int, int);
int fdgetchng(struct fcu_obj *, int);
int fdresetchng(struct fcu_obj *, int);
int fdrecalseek(struct fcu_obj *, int, int, int);
int fdrw(struct fcu_obj *, int, int, int, int, int, caddr_t, uint_t);
int fdtrkformat(struct fcu_obj *, int, int, int, int);
int fdrawioctl(struct fcu_obj *, int, caddr_t);

static struct fcobjops fdc_iops = {
                fdc_abort,      /* controller abort */
                fdc_dkinfo,     /* get disk controller info */

                fdc_select,     /* select / deselect unit */
                fdgetchng,      /* get media change */
                fdresetchng,    /* reset media change */
                fdrecalseek,    /* recal / seek */
                NULL,           /* read /write request (UNUSED) */
                fdrw,           /* read /write sector */
                fdtrkformat,    /* format track */
                fdrawioctl      /* raw ioctl */
};


/*
 * Function prototypes
 */
void encode(xlate_tbl_t *tablep, int val, uchar_t *rcode);
int decode(xlate_tbl_t *, int, int *);
static int fdc_propinit1(struct fdcntlr *, int);
static void fdc_propinit2(struct fdcntlr *);
void fdcquiesce(struct fdcntlr *);
int fdcsense_chng(struct fdcntlr *, int);
int fdcsense_drv(struct fdcntlr *, int);
int fdcsense_int(struct fdcntlr *, int *, int *);
int fdcspecify(struct fdcntlr *, int, int, int);
int fdcspdchange(struct fdcntlr *, struct fcu_obj *, int);
static int fdc_exec(struct fdcntlr *, int, int);
int fdcheckdisk(struct fdcntlr *, int);
static uint_t fdc_intr(caddr_t arg);
static void fdwatch(void *arg);
static void fdmotort(void *arg);
static int fdrecover(struct fdcntlr *);
static int fdc_motorsm(struct fcu_obj *, int, int);
static int fdc_statemach(struct fdcntlr *);
int fdc_docmd(struct fdcntlr *, uchar_t *, uchar_t);
int fdc_result(struct fdcntlr *, uchar_t *, uchar_t);


static int
fdc_bus_ctl(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop,
    void *arg, void *result)
{
        struct  fdcntlr *fcp;
        struct  fcu_obj *fjp;

        _NOTE(ARGUNUSED(result));

        FCERRPRINT(FDEP_L0, FDEM_ATTA,
            (CE_CONT, "fdc_bus_ctl: cmd= %x\n", ctlop));

        if ((fcp = ddi_get_driver_private(dip)) == NULL)
                return (DDI_FAILURE);

        switch (ctlop) {

        case DDI_CTLOPS_REPORTDEV:
                cmn_err(CE_CONT, "?%s%d at %s%d\n",
                    ddi_get_name(rdip), ddi_get_instance(rdip),
                    ddi_get_name(dip), ddi_get_instance(dip));
                FCERRPRINT(FDEP_L3, FDEM_ATTA,
                    (CE_WARN, "fdc_bus_ctl: report %s%d at %s%d",
                    ddi_get_name(rdip), ddi_get_instance(rdip),
                    ddi_get_name(dip), ddi_get_instance(dip)));
                return (DDI_SUCCESS);

        case DDI_CTLOPS_INITCHILD:
        {
                dev_info_t *udip = (dev_info_t *)arg;
                int cntlr;
                int len;
                int unit;
                char name[MAXNAMELEN];

                FCERRPRINT(FDEP_L3, FDEM_ATTA,
                    (CE_WARN, "fdc_bus_ctl: init child 0x%p", (void*)udip));
                cntlr = fcp->c_number;

                len = sizeof (unit);
                if (ddi_prop_op(DDI_DEV_T_ANY, udip, PROP_LEN_AND_VAL_BUF,
                    DDI_PROP_DONTPASS, "unit", (caddr_t)&unit, &len)
                    != DDI_PROP_SUCCESS ||
                    cntlr != FDCTLR(unit) ||
                    (fcp->c_unit[FDUNIT(unit)])->fj_dip)
                        return (DDI_NOT_WELL_FORMED);

                (void) sprintf(name, "%d,%d", cntlr, FDUNIT(unit));
                ddi_set_name_addr(udip, name);

                fjp = fcp->c_unit[FDUNIT(unit)];
                fjp->fj_unit = unit;
                fjp->fj_dip = udip;
                fjp->fj_ops = &fdc_iops;
                fjp->fj_fdc = fcp;
                fjp->fj_iblock = &fcp->c_iblock;

                ddi_set_driver_private(udip, fjp);

                return (DDI_SUCCESS);
        }
        case DDI_CTLOPS_UNINITCHILD:
        {
                dev_info_t *udip = (dev_info_t *)arg;

                FCERRPRINT(FDEP_L3, FDEM_ATTA,
                    (CE_WARN, "fdc_bus_ctl: uninit child 0x%p", (void *)udip));
                fjp = ddi_get_driver_private(udip);
                ddi_set_driver_private(udip, NULL);
                fjp->fj_dip = NULL;
                ddi_set_name_addr(udip, NULL);
                return (DDI_SUCCESS);
        }
        default:
                return (DDI_FAILURE);
        }
}

static int
fdc_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
{
        struct fdcntlr *fcp;
        int rval;

        _NOTE(ARGUNUSED(dip));

        switch (cmd) {
        case DDI_INFO_DEVT2DEVINFO:
                if (fcp = ddi_get_soft_state(fdc_state_head, (dev_t)arg)) {
                        *result = fcp->c_dip;
                        rval = DDI_SUCCESS;
                        break;
                } else {
                        rval = DDI_FAILURE;
                        break;
                }
        case DDI_INFO_DEVT2INSTANCE:
                *result = (void *)(uintptr_t)getminor((dev_t)arg);
                rval = DDI_SUCCESS;
                break;
        default:
                rval = DDI_FAILURE;
        }
        return (rval);
}

static int
fdc_probe(dev_info_t *dip)
{
        int     debug[2];
        int ioaddr;
        int     len;
        uchar_t stat;

        len = sizeof (debug);
        if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
            DDI_PROP_DONTPASS, "debug", (caddr_t)debug, &len) ==
            DDI_PROP_SUCCESS) {
                fcerrlevel = debug[0];
                fcerrmask = (uint_t)debug[1];
        }

        FCERRPRINT(FDEP_L3, FDEM_ATTA, (CE_WARN, "fdc_probe: dip %p",
            (void*)dip));

        if (get_ioaddr(dip, &ioaddr) != DDI_SUCCESS)
                return (DDI_PROBE_FAILURE);

        stat = inb(ioaddr + FCR_MSR);
        if ((stat & (MS_RQM | MS_DIO | MS_CB)) != MS_RQM &&
            (stat & ~MS_DIO) != MS_CB)
                return (DDI_PROBE_FAILURE);

        return (DDI_PROBE_SUCCESS);
}

static int
fdc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        struct fdcntlr *fcp;
        struct fcu_obj *fjp;
        int cntlr_num, ctlr, unit;
        int intr_set = 0;
        int len;
        char name[MAXNAMELEN];

        FCERRPRINT(FDEP_L3, FDEM_ATTA, (CE_WARN, "fdc_attach: dip %p",
            (void*)dip));

        switch (cmd) {
        case DDI_ATTACH:
                if (ddi_getprop
                    (DDI_DEV_T_ANY, dip, 0, "ignore-hardware-nodes", 0)) {
                        len = sizeof (cntlr_num);
                        if (ddi_prop_op(DDI_DEV_T_ANY, dip,
                            PROP_LEN_AND_VAL_BUF, DDI_PROP_DONTPASS, "unit",
                            (caddr_t)&cntlr_num, &len) != DDI_PROP_SUCCESS) {
                                FCERRPRINT(FDEP_L3, FDEM_ATTA, (CE_WARN,
                                    "fdc_attach failed: dip %p", (void*)dip));
                                return (DDI_FAILURE);
                        }
                } else {
                        if (get_unit(dip, &cntlr_num) != DDI_SUCCESS)
                                return (DDI_FAILURE);
                }

                ctlr = ddi_get_instance(dip);
                if (ddi_soft_state_zalloc(fdc_state_head, ctlr) != 0)
                        return (DDI_FAILURE);
                fcp = ddi_get_soft_state(fdc_state_head, ctlr);

                for (unit = 0, fjp = (struct fcu_obj *)(fcp+1);
                    unit < NFDUN; unit++) {
                        fcp->c_unit[unit] = fjp++;
                }
                fcp->c_dip = dip;

                if (fdc_propinit1(fcp, cntlr_num) != DDI_SUCCESS)
                        goto no_attach;

                /* get iblock cookie to initialize mutex used in the ISR */
                if (ddi_get_iblock_cookie(dip, (uint_t)0, &fcp->c_iblock) !=
                    DDI_SUCCESS) {
                        cmn_err(CE_WARN,
                            "fdc_attach: cannot get iblock cookie");
                        goto no_attach;
                }
                mutex_init(&fcp->c_lock, NULL, MUTEX_DRIVER, fcp->c_iblock);
                intr_set = 1;

                /* setup interrupt handler */
                if (ddi_add_intr(dip, (uint_t)0, NULL,
                    (ddi_idevice_cookie_t *)0, fdc_intr, (caddr_t)fcp) !=
                    DDI_SUCCESS) {
                        cmn_err(CE_WARN, "fdc: cannot add intr");
                        goto no_attach;
                }
                intr_set++;

                /*
                 * acquire the DMA channel
                 * this assumes that the chnl is not shared; else allocate
                 * and free the chnl with each fdc request
                 */
                if (ddi_dmae_alloc(dip, fcp->c_dmachan, DDI_DMA_DONTWAIT, NULL)
                    != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "fdc: cannot acquire dma%d",
                            fcp->c_dmachan);
                        goto no_attach;
                }
                (void) ddi_dmae_getattr(dip, &fdc_dma_attr);
                fdc_dma_attr.dma_attr_align = MMU_PAGESIZE;

                mutex_init(&fcp->c_dorlock, NULL, MUTEX_DRIVER, fcp->c_iblock);
                cv_init(&fcp->c_iocv, NULL, CV_DRIVER, fcp->c_iblock);
                sema_init(&fcp->c_selsem, 1, NULL, SEMA_DRIVER, NULL);

                (void) sprintf(name, "fdc%d", ctlr);
                fcp->c_intrstat = kstat_create("fdc", ctlr, name,
                    "controller", KSTAT_TYPE_INTR, 1, KSTAT_FLAG_PERSISTENT);
                if (fcp->c_intrstat) {
                        kstat_install(fcp->c_intrstat);
                }

                ddi_set_driver_private(dip, fcp);

                /*
                 * reset the controller
                 */
                sema_p(&fcp->c_selsem);
                mutex_enter(&fcp->c_lock);
                fcp->c_csb.csb_xstate = FXS_RESET;
                fcp->c_flags |= FCFLG_WAITING;
                fdcquiesce(fcp);

                /* first test for mode == Model 30 */
                fcp->c_mode = (inb(fcp->c_regbase + FCR_SRB) & 0x1c) ?
                    FDCMODE_AT : FDCMODE_30;

                while (fcp->c_flags & FCFLG_WAITING) {
                        cv_wait(&fcp->c_iocv, &fcp->c_lock);
                }
                mutex_exit(&fcp->c_lock);
                sema_v(&fcp->c_selsem);

                fdc_propinit2(fcp);

                ddi_report_dev(dip);
                return (DDI_SUCCESS);

        case DDI_RESUME:

                fcp = ddi_get_driver_private(dip);

                mutex_enter(&fcp->c_lock);
                fcp->c_suspended = B_FALSE;
                fcp->c_csb.csb_xstate = FXS_RESET;
                fcp->c_flags |= FCFLG_WAITING;
                fdcquiesce(fcp);

                while (fcp->c_flags & FCFLG_WAITING) {
                        cv_wait(&fcp->c_iocv, &fcp->c_lock);
                }
                mutex_exit(&fcp->c_lock);

                /* should be good to go now */
                sema_v(&fcp->c_selsem);

                return (DDI_SUCCESS);
                /* break; */

        default:
                return (DDI_FAILURE);
        }

no_attach:
        if (intr_set) {
                if (intr_set > 1)
                        ddi_remove_intr(dip, 0, fcp->c_iblock);
                mutex_destroy(&fcp->c_lock);
        }
        ddi_soft_state_free(fdc_state_head, cntlr_num);
        return (DDI_FAILURE);
}

static int
fdc_propinit1(struct fdcntlr *fcp, int cntlr)
{
        dev_info_t *dip;
        int len;
        int value;

        dip = fcp->c_dip;
        len = sizeof (value);

        if (get_ioaddr(dip, &value) != DDI_SUCCESS)
                return (DDI_FAILURE);

        fcp->c_regbase = (ushort_t)value;

        if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
            DDI_PROP_DONTPASS, "dma-channels", (caddr_t)&value, &len)
            != DDI_PROP_SUCCESS) {
                        cmn_err(CE_WARN,
                            "fdc_attach: Error, could not find a dma channel");
                        return (DDI_FAILURE);
        }
        fcp->c_dmachan = (ushort_t)value;
        fcp->c_number = cntlr;
        return (DDI_SUCCESS);
}

static void
fdc_propinit2(struct fdcntlr *fcp)
{
        dev_info_t *dip;
        int ccr;
        int len;
        int value;

        dip = fcp->c_dip;
        len = sizeof (value);

        if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
            DDI_PROP_DONTPASS, "chip", (caddr_t)&value, &len)
            == DDI_PROP_SUCCESS)
                fcp->c_chip = value;
        else {
                static uchar_t perpindcmd[2] = {FO_PERP, 0};
                static uchar_t versioncmd = FO_VRSN;
                uchar_t result;

                fcp->c_chip = i8272A;
                (void) fdc_docmd(fcp, &versioncmd, 1);
                /*
                 * Ignored return. If failed, warning was issued by fdc_docmd.
                 * fdc_results retrieves the controller/drive status
                 */
                if (!fdc_result(fcp, &result, 1) && result == 0x90) {
                        /*
                         * try a perpendicular_mode cmd to ensure
                         * that we really have an enhanced controller
                         */
                        if (fdc_docmd(fcp, perpindcmd, 2) ||
                            fdc_docmd(fcp, configurecmd, 4))
                                /*
                                 * perpindicular_mode will be rejected by
                                 * older controllers; make sure we don't hang.
                                 */
                                (void) fdc_result(fcp, &result, 1);
                                /*
                                 * Ignored return. If failed, warning was
                                 * issued by fdc_result.
                                 */
                        else
                                /* enhanced type controller */

                                if ((fcp->c_chip = fdc_enhance_probe(fcp)) == 0)
                                        /* default enhanced cntlr */
                                        fcp->c_chip = i82077;
                }
                (void) ddi_prop_update_int(DDI_DEV_T_NONE, dip,
                    "chip", fcp->c_chip);
                /*
                 * Ignoring return value because, for passed arguments, only
                 * DDI_SUCCESS is returned.
                 */
        }
        if (fcp->c_chip >= i82077 && fcp->c_mode == FDCMODE_30 &&
            (inb(fcp->c_regbase + FCR_DIR) & 0x70) == 0)
                for (ccr = 0; ccr <= (FCC_NOPREC | FCC_DRATE); ccr++) {
                        /*
                         * run through all the combinations of NOPREC and
                         * datarate selection, and see if they show up in the
                         * Model 30 DIR
                         */
                        outb(fcp->c_regbase + FCR_CCR, ccr);
                        drv_usecwait(5);
                        if ((inb(fcp->c_regbase + FCR_DIR) &
                            (FCC_NOPREC | FCC_DRATE)) != ccr) {
                                fcp->c_mode = FDCMODE_AT;
                                break;
                        }
                }
        else
                fcp->c_mode = FDCMODE_AT;
        outb(fcp->c_regbase + FCR_CCR, 0);
}

static int
fdc_enhance_probe(struct fdcntlr *fcp)
{
        static uchar_t nsccmd = FO_NSC;
        uint_t  ddic;
        int     retcode = 0;
        uchar_t result;
        uchar_t save;

        /*
         * Try to identify the enhanced floppy controller.
         * This is required so that we can program the DENSEL output to
         * control 3D mode (1.0 MB, 1.6 MB and 2.0 MB unformatted capacity,
         * 720 KB, 1.2 MB, and 1.44 MB formatted capacity) 3.5" dual-speed
         * floppy drives.  Refer to bugid 1195155.
         */

        (void) fdc_docmd(fcp, &nsccmd, 1);
        /*
         * Ignored return. If failed, warning was issued by fdc_docmd.
         * fdc_results retrieves the controller/drive status
         */
        if (!fdc_result(fcp, &result, 1) && result != S0_IVCMD) {
                /*
                 * only enhanced National Semi PC8477 core
                 * should respond to this command
                 */
                if ((result & 0xf0) == 0x70) {
                        /* low 4 bits may change */
                        fcp->c_flags |= FCFLG_3DMODE;
                        retcode = PC87322;
                } else
                        cmn_err(CE_CONT,
"?fdc: unidentified, enhanced, National Semiconductor cntlr %x\n", result);
        } else {
                save = inb(fcp->c_regbase + FCR_SRA);

                do {
                        /* probe for motherboard version of SMC cntlr */

                        /* try to enable configuration mode */
                        ddic = ddi_enter_critical();
                        outb(fcp->c_regbase + FCR_SRA, FSA_ENA5);
                        outb(fcp->c_regbase + FCR_SRA, FSA_ENA5);
                        ddi_exit_critical(ddic);

                        outb(fcp->c_regbase + FCR_SRA, 0x0F);
                        if (inb(fcp->c_regbase + FCR_SRB) != 0x00)
                                /* always expect 0 from config reg F */
                                break;
                        outb(fcp->c_regbase + FCR_SRA, 0x0D);
                        if (inb(fcp->c_regbase + FCR_SRB) != 0x65)
                                /* expect 0x65 from config reg D */
                                break;
                        outb(fcp->c_regbase + FCR_SRA, 0x0E);
                        result = inb(fcp->c_regbase + FCR_SRB);
                        if (result != 0x02) {
                                /* expect revision level 2 from config reg E */
                                cmn_err(CE_CONT,
"?fdc: unidentified, enhanced, SMC cntlr revision %x\n", result);
                                /* break;       */
                        }
                        fcp->c_flags |= FCFLG_3DMODE;
                        retcode = FDC37C665;
                } while (retcode == 0);
                outb(fcp->c_regbase + FCR_SRA, FSA_DISB);

                while (retcode == 0) {
                        /* probe for adapter version of SMC cntlr */
                        ddic = ddi_enter_critical();
                        outb(fcp->c_regbase + FCR_SRA, FSA_ENA6);
                        outb(fcp->c_regbase + FCR_SRA, FSA_ENA6);
                        ddi_exit_critical(ddic);

                        outb(fcp->c_regbase + FCR_SRA, 0x0F);
                        if (inb(fcp->c_regbase + FCR_SRB) != 0x00)
                                /* always expect 0 from config reg F */
                                break;
                        outb(fcp->c_regbase + FCR_SRA, 0x0D);
                        if (inb(fcp->c_regbase + FCR_SRB) != 0x66)
                                /* expect 0x66 from config reg D */
                                break;
                        outb(fcp->c_regbase + FCR_SRA, 0x0E);
                        result = inb(fcp->c_regbase + FCR_SRB);
                        if (result != 0x02) {
                                /* expect revision level 2 from config reg E */
                                cmn_err(CE_CONT,
"?fdc: unidentified, enhanced, SMC cntlr revision %x\n", result);
                                /* break;       */
                        }
                        fcp->c_flags |= FCFLG_3DMODE;
                        retcode = FDC37C666;
                }
                outb(fcp->c_regbase + FCR_SRA, FSA_DISB);

                drv_usecwait(10);
                outb(fcp->c_regbase + FCR_SRA, save);
        }
        return (retcode);
}

static int
fdc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        struct fdcntlr *fcp;
        int unit;
        int rval = 0;

        FCERRPRINT(FDEP_L3, FDEM_ATTA, (CE_WARN, "fdc_detach: dip %p",
            (void*)dip));

        fcp = ddi_get_driver_private(dip);

        switch (cmd) {
        case DDI_DETACH:
                for (unit = 0; unit < NFDUN; unit++)
                        if ((fcp->c_unit[unit])->fj_dip) {
                                rval = EBUSY;
                                break;
                        }
                kstat_delete(fcp->c_intrstat);
                fcp->c_intrstat = NULL;
                ddi_remove_intr(fcp->c_dip, 0, fcp->c_iblock);
                if (ddi_dmae_release(fcp->c_dip, fcp->c_dmachan) !=
                    DDI_SUCCESS)
                        cmn_err(CE_WARN, "fdc_detach: dma release failed, "
                            "dip %p, dmachan %x",
                            (void*)fcp->c_dip, fcp->c_dmachan);
                ddi_prop_remove_all(fcp->c_dip);
                ddi_set_driver_private(fcp->c_dip, NULL);

                mutex_destroy(&fcp->c_lock);
                mutex_destroy(&fcp->c_dorlock);
                cv_destroy(&fcp->c_iocv);
                sema_destroy(&fcp->c_selsem);
                ddi_soft_state_free(fdc_state_head, ddi_get_instance(dip));
                break;

        case DDI_SUSPEND:
                /*
                 * For suspend, we just use the semaphore to
                 * keep any child devices from accessing any of our
                 * hardware routines, and then shutdown the hardware.
                 *
                 * On resume, we'll reinit the hardware and release the
                 * semaphore.
                 */
                sema_p(&fcp->c_selsem);

                if (ddi_dmae_disable(fcp->c_dip, fcp->c_dmachan) !=
                    DDI_SUCCESS) {
                        cmn_err(CE_WARN, "fdc_suspend: dma disable failed, "
                            "dip %p, dmachan %x", (void *)fcp->c_dip,
                            fcp->c_dmachan);
                        /* give it back on failure */
                        sema_v(&fcp->c_selsem);
                        return (DDI_FAILURE);
                }

                mutex_enter(&fcp->c_lock);
                fcp->c_suspended = B_TRUE;
                mutex_exit(&fcp->c_lock);

                rval = DDI_SUCCESS;
                break;

        default:
                rval = EINVAL;
                break;
        }
        return (rval);
}


int
fdc_abort(struct fcu_obj *fjp)
{
        struct fdcntlr *fcp = fjp->fj_fdc;
        int unit = fjp->fj_unit & 3;

        FCERRPRINT(FDEP_L3, FDEM_RESE, (CE_WARN, "fdc_abort"));
        if (fcp->c_curunit == unit) {
                mutex_enter(&fcp->c_lock);
                if (fcp->c_flags & FCFLG_WAITING) {
                        /*
                         * this can cause data corruption !
                         */
                        fdcquiesce(fcp);
                        fcp->c_csb.csb_xstate = FXS_RESET;
                        fcp->c_flags |= FCFLG_TIMEOUT;
                        if (ddi_dmae_stop(fcp->c_dip, fcp->c_dmachan) !=
                            DDI_SUCCESS)
                                cmn_err(CE_WARN,
                                    "fdc_detach: dma release failed, "
                                    "dip %p, dmachan %x",
                                    (void*)fcp->c_dip, fcp->c_dmachan);
                }
                mutex_exit(&fcp->c_lock);
                drv_usecwait(500);
                return (DDI_SUCCESS);
        }
        return (DDI_FAILURE);
}

int
fdc_dkinfo(struct fcu_obj *fjp, struct dk_cinfo *dcp)
{
        struct fdcntlr *fcp = fjp->fj_fdc;

        (void) strncpy((char *)&dcp->dki_cname, ddi_get_name(fcp->c_dip),
            DK_DEVLEN);
        dcp->dki_ctype = DKC_UNKNOWN; /* no code for generic PC/AT fdc */
        dcp->dki_flags = DKI_FMTTRK;
        dcp->dki_addr = fcp->c_regbase;
        dcp->dki_space = 0;
        dcp->dki_prio = fcp->c_intprio;
        dcp->dki_vec = fcp->c_intvec;
        (void) strncpy((char *)&dcp->dki_dname, ddi_driver_name(fjp->fj_dip),
            DK_DEVLEN);
        dcp->dki_slave = fjp->fj_unit & 3;
        dcp->dki_maxtransfer = maxphys / DEV_BSIZE;
        return (DDI_SUCCESS);
}

/*
 * on=> non-zero = select, 0 = de-select
 */
int
fdc_select(struct fcu_obj *fjp, int funit, int on)
{
        struct fdcntlr *fcp = fjp->fj_fdc;
        int unit = funit & 3;

        if (on) {
                /* possess controller */
                sema_p(&fcp->c_selsem);
                FCERRPRINT(FDEP_L2, FDEM_DSEL,
                    (CE_NOTE, "fdc_select unit %d: on", funit));

                if (fcp->c_curunit != unit || !(fjp->fj_flags & FUNIT_CHAROK)) {
                        fcp->c_curunit = unit;
                        fjp->fj_flags |= FUNIT_CHAROK;
                        if (fdcspecify(fcp,
                            fjp->fj_chars->fdc_transfer_rate,
                            fjp->fj_drive->fdd_steprate, 40))
                                cmn_err(CE_WARN,
                                    "fdc_select: controller setup rejected "
                                    "fdcntrl %p transfer rate %x step rate %x"
                                    " head load time 40", (void*)fcp,
                                    fjp->fj_chars->fdc_transfer_rate,
                                    fjp->fj_drive->fdd_steprate);
                }

                mutex_enter(&fcp->c_dorlock);

                /* make sure drive is not selected in case we change speed */
                fcp->c_digout = (fcp->c_digout & ~FD_DRSEL) |
                    (~unit & FD_DRSEL);
                outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);

                (void) fdc_motorsm(fjp, FMI_STARTCMD,
                    fjp->fj_drive->fdd_motoron);
                /*
                 * Return value ignored - fdcmotort deals with failure.
                 */
                if (fdcspdchange(fcp, fjp, fjp->fj_attr->fda_rotatespd)) {
                        /* 3D drive requires 500 ms for speed change */
                        (void) fdc_motorsm(fjp, FMI_RSTARTCMD, 5);
                        /*
                         * Return value ignored - fdcmotort deals with failure.
                         */
                }

                fcp->c_digout = (fcp->c_digout & ~FD_DRSEL) | (unit & FD_DRSEL);
                outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);

                mutex_exit(&fcp->c_dorlock);
                fcp->c_csb.csb_drive = (uchar_t)unit;
        } else {
                FCERRPRINT(FDEP_L2, FDEM_DSEL,
                    (CE_NOTE, "fdc_select unit %d: off", funit));

                mutex_enter(&fcp->c_dorlock);

                fcp->c_digout |= FD_DRSEL;
                outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);
                (void) fdc_motorsm(fjp, FMI_IDLECMD,
                    fjp->fj_drive->fdd_motoroff);
                /*
                 * Return value ignored - fdcmotort deals with failure.
                 */

                mutex_exit(&fcp->c_dorlock);

                /* give up controller */
                sema_v(&fcp->c_selsem);
        }
        return (0);
}


int
fdgetchng(struct fcu_obj *fjp, int funit)
{
        if (fdcsense_drv(fjp->fj_fdc, funit & 3))
                cmn_err(CE_WARN, "fdgetchng: write protect check failed");
        return (fdcsense_chng(fjp->fj_fdc, funit & 3));
}


int
fdresetchng(struct fcu_obj *fjp, int funit)
{
        struct fdcntlr *fcp = fjp->fj_fdc;
        int unit = funit & 3;
        int newcyl;                     /* where to seek for reset of DSKCHG */

        FCERRPRINT(FDEP_L2, FDEM_CHEK, (CE_NOTE, "fdmediachng unit %d", funit));

        if (fcp->c_curpcyl[unit])
                newcyl = fcp->c_curpcyl[unit] - 1;
        else
                newcyl = 1;
        return (fdrecalseek(fjp, funit, newcyl, 0));
}


/*
 * fdrecalseek
 */
int
fdrecalseek(struct fcu_obj *fjp, int funit, int arg, int execflg)
{
        struct fdcntlr *fcp = fjp->fj_fdc;
        struct fdcsb *csb;
        int unit = funit & 3;
        int rval;

        FCERRPRINT(FDEP_L2, FDEM_RECA, (CE_NOTE, "fdrecalseek unit %d to %d",
            funit, arg));

        csb = &fcp->c_csb;
        csb->csb_cmd[1] = (uchar_t)unit;
        if (arg < 0) {                  /* is recal... */
                *csb->csb_cmd = FO_RECAL;
                csb->csb_ncmds = 2;
                csb->csb_timer = 28;
        } else {
                *csb->csb_cmd = FO_SEEK;
                csb->csb_cmd[2] = (uchar_t)arg;
                csb->csb_ncmds = 3;
                csb->csb_timer = 10;
        }
        csb->csb_nrslts = 2;    /* 2 for SENSE INTERRUPTS */
        csb->csb_opflags = CSB_OFINRPT;
        csb->csb_maxretry = skretry;
        csb->csb_dmahandle = NULL;
        csb->csb_handle_bound = 0;
        csb->csb_dmacookiecnt = 0;
        csb->csb_dmacurrcookie = 0;
        csb->csb_dmawincnt = 0;
        csb->csb_dmacurrwin = 0;

        /* send cmd off to fdc_exec */
        if (rval = fdc_exec(fcp, 1, execflg))
                goto out;

        if (!(*csb->csb_rslt & S0_SEKEND) ||
            (*csb->csb_rslt & S0_ICMASK) ||
            ((*csb->csb_rslt & S0_ECHK) && arg < 0) ||
            csb->csb_cmdstat)
                rval = ENODEV;

        if (fdcsense_drv(fcp, unit))
                cmn_err(CE_WARN, "fdgetchng: write protect check failed");
out:
        return (rval);
}


/*
 * fdrw- used only for read/writing sectors into/from kernel buffers.
 */
int
fdrw(struct fcu_obj *fjp, int funit, int rw, int cyl, int head,
    int sector, caddr_t bufp, uint_t len)
{
        struct fdcntlr *fcp = fjp->fj_fdc;
        struct fdcsb *csb;
        uint_t dmar_flags = 0;
        int unit = funit & 3;
        int rval;
        ddi_acc_handle_t mem_handle = NULL;
        caddr_t aligned_buf;
        size_t real_size;

        FCERRPRINT(FDEP_L1, FDEM_RW, (CE_CONT, "fdrw unit %d\n", funit));

        csb = &fcp->c_csb;
        if (rw) {
                dmar_flags = DDI_DMA_READ;
                csb->csb_opflags = CSB_OFDMARD | CSB_OFINRPT;
                *csb->csb_cmd = FO_MT | FO_MFM | FO_SK | FO_RDDAT;
        } else { /* write */
                dmar_flags = DDI_DMA_WRITE;
                csb->csb_opflags = CSB_OFDMAWT | CSB_OFINRPT;
                *csb->csb_cmd = FO_MT | FO_MFM | FO_WRDAT;
        }
        csb->csb_cmd[1] = (uchar_t)(unit | ((head & 0x1) << 2));
        csb->csb_cmd[2] = (uchar_t)cyl;
        csb->csb_cmd[3] = (uchar_t)head;
        csb->csb_cmd[4] = (uchar_t)sector;
        encode(sector_size, fjp->fj_chars->fdc_sec_size,
            &csb->csb_cmd[5]);
        csb->csb_cmd[6] = (uchar_t)max(fjp->fj_chars->fdc_secptrack, sector);
        csb->csb_cmd[7] = fjp->fj_attr->fda_gapl;
        csb->csb_cmd[8] = 0xFF;

        csb->csb_ncmds = 9;
        csb->csb_nrslts = 7;
        csb->csb_timer = 36;
        if (rw == FDRDONE)
                csb->csb_maxretry = 1;
        else
                csb->csb_maxretry = rwretry;

        csb->csb_dmahandle = NULL;
        csb->csb_handle_bound = 0;
        csb->csb_dmacookiecnt = 0;
        csb->csb_dmacurrcookie = 0;
        csb->csb_dmawincnt = 0;
        csb->csb_dmacurrwin = 0;
        dmar_flags |= (DDI_DMA_STREAMING | DDI_DMA_PARTIAL);

        if (ddi_dma_alloc_handle(fcp->c_dip, &fdc_dma_attr, DDI_DMA_SLEEP,
            0, &csb->csb_dmahandle) != DDI_SUCCESS) {
                rval = EINVAL;
                goto out;
        }

        /*
         * allocate a page aligned buffer to dma to/from. This way we can
         * ensure the cookie is a whole multiple of granularity and avoids
         * any alignment issues.
         */
        rval = ddi_dma_mem_alloc(csb->csb_dmahandle, len, &fdc_accattr,
            DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &aligned_buf,
            &real_size, &mem_handle);
        if (rval != DDI_SUCCESS) {
                rval = EINVAL;
                goto out;
        }

        if (dmar_flags & DDI_DMA_WRITE) {
                bcopy(bufp, aligned_buf, len);
        }

        rval = ddi_dma_addr_bind_handle(csb->csb_dmahandle, NULL, aligned_buf,
            len, dmar_flags, DDI_DMA_SLEEP, 0, &csb->csb_dmacookie,
            &csb->csb_dmacookiecnt);

        if (rval == DDI_DMA_MAPPED) {
                csb->csb_dmawincnt = 1;
                csb->csb_handle_bound = 1;
        } else if (rval == DDI_DMA_PARTIAL_MAP) {
                csb->csb_handle_bound = 1;
                if (ddi_dma_numwin(csb->csb_dmahandle, &csb->csb_dmawincnt) !=
                    DDI_SUCCESS) {
                        cmn_err(CE_WARN, "fdrw: dma numwin failed");
                        rval = EINVAL;
                        goto out;
                }
        } else {
                cmn_err(CE_WARN,
                    "fdrw: dma addr bind handle failed, rval = %d", rval);
                rval = EINVAL;
                goto out;
        }
        rval = fdc_exec(fcp, 1, 1);

        if (dmar_flags & DDI_DMA_READ) {
                bcopy(aligned_buf, bufp, len);
        }

out:
        if (csb->csb_dmahandle) {
                if (csb->csb_handle_bound) {
                        if (ddi_dma_unbind_handle(csb->csb_dmahandle) !=
                            DDI_SUCCESS)
                                cmn_err(CE_WARN, "fdrw: "
                                    "dma unbind handle failed");
                        csb->csb_handle_bound = 0;
                }
                if (mem_handle != NULL) {
                        ddi_dma_mem_free(&mem_handle);
                }
                ddi_dma_free_handle(&csb->csb_dmahandle);
                csb->csb_dmahandle = NULL;
        }
        return (rval);
}


int
fdtrkformat(struct fcu_obj *fjp, int funit, int cyl, int head, int filldata)
{
        struct fdcntlr *fcp = fjp->fj_fdc;
        struct fdcsb *csb;
        int unit = funit & 3;
        int fmdatlen, lsector, lstart;
        int interleave, numsctr, offset, psector;
        uchar_t *dp;
        int rval;
        ddi_acc_handle_t mem_handle = NULL;
        caddr_t aligned_buf;
        size_t real_size;

        FCERRPRINT(FDEP_L2, FDEM_FORM,
            (CE_NOTE, "fdformattrk unit %d cyl=%d, hd=%d", funit, cyl, head));

        csb = &fcp->c_csb;

        csb->csb_opflags = CSB_OFDMAWT | CSB_OFINRPT;

        *csb->csb_cmd = FO_FRMT | FO_MFM;
        csb->csb_cmd[1] = (head << 2) | unit;
        encode(sector_size, fjp->fj_chars->fdc_sec_size,
            &csb->csb_cmd[2]);
        csb->csb_cmd[3] = numsctr = fjp->fj_chars->fdc_secptrack;
        csb->csb_cmd[4] = fjp->fj_attr->fda_gapf;
        csb->csb_cmd[5] = (uchar_t)filldata;

        csb->csb_npcyl = (uchar_t)(cyl * fjp->fj_chars->fdc_steps);

        csb->csb_dmahandle = NULL;
        csb->csb_handle_bound = 0;
        csb->csb_dmacookiecnt = 0;
        csb->csb_dmacurrcookie = 0;
        csb->csb_dmawincnt = 0;
        csb->csb_dmacurrwin = 0;
        csb->csb_ncmds = 6;
        csb->csb_nrslts = 7;
        csb->csb_timer = 32;
        csb->csb_maxretry = rwretry;

        /*
         * alloc space for format track cmd
         */
        /*
         * NOTE: have to add size of fifo also - for dummy format action
         */
        fmdatlen = 4 * numsctr;

        if (ddi_dma_alloc_handle(fcp->c_dip, &fdc_dma_attr, DDI_DMA_SLEEP,
            0, &csb->csb_dmahandle) != DDI_SUCCESS) {
                rval = EINVAL;
                goto out;
        }

        /*
         * allocate a page aligned buffer to dma to/from. This way we can
         * ensure the cookie is a whole multiple of granularity and avoids
         * any alignment issues.
         */
        rval = ddi_dma_mem_alloc(csb->csb_dmahandle, fmdatlen, &fdc_accattr,
            DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &aligned_buf,
            &real_size, &mem_handle);
        if (rval != DDI_SUCCESS) {
                rval = EINVAL;
                goto out;
        }
        dp = (uchar_t *)aligned_buf;

        interleave = fjp->fj_attr->fda_intrlv;
        offset = (numsctr + interleave - 1) / interleave;
        for (psector = lstart = 1;
            psector <= numsctr; psector += interleave, lstart++) {
                for (lsector = lstart; lsector <= numsctr; lsector += offset) {
                        *dp++ = (uchar_t)cyl;
                        *dp++ = (uchar_t)head;
                        *dp++ = (uchar_t)lsector;
                        *dp++ = csb->csb_cmd[2];
                }
        }

        rval = ddi_dma_addr_bind_handle(csb->csb_dmahandle, NULL, aligned_buf,
            fmdatlen, DDI_DMA_WRITE | DDI_DMA_STREAMING | DDI_DMA_PARTIAL,
            DDI_DMA_SLEEP, 0, &csb->csb_dmacookie, &csb->csb_dmacookiecnt);

        if (rval == DDI_DMA_MAPPED) {
                csb->csb_dmawincnt = 1;
                csb->csb_handle_bound = 1;
        } else if (rval == DDI_DMA_PARTIAL_MAP) {
                csb->csb_handle_bound = 1;
                if (ddi_dma_numwin(csb->csb_dmahandle, &csb->csb_dmawincnt) !=
                    DDI_SUCCESS) {
                        cmn_err(CE_WARN, "fdtrkformat: dma numwin failed");
                        rval = EINVAL;
                        goto out;
                }
        } else {
                cmn_err(CE_WARN,
                    "fdtrkformat: dma buf bind handle failed, rval = %d",
                    rval);
                rval = EINVAL;
                goto out;
        }

        rval = fdc_exec(fcp, 1, 1);
out:
        if (csb->csb_dmahandle) {
                if (csb->csb_handle_bound) {
                        if (ddi_dma_unbind_handle(csb->csb_dmahandle) !=
                            DDI_SUCCESS)
                                cmn_err(CE_WARN, "fdtrkformat: "
                                    "dma unbind handle failed");
                        csb->csb_handle_bound = 0;
                }
                if (mem_handle != NULL) {
                        ddi_dma_mem_free(&mem_handle);
                }
                ddi_dma_free_handle(&csb->csb_dmahandle);
                csb->csb_dmahandle = NULL;
        }
        return (rval);
}

int
fdrawioctl(struct fcu_obj *fjp, int funit, caddr_t arg)
{
        struct fdcntlr *fcp = fjp->fj_fdc;
        struct fd_raw *fdrp = (struct fd_raw *)arg;
        struct fdcsb *csb;
        uint_t dmar_flags = 0;
        int i;
        int change = 1;
        int sleep = 1;
        int rval = 0;
        int rval_exec = 0;
        ddi_acc_handle_t mem_handle = NULL;
        caddr_t aligned_buf;
        size_t real_size;

        _NOTE(ARGUNUSED(funit));

        FCERRPRINT(FDEP_L2, FDEM_RAWI,
            (CE_NOTE, "fdrawioctl: cmd[0]=0x%x", fdrp->fdr_cmd[0]));

        csb = &fcp->c_csb;

        /* copy cmd bytes into csb */
        for (i = 0; i <= fdrp->fdr_cnum; i++)
                csb->csb_cmd[i] = fdrp->fdr_cmd[i];
        csb->csb_ncmds = (uchar_t)fdrp->fdr_cnum;

        csb->csb_maxretry = 0;  /* let the application deal with errors */
        csb->csb_opflags = CSB_OFRAWIOCTL;
        csb->csb_nrslts = 0;
        csb->csb_timer = 50;

        switch (fdrp->fdr_cmd[0] & 0x0f) {

        case FO_SEEK:
                change = 0;
                /* FALLTHROUGH */
        case FO_RECAL:
                csb->csb_opflags |= CSB_OFINRPT;
                break;

        case FO_FRMT:
                csb->csb_npcyl = *(uchar_t *)(fdrp->fdr_addr) *
                    fjp->fj_chars->fdc_steps;
                /* FALLTHROUGH */
        case FO_WRDAT:
        case FO_WRDEL:
                csb->csb_opflags |= CSB_OFDMAWT | CSB_OFRESLT | CSB_OFINRPT;
                csb->csb_nrslts = 7;
                if (fdrp->fdr_nbytes == 0)
                        return (EINVAL);
                dmar_flags = DDI_DMA_WRITE;
                break;

        case FO_RDDAT:
        case FO_RDDEL:
        case FO_RDTRK:
                csb->csb_opflags |= CSB_OFDMARD | CSB_OFRESLT | CSB_OFINRPT;
                csb->csb_nrslts = 7;
                dmar_flags = DDI_DMA_READ;
                break;

        case FO_RDID:
                csb->csb_opflags |= CSB_OFRESLT | CSB_OFINRPT;
                csb->csb_nrslts = 7;
                break;

        case FO_SDRV:
                sleep = 0;
                csb->csb_nrslts = 1;
                break;

        case FO_SINT:
                sleep = 0;
                change = 0;
                csb->csb_nrslts = 2;
                break;

        case FO_SPEC:
                sleep = 0;
                change = 0;
                break;

        default:
                return (EINVAL);
        }

        csb->csb_dmahandle = NULL;
        csb->csb_handle_bound = 0;
        csb->csb_dmacookiecnt = 0;
        csb->csb_dmacurrcookie = 0;
        csb->csb_dmawincnt = 0;
        csb->csb_dmacurrwin = 0;

        if (csb->csb_opflags & (CSB_OFDMARD | CSB_OFDMAWT)) {
                if (ddi_dma_alloc_handle(fcp->c_dip, &fdc_dma_attr,
                    DDI_DMA_SLEEP, 0, &csb->csb_dmahandle) != DDI_SUCCESS) {
                        rval = EINVAL;
                        goto out;
                }

                /*
                 * allocate a page aligned buffer to dma to/from. This way we
                 * can ensure the cookie is a whole multiple of granularity and
                 * avoids any alignment issues.
                 */
                rval = ddi_dma_mem_alloc(csb->csb_dmahandle,
                    (uint_t)fdrp->fdr_nbytes, &fdc_accattr, DDI_DMA_CONSISTENT,
                    DDI_DMA_SLEEP, NULL, &aligned_buf, &real_size, &mem_handle);
                if (rval != DDI_SUCCESS) {
                        rval = EINVAL;
                        goto out;
                }

                if (dmar_flags & DDI_DMA_WRITE) {
                        bcopy(fdrp->fdr_addr, aligned_buf,
                            (uint_t)fdrp->fdr_nbytes);
                }

                dmar_flags |= (DDI_DMA_STREAMING | DDI_DMA_PARTIAL);
                rval = ddi_dma_addr_bind_handle(csb->csb_dmahandle, NULL,
                    aligned_buf, (uint_t)fdrp->fdr_nbytes, dmar_flags,
                    DDI_DMA_SLEEP, 0, &csb->csb_dmacookie,
                    &csb->csb_dmacookiecnt);

                if (rval == DDI_DMA_MAPPED) {
                        csb->csb_dmawincnt = 1;
                        csb->csb_handle_bound = 1;
                } else if (rval == DDI_DMA_PARTIAL_MAP) {
                        csb->csb_handle_bound = 1;
                        if (ddi_dma_numwin(csb->csb_dmahandle,
                            &csb->csb_dmawincnt) != DDI_SUCCESS) {
                                cmn_err(CE_WARN,
                                    "fdrawioctl: dma numwin failed");
                                rval = EINVAL;
                                goto out;
                        }
                } else {
                        cmn_err(CE_WARN, "fdrawioctl: "
                            "dma buf bind handle failed, rval = %d", rval);
                        rval = EINVAL;
                        goto out;
                }
        }

        FCERRPRINT(FDEP_L1, FDEM_RAWI,
            (CE_CONT, "cmd: %x %x %x %x %x %x %x %x %x %x\n", csb->csb_cmd[0],
            csb->csb_cmd[1], csb->csb_cmd[2], csb->csb_cmd[3],
            csb->csb_cmd[4], csb->csb_cmd[5], csb->csb_cmd[6],
            csb->csb_cmd[7], csb->csb_cmd[8], csb->csb_cmd[9]));
        FCERRPRINT(FDEP_L1, FDEM_RAWI,
            (CE_CONT, "nbytes: %x, opflags: %x, addr: %p, len: %x\n",
            csb->csb_ncmds, csb->csb_opflags, (void *)fdrp->fdr_addr,
            fdrp->fdr_nbytes));

        /*
         * Note that we ignore any error returns from fdexec.
         * This is the way the driver has been, and it may be
         * that the raw ioctl senders simply don't want to
         * see any errors returned in this fashion.
         */

        /*
         * VP/ix sense drive ioctl call checks for the error return.
         */

        rval_exec = fdc_exec(fcp, sleep, change);

        if (dmar_flags & DDI_DMA_READ) {
                bcopy(aligned_buf, fdrp->fdr_addr, (uint_t)fdrp->fdr_nbytes);
        }

        FCERRPRINT(FDEP_L1, FDEM_RAWI,
            (CE_CONT, "rslt: %x %x %x %x %x %x %x %x %x %x\n", csb->csb_rslt[0],
            csb->csb_rslt[1], csb->csb_rslt[2], csb->csb_rslt[3],
            csb->csb_rslt[4], csb->csb_rslt[5], csb->csb_rslt[6],
            csb->csb_rslt[7], csb->csb_rslt[8], csb->csb_rslt[9]));

        /* copy results into fdr */
        for (i = 0; i <= (int)csb->csb_nrslts; i++)
                fdrp->fdr_result[i] = csb->csb_rslt[i];
/*      fdrp->fdr_nbytes = fdc->c_csb.csb_rlen;  return resid */

out:
        if (csb->csb_dmahandle) {
                if (csb->csb_handle_bound) {
                        if (ddi_dma_unbind_handle(csb->csb_dmahandle) !=
                            DDI_SUCCESS)
                                cmn_err(CE_WARN, "fdrawioctl: "
                                    "dma unbind handle failed");
                        csb->csb_handle_bound = 0;
                }
                if (mem_handle != NULL) {
                        ddi_dma_mem_free(&mem_handle);
                }
                ddi_dma_free_handle(&csb->csb_dmahandle);
                csb->csb_dmahandle = NULL;
        }
        if ((fdrp->fdr_cmd[0] & 0x0f) == FO_SDRV) {
                return (rval_exec);
        }
        return (rval);
}

void
encode(xlate_tbl_t *tablep, int val, uchar_t *rcode)
{
        do {
                if (tablep->value >= val) {
                        *rcode = tablep->code;
                        return;
                }
        } while ((++tablep)->value);
        *rcode = tablep->code;
        cmn_err(CE_WARN, "fdc encode failed, table %p val %x code %x",
            (void *)tablep, val, (uint_t)*rcode);
}

int
decode(xlate_tbl_t *tablep, int kode, int *rvalue)
{
        do  {
                if (tablep->code == kode) {
                        *rvalue = tablep->value;
                        return (0);
                }
        } while ((++tablep)->value);
        return (-1);
}

/*
 * quiesce(9E) entry point.
 *
 * This function is called when the system is single-threaded at high
 * PIL with preemption disabled. Therefore, this function must not be
 * blocked.
 *
 * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
 * DDI_FAILURE indicates an error condition and should almost never happen.
 */
int
fdc_quiesce(dev_info_t *dip)
{
        struct fdcntlr *fcp;
        int ctlr = ddi_get_instance(dip);
        int unit;

        fcp = ddi_get_soft_state(fdc_state_head, ctlr);

        if (fcp == NULL)
                return (DDI_FAILURE);

        /*
         * If no FD units are attached, there is no need to quiesce.
         */
        for (unit = 0; unit < NFDUN; unit++) {
                struct fcu_obj *fjp = fcp->c_unit[unit];
                if (fjp->fj_flags & FUNIT_DRVATCH) {
                        break;
                }
        }

        if (unit == NFDUN)
                return (DDI_SUCCESS);

        (void) ddi_dmae_disable(fcp->c_dip, fcp->c_dmachan);

        fcp->c_digout = (fcp->c_digout & (FD_DMTREN | FD_DRSEL)) | FD_ENABLE;
        outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);
        drv_usecwait(20);
        fcp->c_digout |= FD_RSETZ;
        outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);

        if (fcp->c_chip >= i82077) {
                int count = 4;
                uchar_t *oplistp = configurecmd;
                do {
                        int ntries = FDC_RQM_RETRY;
                        do {
                                if ((inb(fcp->c_regbase + FCR_MSR) &
                                    (MS_RQM|MS_DIO)) == MS_RQM)
                                        break;
                                else
                                        drv_usecwait(1);
                        } while (--ntries);
                        if (ntries == 0) {
                                break;
                        }
                        outb(fcp->c_regbase + FCR_DATA, *oplistp++);
                        drv_usecwait(16); /* See comment in fdc_result() */
                } while (--count);
        }

        return (DDI_SUCCESS);
}

void
fdcquiesce(struct fdcntlr *fcp)
{
        int unit;

        FCERRPRINT(FDEP_L2, FDEM_RESE, (CE_NOTE, "fdcquiesce fcp %p",
            (void*)fcp));

        ASSERT(MUTEX_HELD(&fcp->c_lock));
        mutex_enter(&fcp->c_dorlock);

        if (ddi_dmae_stop(fcp->c_dip, fcp->c_dmachan) != DDI_SUCCESS)
                cmn_err(CE_WARN, "fdcquiesce: dmae stop failed, "
                    "dip %p, dmachan %x",
                    (void*)fcp->c_dip, fcp->c_dmachan);

        fcp->c_digout = (fcp->c_digout & (FD_DMTREN | FD_DRSEL)) | FD_ENABLE;
        outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);
        drv_usecwait(20);
        fcp->c_digout |= FD_RSETZ;
        outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);

        mutex_exit(&fcp->c_dorlock);

        /* count resets */
        fcp->fdstats.reset++;
        fcp->c_curunit = -1;
        for (unit = 0; unit < NFDUN; unit++)
                fcp->c_curpcyl[unit] = -1;

        if (fcp->c_chip >= i82077) {
                (void) fdc_docmd(fcp, configurecmd, 4);
                /*
                 * Ignored return. If failed, warning was issued by fdc_docmd.
                 */
        }
}

void
fdcreadid(struct fdcntlr *fcp, struct fdcsb *csb)
{
        static uchar_t readidcmd[2] = {FO_RDID | FO_MFM, 0};

        readidcmd[1] = csb->csb_cmd[1];
        (void) fdc_docmd(fcp, readidcmd, 2);
}

int
fdcseek(struct fdcntlr *fcp, int unit, int cyl)
{
        static uchar_t seekabscmd[3] = {FO_SEEK, 0, 0};

        FCERRPRINT(FDEP_L0, FDEM_RECA, (CE_CONT, "fdcseek unit %d to cyl %d\n",
            unit, cyl));
        seekabscmd[1] = (uchar_t)unit;
        seekabscmd[2] = (uchar_t)cyl;
        return (fdc_docmd(fcp, seekabscmd, 3));
}

/*
 * Returns status of disk change line of selected drive.
 *      = 0 means diskette is present
 *      != 0 means diskette was removed and current state is unknown
 */
int
fdcsense_chng(struct fdcntlr *fcp, int unit)
{
        int digital_input;

        FCERRPRINT(FDEP_L0, FDEM_SCHG,
            (CE_CONT, "fdcsense_chng unit %d\n", unit));
        digital_input = inb(fcp->c_regbase + FCR_DIR);
        if (fcp->c_mode == FDCMODE_30)
                digital_input ^= FDI_DKCHG;
        return (digital_input & FDI_DKCHG);
}

int
fdcsense_drv(struct fdcntlr *fcp, int unit)
{
        static uchar_t sensedrvcmd[2] = {FO_SDRV, 0};
        uchar_t senser;
        int rval;

        sensedrvcmd[1] = (uchar_t)unit;
        (void) fdc_docmd(fcp, sensedrvcmd, 2);
        /*
         * Ignored return. If failed, warning was issued by fdc_docmd.
         * fdc_results retrieves the controller/drive status
         */
        if (rval = fdc_result(fcp, &senser, 1))
                goto done;
        if (senser & S3_WPROT)
                fcp->c_unit[unit]->fj_flags |= FUNIT_WPROT;
        else
                fcp->c_unit[unit]->fj_flags &= ~FUNIT_WPROT;
done:
        return (rval);
}

int
fdcsense_int(struct fdcntlr *fcp, int *unitp, int *cylp)
{
        uchar_t senser[2];
        int rval;

        (void) fdc_docmd(fcp, &senseintcmd, 1);
        /*
         * Ignored return. If failed, warning was issued by fdc_docmd.
         * fdc_results retrieves the controller/drive status
         */

        if (!(rval = fdc_result(fcp, senser, 2))) {
                if ((*senser & (S0_IVCMD | S0_SEKEND | S0_ECHK)) != S0_SEKEND)
                        rval = 1;
                if (unitp)
                        *unitp = *senser & 3;
                if (cylp)
                        *cylp = senser[1];
        }
        return (rval);
}

int
fdcspecify(struct fdcntlr *fcp, int xferrate, int steprate, int hlt)
{
        static uchar_t perpindcmd[2] = {FO_PERP, 0};
        static uchar_t specifycmd[3] = {FO_SPEC, 0, 0};

        encode(drate_mfm, xferrate, &fcp->c_config);
        outb(fcp->c_regbase + FCR_CCR, fcp->c_config);

        if (fcp->c_chip >= i82077) {
                /*
                 * Use old style perpendicular mode command of 82077.
                 */
                if (xferrate == 1000) {
                        /* Set GAP and WGATE */
                        perpindcmd[1] = 3;
                        /* double step rate because xlate table is for 500Kb */
                        steprate <<= 1;
                        hlt <<= 1;
                } else
                        perpindcmd[1] = 0;
                (void) fdc_docmd(fcp, perpindcmd, 2);
                /*
                 * Ignored return. If failed, warning was issued by fdc_docmd.
                 */
        }
        encode(step_rate, steprate, &fcp->c_hutsrt);
        specifycmd[1] = fcp->c_hutsrt |= 0x0F;  /* use max head unload time */
        hlt = (hlt >= 256) ? 0 : (hlt >> 1);    /* encode head load time */
        specifycmd[2] = fcp->c_hlt = hlt << 1;  /* make room for DMA bit */
        return (fdc_docmd(fcp, specifycmd, 3));
}

int
fdcspdchange(struct fdcntlr *fcp, struct fcu_obj *fjp, int rpm)
{
        int     retcode = 0;
        uint_t  ddic;
        uchar_t deselect = 0;
        uchar_t ds_code;
        uchar_t enable_code;
        uchar_t save;

        if (((fcp->c_flags & FCFLG_DSOUT) == 0 && rpm <= fjp->fj_rotspd) ||
            ((fcp->c_flags & FCFLG_DSOUT) && (fjp->fj_flags & FUNIT_3DMODE) &&
            rpm > fjp->fj_rotspd)) {
                return (0);
        }

        FCERRPRINT(FDEP_L1, FDEM_SCHG,
            (CE_CONT, "fdcspdchange: %d rpm\n", rpm));
        ASSERT(MUTEX_HELD(&fcp->c_dorlock));

        switch (fcp->c_chip) {
        default:
                break;
        case i82077:
                break;

        case PC87322:
                {
                uchar_t nscmodecmd[5] = {FO_MODE, 0x02, 0x00, 0xC8, 0x00};

                if (rpm > fjp->fj_rotspd) {
                        nscmodecmd[3] ^= 0xC0;
                        retcode = (fcp->c_flags ^ FCFLG_DSOUT) ||
                            (fjp->fj_flags ^ FUNIT_3DMODE);
                        fcp->c_flags |= FCFLG_DSOUT;
                        fjp->fj_flags |= FUNIT_3DMODE;
                } else {
                        /* program DENSEL to default output */
                        fcp->c_flags &= ~FCFLG_DSOUT;
                        retcode = fjp->fj_flags & FUNIT_3DMODE;
                        fjp->fj_flags &= ~FUNIT_3DMODE;
                }
                if (retcode && (fcp->c_digout & FD_DRSEL) == fcp->c_curunit) {
                        /* de-select drive while changing speed */
                        deselect = fcp->c_digout ^ FD_DRSEL;
                        outb(fcp->c_regbase + FCR_DOR, deselect);
                }

                (void) fdc_docmd(fcp, nscmodecmd, 5);
                /*
                 * Ignored return. If failed, warning was issued by fdc_docmd.
                 */
                break;
                }

        case FDC37C665:
                enable_code = FSA_ENA5;
                goto SMC_config;

        case FDC37C666:
                enable_code = FSA_ENA6;
SMC_config:
                if (rpm > fjp->fj_rotspd) {
                        /* force DENSEL output to active LOW */
                        ds_code = FSB_DSHI;
                        retcode = (fcp->c_flags ^ FCFLG_DSOUT) ||
                            (fjp->fj_flags ^ FUNIT_3DMODE);
                        fcp->c_flags |= FCFLG_DSOUT;
                        fjp->fj_flags |= FUNIT_3DMODE;
                } else {
                        /* program DENSEL to default output */
                        ds_code = 0;
                        fcp->c_flags &= ~FCFLG_DSOUT;
                        retcode = fjp->fj_flags & FUNIT_3DMODE;
                        fjp->fj_flags &= ~FUNIT_3DMODE;
                }
                if (retcode && (fcp->c_digout & FD_DRSEL) == fcp->c_curunit) {
                        /* de-select drive while changing speed */
                        deselect = fcp->c_digout ^ FD_DRSEL;
                        outb(fcp->c_regbase + FCR_DOR, deselect);
                }
                save = inb(fcp->c_regbase + FCR_SRA);

                /* enter configuration mode */
                ddic = ddi_enter_critical();
                outb(fcp->c_regbase + FCR_SRA, enable_code);
                outb(fcp->c_regbase + FCR_SRA, enable_code);
                ddi_exit_critical(ddic);

                outb(fcp->c_regbase + FCR_SRA, FSA_CR5);
                enable_code = inb(fcp->c_regbase + FCR_SRB) & FSB_DSDEF;
                /* update DENSEL mode bits */
                outb(fcp->c_regbase + FCR_SRB, enable_code | ds_code);

                /* exit configuration mode */
                outb(fcp->c_regbase + FCR_SRA, FSA_DISB);
                drv_usecwait(10);
                outb(fcp->c_regbase + FCR_SRA, save);
                break;
        }
        if (deselect)
                /* reselect drive */
                outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);
        return (retcode);
}

static int
fdc_motorsm(struct fcu_obj *fjp, int input, int timeval)
{
        struct fdcntlr *fcp = fjp->fj_fdc;
        int unit = fjp->fj_unit & 3;
        int old_mstate;
        int rval = 0;
        uchar_t motorbit;

        ASSERT(MUTEX_HELD(&fcp->c_dorlock));
        old_mstate = fcp->c_mtrstate[unit];
        encode(motor_onbits, unit, &motorbit);

        switch (input) {
        case FMI_TIMER:         /* timer expired */
                fcp->c_motort[unit] = 0;
                switch (old_mstate) {
                case FMS_START:
                case FMS_DELAY:
                        fcp->c_mtrstate[unit] = FMS_ON;
                        break;
                case FMS_KILLST:
                        fcp->c_motort[unit] = timeout(fdmotort, (void *)fjp,
                            drv_usectohz(1000000));
                        fcp->c_mtrstate[unit] = FMS_IDLE;
                        break;
                case FMS_IDLE:
                        fcp->c_digout &= ~motorbit;
                        outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);
                        fcp->c_mtrstate[unit] = FMS_OFF;
                        fjp->fj_flags &= ~FUNIT_3DMODE;
                        break;
                case 86:
                        rval = -1;
                        break;
                case FMS_OFF:
                case FMS_ON:
                default:
                        rval = -2;
                }
                break;

        case FMI_STARTCMD:      /* start command */
                switch (old_mstate) {
                case FMS_IDLE:
                        fcp->c_mtrstate[unit] = 86;
                        mutex_exit(&fcp->c_dorlock);
                        (void) untimeout(fcp->c_motort[unit]);
                        mutex_enter(&fcp->c_dorlock);
                        fcp->c_motort[unit] = 0;
                        fcp->c_mtrstate[unit] = FMS_ON;
                        break;
                case FMS_OFF:
                        fcp->c_digout |= motorbit;
                        outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);

                        /* start motor_spinup_timer */
                        ASSERT(timeval > 0);
                        fcp->c_motort[unit] = timeout(fdmotort,  (void *)fjp,
                            drv_usectohz(100000 * timeval));
                        /* FALLTHROUGH */
                case FMS_KILLST:
                        fcp->c_mtrstate[unit] = FMS_START;
                        break;
                default:
                        rval = -2;
                }
                break;

        case FMI_RSTARTCMD:     /* restart command */
                if (fcp->c_motort[unit] != 0) {
                        fcp->c_mtrstate[unit] = 86;
                        mutex_exit(&fcp->c_dorlock);
                        (void) untimeout(fcp->c_motort[unit]);
                        mutex_enter(&fcp->c_dorlock);
                }
                ASSERT(timeval > 0);
                fcp->c_motort[unit] = timeout(fdmotort, (void *)fjp,
                    drv_usectohz(100000 * timeval));
                fcp->c_mtrstate[unit] = FMS_START;
                break;

        case FMI_DELAYCMD:      /* delay command */
                if (fcp->c_motort[unit] == 0)
                        fcp->c_motort[unit] = timeout(fdmotort,  (void *)fjp,
                            drv_usectohz(15000));
                fcp->c_mtrstate[unit] = FMS_DELAY;
                break;

        case FMI_IDLECMD:       /* idle command */
                switch (old_mstate) {
                case FMS_DELAY:
                        fcp->c_mtrstate[unit] = 86;
                        mutex_exit(&fcp->c_dorlock);
                        (void) untimeout(fcp->c_motort[unit]);
                        mutex_enter(&fcp->c_dorlock);
                        /* FALLTHROUGH */
                case FMS_ON:
                        ASSERT(timeval > 0);
                        fcp->c_motort[unit] = timeout(fdmotort, (void *)fjp,
                            drv_usectohz(100000 * timeval));
                        fcp->c_mtrstate[unit] = FMS_IDLE;
                        break;
                case FMS_START:
                        fcp->c_mtrstate[unit] = FMS_KILLST;
                        break;
                default:
                        rval = -2;
                }
                break;

        default:
                rval = -3;
        }
        if (rval) {
                FCERRPRINT(FDEP_L4, FDEM_EXEC, (CE_WARN,
                    "fdc_motorsm: unit %d  bad input %d or bad state %d",
                    (int)fjp->fj_unit, input, old_mstate));
#if 0
                cmn_err(CE_WARN,
                    "fdc_motorsm: unit %d  bad input %d or bad state %d",
                    (int)fjp->fj_unit, input, old_mstate);
                fcp->c_mtrstate[unit] = FMS_OFF;
                if (fcp->c_motort[unit] != 0) {
                        mutex_exit(&fcp->c_dorlock);
                        (void) untimeout(fcp->c_motort[unit]);
                        mutex_enter(&fcp->c_dorlock);
                        fcp->c_motort[unit] = 0;
                }
#endif
        } else
                FCERRPRINT(FDEP_L0, FDEM_EXEC,
                    (CE_CONT, "fdc_motorsm unit %d: input %d,  %d -> %d\n",
                    (int)fjp->fj_unit, input, old_mstate,
                    fcp->c_mtrstate[unit]));
        return (rval);
}

/*
 * fdmotort
 *      is called from timeout() when a motor timer has expired.
 */
static void
fdmotort(void *arg)
{
        struct fcu_obj *fjp = (struct fcu_obj *)arg;
        struct fdcntlr *fcp = fjp->fj_fdc;
        struct fdcsb *csb = &fcp->c_csb;
        int unit = fjp->fj_unit & 3;
        int mval;
        int newxstate = 0;

        mutex_enter(&fcp->c_dorlock);
        mval = fdc_motorsm(fjp, FMI_TIMER, 0);
        mutex_exit(&fcp->c_dorlock);
        if (mval < 0)
                return;

        mutex_enter(&fcp->c_lock);

        if ((fcp->c_flags & FCFLG_WAITING) &&
            fcp->c_mtrstate[unit] == FMS_ON &&
            (csb->csb_xstate == FXS_MTRON || csb->csb_xstate == FXS_HDST ||
            csb->csb_xstate == FXS_DKCHGX)) {
                newxstate = fdc_statemach(fcp);
                if (newxstate == -1) {
                        FCERRPRINT(FDEP_L3, FDEM_EXEC,
                            (CE_WARN,
                            "fdc_motort unit %d: motor ready but bad xstate",
                            (int)fjp->fj_unit));
                        fcp->c_csb.csb_cmdstat = EIO;
                }
                if (newxstate == -1 || newxstate == FXS_END) {
                        fcp->c_flags ^= FCFLG_WAITING;
                        cv_signal(&fcp->c_iocv);
                }
        }
        mutex_exit(&fcp->c_lock);
}

/*
 * DMA interrupt service routine
 *
 *      Called by EISA dma interrupt service routine when buffer chaining
 *      is required.
 */

ddi_dma_cookie_t *
fdc_dmae_isr(struct fdcntlr *fcp)
{
        struct fdcsb *csb = &fcp->c_csb;
        off_t off;
        size_t len;

        if (csb->csb_dmahandle && !csb->csb_cmdstat) {
                if (++csb->csb_dmacurrcookie < csb->csb_dmacookiecnt) {
                        ddi_dma_nextcookie(csb->csb_dmahandle,
                            &csb->csb_dmacookie);
                        return (&csb->csb_dmacookie);
                } else if (++csb->csb_dmacurrwin < csb->csb_dmawincnt) {
                        if (ddi_dma_getwin(csb->csb_dmahandle,
                            csb->csb_dmacurrwin, &off, &len,
                            &csb->csb_dmacookie,
                            &csb->csb_dmacookiecnt) != DDI_SUCCESS) {
                                return (NULL);
                        }
                        csb->csb_dmacurrcookie = 0;
                        return (&csb->csb_dmacookie);
                }
        } else
                cmn_err(CE_WARN, "fdc: unsolicited DMA interrupt");
        return (NULL);
}


/*
 * returns:
 *      0 if all ok,
 *      ENXIO - diskette not in drive
 *      ETIMEDOUT - for immediate operations that timed out
 *      EBUSY - if stupid chip is locked busy???
 *      ENOEXEC - for timeout during sending cmds to chip
 *
 * to sleep: set sleep
 * to check for disk changed: set change
 */
static int
fdc_exec(struct fdcntlr *fcp, int sleep, int change)
{
        struct ddi_dmae_req dmaereq;
        struct fcu_obj *fjp;
        struct fdcsb *csb;
        off_t off;
        size_t len;
        int unit;

        mutex_enter(&fcp->c_lock);
        FCERRPRINT(FDEP_L0, FDEM_EXEC,
            (CE_CONT, "fdc_exec: sleep %x change %x\n", sleep, change));
        csb = &fcp->c_csb;
        unit = csb->csb_drive;
        fjp = fcp->c_unit[unit];

        if (csb->csb_opflags & CSB_OFINRPT) {
                if (*csb->csb_cmd == FO_RECAL)
                        csb->csb_npcyl = 0;
                else if ((*csb->csb_cmd & ~FO_MFM) != FO_FRMT)
                        csb->csb_npcyl =
                            csb->csb_cmd[2] * fjp->fj_chars->fdc_steps;
                csb->csb_xstate = FXS_START;
        } else
                csb->csb_xstate = FXS_DOIT;
        csb->csb_retrys = 0;
        csb->csb_ourtrys = 0;

        if (csb->csb_dmahandle) {
                /* ensure that entire format xfer is in one cookie */
                /*
                 * The change from  ddi_dma_buf/addr_setup() to
                 * ddi_dma_buf/addr_bind_handle() has already loaded
                 * the first DMA window and cookie.
                 */
                if ((*csb->csb_cmd & ~FO_MFM) == FO_FRMT &&
                    (4 * csb->csb_cmd[3]) != csb->csb_dmacookie.dmac_size) {
                        mutex_exit(&fcp->c_lock);
                        return (EINVAL);
                }
        }

retry:
        if (fcp->c_curunit != unit || !(fjp->fj_flags & FUNIT_CHAROK)) {
                fcp->c_curunit = unit;
                fjp->fj_flags |= FUNIT_CHAROK;
                if (fjp->fj_chars->fdc_transfer_rate == 417) {
                        /* XXX hack for fdformat */
                        /* fjp->fj_chars->fdc_transfer_rate == 500;     */
                        fjp->fj_attr->fda_rotatespd = 360;
                }
                if (fdcspecify(fcp, fjp->fj_chars->fdc_transfer_rate,
                    fjp->fj_drive->fdd_steprate, 40))
                        cmn_err(CE_WARN,
                            "fdc_select: controller setup rejected "
                            "fdcntrl %p transfer rate %x step rate %x "
                            "head load time 40", (void*)fcp,
                            fjp->fj_chars->fdc_transfer_rate,
                            fjp->fj_drive->fdd_steprate);

                mutex_enter(&fcp->c_dorlock);
                if (fdcspdchange(fcp, fjp, fjp->fj_attr->fda_rotatespd)) {
                        /* 3D drive requires 500 ms for speed change */
                        (void) fdc_motorsm(fjp, FMI_RSTARTCMD, 5);
                        /*
                         * Return value ignored - fdcmotort deals with failure.
                         */
                }
                mutex_exit(&fcp->c_dorlock);
        }

        /*
         * If checking for disk_change is enabled
         * (i.e. not seeking in fdresetchng),
         * we sample the DSKCHG line to see if the diskette has wandered away.
         */
        if (change && fdcsense_chng(fcp, unit)) {
                FCERRPRINT(FDEP_L3, FDEM_EXEC,
                    (CE_WARN, "diskette %d changed!!!", csb->csb_drive));
                fcp->c_unit[unit]->fj_flags |= FUNIT_CHANGED;
                /*
                 * If the diskette is still gone... so are we, adios!
                 */
                if (fdcheckdisk(fcp, unit)) {
                        mutex_exit(&fcp->c_lock);

                        /* VP/ix expects an EBUSY return here */
                        if (*csb->csb_cmd == FO_SDRV) {
                                return (EBUSY);
                        }
                        return (ENXIO);
                }
                /*
                 * delay to ensure that new diskette is up to speed
                 */
                mutex_enter(&fcp->c_dorlock);
                (void) fdc_motorsm(fjp, FMI_RSTARTCMD,
                    fjp->fj_drive->fdd_motoron);
                /*
                 * Return value ignored - fdcmotort deals with failure.
                 */
                mutex_exit(&fcp->c_dorlock);
        }

        /*
         * gather some statistics
         */
        switch (csb->csb_cmd[0] & 0x1f) {
        case FO_RDDAT:
                fcp->fdstats.rd++;
                break;
        case FO_WRDAT:
                fcp->fdstats.wr++;
                break;
        case FO_RECAL:
                fcp->fdstats.recal++;
                break;
        case FO_FRMT:
                fcp->fdstats.form++;
                break;
        default:
                fcp->fdstats.other++;
                break;
        }

        bzero(csb->csb_rslt, 10);
        csb->csb_cmdstat = 0;

        if (csb->csb_dmahandle) {
                bzero(&dmaereq, sizeof (struct ddi_dmae_req));
                dmaereq.der_command = (csb->csb_opflags & CSB_OFDMAWT) ?
                    DMAE_CMD_WRITE : DMAE_CMD_READ;
                /*
                 * setup for dma buffer chaining regardless of bus capability
                 */
                dmaereq.der_bufprocess = DMAE_BUF_CHAIN;
                dmaereq.proc = fdc_dmae_isr;
                dmaereq.procparms = (void *)fcp;
                if (ddi_dmae_prog(fcp->c_dip, &dmaereq, &csb->csb_dmacookie,
                    fcp->c_dmachan) != DDI_SUCCESS)
                        cmn_err(CE_WARN, "fdc_exec: dmae prog failed, "
                            "dip %p, dmachan %x",
                            (void*)fcp->c_dip, fcp->c_dmachan);
        }

        if ((fdc_statemach(fcp) == FXS_DOWT) && !sleep) {
                /*
                 * If the operation has no results - then just return
                 */
                if (!csb->csb_nrslts) {
                        mutex_exit(&fcp->c_lock);
                        return (0);
                }
                /*
                 * this operation has no interrupt and an immediate result
                 * so wait for the results and stuff them into the csb
                 */
                if (fdc_statemach(fcp) == -1) {
                        mutex_exit(&fcp->c_lock);
                        return (EIO);
                }
        } else {
                fcp->c_flags |= FCFLG_WAITING;
                /*
                 * wait for completion interrupt
                 */
                while (fcp->c_flags & FCFLG_WAITING) {
                        cv_wait(&fcp->c_iocv, &fcp->c_lock);
                }
        }

        /*
         * See if there was an error detected, if so, fdrecover()
         * will check it out and say what to do.
         *
         * Don't do this, though, if this was the Sense Drive Status
         * or the Dump Registers command.
         */
        if (csb->csb_cmdstat && *csb->csb_cmd != FO_SDRV) {
                /* if it can restarted OK, then do so, else return error */
                if (fdrecover(fcp)) {
                        mutex_exit(&fcp->c_lock);
                        return (EIO);
                }
                /* ASSUMES that cmd is still intact in csb */
                if (csb->csb_xstate == FXS_END)
                        csb->csb_xstate = FXS_START;
                if (fdc_dma_attr.dma_attr_sgllen > 1 && csb->csb_dmahandle) {
                        /*
                         * restarted read/write operation requires
                         * first DMA cookie of current window
                         */
                        if (ddi_dma_getwin(csb->csb_dmahandle,
                            csb->csb_dmacurrwin, &off, &len,
                            &csb->csb_dmacookie,
                            &csb->csb_dmacookiecnt) != DDI_SUCCESS) {

                                mutex_exit(&fcp->c_lock);
                                return (EIO);
                        }
                        csb->csb_dmacurrcookie = 0;
                }
                goto retry;
        }
        /* things went ok */
        mutex_exit(&fcp->c_lock);
        return (0);
}

/*
 * fdcheckdisk
 *      called by fdc_exec to check if the disk is still there - do a seek
 *      then see if DSKCHG line went away; if so, diskette is in; else
 *      it's (still) out.
 */
int
fdcheckdisk(struct fdcntlr *fcp, int unit)
{
        struct fdcsb *csb = &fcp->c_csb;
        int newcyl;                     /* where to seek for reset of DSKCHG */
        int rval;
        enum fxstate save_xstate;
        uchar_t save_cmd, save_cd1, save_npcyl;

        ASSERT(MUTEX_HELD(&fcp->c_lock));
        FCERRPRINT(FDEP_L1, FDEM_CHEK,
            (CE_CONT, "fdcheckdisk unit %d\n", unit));

        if (fcp->c_curpcyl[unit])
                newcyl = fcp->c_curpcyl[unit] - 1;
        else
                newcyl = 1;

        save_cmd = *csb->csb_cmd;
        save_cd1 = csb->csb_cmd[1];
        save_npcyl = csb->csb_npcyl;
        save_xstate = csb->csb_xstate;

        *csb->csb_cmd = FO_SEEK;
        csb->csb_cmd[1] = (uchar_t)unit;
        csb->csb_npcyl = (uchar_t)newcyl;
        fcp->c_flags |= FCFLG_WAITING;

        if (fcp->c_mtrstate[unit] != FMS_ON && fcp->c_motort[unit] != 0)
                /*
                 * wait for motor to get up to speed,
                 * and let motor_timer issue seek cmd
                 */
                csb->csb_xstate = FXS_DKCHGX;
        else {
                /*
                 * motor is up to speed; issue seek cmd now
                 */
                csb->csb_xstate = FXS_SEEK;
                if (rval = fdcseek(fcp, unit, newcyl)) {
                        /*
                         * any recal/seek errors are too serious to attend to
                         */
                        FCERRPRINT(FDEP_L3, FDEM_CHEK,
                            (CE_WARN, "fdcheckdisk err %d", rval));
                        fcp->c_flags ^= FCFLG_WAITING;
                }
        }
        /*
         * wait for completion interrupt
         * XXX This should be backed up with a watchdog timer!
         */
        while (fcp->c_flags & FCFLG_WAITING) {
                cv_wait(&fcp->c_iocv, &fcp->c_lock);
        }

        /*
         * if disk change still asserted, no diskette in drive!
         */
        if (rval = fdcsense_chng(fcp, unit)) {
                FCERRPRINT(FDEP_L3, FDEM_CHEK,
                    (CE_WARN, "fdcheckdisk no disk %d", unit));
        }

        *csb->csb_cmd = save_cmd;
        csb->csb_cmd[1] = save_cd1;
        csb->csb_npcyl = save_npcyl;
        csb->csb_xstate = save_xstate;
        return (rval);
}

static int
fdrecover(struct fdcntlr *fcp)
{
        struct fcu_obj *fjp;
        struct fdcsb *csb = &fcp->c_csb;
        int residual;
        int unit;
        char *failure;

        FCERRPRINT(FDEP_L2, FDEM_RECO,
            (CE_NOTE, "fdrecover unit %d", csb->csb_drive));

        unit = csb->csb_drive;
        fjp = fcp->c_unit[unit];
        if (fcp->c_flags & FCFLG_TIMEOUT) {
                fcp->c_flags ^= FCFLG_TIMEOUT;
                csb->csb_rslt[1] |= 0x08;
                FCERRPRINT(FDEP_L3, FDEM_RECO,
                    (CE_WARN, "fd unit %d: %s timed out", csb->csb_drive,
                    fdcmds[*csb->csb_cmd & 0x1f].cmdname));
        }

        if (csb->csb_status & S0_SEKEND)
                fcp->c_curpcyl[unit] = -1;

        switch (csb->csb_oldxs) {
        case FXS_RCAL:          /* recalibrate */
        case FXS_SEEK:          /* seek */
        case FXS_RESET:         /* cntlr reset */
                FCERRPRINT(FDEP_L4, FDEM_RECO, (CE_WARN,
                    "fd unit %d: %s error: st0=0x%x pcn=%d", csb->csb_drive,
                    fdcmds[*csb->csb_cmd & 0x1f].cmdname,
                    *csb->csb_rslt, csb->csb_rslt[1]));
                if (csb->csb_retrys++ < skretry &&
                    !(csb->csb_opflags & CSB_OFRAWIOCTL))
                        return (0);
                break;

        case FXS_RDID:          /* read ID */
                if (!(csb->csb_status & S0_SEKEND))
                        csb->csb_xstate = FXS_HDST;
                /* FALLTHROUGH */
        case FXS_DOIT:          /* original operation */
        case FXS_DOWT:          /* waiting on operation */
                if (csb->csb_opflags & (CSB_OFDMARD | CSB_OFDMAWT)) {
                        if (ddi_dmae_getcnt(fcp->c_dip, fcp->c_dmachan,
                            &residual) != DDI_SUCCESS)
                                cmn_err(CE_WARN,
                                    "fdc_recover: dmae getcnt failed, "
                                    "dip %p dmachan %x residual %x",
                                    (void*)fcp->c_dip, fcp->c_dmachan,
                                    residual);
                        FCERRPRINT(FDEP_L2, FDEM_RECO,
                            (CE_NOTE,
                            "fd unit %d: %s error: "
                            "dma count=0x%lx residual=0x%x",
                            csb->csb_drive,
                            fdcmds[*csb->csb_cmd & 0x1f].cmdname,
                            csb->csb_dmacookie.dmac_size, residual));
                }
                if (csb->csb_rslt[1] == S1_OVRUN)
                        /*
                         * handle retries of over/underrun
                         * with a secondary retry counter
                         */
                        if (++csb->csb_ourtrys <= OURUN_TRIES) {
                                FCERRPRINT(FDEP_L2, FDEM_RECO,
                                    (CE_NOTE,
                                    "fd unit %d: %s error: over/under-run",
                                    csb->csb_drive,
                                    fdcmds[*csb->csb_cmd & 0x1f].cmdname));
                                return (0);
                        } else
                                /*
                                 * count 1 set of over/underruns
                                 * as 1 primary retry effort
                                 */
                                csb->csb_ourtrys = 0;

                if ((fjp->fj_flags & (FUNIT_UNLABELED | FUNIT_LABELOK)) &&
                    !(csb->csb_opflags & CSB_OFRAWIOCTL)) {
                        /*
                         * device is open so keep trying and
                         * gather statistics on errors
                         */
                        if (csb->csb_rslt[1] & S1_CRCER)
                                fcp->fdstats.de++;
                        if (csb->csb_rslt[1] & S1_OVRUN)
                                fcp->fdstats.run++;
                        if (csb->csb_rslt[1] & (S1_NODATA | S1_MADMK))
                                fcp->fdstats.bfmt++;
                        if (csb->csb_rslt[1] & 0x08)
                                fcp->fdstats.to++;

                        /*
                         * if we have not run out of retries, return 0
                         */
                        if (csb->csb_retrys++ < csb->csb_maxretry &&
                            (*csb->csb_cmd & ~FO_MFM) != FO_FRMT) {
                                if (csb->csb_opflags &
                                    (CSB_OFDMARD | CSB_OFDMAWT)) {
                                        FCERRPRINT(FDEP_L4, FDEM_RECO,
                                            (CE_WARN,
                                            "fd unit %d: %s error: "
                                            "st0=0x%x st1=0x%x st2=0x%x",
                                            csb->csb_drive,
                                            fdcmds[*csb->csb_cmd &
                                            0x1f].cmdname,
                                            *csb->csb_rslt, csb->csb_rslt[1],
                                            csb->csb_rslt[2]));
                                }
                                if ((csb->csb_retrys & 1) &&
                                    csb->csb_xstate == FXS_END)
                                        csb->csb_xstate = FXS_DOIT;
                                else if (csb->csb_retrys == 3)
                                        csb->csb_xstate = FXS_RESTART;
                                return (0);
                        }
                        if (csb->csb_rslt[1] & S1_CRCER)
                                failure = "crc error";
                        else if (csb->csb_rslt[1] & S1_OVRUN)
                                failure = "over/under-run";
                        else if (csb->csb_rslt[1] & (S1_NODATA | S1_MADMK))
                                failure = "bad format";
                        else if (csb->csb_rslt[1] & 0x08)
                                failure = "timeout";
                        else
                                failure = "failed";
                        cmn_err(CE_NOTE, "!fd unit %d: %s %s (%x %x %x)",
                            csb->csb_drive,
                            fdcmds[*csb->csb_cmd & 0x1f].cmdname, failure,
                            *csb->csb_rslt, csb->csb_rslt[1], csb->csb_rslt[2]);
                } else {
                        FCERRPRINT(FDEP_L2, FDEM_RECO,
                            (CE_NOTE, "fd unit %d: %s failed (%x %x %x)",
                            csb->csb_drive,
                            fdcmds[*csb->csb_cmd & 0x1f].cmdname,
                            *csb->csb_rslt, csb->csb_rslt[1],
                            csb->csb_rslt[2]));
                }
                break;

        default:
                FCERRPRINT(FDEP_L4, FDEM_RECO, (CE_WARN,
                    "fd unit %d: %s failed: st0=0x%x st1=0x%x st2=0x%x",
                    csb->csb_drive, fdcmds[*csb->csb_cmd & 0x1f].cmdname,
                    *csb->csb_rslt, csb->csb_rslt[1], csb->csb_rslt[2]));
                break;
        }
        return (1);
}


/*      Autovector Interrupt Entry Point        */
static uint_t
fdc_intr(caddr_t arg)
{
        struct fdcntlr *fcp = (struct fdcntlr *)arg;
        struct fdcsb *csb;
        off_t off;
        size_t blklen;
        int drive;
        int newstate;
        int pendstate;
        int rval = DDI_DMA_DONE;
        int state;
        int maxspin = 10;

        csb = &fcp->c_csb;

        mutex_enter(&fcp->c_lock);
        if (fcp->c_suspended) {
                mutex_exit(&fcp->c_lock);
                return (DDI_INTR_UNCLAIMED);
        }

        /*
         * Wait for the RQM bit to be set, or until we've tested it
         * a bunch of times (which may imply this isn't our interrupt).
         */
        state = inb(fcp->c_regbase + FCR_MSR);
        pendstate = state & (MS_RQM | MS_DIO | MS_CB);
        while (((pendstate & MS_RQM) == 0) && (maxspin-- > 0)) {
                /* Small pause in between reading the status port */
                drv_usecwait(10);
                /* Reread the status port */
                state = inb(fcp->c_regbase + FCR_MSR);
                pendstate = state & (MS_RQM | MS_DIO | MS_CB);
        }
        FCERRPRINT(FDEP_L0, FDEM_INTR,
            (CE_CONT, "fdc_intr unit %d: xstate=%d MSR=0x%x\n",
            csb->csb_drive, csb->csb_xstate, state));

        /*
         * If there is an operation outstanding AND the controller is ready
         * to receive a command or send us the result of a command (OR if the
         * controller is ready to accept a new command), AND if
         * someone has been waiting for a command to finish AND (if no unit
         * is BUSY OR if the unit that we're waiting for is BUSY (i.e. it's in
         * the middle of a seek/recalibrate)) then this interrupt is for us.
         */
        if ((pendstate == (MS_RQM | MS_DIO | MS_CB) || pendstate == MS_RQM) &&
            (fcp->c_flags & FCFLG_WAITING) &&
            (!(state & 0x0f) || ((1 << csb->csb_drive) & state))) {
                /*
                 * Remove one of the conditions for entering this code.
                 * The state_machine will release the c_lock if it
                 * calls untimeout()
                 */
                fcp->c_flags ^= FCFLG_WAITING;

                if ((newstate = fdc_statemach(fcp)) == -1) {
                        /* restore waiting flag */
                        fcp->c_flags |= FCFLG_WAITING;
                        mutex_exit(&fcp->c_lock);
                        return (DDI_INTR_CLAIMED);
                }

                if (fcp->c_intrstat)
                        KIOIP->intrs[KSTAT_INTR_HARD]++;
                if (newstate == FXS_END) {

                        if (csb->csb_dmahandle && !csb->csb_cmdstat &&
                                /*
                                 * read/write operation may have multiple DMA
                                 * cookies: process next one
                                 */
                            ((csb->csb_dmacurrcookie <
                            (csb->csb_dmacookiecnt - 1)) ||
                            (csb->csb_dmacurrwin) < (csb->csb_dmawincnt - 1))) {
                                /*
                                 * read/write operation requires another
                                 * DMA cookie: process next one
                                 */

                                if (++csb->csb_dmacurrcookie <
                                    csb->csb_dmacookiecnt) {
                                        ddi_dma_nextcookie(csb->csb_dmahandle,
                                            &csb->csb_dmacookie);
                                } else if (++csb->csb_dmacurrwin <
                                    csb->csb_dmawincnt) {
                                        if (ddi_dma_getwin(csb->csb_dmahandle,
                                            csb->csb_dmacurrwin, &off, &blklen,
                                            &csb->csb_dmacookie,
                                            &csb->csb_dmacookiecnt) !=
                                            DDI_SUCCESS) {
                                                cmn_err(CE_WARN,
                                                    "fdc_intr: "
                                                    "dma getwin failed");
                                        }
                                        csb->csb_dmacurrcookie = 0;
                                }

                                if (ddi_dmae_prog(fcp->c_dip, NULL,
                                    &csb->csb_dmacookie, fcp->c_dmachan) !=
                                    DDI_SUCCESS)
                                        cmn_err(CE_WARN,
                                            "fdc_intr: dmae prog failed, "
                                            "dip %p dmachannel %x",
                                            (void*)fcp->c_dip,
                                            fcp->c_dmachan);

                                /*
                                 * status of last operation has disk
                                 * address for continuation
                                 */
                                csb->csb_cmd[2] = csb->csb_rslt[3];
                                csb->csb_cmd[3] = csb->csb_rslt[4];
                                csb->csb_cmd[4] = csb->csb_rslt[5];
                                csb->csb_cmd[1] = (csb->csb_cmd[1] & ~0x04) |
                                    (csb->csb_cmd[3] << 2);

                                csb->csb_xstate = FXS_START;
                                (void) fdc_statemach(fcp);
                                /*
                                 * Ignored return.  If failed, warning already
                                 * posted.  Returned state irrelevant.
                                 */
                                /* restore waiting flag */
                                fcp->c_flags |= FCFLG_WAITING;
                                goto fi_exit;
                        }
                        if (rval != DDI_DMA_DONE)
                                csb->csb_cmdstat = EIO;
                        /*
                         * somebody's waiting for completion of fdcntlr/csb,
                         * wake them
                         */
                        cv_signal(&fcp->c_iocv);
                }
                else
                        /* restore waiting flag */
                        fcp->c_flags |= FCFLG_WAITING;
fi_exit:
                mutex_exit(&fcp->c_lock);
                return (DDI_INTR_CLAIMED);
        }

        if (state & MS_RQM) {
                (void) fdcsense_int(fcp, &drive, NULL);
                /*
                 * Ignored return - senser state already saved
                 */
                FCERRPRINT(FDEP_L4, FDEM_INTR,
                    (CE_WARN, "fdc_intr unit %d: nobody sleeping 0x%x",
                    drive, state));
        } else {
                FCERRPRINT(FDEP_L4, FDEM_INTR,
                    (CE_WARN, "fdc_intr: nobody sleeping on %d 0x%x",
                    csb->csb_drive, state));
        }
        /*
         * This should probably be protected, but, what the
         * heck...the cost isn't worth the accuracy for this
         * statistic.
         */
        if (fcp->c_intrstat)
                KIOIP->intrs[KSTAT_INTR_SPURIOUS]++;
        mutex_exit(&fcp->c_lock);
        return (DDI_INTR_UNCLAIMED);
}

/*
 * fdwatch
 *      is called from timeout() when a floppy operation timer has expired.
 */
static void
fdwatch(void *arg)
{
        struct fdcntlr *fcp = (struct fdcntlr *)arg;
        struct fdcsb *csb;

        mutex_enter(&fcp->c_lock);

        if (fcp->c_timeid == 0) {
                /*
                 * fdc_intr got here first, ergo, no timeout condition..
                 */
                mutex_exit(&fcp->c_lock);
                return;
        }

        if (fcp->c_flags & FCFLG_WAITING) {
                if (ddi_dmae_stop(fcp->c_dip, fcp->c_dmachan) != DDI_SUCCESS)
                        cmn_err(CE_WARN, "fdwatch: dmae stop failed, "
                            "dip %p, dmachan %x",
                            (void*)fcp->c_dip, fcp->c_dmachan);
                csb = &fcp->c_csb;
                FCERRPRINT(FDEP_L3, FDEM_WATC,
                    (CE_WARN, "fdcwatch unit %d: xstate = %d",
                    csb->csb_drive, csb->csb_xstate));
                drv_usecwait(50);

                if (inb(fcp->c_regbase + FCR_MSR) != MS_RQM) {
                        /*
                         * cntlr is still busy, so reset it
                         */
                        csb->csb_xstate = FXS_KILL;
                        (void) fdc_statemach(fcp);
                        /*
                         * Ignored return.  If failed, warning already
                         * posted.  Returned state irrelevant.
                         */
                } else {
                        csb->csb_xstate = FXS_END;
                        fcp->c_timeid = 0;
                        fcp->c_flags ^= FCFLG_WAITING;
                        cv_signal(&fcp->c_iocv);
                }
                csb->csb_cmdstat = EIO;
                fcp->c_flags |= FCFLG_TIMEOUT;
        } else {
                FCERRPRINT(FDEP_L4, FDEM_INTR,
                    (CE_WARN, "fdcwatch: not sleeping for unit %d",
                    fcp->c_csb.csb_drive));
        }
        if (fcp->c_intrstat)
                KIOIP->intrs[KSTAT_INTR_WATCHDOG]++;
        mutex_exit(&fcp->c_lock);
}


static int
fdc_statemach(struct fdcntlr *fcp)
{
        struct fcu_obj *fjp;
        struct fdcsb *csb = &fcp->c_csb;
        int backoff;
        clock_t time;
        int unit;

        ASSERT(MUTEX_HELD(&fcp->c_lock));

        unit = csb->csb_drive;
        fjp = fcp->c_unit[unit];

        csb->csb_oldxs = csb->csb_xstate;
        switch (csb->csb_xstate) {

        case FXS_START:         /* start of operation */
                ASSERT(fcp->c_timeid == 0);
                time = drv_usectohz(100000 * (unsigned int)csb->csb_timer);
                if (time == 0)
                        time = drv_usectohz(2000000);
                fcp->c_timeid = timeout(fdwatch, (void *)fcp, time);

                if (fcp->c_mtrstate[unit] == FMS_START) {
                        /*
                         * wait for motor to get up to speed
                         */
                        csb->csb_xstate = FXS_MTRON;
                        break;
                }
                /* FALLTHROUGH */

        case FXS_MTRON:         /* motor is at speed */
                if (fcp->c_mtrstate[unit] != FMS_ON) {
                        /* how did we get here ?? */
                        cmn_err(CE_WARN, "fdc: selected but motor off");
                        return (-1);
                }
                if (fcp->c_curpcyl[unit] != -1 && *csb->csb_cmd != FO_RECAL)
                        goto nxs_seek;
                recalcmd[1] = (uchar_t)unit;
                if (fdc_docmd(fcp, recalcmd, 2) == -1) {
                        /* cntlr did not accept command bytes */
                        fdcquiesce(fcp);
                        csb->csb_cmdstat = EIO;
                        csb->csb_xstate = FXS_RESET;
                        break;
                }
                fcp->c_sekdir[unit] = 0;
                csb->csb_xstate = FXS_RCAL;
                break;

        case FXS_RCAL:          /* forced recalibrate is complete */
#if 0   /* #ifdef _VPIX */
        /* WARNING: this code breaks SPARC compatibility */
                if (csb->csb_opflags & CSB_OFRAWIOCTL &&
                    *csb->csb_cmd == FO_RECAL) {
                        fcp->c_curpcyl[unit] = 0;
                        csb->csb_status = 0;
                        goto nxs_cmpl;
                }
#endif
                (void) fdc_docmd(fcp, &senseintcmd, 1);
                /*
                 * Ignored return. If failed, warning was issued by fdc_docmd.
                 * fdc_results retrieves the controller/drive status
                 */
                (void) fdc_result(fcp, csb->csb_rslt, 2);
                /*
                 * Ignored return. If failed, warning was issued by fdc_result.
                 * Actual results checked below
                 */
                if ((csb->csb_status = ((*csb->csb_rslt ^ S0_SEKEND) &
                    (S0_ICMASK | S0_SEKEND | S0_ECHK | S0_NOTRDY))) != 0) {
                        FCERRPRINT(FDEP_L3, FDEM_EXEC,
                            (CE_WARN, "fdc_statemach unit %d: recal result %x",
                            csb->csb_drive, *csb->csb_rslt));
                        fdcquiesce(fcp);
                        csb->csb_cmdstat = EIO;
                        csb->csb_xstate = FXS_RESET;
                        break;
                }
                if (unit != (*csb->csb_rslt & 3) || csb->csb_rslt[1]) {
                        csb->csb_status = S0_SEKEND;
                        goto nxs_cmpl;
                }
                fcp->c_curpcyl[unit] = csb->csb_rslt[1];
                if (*csb->csb_cmd == FO_RECAL)
                        goto nxs_cmpl;
nxs_seek:
                if (*csb->csb_cmd != FO_SEEK &&
                    csb->csb_npcyl == fcp->c_curpcyl[unit])
                        goto nxs_doit;
                fcp->c_sekdir[unit] = csb->csb_npcyl - fcp->c_curpcyl[unit];
                /* FALLTHROUGH */

        case FXS_DKCHGX:        /* reset Disk-Change latch */
                (void) fdcseek(fcp, csb->csb_cmd[1], csb->csb_npcyl);
                /*
                 * Ignored return.  If command rejected, warnig already posted
                 * by fdc_docmd().
                 */
                csb->csb_xstate = FXS_SEEK;
                break;

        case FXS_RESTART:       /* special restart of read/write operation */
                ASSERT(fcp->c_timeid == 0);
                time = drv_usectohz(100000 * csb->csb_timer);
                if (time == 0)
                        time = drv_usectohz(2000000);
                fcp->c_timeid = timeout(fdwatch, (void *)fcp, time);

                if (fcp->c_mtrstate[unit] != FMS_ON) {
                        cmn_err(CE_WARN, "fdc: selected but motor off");
                        return (-1);
                }
                if ((csb->csb_npcyl == 0 || fcp->c_sekdir[unit] >= 0) &&
                    (int)csb->csb_cmd[2] < (fjp->fj_chars->fdc_ncyl - 1))
                        backoff = csb->csb_npcyl + 1;
                else
                        backoff = csb->csb_npcyl - 1;
                (void) fdcseek(fcp, csb->csb_cmd[1], backoff);
                /*
                 * Ignored return.  If command rejected, warnig already posted
                 * by fdc_docmd().
                 */
                csb->csb_xstate = FXS_RESEEK;
                break;

        case FXS_RESEEK:        /* seek to backoff-cyl complete */
                (void) fdc_docmd(fcp, &senseintcmd, 1);
                /*
                 * Ignored return. If failed, warning was issued by fdc_docmd.
                 * fdc_results retrieves the controller/drive status
                 */
                (void) fdc_result(fcp, csb->csb_rslt, 2);
                /*
                 * Ignored return. If failed, warning was issued by fdc_result.
                 * Actual results checked below
                 */
                if ((csb->csb_status = ((*csb->csb_rslt ^ S0_SEKEND) &
                    (S0_ICMASK | S0_SEKEND | S0_ECHK | S0_NOTRDY))) != 0)
                        goto nxs_cmpl;
                (void) fdcseek(fcp, csb->csb_cmd[1], csb->csb_npcyl);
                /*
                 * Ignored return.  If command rejected, warnig already posted
                 * by fdc_docmd().
                 */
                csb->csb_xstate = FXS_SEEK;
                break;

        case FXS_SEEK:          /* seek complete */
#if 0   /* #ifdef _VPIX */
        /* WARNING: this code breaks SPARC compatibility and */
        /* rawioctls in fdformat */
                if (csb->csb_opflags & CSB_OFRAWIOCTL) {
                        fcp->c_curpcyl[unit] = csb->csb_npcyl;
                        csb->csb_status = 0;
                        goto nxs_cmpl;
                }
#endif
                (void) fdc_docmd(fcp, &senseintcmd, 1);
                /*
                 * Ignored return. If failed, warning was issued by fdc_docmd.
                 * fdc_results retrieves the controller/drive status
                 */
                (void) fdc_result(fcp, csb->csb_rslt, 2);
                /*
                 * Ignored return. If failed, warning was issued by fdc_result.
                 * Actual results checked below
                 */
                if ((csb->csb_status = ((*csb->csb_rslt ^ S0_SEKEND) &
                    (S0_ICMASK | S0_SEKEND | S0_ECHK | S0_NOTRDY))) != 0)
                        goto nxs_cmpl;
                if (unit != (*csb->csb_rslt & 3) ||
                    csb->csb_rslt[1] != csb->csb_npcyl) {
                        csb->csb_status = S0_SEKEND;
                        goto nxs_cmpl;
                };
                fcp->c_curpcyl[unit] = csb->csb_rslt[1];
                /* use motor_timer to delay for head settle */
                mutex_enter(&fcp->c_dorlock);
                (void) fdc_motorsm(fjp, FMI_DELAYCMD,
                    fjp->fj_drive->fdd_headsettle / 1000);
                /*
                 * Return value ignored - fdcmotort deals with failure.
                 */
                mutex_exit(&fcp->c_dorlock);
                csb->csb_xstate = FXS_HDST;
                break;

        case FXS_HDST:          /* head settle */
                if (*csb->csb_cmd == FO_SEEK)
                        goto nxs_cmpl;
                if ((*csb->csb_cmd & ~FO_MFM) == FO_FRMT)
                        goto nxs_doit;
                fdcreadid(fcp, csb);
                csb->csb_xstate = FXS_RDID;
                break;

        case FXS_RDID:          /* read ID complete */
                (void) fdc_result(fcp, csb->csb_rslt, 7);
                /*
                 * Ignored return. If failed, warning was issued by fdc_result.
                 * Actual results checked below
                 */
                if ((csb->csb_status = (*csb->csb_rslt &
                    (S0_ICMASK | S0_ECHK | S0_NOTRDY))) != 0)
                        goto nxs_cmpl;
                if (csb->csb_cmd[2] != csb->csb_rslt[3]) {
                        /* at wrong logical cylinder */
                        csb->csb_status = S0_SEKEND;
                        goto nxs_cmpl;
                };
                goto nxs_doit;

        case FXS_DOIT:          /* do original operation */
                ASSERT(fcp->c_timeid == 0);
                time = drv_usectohz(100000 * csb->csb_timer);
                if (time == 0)
                        time = drv_usectohz(2000000);
                fcp->c_timeid = timeout(fdwatch, (void *)fcp, time);
nxs_doit:
                if (fdc_docmd(fcp, csb->csb_cmd, csb->csb_ncmds) == -1) {
                        /* cntlr did not accept command bytes */
                        fdcquiesce(fcp);
                        csb->csb_xstate = FXS_RESET;
                        csb->csb_cmdstat = EIO;
                        break;
                }
                csb->csb_xstate = FXS_DOWT;
                break;

        case FXS_DOWT:          /* operation complete */
                (void) fdc_result(fcp, csb->csb_rslt, csb->csb_nrslts);
                /*
                 * Ignored return. If failed, warning was issued by fdc_result.
                 * Actual results checked below.
                 */
                if (*csb->csb_cmd == FO_SDRV) {
                        csb->csb_status =
                            (*csb->csb_rslt ^ (S3_DRRDY | S3_2SIDE)) &
                            ~(S3_HEAD | S3_UNIT);
                } else {
                        csb->csb_status = *csb->csb_rslt &
                            (S0_ICMASK | S0_ECHK | S0_NOTRDY);
                }
nxs_cmpl:
                if (csb->csb_status)
                        csb->csb_cmdstat = EIO;
                csb->csb_xstate = FXS_END;

                /*  remove watchdog timer if armed and not already triggered */
                if (fcp->c_timeid != 0) {
                        timeout_id_t timeid;
                        timeid = fcp->c_timeid;
                        fcp->c_timeid = 0;
                        mutex_exit(&fcp->c_lock);
                        (void) untimeout(timeid);
                        mutex_enter(&fcp->c_lock);
                }
                break;

        case FXS_KILL:          /* quiesce cntlr by reset */
                fdcquiesce(fcp);
                fcp->c_timeid = timeout(fdwatch, (void *)fcp,
                    drv_usectohz(2000000));
                csb->csb_xstate = FXS_RESET;
                break;

        case FXS_RESET:         /* int from reset */
                for (unit = 0; unit < NFDUN; unit++) {
                        (void) fdcsense_int(fcp, NULL, NULL);
                        fcp->c_curpcyl[unit] = -1;
                }
                if (fcp->c_timeid != 0) {
                        timeout_id_t timeid;
                        timeid = fcp->c_timeid;
                        fcp->c_timeid = 0;
                        mutex_exit(&fcp->c_lock);
                        (void) untimeout(timeid);
                        mutex_enter(&fcp->c_lock);
                }
                csb->csb_xstate = FXS_END;
                break;

        default:
                cmn_err(CE_WARN, "fdc: statemach, unknown state");
                return (-1);
        }
        FCERRPRINT(FDEP_L1, FDEM_EXEC,
            (CE_CONT, "fdc_statemach unit %d: %d -> %d\n",
            csb->csb_drive, csb->csb_oldxs, csb->csb_xstate));
        return (csb->csb_xstate);
}


/*
 * routine to program a command into the floppy disk controller.
 */
int
fdc_docmd(struct fdcntlr *fcp, uchar_t *oplistp, uchar_t count)
{
        int ntries;

        ASSERT(count >= 1);
        FCERRPRINT(FDEP_L0, FDEM_EXEC,
            (CE_CONT, "fdc_docmd: %x %x %x %x %x %x %x %x %x\n",
            oplistp[0], oplistp[1], oplistp[2], oplistp[3], oplistp[4],
            oplistp[5], oplistp[6], oplistp[7], oplistp[8]));

        do {
                ntries = FDC_RQM_RETRY;
                do {
                        if ((inb(fcp->c_regbase + FCR_MSR) & (MS_RQM|MS_DIO))
                            == MS_RQM)
                                break;
                        else
                                drv_usecwait(1);
                } while (--ntries);
                if (ntries == 0) {
                        FCERRPRINT(FDEP_L3, FDEM_EXEC,
                            (CE_WARN, "fdc_docmd: ctlr not ready"));
                        return (-1);
                }
                outb(fcp->c_regbase + FCR_DATA, *oplistp++);
                drv_usecwait(16);       /* See comment in fdc_result() */
        } while (--count);
        return (0);
}


/*
 * Routine to return controller/drive status information.
 * The diskette-controller data-register is read the
 * requested number of times and the results are placed in
 * consecutive memory locations starting at the passed
 * address.
 */
int
fdc_result(struct fdcntlr *fcp, uchar_t *rsltp, uchar_t rcount)
{
        int ntries;
        uchar_t *abresultp = rsltp;
        uchar_t stat;
        int laxative = 7;

        ntries = 10 * FDC_RQM_RETRY;
        do {
                do {
                        if ((inb(fcp->c_regbase + FCR_MSR) &
                            (MS_RQM | MS_DIO)) == (MS_RQM | MS_DIO))
                                break;
                        else
                                drv_usecwait(10);
                } while (--ntries);
                if (!ntries) {
                        FCERRPRINT(FDEP_L3, FDEM_EXEC,
                            (CE_WARN, "fdc_result: ctlr not ready"));
                        return (-2);
                }
                *rsltp++ = inb(fcp->c_regbase + FCR_DATA);

                /*
                 * The PRM suggests waiting for 14.5 us.
                 * Adding a bit more to cover the case of bad calibration
                 * of drv_usecwait().
                 */
                drv_usecwait(16);
                ntries = FDC_RQM_RETRY;
        } while (--rcount);
        while ((inb(fcp->c_regbase + FCR_MSR) & MS_CB) && laxative--) {
                FCERRPRINT(FDEP_L3, FDEM_EXEC,
                    (CE_WARN, "fdc_result: ctlr still busy"));
                /*
                 * try to complete Result phase by purging
                 * result bytes queued for reading
                 */
                *abresultp = S0_IVCMD;
                do {
                        stat = inb(fcp->c_regbase + FCR_MSR) &
                            (MS_RQM | MS_DIO);
                        if (stat == MS_RQM) {
                                /*
                                 * Result phase is complete
                                 * but did we get the results corresponding to
                                 * the command we think we executed?
                                 */
                                return (-1);
                        }
                        if (stat == (MS_RQM | MS_DIO))
                                break;
                        else
                                drv_usecwait(10);
                } while (--ntries);
                if (!ntries || !laxative) {
                        FCERRPRINT(FDEP_L3, FDEM_EXEC,
                            (CE_WARN,
                            "fdc_result: ctlr still busy and not ready"));
                        return (-3);
                }
                (void) inb(fcp->c_regbase + FCR_DATA);

                drv_usecwait(16);       /* See comment above */
                ntries = FDC_RQM_RETRY;
        }
        return (0);
}

/*
 *  Function: get_unit()
 *
 *  Assumptions:  ioaddr is either 0x3f0 or 0x370
 */
static int
get_unit(dev_info_t *dip, int *cntrl_num)
{
        int ioaddr;

        if (get_ioaddr(dip, &ioaddr) != DDI_SUCCESS)
                return (DDI_FAILURE);

        switch (ioaddr) {
        case 0x3f0:
                *cntrl_num = 0;
                break;

        case 0x370:
                *cntrl_num = 1;
                break;

        default:
                return (DDI_FAILURE);
        }
        return (DDI_SUCCESS);
}

static int
get_ioaddr(dev_info_t *dip, int *ioaddr)
{
        int reglen, nregs, i;
        int status = DDI_FAILURE;
        struct {
                int bustype;
                int base;
                int size;
        } *reglist;

        if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
            "reg", (caddr_t)&reglist, &reglen) != DDI_PROP_SUCCESS) {
                cmn_err(CE_WARN, "fdc: reg property not found");
                return (DDI_FAILURE);
        }

        nregs = reglen / sizeof (*reglist);
        for (i = 0; i < nregs; i++) {
                if (reglist[i].bustype == 1) {
                        *ioaddr = reglist[i].base;
                        status = DDI_SUCCESS;
                        break;
                }
        }
        kmem_free(reglist, reglen);

        if (status == DDI_SUCCESS) {
                if (*ioaddr == 0x3f2 || *ioaddr == 0x372) {
                        /*
                         * Some BIOS's (ASUS is one) don't include first
                         * two IO ports in the floppy controller resources.
                         */

                        *ioaddr -= 2; /* step back to 0x3f0 or 0x370 */

                        /*
                         * It would be nice to update the regs property as well
                         * so device pathname contains 3f0 instead of 3f2, but
                         * updating the regs now won't have this effect as that
                         * component of the device pathname has already been
                         * constructed by the ISA nexus driver.
                         *
                         * reglist[i].base -= 2;
                         * reglist[i].size += 2;
                         * dev = makedevice(ddi_driver_major(dip), 0);
                         * ddi_prop_update_int_array(dev, dip, "reg",
                         *    (int *)reglist, reglen / sizeof (int));
                         */
                }
        }

        return (status);
}