From 1b8d26206134458044b0689f48194af00c96d406 Mon Sep 17 00:00:00 2001 From: Mike Christie Date: Thu, 17 May 2012 23:56:56 -0500 Subject: [SCSI] add new SDEV_TRANSPORT_OFFLINE state This patch adds a new state SDEV_TRANSPORT_OFFLINE. It will be used by transport classes to offline devices for cases like when the fast_io_fail/recovery_tmo fires. In those cases we want all IO to fail, and we have not yet escalated to dev_loss_tmo behavior where we are removing the devices. Currently to handle this state, transport classes are setting the scsi_device's state to running, setting their internal session/port structs state to something that indicates failed, and then failing IO from some transport check in the queuecommand. The reason for the new value is so that users can distinguish between a device failure that is a result of a transport problem vs the wide range of errors that devices get offlined for when a scsi command times out and we offline the devices there. It also fixes the confusion as to why the transport class is failing IO, but has set the device state from blocked to running. Signed-off-by: Mike Christie Signed-off-by: James Bottomley --- drivers/scsi/scsi_lib.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/scsi/scsi_lib.c') diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 6dfb9785d345..340c569d4535 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -1173,6 +1173,7 @@ int scsi_prep_state_check(struct scsi_device *sdev, struct request *req) if (unlikely(sdev->sdev_state != SDEV_RUNNING)) { switch (sdev->sdev_state) { case SDEV_OFFLINE: + case SDEV_TRANSPORT_OFFLINE: /* * If the device is offline we refuse to process any * commands. The device must be brought online @@ -2081,6 +2082,7 @@ scsi_device_set_state(struct scsi_device *sdev, enum scsi_device_state state) switch (oldstate) { case SDEV_CREATED: case SDEV_OFFLINE: + case SDEV_TRANSPORT_OFFLINE: case SDEV_QUIESCE: case SDEV_BLOCK: break; @@ -2093,6 +2095,7 @@ scsi_device_set_state(struct scsi_device *sdev, enum scsi_device_state state) switch (oldstate) { case SDEV_RUNNING: case SDEV_OFFLINE: + case SDEV_TRANSPORT_OFFLINE: break; default: goto illegal; @@ -2100,6 +2103,7 @@ scsi_device_set_state(struct scsi_device *sdev, enum scsi_device_state state) break; case SDEV_OFFLINE: + case SDEV_TRANSPORT_OFFLINE: switch (oldstate) { case SDEV_CREATED: case SDEV_RUNNING: @@ -2136,6 +2140,7 @@ scsi_device_set_state(struct scsi_device *sdev, enum scsi_device_state state) case SDEV_RUNNING: case SDEV_QUIESCE: case SDEV_OFFLINE: + case SDEV_TRANSPORT_OFFLINE: case SDEV_BLOCK: break; default: @@ -2148,6 +2153,7 @@ scsi_device_set_state(struct scsi_device *sdev, enum scsi_device_state state) case SDEV_CREATED: case SDEV_RUNNING: case SDEV_OFFLINE: + case SDEV_TRANSPORT_OFFLINE: case SDEV_CANCEL: break; default: -- cgit v1.2.3 From 5d9fb5cc1b88277bb28a2a54e51b34cacaa123c2 Mon Sep 17 00:00:00 2001 From: Mike Christie Date: Thu, 17 May 2012 23:56:57 -0500 Subject: [SCSI] core, classes, mpt2sas: have scsi_internal_device_unblock take new state This has scsi_internal_device_unblock/scsi_target_unblock take the new state to set the devices as an argument instead of always setting to running. The patch also converts users of these functions. This allows the FC and iSCSI class to transition devices from blocked to transport-offline, so that when fast_io_fail/replacement_timeout has fired we do not set the devices back to running. Instead, we set them to SDEV_TRANSPORT_OFFLINE. Signed-off-by: Mike Christie Signed-off-by: James Bottomley --- drivers/scsi/mpt2sas/mpt2sas_base.h | 3 ++- drivers/scsi/mpt2sas/mpt2sas_scsih.c | 4 ++-- drivers/scsi/scsi_lib.c | 40 +++++++++++++++++++++--------------- drivers/scsi/scsi_priv.h | 4 +++- drivers/scsi/scsi_transport_fc.c | 16 +++++++-------- drivers/scsi/scsi_transport_iscsi.c | 6 +++--- include/scsi/scsi_device.h | 2 +- 7 files changed, 41 insertions(+), 34 deletions(-) (limited to 'drivers/scsi/scsi_lib.c') diff --git a/drivers/scsi/mpt2sas/mpt2sas_base.h b/drivers/scsi/mpt2sas/mpt2sas_base.h index b6dd3a5de7f9..b3a1a30055d6 100644 --- a/drivers/scsi/mpt2sas/mpt2sas_base.h +++ b/drivers/scsi/mpt2sas/mpt2sas_base.h @@ -1158,6 +1158,7 @@ extern struct scsi_transport_template *mpt2sas_transport_template; extern int scsi_internal_device_block(struct scsi_device *sdev); extern u8 mpt2sas_stm_zero_smid_handler(struct MPT2SAS_ADAPTER *ioc, u8 msix_index, u32 reply); -extern int scsi_internal_device_unblock(struct scsi_device *sdev); +extern int scsi_internal_device_unblock(struct scsi_device *sdev, + enum scsi_device_state new_state); #endif /* MPT2SAS_BASE_H_INCLUDED */ diff --git a/drivers/scsi/mpt2sas/mpt2sas_scsih.c b/drivers/scsi/mpt2sas/mpt2sas_scsih.c index 76973e8ca4ba..b1ebd6f8dab3 100644 --- a/drivers/scsi/mpt2sas/mpt2sas_scsih.c +++ b/drivers/scsi/mpt2sas/mpt2sas_scsih.c @@ -2904,7 +2904,7 @@ _scsih_ublock_io_all_device(struct MPT2SAS_ADAPTER *ioc) dewtprintk(ioc, sdev_printk(KERN_INFO, sdev, "device_running, " "handle(0x%04x)\n", sas_device_priv_data->sas_target->handle)); - scsi_internal_device_unblock(sdev); + scsi_internal_device_unblock(sdev, SDEV_RUNNING); } } /** @@ -2933,7 +2933,7 @@ _scsih_ublock_io_device(struct MPT2SAS_ADAPTER *ioc, u64 sas_address) "sas address(0x%016llx)\n", ioc->name, (unsigned long long)sas_address)); sas_device_priv_data->block = 0; - scsi_internal_device_unblock(sdev); + scsi_internal_device_unblock(sdev, SDEV_RUNNING); } } } diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 340c569d4535..36521a0ac54b 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -2444,6 +2444,7 @@ EXPORT_SYMBOL_GPL(scsi_internal_device_block); /** * scsi_internal_device_unblock - resume a device after a block request * @sdev: device to resume + * @new_state: state to set devices to after unblocking * * Called by scsi lld's or the midlayer to restart the device queue * for the previously suspended scsi device. Called from interrupt or @@ -2453,25 +2454,30 @@ EXPORT_SYMBOL_GPL(scsi_internal_device_block); * * Notes: * This routine transitions the device to the SDEV_RUNNING state - * (which must be a legal transition) allowing the midlayer to - * goose the queue for this device. This routine assumes the - * host_lock is held upon entry. + * or to one of the offline states (which must be a legal transition) + * allowing the midlayer to goose the queue for this device. This + * routine assumes the host_lock is held upon entry. */ int -scsi_internal_device_unblock(struct scsi_device *sdev) +scsi_internal_device_unblock(struct scsi_device *sdev, + enum scsi_device_state new_state) { struct request_queue *q = sdev->request_queue; unsigned long flags; - - /* - * Try to transition the scsi device to SDEV_RUNNING - * and goose the device queue if successful. + + /* + * Try to transition the scsi device to SDEV_RUNNING or one of the + * offlined states and goose the device queue if successful. */ if (sdev->sdev_state == SDEV_BLOCK) - sdev->sdev_state = SDEV_RUNNING; - else if (sdev->sdev_state == SDEV_CREATED_BLOCK) - sdev->sdev_state = SDEV_CREATED; - else if (sdev->sdev_state != SDEV_CANCEL && + sdev->sdev_state = new_state; + else if (sdev->sdev_state == SDEV_CREATED_BLOCK) { + if (new_state == SDEV_TRANSPORT_OFFLINE || + new_state == SDEV_OFFLINE) + sdev->sdev_state = new_state; + else + sdev->sdev_state = SDEV_CREATED; + } else if (sdev->sdev_state != SDEV_CANCEL && sdev->sdev_state != SDEV_OFFLINE) return -EINVAL; @@ -2512,26 +2518,26 @@ EXPORT_SYMBOL_GPL(scsi_target_block); static void device_unblock(struct scsi_device *sdev, void *data) { - scsi_internal_device_unblock(sdev); + scsi_internal_device_unblock(sdev, *(enum scsi_device_state *)data); } static int target_unblock(struct device *dev, void *data) { if (scsi_is_target_device(dev)) - starget_for_each_device(to_scsi_target(dev), NULL, + starget_for_each_device(to_scsi_target(dev), data, device_unblock); return 0; } void -scsi_target_unblock(struct device *dev) +scsi_target_unblock(struct device *dev, enum scsi_device_state new_state) { if (scsi_is_target_device(dev)) - starget_for_each_device(to_scsi_target(dev), NULL, + starget_for_each_device(to_scsi_target(dev), &new_state, device_unblock); else - device_for_each_child(dev, NULL, target_unblock); + device_for_each_child(dev, &new_state, target_unblock); } EXPORT_SYMBOL_GPL(scsi_target_unblock); diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h index 07ce3f51701d..cbfe5df24a3b 100644 --- a/drivers/scsi/scsi_priv.h +++ b/drivers/scsi/scsi_priv.h @@ -2,6 +2,7 @@ #define _SCSI_PRIV_H #include +#include struct request_queue; struct request; @@ -172,6 +173,7 @@ extern struct list_head scsi_sd_probe_domain; #define SCSI_DEVICE_BLOCK_MAX_TIMEOUT 600 /* units in seconds */ extern int scsi_internal_device_block(struct scsi_device *sdev); -extern int scsi_internal_device_unblock(struct scsi_device *sdev); +extern int scsi_internal_device_unblock(struct scsi_device *sdev, + enum scsi_device_state new_state); #endif /* _SCSI_PRIV_H */ diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c index 2fded793997c..2d1e68db9b3f 100644 --- a/drivers/scsi/scsi_transport_fc.c +++ b/drivers/scsi/scsi_transport_fc.c @@ -2495,11 +2495,9 @@ static void fc_terminate_rport_io(struct fc_rport *rport) i->f->terminate_rport_io(rport); /* - * must unblock to flush queued IO. The caller will have set - * the port_state or flags, so that fc_remote_port_chkready will - * fail IO. + * Must unblock to flush queued IO. scsi-ml will fail incoming reqs. */ - scsi_target_unblock(&rport->dev); + scsi_target_unblock(&rport->dev, SDEV_TRANSPORT_OFFLINE); } /** @@ -2830,8 +2828,8 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel, /* if target, initiate a scan */ if (rport->scsi_target_id != -1) { - scsi_target_unblock(&rport->dev); - + scsi_target_unblock(&rport->dev, + SDEV_RUNNING); spin_lock_irqsave(shost->host_lock, flags); rport->flags |= FC_RPORT_SCAN_PENDING; @@ -2900,7 +2898,7 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel, spin_unlock_irqrestore(shost->host_lock, flags); if (ids->roles & FC_PORT_ROLE_FCP_TARGET) { - scsi_target_unblock(&rport->dev); + scsi_target_unblock(&rport->dev, SDEV_RUNNING); /* initiate a scan of the target */ spin_lock_irqsave(shost->host_lock, flags); @@ -3105,7 +3103,7 @@ fc_remote_port_rolechg(struct fc_rport *rport, u32 roles) /* ensure any stgt delete functions are done */ fc_flush_work(shost); - scsi_target_unblock(&rport->dev); + scsi_target_unblock(&rport->dev, SDEV_RUNNING); /* initiate a scan of the target */ spin_lock_irqsave(shost->host_lock, flags); rport->flags |= FC_RPORT_SCAN_PENDING; @@ -3149,7 +3147,7 @@ fc_timeout_deleted_rport(struct work_struct *work) "blocked FC remote port time out: no longer" " a FCP target, removing starget\n"); spin_unlock_irqrestore(shost->host_lock, flags); - scsi_target_unblock(&rport->dev); + scsi_target_unblock(&rport->dev, SDEV_TRANSPORT_OFFLINE); fc_queue_work(shost, &rport->stgt_delete_work); return; } diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c index 1cf640e575da..96ec21a959e9 100644 --- a/drivers/scsi/scsi_transport_iscsi.c +++ b/drivers/scsi/scsi_transport_iscsi.c @@ -907,7 +907,7 @@ static void session_recovery_timedout(struct work_struct *work) session->transport->session_recovery_timedout(session); ISCSI_DBG_TRANS_SESSION(session, "Unblocking SCSI target\n"); - scsi_target_unblock(&session->dev); + scsi_target_unblock(&session->dev, SDEV_TRANSPORT_OFFLINE); ISCSI_DBG_TRANS_SESSION(session, "Completed unblocking SCSI target\n"); } @@ -930,7 +930,7 @@ static void __iscsi_unblock_session(struct work_struct *work) session->state = ISCSI_SESSION_LOGGED_IN; spin_unlock_irqrestore(&session->lock, flags); /* start IO */ - scsi_target_unblock(&session->dev); + scsi_target_unblock(&session->dev, SDEV_RUNNING); /* * Only do kernel scanning if the driver is properly hooked into * the async scanning code (drivers like iscsi_tcp do login and @@ -1180,7 +1180,7 @@ void iscsi_remove_session(struct iscsi_cls_session *session) session->state = ISCSI_SESSION_FREE; spin_unlock_irqrestore(&session->lock, flags); - scsi_target_unblock(&session->dev); + scsi_target_unblock(&session->dev, SDEV_TRANSPORT_OFFLINE); /* flush running scans then delete devices */ scsi_flush_work(shost); __iscsi_unbind_session(&session->unbind_work); diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h index 404575857962..bd1a14d89009 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h @@ -374,7 +374,7 @@ extern void scsi_scan_target(struct device *parent, unsigned int channel, unsigned int id, unsigned int lun, int rescan); extern void scsi_target_reap(struct scsi_target *); extern void scsi_target_block(struct device *); -extern void scsi_target_unblock(struct device *); +extern void scsi_target_unblock(struct device *, enum scsi_device_state); extern void scsi_remove_target(struct device *); extern void int_to_scsilun(unsigned int, struct scsi_lun *); extern int scsilun_to_int(struct scsi_lun *); -- cgit v1.2.3 From d075498c987623107f7bface4dad72fe9260a0d3 Mon Sep 17 00:00:00 2001 From: Mike Christie Date: Thu, 17 May 2012 23:56:58 -0500 Subject: [SCSI] remove old comment from block/unblock functions We do not hold the host lock when calling these functions, so remove comment. Signed-off-by: Mike Christie Signed-off-by: James Bottomley --- drivers/scsi/scsi_lib.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/scsi/scsi_lib.c') diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 36521a0ac54b..9f00c128e4d1 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -2411,7 +2411,6 @@ EXPORT_SYMBOL(scsi_target_resume); * (which must be a legal transition). When the device is in this * state, all commands are deferred until the scsi lld reenables * the device with scsi_device_unblock or device_block_tmo fires. - * This routine assumes the host_lock is held on entry. */ int scsi_internal_device_block(struct scsi_device *sdev) @@ -2455,8 +2454,7 @@ EXPORT_SYMBOL_GPL(scsi_internal_device_block); * Notes: * This routine transitions the device to the SDEV_RUNNING state * or to one of the offline states (which must be a legal transition) - * allowing the midlayer to goose the queue for this device. This - * routine assumes the host_lock is held upon entry. + * allowing the midlayer to goose the queue for this device. */ int scsi_internal_device_unblock(struct scsi_device *sdev, -- cgit v1.2.3 From 67bd94130015c507011af37858989b199c52e1de Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 29 Jun 2012 15:33:22 +0000 Subject: [SCSI] Fix device removal NULL pointer dereference Use blk_queue_dead() to test whether the queue is dead instead of !sdev. Since scsi_prep_fn() may be invoked concurrently with __scsi_remove_device(), keep the queuedata (sdev) pointer in __scsi_remove_device(). This patch fixes a kernel oops that can be triggered by USB device removal. See also http://www.spinics.net/lists/linux-scsi/msg56254.html. Other changes included in this patch: - Swap the blk_cleanup_queue() and kfree() calls in scsi_host_dev_release() to make that code easier to grasp. - Remove the queue dead check from scsi_run_queue() since the queue state can change anyway at any point in that function where the queue lock is not held. - Remove the queue dead check from the start of scsi_request_fn() since it is redundant with the scsi_device_online() check. Reported-by: Jun'ichi Nomura Signed-off-by: Bart Van Assche Reviewed-by: Mike Christie Reviewed-by: Tejun Heo Cc: Signed-off-by: James Bottomley --- drivers/scsi/hosts.c | 7 ++++--- drivers/scsi/scsi_lib.c | 32 ++++---------------------------- drivers/scsi/scsi_priv.h | 1 - drivers/scsi/scsi_sysfs.c | 5 +---- 4 files changed, 9 insertions(+), 36 deletions(-) (limited to 'drivers/scsi/scsi_lib.c') diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index 2b6a03de5787..593085a52275 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -290,6 +290,7 @@ static void scsi_host_dev_release(struct device *dev) struct Scsi_Host *shost = dev_to_shost(dev); struct device *parent = dev->parent; struct request_queue *q; + void *queuedata; scsi_proc_hostdir_rm(shost->hostt); @@ -299,9 +300,9 @@ static void scsi_host_dev_release(struct device *dev) destroy_workqueue(shost->work_q); q = shost->uspace_req_q; if (q) { - kfree(q->queuedata); - q->queuedata = NULL; - scsi_free_queue(q); + queuedata = q->queuedata; + blk_cleanup_queue(q); + kfree(queuedata); } scsi_destroy_command_freelist(shost); diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 9f00c128e4d1..4acf5c284c2e 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -406,10 +406,6 @@ static void scsi_run_queue(struct request_queue *q) LIST_HEAD(starved_list); unsigned long flags; - /* if the device is dead, sdev will be NULL, so no queue to run */ - if (!sdev) - return; - shost = sdev->host; if (scsi_target(sdev)->single_lun) scsi_single_lun_run(sdev); @@ -1371,16 +1367,16 @@ static inline int scsi_host_queue_ready(struct request_queue *q, * may be changed after request stacking drivers call the function, * regardless of taking lock or not. * - * When scsi can't dispatch I/Os anymore and needs to kill I/Os - * (e.g. !sdev), scsi needs to return 'not busy'. - * Otherwise, request stacking drivers may hold requests forever. + * When scsi can't dispatch I/Os anymore and needs to kill I/Os scsi + * needs to return 'not busy'. Otherwise, request stacking drivers + * may hold requests forever. */ static int scsi_lld_busy(struct request_queue *q) { struct scsi_device *sdev = q->queuedata; struct Scsi_Host *shost; - if (!sdev) + if (blk_queue_dead(q)) return 0; shost = sdev->host; @@ -1491,12 +1487,6 @@ static void scsi_request_fn(struct request_queue *q) struct scsi_cmnd *cmd; struct request *req; - if (!sdev) { - while ((req = blk_peek_request(q)) != NULL) - scsi_kill_request(req, q); - return; - } - if(!get_device(&sdev->sdev_gendev)) /* We must be tearing the block queue down already */ return; @@ -1698,20 +1688,6 @@ struct request_queue *scsi_alloc_queue(struct scsi_device *sdev) return q; } -void scsi_free_queue(struct request_queue *q) -{ - unsigned long flags; - - WARN_ON(q->queuedata); - - /* cause scsi_request_fn() to kill all non-finished requests */ - spin_lock_irqsave(q->queue_lock, flags); - q->request_fn(q); - spin_unlock_irqrestore(q->queue_lock, flags); - - blk_cleanup_queue(q); -} - /* * Function: scsi_block_requests() * diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h index cbfe5df24a3b..291db6effffb 100644 --- a/drivers/scsi/scsi_priv.h +++ b/drivers/scsi/scsi_priv.h @@ -85,7 +85,6 @@ extern void scsi_next_command(struct scsi_cmnd *cmd); extern void scsi_io_completion(struct scsi_cmnd *, unsigned int); extern void scsi_run_host_queues(struct Scsi_Host *shost); extern struct request_queue *scsi_alloc_queue(struct scsi_device *sdev); -extern void scsi_free_queue(struct request_queue *q); extern int scsi_init_queue(void); extern void scsi_exit_queue(void); struct request_queue; diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index 5747478a2bf8..9aa578a5da11 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -972,11 +972,8 @@ void __scsi_remove_device(struct scsi_device *sdev) sdev->host->hostt->slave_destroy(sdev); transport_destroy_device(dev); - /* cause the request function to reject all I/O requests */ - sdev->request_queue->queuedata = NULL; - /* Freeing the queue signals to block that we're done */ - scsi_free_queue(sdev->request_queue); + blk_cleanup_queue(sdev->request_queue); put_device(dev); } -- cgit v1.2.3 From 940f5d47e2f2e1fa00443921a0abf4822335b54d Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 29 Jun 2012 15:34:26 +0000 Subject: [SCSI] Avoid dangling pointer in scsi_requeue_command() When we call scsi_unprep_request() the command associated with the request gets destroyed and therefore drops its reference on the device. If this was the only reference, the device may get released and we end up with a NULL pointer deref when we call blk_requeue_request. Reported-by: Mike Christie Signed-off-by: Bart Van Assche Reviewed-by: Mike Christie Reviewed-by: Tejun Heo Cc: [jejb: enhance commend and add commit log for stable] Signed-off-by: James Bottomley --- drivers/scsi/scsi_lib.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'drivers/scsi/scsi_lib.c') diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 4acf5c284c2e..0e52ff035135 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -479,15 +479,26 @@ void scsi_requeue_run_queue(struct work_struct *work) */ static void scsi_requeue_command(struct request_queue *q, struct scsi_cmnd *cmd) { + struct scsi_device *sdev = cmd->device; struct request *req = cmd->request; unsigned long flags; + /* + * We need to hold a reference on the device to avoid the queue being + * killed after the unlock and before scsi_run_queue is invoked which + * may happen because scsi_unprep_request() puts the command which + * releases its reference on the device. + */ + get_device(&sdev->sdev_gendev); + spin_lock_irqsave(q->queue_lock, flags); scsi_unprep_request(req); blk_requeue_request(q, req); spin_unlock_irqrestore(q->queue_lock, flags); scsi_run_queue(q); + + put_device(&sdev->sdev_gendev); } void scsi_next_command(struct scsi_cmnd *cmd) -- cgit v1.2.3 From 84feb1664e5e6823105414df77740fda70846b99 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 29 Jun 2012 15:35:05 +0000 Subject: [SCSI] Change return type of scsi_queue_insert() into void The return value of scsi_queue_insert() is ignored by all its callers, hence change the return type of this function into void. Signed-off-by: Bart Van Assche Reviewed-by: Mike Christie Reviewed-by: Tejun Heo Signed-off-by: James Bottomley --- drivers/scsi/scsi_lib.c | 8 +++----- drivers/scsi/scsi_priv.h | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers/scsi/scsi_lib.c') diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 0e52ff035135..8af6a6497c4f 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -109,7 +109,7 @@ static void scsi_unprep_request(struct request *req) * for a requeue after completion, which should only occur in this * file. */ -static int __scsi_queue_insert(struct scsi_cmnd *cmd, int reason, int unbusy) +static void __scsi_queue_insert(struct scsi_cmnd *cmd, int reason, int unbusy) { struct Scsi_Host *host = cmd->device->host; struct scsi_device *device = cmd->device; @@ -162,8 +162,6 @@ static int __scsi_queue_insert(struct scsi_cmnd *cmd, int reason, int unbusy) spin_unlock_irqrestore(q->queue_lock, flags); kblockd_schedule_work(q, &device->requeue_work); - - return 0; } /* @@ -185,9 +183,9 @@ static int __scsi_queue_insert(struct scsi_cmnd *cmd, int reason, int unbusy) * Notes: This could be called either from an interrupt context or a * normal process context. */ -int scsi_queue_insert(struct scsi_cmnd *cmd, int reason) +void scsi_queue_insert(struct scsi_cmnd *cmd, int reason) { - return __scsi_queue_insert(cmd, reason, 1); + __scsi_queue_insert(cmd, reason, 1); } /** * scsi_execute - insert request and wait for the result diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h index 291db6effffb..13d74da5dfab 100644 --- a/drivers/scsi/scsi_priv.h +++ b/drivers/scsi/scsi_priv.h @@ -80,7 +80,7 @@ int scsi_noretry_cmd(struct scsi_cmnd *scmd); /* scsi_lib.c */ extern int scsi_maybe_unblock_host(struct scsi_device *sdev); extern void scsi_device_unbusy(struct scsi_device *sdev); -extern int scsi_queue_insert(struct scsi_cmnd *cmd, int reason); +extern void scsi_queue_insert(struct scsi_cmnd *cmd, int reason); extern void scsi_next_command(struct scsi_cmnd *cmd); extern void scsi_io_completion(struct scsi_cmnd *, unsigned int); extern void scsi_run_host_queues(struct Scsi_Host *shost); -- cgit v1.2.3 From b485462aca7df4e32bcf7efb6f84a69e8b640243 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 29 Jun 2012 15:36:07 +0000 Subject: [SCSI] Stop accepting SCSI requests before removing a device Avoid that the code for requeueing SCSI requests triggers a crash by making sure that that code isn't scheduled anymore after a device has been removed. Also, source code inspection of __scsi_remove_device() revealed a race condition in this function: no new SCSI requests must be accepted for a SCSI device after device removal started. Signed-off-by: Bart Van Assche Reviewed-by: Mike Christie Acked-by: Tejun Heo Signed-off-by: James Bottomley --- drivers/scsi/scsi_lib.c | 7 ++++--- drivers/scsi/scsi_sysfs.c | 11 +++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) (limited to 'drivers/scsi/scsi_lib.c') diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 8af6a6497c4f..b58327758c58 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -155,13 +155,14 @@ static void __scsi_queue_insert(struct scsi_cmnd *cmd, int reason, int unbusy) /* * Requeue this command. It will go before all other commands - * that are already in the queue. + * that are already in the queue. Schedule requeue work under + * lock such that the kblockd_schedule_work() call happens + * before blk_cleanup_queue() finishes. */ spin_lock_irqsave(q->queue_lock, flags); blk_requeue_request(q, cmd->request); - spin_unlock_irqrestore(q->queue_lock, flags); - kblockd_schedule_work(q, &device->requeue_work); + spin_unlock_irqrestore(q->queue_lock, flags); } /* diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index 9aa578a5da11..d19d7e99626d 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -967,13 +967,20 @@ void __scsi_remove_device(struct scsi_device *sdev) device_del(dev); } else put_device(&sdev->sdev_dev); + + /* + * Stop accepting new requests and wait until all queuecommand() and + * scsi_run_queue() invocations have finished before tearing down the + * device. + */ scsi_device_set_state(sdev, SDEV_DEL); + blk_cleanup_queue(sdev->request_queue); + cancel_work_sync(&sdev->requeue_work); + if (sdev->host->hostt->slave_destroy) sdev->host->hostt->slave_destroy(sdev); transport_destroy_device(dev); - /* Freeing the queue signals to block that we're done */ - blk_cleanup_queue(sdev->request_queue); put_device(dev); } -- cgit v1.2.3