]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/scsi/lpfc/lpfc_init.c
[SCSI] lpfc 8.3.0 : Add active interrupt test for enabling MSI/MSI-X/INTx
[linux-2.6-omap-h63xx.git] / drivers / scsi / lpfc / lpfc_init.c
index 64e3d344f4dda5047ee1918ef8944e2c51632990..e07f12a0871b442ad0b55e51535dcd9a80960368 100644 (file)
@@ -742,11 +742,6 @@ lpfc_hb_timeout_handler(struct lpfc_hba *phba)
                return;
 
        spin_lock_irq(&phba->pport->work_port_lock);
-       /* If the timer is already canceled do nothing */
-       if (!(phba->pport->work_port_events & WORKER_HB_TMO)) {
-               spin_unlock_irq(&phba->pport->work_port_lock);
-               return;
-       }
 
        if (time_after(phba->last_completion_time + LPFC_HB_MBOX_INTERVAL * HZ,
                jiffies)) {
@@ -2279,8 +2274,7 @@ lpfc_enable_msix(struct lpfc_hba *phba)
                                ARRAY_SIZE(phba->msix_entries));
        if (rc) {
                lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
-                               "0420 Enable MSI-X failed (%d), continuing "
-                               "with MSI\n", rc);
+                               "0420 PCI enable MSI-X failed (%d)\n", rc);
                goto msi_fail_out;
        } else
                for (i = 0; i < LPFC_MSIX_VECTORS; i++)
@@ -2297,9 +2291,9 @@ lpfc_enable_msix(struct lpfc_hba *phba)
        rc = request_irq(phba->msix_entries[0].vector, &lpfc_sp_intr_handler,
                         IRQF_SHARED, LPFC_SP_DRIVER_HANDLER_NAME, phba);
        if (rc) {
-               lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
+               lpfc_printf_log(phba, KERN_WARNING, LOG_INIT,
                                "0421 MSI-X slow-path request_irq failed "
-                               "(%d), continuing with MSI\n", rc);
+                               "(%d)\n", rc);
                goto msi_fail_out;
        }
 
@@ -2308,9 +2302,9 @@ lpfc_enable_msix(struct lpfc_hba *phba)
                         IRQF_SHARED, LPFC_FP_DRIVER_HANDLER_NAME, phba);
 
        if (rc) {
-               lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
+               lpfc_printf_log(phba, KERN_WARNING, LOG_INIT,
                                "0429 MSI-X fast-path request_irq failed "
-                               "(%d), continuing with MSI\n", rc);
+                               "(%d)\n", rc);
                goto irq_fail_out;
        }
 
