Device layer rollup commit.
[dragonfly.git] / sys / bus / cam / scsi / scsi_cd.c
index f5c0013..1a073df 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 1997 Justin T. Gibbs.
- * Copyright (c) 1997, 1998, 1999, 2000, 2001 Kenneth D. Merry.
+ * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2003, 2003 Kenneth D. Merry.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -24,8 +24,8 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- * $FreeBSD: src/sys/cam/scsi/scsi_cd.c,v 1.31.2.13 2002/11/25 05:30:31 njl Exp $
- * $DragonFly: src/sys/bus/cam/scsi/scsi_cd.c,v 1.9 2003/08/07 21:16:44 dillon Exp $
+ * $FreeBSD: src/sys/cam/scsi/scsi_cd.c,v 1.31.2.16 2003/10/21 22:26:11 thomas Exp $
+ * $DragonFly: src/sys/bus/cam/scsi/scsi_cd.c,v 1.15 2004/05/19 22:52:38 dillon Exp $
  */
 /*
  * Portions of this driver taken from the original FreeBSD cd driver.
@@ -62,6 +62,7 @@
 #include <sys/dvdio.h>
 #include <sys/devicestat.h>
 #include <sys/sysctl.h>
+#include <sys/taskqueue.h>
 #include <sys/proc.h>
 #include <sys/buf2.h>
 
@@ -84,23 +85,28 @@ struct cd_params {
 };
 
 typedef enum {
-       CD_Q_NONE       = 0x00,
-       CD_Q_NO_TOUCH   = 0x01,
-       CD_Q_BCD_TRACKS = 0x02,
-       CD_Q_NO_CHANGER = 0x04,
-       CD_Q_CHANGER    = 0x08
+       CD_Q_NONE               = 0x00,
+       CD_Q_NO_TOUCH           = 0x01,
+       CD_Q_BCD_TRACKS         = 0x02,
+       CD_Q_NO_CHANGER         = 0x04,
+       CD_Q_CHANGER            = 0x08,
+       CD_Q_10_BYTE_ONLY       = 0x10
 } cd_quirks;
 
 typedef enum {
-       CD_FLAG_INVALID         = 0x001,
-       CD_FLAG_NEW_DISC        = 0x002,
-       CD_FLAG_DISC_LOCKED     = 0x004,
-       CD_FLAG_DISC_REMOVABLE  = 0x008,
-       CD_FLAG_TAGGED_QUEUING  = 0x010,
-       CD_FLAG_CHANGER         = 0x040,
-       CD_FLAG_ACTIVE          = 0x080,
-       CD_FLAG_SCHED_ON_COMP   = 0x100,
-       CD_FLAG_RETRY_UA        = 0x200
+       CD_FLAG_INVALID         = 0x0001,
+       CD_FLAG_NEW_DISC        = 0x0002,
+       CD_FLAG_DISC_LOCKED     = 0x0004,
+       CD_FLAG_DISC_REMOVABLE  = 0x0008,
+       CD_FLAG_TAGGED_QUEUING  = 0x0010,
+       CD_FLAG_CHANGER         = 0x0040,
+       CD_FLAG_ACTIVE          = 0x0080,
+       CD_FLAG_SCHED_ON_COMP   = 0x0100,
+       CD_FLAG_RETRY_UA        = 0x0200,
+       CD_FLAG_VALID_MEDIA     = 0x0400,
+       CD_FLAG_VALID_TOC       = 0x0800,
+       CD_FLAG_OPEN            = 0x1000,
+       CD_FLAG_SCTX_INIT       = 0x2000
 } cd_flags;
 
 typedef enum {
@@ -121,6 +127,16 @@ typedef enum {
 #define ccb_state ppriv_field0
 #define ccb_bp ppriv_ptr1
 
+struct cd_tocdata {
+       struct ioc_toc_header header;
+       struct cd_toc_entry entries[100];
+};
+
+struct cd_toc_single {
+       struct ioc_toc_header header;
+       struct cd_toc_entry entry;
+};
+
 typedef enum {
        CD_STATE_PROBE,
        CD_STATE_NORMAL
@@ -141,6 +157,22 @@ struct cd_softc {
        struct cdchanger        *changer;
        int                     bufs_left;
        struct cam_periph       *periph;
+       int                     minimum_command_size;
+       struct task             sysctl_task;
+       struct sysctl_ctx_list  sysctl_ctx;
+       struct sysctl_oid       *sysctl_tree;
+       STAILQ_HEAD(, cd_mode_params)   mode_queue;
+       struct cd_tocdata       toc;
+};
+
+struct cd_page_sizes {
+       int page;
+       int page_size;
+};
+
+static struct cd_page_sizes cd_page_size_table[] =
+{
+       { AUDIO_PAGE, sizeof(struct cd_audio_page)}
 };
 
 struct cd_quirk_entry {
@@ -149,12 +181,22 @@ struct cd_quirk_entry {
 };
 
 /*
- * These quirk entries aren't strictly necessary.  Basically, what they do
- * is tell cdregister() up front that a device is a changer.  Otherwise, it
- * will figure that fact out once it sees a LUN on the device that is
- * greater than 0.  If it is known up front that a device is a changer, all
- * I/O to the device will go through the changer scheduling routines, as
+ * The changer quirk entries aren't strictly necessary.  Basically, what
+ * they do is tell cdregister() up front that a device is a changer.
+ * Otherwise, it will figure that fact out once it sees a LUN on the device
+ * that is greater than 0.  If it is known up front that a device is a changer,
+ * all I/O to the device will go through the changer scheduling routines, as
  * opposed to the "normal" CD code.
+ *
+ * NOTE ON 10_BYTE_ONLY quirks:  Any 10_BYTE_ONLY quirks MUST be because
+ * your device hangs when it gets a 10 byte command.  Adding a quirk just
+ * to get rid of the informative diagnostic message is not acceptable.  All
+ * 10_BYTE_ONLY quirks must be documented in full in a PR (which should be
+ * referenced in a comment along with the quirk) , and must be approved by
+ * ken@FreeBSD.org.  Any quirks added that don't adhere to this policy may
+ * be removed until the submitter can explain why they are needed.
+ * 10_BYTE_ONLY quirks will be removed (as they will no longer be necessary)
+ * when the CAM_NEW_TRAN_CODE work is done.
  */
 static struct cd_quirk_entry cd_quirk_table[] =
 {
@@ -188,6 +230,7 @@ static      periph_start_t  cdstart;
 static periph_oninv_t  cdoninvalidate;
 static void            cdasync(void *callback_arg, u_int32_t code,
                                struct cam_path *path, void *arg);
+static int             cdcmdsizesysctl(SYSCTL_HANDLER_ARGS);
 static void            cdshorttimeout(void *arg);
 static void            cdschedule(struct cam_periph *periph, int priority);
 static void            cdrunchangerqueue(void *arg);
@@ -203,16 +246,19 @@ static    void            cddone(struct cam_periph *periph,
                               union ccb *start_ccb);
 static int             cderror(union ccb *ccb, u_int32_t cam_flags,
                                u_int32_t sense_flags);
+static union cd_pages  *cdgetpage(struct cd_mode_params *mode_params);
+static int             cdgetpagesize(int page_num);
 static void            cdprevent(struct cam_periph *periph, int action);
-static int             cdsize(dev_t dev, u_int32_t *size);
-static int             cdfirsttrackisdata(struct cam_periph *periph);
+static int             cdcheckmedia(struct cam_periph *periph);
+static int             cdsize(struct cam_periph *periph, u_int32_t *size);
+static int             cd6byteworkaround(union ccb *ccb);
 static int             cdreadtoc(struct cam_periph *periph, u_int32_t mode, 
-                                 u_int32_t start, struct cd_toc_entry *data, 
-                                 u_int32_t len);
+                                 u_int32_t start, u_int8_t *data, 
+                                 u_int32_t len, u_int32_t sense_flags);
 static int             cdgetmode(struct cam_periph *periph, 
-                                 struct cd_mode_data *data, u_int32_t page);
+                                 struct cd_mode_params *data, u_int32_t page);
 static int             cdsetmode(struct cam_periph *periph,
-                                 struct cd_mode_data *data);
+                                 struct cd_mode_params *data);
 static int             cdplay(struct cam_periph *periph, u_int32_t blk, 
                               u_int32_t len);
 static int             cdreadsubchannel(struct cam_periph *periph, 
@@ -229,7 +275,7 @@ static      int             cdplaytracks(struct cam_periph *periph,
                                     u_int32_t etrack, u_int32_t eindex);
 static int             cdpause(struct cam_periph *periph, u_int32_t go);
 static int             cdstopunit(struct cam_periph *periph, u_int32_t eject);
-static int             cdstartunit(struct cam_periph *periph);
+static int             cdstartunit(struct cam_periph *periph, int load);
 static int             cdsetspeed(struct cam_periph *periph,
                                   u_int32_t rdspeed, u_int32_t wrspeed);
 static int             cdreportkey(struct cam_periph *periph,
@@ -256,7 +302,7 @@ static struct cdevsw cd_cdevsw = {
        /* maj */       SCSICD_CDEV_MAJOR,
        /* flags */     D_DISK,
        /* port */      NULL,
-       /* autoq */     0,
+       /* clone */     NULL,
 
        /* open */      cdopen,
        /* close */     cdclose,
@@ -287,13 +333,14 @@ static int changer_max_busy_seconds = CHANGER_MAX_BUSY_SECONDS;
  * XXX KDM this CAM node should be moved if we ever get more CAM sysctl
  * variables.
  */
-SYSCTL_NODE(_kern, OID_AUTO, cam, CTLFLAG_RD, 0, "CAM Subsystem");
 SYSCTL_NODE(_kern_cam, OID_AUTO, cd, CTLFLAG_RD, 0, "CAM CDROM driver");
 SYSCTL_NODE(_kern_cam_cd, OID_AUTO, changer, CTLFLAG_RD, 0, "CD Changer");
 SYSCTL_INT(_kern_cam_cd_changer, OID_AUTO, min_busy_seconds, CTLFLAG_RW,
           &changer_min_busy_seconds, 0, "Minimum changer scheduling quantum");
+TUNABLE_INT("kern.cam.cd.changer.min_busy_seconds", &changer_min_busy_seconds);
 SYSCTL_INT(_kern_cam_cd_changer, OID_AUTO, max_busy_seconds, CTLFLAG_RW,
           &changer_max_busy_seconds, 0, "Maximum changer scheduling quantum");
+TUNABLE_INT("kern.cam.cd.changer.max_busy_seconds", &changer_max_busy_seconds);
 
 struct cdchanger {
        path_id_t                        path_id;
@@ -420,6 +467,12 @@ cdcleanup(struct cam_periph *periph)
        xpt_print_path(periph->path);
        printf("removing device entry\n");
 
+       if ((softc->flags & CD_FLAG_SCTX_INIT) != 0
+           && sysctl_ctx_free(&softc->sysctl_ctx) != 0) {
+               xpt_print_path(periph->path);
+               printf("can't remove sysctl context\n");
+       }
+
        s = splsoftcam();
        /*
         * In the queued, non-active case, the device in question
@@ -492,7 +545,7 @@ cdcleanup(struct cam_periph *periph)
        }
        devstat_remove_entry(&softc->device_stats);
        cam_extend_release(cdperiphs, periph->unit_number);
-       if (softc->disk.d_dev) {
+       if (softc->disk.d_rawdev) {
                disk_destroy(&softc->disk);
        }
        free(softc, M_DEVBUF);
@@ -562,12 +615,85 @@ cdasync(void *callback_arg, u_int32_t code,
        }
 }
 
+static void
+cdsysctlinit(void *context, int pending)
+{
+       struct cam_periph *periph;
+       struct cd_softc *softc;
+       char tmpstr[80], tmpstr2[80];
+
+       periph = (struct cam_periph *)context;
+       softc = (struct cd_softc *)periph->softc;
+
+       snprintf(tmpstr, sizeof(tmpstr), "CAM CD unit %d", periph->unit_number);
+       snprintf(tmpstr2, sizeof(tmpstr2), "%d", periph->unit_number);
+
+       sysctl_ctx_init(&softc->sysctl_ctx);
+       softc->flags |= CD_FLAG_SCTX_INIT;
+       softc->sysctl_tree = SYSCTL_ADD_NODE(&softc->sysctl_ctx,
+       SYSCTL_STATIC_CHILDREN(_kern_cam_cd), OID_AUTO,
+       tmpstr2, CTLFLAG_RD, 0, tmpstr);
+
+       if (softc->sysctl_tree == NULL) {
+               printf("cdsysctlinit: unable to allocate sysctl tree\n");
+               return;
+       }
+
+       /*
+        * Now register the sysctl handler, so the user can the value on
+        * the fly.
+        */
+       SYSCTL_ADD_PROC(&softc->sysctl_ctx,SYSCTL_CHILDREN(softc->sysctl_tree),
+                       OID_AUTO, "minimum_cmd_size", CTLTYPE_INT | CTLFLAG_RW,
+                       &softc->minimum_command_size, 0, cdcmdsizesysctl, "I",
+                       "Minimum CDB size");
+}
+
+/*
+ * We have a handler function for this so we can check the values when the
+ * user sets them, instead of every time we look at them.
+ */
+static int
+cdcmdsizesysctl(SYSCTL_HANDLER_ARGS)
+{
+       int error, value;
+
+       value = *(int *)arg1;
+
+       error = sysctl_handle_int(oidp, &value, 0, req);
+
+       if ((error != 0) || (req->newptr == NULL))
+               return (error);
+
+       /*
+        * The only real values we can have here are 6 or 10.  I don't
+        * really forsee having 12 be an option at any time in the future.
+        * So if the user sets something less than or equal to 6, we'll set
+        * it to 6.  If he sets something greater than 6, we'll set it to 10.
+        *
+        * I suppose we could just return an error here for the wrong values,
+        * but I don't think it's necessary to do so, as long as we can
+        * determine the user's intent without too much trouble.
+        */
+       if (value < 6)
+               value = 6;
+       else if (value > 6)
+               value = 10;
+
+       *(int *)arg1 = value;
+
+       return (0);
+}
+
+
 static cam_status
 cdregister(struct cam_periph *periph, void *arg)
 {
        struct cd_softc *softc;
        struct ccb_setasync csa;
+       struct ccb_pathinq cpi;
        struct ccb_getdev *cgd;
+       char tmpstr[80];
        caddr_t match;
 
        cgd = (struct ccb_getdev *)arg;
@@ -580,16 +706,9 @@ cdregister(struct cam_periph *periph, void *arg)
                return(CAM_REQ_CMP_ERR);
        }
 
-       softc = (struct cd_softc *)malloc(sizeof(*softc),M_DEVBUF,M_NOWAIT);
-
-       if (softc == NULL) {
-               printf("cdregister: Unable to probe new device. "
-                      "Unable to allocate softc\n");                           
-               return(CAM_REQ_CMP_ERR);
-       }
-
-       bzero(softc, sizeof(*softc));
+       softc = malloc(sizeof(*softc), M_DEVBUF, M_INTWAIT | M_ZERO);
        LIST_INIT(&softc->pending_ccbs);
+       STAILQ_INIT(&softc->mode_queue);
        softc->state = CD_STATE_PROBE;
        bufq_init(&softc->buf_queue);
        if (SID_IS_REMOVABLE(&cgd->inq_data))
@@ -615,6 +734,34 @@ cdregister(struct cam_periph *periph, void *arg)
        else
                softc->quirks = CD_Q_NONE;
 
+       /* Check if the SIM does not want 6 byte commands */
+       xpt_setup_ccb(&cpi.ccb_h, periph->path, /*priority*/1);
+       cpi.ccb_h.func_code = XPT_PATH_INQ;
+       xpt_action((union ccb *)&cpi);
+       if (cpi.ccb_h.status == CAM_REQ_CMP && (cpi.hba_misc & PIM_NO_6_BYTE))
+               softc->quirks |= CD_Q_10_BYTE_ONLY;
+
+       TASK_INIT(&softc->sysctl_task, 0, cdsysctlinit, periph);
+
+       /* The default is 6 byte commands, unless quirked otherwise */
+       if (softc->quirks & CD_Q_10_BYTE_ONLY)
+               softc->minimum_command_size = 10;
+       else
+               softc->minimum_command_size = 6;
+
+       /*
+        * Load the user's default, if any.
+        */
+       snprintf(tmpstr, sizeof(tmpstr), "kern.cam.cd.%d.minimum_cmd_size",
+               periph->unit_number);
+       TUNABLE_INT_FETCH(tmpstr, &softc->minimum_command_size);
+
+       /* 6 and 10 are the only permissible values here. */
+       if (softc->minimum_command_size < 6)
+               softc->minimum_command_size = 6;
+       else if (softc->minimum_command_size > 6)
+               softc->minimum_command_size = 10;
+
        /*
         * We need to register the statistics structure for this device,
         * but we don't have the blocksize yet for it.  So, we register
@@ -765,23 +912,7 @@ cdregister(struct cam_periph *periph, void *arg)
                 */
                else {
                        nchanger = malloc(sizeof(struct cdchanger),
-                               M_DEVBUF, M_NOWAIT);
-
-                       if (nchanger == NULL) {
-                               softc->flags &= ~CD_FLAG_CHANGER;
-                               printf("cdregister: unable to malloc "
-                                      "changer structure\ncdregister: "
-                                      "changer support disabled\n");
-
-                               /*
-                                * Yes, gotos can be gross but in this case
-                                * I think it's justified..
-                                */
-                               goto cdregisterexit;
-                       }
-
-                       /* zero the structure */
-                       bzero(nchanger, sizeof(struct cdchanger));
+                               M_DEVBUF, M_INTWAIT | M_ZERO);
 
                        if (camq_init(&nchanger->devq, 1) != 0) {
                                softc->flags &= ~CD_FLAG_CHANGER;
@@ -878,11 +1009,8 @@ cdregisterexit:
 static int
 cdopen(dev_t dev, int flags, int fmt, struct thread *td)
 {
-       struct disklabel *label;
        struct cam_periph *periph;
        struct cd_softc *softc;
-       struct ccb_getdev cgd;
-       u_int32_t size;
        int unit, error;
        int s;
 
@@ -913,72 +1041,19 @@ cdopen(dev_t dev, int flags, int fmt, struct thread *td)
        if (cam_periph_acquire(periph) != CAM_REQ_CMP)
                return(ENXIO);
 
-       cdprevent(periph, PR_PREVENT);
-
-       /* find out the size */
-       if ((error = cdsize(dev, &size)) != 0) {
-               cdprevent(periph, PR_ALLOW);
-               cam_periph_unlock(periph);
-               cam_periph_release(periph);
-               return(error);
-       }
-
-       /*
-        * If we get a non-zero return, revert back to not reading the
-        * label off the disk.  The first track is likely audio, which
-        * won't have a disklabel.
-        */
-       if ((error = cdfirsttrackisdata(periph)) != 0) {
-               softc->disk.d_dsflags &= ~DSO_COMPATLABEL;
-               softc->disk.d_dsflags |= DSO_NOLABELS;
-               error = 0;
-       }
-
-       /*
-        * Build prototype label for whole disk.
-        * Should take information about different data tracks from the
-        * TOC and put it in the partition table.
-        */
-       label = &softc->disk.d_label;
-       bzero(label, sizeof(*label));
-       label->d_type = DTYPE_SCSI;
-
        /*
-        * Grab the inquiry data to get the vendor and product names.
-        * Put them in the typename and packname for the label.
-        */
-       xpt_setup_ccb(&cgd.ccb_h, periph->path, /*priority*/ 1);
-       cgd.ccb_h.func_code = XPT_GDEV_TYPE;
-       xpt_action((union ccb *)&cgd);
-
-       strncpy(label->d_typename, cgd.inq_data.vendor,
-               min(SID_VENDOR_SIZE, sizeof(label->d_typename)));
-       strncpy(label->d_packname, cgd.inq_data.product,
-               min(SID_PRODUCT_SIZE, sizeof(label->d_packname)));
-               
-       label->d_secsize = softc->params.blksize;
-       label->d_secperunit = softc->params.disksize;
-       label->d_flags = D_REMOVABLE;
-       /*
-        * Make partition 'a' cover the whole disk.  This is a temporary
-        * compatibility hack.  The 'a' partition should not exist, so
-        * the slice code won't create it.  The slice code will make
-        * partition (RAW_PART + 'a') cover the whole disk and fill in
-        * some more defaults.
+        * Check for media, and set the appropriate flags.  We don't bail
+        * if we don't have media, but then we don't allow anything but the
+        * CDIOCEJECT/CDIOCCLOSE ioctls if there is no media.
+        *
+        * XXX KDM for now, we do fail the open if we don't have media.  We
+        * can change this once we've figured out how to make the slice
+        * code work well with media changing underneath it.
         */
-       label->d_partitions[0].p_size = label->d_secperunit;
-       label->d_partitions[0].p_fstype = FS_OTHER;
+       error = cdcheckmedia(periph);
 
-       /*
-        * We unconditionally (re)set the blocksize each time the
-        * CD device is opened.  This is because the CD can change,
-        * and therefore the blocksize might change.
-        * XXX problems here if some slice or partition is still
-        * open with the old size?
-        */
-       if ((softc->device_stats.flags & DEVSTAT_BS_UNAVAILABLE) != 0)
-               softc->device_stats.flags &= ~DEVSTAT_BS_UNAVAILABLE;
-       softc->device_stats.block_size = softc->params.blksize;
+       if (error == 0)
+               softc->flags |= CD_FLAG_OPEN;
 
        cam_periph_unlock(periph);
 
@@ -1016,10 +1091,17 @@ cdclose(dev_t dev, int flag, int fmt, struct thread *td)
 
        /*
         * Since we're closing this CD, mark the blocksize as unavailable.
-        * It will be marked as available whence the CD is opened again.
+        * It will be marked as available when the CD is opened again.
         */
        softc->device_stats.flags |= DEVSTAT_BS_UNAVAILABLE;
 
+       /*
+        * We'll check the media and toc again at the next open().
+        */
+       softc->flags &= ~(CD_FLAG_VALID_MEDIA|CD_FLAG_VALID_TOC);
+       
+       softc->flags &= ~CD_FLAG_OPEN;
+
        cam_periph_unlock(periph);
        cam_periph_release(periph);
 
@@ -1398,6 +1480,21 @@ cdstrategy(struct buf *bp)
                goto bad;
        }
 
+       /*
+        * If we don't have valid media, look for it before trying to
+        * schedule the I/O.
+        */
+       if ((softc->flags & CD_FLAG_VALID_MEDIA) == 0) {
+               int error;
+
+               error = cdcheckmedia(periph);
+               if (error != 0) {
+                       splx(s);
+                       bp->b_error = error;
+                       goto bad;
+               }
+       }
+
        /*
         * Place it in the queue of disk activities for this disk
         */
@@ -1509,15 +1606,7 @@ cdstart(struct cam_periph *periph, union ccb *start_ccb)
        case CD_STATE_PROBE:
        {
 
-               rcap = (struct scsi_read_capacity_data *)malloc(sizeof(*rcap),
-                                                               M_TEMP,
-                                                               M_NOWAIT);
-               if (rcap == NULL) {
-                       xpt_print_path(periph->path);
-                       printf("cdstart: Couldn't malloc read_capacity data\n");
-                       /* cd_free_periph??? */
-                       break;
-               }
+               rcap = malloc(sizeof(*rcap), M_TEMP, M_INTWAIT);
                csio = &start_ccb->csio;
                scsi_read_capacity(csio,
                                   /*retries*/1,
@@ -1785,6 +1874,11 @@ cddone(struct cam_periph *periph, union ccb *done_ccb)
                        xpt_announce_periph(periph, announce_buf);
                        if (softc->flags & CD_FLAG_CHANGER)
                                cdchangerschedule(softc);
+                       /*
+                        * Create our sysctl variables, now that we know
+                        * we have successfully attached.
+                        */
+                       taskqueue_enqueue(taskqueue_thread,&softc->sysctl_task);
                }
                softc->state = CD_STATE_NORMAL;         
                /*
@@ -1814,6 +1908,34 @@ cddone(struct cam_periph *periph, union ccb *done_ccb)
        xpt_release_ccb(done_ccb);
 }
 
+static union cd_pages *
+cdgetpage(struct cd_mode_params *mode_params)
+{
+       union cd_pages *page;
+
+       if (mode_params->cdb_size == 10)
+               page = (union cd_pages *)find_mode_page_10(
+                       (struct scsi_mode_header_10 *)mode_params->mode_buf);
+       else
+               page = (union cd_pages *)find_mode_page_6(
+                       (struct scsi_mode_header_6 *)mode_params->mode_buf);
+
+       return (page);
+}
+
+static int
+cdgetpagesize(int page_num)
+{
+       int i;
+
+       for (i = 0; i < (sizeof(cd_page_size_table)/
+           sizeof(cd_page_size_table[0])); i++) {
+               if (cd_page_size_table[i].page == page_num)
+                       return (cd_page_size_table[i].page_size);
+       }
+       return (-1);
+}
+
 static int
 cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
 {
@@ -1840,63 +1962,137 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
        if (error != 0)
                return(error);
 
+       /*
+        * If we don't have media loaded, check for it.  If still don't
+        * have media loaded, we can only do a load or eject.
+        */
+       if (((softc->flags & CD_FLAG_VALID_MEDIA) == 0)
+           && ((cmd != CDIOCCLOSE)
+           && (cmd != CDIOCEJECT))) {
+               error = cdcheckmedia(periph);
+               if (error != 0) {
+                       cam_periph_unlock(periph);
+                       return (error);
+               }
+       }
+
        switch (cmd) {
 
        case CDIOCPLAYTRACKS:
                {
                        struct ioc_play_track *args
                            = (struct ioc_play_track *) addr;
-                       struct cd_mode_data *data;
+                       struct cd_mode_params params;
+                       union cd_pages *page;
 
-                       data = malloc(sizeof(struct cd_mode_data), M_TEMP, 
-                                     M_WAITOK);
+                       params.alloc_len = sizeof(union cd_mode_data_6_10);
+                       params.mode_buf = malloc(params.alloc_len, M_TEMP,
+                                                M_WAITOK | M_ZERO);
 
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOCPLAYTRACKS\n"));
 
-                       error = cdgetmode(periph, data, AUDIO_PAGE);
+                       error = cdgetmode(periph, &params, AUDIO_PAGE);
                        if (error) {
-                               free(data, M_TEMP);
+                               free(params.mode_buf, M_TEMP);
                                break;
                        }
-                       data->page.audio.flags &= ~CD_PA_SOTC;
-                       data->page.audio.flags |= CD_PA_IMMED;
-                       error = cdsetmode(periph, data);
-                       free(data, M_TEMP);
+                       page = cdgetpage(&params);
+
+                       page->audio.flags &= ~CD_PA_SOTC;
+                       page->audio.flags |= CD_PA_IMMED;
+                       error = cdsetmode(periph, &params);
+                       free(params.mode_buf, M_TEMP);
                        if (error)
                                break;
-                       if (softc->quirks & CD_Q_BCD_TRACKS) {
-                               args->start_track = bin2bcd(args->start_track);
-                               args->end_track = bin2bcd(args->end_track);
+
+                       /*
+                        * This was originally implemented with the PLAY
+                        * AUDIO TRACK INDEX command, but that command was
+                        * deprecated after SCSI-2.  Most (all?) SCSI CDROM
+                        * drives support it but ATAPI and ATAPI-derivative
+                        * drives don't seem to support it.  So we keep a
+                        * cache of the table of contents and translate
+                        * track numbers to MSF format.
+                        */
+                       if (softc->flags & CD_FLAG_VALID_TOC) {
+                               union msf_lba *sentry, *eentry;
+                               int st, et;
+
+                               if (args->end_track <
+                                   softc->toc.header.ending_track + 1)
+                                       args->end_track++;
+                               if (args->end_track >
+                                   softc->toc.header.ending_track + 1)
+                                       args->end_track =
+                                           softc->toc.header.ending_track + 1;
+                               st = args->start_track -
+                                       softc->toc.header.starting_track;
+                               et = args->end_track -
+                                       softc->toc.header.starting_track;
+                               if ((st < 0)
+                                || (et < 0)
+                                || (st > (softc->toc.header.ending_track -
+                                    softc->toc.header.starting_track))) {
+                                       error = EINVAL;
+                                       break;
+                               }
+                               sentry = &softc->toc.entries[st].addr;
+                               eentry = &softc->toc.entries[et].addr;
+                               error = cdplaymsf(periph,
+                                                 sentry->msf.minute,
+                                                 sentry->msf.second,
+                                                 sentry->msf.frame,
+                                                 eentry->msf.minute,
+                                                 eentry->msf.second,
+                                                 eentry->msf.frame);
+                       } else {
+                               /*
+                                * If we don't have a valid TOC, try the
+                                * play track index command.  It is part of
+                                * the SCSI-2 spec, but was removed in the
+                                * MMC specs.  ATAPI and ATAPI-derived
+                                * drives don't support it.
+                                */
+                               if (softc->quirks & CD_Q_BCD_TRACKS) {
+                                       args->start_track =
+                                               bin2bcd(args->start_track);
+                                       args->end_track =
+                                               bin2bcd(args->end_track);
+                               }
+                               error = cdplaytracks(periph,
+                                                    args->start_track,
+                                                    args->start_index,
+                                                    args->end_track,
+                                                    args->end_index);
                        }
-                       error = cdplaytracks(periph,
-                                            args->start_track,
-                                            args->start_index,
-                                            args->end_track,
-                                            args->end_index);
                }
                break;
        case CDIOCPLAYMSF:
                {
                        struct ioc_play_msf *args
                                = (struct ioc_play_msf *) addr;
-                       struct cd_mode_data *data;
+                       struct cd_mode_params params;
+                       union cd_pages *page;
 
-                       data = malloc(sizeof(struct cd_mode_data), M_TEMP,
-                                     M_WAITOK);
+                       params.alloc_len = sizeof(union cd_mode_data_6_10);
+                       params.mode_buf = malloc(params.alloc_len, M_TEMP,
+                                                M_WAITOK | M_ZERO);
 
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOCPLAYMSF\n"));
 
-                       error = cdgetmode(periph, data, AUDIO_PAGE);
+                       error = cdgetmode(periph, &params, AUDIO_PAGE);
                        if (error) {
-                               free(data, M_TEMP);
+                               free(params.mode_buf, M_TEMP);
                                break;
                        }
-                       data->page.audio.flags &= ~CD_PA_SOTC;
-                       data->page.audio.flags |= CD_PA_IMMED;
-                       error = cdsetmode(periph, data);
-                       free(data, M_TEMP);
+                       page = cdgetpage(&params);
+
+                       page->audio.flags &= ~CD_PA_SOTC;
+                       page->audio.flags |= CD_PA_IMMED;
+                       error = cdsetmode(periph, &params);
+                       free(params.mode_buf, M_TEMP);
                        if (error)
                                break;
                        error = cdplaymsf(periph,
@@ -1912,23 +2108,27 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                {
                        struct ioc_play_blocks *args
                                = (struct ioc_play_blocks *) addr;
-                       struct cd_mode_data *data;
+                       struct cd_mode_params params;
+                       union cd_pages *page;
 
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOCPLAYBLOCKS\n"));
 
-                       data = malloc(sizeof(struct cd_mode_data), M_TEMP,
-                                     M_WAITOK);
+                       params.alloc_len = sizeof(union cd_mode_data_6_10);
+                       params.mode_buf = malloc(params.alloc_len, M_TEMP,
+                                                M_WAITOK | M_ZERO);
 
-                       error = cdgetmode(periph, data, AUDIO_PAGE);
+                       error = cdgetmode(periph, &params, AUDIO_PAGE);
                        if (error) {
-                               free(data, M_TEMP);
+                               free(params.mode_buf, M_TEMP);
                                break;
                        }
-                       data->page.audio.flags &= ~CD_PA_SOTC;
-                       data->page.audio.flags |= CD_PA_IMMED;
-                       error = cdsetmode(periph, data);
-                       free(data, M_TEMP);
+                       page = cdgetpage(&params);
+
+                       page->audio.flags &= ~CD_PA_SOTC;
+                       page->audio.flags |= CD_PA_IMMED;
+                       error = cdsetmode(periph, &params);
+                       free(params.mode_buf, M_TEMP);
                        if (error)
                                break;
                        error = cdplay(periph, args->blk, args->len);
@@ -1990,9 +2190,8 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
 
                        th = malloc(sizeof(struct ioc_toc_header), M_TEMP,
                                    M_WAITOK);
-                       error = cdreadtoc(periph, 0, 0, 
-                                         (struct cd_toc_entry *)th, 
-                                         sizeof (*th));
+                       error = cdreadtoc(periph, 0, 0, (u_int8_t *)th, 
+                                         sizeof (*th), /*sense_flags*/0);
                        if (error) {
                                free(th, M_TEMP);
                                break;
@@ -2012,17 +2211,8 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                break;
        case CDIOREADTOCENTRYS:
                {
-                       typedef struct {
-                               struct ioc_toc_header header;
-                               struct cd_toc_entry entries[100];
-                       } data_t;
-                       typedef struct {
-                               struct ioc_toc_header header;
-                               struct cd_toc_entry entry;
-                       } lead_t;
-
-                       data_t *data;
-                       lead_t *lead;
+                       struct cd_tocdata *data;
+                       struct cd_toc_single *lead;
                        struct ioc_read_toc_entry *te =
                                (struct ioc_read_toc_entry *) addr;
                        struct ioc_toc_header *th;
@@ -2032,8 +2222,8 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOREADTOCENTRYS\n"));
 
-                       data = malloc(sizeof(data_t), M_TEMP, M_WAITOK);
-                       lead = malloc(sizeof(lead_t), M_TEMP, M_WAITOK);
+                       data = malloc(sizeof(*data), M_TEMP, M_WAITOK);
+                       lead = malloc(sizeof(*lead), M_TEMP, M_WAITOK);
 
                        if (te->data_len < sizeof(struct cd_toc_entry)
                         || (te->data_len % sizeof(struct cd_toc_entry)) != 0
@@ -2048,9 +2238,8 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                        }
 
                        th = &data->header;
-                       error = cdreadtoc(periph, 0, 0, 
-                                         (struct cd_toc_entry *)th, 
-                                         sizeof (*th));
+                       error = cdreadtoc(periph, 0, 0, (u_int8_t *)th, 
+                                         sizeof (*th), /*sense_flags*/0);
                        if (error) {
                                free(data, M_TEMP);
                                free(lead, M_TEMP);
@@ -2104,8 +2293,9 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                        if (readlen > 0) {
                                error = cdreadtoc(periph, te->address_format,
                                                  starting_track,
-                                                 (struct cd_toc_entry *)data,
-                                                 readlen + sizeof (*th));
+                                                 (u_int8_t *)data,
+                                                 readlen + sizeof (*th),
+                                                 /*sense_flags*/0);
                                if (error) {
                                        free(data, M_TEMP);
                                        free(lead, M_TEMP);
@@ -2119,9 +2309,9 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                                th->ending_track = bcd2bin(th->ending_track);
                        if (idx == th->ending_track + 1) {
                                error = cdreadtoc(periph, te->address_format,
-                                                 LEADOUT, 
-                                                 (struct cd_toc_entry *)lead,
-                                                 sizeof(*lead));
+                                                 LEADOUT, (u_int8_t *)lead,
+                                                 sizeof(*lead),
+                                                 /*sense_flags*/0);
                                if (error) {
                                        free(data, M_TEMP);
                                        free(lead, M_TEMP);
@@ -2144,13 +2334,7 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                break;
        case CDIOREADTOCENTRY:
                {
-                       /* yeah yeah, this is ugly */
-                       typedef struct {
-                               struct ioc_toc_header header;
-                               struct cd_toc_entry entry;
-                       } data_t;
-
-                       data_t *data;
+                       struct cd_toc_single *data;
                        struct ioc_read_toc_single_entry *te =
                                (struct ioc_read_toc_single_entry *) addr;
                        struct ioc_toc_header *th;
@@ -2159,7 +2343,7 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOREADTOCENTRY\n"));
 
-                       data = malloc(sizeof(data_t), M_TEMP, M_WAITOK);
+                       data = malloc(sizeof(*data), M_TEMP, M_WAITOK);
 
                        if (te->address_format != CD_MSF_FORMAT
                            && te->address_format != CD_LBA_FORMAT) {
@@ -2171,9 +2355,8 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                        }
 
                        th = &data->header;
-                       error = cdreadtoc(periph, 0, 0, 
-                                         (struct cd_toc_entry *)th,
-                                         sizeof (*th));
+                       error = cdreadtoc(periph, 0, 0, (u_int8_t *)th,
+                                         sizeof (*th), /*sense_flags*/0);
                        if (error) {
                                free(data, M_TEMP);
                                break;
@@ -2202,8 +2385,8 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                        }
 
                        error = cdreadtoc(periph, te->address_format, track,
-                                         (struct cd_toc_entry *)data,
-                                         sizeof(data_t));
+                                         (u_int8_t *)data, sizeof(*data),
+                                         /*sense_flags*/0);
                        if (error) {
                                free(data, M_TEMP);
                                break;
@@ -2218,196 +2401,226 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                break;
        case CDIOCSETPATCH:
                {
-                       struct ioc_patch *arg = (struct ioc_patch *) addr;
-                       struct cd_mode_data *data;
+                       struct ioc_patch *arg = (struct ioc_patch *)addr;
+                       struct cd_mode_params params;
+                       union cd_pages *page;
 
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOCSETPATCH\n"));
 
-                       data = malloc(sizeof(struct cd_mode_data), M_TEMP, 
-                                     M_WAITOK);
-                       error = cdgetmode(periph, data, AUDIO_PAGE);
+                       params.alloc_len = sizeof(union cd_mode_data_6_10);
+                       params.mode_buf = malloc(params.alloc_len, M_TEMP, 
+                                                M_WAITOK | M_ZERO);
+                       error = cdgetmode(periph, &params, AUDIO_PAGE);
                        if (error) {
-                               free(data, M_TEMP);
+                               free(params.mode_buf, M_TEMP);
                                break;
                        }
-                       data->page.audio.port[LEFT_PORT].channels = 
+                       page = cdgetpage(&params);
+
+                       page->audio.port[LEFT_PORT].channels = 
                                arg->patch[0];
-                       data->page.audio.port[RIGHT_PORT].channels = 
+                       page->audio.port[RIGHT_PORT].channels = 
                                arg->patch[1];
-                       data->page.audio.port[2].channels = arg->patch[2];
-                       data->page.audio.port[3].channels = arg->patch[3];
-                       error = cdsetmode(periph, data);
-                       free(data, M_TEMP);
+                       page->audio.port[2].channels = arg->patch[2];
+                       page->audio.port[3].channels = arg->patch[3];
+                       error = cdsetmode(periph, &params);
+                       free(params.mode_buf, M_TEMP);
                }
                break;
        case CDIOCGETVOL:
                {
                        struct ioc_vol *arg = (struct ioc_vol *) addr;
-                       struct cd_mode_data *data;
+                       struct cd_mode_params params;
+                       union cd_pages *page;
 
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOCGETVOL\n"));
 
-                       data = malloc(sizeof(struct cd_mode_data), M_TEMP, 
-                                     M_WAITOK);
-                       error = cdgetmode(periph, data, AUDIO_PAGE);
+                       params.alloc_len = sizeof(union cd_mode_data_6_10);
+                       params.mode_buf = malloc(params.alloc_len, M_TEMP, 
+                                                M_WAITOK | M_ZERO);
+                       error = cdgetmode(periph, &params, AUDIO_PAGE);
                        if (error) {
-                               free(data, M_TEMP);
+                               free(params.mode_buf, M_TEMP);
                                break;
                        }
+                       page = cdgetpage(&params);
+
                        arg->vol[LEFT_PORT] = 
-                               data->page.audio.port[LEFT_PORT].volume;
+                               page->audio.port[LEFT_PORT].volume;
                        arg->vol[RIGHT_PORT] = 
-                               data->page.audio.port[RIGHT_PORT].volume;
-                       arg->vol[2] = data->page.audio.port[2].volume;
-                       arg->vol[3] = data->page.audio.port[3].volume;
-                       free(data, M_TEMP);
+                               page->audio.port[RIGHT_PORT].volume;
+                       arg->vol[2] = page->audio.port[2].volume;
+                       arg->vol[3] = page->audio.port[3].volume;
+                       free(params.mode_buf, M_TEMP);
                }
                break;
        case CDIOCSETVOL:
                {
                        struct ioc_vol *arg = (struct ioc_vol *) addr;
-                       struct cd_mode_data *data;
+                       struct cd_mode_params params;
+                       union cd_pages *page;
 
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOCSETVOL\n"));
 
-                       data = malloc(sizeof(struct cd_mode_data), M_TEMP, 
-                                     M_WAITOK);
-                       error = cdgetmode(periph, data, AUDIO_PAGE);
+                       params.alloc_len = sizeof(union cd_mode_data_6_10);
+                       params.mode_buf = malloc(params.alloc_len, M_TEMP, 
+                                                M_WAITOK | M_ZERO);
+                       error = cdgetmode(periph, &params, AUDIO_PAGE);
                        if (error) {
-                               free(data, M_TEMP);
+                               free(params.mode_buf, M_TEMP);
                                break;
                        }
-                       data->page.audio.port[LEFT_PORT].channels = CHANNEL_0;
-                       data->page.audio.port[LEFT_PORT].volume = 
+                       page = cdgetpage(&params);
+
+                       page->audio.port[LEFT_PORT].channels = CHANNEL_0;
+                       page->audio.port[LEFT_PORT].volume = 
                                arg->vol[LEFT_PORT];
-                       data->page.audio.port[RIGHT_PORT].channels = CHANNEL_1;
-                       data->page.audio.port[RIGHT_PORT].volume = 
+                       page->audio.port[RIGHT_PORT].channels = CHANNEL_1;
+                       page->audio.port[RIGHT_PORT].volume = 
                                arg->vol[RIGHT_PORT];
-                       data->page.audio.port[2].volume = arg->vol[2];
-                       data->page.audio.port[3].volume = arg->vol[3];
-                       error = cdsetmode(periph, data);
-                       free(data, M_TEMP);
+                       page->audio.port[2].volume = arg->vol[2];
+                       page->audio.port[3].volume = arg->vol[3];
+                       error = cdsetmode(periph, &params);
+                       free(params.mode_buf, M_TEMP);
                }
                break;
        case CDIOCSETMONO:
                {
-                       struct cd_mode_data *data;
+                       struct cd_mode_params params;
+                       union cd_pages *page;
 
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOCSETMONO\n"));
 
-                       data = malloc(sizeof(struct cd_mode_data), 
-                                     M_TEMP, M_WAITOK);
-                       error = cdgetmode(periph, data, AUDIO_PAGE);
+                       params.alloc_len = sizeof(union cd_mode_data_6_10);
+                       params.mode_buf = malloc(params.alloc_len, M_TEMP,
+                                                M_WAITOK | M_ZERO);
+                       error = cdgetmode(periph, &params, AUDIO_PAGE);
                        if (error) {
-                               free(data, M_TEMP);
+                               free(params.mode_buf, M_TEMP);
                                break;
                        }
-                       data->page.audio.port[LEFT_PORT].channels = 
+                       page = cdgetpage(&params);
+
+                       page->audio.port[LEFT_PORT].channels = 
                                LEFT_CHANNEL | RIGHT_CHANNEL;
-                       data->page.audio.port[RIGHT_PORT].channels = 
+                       page->audio.port[RIGHT_PORT].channels = 
                                LEFT_CHANNEL | RIGHT_CHANNEL;
-                       data->page.audio.port[2].channels = 0;
-                       data->page.audio.port[3].channels = 0;
-                       error = cdsetmode(periph, data);
-                       free(data, M_TEMP);
+                       page->audio.port[2].channels = 0;
+                       page->audio.port[3].channels = 0;
+                       error = cdsetmode(periph, &params);
+                       free(params.mode_buf, M_TEMP);
                }
                break;
        case CDIOCSETSTEREO:
                {
-                       struct cd_mode_data *data;
+                       struct cd_mode_params params;
+                       union cd_pages *page;
 
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOCSETSTEREO\n"));
 
-                       data = malloc(sizeof(struct cd_mode_data), M_TEMP,
-                                     M_WAITOK);
-                       error = cdgetmode(periph, data, AUDIO_PAGE);
+                       params.alloc_len = sizeof(union cd_mode_data_6_10);
+                       params.mode_buf = malloc(params.alloc_len, M_TEMP,
+                                                M_WAITOK | M_ZERO);
+                       error = cdgetmode(periph, &params, AUDIO_PAGE);
                        if (error) {
-                               free(data, M_TEMP);
+                               free(params.mode_buf, M_TEMP);
                                break;
                        }
-                       data->page.audio.port[LEFT_PORT].channels = 
+                       page = cdgetpage(&params);
+
+                       page->audio.port[LEFT_PORT].channels = 
                                LEFT_CHANNEL;
-                       data->page.audio.port[RIGHT_PORT].channels = 
+                       page->audio.port[RIGHT_PORT].channels = 
                                RIGHT_CHANNEL;
-                       data->page.audio.port[2].channels = 0;
-                       data->page.audio.port[3].channels = 0;
-                       error = cdsetmode(periph, data);
-                       free(data, M_TEMP);
+                       page->audio.port[2].channels = 0;
+                       page->audio.port[3].channels = 0;
+                       error = cdsetmode(periph, &params);
+                       free(params.mode_buf, M_TEMP);
                }
                break;
        case CDIOCSETMUTE:
                {
-                       struct cd_mode_data *data;
+                       struct cd_mode_params params;
+                       union cd_pages *page;
 
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOCSETMUTE\n"));
 
-                       data = malloc(sizeof(struct cd_mode_data), M_TEMP,
-                                     M_WAITOK);
-                       error = cdgetmode(periph, data, AUDIO_PAGE);
+                       params.alloc_len = sizeof(union cd_mode_data_6_10);
+                       params.mode_buf = malloc(params.alloc_len, M_TEMP,
+                                                M_WAITOK | M_ZERO);
+                       error = cdgetmode(periph, &params, AUDIO_PAGE);
                        if (error) {
-                               free(data, M_TEMP);
+                               free(&params, M_TEMP);
                                break;
                        }
-                       data->page.audio.port[LEFT_PORT].channels = 0;
-                       data->page.audio.port[RIGHT_PORT].channels = 0;
-                       data->page.audio.port[2].channels = 0;
-                       data->page.audio.port[3].channels = 0;
-                       error = cdsetmode(periph, data);
-                       free(data, M_TEMP);
+                       page = cdgetpage(&params);
+
+                       page->audio.port[LEFT_PORT].channels = 0;
+                       page->audio.port[RIGHT_PORT].channels = 0;
+                       page->audio.port[2].channels = 0;
+                       page->audio.port[3].channels = 0;
+                       error = cdsetmode(periph, &params);
+                       free(params.mode_buf, M_TEMP);
                }
                break;
        case CDIOCSETLEFT:
                {
-                       struct cd_mode_data *data;
+                       struct cd_mode_params params;
+                       union cd_pages *page;
 
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOCSETLEFT\n"));
 
-                       data = malloc(sizeof(struct cd_mode_data), M_TEMP,
-                                     M_WAITOK);
-                       error = cdgetmode(periph, data, AUDIO_PAGE);
+                       params.alloc_len = sizeof(union cd_mode_data_6_10);
+                       params.mode_buf = malloc(params.alloc_len, M_TEMP,
+                                                M_WAITOK | M_ZERO);
+                       
+                       error = cdgetmode(periph, &params, AUDIO_PAGE);
                        if (error) {
-                               free(data, M_TEMP);
+                               free(params.mode_buf, M_TEMP);
                                break;
                        }
-                       data->page.audio.port[LEFT_PORT].channels = 
-                               LEFT_CHANNEL;
-                       data->page.audio.port[RIGHT_PORT].channels = 
-                               LEFT_CHANNEL;
-                       data->page.audio.port[2].channels = 0;
-                       data->page.audio.port[3].channels = 0;
-                       error = cdsetmode(periph, data);
-                       free(data, M_TEMP);
+                       page = cdgetpage(&params);
+
+                       page->audio.port[LEFT_PORT].channels = LEFT_CHANNEL;
+                       page->audio.port[RIGHT_PORT].channels = LEFT_CHANNEL;
+                       page->audio.port[2].channels = 0;
+                       page->audio.port[3].channels = 0;
+                       error = cdsetmode(periph, &params);
+                       free(params.mode_buf, M_TEMP);
                }
                break;
        case CDIOCSETRIGHT:
                {
-                       struct cd_mode_data *data;
+                       struct cd_mode_params params;
+                       union cd_pages *page;
 
                        CAM_DEBUG(periph->path, CAM_DEBUG_SUBTRACE, 
                                  ("trying to do CDIOCSETRIGHT\n"));
 
-                       data = malloc(sizeof(struct cd_mode_data), M_TEMP,
-                                     M_WAITOK);
-                       error = cdgetmode(periph, data, AUDIO_PAGE);
+                       params.alloc_len = sizeof(union cd_mode_data_6_10);
+                       params.mode_buf = malloc(params.alloc_len, M_TEMP,
+                                                M_WAITOK | M_ZERO);
+
+                       error = cdgetmode(periph, &params, AUDIO_PAGE);
                        if (error) {
-                               free(data, M_TEMP);
+                               free(params.mode_buf, M_TEMP);
                                break;
                        }
-                       data->page.audio.port[LEFT_PORT].channels = 
-                               RIGHT_CHANNEL;
-                       data->page.audio.port[RIGHT_PORT].channels = 
-                               RIGHT_CHANNEL;
-                       data->page.audio.port[2].channels = 0;
-                       data->page.audio.port[3].channels = 0;
-                       error = cdsetmode(periph, data);
-                       free(data, M_TEMP);
+                       page = cdgetpage(&params);
+
+                       page->audio.port[LEFT_PORT].channels = RIGHT_CHANNEL;
+                       page->audio.port[RIGHT_PORT].channels = RIGHT_CHANNEL;
+                       page->audio.port[2].channels = 0;
+                       page->audio.port[3].channels = 0;
+                       error = cdsetmode(periph, &params);
+                       free(params.mode_buf, M_TEMP);
                }
                break;
        case CDIOCRESUME:
@@ -2417,13 +2630,40 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
                error = cdpause(periph, 0);
                break;
        case CDIOCSTART:
-               error = cdstartunit(periph);
+               error = cdstartunit(periph, 0);
+               break;
+       case CDIOCCLOSE:
+               error = cdstartunit(periph, 1);
+
+#ifdef notyet
+               if (error != 0)
+                       break;
+
+               /*
+                * The user successfully closed the tray, run
+                * cdcheckmedia() again so we can re-sync the disklabel
+                * information.
+                */
+               cdcheckmedia(periph);
+#endif /* notyet */
                break;
        case CDIOCSTOP:
                error = cdstopunit(periph, 0);
                break;
        case CDIOCEJECT:
                error = cdstopunit(periph, 1);
+
+#ifdef notyet
+               if (error != 0)
+                       break;
+
+               /*
+                * Since we've successfully ejected the media, run
+                * cdcheckmedia() again so we re-sync the disklabel
+                * information.
+                */
+               cdcheckmedia(periph);
+#endif /* notyet */
                break;
        case CDIOCALLOW:
                cdprevent(periph, PR_ALLOW);
@@ -2523,20 +2763,295 @@ cdprevent(struct cam_periph *periph, int action)
        }
 }
 
+int
+cdcheckmedia(struct cam_periph *periph)
+{
+       struct cd_softc *softc;
+       struct ioc_toc_header *toch;
+       struct cd_toc_single leadout;
+       struct ccb_getdev cgd;
+       u_int32_t size, toclen;
+       int error, num_entries, cdindex;
+       int first_track_audio;
+       struct disklabel *label;
+
+       softc = (struct cd_softc *)periph->softc;
+
+       first_track_audio = -1;
+       error = 0;
+
+       cdprevent(periph, PR_PREVENT);
+
+       /*
+        * Build prototype label for whole disk.
+        * Should take information about different data tracks from the
+        * TOC and put it in the partition table.
+        */
+       label = &softc->disk.d_label;
+       bzero(label, sizeof(*label));
+       label->d_type = DTYPE_SCSI;
+
+       /*
+        * Grab the inquiry data to get the vendor and product names.
+        * Put them in the typename and packname for the label.
+        */
+       xpt_setup_ccb(&cgd.ccb_h, periph->path, /*priority*/ 1);
+       cgd.ccb_h.func_code = XPT_GDEV_TYPE;
+       xpt_action((union ccb *)&cgd);
+
+       strncpy(label->d_typename, cgd.inq_data.vendor,
+               min(SID_VENDOR_SIZE, sizeof(label->d_typename)));
+       strncpy(label->d_packname, cgd.inq_data.product,
+               min(SID_PRODUCT_SIZE, sizeof(label->d_packname)));
+               
+       label->d_flags = D_REMOVABLE;
+       /*
+        * Make partition 'a' cover the whole disk.  This is a temporary
+        * compatibility hack.  The 'a' partition should not exist, so
+        * the slice code won't create it.  The slice code will make
+        * partition (RAW_PART + 'a') cover the whole disk and fill in
+        * some more defaults.
+        */
+       label->d_partitions[0].p_size = label->d_secperunit;
+       label->d_partitions[0].p_fstype = FS_OTHER;
+
+       /*
+        * Default to not reading the disklabel off the disk, until we can
+        * verify that we have media, that we have a table of contents, and
+        * that the first track is a data track (which could theoretically
+        * contain a disklabel).
+        */
+       softc->disk.d_dsflags &= ~DSO_COMPATLABEL;
+       softc->disk.d_dsflags |= DSO_NOLABELS;
+
+       /*
+        * Clear the valid media and TOC flags until we've verified that we
+        * have both.
+        */
+       softc->flags &= ~(CD_FLAG_VALID_MEDIA|CD_FLAG_VALID_TOC);
+
+       /*
+        * Get the disc size and block size.  If we can't get it, we don't
+        * have media, most likely.
+        */
+       if ((error = cdsize(periph, &size)) != 0) {
+               /*
+                * Set a bogus sector size, so the slice code won't try to
+                * divide by 0 and panic the kernel.
+                */
+               label->d_secsize = 2048;
+
+               label->d_secperunit = 0;
+
+               /*
+                * XXX KDM is this a good idea?  Seems to cause more
+                * problems.
+                */
+               if (softc->flags & CD_FLAG_OPEN) {
+                       int force;
+
+                       force = 1;
+
+                       /*
+                        * We don't bother checking the return value here,
+                        * since we already have an error...
+                        */
+                       dsioctl(softc->disk.d_cdev, DIOCSYNCSLICEINFO,
+                               /*data*/(caddr_t)&force, /*flags*/ 0,
+                               &softc->disk.d_slice);
+               }
+
+               /*
+                * Tell devstat(9) we don't have a blocksize.
+                */
+               softc->device_stats.flags |= DEVSTAT_BS_UNAVAILABLE;
+
+               cdprevent(periph, PR_ALLOW);
+
+               return (error);
+       } else {
+
+               label->d_secsize = softc->params.blksize;
+               label->d_secperunit = softc->params.disksize;
+
+               /*
+                * Force a re-sync of slice information, like the blocksize,
+                * now that we know it.  It isn't pretty...but according to
+                * Bruce Evans, this is probably the best way to do it in
+                * -stable.  We only do this if we're already open, and
+                * therefore dsopen() has already run.  If CD_FLAG_OPEN
+                * isn't set, this isn't necessary.
+                */
+               if (softc->flags & CD_FLAG_OPEN) {
+                       int force;
+
+                       force = 1;
+
+                       error = dsioctl(softc->disk.d_cdev, DIOCSYNCSLICEINFO,
+                                       /*data*/(caddr_t)&force, /*flags*/ 0,
+                                       &softc->disk.d_slice);
+                       if (error != 0) {
+                               /*
+                                * Set a bogus sector size, so the slice code
+                                * won't try to divide by 0 and panic the
+                                * kernel.
+                                */
+                               label->d_secsize = 2048;
+
+                               label->d_secperunit = 0;
+
+                               /*
+                                * Tell devstat(9) we don't have a blocksize.
+                                */
+                               softc->device_stats.flags |=
+                                       DEVSTAT_BS_UNAVAILABLE;
+
+                               cdprevent(periph, PR_ALLOW);
+                       }
+               }
+
+               /*
+                * We unconditionally (re)set the blocksize each time the
+                * CD device is opened.  This is because the CD can change,
+                * and therefore the blocksize might change.
+                * XXX problems here if some slice or partition is still
+                * open with the old size?
+                */
+               if ((softc->device_stats.flags & DEVSTAT_BS_UNAVAILABLE) != 0)
+                       softc->device_stats.flags &= ~DEVSTAT_BS_UNAVAILABLE;
+               softc->device_stats.block_size = softc->params.blksize;
+
+               softc->flags |= CD_FLAG_VALID_MEDIA;
+       }
+
+       /*
+        * Now we check the table of contents.  This (currently) is only
+        * used for the CDIOCPLAYTRACKS ioctl.  It may be used later to do
+        * things like present a separate entry in /dev for each track,
+        * like that acd(4) driver does.
+        */
+       bzero(&softc->toc, sizeof(softc->toc));
+       toch = &softc->toc.header;
+
+       /*
+        * We will get errors here for media that doesn't have a table of
+        * contents.  According to the MMC-3 spec: "When a Read TOC/PMA/ATIP
+        * command is presented for a DDCD/CD-R/RW media, where the first TOC
+        * has not been recorded (no complete session) and the Format codes
+        * 0000b, 0001b, or 0010b are specified, this command shall be rejected
+        * with an INVALID FIELD IN CDB.  Devices that are not capable of
+        * reading an incomplete session on DDC/CD-R/RW media shall report
+        * CANNOT READ MEDIUM - INCOMPATIBLE FORMAT."
+        *
+        * So this isn't fatal if we can't read the table of contents, it
+        * just means that the user won't be able to issue the play tracks
+        * ioctl, and likely lots of other stuff won't work either.  They
+        * need to burn the CD before we can do a whole lot with it.  So
+        * we don't print anything here if we get an error back.
+        */
+       error = cdreadtoc(periph, 0, 0, (u_int8_t *)toch, sizeof(*toch),
+                         SF_NO_PRINT);
+       /*
+        * Errors in reading the table of contents aren't fatal, we just
+        * won't have a valid table of contents cached.
+        */
+       if (error != 0) {
+               error = 0;
+               bzero(&softc->toc, sizeof(softc->toc));
+               goto bailout;
+       }
+
+       if (softc->quirks & CD_Q_BCD_TRACKS) {
+               toch->starting_track = bcd2bin(toch->starting_track);
+               toch->ending_track = bcd2bin(toch->ending_track);
+       }
+
+       /* Number of TOC entries, plus leadout */
+       num_entries = (toch->ending_track - toch->starting_track) + 2;
+
+       if (num_entries <= 0)
+               goto bailout;
+
+       toclen = num_entries * sizeof(struct cd_toc_entry);
+
+       error = cdreadtoc(periph, CD_MSF_FORMAT, toch->starting_track,
+                         (u_int8_t *)&softc->toc, toclen + sizeof(*toch),
+                         SF_NO_PRINT);
+       if (error != 0) {
+               error = 0;
+               bzero(&softc->toc, sizeof(softc->toc));
+               goto bailout;
+       }
+
+       if (softc->quirks & CD_Q_BCD_TRACKS) {
+               toch->starting_track = bcd2bin(toch->starting_track);
+               toch->ending_track = bcd2bin(toch->ending_track);
+       }
+       toch->len = scsi_2btoul((uint8_t *)&toch->len);
+
+       /*
+        * XXX KDM is this necessary?  Probably only if the drive doesn't
+        * return leadout information with the table of contents.
+        */
+       cdindex = toch->starting_track + num_entries -1;
+       if (cdindex == toch->ending_track + 1) {
+
+               error = cdreadtoc(periph, CD_MSF_FORMAT, LEADOUT, 
+                                 (u_int8_t *)&leadout, sizeof(leadout),
+                                 SF_NO_PRINT);
+               if (error != 0) {
+                       error = 0;
+                       goto bailout;
+               }
+               softc->toc.entries[cdindex - toch->starting_track] =
+                       leadout.entry;
+       }
+       if (softc->quirks & CD_Q_BCD_TRACKS) {
+               for (cdindex = 0; cdindex < (num_entries - 1); cdindex++) {
+                       softc->toc.entries[cdindex].track =
+                               bcd2bin(softc->toc.entries[cdindex].track);
+               }
+       }
+
+       /*
+        * Run through the TOC entries, find the first entry and determine
+        * whether it is an audio or data track.
+        */
+       for (cdindex = 0; cdindex < (num_entries - 1); cdindex++) {
+               if (softc->toc.entries[cdindex].track == toch->starting_track) {
+                       if (softc->toc.entries[cdindex].control & 0x04)
+                               first_track_audio = 0;
+                       else
+                               first_track_audio = 1;
+                       break;
+               }
+       }
+
+       /*
+        * If first_track_audio is non-zero, we either have an error (e.g.
+        * couldn't find the starting track) or the first track is an audio
+        * track.  If first_track_audio is 0, the first track is a data
+        * track that could have a disklabel.  Attempt to read the
+        * disklabel off the media, just in case the user put one there.
+        */
+       if (first_track_audio == 0) {
+               softc->disk.d_dsflags |= DSO_COMPATLABEL;
+               softc->disk.d_dsflags &= ~DSO_NOLABELS;
+       }
+       softc->flags |= CD_FLAG_VALID_TOC;
+
+bailout:
+       return (error);
+}
+
 static int
-cdsize(dev_t dev, u_int32_t *size)
+cdsize(struct cam_periph *periph, u_int32_t *size)
 {
-       struct cam_periph *periph;
        struct cd_softc *softc;
        union ccb *ccb;
        struct scsi_read_capacity_data *rcap_buf;
        int error;
 
-       periph = cam_extend_get(cdperiphs, dkunit(dev));
-
-       if (periph == NULL)
-               return (ENXIO);
-        
        CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("entering cdsize\n"));
 
        softc = (struct cd_softc *)periph->softc;
@@ -2544,7 +3059,7 @@ cdsize(dev_t dev, u_int32_t *size)
        ccb = cdgetccb(periph, /* priority */ 1);
 
        rcap_buf = malloc(sizeof(struct scsi_read_capacity_data), 
-                         M_TEMP, M_WAITOK);
+                         M_TEMP, M_INTWAIT | M_ZERO);
 
        scsi_read_capacity(&ccb->csio, 
                           /*retries*/ 1,
@@ -2579,78 +3094,153 @@ cdsize(dev_t dev, u_int32_t *size)
 
 }
 
-/*
- * The idea here is to try to figure out whether the first track is data or
- * audio.  If it is data, we can at least attempt to read a disklabel off
- * the first sector of the disk.  If it is audio, there won't be a
- * disklabel.
- *
- * This routine returns 0 if the first track is data, and non-zero if there
- * is an error or the first track is audio.  (If either non-zero case, we
- * should not attempt to read the disklabel.)
- */
 static int
-cdfirsttrackisdata(struct cam_periph *periph)
+cd6byteworkaround(union ccb *ccb)
 {
-       struct cdtocdata {
-               struct ioc_toc_header header;
-               struct cd_toc_entry entries[100];
-       };
+       u_int8_t *cdb;
+       struct cam_periph *periph;
        struct cd_softc *softc;
-       struct ioc_toc_header *th;
-       struct cdtocdata *data;
-       int num_entries, i;
-       int error, first_track_audio;
-
-       error = 0;
-       first_track_audio = -1;
+       struct cd_mode_params *params;
+       int frozen, found;
 
+       periph = xpt_path_periph(ccb->ccb_h.path);
        softc = (struct cd_softc *)periph->softc;
 
-       data = malloc(sizeof(struct cdtocdata), M_TEMP, M_WAITOK);
+       cdb = ccb->csio.cdb_io.cdb_bytes;
 
-       th = &data->header;
-       error = cdreadtoc(periph, 0, 0, (struct cd_toc_entry *)data,
-                         sizeof(*data));
+       if ((ccb->ccb_h.flags & CAM_CDB_POINTER)
+        || ((cdb[0] != MODE_SENSE_6)
+         && (cdb[0] != MODE_SELECT_6)))
+               return (0);
 
-       if (error)
-               goto bailout;
+       /*
+        * Because there is no convenient place to stash the overall
+        * cd_mode_params structure pointer, we have to grab it like this.
+        * This means that ALL MODE_SENSE and MODE_SELECT requests in the
+        * cd(4) driver MUST go through cdgetmode() and cdsetmode()!
+        *
+        * XXX It would be nice if, at some point, we could increase the
+        * number of available peripheral private pointers.  Both pointers
+        * are currently used in most every peripheral driver.
+        */
+       found = 0;
 
-       if (softc->quirks & CD_Q_BCD_TRACKS) {
-               /* we are going to have to convert the BCD
-                * encoding on the cd to what is expected
-                */
-               th->starting_track =
-                   bcd2bin(th->starting_track);
-               th->ending_track = bcd2bin(th->ending_track);
+       STAILQ_FOREACH(params, &softc->mode_queue, links) {
+               if (params->mode_buf == ccb->csio.data_ptr) {
+                       found = 1;
+                       break;
+               }
        }
-       th->len = scsi_2btoul((u_int8_t *)&th->len);
 
-       if ((th->len - 2) > 0)
-               num_entries = (th->len - 2) / sizeof(struct cd_toc_entry);
-       else
-               num_entries = 0;
+       /*
+        * This shouldn't happen.  All mode sense and mode select
+        * operations in the cd(4) driver MUST go through cdgetmode() and
+        * cdsetmode()!
+        */
+       if (found == 0) {
+               xpt_print_path(periph->path);
+               printf("mode buffer not found in mode queue!\n");
+               return (0);
+       }
 
-       for (i = 0; i < num_entries; i++) {
-               if (data->entries[i].track == th->starting_track) {
-                       if (data->entries[i].control & 0x4)
-                               first_track_audio = 0;
-                       else
-                               first_track_audio = 1;
-                       break;
-               }
+       params->cdb_size = 10;
+       softc->minimum_command_size = 10;
+       xpt_print_path(ccb->ccb_h.path);
+       printf("%s(6) failed, increasing minimum CDB size to 10 bytes\n",
+              (cdb[0] == MODE_SENSE_6) ? "MODE_SENSE" : "MODE_SELECT");
+
+       if (cdb[0] == MODE_SENSE_6) {
+               struct scsi_mode_sense_10 ms10;
+               struct scsi_mode_sense_6 *ms6;
+               int len;
+
+               ms6 = (struct scsi_mode_sense_6 *)cdb;
+
+               bzero(&ms10, sizeof(ms10));
+               ms10.opcode = MODE_SENSE_10;
+               ms10.byte2 = ms6->byte2;
+               ms10.page = ms6->page;
+
+               /*
+                * 10 byte mode header, block descriptor,
+                * sizeof(union cd_pages)
+                */
+               len = sizeof(struct cd_mode_data_10);
+               ccb->csio.dxfer_len = len;
+
+               scsi_ulto2b(len, ms10.length);
+               ms10.control = ms6->control;
+               bcopy(&ms10, cdb, 10);
+               ccb->csio.cdb_len = 10;
+       } else {
+               struct scsi_mode_select_10 ms10;
+               struct scsi_mode_select_6 *ms6;
+               struct scsi_mode_header_6 *header6;
+               struct scsi_mode_header_10 *header10;
+               struct scsi_mode_page_header *page_header;
+               int blk_desc_len, page_num, page_size, len;
+
+               ms6 = (struct scsi_mode_select_6 *)cdb;
+
+               bzero(&ms10, sizeof(ms10));
+               ms10.opcode = MODE_SELECT_10;
+               ms10.byte2 = ms6->byte2;
+
+               header6 = (struct scsi_mode_header_6 *)params->mode_buf;
+               header10 = (struct scsi_mode_header_10 *)params->mode_buf;
+
+               page_header = find_mode_page_6(header6);
+               page_num = page_header->page_code;
+
+               blk_desc_len = header6->blk_desc_len;
+
+               page_size = cdgetpagesize(page_num);
+
+               if (page_size != (page_header->page_length +
+                   sizeof(*page_header)))
+                       page_size = page_header->page_length +
+                               sizeof(*page_header);
+
+               len = sizeof(*header10) + blk_desc_len + page_size;
+
+               len = min(params->alloc_len, len);
+
+               /*
+                * Since the 6 byte parameter header is shorter than the 10
+                * byte parameter header, we need to copy the actual mode
+                * page data, and the block descriptor, if any, so things wind
+                * up in the right place.  The regions will overlap, but
+                * bcopy() does the right thing.
+                */
+               bcopy(params->mode_buf + sizeof(*header6),
+                     params->mode_buf + sizeof(*header10),
+                     len - sizeof(*header10));
+
+               /* Make sure these fields are set correctly. */
+               scsi_ulto2b(0, header10->data_length);
+               header10->medium_type = 0;
+               scsi_ulto2b(blk_desc_len, header10->blk_desc_len);
+
+               ccb->csio.dxfer_len = len;
+
+               scsi_ulto2b(len, ms10.length);
+               ms10.control = ms6->control;
+               bcopy(&ms10, cdb, 10);
+               ccb->csio.cdb_len = 10;
        }
 
-       if (first_track_audio == -1)
-               error = ENOENT;
-       else if (first_track_audio == 1)
-               error = EINVAL;
-       else
-               error = 0;
-bailout:
-       free(data, M_TEMP);
+       frozen = (ccb->ccb_h.status & CAM_DEV_QFRZN) != 0;
+       ccb->ccb_h.status = CAM_REQUEUE_REQ;
+       xpt_action(ccb);
+       if (frozen) {
+               cam_release_devq(ccb->ccb_h.path,
+                                /*relsim_flags*/0,
+                                /*openings*/0,
+                                /*timeout*/0,
+                                /*getcount_only*/0);
+       }
 
-       return(error);
+       return (ERESTART);
 }
 
 static int
@@ -2658,10 +3248,37 @@ cderror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags)
 {
        struct cd_softc *softc;
        struct cam_periph *periph;
+       int error;
 
        periph = xpt_path_periph(ccb->ccb_h.path);
        softc = (struct cd_softc *)periph->softc;
 
+       error = 0;
+
+       /*
+        * We use a status of CAM_REQ_INVALID as shorthand -- if a 6 byte
+        * CDB comes back with this particular error, try transforming it
+        * into the 10 byte version.
+        */
+       if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INVALID) {
+               error = cd6byteworkaround(ccb);
+       } else if (((ccb->ccb_h.status & CAM_STATUS_MASK) ==
+                    CAM_SCSI_STATUS_ERROR)
+        && (ccb->ccb_h.status & CAM_AUTOSNS_VALID)
+        && (ccb->csio.scsi_status == SCSI_STATUS_CHECK_COND)
+        && ((ccb->ccb_h.flags & CAM_SENSE_PHYS) == 0)
+        && ((ccb->ccb_h.flags & CAM_SENSE_PTR) == 0)) {
+               int sense_key, error_code, asc, ascq;
+
+               scsi_extract_sense(&ccb->csio.sense_data,
+                                  &error_code, &sense_key, &asc, &ascq);
+               if (sense_key == SSD_KEY_ILLEGAL_REQUEST)
+                       error = cd6byteworkaround(ccb);
+       }
+
+       if (error == ERESTART)
+               return (error);
+
        /*
         * XXX
         * Until we have a better way of doing pack validation,
@@ -2677,7 +3294,7 @@ cderror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags)
  */
 static int 
 cdreadtoc(struct cam_periph *periph, u_int32_t mode, u_int32_t start, 
-           struct cd_toc_entry *data, u_int32_t len)
+         u_int8_t *data, u_int32_t len, u_int32_t sense_flags)
 {
        struct scsi_read_toc *scsi_cmd;
        u_int32_t ntoc;
@@ -2697,7 +3314,7 @@ cdreadtoc(struct cam_periph *periph, u_int32_t mode, u_int32_t start,
                      /* cbfcnp */ cddone, 
                      /* flags */ CAM_DIR_IN,
                      /* tag_action */ MSG_SIMPLE_Q_TAG,
-                     /* data_ptr */ (u_int8_t *)data,
+                     /* data_ptr */ data,
                      /* dxfer_len */ len,
                      /* sense_len */ SSD_FULL_SIZE,
                      sizeof(struct scsi_read_toc),
@@ -2716,7 +3333,8 @@ cdreadtoc(struct cam_periph *periph, u_int32_t mode, u_int32_t start,
        scsi_cmd->op_code = READ_TOC;
 
        error = cdrunccb(ccb, cderror, /*cam_flags*/0,
-                        /*sense_flags*/SF_RETRY_UA|SF_RETRY_SELTO);
+                        /*sense_flags*/SF_RETRY_UA | SF_RETRY_SELTO |
+                        sense_flags);
 
        xpt_release_ccb(ccb);
 
@@ -2770,93 +3388,205 @@ cdreadsubchannel(struct cam_periph *periph, u_int32_t mode,
        return(error);
 }
 
-
+/*
+ * All MODE_SENSE requests in the cd(4) driver MUST go through this
+ * routine.  See comments in cd6byteworkaround() for details.
+ */
 static int
-cdgetmode(struct cam_periph *periph, struct cd_mode_data *data, u_int32_t page)
+cdgetmode(struct cam_periph *periph, struct cd_mode_params *data,
+         u_int32_t page)
 {
-       struct scsi_mode_sense_6 *scsi_cmd;
-        struct ccb_scsiio *csio;
+       struct ccb_scsiio *csio;
+       struct cd_softc *softc;
        union ccb *ccb;
+       int param_len;
        int error;
 
+       softc = (struct cd_softc *)periph->softc;
+
        ccb = cdgetccb(periph, /* priority */ 1);
 
        csio = &ccb->csio;
 
-       bzero(data, sizeof(*data));
-       cam_fill_csio(csio, 
-                     /* retries */ 1, 
-                     /* cbfcnp */ cddone, 
-                     /* flags */ CAM_DIR_IN,
-                     /* tag_action */ MSG_SIMPLE_Q_TAG,
-                     /* data_ptr */ (u_int8_t *)data,
-                     /* dxfer_len */ sizeof(*data),
-                     /* sense_len */ SSD_FULL_SIZE,
-                     sizeof(struct scsi_mode_sense_6),
-                     /* timeout */ 50000);
-
-       scsi_cmd = (struct scsi_mode_sense_6 *)&csio->cdb_io.cdb_bytes;
-       bzero (scsi_cmd, sizeof(*scsi_cmd));
+       data->cdb_size = softc->minimum_command_size;
+       if (data->cdb_size < 10)
+               param_len = sizeof(struct cd_mode_data);
+       else
+               param_len = sizeof(struct cd_mode_data_10);
+
+       /* Don't say we've got more room than we actually allocated */
+       param_len = min(param_len, data->alloc_len);
+
+       scsi_mode_sense_len(csio,
+                           /* retries */ 1,
+                           /* cbfcnp */ cddone,
+                           /* tag_action */ MSG_SIMPLE_Q_TAG,
+                           /* dbd */ 0,
+                           /* page_code */ SMS_PAGE_CTRL_CURRENT,
+                           /* page */ page,
+                           /* param_buf */ data->mode_buf,
+                           /* param_len */ param_len,
+                           /* minimum_cmd_size */ softc->minimum_command_size,
+                           /* sense_len */ SSD_FULL_SIZE,
+                           /* timeout */ 50000);
 
-       scsi_cmd->page = page;
-       scsi_cmd->length = sizeof(*data) & 0xff;
-       scsi_cmd->opcode = MODE_SENSE;
+       /*
+        * It would be nice not to have to do this, but there's no
+        * available pointer in the CCB that would allow us to stuff the
+        * mode params structure in there and retrieve it in
+        * cd6byteworkaround(), so we can set the cdb size.  The cdb size
+        * lets the caller know what CDB size we ended up using, so they
+        * can find the actual mode page offset.
+        */
+       STAILQ_INSERT_TAIL(&softc->mode_queue, data, links);
 
        error = cdrunccb(ccb, cderror, /*cam_flags*/0,
                         /*sense_flags*/SF_RETRY_UA|SF_RETRY_SELTO);
 
        xpt_release_ccb(ccb);
 
-       return(error);
+       STAILQ_REMOVE(&softc->mode_queue, data, cd_mode_params, links);
+
+       /*
+        * This is a bit of belt-and-suspenders checking, but if we run
+        * into a situation where the target sends back multiple block
+        * descriptors, we might not have enough space in the buffer to
+        * see the whole mode page.  Better to return an error than
+        * potentially access memory beyond our malloced region.
+        */
+       if (error == 0) {
+               u_int32_t data_len;
+
+               if (data->cdb_size == 10) {
+                       struct scsi_mode_header_10 *hdr10;
+
+                       hdr10 = (struct scsi_mode_header_10 *)data->mode_buf;
+                       data_len = scsi_2btoul(hdr10->data_length);
+                       data_len += sizeof(hdr10->data_length);
+               } else {
+                       struct scsi_mode_header_6 *hdr6;
+
+                       hdr6 = (struct scsi_mode_header_6 *)data->mode_buf;
+                       data_len = hdr6->data_length;
+                       data_len += sizeof(hdr6->data_length);
+               }
+
+               /*
+                * Complain if there is more mode data available than we
+                * allocated space for.  This could potentially happen if
+                * we miscalculated the page length for some reason, if the
+                * drive returns multiple block descriptors, or if it sets
+                * the data length incorrectly.
+                */
+               if (data_len > data->alloc_len) {
+                       xpt_print_path(periph->path);
+                       printf("allocated modepage %d length %d < returned "
+                              "length %d\n", page, data->alloc_len, data_len);
+
+                       error = ENOSPC;
+               }
+       }
+       return (error);
 }
 
+/*
+ * All MODE_SELECT requests in the cd(4) driver MUST go through this
+ * routine.  See comments in cd6byteworkaround() for details.
+ */
 static int
-cdsetmode(struct cam_periph *periph, struct cd_mode_data *data)
+cdsetmode(struct cam_periph *periph, struct cd_mode_params *data)
 {
-       struct scsi_mode_select_6 *scsi_cmd;
-        struct ccb_scsiio *csio;
+       struct ccb_scsiio *csio;
+       struct cd_softc *softc;
        union ccb *ccb;
+       int cdb_size, param_len;
        int error;
 
+       softc = (struct cd_softc *)periph->softc;
+
        ccb = cdgetccb(periph, /* priority */ 1);
 
        csio = &ccb->csio;
 
        error = 0;
 
-       cam_fill_csio(csio, 
-                     /* retries */ 1, 
-                     /* cbfcnp */ cddone, 
-                     /* flags */ CAM_DIR_OUT,
-                     /* tag_action */ MSG_SIMPLE_Q_TAG,
-                     /* data_ptr */ (u_int8_t *)data,
-                     /* dxfer_len */ sizeof(*data),
-                     /* sense_len */ SSD_FULL_SIZE,
-                     sizeof(struct scsi_mode_select_6),
-                     /* timeout */ 50000);
-
-       scsi_cmd = (struct scsi_mode_select_6 *)&csio->cdb_io.cdb_bytes;
-
-       bzero(scsi_cmd, sizeof(*scsi_cmd));
-       scsi_cmd->opcode = MODE_SELECT;
-       scsi_cmd->byte2 |= SMS_PF;
-       scsi_cmd->length = sizeof(*data) & 0xff;
-       data->header.data_length = 0;
        /*
-        * SONY drives do not allow a mode select with a medium_type
-        * value that has just been returned by a mode sense; use a
-        * medium_type of 0 (Default) instead.
+        * If the data is formatted for the 10 byte version of the mode
+        * select parameter list, we need to use the 10 byte CDB.
+        * Otherwise, we use whatever the stored minimum command size.
         */
-       data->header.medium_type = 0;
+       if (data->cdb_size == 10)
+               cdb_size = data->cdb_size;
+       else
+               cdb_size = softc->minimum_command_size;
+
+       if (cdb_size >= 10) {
+               struct scsi_mode_header_10 *mode_header;
+               u_int32_t data_len;
+
+               mode_header = (struct scsi_mode_header_10 *)data->mode_buf;
+
+               data_len = scsi_2btoul(mode_header->data_length);
+
+               scsi_ulto2b(0, mode_header->data_length);
+               /*
+                * SONY drives do not allow a mode select with a medium_type
+                * value that has just been returned by a mode sense; use a
+                * medium_type of 0 (Default) instead.
+                */
+               mode_header->medium_type = 0;
+
+               /*
+                * Pass back whatever the drive passed to us, plus the size
+                * of the data length field.
+                */
+               param_len = data_len + sizeof(mode_header->data_length);
+
+       } else {
+               struct scsi_mode_header_6 *mode_header;
+
+               mode_header = (struct scsi_mode_header_6 *)data->mode_buf;
+
+               param_len = mode_header->data_length + 1;
+
+               mode_header->data_length = 0;
+               /*
+                * SONY drives do not allow a mode select with a medium_type
+                * value that has just been returned by a mode sense; use a
+                * medium_type of 0 (Default) instead.
+                */
+               mode_header->medium_type = 0;
+       }
+
+       /* Don't say we've got more room than we actually allocated */
+       param_len = min(param_len, data->alloc_len);
+
+       scsi_mode_select_len(csio,
+                            /* retries */ 1,
+                            /* cbfcnp */ cddone,
+                            /* tag_action */ MSG_SIMPLE_Q_TAG,
+                            /* scsi_page_fmt */ 1,
+                            /* save_pages */ 0,
+                            /* param_buf */ data->mode_buf,
+                            /* param_len */ param_len,
+                            /* minimum_cmd_size */ cdb_size,
+                            /* sense_len */ SSD_FULL_SIZE,
+                            /* timeout */ 50000);
+
+       /* See comments in cdgetmode() and cd6byteworkaround(). */
+       STAILQ_INSERT_TAIL(&softc->mode_queue, data, links);
 
        error = cdrunccb(ccb, cderror, /*cam_flags*/0,
                         /*sense_flags*/SF_RETRY_UA | SF_RETRY_SELTO);
 
        xpt_release_ccb(ccb);
 
-       return(error);
+       STAILQ_REMOVE(&softc->mode_queue, data, cd_mode_params, links);
+
+       return (error);
 }
 
+  
 
 static int 
 cdplay(struct cam_periph *periph, u_int32_t blk, u_int32_t len)
@@ -3042,7 +3772,7 @@ cdpause(struct cam_periph *periph, u_int32_t go)
 }
 
 static int
-cdstartunit(struct cam_periph *periph)
+cdstartunit(struct cam_periph *periph, int load)
 {
        union ccb *ccb;
        int error;
@@ -3056,7 +3786,7 @@ cdstartunit(struct cam_periph *periph)
                        /* cbfcnp */ cddone,
                        /* tag_action */ MSG_SIMPLE_Q_TAG,
                        /* start */ TRUE,
-                       /* load_eject */ FALSE,
+                       /* load_eject */ load,
                        /* immediate */ FALSE,
                        /* sense_len */ SSD_FULL_SIZE,
                        /* timeout */ 50000);
@@ -3187,9 +3917,10 @@ cdreportkey(struct cam_periph *periph, struct dvd_authinfo *authinfo)
        }
 
        if (length != 0) {
-               databuf = malloc(length, M_DEVBUF, M_WAITOK | M_ZERO);
-       } else
+               databuf = malloc(length, M_DEVBUF, M_INTWAIT | M_ZERO);
+       } else {
                databuf = NULL;
+       }
 
 
        scsi_report_key(&ccb->csio,
@@ -3320,7 +4051,7 @@ cdsendkey(struct cam_periph *periph, struct dvd_authinfo *authinfo)
 
                length = sizeof(*challenge_data);
 
-               challenge_data = malloc(length, M_DEVBUF, M_WAITOK | M_ZERO);
+               challenge_data = malloc(length, M_DEVBUF, M_INTWAIT | M_ZERO);
 
                databuf = (u_int8_t *)challenge_data;
 
@@ -3337,7 +4068,7 @@ cdsendkey(struct cam_periph *periph, struct dvd_authinfo *authinfo)
 
                length = sizeof(*key2_data);
 
-               key2_data = malloc(length, M_DEVBUF, M_WAITOK | M_ZERO);
+               key2_data = malloc(length, M_DEVBUF, M_INTWAIT | M_ZERO);
 
                databuf = (u_int8_t *)key2_data;
 
@@ -3354,7 +4085,7 @@ cdsendkey(struct cam_periph *periph, struct dvd_authinfo *authinfo)
 
                length = sizeof(*rpc_data);
 
-               rpc_data = malloc(length, M_DEVBUF, M_WAITOK | M_ZERO);
+               rpc_data = malloc(length, M_DEVBUF, M_INTWAIT | M_ZERO);
 
                databuf = (u_int8_t *)rpc_data;
 
@@ -3496,9 +4227,10 @@ cdreaddvdstructure(struct cam_periph *periph, struct dvd_struct *dvdstruct)
        }
 
        if (length != 0) {
-               databuf = malloc(length, M_DEVBUF, M_WAITOK | M_ZERO);
-       } else
+               databuf = malloc(length, M_DEVBUF, M_INTWAIT | M_ZERO);
+       } else {
                databuf = NULL;
+       }
 
        scsi_read_dvd_structure(&ccb->csio,
                                /* retries */ 1,