aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/scsi/libata-eh.c123
-rw-r--r--include/linux/libata.h13
2 files changed, 115 insertions, 21 deletions
diff --git a/drivers/scsi/libata-eh.c b/drivers/scsi/libata-eh.c
index a049bffdf7705e..b53e2e7db498a5 100644
--- a/drivers/scsi/libata-eh.c
+++ b/drivers/scsi/libata-eh.c
@@ -932,10 +932,8 @@ static void ata_eh_analyze_serror(struct ata_port *ap)
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;
@@ -1487,11 +1485,12 @@ static int ata_eh_reset(struct ata_port *ap, int classify,
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");
@@ -1513,6 +1512,23 @@ static int ata_eh_revalidate(struct ata_port *ap,
break;
ehc->i.action &= ~ATA_EH_REVALIDATE;
+ } 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->host_set->lock, flags);
+ ap->flags |= ATA_FLAG_SCSI_HOTPLUG;
+ spin_unlock_irqrestore(&ap->host_set->lock, flags);
}
}
@@ -1533,6 +1549,36 @@ static int ata_port_nr_enabled(struct ata_port *ap)
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;
+
+ if (ap->flags & ATA_FLAG_FROZEN || 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
@@ -1543,9 +1589,10 @@ static int ata_port_nr_enabled(struct ata_port *ap)
*
* 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).
@@ -1568,6 +1615,19 @@ static int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
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:
@@ -1575,15 +1635,18 @@ static int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
rc = 0;
/* 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, 0, prereset, 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");
@@ -1593,8 +1656,8 @@ static int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
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;
@@ -1612,6 +1675,8 @@ static int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
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;
@@ -1624,15 +1689,31 @@ static int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset,
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 "
diff --git a/include/linux/libata.h b/include/linux/libata.h
index a1ceb5b67b9772..56971943d2610f 100644
--- a/include/linux/libata.h
+++ b/include/linux/libata.h
@@ -824,6 +824,19 @@ extern void ata_do_eh(struct ata_port *ap, ata_prereset_fn_t prereset,
(ehi)->desc_len = 0; \
} while (0)
+static inline void ata_ehi_hotplugged(struct ata_eh_info *ehi)
+{
+ if (ehi->flags & ATA_EHI_HOTPLUGGED)
+ return;
+
+ ehi->flags |= ATA_EHI_HOTPLUGGED;
+ ehi->hotplug_timestamp = jiffies;
+
+ ehi->err_mask |= AC_ERR_ATA_BUS;
+ ehi->action |= ATA_EH_SOFTRESET;
+ ehi->probe_mask |= (1 << ATA_MAX_DEVICES) - 1;
+}
+
/*
* qc helpers
*/