|
@@ -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
|
|
@@ -2784,6 +2784,111 @@ lpfc_pci_remove_one(struct pci_dev *pdev)
|
|
|
pci_disable_device(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.
|
|
@@ -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,
|
|
|
};
|
|
|
|