The st driver currently provides very limited information about the errors. The information in errno after an error usually only tells that the command has failed. The MTIOCGET ioctl gives some information about the status of the device. Most (all?) of the current tape drives use the SCSI command set. It defines the sense data that is returned from the device in case of errors. The sense data contains all of the error information the device provides. What it contains, depends on the device. One possibility is to return the sense data together with some other information and leave the interpretation to user space. In this way the kernel interface can stay constant even when the sense data definition evolves. To see if this idea is useful, here is a proof-of-concept patch for the st driver in Linux 2.6.26-rc9. It defines a new ioctl (MTIOCSENSE) to return the sense data. The returned data is the latest "useful" sense data received. What "useful" actually means, will probably need refinement. Conceptually it means the sense data that is relevant to the latest error condition the user is interested in. For example, sense data from the latest read error, even if the driver would receive other sense data from tape movement commands necessary to implement the tape semantics. In addition to the sense data, the ioctl returns the command resulting in check condition and a flag to tell if the sense data is from the latest SCSI command sent to the device. A small program to test the new ioctl can be found from URL http://www.kolumbus.fi/kai.makisara/st.html The patch below this text applies to 2.6.26-rc9 plus the two bug fixes I just sent to linux-scsi. Some questions: - is this useful? - would someone really use this? - what information should be provided together with the sense data? - should the complete command be saved? - is ioctl the best way to return this information? Kai -----------------------8<----------------------------------------------- diff --git a/Documentation/scsi/st.txt b/Documentation/scsi/st.txt index 4075260..7847655 100644 --- a/Documentation/scsi/st.txt +++ b/Documentation/scsi/st.txt @@ -2,7 +2,7 @@ This file contains brief information about the SCSI tape driver. The driver is currently maintained by Kai Mäkisara (email Kai.Makisara@xxxxxxxxxxx) -Last modified: Fri Jul 11 16:55:44 2008 by kai.makisara +Last modified: Fri Jul 11 16:53:06 2008 by kai.makisara BASICS @@ -291,6 +291,8 @@ The supported ioctls are: The following use the structure mtop: +MTIOCTOP implements the following operations + MTFSF Space forward over count filemarks. Tape positioned after filemark. MTFSFM As above but tape positioned before filemark. MTBSF Space backward over count filemarks. Tape positioned before @@ -457,6 +459,17 @@ MTIOCGET Returns some status information. is set if there is no tape in the drive. GMT_EOD means either end of recorded data or end of tape. GMT_EOT means end of tape. +The following ioctl uses the structure mtsense to return error information: +MTIOCSENSE returns the sense data from the previous "interesting" SCSI command + resulting in CHECK CONDITION. Sense data is not saved from a command + if is used only for supporting the implementation of the tape + semantics and the sense data from that command would overwrite the + sense data from the error interesting to the user. For instance, error + from backspacing over a block by st after a read error does not + overwrite the sense data from the read. The field 'latest' tells if + the sense data is from the previous SCSI command sent to the device + (all SCSI commands are counted). The cmd field is the command byte + from the SCSI command resulting in the sense data. MISCELLANEOUS COMPILE OPTIONS diff --git a/drivers/scsi/st.c b/drivers/scsi/st.c index 1986ebc..918f20f 100644 --- a/drivers/scsi/st.c +++ b/drivers/scsi/st.c @@ -352,14 +352,21 @@ static int st_chk_result(struct scsi_tape *STp, struct st_request * SRpnt) char *name = tape_name(STp); struct st_cmdstatus *cmdstatp; + STp->saved_sense_latest = 0; if (!result) return 0; cmdstatp = &STp->buffer->cmdstat; st_analyze_sense(SRpnt, cmdstatp); - if (cmdstatp->have_sense) + if (cmdstatp->have_sense) { scode = STp->buffer->cmdstat.sense_hdr.sense_key; + if (SRpnt->save_sense) { + STp->saved_sense_cmd = SRpnt->cmd[0]; + memcpy(STp->saved_sense, SRpnt->sense, SCSI_SENSE_BUFFERSIZE); + STp->saved_sense_latest = 1; + } + } else scode = 0; @@ -465,7 +472,7 @@ static void st_release_request(struct st_request *streq) has finished. */ static struct st_request * st_do_scsi(struct st_request * SRpnt, struct scsi_tape * STp, unsigned char *cmd, - int bytes, int direction, int timeout, int retries, int do_wait) + int bytes, int direction, int timeout, int retries, int do_wait, int save_sense) { struct completion *waiting; @@ -491,6 +498,7 @@ st_do_scsi(struct st_request * SRpnt, struct scsi_tape * STp, unsigned char *cmd (STp->buffer)->syscall_result = (-EBUSY); return NULL; } + SRpnt->save_sense = save_sense; SRpnt->stp = STp; } @@ -612,7 +620,7 @@ static int cross_eof(struct scsi_tape * STp, int forward) tape_name(STp), forward ? "forward" : "backward")); SRpnt = st_do_scsi(NULL, STp, cmd, 0, DMA_NONE, - STp->device->timeout, MAX_RETRIES, 1); + STp->device->timeout, MAX_RETRIES, 1, 0); if (!SRpnt) return (STp->buffer)->syscall_result; @@ -656,7 +664,7 @@ static int st_flush_write_buffer(struct scsi_tape * STp) cmd[4] = blks; SRpnt = st_do_scsi(NULL, STp, cmd, transfer, DMA_TO_DEVICE, - STp->device->timeout, MAX_WRITE_RETRIES, 1); + STp->device->timeout, MAX_WRITE_RETRIES, 1, 1); if (!SRpnt) return (STp->buffer)->syscall_result; @@ -852,7 +860,7 @@ static int test_ready(struct scsi_tape *STp, int do_wait) memset((void *) &cmd[0], 0, MAX_COMMAND_SIZE); cmd[0] = TEST_UNIT_READY; SRpnt = st_do_scsi(SRpnt, STp, cmd, 0, DMA_NONE, - STp->long_timeout, MAX_READY_RETRIES, 1); + STp->long_timeout, MAX_READY_RETRIES, 1, 1); if (!SRpnt) { retval = (STp->buffer)->syscall_result; @@ -986,7 +994,7 @@ static int check_tape(struct scsi_tape *STp, struct file *filp) cmd[0] = READ_BLOCK_LIMITS; SRpnt = st_do_scsi(SRpnt, STp, cmd, 6, DMA_FROM_DEVICE, - STp->device->timeout, MAX_READY_RETRIES, 1); + STp->device->timeout, MAX_READY_RETRIES, 1, 0); if (!SRpnt) { retval = (STp->buffer)->syscall_result; goto err_out; @@ -1013,7 +1021,7 @@ static int check_tape(struct scsi_tape *STp, struct file *filp) cmd[4] = 12; SRpnt = st_do_scsi(SRpnt, STp, cmd, 12, DMA_FROM_DEVICE, - STp->device->timeout, MAX_READY_RETRIES, 1); + STp->device->timeout, MAX_READY_RETRIES, 1, 0); if (!SRpnt) { retval = (STp->buffer)->syscall_result; goto err_out; @@ -1240,7 +1248,7 @@ static int st_flush(struct file *filp, fl_owner_t id) cmd[4] = 1 + STp->two_fm; SRpnt = st_do_scsi(NULL, STp, cmd, 0, DMA_NONE, - STp->device->timeout, MAX_WRITE_RETRIES, 1); + STp->device->timeout, MAX_WRITE_RETRIES, 1, 1); if (!SRpnt) { result = (STp->buffer)->syscall_result; goto out; @@ -1627,7 +1635,7 @@ st_write(struct file *filp, const char __user *buf, size_t count, loff_t * ppos) cmd[4] = blks; SRpnt = st_do_scsi(SRpnt, STp, cmd, transfer, DMA_TO_DEVICE, - STp->device->timeout, MAX_WRITE_RETRIES, !async_write); + STp->device->timeout, MAX_WRITE_RETRIES, !async_write, 1); if (!SRpnt) { retval = STbp->syscall_result; goto out; @@ -1797,7 +1805,7 @@ static long read_tape(struct scsi_tape *STp, long count, SRpnt = *aSRpnt; SRpnt = st_do_scsi(SRpnt, STp, cmd, bytes, DMA_FROM_DEVICE, - STp->device->timeout, MAX_RETRIES, 1); + STp->device->timeout, MAX_RETRIES, 1, 1); release_buffering(STp, 1); *aSRpnt = SRpnt; if (!SRpnt) @@ -2314,7 +2322,7 @@ static int read_mode_page(struct scsi_tape *STp, int page, int omit_block_descs) cmd[4] = 255; SRpnt = st_do_scsi(SRpnt, STp, cmd, cmd[4], DMA_FROM_DEVICE, - STp->device->timeout, 0, 1); + STp->device->timeout, 0, 1, 0); if (SRpnt == NULL) return (STp->buffer)->syscall_result; @@ -2345,7 +2353,7 @@ static int write_mode_page(struct scsi_tape *STp, int page, int slow) (STp->buffer)->b_data[pgo + MP_OFF_PAGE_NBR] &= MP_MSK_PAGE_NBR; SRpnt = st_do_scsi(SRpnt, STp, cmd, cmd[4], DMA_TO_DEVICE, - (slow ? STp->long_timeout : STp->device->timeout), 0, 1); + (slow ? STp->long_timeout : STp->device->timeout), 0, 1, 0); if (SRpnt == NULL) return (STp->buffer)->syscall_result; @@ -2470,7 +2478,7 @@ static int do_load_unload(struct scsi_tape *STp, struct file *filp, int load_cod ); SRpnt = st_do_scsi(NULL, STp, cmd, 0, DMA_NONE, - timeout, MAX_RETRIES, 1); + timeout, MAX_RETRIES, 1, 1); if (!SRpnt) return (STp->buffer)->syscall_result; @@ -2770,7 +2778,7 @@ static int st_int_ioctl(struct scsi_tape *STp, unsigned int cmd_in, unsigned lon } SRpnt = st_do_scsi(NULL, STp, cmd, datalen, direction, - timeout, MAX_RETRIES, 1); + timeout, MAX_RETRIES, 1, 1); if (!SRpnt) return (STp->buffer)->syscall_result; @@ -2937,7 +2945,7 @@ static int get_location(struct scsi_tape *STp, unsigned int *block, int *partiti scmd[1] = 1; } SRpnt = st_do_scsi(NULL, STp, scmd, 20, DMA_FROM_DEVICE, - STp->device->timeout, MAX_READY_RETRIES, 1); + STp->device->timeout, MAX_READY_RETRIES, 1, 1); if (!SRpnt) return (STp->buffer)->syscall_result; @@ -3042,7 +3050,7 @@ static int set_location(struct scsi_tape *STp, unsigned int block, int partition } SRpnt = st_do_scsi(NULL, STp, scmd, 0, DMA_NONE, - timeout, MAX_READY_RETRIES, 1); + timeout, MAX_READY_RETRIES, 1, 1); if (!SRpnt) return (STp->buffer)->syscall_result; @@ -3548,6 +3556,32 @@ static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg) retval = (-EFAULT); goto out; } + + if (cmd_type == _IOC_TYPE(MTIOCSENSE) && cmd_nr == _IOC_NR(MTIOCSENSE)) { + struct mtsense *mt_sensep; + + mt_sensep = kzalloc(sizeof(struct mtsense), GFP_KERNEL); + if (!mt_sensep) { + retval = -ENOMEM; + goto out; + } + + if (STp->saved_sense) { + mt_sensep->length = _IOC_SIZE(cmd_in) - sizeof(int); + mt_sensep->latest = STp->saved_sense_latest; + if (mt_sensep->length > SCSI_SENSE_BUFFERSIZE) + mt_sensep->length = SCSI_SENSE_BUFFERSIZE; + mt_sensep->cmd = STp->saved_sense_cmd; + memcpy(mt_sensep->data, STp->saved_sense, SCSI_SENSE_BUFFERSIZE); + } + + i = copy_to_user(p, mt_sensep, sizeof(struct mtsense)); + if (i) + retval = (-EFAULT); + kfree(mt_sensep); + goto out; + } + mutex_unlock(&STp->lock); switch (cmd_in) { case SCSI_IOCTL_GET_IDLUN: diff --git a/drivers/scsi/st.h b/drivers/scsi/st.h index b92712f..b3fb81e 100644 --- a/drivers/scsi/st.h +++ b/drivers/scsi/st.h @@ -27,6 +27,7 @@ struct st_request { unsigned char cmd[MAX_COMMAND_SIZE]; unsigned char sense[SCSI_SENSE_BUFFERSIZE]; int result; + int save_sense; struct scsi_tape *stp; struct completion *waiting; }; @@ -160,6 +161,13 @@ struct scsi_tape { int recover_count; /* From tape opening */ int recover_reg; /* From last status call */ + /* The latest sense data to be returned to the user. If the first byte is zero, + there is no sense data to return. */ + unsigned char saved_sense[SCSI_SENSE_BUFFERSIZE]; + unsigned char saved_sense_cmd; /* the command resulting in request sense */ + unsigned char saved_sense_latest; /* is the sense data from the latest command + submitted to the device */ + #if DEBUG unsigned char write_pending; int nbr_finished; diff --git a/include/linux/mtio.h b/include/linux/mtio.h index ef01d6a..96ca0d7 100644 --- a/include/linux/mtio.h +++ b/include/linux/mtio.h @@ -122,11 +122,20 @@ struct mtpos { long mt_blkno; /* current block number */ }; +/* structure for MTIOCSENSE - mag tape get sense data */ +struct mtsense { + __u16 length; /* length of the returned data */ + __u8 cmd; /* the command that returned the sense data */ + __u8 latest; /* is this sense data from the latest SCSI command to device? */ + __u8 data[252]; /* space for all data in REQUEST SENSE response */ +}; + /* mag tape io control commands */ #define MTIOCTOP _IOW('m', 1, struct mtop) /* do a mag tape op */ #define MTIOCGET _IOR('m', 2, struct mtget) /* get tape status */ #define MTIOCPOS _IOR('m', 3, struct mtpos) /* get tape position */ +#define MTIOCSENSE _IOR('m', 4, struct mtsense) /* get latest sense data */ /* Generic Mag Tape (device independent) status macros for examining