From patchwork Thu Oct 3 11:41:44 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eric Auger X-Patchwork-Id: 1171181 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 46kWMV1GDpz9sN1 for ; Thu, 3 Oct 2019 21:42:28 +1000 (AEST) Received: from localhost ([::1]:35104 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iFzV3-0003AI-5D for incoming@patchwork.ozlabs.org; Thu, 03 Oct 2019 07:42:25 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:37190) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iFzUZ-00038j-H5 for qemu-devel@nongnu.org; Thu, 03 Oct 2019 07:41:58 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1iFzUW-0003Ad-D2 for qemu-devel@nongnu.org; Thu, 03 Oct 2019 07:41:54 -0400 Received: from mx1.redhat.com ([209.132.183.28]:34994) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1iFzUW-0003AC-4Q for qemu-devel@nongnu.org; Thu, 03 Oct 2019 07:41:52 -0400 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.23]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 46B6C30860B9; Thu, 3 Oct 2019 11:41:51 +0000 (UTC) Received: from laptop.redhat.com (ovpn-117-64.ams2.redhat.com [10.36.117.64]) by smtp.corp.redhat.com (Postfix) with ESMTP id 0395619C69; Thu, 3 Oct 2019 11:41:46 +0000 (UTC) From: Eric Auger To: eric.auger.pro@gmail.com, eric.auger@redhat.com, qemu-devel@nongnu.org, dgilbert@redhat.com, quintela@redhat.com Subject: [PATCH] migration: Support gtree migration Date: Thu, 3 Oct 2019 13:41:44 +0200 Message-Id: <20191003114144.30129-1-eric.auger@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.84 on 10.5.11.23 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.44]); Thu, 03 Oct 2019 11:41:51 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 209.132.183.28 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: "Qemu-devel" Introduce support for GTree migration. A custom save/restore is implemented. Each item is made of a key and a data. For that reason, 2 VMSD objects are passed into the GTree VMStateField. When putting the items, the tree is traversed in sorted order by g_tree_foreach. On the get() path, gtrees must be allocated using the proper key compare, key destroy and value destroy. This can be done externally of automatically. If done automatically, the set of functions must be stored within the VMStateField in a new opaque pointer. Automatic allocation is needed for complex state save/restore. For instance the virtio-iommu uses a gtree of domain and each domain has a gtree of mappings. Special care was taken about direct key (ie. when the key is not a pointer to an object but is directly a value). Tests are added to test save/dump of structs containing gtrees including the virtio-iommu domain/mappings scenario. Signed-off-by: Eric Auger --- include/migration/vmstate.h | 31 +++ migration/trace-events | 6 + migration/vmstate-types.c | 133 ++++++++++++ tests/test-vmstate.c | 422 ++++++++++++++++++++++++++++++++++++ 4 files changed, 592 insertions(+) diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h index 1fbfd099dd..4d9698eaab 100644 --- a/include/migration/vmstate.h +++ b/include/migration/vmstate.h @@ -171,6 +171,7 @@ struct VMStateField { int version_id; int struct_version_id; bool (*field_exists)(void *opaque, int version_id); + void *data; }; struct VMStateDescription { @@ -224,6 +225,7 @@ extern const VMStateInfo vmstate_info_unused_buffer; extern const VMStateInfo vmstate_info_tmp; extern const VMStateInfo vmstate_info_bitmap; extern const VMStateInfo vmstate_info_qtailq; +extern const VMStateInfo vmstate_info_gtree; #define type_check_2darray(t1,t2,n,m) ((t1(*)[n][m])0 - (t2*)0) /* @@ -754,6 +756,35 @@ extern const VMStateInfo vmstate_info_qtailq; .start = offsetof(_type, _next), \ } +typedef struct GTreeInitData { + GCompareDataFunc key_compare_func; + gpointer key_compare_data; + GDestroyNotify key_destroy_func; + GDestroyNotify value_destroy_func; +} GTreeInitData; + +/* + * For migrating a GTree. + * _vmsd: Start address of the 2 element array containing the key vmsd + * and the data vmsd + * _key_type: type of the key + * _val_type: type of the value + * _data: pointer to a GTreeInitData struct + * if _data is NULL, the target tree must have been properly initialized + */ +#define VMSTATE_GTREE_V(_field, _state, _version, _vmsd, \ + _key_type, _val_type, _data) \ +{ \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .vmsd = (_vmsd), \ + .info = &vmstate_info_gtree, \ + .start = sizeof(_key_type), \ + .size = sizeof(_val_type), \ + .offset = offsetof(_state, _field), \ + .data = (_data) \ +} + /* _f : field name _f_n : num of elements field_name _n : num of elements diff --git a/migration/trace-events b/migration/trace-events index 858d415d56..3e2775bd3b 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -71,6 +71,12 @@ get_qtailq_end(const char *name, const char *reason, int val) "%s %s/%d" put_qtailq(const char *name, int version_id) "%s v%d" put_qtailq_end(const char *name, const char *reason) "%s %s" +get_gtree(const char *key_name, const char *val_name, int version) "%s/%s %d" +get_gtree_fail(const char *key_name, const char *reason, int val) "%s %s/%d" +get_gtree_succeed(const char *key_name, const char *val_name, const char *reason, int val) "%s/%s %s/%d" +put_gtree(const char *key_name, int key_version, const char *val_name, int val_version) "%s(%d)/%s(%d)" +put_gtree_end(const char *key_name, const char *val_name, const char *reason) "%s/%s %s" + # qemu-file.c qemu_file_fclose(void) "" diff --git a/migration/vmstate-types.c b/migration/vmstate-types.c index bee658a1b2..22e7c57204 100644 --- a/migration/vmstate-types.c +++ b/migration/vmstate-types.c @@ -17,6 +17,7 @@ #include "qemu/error-report.h" #include "qemu/queue.h" #include "trace.h" +#include /* bool */ @@ -691,3 +692,135 @@ const VMStateInfo vmstate_info_qtailq = { .get = get_qtailq, .put = put_qtailq, }; + +struct put_gtree_data { + QEMUFile *f; + const VMStateField *field; + QJSON *vmdesc; +}; + +static gboolean put_gtree_elem(gpointer key, gpointer value, gpointer data) +{ + struct put_gtree_data *capsule = (struct put_gtree_data *)data; + const VMStateField *field = capsule->field; + QEMUFile *f = capsule->f; + const VMStateDescription *key_vmsd = &field->vmsd[0]; + const VMStateDescription *data_vmsd = &field->vmsd[1]; + + qemu_put_byte(f, true); + + /* put the key */ + if (!key_vmsd->fields) { + qemu_put_be64(f, (uint64_t)key); + } else { + if (vmstate_save_state(f, key_vmsd, key, capsule->vmdesc)) { + return true; + } + } + + /* put the data */ + if (vmstate_save_state(f, data_vmsd, value, capsule->vmdesc)) { + return true; + } + return false; +} + +static int put_gtree(QEMUFile *f, void *pv, size_t unused_size, + const VMStateField *field, QJSON *vmdesc) +{ + const VMStateDescription *key_vmsd = &field->vmsd[0]; + const VMStateDescription *val_vmsd = &field->vmsd[1]; + struct put_gtree_data capsule = {f, field, vmdesc}; + uint64_t *pval = pv; + GTree *tree = (GTree *)(*pval); + + trace_put_gtree(key_vmsd->name, key_vmsd->version_id, + val_vmsd->name, val_vmsd->version_id); + g_tree_foreach(tree, put_gtree_elem, (gpointer)&capsule); + qemu_put_byte(f, false); + + trace_put_gtree_end(key_vmsd->name, val_vmsd->name, "end"); + return 0; +} + +static int get_gtree(QEMUFile *f, void *pv, size_t unused_size, + const VMStateField *field) +{ + const VMStateDescription *key_vmsd = &field->vmsd[0]; + const VMStateDescription *val_vmsd = &field->vmsd[1]; + int version_id = field->version_id; + size_t key_size = field->start; + size_t val_size = field->size; + GTreeInitData *init_data; + uint64_t *pval = pv; + void *key, *val; + GTree *tree; + int ret; + + /* in case of direct key, the key vmsd can be {}, ie. check fields */ + trace_get_gtree(key_vmsd->name, val_vmsd->name, version_id); + if (key_vmsd->fields && version_id > key_vmsd->version_id) { + error_report("%s %s", key_vmsd->name, "too new"); + trace_get_gtree_fail(key_vmsd->name, "too new", -EINVAL); + return -EINVAL; + } + if (key_vmsd->fields && version_id < key_vmsd->minimum_version_id) { + error_report("%s %s", key_vmsd->name, "too old"); + trace_get_gtree_fail(key_vmsd->name, "too old", -EINVAL); + return -EINVAL; + } + if (version_id > val_vmsd->version_id) { + error_report("%s %s", val_vmsd->name, "too new"); + trace_get_gtree_fail(val_vmsd->name, "too new", -EINVAL); + return -EINVAL; + } + if (version_id < val_vmsd->minimum_version_id) { + error_report("%s %s", val_vmsd->name, "too old"); + trace_get_gtree_fail(val_vmsd->name, "too old", -EINVAL); + return -EINVAL; + } + + if (field->data) { + init_data = (GTreeInitData *)field->data; + tree = g_tree_new_full(init_data->key_compare_func, + init_data->key_compare_data, + init_data->key_destroy_func, + init_data->value_destroy_func); + *pval = (uint64_t)tree; + } else { + /* tree is externally allocated */ + tree = (GTree *)(*pval); + } + + while (qemu_get_byte(f)) { + if (!key_vmsd->fields) { + key = (gpointer)qemu_get_be64(f); + } else { + key = g_malloc0(key_size); + ret = vmstate_load_state(f, key_vmsd, key, version_id); + } + if (ret) { + g_free(key); + trace_get_gtree_fail(key_vmsd->name, "saving state", ret); + return ret; + } + val = g_malloc0(val_size); + ret = vmstate_load_state(f, val_vmsd, val, version_id); + if (ret) { + g_free(key); + g_free(val); + trace_get_gtree_fail(val_vmsd->name, "saving state", ret); + return ret; + } + g_tree_insert(tree, key, val); + } + trace_get_gtree_succeed(key_vmsd->name, val_vmsd->name, "end", 0); + return 0; +} + + +const VMStateInfo vmstate_info_gtree = { + .name = "gtree", + .get = get_gtree, + .put = put_gtree, +}; diff --git a/tests/test-vmstate.c b/tests/test-vmstate.c index e80c4c6143..4d4cf7e126 100644 --- a/tests/test-vmstate.c +++ b/tests/test-vmstate.c @@ -33,6 +33,7 @@ #include "qemu/coroutine.h" #include "qemu/module.h" #include "io/channel-file.h" +#include static char temp_file[] = "/tmp/vmst.test.XXXXXX"; static int temp_fd; @@ -812,6 +813,423 @@ static void test_load_q(void) qemu_fclose(fload); } + +/* interval (key) */ +typedef struct TestGTreeInterval { + uint64_t low; + uint64_t high; +} TestGTreeInterval; + +static const VMStateDescription vmstate_interval = { + .name = "interval", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT64(low, TestGTreeInterval), + VMSTATE_UINT64(high, TestGTreeInterval), + VMSTATE_END_OF_LIST() + } +}; + +/* mapping (value) */ +typedef struct TestGTreeMapping { + uint64_t phys_addr; + uint32_t flags; +} TestGTreeMapping; + +static const VMStateDescription vmstate_mapping = { + .name = "mapping", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT64(phys_addr, TestGTreeMapping), + VMSTATE_UINT32(flags, TestGTreeMapping), + VMSTATE_END_OF_LIST() + }, +}; + +static const VMStateDescription vmstate_interval_mapping[2] = { + vmstate_interval, /* key */ + vmstate_mapping /* value */ +}; + +typedef struct TestGTreeDomain { + int32_t id; + GTree *mappings; +} TestGTreeDomain; + +typedef struct TestGTreeIOMMU { + int32_t id; + GTree *domains; +} TestGTreeIOMMU; + +/* Interval comparison function */ +static gint interval_cmp(gconstpointer a, gconstpointer b, gpointer user_data) +{ + TestGTreeInterval *inta = (TestGTreeInterval *)a; + TestGTreeInterval *intb = (TestGTreeInterval *)b; + + if (inta->high < intb->low) { + return -1; + } else if (intb->high < inta->low) { + return 1; + } else { + return 0; + } +} + +/* ID comparison function */ +static gint int_cmp(gconstpointer a, gconstpointer b, gpointer user_data) +{ + uint ua = GPOINTER_TO_UINT(a); + uint ub = GPOINTER_TO_UINT(b); + return (ua > ub) - (ua < ub); +} + +/* init data for interval/mapping binary tree */ +GTreeInitData gtree_interval_mapping_init = { + .key_compare_func = interval_cmp, + .key_compare_data = NULL, + .key_destroy_func = g_free, + .value_destroy_func = g_free +}; + +static void destroy_domain(gpointer data) +{ + TestGTreeDomain *domain = (TestGTreeDomain *)data; + + g_tree_destroy(domain->mappings); + g_free(domain); +} + +/* init data for id (direct key)/domain binary tree */ +GTreeInitData gtree_id_domain_init = { + .key_compare_func = int_cmp, + .key_compare_data = NULL, + .key_destroy_func = NULL, + .value_destroy_func = destroy_domain +}; + +static const VMStateDescription vmstate_domain = { + .name = "domain", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_INT32(id, TestGTreeDomain), + VMSTATE_GTREE_V(mappings, TestGTreeDomain, 1, vmstate_interval_mapping, + TestGTreeInterval, TestGTreeMapping, + >ree_interval_mapping_init), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_id_domain[2] = { + {}, vmstate_domain /* direct key, value */ +}; + +static const VMStateDescription vmstate_iommu = { + .name = "iommu", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_INT32(id, TestGTreeIOMMU), + VMSTATE_GTREE_V(domains, TestGTreeIOMMU, 1, vmstate_id_domain, + NULL, TestGTreeDomain, + >ree_id_domain_init), + VMSTATE_END_OF_LIST() + } +}; + +uint8_t first_domain_dump[] = { + /* id */ + 0x00, 0x0, 0x0, 0x6, + 0x1, /* start of a */ + /* a */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, + /* map_a */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x1, /* start of b */ + /* b */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0xFF, + /* map_b */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, + 0x0, /* end of gtree */ + QEMU_VM_EOF, /* just to ensure we won't get EOF reported prematurely */ +}; + +static TestGTreeDomain *create_first_domain(void) +{ + TestGTreeDomain *domain; + TestGTreeMapping *map_a, *map_b; + TestGTreeInterval *a, *b; + + domain = g_malloc0(sizeof(TestGTreeDomain)); + domain->id = 6; + + a = g_malloc0(sizeof(TestGTreeInterval)); + a->low = 0x1000; + a->high = 0x1FFF; + + b = g_malloc0(sizeof(TestGTreeInterval)); + b->low = 0x4000; + b->high = 0x4FFF; + + map_a = g_malloc0(sizeof(TestGTreeMapping)); + map_a->phys_addr = 0xa000; + map_a->flags = 1; + + map_b = g_malloc0(sizeof(TestGTreeMapping)); + map_b->phys_addr = 0xe0000; + map_b->flags = 2; + + domain->mappings = g_tree_new_full((GCompareDataFunc)interval_cmp, NULL, + (GDestroyNotify)g_free, + (GDestroyNotify)g_free); + g_tree_insert(domain->mappings, a, map_a); + g_tree_insert(domain->mappings, b, map_b); + return domain; +} + +static void test_gtree_save_domain(void) +{ + TestGTreeDomain *first_domain = create_first_domain(); + + save_vmstate(&vmstate_domain, first_domain); + compare_vmstate(first_domain_dump, sizeof(first_domain_dump)); + destroy_domain(first_domain); +} + +struct match_node_data { + GTree *tree; + gpointer key; + gpointer value; +}; + +struct tree_cmp_data { + GTree *tree1; + GTree *tree2; + GTraverseFunc match_node; +}; + +static gboolean match_interval_mapping_node(gpointer key, + gpointer value, gpointer data) +{ + TestGTreeMapping *map_a, *map_b; + TestGTreeInterval *a, *b; + struct match_node_data *d = (struct match_node_data *)data; + char *str = g_strdup_printf("dest"); + + g_free(str); + a = (TestGTreeInterval *)key; + b = (TestGTreeInterval *)d->key; + + map_a = (TestGTreeMapping *)value; + map_b = (TestGTreeMapping *)d->value; + + assert(a->low == b->low); + assert(a->high == b->high); + assert(map_a->phys_addr == map_b->phys_addr); + assert(map_a->flags == map_b->flags); + g_tree_remove(d->tree, key); + return true; +} + +static gboolean diff_tree(gpointer key, gpointer value, gpointer data) +{ + struct tree_cmp_data *tp = (struct tree_cmp_data *)data; + struct match_node_data d = {tp->tree2, key, value}; + + g_tree_foreach(tp->tree2, tp->match_node, &d); + g_tree_remove(tp->tree1, key); + return false; +} + +static void compare_trees(GTree *tree1, GTree *tree2, + GTraverseFunc function) +{ + struct tree_cmp_data tp = {tree1, tree2, function}; + + g_tree_foreach(tree1, diff_tree, &tp); + assert(g_tree_nnodes(tree1) == 0); + assert(g_tree_nnodes(tree2) == 0); +} + +static void diff_domain(TestGTreeDomain *d1, TestGTreeDomain *d2) +{ + assert(d1->id == d2->id); + compare_trees(d1->mappings, d2->mappings, match_interval_mapping_node); +} + +static gboolean match_domain_node(gpointer key, gpointer value, gpointer data) +{ + uint64_t id1, id2; + TestGTreeDomain *d1, *d2; + struct match_node_data *d = (struct match_node_data *)data; + + id1 = (uint64_t)key; + id2 = (uint64_t)d->key; + d1 = (TestGTreeDomain *)value; + d2 = (TestGTreeDomain *)d->value; + assert(id1 == id2); + diff_domain(d1, d2); + g_tree_remove(d->tree, key); + return true; +} + +static void diff_iommu(TestGTreeIOMMU *iommu1, TestGTreeIOMMU *iommu2) +{ + assert(iommu1->id == iommu2->id); + compare_trees(iommu1->domains, iommu2->domains, match_domain_node); +} + +static void test_gtree_load_domain(void) +{ + TestGTreeDomain *dest_domain = g_malloc0(sizeof(TestGTreeDomain)); + TestGTreeDomain *orig_domain = create_first_domain(); + QEMUFile *fload, *fsave; + char eof; + + fsave = open_test_file(true); + qemu_put_buffer(fsave, first_domain_dump, sizeof(first_domain_dump)); + g_assert(!qemu_file_get_error(fsave)); + qemu_fclose(fsave); + + fload = open_test_file(false); + dest_domain->mappings = g_tree_new_full((GCompareDataFunc)interval_cmp, + NULL, + (GDestroyNotify)g_free, + (GDestroyNotify)g_free); + + vmstate_load_state(fload, &vmstate_domain, dest_domain, 1); + eof = qemu_get_byte(fload); + g_assert(!qemu_file_get_error(fload)); + g_assert_cmpint(orig_domain->id, ==, dest_domain->id); + g_assert_cmpint(eof, ==, QEMU_VM_EOF); + + diff_domain(orig_domain, dest_domain); + destroy_domain(orig_domain); + destroy_domain(dest_domain); + qemu_fclose(fload); +} + +uint8_t iommu_dump[] = { + /* iommu id */ + 0x00, 0x0, 0x0, 0x7, + 0x1,/* start of domain 5 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0, 0x0, 0x5, /* key = 5 */ + 0x00, 0x0, 0x0, 0x5, /* domain1 id */ + 0x1, /* start of mappings */ + /* c */ + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, + /* map_c */ + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, + 0x00, 0x0, 0x0, 0x3, + 0x0, /* end of domain1 mappings*/ + 0x1,/* start of domain 6 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0, 0x0, 0x6, /* key = 6 */ + 0x00, 0x0, 0x0, 0x6, /* domain6 id */ + 0x1, /* start of a */ + /* a */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, + /* map_a */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x1, /* start of b */ + /* b */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0xFF, + /* map_b */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, + 0x0, /* end of domain6 mappings*/ + 0x0, /* end of domains */ + QEMU_VM_EOF, /* just to ensure we won't get EOF reported prematurely */ +}; + +static TestGTreeIOMMU *create_iommu(void) +{ + TestGTreeIOMMU *iommu = g_malloc0(sizeof(TestGTreeIOMMU)); + TestGTreeDomain *first_domain = create_first_domain(); + TestGTreeDomain *second_domain; + TestGTreeMapping *map_c; + TestGTreeInterval *c; + + iommu->id = 7; + iommu->domains = g_tree_new_full((GCompareDataFunc)int_cmp, NULL, + NULL, + destroy_domain); + + second_domain = g_malloc0(sizeof(TestGTreeDomain)); + second_domain->id = 5; + second_domain->mappings = g_tree_new_full((GCompareDataFunc)interval_cmp, + NULL, + (GDestroyNotify)g_free, + (GDestroyNotify)g_free); + + g_tree_insert(iommu->domains, GUINT_TO_POINTER(6), first_domain); + g_tree_insert(iommu->domains, GUINT_TO_POINTER(5), second_domain); + + c = g_malloc0(sizeof(TestGTreeInterval)); + c->low = 0x1000000; + c->high = 0x1FFFFFF; + + map_c = g_malloc0(sizeof(TestGTreeMapping)); + map_c->phys_addr = 0xF000000; + map_c->flags = 0x3; + + g_tree_insert(second_domain->mappings, c, map_c); + return iommu; +} + +static void destroy_iommu(TestGTreeIOMMU *iommu) +{ + g_tree_destroy(iommu->domains); + g_free(iommu); +} + +static void test_gtree_save_iommu(void) +{ + TestGTreeIOMMU *iommu = create_iommu(); + + save_vmstate(&vmstate_iommu, iommu); + compare_vmstate(iommu_dump, sizeof(iommu_dump)); + destroy_iommu(iommu); +} + +static void test_gtree_load_iommu(void) +{ + TestGTreeIOMMU *dest_iommu = g_malloc0(sizeof(TestGTreeIOMMU)); + TestGTreeIOMMU *orig_iommu = create_iommu(); + QEMUFile *fsave, *fload; + char eof; + int ret; + + fsave = open_test_file(true); + qemu_put_buffer(fsave, iommu_dump, sizeof(iommu_dump)); + g_assert(!qemu_file_get_error(fsave)); + qemu_fclose(fsave); + + fload = open_test_file(false); + vmstate_load_state(fload, &vmstate_iommu, dest_iommu, 1); + ret = qemu_file_get_error(fload); + eof = qemu_get_byte(fload); + ret = qemu_file_get_error(fload); + g_assert(!ret); + g_assert_cmpint(orig_iommu->id, ==, dest_iommu->id); + g_assert_cmpint(eof, ==, QEMU_VM_EOF); + + diff_iommu(orig_iommu, dest_iommu); + destroy_iommu(orig_iommu); + destroy_iommu(dest_iommu); + qemu_fclose(fload); +} + typedef struct TmpTestStruct { TestStruct *parent; int64_t diff; @@ -932,6 +1350,10 @@ int main(int argc, char **argv) test_arr_ptr_prim_0_load); g_test_add_func("/vmstate/qtailq/save/saveq", test_save_q); g_test_add_func("/vmstate/qtailq/load/loadq", test_load_q); + g_test_add_func("/vmstate/gtree/save/savedomain", test_gtree_save_domain); + g_test_add_func("/vmstate/gtree/load/loaddomain", test_gtree_load_domain); + g_test_add_func("/vmstate/gtree/save/saveiommu", test_gtree_save_iommu); + g_test_add_func("/vmstate/gtree/load/loadiommu", test_gtree_load_iommu); g_test_add_func("/vmstate/tmp_struct", test_tmp_struct); g_test_run();