root/usr/src/uts/common/io/fd.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 (c) 2018, Joyent, Inc.
 */

/*
 * Floppy Disk driver
 */

/*
 * Set CMOS feature:
 *      CMOS_CONF_MEM:  CMOS memory contains configuration info
 */
#define CMOS_CONF_MEM

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/file.h>
#include <sys/open.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/autoconf.h>
#include <sys/vtoc.h>
#include <sys/dkio.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/kstat.h>
#include <sys/kmem.h>
#include <sys/ddidmareq.h>
#include <sys/fdio.h>
#include <sys/fdc.h>
#include <sys/fd_debug.h>
#include <sys/fdmedia.h>
#include <sys/debug.h>
#include <sys/modctl.h>

/*
 * Local Function Prototypes
 */
static int fd_unit_is_open(struct fdisk *);
static int fdgetlabel(struct fcu_obj *, int);
static void fdstart(struct fcu_obj *);
static int fd_build_label_vtoc(struct fcu_obj *, struct fdisk *,
    struct vtoc *, struct dk_label *);
static void fd_build_user_vtoc(struct fcu_obj *, struct fdisk *,
    struct vtoc *);
static int fd_rawioctl(struct fcu_obj *, int, caddr_t, int);
static void fd_media_watch(void *);

static int fd_open(dev_t *, int, int, cred_t *);
static int fd_close(dev_t, int, int, cred_t *);
static int fd_strategy(struct buf *);
static int fd_read(dev_t, struct uio *, cred_t *);
static int fd_write(dev_t, struct uio *, cred_t *);
static int fd_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int fd_prop_op(dev_t, dev_info_t *, ddi_prop_op_t, int, char *,
    caddr_t, int *);
static int fd_check_media(dev_t dev, enum dkio_state state);
static int fd_get_media_info(struct fcu_obj *fjp, caddr_t buf, int flag);

static struct cb_ops fd_cb_ops = {
        fd_open,                /* open */
        fd_close,               /* close */
        fd_strategy,            /* strategy */
        nodev,                  /* print */
        nodev,                  /* dump */
        fd_read,                /* read */
        fd_write,               /* write */
        fd_ioctl,               /* ioctl */
        nodev,                  /* devmap */
        nodev,                  /* mmap */
        nodev,                  /* segmap */
        nochpoll,               /* poll */
        fd_prop_op,             /* cb_prop_op */
        0,                      /* streamtab  */
        D_NEW | D_MP            /* Driver compatibility flag */
};

static int fd_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int fd_probe(dev_info_t *);
static int fd_attach(dev_info_t *, ddi_attach_cmd_t);
static int fd_detach(dev_info_t *, ddi_detach_cmd_t);

static struct dev_ops fd_ops = {
        DEVO_REV,               /* devo_rev, */
        0,                      /* refcnt  */
        fd_getinfo,             /* getinfo */
        nulldev,                /* identify */
        fd_probe,               /* probe */
        fd_attach,              /* attach */
        fd_detach,              /* detach */
        nodev,                  /* reset */
        &fd_cb_ops,             /* driver operations */
        (struct bus_ops *)0,    /* bus operations */
        NULL,                   /* power */
        ddi_quiesce_not_supported,      /* devo_quiesce */
};


/*
 * static data
 */
static void *fd_state_head;             /* opaque handle top of state structs */
static int fd_check_media_time = 5000000;       /* 5 second state check */

/*
 * error handling
 *
 * for debugging,
 *              set fderrlevel to 1
 *              set fderrmask  to 224  or 644
 */
#ifdef DEBUG
static uint_t fderrmask = FDEM_ALL;
#endif
static int fderrlevel = 5;

#define KIOSP   KSTAT_IO_PTR(fdp->d_iostat)

static struct driver_minor_data {
        char    *name;
        int     minor;
        int     type;
} fd_minor [] = {
        { "a", 0, S_IFBLK},
        { "b", 1, S_IFBLK},
        { "c", 2, S_IFBLK},
        { "a,raw", 0, S_IFCHR},
        { "b,raw", 1, S_IFCHR},
        { "c,raw", 2, S_IFCHR},
        {0}
};

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

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


int
_init(void)
{
        int retval;

        if ((retval = ddi_soft_state_init(&fd_state_head,
            sizeof (struct fdisk) + sizeof (struct fd_drive) +
            sizeof (struct fd_char) + sizeof (struct fdattr), 0)) != 0)
                return (retval);

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

int
_fini(void)
{
        int retval;

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

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


static int
fd_getdrive(dev_t dev, struct fcu_obj **fjpp, struct fdisk **fdpp)
{
        if (fdpp) {
                *fdpp = ddi_get_soft_state(fd_state_head, DRIVE(dev));
                if (*fdpp && fjpp) {
                        *fjpp = (*fdpp)->d_obj;
                        if (*fjpp)
                                return ((*fjpp)->fj_unit);
                }
        }
        return (-1);
}

/*ARGSUSED*/
static int
fd_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
{
        dev_t dev = (dev_t)arg;
        struct fcu_obj *fjp = NULL;
        struct fdisk *fdp = NULL;
        int rval;

        switch (cmd) {
        case DDI_INFO_DEVT2DEVINFO:
                (void) fd_getdrive(dev, &fjp, &fdp);
                /*
                 * Ignoring return value because success is checked by
                 * verifying fjp and fdp and returned unit value is not used.
                 */
                if (fjp && fdp) {
                        *result = fjp->fj_dip;
                        rval = DDI_SUCCESS;
                } else
                        rval = DDI_FAILURE;
                break;
        case DDI_INFO_DEVT2INSTANCE:
                *result = (void *)(uintptr_t)DRIVE(dev);
                rval = DDI_SUCCESS;
                break;
        default:
                rval = DDI_FAILURE;
        }
        return (rval);
}

#ifdef CMOS_CONF_MEM
#define CMOS_ADDR       0x70
#define CMOS_DATA       0x71
#define CMOS_FDRV       0x10
#endif  /* CMOS_CONF_MEM */

static int
fd_probe(dev_info_t *dip)
{
#ifdef CMOS_CONF_MEM
        int cmos;
        int drive_type;
#endif  /* CMOS_CONF_MEM */
        int debug[2];
        int drive_size;
        int len;
        int unit_num;
        char density[8];

        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) {
                fderrlevel = debug[0];
#ifdef DEBUG
                fderrmask = (uint_t)debug[1];
#endif
        }
        len = sizeof (unit_num);
        if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
            DDI_PROP_DONTPASS, "unit", (caddr_t)&unit_num, &len) !=
            DDI_PROP_SUCCESS) {
                FDERRPRINT(FDEP_L3, FDEM_ATTA,
                    (CE_WARN, "fd_probe failed: dip %p", (void *)dip));
                return (DDI_PROBE_FAILURE);
        }

#ifdef CMOS_CONF_MEM
        /* get the cmos memory values quick and dirty */
        outb(CMOS_ADDR, CMOS_FDRV);
        cmos = drive_type = (int)inb(CMOS_DATA);
#endif  /* CMOS_CONF_MEM */

        switch (unit_num) {
#ifdef CMOS_CONF_MEM
        case 0:
                drive_type = drive_type >> 4;
                /* FALLTHROUGH */
        case 1:
                if (cmos && (drive_type & 0x0F)) {
                        break;
                }
                /*
                 * Some enhanced floppy-disk controller adaptor cards
                 * require NO drives defined in the CMOS configuration
                 * memory.
                 * So fall through
                 */
#endif  /* CMOS_CONF_MEM */
                /* FALLTHROUGH */
        default:                /* need to check conf file */
                len = sizeof (density);
                if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
                    DDI_PROP_DONTPASS, "density", (caddr_t)&density, &len) !=
                    DDI_PROP_SUCCESS) {
                        FDERRPRINT(FDEP_L3, FDEM_ATTA,
                            (CE_WARN,
                            "fd_probe failed density: dip %p unit %d",
                            (void *)dip, unit_num));
                        return (DDI_PROBE_FAILURE);
                }
                len = sizeof (drive_size);
                if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
                    DDI_PROP_DONTPASS, "size", (caddr_t)&drive_size, &len) !=
                    DDI_PROP_SUCCESS) {
                        FDERRPRINT(FDEP_L3, FDEM_ATTA,
                            (CE_WARN, "fd_probe failed size: dip %p unit %d",
                            (void *)dip, unit_num));
                        return (DDI_PROBE_FAILURE);
                }
        }
        FDERRPRINT(FDEP_L3, FDEM_ATTA,
            (CE_WARN, "fd_probe dip %p unit %d", (void *)dip, unit_num));
        return (DDI_PROBE_SUCCESS);
}


/* ARGSUSED */
static int
fd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        struct fcu_obj *fjp;
        struct fdisk *fdp;
        struct driver_minor_data *dmdp;
        int mode_3D;
        int drive_num, drive_size, drive_type;
#ifdef CMOS_CONF_MEM
        int cmos;
#endif  /* CMOS_CONF_MEM */
        int len, sig_minor;
        int unit_num;
        char density[8];
        char name[MAXNAMELEN];

        switch (cmd) {
        case DDI_ATTACH:
                len = sizeof (unit_num);
                if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
                    DDI_PROP_DONTPASS, "unit", (caddr_t)&unit_num, &len) !=
                    DDI_PROP_SUCCESS) {
                        FDERRPRINT(FDEP_L3, FDEM_ATTA,
                            (CE_WARN, "fd_attach failed: dip %p", (void *)dip));
                        return (DDI_FAILURE);
                }

#ifdef CMOS_CONF_MEM
                outb(CMOS_ADDR, CMOS_FDRV);
                cmos = drive_type = (int)inb(CMOS_DATA);
