summaryrefslogtreecommitdiff
path: root/drivers/ata/libahci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ata/libahci.c')
-rw-r--r--drivers/ata/libahci.c171
1 files changed, 132 insertions, 39 deletions
diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
index 29acc35bf4a6..8f216de76648 100644
--- a/drivers/ata/libahci.c
+++ b/drivers/ata/libahci.c
@@ -55,7 +55,8 @@ static ssize_t ahci_transmit_led_message(struct ata_port *ap, u32 state,
static int ahci_scr_read(struct ata_link *link, unsigned int sc_reg, u32 *val);
static int ahci_scr_write(struct ata_link *link, unsigned int sc_reg, u32 val);
-static bool ahci_qc_fill_rtf(struct ata_queued_cmd *qc);
+static void ahci_qc_fill_rtf(struct ata_queued_cmd *qc);
+static void ahci_qc_ncq_fill_rtf(struct ata_port *ap, u64 done_mask);
static int ahci_port_start(struct ata_port *ap);
static void ahci_port_stop(struct ata_port *ap);
static enum ata_completion_errors ahci_qc_prep(struct ata_queued_cmd *qc);
@@ -157,6 +158,7 @@ struct ata_port_operations ahci_ops = {
.qc_prep = ahci_qc_prep,
.qc_issue = ahci_qc_issue,
.qc_fill_rtf = ahci_qc_fill_rtf,
+ .qc_ncq_fill_rtf = ahci_qc_ncq_fill_rtf,
.freeze = ahci_freeze,
.thaw = ahci_thaw,
@@ -1847,18 +1849,47 @@ static void ahci_error_intr(struct ata_port *ap, u32 irq_stat)
ata_port_abort(ap);
}
-static void ahci_handle_port_interrupt(struct ata_port *ap,
- void __iomem *port_mmio, u32 status)
+static void ahci_qc_complete(struct ata_port *ap, void __iomem *port_mmio)
{
struct ata_eh_info *ehi = &ap->link.eh_info;
struct ahci_port_priv *pp = ap->private_data;
- struct ahci_host_priv *hpriv = ap->host->private_data;
- int resetting = !!(ap->pflags & ATA_PFLAG_RESETTING);
u32 qc_active = 0;
int rc;
+ /*
+ * pp->active_link is not reliable once FBS is enabled, both
+ * PORT_SCR_ACT and PORT_CMD_ISSUE should be checked because
+ * NCQ and non-NCQ commands may be in flight at the same time.
+ */
+ if (pp->fbs_enabled) {
+ if (ap->qc_active) {
+ qc_active = readl(port_mmio + PORT_SCR_ACT);
+ qc_active |= readl(port_mmio + PORT_CMD_ISSUE);
+ }
+ } else {
+ /* pp->active_link is valid iff any command is in flight */
+ if (ap->qc_active && pp->active_link->sactive)
+ qc_active = readl(port_mmio + PORT_SCR_ACT);
+ else
+ qc_active = readl(port_mmio + PORT_CMD_ISSUE);
+ }
+
+ rc = ata_qc_complete_multiple(ap, qc_active);
+ if (unlikely(rc < 0 && !(ap->pflags & ATA_PFLAG_RESETTING))) {
+ ehi->err_mask |= AC_ERR_HSM;
+ ehi->action |= ATA_EH_RESET;
+ ata_port_freeze(ap);
+ }
+}
+
+static void ahci_handle_port_interrupt(struct ata_port *ap,
+ void __iomem *port_mmio, u32 status)
+{
+ struct ahci_port_priv *pp = ap->private_data;
+ struct ahci_host_priv *hpriv = ap->host->private_data;
+
/* ignore BAD_PMP while resetting */
- if (unlikely(resetting))
+ if (unlikely(ap->pflags & ATA_PFLAG_RESETTING))
status &= ~PORT_IRQ_BAD_PMP;
if (sata_lpm_ignore_phy_events(&ap->link)) {
@@ -1867,6 +1898,12 @@ static void ahci_handle_port_interrupt(struct ata_port *ap,
}
if (unlikely(status & PORT_IRQ_ERROR)) {
+ /*
+ * Before getting the error notification, we may have
+ * received SDB FISes notifying successful completions.
+ * Handle these first and then handle the error.
+ */
+ ahci_qc_complete(ap, port_mmio);
ahci_error_intr(ap, status);
return;
}
@@ -1903,32 +1940,8 @@ static void ahci_handle_port_interrupt(struct ata_port *ap,
}
}
- /* pp->active_link is not reliable once FBS is enabled, both
- * PORT_SCR_ACT and PORT_CMD_ISSUE should be checked because
- * NCQ and non-NCQ commands may be in flight at the same time.
- */
- if (pp->fbs_enabled) {
- if (ap->qc_active) {
- qc_active = readl(port_mmio + PORT_SCR_ACT);
- qc_active |= readl(port_mmio + PORT_CMD_ISSUE);
- }
- } else {
- /* pp->active_link is valid iff any command is in flight */
- if (ap->qc_active && pp->active_link->sactive)
- qc_active = readl(port_mmio + PORT_SCR_ACT);
- else
- qc_active = readl(port_mmio + PORT_CMD_ISSUE);
- }
-
-
- rc = ata_qc_complete_multiple(ap, qc_active);
-
- /* while resetting, invalid completions are expected */
- if (unlikely(rc < 0 && !resetting)) {
- ehi->err_mask |= AC_ERR_HSM;
- ehi->action |= ATA_EH_RESET;
- ata_port_freeze(ap);
- }
+ /* Handle completed commands */
+ ahci_qc_complete(ap, port_mmio);
}
static void ahci_port_intr(struct ata_port *ap)
@@ -2053,11 +2066,18 @@ unsigned int ahci_qc_issue(struct ata_queued_cmd *qc)
}
EXPORT_SYMBOL_GPL(ahci_qc_issue);
-static bool ahci_qc_fill_rtf(struct ata_queued_cmd *qc)
+static void ahci_qc_fill_rtf(struct ata_queued_cmd *qc)
{
struct ahci_port_priv *pp = qc->ap->private_data;
u8 *rx_fis = pp->rx_fis;
+ /*
+ * rtf may already be filled (e.g. for successful NCQ commands).
+ * If that is the case, we have nothing to do.
+ */
+ if (qc->flags & ATA_QCFLAG_RTF_FILLED)
+ return;
+
if (pp->fbs_enabled)
rx_fis += qc->dev->link->pmp * AHCI_RX_FIS_SZ;
@@ -2068,9 +2088,12 @@ static bool ahci_qc_fill_rtf(struct ata_queued_cmd *qc)
* Setup FIS.
*/
if (qc->tf.protocol == ATA_PROT_PIO && qc->dma_dir == DMA_FROM_DEVICE &&
- !(qc->flags & ATA_QCFLAG_FAILED)) {
+ !(qc->flags & ATA_QCFLAG_EH)) {
ata_tf_from_fis(rx_fis + RX_FIS_PIO_SETUP, &qc->result_tf);
qc->result_tf.status = (rx_fis + RX_FIS_PIO_SETUP)[15];
+ qc->flags |= ATA_QCFLAG_RTF_FILLED;
+ return;
+ }
/*
* For NCQ commands, we never get a D2H FIS, so reading the D2H Register
@@ -2080,15 +2103,85 @@ static bool ahci_qc_fill_rtf(struct ata_queued_cmd *qc)
* instead. However, the SDB FIS does not contain the LBA, so we can't
* use the ata_tf_from_fis() helper.
*/
- } else if (ata_is_ncq(qc->tf.protocol)) {
+ if (ata_is_ncq(qc->tf.protocol)) {
const u8 *fis = rx_fis + RX_FIS_SDB;
+ /*
+ * Successful NCQ commands have been filled already.
+ * A failed NCQ command will read the status here.
+ * (Note that a failed NCQ command will get a more specific
+ * error when reading the NCQ Command Error log.)
+ */
qc->result_tf.status = fis[2];
qc->result_tf.error = fis[3];
- } else
- ata_tf_from_fis(rx_fis + RX_FIS_D2H_REG, &qc->result_tf);
+ qc->flags |= ATA_QCFLAG_RTF_FILLED;
+ return;
+ }
+
+ ata_tf_from_fis(rx_fis + RX_FIS_D2H_REG, &qc->result_tf);
+ qc->flags |= ATA_QCFLAG_RTF_FILLED;
+}
+
+static void ahci_qc_ncq_fill_rtf(struct ata_port *ap, u64 done_mask)
+{
+ struct ahci_port_priv *pp = ap->private_data;
+ const u8 *fis;
+
+ /* No outstanding commands. */
+ if (!ap->qc_active)
+ return;
+
+ /*
+ * FBS not enabled, so read status and error once, since they are shared
+ * for all QCs.
+ */
+ if (!pp->fbs_enabled) {
+ u8 status, error;
+
+ /* No outstanding NCQ commands. */
+ if (!pp->active_link->sactive)
+ return;
+
+ fis = pp->rx_fis + RX_FIS_SDB;
+ status = fis[2];
+ error = fis[3];
- return true;
+ while (done_mask) {
+ struct ata_queued_cmd *qc;
+ unsigned int tag = __ffs64(done_mask);
+
+ qc = ata_qc_from_tag(ap, tag);
+ if (qc && ata_is_ncq(qc->tf.protocol)) {
+ qc->result_tf.status = status;
+ qc->result_tf.error = error;
+ qc->flags |= ATA_QCFLAG_RTF_FILLED;
+ }
+ done_mask &= ~(1ULL << tag);
+ }
+
+ return;
+ }
+
+ /*
+ * FBS enabled, so read the status and error for each QC, since the QCs
+ * can belong to different PMP links. (Each PMP link has its own FIS
+ * Receive Area.)
+ */
+ while (done_mask) {
+ struct ata_queued_cmd *qc;
+ unsigned int tag = __ffs64(done_mask);
+
+ qc = ata_qc_from_tag(ap, tag);
+ if (qc && ata_is_ncq(qc->tf.protocol)) {
+ fis = pp->rx_fis;
+ fis += qc->dev->link->pmp * AHCI_RX_FIS_SZ;
+ fis += RX_FIS_SDB;
+ qc->result_tf.status = fis[2];
+ qc->result_tf.error = fis[3];
+ qc->flags |= ATA_QCFLAG_RTF_FILLED;
+ }
+ done_mask &= ~(1ULL << tag);
+ }
}
static void ahci_freeze(struct ata_port *ap)
@@ -2138,7 +2231,7 @@ static void ahci_post_internal_cmd(struct ata_queued_cmd *qc)
struct ata_port *ap = qc->ap;
/* make DMA engine forget about the failed command */
- if (qc->flags & ATA_QCFLAG_FAILED)
+ if (qc->flags & ATA_QCFLAG_EH)
ahci_kick_engine(ap);
}