[PATCH] libata-ncq: update EH to handle NCQ
authorTejun Heo <htejun@gmail.com>
Mon, 15 May 2006 12:03:46 +0000 (21:03 +0900)
committerTejun Heo <htejun@gmail.com>
Mon, 15 May 2006 12:03:46 +0000 (21:03 +0900)
Update EH to handle NCQ.  ata_eh_autopsy() is updated to call
ata_eh_analyze_ncq_error() which reads log page 10h on NCQ device
error and updates eh_context accordingly.  ata_eh_report() is updated
to report SActive.

Signed-off-by: Tejun Heo <htejun@gmail.com>
drivers/scsi/libata-eh.c

index e401f353f84815d5a829d55a37b86bf4df38d6be..7244caff13a05fb7ee2be6da7de4356610a53139 100644 (file)
@@ -684,6 +684,98 @@ static const char * ata_err_string(unsigned int err_mask)
        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
@@ -782,6 +874,66 @@ static void ata_eh_analyze_serror(struct ata_port *ap)
        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->flags & ATA_FLAG_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
@@ -999,6 +1151,9 @@ static void ata_eh_autopsy(struct ata_port *ap)
        } else if (rc != -EOPNOTSUPP)
                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)
                ehc->i.err_mask &= ~AC_ERR_OTHER;
@@ -1093,17 +1248,17 @@ static void ata_eh_report(struct ata_port *ap)
                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);
        }