OpenSolaris_b135/uts/common/io/fd.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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.
 */


/*
 * 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 */
	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 */
		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, NULL)
			    == 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 filesytem 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 */

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