mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
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: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
@@ -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"
|
||||
|
||||
+63
-26
@@ -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),
|
||||
|
||||
+263
-240
File diff suppressed because it is too large
Load Diff
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+323
-5
@@ -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();
|
||||
|
||||
|
||||
+34
-4
@@ -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);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#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)
|
||||
|
||||
+57
-17
@@ -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,
|
||||
|
||||
+37
-10
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user