Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge tag 'ide-pull-request' of https://gitlab.com/jsnow/qemu into st…
…aging

IDE Pull request

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCAAdFiEE+ber27ys35W+dsvQfe+BBqr8OQ4FAmT5RpYACgkQfe+BBqr8
# OQ7GuA//S/gyyqsnltz4W9D0liaan1a2YsSx7Q2gcKdotdmFwgEHWWuVKorCteQt
# 1AtkFiA1bawF9ZSRQIpQzMNDOkSJHOs/0HXhdbNRs6JZ6C+c/aLnNSpxIfFpkP3I
# Wcrmi98F8zHlRc+KGqvZFHW+woqWJxTvglG4OmpMhMWCZRuqADeaxWaywgSXxlK+
# MtmpsslPeTxHdwa6ijXCJd2ghP59z391Ulo4kZ7YOMou/YLEd/AnezBDtepDGnbb
# TnyDcvGf+Dp5nJ4Rcp22frZdcxb44+wt2QlQFDp+h6r7KzIEwGIK2LL37sN8VHwU
# B8GbYkjoPnau2cOaLgmpC1reWkdwaiXfaI+1B/35/jg6hwYHFe6F03+JstMWXHXt
# ++Wy4MKDx5wRt7cmOu6htS776UC15NMcZB0AzxQuE5mL+eSNp1n5Nw5UW2iD/USL
# LD2dlMO05acdqn2iXoMTX/K1cUo1wRkEns7PISk+F2ve0PTS1RJUvuiNXs+aDrt9
# +AfE/e025YMQY8CWLiaihfNH7/QY8vS874SrcDr5rtfhitu16nqq5JpjnyzkqgbR
# PE+5JWT3QGBOcDMQeNUDfxFlcCVDm3ffIKo/7/PDCfeKQsJkG/nVGF7OmlAVmoUD
# GsvIlKBegIQvpp8LRabzfeTfbj7NGKFwaShQ6wcqxOakjy+iKx8=
# =ZRVt
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 06 Sep 2023 23:42:14 EDT
# gpg:                using RSA key F9B7ABDBBCACDF95BE76CBD07DEF8106AAFC390E
# gpg: Good signature from "John Snow (John Huston) <jsnow@redhat.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: FAEB 9711 A12C F475 812F  18F2 88A9 064D 1835 61EB
#      Subkey fingerprint: F9B7 ABDB BCAC DF95 BE76  CBD0 7DEF 8106 AAFC 390E

* tag 'ide-pull-request' of https://gitlab.com/jsnow/qemu:
  hw/ide/ahci: fix broken SError handling
  hw/ide/ahci: fix ahci_write_fis_sdb()
  hw/ide/ahci: PxCI should not get cleared when ERR_STAT is set
  hw/ide/ahci: PxSACT and PxCI is cleared when PxCMD.ST is cleared
  hw/ide/ahci: simplify and document PxCI handling
  hw/ide/ahci: write D2H FIS when processing NCQ command
  hw/ide/core: set ERR_STAT in unsupported command completion

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
  • Loading branch information
Stefan Hajnoczi committed Sep 7, 2023
2 parents c97d45d + 9f89423 commit 13d9f6d
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 63 deletions.
110 changes: 79 additions & 31 deletions hw/ide/ahci.c
Expand Up @@ -41,9 +41,10 @@
#include "trace.h"

static void check_cmd(AHCIState *s, int port);
static int handle_cmd(AHCIState *s, int port, uint8_t slot);
static void handle_cmd(AHCIState *s, int port, uint8_t slot);
static void ahci_reset_port(AHCIState *s, int port);
static bool ahci_write_fis_d2h(AHCIDevice *ad);
static bool ahci_write_fis_d2h(AHCIDevice *ad, bool d2h_fis_i);
static void ahci_clear_cmd_issue(AHCIDevice *ad, uint8_t slot);
static void ahci_init_d2h(AHCIDevice *ad);
static int ahci_dma_prepare_buf(const IDEDMA *dma, int32_t limit);
static bool ahci_map_clb_address(AHCIDevice *ad);
Expand Down Expand Up @@ -328,6 +329,11 @@ static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val)
ahci_check_irq(s);
break;
case AHCI_PORT_REG_CMD:
if ((pr->cmd & PORT_CMD_START) && !(val & PORT_CMD_START)) {
pr->scr_act = 0;
pr->cmd_issue = 0;
}

