diff options
Diffstat (limited to 'drivers/scsi/ibmvscsi/ibmvfc.c')
-rw-r--r-- | drivers/scsi/ibmvscsi/ibmvfc.c | 98 |
1 files changed, 97 insertions, 1 deletions
diff --git a/drivers/scsi/ibmvscsi/ibmvfc.c b/drivers/scsi/ibmvscsi/ibmvfc.c index 0ac2dedb413..ea4abee7a2a 100644 --- a/drivers/scsi/ibmvscsi/ibmvfc.c +++ b/drivers/scsi/ibmvscsi/ibmvfc.c @@ -3123,6 +3123,7 @@ static void ibmvfc_tgt_adisc_done(struct ibmvfc_event *evt) vhost->discovery_threads--; ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_NONE); + del_timer(&tgt->timer); switch (status) { case IBMVFC_MAD_SUCCESS: @@ -3179,9 +3180,89 @@ static void ibmvfc_init_passthru(struct ibmvfc_event *evt) } /** + * ibmvfc_tgt_adisc_cancel_done - Completion handler when cancelling an ADISC + * @evt: ibmvfc event struct + * + * Just cleanup this event struct. Everything else is handled by + * the ADISC completion handler. If the ADISC never actually comes + * back, we still have the timer running on the ADISC event struct + * which will fire and cause the CRQ to get reset. + * + **/ +static void ibmvfc_tgt_adisc_cancel_done(struct ibmvfc_event *evt) +{ + struct ibmvfc_host *vhost = evt->vhost; + struct ibmvfc_target *tgt = evt->tgt; + + tgt_dbg(tgt, "ADISC cancel complete\n"); + vhost->abort_threads--; + ibmvfc_free_event(evt); + kref_put(&tgt->kref, ibmvfc_release_tgt); + wake_up(&vhost->work_wait_q); +} + +/** + * ibmvfc_adisc_timeout - Handle an ADISC timeout + * @tgt: ibmvfc target struct + * + * If an ADISC times out, send a cancel. If the cancel times + * out, reset the CRQ. When the ADISC comes back as cancelled, + * log back into the target. + **/ +static void ibmvfc_adisc_timeout(struct ibmvfc_target *tgt) +{ + struct ibmvfc_host *vhost = tgt->vhost; + struct ibmvfc_event *evt; + struct ibmvfc_tmf *tmf; + unsigned long flags; + int rc; + + tgt_dbg(tgt, "ADISC timeout\n"); + spin_lock_irqsave(vhost->host->host_lock, flags); + if (vhost->abort_threads >= disc_threads || + tgt->action != IBMVFC_TGT_ACTION_INIT_WAIT || + vhost->state != IBMVFC_INITIALIZING || + vhost->action != IBMVFC_HOST_ACTION_QUERY_TGTS) { + spin_unlock_irqrestore(vhost->host->host_lock, flags); + return; + } + + vhost->abort_threads++; + kref_get(&tgt->kref); + evt = ibmvfc_get_event(vhost); + ibmvfc_init_event(evt, ibmvfc_tgt_adisc_cancel_done, IBMVFC_MAD_FORMAT); + + evt->tgt = tgt; + tmf = &evt->iu.tmf; + memset(tmf, 0, sizeof(*tmf)); + tmf->common.version = 1; + tmf->common.opcode = IBMVFC_TMF_MAD; + tmf->common.length = sizeof(*tmf); + tmf->scsi_id = tgt->scsi_id; + tmf->cancel_key = tgt->cancel_key; + + rc = ibmvfc_send_event(evt, vhost, default_timeout); + + if (rc) { + tgt_err(tgt, "Failed to send cancel event for ADISC. rc=%d\n", rc); + vhost->abort_threads--; + kref_put(&tgt->kref, ibmvfc_release_tgt); + __ibmvfc_reset_host(vhost); + } else + tgt_dbg(tgt, "Attempting to cancel ADISC\n"); + spin_unlock_irqrestore(vhost->host->host_lock, flags); +} + +/** * ibmvfc_tgt_adisc - Initiate an ADISC for specified target * @tgt: ibmvfc target struct * + * When sending an ADISC we end up with two timers running. The + * first timer is the timer in the ibmvfc target struct. If this + * fires, we send a cancel to the target. The second timer is the + * timer on the ibmvfc event for the ADISC, which is longer. If that + * fires, it means the ADISC timed out and our attempt to cancel it + * also failed, so we need to reset the CRQ. **/ static void ibmvfc_tgt_adisc(struct ibmvfc_target *tgt) { @@ -3202,6 +3283,7 @@ static void ibmvfc_tgt_adisc(struct ibmvfc_target *tgt) mad = &evt->iu.passthru; mad->iu.flags = IBMVFC_FC_ELS; mad->iu.scsi_id = tgt->scsi_id; + mad->iu.cancel_key = tgt->cancel_key; mad->fc_iu.payload[0] = IBMVFC_ADISC; memcpy(&mad->fc_iu.payload[2], &vhost->login_buf->resp.port_name, @@ -3210,9 +3292,19 @@ static void ibmvfc_tgt_adisc(struct ibmvfc_target *tgt) sizeof(vhost->login_buf->resp.node_name)); mad->fc_iu.payload[6] = vhost->login_buf->resp.scsi_id & 0x00ffffff; + if (timer_pending(&tgt->timer)) + mod_timer(&tgt->timer, jiffies + (IBMVFC_ADISC_TIMEOUT * HZ)); + else { + tgt->timer.data = (unsigned long) tgt; + tgt->timer.expires = jiffies + (IBMVFC_ADISC_TIMEOUT * HZ); + tgt->timer.function = (void (*)(unsigned long))ibmvfc_adisc_timeout; + add_timer(&tgt->timer); + } + ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_INIT_WAIT); - if (ibmvfc_send_event(evt, vhost, default_timeout)) { + if (ibmvfc_send_event(evt, vhost, IBMVFC_ADISC_PLUS_CANCEL_TIMEOUT)) { vhost->discovery_threads--; + del_timer(&tgt->timer); ibmvfc_set_tgt_action(tgt, IBMVFC_TGT_ACTION_NONE); kref_put(&tgt->kref, ibmvfc_release_tgt); } else @@ -3340,6 +3432,8 @@ static int ibmvfc_alloc_target(struct ibmvfc_host *vhost, u64 scsi_id) tgt->new_scsi_id = scsi_id; tgt->vhost = vhost; tgt->need_login = 1; + tgt->cancel_key = vhost->task_set++; + init_timer(&tgt->timer); kref_init(&tgt->kref); ibmvfc_init_tgt(tgt, ibmvfc_tgt_implicit_logout); spin_lock_irqsave(vhost->host->host_lock, flags); @@ -3734,6 +3828,7 @@ static void ibmvfc_do_work(struct ibmvfc_host *vhost) spin_unlock_irqrestore(vhost->host->host_lock, flags); if (rport) fc_remote_port_delete(rport); + del_timer_sync(&tgt->timer); kref_put(&tgt->kref, ibmvfc_release_tgt); return; } @@ -4061,6 +4156,7 @@ static int ibmvfc_probe(struct vio_dev *vdev, const struct vio_device_id *id) vhost->dev = dev; vhost->partition_number = -1; vhost->log_level = log_level; + vhost->task_set = 1; strcpy(vhost->partition_name, "UNKNOWN"); init_waitqueue_head(&vhost->work_wait_q); init_waitqueue_head(&vhost->init_wait_q); |