diff options
author | Jeff Skirvin <jeffrey.d.skirvin@intel.com> | 2011-06-20 14:09:16 -0700 |
---|---|---|
committer | Dan Williams <dan.j.williams@intel.com> | 2011-07-03 04:04:50 -0700 |
commit | 77c852f312243192b1f2ce7fc56d678784786692 (patch) | |
tree | bb0f8a9694f1f2cf4d7985ef3dc40af79963c4f7 /drivers/scsi/isci | |
parent | f53a3a32c1e799e27f63bff7b42b4c36749e5e6f (diff) |
isci: Handle timed-out request terminations correctly
In the situation where a termination of an I/O times-out,
make sure that the linkage from the request to the task
is severed completely. Also make sure that the selection
of tasks to terminate occurs under scic_lock.
Signed-off-by: Jeff Skirvin <jeffrey.d.skirvin@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Diffstat (limited to 'drivers/scsi/isci')
-rw-r--r-- | drivers/scsi/isci/request.c | 9 | ||||
-rw-r--r-- | drivers/scsi/isci/task.c | 358 |
2 files changed, 181 insertions, 186 deletions
diff --git a/drivers/scsi/isci/request.c b/drivers/scsi/isci/request.c index 5879e5f308e..433565c2b34 100644 --- a/drivers/scsi/isci/request.c +++ b/drivers/scsi/isci/request.c @@ -2741,6 +2741,15 @@ static void isci_request_io_request_complete(struct isci_host *isci_host, spin_unlock(&request->state_lock); break; + case dead: + /* This was a terminated request that timed-out during the + * termination process. There is no task to complete to + * libsas. + */ + complete_to_host = isci_perform_normal_io_completion; + spin_unlock(&request->state_lock); + break; + default: /* The request is done from an SCU HW perspective. */ diff --git a/drivers/scsi/isci/task.c b/drivers/scsi/isci/task.c index 709c0817174..573cf1c9e81 100644 --- a/drivers/scsi/isci/task.c +++ b/drivers/scsi/isci/task.c @@ -567,31 +567,33 @@ static enum isci_request_status isci_task_validate_request_to_abort( return old_state; } +/** +* isci_request_cleanup_completed_loiterer() - This function will take care of +* the final cleanup on any request which has been explicitly terminated. +* @isci_host: This parameter specifies the ISCI host object +* @isci_device: This is the device to which the request is pending. +* @isci_request: This parameter specifies the terminated request object. +* @task: This parameter is the libsas I/O request. +*/ static void isci_request_cleanup_completed_loiterer( - struct isci_host *isci_host, + struct isci_host *isci_host, struct isci_remote_device *isci_device, - struct isci_request *isci_request) + struct isci_request *isci_request, + struct sas_task *task) { - struct sas_task *task; - unsigned long flags; - - task = (isci_request->ttype == io_task) - ? isci_request_access_task(isci_request) - : NULL; + unsigned long flags; dev_dbg(&isci_host->pdev->dev, "%s: isci_device=%p, request=%p, task=%p\n", __func__, isci_device, isci_request, task); - spin_lock_irqsave(&isci_host->scic_lock, flags); - list_del_init(&isci_request->dev_node); - spin_unlock_irqrestore(&isci_host->scic_lock, flags); - if (task != NULL) { spin_lock_irqsave(&task->task_state_lock, flags); task->lldd_task = NULL; + task->task_state_flags &= ~SAS_TASK_NEED_DEV_RESET; + isci_set_task_doneflags(task); /* If this task is not in the abort path, call task_done. */ @@ -602,61 +604,16 @@ static void isci_request_cleanup_completed_loiterer( } else spin_unlock_irqrestore(&task->task_state_lock, flags); } - isci_request_free(isci_host, isci_request); -} - -/** -* @isci_termination_timed_out(): this function will deal with a request for -* which the wait for termination has timed-out. -* -* @isci_host This SCU. -* @isci_request The I/O request being terminated. -*/ -static void -isci_termination_timed_out( - struct isci_host * host, - struct isci_request * request - ) -{ - unsigned long state_flags; - - dev_warn(&host->pdev->dev, - "%s: host = %p; request = %p\n", - __func__, host, request); - /* At this point, the request to terminate - * has timed out. The best we can do is to - * have the request die a silent death - * if it ever completes. - */ - spin_lock_irqsave(&request->state_lock, state_flags); - - if (request->status == started) { - - /* Set the request state to "dead", - * and clear the task pointer so that an actual - * completion event callback doesn't do - * anything. - */ - request->status = dead; - - /* Clear the timeout completion event pointer.*/ - request->io_request_completion = NULL; - - if (request->ttype == io_task) { - - /* Break links with the sas_task. */ - if (request->ttype_ptr.io_task_ptr != NULL) { + if (isci_request != NULL) { + spin_lock_irqsave(&isci_host->scic_lock, flags); + list_del_init(&isci_request->dev_node); + spin_unlock_irqrestore(&isci_host->scic_lock, flags); - request->ttype_ptr.io_task_ptr->lldd_task = NULL; - request->ttype_ptr.io_task_ptr = NULL; - } - } + isci_request_free(isci_host, isci_request); } - spin_unlock_irqrestore(&request->state_lock, state_flags); } - /** * isci_terminate_request_core() - This function will terminate the given * request, and wait for it to complete. This function must only be called @@ -666,7 +623,6 @@ isci_termination_timed_out( * @isci_device: The target. * @isci_request: The I/O request to be terminated. * - * */ static void isci_terminate_request_core( struct isci_host *isci_host, @@ -677,9 +633,10 @@ static void isci_terminate_request_core( bool was_terminated = false; bool needs_cleanup_handling = false; enum isci_request_status request_status; - unsigned long flags; - unsigned long timeout_remaining; - + unsigned long flags; + unsigned long termination_completed = 1; + struct completion *io_request_completion; + struct sas_task *task; dev_dbg(&isci_host->pdev->dev, "%s: device = %p; request = %p\n", @@ -687,6 +644,12 @@ static void isci_terminate_request_core( spin_lock_irqsave(&isci_host->scic_lock, flags); + io_request_completion = isci_request->io_request_completion; + + task = (isci_request->ttype == io_task) + ? isci_request_access_task(isci_request) + : NULL; + /* Note that we are not going to control * the target to abort the request. */ @@ -715,119 +678,112 @@ static void isci_terminate_request_core( dev_err(&isci_host->pdev->dev, "%s: scic_controller_terminate_request" " returned = 0x%x\n", - __func__, - status); - /* Clear the completion pointer from the request. */ + __func__, status); + isci_request->io_request_completion = NULL; } else { if (was_terminated) { dev_dbg(&isci_host->pdev->dev, - "%s: before completion wait (%p)\n", - __func__, - isci_request->io_request_completion); + "%s: before completion wait (%p/%p)\n", + __func__, isci_request, io_request_completion); /* Wait here for the request to complete. */ - #define TERMINATION_TIMEOUT_MSEC 50 - timeout_remaining + #define TERMINATION_TIMEOUT_MSEC 500 + termination_completed = wait_for_completion_timeout( - isci_request->io_request_completion, + io_request_completion, msecs_to_jiffies(TERMINATION_TIMEOUT_MSEC)); - if (!timeout_remaining) { + if (!termination_completed) { + + /* The request to terminate has timed out. */ + spin_lock_irqsave(&isci_host->scic_lock, + flags); + + /* Check for state changes. */ + if (!isci_request->terminated) { + + /* The best we can do is to have the + * request die a silent death if it + * ever really completes. + * + * Set the request state to "dead", + * and clear the task pointer so that + * an actual completion event callback + * doesn't do anything. + */ + isci_request->status = dead; + isci_request->io_request_completion + = NULL; + + if (isci_request->ttype == io_task) { + + /* Break links with the + * sas_task. + */ + isci_request->ttype_ptr.io_task_ptr + = NULL; + } + } else + termination_completed = 1; + + spin_unlock_irqrestore(&isci_host->scic_lock, + flags); - isci_termination_timed_out(isci_host, - isci_request); + if (!termination_completed) { - dev_err(&isci_host->pdev->dev, - "%s: *** Timeout waiting for " - "termination(%p/%p)\n", - __func__, - isci_request->io_request_completion, - isci_request); + dev_err(&isci_host->pdev->dev, + "%s: *** Timeout waiting for " + "termination(%p/%p)\n", + __func__, io_request_completion, + isci_request); - } else + /* The request can no longer be referenced + * safely since it may go away if the + * termination every really does complete. + */ + isci_request = NULL; + } + } + if (termination_completed) dev_dbg(&isci_host->pdev->dev, - "%s: after completion wait (%p)\n", - __func__, - isci_request->io_request_completion); + "%s: after completion wait (%p/%p)\n", + __func__, isci_request, io_request_completion); } - /* Clear the completion pointer from the request. */ - isci_request->io_request_completion = NULL; - /* Peek at the status of the request. This will tell - * us if there was special handling on the request such that it - * needs to be detached and freed here. - */ - spin_lock_irqsave(&isci_request->state_lock, flags); - request_status = isci_request_get_state(isci_request); - - if ((isci_request->ttype == io_task) /* TMFs are in their own thread */ - && ((request_status == aborted) - || (request_status == aborting) - || (request_status == terminating) - || (request_status == completed) - || (request_status == dead) - ) - ) { - - /* The completion routine won't free a request in - * the aborted/aborting/etc. states, so we do - * it here. - */ - needs_cleanup_handling = true; - } - spin_unlock_irqrestore(&isci_request->state_lock, flags); - - if (needs_cleanup_handling) - isci_request_cleanup_completed_loiterer( - isci_host, isci_device, isci_request - ); - } -} + if (termination_completed) { -static void isci_terminate_request( - struct isci_host *isci_host, - struct isci_remote_device *isci_device, - struct isci_request *isci_request, - enum isci_request_status new_request_state) -{ - enum isci_request_status old_state; - DECLARE_COMPLETION_ONSTACK(request_completion); + isci_request->io_request_completion = NULL; - /* Change state to "new_request_state" if it is currently "started" */ - old_state = isci_request_change_started_to_newstate( - isci_request, - &request_completion, - new_request_state - ); + /* Peek at the status of the request. This will tell + * us if there was special handling on the request such that it + * needs to be detached and freed here. + */ + spin_lock_irqsave(&isci_request->state_lock, flags); + request_status = isci_request_get_state(isci_request); + + if ((isci_request->ttype == io_task) /* TMFs are in their own thread */ + && ((request_status == aborted) + || (request_status == aborting) + || (request_status == terminating) + || (request_status == completed) + || (request_status == dead) + ) + ) { + + /* The completion routine won't free a request in + * the aborted/aborting/etc. states, so we do + * it here. + */ + needs_cleanup_handling = true; + } + spin_unlock_irqrestore(&isci_request->state_lock, flags); - if ((old_state == started) || - (old_state == completed) || - (old_state == aborting)) { - - /* If the old_state is started: - * This request was not already being aborted. If it had been, - * then the aborting I/O (ie. the TMF request) would not be in - * the aborting state, and thus would be terminated here. Note - * that since the TMF completion's call to the kernel function - * "complete()" does not happen until the pending I/O request - * terminate fully completes, we do not have to implement a - * special wait here for already aborting requests - the - * termination of the TMF request will force the request - * to finish it's already started terminate. - * - * If old_state == completed: - * This request completed from the SCU hardware perspective - * and now just needs cleaning up in terms of freeing the - * request and potentially calling up to libsas. - * - * If old_state == aborting: - * This request has already gone through a TMF timeout, but may - * not have been terminated; needs cleaning up at least. - */ - isci_terminate_request_core(isci_host, isci_device, - isci_request); + } + if (needs_cleanup_handling) + isci_request_cleanup_completed_loiterer( + isci_host, isci_device, isci_request, task); } } @@ -850,9 +806,8 @@ void isci_terminate_pending_requests( struct isci_request *request; struct isci_request *next_request; unsigned long flags; - struct list_head aborted_request_list; - - INIT_LIST_HEAD(&aborted_request_list); + enum isci_request_status old_state; + DECLARE_COMPLETION_ONSTACK(request_completion); dev_dbg(&isci_host->pdev->dev, "%s: isci_device = %p (new request state = %d)\n", @@ -860,31 +815,62 @@ void isci_terminate_pending_requests( spin_lock_irqsave(&isci_host->scic_lock, flags); - /* Move all of the pending requests off of the device list. */ - list_splice_init(&isci_device->reqs_in_process, - &aborted_request_list); - - spin_unlock_irqrestore(&isci_host->scic_lock, flags); - - /* Iterate through the now-local list. */ + /* Iterate through the list. */ list_for_each_entry_safe(request, next_request, - &aborted_request_list, dev_node) { + &isci_device->reqs_in_process, dev_node) { - dev_warn(&isci_host->pdev->dev, - "%s: isci_device=%p request=%p; task=%p\n", - __func__, - isci_device, request, - ((request->ttype == io_task) - ? isci_request_access_task(request) - : NULL)); + init_completion(&request_completion); - /* Mark all still pending I/O with the selected next - * state, terminate and free it. + /* Change state to "new_request_state" if it is currently + * "started". */ - isci_terminate_request(isci_host, isci_device, - request, new_request_state - ); + old_state = isci_request_change_started_to_newstate( + request, + &request_completion, + new_request_state); + + spin_unlock_irqrestore(&isci_host->scic_lock, flags); + + if ((old_state == started) || + (old_state == completed) || + (old_state == aborting)) { + + dev_warn(&isci_host->pdev->dev, + "%s: isci_device=%p request=%p; task=%p " + "old_state=%d\n", + __func__, + isci_device, request, + ((request->ttype == io_task) + ? isci_request_access_task(request) + : NULL), + old_state); + + /* If the old_state is started: + * This request was not already being aborted. If it had been, + * then the aborting I/O (ie. the TMF request) would not be in + * the aborting state, and thus would be terminated here. Note + * that since the TMF completion's call to the kernel function + * "complete()" does not happen until the pending I/O request + * terminate fully completes, we do not have to implement a + * special wait here for already aborting requests - the + * termination of the TMF request will force the request + * to finish it's already started terminate. + * + * If old_state == completed: + * This request completed from the SCU hardware perspective + * and now just needs cleaning up in terms of freeing the + * request and potentially calling up to libsas. + * + * If old_state == aborting: + * This request has already gone through a TMF timeout, but may + * not have been terminated; needs cleaning up at least. + */ + isci_terminate_request_core(isci_host, isci_device, + request); + } + spin_lock_irqsave(&isci_host->scic_lock, flags); } + spin_unlock_irqrestore(&isci_host->scic_lock, flags); } /** @@ -1257,9 +1243,9 @@ int isci_task_abort_task(struct sas_task *task) if (ret == TMF_RESP_FUNC_COMPLETE) { old_request->complete_in_target = true; - /* Clean up the request on our side, and wait for the aborted I/O to - * complete. - */ + /* Clean up the request on our side, and wait for the aborted + * I/O to complete. + */ isci_terminate_request_core(isci_host, isci_device, old_request); } |