@samitouri / QOSamiQemu / commits / 0b70c6ce7b

Add functional and unit tests for the vm-launch-update device

This patchset adds functional and unit tests that exercize various functions and behaviors of vm-launch-update device. It uses the IGVM files that were introduced in the previous patch for exercizing the hypervisor interface. CC: Alex Graf <graf@amazon.com> CC: Gerd Hoffman <kraxel@redhat.com> Reviewed-by: Alexander Graf <graf@amazon.com> Signed-off-by: Ani Sinha <anisinha@redhat.com> Message-ID: <20260817142010.80693-11-anisinha@redhat.com> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>

Ani Sinha committed Aug 17, 2026 at 19:50 UTC 0b70c6ce7b31fb0cab1b01745f9fd8a134c2c6fb
6 files changed +716
tests/functional/aarch64/meson.build
+4
@@ -25,6 +25,10 @@ tests_aarch64_system_quick = [
25 'vmstate',
26 ]
27
28 +if igvm.found()
29 + tests_aarch64_system_quick += [ 'vm_launch_update_aarch' ]
30 +endif
31 +
32 tests_aarch64_system_thorough = [
33 'aspeed_ast2700a1',
34 'aspeed_ast2700a2',
tests/functional/aarch64/test_vm_launch_update_aarch.py new
+33
@@ -0,0 +1,33 @@
1 +#!/usr/bin/env python3
2 +#
3 +# Check for vm-launch-update device.
4 +#
5 +# Copyright (c) 2026 Red Hat, Inc.
6 +#
7 +# Author:
8 +# Ani Sinha <anisinha@redhat.com>
9 +#
10 +# SPDX-License-Identifier: GPL-2.0-or-later
11 +
12 +from qemu_test import QemuSystemTest
13 +
14 +class VmLaunchUpdateDeviceCheck(QemuSystemTest):
15 +
16 + def aarch64_fail_test(self):
17 + """
18 + Currently the device is only supported for pc platforms.
19 + """
20 + self.vm.add_args('-machine', 'virt', '-device',
21 + 'vm-launch-update,id=fwupd1')
22 + self.vm.set_qmp_monitor(enabled=False)
23 + self.vm.launch()
24 + self.vm.wait()
25 + self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1")
26 + self.assertRegex(self.vm.get_log(),
27 + r'This machine does not support vm-launch-update device')
28 +
29 + def test_vm_launch_update(self):
30 + self.aarch64_fail_test()
31 +
32 +if __name__ == '__main__':
33 + QemuSystemTest.main()
tests/functional/x86_64/meson.build
+4
@@ -28,6 +28,10 @@ if not get_option('asan')
28 tests_x86_64_system_quick += [ 'memlock' ]
29 endif
30
31 +if igvm.found()
32 + tests_x86_64_system_quick += [ 'vm_launch_update' ]
33 +endif
34 +
35 tests_x86_64_system_thorough = [
36 'acpi_bits',
37 'hotplug_blk',
tests/functional/x86_64/test_vm_launch_update.py new
+48
@@ -0,0 +1,48 @@
1 +#!/usr/bin/env python3
2 +#
3 +# Check for vm-launch-update device.
4 +#
5 +# Copyright (c) 2026 Red Hat, Inc.
6 +#
7 +# Author:
8 +# Ani Sinha <anisinha@redhat.com>
9 +#
10 +# SPDX-License-Identifier: GPL-2.0-or-later
11 +
12 +from qemu_test import QemuSystemTest
13 +import time
14 +
15 +class VmLaunchUpdateDeviceCheck(QemuSystemTest):
16 + DELAY_BOOT_SEQUENCE = 1
17 +
18 + def vm_launch_update_pass(self):
19 + """
20 + Basic test to make sure vm-launch-update device can be instantiated.
21 + """
22 + self.vm.add_args('-device', 'vm-launch-update,id=fwupd1')
23 + self.vm.set_qmp_monitor(enabled=False)
24 + self.vm.launch()
25 + time.sleep(self.DELAY_BOOT_SEQUENCE)
26 + self.vm.shutdown()
27 + self.assertEqual(self.vm.exitcode(), 0, "QEMU exit code should be 0")
28 +
29 + def multiple_device_fail(self):
30 + """
31 + Only one vm-launch-update device can be instantiated. Ensure failure if
32 + user tries to create more than one device.
33 + """
34 + self.vm.add_args('-device', 'vm-launch-update,id=fw1',
35 + '-device', 'vm-launch-update,id=fw2')
36 + self.vm.set_qmp_monitor(enabled=False)
37 + self.vm.launch()
38 + self.vm.wait()
39 + self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1")
40 + self.assertRegex(self.vm.get_log(),
41 + r'at most one vm-launch-update device is permitted')
42 +
43 + def test_vm_launch_update(self):
44 + self.vm_launch_update_pass()
45 + self.multiple_device_fail()
46 +
47 +if __name__ == '__main__':
48 + QemuSystemTest.main()
tests/qtest/launchupdate-test.c new
+625
@@ -0,0 +1,625 @@
1 +/*
2 + * vmlaunchupdate device fwcfg test.
3 + *
4 + * Copyright (c) 2026 Red Hat, Inc.
5 + *
6 + * Author:
7 + * Ani Sinha <anisinha@redhat.com>
8 + *
9 + * SPDX-License-Identifier: GPL-2.0-or-later
10 + */
11 +
12 +#include "qemu/osdep.h"
13 +#include "libqos/libqos-pc.h"
14 +#include "libqtest.h"
15 +#include "standard-headers/linux/qemu_fw_cfg.h"
16 +#include "libqos/fw_cfg.h"
17 +#include "qemu/bswap.h"
18 +#include "hw/misc/vmlaunchupdate.h"
19 +
20 +#define WAIT_SEC 10
21 +static bool debug;
22 +static bool trace;
23 +static bool confidential;
24 +
25 +static void test_vm_launch_update_capability(void)
26 +{
27 + QFWCFG *fw_cfg;
28 + QTestState *s;
29 + VMLaunchUpdate launch_update;
30 + size_t filesize;
31 + uint64_t capabilities;
32 +
33 + if (!qtest_has_device("vm-launch-update")) {
34 + g_test_skip("Device vm-launch-update is not available");
35 + return;
36 + }
37 +
38 + s = qtest_init("-device vm-launch-update");
39 + fw_cfg = pc_fw_cfg_init(s);
40 +
41 + filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
42 + &launch_update, sizeof(launch_update));
43 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
44 + capabilities = le64_to_cpu(launch_update.capabilities);
45 + g_assert_cmpint(capabilities, ==, VM_LAUNCHUPDATE_FORMAT_IGVM);
46 + pc_fw_cfg_uninit(fw_cfg);
47 + qtest_quit(s);
48 +}
49 +
50 +
51 +static void test_vm_launch_update_disable(void)
52 +{
53 + QFWCFG *fw_cfg;
54 + QOSState *qs;
55 + VMLaunchUpdate launch_update;
56 + uint64_t control;
57 + size_t filesize;
58 +
59 + if (!qtest_has_device("vm-launch-update")) {
60 + g_test_skip("Device vm-launch-update is not available");
61 + return;
62 + }
63 +
64 + /* use default accelerator */
65 + qs = qtest_pc_boot("-device vm-launch-update");
66 +
67 + fw_cfg = pc_fw_cfg_init(qs->qts);
68 +
69 + filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
70 + &launch_update, sizeof(launch_update));
71 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
72 + control = le64_to_cpu(launch_update.control);
73 + g_assert_cmpint(VM_LAUNCHUPDATE_CTL_DISABLE & control, ==, 0);
74 +
75 + /* disable the device */
76 + memset(&launch_update, 0, sizeof(launch_update));
77 + launch_update.control |= VM_LAUNCHUPDATE_CTL_DISABLE;
78 +
79 + filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
80 + &launch_update, sizeof(launch_update));
81 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
82 +
83 + /* try to clear the dsable flag */
84 + memset(&launch_update, 0, sizeof(launch_update));
85 +
86 + filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
87 + &launch_update, sizeof(launch_update));
88 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
89 +
90 + /* check if the device is still disabled */
91 + filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
92 + &launch_update, sizeof(launch_update));
93 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
94 + control = le64_to_cpu(launch_update.control);
95 + g_assert_cmpint(VM_LAUNCHUPDATE_CTL_DISABLE & control, ==, 1);
96 +
97 + pc_fw_cfg_uninit(fw_cfg);
98 + qtest_shutdown(qs);
99 +}
100 +
101 +static void check_error(void)
102 +{
103 + QFWCFG *fw_cfg;
104 + QOSState *qs;
105 + VMLaunchUpdate launch_update;
106 + uint16_t status;
107 + size_t filesize;
108 +
109 + if (!qtest_has_device("vm-launch-update")) {
110 + g_test_skip("Device vm-launch-update is not available");
111 + return;
112 + }
113 +
114 + /* guest not started with IGVM and with default accelerator */
115 + qs = qtest_pc_boot("-device vm-launch-update");
116 +
117 + fw_cfg = pc_fw_cfg_init(qs->qts);
118 +
119 + memset(&launch_update, 0, sizeof(launch_update));
120 + launch_update.fw_image_size = 50;
121 + launch_update.fw_image_addr = cpu_to_le64(0xdeadbeef);
122 + launch_update.control |= VM_LAUNCHUPDATE_FORMAT_IGVM;
123 +
124 + filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
125 + &launch_update, sizeof(launch_update));
126 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
127 +
128 + memset(&launch_update, 0, sizeof(launch_update));
129 + filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
130 + &launch_update, sizeof(launch_update));
131 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
132 + status = le64_to_cpu(launch_update.status);
133 + /* should fail with NOT_IGVM_INIT */
134 + g_assert_cmpint(status, ==, VM_LAUNCHUPDATE_NOT_IGVM_INIT);
135 +
136 + memset(&launch_update, 0, sizeof(launch_update));
137 + launch_update.fw_image_size = 50;
138 + launch_update.fw_image_addr = cpu_to_le64(0xdeadbeef);
139 + /* control set to 0, not VM_LAUNCHUPDATE_FORMAT_IGVM */
140 +
141 + filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
142 + &launch_update, sizeof(launch_update));
143 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
144 +
145 + memset(&launch_update, 0, sizeof(launch_update));
146 + filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
147 + &launch_update, sizeof(launch_update));
148 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
149 + status = le64_to_cpu(launch_update.status);
150 + /* should fail with LOAD_FAIL since it was not IGVM format */
151 + g_assert_cmpint(status, ==, VM_LAUNCHUPDATE_LOAD_FAIL);
152 +}
153 +
154 +static int64_t get_image_size(const char *filename)
155 +{
156 + int fd;
157 + int64_t size;
158 + fd = open(filename, O_RDONLY | O_BINARY);
159 + g_assert_true(fd > 0);
160 + size = lseek(fd, 0, SEEK_END);
161 + close(fd);
162 + return size;
163 +}
164 +
165 +static ssize_t load_image(const char *igvm_f, void **addr, size_t *size)
166 +{
167 + ssize_t actsize = 0, l = 0;
168 + int f_igvm_f;
169 + size_t l_size;
170 +
171 + f_igvm_f = open(igvm_f, O_RDONLY | O_BINARY);
172 + g_assert_true(f_igvm_f);
173 + l_size = get_image_size(igvm_f);
174 + g_assert_true(l_size > 0);
175 + *addr = g_malloc0(l_size);
176 + g_assert_true(*addr);
177 +
178 + while (l < l_size) {
179 + actsize = read(f_igvm_f, *addr + l, 1);
180 + if (actsize < 0) {
181 + break;
182 + }
183 + l += actsize;
184 + }
185 +
186 + close(f_igvm_f);
187 + *size = l_size;
188 + return actsize < 0 ? -1 : l;
189 +}
190 +
191 +static guint32 match_string(char *serial_f, const char *exp_out)
192 +{
193 + GError *error = NULL;
194 + g_autofree gchar *f_contents = NULL;
195 + g_autofree GRegex *regex = NULL;
196 + g_autofree GMatchInfo *match_info = NULL;
197 + gsize len;
198 + guint32 count = 0;
199 + gboolean ret;
200 +
201 + ret = g_file_get_contents(serial_f, &f_contents, &len, &error);
202 + g_assert(ret);
203 + g_assert_no_error(error);
204 +
205 + regex = g_regex_new(exp_out, G_REGEX_CASELESS, 0, &error);
206 + g_assert_no_error(error);
207 +
208 + ret = g_regex_match_full(regex, f_contents, -1, 0, 0, &match_info, &error);
209 + g_assert_no_error(error);
210 +
211 + while (g_match_info_matches(match_info)) {
212 + gchar *word = g_match_info_fetch(match_info, 0);
213 + g_free(word);
214 + g_match_info_next(match_info, &error);
215 + count++;
216 + }
217 + g_regex_unref(regex);
218 + return count;
219 +}
220 +
221 +static int wait_for_match(char *serial_f,
222 + const char *exp_out, int64_t timeout_s,
223 + guint32 count)
224 +{
225 + time_t start, delta;
226 + int ret = -1;
227 +
228 + start = time(NULL);
229 + while (1) {
230 + if (match_string(serial_f, exp_out) == count) {
231 + ret = 0;
232 + break;
233 + }
234 +
235 + delta = time(NULL) - start;
236 + if (delta >= timeout_s) {
237 + fprintf(stderr, "timed out waiting to read serial output\n");
238 + break;
239 + }
240 +
241 + /* wait 20 ms before trying again */
242 + if (false) {
243 + fprintf(stderr,
244 + "sleeping 20 ms before checking serial output again.\n");
245 + }
246 + g_usleep(20000);
247 + }
248 + return ret;
249 +}
250 +
251 +static void set_test_params(const char **igvm_f, const char **igvm_init,
252 + const char **snp, const char **cgs)
253 +{
254 + if (confidential) {
255 + *snp = "-object \'{\"qom-type\":\"sev-snp-guest\",\"id\":\"lsec0\","
256 + "\"cbitpos\":51,\"reduced-phys-bits\":1,\"policy\":196608}\'";
257 + *cgs = "confidential-guest-support=lsec0";
258 + /*
259 + * The following two IGVM files can be built from the source
260 + * present in https://gitlab.com/anisinha/virt-firmware-rs .
261 + * Typing 'make' from the top of this repository will build the
262 + * IGVM files for both confidential and
263 + * non-confidential tests. The IGVM files for the non-coco
264 + * case has been checked-in into the QEMU repository for
265 + * convenience and easy CI pipeline testing.
266 + */
267 + *igvm_f = "tests/data/igvm/snptest.igvm"; /* prints 'hello world' */
268 + *igvm_init = "tests/data/igvm/snptest-nohello.igvm";
269 + } else {
270 + *igvm_f = "tests/data/igvm/hello.igvm";
271 + *igvm_init = "tests/data/igvm/qemuinit.igvm";
272 + *snp = "";
273 + *cgs = "";
274 + }
275 +
276 + return;
277 +}
278 +
279 +static void set_expected_out(const char **exp_out, const char **exp_out2,
280 + const char **exp_out3)
281 +{
282 + *exp_out = "Hello world!";
283 + *exp_out2 = "Test succeeded!";
284 + *exp_out3 = "boot process complete with initial igvm";
285 +
286 + return;
287 +}
288 +
289 +static QOSState *set_qemu_args(const char *cgs, const char *tp, char *serialf,
290 + const char *igvm_init, const char *snp)
291 +{
292 + QOSState *qs;
293 +
294 + if (tp) {
295 + qs = qtest_pc_boot("-machine q35,igvm-cfg=igvm0,%s -m 1G -accel kvm "
296 + "-device vm-launch-update %s "
297 + "-chardev file,id=serial0,path=%s "
298 + "-serial chardev:serial0 "
299 + "-object igvm-cfg,id=igvm0,file=%s %s",
300 + cgs, tp, serialf, igvm_init, snp);
301 + } else {
302 + qs = qtest_pc_boot("-machine q35,igvm-cfg=igvm0,%s -m 1G -accel kvm "
303 + "-device vm-launch-update "
304 + "-chardev file,id=serial0,path=%s "
305 + "-serial chardev:serial0 "
306 + "-object igvm-cfg,id=igvm0,file=%s %s",
307 + cgs, serialf, igvm_init, snp);
308 + }
309 +
310 + return qs;
311 +}
312 +
313 +static void test_load_igvm(void)
314 +{
315 + const char *igvm_f;
316 + const char *igvm_init;
317 + int ser_fd;
318 + g_autofree void *igvm_blob = NULL;
319 + g_autofree char *serialtmp = NULL;
320 + const char *exp_out;
321 + const char *exp_out2;
322 + const char *exp_out3;
323 + const char *tracepoints = "--trace memory_region_finalize "
324 + "--trace qigvm_cleanup_memory -D /tmp/qemu-debug.log ";
325 + const char *snp, *cgs;
326 + uint64_t gaddr;
327 + size_t igvm_sz;
328 + size_t filesize;
329 + QFWCFG *fw_cfg;
330 + QOSState *qs;
331 + VMLaunchUpdate launch_update;
332 +
333 + if (!trace) {
334 + tracepoints = "";
335 + }
336 +
337 + if (!qtest_has_machine("q35")) {
338 + g_test_skip("q35 machine not available");
339 + return;
340 + }
341 +
342 + if (!qtest_has_accel("kvm")) {
343 + g_test_skip("No KVM accelerator available");
344 + return;
345 + }
346 +
347 + if (!qtest_has_device("vm-launch-update")) {
348 + g_test_skip("Device vm-launch-update is not available");
349 + return;
350 + }
351 +
352 + set_test_params(&igvm_f, &igvm_init, &snp, &cgs);
353 + set_expected_out(&exp_out, &exp_out2, &exp_out3);
354 +
355 + if (!g_file_test(igvm_f, G_FILE_TEST_EXISTS) ||
356 + !g_file_test(igvm_init, G_FILE_TEST_EXISTS)) {
357 + g_test_skip("igvm file bundle(s) does not exist!");
358 + return;
359 + }
360 +
361 + ser_fd = g_file_open_tmp("launchupdate-qtest-serial-sXXXXXX",
362 + &serialtmp, NULL);
363 + g_assert_true(ser_fd != -1);
364 +
365 + if (debug) {
366 + fprintf(stderr, "serial console file is %s\n", serialtmp);
367 + }
368 +
369 + qs = set_qemu_args(cgs, tracepoints, serialtmp, igvm_init, snp);
370 +
371 + fw_cfg = pc_fw_cfg_init(qs->qts);
372 +
373 + if (debug) {
374 + fprintf(stderr, "target endianness: %s\n",
375 + qtest_big_endian(qs->qts) ? "big" : "little");
376 + }
377 +
378 + /* exp_out3 should be printed once from initial boot */
379 + g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 1) == 0);
380 +
381 + if (debug) {
382 + fprintf(stderr, "initially booted with host igvm\n");
383 + }
384 +
385 + g_assert_true(load_image(igvm_f, &igvm_blob, &igvm_sz) == igvm_sz);
386 +
387 + /* create a data buffer in guest memory */
388 + gaddr = guest_alloc(&qs->alloc, igvm_sz);
389 +
390 + if (debug) {
391 + fprintf(stderr, "guest paddr: %"PRIx64 " igvm size: %lu\n",
392 + gaddr, igvm_sz);
393 + }
394 +
395 + if (debug) {
396 + fprintf(stderr, "writing igvm file into the guest memory\n");
397 + }
398 +
399 + qtest_bufwrite(qs->qts, gaddr, igvm_blob, igvm_sz);
400 +
401 + if (debug) {
402 + fprintf(stderr,
403 + "tell hypervisor where igvm is loaded in guest memory\n");
404 + }
405 +
406 + /* now tell hypervisor where we loaded the bios */
407 + memset(&launch_update, 0, sizeof(launch_update));
408 + launch_update.fw_image_size = cpu_to_le64(igvm_sz);
409 + launch_update.fw_image_addr = cpu_to_le64(gaddr);
410 + launch_update.control |= VM_LAUNCHUPDATE_FORMAT_IGVM;
411 +
412 + filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
413 + &launch_update, sizeof(launch_update));
414 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
415 +
416 + if (debug) {
417 + fprintf(stderr, "resetting the virtual machine now\n");
418 + }
419 +
420 + qtest_system_reset(qs->qts);
421 +
422 + /* expected string should be printed on the console */
423 + g_assert_true(wait_for_match(serialtmp, exp_out, WAIT_SEC, 1) == 0);
424 + g_assert_true(wait_for_match(serialtmp, exp_out2, WAIT_SEC, 1) == 0);
425 +
426 + if (debug) {
427 + fprintf(stderr, "hello world found on console\n");
428 + }
429 +
430 + /* check if VM_LAUNCHUPDATE_CTL_HOST_IGVM function works */
431 +
432 + /* set only VM_LAUNCHUPDATE_CTL_HOST_IGVM control without IGVM bundle */
433 + memset(&launch_update, 0, sizeof(launch_update));
434 + launch_update.control |= VM_LAUNCHUPDATE_CTL_HOST_IGVM;
435 +
436 + filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
437 + &launch_update, sizeof(launch_update));
438 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
439 +
440 + /* now reset the guest */
441 + if (debug) {
442 + fprintf(stderr,
443 + "resetting again in order to restore host provided IGVM\n");
444 + }
445 + qtest_system_reset(qs->qts);
446 +
447 + /*
448 + * exp_out3 should be printed twice, once from initial boot,
449 + * once from restoring host igvm.
450 + */
451 + g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 2) == 0);
452 +
453 + if (debug) {
454 + fprintf(stderr, "booted with host igvm again\n");
455 + }
456 +
457 + close(ser_fd);
458 + guest_free(&qs->alloc, gaddr);
459 + pc_fw_cfg_uninit(fw_cfg);
460 + /* qtest_quit() kils QEMU, first by sending SIGTERM, then SIGKILL */
461 + qtest_quit(qs->qts);
462 +}
463 +
464 +static void test_set_ctrl_once_and_reset_to_host_igvm(void)
465 +{
466 + const char *igvm_f;
467 + const char *igvm_init;
468 + int ser_fd;
469 + g_autofree void *igvm_blob = NULL;
470 + g_autofree char *serialtmp = NULL;
471 + const char *exp_out;
472 + const char *exp_out2;
473 + const char *exp_out3;
474 + const char *snp, *cgs;
475 + uint64_t gaddr;
476 + size_t igvm_sz;
477 + size_t filesize;
478 + QFWCFG *fw_cfg;
479 + QOSState *qs;
480 + VMLaunchUpdate launch_update;
481 +
482 + if (!qtest_has_machine("q35")) {
483 + g_test_skip("q35 machine not available");
484 + return;
485 + }
486 +
487 + if (!qtest_has_accel("kvm")) {
488 + g_test_skip("No KVM accelerator available");
489 + return;
490 + }
491 +
492 + if (!qtest_has_device("vm-launch-update")) {
493 + g_test_skip("Device vm-launch-update is not available");
494 + return;
495 + }
496 +
497 + set_test_params(&igvm_f, &igvm_init, &snp, &cgs);
498 + set_expected_out(&exp_out, &exp_out2, &exp_out3);
499 +
500 + if (!g_file_test(igvm_f, G_FILE_TEST_EXISTS) ||
501 + !g_file_test(igvm_init, G_FILE_TEST_EXISTS)) {
502 + g_test_skip("igvm file bundle(s) does not exist!");
503 + return;
504 + }
505 +
506 + ser_fd = g_file_open_tmp("launchupdate-qtest-serial-sXXXXXX",
507 + &serialtmp, NULL);
508 + g_assert_true(ser_fd != -1);
509 +
510 + if (debug) {
511 + fprintf(stderr, "serial console file is %s\n", serialtmp);
512 + }
513 +
514 + qs = set_qemu_args(cgs, NULL, serialtmp, igvm_init, snp);
515 +
516 + fw_cfg = pc_fw_cfg_init(qs->qts);
517 +
518 + g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 1) == 0);
519 +
520 + if (debug) {
521 + fprintf(stderr, "initially booted with host igvm\n");
522 + }
523 +
524 + g_assert_true(load_image(igvm_f, &igvm_blob, &igvm_sz) == igvm_sz);
525 +
526 + /* create a data buffer in guest memory */
527 + gaddr = guest_alloc(&qs->alloc, igvm_sz);
528 +
529 + if (debug) {
530 + fprintf(stderr, "guest paddr: %"PRIx64 " igvm size: %lu\n",
531 + gaddr, igvm_sz);
532 + }
533 +
534 + if (debug) {
535 + fprintf(stderr, "writing igvm file into the guest memory\n");
536 + }
537 +
538 + qtest_bufwrite(qs->qts, gaddr, igvm_blob, igvm_sz);
539 +
540 + if (debug) {
541 + fprintf(stderr,
542 + "tell hypervisor where igvm is loaded in guest memory\n");
543 + }
544 +
545 + /* now tell hypervisor where we loaded the bios */
546 + memset(&launch_update, 0, sizeof(launch_update));
547 + launch_update.fw_image_size = cpu_to_le64(igvm_sz);
548 + launch_update.fw_image_addr = cpu_to_le64(gaddr);
549 +
550 + /* set both host ctrl and format_igvm ctrl once */
551 + launch_update.control |= VM_LAUNCHUPDATE_FORMAT_IGVM;
552 + launch_update.control |= VM_LAUNCHUPDATE_CTL_HOST_IGVM;
553 +
554 + filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
555 + &launch_update, sizeof(launch_update));
556 + g_assert_cmpint(filesize, ==, sizeof(launch_update));
557 +
558 + if (debug) {
559 + fprintf(stderr, "resetting the virtual machine. This should load "
560 + "user provided igvm.\n");
561 + }
562 +
563 + qtest_system_reset(qs->qts);
564 +
565 + /* expected string should be printed on the console */
566 + g_assert_true(wait_for_match(serialtmp, exp_out, WAIT_SEC, 1) == 0);
567 + g_assert_true(wait_for_match(serialtmp, exp_out2, WAIT_SEC, 1) == 0);
568 +
569 + if (debug) {
570 + fprintf(stderr, "hello world found on console\n");
571 + fprintf(stderr, "Now resetting again in order to reset to host igvm\n");
572 + }
573 +
574 + qtest_system_reset(qs->qts);
575 +
576 + /*
577 + * exp_out3 should be printed twice, once from initial boot,
578 + * once from restoring host igvm.
579 + */
580 + g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 2) == 0);
581 +
582 + if (debug) {
583 + fprintf(stderr, "booted with host igvm\n");
584 + }
585 +
586 + close(ser_fd);
587 + guest_free(&qs->alloc, gaddr);
588 + pc_fw_cfg_uninit(fw_cfg);
589 + /* qtest_quit() kils QEMU, first by sending SIGTERM, then SIGKILL */
590 + qtest_quit(qs->qts);
591 +}
592 +
593 +int main(int argc, char **argv)
594 +{
595 + const char *arch = qtest_get_arch();
596 +
597 + g_test_init(&argc, &argv, NULL);
598 +
599 + if (strcmp(arch, "x86_64")) {
600 + g_test_skip("vmlaunchupdate tests are only available on x86_64\n");
601 + return 0;
602 + }
603 +
604 + g_test_add_func("/vm-launch-update/cap", test_vm_launch_update_capability);
605 + g_test_add_func("/vm-launch-update/disabled",
606 + test_vm_launch_update_disable);
607 +
608 + g_test_add_func("/vm-launch-update/errorcheck", check_error);
609 + g_test_add_func("/vm-launch-update/load_igvm",
610 + test_load_igvm);
611 + g_test_add_func("/vm-launch-update/ctrl_set_once",
612 + test_set_ctrl_once_and_reset_to_host_igvm);
613 +
614 + if (getenv("LAUNCHUPDATE_DEBUG")) {
615 + debug = true;
616 + }
617 + if (getenv("LAUNCHUPDATE_TRACE")) {
618 + trace = true;
619 + }
620 + if (getenv("COCO")) {
621 + confidential = true;
622 + }
623 +
624 + return g_test_run();
625 +}
tests/qtest/meson.build
+2
@@ -61,6 +61,8 @@ qtests_i386 = \
61 (config_all_devices.has_key('CONFIG_Q35') ? ['e820-test'] : []) + \
62 (config_all_devices.has_key('CONFIG_FW_CFG_DMA') ? ['vmcoreinfo-test'] : []) + \
63 (config_all_devices.has_key('CONFIG_Q35') ? ['dump-test'] : []) + \
64 + (igvm.found() and
65 + config_all_devices.has_key('CONFIG_FW_CFG_DMA') ? ['launchupdate-test'] : []) + \
66 (config_all_devices.has_key('CONFIG_I440FX') ? ['i440fx-test'] : []) + \
67 (config_all_devices.has_key('CONFIG_I440FX') ? ['ide-test'] : []) + \
68 (config_all_devices.has_key('CONFIG_I440FX') ? ['numa-test'] : []) + \