06_libata_implement-ncq-helpers.patch
This patch implements generic NCQ completion/error-handling
helpers.
Signed-off-by: Tejun Heo <[EMAIL PROTECTED]>
drivers/scsi/libata-core.c | 237 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/libata.h | 3
2 files changed, 240 insertions(+)
Index: work/include/linux/libata.h
===================================================================
--- work.orig/include/linux/libata.h 2005-07-07 22:08:36.000000000 +0900
+++ work/include/linux/libata.h 2005-07-07 22:08:36.000000000 +0900
@@ -463,6 +463,9 @@ extern int ata_read_log_page(struct ata_
unsigned int);
extern void ata_eh_qc_complete(struct ata_queued_cmd *qc);
extern void ata_eh_qc_retry(struct ata_queued_cmd *qc);
+extern int ata_ncq_complete(struct ata_port *ap);
+extern int ata_ncq_error(struct ata_port *ap);
+extern void ata_ncq_recover(struct ata_port *ap, int did_reset);
#ifdef CONFIG_PCI
Index: work/drivers/scsi/libata-core.c
===================================================================
--- work.orig/drivers/scsi/libata-core.c 2005-07-07 22:08:36.000000000
+0900
+++ work/drivers/scsi/libata-core.c 2005-07-07 22:08:36.000000000 +0900
@@ -49,6 +49,10 @@
#include "libata.h"
+#define ata_for_each_tag(tag, mask) \
+ for (tag = find_first_bit(&mask, ATA_MAX_CMDS); tag < ATA_MAX_CMDS; \
+ tag = find_next_bit(&mask, ATA_MAX_CMDS, tag + 1))
+
static unsigned int ata_busy_sleep (struct ata_port *ap,
unsigned long tmout_pat,
unsigned long tmout);
@@ -3861,6 +3865,236 @@ irqreturn_t ata_interrupt (int irq, void
return IRQ_RETVAL(handled);
}
+/*
+ * NCQ helpers
+ */
+
+/**
+ * ata_ncq_complete - NCQ driver helper. Complete requests normally.
+ * @ap: port in question
+ *
+ * Complete in-flight commands. One device per port is assumed.
+ * This functions is meant to be called from specific driver's
+ * interrupt routine to complete requests normally. On
+ * invocation, if non-NCQ command was in-flight, it's completed
+ * normally. If NCQ commands were in-flight, Sactive register is
+ * read and completed commands are processed.
+ *
+ * LOCKING:
+ * spin_lock_irqsave(host_set lock)
+ */
+int ata_ncq_complete(struct ata_port *ap)
+{
+ int nr_done = 0;
+ unsigned long done_mask = 0;
+ unsigned tag;
+
+ /*
+ * ap->active_tag test should come before ap->sactive test to
+ * complete EH requests.
+ */
+ if (ap->active_tag != ATA_TAG_POISON)
+ done_mask = 1 << ap->active_tag;
+ else if (ap->sactive) {
+ unsigned long new_sactive = scr_read(ap, SCR_ACTIVE);
+ done_mask = new_sactive ^ ap->sactive;
+
+ if (unlikely(done_mask & new_sactive)) {
+ printk(KERN_ERR "ata%u: illegal sactive transition
(%08lx->%08lx)\n",
+ ap->id, ap->sactive, new_sactive);
+ done_mask &= ~new_sactive;
+ }
+ }
+
+ ata_for_each_tag(tag, done_mask) {
+ struct ata_queued_cmd *qc = ata_qc_from_tag(ap, tag);
+ assert(qc);
+ ata_qc_complete(qc);
+ nr_done++;
+ }
+
+ return nr_done;
+}
+
+/**
+ * ata_ncq_error - NCQ driver helper. Abort commands and invoke EH.
+ * @ap: port in question.
+ *
+ * Abort in-flight commands and invoke EH. This function is
+ * meant to be called from specific driver's interrupt routine to
+ * indicate error.
+ *
+ * LOCKING:
+ * spin_lock_irqsave(host_set lock)
+ */
+
+int ata_ncq_error(struct ata_port *ap)
+{
+ int nr_aborted = 0;
+ unsigned long sactive = 0;
+ unsigned tag;
+
+ printk(KERN_WARNING "ata%u: aborting commands due to error. "
+ "active_tag %d, sactive %08lx\n",
+ ap->id,
+ ap->active_tag != ATA_TAG_POISON ? ap->active_tag : -1,
+ ap->sactive);
+
+ /*
+ * ap->active_tag test should come before ap->sactive test to
+ * complete EH requests.
+ */
+ if (ap->active_tag != ATA_TAG_POISON)
+ sactive = 1 << ap->active_tag;
+ else if (ap->sactive) {
+ /* Complete successful requests before aborting. */
+ ata_ncq_complete(ap);
+ if (!ap->sactive)
+ printk(KERN_WARNING "ata%u: device reports successful "
+ "completion of all NCQ commands after error\n",
+ ap->id);
+ sactive = ap->sactive;
+ }
+
+ ata_for_each_tag(tag, sactive) {
+ struct ata_queued_cmd *qc = ata_qc_from_tag(ap, tag);
+ assert(qc);
+ ata_qc_error(qc);
+ nr_aborted++;
+ }
+
+ return nr_aborted;
+}
+
+static inline int ata_read_log_10h(struct ata_port *ap, unsigned *tagp,
+ u8 *drv_statp, u8 *drv_errp)
+{
+ char *buffer;
+ int rc;
+
+ buffer = kmalloc(512, GFP_KERNEL);
+ if (buffer == NULL) {
+ printk(KERN_ERR "ata%u: unable to allocate memory for error\n",
+ ap->id);
+ return -ENOMEM;
+ }
+
+ rc = ata_read_log_page(ap, 0, READ_LOG_SATA_NCQ_PAGE, buffer, 1);
+ if (rc < 0) {
+ printk(KERN_ERR "ata%u: failed to read log page 10h (%d)\n",
+ ap->id, rc);
+ goto out;
+ }
+
+ if (buffer[0] & 0x80) {
+ printk(KERN_WARNING "ata%u: NQ bit set on log page 10h\n",
+ ap->id);
+ rc = -EIO;
+ goto out;
+ }
+
+ *tagp = buffer[0] & 0x1f;
+ *drv_statp = buffer[2] | ATA_ERR;
+ *drv_errp = buffer[3];
+ rc = 0;
+ out:
+ kfree(buffer);
+ return rc;
+}
+
+/**
+ * ata_ncq_recover - NCQ driver helper. Recover from error.
+ * @ap: port in question
+ * @did_reset: specific driver performed reset. Log page 10h might
+ * be invalid.
+ *
+ * This function is to be called from eng_timeout routine of
+ * specific drivers. Before calling this function, specific
+ * drivers are required to
+ *
+ * - Clear all in-flight requests. Drivers only have to clear
+ * low-level state (like stopping DMA engine and clearing
+ * interrupts). All generic command cancelling are dealt by
+ * libata-core layer.
+ *
+ * - Make the controller ready for new commands.
+ *
+ * LOCKING:
+ * Inherited from SCSI layer (in EH context, can sleep)
+ */
+void ata_ncq_recover(struct ata_port *ap, int did_reset)
+{
+ unsigned ncq_abort_tag = ATA_TAG_POISON;
+ u8 stat = 0, err = 0;
+ unsigned long sactive;
+ unsigned tag;
+
+ DPRINTK("ENTER\n");
+
+ stat = ata_chk_status(ap);
+ err = ata_chk_err(ap);
+
+ /*
+ * if commands have timed out or DRQ/BSY is set, device needs
+ * to be reset.
+ */
+ if (!(ap->flags & ATA_FLAG_ERROR) || stat & (ATA_BUSY | ATA_DRQ)) {
+ printk(KERN_WARNING "ata%u: stat=%x, issuing COMRESET\n",
ap->id, stat);
+ ap->ops->phy_reset(ap);
+ did_reset = 1;
+ }
+
+ if (ap->sactive) {
+ if (ap->flags & ATA_FLAG_ERROR) {
+ u8 tstat, terr;
+ /*
+ * Regardless of did_reset, we read log page
+ * 10h. The drive might need it to restart
+ * operation. If did_reset, we ignore the
+ * result.
+ */
+ if (ata_read_log_10h(ap, &tag, &tstat, &terr) == 0) {
+ printk(KERN_INFO "ata%u: log_ext_10h, tag=%d "
+ "stat=%02x err=%02x%s\n",
+ ap->id, tag, tstat, terr,
+ did_reset ? " (ignored due to reset)" :
"");
+ if (!did_reset) {
+ ncq_abort_tag = tag;
+ stat = tstat;
+ err = terr;
+ }
+ } else {
+ printk(KERN_WARNING "ata%u: resetting...\n",
+ ap->id);
+ ap->ops->phy_reset(ap);
+ }
+ }
+ sactive = ap->sactive;
+ } else
+ sactive = 1 << ap->active_tag;
+
+ ata_for_each_tag(tag, sactive) {
+ struct ata_queued_cmd *qc = ata_qc_from_tag(ap, tag);
+ assert(qc);
+ if (ncq_abort_tag != ATA_TAG_POISON && tag != ncq_abort_tag) {
+ ata_eh_qc_retry(qc);
+ continue;
+ }
+
+ if (qc->scsicmd) {
+ if (qc->dev->class == ATA_DEV_ATA ||
+ !(qc->flags & ATA_QCFLAG_ERROR))
+ ata_to_sense_error(qc, stat | ATA_ERR, err);
+ else
+ atapi_request_sense(ap, qc->dev, qc->scsicmd);
+ }
+
+ ata_eh_qc_complete(qc);
+ }
+
+ DPRINTK("EXIT\n");
+}
+
/**
* atapi_packet_task - Write CDB bytes to hardware
* @_data: Port to which ATAPI device is attached.
@@ -4719,6 +4953,9 @@ EXPORT_SYMBOL_GPL(ata_scsi_requeue);
EXPORT_SYMBOL_GPL(ata_read_log_page);
EXPORT_SYMBOL_GPL(ata_eh_qc_complete);
EXPORT_SYMBOL_GPL(ata_eh_qc_retry);
+EXPORT_SYMBOL_GPL(ata_ncq_complete);
+EXPORT_SYMBOL_GPL(ata_ncq_error);
+EXPORT_SYMBOL_GPL(ata_ncq_recover);
#ifdef CONFIG_PCI
EXPORT_SYMBOL_GPL(pci_test_config_bits);
-
To unsubscribe from this list: send the line "unsubscribe linux-ide" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at http://vger.kernel.org/majordomo-info.html