diff options
author | Stefan Hajnoczi <stefanha@redhat.com> | 2023-09-07 10:28:33 -0400 |
---|---|---|
committer | Stefan Hajnoczi <stefanha@redhat.com> | 2023-09-07 10:28:33 -0400 |
commit | 13d9f6dca08a38e9258b6328f3ad61bdb8e19619 (patch) | |
tree | d38f53af66f74ce012790566fd7623182b8f7b17 | |
parent | c97d45d55798b27410253df8fc724d2a02189aa8 (diff) | |
parent | 9f89423537653de07ca40c18b5ff5b70b104cc93 (diff) |
Merge tag 'ide-pull-request' of https://gitlab.com/jsnow/qemu into staging
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>
-rw-r--r-- | hw/ide/ahci.c | 110 | ||||
-rw-r--r-- | hw/ide/core.c | 2 | ||||
-rw-r--r-- | tests/qtest/libqos/ahci.c | 106 | ||||
-rw-r--r-- | tests/qtest/libqos/ahci.h | 8 |
4 files changed, 163 insertions, 63 deletions
diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c index 48d550f633..d0a774bc17 100644 --- a/hw/ide/ahci.c +++ b/hw/ide/ahci.c @@ -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); @@ -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 @@ -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); } } } @@ -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. */ @@ -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); } } @@ -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; @@ -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; @@ -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; } @@ -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; } @@ -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); } @@ -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; @@ -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) { @@ -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; @@ -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 */ @@ -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); @@ -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); @@ -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 */ @@ -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); diff --git a/hw/ide/core.c b/hw/ide/core.c index ee116891ed..b5e0dcd29b 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -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) diff --git a/tests/qtest/libqos/ahci.c b/tests/qtest/libqos/ahci.c index f53f12aa99..a2c94c6e06 100644 --- a/tests/qtest/libqos/ahci.c +++ b/tests/qtest/libqos/ahci.c @@ -404,57 +404,110 @@ void ahci_port_clear(AHCIQState *ahci, uint8_t port) /** * Check a port for errors. */ -void ahci_port_check_error(AHCIQState *ahci, uint8_t port, - uint32_t imask, uint8_t emask) +void ahci_port_check_error(AHCIQState *ahci, AHCICommand *cmd) { + uint8_t port = cmd->port; uint32_t reg; - /* The upper 9 bits of the IS register all indicate errors. */ - reg = ahci_px_rreg(ahci, port, AHCI_PX_IS); - reg &= ~imask; - reg >>= 23; - g_assert_cmphex(reg, ==, 0); + /* If expecting TF error, ensure that TFES is set. */ + if (cmd->errors) { + reg = ahci_px_rreg(ahci, port, AHCI_PX_IS); + ASSERT_BIT_SET(reg, AHCI_PX_IS_TFES); + } else { + /* The upper 9 bits of the IS register all indicate errors. */ + reg = ahci_px_rreg(ahci, port, AHCI_PX_IS); + reg &= ~cmd->interrupts; + reg >>= 23; + g_assert_cmphex(reg, ==, 0); + } - /* The Sata Error Register should be empty. */ + /* The Sata Error Register should be empty, even when expecting TF error. */ reg = ahci_px_rreg(ahci, port, AHCI_PX_SERR); g_assert_cmphex(reg, ==, 0); + /* If expecting TF error, and TFES was set, perform error recovery + * (see AHCI 1.3 section 6.2.2.1) such that we can send new commands. */ + if (cmd->errors) { + /* This will clear PxCI. */ + ahci_px_clr(ahci, port, AHCI_PX_CMD, AHCI_PX_CMD_ST); + + /* The port has 500ms to disengage. */ + usleep(500000); + reg = ahci_px_rreg(ahci, port, AHCI_PX_CMD); + ASSERT_BIT_CLEAR(reg, AHCI_PX_CMD_CR); + + /* Clear PxIS. */ + reg = ahci_px_rreg(ahci, port, AHCI_PX_IS); + ahci_px_wreg(ahci, port, AHCI_PX_IS, reg); + + /* Check if we need to perform a COMRESET. + * Not implemented right now, as there is no reason why our QEMU model + * should need a COMRESET when expecting TF error. */ + reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD); + ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_BSY | AHCI_PX_TFD_STS_DRQ); + + /* Enable issuing new commands. */ + ahci_px_set(ahci, port, AHCI_PX_CMD, AHCI_PX_CMD_ST); + } + /* The TFD also has two error sections. */ reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD); - if (!emask) { + if (!cmd->errors) { ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_ERR); } else { ASSERT_BIT_SET(reg, AHCI_PX_TFD_STS_ERR); } - ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_ERR & (~emask << 8)); - ASSERT_BIT_SET(reg, AHCI_PX_TFD_ERR & (emask << 8)); + ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_ERR & (~cmd->errors << 8)); + ASSERT_BIT_SET(reg, AHCI_PX_TFD_ERR & (cmd->errors << 8)); } -void ahci_port_check_interrupts(AHCIQState *ahci, uint8_t port, - uint32_t intr_mask) +void ahci_port_check_interrupts(AHCIQState *ahci, AHCICommand *cmd) { + uint8_t port = cmd->port; uint32_t reg; + /* If we expect errors, error handling in ahci_port_check_error() will + * already have cleared PxIS, so in that case this function cannot verify + * and clear expected interrupts. */ + if (cmd->errors) { + return; + } + /* Check for expected interrupts */ reg = ahci_px_rreg(ahci, port, AHCI_PX_IS); - ASSERT_BIT_SET(reg, intr_mask); + ASSERT_BIT_SET(reg, cmd->interrupts); /* Clear expected interrupts and assert all interrupts now cleared. */ - ahci_px_wreg(ahci, port, AHCI_PX_IS, intr_mask); + ahci_px_wreg(ahci, port, AHCI_PX_IS, cmd->interrupts); g_assert_cmphex(ahci_px_rreg(ahci, port, AHCI_PX_IS), ==, 0); } -void ahci_port_check_nonbusy(AHCIQState *ahci, uint8_t port, uint8_t slot) +void ahci_port_check_nonbusy(AHCIQState *ahci, AHCICommand *cmd) { + uint8_t slot = cmd->slot; + uint8_t port = cmd->port; uint32_t reg; - /* Assert that the command slot is no longer busy (NCQ) */ + /* For NCQ command with error PxSACT bit should still be set. + * For NCQ command without error, PxSACT bit should be cleared. + * For non-NCQ command, PxSACT bit should always be cleared. */ reg = ahci_px_rreg(ahci, port, AHCI_PX_SACT); - ASSERT_BIT_CLEAR(reg, (1 << slot)); + if (cmd->props->ncq && cmd->errors) { + ASSERT_BIT_SET(reg, (1 << slot)); + } else { + ASSERT_BIT_CLEAR(reg, (1 << slot)); + } - /* Non-NCQ */ + /* For non-NCQ command with error, PxCI bit should still be set. + * For non-NCQ command without error, PxCI bit should be cleared. + * For NCQ command without error, PxCI bit should be cleared. + * For NCQ command with error, PxCI bit may or may not be cleared. */ reg = ahci_px_rreg(ahci, port, AHCI_PX_CI); - ASSERT_BIT_CLEAR(reg, (1 << slot)); + if (!cmd->props->ncq && cmd->errors) { + ASSERT_BIT_SET(reg, (1 << slot)); + } else if (!cmd->errors) { + ASSERT_BIT_CLEAR(reg, (1 << slot)); + } /* And assert that we are generally not busy. */ reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD); @@ -1207,9 +1260,10 @@ void ahci_command_wait(AHCIQState *ahci, AHCICommand *cmd) #define RSET(REG, MASK) (BITSET(ahci_px_rreg(ahci, cmd->port, (REG)), (MASK))) - while (RSET(AHCI_PX_TFD, AHCI_PX_TFD_STS_BSY) || - RSET(AHCI_PX_CI, 1 << cmd->slot) || - (cmd->props->ncq && RSET(AHCI_PX_SACT, 1 << cmd->slot))) { + while (!RSET(AHCI_PX_TFD, AHCI_PX_TFD_STS_ERR) && + (RSET(AHCI_PX_TFD, AHCI_PX_TFD_STS_BSY) || + RSET(AHCI_PX_CI, 1 << cmd->slot) || + (cmd->props->ncq && RSET(AHCI_PX_SACT, 1 << cmd->slot)))) { usleep(50); } @@ -1226,9 +1280,9 @@ void ahci_command_verify(AHCIQState *ahci, AHCICommand *cmd) uint8_t slot = cmd->slot; uint8_t port = cmd->port; - ahci_port_check_error(ahci, port, cmd->interrupts, cmd->errors); - ahci_port_check_interrupts(ahci, port, cmd->interrupts); - ahci_port_check_nonbusy(ahci, port, slot); + ahci_port_check_nonbusy(ahci, cmd); + ahci_port_check_error(ahci, cmd); + ahci_port_check_interrupts(ahci, cmd); ahci_port_check_cmd_sanity(ahci, cmd); if (cmd->interrupts & AHCI_PX_IS_DHRS) { ahci_port_check_d2h_sanity(ahci, port, slot); diff --git a/tests/qtest/libqos/ahci.h b/tests/qtest/libqos/ahci.h index 88835b6228..48017864bf 100644 --- a/tests/qtest/libqos/ahci.h +++ b/tests/qtest/libqos/ahci.h @@ -590,11 +590,9 @@ void ahci_set_command_header(AHCIQState *ahci, uint8_t port, void ahci_destroy_command(AHCIQState *ahci, uint8_t port, uint8_t slot); /* AHCI sanity check routines */ -void ahci_port_check_error(AHCIQState *ahci, uint8_t port, - uint32_t imask, uint8_t emask); -void ahci_port_check_interrupts(AHCIQState *ahci, uint8_t port, - uint32_t intr_mask); -void ahci_port_check_nonbusy(AHCIQState *ahci, uint8_t port, uint8_t slot); +void ahci_port_check_error(AHCIQState *ahci, AHCICommand *cmd); +void ahci_port_check_interrupts(AHCIQState *ahci, AHCICommand *cmd); +void ahci_port_check_nonbusy(AHCIQState *ahci, AHCICommand *cmd); void ahci_port_check_d2h_sanity(AHCIQState *ahci, uint8_t port, uint8_t slot); void ahci_port_check_pio_sanity(AHCIQState *ahci, AHCICommand *cmd); void ahci_port_check_cmd_sanity(AHCIQState *ahci, AHCICommand *cmd); |