[SCSI] lpfc 8.3.20: Updates to FC discovery commands
[linux-2.6.git] / drivers / scsi / lpfc / lpfc_nportdisc.c
index 6efe459..d85a742 100644 (file)
@@ -1,7 +1,7 @@
  /*******************************************************************
  * This file is part of the Emulex Linux Device Driver for         *
  * Fibre Channel Host Bus Adapters.                                *
- * Copyright (C) 2004-2008 Emulex.  All rights reserved.           *
+ * Copyright (C) 2004-2009 Emulex.  All rights reserved.           *
  * EMULEX and SLI are trademarks of Emulex.                        *
  * www.emulex.com                                                  *
  * Portions Copyright (C) 2004-2005 Christoph Hellwig              *
@@ -21,6 +21,7 @@
 
 #include <linux/blkdev.h>
 #include <linux/pci.h>
+#include <linux/slab.h>
 #include <linux/interrupt.h>
 
 #include <scsi/scsi.h>
@@ -62,7 +63,7 @@ lpfc_check_adisc(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp,
 
 int
 lpfc_check_sparm(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp,
-                struct serv_parm * sp, uint32_t class)
+                struct serv_parm *sp, uint32_t class, int flogi)
 {
        volatile struct serv_parm *hsp = &vport->fc_sparam;
        uint16_t hsp_value, ssp_value = 0;
@@ -75,49 +76,56 @@ lpfc_check_sparm(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp,
         * correcting the byte values.
         */
        if (sp->cls1.classValid) {
-               hsp_value = (hsp->cls1.rcvDataSizeMsb << 8) |
-                               hsp->cls1.rcvDataSizeLsb;
-               ssp_value = (sp->cls1.rcvDataSizeMsb << 8) |
-                               sp->cls1.rcvDataSizeLsb;
-               if (!ssp_value)
-                       goto bad_service_param;
-               if (ssp_value > hsp_value) {
-                       sp->cls1.rcvDataSizeLsb = hsp->cls1.rcvDataSizeLsb;
-                       sp->cls1.rcvDataSizeMsb = hsp->cls1.rcvDataSizeMsb;
+               if (!flogi) {
+                       hsp_value = ((hsp->cls1.rcvDataSizeMsb << 8) |
+                                    hsp->cls1.rcvDataSizeLsb);
+                       ssp_value = ((sp->cls1.rcvDataSizeMsb << 8) |
+                                    sp->cls1.rcvDataSizeLsb);
+                       if (!ssp_value)
+                               goto bad_service_param;
+                       if (ssp_value > hsp_value) {
+                               sp->cls1.rcvDataSizeLsb =
+                                       hsp->cls1.rcvDataSizeLsb;
+                               sp->cls1.rcvDataSizeMsb =
+                                       hsp->cls1.rcvDataSizeMsb;
+                       }
                }
-       } else if (class == CLASS1) {
+       } else if (class == CLASS1)
                goto bad_service_param;
-       }
-
        if (sp->cls2.classValid) {
-               hsp_value = (hsp->cls2.rcvDataSizeMsb << 8) |
-                               hsp->cls2.rcvDataSizeLsb;
-               ssp_value = (sp->cls2.rcvDataSizeMsb << 8) |
-                               sp->cls2.rcvDataSizeLsb;
-               if (!ssp_value)
-                       goto bad_service_param;
-               if (ssp_value > hsp_value) {
-                       sp->cls2.rcvDataSizeLsb = hsp->cls2.rcvDataSizeLsb;
-                       sp->cls2.rcvDataSizeMsb = hsp->cls2.rcvDataSizeMsb;
+               if (!flogi) {
+                       hsp_value = ((hsp->cls2.rcvDataSizeMsb << 8) |
+                                    hsp->cls2.rcvDataSizeLsb);
+                       ssp_value = ((sp->cls2.rcvDataSizeMsb << 8) |
+                                    sp->cls2.rcvDataSizeLsb);
+                       if (!ssp_value)
+                               goto bad_service_param;
+                       if (ssp_value > hsp_value) {
+                               sp->cls2.rcvDataSizeLsb =
+                                       hsp->cls2.rcvDataSizeLsb;
+                               sp->cls2.rcvDataSizeMsb =
+                                       hsp->cls2.rcvDataSizeMsb;
+                       }
                }
-       } else if (class == CLASS2) {
+       } else if (class == CLASS2)
                goto bad_service_param;
-       }
-
        if (sp->cls3.classValid) {
-               hsp_value = (hsp->cls3.rcvDataSizeMsb << 8) |
-                               hsp->cls3.rcvDataSizeLsb;
-               ssp_value = (sp->cls3.rcvDataSizeMsb << 8) |
-                               sp->cls3.rcvDataSizeLsb;
-               if (!ssp_value)
-                       goto bad_service_param;
-               if (ssp_value > hsp_value) {
-                       sp->cls3.rcvDataSizeLsb = hsp->cls3.rcvDataSizeLsb;
-                       sp->cls3.rcvDataSizeMsb = hsp->cls3.rcvDataSizeMsb;
+               if (!flogi) {
+                       hsp_value = ((hsp->cls3.rcvDataSizeMsb << 8) |
+                                    hsp->cls3.rcvDataSizeLsb);
+                       ssp_value = ((sp->cls3.rcvDataSizeMsb << 8) |
+                                    sp->cls3.rcvDataSizeLsb);
+                       if (!ssp_value)
+                               goto bad_service_param;
+                       if (ssp_value > hsp_value) {
+                               sp->cls3.rcvDataSizeLsb =
+                                       hsp->cls3.rcvDataSizeLsb;
+                               sp->cls3.rcvDataSizeMsb =
+                                       hsp->cls3.rcvDataSizeMsb;
+                       }
                }
-       } else if (class == CLASS3) {
+       } else if (class == CLASS3)
                goto bad_service_param;
-       }
 
        /*
         * Preserve the upper four bits of the MSB from the PLOGI response.
@@ -182,6 +190,7 @@ lpfc_check_elscmpl_iocb(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb,
 }
 
 
+
 /*
  * Free resources / clean up outstanding I/Os
  * associated with a LPFC_NODELIST entry. This
@@ -191,13 +200,15 @@ int
 lpfc_els_abort(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp)
 {
        LIST_HEAD(completions);
+       LIST_HEAD(txcmplq_completions);
+       LIST_HEAD(abort_list);
        struct lpfc_sli  *psli = &phba->sli;
        struct lpfc_sli_ring *pring = &psli->ring[LPFC_ELS_RING];
        struct lpfc_iocbq *iocb, *next_iocb;
 
        /* Abort outstanding I/O on NPort <nlp_DID> */
        lpfc_printf_vlog(ndlp->vport, KERN_INFO, LOG_DISCOVERY,
-                        "0205 Abort outstanding I/O on NPort x%x "
+                        "2819 Abort outstanding I/O on NPort x%x "
                         "Data: x%x x%x x%x\n",
                         ndlp->nlp_DID, ndlp->nlp_flag, ndlp->nlp_state,
                         ndlp->nlp_rpi);
