diff options
author | ken <ken@FreeBSD.org> | 2003-02-21 06:19:38 +0000 |
---|---|---|
committer | ken <ken@FreeBSD.org> | 2003-02-21 06:19:38 +0000 |
commit | 915501ccb2cd04785110ff8472f007e5a0b61101 (patch) | |
tree | 914528d9dce9c7668d13bb7c38e332bc381a4abb /sys/cam | |
parent | f66a29b519183414fcd71d2fcc007723367a37bf (diff) | |
download | FreeBSD-src-915501ccb2cd04785110ff8472f007e5a0b61101.zip FreeBSD-src-915501ccb2cd04785110ff8472f007e5a0b61101.tar.gz |
Fix ATAPI/USB/Firewire CDROM drive handling in cd(4) and hopefully fix
a number of related problems along the way.
- Automatically detect CDROM drives that can't handle 6 byte mode
sense and mode select, and adjust our command size accordingly.
We have to handle this in the cd(4) driver (where the buffers are
allocated), since the parameter list length is different for the
6 and 10 byte mode sense commands.
- Remove MODE_SENSE and MODE_SELECT translation removed in ATAPICAM
and in the umass(4) driver, since there's no way for that to work
properly.
- Add a quirk entry for CDROM drives that just hang when they get a 6
byte mode sense or mode select. The reason for the quirk must be
documented in a PR, and all quirks must be approved by
ken@FreeBSD.org. This is to make sure that we fully understand why
each quirk is needed. Once the CAM_NEW_TRAN_CODE is finished, we
should be able to remove any such quirks, since we'll know what
protocol the drive speaks (SCSI, ATAPI, etc.) and therefore whether
we should use 6 or 10 byte mode sense/select commands.
- Change the way the da(4) handles the no_6_byte sysctl. There is
now a per-drive sysctl to set the minimum command size for that
particular disk. (Since you could have multiple disks with
multiple requirements in one system.)
- Loader tunable support for all the sysctls in the da(4) and cd(4)
drivers.
- Add a CDIOCCLOSE ioctl for cd(4) (bde pointed this out a long
time ago).
- Add a media validation routine (cdcheckmedia()) to the cd(4)
driver, to fix some problems bde pointed out a long time ago. We
now allow open() to succeed no matter what, but if we don't detect
valid media, the user can only issue CDIOCCLOSE or CDIOCEJECT
ioctls.
- The media validation routine also reads the table of contents off
the drive. We use the table of contents to implement the
CDIOCPLAYTRACKS ioctl using the PLAY AUDIO MSF command. The
PLAY AUDIO TRACK INDEX command that we previously used was
deprecated after SCSI-2. It works in every SCSI CDROM I've tried,
but doesn't seem to work on ATAPI CDROM drives. We still use the
play audio track index command if we don't have a valid TOC, but
I suppose it'll fail anyway in that case.
- Add _len() versions of scsi_mode_sense() and scsi_mode_select() so
that we can specify the minimum command length.
- Fix a couple of formatting problems in the sense printing code.
MFC after: 4 weeks
Diffstat (limited to 'sys/cam')
-rw-r--r-- | sys/cam/scsi/scsi_all.c | 35 | ||||
-rw-r--r-- | sys/cam/scsi/scsi_all.h | 17 | ||||
-rw-r--r-- | sys/cam/scsi/scsi_cd.c | 1191 | ||||
-rw-r--r-- | sys/cam/scsi/scsi_cd.h | 85 | ||||
-rw-r--r-- | sys/cam/scsi/scsi_da.c | 97 |
5 files changed, 1117 insertions, 308 deletions
diff --git a/sys/cam/scsi/scsi_all.c b/sys/cam/scsi/scsi_all.c index 1ebbb2c..0f4100d 100644 --- a/sys/cam/scsi/scsi_all.c +++ b/sys/cam/scsi/scsi_all.c @@ -1950,6 +1950,7 @@ scsi_sense_sbuf(struct cam_device *device, struct ccb_scsiio *csio, #else /* !_KERNEL */ scsi_command_string(device, csio, sb); #endif /* _KERNEL/!_KERNEL */ + sbuf_printf(sb, "\n"); } /* @@ -2068,9 +2069,9 @@ scsi_sense_sbuf(struct cam_device *device, struct ccb_scsiio *csio, /* Bit pointer is valid */ if (sense->sense_key_spec[0] & 0x08) snprintf(tmpstr2, sizeof(tmpstr2), - "bit %d", + "bit %d ", sense->sense_key_spec[0] & 0x7); - sbuf_printf(sb, ": %s byte %d %s is invalid", + sbuf_printf(sb, ": %s byte %d %sis invalid", bad_command ? "Command" : "Data", scsi_2btoul( &sense->sense_key_spec[1]), @@ -2447,12 +2448,24 @@ scsi_mode_sense(struct ccb_scsiio *csio, u_int32_t retries, u_int8_t page, u_int8_t *param_buf, u_int32_t param_len, u_int8_t sense_len, u_int32_t timeout) { + return(scsi_mode_sense_len(csio, retries, cbfcnp, tag_action, dbd, + page_code, page, param_buf, param_len, 0, + sense_len, timeout)); +} +void +scsi_mode_sense_len(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + u_int8_t tag_action, int dbd, u_int8_t page_code, + u_int8_t page, u_int8_t *param_buf, u_int32_t param_len, + int minimum_cmd_size, u_int8_t sense_len, u_int32_t timeout) +{ u_int8_t cdb_len; /* * Use the smallest possible command to perform the operation. */ - if (param_len < 256) { + if ((param_len < 256) + && (minimum_cmd_size < 10)) { /* * We can fit in a 6 byte cdb. */ @@ -2500,12 +2513,26 @@ scsi_mode_select(struct ccb_scsiio *csio, u_int32_t retries, u_int8_t *param_buf, u_int32_t param_len, u_int8_t sense_len, u_int32_t timeout) { + return(scsi_mode_select_len(csio, retries, cbfcnp, tag_action, + scsi_page_fmt, save_pages, param_buf, + param_len, 0, sense_len, timeout)); +} + +void +scsi_mode_select_len(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + u_int8_t tag_action, int scsi_page_fmt, int save_pages, + u_int8_t *param_buf, u_int32_t param_len, + int minimum_cmd_size, u_int8_t sense_len, + u_int32_t timeout) +{ u_int8_t cdb_len; /* * Use the smallest possible command to perform the operation. */ - if (param_len < 256) { + if ((param_len < 256) + && (minimum_cmd_size < 10)) { /* * We can fit in a 6 byte cdb. */ diff --git a/sys/cam/scsi/scsi_all.h b/sys/cam/scsi/scsi_all.h index 7841b3c..b972c5a 100644 --- a/sys/cam/scsi/scsi_all.h +++ b/sys/cam/scsi/scsi_all.h @@ -926,6 +926,15 @@ void scsi_mode_sense(struct ccb_scsiio *csio, u_int32_t retries, u_int8_t *param_buf, u_int32_t param_len, u_int8_t sense_len, u_int32_t timeout); +void scsi_mode_sense_len(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, + union ccb *), + u_int8_t tag_action, int dbd, + u_int8_t page_code, u_int8_t page, + u_int8_t *param_buf, u_int32_t param_len, + int minimum_cmd_size, u_int8_t sense_len, + u_int32_t timeout); + void scsi_mode_select(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), @@ -934,6 +943,14 @@ void scsi_mode_select(struct ccb_scsiio *csio, u_int32_t retries, u_int32_t param_len, u_int8_t sense_len, u_int32_t timeout); +void scsi_mode_select_len(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, + union ccb *), + u_int8_t tag_action, int scsi_page_fmt, + int save_pages, u_int8_t *param_buf, + u_int32_t param_len, int minimum_cmd_size, + u_int8_t sense_len, u_int32_t timeout); + void scsi_log_sense(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t page_code, diff --git a/sys/cam/scsi/scsi_cd.c b/sys/cam/scsi/scsi_cd.c index 543714f..f785962 100644 --- a/sys/cam/scsi/scsi_cd.c +++ b/sys/cam/scsi/scsi_cd.c @@ -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, 2002, 2003 Kenneth D. Merry. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -79,11 +79,12 @@ 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 { @@ -95,7 +96,9 @@ typedef enum { CD_FLAG_CHANGER = 0x040, CD_FLAG_ACTIVE = 0x080, CD_FLAG_SCHED_ON_COMP = 0x100, - CD_FLAG_RETRY_UA = 0x200 + CD_FLAG_RETRY_UA = 0x200, + CD_FLAG_VALID_MEDIA = 0x400, + CD_FLAG_VALID_TOC = 0x800 } cd_flags; typedef enum { @@ -116,6 +119,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 @@ -137,6 +150,21 @@ struct cd_softc { struct cam_periph *periph; dev_t dev; eventhandler_tag clonetag; + int minimum_command_size; + 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 { @@ -145,12 +173,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[] = { @@ -186,6 +224,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); @@ -195,21 +234,25 @@ static int cdrunccb(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags), u_int32_t cam_flags, u_int32_t sense_flags); -static union ccb *cdgetccb(struct cam_periph *periph, +static union ccb *cdgetccb(struct cam_periph *periph, u_int32_t priority); static void cddone(struct cam_periph *periph, union ccb *start_ccb); +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 cdcheckmedia(struct cam_periph *periph); +static int cdsize(struct cam_periph *periph, u_int32_t *size); +static int cd6byteworkaround(union ccb *ccb); static int cderror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags); -static void cdprevent(struct cam_periph *periph, int action); -static int cdsize(dev_t dev, u_int32_t *size); 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, @@ -226,7 +269,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, @@ -276,8 +319,10 @@ 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; @@ -554,12 +599,50 @@ cdasync(void *callback_arg, u_int32_t code, } } +/* + * 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_getdev *cgd; + char tmpstr[80], tmpstr2[80]; caddr_t match; cgd = (struct ccb_getdev *)arg; @@ -582,6 +665,7 @@ cdregister(struct cam_periph *periph, void *arg) bzero(softc, sizeof(*softc)); LIST_INIT(&softc->pending_ccbs); + STAILQ_INIT(&softc->mode_queue); softc->state = CD_STATE_PROBE; bioq_init(&softc->bio_queue); if (SID_IS_REMOVABLE(&cgd->inq_data)) @@ -605,6 +689,45 @@ cdregister(struct cam_periph *periph, void *arg) else softc->quirks = CD_Q_NONE; + snprintf(tmpstr, sizeof(tmpstr), "CAM CD unit %d", periph->unit_number); + snprintf(tmpstr2, sizeof(tmpstr2), "%d", periph->unit_number); + 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("cdregister: unable to allocate sysctl tree\n"); + free(softc, M_DEVBUF); + return (CAM_REQ_CMP_ERR); + } + + /* 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; + + /* + * 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 need to register the statistics structure for this device, * but we don't have the blocksize yet for it. So, we register @@ -873,7 +996,6 @@ cdopen(dev_t dev, int flags, int fmt, struct thread *td) { struct cam_periph *periph; struct cd_softc *softc; - u_int32_t size; int error; int s; @@ -902,26 +1024,12 @@ 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); - } - /* - * 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? + * 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. */ - if ((softc->device_stats.flags & DEVSTAT_BS_UNAVAILABLE) != 0) - softc->device_stats.flags &= ~DEVSTAT_BS_UNAVAILABLE; - softc->device_stats.block_size = softc->params.blksize; + cdcheckmedia(periph); cam_periph_unlock(periph); @@ -951,10 +1059,15 @@ 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); + cam_periph_unlock(periph); cam_periph_release(periph); @@ -1330,6 +1443,21 @@ cdstrategy(struct bio *bp) return; } + /* + * 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); + biofinish(bp, NULL, error); + return; + } + } + /* * Place it in the queue of disk activities for this disk */ @@ -1743,6 +1871,35 @@ 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) { @@ -1766,6 +1923,19 @@ 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) { @@ -1781,57 +1951,117 @@ cdioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { 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, ¶ms, 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(¶ms); + + page->audio.flags &= ~CD_PA_SOTC; + page->audio.flags |= CD_PA_IMMED; + error = cdsetmode(periph, ¶ms); + 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, ¶ms, 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(¶ms); + + page->audio.flags &= ~CD_PA_SOTC; + page->audio.flags |= CD_PA_IMMED; + error = cdsetmode(periph, ¶ms); + free(params.mode_buf, M_TEMP); if (error) break; error = cdplaymsf(periph, @@ -1847,23 +2077,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, ¶ms, 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(¶ms); + + page->audio.flags &= ~CD_PA_SOTC; + page->audio.flags |= CD_PA_IMMED; + error = cdsetmode(periph, ¶ms); + free(params.mode_buf, M_TEMP); if (error) break; error = cdplay(periph, args->blk, args->len); @@ -1925,9 +2159,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; @@ -1947,17 +2180,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; @@ -1967,8 +2191,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 @@ -1983,9 +2207,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); @@ -2039,8 +2262,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); @@ -2054,9 +2278,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); @@ -2079,13 +2303,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; @@ -2094,7 +2312,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) { @@ -2106,9 +2324,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; @@ -2137,8 +2354,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; @@ -2153,196 +2370,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, ¶ms, AUDIO_PAGE); if (error) { - free(data, M_TEMP); + free(params.mode_buf, M_TEMP); break; } - data->page.audio.port[LEFT_PORT].channels = + page = cdgetpage(¶ms); + + 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, ¶ms); + 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, ¶ms, AUDIO_PAGE); if (error) { - free(data, M_TEMP); + free(params.mode_buf, M_TEMP); break; } + page = cdgetpage(¶ms); + 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, ¶ms, 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(¶ms); + + 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, ¶ms); + 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, ¶ms, AUDIO_PAGE); if (error) { - free(data, M_TEMP); + free(params.mode_buf, M_TEMP); break; } - data->page.audio.port[LEFT_PORT].channels = + page = cdgetpage(¶ms); + + 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, ¶ms); + 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, ¶ms, AUDIO_PAGE); if (error) { - free(data, M_TEMP); + free(params.mode_buf, M_TEMP); break; } - data->page.audio.port[LEFT_PORT].channels = + page = cdgetpage(¶ms); + + 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, ¶ms); + 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, ¶ms, AUDIO_PAGE); if (error) { - free(data, M_TEMP); + free(¶ms, 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(¶ms); + + 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, ¶ms); + 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, ¶ms, 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(¶ms); + + 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, ¶ms); + 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, ¶ms, 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(¶ms); + + 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, ¶ms); + free(params.mode_buf, M_TEMP); } break; case CDIOCRESUME: @@ -2352,7 +2599,10 @@ 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); break; case CDIOCSTOP: error = cdstopunit(periph, 0); @@ -2462,18 +2712,141 @@ cdprevent(struct cam_periph *periph, int action) } static int -cdsize(dev_t dev, u_int32_t *size) +cdcheckmedia(struct cam_periph *periph) +{ + struct cd_softc *softc; + struct ioc_toc_header *toch; + struct cd_toc_single leadout; + u_int32_t size, toclen; + int error, num_entries, cdindex; + + softc = (struct cd_softc *)periph->softc; + + cdprevent(periph, PR_PREVENT); + + /* + * 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) { + softc->flags &= ~(CD_FLAG_VALID_MEDIA|CD_FLAG_VALID_TOC); + cdprevent(periph, PR_ALLOW); + return (error); + } else + 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); + } + /* + * 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); + } + } + + softc->flags |= CD_FLAG_VALID_TOC; + +bailout: + + /* + * 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; + + return (error); +} + +static int +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 = (struct cam_periph *)dev->si_drv1; - if (periph == NULL) - return (ENXIO); - CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("entering cdsize\n")); softc = (struct cd_softc *)periph->softc; @@ -2517,14 +2890,190 @@ cdsize(dev_t dev, u_int32_t *size) } static int +cd6byteworkaround(union ccb *ccb) +{ + u_int8_t *cdb; + struct cam_periph *periph; + struct cd_softc *softc; + struct cd_mode_params *params; + int frozen, found; + + periph = xpt_path_periph(ccb->ccb_h.path); + softc = (struct cd_softc *)periph->softc; + + cdb = ccb->csio.cdb_io.cdb_bytes; + + if ((ccb->ccb_h.flags & CAM_CDB_POINTER) + || ((cdb[0] != MODE_SENSE_6) + && (cdb[0] != MODE_SELECT_6))) + return (0); + + /* + * 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; + + STAILQ_FOREACH(params, &softc->mode_queue, links) { + if (params->mode_buf == ccb->csio.data_ptr) { + found = 1; + break; + } + } + + /* + * 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); + } + + 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; + } + + 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 (ERESTART); +} + +static int 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, @@ -2540,7 +3089,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; @@ -2560,7 +3109,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), @@ -2579,7 +3128,7 @@ 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*/CAM_RETRY_SELTO, - /*sense_flags*/SF_RETRY_UA); + /*sense_flags*/SF_RETRY_UA | sense_flags); xpt_release_ccb(ccb); @@ -2634,90 +3183,202 @@ cdreadsubchannel(struct cam_periph *periph, u_int32_t mode, } +/* + * 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*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); 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*/CAM_RETRY_SELTO, /*sense_flags*/SF_RETRY_UA); xpt_release_ccb(ccb); - return(error); + STAILQ_REMOVE(&softc->mode_queue, data, cd_mode_params, links); + + return (error); } @@ -2905,7 +3566,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; @@ -2919,7 +3580,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); diff --git a/sys/cam/scsi/scsi_cd.h b/sys/cam/scsi/scsi_cd.h index f242139..f502d66 100644 --- a/sys/cam/scsi/scsi_cd.h +++ b/sys/cam/scsi/scsi_cd.h @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2000 Kenneth D. Merry + * Copyright (c) 2000, 2002 Kenneth D. Merry * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -655,38 +655,47 @@ struct scsi_read_cd_cap_data u_int8_t length_0; /* Least significant */ }; -union cd_pages +struct cd_audio_page { - struct audio_page + u_int8_t page_code; +#define CD_PAGE_CODE 0x3F +#define AUDIO_PAGE 0x0e +#define CD_PAGE_PS 0x80 + u_int8_t param_len; + u_int8_t flags; +#define CD_PA_SOTC 0x02 +#define CD_PA_IMMED 0x04 + u_int8_t unused[2]; + u_int8_t format_lba; +#define CD_PA_FORMAT_LBA 0x0F +#define CD_PA_APR_VALID 0x80 + u_int8_t lb_per_sec[2]; + struct port_control { - u_int8_t page_code; -#define CD_PAGE_CODE 0x3F -#define AUDIO_PAGE 0x0e -#define CD_PAGE_PS 0x80 - u_int8_t param_len; - u_int8_t flags; -#define CD_PA_SOTC 0x02 -#define CD_PA_IMMED 0x04 - u_int8_t unused[2]; - u_int8_t format_lba; -#define CD_PA_FORMAT_LBA 0x0F -#define CD_PA_APR_VALID 0x80 - u_int8_t lb_per_sec[2]; - struct port_control - { - u_int8_t channels; -#define CHANNEL 0x0F -#define CHANNEL_0 1 -#define CHANNEL_1 2 -#define CHANNEL_2 4 -#define CHANNEL_3 8 -#define LEFT_CHANNEL CHANNEL_0 -#define RIGHT_CHANNEL CHANNEL_1 - u_int8_t volume; - } port[4]; -#define LEFT_PORT 0 -#define RIGHT_PORT 1 - }audio; + u_int8_t channels; +#define CHANNEL 0x0F +#define CHANNEL_0 1 +#define CHANNEL_1 2 +#define CHANNEL_2 4 +#define CHANNEL_3 8 +#define LEFT_CHANNEL CHANNEL_0 +#define RIGHT_CHANNEL CHANNEL_1 + u_int8_t volume; + } port[4]; +#define LEFT_PORT 0 +#define RIGHT_PORT 1 +}; + +union cd_pages +{ + struct cd_audio_page audio; +}; + +struct cd_mode_data_10 +{ + struct scsi_mode_header_10 header; + struct scsi_mode_blk_desc blk_desc; + union cd_pages page; }; struct cd_mode_data @@ -696,6 +705,20 @@ struct cd_mode_data union cd_pages page; }; +union cd_mode_data_6_10 +{ + struct cd_mode_data mode_data_6; + struct cd_mode_data_10 mode_data_10; +}; + +struct cd_mode_params +{ + STAILQ_ENTRY(cd_mode_params) links; + int cdb_size; + int alloc_len; + u_int8_t *mode_buf; +}; + __BEGIN_DECLS void scsi_report_key(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), diff --git a/sys/cam/scsi/scsi_da.c b/sys/cam/scsi/scsi_da.c index 65878a1..2f1338a 100644 --- a/sys/cam/scsi/scsi_da.c +++ b/sys/cam/scsi/scsi_da.c @@ -128,6 +128,8 @@ struct da_softc { struct disk disk; union ccb saved_ccb; dev_t dev; + struct sysctl_ctx_list sysctl_ctx; + struct sysctl_oid *sysctl_tree; }; struct da_quirk_entry { @@ -479,16 +481,15 @@ static void dashutdown(void *arg, int howto); static int da_retry_count = DA_DEFAULT_RETRY; static int da_default_timeout = DA_DEFAULT_TIMEOUT; -static int da_no_6_byte = 0; SYSCTL_NODE(_kern_cam, OID_AUTO, da, CTLFLAG_RD, 0, "CAM Direct Access Disk driver"); SYSCTL_INT(_kern_cam_da, OID_AUTO, retry_count, CTLFLAG_RW, &da_retry_count, 0, "Normal I/O retry count"); +TUNABLE_INT("kern.cam.da.retry_count", &da_retry_count); SYSCTL_INT(_kern_cam_da, OID_AUTO, default_timeout, CTLFLAG_RW, &da_default_timeout, 0, "Normal I/O timeout (in seconds)"); -SYSCTL_INT(_kern_cam_da, OID_AUTO, no_6_byte, CTLFLAG_RW, - &da_no_6_byte, 0, "No 6 bytes commands"); +TUNABLE_INT("kern.cam.da.default_timeout", &da_default_timeout); /* * DA_ORDEREDTAG_INTERVAL determines how often, relative @@ -1075,6 +1076,38 @@ daasync(void *callback_arg, u_int32_t code, } } +static int +dacmdsizesysctl(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); + + /* + * Acceptable values here are 6, 10 or 12. It's possible we may + * support a 16 byte minimum command size in the future, since + * there are now READ(16) and WRITE(16) commands defined in the + * SBC-2 spec. + */ + if (value < 6) + value = 6; + else if ((value > 6) + && (value <= 10)) + value = 10; + else if (value > 10) + value = 12; + + *(int *)arg1 = value; + + return (0); +} + static cam_status daregister(struct cam_periph *periph, void *arg) { @@ -1082,6 +1115,7 @@ daregister(struct cam_periph *periph, void *arg) struct da_softc *softc; struct ccb_setasync csa; struct ccb_getdev *cgd; + char tmpstr[80], tmpstr2[80]; caddr_t match; cgd = (struct ccb_getdev *)arg; @@ -1127,12 +1161,53 @@ daregister(struct cam_periph *periph, void *arg) else softc->quirks = DA_Q_NONE; + snprintf(tmpstr, sizeof(tmpstr), "CAM DA unit %d", periph->unit_number); + snprintf(tmpstr2, sizeof(tmpstr2), "%d", periph->unit_number); + softc->sysctl_tree = SYSCTL_ADD_NODE(&softc->sysctl_ctx, + SYSCTL_STATIC_CHILDREN(_kern_cam_da), OID_AUTO, tmpstr2, + CTLFLAG_RD, 0, tmpstr); + if (softc->sysctl_tree == NULL) { + printf("daregister: unable to allocate sysctl tree\n"); + free(softc, M_DEVBUF); + return (CAM_REQ_CMP_ERR); + } + + /* + * RBC devices don't have to support READ(6), only READ(10). + */ if (softc->quirks & DA_Q_NO_6_BYTE || SID_TYPE(&cgd->inq_data) == T_RBC) softc->minimum_cmd_size = 10; else softc->minimum_cmd_size = 6; /* + * Load the user's default, if any. + */ + snprintf(tmpstr, sizeof(tmpstr), "kern.cam.da.%d.minimum_cmd_size", + periph->unit_number); + TUNABLE_INT_FETCH(tmpstr, &softc->minimum_cmd_size); + + /* + * 6, 10 and 12 are the currently permissible values. + */ + if (softc->minimum_cmd_size < 6) + softc->minimum_cmd_size = 6; + else if ((softc->minimum_cmd_size > 6) + && (softc->minimum_cmd_size <= 10)) + softc->minimum_cmd_size = 10; + else if (softc->minimum_cmd_size > 12) + softc->minimum_cmd_size = 12; + + /* + * 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_cmd_size, 0, dacmdsizesysctl, "I", + "Minimum CDB size"); + + /* * Block our timeout handler while we * add this softc to the dev list. */ @@ -1233,8 +1308,6 @@ dastart(struct cam_periph *periph, union ccb *start_ccb) } else { tag_code = MSG_SIMPLE_Q_TAG; } - if (da_no_6_byte && softc->minimum_cmd_size == 6) - softc->minimum_cmd_size = 10; scsi_read_write(&start_ccb->csio, /*retries*/da_retry_count, dadone, @@ -1616,7 +1689,7 @@ daerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) { struct da_softc *softc; struct cam_periph *periph; - int error, sense_key, error_code, asc, ascq; + int error; periph = xpt_path_periph(ccb->ccb_h.path); softc = (struct da_softc *)periph->softc; @@ -1626,8 +1699,16 @@ daerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) * READ(6)/WRITE(6) and upgrade to using 10 byte cdbs. */ error = 0; - if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR - && ccb->csio.scsi_status == SCSI_STATUS_CHECK_COND) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INVALID) { + error = cmd6workaround(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) |