#endif  /* CMOS_CONF_MEM */

                switch (unit_num) {
#ifdef CMOS_CONF_MEM
                case 0:
                        drive_type = drive_type >> 4;
                        /* FALLTHROUGH */
                case 1:
                        drive_type = drive_type & 0x0F;
                        if (cmos)
                                break;
                        /*
                         * Some enhanced floppy-disk controller adaptor cards
                         * require NO drives defined in the CMOS configuration
                         * memory.
                         * So fall through
                         */
#endif  /* CMOS_CONF_MEM */
                        /* FALLTHROUGH */
                default:                /* need to check .conf file */
                        drive_type = 0;
                        len = sizeof (density);
                        if (ddi_prop_op(DDI_DEV_T_ANY, dip,
                            PROP_LEN_AND_VAL_BUF, DDI_PROP_DONTPASS, "density",
                            (caddr_t)&density, &len) != DDI_PROP_SUCCESS)
                                density[0] = '\0';
                        len = sizeof (drive_size);
                        if (ddi_prop_op(DDI_DEV_T_ANY, dip,
                            PROP_LEN_AND_VAL_BUF, DDI_PROP_DONTPASS, "size",
                            (caddr_t)&drive_size, &len) != DDI_PROP_SUCCESS)
                                drive_size = 0;
                        if (strcmp(density, "DSDD") == 0) {
                                if (drive_size == 5)
                                        drive_type = 1;
                                else if (drive_size == 3)
                                        drive_type = 3;
                        } else if (strcmp(density, "DSHD") == 0) {
                                if (drive_size == 5)
                                        drive_type = 2;
                                else if (drive_size == 3)
                                        drive_type = 4;
                        } else if (strcmp(density, "DSED") == 0 &&
                            drive_size == 3) {
                                drive_type = 6;
                        }
                        break;
                }
                if (drive_type == 0) {
                        FDERRPRINT(FDEP_L3, FDEM_ATTA,
                            (CE_WARN, "fd_attach failed type: dip %p unit %d",
                            (void *)dip, unit_num));
                        return (DDI_FAILURE);
                }

                drive_num = ddi_get_instance(dip);
                if (ddi_soft_state_zalloc(fd_state_head, drive_num) != 0)
                        return (DDI_FAILURE);
                fdp = ddi_get_soft_state(fd_state_head, drive_num);
                fjp = fdp->d_obj = ddi_get_driver_private(dip);

                mutex_init(&fjp->fj_lock, NULL, MUTEX_DRIVER, *fjp->fj_iblock);
                sema_init(&fdp->d_ocsem, 1, NULL, SEMA_DRIVER, NULL);

                fjp->fj_drive = (struct fd_drive *)(fdp + 1);
                fjp->fj_chars = (struct fd_char *)(fjp->fj_drive + 1);
                fjp->fj_attr = (struct fdattr *)(fjp->fj_chars + 1);

                /*
                 * set default floppy drive characteristics & geometry
                 */
                switch (drive_type) {   /* assume doubled sided */
                case 2:                 /* 5.25 high density */
                        *fjp->fj_drive = dfd_525HD;
                        fdp->d_media = 1<<FMT_5H | 1<<FMT_5D9 | 1<<FMT_5D8 |
                            1<<FMT_5D4 | 1<<FMT_5D16;
                        fdp->d_deffdtype = fdp->d_curfdtype = FMT_5H;
                        break;
                case 4:                 /* 3.5 high density */
                        *fjp->fj_drive = dfd_350HD;
                        fdp->d_media = 1<<FMT_3H | 1<<FMT_3I | 1<<FMT_3D;
                        len = sizeof (mode_3D);
                        if (ddi_prop_op(DDI_DEV_T_ANY, dip,
                            PROP_LEN_AND_VAL_BUF, DDI_PROP_DONTPASS, "mode_3D",
                            (caddr_t)&mode_3D, &len) != DDI_PROP_SUCCESS)
                                mode_3D = 0;
                        if (mode_3D && (fjp->fj_fdc->c_flags & FCFLG_3DMODE))
                                /*
                                 * 3D mode should be enabled only if a dual-
                                 * speed 3.5" high-density drive and a
                                 * supported floppy controller are installed.
                                 */
                                fdp->d_media |= 1 << FMT_3M;
                        fdp->d_deffdtype = fdp->d_curfdtype = FMT_3H;
                        break;
                case 1:                 /* 5.25 double density */
                        *fjp->fj_drive = dfd_525DD;
                        fdp->d_media = 1<<FMT_5D9 | 1<<FMT_5D8 | 1<<FMT_5D4 |
                            1<<FMT_5D16;
                        fdp->d_deffdtype = fdp->d_curfdtype = FMT_5D9;
                        break;
                case 3:                 /* 3.5 double density */
                        *fjp->fj_drive = dfd_350HD;
                        fdp->d_media = 1<<FMT_3D;
                        fdp->d_deffdtype = fdp->d_curfdtype = FMT_3D;
                        break;
                case 5:                 /* 3.5 extended density */
                case 6:
                case 7:
                        *fjp->fj_drive = dfd_350ED;
                        fdp->d_media = 1<<FMT_3E | 1<<FMT_3H | 1<<FMT_3I |
                            1<<FMT_3D;
                        fdp->d_deffdtype = fdp->d_curfdtype = FMT_3E;
                        break;
                case 0:                 /* no drive defined */
                default:
                        goto no_attach;
                }
                *fjp->fj_chars = *defchar[fdp->d_deffdtype];
                *fjp->fj_attr = fdtypes[fdp->d_deffdtype];
                bcopy(fdparts[fdp->d_deffdtype], fdp->d_part,
                    sizeof (struct partition) * NDKMAP);
                fjp->fj_rotspd = fdtypes[fdp->d_deffdtype].fda_rotatespd;

                sig_minor = drive_num << 3;
                for (dmdp = fd_minor; dmdp->name != NULL; dmdp++) {
                        if (ddi_create_minor_node(dip, dmdp->name, dmdp->type,
                            sig_minor | dmdp->minor, DDI_NT_FD, 0)
                            == DDI_FAILURE) {
                                ddi_remove_minor_node(dip, NULL);
                                goto no_attach;
                        }
                }

                FDERRPRINT(FDEP_L3, FDEM_ATTA,
                    (CE_WARN, "fd_attach: dip %p unit %d",
                    (void *)dip, unit_num));
                (void) sprintf(name, "fd%d", drive_num);
                fdp->d_iostat = kstat_create("fd", drive_num, name, "disk",
                    KSTAT_TYPE_IO, 1, KSTAT_FLAG_PERSISTENT);
                if (fdp->d_iostat) {
                        fdp->d_iostat->ks_lock = &fjp->fj_lock;
                        kstat_install(fdp->d_iostat);
                }

                fjp->fj_data = (caddr_t)fdp;
                fjp->fj_flags |= FUNIT_DRVATCH;

                /*
                 * Add a zero-length attribute to tell the world we support
                 * kernel ioctls (for layered drivers)
                 */
                (void) ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP,
                    DDI_KERNEL_IOCTL, NULL, 0);

                /*
                 * We want to get suspend/resume events, so that we can
                 * refuse to suspend when pcfs is mounted.
                 */
                (void) ddi_prop_update_string(DDI_DEV_T_NONE, dip,
                    "pm-hardware-state", "needs-suspend-resume");

                /*
                 * Ignoring return value because, for passed arguments, only
                 * DDI_SUCCESS is returned.
                 */
                ddi_report_dev(dip);
                return (DDI_SUCCESS);

        case DDI_RESUME:
                /* nothing for us to do */
                return (DDI_SUCCESS);

        default:
                return (DDI_FAILURE);
        }
no_attach:
        fjp->fj_drive = NULL;
        fjp->fj_chars = NULL;
        fjp->fj_attr = NULL;
        mutex_destroy(&fjp->fj_lock);
        sema_destroy(&fdp->d_ocsem);
        ddi_soft_state_free(fd_state_head, drive_num);
        FDERRPRINT(FDEP_L3, FDEM_ATTA,
            (CE_WARN, "fd_attach failed: dip %p unit %d",
            (void *)dip, unit_num));
        return (DDI_FAILURE);
}


