📄 scsi.c
字号:
/* * scsi_do_cmd sends all the commands out to the low-level driver. It * handles the specifics required for each low level driver - ie queued * or non queued. It also prevents conflicts when different high level * drivers go for the same host at the same time. */void scsi_do_cmd (Scsi_Cmnd * SCpnt, const void *cmnd , void *buffer, unsigned bufflen, void (*done)(Scsi_Cmnd *), int timeout, int retries){ unsigned long flags; struct Scsi_Host * host = SCpnt->host; #ifdef DEBUG { int i; int target = SCpnt->target; printk ("scsi_do_cmd (host = %d, channel = %d target = %d, " "buffer =%p, bufflen = %d, done = %p, timeout = %d, " "retries = %d)\n" "command : " , host->host_no, SCpnt->channel, target, buffer, bufflen, done, timeout, retries); for (i = 0; i < 10; ++i) printk ("%02x ", ((unsigned char *) cmnd)[i]); printk("\n"); }#endif if (!host) { panic ("Invalid or not present host.\n"); } /* * We must prevent reentrancy to the lowlevel host driver. This prevents * it - we enter a loop until the host we want to talk to is not busy. * Race conditions are prevented, as interrupts are disabled in between the * time we check for the host being not busy, and the time we mark it busy * ourselves. */ save_flags(flags); cli(); SCpnt->pid = scsi_pid++; while (SCSI_BLOCK(host)) { restore_flags(flags); SCSI_SLEEP(&host->host_wait, SCSI_BLOCK(host)); cli(); } if (host->block) host_active = host; host->host_busy++; restore_flags(flags); /* * Our own function scsi_done (which marks the host as not busy, disables * the timeout counter, etc) will be called by us or by the * scsi_hosts[host].queuecommand() function needs to also call * the completion function for the high level driver. */ memcpy ((void *) SCpnt->data_cmnd , (const void *) cmnd, 12);#if 0 SCpnt->host = host; SCpnt->channel = channel; SCpnt->target = target; SCpnt->lun = (SCpnt->data_cmnd[1] >> 5);#endif SCpnt->reset_chain = NULL; SCpnt->serial_number = 0; SCpnt->bufflen = bufflen; SCpnt->buffer = buffer; SCpnt->flags = 0; SCpnt->retries = 0; SCpnt->allowed = retries; SCpnt->done = done; SCpnt->timeout_per_command = timeout; memcpy ((void *) SCpnt->cmnd , (const void *) cmnd, 12); /* Zero the sense buffer. Some host adapters automatically request * sense on error. 0 is not a valid sense code. */ memset ((void *) SCpnt->sense_buffer, 0, sizeof SCpnt->sense_buffer); SCpnt->request_buffer = buffer; SCpnt->request_bufflen = bufflen; SCpnt->old_use_sg = SCpnt->use_sg; if (SCpnt->cmd_len == 0) SCpnt->cmd_len = COMMAND_SIZE(SCpnt->cmnd[0]); SCpnt->old_cmd_len = SCpnt->cmd_len; /* Start the timer ticking. */ SCpnt->internal_timeout = NORMAL_TIMEOUT; SCpnt->abort_reason = 0; internal_cmnd (SCpnt);#ifdef DEBUG printk ("Leaving scsi_do_cmd()\n");#endif}static int check_sense (Scsi_Cmnd * SCpnt){ /* If there is no sense information, request it. If we have already * requested it, there is no point in asking again - the firmware must * be confused. */ if (((SCpnt->sense_buffer[0] & 0x70) >> 4) != 7) { if(!(SCpnt->flags & ASKED_FOR_SENSE)) return SUGGEST_SENSE; else return SUGGEST_RETRY; } SCpnt->flags &= ~ASKED_FOR_SENSE; #ifdef DEBUG_INIT printk("scsi%d, channel%d : ", SCpnt->host->host_no, SCpnt->channel); print_sense("", SCpnt); printk("\n");#endif if (SCpnt->sense_buffer[2] & 0xe0) return SUGGEST_ABORT; switch (SCpnt->sense_buffer[2] & 0xf) { case NO_SENSE: return 0; case RECOVERED_ERROR: return SUGGEST_IS_OK; case ABORTED_COMMAND: return SUGGEST_RETRY; case NOT_READY: case UNIT_ATTENTION: /* * If we are expecting a CC/UA because of a bus reset that we * performed, treat this just as a retry. Otherwise this is * information that we should pass up to the upper-level driver * so that we can deal with it there. */ if( SCpnt->device->expecting_cc_ua ) { SCpnt->device->expecting_cc_ua = 0; return SUGGEST_RETRY; } return SUGGEST_ABORT; /* these three are not supported */ case COPY_ABORTED: case VOLUME_OVERFLOW: case MISCOMPARE: case MEDIUM_ERROR: return SUGGEST_REMAP; case BLANK_CHECK: case DATA_PROTECT: case HARDWARE_ERROR: case ILLEGAL_REQUEST: default: return SUGGEST_ABORT; }}/* This function is the mid-level interrupt routine, which decides how * to handle error conditions. Each invocation of this function must * do one and *only* one of the following: * * (1) Call last_cmnd[host].done. This is done for fatal errors and * normal completion, and indicates that the handling for this * request is complete. * (2) Call internal_cmnd to requeue the command. This will result in * scsi_done being called again when the retry is complete. * (3) Call scsi_request_sense. This asks the host adapter/drive for * more information about the error condition. When the information * is available, scsi_done will be called again. * (4) Call reset(). This is sort of a last resort, and the idea is that * this may kick things loose and get the drive working again. reset() * automatically calls scsi_request_sense, and thus scsi_done will be * called again once the reset is complete. * * If none of the above actions are taken, the drive in question * will hang. If more than one of the above actions are taken by * scsi_done, then unpredictable behavior will result. */static void scsi_done (Scsi_Cmnd * SCpnt){ int status=0; int exit=0; int checked; int oldto; struct Scsi_Host * host = SCpnt->host; int result = SCpnt->result; SCpnt->serial_number = 0; oldto = update_timeout(SCpnt, 0); #ifdef DEBUG_TIMEOUT if(result) printk("Non-zero result in scsi_done %x %d:%d\n", result, SCpnt->target, SCpnt->lun);#endif /* If we requested an abort, (and we got it) then fix up the return * status to say why */ if(host_byte(result) == DID_ABORT && SCpnt->abort_reason) SCpnt->result = result = (result & 0xff00ffff) | (SCpnt->abort_reason << 16);#define FINISHED 0#define MAYREDO 1#define REDO 3#define PENDING 4#ifdef DEBUG printk("In scsi_done(host = %d, result = %06x)\n", host->host_no, result);#endif if(SCpnt->flags & WAS_SENSE) { SCpnt->use_sg = SCpnt->old_use_sg; SCpnt->cmd_len = SCpnt->old_cmd_len; } switch (host_byte(result)) { case DID_OK: if (status_byte(result) && (SCpnt->flags & WAS_SENSE)) /* Failed to obtain sense information */ { SCpnt->flags &= ~WAS_SENSE;#if 0 /* This cannot possibly be correct. */ SCpnt->internal_timeout &= ~SENSE_TIMEOUT;#endif if (!(SCpnt->flags & WAS_RESET)) { printk("scsi%d : channel %d target %d lun %d request sense" " failed, performing reset.\n", SCpnt->host->host_no, SCpnt->channel, SCpnt->target, SCpnt->lun); scsi_reset(SCpnt, SCSI_RESET_SYNCHRONOUS); return; } else { exit = (DRIVER_HARD | SUGGEST_ABORT); status = FINISHED; } } else switch(msg_byte(result)) { case COMMAND_COMPLETE: switch (status_byte(result)) { case GOOD: if (SCpnt->flags & WAS_SENSE) {#ifdef DEBUG printk ("In scsi_done, GOOD status, COMMAND COMPLETE, " "parsing sense information.\n");#endif SCpnt->flags &= ~WAS_SENSE;#if 0 /* This cannot possibly be correct. */ SCpnt->internal_timeout &= ~SENSE_TIMEOUT;#endif switch (checked = check_sense(SCpnt)) { case SUGGEST_SENSE: case 0:#ifdef DEBUG printk("NO SENSE. status = REDO\n");#endif update_timeout(SCpnt, oldto); status = REDO; break; case SUGGEST_IS_OK: break; case SUGGEST_REMAP:#ifdef DEBUG printk("SENSE SUGGEST REMAP - status = FINISHED\n");#endif status = FINISHED; exit = DRIVER_SENSE | SUGGEST_ABORT; break; case SUGGEST_RETRY:#ifdef DEBUG printk("SENSE SUGGEST RETRY - status = MAYREDO\n");#endif status = MAYREDO; exit = DRIVER_SENSE | SUGGEST_RETRY; break; case SUGGEST_ABORT:#ifdef DEBUG printk("SENSE SUGGEST ABORT - status = FINISHED");#endif status = FINISHED; exit = DRIVER_SENSE | SUGGEST_ABORT; break; default: printk ("Internal error %s %d \n", __FILE__, __LINE__); } } /* end WAS_SENSE */ else {#ifdef DEBUG printk("COMMAND COMPLETE message returned, " "status = FINISHED. \n");#endif exit = DRIVER_OK; status = FINISHED; } break; case CHECK_CONDITION: case COMMAND_TERMINATED: switch (check_sense(SCpnt)) { case 0: update_timeout(SCpnt, oldto); status = REDO; break; case SUGGEST_REMAP: status = FINISHED; exit = DRIVER_SENSE | SUGGEST_ABORT; break; case SUGGEST_RETRY: status = MAYREDO; exit = DRIVER_SENSE | SUGGEST_RETRY; break; case SUGGEST_ABORT: status = FINISHED; exit = DRIVER_SENSE | SUGGEST_ABORT; break; case SUGGEST_SENSE: scsi_request_sense (SCpnt); status = PENDING; break; } break; case CONDITION_GOOD: case INTERMEDIATE_GOOD: case INTERMEDIATE_C_GOOD: break; case BUSY: case QUEUE_FULL: update_timeout(SCpnt, oldto); status = REDO; 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); return;#if 0 exit = DRIVER_SOFT | SUGGEST_ABORT; status = MAYREDO; break;#endif default: printk ("Internal error %s %d \n" "status byte = %d \n", __FILE__, __LINE__, status_byte(result)); } break; default: panic("scsi: unsupported message byte %d received\n", msg_byte(result)); } break; case DID_TIME_OUT:#ifdef DEBUG printk("Host returned DID_TIME_OUT - ");#endif if (SCpnt->flags & WAS_TIMEDOUT) {#ifdef DEBUG printk("Aborting\n");#endif /* Allow TEST_UNIT_READY and INQUIRY commands to timeout early without causing resets. All other commands should be retried. */ if (SCpnt->cmnd[0] != TEST_UNIT_READY && SCpnt->cmnd[0] != INQUIRY) status = MAYREDO; exit = (DRIVER_TIMEOUT | SUGGEST_ABORT); } else {#ifdef DEBUG printk ("Retrying.\n");#endif SCpnt->flags |= WAS_TIMEDOUT; SCpnt->internal_timeout &= ~IN_ABORT; status = REDO; } break; case DID_BUS_BUSY: case DID_PARITY: status = REDO; break; case DID_NO_CONNECT:#ifdef DEBUG printk("Couldn't connect.\n");#endif exit = (DRIVER_HARD | SUGGEST_ABORT); break; case DID_ERROR: status = MAYREDO; exit = (DRIVER_HARD | SUGGEST_ABORT); break; case DID_BAD_TARGET: case DID_ABORT: exit = (DRIVER_INVALID | SUGGEST_ABORT); break; case DID_RESET: if (SCpnt->flags & IS_RESETTING) { SCpnt->flags &= ~IS_RESETTING; status = REDO; break; } if(msg_byte(result) == GOOD && status_byte(result) == CHECK_CONDITION) { switch (check_sense(SCpnt)) { case 0: update_timeout(SCpnt, oldto); status = REDO; break; case SUGGEST_REMAP: case SUGGEST_RETRY: status = MAYREDO;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -