Skip to content

Commit

Permalink
[SCSI] libsas: enforce eh strategy handlers only in eh context
Browse files Browse the repository at this point in the history
The strategy handlers may be called in places that are problematic for
libsas (i.e. sata resets outside of domain revalidation filtering /
libata link recovery), or problematic for userspace (non-blocking ioctl
to sleeping reset functions).  However, these routines are also called
for eh escalations and recovery of scsi_eh_prep_cmnd(), so permit them
as long as we are running in the host's error handler, otherwise arrange
for them to be triggered in eh_context.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: James Bottomley <JBottomley@Parallels.com>
  • Loading branch information
Dan Williams authored and James Bottomley committed Jul 20, 2012
1 parent b9d5c6b commit 5db45bd
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 4 deletions.
11 changes: 11 additions & 0 deletions drivers/scsi/libsas/sas_discover.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void sas_init_dev(struct domain_device *dev)
{
switch (dev->dev_type) {
case SAS_END_DEV:
INIT_LIST_HEAD(&dev->ssp_dev.eh_list_node);
break;
case EDGE_DEV:
case FANOUT_DEV:
Expand Down Expand Up @@ -286,6 +287,8 @@ void sas_free_device(struct kref *kref)

static void sas_unregister_common_dev(struct asd_sas_port *port, struct domain_device *dev)
{
struct sas_ha_struct *ha = port->ha;

sas_notify_lldd_dev_gone(dev);
if (!dev->parent)
dev->port->port_dev = NULL;
Expand All @@ -298,6 +301,14 @@ static void sas_unregister_common_dev(struct asd_sas_port *port, struct domain_d
sas_ata_end_eh(dev->sata_dev.ap);
spin_unlock_irq(&port->dev_list_lock);

spin_lock_irq(&ha->lock);
if (dev->dev_type == SAS_END_DEV &&
!list_empty(&dev->ssp_dev.eh_list_node)) {
list_del_init(&dev->ssp_dev.eh_list_node);
ha->eh_active--;
}
spin_unlock_irq(&ha->lock);

sas_put_device(dev);
}

Expand Down
2 changes: 2 additions & 0 deletions drivers/scsi/libsas/sas_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ int sas_register_ha(struct sas_ha_struct *sas_ha)
set_bit(SAS_HA_REGISTERED, &sas_ha->state);
spin_lock_init(&sas_ha->lock);
mutex_init(&sas_ha->drain_mutex);
init_waitqueue_head(&sas_ha->eh_wait_q);
INIT_LIST_HEAD(&sas_ha->defer_q);
INIT_LIST_HEAD(&sas_ha->eh_dev_q);

error = sas_register_phys(sas_ha);
if (error) {
Expand Down
121 changes: 117 additions & 4 deletions drivers/scsi/libsas/sas_scsi_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -460,14 +460,88 @@ struct sas_phy *sas_get_local_phy(struct domain_device *dev)
}
EXPORT_SYMBOL_GPL(sas_get_local_phy);

static void sas_wait_eh(struct domain_device *dev)
{
struct sas_ha_struct *ha = dev->port->ha;
DEFINE_WAIT(wait);

if (dev_is_sata(dev)) {
ata_port_wait_eh(dev->sata_dev.ap);
return;
}
retry:
spin_lock_irq(&ha->lock);

while (test_bit(SAS_DEV_EH_PENDING, &dev->state)) {
prepare_to_wait(&ha->eh_wait_q, &wait, TASK_UNINTERRUPTIBLE);
spin_unlock_irq(&ha->lock);
schedule();
spin_lock_irq(&ha->lock);
}
finish_wait(&ha->eh_wait_q, &wait);

spin_unlock_irq(&ha->lock);

/* make sure SCSI EH is complete */
if (scsi_host_in_recovery(ha->core.shost)) {
msleep(10);
goto retry;
}
}
EXPORT_SYMBOL(sas_wait_eh);

static int sas_queue_reset(struct domain_device *dev, int reset_type, int lun, int wait)
{
struct sas_ha_struct *ha = dev->port->ha;
int scheduled = 0, tries = 100;

/* ata: promote lun reset to bus reset */
if (dev_is_sata(dev)) {
sas_ata_schedule_reset(dev);
if (wait)
sas_ata_wait_eh(dev);
return SUCCESS;
}

while (!scheduled && tries--) {
spin_lock_irq(&ha->lock);
if (!test_bit(SAS_DEV_EH_PENDING, &dev->state) &&
!test_bit(reset_type, &dev->state)) {
scheduled = 1;
ha->eh_active++;
list_add_tail(&dev->ssp_dev.eh_list_node, &ha->eh_dev_q);
set_bit(SAS_DEV_EH_PENDING, &dev->state);
set_bit(reset_type, &dev->state);
int_to_scsilun(lun, &dev->ssp_dev.reset_lun);
scsi_schedule_eh(ha->core.shost);
}
spin_unlock_irq(&ha->lock);

if (wait)
sas_wait_eh(dev);

if (scheduled)
return SUCCESS;
}

SAS_DPRINTK("%s reset of %s failed\n",
reset_type == SAS_DEV_LU_RESET ? "LUN" : "Bus",
dev_name(&dev->rphy->dev));

return FAILED;
}

/* Attempt to send a LUN reset message to a device */
int sas_eh_device_reset_handler(struct scsi_cmnd *cmd)
{
struct domain_device *dev = cmd_to_domain_dev(cmd);
struct sas_internal *i =
to_sas_internal(dev->port->ha->core.shost->transportt);
struct scsi_lun lun;
int res;
struct scsi_lun lun;
struct Scsi_Host *host = cmd->device->host;
struct domain_device *dev = cmd_to_domain_dev(cmd);
struct sas_internal *i = to_sas_internal(host->transportt);

if (current != host->ehandler)
return sas_queue_reset(dev, SAS_DEV_LU_RESET, cmd->device->lun, 0);

int_to_scsilun(cmd->device->lun, &lun);

Expand All @@ -486,8 +560,12 @@ int sas_eh_bus_reset_handler(struct scsi_cmnd *cmd)
{
struct domain_device *dev = cmd_to_domain_dev(cmd);
struct sas_phy *phy = sas_get_local_phy(dev);
struct Scsi_Host *host = cmd->device->host;
int res;

if (current != host->ehandler)
return sas_queue_reset(dev, SAS_DEV_RESET, 0, 0);

res = sas_phy_reset(phy, 1);
if (res)
SAS_DPRINTK("Bus reset of %s failed 0x%x\n",
Expand Down Expand Up @@ -667,6 +745,39 @@ static void sas_eh_handle_sas_errors(struct Scsi_Host *shost, struct list_head *
goto out;
}

static void sas_eh_handle_resets(struct Scsi_Host *shost)
{
struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost);
struct sas_internal *i = to_sas_internal(shost->transportt);

/* handle directed resets to sas devices */
spin_lock_irq(&ha->lock);
while (!list_empty(&ha->eh_dev_q)) {
struct domain_device *dev;
struct ssp_device *ssp;

ssp = list_entry(ha->eh_dev_q.next, typeof(*ssp), eh_list_node);
list_del_init(&ssp->eh_list_node);
dev = container_of(ssp, typeof(*dev), ssp_dev);
kref_get(&dev->kref);
WARN_ONCE(dev_is_sata(dev), "ssp reset to ata device?\n");

spin_unlock_irq(&ha->lock);

if (test_and_clear_bit(SAS_DEV_LU_RESET, &dev->state))
i->dft->lldd_lu_reset(dev, ssp->reset_lun.scsi_lun);

if (test_and_clear_bit(SAS_DEV_RESET, &dev->state))
i->dft->lldd_I_T_nexus_reset(dev);

sas_put_device(dev);
spin_lock_irq(&ha->lock);
clear_bit(SAS_DEV_EH_PENDING, &dev->state);
ha->eh_active--;
}
spin_unlock_irq(&ha->lock);
}


void sas_scsi_recover_host(struct Scsi_Host *shost)
{
Expand Down Expand Up @@ -709,6 +820,8 @@ void sas_scsi_recover_host(struct Scsi_Host *shost)
if (ha->lldd_max_execute_num > 1)
wake_up_process(ha->core.queue_thread);

sas_eh_handle_resets(shost);

/* now link into libata eh --- if we have any ata devices */
sas_ata_strategy_handler(shost);

Expand Down
10 changes: 10 additions & 0 deletions include/scsi/libsas.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,17 @@ struct sata_device {
u8 fis[ATA_RESP_FIS_SIZE];
};

struct ssp_device {
struct list_head eh_list_node; /* pending a user requested eh action */
struct scsi_lun reset_lun;
};

enum {
SAS_DEV_GONE,
SAS_DEV_DESTROY,
SAS_DEV_EH_PENDING,
SAS_DEV_LU_RESET,
SAS_DEV_RESET,
};

struct domain_device {
Expand Down Expand Up @@ -213,6 +220,7 @@ struct domain_device {
union {
struct expander_device ex_dev;
struct sata_device sata_dev; /* STP & directly attached */
struct ssp_device ssp_dev;
};

void *lldd_dev;
Expand Down Expand Up @@ -389,6 +397,8 @@ struct sas_ha_struct {
unsigned long state;
spinlock_t lock;
int eh_active;
wait_queue_head_t eh_wait_q;
struct list_head eh_dev_q;

struct mutex disco_mutex;

Expand Down

0 comments on commit 5db45bd

Please sign in to comment.