@@ -216,14 +227,25 @@ lpfc_els_abort(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp)
        }
 
        /* Next check the txcmplq */
-       list_for_each_entry_safe(iocb, next_iocb, &pring->txcmplq, list) {
+       list_splice_init(&pring->txcmplq, &txcmplq_completions);
+       spin_unlock_irq(&phba->hbalock);
+
+       list_for_each_entry_safe(iocb, next_iocb, &txcmplq_completions, list) {
                /* Check to see if iocb matches the nport we are looking for */
-               if (lpfc_check_sli_ndlp(phba, pring, iocb, ndlp)) {
-                       lpfc_sli_issue_abort_iotag(phba, pring, iocb);
-               }
+               if (lpfc_check_sli_ndlp(phba, pring, iocb, ndlp))
+                       list_add_tail(&iocb->dlist, &abort_list);
        }
+       spin_lock_irq(&phba->hbalock);
+       list_splice(&txcmplq_completions, &pring->txcmplq);
        spin_unlock_irq(&phba->hbalock);
 
+       list_for_each_entry_safe(iocb, next_iocb, &abort_list, dlist) {
+                       spin_lock_irq(&phba->hbalock);
+                       list_del_init(&iocb->dlist);
+                       lpfc_sli_issue_abort_iotag(phba, pring, iocb);
+                       spin_unlock_irq(&phba->hbalock);
+       }
+
        /* Cancel all the IOCBs from the completions list */
        lpfc_sli_cancel_iocbs(phba, &completions, IOSTAT_LOCAL_REJECT,
                              IOERR_SLI_ABORTED);
@@ -247,7 +269,7 @@ lpfc_rcv_plogi(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp,
        int rc;
 
        memset(&stat, 0, sizeof (struct ls_rjt));
-       if (vport->port_state <= LPFC_FLOGI) {
+       if (vport->port_state <= LPFC_FDISC) {
                /* Before responding to PLOGI, check for pt2pt mode.
                 * If we are pt2pt, with an outstanding FLOGI, abort
                 * the FLOGI and resend it first.
@@ -295,7 +317,7 @@ lpfc_rcv_plogi(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp,
                        NULL);
                return 0;
        }
-       if ((lpfc_check_sparm(vport, ndlp, sp, CLASS3) == 0)) {
+       if ((lpfc_check_sparm(vport, ndlp, sp, CLASS3, 0) == 0)) {
                /* Reject this request because invalid parameters */
                stat.un.b.lsRjtRsnCode = LSRJT_UNABLE_TPC;
                stat.un.b.lsRjtRsnCodeExp = LSEXP_SPARM_OPTIONS;
@@ -363,8 +385,8 @@ lpfc_rcv_plogi(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp,
        if (!mbox)
                goto out;
 
-       rc = lpfc_reg_login(phba, vport->vpi, icmd->un.rcvels.remoteID,
-                           (uint8_t *) sp, mbox, 0);
+       rc = lpfc_reg_rpi(phba, vport->vpi, icmd->un.rcvels.remoteID,
+                           (uint8_t *) sp, mbox, ndlp->nlp_rpi);
        if (rc) {
                mempool_free(mbox, phba->mbox_mem_pool);
                goto out;
@@ -485,6 +507,9 @@ lpfc_rcv_logo(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp,
              struct lpfc_iocbq *cmdiocb, uint32_t els_cmd)
 {
        struct Scsi_Host *shost = lpfc_shost_from_vport(vport);
+       struct lpfc_hba    *phba = vport->phba;
+       struct lpfc_vport **vports;
+       int i, active_vlink_present = 0 ;
 
        /* Put ndlp in NPR state with 1 sec timeout for plogi, ACC logo */
        /* Only call LOGO ACC for first LOGO, this avoids sending unnecessary
@@ -497,11 +522,48 @@ lpfc_rcv_logo(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp,
                lpfc_els_rsp_acc(vport, ELS_CMD_PRLO, cmdiocb, ndlp, NULL);
        else
                lpfc_els_rsp_acc(vport, ELS_CMD_ACC, cmdiocb, ndlp, NULL);
+       if (ndlp->nlp_DID == Fabric_DID) {
+               if (vport->port_state <= LPFC_FDISC)
+                       goto out;
+               lpfc_linkdown_port(vport);
+               spin_lock_irq(shost->host_lock);
+               vport->fc_flag |= FC_VPORT_LOGO_RCVD;
+               spin_unlock_irq(shost->host_lock);
+               vports = lpfc_create_vport_work_array(phba);
+               if (vports) {
+                       for (i = 0; i <= phba->max_vports && vports[i] != NULL;
+                                       i++) {
+                               if ((!(vports[i]->fc_flag &
+                                       FC_VPORT_LOGO_RCVD)) &&
+                                       (vports[i]->port_state > LPFC_FDISC)) {
+                                       active_vlink_present = 1;
+                                       break;
+                               }
+                       }
+                       lpfc_destroy_vport_work_array(phba, vports);
+               }
 
-       if ((!(ndlp->nlp_type & NLP_FABRIC) &&
-            ((ndlp->nlp_type & NLP_FCP_TARGET) ||
-             !(ndlp->nlp_type & NLP_FCP_INITIATOR))) ||
-           (ndlp->nlp_state == NLP_STE_ADISC_ISSUE)) {
+               if (active_vlink_present) {
+                       /*
+                        * If there are other active VLinks present,
+                        * re-instantiate the Vlink using FDISC.
+                        */
+                       mod_timer(&ndlp->nlp_delayfunc, jiffies + HZ);
+                       spin_lock_irq(shost->host_lock);
+                       ndlp->nlp_flag |= NLP_DELAY_TMO;
+                       spin_unlock_irq(shost->host_lock);
+                       ndlp->nlp_last_elscmd = ELS_CMD_FDISC;
+                       vport->port_state = LPFC_FDISC;
+               } else {
+                       spin_lock_irq(shost->host_lock);
+                       phba->pport->fc_flag &= ~FC_LOGO_RCVD_DID_CHNG;
+                       spin_unlock_irq(shost->host_lock);
+                       lpfc_retry_pport_discovery(phba);
+               }
+       } else if ((!(ndlp->nlp_type & NLP_FABRIC) &&
+               ((ndlp->nlp_type & NLP_FCP_TARGET) ||
+               !(ndlp->nlp_type & NLP_FCP_INITIATOR))) ||
+               (ndlp->nlp_state == NLP_STE_ADISC_ISSUE)) {
                /* Only try to re-login if this is NOT a Fabric Node */
                mod_timer(&ndlp->nlp_delayfunc, jiffies + HZ * 1);
                spin_lock_irq(shost->host_lock);
@@ -510,6 +572,7 @@ lpfc_rcv_logo(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp,
 
                ndlp->nlp_last_elscmd = ELS_CMD_PLOGI;
        }
+out:
        ndlp->nlp_prev_state = ndlp->nlp_state;
        lpfc_nlp_set_state(vport, ndlp, NLP_STE_NPR_NODE);
 
@@ -569,7 +632,7 @@ lpfc_disc_set_adisc(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp)
 {
        struct Scsi_Host *shost = lpfc_shost_from_vport(vport);
 
-       if (!ndlp->nlp_rpi) {
+       if (!(ndlp->nlp_flag & NLP_RPI_REGISTERED)) {
                ndlp->nlp_flag &= ~NLP_NPR_ADISC;
                return 0;
        }
@@ -577,7 +640,8 @@ lpfc_disc_set_adisc(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp)
        if (!(vport->fc_flag & FC_PT2PT)) {
                /* Check config parameter use-adisc or FCP-2 */
                if ((vport->cfg_use_adisc && (vport->fc_flag & FC_RSCN_MODE)) ||
-                   ndlp->nlp_fcp_info & NLP_FCP_2_DEVICE) {
+                   ((ndlp->nlp_fcp_info & NLP_FCP_2_DEVICE) &&
+                    (ndlp->nlp_type & NLP_FCP_TARGET))) {
                        spin_lock_irq(shost->host_lock);
                        ndlp->nlp_flag |= NLP_NPR_ADISC;
                        spin_unlock_irq(shost->host_lock);
@@ -588,11 +652,55 @@ lpfc_disc_set_adisc(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp)
        lpfc_unreg_rpi(vport, ndlp);
        return 0;
 }
+/**
+ * lpfc_release_rpi - Release a RPI by issueing unreg_login mailbox cmd.
+ * @phba : Pointer to lpfc_hba structure.
+ * @vport: Pointer to lpfc_vport structure.
+ * @rpi  : rpi to be release.
+ *
+ * This function will send a unreg_login mailbox command to the firmware
+ * to release a rpi.
+ **/
+void
+lpfc_release_rpi(struct lpfc_hba *phba,
+               struct lpfc_vport *vport,
+               uint16_t rpi)
+{
+       LPFC_MBOXQ_t *pmb;
+       int rc;
+
+       pmb = (LPFC_MBOXQ_t *) mempool_alloc(phba->mbox_mem_pool,
+                       GFP_KERNEL);
+       if (!pmb)
+               lpfc_printf_vlog(vport, KERN_ERR, LOG_MBOX,
+                       "2796 mailbox memory allocation failed \n");
+       else {
+               lpfc_unreg_login(phba, vport->vpi, rpi, pmb);
+               pmb->mbox_cmpl = lpfc_sli_def_mbox_cmpl;
+               rc = lpfc_sli_issue_mbox(phba, pmb, MBX_NOWAIT);
+               if (rc == MBX_NOT_FINISHED)
+                       mempool_free(pmb, phba->mbox_mem_pool);
+       }
+}
 
 static uint32_t
 lpfc_disc_illegal(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp,
                  void *arg, uint32_t evt)
 {
+       struct lpfc_hba *phba;
+       LPFC_MBOXQ_t *pmb = (LPFC_MBOXQ_t *) arg;
+       MAILBOX_t *mb;
+       uint16_t rpi;
+
+       phba = vport->phba;
+       /* Release the RPI if reglogin completing */
+       if (!(phba->pport->load_flag & FC_UNLOADING) &&
+               (evt == NLP_EVT_CMPL_REG_LOGIN) &&
+               (!pmb->u.mb.mbxStatus)) {
+               mb = &pmb->u.mb;
+               rpi = pmb->u.mb.un.varWords[0];
+               lpfc_release_rpi(phba, vport, rpi);
+       }
        lpfc_printf_vlog(vport, KERN_ERR, LOG_DISCOVERY,
                         "0271 Illegal State Transition: node x%x "
                         "event x%x, state x%x Data: x%x x%x\n",
@@ -823,7 +931,7 @@ lpfc_cmpl_plogi_plogi_issue(struct lpfc_vport *vport,
                                 "0142 PLOGI RSP: Invalid WWN.\n");
                goto out;
        }
-       if (!lpfc_check_sparm(vport, ndlp, sp, CLASS3))
+       if (!lpfc_check_sparm(vport, ndlp, sp, CLASS3, 0))
                goto out;
        /* PLOGI chkparm OK */
        lpfc_printf_vlog(vport, KERN_INFO, LOG_ELS,
@@ -859,8 +967,8 @@ lpfc_cmpl_plogi_plogi_issue(struct lpfc_vport *vport,
 
        lpfc_unreg_rpi(vport, ndlp);
 
-       if (lpfc_reg_login(phba, vport->vpi, irsp->un.elsreq64.remoteID,
-                          (uint8_t *) sp, mbox, 0) == 0) {
+       if (lpfc_reg_rpi(phba, vport->vpi, irsp->un.elsreq64.remoteID,
+                        (uint8_t *) sp, mbox, ndlp->nlp_rpi) == 0) {
                switch (ndlp->nlp_DID) {
                case NameServer_DID:
                        mbox->mbox_cmpl = lpfc_mbx_cmpl_ns_reg_login;
@@ -869,6 +977,7 @@ lpfc_cmpl_plogi_plogi_issue(struct lpfc_vport *vport,
                        mbox->mbox_cmpl = lpfc_mbx_cmpl_fdmi_reg_login;
                        break;
                default:
+                       ndlp->nlp_flag |= NLP_REG_LOGIN_SEND;
                        mbox->mbox_cmpl = lpfc_mbx_cmpl_reg_login;
                }
                mbox->context2 = lpfc_nlp_get(ndlp);
@@ -879,6 +988,8 @@ lpfc_cmpl_plogi_plogi_issue(struct lpfc_vport *vport,
                                           NLP_STE_REG_LOGIN_ISSUE);
                        return ndlp->nlp_state;
                }
+               if (ndlp->nlp_flag & NLP_REG_LOGIN_SEND)
+                       ndlp->nlp_flag &= ~NLP_REG_LOGIN_SEND;
                /* decrement node reference count to the failed mbox
                 * command
                 */
@@ -928,6 +1039,18 @@ static uint32_t
 lpfc_cmpl_reglogin_plogi_issue(struct lpfc_vport *vport,
        struct lpfc_nodelist *ndlp, void *arg, uint32_t evt)
 {
+       struct lpfc_hba *phba;
+       LPFC_MBOXQ_t *pmb = (LPFC_MBOXQ_t *) arg;
+       MAILBOX_t *mb = &pmb->u.mb;
+       uint16_t rpi;
+
+       phba = vport->phba;
+       /* Release the RPI */
+       if (!(phba->pport->load_flag & FC_UNLOADING) &&
+               !mb->mbxStatus) {
+               rpi = pmb->u.mb.un.varWords[0];
+               lpfc_release_rpi(phba, vport, rpi);
+       }
        return ndlp->nlp_state;
 }
 
@@ -1070,6 +1193,7 @@ lpfc_cmpl_adisc_adisc_issue(struct lpfc_vport *vport,
        struct lpfc_iocbq *cmdiocb, *rspiocb;
        IOCB_t *irsp;
        ADISC *ap;
+       int rc;
 
        cmdiocb = (struct lpfc_iocbq *) arg;
        rspiocb = cmdiocb->context_un.rsp_iocb;
@@ -1095,6 +1219,15 @@ lpfc_cmpl_adisc_adisc_issue(struct lpfc_vport *vport,
                return ndlp->nlp_state;
        }
 
+       if (phba->sli_rev == LPFC_SLI_REV4) {
+               rc = lpfc_sli4_resume_rpi(ndlp);
+               if (rc) {
+                       /* Stay in state and retry. */
+                       ndlp->nlp_prev_state = NLP_STE_ADISC_ISSUE;
+                       return ndlp->nlp_state;
+               }
+       }
+
        if (ndlp->nlp_type & NLP_FCP_TARGET) {
                ndlp->nlp_prev_state = NLP_STE_ADISC_ISSUE;
                lpfc_nlp_set_state(vport, ndlp, NLP_STE_MAPPED_NODE);
@@ -1102,6 +1235,7 @@ lpfc_cmpl_adisc_adisc_issue(struct lpfc_vport *vport,
                ndlp->nlp_prev_state = NLP_STE_ADISC_ISSUE;
                lpfc_nlp_set_state(vport, ndlp, NLP_STE_UNMAPPED_NODE);
        }
+
        return ndlp->nlp_state;
 }
 
@@ -1211,6 +1345,7 @@ lpfc_rcv_logo_reglogin_issue(struct lpfc_vport *vport,
                        }
                        lpfc_nlp_put(ndlp);
                        list_del(&mb->list);
+                       phba->sli.mboxq_cnt--;
                        mempool_free(mb, phba->mbox_mem_pool);
                }
        }
@@ -1285,6 +1420,7 @@ lpfc_cmpl_reglogin_reglogin_issue(struct lpfc_vport *vport,
        }
 
        ndlp->nlp_rpi = mb->un.varWords[0];
+       ndlp->nlp_flag |= NLP_RPI_REGISTERED;
 
        /* Only if we are not a fabric nport do we issue PRLI */
        if (!(ndlp->nlp_type & NLP_FABRIC)) {
@@ -1334,6 +1470,7 @@ lpfc_device_recov_reglogin_issue(struct lpfc_vport *vport,
        ndlp->nlp_prev_state = NLP_STE_REG_LOGIN_ISSUE;
        lpfc_nlp_set_state(vport, ndlp, NLP_STE_NPR_NODE);
        spin_lock_irq(shost->host_lock);
+       ndlp->nlp_flag |= NLP_IGNR_REG_CMPL;
        ndlp->nlp_flag &= ~(NLP_NODEV_REMOVE | NLP_NPR_2B_DISC);
        spin_unlock_irq(shost->host_lock);
        lpfc_disc_set_adisc(vport, ndlp);
@@ -1884,7 +2021,7 @@ lpfc_cmpl_reglogin_npr_node(struct lpfc_vport *vport,
 
        if (!mb->mbxStatus) {
                ndlp->nlp_rpi = mb->un.varWords[0];
-               ndlp->nlp_flag |= NLP_RPI_VALID;
+               ndlp->nlp_flag |= NLP_RPI_REGISTERED;
        } else {
                if (ndlp->nlp_flag & NLP_NODEV_REMOVE) {
                        lpfc_drop_node(vport, ndlp);