diff options
Diffstat (limited to 'qemu/tests/ide-test.c')
-rw-r--r-- | qemu/tests/ide-test.c | 275 |
1 files changed, 247 insertions, 28 deletions
diff --git a/qemu/tests/ide-test.c b/qemu/tests/ide-test.c index 4a07e3a9d..0d9ab4df9 100644 --- a/qemu/tests/ide-test.c +++ b/qemu/tests/ide-test.c @@ -22,9 +22,7 @@ * THE SOFTWARE. */ -#include <stdint.h> -#include <string.h> -#include <stdio.h> +#include "qemu/osdep.h" #include <glib.h> @@ -45,8 +43,15 @@ #define IDE_BASE 0x1f0 #define IDE_PRIMARY_IRQ 14 +#define ATAPI_BLOCK_SIZE 2048 + +/* How many bytes to receive via ATAPI PIO at one time. + * Must be less than 0xFFFF. */ +#define BYTE_COUNT_LIMIT 5120 + enum { reg_data = 0x0, + reg_feature = 0x1, reg_nsectors = 0x2, reg_lba_low = 0x3, reg_lba_middle = 0x4, @@ -80,6 +85,7 @@ enum { CMD_WRITE_DMA = 0xca, CMD_FLUSH_CACHE = 0xe7, CMD_IDENTIFY = 0xec, + CMD_PACKET = 0xa0, CMDF_ABORT = 0x100, CMDF_NO_BM = 0x200, @@ -172,7 +178,8 @@ typedef struct PrdtEntry { #define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0) static int send_dma_request(int cmd, uint64_t sector, int nb_sectors, - PrdtEntry *prdt, int prdt_entries) + PrdtEntry *prdt, int prdt_entries, + void(*post_exec)(uint64_t sector, int nb_sectors)) { QPCIDevice *dev; uint16_t bmdma_base; @@ -189,6 +196,9 @@ static int send_dma_request(int cmd, uint64_t sector, int nb_sectors, switch (cmd) { case CMD_READ_DMA: + case CMD_PACKET: + /* Assuming we only test data reads w/ ATAPI, otherwise we need to know + * the SCSI command being sent in the packet, too. */ from_dev = true; break; case CMD_WRITE_DMA: @@ -217,14 +227,22 @@ static int send_dma_request(int cmd, uint64_t sector, int nb_sectors, outl(bmdma_base + bmreg_prdt, guest_prdt); /* ATA DMA command */ - outb(IDE_BASE + reg_nsectors, nb_sectors); - - outb(IDE_BASE + reg_lba_low, sector & 0xff); - outb(IDE_BASE + reg_lba_middle, (sector >> 8) & 0xff); - outb(IDE_BASE + reg_lba_high, (sector >> 16) & 0xff); + if (cmd == CMD_PACKET) { + /* Enables ATAPI DMA; otherwise PIO is attempted */ + outb(IDE_BASE + reg_feature, 0x01); + } else { + outb(IDE_BASE + reg_nsectors, nb_sectors); + outb(IDE_BASE + reg_lba_low, sector & 0xff); + outb(IDE_BASE + reg_lba_middle, (sector >> 8) & 0xff); + outb(IDE_BASE + reg_lba_high, (sector >> 16) & 0xff); + } outb(IDE_BASE + reg_command, cmd); + if (post_exec) { + post_exec(sector, nb_sectors); + } + /* Start DMA transfer */ outb(bmdma_base + bmreg_cmd, BM_CMD_START | (from_dev ? BM_CMD_WRITE : 0)); @@ -278,7 +296,8 @@ static void test_bmdma_simple_rw(void) memset(buf, 0x55, len); memwrite(guest_buf, buf, len); - status = send_dma_request(CMD_WRITE_DMA, 0, 1, prdt, ARRAY_SIZE(prdt)); + status = send_dma_request(CMD_WRITE_DMA, 0, 1, prdt, + ARRAY_SIZE(prdt), NULL); g_assert_cmphex(status, ==, BM_STS_INTR); assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); @@ -286,14 +305,15 @@ static void test_bmdma_simple_rw(void) memset(buf, 0xaa, len); memwrite(guest_buf, buf, len); - status = send_dma_request(CMD_WRITE_DMA, 1, 1, prdt, ARRAY_SIZE(prdt)); + status = send_dma_request(CMD_WRITE_DMA, 1, 1, prdt, + ARRAY_SIZE(prdt), NULL); g_assert_cmphex(status, ==, BM_STS_INTR); assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); /* Read and verify 0x55 pattern in sector 0 */ memset(cmpbuf, 0x55, len); - status = send_dma_request(CMD_READ_DMA, 0, 1, prdt, ARRAY_SIZE(prdt)); + status = send_dma_request(CMD_READ_DMA, 0, 1, prdt, ARRAY_SIZE(prdt), NULL); g_assert_cmphex(status, ==, BM_STS_INTR); assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); @@ -303,7 +323,7 @@ static void test_bmdma_simple_rw(void) /* Read and verify 0xaa pattern in sector 1 */ memset(cmpbuf, 0xaa, len); - status = send_dma_request(CMD_READ_DMA, 1, 1, prdt, ARRAY_SIZE(prdt)); + status = send_dma_request(CMD_READ_DMA, 1, 1, prdt, ARRAY_SIZE(prdt), NULL); g_assert_cmphex(status, ==, BM_STS_INTR); assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); @@ -328,13 +348,13 @@ static void test_bmdma_short_prdt(void) /* Normal request */ status = send_dma_request(CMD_READ_DMA, 0, 1, - prdt, ARRAY_SIZE(prdt)); + prdt, ARRAY_SIZE(prdt), NULL); g_assert_cmphex(status, ==, 0); assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); /* Abort the request before it completes */ status = send_dma_request(CMD_READ_DMA | CMDF_ABORT, 0, 1, - prdt, ARRAY_SIZE(prdt)); + prdt, ARRAY_SIZE(prdt), NULL); g_assert_cmphex(status, ==, 0); assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); } @@ -353,13 +373,13 @@ static void test_bmdma_one_sector_short_prdt(void) /* Normal request */ status = send_dma_request(CMD_READ_DMA, 0, 2, - prdt, ARRAY_SIZE(prdt)); + prdt, ARRAY_SIZE(prdt), NULL); g_assert_cmphex(status, ==, 0); assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); /* Abort the request before it completes */ status = send_dma_request(CMD_READ_DMA | CMDF_ABORT, 0, 2, - prdt, ARRAY_SIZE(prdt)); + prdt, ARRAY_SIZE(prdt), NULL); g_assert_cmphex(status, ==, 0); assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); } @@ -377,13 +397,13 @@ static void test_bmdma_long_prdt(void) /* Normal request */ status = send_dma_request(CMD_READ_DMA, 0, 1, - prdt, ARRAY_SIZE(prdt)); + prdt, ARRAY_SIZE(prdt), NULL); g_assert_cmphex(status, ==, BM_STS_ACTIVE | BM_STS_INTR); assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); /* Abort the request before it completes */ status = send_dma_request(CMD_READ_DMA | CMDF_ABORT, 0, 1, - prdt, ARRAY_SIZE(prdt)); + prdt, ARRAY_SIZE(prdt), NULL); g_assert_cmphex(status, ==, BM_STS_INTR); assert_bit_clear(inb(IDE_BASE + reg_status), DF | ERR); } @@ -399,7 +419,7 @@ static void test_bmdma_no_busmaster(void) PrdtEntry prdt[4096] = { }; status = send_dma_request(CMD_READ_DMA | CMDF_NO_BM, 0, 512, - prdt, ARRAY_SIZE(prdt)); + prdt, ARRAY_SIZE(prdt), NULL); /* Not entirely clear what the expected result is, but this is what we get * in practice. At least we want to be aware of any changes. */ @@ -488,9 +508,7 @@ static void test_flush(void) tmp_path); /* Delay the completion of the flush request until we explicitly do it */ - qmp_discard_response("{'execute':'human-monitor-command', 'arguments': {" - " 'command-line':" - " 'qemu-io ide0-hd0 \"break flush_to_os A\"'} }"); + g_free(hmp("qemu-io ide0-hd0 \"break flush_to_os A\"")); /* FLUSH CACHE command on device 0*/ outb(IDE_BASE + reg_device, 0); @@ -502,9 +520,7 @@ static void test_flush(void) assert_bit_clear(data, DF | ERR | DRQ); /* Complete the command */ - qmp_discard_response("{'execute':'human-monitor-command', 'arguments': {" - " 'command-line':" - " 'qemu-io ide0-hd0 \"resume A\"'} }"); + g_free(hmp("qemu-io ide0-hd0 \"resume A\"")); /* Check registers */ data = inb(IDE_BASE + reg_device); @@ -575,16 +591,215 @@ static void test_flush_nodev(void) ide_test_quit(); } -static void test_pci_retry_flush(const char *machine) +static void test_pci_retry_flush(void) { test_retry_flush("pc"); } -static void test_isa_retry_flush(const char *machine) +static void test_isa_retry_flush(void) { test_retry_flush("isapc"); } +typedef struct Read10CDB { + uint8_t opcode; + uint8_t flags; + uint32_t lba; + uint8_t reserved; + uint16_t nblocks; + uint8_t control; + uint16_t padding; +} __attribute__((__packed__)) Read10CDB; + +static void send_scsi_cdb_read10(uint64_t lba, int nblocks) +{ + Read10CDB pkt = { .padding = 0 }; + int i; + + g_assert_cmpint(lba, <=, UINT32_MAX); + g_assert_cmpint(nblocks, <=, UINT16_MAX); + g_assert_cmpint(nblocks, >=, 0); + + /* Construct SCSI CDB packet */ + pkt.opcode = 0x28; + pkt.lba = cpu_to_be32(lba); + pkt.nblocks = cpu_to_be16(nblocks); + + /* Send Packet */ + for (i = 0; i < sizeof(Read10CDB)/2; i++) { + outw(IDE_BASE + reg_data, cpu_to_le16(((uint16_t *)&pkt)[i])); + } +} + +static void nsleep(int64_t nsecs) +{ + const struct timespec val = { .tv_nsec = nsecs }; + nanosleep(&val, NULL); + clock_set(nsecs); +} + +static uint8_t ide_wait_clear(uint8_t flag) +{ + uint8_t data; + time_t st; + + /* Wait with a 5 second timeout */ + time(&st); + while (true) { + data = inb(IDE_BASE + reg_status); + if (!(data & flag)) { + return data; + } + if (difftime(time(NULL), st) > 5.0) { + break; + } + nsleep(400); + } + g_assert_not_reached(); +} + +static void ide_wait_intr(int irq) +{ + time_t st; + bool intr; + + time(&st); + while (true) { + intr = get_irq(irq); + if (intr) { + return; + } + if (difftime(time(NULL), st) > 5.0) { + break; + } + nsleep(400); + } + + g_assert_not_reached(); +} + +static void cdrom_pio_impl(int nblocks) +{ + FILE *fh; + int patt_blocks = MAX(16, nblocks); + size_t patt_len = ATAPI_BLOCK_SIZE * patt_blocks; + char *pattern = g_malloc(patt_len); + size_t rxsize = ATAPI_BLOCK_SIZE * nblocks; + uint16_t *rx = g_malloc0(rxsize); + int i, j; + uint8_t data; + uint16_t limit; + + /* Prepopulate the CDROM with an interesting pattern */ + generate_pattern(pattern, patt_len, ATAPI_BLOCK_SIZE); + fh = fopen(tmp_path, "w+"); + fwrite(pattern, ATAPI_BLOCK_SIZE, patt_blocks, fh); + fclose(fh); + + ide_test_start("-drive if=none,file=%s,media=cdrom,format=raw,id=sr0,index=0 " + "-device ide-cd,drive=sr0,bus=ide.0", tmp_path); + qtest_irq_intercept_in(global_qtest, "ioapic"); + + /* PACKET command on device 0 */ + outb(IDE_BASE + reg_device, 0); + outb(IDE_BASE + reg_lba_middle, BYTE_COUNT_LIMIT & 0xFF); + outb(IDE_BASE + reg_lba_high, (BYTE_COUNT_LIMIT >> 8 & 0xFF)); + outb(IDE_BASE + reg_command, CMD_PACKET); + /* HP0: Check_Status_A State */ + nsleep(400); + data = ide_wait_clear(BSY); + /* HP1: Send_Packet State */ + assert_bit_set(data, DRQ | DRDY); + assert_bit_clear(data, ERR | DF | BSY); + + /* SCSI CDB (READ10) -- read n*2048 bytes from block 0 */ + send_scsi_cdb_read10(0, nblocks); + + /* Read data back: occurs in bursts of 'BYTE_COUNT_LIMIT' bytes. + * If BYTE_COUNT_LIMIT is odd, we transfer BYTE_COUNT_LIMIT - 1 bytes. + * We allow an odd limit only when the remaining transfer size is + * less than BYTE_COUNT_LIMIT. However, SCSI's read10 command can only + * request n blocks, so our request size is always even. + * For this reason, we assume there is never a hanging byte to fetch. */ + g_assert(!(rxsize & 1)); + limit = BYTE_COUNT_LIMIT & ~1; + for (i = 0; i < DIV_ROUND_UP(rxsize, limit); i++) { + size_t offset = i * (limit / 2); + size_t rem = (rxsize / 2) - offset; + + /* HP3: INTRQ_Wait */ + ide_wait_intr(IDE_PRIMARY_IRQ); + + /* HP2: Check_Status_B (and clear IRQ) */ + data = ide_wait_clear(BSY); + assert_bit_set(data, DRQ | DRDY); + assert_bit_clear(data, ERR | DF | BSY); + + /* HP4: Transfer_Data */ + for (j = 0; j < MIN((limit / 2), rem); j++) { + rx[offset + j] = le16_to_cpu(inw(IDE_BASE + reg_data)); + } + } + + /* Check for final completion IRQ */ + ide_wait_intr(IDE_PRIMARY_IRQ); + + /* Sanity check final state */ + data = ide_wait_clear(DRQ); + assert_bit_set(data, DRDY); + assert_bit_clear(data, DRQ | ERR | DF | BSY); + + g_assert_cmpint(memcmp(pattern, rx, rxsize), ==, 0); + g_free(pattern); + g_free(rx); + test_bmdma_teardown(); +} + +static void test_cdrom_pio(void) +{ + cdrom_pio_impl(1); +} + +static void test_cdrom_pio_large(void) +{ + /* Test a few loops of the PIO DRQ mechanism. */ + cdrom_pio_impl(BYTE_COUNT_LIMIT * 4 / ATAPI_BLOCK_SIZE); +} + + +static void test_cdrom_dma(void) +{ + static const size_t len = ATAPI_BLOCK_SIZE; + char *pattern = g_malloc(ATAPI_BLOCK_SIZE * 16); + char *rx = g_malloc0(len); + uintptr_t guest_buf; + PrdtEntry prdt[1]; + FILE *fh; + + ide_test_start("-drive if=none,file=%s,media=cdrom,format=raw,id=sr0,index=0 " + "-device ide-cd,drive=sr0,bus=ide.0", tmp_path); + qtest_irq_intercept_in(global_qtest, "ioapic"); + + guest_buf = guest_alloc(guest_malloc, len); + prdt[0].addr = cpu_to_le32(guest_buf); + prdt[0].size = cpu_to_le32(len | PRDT_EOT); + + generate_pattern(pattern, ATAPI_BLOCK_SIZE * 16, ATAPI_BLOCK_SIZE); + fh = fopen(tmp_path, "w+"); + fwrite(pattern, ATAPI_BLOCK_SIZE, 16, fh); + fclose(fh); + + send_dma_request(CMD_PACKET, 0, 1, prdt, 1, send_scsi_cdb_read10); + + /* Read back data from guest memory into local qtest memory */ + memread(guest_buf, rx, len); + g_assert_cmpint(memcmp(pattern, rx, len), ==, 0); + + g_free(pattern); + g_free(rx); + test_bmdma_teardown(); +} + int main(int argc, char **argv) { const char *arch = qtest_get_arch(); @@ -628,6 +843,10 @@ int main(int argc, char **argv) qtest_add_func("/ide/flush/retry_pci", test_pci_retry_flush); qtest_add_func("/ide/flush/retry_isa", test_isa_retry_flush); + qtest_add_func("/ide/cdrom/pio", test_cdrom_pio); + qtest_add_func("/ide/cdrom/pio_large", test_cdrom_pio_large); + qtest_add_func("/ide/cdrom/dma", test_cdrom_dma); + ret = g_test_run(); /* Cleanup */ |