]> nv-tegra.nvidia Code Review - linux-2.6.git/blobdiff - drivers/ata/libata-eh.c
mfd: Add pm8xxx irq support
[linux-2.6.git] / drivers / ata / libata-eh.c
index 06a4db1ec10ef376dac93d8e07f19cba5cf15251..dfb6e9d3d759921419d8713fc7c48764ad943207 100644 (file)
@@ -462,6 +462,41 @@ static void ata_eh_clear_action(struct ata_link *link, struct ata_device *dev,
        }
 }
 
+/**
+ *     ata_eh_acquire - acquire EH ownership
+ *     @ap: ATA port to acquire EH ownership for
+ *
+ *     Acquire EH ownership for @ap.  This is the basic exclusion
+ *     mechanism for ports sharing a host.  Only one port hanging off
+ *     the same host can claim the ownership of EH.
+ *
+ *     LOCKING:
+ *     EH context.
+ */
+void ata_eh_acquire(struct ata_port *ap)
+{
+       mutex_lock(&ap->host->eh_mutex);
+       WARN_ON_ONCE(ap->host->eh_owner);
+       ap->host->eh_owner = current;
+}
+
+/**
+ *     ata_eh_release - release EH ownership
+ *     @ap: ATA port to release EH ownership for
+ *
+ *     Release EH ownership for @ap if the caller.  The caller must
+ *     have acquired EH ownership using ata_eh_acquire() previously.
+ *
+ *     LOCKING:
+ *     EH context.
+ */
+void ata_eh_release(struct ata_port *ap)
+{
+       WARN_ON_ONCE(ap->host->eh_owner != current);
+       ap->host->eh_owner = NULL;
+       mutex_unlock(&ap->host->eh_mutex);
+}
+
 /**
  *     ata_scsi_timed_out - SCSI layer time out callback
  *     @cmd: timed out SCSI command
@@ -552,11 +587,43 @@ static void ata_eh_unload(struct ata_port *ap)
 void ata_scsi_error(struct Scsi_Host *host)
 {
        struct ata_port *ap = ata_shost_to_port(host);
-       int i;
        unsigned long flags;
+       LIST_HEAD(eh_work_q);
 
        DPRINTK("ENTER\n");
 
+       spin_lock_irqsave(host->host_lock, flags);
+       list_splice_init(&host->eh_cmd_q, &eh_work_q);
+       spin_unlock_irqrestore(host->host_lock, flags);
+
+       ata_scsi_cmd_error_handler(host, ap, &eh_work_q);
+
+       /* If we timed raced normal completion and there is nothing to
+          recover nr_timedout == 0 why exactly are we doing error recovery ? */
+       ata_scsi_port_error_handler(host, ap);
+
+       /* finish or retry handled scmd's and clean up */
+       WARN_ON(host->host_failed || !list_empty(&eh_work_q));
+
+       DPRINTK("EXIT\n");
+}
+
+/**
+ * ata_scsi_cmd_error_handler - error callback for a list of commands
+ * @host:      scsi host containing the port
+ * @ap:                ATA port within the host
+ * @eh_work_q: list of commands to process
+ *
+ * process the given list of commands and return those finished to the
+ * ap->eh_done_q.  This function is the first part of the libata error
+ * handler which processes a given list of failed commands.
+ */
+void ata_scsi_cmd_error_handler(struct Scsi_Host *host, struct ata_port *ap,
+                               struct list_head *eh_work_q)
+{
+       int i;
+       unsigned long flags;
+
        /* make sure sff pio task is not running */
        ata_sff_flush_pio_task(ap);
 
@@ -592,7 +659,7 @@ void ata_scsi_error(struct Scsi_Host *host)
                if (ap->ops->lost_interrupt)
                        ap->ops->lost_interrupt(ap);
 
-               list_for_each_entry_safe(scmd, tmp, &host->eh_cmd_q, eh_entry) {
+               list_for_each_entry_safe(scmd, tmp, eh_work_q, eh_entry) {
                        struct ata_queued_cmd *qc;
 
                        for (i = 0; i < ATA_MAX_QUEUE; i++) {
@@ -636,14 +703,28 @@ void ata_scsi_error(struct Scsi_Host *host)
        } else
                spin_unlock_wait(ap->lock);
 
-       /* If we timed raced normal completion and there is nothing to
-          recover nr_timedout == 0 why exactly are we doing error recovery ? */
+}
+EXPORT_SYMBOL(ata_scsi_cmd_error_handler);
+
+/**
+ * ata_scsi_port_error_handler - recover the port after the commands
+ * @host:      SCSI host containing the port
+ * @ap:                the ATA port
+ *
+ * Handle the recovery of the port @ap after all the commands
+ * have been recovered.
+ */
+void ata_scsi_port_error_handler(struct Scsi_Host *host, struct ata_port *ap)
+{
+       unsigned long flags;
 
- repeat:
        /* invoke error handler */
        if (ap->ops->error_handler) {
                struct ata_link *link;
 
+               /* acquire EH ownership */
+               ata_eh_acquire(ap);
+ repeat:
                /* kill fast drain timer */
                del_timer_sync(&ap->fastdrain_timer);
 
@@ -690,7 +771,7 @@ void ata_scsi_error(struct Scsi_Host *host)
                /* process port suspend request */
                ata_eh_handle_port_suspend(ap);
 
-               /* Exception might have happend after ->error_handler
+               /* Exception might have happened after ->error_handler
                 * recovered the port but before this point.  Repeat
                 * EH in such case.
                 */
@@ -718,14 +799,12 @@ void ata_scsi_error(struct Scsi_Host *host)
                host->host_eh_scheduled = 0;
 
                spin_unlock_irqrestore(ap->lock, flags);
+               ata_eh_release(ap);
        } else {
                WARN_ON(ata_qc_from_tag(ap, ap->link.active_tag) == NULL);
                ap->ops->eng_timeout(ap);
        }
 
-       /* finish or retry handled scmd's and clean up */
-       WARN_ON(host->host_failed || !list_empty(&host->eh_cmd_q));
-
        scsi_eh_flush_done_q(&ap->eh_done_q);
 
        /* clean up */
@@ -746,9 +825,8 @@ void ata_scsi_error(struct Scsi_Host *host)
        wake_up_all(&ap->eh_wait_q);
 
        spin_unlock_irqrestore(ap->lock, flags);
-
-       DPRINTK("EXIT\n");
 }
+EXPORT_SYMBOL_GPL(ata_scsi_port_error_handler);
 
 /**
  *     ata_port_wait_eh - Wait for the currently pending EH to complete
@@ -779,7 +857,7 @@ void ata_port_wait_eh(struct ata_port *ap)
 
        /* make sure SCSI EH is complete */
        if (scsi_host_in_recovery(ap->scsi_host)) {
-               msleep(10);
+               ata_msleep(ap, 10);
                goto retry;
        }
 }
@@ -1580,7 +1658,7 @@ static void ata_eh_analyze_serror(struct ata_link *link)
         * host links.  For disabled PMP links, only N bit is
         * considered as X bit is left at 1 for link plugging.
         */
-       if (link->lpm_policy != ATA_LPM_MAX_POWER)
+       if (link->lpm_policy > ATA_LPM_MAX_POWER)
                hotplug_mask = 0;       /* hotplug doesn't work w/ LPM */
        else if (!(link->flags & ATA_LFLAG_DISABLED) || ata_is_host_link(link))
                hotplug_mask = SERR_PHYRDY_CHG | SERR_DEV_XCHG;
@@ -1664,7 +1742,7 @@ void ata_eh_analyze_ncq_error(struct ata_link *link)
  *
  *     Analyze taskfile of @qc and further determine cause of
  *     failure.  This function also requests ATAPI sense data if
- *     avaliable.
+ *     available.
  *
  *     LOCKING:
  *     Kernel thread context (may sleep).
@@ -1815,7 +1893,7 @@ static int speed_down_verdict_cb(struct ata_ering_entry *ent, void *void_arg)
  *        occurred during last 5 mins, NCQ_OFF.
  *
  *     3. If more than 8 ATA_BUS, TOUT_HSM or UNK_DEV errors
- *        ocurred during last 5 mins, FALLBACK_TO_PIO
+ *        occurred during last 5 mins, FALLBACK_TO_PIO
  *
  *     4. If more than 3 TOUT_HSM or UNK_DEV errors occurred
  *        during last 10 mins, NCQ_OFF.
@@ -2499,7 +2577,7 @@ int ata_eh_reset(struct ata_link *link, int classify,
        if (link->flags & ATA_LFLAG_NO_SRST)
                softreset = NULL;
 
-       /* make sure each reset attemp is at least COOL_DOWN apart */
+       /* make sure each reset attempt is at least COOL_DOWN apart */
        if (ehc->i.flags & ATA_EHI_DID_RESET) {
                now = jiffies;
                WARN_ON(time_after(ehc->last_reset, now));
@@ -2658,7 +2736,7 @@ int ata_eh_reset(struct ata_link *link, int classify,
                        if (!reset) {
                                ata_link_printk(link, KERN_ERR,
                                                "follow-up softreset required "
-                                               "but no softreset avaliable\n");
+                                               "but no softreset available\n");
                                failed_link = link;
                                rc = -EINVAL;
                                goto fail;
@@ -2818,8 +2896,10 @@ int ata_eh_reset(struct ata_link *link, int classify,
                        "reset failed (errno=%d), retrying in %u secs\n",
                        rc, DIV_ROUND_UP(jiffies_to_msecs(delta), 1000));
 
+               ata_eh_release(ap);
                while (delta)
                        delta = schedule_timeout_uninterruptible(delta);
+               ata_eh_acquire(ap);
        }
 
        if (try == max_tries - 1) {
@@ -3235,6 +3315,8 @@ static int ata_eh_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
        struct ata_port *ap = ata_is_host_link(link) ? link->ap : NULL;
        struct ata_eh_context *ehc = &link->eh_context;
        struct ata_device *dev, *link_dev = NULL, *lpm_dev = NULL;
+       enum ata_lpm_policy old_policy = link->lpm_policy;
+       bool no_dipm = link->ap->flags & ATA_FLAG_NO_DIPM;
        unsigned int hints = ATA_LPM_EMPTY | ATA_LPM_HIPM;
        unsigned int err_mask;
        int rc;
@@ -3251,7 +3333,7 @@ static int ata_eh_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
         */
        ata_for_each_dev(dev, link, ENABLED) {
                bool hipm = ata_id_has_hipm(dev->id);
-               bool dipm = ata_id_has_dipm(dev->id);
+               bool dipm = ata_id_has_dipm(dev->id) && !no_dipm;
 
                /* find the first enabled and LPM enabled devices */
                if (!link_dev)
@@ -3298,9 +3380,18 @@ static int ata_eh_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
                goto fail;
        }
 
+       /*
+        * Low level driver acked the transition.  Issue DIPM command
+        * with the new policy set.
+        */
+       link->lpm_policy = policy;
+       if (ap && ap->slave_link)
+               ap->slave_link->lpm_policy = policy;
+
        /* host config updated, enable DIPM if transitioning to MIN_POWER */
        ata_for_each_dev(dev, link, ENABLED) {
-               if (policy == ATA_LPM_MIN_POWER && ata_id_has_dipm(dev->id)) {
+               if (policy == ATA_LPM_MIN_POWER && !no_dipm &&
+                   ata_id_has_dipm(dev->id)) {
                        err_mask = ata_dev_set_feature(dev,
                                        SETFEATURES_SATA_ENABLE, SATA_DIPM);
                        if (err_mask && err_mask != AC_ERR_DEV) {
@@ -3313,12 +3404,14 @@ static int ata_eh_set_lpm(struct ata_link *link, enum ata_lpm_policy policy,
                }
        }
 
-       link->lpm_policy = policy;
-       if (ap && ap->slave_link)
-               ap->slave_link->lpm_policy = policy;
        return 0;
 
 fail:
+       /* restore the old policy */
+       link->lpm_policy = old_policy;
+       if (ap && ap->slave_link)
+               ap->slave_link->lpm_policy = old_policy;
+
        /* if no device or only one more chance is left, disable LPM */
        if (!dev || ehc->tries[dev->devno] <= 2) {
                ata_link_printk(link, KERN_WARNING,
@@ -3330,7 +3423,7 @@ fail:
        return rc;
 }
 
-static int ata_link_nr_enabled(struct ata_link *link)
+int ata_link_nr_enabled(struct ata_link *link)
 {
        struct ata_device *dev;
        int cnt = 0;
@@ -3635,8 +3728,10 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
                if (time_before_eq(deadline, now))
                        break;
 
+               ata_eh_release(ap);
                deadline = wait_for_completion_timeout(&ap->park_req_pending,
                                                       deadline - now);
+               ata_eh_acquire(ap);
        } while (deadline);
        ata_for_each_link(link, ap, EDGE) {
                ata_for_each_dev(dev, link, ALL) {