diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2015-07-05 20:35:47 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2015-07-05 20:35:47 +0100 |
commit | f50a1640fb82708a5d528dee1ace42a224b95b15 (patch) | |
tree | f0d398aa4decd98c0b3245c5dc4b81135c0d8878 /hw | |
parent | 63a9294ddc9cf4f2bdcd0179324fedcbb6fae59f (diff) | |
parent | 7c649ac5b607e2339fb54fc0fc01311ba5eacadd (diff) |
Merge remote-tracking branch 'remotes/jnsnow/tags/ide-pull-request' into staging
# gpg: Signature made Sat Jul 4 07:06:08 2015 BST using RSA key ID AAFC390E
# gpg: Good signature from "John Snow (John Huston) <jsnow@redhat.com>"
# gpg: WARNING: This key is not certified with sufficiently trusted signatures!
# gpg: It is not certain 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
* remotes/jnsnow/tags/ide-pull-request: (35 commits)
ahci: fix sdb fis semantics
qtest/ahci: halted ncq migration test
ahci: Do not map cmd_fis to generate response
ahci: ncq migration
ahci: add get_cmd_header helper
ahci: add cmd header to ncq transfer state
qtest/ahci: halted NCQ test
ahci: correct ncq sector count
ahci: correct types in NCQTransferState
ahci: add rwerror=stop support for ncq
ahci: factor ncq_finish out of ncq_cb
ahci: refactor process_ncq_command
ahci: assert is_ncq for process_ncq
ahci: stash ncq command
ide: add limit to .prepare_buf()
qtest/ahci: ncq migration test
qtest/ahci: simple ncq data test
libqos/ahci: Force all NCQ commands to be LBA48
libqos/ahci: set the NCQ tag on command_commit
libqos/ahci: adjust expected NCQ interrupts
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw')
-rw-r--r-- | hw/ide/ahci.c | 437 | ||||
-rw-r--r-- | hw/ide/ahci.h | 47 | ||||
-rw-r--r-- | hw/ide/core.c | 15 | ||||
-rw-r--r-- | hw/ide/internal.h | 4 | ||||
-rw-r--r-- | hw/ide/macio.c | 2 | ||||
-rw-r--r-- | hw/ide/pci.c | 21 |
6 files changed, 350 insertions, 176 deletions
diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c index b4b65c100a..bb6a92f7f4 100644 --- a/hw/ide/ahci.c +++ b/hw/ide/ahci.c @@ -45,11 +45,11 @@ do { \ } while (0) static void check_cmd(AHCIState *s, int port); -static int handle_cmd(AHCIState *s,int port,int slot); +static int handle_cmd(AHCIState *s, int port, uint8_t slot); static void ahci_reset_port(AHCIState *s, int port); static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis); static void ahci_init_d2h(AHCIDevice *ad); -static int ahci_dma_prepare_buf(IDEDMA *dma, int is_write); +static int ahci_dma_prepare_buf(IDEDMA *dma, int32_t limit); static void ahci_commit_buf(IDEDMA *dma, uint32_t tx_bytes); static bool ahci_map_clb_address(AHCIDevice *ad); static bool ahci_map_fis_address(AHCIDevice *ad); @@ -106,8 +106,6 @@ static uint32_t ahci_port_read(AHCIState *s, int port, int offset) val = pr->scr_err; break; case PORT_SCR_ACT: - pr->scr_act &= ~s->dev[port].finished; - s->dev[port].finished = 0; val = pr->scr_act; break; case PORT_CMD_ISSUE: @@ -331,8 +329,7 @@ static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val) } } -static uint64_t ahci_mem_read(void *opaque, hwaddr addr, - unsigned size) +static uint64_t ahci_mem_read_32(void *opaque, hwaddr addr) { AHCIState *s = opaque; uint32_t val = 0; @@ -368,6 +365,30 @@ static uint64_t ahci_mem_read(void *opaque, hwaddr addr, } +/** + * AHCI 1.3 section 3 ("HBA Memory Registers") + * Support unaligned 8/16/32 bit reads, and 64 bit aligned reads. + * Caller is responsible for masking unwanted higher order bytes. + */ +static uint64_t ahci_mem_read(void *opaque, hwaddr addr, unsigned size) +{ + hwaddr aligned = addr & ~0x3; + int ofst = addr - aligned; + uint64_t lo = ahci_mem_read_32(opaque, aligned); + uint64_t hi; + + /* if < 8 byte read does not cross 4 byte boundary */ + if (ofst + size <= 4) { + return lo >> (ofst * 8); + } + g_assert_cmpint(size, >, 1); + + /* If the 64bit read is unaligned, we will produce undefined + * results. AHCI does not support unaligned 64bit reads. */ + hi = ahci_mem_read_32(opaque, aligned + 4); + return (hi << 32 | lo) >> (ofst * 8); +} + static void ahci_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) @@ -483,7 +504,7 @@ static void ahci_reg_init(AHCIState *s) static void check_cmd(AHCIState *s, int port) { AHCIPortRegs *pr = &s->dev[port].port_regs; - int slot; + uint8_t slot; if ((pr->cmd & PORT_CMD_START) && pr->cmd_issue) { for (slot = 0; (slot < 32) && pr->cmd_issue; slot++) { @@ -558,6 +579,7 @@ static void ahci_reset_port(AHCIState *s, int port) /* reset ncq queue */ for (i = 0; i < AHCI_MAX_CMDS; i++) { NCQTransferState *ncq_tfs = &s->dev[port].ncq_tfs[i]; + ncq_tfs->halt = false; if (!ncq_tfs->used) { continue; } @@ -642,14 +664,14 @@ static void ahci_unmap_clb_address(AHCIDevice *ad) 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; IDEState *ide_state; SDBFIS *sdb_fis; - if (!s->dev[port].res_fis || + if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) { return; } @@ -659,53 +681,35 @@ static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished) sdb_fis->type = SATA_FIS_TYPE_SDB; /* 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->error = ide_state->error; /* update SAct field in SDB_FIS */ - s->dev[port].finished |= finished; sdb_fis->payload = cpu_to_le32(ad->finished); /* Update shadow registers (except BSY 0x80 and DRQ 0x08) */ pr->tfdata = (ad->port.ifs[0].error << 8) | (ad->port.ifs[0].status & 0x77) | (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) { AHCIPortRegs *pr = &ad->port_regs; - uint8_t *pio_fis, *cmd_fis; - uint64_t tbl_addr; - dma_addr_t cmd_len = 0x80; + uint8_t *pio_fis; IDEState *s = &ad->port.ifs[0]; if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) { return; } - /* map cmd_fis */ - tbl_addr = le64_to_cpu(ad->cur_cmd->tbl_addr); - cmd_fis = dma_memory_map(ad->hba->as, tbl_addr, &cmd_len, - DMA_DIRECTION_TO_DEVICE); - - if (cmd_fis == NULL) { - DPRINTF(ad->port_no, "dma_memory_map failed in ahci_write_fis_pio"); - ahci_trigger_irq(ad->hba, ad, PORT_IRQ_HBUS_ERR); - return; - } - - if (cmd_len != 0x80) { - DPRINTF(ad->port_no, - "dma_memory_map mapped too few bytes in ahci_write_fis_pio"); - dma_memory_unmap(ad->hba->as, cmd_fis, cmd_len, - DMA_DIRECTION_TO_DEVICE, cmd_len); - ahci_trigger_irq(ad->hba, ad, PORT_IRQ_HBUS_ERR); - return; - } - pio_fis = &ad->res_fis[RES_FIS_PSFIS]; pio_fis[0] = SATA_FIS_TYPE_PIO_SETUP; @@ -721,8 +725,8 @@ static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len) pio_fis[9] = s->hob_lcyl; pio_fis[10] = s->hob_hcyl; pio_fis[11] = 0; - pio_fis[12] = cmd_fis[12]; - pio_fis[13] = cmd_fis[13]; + pio_fis[12] = s->nsector & 0xFF; + pio_fis[13] = (s->nsector >> 8) & 0xFF; pio_fis[14] = 0; pio_fis[15] = s->status; pio_fis[16] = len & 255; @@ -739,9 +743,6 @@ static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len) } ahci_trigger_irq(ad->hba, ad, PORT_IRQ_PIOS_FIS); - - dma_memory_unmap(ad->hba->as, cmd_fis, cmd_len, - DMA_DIRECTION_TO_DEVICE, cmd_len); } static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis) @@ -749,22 +750,12 @@ static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis) AHCIPortRegs *pr = &ad->port_regs; uint8_t *d2h_fis; int i; - dma_addr_t cmd_len = 0x80; - int cmd_mapped = 0; IDEState *s = &ad->port.ifs[0]; if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) { return; } - if (!cmd_fis) { - /* map cmd_fis */ - uint64_t tbl_addr = le64_to_cpu(ad->cur_cmd->tbl_addr); - cmd_fis = dma_memory_map(ad->hba->as, tbl_addr, &cmd_len, - DMA_DIRECTION_TO_DEVICE); - cmd_mapped = 1; - } - d2h_fis = &ad->res_fis[RES_FIS_RFIS]; d2h_fis[0] = SATA_FIS_TYPE_REGISTER_D2H; @@ -780,8 +771,8 @@ static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis) d2h_fis[9] = s->hob_lcyl; d2h_fis[10] = s->hob_hcyl; d2h_fis[11] = 0; - d2h_fis[12] = cmd_fis[12]; - d2h_fis[13] = cmd_fis[13]; + d2h_fis[12] = s->nsector & 0xFF; + d2h_fis[13] = (s->nsector >> 8) & 0xFF; for (i = 14; i < 20; i++) { d2h_fis[i] = 0; } @@ -795,26 +786,22 @@ static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis) } ahci_trigger_irq(ad->hba, ad, PORT_IRQ_D2H_REG_FIS); - - if (cmd_mapped) { - dma_memory_unmap(ad->hba->as, cmd_fis, cmd_len, - DMA_DIRECTION_TO_DEVICE, cmd_len); - } } static int prdt_tbl_entry_size(const AHCI_SG *tbl) { + /* flags_size is zero-based */ return (le32_to_cpu(tbl->flags_size) & AHCI_PRDT_SIZE_MASK) + 1; } static int ahci_populate_sglist(AHCIDevice *ad, QEMUSGList *sglist, - int32_t offset) + AHCICmdHdr *cmd, int64_t limit, int32_t offset) { - AHCICmdHdr *cmd = ad->cur_cmd; - uint32_t opts = le32_to_cpu(cmd->opts); - uint64_t prdt_addr = le64_to_cpu(cmd->tbl_addr) + 0x80; - int sglist_alloc_hint = opts >> AHCI_CMD_HDR_PRDT_LEN; - dma_addr_t prdt_len = (sglist_alloc_hint * sizeof(AHCI_SG)); + uint16_t opts = le16_to_cpu(cmd->opts); + uint16_t prdtl = le16_to_cpu(cmd->prdtl); + uint64_t cfis_addr = le64_to_cpu(cmd->tbl_addr); + uint64_t prdt_addr = cfis_addr + 0x80; + dma_addr_t prdt_len = (prdtl * sizeof(AHCI_SG)); dma_addr_t real_prdt_len = prdt_len; uint8_t *prdt; int i; @@ -834,7 +821,7 @@ static int ahci_populate_sglist(AHCIDevice *ad, QEMUSGList *sglist, * request for sector sizes up to 32K. */ - if (!sglist_alloc_hint) { + if (!prdtl) { DPRINTF(ad->port_no, "no sg list given by guest: 0x%08x\n", opts); return -1; } @@ -853,13 +840,12 @@ static int ahci_populate_sglist(AHCIDevice *ad, QEMUSGList *sglist, } /* Get entries in the PRDT, init a qemu sglist accordingly */ - if (sglist_alloc_hint > 0) { + if (prdtl > 0) { AHCI_SG *tbl = (AHCI_SG *)prdt; sum = 0; - for (i = 0; i < sglist_alloc_hint; i++) { - /* flags_size is zero-based */ + for (i = 0; i < prdtl; i++) { tbl_entry_size = prdt_tbl_entry_size(&tbl[i]); - if (offset <= (sum + tbl_entry_size)) { + if (offset < (sum + tbl_entry_size)) { off_idx = i; off_pos = offset - sum; break; @@ -874,15 +860,16 @@ static int ahci_populate_sglist(AHCIDevice *ad, QEMUSGList *sglist, goto out; } - qemu_sglist_init(sglist, qbus->parent, (sglist_alloc_hint - off_idx), + qemu_sglist_init(sglist, qbus->parent, (prdtl - off_idx), ad->hba->as); qemu_sglist_add(sglist, le64_to_cpu(tbl[off_idx].addr) + off_pos, - prdt_tbl_entry_size(&tbl[off_idx]) - off_pos); + MIN(prdt_tbl_entry_size(&tbl[off_idx]) - off_pos, + limit)); - for (i = off_idx + 1; i < sglist_alloc_hint; i++) { - /* flags_size is zero-based */ + for (i = off_idx + 1; i < prdtl && sglist->size < limit; i++) { qemu_sglist_add(sglist, le64_to_cpu(tbl[i].addr), - prdt_tbl_entry_size(&tbl[i])); + MIN(prdt_tbl_entry_size(&tbl[i]), + limit - sglist->size)); if (sglist->size > INT32_MAX) { error_report("AHCI Physical Region Descriptor Table describes " "more than 2 GiB.\n"); @@ -899,28 +886,25 @@ out: return r; } -static void ncq_cb(void *opaque, int ret) +static void ncq_err(NCQTransferState *ncq_tfs) { - NCQTransferState *ncq_tfs = (NCQTransferState *)opaque; IDEState *ide_state = &ncq_tfs->drive->port.ifs[0]; - if (ret == -ECANCELED) { - return; - } - /* Clear bit for this tag in SActive */ - ncq_tfs->drive->port_regs.scr_act &= ~(1 << ncq_tfs->tag); + ide_state->error = ABRT_ERR; + ide_state->status = READY_STAT | ERR_STAT; + ncq_tfs->drive->port_regs.scr_err |= (1 << ncq_tfs->tag); +} - if (ret < 0) { - /* error */ - ide_state->error = ABRT_ERR; - ide_state->status = READY_STAT | ERR_STAT; - ncq_tfs->drive->port_regs.scr_err |= (1 << ncq_tfs->tag); - } else { - ide_state->status = READY_STAT | SEEK_STAT; +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))) { + ncq_tfs->drive->finished |= (1 << ncq_tfs->tag); } - ahci_write_fis_sdb(ncq_tfs->drive->hba, ncq_tfs->drive->port_no, - (1 << ncq_tfs->tag)); + ahci_write_fis_sdb(ncq_tfs->drive->hba, ncq_tfs); DPRINTF(ncq_tfs->drive->port_no, "NCQ transfer tag %d finished\n", ncq_tfs->tag); @@ -931,6 +915,35 @@ static void ncq_cb(void *opaque, int ret) ncq_tfs->used = 0; } +static void ncq_cb(void *opaque, int ret) +{ + NCQTransferState *ncq_tfs = (NCQTransferState *)opaque; + IDEState *ide_state = &ncq_tfs->drive->port.ifs[0]; + + if (ret == -ECANCELED) { + return; + } + + if (ret < 0) { + bool is_read = ncq_tfs->cmd == READ_FPDMA_QUEUED; + BlockErrorAction action = blk_get_error_action(ide_state->blk, + is_read, -ret); + if (action == BLOCK_ERROR_ACTION_STOP) { + ncq_tfs->halt = true; + ide_state->bus->error_status = IDE_RETRY_HBA; + } else if (action == BLOCK_ERROR_ACTION_REPORT) { + ncq_err(ncq_tfs); + } + blk_error_action(ide_state->blk, action, is_read, -ret); + } else { + ide_state->status = READY_STAT | SEEK_STAT; + } + + if (!ncq_tfs->halt) { + ncq_finish(ncq_tfs); + } +} + static int is_ncq(uint8_t ata_cmd) { /* Based on SATA 3.2 section 13.6.3.2 */ @@ -946,13 +959,60 @@ static int is_ncq(uint8_t ata_cmd) } } +static void execute_ncq_command(NCQTransferState *ncq_tfs) +{ + AHCIDevice *ad = ncq_tfs->drive; + IDEState *ide_state = &ad->port.ifs[0]; + int port = ad->port_no; + + g_assert(is_ncq(ncq_tfs->cmd)); + ncq_tfs->halt = false; + + switch (ncq_tfs->cmd) { + case READ_FPDMA_QUEUED: + DPRINTF(port, "NCQ reading %d sectors from LBA %"PRId64", tag %d\n", + ncq_tfs->sector_count, ncq_tfs->lba, ncq_tfs->tag); + + DPRINTF(port, "tag %d aio read %"PRId64"\n", + ncq_tfs->tag, ncq_tfs->lba); + + dma_acct_start(ide_state->blk, &ncq_tfs->acct, + &ncq_tfs->sglist, BLOCK_ACCT_READ); + ncq_tfs->aiocb = dma_blk_read(ide_state->blk, &ncq_tfs->sglist, + ncq_tfs->lba, ncq_cb, ncq_tfs); + break; + case WRITE_FPDMA_QUEUED: + DPRINTF(port, "NCQ writing %d sectors to LBA %"PRId64", tag %d\n", + ncq_tfs->sector_count, ncq_tfs->lba, ncq_tfs->tag); + + DPRINTF(port, "tag %d aio write %"PRId64"\n", + ncq_tfs->tag, ncq_tfs->lba); + + dma_acct_start(ide_state->blk, &ncq_tfs->acct, + &ncq_tfs->sglist, BLOCK_ACCT_WRITE); + ncq_tfs->aiocb = dma_blk_write(ide_state->blk, &ncq_tfs->sglist, + ncq_tfs->lba, ncq_cb, ncq_tfs); + break; + default: + DPRINTF(port, "error: unsupported NCQ command (0x%02x) received\n", + ncq_tfs->cmd); + qemu_sglist_destroy(&ncq_tfs->sglist); + ncq_err(ncq_tfs); + } +} + + static void process_ncq_command(AHCIState *s, int port, uint8_t *cmd_fis, - int slot) + uint8_t slot) { + AHCIDevice *ad = &s->dev[port]; + IDEState *ide_state = &ad->port.ifs[0]; NCQFrame *ncq_fis = (NCQFrame*)cmd_fis; uint8_t tag = ncq_fis->tag >> 3; - NCQTransferState *ncq_tfs = &s->dev[port].ncq_tfs[tag]; + NCQTransferState *ncq_tfs = &ad->ncq_tfs[tag]; + size_t size; + g_assert(is_ncq(ncq_fis->command)); if (ncq_tfs->used) { /* error - already in use */ fprintf(stderr, "%s: tag %d already used\n", __FUNCTION__, tag); @@ -960,75 +1020,82 @@ static void process_ncq_command(AHCIState *s, int port, uint8_t *cmd_fis, } ncq_tfs->used = 1; - ncq_tfs->drive = &s->dev[port]; + ncq_tfs->drive = ad; ncq_tfs->slot = slot; + ncq_tfs->cmdh = &((AHCICmdHdr *)ad->lst)[slot]; + ncq_tfs->cmd = ncq_fis->command; ncq_tfs->lba = ((uint64_t)ncq_fis->lba5 << 40) | ((uint64_t)ncq_fis->lba4 << 32) | ((uint64_t)ncq_fis->lba3 << 24) | ((uint64_t)ncq_fis->lba2 << 16) | ((uint64_t)ncq_fis->lba1 << 8) | (uint64_t)ncq_fis->lba0; + ncq_tfs->tag = tag; - /* Note: We calculate the sector count, but don't currently rely on it. - * The total size of the DMA buffer tells us the transfer size instead. */ - ncq_tfs->sector_count = ((uint16_t)ncq_fis->sector_count_high << 8) | - ncq_fis->sector_count_low; + /* Sanity-check the NCQ packet */ + if (tag != slot) { + DPRINTF(port, "Warn: NCQ slot (%d) did not match the given tag (%d)\n", + slot, tag); + } - DPRINTF(port, "NCQ transfer LBA from %"PRId64" to %"PRId64", " - "drive max %"PRId64"\n", - ncq_tfs->lba, ncq_tfs->lba + ncq_tfs->sector_count - 2, - s->dev[port].port.ifs[0].nb_sectors - 1); + if (ncq_fis->aux0 || ncq_fis->aux1 || ncq_fis->aux2 || ncq_fis->aux3) { + DPRINTF(port, "Warn: Attempt to use NCQ auxiliary fields.\n"); + } + if (ncq_fis->prio || ncq_fis->icc) { + DPRINTF(port, "Warn: Unsupported attempt to use PRIO/ICC fields\n"); + } + if (ncq_fis->fua & NCQ_FIS_FUA_MASK) { + DPRINTF(port, "Warn: Unsupported attempt to use Force Unit Access\n"); + } + if (ncq_fis->tag & NCQ_FIS_RARC_MASK) { + DPRINTF(port, "Warn: Unsupported attempt to use Rebuild Assist\n"); + } - ahci_populate_sglist(&s->dev[port], &ncq_tfs->sglist, 0); - ncq_tfs->tag = tag; + ncq_tfs->sector_count = ((ncq_fis->sector_count_high << 8) | + ncq_fis->sector_count_low); + if (!ncq_tfs->sector_count) { + ncq_tfs->sector_count = 0x10000; + } + size = ncq_tfs->sector_count * 512; + ahci_populate_sglist(ad, &ncq_tfs->sglist, ncq_tfs->cmdh, size, 0); - switch(ncq_fis->command) { - case READ_FPDMA_QUEUED: - DPRINTF(port, "NCQ reading %d sectors from LBA %"PRId64", " - "tag %d\n", - ncq_tfs->sector_count-1, ncq_tfs->lba, ncq_tfs->tag); + if (ncq_tfs->sglist.size < size) { + error_report("ahci: PRDT length for NCQ command (0x%zx) " + "is smaller than the requested size (0x%zx)", + ncq_tfs->sglist.size, size); + qemu_sglist_destroy(&ncq_tfs->sglist); + ncq_err(ncq_tfs); + ahci_trigger_irq(ad->hba, ad, PORT_IRQ_OVERFLOW); + return; + } else if (ncq_tfs->sglist.size != size) { + DPRINTF(port, "Warn: PRDTL (0x%zx)" + " does not match requested size (0x%zx)", + ncq_tfs->sglist.size, size); + } - DPRINTF(port, "tag %d aio read %"PRId64"\n", - ncq_tfs->tag, ncq_tfs->lba); + DPRINTF(port, "NCQ transfer LBA from %"PRId64" to %"PRId64", " + "drive max %"PRId64"\n", + ncq_tfs->lba, ncq_tfs->lba + ncq_tfs->sector_count - 1, + ide_state->nb_sectors - 1); - dma_acct_start(ncq_tfs->drive->port.ifs[0].blk, &ncq_tfs->acct, - &ncq_tfs->sglist, BLOCK_ACCT_READ); - ncq_tfs->aiocb = dma_blk_read(ncq_tfs->drive->port.ifs[0].blk, - &ncq_tfs->sglist, ncq_tfs->lba, - ncq_cb, ncq_tfs); - break; - case WRITE_FPDMA_QUEUED: - DPRINTF(port, "NCQ writing %d sectors to LBA %"PRId64", tag %d\n", - ncq_tfs->sector_count-1, ncq_tfs->lba, ncq_tfs->tag); - - DPRINTF(port, "tag %d aio write %"PRId64"\n", - ncq_tfs->tag, ncq_tfs->lba); - - dma_acct_start(ncq_tfs->drive->port.ifs[0].blk, &ncq_tfs->acct, - &ncq_tfs->sglist, BLOCK_ACCT_WRITE); - ncq_tfs->aiocb = dma_blk_write(ncq_tfs->drive->port.ifs[0].blk, - &ncq_tfs->sglist, ncq_tfs->lba, - ncq_cb, ncq_tfs); - break; - default: - if (is_ncq(cmd_fis[2])) { - DPRINTF(port, - "error: unsupported NCQ command (0x%02x) received\n", - cmd_fis[2]); - } else { - DPRINTF(port, - "error: tried to process non-NCQ command as NCQ\n"); - } - qemu_sglist_destroy(&ncq_tfs->sglist); + execute_ncq_command(ncq_tfs); +} + +static AHCICmdHdr *get_cmd_header(AHCIState *s, uint8_t port, uint8_t slot) +{ + if (port >= s->ports || slot >= AHCI_MAX_CMDS) { + return NULL; } + + return s->dev[port].lst ? &((AHCICmdHdr *)s->dev[port].lst)[slot] : NULL; } static void handle_reg_h2d_fis(AHCIState *s, int port, - int slot, uint8_t *cmd_fis) + uint8_t slot, uint8_t *cmd_fis) { IDEState *ide_state = &s->dev[port].port.ifs[0]; - AHCICmdHdr *cmd = s->dev[port].cur_cmd; - uint32_t opts = le32_to_cpu(cmd->opts); + AHCICmdHdr *cmd = get_cmd_header(s, port, slot); + uint16_t opts = le16_to_cpu(cmd->opts); if (cmd_fis[1] & 0x0F) { DPRINTF(port, "Port Multiplier not supported." @@ -1108,7 +1175,7 @@ static void handle_reg_h2d_fis(AHCIState *s, int port, ide_exec_cmd(&s->dev[port].port, cmd_fis[2]); } -static int handle_cmd(AHCIState *s, int port, int slot) +static int handle_cmd(AHCIState *s, int port, uint8_t slot) { IDEState *ide_state; uint64_t tbl_addr; @@ -1126,7 +1193,7 @@ static int handle_cmd(AHCIState *s, int port, int slot) DPRINTF(port, "error: lst not given but cmd handled"); return -1; } - cmd = &((AHCICmdHdr *)s->dev[port].lst)[slot]; + cmd = get_cmd_header(s, port, slot); /* remember current slot handle for later */ s->dev[port].cur_cmd = cmd; @@ -1185,7 +1252,7 @@ static void ahci_start_transfer(IDEDMA *dma) IDEState *s = &ad->port.ifs[0]; uint32_t size = (uint32_t)(s->data_end - s->data_ptr); /* write == ram -> device */ - uint32_t opts = le32_to_cpu(ad->cur_cmd->opts); + uint16_t opts = le16_to_cpu(ad->cur_cmd->opts); int is_write = opts & AHCI_CMD_WRITE; int is_atapi = opts & AHCI_CMD_ATAPI; int has_sglist = 0; @@ -1197,7 +1264,7 @@ static void ahci_start_transfer(IDEDMA *dma) goto out; } - if (ahci_dma_prepare_buf(dma, is_write)) { + if (ahci_dma_prepare_buf(dma, size)) { has_sglist = 1; } @@ -1243,16 +1310,34 @@ static void ahci_restart_dma(IDEDMA *dma) } /** + * IDE/PIO restarts are handled by the core layer, but NCQ commands + * need an extra kick from the AHCI HBA. + */ +static void ahci_restart(IDEDMA *dma) +{ + AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); + int i; + + for (i = 0; i < AHCI_MAX_CMDS; i++) { + NCQTransferState *ncq_tfs = &ad->ncq_tfs[i]; + if (ncq_tfs->halt) { + execute_ncq_command(ncq_tfs); + } + } +} + +/** * Called in DMA R/W chains to read the PRDT, utilizing ahci_populate_sglist. * Not currently invoked by PIO R/W chains, * which invoke ahci_populate_sglist via ahci_start_transfer. */ -static int32_t ahci_dma_prepare_buf(IDEDMA *dma, int is_write) +static int32_t ahci_dma_prepare_buf(IDEDMA *dma, int32_t limit) { AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); IDEState *s = &ad->port.ifs[0]; - if (ahci_populate_sglist(ad, &s->sg, s->io_buffer_offset) == -1) { + if (ahci_populate_sglist(ad, &s->sg, ad->cur_cmd, + limit, s->io_buffer_offset) == -1) { DPRINTF(ad->port_no, "ahci_dma_prepare_buf failed.\n"); return -1; } @@ -1287,7 +1372,7 @@ static int ahci_dma_rw_buf(IDEDMA *dma, int is_write) uint8_t *p = s->io_buffer + s->io_buffer_index; int l = s->io_buffer_size - s->io_buffer_index; - if (ahci_populate_sglist(ad, &s->sg, s->io_buffer_offset)) { + if (ahci_populate_sglist(ad, &s->sg, ad->cur_cmd, l, s->io_buffer_offset)) { return 0; } @@ -1330,6 +1415,7 @@ static void ahci_irq_set(void *opaque, int n, int level) static const IDEDMAOps ahci_dma_ops = { .start_dma = ahci_start_dma, + .restart = ahci_restart, .restart_dma = ahci_restart_dma, .start_transfer = ahci_start_transfer, .prepare_buf = ahci_dma_prepare_buf, @@ -1400,6 +1486,21 @@ void ahci_reset(AHCIState *s) } } +static const VMStateDescription vmstate_ncq_tfs = { + .name = "ncq state", + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(sector_count, NCQTransferState), + VMSTATE_UINT64(lba, NCQTransferState), + VMSTATE_UINT8(tag, NCQTransferState), + VMSTATE_UINT8(cmd, NCQTransferState), + VMSTATE_UINT8(slot, NCQTransferState), + VMSTATE_BOOL(used, NCQTransferState), + VMSTATE_BOOL(halt, NCQTransferState), + VMSTATE_END_OF_LIST() + }, +}; + static const VMStateDescription vmstate_ahci_device = { .name = "ahci port", .version_id = 1, @@ -1425,14 +1526,17 @@ static const VMStateDescription vmstate_ahci_device = { VMSTATE_BOOL(done_atapi_packet, AHCIDevice), VMSTATE_INT32(busy_slot, AHCIDevice), VMSTATE_BOOL(init_d2h_sent, AHCIDevice), + VMSTATE_STRUCT_ARRAY(ncq_tfs, AHCIDevice, AHCI_MAX_CMDS, + 1, vmstate_ncq_tfs, NCQTransferState), VMSTATE_END_OF_LIST() }, }; static int ahci_state_post_load(void *opaque, int version_id) { - int i; + int i, j; struct AHCIDevice *ad; + NCQTransferState *ncq_tfs; AHCIState *s = opaque; for (i = 0; i < s->ports; i++) { @@ -1444,6 +1548,37 @@ static int ahci_state_post_load(void *opaque, int version_id) return -1; } + for (j = 0; j < AHCI_MAX_CMDS; j++) { + ncq_tfs = &ad->ncq_tfs[j]; + ncq_tfs->drive = ad; + + if (ncq_tfs->used != ncq_tfs->halt) { + return -1; + } + if (!ncq_tfs->halt) { + continue; + } + if (!is_ncq(ncq_tfs->cmd)) { + return -1; + } + if (ncq_tfs->slot != ncq_tfs->tag) { + return -1; + } + /* If ncq_tfs->halt is justly set, the engine should be engaged, + * and the command list buffer should be mapped. */ + ncq_tfs->cmdh = get_cmd_header(s, i, ncq_tfs->slot); + if (!ncq_tfs->cmdh) { + return -1; + } + ahci_populate_sglist(ncq_tfs->drive, &ncq_tfs->sglist, + ncq_tfs->cmdh, ncq_tfs->sector_count * 512, + 0); + if (ncq_tfs->sector_count != ncq_tfs->sglist.size >> 9) { + return -1; + } + } + + /* * If an error is present, ad->busy_slot will be valid and not -1. * In this case, an operation is waiting to resume and will re-check @@ -1460,7 +1595,7 @@ static int ahci_state_post_load(void *opaque, int version_id) if (ad->busy_slot < 0 || ad->busy_slot >= AHCI_MAX_CMDS) { return -1; } - ad->cur_cmd = &((AHCICmdHdr *)ad->lst)[ad->busy_slot]; + ad->cur_cmd = get_cmd_header(s, i, ad->busy_slot); } } diff --git a/hw/ide/ahci.h b/hw/ide/ahci.h index 501c002c31..9f5b4d20b5 100644 --- a/hw/ide/ahci.h +++ b/hw/ide/ahci.h @@ -195,6 +195,9 @@ #define RECEIVE_FPDMA_QUEUED 0x65 #define SEND_FPDMA_QUEUED 0x64 +#define NCQ_FIS_FUA_MASK 0x80 +#define NCQ_FIS_RARC_MASK 0x01 + #define RES_FIS_DSFIS 0x00 #define RES_FIS_PSFIS 0x20 #define RES_FIS_RFIS 0x40 @@ -233,7 +236,8 @@ typedef struct AHCIPortRegs { } AHCIPortRegs; typedef struct AHCICmdHdr { - uint32_t opts; + uint16_t opts; + uint16_t prdtl; uint32_t status; uint64_t tbl_addr; uint32_t reserved[4]; @@ -250,13 +254,16 @@ typedef struct AHCIDevice AHCIDevice; typedef struct NCQTransferState { AHCIDevice *drive; BlockAIOCB *aiocb; + AHCICmdHdr *cmdh; QEMUSGList sglist; BlockAcctCookie acct; - uint16_t sector_count; + uint32_t sector_count; uint64_t lba; uint8_t tag; - int slot; - int used; + uint8_t cmd; + uint8_t slot; + bool used; + bool halt; } NCQTransferState; struct AHCIDevice { @@ -312,27 +319,39 @@ extern const VMStateDescription vmstate_ahci; .offset = vmstate_offset_value(_state, _field, AHCIState), \ } +/** + * NCQFrame is the same as a Register H2D FIS (described in SATA 3.2), + * but some fields have been re-mapped and re-purposed, as seen in + * SATA 3.2 section 13.6.4.1 ("READ FPDMA QUEUED") + * + * cmd_fis[3], feature 7:0, becomes sector count 7:0. + * cmd_fis[7], device 7:0, uses bit 7 as the Force Unit Access bit. + * cmd_fis[11], feature 15:8, becomes sector count 15:8. + * cmd_fis[12], count 7:0, becomes the NCQ TAG (7:3) and RARC bit (0) + * cmd_fis[13], count 15:8, becomes the priority value (7:6) + * bytes 16-19 become an le32 "auxiliary" field. + */ typedef struct NCQFrame { uint8_t fis_type; uint8_t c; uint8_t command; - uint8_t sector_count_low; + uint8_t sector_count_low; /* (feature 7:0) */ uint8_t lba0; uint8_t lba1; uint8_t lba2; - uint8_t fua; + uint8_t fua; /* (device 7:0) */ uint8_t lba3; uint8_t lba4; uint8_t lba5; - uint8_t sector_count_high; - uint8_t tag; - uint8_t reserved5; - uint8_t reserved6; + uint8_t sector_count_high; /* (feature 15:8) */ + uint8_t tag; /* (count 0:7) */ + uint8_t prio; /* (count 15:8) */ + uint8_t icc; uint8_t control; - uint8_t reserved7; - uint8_t reserved8; - uint8_t reserved9; - uint8_t reserved10; + uint8_t aux0; + uint8_t aux1; + uint8_t aux2; + uint8_t aux3; } QEMU_PACKED NCQFrame; typedef struct SDBFIS { diff --git a/hw/ide/core.c b/hw/ide/core.c index 1efd98af63..122e955084 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -716,8 +716,8 @@ static void ide_dma_cb(void *opaque, int ret) sector_num = ide_get_sector(s); if (n > 0) { - assert(s->io_buffer_size == s->sg.size); - dma_buf_commit(s, s->io_buffer_size); + assert(n * 512 == s->sg.size); + dma_buf_commit(s, s->sg.size); sector_num += n; ide_set_sector(s, sector_num); s->nsector -= n; @@ -734,7 +734,7 @@ static void ide_dma_cb(void *opaque, int ret) n = s->nsector; s->io_buffer_index = 0; s->io_buffer_size = n * 512; - if (s->bus->dma->ops->prepare_buf(s->bus->dma, ide_cmd_is_read(s)) < 512) { + if (s->bus->dma->ops->prepare_buf(s->bus->dma, s->io_buffer_size) < 512) { /* The PRDs were too short. Reset the Active bit, but don't raise an * interrupt. */ s->status = READY_STAT | SEEK_STAT; @@ -2326,7 +2326,7 @@ static void ide_nop(IDEDMA *dma) { } -static int32_t ide_nop_int32(IDEDMA *dma, int x) +static int32_t ide_nop_int32(IDEDMA *dma, int32_t l) { return 0; } @@ -2371,6 +2371,13 @@ static void ide_restart_bh(void *opaque) * called function can set a new error status. */ bus->error_status = 0; + /* The HBA has generically asked to be kicked on retry */ + if (error_status & IDE_RETRY_HBA) { + if (s->bus->dma->ops->restart) { + s->bus->dma->ops->restart(s->bus->dma); + } + } + if (error_status & IDE_RETRY_DMA) { if (error_status & IDE_RETRY_TRIM) { ide_restart_dma(s, IDE_DMA_TRIM); diff --git a/hw/ide/internal.h b/hw/ide/internal.h index 965cc55cb8..30fdcbc5fa 100644 --- a/hw/ide/internal.h +++ b/hw/ide/internal.h @@ -324,7 +324,7 @@ typedef void EndTransferFunc(IDEState *); typedef void DMAStartFunc(IDEDMA *, IDEState *, BlockCompletionFunc *); typedef void DMAVoidFunc(IDEDMA *); typedef int DMAIntFunc(IDEDMA *, int); -typedef int32_t DMAInt32Func(IDEDMA *, int); +typedef int32_t DMAInt32Func(IDEDMA *, int32_t len); typedef void DMAu32Func(IDEDMA *, uint32_t); typedef void DMAStopFunc(IDEDMA *, bool); typedef void DMARestartFunc(void *, int, RunState); @@ -436,6 +436,7 @@ struct IDEDMAOps { DMAInt32Func *prepare_buf; DMAu32Func *commit_buf; DMAIntFunc *rw_buf; + DMAVoidFunc *restart; DMAVoidFunc *restart_dma; DMAStopFunc *set_inactive; DMAVoidFunc *cmd_done; @@ -499,6 +500,7 @@ struct IDEDevice { #define IDE_RETRY_READ 0x20 #define IDE_RETRY_FLUSH 0x40 #define IDE_RETRY_TRIM 0x80 +#define IDE_RETRY_HBA 0x100 static inline IDEState *idebus_active_if(IDEBus *bus) { diff --git a/hw/ide/macio.c b/hw/ide/macio.c index dd52d50732..a55a479da6 100644 --- a/hw/ide/macio.c +++ b/hw/ide/macio.c @@ -499,7 +499,7 @@ static int ide_nop_int(IDEDMA *dma, int x) return 0; } -static int32_t ide_nop_int32(IDEDMA *dma, int x) +static int32_t ide_nop_int32(IDEDMA *dma, int32_t l) { return 0; } diff --git a/hw/ide/pci.c b/hw/ide/pci.c index 4afd0cfe8c..d31ff885b7 100644 --- a/hw/ide/pci.c +++ b/hw/ide/pci.c @@ -53,10 +53,14 @@ static void bmdma_start_dma(IDEDMA *dma, IDEState *s, } /** - * Return the number of bytes successfully prepared. - * -1 on error. + * Prepare an sglist based on available PRDs. + * @limit: How many bytes to prepare total. + * + * Returns the number of bytes prepared, -1 on error. + * IDEState.io_buffer_size will contain the number of bytes described + * by the PRDs, whether or not we added them to the sglist. */ -static int32_t bmdma_prepare_buf(IDEDMA *dma, int is_write) +static int32_t bmdma_prepare_buf(IDEDMA *dma, int32_t limit) { BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma); IDEState *s = bmdma_active_if(bm); @@ -75,7 +79,7 @@ static int32_t bmdma_prepare_buf(IDEDMA *dma, int is_write) /* end of table (with a fail safe of one page) */ if (bm->cur_prd_last || (bm->cur_addr - bm->addr) >= BMDMA_PAGE_SIZE) { - return s->io_buffer_size; + return s->sg.size; } pci_dma_read(pci_dev, bm->cur_addr, &prd, 8); bm->cur_addr += 8; @@ -90,7 +94,14 @@ static int32_t bmdma_prepare_buf(IDEDMA *dma, int is_write) } l = bm->cur_prd_len; if (l > 0) { - qemu_sglist_add(&s->sg, bm->cur_prd_addr, l); + uint64_t sg_len; + + /* Don't add extra bytes to the SGList; consume any remaining + * PRDs from the guest, but ignore them. */ + sg_len = MIN(limit - s->sg.size, bm->cur_prd_len); + if (sg_len) { + qemu_sglist_add(&s->sg, bm->cur_prd_addr, sg_len); + } /* Note: We limit the max transfer to be 2GiB. * This should accommodate the largest ATA transaction |