/* Block any Read-only fields from being set;
* including LIST_ON and FIS_ON.
* The spec requires to set ICC bits to zero after the ICC change
Expand Down Expand Up @@ -591,9 +597,8 @@ static void check_cmd(AHCIState *s, int port)

if ((pr->cmd & PORT_CMD_START) && pr->cmd_issue) {
for (slot = 0; (slot < 32) && pr->cmd_issue; slot++) {
if ((pr->cmd_issue & (1U << slot)) &&
!handle_cmd(s, port, slot)) {
pr->cmd_issue &= ~(1U << slot);
if (pr->cmd_issue & (1U << slot)) {
handle_cmd(s, port, slot);
}
}
}
Expand All @@ -618,7 +623,7 @@ static void ahci_init_d2h(AHCIDevice *ad)
return;
}

if (ahci_write_fis_d2h(ad)) {
if (ahci_write_fis_d2h(ad, true)) {
ad->init_d2h_sent = true;
/* We're emulating receiving the first Reg H2D Fis from the device;
* Update the SIG register, but otherwise proceed as normal. */
Expand Down Expand Up @@ -801,8 +806,14 @@ static void ahci_write_fis_sdb(AHCIState *s, NCQTransferState *ncq_tfs)
pr->scr_act &= ~ad->finished;
ad->finished = 0;

/* Trigger IRQ if interrupt bit is set (which currently, it always is) */
if (sdb_fis->flags & 0x40) {
/*
* TFES IRQ is always raised if ERR_STAT is set, regardless of I bit.
* If ERR_STAT is not set, trigger SDBS IRQ if interrupt bit is set
* (which currently, it always is).
*/
if (sdb_fis->status & ERR_STAT) {
ahci_trigger_irq(s, ad, AHCI_PORT_IRQ_BIT_TFES);
} else if (sdb_fis->flags & 0x40) {
ahci_trigger_irq(s, ad, AHCI_PORT_IRQ_BIT_SDBS);
}
}
Expand Down Expand Up @@ -850,7 +861,7 @@ static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len, bool pio_fis_i)
}
}

static bool ahci_write_fis_d2h(AHCIDevice *ad)
static bool ahci_write_fis_d2h(AHCIDevice *ad, bool d2h_fis_i)
{
AHCIPortRegs *pr = &ad->port_regs;
uint8_t *d2h_fis;
Expand All @@ -864,7 +875,7 @@ static bool ahci_write_fis_d2h(AHCIDevice *ad)
d2h_fis = &ad->res_fis[RES_FIS_RFIS];

d2h_fis[0] = SATA_FIS_TYPE_REGISTER_D2H;
d2h_fis[1] = (1 << 6); /* interrupt bit */
d2h_fis[1] = d2h_fis_i ? (1 << 6) : 0; /* interrupt bit */
d2h_fis[2] = s->status;
d2h_fis[3] = s->error;

Expand All @@ -890,7 +901,10 @@ static bool ahci_write_fis_d2h(AHCIDevice *ad)
ahci_trigger_irq(ad->hba, ad, AHCI_PORT_IRQ_BIT_TFES);
}

ahci_trigger_irq(ad->hba, ad, AHCI_PORT_IRQ_BIT_DHRS);
if (d2h_fis_i) {
ahci_trigger_irq(ad->hba, ad, AHCI_PORT_IRQ_BIT_DHRS);
}

return true;
}

Expand Down Expand Up @@ -998,7 +1012,6 @@ static void ncq_err(NCQTransferState *ncq_tfs)

ide_state->error = ABRT_ERR;
ide_state->status = READY_STAT | ERR_STAT;
ncq_tfs->drive->port_regs.scr_err |= (1 << ncq_tfs->tag);
qemu_sglist_destroy(&ncq_tfs->sglist);
ncq_tfs->used = 0;
}
Expand All @@ -1008,7 +1021,7 @@ static void ncq_finish(NCQTransferState *ncq_tfs)
/* If we didn't error out, set our finished bit. Errored commands
* 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))) {
if (ncq_tfs->used) {
ncq_tfs->drive->finished |= (1 << ncq_tfs->tag);
}

Expand Down Expand Up @@ -1120,6 +1133,24 @@ static void process_ncq_command(AHCIState *s, int port, const uint8_t *cmd_fis,
return;
}

/*
* A NCQ command clears the bit in PxCI after the command has been QUEUED
* successfully (ERROR not set, BUSY and DRQ cleared).
*
* For NCQ commands, PxCI will always be cleared here.
*
* (Once the NCQ command is COMPLETED, the device will send a SDB FIS with
* the interrupt bit set, which will clear PxSACT and raise an interrupt.)
*/
ahci_clear_cmd_issue(ad, slot);

/*
* In reality, for NCQ commands, PxCI is cleared after receiving a D2H FIS
* without the interrupt bit set, but since ahci_write_fis_d2h() can raise
* an IRQ on error, we need to call them in reverse order.
*/
ahci_write_fis_d2h(ad, false);

