The attached patch corrects the linux kernel reservation handling. Currently linux fails badly when presented with SCSI reservations in a clustered environment. The patch also completes Doug Gilbert's reset injection system in the sg driver. The patches have no effect on the main line SCSI code, but they are fairly essential to those of us working in clustering and using reservations for I/O fencing. I've run the patch through a 12 hour stress test in a two node cluster using the sym53c875 and aic7xxx (AHA 2944/UW) SCSI cards. James Bottomley
Index: linux/drivers/scsi/scsi.c =================================================================== RCS file: /home/cvs/CVSROOT/linux/2.4/drivers/scsi/scsi.c,v retrieving revision 1.1.1.4 retrieving revision 1.1.1.4.6.4 diff -u -r1.1.1.4 -r1.1.1.4.6.4 --- linux/drivers/scsi/scsi.c 2000/11/30 17:19:15 1.1.1.4 +++ linux/drivers/scsi/scsi.c 2001/02/12 04:58:05 1.1.1.4.6.4 @@ -146,7 +146,12 @@ */ extern void scsi_old_done(Scsi_Cmnd * SCpnt); extern void scsi_old_times_out(Scsi_Cmnd * SCpnt); +extern int scsi_old_reset(Scsi_Cmnd *SCpnt, unsigned int flag); +/* + * private interface into the new error handling code + */ +extern int scsi_new_reset(Scsi_Cmnd *SCpnt, unsigned int flag); /* * Function: scsi_initialize_queue() @@ -2657,6 +2662,67 @@ */ scsi_release_commandblocks(SDpnt); kfree(SDpnt); +} + +/* Dummy done routine. We don't want the bogus command used for the + * bus/device reset to find its way into the mid-layer so we intercept + * it here */ +static void +scsi_reset_provider_done_command(Scsi_Cmnd *SCpnt) { + /* Empty function. Some low level drivers will call scsi_done + * (and end up here), others won't bother */ +} + + +/* + * Function: scsi_reset_provider + * + * Purpose: Send requested reset to a bus or device at any phase. + * + * Arguments: device - device to send reset to + * flag - reset type (see scsi.h) + * + * Returns: SUCCESS/FAILURE. + * + * Notes: This is used by the SCSI Generic driver to provide + * Bus/Device reset capability. + */ +int +scsi_reset_provider(Scsi_Device *dev, int flag) +{ + int rtn; + Scsi_Cmnd *SCpnt; + + /* get a dummy command to issue the reset to */ + SCpnt = scsi_allocate_device(dev, 1, 1); + /* NULL indicates interrupt */ + if(SCpnt == NULL) + return -EINTR; + + /* initialise the command */ + memset(&SCpnt->cmnd, '\0', sizeof(SCpnt->cmnd)); + + SCpnt->channel = dev->channel; + SCpnt->lun = dev->lun; + SCpnt->target = dev->id; + SCpnt->owner = SCSI_OWNER_MIDLEVEL; + SCpnt->scsi_done = scsi_reset_provider_done_command; + SCpnt->done = NULL; + SCpnt->reset_chain = NULL; + + SCpnt->internal_timeout = NORMAL_TIMEOUT; + SCpnt->abort_reason = DID_ABORT; + + if(dev->host->hostt->use_new_eh_code) { + rtn = scsi_new_reset(SCpnt, flag); + } else { + rtn = scsi_old_reset(SCpnt, flag); + } + if(timer_pending(&SCpnt->eh_timeout)) + scsi_delete_timer(SCpnt); + scsi_release_command(SCpnt); + + return rtn; } /* Index: linux/drivers/scsi/scsi.h =================================================================== RCS file: /home/cvs/CVSROOT/linux/2.4/drivers/scsi/scsi.h,v retrieving revision 1.1.1.3 diff -u -r1.1.1.3 scsi.h --- linux/drivers/scsi/scsi.h 2001/01/11 16:39:27 1.1.1.3 +++ linux/drivers/scsi/scsi.h 2001/02/12 17:58:41 @@ -842,6 +842,14 @@ remove_wait_queue(QUEUE, &wait);\ current->state = TASK_RUNNING; \ }; } +/* reset request from external source (private to sg.c, scsi.c + * scsi_error.c and scsi_obsolete.c) + * */ +#define SCSI_TRY_RESET_DEVICE 1 +#define SCSI_TRY_RESET_BUS 2 +#define SCSI_TRY_RESET_HOST 3 + +extern int scsi_reset_provider(Scsi_Device *, int); #endif Index: linux/drivers/scsi/scsi_error.c =================================================================== RCS file: /home/cvs/CVSROOT/linux/2.4/drivers/scsi/scsi_error.c,v retrieving revision 1.1.1.2 retrieving revision 1.1.1.2.6.1 diff -u -r1.1.1.2 -r1.1.1.2.6.1 --- linux/drivers/scsi/scsi_error.c 2000/09/30 17:38:23 1.1.1.2 +++ linux/drivers/scsi/scsi_error.c 2001/02/11 17:33:42 1.1.1.2.6.1 @@ -996,9 +996,17 @@ case DID_SOFT_ERROR: goto maybe_retry; + case DID_ERROR: + if(msg_byte(SCpnt->result) == COMMAND_COMPLETE + && status_byte(SCpnt->result) == RESERVATION_CONFLICT) + /* execute reservation conflict processing + code lower down */ + break; + /* fall through */ + + case DID_BUS_BUSY: case DID_PARITY: - case DID_ERROR: goto maybe_retry; case DID_TIME_OUT: /* @@ -1065,8 +1073,12 @@ */ return SUCCESS; case BUSY: - case RESERVATION_CONFLICT: - goto maybe_retry; + + case RESERVATION_CONFLICT: + printk("scsi%d (%d,%d,%d) : RESERVATION CONFLICT\n", + SCpnt->host->host_no, SCpnt->channel, + SCpnt->device->id, SCpnt->device->lun); + return SUCCESS; /* causes immediate I/O error */ default: return FAILED; } @@ -1959,6 +1971,44 @@ */ if (host->eh_notify != NULL) up(host->eh_notify); +} + +/* + * Function: scsi_new_reset + * + * Purpose: Send requested reset to a bus or device at any phase. + * + * Arguments: SCpnt - command ptr to send reset with (usually a dummy) + * flag - reset type (see scsi.h) + * + * Returns: SUCCESS/FAILURE. + * + * Notes: This is used by the SCSI Generic driver to provide + * Bus/Device reset capability. + */ +int +scsi_new_reset(Scsi_Cmnd *SCpnt, int flag) +{ + int rtn; + + switch(flag) { + case SCSI_TRY_RESET_DEVICE: + rtn = scsi_try_bus_device_reset(SCpnt, 0); + if(rtn == SUCCESS) + break; + /* fall through */ + case SCSI_TRY_RESET_BUS: + rtn = scsi_try_bus_reset(SCpnt); + if(rtn == SUCCESS) + break; + /* fall through */ + case SCSI_TRY_RESET_HOST: + rtn = scsi_try_host_reset(SCpnt); + break; + default: + rtn = FAILED; + } + return rtn; } /* Index: linux/drivers/scsi/scsi_obsolete.c =================================================================== RCS file: /home/cvs/CVSROOT/linux/2.4/drivers/scsi/scsi_obsolete.c,v retrieving revision 1.1.1.2 retrieving revision 1.1.1.2.6.1 diff -u -r1.1.1.2 -r1.1.1.2.6.1 --- linux/drivers/scsi/scsi_obsolete.c 2000/11/30 17:19:23 1.1.1.2 +++ linux/drivers/scsi/scsi_obsolete.c 2001/02/11 17:33:42 1.1.1.2.6.1 @@ -502,11 +502,17 @@ break; case RESERVATION_CONFLICT: - printk("scsi%d, channel %d : RESERVATION CONFLICT performing" - " reset.\n", SCpnt->host->host_no, SCpnt->channel); - scsi_reset(SCpnt, SCSI_RESET_SYNCHRONOUS); - status = REDO; - break; + /* Most HAs will return an + * error for this, so usually + * reservation conflicts will + * be processed under + * DID_ERROR code */ + printk("scsi%d (%d,%d,%d) : RESERVATION +CONFLICT\n", + SCpnt->host->host_no, SCpnt->channel, + SCpnt->device->id, SCpnt->device->lun); + status = CMD_FINISHED; /* returns I/O error */ + break; + default: printk("Internal error %s %d \n" "status byte = %d \n", __FILE__, @@ -556,6 +562,14 @@ exit = (DRIVER_HARD | SUGGEST_ABORT); break; case DID_ERROR: + if(msg_byte(result) == COMMAND_COMPLETE + && status_byte(result) == RESERVATION_CONFLICT) { + printk("scsi%d (%d,%d,%d) : RESERVATION CONFLICT\n", + SCpnt->host->host_no, SCpnt->channel, + SCpnt->device->id, SCpnt->device->lun); + status = CMD_FINISHED; /* returns I/O error */ + break; + } status = MAYREDO; exit = (DRIVER_HARD | SUGGEST_ABORT); break; @@ -1097,6 +1111,31 @@ return rtn; } + +/* This function exports SCSI Bus, Device or Host reset capability + * and is for use with the SCSI generic driver. + */ +int +scsi_old_reset(Scsi_Cmnd *SCpnt, unsigned int flag) +{ + int rtn; + unsigned int old_flags = SCSI_RESET_SYNCHRONOUS; + + switch(flag) { + case SCSI_TRY_RESET_DEVICE: + /* no suggestion flags to add, device reset is default */ + break; + case SCSI_TRY_RESET_BUS: + old_flags |= SCSI_RESET_SUGGEST_BUS_RESET; + break; + case SCSI_TRY_RESET_HOST: + old_flags |= SCSI_RESET_SUGGEST_HOST_RESET; + break; + default: + return FAILED; + } + return (scsi_reset(SCpnt, old_flags) == 0) ? SUCCESS : FAILED; +} /* * Overrides for Emacs so that we follow Linus's tabbing style. Index: linux/drivers/scsi/scsi_syms.c =================================================================== RCS file: /home/cvs/CVSROOT/linux/2.4/drivers/scsi/scsi_syms.c,v retrieving revision 1.1.1.2 retrieving revision 1.1.1.2.6.1 diff -u -r1.1.1.2 -r1.1.1.2.6.1 --- linux/drivers/scsi/scsi_syms.c 2000/11/30 17:19:11 1.1.1.2 +++ linux/drivers/scsi/scsi_syms.c 2001/02/11 17:33:42 1.1.1.2.6.1 @@ -84,6 +84,10 @@ EXPORT_SYMBOL(scsi_deregister_blocked_host); /* + * This symbol is for the sg device only + */ +EXPORT_SYMBOL(scsi_reset_provider); +/* * These are here only while I debug the rest of the scsi stuff. */ EXPORT_SYMBOL(scsi_hostlist);