Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 32140
b: refs/heads/master
c: 500530f
h: refs/heads/master
v: v3
  • Loading branch information
Tejun Heo authored and Jeff Garzik committed Jul 6, 2006
1 parent 626c94f commit 12cb8b1
Show file tree
Hide file tree
Showing 4 changed files with 299 additions and 8 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: d6f26d1f1f1128a896f38a7f8426daed0a1205a2
refs/heads/master: 500530f652f9e5dabe7571b018dec47742ce0f16
165 changes: 160 additions & 5 deletions trunk/drivers/scsi/libata-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -5009,6 +5009,122 @@ int ata_flush_cache(struct ata_device *dev)
return 0;
}

static int ata_host_set_request_pm(struct ata_host_set *host_set,
pm_message_t mesg, unsigned int action,
unsigned int ehi_flags, int wait)
{
unsigned long flags;
int i, rc;

for (i = 0; i < host_set->n_ports; i++) {
struct ata_port *ap = host_set->ports[i];

/* Previous resume operation might still be in
* progress. Wait for PM_PENDING to clear.
*/
if (ap->pflags & ATA_PFLAG_PM_PENDING) {
ata_port_wait_eh(ap);
WARN_ON(ap->pflags & ATA_PFLAG_PM_PENDING);
}

/* request PM ops to EH */
spin_lock_irqsave(ap->lock, flags);

ap->pm_mesg = mesg;
if (wait) {
rc = 0;
ap->pm_result = &rc;
}

ap->pflags |= ATA_PFLAG_PM_PENDING;
ap->eh_info.action |= action;
ap->eh_info.flags |= ehi_flags;

ata_port_schedule_eh(ap);

spin_unlock_irqrestore(ap->lock, flags);

/* wait and check result */
if (wait) {
ata_port_wait_eh(ap);
WARN_ON(ap->pflags & ATA_PFLAG_PM_PENDING);
if (rc)
return rc;
}
}

return 0;
}

/**
* ata_host_set_suspend - suspend host_set
* @host_set: host_set to suspend
* @mesg: PM message
*
* Suspend @host_set. Actual operation is performed by EH. This
* function requests EH to perform PM operations and waits for EH
* to finish.
*
* LOCKING:
* Kernel thread context (may sleep).
*
* RETURNS:
* 0 on success, -errno on failure.
*/
int ata_host_set_suspend(struct ata_host_set *host_set, pm_message_t mesg)
{
int i, j, rc;

rc = ata_host_set_request_pm(host_set, mesg, 0, ATA_EHI_QUIET, 1);
if (rc)
goto fail;

/* EH is quiescent now. Fail if we have any ready device.
* This happens if hotplug occurs between completion of device
* suspension and here.
*/
for (i = 0; i < host_set->n_ports; i++) {
struct ata_port *ap = host_set->ports[i];

for (j = 0; j < ATA_MAX_DEVICES; j++) {
struct ata_device *dev = &ap->device[j];

if (ata_dev_ready(dev)) {
ata_port_printk(ap, KERN_WARNING,
"suspend failed, device %d "
"still active\n", dev->devno);
rc = -EBUSY;
goto fail;
}
}
}

host_set->dev->power.power_state = mesg;
return 0;

fail:
ata_host_set_resume(host_set);
return rc;
}

/**
* ata_host_set_resume - resume host_set
* @host_set: host_set to resume
*
* Resume @host_set. Actual operation is performed by EH. This
* function requests EH to perform PM operations and returns.
* Note that all resume operations are performed parallely.
*
* LOCKING:
* Kernel thread context (may sleep).
*/
void ata_host_set_resume(struct ata_host_set *host_set)
{
ata_host_set_request_pm(host_set, PMSG_ON, ATA_EH_SOFTRESET,
ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET, 0);
host_set->dev->power.power_state = PMSG_ON;
}