/* ARGSUSED */
static int
fd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        struct fcu_obj *fjp;
        struct fdisk *fdp;
        int drive_num;
        int rval = DDI_SUCCESS;

        FDERRPRINT(FDEP_L3, FDEM_ATTA, (CE_WARN, "fd_detach dip %p",
            (void *)dip));

        drive_num = ddi_get_instance(dip);
        if (!(fdp = ddi_get_soft_state(fd_state_head, drive_num)))
                return (rval);

        switch (cmd) {
        case DDI_DETACH:
                if (fd_unit_is_open(fdp)) {
                        rval = DDI_FAILURE;
                        break;
                }
                kstat_delete(fdp->d_iostat);
                fdp->d_iostat = NULL;
                fjp = (struct fcu_obj *)fdp->d_obj;
                fjp->fj_flags &= ~FUNIT_DRVATCH;
                fjp->fj_data = NULL;
                fjp->fj_drive = NULL;
                fjp->fj_chars = NULL;
                fjp->fj_attr = NULL;
                ddi_prop_remove_all(dip);
                mutex_destroy(&fjp->fj_lock);
                sema_destroy(&fdp->d_ocsem);
                ddi_soft_state_free(fd_state_head, drive_num);
                break;

        case DDI_SUSPEND:
                /*
                 * Bad, bad, bad things will happen if someone
                 * *changes* the disk in the drive while it is mounted
                 * and the system is suspended.  We have no way to
                 * detect that.  (Undetected filesystem corruption.
                 * Its akin to changing the boot disk while the system
                 * is suspended.  Don't do it!)
                 *
                 * So we refuse to suspend if there is a mounted filesystem.
                 * (We guess this by looking for a block open.  Character
                 * opens are fine.)  This limits some of the usability of
                 * suspend/resume, but it certainly avoids this
                 * potential filesystem corruption from pilot error.
                 * Given the decreasing popularity of floppy media, we
                 * don't see this as much of a limitation.
                 */
                if (fdp->d_regopen[OTYP_BLK]) {
                        cmn_err(CE_NOTE,
                            "Unable to suspend while floppy is in use.");
                        rval = DDI_FAILURE;
                }
                break;

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


static int
fd_part_is_open(struct fdisk *fdp, int part)
{
        int i;

        for (i = 0; i < (OTYPCNT - 1); i++)
                if (fdp->d_regopen[i] & (1 << part))
                        return (1);
        return (0);
}

static int
fd_unit_is_open(struct fdisk *fdp)
{
        int i;

        for (i = 0; i < NDKMAP; i++)
                if (fdp->d_lyropen[i])
                        return (1);
        for (i = 0; i < (OTYPCNT - 1); i++)
                if (fdp->d_regopen[i])
                        return (1);
        return (0);
}

/*ARGSUSED*/
static int
fd_open(dev_t *devp, int flag, int otyp, cred_t *cred_p)
{
        struct fcu_obj *fjp = NULL;
        struct fdisk *fdp = NULL;
        struct partition *pp;
        dev_t dev;
        int part, unit;
        int part_is_open;
        int rval;
        uint_t pbit;

        dev = *devp;
        unit = fd_getdrive(dev, &fjp, &fdp);
        if (!fjp || !fdp)
                return (ENXIO);
        part = PARTITION(dev);
        pbit = 1 << part;
        pp = &fdp->d_part[part];

        /*
         * Serialize opens/closes
         */
        sema_p(&fdp->d_ocsem);
        FDERRPRINT(FDEP_L1, FDEM_OPEN,
            (CE_CONT, "fd_open: fd%d part %d flag %x otype %x\n", DRIVE(dev),
            part, flag, otyp));

        /*
         * Check for previous exclusive open, or trying to exclusive open
         * An "exclusive open" on any partition is not guaranteed to
         * protect against opens on another partition that overlaps it.
         */
        if (otyp == OTYP_LYR) {
                part_is_open = (fdp->d_lyropen[part] != 0);
        } else {
                part_is_open = fd_part_is_open(fdp, part);
        }
        if ((fdp->d_exclmask & pbit) || ((flag & FEXCL) && part_is_open)) {
                FDERRPRINT(FDEP_L0, FDEM_OPEN, (CE_CONT,
                    "fd_open: exclparts %lx openparts %lx lyrcnt %lx pbit %x\n",
                    fdp->d_exclmask, fdp->d_regopen[otyp], fdp->d_lyropen[part],
                    pbit));
                sema_v(&fdp->d_ocsem);
                return (EBUSY);
        }

        /*
         * Ensure that drive is recalibrated on first open of new diskette.
         */
        fjp->fj_ops->fco_select(fjp, unit, 1);
        if (fjp->fj_ops->fco_getchng(fjp, unit) != 0) {
                if (fjp->fj_ops->fco_rcseek(fjp, unit, -1, 0)) {
                        FDERRPRINT(FDEP_L2, FDEM_OPEN,
                            (CE_NOTE, "fd_open fd%d: not ready", DRIVE(dev)));
                        fjp->fj_ops->fco_select(fjp, unit, 0);
                        sema_v(&fdp->d_ocsem);
                        return (ENXIO);
                }
                fjp->fj_flags &= ~(FUNIT_LABELOK | FUNIT_UNLABELED);
        }
        if (flag & (FNDELAY | FNONBLOCK)) {
                /* don't attempt access, just return successfully */
                fjp->fj_ops->fco_select(fjp, unit, 0);
                goto out;
        }

        /*
         * auto-sense the density/format of the diskette
         */
        rval = fdgetlabel(fjp, unit);
        fjp->fj_ops->fco_select(fjp, unit, 0);
        if (rval) {
                /* didn't find label (couldn't read anything) */
                FDERRPRINT(FDEP_L2, FDEM_OPEN,
                    (CE_NOTE, "fd%d: drive not ready", DRIVE(dev)));
                sema_v(&fdp->d_ocsem);
                return (EIO);
        }
        /* check partition */
        if (pp->p_size == 0) {
                sema_v(&fdp->d_ocsem);
                return (ENXIO);
        }
        /*
         * if opening for writing, check write protect on diskette
         */
        if ((flag & FWRITE) && (fdp->d_obj->fj_flags & FUNIT_WPROT)) {
                sema_v(&fdp->d_ocsem);
                return (EROFS);
        }

out:
        /*
         * mark open as having succeeded
         */
        if (flag & FEXCL)
                fdp->d_exclmask |= pbit;
        if (otyp == OTYP_LYR)
                fdp->d_lyropen[part]++;
        else
                fdp->d_regopen[otyp] |= 1 << part;

        sema_v(&fdp->d_ocsem);
        return (0);
}

/*
 * fdgetlabel - read the SunOS label off the diskette
 *      if it can read a valid label it does so, else it will use a
 *      default.  If it can`t read the diskette - that is an error.
 *
 * RETURNS: 0 for ok - meaning that it could at least read the device,
 *      !0 for error XXX TBD NYD error codes
 */
static int
fdgetlabel(struct fcu_obj *fjp, int unit)
{
        struct dk_label *label;
        struct fdisk *fdp;
        char *newlabel;
        short *sp;
        short count;
        short xsum;
        int tries, try_this;
        uint_t nexttype;
        int rval;
        short oldlvl;
        int i;

        FDERRPRINT(FDEP_L0, FDEM_GETL,
            (CE_CONT, "fdgetlabel fd unit %d\n", unit));
        fdp = (struct fdisk *)fjp->fj_data;
        fjp->fj_flags &= ~(FUNIT_UNLABELED);

        /*
         * get some space to play with the label
         */
        label = kmem_zalloc(sizeof (struct dk_label), KM_SLEEP);
        FDERRPRINT(FDEP_L0, FDEM_GETL, (CE_CONT,
            "fdgetlabel fd unit %d kmem_zalloc: ptr = %p, size = %lx\n",
            unit, (void *)label, (size_t)sizeof (struct dk_label)));

        /*
         * read block 0 (0/0/1) to find the label
         * (disk is potentially not present or unformatted)
         */
        /* noerrprint since this is a private cmd */
        oldlvl = fderrlevel;
        fderrlevel = FDEP_LMAX;
        /*
         * try different characteristics (ie densities)
         *
         * if fdp->d_curfdtype is -1 then the current characteristics
         * were set by ioctl and need to try it as well as everything
         * in the table
         */
        nexttype = fdp->d_deffdtype;
        try_this = 1;           /* always try the current characteristics */

        rval = ENXIO;
        for (tries = nfdtypes; tries; tries--) {
                if (try_this) {
                        fjp->fj_flags &= ~FUNIT_CHAROK;

                        /* try reading last sector of cyl 1, head 0 */
                        if (!(rval = fjp->fj_ops->fco_rw(fjp, unit,
                            FDREAD, 1, 0, fjp->fj_chars->fdc_secptrack,
                            (caddr_t)label,
                            sizeof (struct dk_label))) &&
                            /* and last sector plus 1 of cylinder 1 */
                            fjp->fj_ops->fco_rw(fjp, unit, FDREAD, 1,
                            0, fjp->fj_chars->fdc_secptrack + 1,
                            (caddr_t)label,
                            sizeof (struct dk_label)) &&
                            /* and label sector on cylinder 0 */
                            !(rval = fjp->fj_ops->fco_rw(fjp, unit,
                            FDREAD, 0, 0, 1, (caddr_t)label,
                            sizeof (struct dk_label))))
                                break;
                        if (rval == ENXIO)
                                break;
                }
                /*
                 * try the next entry in the characteristics tbl
                 */
                fdp->d_curfdtype = (signed char)nexttype;
                nexttype = (nexttype + 1) % nfdtypes;
                if ((1 << fdp->d_curfdtype) & fdp->d_media) {
                        *fjp->fj_chars = *defchar[fdp->d_curfdtype];
                        *fjp->fj_attr = fdtypes[fdp->d_curfdtype];
                        bcopy(fdparts[fdp->d_curfdtype], fdp->d_part,
                            sizeof (struct partition) * NDKMAP);
                        /*
                         * check for a double_density diskette
                         * in a high_density 5.25" drive
                         */
                        if (fjp->fj_chars->fdc_transfer_rate == 250 &&
                            fjp->fj_rotspd > fjp->fj_attr->fda_rotatespd) {
                                /*
                                 * yes - adjust transfer rate since we don't
                                 * know if we have a 5.25" dual-speed drive
                                 */
                                fjp->fj_attr->fda_rotatespd = 360;
                                fjp->fj_chars->fdc_transfer_rate = 300;
                                fjp->fj_chars->fdc_medium = 5;
                        }
                        if ((2 * fjp->fj_chars->fdc_ncyl) ==
                            defchar[fdp->d_deffdtype]->fdc_ncyl) {
                                /* yes - adjust steps per cylinder */
                                fjp->fj_chars->fdc_steps = 2;
                        } else
                                fjp->fj_chars->fdc_steps = 1;
                        try_this = 1;
                } else
                        try_this = 0;
        }
        fderrlevel = oldlvl;    /* print errors again */

        if (rval) {
                fdp->d_curfdtype = fdp->d_deffdtype;
                goto out;                       /* couldn't read anything */
        }

        FDERRPRINT(FDEP_L0, FDEM_GETL,
            (CE_CONT,
            "fdgetlabel fd unit=%d ncyl=%d nsct=%d step=%d rpm=%d intlv=%d\n",
            unit, fjp->fj_chars->fdc_ncyl, fjp->fj_chars->fdc_secptrack,
            fjp->fj_chars->fdc_steps, fjp->fj_attr->fda_rotatespd,
            fjp->fj_attr->fda_intrlv));

        /*
         * _something_ was read  -  look for unixtype label
         */
        if (label->dkl_magic != DKL_MAGIC ||
            label->dkl_vtoc.v_sanity != VTOC_SANE) {
                /* not a label - no magic number */
                goto nolabel;   /* no errors, but no label */
        }

        count = sizeof (struct dk_label) / sizeof (short);
        sp = (short *)label;
        xsum = 0;
        while (count--)
                xsum ^= *sp++;  /* should add up to 0 */
        if (xsum) {
                /* not a label - checksum didn't compute */
                goto nolabel;   /* no errors, but no label */
        }

        /*
         * the SunOS label overrides current diskette characteristics
         */
        fjp->fj_chars->fdc_ncyl = label->dkl_pcyl;
        fjp->fj_chars->fdc_nhead = label->dkl_nhead;
        fjp->fj_chars->fdc_secptrack = (label->dkl_nsect * DEV_BSIZE) /
            fjp->fj_chars->fdc_sec_size;
        if (defchar[fdp->d_deffdtype]->fdc_ncyl == 2 * fjp->fj_chars->fdc_ncyl)
                fjp->fj_chars->fdc_steps = 2;
        else
                fjp->fj_chars->fdc_steps = 1;

        fjp->fj_attr->fda_rotatespd = label->dkl_rpm;
        fjp->fj_attr->fda_intrlv = label->dkl_intrlv;

        fdp->d_vtoc_version = label->dkl_vtoc.v_version;
        bcopy(label->dkl_vtoc.v_volume, fdp->d_vtoc_volume, LEN_DKL_VVOL);
        bcopy(label->dkl_vtoc.v_asciilabel,
            fdp->d_vtoc_asciilabel, LEN_DKL_ASCII);
        /*
         * logical partitions
         */
        for (i = 0; i < NDKMAP; i++) {
                fdp->d_part[i].p_tag = label->dkl_vtoc.v_part[i].p_tag;
                fdp->d_part[i].p_flag = label->dkl_vtoc.v_part[i].p_flag;
                fdp->d_part[i].p_start = label->dkl_vtoc.v_part[i].p_start;
                fdp->d_part[i].p_size = label->dkl_vtoc.v_part[i].p_size;

                fdp->d_vtoc_timestamp[i] = label->dkl_vtoc.timestamp[i];
        }

        fjp->fj_flags |= FUNIT_LABELOK;
        goto out;

nolabel:
        /*
         * if not found, fill in label info from default (mark default used)
         */
        if (fdp->d_media & (1<<FMT_3D))
                newlabel = deflabel_35;
        else /* if (fdp->d_media & (1<<FMT_5D9)) */
                newlabel = deflabel_525;
        bzero(fdp->d_vtoc_volume, LEN_DKL_VVOL);
        (void) sprintf(fdp->d_vtoc_asciilabel, newlabel,
            fjp->fj_chars->fdc_ncyl, fjp->fj_chars->fdc_nhead,
            fjp->fj_chars->fdc_secptrack);
        fjp->fj_flags |= FUNIT_UNLABELED;

out:
        kmem_free(label, sizeof (struct dk_label));
        return (rval);
}


/*ARGSUSED*/
static int
fd_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
{
        struct fcu_obj *fjp = NULL;
        struct fdisk *fdp = NULL;
        int part, part_is_closed;

#ifdef DEBUG
        int unit;
#define DEBUG_ASSIGN    unit=
#else
#define DEBUG_ASSIGN    (void)
#endif

        DEBUG_ASSIGN fd_getdrive(dev, &fjp, &fdp);
        /*
         * Ignoring return in non DEBUG mode because success is checked by
         * verifying fjp and fdp and returned unit value is not used.
         */
        if (!fjp || !fdp)
                return (ENXIO);
        part = PARTITION(dev);

        sema_p(&fdp->d_ocsem);
        FDERRPRINT(FDEP_L1, FDEM_CLOS,
            (CE_CONT, "fd_close: fd unit %d part %d otype %x\n",
            unit, part, otyp));

        if (otyp == OTYP_LYR) {
                if (fdp->d_lyropen[part])
                        fdp->d_lyropen[part]--;
                part_is_closed = (fdp->d_lyropen[part] == 0);
        } else {
                fdp->d_regopen[otyp] &= ~(1<<part);
                part_is_closed = 1;
        }
        if (part_is_closed) {
                if (part == 2 && fdp->d_exclmask&(1<<part))
                        fdp->d_exclmask = 0;
                else
                        fdp->d_exclmask &= ~(1<<part);
                FDERRPRINT(FDEP_L0, FDEM_CLOS,
                    (CE_CONT,
                    "fd_close: exclparts %lx openparts %lx lyrcnt %lx\n",
                    fdp->d_exclmask, fdp->d_regopen[otyp],
                    fdp->d_lyropen[part]));

                if (fd_unit_is_open(fdp) == 0)
                        fdp->d_obj->fj_flags &= ~FUNIT_CHANGED;
        }
        sema_v(&fdp->d_ocsem);
        return (0);
}

/* ARGSUSED */
static int
fd_read(dev_t dev, struct uio *uio, cred_t *cred_p)
{
        return (physio(fd_strategy, NULL, dev, B_READ, minphys, uio));
}

/* ARGSUSED */
static int
fd_write(dev_t dev, struct uio *uio, cred_t *cred_p)
{
        return (physio(fd_strategy, NULL, dev, B_WRITE, minphys, uio));
}

/*
 * fd_strategy
 *      checks operation, hangs buf struct off fdcntlr, calls fdstart
 *      if not already busy.  Note that if we call start, then the operation
 *      will already be done on return (start sleeps).
 */
static int
fd_strategy(struct buf *bp)
{
        struct fcu_obj *fjp;
        struct fdisk *fdp;
        struct partition *pp;

        FDERRPRINT(FDEP_L1, FDEM_STRA,
            (CE_CONT, "fd_strategy: bp = 0x%p, dev = 0x%lx\n",
            (void *)bp, bp->b_edev));

        (void) fd_getdrive(bp->b_edev, &fjp, &fdp);

        /*
         * Ignoring return because device exist.
         * Returned unit value is not used.
         */
        pp = &fdp->d_part[PARTITION(bp->b_edev)];

        if (fjp->fj_chars->fdc_sec_size > NBPSCTR && (bp->b_blkno & 1))  {
                FDERRPRINT(FDEP_L3, FDEM_STRA,
                    (CE_WARN, "fd%d: block %ld is not start of sector!",
                    DRIVE(bp->b_edev), (long)bp->b_blkno));
                bp->b_error = EINVAL;
                goto bad;
        }

        if ((bp->b_blkno > pp->p_size)) {
                FDERRPRINT(FDEP_L3, FDEM_STRA,
                    (CE_WARN, "fd%d: block %ld is past the end! (nblk=%ld)",
                    DRIVE(bp->b_edev), (long)bp->b_blkno, pp->p_size));
                bp->b_error = ENOSPC;
                goto bad;
        }

        /* if at end of file, skip out now */
        if (bp->b_blkno == pp->p_size) {
                if ((bp->b_flags & B_READ) == 0) {
                        /* a write needs to get an error! */
                        bp->b_error = ENOSPC;
                        goto bad;
                }
                bp->b_resid = bp->b_bcount;
                biodone(bp);
                return (0);
        }

        /* if operation not a multiple of sector size, is error! */
        if (bp->b_bcount % fjp->fj_chars->fdc_sec_size)  {
                FDERRPRINT(FDEP_L3, FDEM_STRA,
                    (CE_WARN, "fd%d: count %ld must be a multiple of %d",
                    DRIVE(bp->b_edev), bp->b_bcount,
                    fjp->fj_chars->fdc_sec_size));
                bp->b_error = EINVAL;
                goto bad;
        }

        /*
         * Put the buf request in the drive's queue, FIFO.
         */
        bp->av_forw = 0;
        mutex_enter(&fjp->fj_lock);
        if (fdp->d_iostat)
                kstat_waitq_enter(KIOSP);
        if (fdp->d_actf)
                fdp->d_actl->av_forw = bp;
        else
                fdp->d_actf = bp;
        fdp->d_actl = bp;
        if (!(fjp->fj_flags & FUNIT_BUSY)) {
                fdstart(fjp);
        }
        mutex_exit(&fjp->fj_lock);
        return (0);

bad:
        bp->b_resid = bp->b_bcount;
        bp->b_flags |= B_ERROR;
        biodone(bp);
        return (0);
}

/*
 * fdstart
 *      called from fd_strategy() or from fdXXXX() to setup and
 *      start operations of read or write only (using buf structs).
 *      Because the chip doesn't handle crossing cylinder boundaries on
 *      the fly, this takes care of those boundary conditions.  Note that
 *      it sleeps until the operation is done *within fdstart* - so that
 *      when fdstart returns, the operation is already done.
 */
static void
fdstart(struct fcu_obj *fjp)
{
        struct buf *bp;
        struct fdisk *fdp = (struct fdisk *)fjp->fj_data;
        struct fd_char *chp;
        struct partition *pp;
        uint_t ptend;
        uint_t bincyl;          /* (the number of the desired) block in cyl. */
        uint_t blk, len, tlen;
        uint_t secpcyl;         /* number of sectors per cylinder */
        int cyl, head, sect;
        int sctrshft, unit;
        caddr_t addr;

        ASSERT(MUTEX_HELD(&fjp->fj_lock));
        fjp->fj_flags |= FUNIT_BUSY;

        while ((bp = fdp->d_actf) != NULL) {
                fdp->d_actf = bp->av_forw;
                fdp->d_current = bp;
                if (fdp->d_iostat) {
                        kstat_waitq_to_runq(KIOSP);
                }
                mutex_exit(&fjp->fj_lock);

                FDERRPRINT(FDEP_L0, FDEM_STRT,
                    (CE_CONT, "fdstart: bp=0x%p blkno=0x%lx bcount=0x%lx\n",
                    (void *)bp, (long)bp->b_blkno, bp->b_bcount));
                bp->b_flags &= ~B_ERROR;
                bp->b_error = 0;
                bp->b_resid = bp->b_bcount;     /* init resid */

                ASSERT(DRIVE(bp->b_edev) == ddi_get_instance(fjp->fj_dip));
                unit = fjp->fj_unit;
                fjp->fj_ops->fco_select(fjp, unit, 1);

                bp_mapin(bp);                   /* map in buffers */

                pp = &fdp->d_part[PARTITION(bp->b_edev)];
                /* starting blk adjusted for the partition */
                blk = bp->b_blkno + pp->p_start;
                ptend = pp->p_start + pp->p_size;   /* end of the partition */

                chp = fjp->fj_chars;
                secpcyl = chp->fdc_nhead * chp->fdc_secptrack;
                switch (chp->fdc_sec_size) {
                /* convert logical block numbers to sector numbers */
                case 1024:
                        sctrshft = SCTRSHFT + 1;
                        blk >>= 1;
                        ptend >>= 1;
                        break;
                default:
                case NBPSCTR:
                        sctrshft = SCTRSHFT;
                        break;
                case 256:
                        sctrshft = SCTRSHFT - 1;
                        blk <<= 1;
                        ptend <<= 1;
                        break;
                }

                /*
                 * If off the end, limit to actual amount that
                 * can be transferred.
                 */
                if ((blk + (bp->b_bcount >> sctrshft)) > ptend)
                        /* to end of partition */
                        len = (ptend - blk) << sctrshft;
                else
                        len = bp->b_bcount;
                addr = bp->b_un.b_addr;         /* data buffer address */

                /*
                 * now we have the real start blk, addr and len for xfer op
                 */
                while (len != 0) {
                        /* start cyl of req */
                        cyl = blk / secpcyl;
                        bincyl = blk % secpcyl;
                        /* start head of req */
                        head = bincyl / chp->fdc_secptrack;
                        /* start sector of req */
                        sect = (bincyl % chp->fdc_secptrack) + 1;
                        /*
                         * If the desired block and length will go beyond the
                         * cylinder end, then limit it to the cylinder end.
                         */
                        if (bp->b_flags & B_READ) {
                                if (len > ((secpcyl - bincyl) << sctrshft))
                                        tlen = (secpcyl - bincyl) << sctrshft;
                                else
                                        tlen = len;
                        } else {
                                if (len >
                                    ((chp->fdc_secptrack - sect + 1) <<
                                    sctrshft))
                                        tlen =
                                            (chp->fdc_secptrack - sect + 1) <<
                                            sctrshft;
                                else
                                        tlen = len;
                        }

                        FDERRPRINT(FDEP_L0, FDEM_STRT, (CE_CONT,
                            "  blk 0x%x addr 0x%p len 0x%x "
                            "cyl %d head %d sec %d\n  resid 0x%lx, tlen %d\n",
                            blk, (void *)addr, len, cyl, head, sect,
                            bp->b_resid, tlen));

                        /*
                         * (try to) do the operation - failure returns an errno
                         */
                        bp->b_error = fjp->fj_ops->fco_rw(fjp, unit,
                            bp->b_flags & B_READ, cyl, head, sect, addr, tlen);
                        if (bp->b_error != 0) {
                                FDERRPRINT(FDEP_L3, FDEM_STRT, (CE_WARN,
                                    "fdstart: bad exec of bp: 0x%p, err=%d",
                                    (void *)bp, bp->b_error));
                                bp->b_flags |= B_ERROR;
                                break;
                        }
                        blk += tlen >> sctrshft;
                        len -= tlen;
                        addr += tlen;
                        bp->b_resid -= tlen;
                }
                FDERRPRINT(FDEP_L0, FDEM_STRT,
                    (CE_CONT, "fdstart done: b_resid %lu, b_count %lu\n",
                    bp->b_resid, bp->b_bcount));
                if (fdp->d_iostat) {
                        if (bp->b_flags & B_READ) {
                                KIOSP->reads++;
                                KIOSP->nread += (bp->b_bcount - bp->b_resid);
                        } else {
                                KIOSP->writes++;
                                KIOSP->nwritten += (bp->b_bcount - bp->b_resid);
                        }
                        kstat_runq_exit(KIOSP);
                }
                bp_mapout(bp);
                biodone(bp);

                fjp->fj_ops->fco_select(fjp, unit, 0);
                mutex_enter(&fjp->fj_lock);
                fdp->d_current = 0;
        }
        fjp->fj_flags ^= FUNIT_BUSY;
}

/* ARGSUSED */
static int
fd_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred_p,
    int *rval_p)
{
        union {
                struct dk_cinfo dki;
                struct dk_geom dkg;
                struct dk_allmap dka;
                struct fd_char fdchar;
                struct fd_drive drvchar;
                int     temp;
        } cpy;
        struct vtoc vtoc;
        struct fcu_obj *fjp = NULL;
        struct fdisk *fdp = NULL;
        struct dk_map *dmp;
        struct dk_label *label;
        int nblks, part, unit;
        int rval = 0;
        enum dkio_state state;

        unit = fd_getdrive(dev, &fjp, &fdp);
        if (!fjp || !fdp)
                return (ENXIO);

        FDERRPRINT(FDEP_L1, FDEM_IOCT,
            (CE_CONT, "fd_ioctl fd unit %d: cmd %x, arg %lx\n",
            unit, cmd, arg));

        switch (cmd) {
        case DKIOCINFO:
                fjp->fj_ops->fco_dkinfo(fjp, &cpy.dki);
                cpy.dki.dki_cnum = FDCTLR(fjp->fj_unit);
                cpy.dki.dki_unit = FDUNIT(fjp->fj_unit);
                cpy.dki.dki_partition = PARTITION(dev);
                if (ddi_copyout(&cpy.dki, (void *)arg, sizeof (cpy.dki), flag))
                        rval = EFAULT;
                break;

        case DKIOCG_PHYGEOM:
        case DKIOCG_VIRTGEOM:
                cpy.dkg.dkg_nsect = fjp->fj_chars->fdc_secptrack;
                goto get_geom;
        case DKIOCGGEOM:
                if (fjp->fj_flags & FUNIT_LABELOK)
                        cpy.dkg.dkg_nsect = (fjp->fj_chars->fdc_secptrack *
                            fjp->fj_chars->fdc_sec_size) / DEV_BSIZE;
                else
                        cpy.dkg.dkg_nsect = fjp->fj_chars->fdc_secptrack;
get_geom:
                cpy.dkg.dkg_pcyl = fjp->fj_chars->fdc_ncyl;
                cpy.dkg.dkg_ncyl = fjp->fj_chars->fdc_ncyl;
                cpy.dkg.dkg_nhead = fjp->fj_chars->fdc_nhead;
                cpy.dkg.dkg_intrlv = fjp->fj_attr->fda_intrlv;
                cpy.dkg.dkg_rpm = fjp->fj_attr->fda_rotatespd;
                cpy.dkg.dkg_read_reinstruct =
                    (int)(cpy.dkg.dkg_nsect * cpy.dkg.dkg_rpm * 4) / 60000;
                cpy.dkg.dkg_write_reinstruct = cpy.dkg.dkg_read_reinstruct;
                if (ddi_copyout(&cpy.dkg, (void *)arg, sizeof (cpy.dkg), flag))
                        rval = EFAULT;
                break;

        case DKIOCSGEOM:
                if (ddi_copyin((void *)arg, &cpy.dkg,
                    sizeof (struct dk_geom), flag)) {
                        rval = EFAULT;
                        break;
                }
                mutex_enter(&fjp->fj_lock);
                fjp->fj_chars->fdc_ncyl = cpy.dkg.dkg_ncyl;
                fjp->fj_chars->fdc_nhead = cpy.dkg.dkg_nhead;
                fjp->fj_chars->fdc_secptrack = cpy.dkg.dkg_nsect;
                fjp->fj_attr->fda_intrlv = cpy.dkg.dkg_intrlv;
                fjp->fj_attr->fda_rotatespd = cpy.dkg.dkg_rpm;
                fdp->d_curfdtype = -1;
                mutex_exit(&fjp->fj_lock);
                break;

        /*
         * return the map of all logical partitions
         */
        case DKIOCGAPART:
                /*
                 * Note the conversion from starting sector number
                 * to starting cylinder number.
                 * Return error if division results in a remainder.
                 */
                nblks = fjp->fj_chars->fdc_nhead * fjp->fj_chars->fdc_secptrack;

#ifdef _MULTI_DATAMODEL
                switch (ddi_model_convert_from(flag & FMODELS)) {
                case DDI_MODEL_ILP32:
                {
                        struct dk_allmap32 dka32;

                        for (part = 0; part < NDKMAP; part++) {
                                if ((fdp->d_part[part].p_start % nblks) != 0)
                                        return (EINVAL);
                                dka32.dka_map[part].dkl_cylno =
                                    fdp->d_part[part].p_start / nblks;
                                dka32.dka_map[part].dkl_nblk =
                                    fdp->d_part[part].p_size;
                        }

                        if (ddi_copyout(&dka32, (void *)arg,
                            sizeof (struct dk_allmap32), flag))
                                rval = EFAULT;

                        break;
                }
                case DDI_MODEL_NONE:

#endif /* _MULTI_DATAMODEL */

                        dmp = (struct dk_map *)&cpy.dka;
                        for (part = 0; part < NDKMAP; part++) {
                                if ((fdp->d_part[part].p_start % nblks) != 0)
                                        return (EINVAL);
                                dmp->dkl_cylno =
                                    fdp->d_part[part].p_start / nblks;
                                dmp->dkl_nblk = fdp->d_part[part].p_size;
                                dmp++;
                        }

                        if (ddi_copyout(&cpy.dka, (void *)arg,
                            sizeof (struct dk_allmap), flag))
                                rval = EFAULT;
#ifdef _MULTI_DATAMODEL
                        break;

                }
#endif /* _MULTI_DATAMODEL */

                break;

        /*
         * Set the map of all logical partitions
         */
        case DKIOCSAPART:

#ifdef _MULTI_DATAMODEL
                switch (ddi_model_convert_from(flag & FMODELS)) {
                case DDI_MODEL_ILP32:
                {
                        struct dk_allmap32 dka32;

                        if (ddi_copyin((void *)arg, &dka32,
                            sizeof (dka32), flag)) {
                                rval = EFAULT;
                                break;
                        }
                        for (part = 0; part < NDKMAP; part++) {
                                cpy.dka.dka_map[part].dkl_cylno =
                                    dka32.dka_map[part].dkl_cylno;
                                cpy.dka.dka_map[part].dkl_nblk =
                                    dka32.dka_map[part].dkl_nblk;
                        }
                        break;
                }
                case DDI_MODEL_NONE:

#endif /* _MULTI_DATAMODEL */
                if (ddi_copyin((void *)arg, &cpy.dka, sizeof (cpy.dka), flag))
                        rval = EFAULT;
#ifdef _MULTI_DATAMODEL

                        break;
                }
#endif /* _MULTI_DATAMODEL */

                if (rval != 0)
                        break;

                dmp = (struct dk_map *)&cpy.dka;
                nblks = fjp->fj_chars->fdc_nhead *
                    fjp->fj_chars->fdc_secptrack;
                mutex_enter(&fjp->fj_lock);
                /*
                 * Note the conversion from starting cylinder number
                 * to starting sector number.
                 */
                for (part = 0; part < NDKMAP; part++) {
                        fdp->d_part[part].p_start = dmp->dkl_cylno *
                            nblks;
                        fdp->d_part[part].p_size = dmp->dkl_nblk;
                        dmp++;
                }
                mutex_exit(&fjp->fj_lock);

                break;

        case DKIOCGVTOC:
                mutex_enter(&fjp->fj_lock);

                /*
                 * Exit if the diskette has no label.
                 * Also, get the label to make sure the correct one is
                 * being used since the diskette may have changed
                 */
                fjp->fj_ops->fco_select(fjp, unit, 1);
                rval = fdgetlabel(fjp, unit);
                fjp->fj_ops->fco_select(fjp, unit, 0);
                if (rval) {
                        mutex_exit(&fjp->fj_lock);
                        rval = EINVAL;
                        break;
                }

                fd_build_user_vtoc(fjp, fdp, &vtoc);
                mutex_exit(&fjp->fj_lock);

#ifdef _MULTI_DATAMODEL
                switch (ddi_model_convert_from(flag & FMODELS)) {
                case DDI_MODEL_ILP32:
                {
                        struct vtoc32   vtoc32;

                        vtoctovtoc32(vtoc, vtoc32);

                        if (ddi_copyout(&vtoc32, (void *)arg,
                            sizeof (vtoc32), flag))
                                rval = EFAULT;

                        break;
                }
                case DDI_MODEL_NONE:

#endif /* _MULTI_DATAMODEL */
                        if (ddi_copyout(&vtoc, (void *)arg,
                            sizeof (vtoc), flag))
                                rval = EFAULT;
#ifdef _MULTI_DATAMODEL
                        break;
                }
#endif /* _MULTI_DATAMODEL */

                break;

        case DKIOCSVTOC:

#ifdef _MULTI_DATAMODEL
                switch (ddi_model_convert_from(flag & FMODELS)) {
                case DDI_MODEL_ILP32:
                {
                        struct vtoc32   vtoc32;

                        if (ddi_copyin((void *)arg, &vtoc32,
                            sizeof (vtoc32), flag)) {
                                rval = EFAULT;
                                break;
                        }

                        vtoc32tovtoc(vtoc32, vtoc);

                        break;
                }
                case DDI_MODEL_NONE:

#endif /* _MULTI_DATAMODEL */
                        if (ddi_copyin((void *)arg, &vtoc, sizeof (vtoc), flag))
                                rval = EFAULT;
#ifdef _MULTI_DATAMODEL
                        break;
                }
#endif /* _MULTI_DATAMODEL */

                if (rval != 0)
                        break;


                label = kmem_zalloc(sizeof (struct dk_label), KM_SLEEP);

                mutex_enter(&fjp->fj_lock);

                if ((rval = fd_build_label_vtoc(fjp, fdp, &vtoc, label)) == 0) {
                        fjp->fj_ops->fco_select(fjp, unit, 1);
                        rval = fjp->fj_ops->fco_rw(fjp, unit, FDWRITE,
                            0, 0, 1, (caddr_t)label, sizeof (struct dk_label));
                        fjp->fj_ops->fco_select(fjp, unit, 0);
                }
                mutex_exit(&fjp->fj_lock);
                kmem_free(label, sizeof (struct dk_label));
                break;

        case DKIOCSTATE:
                FDERRPRINT(FDEP_L1, FDEM_IOCT,
                    (CE_CONT, "fd_ioctl fd unit %d: DKIOCSTATE\n", unit));

                if (ddi_copyin((void *)arg, &state, sizeof (int), flag)) {
                        rval = EFAULT;
                        break;
                }

                rval = fd_check_media(dev, state);

                if (ddi_copyout(&fdp->d_media_state, (void *)arg,
                    sizeof (int), flag))
                        rval = EFAULT;
                break;

        case FDIOGCHAR:
                if (ddi_copyout(fjp->fj_chars, (void *)arg,
                    sizeof (struct fd_char), flag))
                        rval = EFAULT;
                break;

        case FDIOSCHAR:
                if (ddi_copyin((void *)arg, &cpy.fdchar,
                    sizeof (struct fd_char), flag)) {
                        rval = EFAULT;
                        break;
                }
                switch (cpy.fdchar.fdc_transfer_rate) {
                case 417:
                        if ((fdp->d_media & (1 << FMT_3M)) == 0) {
                                cmn_err(CE_CONT,
                                    "fdioschar:Medium density not supported\n");
                                rval = EINVAL;
                                break;
                        }
                        mutex_enter(&fjp->fj_lock);
                        fjp->fj_attr->fda_rotatespd = 360;
                        mutex_exit(&fjp->fj_lock);
                        /* cpy.fdchar.fdc_transfer_rate = 500; */
                        /* FALLTHROUGH */
                case 1000:
                case 500:
                case 300:
                case 250:
                        mutex_enter(&fjp->fj_lock);
                        *(fjp->fj_chars) = cpy.fdchar;
                        fdp->d_curfdtype = -1;
                        fjp->fj_flags &= ~FUNIT_CHAROK;
                        mutex_exit(&fjp->fj_lock);

                        break;

                default:
                        FDERRPRINT(FDEP_L4, FDEM_IOCT,
                            (CE_WARN, "fd_ioctl fd unit %d: FDIOSCHAR odd "
                            "xfer rate %dkbs",
                            unit, cpy.fdchar.fdc_transfer_rate));
                        rval = EINVAL;
                        break;
                }
                break;

        /*
         * set all characteristics and geometry to the defaults
         */
        case FDDEFGEOCHAR:
                mutex_enter(&fjp->fj_lock);
                fdp->d_curfdtype = fdp->d_deffdtype;
                *fjp->fj_chars = *defchar[fdp->d_curfdtype];
                *fjp->fj_attr = fdtypes[fdp->d_curfdtype];
                bcopy(fdparts[fdp->d_curfdtype],
                    fdp->d_part, sizeof (struct partition) * NDKMAP);
                fjp->fj_flags &= ~FUNIT_CHAROK;
                mutex_exit(&fjp->fj_lock);
                break;

        case FDEJECT:  /* eject disk */
        case DKIOCEJECT:
                fjp->fj_flags &= ~(FUNIT_LABELOK | FUNIT_UNLABELED);
                rval = ENOSYS;
                break;

        case FDGETCHANGE: /* disk changed */
                if (ddi_copyin((void *)arg, &cpy.temp, sizeof (int), flag)) {
                        rval = EFAULT;
                        break;
                }
                mutex_enter(&fjp->fj_lock);
                fjp->fj_ops->fco_select(fjp, unit, 1);

                if (fjp->fj_flags & FUNIT_CHANGED)
                        cpy.temp |= FDGC_HISTORY;
                else
                        cpy.temp &= ~FDGC_HISTORY;
                fjp->fj_flags &= ~FUNIT_CHANGED;

                if (fjp->fj_ops->fco_getchng(fjp, unit)) {
                        cpy.temp |= FDGC_DETECTED;
                        fjp->fj_ops->fco_resetchng(fjp, unit);
                        /*
                         * check diskette again only if it was removed
                         */
                        if (fjp->fj_ops->fco_getchng(fjp, unit)) {
                                /*
                                 * no diskette is present
                                 */
                                cpy.temp |= FDGC_CURRENT;
                                if (fjp->fj_flags & FUNIT_CHGDET)
                                        /*
                                         * again no diskette; not a new change
                                         */
                                        cpy.temp ^= FDGC_DETECTED;
                                else
                                        fjp->fj_flags |= FUNIT_CHGDET;
                        } else {
                                /*
                                 * a new diskette is present
                                 */
                                cpy.temp &= ~FDGC_CURRENT;
                                fjp->fj_flags &= ~FUNIT_CHGDET;
                        }
                } else {
                        cpy.temp &= ~(FDGC_DETECTED | FDGC_CURRENT);
                        fjp->fj_flags &= ~FUNIT_CHGDET;
                }
                /*
                 * also get state of write protection
                 */
                if (fjp->fj_flags & FUNIT_WPROT) {
                        cpy.temp |= FDGC_CURWPROT;
                } else {
                        cpy.temp &= ~FDGC_CURWPROT;
                }
                fjp->fj_ops->fco_select(fjp, unit, 0);
                mutex_exit(&fjp->fj_lock);

                if (ddi_copyout(&cpy.temp, (void *)arg, sizeof (int), flag))
                        rval = EFAULT;
                break;

        case FDGETDRIVECHAR:
                if (ddi_copyout(fjp->fj_drive, (void *)arg,
                    sizeof (struct fd_drive), flag))
                        rval = EFAULT;
                break;

        case FDSETDRIVECHAR:
                if (ddi_copyin((void *)arg, &cpy.drvchar,
                    sizeof (struct fd_drive), flag)) {
                        rval = EFAULT;
                        break;
                }
                mutex_enter(&fjp->fj_lock);
                *(fjp->fj_drive) = cpy.drvchar;
                fdp->d_curfdtype = -1;
                fjp->fj_flags &= ~FUNIT_CHAROK;
                mutex_exit(&fjp->fj_lock);
                break;

        case DKIOCREMOVABLE: {
                int     i = 1;

                /* no brainer: floppies are always removable */
                if (ddi_copyout(&i, (void *)arg, sizeof (int), flag)) {
                        rval = EFAULT;
                }
                break;
        }

        case DKIOCGMEDIAINFO:
                rval = fd_get_media_info(fjp, (caddr_t)arg, flag);
                break;

        case FDIOCMD:
        {
                struct fd_cmd fc;
                int cyl, head, spc, spt;

#ifdef _MULTI_DATAMODEL
                switch (ddi_model_convert_from(flag & FMODELS)) {
                case DDI_MODEL_ILP32:
                {
                        struct fd_cmd32 fc32;

                        if (ddi_copyin((void *)arg, &fc32,
                            sizeof (fc32), flag)) {
                                rval = EFAULT;
                                break;
                        }

                        fc.fdc_cmd = fc32.fdc_cmd;
                        fc.fdc_flags = fc32.fdc_flags;
                        fc.fdc_blkno = fc32.fdc_blkno;
                        fc.fdc_secnt = fc32.fdc_secnt;
                        fc.fdc_bufaddr = (caddr_t)(uintptr_t)fc32.fdc_bufaddr;
                        fc.fdc_buflen = fc32.fdc_buflen;

                        break;
                }
                case DDI_MODEL_NONE:

#endif /* _MULTI_DATAMODEL */

                        if (ddi_copyin((void *)arg, &fc, sizeof (fc), flag)) {
                                rval = EFAULT;
                                break;
                        }
#ifdef _MULTI_DATAMODEL
                        break;
                }
#endif /* _MULTI_DATAMODEL */

                if (rval != 0)
                        break;

                if (fc.fdc_cmd == FDCMD_READ || fc.fdc_cmd == FDCMD_WRITE) {
                        auto struct iovec aiov;
                        auto struct uio auio;
                        struct uio *uio = &auio;

                        spc = (fc.fdc_cmd == FDCMD_READ)? B_READ: B_WRITE;

                        bzero(&auio, sizeof (struct uio));
                        bzero(&aiov, sizeof (struct iovec));
                        aiov.iov_base = fc.fdc_bufaddr;
                        aiov.iov_len = (uint_t)fc.fdc_secnt *
                            fjp->fj_chars->fdc_sec_size;
                        uio->uio_iov = &aiov;

                        uio->uio_iovcnt = 1;
                        uio->uio_resid = aiov.iov_len;
                        uio->uio_segflg = UIO_USERSPACE;

                        rval = physio(fd_strategy, (struct buf *)0, dev,
                            spc, minphys, uio);
                        break;
                } else if (fc.fdc_cmd == FDCMD_FORMAT_TRACK) {
                        spt = fjp->fj_chars->fdc_secptrack;     /* sec/trk */
                        spc = fjp->fj_chars->fdc_nhead * spt;   /* sec/cyl */
                        cyl = fc.fdc_blkno / spc;
                        head = (fc.fdc_blkno % spc) / spt;
                        if ((cyl | head) == 0)
                                fjp->fj_flags &=
                                    ~(FUNIT_LABELOK | FUNIT_UNLABELED);

                        FDERRPRINT(FDEP_L0, FDEM_FORM,
                            (CE_CONT, "fd_format cyl %d, hd %d\n", cyl, head));
                        fjp->fj_ops->fco_select(fjp, unit, 1);
                        rval = fjp->fj_ops->fco_format(fjp, unit, cyl, head,
                            (int)fc.fdc_flags);
                        fjp->fj_ops->fco_select(fjp, unit, 0);

                        break;
                }
                FDERRPRINT(FDEP_L4, FDEM_IOCT,
                    (CE_WARN, "fd_ioctl fd unit %d: FDIOCSCMD not yet complete",
                    unit));
                rval = EINVAL;
                break;
        }

        case FDRAW:
                rval = fd_rawioctl(fjp, unit, (caddr_t)arg, flag);
                break;

        default:
                FDERRPRINT(FDEP_L4, FDEM_IOCT,
                    (CE_WARN, "fd_ioctl fd unit %d: invalid ioctl 0x%x",
                    unit, cmd));
                rval = ENOTTY;
                break;
        }
        return (rval);
}