@@ -2331,7 +2325,7 @@ lpfc_enable_msix(struct lpfc_hba *phba)
                goto mbx_fail_out;
        rc = lpfc_sli_issue_mbox(phba, pmb, MBX_POLL);
        if (rc != MBX_SUCCESS) {
-               lpfc_printf_log(phba, KERN_ERR, LOG_MBOX,
+               lpfc_printf_log(phba, KERN_WARNING, LOG_MBOX,
                                "0351 Config MSI mailbox command failed, "
                                "mbxCmd x%x, mbxStatus x%x\n",
                                pmb->mb.mbxCommand, pmb->mb.mbxStatus);
@@ -2379,6 +2373,195 @@ lpfc_disable_msix(struct lpfc_hba *phba)
        pci_disable_msix(phba->pcidev);
 }
 
+/**
+ * lpfc_enable_msi: Enable MSI interrupt mode.
+ * @phba: pointer to lpfc hba data structure.
+ *
+ * This routine is invoked to enable the MSI interrupt mode. The kernel
+ * function pci_enable_msi() is called to enable the MSI vector. The
+ * device driver is responsible for calling the request_irq() to register
+ * MSI vector with a interrupt the handler, which is done in this function.
+ *
+ * Return codes
+ *     0 - sucessful
+ *     other values - error
+ */
+static int
+lpfc_enable_msi(struct lpfc_hba *phba)
+{
+       int rc;
+
+       rc = pci_enable_msi(phba->pcidev);
+       if (!rc)
+               lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
+                               "0462 PCI enable MSI mode success.\n");
+       else {
+               lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
+                               "0471 PCI enable MSI mode failed (%d)\n", rc);
+               return rc;
+       }
+
+       rc = request_irq(phba->pcidev->irq, lpfc_intr_handler,
+                        IRQF_SHARED, LPFC_DRIVER_NAME, phba);
+       if (rc) {
+               pci_disable_msi(phba->pcidev);
+               lpfc_printf_log(phba, KERN_WARNING, LOG_INIT,
+                               "0478 MSI request_irq failed (%d)\n", rc);
+       }
+       return rc;
+}
+
+/**
+ * lpfc_disable_msi: Disable MSI interrupt mode.
+ * @phba: pointer to lpfc hba data structure.
+ *
+ * This routine is invoked to disable the MSI interrupt mode. The driver
+ * calls free_irq() on MSI vector it has done request_irq() on before
+ * calling pci_disable_msi(). Failure to do so results in a BUG_ON() and
+ * a device will be left with MSI enabled and leaks its vector.
+ */
+
+static void
+lpfc_disable_msi(struct lpfc_hba *phba)
+{
+       free_irq(phba->pcidev->irq, phba);
+       pci_disable_msi(phba->pcidev);
+       return;
+}
+
+/**
+ * lpfc_log_intr_mode: Log the active interrupt mode
+ * @phba: pointer to lpfc hba data structure.
+ * @intr_mode: active interrupt mode adopted.
+ *
+ * This routine it invoked to log the currently used active interrupt mode
+ * to the device.
+ */
+static void
+lpfc_log_intr_mode(struct lpfc_hba *phba, uint32_t intr_mode)
+{
+       switch (intr_mode) {
+       case 0:
+               lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
+                               "0470 Enable INTx interrupt mode.\n");
+               break;
+       case 1:
+               lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
+                               "0481 Enabled MSI interrupt mode.\n");
+               break;
+       case 2:
+               lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
+                               "0480 Enabled MSI-X interrupt mode.\n");
+               break;
+       default:
+               lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
+                               "0482 Illegal interrupt mode.\n");
+               break;
+       }
+       return;
+}
+
+static void
+lpfc_stop_port(struct lpfc_hba *phba)
+{
+       /* Clear all interrupt enable conditions */
+       writel(0, phba->HCregaddr);
+       readl(phba->HCregaddr); /* flush */
+       /* Clear all pending interrupts */
+       writel(0xffffffff, phba->HAregaddr);
+       readl(phba->HAregaddr); /* flush */
+
+       /* Reset some HBA SLI setup states */
+       lpfc_stop_phba_timers(phba);
+       phba->pport->work_port_events = 0;
+
+       return;
+}
+
+/**
+ * lpfc_enable_intr: Enable device interrupt.
+ * @phba: pointer to lpfc hba data structure.
+ *
+ * This routine is invoked to enable device interrupt and associate driver's
+ * interrupt handler(s) to interrupt vector(s). Depends on the interrupt
+ * mode configured to the driver, the driver will try to fallback from the
+ * configured interrupt mode to an interrupt mode which is supported by the
+ * platform, kernel, and device in the order of: MSI-X -> MSI -> IRQ.
+ *
+ * Return codes
+ *   0 - sucessful
+ *   other values - error
+ **/
+static uint32_t
+lpfc_enable_intr(struct lpfc_hba *phba, uint32_t cfg_mode)
+{
+       uint32_t intr_mode = LPFC_INTR_ERROR;
+       int retval;
+
+       if (cfg_mode == 2) {
+               /* Need to issue conf_port mbox cmd before conf_msi mbox cmd */
+               retval = lpfc_sli_config_port(phba, 3);
+               if (!retval) {
+                       /* Now, try to enable MSI-X interrupt mode */
+                       retval = lpfc_enable_msix(phba);
+                       if (!retval) {
+                               /* Indicate initialization to MSI-X mode */
+                               phba->intr_type = MSIX;
+                               intr_mode = 2;
+                       }
+               }
+       }
+
+       /* Fallback to MSI if MSI-X initialization failed */
+       if (cfg_mode >= 1 && phba->intr_type == NONE) {
+               retval = lpfc_enable_msi(phba);
+               if (!retval) {
+                       /* Indicate initialization to MSI mode */
+                       phba->intr_type = MSI;
+                       intr_mode = 1;
+               }
+       }
+
+       /* Fallback to INTx if both MSI-X/MSI initalization failed */
+       if (phba->intr_type == NONE) {
+               retval = request_irq(phba->pcidev->irq, lpfc_intr_handler,
+                                    IRQF_SHARED, LPFC_DRIVER_NAME, phba);
+               if (!retval) {
+                       /* Indicate initialization to INTx mode */
+                       phba->intr_type = INTx;
+                       intr_mode = 0;
+               }
+       }
+       return intr_mode;
+}
+
+/**
+ * lpfc_disable_intr: Disable device interrupt.
+ * @phba: pointer to lpfc hba data structure.
+ *
+ * This routine is invoked to disable device interrupt and disassociate the
+ * driver's interrupt handler(s) from interrupt vector(s). Depending on the
+ * interrupt mode, the driver will release the interrupt vector(s) for the
+ * message signaled interrupt.
+ **/
+static void
+lpfc_disable_intr(struct lpfc_hba *phba)
+{
+       /* Disable the currently initialized interrupt mode */
+       if (phba->intr_type == MSIX)
+               lpfc_disable_msix(phba);
+       else if (phba->intr_type == MSI)
+               lpfc_disable_msi(phba);
+       else if (phba->intr_type == INTx)
+               free_irq(phba->pcidev->irq, phba);
+
+       /* Reset interrupt management states */
+       phba->intr_type = NONE;
+       phba->sli.slistat.sli_intr = 0;
+
+       return;
+}
+
 /**
  * lpfc_pci_probe_one: lpfc PCI probe func to register device to PCI subsystem.
  * @pdev: pointer to PCI device
@@ -2409,6 +2592,7 @@ lpfc_pci_probe_one(struct pci_dev *pdev, const struct pci_device_id *pid)
        int error = -ENODEV, retval;
        int  i, hbq_count;
        uint16_t iotag;
+       uint32_t cfg_mode, intr_mode;
        int bars = pci_select_bars(pdev, IORESOURCE_MEM);
        struct lpfc_adapter_event_header adapter_event;
 
@@ -2611,7 +2795,6 @@ lpfc_pci_probe_one(struct pci_dev *pdev, const struct pci_device_id *pid)
        lpfc_debugfs_initialize(vport);
 
        pci_set_drvdata(pdev, shost);
-       phba->intr_type = NONE;
 
        phba->MBslimaddr = phba->slim_memmap_p;
        phba->HAregaddr = phba->ctrl_regs_memmap_p + HA_REG_OFFSET;
@@ -2619,63 +2802,58 @@ lpfc_pci_probe_one(struct pci_dev *pdev, const struct pci_device_id *pid)
        phba->HSregaddr = phba->ctrl_regs_memmap_p + HS_REG_OFFSET;
        phba->HCregaddr = phba->ctrl_regs_memmap_p + HC_REG_OFFSET;
 
-       /* Configure and enable interrupt */
-       if (phba->cfg_use_msi == 2) {
-               /* Need to issue conf_port mbox cmd before conf_msi mbox cmd */
-               error = lpfc_sli_config_port(phba, 3);
-               if (error)
-                       lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
-                               "0427 Firmware not capable of SLI 3 mode.\n");
-               else {
-                       lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
-                               "0426 Firmware capable of SLI 3 mode.\n");
-                       /* Now, try to enable MSI-X interrupt mode */
-                       error = lpfc_enable_msix(phba);
-                       if (!error) {
-                               phba->intr_type = MSIX;
-                               lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
-                                               "0430 enable MSI-X mode.\n");
-                       }
-               }
-       }
-
-       /* Fallback to MSI if MSI-X initialization failed */
-       if (phba->cfg_use_msi >= 1 && phba->intr_type == NONE) {
-               retval = pci_enable_msi(phba->pcidev);
-               if (!retval) {
-                       phba->intr_type = MSI;
-                       lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
-                                       "0473 enable MSI mode.\n");
-               } else
-                       lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
-                                       "0452 enable IRQ mode.\n");
-       }
-
-       /* MSI-X is the only case the doesn't need to call request_irq */
-       if (phba->intr_type != MSIX) {
-               retval = request_irq(phba->pcidev->irq, lpfc_intr_handler,
-                                    IRQF_SHARED, LPFC_DRIVER_NAME, phba);
-               if (retval) {
-                       lpfc_printf_log(phba, KERN_ERR, LOG_INIT, "0451 Enable "
-                                       "interrupt handler failed\n");
-                       error = retval;
-                       goto out_disable_msi;
-               } else if (phba->intr_type != MSI)
-                       phba->intr_type = INTx;
-       }
-
+       /* Configure sysfs attributes */
        if (lpfc_alloc_sysfs_attr(vport)) {
                lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
                                "1476 Failed to allocate sysfs attr\n");
                error = -ENOMEM;