ncq_tfs->used = 1;
ncq_tfs->drive = ad;
ncq_tfs->slot = slot;
Expand Down Expand Up @@ -1192,6 +1223,7 @@ static void handle_reg_h2d_fis(AHCIState *s, int port,
{
IDEState *ide_state = &s->dev[port].port.ifs[0];
AHCICmdHdr *cmd = get_cmd_header(s, port, slot);
AHCIDevice *ad = &s->dev[port];
uint16_t opts = le16_to_cpu(cmd->opts);

if (cmd_fis[1] & 0x0F) {
Expand Down Expand Up @@ -1268,11 +1300,19 @@ static void handle_reg_h2d_fis(AHCIState *s, int port,
/* Reset transferred byte counter */
cmd->status = 0;

/*
* A non-NCQ command clears the bit in PxCI after the command has COMPLETED
* successfully (ERROR not set, BUSY and DRQ cleared).
*
* For non-NCQ commands, PxCI will always be cleared by ahci_cmd_done().
*/
ad->busy_slot = slot;

/* We're ready to process the command in FIS byte 2. */
ide_bus_exec_cmd(&s->dev[port].port, cmd_fis[2]);
}

static int handle_cmd(AHCIState *s, int port, uint8_t slot)
static void handle_cmd(AHCIState *s, int port, uint8_t slot)
{
IDEState *ide_state;
uint64_t tbl_addr;
Expand All @@ -1283,12 +1323,12 @@ static int handle_cmd(AHCIState *s, int port, uint8_t slot)
if (s->dev[port].port.ifs[0].status & (BUSY_STAT|DRQ_STAT)) {
/* Engine currently busy, try again later */
trace_handle_cmd_busy(s, port);
return -1;
return;
}

if (!s->dev[port].lst) {
trace_handle_cmd_nolist(s, port);
return -1;
return;
}
cmd = get_cmd_header(s, port, slot);
/* remember current slot handle for later */
Expand All @@ -1298,7 +1338,7 @@ static int handle_cmd(AHCIState *s, int port, uint8_t slot)
ide_state = &s->dev[port].port.ifs[0];
if (!ide_state->blk) {
trace_handle_cmd_badport(s, port);
return -1;
return;
}

tbl_addr = le64_to_cpu(cmd->tbl_addr);
Expand All @@ -1307,7 +1347,7 @@ static int handle_cmd(AHCIState *s, int port, uint8_t slot)
DMA_DIRECTION_TO_DEVICE, MEMTXATTRS_UNSPECIFIED);
if (!cmd_fis) {
trace_handle_cmd_badfis(s, port);
return -1;
return;
} else if (cmd_len != 0x80) {
ahci_trigger_irq(s, &s->dev[port], AHCI_PORT_IRQ_BIT_HBFS);
trace_handle_cmd_badmap(s, port, cmd_len);
Expand All @@ -1331,15 +1371,6 @@ static int handle_cmd(AHCIState *s, int port, uint8_t slot)
out:
dma_memory_unmap(s->as, cmd_fis, cmd_len, DMA_DIRECTION_TO_DEVICE,
cmd_len);

if (s->dev[port].port.ifs[0].status & (BUSY_STAT|DRQ_STAT)) {
/* async command, complete later */
s->dev[port].busy_slot = slot;
return -1;
}

/* done handling the command */
return 0;
}

/* Transfer PIO data between RAM and device */
Expand Down Expand Up @@ -1493,22 +1524,39 @@ static int ahci_dma_rw_buf(const IDEDMA *dma, bool is_write)
return 1;
}

static void ahci_clear_cmd_issue(AHCIDevice *ad, uint8_t slot)
{
IDEState *ide_state = &ad->port.ifs[0];

if (!(ide_state->status & ERR_STAT) &&
!(ide_state->status & (BUSY_STAT | DRQ_STAT))) {
ad->port_regs.cmd_issue &= ~(1 << slot);
}
}

/* Non-NCQ command is done - This function is never called for NCQ commands. */
static void ahci_cmd_done(const IDEDMA *dma)
{
AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
IDEState *ide_state = &ad->port.ifs[0];

trace_ahci_cmd_done(ad->hba, ad->port_no);

/* no longer busy */
if (ad->busy_slot != -1) {
ad->port_regs.cmd_issue &= ~(1 << ad->busy_slot);
ahci_clear_cmd_issue(ad, ad->busy_slot);
ad->busy_slot = -1;
}

/* update d2h status */
ahci_write_fis_d2h(ad);
/*
* In reality, for non-NCQ commands, PxCI is cleared after receiving a D2H
* FIS with the interrupt bit set, but since ahci_write_fis_d2h() will raise
* an IRQ, we need to call them in reverse order.
*/
ahci_write_fis_d2h(ad, true);

if (ad->port_regs.cmd_issue && !ad->check_bh) {
if (!(ide_state->status & ERR_STAT) &&
ad->port_regs.cmd_issue && !ad->check_bh) {
ad->check_bh = qemu_bh_new_guarded(ahci_check_cmd_bh, ad,
&ad->mem_reentrancy_guard);
qemu_bh_schedule(ad->check_bh);
Expand Down
2 changes: 1 addition & 1 deletion hw/ide/core.c
Expand Up @@ -533,9 +533,9 @@ BlockAIOCB *ide_issue_trim(

void ide_abort_command(IDEState *s)
{
ide_transfer_stop(s);
s->status = READY_STAT | ERR_STAT;
s->error = ABRT_ERR;
ide_transfer_stop(s);
}

static void ide_set_retry(IDEState *s)
Expand Down

0 comments on commit 13d9f6d

Please sign in to comment.