Skip to content

Commit

Permalink
isci: atomic device lookup and reference counting
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Dan Williams committed Jul 3, 2011
1 parent 360b03e commit 209fae1
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 237 deletions.
4 changes: 2 additions & 2 deletions drivers/scsi/isci/host.c
Original file line number Diff line number Diff line change
Expand Up @@ -1327,8 +1327,8 @@ void isci_host_deinit(struct isci_host *ihost)
struct isci_remote_device *idev, *d;

list_for_each_entry_safe(idev, d, &iport->remote_dev_list, node) {
isci_remote_device_change_state(idev, isci_stopping);
isci_remote_device_stop(ihost, idev);
if (test_bit(IDEV_ALLOCATED, &idev->flags))
isci_remote_device_stop(ihost, idev);
}
}

Expand Down
3 changes: 1 addition & 2 deletions drivers/scsi/isci/port.c
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,7 @@ static void isci_port_link_down(struct isci_host *isci_host,
dev_dbg(&isci_host->pdev->dev,
"%s: isci_device = %p\n",
__func__, isci_device);
isci_remote_device_change_state(isci_device,
isci_stopping);
set_bit(IDEV_GONE, &isci_device->flags);
}
}
isci_port_change_state(isci_port, isci_stopping);
Expand Down
80 changes: 41 additions & 39 deletions drivers/scsi/isci/remote_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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.
Expand All @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions drivers/scsi/isci/remote_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#ifndef _ISCI_REMOTE_DEVICE_H_
#define _ISCI_REMOTE_DEVICE_H_
#include <scsi/libsas.h>
#include <linux/kref.h>
#include "scu_remote_node_context.h"
#include "remote_node_context.h"
#include "port.h"
Expand Down Expand Up @@ -134,7 +135,9 @@ struct isci_remote_device {
#define IDEV_STOP_PENDING 1
#define IDEV_ALLOCATED 2
#define IDEV_EH 3
#define IDEV_GONE 4
unsigned long flags;
struct kref kref;
struct isci_port *isci_port;
struct domain_device *domain_dev;
struct list_head node;
Expand All @@ -145,6 +148,26 @@ struct isci_remote_device {

#define ISCI_REMOTE_DEVICE_START_TIMEOUT 5000

/* device reference routines must be called under scic_lock */
static inline struct isci_remote_device *isci_lookup_device(struct domain_device *dev)
{
struct isci_remote_device *idev = dev->lldd_dev;

if (idev && !test_bit(IDEV_GONE, &idev->flags)) {
kref_get(&idev->kref);
return idev;
}

return NULL;
}

void isci_remote_device_release(struct kref *kref);
static inline void isci_put_device(struct isci_remote_device *idev)
{
if (idev)
kref_put(&idev->kref, isci_remote_device_release);
}

enum sci_status isci_remote_device_stop(struct isci_host *ihost,
struct isci_remote_device *idev);
void isci_remote_device_nuke_requests(struct isci_host *ihost,
Expand Down
Loading

0 comments on commit 209fae1

Please sign in to comment.