Commit ad8dcffa authored by Brian King's avatar Brian King Committed by James Bottomley

[SCSI] ibmvfc: Error handling fixes

Due to an ambiguity in the VIOS VFC interface specification,
abort/cancel handling is not done correctly and can result in double
completion of commands. In order to cancel all outstanding commands to
a device, a cancel must be sent, followed by an abort task set. After
the responses are received for these commands, there may still be
commands outstanding, in the process of getting flushed back, in which
case, we need to wait for them. This patch removes the assumption that
if the abort and the cancel both complete successfully that the device
queue has been flushed and waits for all the responses to come back.
Signed-off-by: default avatarBrian King <brking@linux.vnet.ibm.com>
Signed-off-by: default avatarJames Bottomley <James.Bottomley@HansenPartnership.com>
parent 50119dad
...@@ -758,6 +758,9 @@ static void ibmvfc_scsi_eh_done(struct ibmvfc_event *evt) ...@@ -758,6 +758,9 @@ static void ibmvfc_scsi_eh_done(struct ibmvfc_event *evt)
cmnd->scsi_done(cmnd); cmnd->scsi_done(cmnd);
} }
if (evt->eh_comp)
complete(evt->eh_comp);
ibmvfc_free_event(evt); ibmvfc_free_event(evt);
} }
...@@ -1245,6 +1248,7 @@ static void ibmvfc_init_event(struct ibmvfc_event *evt, ...@@ -1245,6 +1248,7 @@ static void ibmvfc_init_event(struct ibmvfc_event *evt,
evt->sync_iu = NULL; evt->sync_iu = NULL;
evt->crq.format = format; evt->crq.format = format;
evt->done = done; evt->done = done;
evt->eh_comp = NULL;
} }
/** /**
...@@ -1485,6 +1489,9 @@ static void ibmvfc_scsi_done(struct ibmvfc_event *evt) ...@@ -1485,6 +1489,9 @@ static void ibmvfc_scsi_done(struct ibmvfc_event *evt)
cmnd->scsi_done(cmnd); cmnd->scsi_done(cmnd);
} }
if (evt->eh_comp)
complete(evt->eh_comp);
ibmvfc_free_event(evt); ibmvfc_free_event(evt);
} }
...@@ -1785,7 +1792,8 @@ static int ibmvfc_abort_task_set(struct scsi_device *sdev) ...@@ -1785,7 +1792,8 @@ static int ibmvfc_abort_task_set(struct scsi_device *sdev)
static int ibmvfc_cancel_all(struct scsi_device *sdev, int type) static int ibmvfc_cancel_all(struct scsi_device *sdev, int type)
{ {
struct ibmvfc_host *vhost = shost_priv(sdev->host); struct ibmvfc_host *vhost = shost_priv(sdev->host);
struct fc_rport *rport = starget_to_rport(scsi_target(sdev)); struct scsi_target *starget = scsi_target(sdev);
struct fc_rport *rport = starget_to_rport(starget);
struct ibmvfc_tmf *tmf; struct ibmvfc_tmf *tmf;
struct ibmvfc_event *evt, *found_evt; struct ibmvfc_event *evt, *found_evt;
union ibmvfc_iu rsp; union ibmvfc_iu rsp;
...@@ -1823,7 +1831,7 @@ static int ibmvfc_cancel_all(struct scsi_device *sdev, int type) ...@@ -1823,7 +1831,7 @@ static int ibmvfc_cancel_all(struct scsi_device *sdev, int type)
int_to_scsilun(sdev->lun, &tmf->lun); int_to_scsilun(sdev->lun, &tmf->lun);
tmf->flags = (type | IBMVFC_TMF_LUA_VALID); tmf->flags = (type | IBMVFC_TMF_LUA_VALID);
tmf->cancel_key = (unsigned long)sdev->hostdata; tmf->cancel_key = (unsigned long)sdev->hostdata;
tmf->my_cancel_key = (IBMVFC_TMF_CANCEL_KEY | (unsigned long)sdev->hostdata); tmf->my_cancel_key = (unsigned long)starget->hostdata;
evt->sync_iu = &rsp; evt->sync_iu = &rsp;
init_completion(&evt->comp); init_completion(&evt->comp);
...@@ -1855,37 +1863,114 @@ static int ibmvfc_cancel_all(struct scsi_device *sdev, int type) ...@@ -1855,37 +1863,114 @@ static int ibmvfc_cancel_all(struct scsi_device *sdev, int type)
} }
/** /**
* ibmvfc_eh_abort_handler - Abort a command * ibmvfc_match_target - Match function for specified target
* @cmd: scsi command to abort * @evt: ibmvfc event struct
* @device: device to match (starget)
*
* Returns:
* 1 if event matches starget / 0 if event does not match starget
**/
static int ibmvfc_match_target(struct ibmvfc_event *evt, void *device)
{
if (evt->cmnd && scsi_target(evt->cmnd->device) == device)
return 1;
return 0;
}
/**
* ibmvfc_match_lun - Match function for specified LUN
* @evt: ibmvfc event struct
* @device: device to match (sdev)
*
* Returns:
* 1 if event matches sdev / 0 if event does not match sdev
**/
static int ibmvfc_match_lun(struct ibmvfc_event *evt, void *device)
{
if (evt->cmnd && evt->cmnd->device == device)
return 1;
return 0;
}
/**
* ibmvfc_wait_for_ops - Wait for ops to complete
* @vhost: ibmvfc host struct
* @device: device to match (starget or sdev)
* @match: match function
* *
* Returns: * Returns:
* SUCCESS / FAILED * SUCCESS / FAILED
**/ **/
static int ibmvfc_eh_abort_handler(struct scsi_cmnd *cmd) static int ibmvfc_wait_for_ops(struct ibmvfc_host *vhost, void *device,
int (*match) (struct ibmvfc_event *, void *))
{ {
struct ibmvfc_host *vhost = shost_priv(cmd->device->host); struct ibmvfc_event *evt;
struct ibmvfc_event *evt, *pos; DECLARE_COMPLETION_ONSTACK(comp);
int cancel_rc, abort_rc; int wait;
unsigned long flags; unsigned long flags;
signed long timeout = init_timeout * HZ;
ENTER; ENTER;
ibmvfc_wait_while_resetting(vhost); do {
cancel_rc = ibmvfc_cancel_all(cmd->device, IBMVFC_TMF_ABORT_TASK_SET); wait = 0;
abort_rc = ibmvfc_abort_task_set(cmd->device); spin_lock_irqsave(vhost->host->host_lock, flags);
list_for_each_entry(evt, &vhost->sent, queue) {
if (match(evt, device)) {
evt->eh_comp = &comp;
wait++;
}
}
spin_unlock_irqrestore(vhost->host->host_lock, flags);
if (wait) {
timeout = wait_for_completion_timeout(&comp, timeout);
if (!cancel_rc && !abort_rc) { if (!timeout) {
wait = 0;
spin_lock_irqsave(vhost->host->host_lock, flags); spin_lock_irqsave(vhost->host->host_lock, flags);
list_for_each_entry_safe(evt, pos, &vhost->sent, queue) { list_for_each_entry(evt, &vhost->sent, queue) {
if (evt->cmnd && evt->cmnd->device == cmd->device) if (match(evt, device)) {
ibmvfc_fail_request(evt, DID_ABORT); evt->eh_comp = NULL;
wait++;
}
} }
spin_unlock_irqrestore(vhost->host->host_lock, flags); spin_unlock_irqrestore(vhost->host->host_lock, flags);
if (wait)
dev_err(vhost->dev, "Timed out waiting for aborted commands\n");
LEAVE; LEAVE;
return SUCCESS; return wait ? FAILED : SUCCESS;
} }
}
} while (wait);
LEAVE;
return SUCCESS;
}
/**
* ibmvfc_eh_abort_handler - Abort a command
* @cmd: scsi command to abort
*
* Returns:
* SUCCESS / FAILED
**/
static int ibmvfc_eh_abort_handler(struct scsi_cmnd *cmd)
{
struct scsi_device *sdev = cmd->device;
struct ibmvfc_host *vhost = shost_priv(sdev->host);
int cancel_rc, abort_rc;
int rc = FAILED;
ENTER;
ibmvfc_wait_while_resetting(vhost);
cancel_rc = ibmvfc_cancel_all(sdev, IBMVFC_TMF_ABORT_TASK_SET);
abort_rc = ibmvfc_abort_task_set(sdev);
if (!cancel_rc && !abort_rc)
rc = ibmvfc_wait_for_ops(vhost, sdev, ibmvfc_match_lun);
LEAVE; LEAVE;
return FAILED; return rc;
} }
/** /**
...@@ -1897,29 +1982,21 @@ static int ibmvfc_eh_abort_handler(struct scsi_cmnd *cmd) ...@@ -1897,29 +1982,21 @@ static int ibmvfc_eh_abort_handler(struct scsi_cmnd *cmd)
**/ **/
static int ibmvfc_eh_device_reset_handler(struct scsi_cmnd *cmd) static int ibmvfc_eh_device_reset_handler(struct scsi_cmnd *cmd)
{ {
struct ibmvfc_host *vhost = shost_priv(cmd->device->host); struct scsi_device *sdev = cmd->device;
struct ibmvfc_event *evt, *pos; struct ibmvfc_host *vhost = shost_priv(sdev->host);
int cancel_rc, reset_rc; int cancel_rc, reset_rc;
unsigned long flags; int rc = FAILED;
ENTER; ENTER;
ibmvfc_wait_while_resetting(vhost); ibmvfc_wait_while_resetting(vhost);
cancel_rc = ibmvfc_cancel_all(cmd->device, IBMVFC_TMF_LUN_RESET); cancel_rc = ibmvfc_cancel_all(sdev, IBMVFC_TMF_LUN_RESET);
reset_rc = ibmvfc_reset_device(cmd->device, IBMVFC_LUN_RESET, "LUN"); reset_rc = ibmvfc_reset_device(sdev, IBMVFC_LUN_RESET, "LUN");
if (!cancel_rc && !reset_rc) { if (!cancel_rc && !reset_rc)
spin_lock_irqsave(vhost->host->host_lock, flags); rc = ibmvfc_wait_for_ops(vhost, sdev, ibmvfc_match_lun);
list_for_each_entry_safe(evt, pos, &vhost->sent, queue) {
if (evt->cmnd && evt->cmnd->device == cmd->device)
ibmvfc_fail_request(evt, DID_ABORT);
}
spin_unlock_irqrestore(vhost->host->host_lock, flags);
LEAVE;
return SUCCESS;
}
LEAVE; LEAVE;
return FAILED; return rc;
} }
/** /**
...@@ -1955,31 +2032,23 @@ static void ibmvfc_dev_abort_all(struct scsi_device *sdev, void *data) ...@@ -1955,31 +2032,23 @@ static void ibmvfc_dev_abort_all(struct scsi_device *sdev, void *data)
**/ **/
static int ibmvfc_eh_target_reset_handler(struct scsi_cmnd *cmd) static int ibmvfc_eh_target_reset_handler(struct scsi_cmnd *cmd)
{ {
struct ibmvfc_host *vhost = shost_priv(cmd->device->host); struct scsi_device *sdev = cmd->device;
struct scsi_target *starget = scsi_target(cmd->device); struct ibmvfc_host *vhost = shost_priv(sdev->host);
struct ibmvfc_event *evt, *pos; struct scsi_target *starget = scsi_target(sdev);
int reset_rc; int reset_rc;
int rc = FAILED;
unsigned long cancel_rc = 0; unsigned long cancel_rc = 0;
unsigned long flags;
ENTER; ENTER;
ibmvfc_wait_while_resetting(vhost); ibmvfc_wait_while_resetting(vhost);
starget_for_each_device(starget, &cancel_rc, ibmvfc_dev_cancel_all); starget_for_each_device(starget, &cancel_rc, ibmvfc_dev_cancel_all);
reset_rc = ibmvfc_reset_device(cmd->device, IBMVFC_TARGET_RESET, "target"); reset_rc = ibmvfc_reset_device(sdev, IBMVFC_TARGET_RESET, "target");
if (!cancel_rc && !reset_rc) { if (!cancel_rc && !reset_rc)
spin_lock_irqsave(vhost->host->host_lock, flags); rc = ibmvfc_wait_for_ops(vhost, starget, ibmvfc_match_target);
list_for_each_entry_safe(evt, pos, &vhost->sent, queue) {
if (evt->cmnd && scsi_target(evt->cmnd->device) == starget)
ibmvfc_fail_request(evt, DID_ABORT);
}
spin_unlock_irqrestore(vhost->host->host_lock, flags);
LEAVE;
return SUCCESS;
}
LEAVE; LEAVE;
return FAILED; return rc;
} }
/** /**
...@@ -2009,23 +2078,18 @@ static void ibmvfc_terminate_rport_io(struct fc_rport *rport) ...@@ -2009,23 +2078,18 @@ static void ibmvfc_terminate_rport_io(struct fc_rport *rport)
struct scsi_target *starget = to_scsi_target(&rport->dev); struct scsi_target *starget = to_scsi_target(&rport->dev);
struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
struct ibmvfc_host *vhost = shost_priv(shost); struct ibmvfc_host *vhost = shost_priv(shost);
struct ibmvfc_event *evt, *pos;
unsigned long cancel_rc = 0; unsigned long cancel_rc = 0;
unsigned long abort_rc = 0; unsigned long abort_rc = 0;
unsigned long flags; int rc = FAILED;
ENTER; ENTER;
starget_for_each_device(starget, &cancel_rc, ibmvfc_dev_cancel_all); starget_for_each_device(starget, &cancel_rc, ibmvfc_dev_cancel_all);
starget_for_each_device(starget, &abort_rc, ibmvfc_dev_abort_all); starget_for_each_device(starget, &abort_rc, ibmvfc_dev_abort_all);
if (!cancel_rc && !abort_rc) { if (!cancel_rc && !abort_rc)
spin_lock_irqsave(shost->host_lock, flags); rc = ibmvfc_wait_for_ops(vhost, starget, ibmvfc_match_target);
list_for_each_entry_safe(evt, pos, &vhost->sent, queue) {
if (evt->cmnd && scsi_target(evt->cmnd->device) == starget) if (rc == FAILED)
ibmvfc_fail_request(evt, DID_ABORT);
}
spin_unlock_irqrestore(shost->host_lock, flags);
} else
ibmvfc_issue_fc_host_lip(shost); ibmvfc_issue_fc_host_lip(shost);
LEAVE; LEAVE;
} }
...@@ -2258,6 +2322,28 @@ static int ibmvfc_slave_alloc(struct scsi_device *sdev) ...@@ -2258,6 +2322,28 @@ static int ibmvfc_slave_alloc(struct scsi_device *sdev)
return 0; return 0;
} }
/**
* ibmvfc_target_alloc - Setup the target's task set value
* @starget: struct scsi_target
*
* Set the target's task set value so that error handling works as
* expected.
*
* Returns:
* 0 on success / -ENXIO if device does not exist
**/
static int ibmvfc_target_alloc(struct scsi_target *starget)
{
struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
struct ibmvfc_host *vhost = shost_priv(shost);
unsigned long flags = 0;
spin_lock_irqsave(shost->host_lock, flags);
starget->hostdata = (void *)(unsigned long)vhost->task_set++;
spin_unlock_irqrestore(shost->host_lock, flags);
return 0;
}
/** /**
* ibmvfc_slave_configure - Configure the device * ibmvfc_slave_configure - Configure the device
* @sdev: struct scsi_device device to configure * @sdev: struct scsi_device device to configure
...@@ -2537,6 +2623,7 @@ static struct scsi_host_template driver_template = { ...@@ -2537,6 +2623,7 @@ static struct scsi_host_template driver_template = {
.eh_host_reset_handler = ibmvfc_eh_host_reset_handler, .eh_host_reset_handler = ibmvfc_eh_host_reset_handler,
.slave_alloc = ibmvfc_slave_alloc, .slave_alloc = ibmvfc_slave_alloc,
.slave_configure = ibmvfc_slave_configure, .slave_configure = ibmvfc_slave_configure,
.target_alloc = ibmvfc_target_alloc,
.scan_finished = ibmvfc_scan_finished, .scan_finished = ibmvfc_scan_finished,
.change_queue_depth = ibmvfc_change_queue_depth, .change_queue_depth = ibmvfc_change_queue_depth,
.change_queue_type = ibmvfc_change_queue_type, .change_queue_type = ibmvfc_change_queue_type,
......
...@@ -337,7 +337,6 @@ struct ibmvfc_tmf { ...@@ -337,7 +337,6 @@ struct ibmvfc_tmf {
#define IBMVFC_TMF_LUA_VALID 0x40 #define IBMVFC_TMF_LUA_VALID 0x40
u32 cancel_key; u32 cancel_key;
u32 my_cancel_key; u32 my_cancel_key;
#define IBMVFC_TMF_CANCEL_KEY 0x80000000
u32 pad; u32 pad;
u64 reserved[2]; u64 reserved[2];
}__attribute__((packed, aligned (8))); }__attribute__((packed, aligned (8)));
...@@ -606,6 +605,7 @@ struct ibmvfc_event { ...@@ -606,6 +605,7 @@ struct ibmvfc_event {
struct srp_direct_buf *ext_list; struct srp_direct_buf *ext_list;
dma_addr_t ext_list_token; dma_addr_t ext_list_token;
struct completion comp; struct completion comp;
struct completion *eh_comp;
struct timer_list timer; struct timer_list timer;
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment