aboutsummaryrefslogtreecommitdiff
path: root/tests/ahci-test.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/ahci-test.c')
-rw-r--r--tests/ahci-test.c328
1 files changed, 323 insertions, 5 deletions
diff --git a/tests/ahci-test.c b/tests/ahci-test.c
index 7c23bb2180..6e3fa819e0 100644
--- a/tests/ahci-test.c
+++ b/tests/ahci-test.c
@@ -97,6 +97,72 @@ static void generate_pattern(void *buffer, size_t len, size_t cycle_len)
}
}
+/**
+ * Verify that the transfer did not corrupt our state at all.
+ */
+static void verify_state(AHCIQState *ahci)
+{
+ int i, j;
+ uint32_t ahci_fingerprint;
+ uint64_t hba_base;
+ uint64_t hba_stored;
+ AHCICommandHeader cmd;
+
+ ahci_fingerprint = qpci_config_readl(ahci->dev, PCI_VENDOR_ID);
+ g_assert_cmphex(ahci_fingerprint, ==, ahci->fingerprint);
+
+ /* If we haven't initialized, this is as much as can be validated. */
+ if (!ahci->hba_base) {
+ return;
+ }
+
+ hba_base = (uint64_t)qpci_config_readl(ahci->dev, PCI_BASE_ADDRESS_5);
+ hba_stored = (uint64_t)(uintptr_t)ahci->hba_base;
+ g_assert_cmphex(hba_base, ==, hba_stored);
+
+ g_assert_cmphex(ahci_rreg(ahci, AHCI_CAP), ==, ahci->cap);
+ g_assert_cmphex(ahci_rreg(ahci, AHCI_CAP2), ==, ahci->cap2);
+
+ for (i = 0; i < 32; i++) {
+ g_assert_cmphex(ahci_px_rreg(ahci, i, AHCI_PX_FB), ==,
+ ahci->port[i].fb);
+ g_assert_cmphex(ahci_px_rreg(ahci, i, AHCI_PX_CLB), ==,
+ ahci->port[i].clb);
+ for (j = 0; j < 32; j++) {
+ ahci_get_command_header(ahci, i, j, &cmd);
+ g_assert_cmphex(cmd.prdtl, ==, ahci->port[i].prdtl[j]);
+ g_assert_cmphex(cmd.ctba, ==, ahci->port[i].ctba[j]);
+ }
+ }
+}
+
+static void ahci_migrate(AHCIQState *from, AHCIQState *to, const char *uri)
+{
+ QOSState *tmp = to->parent;
+ QPCIDevice *dev = to->dev;
+ if (uri == NULL) {
+ uri = "tcp:127.0.0.1:1234";
+ }
+
+ /* context will be 'to' after completion. */
+ migrate(from->parent, to->parent, uri);
+
+ /* We'd like for the AHCIState objects to still point
+ * to information specific to its specific parent
+ * instance, but otherwise just inherit the new data. */
+ memcpy(to, from, sizeof(AHCIQState));
+ to->parent = tmp;
+ to->dev = dev;
+
+ tmp = from->parent;
+ dev = from->dev;
+ memset(from, 0x00, sizeof(AHCIQState));
+ from->parent = tmp;
+ from->dev = dev;
+
+ verify_state(to);
+}
+
/*** Test Setup & Teardown ***/
/**
@@ -146,6 +212,8 @@ static AHCIQState *ahci_boot(const char *cli, ...)
static void ahci_shutdown(AHCIQState *ahci)
{
QOSState *qs = ahci->parent;
+
+ set_context(qs);
ahci_clean_mem(ahci);
free_ahci_device(ahci->dev);
g_free(ahci);
@@ -806,7 +874,7 @@ static void ahci_test_io_rw_simple(AHCIQState *ahci, unsigned bufsize,
/* Write some indicative pattern to our buffer. */
generate_pattern(tx, bufsize, AHCI_SECTOR_SIZE);
- memwrite(ptr, tx, bufsize);
+ bufwrite(ptr, tx, bufsize);
/* Write this buffer to disk, then read it back to the DMA buffer. */
ahci_guest_io(ahci, port, write_cmd, ptr, bufsize, sector);
@@ -814,7 +882,7 @@ static void ahci_test_io_rw_simple(AHCIQState *ahci, unsigned bufsize,
ahci_guest_io(ahci, port, read_cmd, ptr, bufsize, sector);
/*** Read back the Data ***/
- memread(ptr, rx, bufsize);
+ bufread(ptr, rx, bufsize);
g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0);
ahci_free(ahci, ptr);
@@ -950,7 +1018,7 @@ static void test_dma_fragmented(void)
/* Create a DMA buffer in guest memory, and write our pattern to it. */
ptr = guest_alloc(ahci->parent->alloc, bufsize);
g_assert(ptr);
- memwrite(ptr, tx, bufsize);
+ bufwrite(ptr, tx, bufsize);
cmd = ahci_command_create(CMD_WRITE_DMA);
ahci_command_adjust(cmd, 0, ptr, bufsize, 32);
@@ -967,7 +1035,7 @@ static void test_dma_fragmented(void)
g_free(cmd);
/* Read back the guest's receive buffer into local memory */
- memread(ptr, rx, bufsize);
+ bufread(ptr, rx, bufsize);
guest_free(ahci->parent->alloc, ptr);
g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0);
@@ -1003,7 +1071,7 @@ static void test_flush_retry(void)
debug_path,
tmp_path);
- /* Issue Flush Command */
+ /* Issue Flush Command and wait for error */
port = ahci_port_select(ahci);
ahci_port_clear(ahci, port);
cmd = ahci_command_create(CMD_FLUSH_CACHE);
@@ -1022,6 +1090,250 @@ static void test_flush_retry(void)
ahci_shutdown(ahci);
}
+/**
+ * Basic sanity test to boot a machine, find an AHCI device, and shutdown.
+ */
+static void test_migrate_sanity(void)
+{
+ AHCIQState *src, *dst;
+ const char *uri = "tcp:127.0.0.1:1234";
+
+ src = ahci_boot("-m 1024 -M q35 "
+ "-hda %s ", tmp_path);
+ dst = ahci_boot("-m 1024 -M q35 "
+ "-hda %s "
+ "-incoming %s", tmp_path, uri);
+
+ ahci_migrate(src, dst, uri);
+
+ ahci_shutdown(src);
+ ahci_shutdown(dst);
+}
+
+/**
+ * DMA Migration test: Write a pattern, migrate, then read.
+ */
+static void test_migrate_dma(void)
+{
+ AHCIQState *src, *dst;
+ uint8_t px;
+ size_t bufsize = 4096;
+ unsigned char *tx = g_malloc(bufsize);
+ unsigned char *rx = g_malloc0(bufsize);
+ unsigned i;
+ const char *uri = "tcp:127.0.0.1:1234";
+
+ src = ahci_boot_and_enable("-m 1024 -M q35 "
+ "-hda %s ", tmp_path);
+ dst = ahci_boot("-m 1024 -M q35 "
+ "-hda %s "
+ "-incoming %s", tmp_path, uri);
+
+ set_context(src->parent);
+
+ /* initialize */
+ px = ahci_port_select(src);
+ ahci_port_clear(src, px);
+
+ /* create pattern */
+ for (i = 0; i < bufsize; i++) {
+ tx[i] = (bufsize - i);
+ }
+
+ /* Write, migrate, then read. */
+ ahci_io(src, px, CMD_WRITE_DMA, tx, bufsize, 0);
+ ahci_migrate(src, dst, uri);
+ ahci_io(dst, px, CMD_READ_DMA, rx, bufsize, 0);
+
+ /* Verify pattern */
+ g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0);
+
+ ahci_shutdown(src);
+ ahci_shutdown(dst);
+ g_free(rx);
+ g_free(tx);
+}
+
+/**
+ * DMA Error Test
+ *
+ * Simulate an error on first write, Try to write a pattern,
+ * Confirm the VM has stopped, resume the VM, verify command
+ * has completed, then read back the data and verify.
+ */
+static void test_halted_dma(void)
+{
+ AHCIQState *ahci;
+ uint8_t port;
+ size_t bufsize = 4096;
+ unsigned char *tx = g_malloc(bufsize);
+ unsigned char *rx = g_malloc0(bufsize);
+ unsigned i;
+ uint64_t ptr;
+ AHCICommand *cmd;
+
+ prepare_blkdebug_script(debug_path, "write_aio");
+
+ ahci = ahci_boot_and_enable("-drive file=blkdebug:%s:%s,if=none,id=drive0,"
+ "format=qcow2,cache=writeback,"
+ "rerror=stop,werror=stop "
+ "-M q35 "
+ "-device ide-hd,drive=drive0 ",
+ debug_path,
+ tmp_path);
+
+ /* Initialize and prepare */
+ port = ahci_port_select(ahci);
+ ahci_port_clear(ahci, port);
+
+ for (i = 0; i < bufsize; i++) {
+ tx[i] = (bufsize - i);
+ }
+
+ /* create DMA source buffer and write pattern */
+ ptr = ahci_alloc(ahci, bufsize);
+ g_assert(ptr);
+ memwrite(ptr, tx, bufsize);
+
+ /* Attempt to write (and fail) */
+ cmd = ahci_guest_io_halt(ahci, port, CMD_WRITE_DMA,
+ ptr, bufsize, 0);
+
+ /* Attempt to resume the command */
+ ahci_guest_io_resume(ahci, cmd);
+ ahci_free(ahci, ptr);
+
+ /* Read back and verify */
+ ahci_io(ahci, port, CMD_READ_DMA, rx, bufsize, 0);
+ g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0);
+
+ /* Cleanup and go home */
+ ahci_shutdown(ahci);
+ g_free(rx);
+ g_free(tx);
+}
+
+/**
+ * DMA Error Migration Test
+ *
+ * Simulate an error on first write, Try to write a pattern,
+ * Confirm the VM has stopped, migrate, resume the VM,
+ * verify command has completed, then read back the data and verify.
+ */
+static void test_migrate_halted_dma(void)
+{
+ AHCIQState *src, *dst;
+ uint8_t port;
+ size_t bufsize = 4096;
+ unsigned char *tx = g_malloc(bufsize);
+ unsigned char *rx = g_malloc0(bufsize);
+ unsigned i;
+ uint64_t ptr;
+ AHCICommand *cmd;
+ const char *uri = "tcp:127.0.0.1:1234";
+
+ prepare_blkdebug_script(debug_path, "write_aio");
+
+ src = ahci_boot_and_enable("-drive file=blkdebug:%s:%s,if=none,id=drive0,"
+ "format=qcow2,cache=writeback,"
+ "rerror=stop,werror=stop "
+ "-M q35 "
+ "-device ide-hd,drive=drive0 ",
+ debug_path,
+ tmp_path);
+
+ dst = ahci_boot("-drive file=%s,if=none,id=drive0,"
+ "format=qcow2,cache=writeback,"
+ "rerror=stop,werror=stop "
+ "-M q35 "
+ "-device ide-hd,drive=drive0 "
+ "-incoming %s",
+ tmp_path, uri);
+
+ set_context(src->parent);
+
+ /* Initialize and prepare */
+ port = ahci_port_select(src);
+ ahci_port_clear(src, port);
+
+ for (i = 0; i < bufsize; i++) {
+ tx[i] = (bufsize - i);
+ }
+
+ /* create DMA source buffer and write pattern */
+ ptr = ahci_alloc(src, bufsize);
+ g_assert(ptr);
+ memwrite(ptr, tx, bufsize);
+
+ /* Write, trigger the VM to stop, migrate, then resume. */
+ cmd = ahci_guest_io_halt(src, port, CMD_WRITE_DMA,
+ ptr, bufsize, 0);
+ ahci_migrate(src, dst, uri);
+ ahci_guest_io_resume(dst, cmd);
+ ahci_free(dst, ptr);
+
+ /* Read back */
+ ahci_io(dst, port, CMD_READ_DMA, rx, bufsize, 0);
+
+ /* Verify TX and RX are identical */
+ g_assert_cmphex(memcmp(tx, rx, bufsize), ==, 0);
+
+ /* Cleanup and go home. */
+ ahci_shutdown(src);
+ ahci_shutdown(dst);
+ g_free(rx);
+ g_free(tx);
+}
+
+/**
+ * Migration test: Try to flush, migrate, then resume.
+ */
+static void test_flush_migrate(void)
+{
+ AHCIQState *src, *dst;
+ AHCICommand *cmd;
+ uint8_t px;
+ const char *s;
+ const char *uri = "tcp:127.0.0.1:1234";
+
+ prepare_blkdebug_script(debug_path, "flush_to_disk");
+
+ src = ahci_boot_and_enable("-drive file=blkdebug:%s:%s,if=none,id=drive0,"
+ "cache=writeback,rerror=stop,werror=stop "
+ "-M q35 "
+ "-device ide-hd,drive=drive0 ",
+ debug_path, tmp_path);
+ dst = ahci_boot("-drive file=%s,if=none,id=drive0,"
+ "cache=writeback,rerror=stop,werror=stop "
+ "-M q35 "
+ "-device ide-hd,drive=drive0 "
+ "-incoming %s", tmp_path, uri);
+
+ set_context(src->parent);
+
+ /* Issue Flush Command */
+ px = ahci_port_select(src);
+ ahci_port_clear(src, px);
+ cmd = ahci_command_create(CMD_FLUSH_CACHE);
+ ahci_command_commit(src, cmd, px);
+ ahci_command_issue_async(src, cmd);
+ qmp_eventwait("STOP");
+
+ /* Migrate over */
+ ahci_migrate(src, dst, uri);
+
+ /* Complete the command */
+ s = "{'execute':'cont' }";
+ qmp_async(s);
+ qmp_eventwait("RESUME");
+ ahci_command_wait(dst, cmd);
+ ahci_command_verify(dst, cmd);
+
+ ahci_command_free(cmd);
+ ahci_shutdown(src);
+ ahci_shutdown(dst);
+}
+
/******************************************************************************/
/* AHCI I/O Test Matrix Definitions */
@@ -1270,6 +1582,12 @@ int main(int argc, char **argv)
qtest_add_func("/ahci/flush/simple", test_flush);
qtest_add_func("/ahci/flush/retry", test_flush_retry);
+ qtest_add_func("/ahci/flush/migrate", test_flush_migrate);
+
+ qtest_add_func("/ahci/migrate/sanity", test_migrate_sanity);
+ qtest_add_func("/ahci/migrate/dma/simple", test_migrate_dma);
+ qtest_add_func("/ahci/io/dma/lba28/retry", test_halted_dma);
+ qtest_add_func("/ahci/migrate/dma/halted", test_migrate_halted_dma);
ret = g_test_run();