-               goto out_free_irq;
+               goto out_destroy_port;
        }
 
-       if (lpfc_sli_hba_setup(phba)) {
-               lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
-                               "1477 Failed to set up hba\n");
-               error = -ENODEV;
-               goto out_remove_device;
+       cfg_mode = phba->cfg_use_msi;
+       while (true) {
+               /* Configure and enable interrupt */
+               intr_mode = lpfc_enable_intr(phba, cfg_mode);
+               if (intr_mode == LPFC_INTR_ERROR) {
+                       lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
+                                       "0426 Failed to enable interrupt.\n");
+                       goto out_free_sysfs_attr;
+               }
+               /* HBA SLI setup */
+               if (lpfc_sli_hba_setup(phba)) {
+                       lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
+                                       "1477 Failed to set up hba\n");
+                       error = -ENODEV;
+                       goto out_remove_device;
+               }
+
+               /* Wait 50ms for the interrupts of previous mailbox commands */
+               msleep(50);
+               /* Check active interrupts received */
+               if (phba->sli.slistat.sli_intr > LPFC_MSIX_VECTORS) {
+                       /* Log the current active interrupt mode */
+                       phba->intr_mode = intr_mode;
+                       lpfc_log_intr_mode(phba, intr_mode);
+                       break;
+               } else {
+                       lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
+                                       "0451 Configure interrupt mode (%d) "
+                                       "failed active interrupt test.\n",
+                                       intr_mode);
+                       if (intr_mode == 0) {
+                               lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
+                                               "0479 Failed to enable "
+                                               "interrupt.\n");
+                               error = -ENODEV;
+                               goto out_remove_device;
+                       }
+                       /* Stop HBA SLI setups */
+                       lpfc_stop_port(phba);
+                       /* Disable the current interrupt mode */
+                       lpfc_disable_intr(phba);
+                       /* Try next level of interrupt mode */
+                       cfg_mode = --intr_mode;
+               }
        }
 
        /*
@@ -2702,27 +2880,18 @@ lpfc_pci_probe_one(struct pci_dev *pdev, const struct pci_device_id *pid)
                (char *) &adapter_event,
                LPFC_NL_VENDOR_ID);
 
-       scsi_scan_host(shost);
-
        return 0;
 
 out_remove_device:
-       lpfc_free_sysfs_attr(vport);
        spin_lock_irq(shost->host_lock);
        vport->load_flag |= FC_UNLOADING;
        spin_unlock_irq(shost->host_lock);
-out_free_irq:
        lpfc_stop_phba_timers(phba);
        phba->pport->work_port_events = 0;
-
-       if (phba->intr_type == MSIX)
-               lpfc_disable_msix(phba);
-       else
-               free_irq(phba->pcidev->irq, phba);
-
-out_disable_msi:
-       if (phba->intr_type == MSI)
-               pci_disable_msi(phba->pcidev);
+       lpfc_disable_intr(phba);
+out_free_sysfs_attr:
+       lpfc_free_sysfs_attr(vport);
+out_destroy_port:
        destroy_port(vport);
 out_kthread_stop:
        kthread_stop(phba->worker_thread);
@@ -2811,13 +2980,8 @@ lpfc_pci_remove_one(struct pci_dev *pdev)
 
        lpfc_debugfs_terminate(vport);
 
-       if (phba->intr_type == MSIX)
-               lpfc_disable_msix(phba);
-       else {
-               free_irq(phba->pcidev->irq, phba);
-               if (phba->intr_type == MSI)
-                       pci_disable_msi(phba->pcidev);
-       }
+       /* Disable interrupt */
+       lpfc_disable_intr(phba);
 
        pci_set_drvdata(pdev, NULL);
        scsi_host_put(shost);
