Commit 939647ee authored by James Bottomley's avatar James Bottomley Committed by James Bottomley

[SCSI] fix oops on usb storage device disconnect

We fix the oops by enforcing the host state model.  There have also
been two extra states added: SHOST_CANCEL_RECOVERY and
SHOST_DEL_RECOVERY so we can take the model through host removal while
the recovery thread is active.
Signed-off-by: default avatarJames Bottomley <James.Bottomley@SteelEye.com>
parent a64358db
...@@ -98,6 +98,7 @@ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state) ...@@ -98,6 +98,7 @@ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state)
switch (oldstate) { switch (oldstate) {
case SHOST_CREATED: case SHOST_CREATED:
case SHOST_RUNNING: case SHOST_RUNNING:
case SHOST_CANCEL_RECOVERY:
break; break;
default: default:
goto illegal; goto illegal;
...@@ -107,12 +108,31 @@ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state) ...@@ -107,12 +108,31 @@ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state)
case SHOST_DEL: case SHOST_DEL:
switch (oldstate) { switch (oldstate) {
case SHOST_CANCEL: case SHOST_CANCEL:
case SHOST_DEL_RECOVERY:
break; break;
default: default:
goto illegal; goto illegal;
} }
break; break;
case SHOST_CANCEL_RECOVERY:
switch (oldstate) {
case SHOST_CANCEL:
case SHOST_RECOVERY:
break;
default:
goto illegal;
}
break;
case SHOST_DEL_RECOVERY:
switch (oldstate) {
case SHOST_CANCEL_RECOVERY:
break;
default:
goto illegal;
}
break;
} }
shost->shost_state = state; shost->shost_state = state;
return 0; return 0;
...@@ -134,13 +154,24 @@ EXPORT_SYMBOL(scsi_host_set_state); ...@@ -134,13 +154,24 @@ EXPORT_SYMBOL(scsi_host_set_state);
**/ **/
void scsi_remove_host(struct Scsi_Host *shost) void scsi_remove_host(struct Scsi_Host *shost)
{ {
unsigned long flags;
down(&shost->scan_mutex); down(&shost->scan_mutex);
scsi_host_set_state(shost, SHOST_CANCEL); spin_lock_irqsave(shost->host_lock, flags);
if (scsi_host_set_state(shost, SHOST_CANCEL))
if (scsi_host_set_state(shost, SHOST_CANCEL_RECOVERY)) {
spin_unlock_irqrestore(shost->host_lock, flags);
up(&shost->scan_mutex);
return;
}
spin_unlock_irqrestore(shost->host_lock, flags);
up(&shost->scan_mutex); up(&shost->scan_mutex);
scsi_forget_host(shost); scsi_forget_host(shost);
scsi_proc_host_rm(shost); scsi_proc_host_rm(shost);
scsi_host_set_state(shost, SHOST_DEL); spin_lock_irqsave(shost->host_lock, flags);
if (scsi_host_set_state(shost, SHOST_DEL))
BUG_ON(scsi_host_set_state(shost, SHOST_DEL_RECOVERY));
spin_unlock_irqrestore(shost->host_lock, flags);
transport_unregister_device(&shost->shost_gendev); transport_unregister_device(&shost->shost_gendev);
class_device_unregister(&shost->shost_classdev); class_device_unregister(&shost->shost_classdev);
......
...@@ -1265,9 +1265,8 @@ int scsi_device_cancel(struct scsi_device *sdev, int recovery) ...@@ -1265,9 +1265,8 @@ int scsi_device_cancel(struct scsi_device *sdev, int recovery)
list_for_each_safe(lh, lh_sf, &active_list) { list_for_each_safe(lh, lh_sf, &active_list) {
scmd = list_entry(lh, struct scsi_cmnd, eh_entry); scmd = list_entry(lh, struct scsi_cmnd, eh_entry);
list_del_init(lh); list_del_init(lh);
if (recovery) { if (recovery &&
scsi_eh_scmd_add(scmd, SCSI_EH_CANCEL_CMD); !scsi_eh_scmd_add(scmd, SCSI_EH_CANCEL_CMD)) {
} else {
scmd->result = (DID_ABORT << 16); scmd->result = (DID_ABORT << 16);
scsi_finish_command(scmd); scsi_finish_command(scmd);
} }
......
...@@ -68,19 +68,24 @@ int scsi_eh_scmd_add(struct scsi_cmnd *scmd, int eh_flag) ...@@ -68,19 +68,24 @@ int scsi_eh_scmd_add(struct scsi_cmnd *scmd, int eh_flag)
{ {
struct Scsi_Host *shost = scmd->device->host; struct Scsi_Host *shost = scmd->device->host;
unsigned long flags; unsigned long flags;
int ret = 0;
if (shost->eh_wait == NULL) if (shost->eh_wait == NULL)
return 0; return 0;
spin_lock_irqsave(shost->host_lock, flags); spin_lock_irqsave(shost->host_lock, flags);
if (scsi_host_set_state(shost, SHOST_RECOVERY))
if (scsi_host_set_state(shost, SHOST_CANCEL_RECOVERY))
goto out_unlock;
ret = 1;
scmd->eh_eflags |= eh_flag; scmd->eh_eflags |= eh_flag;
list_add_tail(&scmd->eh_entry, &shost->eh_cmd_q); list_add_tail(&scmd->eh_entry, &shost->eh_cmd_q);
scsi_host_set_state(shost, SHOST_RECOVERY);
shost->host_failed++; shost->host_failed++;
scsi_eh_wakeup(shost); scsi_eh_wakeup(shost);
out_unlock:
spin_unlock_irqrestore(shost->host_lock, flags); spin_unlock_irqrestore(shost->host_lock, flags);
return 1; return ret;
} }
/** /**
...@@ -176,8 +181,8 @@ void scsi_times_out(struct scsi_cmnd *scmd) ...@@ -176,8 +181,8 @@ void scsi_times_out(struct scsi_cmnd *scmd)
} }
if (unlikely(!scsi_eh_scmd_add(scmd, SCSI_EH_CANCEL_CMD))) { if (unlikely(!scsi_eh_scmd_add(scmd, SCSI_EH_CANCEL_CMD))) {
panic("Error handler thread not present at %p %p %s %d", scmd->result |= DID_TIME_OUT << 16;
scmd, scmd->device->host, __FILE__, __LINE__); __scsi_done(scmd);
} }
} }
...@@ -196,8 +201,7 @@ int scsi_block_when_processing_errors(struct scsi_device *sdev) ...@@ -196,8 +201,7 @@ int scsi_block_when_processing_errors(struct scsi_device *sdev)
{ {
int online; int online;
wait_event(sdev->host->host_wait, (sdev->host->shost_state != wait_event(sdev->host->host_wait, !scsi_host_in_recovery(sdev->host));
SHOST_RECOVERY));
online = scsi_device_online(sdev); online = scsi_device_online(sdev);
...@@ -1441,6 +1445,7 @@ static void scsi_eh_lock_door(struct scsi_device *sdev) ...@@ -1441,6 +1445,7 @@ static void scsi_eh_lock_door(struct scsi_device *sdev)
static void scsi_restart_operations(struct Scsi_Host *shost) static void scsi_restart_operations(struct Scsi_Host *shost)
{ {
struct scsi_device *sdev; struct scsi_device *sdev;
unsigned long flags;
/* /*
* If the door was locked, we need to insert a door lock request * If the door was locked, we need to insert a door lock request
...@@ -1460,7 +1465,11 @@ static void scsi_restart_operations(struct Scsi_Host *shost) ...@@ -1460,7 +1465,11 @@ static void scsi_restart_operations(struct Scsi_Host *shost)
SCSI_LOG_ERROR_RECOVERY(3, printk("%s: waking up host to restart\n", SCSI_LOG_ERROR_RECOVERY(3, printk("%s: waking up host to restart\n",
__FUNCTION__)); __FUNCTION__));
scsi_host_set_state(shost, SHOST_RUNNING); spin_lock_irqsave(shost->host_lock, flags);
if (scsi_host_set_state(shost, SHOST_RUNNING))
if (scsi_host_set_state(shost, SHOST_CANCEL))
BUG_ON(scsi_host_set_state(shost, SHOST_DEL));
spin_unlock_irqrestore(shost->host_lock, flags);
wake_up(&shost->host_wait); wake_up(&shost->host_wait);
......
...@@ -458,7 +458,7 @@ int scsi_nonblockable_ioctl(struct scsi_device *sdev, int cmd, ...@@ -458,7 +458,7 @@ int scsi_nonblockable_ioctl(struct scsi_device *sdev, int cmd,
* error processing, as long as the device was opened * error processing, as long as the device was opened
* non-blocking */ * non-blocking */
if (filp && filp->f_flags & O_NONBLOCK) { if (filp && filp->f_flags & O_NONBLOCK) {
if (sdev->host->shost_state == SHOST_RECOVERY) if (scsi_host_in_recovery(sdev->host))
return -ENODEV; return -ENODEV;
} else if (!scsi_block_when_processing_errors(sdev)) } else if (!scsi_block_when_processing_errors(sdev))
return -ENODEV; return -ENODEV;
......
...@@ -447,7 +447,7 @@ void scsi_device_unbusy(struct scsi_device *sdev) ...@@ -447,7 +447,7 @@ void scsi_device_unbusy(struct scsi_device *sdev)
spin_lock_irqsave(shost->host_lock, flags); spin_lock_irqsave(shost->host_lock, flags);
shost->host_busy--; shost->host_busy--;
if (unlikely((shost->shost_state == SHOST_RECOVERY) && if (unlikely(scsi_host_in_recovery(shost) &&
shost->host_failed)) shost->host_failed))
scsi_eh_wakeup(shost); scsi_eh_wakeup(shost);
spin_unlock(shost->host_lock); spin_unlock(shost->host_lock);
...@@ -1339,7 +1339,7 @@ static inline int scsi_host_queue_ready(struct request_queue *q, ...@@ -1339,7 +1339,7 @@ static inline int scsi_host_queue_ready(struct request_queue *q,
struct Scsi_Host *shost, struct Scsi_Host *shost,
struct scsi_device *sdev) struct scsi_device *sdev)
{ {
if (shost->shost_state == SHOST_RECOVERY) if (scsi_host_in_recovery(shost))
return 0; return 0;
if (shost->host_busy == 0 && shost->host_blocked) { if (shost->host_busy == 0 && shost->host_blocked) {
/* /*
......
...@@ -57,6 +57,8 @@ static struct { ...@@ -57,6 +57,8 @@ static struct {
{ SHOST_CANCEL, "cancel" }, { SHOST_CANCEL, "cancel" },
{ SHOST_DEL, "deleted" }, { SHOST_DEL, "deleted" },
{ SHOST_RECOVERY, "recovery" }, { SHOST_RECOVERY, "recovery" },
{ SHOST_CANCEL_RECOVERY, "cancel/recovery" },
{ SHOST_DEL_RECOVERY, "deleted/recovery", },
}; };
const char *scsi_host_state_name(enum scsi_host_state state) const char *scsi_host_state_name(enum scsi_host_state state)
{ {
......
...@@ -1027,7 +1027,7 @@ sg_ioctl(struct inode *inode, struct file *filp, ...@@ -1027,7 +1027,7 @@ sg_ioctl(struct inode *inode, struct file *filp,
if (sdp->detached) if (sdp->detached)
return -ENODEV; return -ENODEV;
if (filp->f_flags & O_NONBLOCK) { if (filp->f_flags & O_NONBLOCK) {
if (sdp->device->host->shost_state == SHOST_RECOVERY) if (scsi_host_in_recovery(sdp->device->host))
return -EBUSY; return -EBUSY;
} else if (!scsi_block_when_processing_errors(sdp->device)) } else if (!scsi_block_when_processing_errors(sdp->device))
return -EBUSY; return -EBUSY;
......
...@@ -439,6 +439,8 @@ enum scsi_host_state { ...@@ -439,6 +439,8 @@ enum scsi_host_state {
SHOST_CANCEL, SHOST_CANCEL,
SHOST_DEL, SHOST_DEL,
SHOST_RECOVERY, SHOST_RECOVERY,
SHOST_CANCEL_RECOVERY,
SHOST_DEL_RECOVERY,
}; };
struct Scsi_Host { struct Scsi_Host {
...@@ -621,6 +623,13 @@ static inline struct Scsi_Host *dev_to_shost(struct device *dev) ...@@ -621,6 +623,13 @@ static inline struct Scsi_Host *dev_to_shost(struct device *dev)
return container_of(dev, struct Scsi_Host, shost_gendev); return container_of(dev, struct Scsi_Host, shost_gendev);
} }
static inline int scsi_host_in_recovery(struct Scsi_Host *shost)
{
return shost->shost_state == SHOST_RECOVERY ||
shost->shost_state == SHOST_CANCEL_RECOVERY ||
shost->shost_state == SHOST_DEL_RECOVERY;
}
extern int scsi_queue_work(struct Scsi_Host *, struct work_struct *); extern int scsi_queue_work(struct Scsi_Host *, struct work_struct *);
extern void scsi_flush_work(struct Scsi_Host *); extern void scsi_flush_work(struct Scsi_Host *);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment