#include <scsi/scsi_eh.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_cmnd.h>
+#include "scsi_transport_api.h"
#include <linux/libata.h>
#include "libata.h"
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)
return rc;
}
+static unsigned int ata_eh_dev_action(struct ata_device *dev)
+{
+ struct ata_eh_context *ehc = &dev->ap->eh_context;
+
+ return ehc->i.action | ehc->i.dev_action[dev->devno];
+}
+
+static void ata_eh_clear_action(struct ata_device *dev,
+ struct ata_eh_info *ehi, unsigned int action)
+{
+ int i;
+
+ if (!dev) {
+ ehi->action &= ~action;
+ for (i = 0; i < ATA_MAX_DEVICES; i++)
+ ehi->dev_action[i] &= ~action;
+ } else {
+ /* doesn't make sense for port-wide EH actions */
+ WARN_ON(!(action & ATA_EH_PERDEV_MASK));
+
+ /* break ehi->action into ehi->dev_action */
+ if (ehi->action & action) {
+ for (i = 0; i < ATA_MAX_DEVICES; i++)
+ ehi->dev_action[i] |= ehi->action & action;
+ ehi->action &= ~action;
+ }
+
+ /* turn off the specified per-dev action */
+ ehi->dev_action[dev->devno] &= ~action;
+ }
+}
+
/**
* ata_scsi_timed_out - SCSI layer time out callback
* @cmd: timed out SCSI command
}
ret = EH_HANDLED;
- spin_lock_irqsave(&ap->host_set->lock, flags);
+ spin_lock_irqsave(ap->lock, flags);
qc = ata_qc_from_tag(ap, ap->active_tag);
if (qc) {
WARN_ON(qc->scsicmd != cmd);
qc->err_mask |= AC_ERR_TIMEOUT;
ret = EH_NOT_HANDLED;
}
- spin_unlock_irqrestore(&ap->host_set->lock, flags);
+ spin_unlock_irqrestore(ap->lock, flags);
out:
DPRINTK("EXIT, ret=%d\n", ret);
void ata_scsi_error(struct Scsi_Host *host)
{
struct ata_port *ap = ata_shost_to_port(host);
- spinlock_t *hs_lock = &ap->host_set->lock;
int i, repeat_cnt = ATA_EH_MAX_REPEAT;
unsigned long flags;
struct scsi_cmnd *scmd, *tmp;
int nr_timedout = 0;
- spin_lock_irqsave(hs_lock, flags);
+ spin_lock_irqsave(ap->lock, flags);
list_for_each_entry_safe(scmd, tmp, &host->eh_cmd_q, eh_entry) {
struct ata_queued_cmd *qc;
if (nr_timedout)
__ata_port_freeze(ap);
- spin_unlock_irqrestore(hs_lock, flags);
+ spin_unlock_irqrestore(ap->lock, flags);
} else
- spin_unlock_wait(hs_lock);
+ spin_unlock_wait(ap->lock);
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(hs_lock, flags);
+ spin_lock_irqsave(ap->lock, flags);
memset(&ap->eh_context, 0, sizeof(ap->eh_context));
ap->eh_context.i = ap->eh_info;
memset(&ap->eh_info, 0, sizeof(ap->eh_info));
- ap->flags &= ~ATA_FLAG_EH_PENDING;
+ ap->pflags |= ATA_PFLAG_EH_IN_PROGRESS;
+ ap->pflags &= ~ATA_PFLAG_EH_PENDING;
+
+ spin_unlock_irqrestore(ap->lock, flags);
- spin_unlock_irqrestore(hs_lock, flags);
+ /* 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);
- /* invoke EH */
- ap->ops->error_handler(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.
*/
- spin_lock_irqsave(hs_lock, flags);
+ spin_lock_irqsave(ap->lock, flags);
- if (ap->flags & ATA_FLAG_EH_PENDING) {
+ if (ap->pflags & ATA_PFLAG_EH_PENDING) {
if (--repeat_cnt) {
ata_port_printk(ap, KERN_INFO,
"EH pending after completion, "
"repeating EH (cnt=%d)\n", repeat_cnt);
- spin_unlock_irqrestore(hs_lock, flags);
+ spin_unlock_irqrestore(ap->lock, flags);
goto repeat;
}
ata_port_printk(ap, KERN_ERR, "EH pending after %d "
/* this run is complete, make sure EH info is clear */
memset(&ap->eh_info, 0, sizeof(ap->eh_info));
- /* Clear host_eh_scheduled while holding hs_lock such
+ /* Clear host_eh_scheduled while holding ap->lock such
* that if exception occurs after this point but
* before EH completion, SCSI midlayer will
* re-initiate EH.
*/
host->host_eh_scheduled = 0;
- spin_unlock_irqrestore(hs_lock, flags);
+ spin_unlock_irqrestore(ap->lock, flags);
} else {
WARN_ON(ata_qc_from_tag(ap, ap->active_tag) == NULL);
ap->ops->eng_timeout(ap);
scsi_eh_flush_done_q(&ap->eh_done_q);
/* clean up */
- spin_lock_irqsave(hs_lock, flags);
+ spin_lock_irqsave(ap->lock, flags);
- if (ap->flags & ATA_FLAG_RECOVERED)
+ if (ap->pflags & ATA_PFLAG_LOADING)
+ ap->pflags &= ~ATA_PFLAG_LOADING;
+ else if (ap->pflags & ATA_PFLAG_SCSI_HOTPLUG)
+ queue_work(ata_aux_wq, &ap->hotplug_task);
+
+ if (ap->pflags & ATA_PFLAG_RECOVERED)
ata_port_printk(ap, KERN_INFO, "EH complete\n");
- ap->flags &= ~ATA_FLAG_RECOVERED;
- spin_unlock_irqrestore(hs_lock, flags);
+ ap->pflags &= ~(ATA_PFLAG_SCSI_HOTPLUG | ATA_PFLAG_RECOVERED);
+
+ /* tell wait_eh that we're done */
+ ap->pflags &= ~ATA_PFLAG_EH_IN_PROGRESS;
+ wake_up_all(&ap->eh_wait_q);
+
+ spin_unlock_irqrestore(ap->lock, flags);
DPRINTK("EXIT\n");
}
+/**
+ * ata_port_wait_eh - Wait for the currently pending EH to complete
+ * @ap: Port to wait EH for
+ *
+ * Wait until the currently pending EH is complete.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ */
+void ata_port_wait_eh(struct ata_port *ap)
+{
+ unsigned long flags;
+ DEFINE_WAIT(wait);
+
+ retry:
+ spin_lock_irqsave(ap->lock, flags);
+
+ while (ap->pflags & (ATA_PFLAG_EH_PENDING | ATA_PFLAG_EH_IN_PROGRESS)) {
+ prepare_to_wait(&ap->eh_wait_q, &wait, TASK_UNINTERRUPTIBLE);
+ spin_unlock_irqrestore(ap->lock, flags);
+ schedule();
+ spin_lock_irqsave(ap->lock, flags);
+ }
+ finish_wait(&ap->eh_wait_q, &wait);
+
+ spin_unlock_irqrestore(ap->lock, flags);
+
+ /* make sure SCSI EH is complete */
+ if (scsi_host_in_recovery(ap->host)) {
+ msleep(10);
+ goto retry;
+ }
+}
+
/**
* ata_qc_timeout - Handle timeout of queued command
* @qc: Command that timed out
static void ata_qc_timeout(struct ata_queued_cmd *qc)
{
struct ata_port *ap = qc->ap;
- struct ata_host_set *host_set = ap->host_set;
u8 host_stat = 0, drv_stat;
unsigned long flags;
ap->hsm_task_state = HSM_ST_IDLE;
- spin_lock_irqsave(&host_set->lock, flags);
+ spin_lock_irqsave(ap->lock, flags);
switch (qc->tf.protocol) {
qc->tf.command, drv_stat, host_stat);
/* complete taskfile transaction */
- qc->err_mask |= ac_err_mask(drv_stat);
+ qc->err_mask |= AC_ERR_TIMEOUT;
break;
}
- spin_unlock_irqrestore(&host_set->lock, flags);
+ spin_unlock_irqrestore(ap->lock, flags);
ata_eh_qc_complete(qc);
WARN_ON(!ap->ops->error_handler);
qc->flags |= ATA_QCFLAG_FAILED;
- qc->ap->flags |= ATA_FLAG_EH_PENDING;
+ qc->ap->pflags |= ATA_PFLAG_EH_PENDING;
/* The following will fail if timeout has already expired.
* ata_scsi_error() takes care of such scmds on EH entry.
{
WARN_ON(!ap->ops->error_handler);
- ap->flags |= ATA_FLAG_EH_PENDING;
- ata_schedule_scsi_eh(ap->host);
+ ap->pflags |= ATA_PFLAG_EH_PENDING;
+ scsi_schedule_eh(ap->host);
DPRINTK("port EH scheduled\n");
}
if (ap->ops->freeze)
ap->ops->freeze(ap);
- ap->flags |= ATA_FLAG_FROZEN;
+ ap->pflags |= ATA_PFLAG_FROZEN;
DPRINTK("ata%u port frozen\n", ap->id);
}
if (!ap->ops->error_handler)
return;
- spin_lock_irqsave(&ap->host_set->lock, flags);
+ spin_lock_irqsave(ap->lock, flags);
__ata_port_freeze(ap);
- spin_unlock_irqrestore(&ap->host_set->lock, flags);
+ spin_unlock_irqrestore(ap->lock, flags);
}
/**
if (!ap->ops->error_handler)
return;
- spin_lock_irqsave(&ap->host_set->lock, flags);
+ spin_lock_irqsave(ap->lock, flags);
- ap->flags &= ~ATA_FLAG_FROZEN;
+ ap->pflags &= ~ATA_PFLAG_FROZEN;
if (ap->ops->thaw)
ap->ops->thaw(ap);
- spin_unlock_irqrestore(&ap->host_set->lock, flags);
+ spin_unlock_irqrestore(ap->lock, flags);
DPRINTK("ata%u port thawed\n", ap->id);
}
struct scsi_cmnd *scmd = qc->scsicmd;
unsigned long flags;
- spin_lock_irqsave(&ap->host_set->lock, flags);
+ spin_lock_irqsave(ap->lock, flags);
qc->scsidone = ata_eh_scsidone;
__ata_qc_complete(qc);
WARN_ON(ata_tag_valid(qc->tag));
- spin_unlock_irqrestore(&ap->host_set->lock, flags);
+ spin_unlock_irqrestore(ap->lock, flags);
scsi_eh_finish_cmd(scmd, &ap->eh_done_q);
}
__ata_eh_qc_complete(qc);
}
+/**
+ * ata_eh_detach_dev - detach ATA device
+ * @dev: ATA device to detach
+ *
+ * Detach @dev.
+ *
+ * LOCKING:
+ * None.
+ */
+static void ata_eh_detach_dev(struct ata_device *dev)
+{
+ struct ata_port *ap = dev->ap;
+ unsigned long flags;
+
+ ata_dev_disable(dev);
+
+ spin_lock_irqsave(ap->lock, flags);
+
+ dev->flags &= ~ATA_DFLAG_DETACH;
+
+ if (ata_scsi_offline_dev(dev)) {
+ dev->flags |= ATA_DFLAG_DETACHED;
+ ap->pflags |= ATA_PFLAG_SCSI_HOTPLUG;
+ }
+
+ /* clear per-dev EH actions */
+ ata_eh_clear_action(dev, &ap->eh_info, ATA_EH_PERDEV_MASK);
+ ata_eh_clear_action(dev, &ap->eh_context.i, ATA_EH_PERDEV_MASK);
+
+ spin_unlock_irqrestore(ap->lock, flags);
+}
+
/**
* ata_eh_about_to_do - about to perform eh_action
* @ap: target ATA port
+ * @dev: target ATA dev for per-dev action (can be NULL)
* @action: action about to be performed
*
* Called just before performing EH actions to clear related bits
* LOCKING:
* None.
*/
-static void ata_eh_about_to_do(struct ata_port *ap, unsigned int action)
+static void ata_eh_about_to_do(struct ata_port *ap, struct ata_device *dev,
+ unsigned int action)
{
unsigned long flags;
+ struct ata_eh_info *ehi = &ap->eh_info;
+ struct ata_eh_context *ehc = &ap->eh_context;
+
+ spin_lock_irqsave(ap->lock, flags);
+
+ /* Reset is represented by combination of actions and EHI
+ * flags. Suck in all related bits before clearing eh_info to
+ * avoid losing requested action.
+ */
+ if (action & ATA_EH_RESET_MASK) {
+ ehc->i.action |= ehi->action & ATA_EH_RESET_MASK;
+ ehc->i.flags |= ehi->flags & ATA_EHI_RESET_MODIFIER_MASK;
+
+ /* make sure all reset actions are cleared & clear EHI flags */
+ action |= ATA_EH_RESET_MASK;
+ ehi->flags &= ~ATA_EHI_RESET_MODIFIER_MASK;
+ }
+
+ ata_eh_clear_action(dev, ehi, action);
+
+ if (!(ehc->i.flags & ATA_EHI_QUIET))
+ ap->pflags |= ATA_PFLAG_RECOVERED;
+
+ spin_unlock_irqrestore(ap->lock, flags);
+}
- spin_lock_irqsave(&ap->host_set->lock, flags);
- ap->eh_info.action &= ~action;
- ap->flags |= ATA_FLAG_RECOVERED;
- spin_unlock_irqrestore(&ap->host_set->lock, flags);
+/**
+ * ata_eh_done - EH action complete
+ * @ap: target ATA port
+ * @dev: target ATA dev for per-dev action (can be NULL)
+ * @action: action just completed
+ *
+ * Called right after performing EH actions to clear related bits
+ * in @ap->eh_context.
+ *
+ * LOCKING:
+ * None.
+ */
+static void ata_eh_done(struct ata_port *ap, struct ata_device *dev,
+ unsigned int action)
+{
+ /* if reset is complete, clear all reset actions & reset modifier */
+ if (action & ATA_EH_RESET_MASK) {
+ action |= ATA_EH_RESET_MASK;
+ ap->eh_context.i.flags &= ~ATA_EHI_RESET_MODIFIER_MASK;
+ }
+
+ ata_eh_clear_action(dev, &ap->eh_context.i, action);
}
/**
return "unknown error";
}
+/**
+ * ata_read_log_page - read a specific log page
+ * @dev: target device
+ * @page: page to read
+ * @buf: buffer to store read page
+ * @sectors: number of sectors to read
+ *
+ * Read log page using READ_LOG_EXT command.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, AC_ERR_* mask otherwise.
+ */
+static unsigned int ata_read_log_page(struct ata_device *dev,
+ u8 page, void *buf, unsigned int sectors)
+{
+ struct ata_taskfile tf;
+ unsigned int err_mask;
+
+ DPRINTK("read log page - page %d\n", page);
+
+ ata_tf_init(dev, &tf);
+ tf.command = ATA_CMD_READ_LOG_EXT;
+ tf.lbal = page;
+ tf.nsect = sectors;
+ tf.hob_nsect = sectors >> 8;
+ tf.flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_LBA48 | ATA_TFLAG_DEVICE;
+ tf.protocol = ATA_PROT_PIO;
+
+ err_mask = ata_exec_internal(dev, &tf, NULL, DMA_FROM_DEVICE,
+ buf, sectors * ATA_SECT_SIZE);
+
+ DPRINTK("EXIT, err_mask=%x\n", err_mask);
+ return err_mask;
+}
+
+/**
+ * ata_eh_read_log_10h - Read log page 10h for NCQ error details
+ * @dev: Device to read log page 10h from
+ * @tag: Resulting tag of the failed command
+ * @tf: Resulting taskfile registers of the failed command
+ *
+ * Read log page 10h to obtain NCQ error details and clear error
+ * condition.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, -errno otherwise.
+ */
+static int ata_eh_read_log_10h(struct ata_device *dev,
+ int *tag, struct ata_taskfile *tf)
+{
+ u8 *buf = dev->ap->sector_buf;
+ unsigned int err_mask;
+ u8 csum;
+ int i;
+
+ err_mask = ata_read_log_page(dev, ATA_LOG_SATA_NCQ, buf, 1);
+ if (err_mask)
+ return -EIO;
+
+ csum = 0;
+ for (i = 0; i < ATA_SECT_SIZE; i++)
+ csum += buf[i];
+ if (csum)
+ ata_dev_printk(dev, KERN_WARNING,
+ "invalid checksum 0x%x on log page 10h\n", csum);
+
+ if (buf[0] & 0x80)
+ return -ENOENT;
+
+ *tag = buf[0] & 0x1f;
+
+ tf->command = buf[2];
+ tf->feature = buf[3];
+ tf->lbal = buf[4];
+ tf->lbam = buf[5];
+ tf->lbah = buf[6];
+ tf->device = buf[7];
+ tf->hob_lbal = buf[8];
+ tf->hob_lbam = buf[9];
+ tf->hob_lbah = buf[10];
+ tf->nsect = buf[12];
+ tf->hob_nsect = buf[13];
+
+ return 0;
+}
+
/**
* atapi_eh_request_sense - perform ATAPI REQUEST_SENSE
* @dev: device to perform REQUEST_SENSE to
err_mask |= AC_ERR_SYSTEM;
action |= ATA_EH_SOFTRESET;
}
- if (serror & (SERR_PHYRDY_CHG | SERR_DEV_XCHG)) {
- err_mask |= AC_ERR_ATA_BUS;
- action |= ATA_EH_HARDRESET;
- }
+ if (serror & (SERR_PHYRDY_CHG | SERR_DEV_XCHG))
+ ata_ehi_hotplugged(&ehc->i);
ehc->i.err_mask |= err_mask;
ehc->i.action |= action;
}
+/**
+ * ata_eh_analyze_ncq_error - analyze NCQ error
+ * @ap: ATA port to analyze NCQ error for
+ *
+ * Read log page 10h, determine the offending qc and acquire
+ * error status TF. For NCQ device errors, all LLDDs have to do
+ * is setting AC_ERR_DEV in ehi->err_mask. This function takes
+ * care of the rest.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ */
+static void ata_eh_analyze_ncq_error(struct ata_port *ap)
+{
+ struct ata_eh_context *ehc = &ap->eh_context;
+ struct ata_device *dev = ap->device;
+ struct ata_queued_cmd *qc;
+ struct ata_taskfile tf;
+ int tag, rc;
+
+ /* if frozen, we can't do much */
+ if (ap->pflags & ATA_PFLAG_FROZEN)
+ return;
+
+ /* is it NCQ device error? */
+ if (!ap->sactive || !(ehc->i.err_mask & AC_ERR_DEV))
+ return;
+
+ /* has LLDD analyzed already? */
+ for (tag = 0; tag < ATA_MAX_QUEUE; tag++) {
+ qc = __ata_qc_from_tag(ap, tag);
+
+ if (!(qc->flags & ATA_QCFLAG_FAILED))
+ continue;
+
+ if (qc->err_mask)
+ return;
+ }
+
+ /* okay, this error is ours */
+ rc = ata_eh_read_log_10h(dev, &tag, &tf);
+ if (rc) {
+ ata_port_printk(ap, KERN_ERR, "failed to read log page 10h "
+ "(errno=%d)\n", rc);
+ return;
+ }
+
+ if (!(ap->sactive & (1 << tag))) {
+ ata_port_printk(ap, KERN_ERR, "log page 10h reported "
+ "inactive tag %d\n", tag);
+ return;
+ }
+
+ /* we've got the perpetrator, condemn it */
+ qc = __ata_qc_from_tag(ap, tag);
+ memcpy(&qc->result_tf, &tf, sizeof(tf));
+ qc->err_mask |= AC_ERR_DEV;
+ ehc->i.err_mask &= ~AC_ERR_DEV;
+}
+
/**
* ata_eh_analyze_tf - analyze taskfile of a failed qc
* @qc: qc to analyze
static void ata_eh_autopsy(struct ata_port *ap)
{
struct ata_eh_context *ehc = &ap->eh_context;
- unsigned int action = ehc->i.action;
- struct ata_device *failed_dev = NULL;
unsigned int all_err_mask = 0;
int tag, is_io = 0;
u32 serror;
DPRINTK("ENTER\n");
+ if (ehc->i.flags & ATA_EHI_NO_AUTOPSY)
+ return;
+
/* obtain and analyze SError */
rc = sata_scr_read(ap, SCR_ERROR, &serror);
if (rc == 0) {
ehc->i.serror |= serror;
ata_eh_analyze_serror(ap);
} else if (rc != -EOPNOTSUPP)
- action |= ATA_EH_HARDRESET;
+ ehc->i.action |= ATA_EH_HARDRESET;
+
+ /* analyze NCQ failure */
+ ata_eh_analyze_ncq_error(ap);
/* any real error trumps AC_ERR_OTHER */
if (ehc->i.err_mask & ~AC_ERR_OTHER)
/* inherit upper level err_mask */
qc->err_mask |= ehc->i.err_mask;
- if (qc->err_mask & AC_ERR_TIMEOUT)
- action |= ATA_EH_SOFTRESET;
-
/* analyze TF */
- action |= ata_eh_analyze_tf(qc, &qc->result_tf);
+ ehc->i.action |= ata_eh_analyze_tf(qc, &qc->result_tf);
/* DEV errors are probably spurious in case of ATA_BUS error */
if (qc->err_mask & AC_ERR_ATA_BUS)
/* SENSE_VALID trumps dev/unknown error and revalidation */
if (qc->flags & ATA_QCFLAG_SENSE_VALID) {
qc->err_mask &= ~(AC_ERR_DEV | AC_ERR_OTHER);
- action &= ~ATA_EH_REVALIDATE;
+ ehc->i.action &= ~ATA_EH_REVALIDATE;
}
/* accumulate error info */
- failed_dev = qc->dev;
+ ehc->i.dev = qc->dev;
all_err_mask |= qc->err_mask;
if (qc->flags & ATA_QCFLAG_IO)
is_io = 1;
}
- /* speed down iff command was in progress */
- if (failed_dev)
- action |= ata_eh_speed_down(failed_dev, is_io, all_err_mask);
-
- if (all_err_mask)
- action |= ATA_EH_REVALIDATE;
+ /* enforce default EH actions */
+ if (ap->pflags & ATA_PFLAG_FROZEN ||
+ all_err_mask & (AC_ERR_HSM | AC_ERR_TIMEOUT))
+ ehc->i.action |= ATA_EH_SOFTRESET;
+ else if (all_err_mask)
+ ehc->i.action |= ATA_EH_REVALIDATE;
- ehc->i.dev = failed_dev;
- ehc->i.action = action;
+ /* if we have offending qcs and the associated failed device */
+ if (ehc->i.dev) {
+ /* speed down */
+ ehc->i.action |= ata_eh_speed_down(ehc->i.dev, is_io,
+ all_err_mask);
+
+ /* perform per-dev EH action only on the offending device */
+ ehc->i.dev_action[ehc->i.dev->devno] |=
+ ehc->i.action & ATA_EH_PERDEV_MASK;
+ ehc->i.action &= ~ATA_EH_PERDEV_MASK;
+ }
DPRINTK("EXIT\n");
}
return;
frozen = "";
- if (ap->flags & ATA_FLAG_FROZEN)
+ if (ap->pflags & ATA_PFLAG_FROZEN)
frozen = " frozen";
if (ehc->i.dev) {
- ata_dev_printk(ehc->i.dev, KERN_ERR,
- "exception Emask 0x%x SErr 0x%x action 0x%x%s\n",
- ehc->i.err_mask, ehc->i.serror, ehc->i.action,
- frozen);
+ ata_dev_printk(ehc->i.dev, KERN_ERR, "exception Emask 0x%x "
+ "SAct 0x%x SErr 0x%x action 0x%x%s\n",
+ ehc->i.err_mask, ap->sactive, ehc->i.serror,
+ ehc->i.action, frozen);
if (desc)
ata_dev_printk(ehc->i.dev, KERN_ERR, "(%s)\n", desc);
} else {
- ata_port_printk(ap, KERN_ERR,
- "exception Emask 0x%x SErr 0x%x action 0x%x%s\n",
- ehc->i.err_mask, ehc->i.serror, ehc->i.action,
- frozen);
+ ata_port_printk(ap, KERN_ERR, "exception Emask 0x%x "
+ "SAct 0x%x SErr 0x%x action 0x%x%s\n",
+ ehc->i.err_mask, ap->sactive, ehc->i.serror,
+ ehc->i.action, frozen);
if (desc)
ata_port_printk(ap, KERN_ERR, "(%s)\n", desc);
}
}
}
-static int ata_eh_reset(struct ata_port *ap, ata_reset_fn_t softreset,
+static int ata_do_reset(struct ata_port *ap, ata_reset_fn_t reset,
+ unsigned int *classes)
+{
+ int i, rc;
+
+ for (i = 0; i < ATA_MAX_DEVICES; i++)
+ classes[i] = ATA_DEV_UNKNOWN;
+
+ rc = reset(ap, classes);
+ if (rc)
+ return rc;
+
+ /* If any class isn't ATA_DEV_UNKNOWN, consider classification
+ * is complete and convert all ATA_DEV_UNKNOWN to
+ * ATA_DEV_NONE.
+ */
+ for (i = 0; i < ATA_MAX_DEVICES; i++)
+ if (classes[i] != ATA_DEV_UNKNOWN)
+ break;
+
+ if (i < ATA_MAX_DEVICES)
+ for (i = 0; i < ATA_MAX_DEVICES; i++)
+ if (classes[i] == ATA_DEV_UNKNOWN)
+ classes[i] = ATA_DEV_NONE;
+
+ return 0;
+}
+
+static int ata_eh_followup_srst_needed(int rc, int classify,
+ const unsigned int *classes)
+{
+ if (rc == -EAGAIN)
+ return 1;
+ if (rc != 0)
+ return 0;
+ if (classify && classes[0] == ATA_DEV_UNKNOWN)
+ return 1;
+ return 0;
+}
+
+static int ata_eh_reset(struct ata_port *ap, int classify,
+ ata_prereset_fn_t prereset, ata_reset_fn_t softreset,
ata_reset_fn_t hardreset, ata_postreset_fn_t postreset)
{
struct ata_eh_context *ehc = &ap->eh_context;
- unsigned int classes[ATA_MAX_DEVICES];
+ unsigned int *classes = ehc->classes;
int tries = ATA_EH_RESET_TRIES;
+ int verbose = !(ehc->i.flags & ATA_EHI_QUIET);
+ unsigned int action;
ata_reset_fn_t reset;
- int rc;
+ int i, did_followup_srst, rc;
+
+ /* about to reset */
+ ata_eh_about_to_do(ap, NULL, ehc->i.action & ATA_EH_RESET_MASK);
+ /* Determine which reset to use and record in ehc->i.action.
+ * prereset() may examine and modify it.
+ */
+ action = ehc->i.action;
+ ehc->i.action &= ~ATA_EH_RESET_MASK;
if (softreset && (!hardreset || (!sata_set_spd_needed(ap) &&
- !(ehc->i.action & ATA_EH_HARDRESET))))
- reset = softreset;
+ !(action & ATA_EH_HARDRESET))))
+ ehc->i.action |= ATA_EH_SOFTRESET;
else
+ ehc->i.action |= ATA_EH_HARDRESET;
+
+ if (prereset) {
+ rc = prereset(ap);
+ if (rc) {
+ ata_port_printk(ap, KERN_ERR,
+ "prereset failed (errno=%d)\n", rc);
+ return rc;
+ }
+ }
+
+ /* prereset() might have modified ehc->i.action */
+ if (ehc->i.action & ATA_EH_HARDRESET)
reset = hardreset;
+ else if (ehc->i.action & ATA_EH_SOFTRESET)
+ reset = softreset;
+ else {
+ /* prereset told us not to reset, bang classes and return */
+ for (i = 0; i < ATA_MAX_DEVICES; i++)
+ classes[i] = ATA_DEV_NONE;
+ return 0;
+ }
+
+ /* did prereset() screw up? if so, fix up to avoid oopsing */
+ if (!reset) {
+ ata_port_printk(ap, KERN_ERR, "BUG: prereset() requested "
+ "invalid reset type\n");
+ if (softreset)
+ reset = softreset;
+ else
+ reset = hardreset;
+ }
retry:
- ata_port_printk(ap, KERN_INFO, "%s resetting port\n",
- reset == softreset ? "soft" : "hard");
+ /* shut up during boot probing */
+ if (verbose)
+ ata_port_printk(ap, KERN_INFO, "%s resetting port\n",
+ reset == softreset ? "soft" : "hard");
- /* reset */
- ata_eh_about_to_do(ap, ATA_EH_RESET_MASK);
+ /* mark that this EH session started with reset */
ehc->i.flags |= ATA_EHI_DID_RESET;
rc = ata_do_reset(ap, reset, classes);
+ did_followup_srst = 0;
+ if (reset == hardreset &&
+ ata_eh_followup_srst_needed(rc, classify, classes)) {
+ /* okay, let's do follow-up softreset */
+ did_followup_srst = 1;
+ reset = softreset;
+
+ if (!reset) {
+ ata_port_printk(ap, KERN_ERR,
+ "follow-up softreset required "
+ "but no softreset avaliable\n");
+ return -EINVAL;
+ }
+
+ ata_eh_about_to_do(ap, NULL, ATA_EH_RESET_MASK);
+ rc = ata_do_reset(ap, reset, classes);
+
+ if (rc == 0 && classify &&
+ classes[0] == ATA_DEV_UNKNOWN) {
+ ata_port_printk(ap, KERN_ERR,
+ "classification failed\n");
+ return -EINVAL;
+ }
+ }
+
if (rc && --tries) {
+ const char *type;
+
+ if (reset == softreset) {
+ if (did_followup_srst)
+ type = "follow-up soft";
+ else
+ type = "soft";
+ } else
+ type = "hard";
+
ata_port_printk(ap, KERN_WARNING,
- "%sreset failed, retrying in 5 secs\n",
- reset == softreset ? "soft" : "hard");
+ "%sreset failed, retrying in 5 secs\n", type);
ssleep(5);
if (reset == hardreset)
}
if (rc == 0) {
+ /* After the reset, the device state is PIO 0 and the
+ * controller state is undefined. Record the mode.
+ */
+ for (i = 0; i < ATA_MAX_DEVICES; i++)
+ ap->device[i].pio_mode = XFER_PIO_0;
+
if (postreset)
postreset(ap, classes);
/* reset successful, schedule revalidation */
- ehc->i.dev = NULL;
- ehc->i.action &= ~ATA_EH_RESET_MASK;
+ ata_eh_done(ap, NULL, ehc->i.action & ATA_EH_RESET_MASK);
ehc->i.action |= ATA_EH_REVALIDATE;
}
return rc;
}
-static int ata_eh_revalidate(struct ata_port *ap,
- struct ata_device **r_failed_dev)
+static int ata_eh_revalidate_and_attach(struct ata_port *ap,
+ struct ata_device **r_failed_dev)
{
struct ata_eh_context *ehc = &ap->eh_context;
struct ata_device *dev;
+ unsigned long flags;
int i, rc = 0;
DPRINTK("ENTER\n");
for (i = 0; i < ATA_MAX_DEVICES; i++) {
+ unsigned int action;
+
dev = &ap->device[i];
+ action = ata_eh_dev_action(dev);
- if (ehc->i.action & ATA_EH_REVALIDATE && ata_dev_enabled(dev) &&
- (!ehc->i.dev || ehc->i.dev == dev)) {
+ if (action & ATA_EH_REVALIDATE && ata_dev_ready(dev)) {
if (ata_port_offline(ap)) {
rc = -EIO;
break;
}
- ata_eh_about_to_do(ap, ATA_EH_REVALIDATE);
+ ata_eh_about_to_do(ap, dev, ATA_EH_REVALIDATE);
rc = ata_dev_revalidate(dev,
ehc->i.flags & ATA_EHI_DID_RESET);
if (rc)
break;
- ehc->i.action &= ~ATA_EH_REVALIDATE;
+ ata_eh_done(ap, dev, ATA_EH_REVALIDATE);
+
+ /* schedule the scsi_rescan_device() here */
+ queue_work(ata_aux_wq, &(ap->scsi_rescan_task));
+ } else if (dev->class == ATA_DEV_UNKNOWN &&
+ ehc->tries[dev->devno] &&
+ ata_class_enabled(ehc->classes[dev->devno])) {
+ dev->class = ehc->classes[dev->devno];
+
+ rc = ata_dev_read_id(dev, &dev->class, 1, dev->id);
+ if (rc == 0)
+ rc = ata_dev_configure(dev, 1);
+
+ if (rc) {
+ dev->class = ATA_DEV_UNKNOWN;
+ break;
+ }
+
+ spin_lock_irqsave(ap->lock, flags);
+ ap->pflags |= ATA_PFLAG_SCSI_HOTPLUG;
+ spin_unlock_irqrestore(ap->lock, flags);
}
}
return rc;
}
+/**
+ * ata_eh_suspend - handle suspend EH action
+ * @ap: target host port
+ * @r_failed_dev: result parameter to indicate failing device
+ *
+ * Handle suspend EH action. Disk devices are spinned down and
+ * other types of devices are just marked suspended. Once
+ * suspended, no EH action to the device is allowed until it is
+ * resumed.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, -errno otherwise
+ */
+static int ata_eh_suspend(struct ata_port *ap, struct ata_device **r_failed_dev)
+{
+ struct ata_device *dev;
+ int i, rc = 0;
+
+ DPRINTK("ENTER\n");
+
+ for (i = 0; i < ATA_MAX_DEVICES; i++) {
+ unsigned long flags;
+ unsigned int action, err_mask;
+
+ dev = &ap->device[i];
+ action = ata_eh_dev_action(dev);
+
+ if (!ata_dev_enabled(dev) || !(action & ATA_EH_SUSPEND))
+ continue;
+
+ WARN_ON(dev->flags & ATA_DFLAG_SUSPENDED);
+
+ ata_eh_about_to_do(ap, dev, ATA_EH_SUSPEND);
+
+ if (dev->class == ATA_DEV_ATA && !(action & ATA_EH_PM_FREEZE)) {
+ /* flush cache */
+ rc = ata_flush_cache(dev);
+ if (rc)
+ break;
+
+ /* spin down */
+ err_mask = ata_do_simple_cmd(dev, ATA_CMD_STANDBYNOW1);
+ if (err_mask) {
+ ata_dev_printk(dev, KERN_ERR, "failed to "
+ "spin down (err_mask=0x%x)\n",
+ err_mask);
+ rc = -EIO;
+ break;
+ }
+ }
+
+ spin_lock_irqsave(ap->lock, flags);
+ dev->flags |= ATA_DFLAG_SUSPENDED;
+ spin_unlock_irqrestore(ap->lock, flags);
+
+ ata_eh_done(ap, dev, ATA_EH_SUSPEND);
+ }
+
+ if (rc)
+ *r_failed_dev = dev;
+
+ DPRINTK("EXIT\n");
+ return 0;
+}
+
+/**
+ * ata_eh_prep_resume - prep for resume EH action
+ * @ap: target host port
+ *
+ * Clear SUSPENDED in preparation for scheduled resume actions.
+ * This allows other parts of EH to access the devices being
+ * resumed.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ */
+static void ata_eh_prep_resume(struct ata_port *ap)
+{
+ struct ata_device *dev;
+ unsigned long flags;
+ int i;
+
+ DPRINTK("ENTER\n");
+
+ for (i = 0; i < ATA_MAX_DEVICES; i++) {
+ unsigned int action;
+
+ dev = &ap->device[i];
+ action = ata_eh_dev_action(dev);
+
+ if (!ata_dev_enabled(dev) || !(action & ATA_EH_RESUME))
+ continue;
+
+ spin_lock_irqsave(ap->lock, flags);
+ dev->flags &= ~ATA_DFLAG_SUSPENDED;
+ spin_unlock_irqrestore(ap->lock, flags);
+ }
+
+ DPRINTK("EXIT\n");
+}
+
+/**
+ * ata_eh_resume - handle resume EH action
+ * @ap: target host port
+ * @r_failed_dev: result parameter to indicate failing device
+ *
+ * Handle resume EH action. Target devices are already reset and
+ * revalidated. Spinning up is the only operation left.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, -errno otherwise
+ */
+static int ata_eh_resume(struct ata_port *ap, struct ata_device **r_failed_dev)
+{
+ struct ata_device *dev;
+ int i, rc = 0;
+
+ DPRINTK("ENTER\n");
+
+ for (i = 0; i < ATA_MAX_DEVICES; i++) {
+ unsigned int action, err_mask;
+
+ dev = &ap->device[i];
+ action = ata_eh_dev_action(dev);
+
+ if (!ata_dev_enabled(dev) || !(action & ATA_EH_RESUME))
+ continue;
+
+ ata_eh_about_to_do(ap, dev, ATA_EH_RESUME);
+
+ if (dev->class == ATA_DEV_ATA && !(action & ATA_EH_PM_FREEZE)) {
+ err_mask = ata_do_simple_cmd(dev,
+ ATA_CMD_IDLEIMMEDIATE);
+ if (err_mask) {
+ ata_dev_printk(dev, KERN_ERR, "failed to "
+ "spin up (err_mask=0x%x)\n",
+ err_mask);
+ rc = -EIO;
+ break;
+ }
+ }
+
+ ata_eh_done(ap, dev, ATA_EH_RESUME);
+ }
+
+ if (rc)
+ *r_failed_dev = dev;
+
+ DPRINTK("EXIT\n");
+ return 0;
+}
+
static int ata_port_nr_enabled(struct ata_port *ap)
{
int i, cnt = 0;
return cnt;
}
+static int ata_port_nr_vacant(struct ata_port *ap)
+{
+ int i, cnt = 0;
+
+ for (i = 0; i < ATA_MAX_DEVICES; i++)
+ if (ap->device[i].class == ATA_DEV_UNKNOWN)
+ cnt++;
+ return cnt;
+}
+
+static int ata_eh_skip_recovery(struct ata_port *ap)
+{
+ struct ata_eh_context *ehc = &ap->eh_context;
+ int i;
+
+ /* skip if all possible devices are suspended */
+ for (i = 0; i < ata_port_max_devices(ap); i++) {
+ struct ata_device *dev = &ap->device[i];
+
+ if (!(dev->flags & ATA_DFLAG_SUSPENDED))
+ break;
+ }
+
+ if (i == ata_port_max_devices(ap))
+ return 1;
+
+ /* thaw frozen port, resume link and recover failed devices */
+ if ((ap->pflags & ATA_PFLAG_FROZEN) ||
+ (ehc->i.flags & ATA_EHI_RESUME_LINK) || ata_port_nr_enabled(ap))
+ return 0;
+
+ /* skip if class codes for all vacant slots are ATA_DEV_NONE */
+ for (i = 0; i < ATA_MAX_DEVICES; i++) {
+ struct ata_device *dev = &ap->device[i];
+
+ if (dev->class == ATA_DEV_UNKNOWN &&
+ ehc->classes[dev->devno] != ATA_DEV_NONE)
+ return 0;
+ }
+
+ return 1;
+}
+
/**
* ata_eh_recover - recover host port after error
* @ap: host port to recover
+ * @prereset: prereset method (can be NULL)
* @softreset: softreset method (can be NULL)
* @hardreset: hardreset method (can be NULL)
* @postreset: postreset method (can be NULL)
*
* This is the alpha and omega, eum and yang, heart and soul of
* libata exception handling. On entry, actions required to
- * recover each devices are recorded in eh_context. This
- * function executes all the operations with appropriate retrials
- * and fallbacks to resurrect failed devices.
+ * recover the port and hotplug requests are recorded in
+ * eh_context. This function executes all the operations with
+ * appropriate retrials and fallbacks to resurrect failed
+ * devices, detach goners and greet newcomers.
*
* LOCKING:
* Kernel thread context (may sleep).
* RETURNS:
* 0 on success, -errno on failure.
*/
-static int ata_eh_recover(struct ata_port *ap, ata_reset_fn_t softreset,
- ata_reset_fn_t hardreset,
+static int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
+ ata_reset_fn_t softreset, ata_reset_fn_t hardreset,
ata_postreset_fn_t postreset)
{
struct ata_eh_context *ehc = &ap->eh_context;
dev = &ap->device[i];
ehc->tries[dev->devno] = ATA_EH_DEV_TRIES;
+
+ /* process hotplug request */
+ if (dev->flags & ATA_DFLAG_DETACH)
+ ata_eh_detach_dev(dev);
+
+ if (!ata_dev_enabled(dev) &&
+ ((ehc->i.probe_mask & (1 << dev->devno)) &&
+ !(ehc->did_probe_mask & (1 << dev->devno)))) {
+ ata_eh_detach_dev(dev);
+ ata_dev_init(dev);
+ ehc->did_probe_mask |= (1 << dev->devno);
+ ehc->i.action |= ATA_EH_SOFTRESET;
+ }
}
retry:
down_xfermask = 0;
rc = 0;
+ /* if UNLOADING, finish immediately */
+ if (ap->pflags & ATA_PFLAG_UNLOADING)
+ goto out;
+
+ /* prep for resume */
+ ata_eh_prep_resume(ap);
+
/* skip EH if possible. */
- if (!ata_port_nr_enabled(ap) && !(ap->flags & ATA_FLAG_FROZEN))
+ if (ata_eh_skip_recovery(ap))
ehc->i.action = 0;
+ for (i = 0; i < ATA_MAX_DEVICES; i++)
+ ehc->classes[i] = ATA_DEV_UNKNOWN;
+
/* reset */
if (ehc->i.action & ATA_EH_RESET_MASK) {
ata_eh_freeze_port(ap);
- rc = ata_eh_reset(ap, softreset, hardreset, postreset);
+ rc = ata_eh_reset(ap, ata_port_nr_vacant(ap), prereset,
+ softreset, hardreset, postreset);
if (rc) {
ata_port_printk(ap, KERN_ERR,
"reset failed, giving up\n");
ata_eh_thaw_port(ap);
}
- /* revalidate existing devices */
- rc = ata_eh_revalidate(ap, &dev);
+ /* revalidate existing devices and attach new ones */
+ rc = ata_eh_revalidate_and_attach(ap, &dev);
+ if (rc)
+ goto dev_fail;
+
+ /* resume devices */
+ rc = ata_eh_resume(ap, &dev);
if (rc)
goto dev_fail;
}
}
+ /* suspend devices */
+ rc = ata_eh_suspend(ap, &dev);
+ if (rc)
+ goto dev_fail;
+
goto out;
dev_fail:
switch (rc) {
case -ENODEV:
+ /* device missing, schedule probing */
+ ehc->i.probe_mask |= (1 << dev->devno);
case -EINVAL:
ehc->tries[dev->devno] = 0;
break;
ehc->tries[dev->devno] = 0;
}
- /* disable device if it has used up all its chances */
- if (ata_dev_enabled(dev) && !ehc->tries[dev->devno])
+ if (ata_dev_enabled(dev) && !ehc->tries[dev->devno]) {
+ /* disable device if it has used up all its chances */
ata_dev_disable(dev);
- /* soft didn't work? be haaaaard */
- if (ehc->i.flags & ATA_EHI_DID_RESET)
- ehc->i.action |= ATA_EH_HARDRESET;
- else
- ehc->i.action |= ATA_EH_SOFTRESET;
+ /* detach if offline */
+ if (ata_port_offline(ap))
+ ata_eh_detach_dev(dev);
+
+ /* probe if requested */
+ if ((ehc->i.probe_mask & (1 << dev->devno)) &&
+ !(ehc->did_probe_mask & (1 << dev->devno))) {
+ ata_eh_detach_dev(dev);
+ ata_dev_init(dev);
+
+ ehc->tries[dev->devno] = ATA_EH_DEV_TRIES;
+ ehc->did_probe_mask |= (1 << dev->devno);
+ ehc->i.action |= ATA_EH_SOFTRESET;
+ }
+ } else {
+ /* soft didn't work? be haaaaard */
+ if (ehc->i.flags & ATA_EHI_DID_RESET)
+ ehc->i.action |= ATA_EH_HARDRESET;
+ else
+ ehc->i.action |= ATA_EH_SOFTRESET;
+ }
if (ata_port_nr_enabled(ap)) {
ata_port_printk(ap, KERN_WARNING, "failed to recover some "
/**
* ata_do_eh - do standard error handling
* @ap: host port to handle error for
+ * @prereset: prereset method (can be NULL)
* @softreset: softreset method (can be NULL)
* @hardreset: hardreset method (can be NULL)
* @postreset: postreset method (can be NULL)
* LOCKING:
* Kernel thread context (may sleep).
*/
-void ata_do_eh(struct ata_port *ap, ata_reset_fn_t softreset,
- ata_reset_fn_t hardreset, ata_postreset_fn_t postreset)
+void ata_do_eh(struct ata_port *ap, ata_prereset_fn_t prereset,
+ ata_reset_fn_t softreset, ata_reset_fn_t hardreset,
+ ata_postreset_fn_t postreset)
{
ata_eh_autopsy(ap);
ata_eh_report(ap);
- ata_eh_recover(ap, softreset, hardreset, postreset);
+ 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);
+}