static void
fd_build_user_vtoc(struct fcu_obj *fjp, struct fdisk *fdp, struct vtoc *vtocp)
{
        struct partition *vpart;
        int     i;
        int     xblk;

        /*
         * Return vtoc structure fields in the provided VTOC area, addressed
         * by *vtocp.
         *
         */
        bzero(vtocp, sizeof (struct vtoc));

        bcopy(fdp->d_vtoc_bootinfo,
            vtocp->v_bootinfo, sizeof (vtocp->v_bootinfo));

        vtocp->v_sanity = VTOC_SANE;
        vtocp->v_version = fdp->d_vtoc_version;
        bcopy(fdp->d_vtoc_volume, vtocp->v_volume, LEN_DKL_VVOL);
        if (fjp->fj_flags & FUNIT_LABELOK) {
                vtocp->v_sectorsz = DEV_BSIZE;
                xblk = 1;
        } else {
                vtocp->v_sectorsz = fjp->fj_chars->fdc_sec_size;
                xblk = vtocp->v_sectorsz / DEV_BSIZE;
        }
        vtocp->v_nparts = 3;    /* <= NDKMAP;   */

        /*
         * Copy partitioning information.
         */
        bcopy(fdp->d_part, vtocp->v_part, sizeof (struct partition) * NDKMAP);
        for (i = NDKMAP, vpart = vtocp->v_part; i && (xblk > 1); i--, vpart++) {
                /* correct partition info if sector size > 512 bytes */
                vpart->p_start /= xblk;
                vpart->p_size /= xblk;
        }

        bcopy(fdp->d_vtoc_timestamp,
            vtocp->timestamp, sizeof (fdp->d_vtoc_timestamp));
        bcopy(fdp->d_vtoc_asciilabel, vtocp->v_asciilabel, LEN_DKL_ASCII);
}


