Index: share/man/man4/sa.4 =================================================================== --- share/man/man4/sa.4 +++ share/man/man4/sa.4 @@ -25,7 +25,7 @@ .\" .\" $FreeBSD$ .\" -.Dd May 5, 2017 +.Dd September 14, 2021 .Dt SA 4 .Os .Sh NAME @@ -323,6 +323,97 @@ driver does not currently use the RECOVER BUFFERED DATA command. .El +.Sh TIMEOUTS +The +.Nm +driver has a set of default timeouts for SCSI commands (READ, WRITE, TEST UNIT +READY, etc.) that will likely work in most cases for many tape drives. +.Pp +For newer tape drives that claim to support the SPC-4 +standard (SCSI Primary Commands 4) or later standards, the +.Nm +driver will attempt to use the REPORT SUPPORTED OPERATION CODES command to +fetch timeout descriptors from the drive. +If the drive does report timeout descriptors, the +.Nm +driver will use the drive's recommended timeouts for commands. +.Pp +The timeouts in use are reported in units of THOUSANDTHS of a second via +the +.Va kern.cam.sa.%d.timeout.* +.Xr sysctl 8 +variables. +.Pp +To override either the default timeouts, or the timeouts recommended by the +drive, you can set one of two sets of loader tunable values. +If you have a drive that supports the REPORT SUPPORTED OPERATION CODES +timeout descriptors (see the +.Xr camcontrol 8 +.Va opcodes +subcommand) it is generally best to use those values. +The global +.Va kern.cam.sa.timeout.* +values will override the timeouts for all +.Nm +driver instances. +If there are 5 tape drives in the system, they'll all get the same timeouts. +The +.Va kern.cam.sa.%d.timeout.* +values (where %d is the numeric +.Nm +instance number) will override the global timeouts as well as either the +default timeouts or the timeouts recommended by the drive. +.Pp +To set timeouts after boot, the per-instance timeout values, for example: +.Va kern.cam.sa.0.timeout.read , +are available as sysctl variables. +.Pp +Loader tunables: +.Pp +.Bl -tag -compact +.It kern.cam.sa.timeout.erase +.It kern.cam.sa.timeout.load +.It kern.cam.sa.timeout.locate +.It kern.cam.sa.timeout.mode_select +.It kern.cam.sa.timeout.mode_sense +.It kern.cam.sa.timeout.prevent +.It kern.cam.sa.timeout.read +.It kern.cam.sa.timeout.read_position +.It kern.cam.sa.timeout.read_block_limits +.It kern.cam.sa.timeout.report_density +.It kern.cam.sa.timeout.reserve +.It kern.cam.sa.timeout.rewind +.It kern.cam.sa.timeout.space +.It kern.cam.sa.timeout.tur +.It kern.cam.sa.timeout.write +.It kern.cam.sa.timeout.write_filemarks +.El +.Pp +Loader tunable values and +.Xr sysctl 8 +values: +.Pp +.Bl -tag -compact +.It kern.cam.sa.%d.timeout.erase +.It kern.cam.sa.%d.timeout.load +.It kern.cam.sa.%d.timeout.locate +.It kern.cam.sa.%d.timeout.mode_select +.It kern.cam.sa.%d.timeout.mode_sense +.It kern.cam.sa.%d.timeout.prevent +.It kern.cam.sa.%d.timeout.read +.It kern.cam.sa.%d.timeout.read_position +.It kern.cam.sa.%d.timeout.read_block_limits +.It kern.cam.sa.%d.timeout.report_density +.It kern.cam.sa.%d.timeout.reserve +.It kern.cam.sa.%d.timeout.rewind +.It kern.cam.sa.%d.timeout.space +.It kern.cam.sa.%d.timeout.tur +.It kern.cam.sa.%d.timeout.write +.It kern.cam.sa.%d.timeout.write_filemarks +.El +.Pp +As mentioned above, the timeouts are set and reported in THOUSANDTHS of a +second, so be sure to account for that when setting them. .Sh IOCTLS The .Nm Index: sys/cam/scsi/scsi_sa.c =================================================================== --- sys/cam/scsi/scsi_sa.c +++ sys/cam/scsi/scsi_sa.c @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1999, 2000 Matthew Jacob - * Copyright (c) 2013, 2014, 2015 Spectra Logic Corporation + * Copyright (c) 2013, 2014, 2015, 2021 Spectra Logic Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -85,7 +85,7 @@ #define SA_ERASE_TIMEOUT 4 * 60 #endif #ifndef SA_REP_DENSITY_TIMEOUT -#define SA_REP_DENSITY_TIMEOUT 90 +#define SA_REP_DENSITY_TIMEOUT 1 #endif #define SCSIOP_TIMEOUT (60 * 1000) /* not an option */ @@ -115,7 +115,7 @@ static MALLOC_DEFINE(M_SCSISA, "SCSI sa", "SCSI sequential access buffers"); typedef enum { - SA_STATE_NORMAL, SA_STATE_ABNORMAL + SA_STATE_NORMAL, SA_STATE_PROBE, SA_STATE_ABNORMAL } sa_state; #define ccb_pflags ppriv_field0 @@ -125,27 +125,28 @@ #define SA_POSITION_UPDATED 0x1 typedef enum { - SA_FLAG_OPEN = 0x0001, - SA_FLAG_FIXED = 0x0002, - SA_FLAG_TAPE_LOCKED = 0x0004, - SA_FLAG_TAPE_MOUNTED = 0x0008, - SA_FLAG_TAPE_WP = 0x0010, - SA_FLAG_TAPE_WRITTEN = 0x0020, - SA_FLAG_EOM_PENDING = 0x0040, - SA_FLAG_EIO_PENDING = 0x0080, - SA_FLAG_EOF_PENDING = 0x0100, + SA_FLAG_OPEN = 0x00001, + SA_FLAG_FIXED = 0x00002, + SA_FLAG_TAPE_LOCKED = 0x00004, + SA_FLAG_TAPE_MOUNTED = 0x00008, + SA_FLAG_TAPE_WP = 0x00010, + SA_FLAG_TAPE_WRITTEN = 0x00020, + SA_FLAG_EOM_PENDING = 0x00040, + SA_FLAG_EIO_PENDING = 0x00080, + SA_FLAG_EOF_PENDING = 0x00100, SA_FLAG_ERR_PENDING = (SA_FLAG_EOM_PENDING|SA_FLAG_EIO_PENDING| SA_FLAG_EOF_PENDING), - SA_FLAG_INVALID = 0x0200, - SA_FLAG_COMP_ENABLED = 0x0400, - SA_FLAG_COMP_SUPP = 0x0800, - SA_FLAG_COMP_UNSUPP = 0x1000, - SA_FLAG_TAPE_FROZEN = 0x2000, - SA_FLAG_PROTECT_SUPP = 0x4000, + SA_FLAG_INVALID = 0x00200, + SA_FLAG_COMP_ENABLED = 0x00400, + SA_FLAG_COMP_SUPP = 0x00800, + SA_FLAG_COMP_UNSUPP = 0x01000, + SA_FLAG_TAPE_FROZEN = 0x02000, + SA_FLAG_PROTECT_SUPP = 0x04000, SA_FLAG_COMPRESSION = (SA_FLAG_COMP_SUPP|SA_FLAG_COMP_ENABLED| SA_FLAG_COMP_UNSUPP), - SA_FLAG_SCTX_INIT = 0x8000 + SA_FLAG_SCTX_INIT = 0x08000, + SA_FLAG_RSOC_TO_TRY = 0x10000, } sa_flags; typedef enum { @@ -154,6 +155,48 @@ SA_MODE_OFFLINE = 0x02 } sa_mode; +typedef enum { + SA_TIMEOUT_ERASE, + SA_TIMEOUT_LOAD, + SA_TIMEOUT_LOCATE, + SA_TIMEOUT_MODE_SELECT, + SA_TIMEOUT_MODE_SENSE, + SA_TIMEOUT_PREVENT, + SA_TIMEOUT_READ, + SA_TIMEOUT_READ_BLOCK_LIMITS, + SA_TIMEOUT_READ_POSITION, + SA_TIMEOUT_REP_DENSITY, + SA_TIMEOUT_RESERVE, + SA_TIMEOUT_REWIND, + SA_TIMEOUT_SPACE, + SA_TIMEOUT_TUR, + SA_TIMEOUT_WRITE, + SA_TIMEOUT_WRITE_FILEMARKS, + SA_TIMEOUT_TYPE_MAX +} sa_timeout_types; + +static struct sa_timeout_desc { + const char *desc; + int value; +} sa_default_timeouts[SA_TIMEOUT_TYPE_MAX] = { + {"erase", ERASE_TIMEOUT}, + {"load", REWIND_TIMEOUT}, + {"locate", SPACE_TIMEOUT}, + {"mode_select", SCSIOP_TIMEOUT}, + {"mode_sense", SCSIOP_TIMEOUT}, + {"prevent", SCSIOP_TIMEOUT}, + {"read", IO_TIMEOUT}, + {"read_block_limits", SCSIOP_TIMEOUT}, + {"read_position", SCSIOP_TIMEOUT}, + {"report_density", REP_DENSITY_TIMEOUT}, + {"reserve", SCSIOP_TIMEOUT}, + {"rewind", REWIND_TIMEOUT}, + {"space", SPACE_TIMEOUT}, + {"tur", SCSIOP_TIMEOUT}, + {"write", IO_TIMEOUT}, + {"write_filemarks", IO_TIMEOUT}, +}; + typedef enum { SA_PARAM_NONE = 0x000, SA_PARAM_BLOCKSIZE = 0x001, @@ -355,6 +398,7 @@ uint8_t density_type_bits[SA_DENSITY_TYPES]; int density_info_valid[SA_DENSITY_TYPES]; uint8_t density_info[SA_DENSITY_TYPES][SRDS_MAX_LENGTH]; + int timeout_info[SA_TIMEOUT_TYPE_MAX]; struct sa_prot_info prot_info; @@ -413,6 +457,8 @@ struct task sysctl_task; struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; + struct sysctl_ctx_list sysctl_timeout_ctx; + struct sysctl_oid *sysctl_timeout_tree; }; struct sa_quirk_entry { @@ -585,6 +631,8 @@ scsi_space_code code); static void sadevgonecb(void *arg); static void sasetupdev(struct sa_softc *softc, struct cdev *dev); +static void saloadtotunables(struct sa_softc *softc); +static void sasysctlinit(void *context, int pending); static int samount(struct cam_periph *, int, struct cdev *); static int saretension(struct cam_periph *periph); static int sareservereleaseunit(struct cam_periph *periph, @@ -602,6 +650,7 @@ int is_density); static void safilldensitysb(struct sa_softc *softc, int *indent, struct sbuf *sb); +static void saloadtimeouts(struct sa_softc *softc, union ccb *ccb); #ifndef SA_DEFAULT_IO_SPLIT #define SA_DEFAULT_IO_SPLIT 0 @@ -2211,7 +2260,9 @@ cam_periph_unlock(periph); if ((softc->flags & SA_FLAG_SCTX_INIT) != 0 - && sysctl_ctx_free(&softc->sysctl_ctx) != 0) + && (((softc->sysctl_timeout_tree != NULL) + && (sysctl_ctx_free(&softc->sysctl_timeout_ctx) != 0)) + || sysctl_ctx_free(&softc->sysctl_ctx) != 0)) xpt_print(periph->path, "can't remove sysctl context\n"); cam_periph_lock(periph); @@ -2282,12 +2333,36 @@ softc->num_devs_to_destroy++; } +static void +saloadtotunables(struct sa_softc *softc) +{ + int i; + char tmpstr[80]; + + for (i = 0; i < SA_TIMEOUT_TYPE_MAX; i++) { + int tmpval, retval; + + snprintf(tmpstr, sizeof(tmpstr), "kern.cam.sa.timeout.%s", + sa_default_timeouts[i].desc); + retval = TUNABLE_INT_FETCH(tmpstr, &tmpval); + if (retval != 0) + softc->timeout_info[i] = tmpval; + + snprintf(tmpstr, sizeof(tmpstr), "kern.cam.sa.%u.timeout.%s", + softc->periph->unit_number, sa_default_timeouts[i].desc); + retval = TUNABLE_INT_FETCH(tmpstr, &tmpval); + if (retval != 0) + softc->timeout_info[i] = tmpval; + } +} + static void sasysctlinit(void *context, int pending) { struct cam_periph *periph; struct sa_softc *softc; - char tmpstr[32], tmpstr2[16]; + char tmpstr[64], tmpstr2[16]; + int i; periph = (struct cam_periph *)context; /* @@ -2322,6 +2397,23 @@ OID_AUTO, "inject_eom", CTLFLAG_RW, &softc->inject_eom, 0, "Queue EOM for the next write/read"); + sysctl_ctx_init(&softc->sysctl_timeout_ctx); + softc->sysctl_timeout_tree = SYSCTL_ADD_NODE(&softc->sysctl_timeout_ctx, + SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "timeout", + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Timeouts"); + if (softc->sysctl_timeout_tree == NULL) + goto bailout; + + for (i = 0; i < SA_TIMEOUT_TYPE_MAX; i++) { + snprintf(tmpstr, sizeof(tmpstr), "%s timeout", + sa_default_timeouts[i].desc); + + SYSCTL_ADD_INT(&softc->sysctl_timeout_ctx, + SYSCTL_CHILDREN(softc->sysctl_timeout_tree), + OID_AUTO, sa_default_timeouts[i].desc, CTLFLAG_RW, + &softc->timeout_info[i], 0, tmpstr); + } + bailout: /* * Release the reference that was held when this task was enqueued. @@ -2339,6 +2431,7 @@ caddr_t match; char tmpstr[80]; int error; + int i; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) { @@ -2383,6 +2476,15 @@ } else softc->quirks = SA_QUIRK_NONE; + + /* + * Initialize the default timeouts. If this drive supports + * timeout descriptors we'll overwrite these values with the + * recommended timeouts from the drive. + */ + for (i = 0; i < SA_TIMEOUT_TYPE_MAX; i++) + softc->timeout_info[i] = sa_default_timeouts[i].value; + /* * Long format data for READ POSITION was introduced in SSC, which * was after SCSI-2. (Roughly equivalent to SCSI-3.) If the drive @@ -2396,6 +2498,19 @@ if (cgd->inq_data.version <= SCSI_REV_CCS) softc->quirks |= SA_QUIRK_NO_LONG_POS; + /* + * The SCSI REPORT SUPPORTED OPERATION CODES command was added in + * SPC-4. That command optionally includes timeout data for + * different commands. Timeout values can vary wildly among + * different drives, so if the drive itself has recommended values, + * we will try to use them. Set this flag to indicate we're going + * to ask the drive for timeout data. This flag also tells us to + * wait on loading timeout tunables so we can properly override + * timeouts with any user-specified values. + */ + if (SID_ANSI_REV(&cgd->inq_data) >= SCSI_REV_SPC4) + softc->flags |= SA_FLAG_RSOC_TO_TRY; + if (cgd->inq_data.spc3_flags & SPC3_SID_PROTECT) { struct ccb_dev_advinfo cdai; struct scsi_vpd_extended_inquiry_data ext_inq; @@ -2556,8 +2671,28 @@ */ xpt_register_async(AC_LOST_DEVICE, saasync, periph, periph->path); - xpt_announce_periph(periph, NULL); - xpt_announce_quirks(periph, softc->quirks, SA_QUIRK_BIT_STRING); + /* + * See comment above, try fetching timeout values for drives that + * might support it. Otherwise, use the defaults. + */ + if (softc->flags & SA_FLAG_RSOC_TO_TRY) { + /* + * Bump the peripheral refcount while we are probing. + */ + cam_periph_acquire(periph); + softc->state = SA_STATE_PROBE; + xpt_schedule(periph, CAM_PRIORITY_DEV); + } else { + /* + * This drive doesn't support Report Supported Operation + * Codes, so we load the tunables at this point to bring + * in any user preferences. + */ + saloadtotunables(softc); + + xpt_announce_periph(periph, NULL); + xpt_announce_quirks(periph, softc->quirks, SA_QUIRK_BIT_STRING); + } return (CAM_REQ_CMP); } @@ -2752,7 +2887,9 @@ (softc->flags & SA_FLAG_FIXED) != 0, length, (bp->bio_flags & BIO_UNMAPPED) != 0 ? (void *)bp : bp->bio_data, bp->bio_bcount, SSD_FULL_SIZE, - IO_TIMEOUT); + (bp->bio_cmd == BIO_READ) ? + softc->timeout_info[SA_TIMEOUT_READ] : + softc->timeout_info[SA_TIMEOUT_WRITE]); start_ccb->ccb_h.ccb_pflags &= ~SA_POSITION_UPDATED; start_ccb->ccb_h.ccb_bp = bp; bp = bioq_first(&softc->bio_queue); @@ -2765,6 +2902,59 @@ } break; } + case SA_STATE_PROBE: { + int num_opcodes; + size_t alloc_len; + uint8_t *params; + + /* + * This is an arbitrary number. An IBM LTO-6 drive reports + * 67 entries, and an IBM LTO-9 drive reports 71 entries. + * There can theoretically be more than 256 because + * service actions of a particular opcode are reported + * separately, but we're far enough ahead of the practical + * number here that we don't need to implement logic to + * retry if we don't get all the timeout descriptors. + */ + num_opcodes = 256; + + alloc_len = num_opcodes * + (sizeof(struct scsi_report_supported_opcodes_descr) + + sizeof(struct scsi_report_supported_opcodes_timeout)); + + params = malloc(alloc_len, M_SCSISA, M_NOWAIT| M_ZERO); + if (params == NULL) { + /* + * If this happens, go with default + * timeouts and announce the drive. + */ + saloadtotunables(softc); + + softc->state = SA_STATE_NORMAL; + + xpt_announce_periph(periph, NULL); + xpt_announce_quirks(periph, softc->quirks, + SA_QUIRK_BIT_STRING); + xpt_release_ccb(start_ccb); + cam_periph_release_locked(periph); + return; + } + + scsi_report_supported_opcodes(&start_ccb->csio, + /*retries*/ 3, + /*cbfcnp*/ sadone, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*options*/ RSO_RCTD, + /*req_opcode*/ 0, + /*req_service_action*/ 0, + /*data_ptr*/ params, + /*dxfer_len*/ alloc_len, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ softc->timeout_info[SA_TIMEOUT_TUR]); + + xpt_action(start_ccb); + break; + } case SA_STATE_ABNORMAL: default: panic("state 0x%x in sastart", softc->state); @@ -2782,17 +2972,79 @@ softc = (struct sa_softc *)periph->softc; csio = &done_ccb->csio; - - softc->dsreg = MTIO_DSREG_REST; - bp = (struct bio *)done_ccb->ccb_h.ccb_bp; error = 0; - if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { - if ((error = saerror(done_ccb, 0, 0)) == ERESTART) { + + if (softc->state == SA_STATE_NORMAL) { + softc->dsreg = MTIO_DSREG_REST; + bp = (struct bio *)done_ccb->ccb_h.ccb_bp; + + if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + if ((error = saerror(done_ccb, 0, 0)) == ERESTART) { + /* + * A retry was scheduled, so just return. + */ + return; + } + } + } else if (softc->state == SA_STATE_PROBE) { + bp = NULL; + if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { /* - * A retry was scheduled, so just return. + * Note that on probe, we just run through + * cam_periph_error(), since saerror() has a lot of + * special handling for I/O errors. We don't need + * that to get the opcodes. We either succeed + * after a retry or two, or give up. We don't + * print sense, we don't need to worry the user if + * this drive doesn't support timeout descriptors. */ - return; + if ((error = cam_periph_error(done_ccb, 0, + SF_NO_PRINT)) == ERESTART) { + /* + * A retry was scheduled, so just return. + */ + return; + } else if (error != 0) { + /* We failed to get opcodes. Give up. */ + + saloadtotunables(softc); + + softc->state = SA_STATE_NORMAL; + + xpt_release_ccb(done_ccb); + + xpt_announce_periph(periph, NULL); + xpt_announce_quirks(periph, softc->quirks, + SA_QUIRK_BIT_STRING); + cam_periph_release_locked(periph); + return; + } } + /* + * At this point, we have succeeded, so load the timeouts + * and go into the normal state. + */ + softc->state = SA_STATE_NORMAL; + + /* + * First, load the timeouts we got from the drive. + */ + saloadtimeouts(softc, done_ccb); + + /* + * Next, overwrite the timeouts from the drive with any + * loader tunables that the user set. + */ + saloadtotunables(softc); + + xpt_release_ccb(done_ccb); + xpt_announce_periph(periph, NULL); + xpt_announce_quirks(periph, softc->quirks, + SA_QUIRK_BIT_STRING); + cam_periph_release_locked(periph); + return; + } else { + panic("state 0x%x in sadone", softc->state); } if (error == EIO) { @@ -2890,13 +3142,15 @@ if (softc->flags & SA_FLAG_TAPE_MOUNTED) { ccb = cam_periph_getccb(periph, 1); scsi_test_unit_ready(&ccb->csio, 0, NULL, - MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, IO_TIMEOUT); + MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_TUR]); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); if (error == ENXIO) { softc->flags &= ~SA_FLAG_TAPE_MOUNTED; scsi_test_unit_ready(&ccb->csio, 0, NULL, - MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, IO_TIMEOUT); + MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_TUR]); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); } else if (error) { @@ -2917,7 +3171,8 @@ } ccb = cam_periph_getccb(periph, 1); scsi_test_unit_ready(&ccb->csio, 0, NULL, - MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, IO_TIMEOUT); + MSG_SIMPLE_Q_TAG, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_TUR]); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); } @@ -2938,7 +3193,8 @@ * *Very* first off, make sure we're loaded to BOT. */ scsi_load_unload(&ccb->csio, 2, NULL, MSG_SIMPLE_Q_TAG, FALSE, - FALSE, FALSE, 1, SSD_FULL_SIZE, REWIND_TIMEOUT); + FALSE, FALSE, 1, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_LOAD]); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); @@ -2947,7 +3203,8 @@ */ if (error) { scsi_rewind(&ccb->csio, 2, NULL, MSG_SIMPLE_Q_TAG, - FALSE, SSD_FULL_SIZE, REWIND_TIMEOUT); + FALSE, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_REWIND]); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); } @@ -2976,11 +3233,12 @@ scsi_sa_read_write(&ccb->csio, 0, NULL, MSG_SIMPLE_Q_TAG, 1, FALSE, 0, 8192, (void *) rblim, 8192, SSD_FULL_SIZE, - IO_TIMEOUT); + softc->timeout_info[SA_TIMEOUT_READ]); (void) cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); scsi_rewind(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, - FALSE, SSD_FULL_SIZE, REWIND_TIMEOUT); + FALSE, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_REWIND]); error = cam_periph_runccb(ccb, saerror, CAM_RETRY_SELTO, SF_NO_PRINT | SF_RETRY_UA, softc->device_stats); @@ -2996,7 +3254,8 @@ * Next off, determine block limits. */ scsi_read_block_limits(&ccb->csio, 5, NULL, MSG_SIMPLE_Q_TAG, - rblim, SSD_FULL_SIZE, SCSIOP_TIMEOUT); + rblim, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_READ_BLOCK_LIMITS]); error = cam_periph_runccb(ccb, saerror, CAM_RETRY_SELTO, SF_NO_PRINT | SF_RETRY_UA, softc->device_stats); @@ -3610,7 +3869,7 @@ scsi_mode_sense(&ccb->csio, 5, NULL, MSG_SIMPLE_Q_TAG, FALSE, SMS_PAGE_CTRL_CURRENT, (params_to_get & SA_PARAM_COMPRESSION) ? cpage : SMS_VENDOR_SPECIFIC_PAGE, mode_buffer, mode_buffer_len, - SSD_FULL_SIZE, SCSIOP_TIMEOUT); + SSD_FULL_SIZE, softc->timeout_info[SA_TIMEOUT_MODE_SENSE]); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); @@ -3672,7 +3931,7 @@ scsi_mode_sense(&ccb->csio, 2, NULL, MSG_SIMPLE_Q_TAG, FALSE, SMS_PAGE_CTRL_CURRENT, SMS_VENDOR_SPECIFIC_PAGE, mode_buffer, mode_buffer_len, SSD_FULL_SIZE, - SCSIOP_TIMEOUT); + softc->timeout_info[SA_TIMEOUT_MODE_SENSE]); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); @@ -3739,7 +3998,8 @@ /*data_ptr*/ softc->density_info[i], /*length*/ sizeof(softc->density_info[i]), /*sense_len*/ SSD_FULL_SIZE, - /*timeout*/ REP_DENSITY_TIMEOUT); + /*timeout*/ + softc->timeout_info[SA_TIMEOUT_REP_DENSITY]); error = cam_periph_runccb(ccb, saerror, 0, SF_NO_PRINT, softc->device_stats); status = ccb->ccb_h.status & CAM_STATUS_MASK; @@ -3801,7 +4061,8 @@ /*param_len*/ dp_len, /*minimum_cmd_size*/ 10, /*sense_len*/ SSD_FULL_SIZE, - /*timeout*/ SCSIOP_TIMEOUT); + /*timeout*/ + softc->timeout_info[SA_TIMEOUT_MODE_SENSE]); /* * XXX KDM we need to be able to set the subpage in the * fill function. @@ -4029,7 +4290,8 @@ /*param_len*/ dp_len, /*minimum_cmd_size*/ 10, /*sense_len*/ SSD_FULL_SIZE, - /*timeout*/ SCSIOP_TIMEOUT); + /*timeout*/ + softc->timeout_info[SA_TIMEOUT_MODE_SELECT]); error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); if (error != 0) @@ -4299,7 +4561,8 @@ /* It is safe to retry this operation */ scsi_mode_select(&ccb->csio, 5, NULL, MSG_SIMPLE_Q_TAG, (params_to_set & SA_PARAM_COMPRESSION)? TRUE : FALSE, - FALSE, mode_buffer, mode_buffer_len, SSD_FULL_SIZE, SCSIOP_TIMEOUT); + FALSE, mode_buffer, mode_buffer_len, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_MODE_SELECT]); error = cam_periph_runccb(ccb, saerror, 0, sense_flags, softc->device_stats); @@ -4616,7 +4879,7 @@ /* It is safe to retry this operation */ scsi_prevent(&ccb->csio, 5, NULL, MSG_SIMPLE_Q_TAG, action, - SSD_FULL_SIZE, SCSIOP_TIMEOUT); + SSD_FULL_SIZE, softc->timeout_info[SA_TIMEOUT_PREVENT]); error = cam_periph_runccb(ccb, saerror, 0, sf, softc->device_stats); if (error == 0) { @@ -4642,7 +4905,7 @@ /* It is safe to retry this operation */ scsi_rewind(&ccb->csio, 2, NULL, MSG_SIMPLE_Q_TAG, FALSE, - SSD_FULL_SIZE, REWIND_TIMEOUT); + SSD_FULL_SIZE, softc->timeout_info[SA_TIMEOUT_REWIND]); softc->dsreg = MTIO_DSREG_REW; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); @@ -4674,7 +4937,7 @@ /* This cannot be retried */ scsi_space(&ccb->csio, 0, NULL, MSG_SIMPLE_Q_TAG, code, count, - SSD_FULL_SIZE, SPACE_TIMEOUT); + SSD_FULL_SIZE, softc->timeout_info[SA_TIMEOUT_SPACE]); /* * Clear residual because we will be using it. @@ -4755,7 +5018,8 @@ softc->dsreg = MTIO_DSREG_FMK; /* this *must* not be retried */ scsi_write_filemarks(&ccb->csio, 0, NULL, MSG_SIMPLE_Q_TAG, - immed, setmarks, nmarks, SSD_FULL_SIZE, IO_TIMEOUT); + immed, setmarks, nmarks, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_WRITE_FILEMARKS]); softc->dsreg = MTIO_DSREG_REST; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); @@ -4822,7 +5086,8 @@ /*data_ptr*/ (uint8_t *)&long_pos, /*length*/ sizeof(long_pos), /*sense_len*/ SSD_FULL_SIZE, - /*timeout*/ SCSIOP_TIMEOUT); + /*timeout*/ + softc->timeout_info[SA_TIMEOUT_READ_POSITION]); softc->dsreg = MTIO_DSREG_RBSY; error = cam_periph_runccb(ccb, saerror, 0, SF_QUIET_IR, @@ -4918,7 +5183,8 @@ ccb = cam_periph_getccb(periph, 1); scsi_read_position(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, - hard, &loc, SSD_FULL_SIZE, SCSIOP_TIMEOUT); + hard, &loc, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_READ_POSITION]); softc->dsreg = MTIO_DSREG_RBSY; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); softc->dsreg = MTIO_DSREG_REST; @@ -4986,7 +5252,8 @@ /*partition*/ locate_info->partition, /*logical_id*/ locate_info->logical_id, /*sense_len*/ SSD_FULL_SIZE, - /*timeout*/ SPACE_TIMEOUT); + /*timeout*/ + softc->timeout_info[SA_TIMEOUT_LOCATE]); } else { scsi_locate_10(&ccb->csio, /*retries*/ 1, @@ -4998,7 +5265,8 @@ /*partition*/ locate_info->partition, /*block_address*/ locate_info->logical_id, /*sense_len*/ SSD_FULL_SIZE, - /*timeout*/ SPACE_TIMEOUT); + /*timeout*/ + softc->timeout_info[SA_TIMEOUT_LOCATE]); } softc->dsreg = MTIO_DSREG_POS; @@ -5061,7 +5329,8 @@ /* It is safe to retry this operation */ scsi_load_unload(&ccb->csio, 5, NULL, MSG_SIMPLE_Q_TAG, FALSE, - FALSE, TRUE, TRUE, SSD_FULL_SIZE, ERASE_TIMEOUT); + FALSE, TRUE, TRUE, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_LOAD]); softc->dsreg = MTIO_DSREG_TEN; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); @@ -5088,7 +5357,8 @@ /* It is safe to retry this operation */ scsi_reserve_release_unit(&ccb->csio, 2, NULL, MSG_SIMPLE_Q_TAG, - FALSE, 0, SSD_FULL_SIZE, SCSIOP_TIMEOUT, reserve); + FALSE, 0, SSD_FULL_SIZE, softc->timeout_info[SA_TIMEOUT_RESERVE], + reserve); softc->dsreg = MTIO_DSREG_RBSY; error = cam_periph_runccb(ccb, saerror, 0, SF_RETRY_UA | SF_NO_PRINT, softc->device_stats); @@ -5119,7 +5389,8 @@ /* It is safe to retry this operation */ scsi_load_unload(&ccb->csio, 5, NULL, MSG_SIMPLE_Q_TAG, FALSE, - FALSE, FALSE, load, SSD_FULL_SIZE, REWIND_TIMEOUT); + FALSE, FALSE, load, SSD_FULL_SIZE, + softc->timeout_info[SA_TIMEOUT_LOAD]); softc->dsreg = (load)? MTIO_DSREG_LD : MTIO_DSREG_UNL; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); @@ -5151,7 +5422,7 @@ ccb = cam_periph_getccb(periph, 1); scsi_erase(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, FALSE, longerase, - SSD_FULL_SIZE, ERASE_TIMEOUT); + SSD_FULL_SIZE, softc->timeout_info[SA_TIMEOUT_ERASE]); softc->dsreg = MTIO_DSREG_ZER; error = cam_periph_runccb(ccb, saerror, 0, 0, softc->device_stats); @@ -5450,6 +5721,159 @@ SASBENDNODE(sb, *indent, mtdensity); } +/* + * Given a completed REPORT SUPPORTED OPERATION CODES command with timeout + * descriptors, go through the descriptors and set the sa(4) driver + * timeouts to the recommended values. + */ +static void +saloadtimeouts(struct sa_softc *softc, union ccb *ccb) +{ + uint32_t valid_len, avail_len = 0, used_len = 0; + struct scsi_report_supported_opcodes_all *hdr; + struct scsi_report_supported_opcodes_descr *desc; + uint8_t *buf; + + hdr = (struct scsi_report_supported_opcodes_all *)ccb->csio.data_ptr; + valid_len = ccb->csio.dxfer_len - ccb->csio.resid; + + if (valid_len < sizeof(*hdr)) + return; + + avail_len = scsi_4btoul(hdr->length) + sizeof(hdr->length); + if ((avail_len != 0) + && (avail_len > valid_len)) { + xpt_print(softc->periph->path, "WARNING: available timeout " + "descriptor len %zu > valid len %u\n", avail_len,valid_len); + } + + used_len = sizeof(hdr->length); + avail_len = MIN(avail_len, valid_len - sizeof(*hdr)); + buf = ccb->csio.data_ptr; + while ((avail_len - used_len) > sizeof(*desc)) { + struct scsi_report_supported_opcodes_timeout *td; + uint32_t td_len; + uint32_t rec_time; + uint8_t *cur_ptr; + + cur_ptr = &buf[used_len]; + desc = (struct scsi_report_supported_opcodes_descr *)cur_ptr; + + used_len += sizeof(*desc); + /* If there's no timeout descriptor, keep going */ + if ((desc->flags & RSO_CTDP) == 0) + continue; + + /* + * If we don't have enough space to fit a timeout + * descriptor then we're done. + */ + if ((avail_len - used_len) < sizeof(*td)) { + used_len = avail_len; + continue; + } + + cur_ptr = &buf[used_len]; + td = (struct scsi_report_supported_opcodes_timeout *)cur_ptr; + td_len = scsi_2btoul(td->length); + td_len += sizeof(td->length); + used_len += td_len; + + if (td_len < sizeof(*td)) + continue; + + /* + * Use the recommended timeout. The nominal time is the + * time to wait before querying for status. + */ + rec_time = scsi_4btoul(td->recommended_time); + + /* + * Our timeouts are set in thousandths of a seconds. + */ + rec_time *= 1000; + + switch(desc->opcode) { + case ERASE: + softc->timeout_info[SA_TIMEOUT_ERASE] = rec_time; + break; + case LOAD_UNLOAD: + softc->timeout_info[SA_TIMEOUT_LOAD] = rec_time; + break; + case LOCATE: + case LOCATE_16: + /* + * We are assuming these are the same timeout. + */ + softc->timeout_info[SA_TIMEOUT_LOCATE] = rec_time; + break; + case MODE_SELECT_6: + case MODE_SELECT_10: + /* + * We are assuming these are the same timeout. + */ + softc->timeout_info[SA_TIMEOUT_MODE_SELECT] = rec_time; + break; + case MODE_SENSE_6: + case MODE_SENSE_10: + /* + * We are assuming these are the same timeout. + */ + softc->timeout_info[SA_TIMEOUT_MODE_SENSE] = rec_time; + break; + case PREVENT_ALLOW: + softc->timeout_info[SA_TIMEOUT_PREVENT] = rec_time; + break; + case SA_READ: + softc->timeout_info[SA_TIMEOUT_READ] = rec_time; + break; + case READ_BLOCK_LIMITS: + softc->timeout_info[SA_TIMEOUT_READ_BLOCK_LIMITS] = + rec_time; + break; + case READ_POSITION: + /* + * Note that this may show up multiple times for + * the short form, long form and extended form + * service actions. We're assuming they are all + * the same. + */ + softc->timeout_info[SA_TIMEOUT_READ_POSITION] =rec_time; + break; + case REPORT_DENSITY_SUPPORT: + softc->timeout_info[SA_TIMEOUT_REP_DENSITY] = rec_time; + break; + case RESERVE_UNIT: + case RELEASE_UNIT: + /* We are assuming these are the same timeout.*/ + softc->timeout_info[SA_TIMEOUT_RESERVE] = rec_time; + break; + case REWIND: + softc->timeout_info[SA_TIMEOUT_REWIND] = rec_time; + break; + case SPACE: + softc->timeout_info[SA_TIMEOUT_SPACE] = rec_time; + break; + case TEST_UNIT_READY: + softc->timeout_info[SA_TIMEOUT_TUR] = rec_time; + break; + case SA_WRITE: + softc->timeout_info[SA_TIMEOUT_WRITE] = rec_time; + break; + case WRITE_FILEMARKS: + softc->timeout_info[SA_TIMEOUT_WRITE_FILEMARKS] = + rec_time; + break; + default: + /* + * We have explicit cases for all of the timeouts + * we use. + */ + break; + } + } +} + #endif /* _KERNEL */ /*