@@ -2915,6 +3079,7 @@ lpfc_pci_resume_one(struct pci_dev *pdev)
 {
        struct Scsi_Host *shost = pci_get_drvdata(pdev);
        struct lpfc_hba *phba = ((struct lpfc_vport *)shost->hostdata)->phba;
+       uint32_t intr_mode;
        int error;
 
        lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
@@ -2937,19 +3102,22 @@ lpfc_pci_resume_one(struct pci_dev *pdev)
                return error;
        }
 
-       /* Enable interrupt from device */
-       error = lpfc_enable_intr(phba);
-       if (error) {
+       /* Configure and enable interrupt */
+       intr_mode = lpfc_enable_intr(phba, phba->intr_mode);
+       if (intr_mode == LPFC_INTR_ERROR) {
                lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
-                               "0430 PM resume Failed to enable interrupt: "
-                               "error=x%x.\n", error);
-               return error;
-       }
+                               "0430 PM resume Failed to enable interrupt\n");
+               return -EIO;
+       } else
+               phba->intr_mode = intr_mode;
 
        /* Restart HBA and bring it online */
        lpfc_sli_brdrestart(phba);
        lpfc_online(phba);
 
+       /* Log the current active interrupt mode */
+       lpfc_log_intr_mode(phba, phba->intr_mode);
+
        return 0;
 }
 