static int
fd_build_label_vtoc(struct fcu_obj *fjp, struct fdisk *fdp, struct vtoc *vtocp,
    struct dk_label *labelp)
{
        struct partition *vpart;
        int     i;
        int     nblks;
        int     ncyl;
        ushort_t sum, *sp;


        /*
         * Sanity-check the vtoc
         */
        if (vtocp->v_sanity != VTOC_SANE ||
            vtocp->v_nparts > NDKMAP || vtocp->v_nparts <= 0) {
                FDERRPRINT(FDEP_L3, FDEM_IOCT,
                    (CE_WARN, "fd_build_label:  sanity check on vtoc failed"));
                return (EINVAL);
        }

        /*
         * before copying the vtoc, the partition information in it should be
         * checked against the information the driver already has on the
         * diskette.
         */

        nblks = (fjp->fj_chars->fdc_nhead * fjp->fj_chars->fdc_secptrack *
            fjp->fj_chars->fdc_sec_size) / DEV_BSIZE;
        if (nblks == 0 || fjp->fj_chars->fdc_ncyl == 0)
                return (EFAULT);
        vpart = vtocp->v_part;

        /*
         * Check the partition information in the vtoc.  The starting sectors
         * must lie along cylinder boundaries. (NDKMAP entries are checked
         * to ensure that the unused entries are set to 0 if vtoc->v_nparts
         * is less than NDKMAP)
         */
        for (i = NDKMAP; i; i--) {
                if ((vpart->p_start % nblks) != 0) {
                        return (EINVAL);
                }
                ncyl = vpart->p_start / nblks;
                ncyl += vpart->p_size / nblks;
                if ((vpart->p_size % nblks) != 0)
                        ncyl++;
                if (ncyl > (long)fjp->fj_chars->fdc_ncyl) {
                        return (EINVAL);
                }
                vpart++;
        }


