Skip to content

Commit

Permalink
[PATCH] libata-hp: implement hotplug
Browse files Browse the repository at this point in the history
Implement ATA part of hotplug.  To avoid probing broken devices over
and over again, disabled devices are not automatically detached.  They
are detached only if probing is requested for the device or the
associated port is offline.  Also, to avoid infinite probing loop,
Each device is probed only once per EH run.

As SATA PHY status is fragile, devices are detached only after it has
used up its recovery chances unless explicitly requested by LLDD or
user (LLDD may request direct detach if, for example, it supports cold
presence detection).

Signed-off-by: Tejun Heo <htejun@gmail.com>
  • Loading branch information
Tejun Heo committed May 31, 2006
1 parent 0ea035a commit 084fe63
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 21 deletions.
123 changes: 102 additions & 21 deletions drivers/scsi/libata-eh.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand All @@ -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);
}
}

Expand All @@ -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
Expand All @@ -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).
Expand All @@ -1568,22 +1615,38 @@ 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:
down_xfermask = 0;
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");
Expand All @@ -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;

Expand All @@ -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;
Expand All @@ -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 "
Expand Down
13 changes: 13 additions & 0 deletions include/linux/libata.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down

0 comments on commit 084fe63

Please sign in to comment.