uint8_t name;
uint8_t port;
uint8_t slot;
+ uint8_t errors;
uint32_t interrupts;
uint64_t xbytes;
uint32_t prd_size;
+ uint32_t sector_size;
uint64_t buffer;
AHCICommandProp *props;
/* Data to be transferred to the guest */
/**
* Locate, verify, and return a handle to the AHCI device.
*/
-QPCIDevice *get_ahci_device(uint32_t *fingerprint)
+QPCIDevice *get_ahci_device(QTestState *qts, uint32_t *fingerprint)
{
QPCIDevice *ahci;
uint32_t ahci_fingerprint;
QPCIBus *pcibus;
- pcibus = qpci_init_pc(NULL);
+ pcibus = qpci_new_pc(qts, NULL);
/* Find the AHCI PCI device and verify it's the right one. */
ahci = qpci_device_find(pcibus, QPCI_DEVFN(0x1F, 0x02));
void start_ahci_device(AHCIQState *ahci)
{
/* Map AHCI's ABAR (BAR5) */
- ahci->hba_base = qpci_iomap(ahci->dev, 5, &ahci->barsize);
- g_assert(ahci->hba_base);
+ ahci->hba_bar = qpci_iomap(ahci->dev, 5, &ahci->barsize);
/* turns on pci.cmd.iose, pci.cmd.mse and pci.cmd.bme */
qpci_device_enable(ahci->dev);
/* Allocate Memory for the Command List Buffer & FIS Buffer */
/* PxCLB space ... 0x20 per command, as in 4.2.2 p 36 */
ahci->port[i].clb = ahci_alloc(ahci, num_cmd_slots * 0x20);
- qmemset(ahci->port[i].clb, 0x00, num_cmd_slots * 0x20);
+ qtest_memset(ahci->parent->qts, ahci->port[i].clb, 0x00,
+ num_cmd_slots * 0x20);
g_test_message("CLB: 0x%08" PRIx64, ahci->port[i].clb);
ahci_px_wreg(ahci, i, AHCI_PX_CLB, ahci->port[i].clb);
g_assert_cmphex(ahci->port[i].clb, ==,
/* PxFB space ... 0x100, as in 4.2.1 p 35 */
ahci->port[i].fb = ahci_alloc(ahci, 0x100);
- qmemset(ahci->port[i].fb, 0x00, 0x100);
+ qtest_memset(ahci->parent->qts, ahci->port[i].fb, 0x00, 0x100);
g_test_message("FB: 0x%08" PRIx64, ahci->port[i].fb);
ahci_px_wreg(ahci, i, AHCI_PX_FB, ahci->port[i].fb);
g_assert_cmphex(ahci->port[i].fb, ==,
reg = ahci_rreg(ahci, AHCI_GHC);
ASSERT_BIT_SET(reg, AHCI_GHC_IE);
+ ahci->enabled = true;
/* TODO: The device should now be idling and waiting for commands.
* In the future, a small test-case to inspect the Register D2H FIS
* and clear the initial interrupts might be good. */
g_assert_cmphex(ahci_px_rreg(ahci, port, AHCI_PX_IS), ==, 0);
/* Wipe the FIS-Receive Buffer */
- qmemset(ahci->port[port].fb, 0x00, 0x100);
+ qtest_memset(ahci->parent->qts, ahci->port[port].fb, 0x00, 0x100);
}
/**
* Check a port for errors.
*/
-void ahci_port_check_error(AHCIQState *ahci, uint8_t port)
+void ahci_port_check_error(AHCIQState *ahci, uint8_t port,
+ uint32_t imask, uint8_t emask)
{
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);
/* The TFD also has two error sections. */
reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD);
- ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_STS_ERR);
- ASSERT_BIT_CLEAR(reg, AHCI_PX_TFD_ERR);
+ if (!emask) {
+ 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));
}
void ahci_port_check_interrupts(AHCIQState *ahci, uint8_t port,
RegD2HFIS *d2h = g_malloc0(0x20);
uint32_t reg;
- memread(ahci->port[port].fb + 0x40, d2h, 0x20);
+ qtest_memread(ahci->parent->qts, ahci->port[port].fb + 0x40, d2h, 0x20);
g_assert_cmphex(d2h->fis_type, ==, 0x34);
reg = ahci_px_rreg(ahci, port, AHCI_PX_TFD);
g_free(d2h);
}
-void ahci_port_check_pio_sanity(AHCIQState *ahci, uint8_t port,
- uint8_t slot, size_t buffsize)
+void ahci_port_check_pio_sanity(AHCIQState *ahci, AHCICommand *cmd)
{
PIOSetupFIS *pio = g_malloc0(0x20);
+ uint8_t port = cmd->port;
/* We cannot check the Status or E_Status registers, because
* the status may have again changed between the PIO Setup FIS
* and the conclusion of the command with the D2H Register FIS. */
- memread(ahci->port[port].fb + 0x20, pio, 0x20);
+ qtest_memread(ahci->parent->qts, ahci->port[port].fb + 0x20, pio, 0x20);
g_assert_cmphex(pio->fis_type, ==, 0x5f);
- /* BUG: PIO Setup FIS as utilized by QEMU tries to fit the entire
- * transfer size in a uint16_t field. The maximum transfer size can
- * eclipse this; the field is meant to convey the size of data per
- * each Data FIS, not the entire operation as a whole. For now,
- * we will sanity check the broken case where applicable. */
- if (buffsize <= UINT16_MAX) {
- g_assert_cmphex(le16_to_cpu(pio->tx_count), ==, buffsize);
+ /* Data transferred by PIO will either be:
+ * (1) 12 or 16 bytes for an ATAPI command packet (QEMU always uses 12), or
+ * (2) Actual data from the drive.
+ * If we do both, (2) winds up erasing any evidence of (1).
+ */
+ if (cmd->props->atapi && (cmd->xbytes == 0 || cmd->props->dma)) {
+ g_assert(le16_to_cpu(pio->tx_count) == 12 ||
+ le16_to_cpu(pio->tx_count) == 16);
+ } else {
+ /* The AHCI test suite here does not test any PIO command that specifies
+ * a DRQ block larger than one sector (like 0xC4), so this should always
+ * be one sector or less. */
+ size_t pio_len = ((cmd->xbytes % cmd->sector_size) ?
+ (cmd->xbytes % cmd->sector_size) : cmd->sector_size);
+ g_assert_cmphex(le16_to_cpu(pio->tx_count), ==, pio_len);
}
-
g_free(pio);
}
{
uint64_t ba = ahci->port[port].clb;
ba += slot * sizeof(AHCICommandHeader);
- memread(ba, cmd, sizeof(AHCICommandHeader));
+ qtest_memread(ahci->parent->qts, ba, cmd, sizeof(AHCICommandHeader));
cmd->flags = le16_to_cpu(cmd->flags);
cmd->prdtl = le16_to_cpu(cmd->prdtl);
tmp.prdbc = cpu_to_le32(cmd->prdbc);
tmp.ctba = cpu_to_le64(cmd->ctba);
- memwrite(ba, &tmp, sizeof(AHCICommandHeader));
+ qtest_memwrite(ahci->parent->qts, ba, &tmp, sizeof(AHCICommandHeader));
}
void ahci_destroy_command(AHCIQState *ahci, uint8_t port, uint8_t slot)
tmp.count = cpu_to_le16(tmp.count);
}
- memwrite(addr, &tmp, sizeof(tmp));
+ qtest_memwrite(ahci->parent->qts, addr, &tmp, sizeof(tmp));
}
unsigned ahci_pick_cmd(AHCIQState *ahci, uint8_t port)
if (opts->size && !opts->buffer) {
opts->buffer = ahci_alloc(ahci, opts->size);
g_assert(opts->buffer);
- qmemset(opts->buffer, 0x00, opts->size);
+ qtest_memset(ahci->parent->qts, opts->buffer, 0x00, opts->size);
}
/* Command creation */
if (opts->atapi) {
- cmd = ahci_atapi_command_create(op);
- if (opts->atapi_dma) {
- ahci_command_enable_atapi_dma(cmd);
- }
+ uint16_t bcl = opts->set_bcl ? opts->bcl : ATAPI_SECTOR_SIZE;
+ cmd = ahci_atapi_command_create(op, bcl, opts->atapi_dma);
} else {
cmd = ahci_command_create(op);
}
ahci_command_commit(ahci, cmd, port);
ahci_command_issue_async(ahci, cmd);
if (opts->error) {
- qmp_eventwait("STOP");
+ qtest_qmp_eventwait(ahci->parent->qts, "STOP");
}
if (opts->mid_cb) {
rc = opts->mid_cb(ahci, cmd, opts);
g_assert_cmpint(rc, ==, 0);
}
if (opts->error) {
- qmp_async("{'execute':'cont' }");
- qmp_eventwait("RESUME");
+ qtest_qmp_send(ahci->parent->qts, "{'execute':'cont' }");
+ qtest_qmp_eventwait(ahci->parent->qts, "RESUME");
}
/* Wait for command to complete and verify sanity */
ahci_command_adjust(cmd, sector, buffer, bufsize, 0);
ahci_command_commit(ahci, cmd, port);
ahci_command_issue_async(ahci, cmd);
- qmp_eventwait("STOP");
+ qtest_qmp_eventwait(ahci->parent->qts, "STOP");
return cmd;
}
void ahci_guest_io_resume(AHCIQState *ahci, AHCICommand *cmd)
{
/* Complete the command */
- qmp_async("{'execute':'cont' }");
- qmp_eventwait("RESUME");
+ qtest_qmp_send(ahci->parent->qts, "{'execute':'cont' }");
+ qtest_qmp_eventwait(ahci->parent->qts, "RESUME");
ahci_command_wait(ahci, cmd);
ahci_command_verify(ahci, cmd);
ahci_command_free(cmd);
g_assert(props);
ptr = ahci_alloc(ahci, bufsize);
g_assert(!bufsize || ptr);
- qmemset(ptr, 0x00, bufsize);
+ qtest_memset(ahci->parent->qts, ptr, 0x00, bufsize);
if (bufsize && props->write) {
- bufwrite(ptr, buffer, bufsize);
+ qtest_bufwrite(ahci->parent->qts, ptr, buffer, bufsize);
}
ahci_guest_io(ahci, port, ide_cmd, ptr, bufsize, sector);
if (bufsize && props->read) {
- bufread(ptr, buffer, bufsize);
+ qtest_bufread(ahci->parent->qts, ptr, buffer, bufsize);
}
ahci_free(ahci, ptr);
static void command_table_init(AHCICommand *cmd)
{
RegH2DFIS *fis = &(cmd->fis);
- uint16_t sect_count = (cmd->xbytes / AHCI_SECTOR_SIZE);
+ uint16_t sect_count = (cmd->xbytes / cmd->sector_size);
fis->fis_type = REG_H2D_FIS;
fis->flags = REG_H2D_FIS_CMD; /* "Command" bit */
if (cmd->props->lba28 || cmd->props->lba48) {
fis->device = ATA_DEVICE_LBA;
}
- fis->count = (cmd->xbytes / AHCI_SECTOR_SIZE);
+ fis->count = (cmd->xbytes / cmd->sector_size);
}
fis->icc = 0x00;
fis->control = 0x00;
RegH2DFIS *fis = &(cmd->fis);
g_assert(cmd->props->atapi);
fis->feature_low |= 0x01;
- cmd->interrupts &= ~AHCI_PX_IS_PSS;
+ /* PIO is still used to transfer the ATAPI command */
+ g_assert(cmd->props->pio);
cmd->props->dma = true;
- cmd->props->pio = false;
/* BUG: We expect the DMA Setup interrupt for DMA commands */
/* cmd->interrupts |= AHCI_PX_IS_DSS; */
}
AHCICommand *cmd;
g_assert(props);
- cmd = g_malloc0(sizeof(AHCICommand));
- g_assert(!(props->dma && props->pio));
+ cmd = g_new0(AHCICommand, 1);
+ g_assert(!(props->dma && props->pio) || props->atapi);
g_assert(!(props->lba28 && props->lba48));
g_assert(!(props->read && props->write));
g_assert(!props->size || props->data);
cmd->xbytes = props->size;
cmd->prd_size = 4096;
cmd->buffer = 0xabad1dea;
+ cmd->sector_size = props->atapi ? ATAPI_SECTOR_SIZE : AHCI_SECTOR_SIZE;
if (!cmd->props->ncq) {
cmd->interrupts = AHCI_PX_IS_DHRS;
/* cmd->interrupts |= props->data ? AHCI_PX_IS_DPS : 0; */
/* BUG: We expect the DMA Setup interrupt for DMA commands */
/* cmd->interrupts |= props->dma ? AHCI_PX_IS_DSS : 0; */
- cmd->interrupts |= props->pio ? AHCI_PX_IS_PSS : 0;
cmd->interrupts |= props->ncq ? AHCI_PX_IS_SDBS : 0;
command_header_init(cmd);
return cmd;
}
-AHCICommand *ahci_atapi_command_create(uint8_t scsi_cmd)
+AHCICommand *ahci_atapi_command_create(uint8_t scsi_cmd, uint16_t bcl, bool dma)
{
AHCICommand *cmd = ahci_command_create(CMD_PACKET);
cmd->atapi_cmd = g_malloc0(16);
cmd->atapi_cmd[0] = scsi_cmd;
- /* ATAPI needs a PIO transfer chunk size set inside of the LBA registers.
- * The block/sector size is a natural default. */
- cmd->fis.lba_lo[1] = ATAPI_SECTOR_SIZE >> 8 & 0xFF;
- cmd->fis.lba_lo[2] = ATAPI_SECTOR_SIZE & 0xFF;
-
+ stw_le_p(&cmd->fis.lba_lo[1], bcl);
+ if (dma) {
+ ahci_command_enable_atapi_dma(cmd);
+ } else {
+ cmd->interrupts |= bcl ? AHCI_PX_IS_PSS : 0;
+ }
return cmd;
}
+void ahci_atapi_test_ready(AHCIQState *ahci, uint8_t port,
+ bool ready, uint8_t expected_sense)
+{
+ AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_TEST_UNIT_READY, 0, false);
+ ahci_command_set_size(cmd, 0);
+ if (!ready) {
+ cmd->interrupts |= AHCI_PX_IS_TFES;
+ cmd->errors |= expected_sense << 4;
+ }
+ ahci_command_commit(ahci, cmd, port);
+ ahci_command_issue(ahci, cmd);
+ ahci_command_verify(ahci, cmd);
+ ahci_command_free(cmd);
+}
+
+static int copy_buffer(AHCIQState *ahci, AHCICommand *cmd,
+ const AHCIOpts *opts)
+{
+ unsigned char *rx = opts->opaque;
+ qtest_bufread(ahci->parent->qts, opts->buffer, rx, opts->size);
+ return 0;
+}
+
+void ahci_atapi_get_sense(AHCIQState *ahci, uint8_t port,
+ uint8_t *sense, uint8_t *asc)
+{
+ unsigned char *rx;
+ AHCIOpts opts = {
+ .size = 18,
+ .atapi = true,
+ .post_cb = copy_buffer,
+ };
+ rx = g_malloc(18);
+ opts.opaque = rx;
+
+ ahci_exec(ahci, port, CMD_ATAPI_REQUEST_SENSE, &opts);
+
+ *sense = rx[2];
+ *asc = rx[12];
+
+ g_free(rx);
+}
+
+void ahci_atapi_eject(AHCIQState *ahci, uint8_t port)
+{
+ AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_START_STOP_UNIT, 0, false);
+ ahci_command_set_size(cmd, 0);
+
+ cmd->atapi_cmd[4] = 0x02; /* loej = true */
+ ahci_command_commit(ahci, cmd, port);
+ ahci_command_issue(ahci, cmd);
+ ahci_command_verify(ahci, cmd);
+ ahci_command_free(cmd);
+}
+
+void ahci_atapi_load(AHCIQState *ahci, uint8_t port)
+{
+ AHCICommand *cmd = ahci_atapi_command_create(CMD_ATAPI_START_STOP_UNIT, 0, false);
+ ahci_command_set_size(cmd, 0);
+
+ cmd->atapi_cmd[4] = 0x03; /* loej,start = true */
+ ahci_command_commit(ahci, cmd, port);
+ ahci_command_issue(ahci, cmd);
+ ahci_command_verify(ahci, cmd);
+ ahci_command_free(cmd);
+}
+
void ahci_command_free(AHCICommand *cmd)
{
g_free(cmd->atapi_cmd);
switch (cbd[0]) {
case CMD_ATAPI_READ_10:
+ case CMD_ATAPI_READ_CD:
g_assert_cmpuint(lba, <=, UINT32_MAX);
stl_be_p(&cbd[2], lba);
break;
+ case CMD_ATAPI_REQUEST_SENSE:
+ case CMD_ATAPI_TEST_UNIT_READY:
+ case CMD_ATAPI_START_STOP_UNIT:
+ g_assert_cmpuint(lba, ==, 0x00);
+ break;
default:
/* SCSI doesn't have uniform packet formats,
* so you have to add support for it manually. Sorry! */
+ fprintf(stderr, "The Libqos AHCI driver does not support the "
+ "set_offset operation for ATAPI command 0x%02x, "
+ "please add support.\n",
+ cbd[0]);
g_assert_not_reached();
}
}
static void ahci_atapi_set_size(AHCICommand *cmd, uint64_t xbytes)
{
unsigned char *cbd = cmd->atapi_cmd;
- uint64_t nsectors = xbytes / 2048;
+ uint64_t nsectors = xbytes / ATAPI_SECTOR_SIZE;
+ uint32_t tmp;
g_assert(cbd);
switch (cbd[0]) {
g_assert_cmpuint(nsectors, <=, UINT16_MAX);
stw_be_p(&cbd[7], nsectors);
break;
+ case CMD_ATAPI_READ_CD:
+ /* 24bit BE store */
+ g_assert_cmpuint(nsectors, <, 1ULL << 24);
+ tmp = nsectors;
+ cbd[6] = (tmp & 0xFF0000) >> 16;
+ cbd[7] = (tmp & 0xFF00) >> 8;
+ cbd[8] = (tmp & 0xFF);
+ break;
+ case CMD_ATAPI_REQUEST_SENSE:
+ g_assert_cmpuint(xbytes, <=, UINT8_MAX);
+ cbd[4] = (uint8_t)xbytes;
+ break;
+ case CMD_ATAPI_TEST_UNIT_READY:
+ case CMD_ATAPI_START_STOP_UNIT:
+ g_assert_cmpuint(xbytes, ==, 0);
+ break;
default:
/* SCSI doesn't have uniform packet formats,
* so you have to add support for it manually. Sorry! */
+ fprintf(stderr, "The Libqos AHCI driver does not support the set_size "
+ "operation for ATAPI command 0x%02x, please add support.\n",
+ cbd[0]);
g_assert_not_reached();
}
}
cmd->prd_size = prd_size;
}
cmd->xbytes = xbytes;
- sect_count = (cmd->xbytes / AHCI_SECTOR_SIZE);
+ sect_count = (cmd->xbytes / cmd->sector_size);
if (cmd->props->ncq) {
NCQFIS *nfis = (NCQFIS *)&(cmd->fis);
} else if (cmd->props->atapi) {
ahci_atapi_set_size(cmd, xbytes);
} else {
+ /* For writes, the PIO Setup FIS interrupt only comes from DRQs
+ * after the first.
+ */
+ if (cmd->props->pio && sect_count > (cmd->props->read ? 0 : 1)) {
+ cmd->interrupts |= AHCI_PX_IS_PSS;
+ }
cmd->fis.count = sect_count;
}
cmd->header.prdtl = size_to_prdtl(cmd->xbytes, cmd->prd_size);
ahci_write_fis(ahci, cmd);
/* Then ATAPI CMD, if needed */
if (cmd->props->atapi) {
- memwrite(table_ptr + 0x40, cmd->atapi_cmd, 16);
+ qtest_memwrite(ahci->parent->qts, table_ptr + 0x40, cmd->atapi_cmd, 16);
}
/* Construct and write the PRDs to the command table */
prd.dbc |= cpu_to_le32(0x80000000); /* Request DPS Interrupt */
/* Commit the PRD entry to the Command Table */
- memwrite(table_ptr + 0x80 + (i * sizeof(PRD)),
- &prd, sizeof(PRD));
+ qtest_memwrite(ahci->parent->qts, table_ptr + 0x80 + (i * sizeof(PRD)),
+ &prd, sizeof(PRD));
}
/* Bookmark the PRDTL and CTBA values */
uint8_t slot = cmd->slot;
uint8_t port = cmd->port;
- ahci_port_check_error(ahci, 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_cmd_sanity(ahci, cmd);
ahci_port_check_d2h_sanity(ahci, port, slot);
}
if (cmd->props->pio) {
- ahci_port_check_pio_sanity(ahci, port, slot, cmd->xbytes);
+ ahci_port_check_pio_sanity(ahci, cmd);
}
}