        bcopy(vtocp->v_bootinfo, fdp->d_vtoc_bootinfo,
            sizeof (vtocp->v_bootinfo));
        fdp->d_vtoc_version = vtocp->v_version;
        bcopy(vtocp->v_volume, fdp->d_vtoc_volume, LEN_DKL_VVOL);

        /*
         * Copy partitioning information.
         */
        bcopy(vtocp->v_part, fdp->d_part, sizeof (struct partition) * NDKMAP);
        bcopy(vtocp->timestamp, fdp->d_vtoc_timestamp,
            sizeof (fdp->d_vtoc_timestamp));
        bcopy(vtocp->v_asciilabel, fdp->d_vtoc_asciilabel, LEN_DKL_ASCII);

        /*
         * construct the diskette label in supplied buffer
         */

        /* Put appropriate vtoc structure fields into the disk label */
        labelp->dkl_vtoc.v_bootinfo[0] = (uint32_t)vtocp->v_bootinfo[0];
        labelp->dkl_vtoc.v_bootinfo[1] = (uint32_t)vtocp->v_bootinfo[1];
        labelp->dkl_vtoc.v_bootinfo[2] = (uint32_t)vtocp->v_bootinfo[2];

        labelp->dkl_vtoc.v_sanity = vtocp->v_sanity;
        labelp->dkl_vtoc.v_version = vtocp->v_version;

