summaryrefslogtreecommitdiff
path: root/drivers/scsi/isci/remote_device.c
diff options
context:
space:
mode:
authorDan Williams <dan.j.williams@intel.com>2011-06-13 17:39:44 -0700
committerDan Williams <dan.j.williams@intel.com>2011-07-03 04:04:51 -0700
commit209fae14fabfd48525e5630bebbbd4ca15090c60 (patch)
treeb251b9b394b3493cc15242ea31002abcb4e9bb59 /drivers/scsi/isci/remote_device.c
parent360b03ed178a4fe3971b0a098d8feeb53333481b (diff)
isci: atomic device lookup and reference counting
We have unsafe references to remote devices that are notified to disappear at lldd_dev_gone. In order to clean this up we need a single canonical source for device lookups and stable references once a lookup succeeds. Towards that end guarantee that domain_device.lldd_dev is NULL as soon as we start the process of stopping a device. Any code path that wants to safely lookup a remote device must do so through task->dev->lldd_dev (isci_lookup_device()). For in-flight references outside of scic_lock we need reference counting to ensure that the device is not recycled before we are done with it. Simplify device back references to just scic_sds_request.target_device which is now the only permissible internal reference that is maintained relative to the reference count. There were two occasions where we wanted new i/o's to be treated as SAS_TASK_UNDELIVERED but where the domain_dev->lldd_dev link is still intact. Introduce a 'gone' flag to prevent i/o while waiting for libsas to take action on the port down event. One 'core' leftover is that we currently call scic_remote_device_destruct() from isci_remote_device_deconstruct() which is called when the 'core' says the device is stopped. It would be more natural for the final put to trigger isci_remote_device_deconstruct() but this implementation is deferred as it requires other changes. Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Diffstat (limited to 'drivers/scsi/isci/remote_device.c')
-rw-r--r--drivers/scsi/isci/remote_device.c80
1 files changed, 41 insertions, 39 deletions
diff --git a/drivers/scsi/isci/remote_device.c b/drivers/scsi/isci/remote_device.c
index 45592ad33c3b..ab5f9868e4ef 100644
--- a/drivers/scsi/isci/remote_device.c
+++ b/drivers/scsi/isci/remote_device.c
@@ -94,7 +94,7 @@ static void isci_remote_device_not_ready(struct isci_host *ihost,
"%s: isci_device = %p\n", __func__, idev);
if (reason == SCIC_REMOTE_DEVICE_NOT_READY_STOP_REQUESTED)
- isci_remote_device_change_state(idev, isci_stopping);
+ set_bit(IDEV_GONE, &idev->flags);
else
/* device ready is actually a "not ready for io" state. */
isci_remote_device_change_state(idev, isci_ready);
@@ -449,8 +449,10 @@ static void scic_sds_remote_device_start_request(struct scic_sds_remote_device *
/* cleanup requests that failed after starting on the port */
if (status != SCI_SUCCESS)
scic_sds_port_complete_io(sci_port, sci_dev, sci_req);
- else
+ else {
+ kref_get(&sci_dev_to_idev(sci_dev)->kref);
scic_sds_remote_device_increment_request_count(sci_dev);
+ }
}
enum sci_status scic_sds_remote_device_start_io(struct scic_sds_controller *scic,
@@ -656,6 +658,8 @@ enum sci_status scic_sds_remote_device_complete_io(struct scic_sds_controller *s
"%s: Port:0x%p Device:0x%p Request:0x%p Status:0x%x "
"could not complete\n", __func__, sci_port,
sci_dev, sci_req, status);
+ else
+ isci_put_device(sci_dev_to_idev(sci_dev));
return status;
}
@@ -860,23 +864,11 @@ static void isci_remote_device_deconstruct(struct isci_host *ihost, struct isci_
* here should go through isci_remote_device_nuke_requests.
* If we hit this condition, we will need a way to complete
* io requests in process */
- while (!list_empty(&idev->reqs_in_process)) {
-
- dev_err(&ihost->pdev->dev,
- "%s: ** request list not empty! **\n", __func__);
- BUG();
- }
+ BUG_ON(!list_empty(&idev->reqs_in_process));
scic_remote_device_destruct(&idev->sci);
- idev->domain_dev->lldd_dev = NULL;
- idev->domain_dev = NULL;
- idev->isci_port = NULL;
list_del_init(&idev->node);
-
- clear_bit(IDEV_START_PENDING, &idev->flags);
- clear_bit(IDEV_STOP_PENDING, &idev->flags);
- clear_bit(IDEV_EH, &idev->flags);
- wake_up(&ihost->eventq);
+ isci_put_device(idev);
}
/**
@@ -1314,6 +1306,22 @@ isci_remote_device_alloc(struct isci_host *ihost, struct isci_port *iport)
return idev;
}
+void isci_remote_device_release(struct kref *kref)
+{
+ struct isci_remote_device *idev = container_of(kref, typeof(*idev), kref);
+ struct isci_host *ihost = idev->isci_port->isci_host;
+
+ idev->domain_dev = NULL;
+ idev->isci_port = NULL;
+ clear_bit(IDEV_START_PENDING, &idev->flags);
+ clear_bit(IDEV_STOP_PENDING, &idev->flags);
+ clear_bit(IDEV_GONE, &idev->flags);
+ clear_bit(IDEV_EH, &idev->flags);
+ smp_mb__before_clear_bit();
+ clear_bit(IDEV_ALLOCATED, &idev->flags);
+ wake_up(&ihost->eventq);
+}
+
/**
* isci_remote_device_stop() - This function is called internally to stop the
* remote device.
@@ -1330,7 +1338,11 @@ enum sci_status isci_remote_device_stop(struct isci_host *ihost, struct isci_rem
dev_dbg(&ihost->pdev->dev,
"%s: isci_device = %p\n", __func__, idev);
+ spin_lock_irqsave(&ihost->scic_lock, flags);
+ idev->domain_dev->lldd_dev = NULL; /* disable new lookups */
+ set_bit(IDEV_GONE, &idev->flags);
isci_remote_device_change_state(idev, isci_stopping);
+ spin_unlock_irqrestore(&ihost->scic_lock, flags);
/* Kill all outstanding requests. */
isci_remote_device_nuke_requests(ihost, idev);
@@ -1342,14 +1354,10 @@ enum sci_status isci_remote_device_stop(struct isci_host *ihost, struct isci_rem
spin_unlock_irqrestore(&ihost->scic_lock, flags);
/* Wait for the stop complete callback. */
- if (status == SCI_SUCCESS) {
+ if (WARN_ONCE(status != SCI_SUCCESS, "failed to stop device\n"))
+ /* nothing to wait for */;
+ else
wait_for_device_stop(ihost, idev);
- clear_bit(IDEV_ALLOCATED, &idev->flags);
- }
-
- dev_dbg(&ihost->pdev->dev,
- "%s: idev = %p - after completion wait\n",
- __func__, idev);
return status;
}
@@ -1416,39 +1424,33 @@ int isci_remote_device_found(struct domain_device *domain_dev)
if (!isci_device)
return -ENODEV;
+ kref_init(&isci_device->kref);
INIT_LIST_HEAD(&isci_device->node);
- domain_dev->lldd_dev = isci_device;
+
+ spin_lock_irq(&isci_host->scic_lock);
isci_device->domain_dev = domain_dev;
isci_device->isci_port = isci_port;
isci_remote_device_change_state(isci_device, isci_starting);
-
-
- spin_lock_irq(&isci_host->scic_lock);
list_add_tail(&isci_device->node, &isci_port->remote_dev_list);
set_bit(IDEV_START_PENDING, &isci_device->flags);
status = isci_remote_device_construct(isci_port, isci_device);
- spin_unlock_irq(&isci_host->scic_lock);
dev_dbg(&isci_host->pdev->dev,
"%s: isci_device = %p\n",
__func__, isci_device);
- if (status != SCI_SUCCESS) {
-
- spin_lock_irq(&isci_host->scic_lock);
- isci_remote_device_deconstruct(
- isci_host,
- isci_device
- );
- spin_unlock_irq(&isci_host->scic_lock);
- return -ENODEV;
- }
+ if (status == SCI_SUCCESS) {
+ /* device came up, advertise it to the world */
+ domain_dev->lldd_dev = isci_device;
+ } else
+ isci_put_device(isci_device);
+ spin_unlock_irq(&isci_host->scic_lock);
/* wait for the device ready callback. */
wait_for_device_start(isci_host, isci_device);
- return 0;
+ return status == SCI_SUCCESS ? 0 : -ENODEV;
}
/**
* isci_device_is_reset_pending() - This function will check if there is any