[PATCH 10/15] tests/qtest/ide-test: cover the CHS translation across migration
"Denis V. Lunev" <[email protected]>
| Newsgroups | gmane.comp.emulators.qemu,gmane.comp.emulators.qemu.block |
|---|---|
| Message-ID | <[email protected]> |
From: Denis V. Lunev <[email protected]> Both defects here are silent: the guest addresses the disk in the translation it selected while the device translates with another, so reads and writes land on a sector nobody asked for. Put a marker in each of the two candidate sectors and name the one the translation picked. CHS 0/1/1 is LBA 32 under 8 heads and 32 sectors per track and LBA 63 under the 16/63 the test drive is configured with; both markers are written by LBA, which no translation can influence. A translation the guest selected has to survive migration, and one it selected after a snapshot was taken must not outlive loading that snapshot back. The second needs a qcow2 image, so it is skipped without qemu-img. Cc: John Snow <[email protected]> Cc: Peter Maydell <[email protected]> Signed-off-by: Denis V. Lunev <[email protected]> --- tests/qtest/ide-test.c | 231 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c index 99aa5e039c..19e9734d3c 100644 --- a/tests/qtest/ide-test.c +++ b/tests/qtest/ide-test.c @@ -96,6 +96,7 @@ enum { enum { CMD_DSM = 0x06, CMD_READ = 0x20, /* READ SECTOR(S) */ + CMD_WRITE = 0x30, /* WRITE SECTOR(S) */ CMD_DIAGNOSE = 0x90, CMD_INIT_DP = 0x91, /* INITIALIZE DEVICE PARAMETERS */ CMD_READ_DMA = 0xc8, @@ -1255,6 +1256,233 @@ static void test_specify_zero_sectors(void) free_pci_device(dev); } +/* Addressed by LBA, so no translation can influence where it lands */ +static void ide_write_marker(QTestState *qts, QPCIDevice *dev, QPCIBar ide_bar, + uint32_t lba, const char *marker) +{ + uint16_t buf[256]; + uint8_t data; + int i; + + memset(buf, 0, sizeof(buf)); + memcpy(buf, marker, strlen(marker)); + + qpci_io_writeb(dev, ide_bar, reg_nsectors, 1); + qpci_io_writeb(dev, ide_bar, reg_lba_low, lba & 0xff); + qpci_io_writeb(dev, ide_bar, reg_lba_middle, (lba >> 8) & 0xff); + qpci_io_writeb(dev, ide_bar, reg_lba_high, (lba >> 16) & 0xff); + qpci_io_writeb(dev, ide_bar, reg_device, LBA | ((lba >> 24) & 0xf)); + qpci_io_writeb(dev, ide_bar, reg_command, CMD_WRITE); + + data = ide_wait_clear(qts, BSY); + assert_bit_set(data, DRQ); + for (i = 0; i < 256; i++) { + qpci_io_writew(dev, ide_bar, reg_data, buf[i]); + } + data = ide_wait_clear(qts, BSY); + assert_bit_clear(data, ERR | DF | DRQ); + + qpci_io_writeb(dev, ide_bar, reg_command, CMD_FLUSH_CACHE); + data = ide_wait_clear(qts, BSY); + assert_bit_clear(data, ERR | DF); +} + +/* The marker read back names the sector the translation selected */ +static void ide_read_chs_marker(QTestState *qts, QPCIDevice *dev, + QPCIBar ide_bar, uint8_t cyl_lo, uint8_t head, + uint8_t sector, char out[9]) +{ + uint16_t buf[256]; + uint8_t data; + int i; + + qpci_io_writeb(dev, ide_bar, reg_nsectors, 1); + qpci_io_writeb(dev, ide_bar, reg_lba_low, sector); + qpci_io_writeb(dev, ide_bar, reg_lba_middle, cyl_lo); + qpci_io_writeb(dev, ide_bar, reg_lba_high, 0); + qpci_io_writeb(dev, ide_bar, reg_device, head & 0xf); + qpci_io_writeb(dev, ide_bar, reg_command, CMD_READ); + + data = ide_wait_clear(qts, BSY); + assert_bit_set(data, DRQ); + assert_bit_clear(data, ERR | DF); + for (i = 0; i < 256; i++) { + buf[i] = qpci_io_readw(dev, ide_bar, reg_data); + } + data = ide_wait_clear(qts, BSY); + assert_bit_clear(data, ERR | DF | DRQ); + + memcpy(out, buf, 8); + out[8] = '\0'; +} + +static void ide_set_translation(QPCIDevice *dev, QPCIBar ide_bar, + uint8_t heads, uint8_t sectors) +{ + qpci_io_writeb(dev, ide_bar, reg_nsectors, sectors); + qpci_io_writeb(dev, ide_bar, reg_device, heads - 1); + qpci_io_writeb(dev, ide_bar, reg_command, CMD_INIT_DP); + assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), ERR); +} + +/* CHS 0/1/1 is LBA 32 under 8/32, and LBA 63 under the drive's own 16/63 */ +#define CHS_MARKER_CUSTOM "CUSTOM__" +#define CHS_MARKER_DEFAULT "DEFAULT_" + +static void ide_prepare_markers(QTestState *qts, QPCIDevice *dev, + QPCIBar ide_bar) +{ + ide_write_marker(qts, dev, ide_bar, 32, CHS_MARKER_CUSTOM); + ide_write_marker(qts, dev, ide_bar, 63, CHS_MARKER_DEFAULT); +} + +static void ide_hmp_quiet(QTestState *qts, const char *command) +{ + g_autofree char *out = qtest_hmp(qts, "%s", command); + + g_assert_cmpstr(out, ==, ""); +} + +static char *ide_migration_status(QTestState *qts) +{ + QDict *ret; + char *status; + + ret = qtest_qmp_assert_success_ref(qts, "{ 'execute': 'query-migrate' }"); + g_assert(qdict_haskey(ret, "status")); + status = g_strdup(qdict_get_str(ret, "status")); + qobject_unref(ret); + + return status; +} + +/* Waiting for the other side's event would hang if it refuses the stream */ +static void ide_migration_wait(QTestState *qts, const char *expected) +{ + while (true) { + g_autofree char *status = ide_migration_status(qts); + + if (g_str_equal(status, expected)) { + return; + } + if (!g_str_equal(status, "setup") && !g_str_equal(status, "active") && + !g_str_equal(status, "device")) { + fprintf(stderr, "Migration status is %s, expected %s\n", + status, expected); + g_assert_not_reached(); + } + g_usleep(5000); + } +} + +static void ide_migrate(QTestState *src, QTestState *dst, const char *uri) +{ + qtest_qmp_assert_success(src, "{ 'execute': 'migrate'," + " 'arguments': { 'uri': %s } }", uri); + qtest_qmp_eventwait(src, "STOP"); + ide_migration_wait(src, "completed"); + qtest_qmp_eventwait(dst, "RESUME"); +} + +/* A translation the guest selected has to survive migration */ +static void test_migrate_chs_translation(void) +{ + QTestState *src, *dst; + QPCIDevice *dev; + QPCIBar bmdma_bar, ide_bar; + g_autofree char *mig_path = NULL; + g_autofree char *uri = NULL; + g_autofree char *dst_args = NULL; + char marker[9]; + int fd; + + fd = g_file_open_tmp("qtest-ide-migration.XXXXXX", &mig_path, NULL); + g_assert(fd >= 0); + close(fd); + uri = g_strdup_printf("unix:%s", mig_path); + + src = ide_test_start( + "-blockdev driver=file,node-name=hda,filename=%s,locking=off " + "-device ide-hd,drive=hda,bus=ide.0,unit=0 ", + tmp_path[0]); + dev = get_pci_device(src, &bmdma_bar, &ide_bar); + + ide_prepare_markers(src, dev, ide_bar); + ide_set_translation(dev, ide_bar, 8, 32); + ide_read_chs_marker(src, dev, ide_bar, 0, 1, 1, marker); + g_assert_cmpstr(marker, ==, CHS_MARKER_CUSTOM); + + dst_args = g_strdup_printf( + "-machine pc " + "-blockdev driver=file,node-name=hda,filename=%s,locking=off " + "-device ide-hd,drive=hda,bus=ide.0,unit=0 -incoming %s", + tmp_path[0], uri); + dst = qtest_init(dst_args); + + ide_migrate(src, dst, uri); + + /* Talk to the destination instead of the source */ + qpci_free_pc(pcibus); + pcibus = NULL; + free_pci_device(dev); + dev = get_pci_device(dst, &bmdma_bar, &ide_bar); + + ide_read_chs_marker(dst, dev, ide_bar, 0, 1, 1, marker); + g_assert_cmpstr(marker, ==, CHS_MARKER_CUSTOM); + + free_pci_device(dev); + qtest_quit(dst); + ide_test_quit(src); + unlink(mig_path); +} + +/* A translation selected after the snapshot must not outlive loading it */ +static void test_migrate_chs_snapshot(void) +{ + QTestState *qts; + QPCIDevice *dev; + QPCIBar bmdma_bar, ide_bar; + g_autofree char *img = NULL; + char marker[9]; + int fd; + + if (!have_qemu_img()) { + g_test_skip("QTEST_QEMU_IMG not set, snapshots need a qcow2 image"); + return; + } + + fd = g_file_open_tmp("qtest-ide-snapshot.XXXXXX", &img, NULL); + g_assert(fd >= 0); + close(fd); + g_assert(mkimg(img, "qcow2", TEST_IMAGE_SIZE / (1024 * 1024))); + + qts = ide_test_start( + "-blockdev driver=qcow2,node-name=hda,file.driver=file," + "file.filename=%s " + "-device ide-hd,drive=hda,bus=ide.0,unit=0 ", img); + dev = get_pci_device(qts, &bmdma_bar, &ide_bar); + + ide_prepare_markers(qts, dev, ide_bar); + + /* Snapshot taken while the default translation is in effect */ + ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker); + g_assert_cmpstr(marker, ==, CHS_MARKER_DEFAULT); + ide_hmp_quiet(qts, "savevm s0"); + + ide_set_translation(dev, ide_bar, 8, 32); + ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker); + g_assert_cmpstr(marker, ==, CHS_MARKER_CUSTOM); + + ide_hmp_quiet(qts, "loadvm s0"); + + ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker); + g_assert_cmpstr(marker, ==, CHS_MARKER_DEFAULT); + + free_pci_device(dev); + ide_test_quit(qts); + unlink(img); +} + static void test_cdrom_pio(void) { cdrom_read_impl(1, CDROM_PIO); @@ -1327,6 +1555,9 @@ int main(int argc, char **argv) qtest_add_func("/ide/read_native", test_specify); qtest_add_func("/ide/specify/zero_sectors", test_specify_zero_sectors); + qtest_add_func("/ide/migration/chs_translation", + test_migrate_chs_translation); + qtest_add_func("/ide/migration/chs_snapshot", test_migrate_chs_snapshot); qtest_add_func("/ide/identify", test_identify); -- 2.53.0