        bcopy(vtocp->v_volume, labelp->dkl_vtoc.v_volume, LEN_DKL_VVOL);

        labelp->dkl_vtoc.v_nparts = vtocp->v_nparts;

        bcopy(vtocp->v_reserved, labelp->dkl_vtoc.v_reserved,
            sizeof (labelp->dkl_vtoc.v_reserved));

        for (i = 0; i < (int)vtocp->v_nparts; i++) {
                labelp->dkl_vtoc.v_part[i].p_tag  = vtocp->v_part[i].p_tag;
                labelp->dkl_vtoc.v_part[i].p_flag  = vtocp->v_part[i].p_flag;
                labelp->dkl_vtoc.v_part[i].p_start  = vtocp->v_part[i].p_start;
                labelp->dkl_vtoc.v_part[i].p_size  = vtocp->v_part[i].p_size;
        }

        for (i = 0; i < NDKMAP; i++) {
                labelp->dkl_vtoc.v_timestamp[i] = vtocp->timestamp[i];
        }
        bcopy(vtocp->v_asciilabel, labelp->dkl_asciilabel, LEN_DKL_ASCII);


        labelp->dkl_pcyl = fjp->fj_chars->fdc_ncyl;
        labelp->dkl_ncyl = fjp->fj_chars->fdc_ncyl;
        labelp->dkl_nhead = fjp->fj_chars->fdc_nhead;
        /*
         * The fdc_secptrack field of the fd_char structure is the number
         * of sectors per track where the sectors are fdc_sec_size.
         * The dkl_nsect field of the dk_label structure is the number of
         * DEV_BSIZE (512) byte sectors per track.
         */
        labelp->dkl_nsect = (fjp->fj_chars->fdc_secptrack *
            fjp->fj_chars->fdc_sec_size) / DEV_BSIZE;
        labelp->dkl_intrlv = fjp->fj_attr->fda_intrlv;
        labelp->dkl_rpm = fjp->fj_attr->fda_rotatespd;
        labelp->dkl_read_reinstruct =
            (int)(labelp->dkl_nsect * labelp->dkl_rpm * 4) / 60000;
        labelp->dkl_write_reinstruct = labelp->dkl_read_reinstruct;

        labelp->dkl_magic = DKL_MAGIC;

        sum = 0;
        labelp->dkl_cksum = 0;
        sp = (ushort_t *)labelp;
        while (sp < &(labelp->dkl_cksum)) {
                sum ^= *sp++;
        }
        labelp->dkl_cksum = sum;

        return (0);
}

