ahci: fix sdb fis semantics
There are two things to fix here: The first one is subtle: the PxSACT register in the AHCI HBA has different semantics from the field it is shadowing, the ACT field in the Set Device Bits FIS. In the HBA register, PxSACT acts as a bitfield indicating outstanding NCQ commands where a set bit indicates a pending NCQ operation. The FIS field however operates as an RWC register update to PxSACT, where a set bit indicates a *successfully* completed command. Correct the FIS semantics. At the same time, move the "clear finished" action to the SDB FIS generation instead of the register read to mimick how the other shadow registers work, which always just report the last reported value from a FIS, and not the most current values which may not have been reported by a FIS yet. Lastly and more simply, SATA 3.2 section 13.6.4.2 (and later sections) all specify that the Interrupt bit for the SDB FIS should always be set to one for NCQ commands. That's currently the only time we generate this FIS, so set it on all the time. Signed-off-by: John Snow <jsnow@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Message-id: 1435767578-32743-16-git-send-email-jsnow@redhat.com
This commit is contained in:
parent
8146d7dc27
commit
7c649ac5b6
|
@ -106,8 +106,6 @@ static uint32_t ahci_port_read(AHCIState *s, int port, int offset)
|
||||||
val = pr->scr_err;
|
val = pr->scr_err;
|
||||||
break;
|
break;
|
||||||
case PORT_SCR_ACT:
|
case PORT_SCR_ACT:
|
||||||
pr->scr_act &= ~s->dev[port].finished;
|
|
||||||
s->dev[port].finished = 0;
|
|
||||||
val = pr->scr_act;
|
val = pr->scr_act;
|
||||||
break;
|
break;
|
||||||
case PORT_CMD_ISSUE:
|
case PORT_CMD_ISSUE:
|
||||||
|
@ -666,14 +664,14 @@ static void ahci_unmap_clb_address(AHCIDevice *ad)
|
||||||
ad->lst = NULL;
|
ad->lst = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished)
|
static void ahci_write_fis_sdb(AHCIState *s, NCQTransferState *ncq_tfs)
|
||||||
{
|
{
|
||||||
AHCIDevice *ad = &s->dev[port];
|
AHCIDevice *ad = ncq_tfs->drive;
|
||||||
AHCIPortRegs *pr = &ad->port_regs;
|
AHCIPortRegs *pr = &ad->port_regs;
|
||||||
IDEState *ide_state;
|
IDEState *ide_state;
|
||||||
SDBFIS *sdb_fis;
|
SDBFIS *sdb_fis;
|
||||||
|
|
||||||
if (!s->dev[port].res_fis ||
|
if (!ad->res_fis ||
|
||||||
!(pr->cmd & PORT_CMD_FIS_RX)) {
|
!(pr->cmd & PORT_CMD_FIS_RX)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -683,19 +681,23 @@ static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished)
|
||||||
|
|
||||||
sdb_fis->type = SATA_FIS_TYPE_SDB;
|
sdb_fis->type = SATA_FIS_TYPE_SDB;
|
||||||
/* Interrupt pending & Notification bit */
|
/* Interrupt pending & Notification bit */
|
||||||
sdb_fis->flags = (ad->hba->control_regs.irqstatus ? (1 << 6) : 0);
|
sdb_fis->flags = 0x40; /* Interrupt bit, always 1 for NCQ */
|
||||||
sdb_fis->status = ide_state->status & 0x77;
|
sdb_fis->status = ide_state->status & 0x77;
|
||||||
sdb_fis->error = ide_state->error;
|
sdb_fis->error = ide_state->error;
|
||||||
/* update SAct field in SDB_FIS */
|
/* update SAct field in SDB_FIS */
|
||||||
s->dev[port].finished |= finished;
|
|
||||||
sdb_fis->payload = cpu_to_le32(ad->finished);
|
sdb_fis->payload = cpu_to_le32(ad->finished);
|
||||||
|
|
||||||
/* Update shadow registers (except BSY 0x80 and DRQ 0x08) */
|
/* Update shadow registers (except BSY 0x80 and DRQ 0x08) */
|
||||||
pr->tfdata = (ad->port.ifs[0].error << 8) |
|
pr->tfdata = (ad->port.ifs[0].error << 8) |
|
||||||
(ad->port.ifs[0].status & 0x77) |
|
(ad->port.ifs[0].status & 0x77) |
|
||||||
(pr->tfdata & 0x88);
|
(pr->tfdata & 0x88);
|
||||||
|
pr->scr_act &= ~ad->finished;
|
||||||
|
ad->finished = 0;
|
||||||
|
|
||||||
ahci_trigger_irq(s, ad, PORT_IRQ_SDB_FIS);
|
/* Trigger IRQ if interrupt bit is set (which currently, it always is) */
|
||||||
|
if (sdb_fis->flags & 0x40) {
|
||||||
|
ahci_trigger_irq(s, ad, PORT_IRQ_SDB_FIS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len)
|
static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len)
|
||||||
|
@ -895,11 +897,14 @@ static void ncq_err(NCQTransferState *ncq_tfs)
|
||||||
|
|
||||||
static void ncq_finish(NCQTransferState *ncq_tfs)
|
static void ncq_finish(NCQTransferState *ncq_tfs)
|
||||||
{
|
{
|
||||||
/* Clear bit for this tag in SActive */
|
/* If we didn't error out, set our finished bit. Errored commands
|
||||||
ncq_tfs->drive->port_regs.scr_act &= ~(1 << ncq_tfs->tag);
|
* do not get a bit set for the SDB FIS ACT register, nor do they
|
||||||
|
* clear the outstanding bit in scr_act (PxSACT). */
|
||||||
|
if (!(ncq_tfs->drive->port_regs.scr_err & (1 << ncq_tfs->tag))) {
|
||||||
|
ncq_tfs->drive->finished |= (1 << ncq_tfs->tag);
|
||||||
|
}
|
||||||
|
|
||||||
ahci_write_fis_sdb(ncq_tfs->drive->hba, ncq_tfs->drive->port_no,
|
ahci_write_fis_sdb(ncq_tfs->drive->hba, ncq_tfs);
|
||||||
(1 << ncq_tfs->tag));
|
|
||||||
|
|
||||||
DPRINTF(ncq_tfs->drive->port_no, "NCQ transfer tag %d finished\n",
|
DPRINTF(ncq_tfs->drive->port_no, "NCQ transfer tag %d finished\n",
|
||||||
ncq_tfs->tag);
|
ncq_tfs->tag);
|
||||||
|
|
Loading…
Reference in a new issue