xref: /qemu/tests/qtest/vmgenid-test.c (revision 91e01270)
1 /*
2  * QTest testcase for VM Generation ID
3  *
4  * Copyright (c) 2016 Red Hat, Inc.
5  * Copyright (c) 2017 Skyport Systems
6  *
7  * This work is licensed under the terms of the GNU GPL, version 2 or later.
8  * See the COPYING file in the top-level directory.
9  */
10 
11 #include "qemu/osdep.h"
12 #include "qemu/bitmap.h"
13 #include "qemu/uuid.h"
14 #include "hw/acpi/acpi-defs.h"
15 #include "boot-sector.h"
16 #include "acpi-utils.h"
17 #include "libqtest.h"
18 #include "qapi/qmp/qdict.h"
19 
20 #define VGID_GUID "324e6eaf-d1d1-4bf6-bf41-b9bb6c91fb87"
21 #define VMGENID_GUID_OFFSET 40   /* allow space for
22                                   * OVMF SDT Header Probe Suppressor
23                                   */
24 #define RSDP_ADDR_INVALID 0x100000 /* RSDP must be below this address */
25 
26 static uint32_t acpi_find_vgia(QTestState *qts)
27 {
28     uint32_t rsdp_offset;
29     uint32_t guid_offset = 0;
30     uint8_t rsdp_table[36 /* ACPI 2.0+ RSDP size */];
31     uint32_t rsdt_len, table_length;
32     uint8_t *rsdt, *ent;
33 
34     /* Wait for guest firmware to finish and start the payload. */
35     boot_sector_test(qts);
36 
37     /* Tables should be initialized now. */
38     rsdp_offset = acpi_find_rsdp_address(qts);
39 
40     g_assert_cmphex(rsdp_offset, <, RSDP_ADDR_INVALID);
41 
42 
43     acpi_fetch_rsdp_table(qts, rsdp_offset, rsdp_table);
44     acpi_fetch_table(qts, &rsdt, &rsdt_len, &rsdp_table[16 /* RsdtAddress */],
45                      4, "RSDT", true);
46 
47     ACPI_FOREACH_RSDT_ENTRY(rsdt, rsdt_len, ent, 4 /* Entry size */) {
48         uint8_t *table_aml;
49 
50         acpi_fetch_table(qts, &table_aml, &table_length, ent, 4, NULL, true);
51         if (!memcmp(table_aml + 16 /* OEM Table ID */, "VMGENID", 7)) {
52             uint32_t vgia_val;
53             uint8_t *aml = &table_aml[36 /* AML byte-code start */];
54             /* the first entry in the table should be VGIA
55              * That's all we need
56              */
57             g_assert(aml[0 /* name_op*/] == 0x08);
58             g_assert(memcmp(&aml[1 /* name */], "VGIA", 4) == 0);
59             g_assert(aml[5 /* value op */] == 0x0C /* dword */);
60             memcpy(&vgia_val, &aml[6 /* value */], 4);
61 
62             /* The GUID is written at a fixed offset into the fw_cfg file
63              * in order to implement the "OVMF SDT Header probe suppressor"
64              * see docs/specs/vmgenid.txt for more details
65              */
66             guid_offset = le32_to_cpu(vgia_val) + VMGENID_GUID_OFFSET;
67             g_free(table_aml);
68             break;
69         }
70         g_free(table_aml);
71     }
72     g_free(rsdt);
73     return guid_offset;
74 }
75 
76 static void read_guid_from_memory(QTestState *qts, QemuUUID *guid)
77 {
78     uint32_t vmgenid_addr;
79     int i;
80 
81     vmgenid_addr = acpi_find_vgia(qts);
82     g_assert(vmgenid_addr);
83 
84     /* Read the GUID directly from guest memory */
85     for (i = 0; i < 16; i++) {
86         guid->data[i] = qtest_readb(qts, vmgenid_addr + i);
87     }
88     /* The GUID is in little-endian format in the guest, while QEMU
89      * uses big-endian.  Swap after reading.
90      */
91     *guid = qemu_uuid_bswap(*guid);
92 }
93 
94 static void read_guid_from_monitor(QTestState *qts, QemuUUID *guid)
95 {
96     QDict *rsp, *rsp_ret;
97     const char *guid_str;
98 
99     rsp = qtest_qmp(qts, "{ 'execute': 'query-vm-generation-id' }");
100     if (qdict_haskey(rsp, "return")) {
101         rsp_ret = qdict_get_qdict(rsp, "return");
102         g_assert(qdict_haskey(rsp_ret, "guid"));
103         guid_str = qdict_get_str(rsp_ret, "guid");
104         g_assert(qemu_uuid_parse(guid_str, guid) == 0);
105     }
106     qobject_unref(rsp);
107 }
108 
109 static char disk[] = "tests/vmgenid-test-disk-XXXXXX";
110 
111 #define GUID_CMD(guid)                          \
112     "-accel kvm -accel tcg "                    \
113     "-device vmgenid,id=testvgid,guid=%s "      \
114     "-drive id=hd0,if=none,file=%s,format=raw " \
115     "-device ide-hd,drive=hd0 ", guid, disk
116 
117 static void vmgenid_set_guid_test(void)
118 {
119     QemuUUID expected, measured;
120     QTestState *qts;
121 
122     g_assert(qemu_uuid_parse(VGID_GUID, &expected) == 0);
123 
124     qts = qtest_initf(GUID_CMD(VGID_GUID));
125 
126     /* Read the GUID from accessing guest memory */
127     read_guid_from_memory(qts, &measured);
128     g_assert(memcmp(measured.data, expected.data, sizeof(measured.data)) == 0);
129 
130     qtest_quit(qts);
131 }
132 
133 static void vmgenid_set_guid_auto_test(void)
134 {
135     QemuUUID measured;
136     QTestState *qts;
137 
138     qts = qtest_initf(GUID_CMD("auto"));
139 
140     read_guid_from_memory(qts, &measured);
141 
142     /* Just check that the GUID is non-null */
143     g_assert(!qemu_uuid_is_null(&measured));
144 
145     qtest_quit(qts);
146 }
147 
148 static void vmgenid_query_monitor_test(void)
149 {
150     QemuUUID expected, measured;
151     QTestState *qts;
152 
153     g_assert(qemu_uuid_parse(VGID_GUID, &expected) == 0);
154 
155     qts = qtest_initf(GUID_CMD(VGID_GUID));
156 
157     /* Read the GUID via the monitor */
158     read_guid_from_monitor(qts, &measured);
159     g_assert(memcmp(measured.data, expected.data, sizeof(measured.data)) == 0);
160 
161     qtest_quit(qts);
162 }
163 
164 int main(int argc, char **argv)
165 {
166     int ret;
167 
168     g_test_init(&argc, &argv, NULL);
169 
170     if (!qtest_has_accel("tcg") && !qtest_has_accel("kvm")) {
171         g_test_skip("No KVM or TCG accelerator available");
172         return 0;
173     }
174 
175     ret = boot_sector_init(disk);
176     if (ret) {
177         return ret;
178     }
179 
180     qtest_add_func("/vmgenid/vmgenid/set-guid",
181                    vmgenid_set_guid_test);
182     qtest_add_func("/vmgenid/vmgenid/set-guid-auto",
183                    vmgenid_set_guid_auto_test);
184     qtest_add_func("/vmgenid/vmgenid/query-monitor",
185                    vmgenid_query_monitor_test);
186     ret = g_test_run();
187     boot_sector_cleanup(disk);
188 
189     return ret;
190 }
191