[SCSI] lpfc 8.3.0 : Add support for Power Management Suspend/Resume operations

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>
This commit is contained in:
James Smart 2008-12-04 22:38:54 -05:00 committed by James Bottomley
parent ddcc50f0f3
commit 3a55b5327b
2 changed files with 117 additions and 5 deletions

View 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;
}

View 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
@ -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,
};