static int
fd_rawioctl(struct fcu_obj *fjp, int unit, caddr_t arg, int mode)
{
        struct fd_raw fdr;
        char *arg_result = NULL;
        int flag = B_READ;
        int rval = 0;
        caddr_t uaddr;
        uint_t ucount;

        FDERRPRINT(FDEP_L1, FDEM_RAWI,
            (CE_CONT, "fd_rawioctl: cmd[0]=0x%x\n", fdr.fdr_cmd[0]));

        if (fjp->fj_chars->fdc_medium != 3 && fjp->fj_chars->fdc_medium != 5) {
                cmn_err(CE_CONT, "fd_rawioctl: Medium density not supported\n");
                return (ENXIO);
        }

#ifdef _MULTI_DATAMODEL
        switch (ddi_model_convert_from(mode & FMODELS)) {
        case DDI_MODEL_ILP32:
        {
                struct fd_raw32 fdr32;

                if (ddi_copyin(arg, &fdr32, sizeof (fdr32), mode))
                        return (EFAULT);

                bcopy(fdr32.fdr_cmd, fdr.fdr_cmd, sizeof (fdr.fdr_cmd));
                fdr.fdr_cnum = fdr32.fdr_cnum;
                fdr.fdr_nbytes = fdr32.fdr_nbytes;
                fdr.fdr_addr = (caddr_t)(uintptr_t)fdr32.fdr_addr;
                arg_result = ((struct fd_raw32 *)arg)->fdr_result;

                break;
        }
        case DDI_MODEL_NONE:
#endif /* ! _MULTI_DATAMODEL */

                if (ddi_copyin(arg, &fdr, sizeof (fdr), mode))
                        return (EFAULT);

                arg_result = ((struct fd_raw *)arg)->fdr_result;

#ifdef _MULTI_DATAMODEL
                break;
        }
#endif /* _MULTI_DATAMODEL */



        /*
         * copy user address & nbytes from raw_req so that we can
         * put kernel address in req structure
         */
        uaddr = fdr.fdr_addr;
        ucount = (uint_t)fdr.fdr_nbytes;
        unit &= 3;

        switch (fdr.fdr_cmd[0] & 0x0f) {

        case FDRAW_FORMAT:
                ucount += 16;
                fdr.fdr_addr = kmem_zalloc(ucount, KM_SLEEP);
                if (ddi_copyin(uaddr, fdr.fdr_addr,
                    (size_t)fdr.fdr_nbytes, mode)) {
                        kmem_free(fdr.fdr_addr, ucount);
                        return (EFAULT);
                }
                if ((*fdr.fdr_addr | fdr.fdr_addr[1]) == 0)
                        fjp->fj_flags &= ~(FUNIT_LABELOK | FUNIT_UNLABELED);
                flag = B_WRITE;
                fdr.fdr_cmd[1] = (fdr.fdr_cmd[1] & ~3) | unit;
                break;

        case FDRAW_WRCMD:
        case FDRAW_WRITEDEL:
                flag = B_WRITE;
                /* FALLTHROUGH */
        case FDRAW_RDCMD:
        case FDRAW_READDEL:
        case FDRAW_READTRACK:
                if (ucount) {
                        /*
                         * In SunOS 4.X, we used to as_fault things in.
                         * We really cannot do this in 5.0/SVr4. Unless
                         * someone really believes that speed is of the
                         * essence here, it is just much simpler to do
                         * this in kernel space and use copyin/copyout.
                         */
                        fdr.fdr_addr = kmem_alloc((size_t)ucount, KM_SLEEP);
                        if (flag == B_WRITE) {
                                if (ddi_copyin(uaddr, fdr.fdr_addr, ucount,
                                    mode)) {
                                        kmem_free(fdr.fdr_addr, ucount);
                                        return (EFAULT);
                                }
                        }
                } else
                        return (EINVAL);
                fdr.fdr_cmd[1] = (fdr.fdr_cmd[1] & ~3) | unit;
                break;

        case FDRAW_READID:
        case FDRAW_REZERO:
        case FDRAW_SEEK:
        case FDRAW_SENSE_DRV:
                ucount = 0;
                fdr.fdr_cmd[1] = (fdr.fdr_cmd[1] & ~3) | unit;
                break;

        case FDRAW_SPECIFY:
                fdr.fdr_cmd[2] &= 0xfe; /* keep NoDMA bit clear */
                /* FALLTHROUGH */
        case FDRAW_SENSE_INT:
                ucount = 0;
                break;

        default:
                return (EINVAL);
        }

        /*
         * Note that we ignore any error returns from controller
         * 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.
         */

        fjp->fj_ops->fco_select(fjp, unit, 1);
        rval = fjp->fj_ops->fco_rwioctl(fjp, unit, (caddr_t)&fdr);

        if (ucount && flag == B_READ && rval == 0) {
                if (ddi_copyout(fdr.fdr_addr, uaddr, ucount, mode)) {
                        rval = EFAULT;
                }
        }
        if (ddi_copyout(fdr.fdr_result, arg_result, sizeof (fdr.fdr_cmd), mode))
                rval = EFAULT;

        fjp->fj_ops->fco_select(fjp, unit, 0);
        if (ucount)
                kmem_free(fdr.fdr_addr, ucount);

        return (rval);
}

/*
 * property operation routine.  return the number of blocks for the partition
 * in question or forward the request to the property facilities.
 */
static int
fd_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int mod_flags,
    char *name, caddr_t valuep, int *lengthp)
{
        struct fcu_obj  *fjp = NULL;
        struct fdisk    *fdp = NULL;
        uint64_t        nblocks64;

        FDERRPRINT(FDEP_L1, FDEM_PROP,
            (CE_CONT, "fd_prop_op: dip %p %s\n", (void *)dip, name));

        /*
         * Our dynamic properties are all device specific and size oriented.
         * Requests issued under conditions where size is valid are passed
         * to ddi_prop_op_nblocks with the size information, otherwise the
         * request is passed to ddi_prop_op.
         */
        if (dev == DDI_DEV_T_ANY) {
pass:           return (ddi_prop_op(dev, dip, prop_op, mod_flags,
                    name, valuep, lengthp));
        } else {
                /*
                 * Ignoring return value because success is checked by
                 * verifying fjp and fdp and returned unit value is not used.
                 */
                (void) fd_getdrive(dev, &fjp, &fdp);
                if (!fjp || !fdp)
                        goto pass;

                /* get nblocks value */
                nblocks64 = (ulong_t)fdp->d_part[PARTITION(dev)].p_size;

                return (ddi_prop_op_nblocks(dev, dip, prop_op, mod_flags,
                    name, valuep, lengthp, nblocks64));
        }
}

static void
fd_media_watch(void *arg)
{
        struct fcu_obj *fjp;
        struct fdisk *fdp;

#ifdef DEBUG
        int     unit;
#define DEBUG_ASSIGN    unit=
#else
#define DEBUG_ASSIGN    (void)
#endif
        DEBUG_ASSIGN fd_getdrive((dev_t)arg, &fjp, &fdp);
        /*
         * Ignoring return in non DEBUG mode because device exist.
         * Returned unit value is not used.
         */

        FDERRPRINT(FDEP_L0, FDEM_IOCT,
            (CE_CONT, "fd_media_watch unit %d\n", unit));

        /*
         * fd_get_media_state() cannot be called from this timeout function
         * because the  floppy drive has to be selected first, and that could
         * force this function to sleep (while waiting for the select
         * semaphore).
         * Instead, just wakeup up driver.
         */
        mutex_enter(&fjp->fj_lock);
        cv_broadcast(&fdp->d_statecv);
        mutex_exit(&fjp->fj_lock);
}

enum dkio_state
fd_get_media_state(struct fcu_obj *fjp, int unit)
{
        enum dkio_state state;

        if (fjp->fj_ops->fco_getchng(fjp, unit)) {
                /* recheck disk only if DSKCHG "high" */
                fjp->fj_ops->fco_resetchng(fjp, unit);
                if (fjp->fj_ops->fco_getchng(fjp, unit)) {
                        if (fjp->fj_flags & FUNIT_CHGDET) {
                                /*
                                 * again no diskette; not a new change
                                 */
                                state = DKIO_NONE;
                        } else {
                                /*
                                 * a new change; diskette was ejected
                                 */
                                fjp->fj_flags |= FUNIT_CHGDET;
                                state = DKIO_EJECTED;
                        }
                } else {
                        fjp->fj_flags &= ~FUNIT_CHGDET;
                        state = DKIO_INSERTED;
                }
        } else {
                fjp->fj_flags &= ~FUNIT_CHGDET;
                state = DKIO_INSERTED;
        }
        FDERRPRINT(FDEP_L0, FDEM_IOCT,
            (CE_CONT, "fd_get_media_state unit %d: state %x\n", unit, state));
        return (state);
}

static int
fd_check_media(dev_t dev, enum dkio_state state)
{
        struct fcu_obj *fjp;
        struct fdisk *fdp;
        int     unit;
        int     err;

        unit = fd_getdrive(dev, &fjp, &fdp);

        mutex_enter(&fjp->fj_lock);

        fjp->fj_ops->fco_select(fjp, unit, 1);
        fdp->d_media_state = fd_get_media_state(fjp, unit);
        fdp->d_media_timeout = drv_usectohz(fd_check_media_time);

        while (fdp->d_media_state == state) {
                /* release the controller and drive */
                fjp->fj_ops->fco_select(fjp, unit, 0);

                /* turn on timer */
                fdp->d_media_timeout_id = timeout(fd_media_watch,
                    (void *)dev, fdp->d_media_timeout);

                if (cv_wait_sig(&fdp->d_statecv, &fjp->fj_lock) == 0) {
                        fdp->d_media_timeout = 0;
                        mutex_exit(&fjp->fj_lock);
                        return (EINTR);
                }
                fjp->fj_ops->fco_select(fjp, unit, 1);
                fdp->d_media_state = fd_get_media_state(fjp, unit);
        }

        if (fdp->d_media_state == DKIO_INSERTED) {
                err = fdgetlabel(fjp, unit);
                if (err) {
                        fjp->fj_ops->fco_select(fjp, unit, 0);
                        mutex_exit(&fjp->fj_lock);
                        return (EIO);
                }
        }
        fjp->fj_ops->fco_select(fjp, unit, 0);
        mutex_exit(&fjp->fj_lock);
        return (0);
}

/*
 * fd_get_media_info :
 *      Collects medium information for
 *      DKIOCGMEDIAINFO ioctl.
 */

static int
fd_get_media_info(struct fcu_obj *fjp, caddr_t buf, int flag)
{
        struct dk_minfo media_info;
        int err = 0;

        media_info.dki_media_type = DK_FLOPPY;
        media_info.dki_lbsize = fjp->fj_chars->fdc_sec_size;
        media_info.dki_capacity = fjp->fj_chars->fdc_ncyl *
            fjp->fj_chars->fdc_secptrack * fjp->fj_chars->fdc_nhead;

        if (ddi_copyout(&media_info, buf, sizeof (struct dk_minfo), flag))
                err = EFAULT;
        return (err);
}