@@ -2996,13 +3164,8 @@ static pci_ers_result_t lpfc_io_error_detected(struct pci_dev *pdev,
        pring = &psli->ring[psli->fcp_ring];
        lpfc_sli_abort_iocb_ring(phba, pring);
 
-       if (phba->intr_type == MSIX)
-               lpfc_disable_msix(phba);
-       else {
-               free_irq(phba->pcidev->irq, phba);
-               if (phba->intr_type == MSI)
-                       pci_disable_msi(phba->pcidev);
-       }
+       /* Disable interrupt */
+       lpfc_disable_intr(phba);
 
        /* Request a slot reset. */
        return PCI_ERS_RESULT_NEED_RESET;
@@ -3030,7 +3193,7 @@ static pci_ers_result_t lpfc_io_slot_reset(struct pci_dev *pdev)
        struct Scsi_Host *shost = pci_get_drvdata(pdev);
        struct lpfc_hba *phba = ((struct lpfc_vport *)shost->hostdata)->phba;
        struct lpfc_sli *psli = &phba->sli;
-       int error, retval;
+       uint32_t intr_mode;
 
        dev_printk(KERN_INFO, &pdev->dev, "recovering from a slot reset.\n");
        if (pci_enable_device_mem(pdev)) {
@@ -3047,55 +3210,23 @@ static pci_ers_result_t lpfc_io_slot_reset(struct pci_dev *pdev)
        psli->sli_flag &= ~LPFC_SLI2_ACTIVE;
        spin_unlock_irq(&phba->hbalock);
 
-       /* Enable configured interrupt method */
-       phba->intr_type = NONE;
-       if (phba->cfg_use_msi == 2) {
-               /* Need to issue conf_port mbox cmd before conf_msi mbox cmd */
-               error = lpfc_sli_config_port(phba, 3);
-               if (error)
-                       lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
-                               "0478 Firmware not capable of SLI 3 mode.\n");
-               else {
-                       lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
-                               "0479 Firmware capable of SLI 3 mode.\n");
-                       /* Now, try to enable MSI-X interrupt mode */
-                       error = lpfc_enable_msix(phba);
-                       if (!error) {
-                               phba->intr_type = MSIX;
-                               lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
-                                               "0480 enable MSI-X mode.\n");
-                       }
-               }
-       }
-
-       /* Fallback to MSI if MSI-X initialization failed */
-       if (phba->cfg_use_msi >= 1 && phba->intr_type == NONE) {
-               retval = pci_enable_msi(phba->pcidev);
-               if (!retval) {
-                       phba->intr_type = MSI;
-                       lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
-                                       "0481 enable MSI mode.\n");
-               } else
-                       lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
-                                       "0470 enable IRQ mode.\n");
-       }
-
-       /* MSI-X is the only case the doesn't need to call request_irq */
-       if (phba->intr_type != MSIX) {
-               retval = request_irq(phba->pcidev->irq, lpfc_intr_handler,
-                                    IRQF_SHARED, LPFC_DRIVER_NAME, phba);
-               if (retval) {
-                       lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
-                                       "0471 Enable interrupt handler "
-                                       "failed\n");
-               } else if (phba->intr_type != MSI)
-                       phba->intr_type = INTx;
-       }
+       /* Configure and enable interrupt */
+       intr_mode = lpfc_enable_intr(phba, phba->intr_mode);
+       if (intr_mode == LPFC_INTR_ERROR) {
+               lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
+                               "0427 Cannot re-enable interrupt after "
+                               "slot reset.\n");
+               return PCI_ERS_RESULT_DISCONNECT;
+       } else
+               phba->intr_mode = intr_mode;
 
        /* Take device offline; this will perform cleanup */
        lpfc_offline(phba);
        lpfc_sli_brdrestart(phba);
 
+       /* Log the current active interrupt mode */
+       lpfc_log_intr_mode(phba, phba->intr_mode);
+
        return PCI_ERS_RESULT_RECOVERED;
 }