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'] : []) + \