mirror of
https://github.com/torvalds/linux.git
synced 2024-12-27 05:11:48 +00:00
ata: libata: read the shared status for successful NCQ commands once
Currently, the status is being read for each QC, inside ata_qc_complete(), which means that QCs being completed by ata_qc_complete_multiple() (i.e. multiple QCs completed during a single interrupt), can have different status and error bits set. This is because the FIS Receive Area will get updated as soon as the HBA receives a new FIS from the device in the NCQ case. Here is an example of the problem: ata14.00: ata_qc_complete_multiple: done_mask: 0x180000 qc tag: 19 cmd: 0x61 flags: 0x11b err_mask: 0x0 tf->status: 0x40 qc tag: 20 cmd: 0x61 flags: 0x11b err_mask: 0x0 tf->status: 0x43 A print in ata_qc_complete_multiple(), shows that done_mask is: 0x180000 which means that tag 19 and 20 were completed. Another print in ata_qc_complete(), after the call to fill_result_tf(), shows that tag 19 and 20 have different status values, even though they were completed in the same ata_qc_complete_multiple() call. If PMP is not enabled, simply read the status and error once, before calling ata_qc_complete() for each QC. Without PMP, we know that all QCs must share the same status and error values. If PMP is enabled, we also read the status before calling ata_qc_complete(), however, we still read the status for each QC, since the QCs can belong to different PMP links (which means that the QCs does not necessarily share the same status and error values). Do all this by introducing the new port operation .qc_ncq_fill_rtf. If set, this operation is called in ata_qc_complete_multiple() to set the result tf for all completed QCs signaled by the last SDB FIS received. QCs that have their result tf filled are marked with the new flag ATA_QCFLAG_RTF_FILLED so that any later execution of the qc_fill_rtf port operation does nothing (e.g. when called from ata_qc_complete()). Co-developed-by: Damien Le Moal <damien.lemoal@opensource.wdc.com> Signed-off-by: Damien Le Moal <damien.lemoal@opensource.wdc.com> Signed-off-by: Niklas Cassel <niklas.cassel@wdc.com>
This commit is contained in:
parent
931139af57
commit
93c4aa449b
@ -56,6 +56,7 @@ 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 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,
|
||||
@ -2058,6 +2060,13 @@ 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;
|
||||
|
||||
@ -2071,6 +2080,9 @@ static void ahci_qc_fill_rtf(struct ata_queued_cmd *qc)
|
||||
!(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,13 +2092,85 @@ static void 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];
|
||||
|
||||
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)
|
||||
|
@ -655,6 +655,9 @@ int ata_qc_complete_multiple(struct ata_port *ap, u64 qc_active)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ap->ops->qc_ncq_fill_rtf)
|
||||
ap->ops->qc_ncq_fill_rtf(ap, done_mask);
|
||||
|
||||
while (done_mask) {
|
||||
struct ata_queued_cmd *qc;
|
||||
unsigned int tag = __ffs64(done_mask);
|
||||
|
@ -200,6 +200,7 @@ enum {
|
||||
/* struct ata_queued_cmd flags */
|
||||
ATA_QCFLAG_ACTIVE = (1 << 0), /* cmd not yet ack'd to scsi lyer */
|
||||
ATA_QCFLAG_DMAMAP = (1 << 1), /* SG table is DMA mapped */
|
||||
ATA_QCFLAG_RTF_FILLED = (1 << 2), /* result TF has been filled */
|
||||
ATA_QCFLAG_IO = (1 << 3), /* standard IO command */
|
||||
ATA_QCFLAG_RESULT_TF = (1 << 4), /* result TF requested */
|
||||
ATA_QCFLAG_CLEAR_EXCL = (1 << 5), /* clear excl_link on completion */
|
||||
@ -877,6 +878,7 @@ struct ata_port_operations {
|
||||
enum ata_completion_errors (*qc_prep)(struct ata_queued_cmd *qc);
|
||||
unsigned int (*qc_issue)(struct ata_queued_cmd *qc);
|
||||
void (*qc_fill_rtf)(struct ata_queued_cmd *qc);
|
||||
void (*qc_ncq_fill_rtf)(struct ata_port *ap, u64 done_mask);
|
||||
|
||||
/*
|
||||
* Configuration and exception handling
|
||||
|
Loading…
Reference in New Issue
Block a user