提交 0915aed5 编写于 作者: P Peter Maydell

Merge remote-tracking branch 'remotes/jnsnow/tags/ide-pull-request' into staging

# gpg: Signature made Fri May 22 20:58:44 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:
  ahci: do not remap clb/fis unconditionally
  macio: move unaligned DMA write code into separate pmac_dma_write() function
  macio: move unaligned DMA read code into separate pmac_dma_read() function
  qtest: pre-buffer hex nibs
  libqos/ahci: Swap memread/write with bufread/write
  qtest: add memset to qtest protocol
  qtest: Add base64 encoded read/write
  qtest: allow arbitrarily long sends
  qtest/ahci: add migrate halted dma test
  qtest/ahci: add halted dma test
  qtest/ahci: add flush migrate test
  qtest/ahci: add migrate dma test
  qtest/ahci: Add migration test
  ich9/ahci: Enable Migration
  libqos: Add migration helpers
  libqos/ahci: Fix sector set method
  libqos/ahci: Add halted command helpers
  glib: remove stale compat functions
  configure: require glib 2.22
Signed-off-by: NPeter Maydell <peter.maydell@linaro.org>
......@@ -2779,12 +2779,7 @@ fi
##########################################
# glib support probe
if test "$mingw32" = yes; then
# g_poll is required in order to integrate with the glib main loop.
glib_req_ver=2.20
else
glib_req_ver=2.12
fi
glib_req_ver=2.22
glib_modules=gthread-2.0
if test "$modules" = yes; then
glib_modules="$glib_modules gmodule-2.0"
......
......@@ -198,6 +198,61 @@ static void map_page(AddressSpace *as, uint8_t **ptr, uint64_t addr,
}
}
/**
* Check the cmd register to see if we should start or stop
* the DMA or FIS RX engines.
*
* @ad: Device to engage.
* @allow_stop: Allow device to transition from started to stopped?
* 'no' is useful for migration post_load, which does not expect a transition.
*
* @return 0 on success, -1 on error.
*/
static int ahci_cond_start_engines(AHCIDevice *ad, bool allow_stop)
{
AHCIPortRegs *pr = &ad->port_regs;
if (pr->cmd & PORT_CMD_START) {
if (ahci_map_clb_address(ad)) {
pr->cmd |= PORT_CMD_LIST_ON;
} else {
error_report("AHCI: Failed to start DMA engine: "
"bad command list buffer address");
return -1;
}
} else if (pr->cmd & PORT_CMD_LIST_ON) {
if (allow_stop) {
ahci_unmap_clb_address(ad);
pr->cmd = pr->cmd & ~(PORT_CMD_LIST_ON);
} else {
error_report("AHCI: DMA engine should be off, "
"but appears to still be running");
return -1;
}
}
if (pr->cmd & PORT_CMD_FIS_RX) {
if (ahci_map_fis_address(ad)) {
pr->cmd |= PORT_CMD_FIS_ON;
} else {
error_report("AHCI: Failed to start FIS receive engine: "
"bad FIS receive buffer address");
return -1;
}
} else if (pr->cmd & PORT_CMD_FIS_ON) {
if (allow_stop) {
ahci_unmap_fis_address(ad);
pr->cmd = pr->cmd & ~(PORT_CMD_FIS_ON);
} else {
error_report("AHCI: FIS receive engine should be off, "
"but appears to still be running");
return -1;
}
}
return 0;
}
static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val)
{
AHCIPortRegs *pr = &s->dev[port].port_regs;
......@@ -229,29 +284,8 @@ static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val)
* including LIST_ON and FIS_ON. */
pr->cmd = (pr->cmd & PORT_CMD_RO_MASK) | (val & ~PORT_CMD_RO_MASK);
if (pr->cmd & PORT_CMD_START) {
if (ahci_map_clb_address(&s->dev[port])) {
pr->cmd |= PORT_CMD_LIST_ON;
} else {
error_report("AHCI: Failed to start DMA engine: "
"bad command list buffer address");
}
} else if (pr->cmd & PORT_CMD_LIST_ON) {
ahci_unmap_clb_address(&s->dev[port]);
pr->cmd = pr->cmd & ~(PORT_CMD_LIST_ON);
}
if (pr->cmd & PORT_CMD_FIS_RX) {
if (ahci_map_fis_address(&s->dev[port])) {
pr->cmd |= PORT_CMD_FIS_ON;
} else {
error_report("AHCI: Failed to start FIS receive engine: "
"bad FIS receive buffer address");
}
} else if (pr->cmd & PORT_CMD_FIS_ON) {
ahci_unmap_fis_address(&s->dev[port]);
pr->cmd = pr->cmd & ~(PORT_CMD_FIS_ON);
}
/* Check FIS RX and CLB engines, allow transition to false: */
ahci_cond_start_engines(&s->dev[port], true);
/* XXX usually the FIS would be pending on the bus here and
issuing deferred until the OS enables FIS receival.
......@@ -1404,8 +1438,12 @@ static int ahci_state_post_load(void *opaque, int version_id)
for (i = 0; i < s->ports; i++) {
ad = &s->dev[i];
ahci_map_clb_address(ad);
ahci_map_fis_address(ad);
/* Only remap the CLB address if appropriate, disallowing a state
* transition from 'on' to 'off' it should be consistent here. */
if (ahci_cond_start_engines(ad, false) != 0) {
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
......@@ -1461,7 +1499,6 @@ typedef struct SysbusAHCIState {
static const VMStateDescription vmstate_sysbus_ahci = {
.name = "sysbus-ahci",
.unmigratable = 1, /* Still buggy under I/O load */
.fields = (VMStateField[]) {
VMSTATE_AHCI(ahci, SysbusAHCIState),
VMSTATE_END_OF_LIST()
......
......@@ -82,7 +82,6 @@
static const VMStateDescription vmstate_ich9_ahci = {
.name = "ich9_ahci",
.unmigratable = 1, /* Still buggy under I/O load */
.version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_PCI_DEVICE(parent_obj, AHCIPCIState),
......
此差异已折叠。
......@@ -23,14 +23,6 @@
#define G_TIME_SPAN_SECOND (G_GINT64_CONSTANT(1000000))
#endif
#if !GLIB_CHECK_VERSION(2, 14, 0)
static inline guint g_timeout_add_seconds(guint interval, GSourceFunc function,
gpointer data)
{
return g_timeout_add(interval * 1000, function, data);
}
#endif
#if !GLIB_CHECK_VERSION(2, 28, 0)
static inline gint64 qemu_g_get_monotonic_time(void)
{
......@@ -47,23 +39,6 @@ static inline gint64 qemu_g_get_monotonic_time(void)
#define g_get_monotonic_time() qemu_g_get_monotonic_time()
#endif
#if !GLIB_CHECK_VERSION(2, 16, 0)
static inline int g_strcmp0(const char *str1, const char *str2)
{
int result;
if (!str1) {
result = -(str1 != str2);
} else if (!str2) {
result = (str1 != str2);
} else {
result = strcmp(str1, str2);
}
return result;
}
#endif
#ifdef _WIN32
/*
* g_poll has a problem on Windows when using
......@@ -71,16 +46,6 @@ static inline int g_strcmp0(const char *str1, const char *str2)
*/
#define g_poll(fds, nfds, timeout) g_poll_fixed(fds, nfds, timeout)
gint g_poll_fixed(GPollFD *fds, guint nfds, gint timeout);
#elif !GLIB_CHECK_VERSION(2, 20, 0)
/*
* Glib before 2.20.0 doesn't implement g_poll, so wrap it to compile properly
* on older systems.
*/
static inline gint g_poll(GPollFD *fds, guint nfds, gint timeout)
{
GMainContext *ctx = g_main_context_default();
return g_main_context_get_poll_func(ctx)(fds, nfds, timeout);
}
#endif
#if !GLIB_CHECK_VERSION(2, 31, 0)
......
......@@ -43,10 +43,6 @@ struct DBDMA_io {
uint8_t remainder[0x200];
int remainder_len;
QEMUIOVector iov;
bool finish_remain_read;
hwaddr finish_addr;
hwaddr finish_len;
int requests;
};
/*
......
......@@ -119,12 +119,24 @@ static bool qtest_opened;
* > write ADDR SIZE DATA
* < OK
*
* > b64read ADDR SIZE
* < OK B64_DATA
*
* > b64write ADDR SIZE B64_DATA
* < OK
*
* > memset ADDR SIZE VALUE
* < OK
*
* ADDR, SIZE, VALUE are all integers parsed with strtoul() with a base of 0.
*
* DATA is an arbitrarily long hex number prefixed with '0x'. If it's smaller
* than the expected size, the value will be zero filled at the end of the data
* sequence.
*
* B64_DATA is an arbitrarily long base64 encoded string.
* If the sizes do not match, the data will be truncated.
*
* IRQ management:
*
* > irq_intercept_in QOM-PATH
......@@ -182,23 +194,46 @@ static void qtest_send_prefix(CharDriverState *chr)
(long) tv.tv_sec, (long) tv.tv_usec);
}
static void GCC_FMT_ATTR(2, 3) qtest_send(CharDriverState *chr,
const char *fmt, ...)
static void GCC_FMT_ATTR(1, 2) qtest_log_send(const char *fmt, ...)
{
va_list ap;
char buffer[1024];
size_t len;
if (!qtest_log_fp || !qtest_opened) {
return;
}
qtest_send_prefix(NULL);
va_start(ap, fmt);
len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
vfprintf(qtest_log_fp, fmt, ap);
va_end(ap);
}
qemu_chr_fe_write_all(chr, (uint8_t *)buffer, len);
static void do_qtest_send(CharDriverState *chr, const char *str, size_t len)
{
qemu_chr_fe_write_all(chr, (uint8_t *)str, len);
if (qtest_log_fp && qtest_opened) {
fprintf(qtest_log_fp, "%s", buffer);
fprintf(qtest_log_fp, "%s", str);
}
}
static void qtest_send(CharDriverState *chr, const char *str)
{
do_qtest_send(chr, str, strlen(str));
}
static void GCC_FMT_ATTR(2, 3) qtest_sendf(CharDriverState *chr,
const char *fmt, ...)
{
va_list ap;
gchar *buffer;
va_start(ap, fmt);
buffer = g_strdup_vprintf(fmt, ap);
qtest_send(chr, buffer);
va_end(ap);
}
static void qtest_irq_handler(void *opaque, int n, int level)
{
qemu_irq old_irq = *(qemu_irq *)opaque;
......@@ -208,8 +243,8 @@ static void qtest_irq_handler(void *opaque, int n, int level)
CharDriverState *chr = qtest_chr;
irq_levels[n] = level;
qtest_send_prefix(chr);
qtest_send(chr, "IRQ %s %d\n",
level ? "raise" : "lower", n);
qtest_sendf(chr, "IRQ %s %d\n",
level ? "raise" : "lower", n);
}
}
......@@ -318,7 +353,7 @@ static void qtest_process_command(CharDriverState *chr, gchar **words)
value = cpu_inl(addr);
}
qtest_send_prefix(chr);
qtest_send(chr, "OK 0x%04x\n", value);
qtest_sendf(chr, "OK 0x%04x\n", value);
} else if (strcmp(words[0], "writeb") == 0 ||
strcmp(words[0], "writew") == 0 ||
strcmp(words[0], "writel") == 0 ||
......@@ -375,10 +410,11 @@ static void qtest_process_command(CharDriverState *chr, gchar **words)
tswap64s(&value);
}
qtest_send_prefix(chr);
qtest_send(chr, "OK 0x%016" PRIx64 "\n", value);
qtest_sendf(chr, "OK 0x%016" PRIx64 "\n", value);
} else if (strcmp(words[0], "read") == 0) {
uint64_t addr, len, i;
uint8_t *data;
char *enc;
g_assert(words[1] && words[2]);
addr = strtoull(words[1], NULL, 0);
......@@ -387,14 +423,33 @@ static void qtest_process_command(CharDriverState *chr, gchar **words)
data = g_malloc(len);
cpu_physical_memory_read(addr, data, len);
qtest_send_prefix(chr);
qtest_send(chr, "OK 0x");
enc = g_malloc(2 * len + 1);
for (i = 0; i < len; i++) {
qtest_send(chr, "%02x", data[i]);
sprintf(&enc[i * 2], "%02x", data[i]);
}
qtest_send(chr, "\n");
qtest_send_prefix(chr);
qtest_sendf(chr, "OK 0x%s\n", enc);
g_free(data);
g_free(enc);
} else if (strcmp(words[0], "b64read") == 0) {
uint64_t addr, len;
uint8_t *data;
gchar *b64_data;
g_assert(words[1] && words[2]);
addr = strtoull(words[1], NULL, 0);
len = strtoull(words[2], NULL, 0);
data = g_malloc(len);
cpu_physical_memory_read(addr, data, len);
b64_data = g_base64_encode(data, len);
qtest_send_prefix(chr);
qtest_sendf(chr, "OK %s\n", b64_data);
g_free(data);
g_free(b64_data);
} else if (strcmp(words[0], "write") == 0) {
uint64_t addr, len, i;
uint8_t *data;
......@@ -422,6 +477,51 @@ static void qtest_process_command(CharDriverState *chr, gchar **words)
cpu_physical_memory_write(addr, data, len);
g_free(data);
qtest_send_prefix(chr);
qtest_send(chr, "OK\n");
} else if (strcmp(words[0], "memset") == 0) {
uint64_t addr, len;
uint8_t *data;
uint8_t pattern;
g_assert(words[1] && words[2] && words[3]);
addr = strtoull(words[1], NULL, 0);
len = strtoull(words[2], NULL, 0);
pattern = strtoull(words[3], NULL, 0);
data = g_malloc(len);
memset(data, pattern, len);
cpu_physical_memory_write(addr, data, len);
g_free(data);
qtest_send_prefix(chr);
qtest_send(chr, "OK\n");
} else if (strcmp(words[0], "b64write") == 0) {
uint64_t addr, len;
uint8_t *data;
size_t data_len;
gsize out_len;
g_assert(words[1] && words[2] && words[3]);
addr = strtoull(words[1], NULL, 0);
len = strtoull(words[2], NULL, 0);
data_len = strlen(words[3]);
if (data_len < 3) {
qtest_send(chr, "ERR invalid argument size\n");
return;
}
data = g_base64_decode_inplace(words[3], &out_len);
if (out_len != len) {
qtest_log_send("b64write: data length mismatch (told %"PRIu64", "
"found %zu)\n",
len, out_len);
out_len = MIN(out_len, len);
}
cpu_physical_memory_write(addr, data, out_len);
qtest_send_prefix(chr);
qtest_send(chr, "OK\n");
} else if (qtest_enabled() && strcmp(words[0], "clock_step") == 0) {
......@@ -434,7 +534,8 @@ static void qtest_process_command(CharDriverState *chr, gchar **words)
}
qtest_clock_warp(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns);
qtest_send_prefix(chr);
qtest_send(chr, "OK %"PRIi64"\n", (int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
qtest_sendf(chr, "OK %"PRIi64"\n",
(int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
} else if (qtest_enabled() && strcmp(words[0], "clock_set") == 0) {
int64_t ns;
......@@ -442,10 +543,11 @@ static void qtest_process_command(CharDriverState *chr, gchar **words)
ns = strtoll(words[1], NULL, 0);
qtest_clock_warp(ns);
qtest_send_prefix(chr);
qtest_send(chr, "OK %"PRIi64"\n", (int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
qtest_sendf(chr, "OK %"PRIi64"\n",
(int64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
} else {
qtest_send_prefix(chr);
qtest_send(chr, "FAIL Unknown command `%s'\n", words[0]);
qtest_sendf(chr, "FAIL Unknown command '%s'\n", words[0]);
}
}
......
......@@ -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();
......
......@@ -566,6 +566,33 @@ inline unsigned size_to_prdtl(unsigned bytes, unsigned bytes_per_prd)
return (bytes + bytes_per_prd - 1) / bytes_per_prd;
}
/* Issue a command, expecting it to fail and STOP the VM */
AHCICommand *ahci_guest_io_halt(AHCIQState *ahci, uint8_t port,
uint8_t ide_cmd, uint64_t buffer,
size_t bufsize, uint64_t sector)
{
AHCICommand *cmd;
cmd = ahci_command_create(ide_cmd);
ahci_command_adjust(cmd, sector, buffer, bufsize, 0);
ahci_command_commit(ahci, cmd, port);
ahci_command_issue_async(ahci, cmd);
qmp_eventwait("STOP");
return cmd;
}
/* Resume a previously failed command and verify/finalize */
void ahci_guest_io_resume(AHCIQState *ahci, AHCICommand *cmd)
{
/* Complete the command */
qmp_async("{'execute':'cont' }");
qmp_eventwait("RESUME");
ahci_command_wait(ahci, cmd);
ahci_command_verify(ahci, cmd);
ahci_command_free(cmd);
}
/* Given a guest buffer address, perform an IO operation */
void ahci_guest_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd,
uint64_t buffer, size_t bufsize, uint64_t sector)
......@@ -623,15 +650,16 @@ void ahci_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd,
g_assert(props);
ptr = ahci_alloc(ahci, bufsize);
g_assert(ptr);
qmemset(ptr, 0x00, bufsize);
if (props->write) {
memwrite(ptr, buffer, bufsize);
bufwrite(ptr, buffer, bufsize);
}
ahci_guest_io(ahci, port, ide_cmd, ptr, bufsize, sector);
if (props->read) {
memread(ptr, buffer, bufsize);
bufread(ptr, buffer, bufsize);
}
ahci_free(ahci, ptr);
......@@ -742,7 +770,7 @@ void ahci_command_set_offset(AHCICommand *cmd, uint64_t lba_sect)
fis->lba_lo[1] = (lba_sect >> 8) & 0xFF;
fis->lba_lo[2] = (lba_sect >> 16) & 0xFF;
if (cmd->props->lba28) {
fis->device = (fis->device & 0xF0) || (lba_sect >> 24) & 0x0F;
fis->device = (fis->device & 0xF0) | ((lba_sect >> 24) & 0x0F);
}
fis->lba_hi[0] = (lba_sect >> 24) & 0xFF;
fis->lba_hi[1] = (lba_sect >> 32) & 0xFF;
......@@ -760,7 +788,9 @@ void ahci_command_set_sizes(AHCICommand *cmd, uint64_t xbytes,
/* Each PRD can describe up to 4MiB, and must not be odd. */
g_assert_cmphex(prd_size, <=, 4096 * 1024);
g_assert_cmphex(prd_size & 0x01, ==, 0x00);
cmd->prd_size = prd_size;
if (prd_size) {
cmd->prd_size = prd_size;
}
cmd->xbytes = xbytes;
cmd->fis.count = (cmd->xbytes / AHCI_SECTOR_SIZE);
cmd->header.prdtl = size_to_prdtl(cmd->xbytes, cmd->prd_size);
......
......@@ -524,6 +524,9 @@ unsigned ahci_pick_cmd(AHCIQState *ahci, uint8_t port);
unsigned size_to_prdtl(unsigned bytes, unsigned bytes_per_prd);
void ahci_guest_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd,
uint64_t gbuffer, size_t size, uint64_t sector);
AHCICommand *ahci_guest_io_halt(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd,
uint64_t gbuffer, size_t size, uint64_t sector);
void ahci_guest_io_resume(AHCIQState *ahci, AHCICommand *cmd);
void ahci_io(AHCIQState *ahci, uint8_t port, uint8_t ide_cmd,
void *buffer, size_t bufsize, uint64_t sector);
......
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <unistd.h>
#include <fcntl.h>
......@@ -62,6 +63,90 @@ void qtest_shutdown(QOSState *qs)
g_free(qs);
}
void set_context(QOSState *s)
{
global_qtest = s->qts;
}
static QDict *qmp_execute(const char *command)
{
char *fmt;
QDict *rsp;
fmt = g_strdup_printf("{ 'execute': '%s' }", command);
rsp = qmp(fmt);
g_free(fmt);
return rsp;
}
void migrate(QOSState *from, QOSState *to, const char *uri)
{
const char *st;
char *s;
QDict *rsp, *sub;
bool running;
set_context(from);
/* Is the machine currently running? */
rsp = qmp_execute("query-status");
g_assert(qdict_haskey(rsp, "return"));
sub = qdict_get_qdict(rsp, "return");
g_assert(qdict_haskey(sub, "running"));
running = qdict_get_bool(sub, "running");
QDECREF(rsp);
/* Issue the migrate command. */
s = g_strdup_printf("{ 'execute': 'migrate',"
"'arguments': { 'uri': '%s' } }",
uri);
rsp = qmp(s);
g_free(s);
g_assert(qdict_haskey(rsp, "return"));
QDECREF(rsp);
/* Wait for STOP event, but only if we were running: */
if (running) {
qmp_eventwait("STOP");
}
/* If we were running, we can wait for an event. */
if (running) {
migrate_allocator(from->alloc, to->alloc);
set_context(to);
qmp_eventwait("RESUME");
return;
}
/* Otherwise, we need to wait: poll until migration is completed. */
while (1) {
rsp = qmp_execute("query-migrate");
g_assert(qdict_haskey(rsp, "return"));
sub = qdict_get_qdict(rsp, "return");
g_assert(qdict_haskey(sub, "status"));
st = qdict_get_str(sub, "status");
/* "setup", "active", "completed", "failed", "cancelled" */
if (strcmp(st, "completed") == 0) {
QDECREF(rsp);
break;
}
if ((strcmp(st, "setup") == 0) || (strcmp(st, "active") == 0)) {
QDECREF(rsp);
g_usleep(5000);
continue;
}
fprintf(stderr, "Migration did not complete, status: %s\n", st);
g_assert_not_reached();
}
migrate_allocator(from->alloc, to->alloc);
set_context(to);
}
void mkimg(const char *file, const char *fmt, unsigned size_mb)
{
gchar *cli;
......
......@@ -21,6 +21,8 @@ QOSState *qtest_boot(QOSOps *ops, const char *cmdline_fmt, ...);
void qtest_shutdown(QOSState *qs);
void mkimg(const char *file, const char *fmt, unsigned size_mb);
void mkqcow2(const char *file, unsigned size_mb);
void set_context(QOSState *s);
void migrate(QOSState *from, QOSState *to, const char *uri);
void prepare_blkdebug_script(const char *debug_fn, const char *event);
static inline uint64_t qmalloc(QOSState *q, size_t bytes)
......
......@@ -30,8 +30,8 @@ struct QGuestAllocator {
uint64_t end;
uint32_t page_size;
MemList used;
MemList free;
MemList *used;
MemList *free;
};
#define DEFAULT_PAGE_SIZE 4096
......@@ -150,7 +150,7 @@ static uint64_t mlist_fulfill(QGuestAllocator *s, MemBlock *freenode,
addr = freenode->addr;
if (freenode->size == size) {
/* re-use this freenode as our used node */
QTAILQ_REMOVE(&s->free, freenode, MLIST_ENTNAME);
QTAILQ_REMOVE(s->free, freenode, MLIST_ENTNAME);
usednode = freenode;
} else {
/* adjust the free node and create a new used node */
......@@ -159,7 +159,7 @@ static uint64_t mlist_fulfill(QGuestAllocator *s, MemBlock *freenode,
usednode = mlist_new(addr, size);
}
mlist_sort_insert(&s->used, usednode);
mlist_sort_insert(s->used, usednode);
return addr;
}
......@@ -171,7 +171,7 @@ static void mlist_check(QGuestAllocator *s)
uint64_t addr = s->start > 0 ? s->start - 1 : 0;
uint64_t next = s->start;
QTAILQ_FOREACH(node, &s->free, MLIST_ENTNAME) {
QTAILQ_FOREACH(node, s->free, MLIST_ENTNAME) {
g_assert_cmpint(node->addr, >, addr);
g_assert_cmpint(node->addr, >=, next);
addr = node->addr;
......@@ -180,7 +180,7 @@ static void mlist_check(QGuestAllocator *s)
addr = s->start > 0 ? s->start - 1 : 0;
next = s->start;
QTAILQ_FOREACH(node, &s->used, MLIST_ENTNAME) {
QTAILQ_FOREACH(node, s->used, MLIST_ENTNAME) {
g_assert_cmpint(node->addr, >, addr);
g_assert_cmpint(node->addr, >=, next);
addr = node->addr;
......@@ -192,7 +192,7 @@ static uint64_t mlist_alloc(QGuestAllocator *s, uint64_t size)
{
MemBlock *node;
node = mlist_find_space(&s->free, size);
node = mlist_find_space(s->free, size);
if (!node) {
fprintf(stderr, "Out of guest memory.\n");
g_assert_not_reached();
......@@ -208,7 +208,7 @@ static void mlist_free(QGuestAllocator *s, uint64_t addr)
return;
}
node = mlist_find_key(&s->used, addr);
node = mlist_find_key(s->used, addr);
if (!node) {
fprintf(stderr, "Error: no record found for an allocation at "
"0x%016" PRIx64 ".\n",
......@@ -217,9 +217,9 @@ static void mlist_free(QGuestAllocator *s, uint64_t addr)
}
/* Rip it out of the used list and re-insert back into the free list. */
QTAILQ_REMOVE(&s->used, node, MLIST_ENTNAME);
mlist_sort_insert(&s->free, node);
mlist_coalesce(&s->free, node);
QTAILQ_REMOVE(s->used, node, MLIST_ENTNAME);
mlist_sort_insert(s->free, node);
mlist_coalesce(s->free, node);
}
/*
......@@ -233,7 +233,7 @@ void alloc_uninit(QGuestAllocator *allocator)
QAllocOpts mask;
/* Check for guest leaks, and destroy the list. */
QTAILQ_FOREACH_SAFE(node, &allocator->used, MLIST_ENTNAME, tmp) {
QTAILQ_FOREACH_SAFE(node, allocator->used, MLIST_ENTNAME, tmp) {
if (allocator->opts & (ALLOC_LEAK_WARN | ALLOC_LEAK_ASSERT)) {
fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; "
"size 0x%016" PRIx64 ".\n",
......@@ -248,7 +248,7 @@ void alloc_uninit(QGuestAllocator *allocator)
/* If we have previously asserted that there are no leaks, then there
* should be only one node here with a specific address and size. */
mask = ALLOC_LEAK_ASSERT | ALLOC_PARANOID;
QTAILQ_FOREACH_SAFE(node, &allocator->free, MLIST_ENTNAME, tmp) {
QTAILQ_FOREACH_SAFE(node, allocator->free, MLIST_ENTNAME, tmp) {
if ((allocator->opts & mask) == mask) {
if ((node->addr != allocator->start) ||
(node->size != allocator->end - allocator->start)) {
......@@ -260,6 +260,8 @@ void alloc_uninit(QGuestAllocator *allocator)
g_free(node);
}
g_free(allocator->used);
g_free(allocator->free);
g_free(allocator);
}
......@@ -297,11 +299,13 @@ QGuestAllocator *alloc_init(uint64_t start, uint64_t end)
s->start = start;
s->end = end;
QTAILQ_INIT(&s->used);
QTAILQ_INIT(&s->free);
s->used = g_malloc(sizeof(MemList));
s->free = g_malloc(sizeof(MemList));
QTAILQ_INIT(s->used);
QTAILQ_INIT(s->free);
node = mlist_new(s->start, s->end - s->start);
QTAILQ_INSERT_HEAD(&s->free, node, MLIST_ENTNAME);
QTAILQ_INSERT_HEAD(s->free, node, MLIST_ENTNAME);
s->page_size = DEFAULT_PAGE_SIZE;
......@@ -319,7 +323,7 @@ QGuestAllocator *alloc_init_flags(QAllocOpts opts,
void alloc_set_page_size(QGuestAllocator *allocator, size_t page_size)
{
/* Can't alter the page_size for an allocator in-use */
g_assert(QTAILQ_EMPTY(&allocator->used));
g_assert(QTAILQ_EMPTY(allocator->used));
g_assert(is_power_of_2(page_size));
allocator->page_size = page_size;
......@@ -329,3 +333,39 @@ void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts)
{
allocator->opts |= opts;
}
void migrate_allocator(QGuestAllocator *src,
QGuestAllocator *dst)
{
MemBlock *node, *tmp;
MemList *tmpused, *tmpfree;
/* The general memory layout should be equivalent,
* though opts can differ. */
g_assert_cmphex(src->start, ==, dst->start);
g_assert_cmphex(src->end, ==, dst->end);
/* Destroy (silently, regardless of options) the dest-list: */
QTAILQ_FOREACH_SAFE(node, dst->used, MLIST_ENTNAME, tmp) {
g_free(node);
}
QTAILQ_FOREACH_SAFE(node, dst->free, MLIST_ENTNAME, tmp) {
g_free(node);
}
tmpused = dst->used;
tmpfree = dst->free;
/* Inherit the lists of the source allocator: */
dst->used = src->used;
dst->free = src->free;
/* Source is now re-initialized, the source memory is 'invalid' now: */
src->used = tmpused;
src->free = tmpfree;
QTAILQ_INIT(src->used);
QTAILQ_INIT(src->free);
node = mlist_new(src->start, src->end - src->start);
QTAILQ_INSERT_HEAD(src->free, node, MLIST_ENTNAME);
return;
}
......@@ -31,6 +31,7 @@ void alloc_uninit(QGuestAllocator *allocator);
/* Always returns page aligned values */
uint64_t guest_alloc(QGuestAllocator *allocator, size_t size);
void guest_free(QGuestAllocator *allocator, uint64_t addr);
void migrate_allocator(QGuestAllocator *src, QGuestAllocator *dst);
QGuestAllocator *alloc_init(uint64_t start, uint64_t end);
QGuestAllocator *alloc_init_flags(QAllocOpts flags,
......
......@@ -695,28 +695,55 @@ void qtest_add_data_func(const char *str, const void *data, void (*fn))
g_free(path);
}
void qtest_bufwrite(QTestState *s, uint64_t addr, const void *data, size_t size)
{
gchar *bdata;
bdata = g_base64_encode(data, size);
qtest_sendf(s, "b64write 0x%" PRIx64 " 0x%zx ", addr, size);
socket_send(s->fd, bdata, strlen(bdata));
socket_send(s->fd, "\n", 1);
qtest_rsp(s, 0);
g_free(bdata);
}
void qtest_bufread(QTestState *s, uint64_t addr, void *data, size_t size)
{
gchar **args;
size_t len;
qtest_sendf(s, "b64read 0x%" PRIx64 " 0x%zx\n", addr, size);
args = qtest_rsp(s, 2);
g_base64_decode_inplace(args[1], &len);
if (size != len) {
fprintf(stderr, "bufread: asked for %zu bytes but decoded %zu\n",
size, len);
len = MIN(len, size);
}
memcpy(data, args[1], len);
g_strfreev(args);
}
void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size)
{
const uint8_t *ptr = data;
size_t i;
char *enc = g_malloc(2 * size + 1);
qtest_sendf(s, "write 0x%" PRIx64 " 0x%zx 0x", addr, size);
for (i = 0; i < size; i++) {
qtest_sendf(s, "%02x", ptr[i]);
sprintf(&enc[i * 2], "%02x", ptr[i]);
}
qtest_sendf(s, "\n");
qtest_sendf(s, "write 0x%" PRIx64 " 0x%zx 0x%s\n", addr, size, enc);
qtest_rsp(s, 0);
g_free(enc);
}
void qtest_memset(QTestState *s, uint64_t addr, uint8_t pattern, size_t size)
{
size_t i;
qtest_sendf(s, "write 0x%" PRIx64 " 0x%zx 0x", addr, size);
for (i = 0; i < size; i++) {
qtest_sendf(s, "%02x", pattern);
}
qtest_sendf(s, "\n");
qtest_sendf(s, "memset 0x%" PRIx64 " 0x%zx 0x%02x\n", addr, size, pattern);
qtest_rsp(s, 0);
}
......
......@@ -300,6 +300,17 @@ uint64_t qtest_readq(QTestState *s, uint64_t addr);
*/
void qtest_memread(QTestState *s, uint64_t addr, void *data, size_t size);
/**
* qtest_bufread:
* @s: #QTestState instance to operate on.
* @addr: Guest address to read from.
* @data: Pointer to where memory contents will be stored.
* @size: Number of bytes to read.
*
* Read guest memory into a buffer and receive using a base64 encoding.
*/
void qtest_bufread(QTestState *s, uint64_t addr, void *data, size_t size);
/**
* qtest_memwrite:
* @s: #QTestState instance to operate on.
......@@ -311,6 +322,18 @@ void qtest_memread(QTestState *s, uint64_t addr, void *data, size_t size);
*/
void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size);
/**
* qtest_bufwrite:
* @s: #QTestState instance to operate on.
* @addr: Guest address to write to.
* @data: Pointer to the bytes that will be written to guest memory.
* @size: Number of bytes to write.
*
* Write a buffer to guest memory and transmit using a base64 encoding.
*/
void qtest_bufwrite(QTestState *s, uint64_t addr,
const void *data, size_t size);
/**
* qtest_memset:
* @s: #QTestState instance to operate on.
......@@ -698,6 +721,19 @@ static inline void memread(uint64_t addr, void *data, size_t size)
qtest_memread(global_qtest, addr, data, size);
}
/**
* bufread:
* @addr: Guest address to read from.
* @data: Pointer to where memory contents will be stored.
* @size: Number of bytes to read.
*
* Read guest memory into a buffer, receive using a base64 encoding.
*/
static inline void bufread(uint64_t addr, void *data, size_t size)
{
qtest_bufread(global_qtest, addr, data, size);
}
/**
* memwrite:
* @addr: Guest address to write to.
......@@ -711,6 +747,19 @@ static inline void memwrite(uint64_t addr, const void *data, size_t size)
qtest_memwrite(global_qtest, addr, data, size);
}
/**
* bufwrite:
* @addr: Guest address to write to.
* @data: Pointer to the bytes that will be written to guest memory.
* @size: Number of bytes to write.
*
* Write a buffer to guest memory, transmit using a base64 encoding.
*/
static inline void bufwrite(uint64_t addr, const void *data, size_t size)
{
qtest_bufwrite(global_qtest, addr, data, size);
}
/**
* qmemset:
* @addr: Guest address to write to.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册