summaryrefslogtreecommitdiffstats
path: root/drivers/ata
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ata')
-rw-r--r--drivers/ata/libata-eh.c97
1 files changed, 83 insertions, 14 deletions
diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c
index 359a5ace847..3a2f7ef3e60 100644
--- a/drivers/ata/libata-eh.c
+++ b/drivers/ata/libata-eh.c
@@ -50,16 +50,23 @@ enum {
ATA_EH_SPDN_NCQ_OFF = (1 << 0),
ATA_EH_SPDN_SPEED_DOWN = (1 << 1),
ATA_EH_SPDN_FALLBACK_TO_PIO = (1 << 2),
+ ATA_EH_SPDN_KEEP_ERRORS = (1 << 3),
/* error flags */
ATA_EFLAG_IS_IO = (1 << 0),
+ ATA_EFLAG_DUBIOUS_XFER = (1 << 1),
/* error categories */
ATA_ECAT_NONE = 0,
ATA_ECAT_ATA_BUS = 1,
ATA_ECAT_TOUT_HSM = 2,
ATA_ECAT_UNK_DEV = 3,
- ATA_ECAT_NR = 4,
+ ATA_ECAT_DUBIOUS_ATA_BUS = 4,
+ ATA_ECAT_DUBIOUS_TOUT_HSM = 5,
+ ATA_ECAT_DUBIOUS_UNK_DEV = 6,
+ ATA_ECAT_NR = 7,
+
+ ATA_ECAT_DUBIOUS_BASE = ATA_ECAT_DUBIOUS_ATA_BUS,
};
/* Waiting in ->prereset can never be reliable. It's sometimes nice
@@ -245,6 +252,15 @@ static void ata_ering_record(struct ata_ering *ering, unsigned int eflags,
ent->timestamp = get_jiffies_64();
}
+static struct ata_ering_entry *ata_ering_top(struct ata_ering *ering)
+{
+ struct ata_ering_entry *ent = &ering->ring[ering->cursor];
+
+ if (ent->err_mask)
+ return ent;
+ return NULL;
+}
+
static void ata_ering_clear(struct ata_ering *ering)
{
memset(ering, 0, sizeof(*ering));
@@ -1473,20 +1489,29 @@ static unsigned int ata_eh_analyze_tf(struct ata_queued_cmd *qc,
return action;
}
-static int ata_eh_categorize_error(unsigned int eflags, unsigned int err_mask)
+static int ata_eh_categorize_error(unsigned int eflags, unsigned int err_mask,
+ int *xfer_ok)
{
+ int base = 0;
+
+ if (!(eflags & ATA_EFLAG_DUBIOUS_XFER))
+ *xfer_ok = 1;
+
+ if (!*xfer_ok)
+ base = ATA_ECAT_DUBIOUS_BASE;
+
if (err_mask & AC_ERR_ATA_BUS)
- return ATA_ECAT_ATA_BUS;
+ return base + ATA_ECAT_ATA_BUS;
if (err_mask & AC_ERR_TIMEOUT)
- return ATA_ECAT_TOUT_HSM;
+ return base + ATA_ECAT_TOUT_HSM;
if (eflags & ATA_EFLAG_IS_IO) {
if (err_mask & AC_ERR_HSM)
- return ATA_ECAT_TOUT_HSM;
+ return base + ATA_ECAT_TOUT_HSM;
if ((err_mask &
(AC_ERR_DEV|AC_ERR_MEDIA|AC_ERR_INVALID)) == AC_ERR_DEV)
- return ATA_ECAT_UNK_DEV;
+ return base + ATA_ECAT_UNK_DEV;
}
return 0;
@@ -1494,18 +1519,22 @@ static int ata_eh_categorize_error(unsigned int eflags, unsigned int err_mask)
struct speed_down_verdict_arg {
u64 since;
+ int xfer_ok;
int nr_errors[ATA_ECAT_NR];
};
static int speed_down_verdict_cb(struct ata_ering_entry *ent, void *void_arg)
{
struct speed_down_verdict_arg *arg = void_arg;
- int cat = ata_eh_categorize_error(ent->eflags, ent->err_mask);
+ int cat;
if (ent->timestamp < arg->since)
return -1;
+ cat = ata_eh_categorize_error(ent->eflags, ent->err_mask,
+ &arg->xfer_ok);
arg->nr_errors[cat]++;
+
return 0;
}
@@ -1524,6 +1553,9 @@ static int speed_down_verdict_cb(struct ata_ering_entry *ent, void *void_arg)
*
* ECAT_UNK_DEV : Unknown DEV error for IO commands
*
+ * ECAT_DUBIOUS_* : Identical to above three but occurred while
+ * data transfer hasn't been verified.
+ *
* Verdicts are
*
* NCQ_OFF : Turn off NCQ.
@@ -1534,15 +1566,27 @@ static int speed_down_verdict_cb(struct ata_ering_entry *ent, void *void_arg)
* FALLBACK_TO_PIO : Fall back to PIO.
*
* Even if multiple verdicts are returned, only one action is
- * taken per error. ering is cleared after an action is taken.
+ * taken per error. An action triggered by non-DUBIOUS errors
+ * clears ering, while one triggered by DUBIOUS_* errors doesn't.
+ * This is to expedite speed down decisions right after device is
+ * initially configured.
+ *
+ * The followings are speed down rules. #1 and #2 deal with
+ * DUBIOUS errors.
*
- * 1. If more than 6 ATA_BUS, TOUT_HSM or UNK_DEV errors
+ * 1. If more than one DUBIOUS_ATA_BUS or DUBIOUS_TOUT_HSM errors
+ * occurred during last 5 mins, SPEED_DOWN and FALLBACK_TO_PIO.
+ *
+ * 2. If more than one DUBIOUS_TOUT_HSM or DUBIOUS_UNK_DEV errors
+ * 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
*
- * 2. If more than 3 TOUT_HSM or UNK_DEV errors occurred
+ * 4. If more than 3 TOUT_HSM or UNK_DEV errors occurred
* during last 10 mins, NCQ_OFF.
*
- * 3. If more than 3 ATA_BUS or TOUT_HSM errors, or more than 6
+ * 5. If more than 3 ATA_BUS or TOUT_HSM errors, or more than 6
* UNK_DEV errors occurred during last 10 mins, SPEED_DOWN.
*
* LOCKING:
@@ -1563,6 +1607,15 @@ static unsigned int ata_eh_speed_down_verdict(struct ata_device *dev)
arg.since = j64 - min(j64, j5mins);
ata_ering_map(&dev->ering, speed_down_verdict_cb, &arg);
+ if (arg.nr_errors[ATA_ECAT_DUBIOUS_ATA_BUS] +
+ arg.nr_errors[ATA_ECAT_DUBIOUS_TOUT_HSM] > 1)
+ verdict |= ATA_EH_SPDN_SPEED_DOWN |
+ ATA_EH_SPDN_FALLBACK_TO_PIO | ATA_EH_SPDN_KEEP_ERRORS;
+
+ if (arg.nr_errors[ATA_ECAT_DUBIOUS_TOUT_HSM] +
+ arg.nr_errors[ATA_ECAT_DUBIOUS_UNK_DEV] > 1)
+ verdict |= ATA_EH_SPDN_NCQ_OFF | ATA_EH_SPDN_KEEP_ERRORS;
+
if (arg.nr_errors[ATA_ECAT_ATA_BUS] +
arg.nr_errors[ATA_ECAT_TOUT_HSM] +
arg.nr_errors[ATA_ECAT_UNK_DEV] > 6)
@@ -1606,11 +1659,12 @@ static unsigned int ata_eh_speed_down(struct ata_device *dev,
unsigned int eflags, unsigned int err_mask)
{
struct ata_link *link = dev->link;
+ int xfer_ok = 0;
unsigned int verdict;
unsigned int action = 0;
/* don't bother if Cat-0 error */
- if (ata_eh_categorize_error(eflags, err_mask) == 0)
+ if (ata_eh_categorize_error(eflags, err_mask, &xfer_ok) == 0)
return 0;
/* record error and determine whether speed down is necessary */
@@ -1673,7 +1727,8 @@ static unsigned int ata_eh_speed_down(struct ata_device *dev,
return 0;
done:
/* device has been slowed down, blow error history */
- ata_ering_clear(&dev->ering);
+ if (!(verdict & ATA_EH_SPDN_KEEP_ERRORS))
+ ata_ering_clear(&dev->ering);
return action;
}
@@ -1783,8 +1838,11 @@ static void ata_eh_link_autopsy(struct ata_link *link)
ata_dev_enabled(link->device))))
dev = link->device;
- if (dev)
+ if (dev) {
+ if (dev->flags & ATA_DFLAG_DUBIOUS_XFER)
+ eflags |= ATA_EFLAG_DUBIOUS_XFER;
ehc->i.action |= ata_eh_speed_down(dev, eflags, all_err_mask);
+ }
DPRINTK("EXIT\n");
}
@@ -2390,6 +2448,17 @@ int ata_set_mode(struct ata_link *link, struct ata_device **r_failed_dev)
struct ata_device *dev;
int rc;
+ /* if data transfer is verified, clear DUBIOUS_XFER on ering top */
+ ata_link_for_each_dev(dev, link) {
+ if (!(dev->flags & ATA_DFLAG_DUBIOUS_XFER)) {
+ struct ata_ering_entry *ent;
+
+ ent = ata_ering_top(&dev->ering);
+ if (ent)
+ ent->eflags &= ~ATA_EFLAG_DUBIOUS_XFER;
+ }
+ }
+
/* has private set_mode? */
if (ap->ops->set_mode)
rc = ap->ops->set_mode(link, r_failed_dev);