/**
* ata_port_start - Set port up for dma.
* @ap: Port to initialize
Expand Down Expand Up @@ -5651,20 +5767,55 @@ int pci_test_config_bits(struct pci_dev *pdev, const struct pci_bits *bits)
return (tmp == bits->val) ? 1 : 0;
}

int ata_pci_device_suspend(struct pci_dev *pdev, pm_message_t state)
void ata_pci_device_do_suspend(struct pci_dev *pdev, pm_message_t state)
{
pci_save_state(pdev);
pci_disable_device(pdev);
pci_set_power_state(pdev, PCI_D3hot);
return 0;

if (state.event == PM_EVENT_SUSPEND) {
pci_disable_device(pdev);
pci_set_power_state(pdev, PCI_D3hot);
}
}

int ata_pci_device_resume(struct pci_dev *pdev)
void ata_pci_device_do_resume(struct pci_dev *pdev)
{
pci_set_power_state(pdev, PCI_D0);
pci_restore_state(pdev);
pci_enable_device(pdev);
pci_set_master(pdev);
}

int ata_pci_device_suspend(struct pci_dev *pdev, pm_message_t state)
{
struct ata_host_set *host_set = dev_get_drvdata(&pdev->dev);
int rc = 0;

rc = ata_host_set_suspend(host_set, state);
if (rc)
return rc;

if (host_set->next) {
rc = ata_host_set_suspend(host_set->next, state);
if (rc) {
ata_host_set_resume(host_set);
return rc;
}
}

ata_pci_device_do_suspend(pdev, state);

return 0;
}

int ata_pci_device_resume(struct pci_dev *pdev)
{
struct ata_host_set *host_set = dev_get_drvdata(&pdev->dev);

ata_pci_device_do_resume(pdev);
ata_host_set_resume(host_set);
if (host_set->next)
ata_host_set_resume(host_set->next);

return 0;
}
#endif /* CONFIG_PCI */
Expand Down Expand Up @@ -5844,6 +5995,8 @@ EXPORT_SYMBOL_GPL(sata_scr_write);
EXPORT_SYMBOL_GPL(sata_scr_write_flush);
EXPORT_SYMBOL_GPL(ata_port_online);
EXPORT_SYMBOL_GPL(ata_port_offline);
EXPORT_SYMBOL_GPL(ata_host_set_suspend);
EXPORT_SYMBOL_GPL(ata_host_set_resume);
EXPORT_SYMBOL_GPL(ata_id_string);
EXPORT_SYMBOL_GPL(ata_id_c_string);
EXPORT_SYMBOL_GPL(ata_scsi_simulate);
Expand All @@ -5858,6 +6011,8 @@ EXPORT_SYMBOL_GPL(ata_pci_host_stop);
EXPORT_SYMBOL_GPL(ata_pci_init_native_mode);
EXPORT_SYMBOL_GPL(ata_pci_init_one);
EXPORT_SYMBOL_GPL(ata_pci_remove_one);
EXPORT_SYMBOL_GPL(ata_pci_device_do_suspend);
EXPORT_SYMBOL_GPL(ata_pci_device_do_resume);
EXPORT_SYMBOL_GPL(ata_pci_device_suspend);
EXPORT_SYMBOL_GPL(ata_pci_device_resume);
EXPORT_SYMBOL_GPL(ata_pci_default_filter);
Expand Down
128 changes: 126 additions & 2 deletions trunk/drivers/scsi/libata-eh.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@

static void __ata_port_freeze(struct ata_port *ap);
static void ata_eh_finish(struct ata_port *ap);
static void ata_eh_handle_port_suspend(struct ata_port *ap);
static void ata_eh_handle_port_resume(struct ata_port *ap);

