diff --git a/src/drivers/block/scsi.c b/src/drivers/block/scsi.c index d51b5cfa..f765c976 100644 --- a/src/drivers/block/scsi.c +++ b/src/drivers/block/scsi.c @@ -40,6 +40,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ +/** Maximum number of TEST UNIT READY retries */ +#define SCSI_READY_MAX_RETRIES 10 + /* Error numbers generated by SCSI sense data */ #define EIO_NO_SENSE __einfo_error ( EINFO_EIO_NO_SENSE ) #define EINFO_EIO_NO_SENSE \ @@ -240,6 +243,8 @@ struct scsi_device { struct interface ready; /** TEST UNIT READY process */ struct process process; + /** TEST UNIT READY retry count */ + unsigned int retries; /** List of commands */ struct list_head cmds; @@ -875,20 +880,40 @@ static struct interface_descriptor scsidev_block_desc = static void scsidev_ready ( struct scsi_device *scsidev, int rc ) { /* Shut down interface */ - intf_shutdown ( &scsidev->ready, rc ); + intf_restart ( &scsidev->ready, rc ); - /* Close device on failure */ - if ( rc != 0 ) { - DBGC ( scsidev, "SCSI %p not ready: %s\n", - scsidev, strerror ( rc ) ); - scsidev_close ( scsidev, rc ); + /* Mark device as ready, if applicable */ + if ( rc == 0 ) { + DBGC ( scsidev, "SCSI %p unit is ready\n", scsidev ); + scsidev->flags |= SCSIDEV_UNIT_READY; + xfer_window_changed ( &scsidev->block ); + return; + } + DBGC ( scsidev, "SCSI %p not ready: %s\n", scsidev, strerror ( rc ) ); + + /* SCSI targets have an annoying habit of returning occasional + * pointless "error" messages such as "power-on occurred", so + * we have to be prepared to retry commands. + * + * For most commands, we rely on the caller (e.g. the generic + * SAN device layer) to retry commands as needed. However, a + * TEST UNIT READY failure is used as an indication that the + * whole SCSI device is unavailable and should be closed. We + * must therefore perform this retry loop within the SCSI + * layer. + */ + if ( scsidev->retries++ < SCSI_READY_MAX_RETRIES ) { + DBGC ( scsidev, "SCSI %p retrying (retry %d)\n", + scsidev, scsidev->retries ); + scsidev->flags &= ~SCSIDEV_UNIT_TESTED; + process_add ( &scsidev->process ); return; } - /* Mark device as ready */ - scsidev->flags |= SCSIDEV_UNIT_READY; - xfer_window_changed ( &scsidev->block ); - DBGC ( scsidev, "SCSI %p unit is ready\n", scsidev ); + /* Close device */ + DBGC ( scsidev, "SCSI %p never became ready: %s\n", + scsidev, strerror ( rc ) ); + scsidev_close ( scsidev, rc ); } /** SCSI device TEST UNIT READY interface operations */