[SCSI] lpfc 8.3.0 : Add support for Power Management Suspend/Resume operations
authorJames Smart <James.Smart@Emulex.Com>
Fri, 5 Dec 2008 03:38:54 +0000 (22:38 -0500)
committerJames Bottomley <James.Bottomley@HansenPartnership.com>
Mon, 29 Dec 2008 17:24:25 +0000 (11:24 -0600)
Implement lpfc_pci_suspend_one() and lpfc_pci_resume_one() and
register them in the pci_driver table.

Signed-off-by: James Smart <James.Smart@emulex.com>
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
drivers/scsi/lpfc/lpfc_hbadisc.c
drivers/scsi/lpfc/lpfc_init.c

index 65fddf4..58ed685 100644 (file)
@@ -585,20 +585,25 @@ lpfc_do_work(void *p)
        set_user_nice(current, -20);
        phba->data_flags = 0;
 
-       while (1) {
+       while (!kthread_should_stop()) {
                /* wait and check worker queue activities */
                rc = wait_event_interruptible(phba->work_waitq,
                                        (test_and_clear_bit(LPFC_DATA_READY,
                                                            &phba->data_flags)
                                         || kthread_should_stop()));
-               BUG_ON(rc);
-
-               if (kthread_should_stop())
+               /* Signal wakeup shall terminate the worker thread */
+               if (rc) {
+                       lpfc_printf_log(phba, KERN_ERR, LOG_ELS,
+                                       "0433 Wakeup on signal: rc=x%x\n", rc);
                        break;
+               }
 
                /* Attend pending lpfc data processing */
                lpfc_work_done(phba);
        }
+       phba->worker_thread = NULL;
+       lpfc_printf_log(phba, KERN_INFO, LOG_ELS,
+                       "0432 Worker thread stopped.\n");
        return 0;
 }
 
index 1e2a952..b213d1d 100644 (file)
@@ -2708,7 +2708,7 @@ out:
  * @pdev: pointer to PCI device
  *
  * This routine is to be registered to the kernel's PCI subsystem. When an
- * Emulex HBA is removed from PCI bus. It perform all the necessary cleanup
+ * Emulex HBA is removed from PCI bus, it performs all the necessary cleanup
  * for the HBA device to be removed from the PCI subsystem properly.
  **/
 static void __devexit
@@ -2785,6 +2785,111 @@ lpfc_pci_remove_one(struct pci_dev *pdev)
 }
 
 /**
+ * lpfc_pci_suspend_one: lpfc PCI func to suspend device for power management.
+ * @pdev: pointer to PCI device
+ * @msg: power management message
+ *
+ * This routine is to be registered to the kernel's PCI subsystem to support
+ * system Power Management (PM). When PM invokes this method, it quiesces the
+ * device by stopping the driver's worker thread for the device, turning off
+ * device's interrupt and DMA, and bring the device offline. Note that as the
+ * driver implements the minimum PM requirements to a power-aware driver's PM
+ * support for suspend/resume -- all the possible PM messages (SUSPEND,
+ * HIBERNATE, FREEZE) to the suspend() method call will be treated as SUSPEND
+ * and the driver will fully reinitialize its device during resume() method
+ * call, the driver will set device to PCI_D3hot state in PCI config space
+ * instead of setting it according to the @msg provided by the PM.
+ *
+ * Return code
+ *   0 - driver suspended the device
+ *   Error otherwise
+ **/
+static int
+lpfc_pci_suspend_one(struct pci_dev *pdev, pm_message_t msg)
+{
+       struct Scsi_Host *shost = pci_get_drvdata(pdev);
+       struct lpfc_hba *phba = ((struct lpfc_vport *)shost->hostdata)->phba;
+
+       lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
+                       "0473 PCI device Power Management suspend.\n");
+
+       /* Bring down the device */
+       lpfc_offline_prep(phba);
+       lpfc_offline(phba);
+       kthread_stop(phba->worker_thread);
+
+       /* Disable interrupt from device */
+       lpfc_disable_intr(phba);
+
+       /* Save device state to PCI config space */
+       pci_save_state(pdev);
+       pci_set_power_state(pdev, PCI_D3hot);
+
+       return 0;
+}
+
+/**
+ * lpfc_pci_resume_one: lpfc PCI func to resume device for power management.
+ * @pdev: pointer to PCI device
+ *
+ * This routine is to be registered to the kernel's PCI subsystem to support
+ * system Power Management (PM). When PM invokes this method, it restores
+ * the device's PCI config space state and fully reinitializes the device
+ * and brings it online. Note that as the driver implements the minimum PM
+ * requirements to a power-aware driver's PM for suspend/resume -- all
+ * the possible PM messages (SUSPEND, HIBERNATE, FREEZE) to the suspend()
+ * method call will be treated as SUSPEND and the driver will fully
+ * reinitialize its device during resume() method call, the device will be
+ * set to PCI_D0 directly in PCI config space before restoring the state.
+ *
+ * Return code
+ *   0 - driver suspended the device
+ *   Error otherwise
+ **/
+static int
+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;
+       int error;
+
+       lpfc_printf_log(phba, KERN_INFO, LOG_INIT,
+                       "0452 PCI device Power Management resume.\n");
+
+       /* Restore device state from PCI config space */
+       pci_set_power_state(pdev, PCI_D0);
+       pci_restore_state(pdev);
+       if (pdev->is_busmaster)
+               pci_set_master(pdev);
+
+       /* Startup the kernel thread for this host adapter. */
+       phba->worker_thread = kthread_run(lpfc_do_work, phba,
+                                       "lpfc_worker_%d", phba->brd_no);
+       if (IS_ERR(phba->worker_thread)) {
+               error = PTR_ERR(phba->worker_thread);
+               lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
+                               "0434 PM resume failed to start worker "
+                               "thread: error=x%x.\n", error);
+               return error;
+       }
+
+       /* Enable interrupt from device */
+       error = lpfc_enable_intr(phba);
+       if (error) {
+               lpfc_printf_log(phba, KERN_ERR, LOG_INIT,
+                               "0430 PM resume Failed to enable interrupt: "
+                               "error=x%x.\n", error);
+               return error;
+       }
+
+       /* Restart HBA and bring it online */
+       lpfc_sli_brdrestart(phba);
+       lpfc_online(phba);
+
+       return 0;
+}
+
+/**
  * lpfc_io_error_detected: Driver method for handling PCI I/O error detected.
  * @pdev: pointer to PCI device.
  * @state: the current PCI connection state.
@@ -3036,6 +3141,8 @@ static struct pci_driver lpfc_driver = {
        .id_table       = lpfc_id_table,
        .probe          = lpfc_pci_probe_one,
        .remove         = __devexit_p(lpfc_pci_remove_one),
+       .suspend        = lpfc_pci_suspend_one,
+       .resume         = lpfc_pci_resume_one,
        .err_handler    = &lpfc_err_handler,
 };