static void ata_ering_record(struct ata_ering *ering, int is_io,
unsigned int err_mask)
Expand Down Expand Up @@ -262,6 +264,9 @@ void ata_scsi_error(struct Scsi_Host *host)
repeat:
/* invoke error handler */
if (ap->ops->error_handler) {
/* process port resume request */
ata_eh_handle_port_resume(ap);

/* fetch & clear EH info */
spin_lock_irqsave(ap->lock, flags);

Expand All @@ -274,12 +279,15 @@ void ata_scsi_error(struct Scsi_Host *host)

spin_unlock_irqrestore(ap->lock, flags);

/* invoke EH. if unloading, just finish failed qcs */
if (!(ap->pflags & ATA_PFLAG_UNLOADING))
/* invoke EH, skip if unloading or suspended */
if (!(ap->pflags & (ATA_PFLAG_UNLOADING | ATA_PFLAG_SUSPENDED)))
ap->ops->error_handler(ap);
else
ata_eh_finish(ap);

/* process port suspend request */
ata_eh_handle_port_suspend(ap);

/* Exception might have happend after ->error_handler
* recovered the port but before this point. Repeat
* EH in such case.
Expand Down Expand Up @@ -2101,3 +2109,119 @@ void ata_do_eh(struct ata_port *ap, ata_prereset_fn_t prereset,
ata_eh_recover(ap, prereset, softreset, hardreset, postreset);
ata_eh_finish(ap);
}

/**
* ata_eh_handle_port_suspend - perform port suspend operation
* @ap: port to suspend
*
* Suspend @ap.
*
* LOCKING:
* Kernel thread context (may sleep).
*/
static void ata_eh_handle_port_suspend(struct ata_port *ap)
{
unsigned long flags;
int rc = 0;

/* are we suspending? */
spin_lock_irqsave(ap->lock, flags);
if (!(ap->pflags & ATA_PFLAG_PM_PENDING) ||
ap->pm_mesg.event == PM_EVENT_ON) {
spin_unlock_irqrestore(ap->lock, flags);
return;
}
spin_unlock_irqrestore(ap->lock, flags);

WARN_ON(ap->pflags & ATA_PFLAG_SUSPENDED);

/* suspend */
ata_eh_freeze_port(ap);

if (ap->ops->port_suspend)
rc = ap->ops->port_suspend(ap, ap->pm_mesg);

/* report result */
spin_lock_irqsave(ap->lock, flags);

ap->pflags &= ~ATA_PFLAG_PM_PENDING;
if (rc == 0)
ap->pflags |= ATA_PFLAG_SUSPENDED;
else
ata_port_schedule_eh(ap);

if (ap->pm_result) {
*ap->pm_result = rc;
ap->pm_result = NULL;
}

spin_unlock_irqrestore(ap->lock, flags);

return;
}

/**
* ata_eh_handle_port_resume - perform port resume operation
* @ap: port to resume
*
* Resume @ap.
*
* This function also waits upto one second until all devices
* hanging off this port requests resume EH action. This is to
* prevent invoking EH and thus reset multiple times on resume.
*
* On DPM resume, where some of devices might not be resumed
* together, this may delay port resume upto one second, but such
* DPM resumes are rare and 1 sec delay isn't too bad.
*
* LOCKING:
* Kernel thread context (may sleep).
*/
static void ata_eh_handle_port_resume(struct ata_port *ap)
{
unsigned long timeout;
unsigned long flags;
int i, rc = 0;

/* are we resuming? */
spin_lock_irqsave(ap->lock, flags);
if (!(ap->pflags & ATA_PFLAG_PM_PENDING) ||
ap->pm_mesg.event != PM_EVENT_ON) {
spin_unlock_irqrestore(ap->lock, flags);
return;
}
spin_unlock_irqrestore(ap->lock, flags);

/* spurious? */
if (!(ap->pflags & ATA_PFLAG_SUSPENDED))
goto done;

if (ap->ops->port_resume)
rc = ap->ops->port_resume(ap);

/* give devices time to request EH */
timeout = jiffies + HZ; /* 1s max */
while (1) {
for (i = 0; i < ATA_MAX_DEVICES; i++) {
struct ata_device *dev = &ap->device[i];
unsigned int action = ata_eh_dev_action(dev);

if ((dev->flags & ATA_DFLAG_SUSPENDED) &&
!(action & ATA_EH_RESUME))
break;
}

if (i == ATA_MAX_DEVICES || time_after(jiffies, timeout))
break;
msleep(10);
}

done:
spin_lock_irqsave(ap->lock, flags);
ap->pflags &= ~(ATA_PFLAG_PM_PENDING | ATA_PFLAG_SUSPENDED);
if (ap->pm_result) {
*ap->pm_result = rc;
ap->pm_result = NULL;
}
spin_unlock_irqrestore(ap->lock, flags);
}
Loading

0 comments on commit 12cb8b1

Please sign in to comment.