1
+/*
2
+ * Guest driven VM launch component update (using IGVM) device
3
+ * For details and specification, please look at docs/specs/vmlaunchupdate.rst.
4
+ *
5
+ * Copyright (C) 2026 Red Hat, Inc.
6
+ *
7
+ * Authors: Ani Sinha <anisinha@redhat.com>
8
+ *
9
+ * SPDX-License-Identifier: GPL-2.0-or-later
10
+ */
11
+
12
+#include "qemu/osdep.h"
13
+#include "qapi/error.h"
14
+#include "qemu/module.h"
15
+#include "system/physmem.h"
16
+#include "system/reset.h"
17
+#include "qemu/target-info-qapi.h"
18
+#include "hw/nvram/fw_cfg.h"
19
+#include "hw/core/qdev-properties.h"
20
+#include "hw/i386/pc.h"
21
+#include "exec/cpu-common.h"
22
+#include "hw/misc/vmlaunchupdate.h"
23
+#include "system/igvm.h"
24
+#include "system/igvm-internal.h"
25
+#include "qemu/error-report.h"
26
+#include "trace.h"
27
+
28
+/* returns NULL unless there is exactly one device */
29
+static VMLaunchUpdateState *vm_launchupdate_find(void)
30
+{
31
+ Object *o = object_resolve_path_type("", TYPE_VMLAUNCHUPDATE, NULL);
32
+
33
+ return o ? VMLAUNCHUPDATE(o) : NULL;
34
+}
35
+
36
+static bool vmlaunchupdate_supported(void)
37
+{
38
+ return target_arch() == SYS_EMU_TARGET_X86_64;
39
+}
40
+
41
+static void init_vm_launch_update(VMLaunchUpdateState *s)
42
+{
43
+ s->launch_update.capabilities = VM_LAUNCHUPDATE_FORMAT_IGVM;
44
+ s->launch_update.control = 0;
45
+
46
+ if (s->disabled) {
47
+ s->launch_update.control |= VM_LAUNCHUPDATE_CTL_DISABLE;
48
+ }
49
+
50
+ s->launch_update.version = VM_LAUNCHUPDATE_VERSION;
51
+ return;
52
+}
53
+
54
+static void clear_init_vm_launch_update(VMLaunchUpdateState *s)
55
+{
56
+ memset(&s->launch_update, 0, sizeof(s->launch_update));
57
+ init_vm_launch_update(s);
58
+}
59
+
60
+static bool no_igvmcfg(X86MachineState *x86m)
61
+{
62
+ IgvmCfg *igvmc;
63
+
64
+ if (!x86m) {
65
+ return true;
66
+ }
67
+
68
+ igvmc = x86m->igvm;
69
+
70
+ if (!igvmc) {
71
+ /* The VM was not started with an IGVM, bail */
72
+ info_report("guest was not initially started with IGVM, "
73
+ "not changing launch state.");
74
+ return true;
75
+ }
76
+ return false;
77
+}
78
+
79
+static int process_x86_igvm(VMLaunchUpdateState *s,
80
+ uint64_t fw_image_addr, uint64_t fw_image_size)
81
+{
82
+ X86MachineState *x86machine = X86_MACHINE(qdev_get_machine());
83
+ IgvmCfg *igvmc = x86machine->igvm;
84
+ IgvmHandle igvm;
85
+ void *image_addr_ptr;
86
+ hwaddr len;
87
+
88
+ if (no_igvmcfg(x86machine)) {
89
+ return -2;
90
+ }
91
+
92
+ if (!fw_image_addr || !fw_image_size) {
93
+ return -1;
94
+ }
95
+
96
+ len = (hwaddr) fw_image_size;
97
+ image_addr_ptr = physical_memory_map((hwaddr) fw_image_addr,
98
+ (hwaddr *) &len, 0);
99
+
100
+ if (!image_addr_ptr || (len < fw_image_size)) {
101
+ warn_report("vmlaunchupdate: Invalid guest addresses.");
102
+ goto err;
103
+ }
104
+
105
+ igvm = igvm_new_from_binary(image_addr_ptr, fw_image_size);
106
+ if (igvm < 0) {
107
+ warn_report("vmlaunchupdate: Unable to parse IGVM file %"
108
+ PRIx64 ": %" PRIx64, fw_image_addr, fw_image_size);
109
+ goto err;
110
+ }
111
+
112
+ /* free previous file context */
113
+ if (igvmc->file >= 0) {
114
+ igvm_free(igvmc->file);
115
+ }
116
+ /* set new context */
117
+ igvmc->file = igvm;
118
+
119
+ physical_memory_unmap(image_addr_ptr, len, 0, 0);
120
+ info_report("vmlaunchupdate: new IGVM context set.");
121
+
122
+ return 0;
123
+ err:
124
+ if (image_addr_ptr) {
125
+ physical_memory_unmap(image_addr_ptr, len, 0, 0);
126
+ }
127
+ return -1;
128
+}
129
+
130
+static void restore_host_x86_igvm(void)
131
+{
132
+ X86MachineState *x86machine = X86_MACHINE(qdev_get_machine());
133
+ IgvmCfg *igvmc = x86machine->igvm;
134
+ Error *errp = NULL;
135
+
136
+ if (no_igvmcfg(x86machine)) {
137
+ return;
138
+ }
139
+
140
+ /* free previous file context */
141
+ if (igvmc->file >= 0) {
142
+ igvm_free(igvmc->file);
143
+ }
144
+
145
+ info_report("restoring original host IGVM: %s", igvmc->filename);
146
+ igvmc->file = qigvm_file_init(igvmc->filename, &errp);
147
+ assert(!errp);
148
+
149
+ info_report("vmlaunchupdate: host IGVM context set.");
150
+
151
+ trace_restore_host_x86_igvm();
152
+
153
+ return;
154
+}
155
+
156
+static bool fw_address_cleared(VMLaunchUpdateState *s)
157
+{
158
+ return !s->launch_update.fw_image_addr &&
159
+ !s->launch_update.fw_image_size;
160
+}
161
+
162
+static void launch_update_write(void *dev, off_t offset, size_t len)
163
+{
164
+ VMLaunchUpdateState *s = VMLAUNCHUPDATE(dev);
165
+ uint64_t addr;
166
+ uint64_t size;
167
+ int rc;
168
+
169
+ s->launch_update.status = VM_LAUNCHUPDATE_SUCCESS;
170
+
171
+ if (s->disabled) {
172
+ goto end;
173
+ }
174
+
175
+ if (s->launch_update.control & VM_LAUNCHUPDATE_CTL_DISABLE) {
176
+ s->disabled = true;
177
+ goto end;
178
+ }
179
+
180
+ if (fw_address_cleared(s) &&
181
+ (s->launch_update.control & VM_LAUNCHUPDATE_CTL_HOST_IGVM)) {
182
+ /* restore host IGVM on immediate next reset */
183
+ s->host_igvm_on_reset = true;
184
+ goto end;
185
+ }
186
+
187
+ if (!(s->launch_update.control & VM_LAUNCHUPDATE_FORMAT_IGVM) &&
188
+ !fw_address_cleared(s)) {
189
+ /* at least one address provided but the format is not IGVM */
190
+ s->launch_update.status = VM_LAUNCHUPDATE_LOAD_FAIL;
191
+ goto end;
192
+ }
193
+
194
+ /* process guest provided IGVM image */
195
+ if (s->launch_update.control & VM_LAUNCHUPDATE_FORMAT_IGVM) {
196
+ if (target_arch() == SYS_EMU_TARGET_X86_64) {
197
+ addr = le64_to_cpu(s->launch_update.fw_image_addr);
198
+ size = le64_to_cpu(s->launch_update.fw_image_size);
199
+ rc = process_x86_igvm(s, addr, size);
200
+ if (rc < 0) {
201
+ switch (rc) {
202
+ case -2:
203
+ s->launch_update.status = VM_LAUNCHUPDATE_NOT_IGVM_INIT;
204
+ break;
205
+ default:
206
+ s->launch_update.status = VM_LAUNCHUPDATE_LOAD_FAIL;
207
+ }
208
+ goto end;
209
+ }
210
+ }
211
+ /* process other machines here when support is added */
212
+ }
213
+
214
+ /* clear the addresses */
215
+ s->launch_update.fw_image_addr = 0x0;
216
+ s->launch_update.fw_image_size = 0x0;
217
+
218
+ end:
219
+ trace_launch_update_write();
220
+ return;
221
+}
222
+
223
+static void launch_update_select(void *dev)
224
+{
225
+ VMLaunchUpdateState *s = VMLAUNCHUPDATE(dev);
226
+ init_vm_launch_update(s);
227
+}
228
+
229
+static void vmlaunch_reset_enter(Object *obj, ResetType type)
230
+{
231
+ VMLaunchUpdateState *s = VMLAUNCHUPDATE(obj);
232
+
233
+ if (target_arch() != SYS_EMU_TARGET_X86_64) {
234
+ return;
235
+ }
236
+
237
+ if (s->host_igvm_on_reset) {
238
+ restore_host_x86_igvm();
239
+ s->host_igvm_on_reset = false;
240
+ /* restoring host igvm enables the interface again */
241
+ s->disabled = false;
242
+ /* clear the host IGVM ctrl bit */
243
+ s->launch_update.control &= ~VM_LAUNCHUPDATE_CTL_HOST_IGVM;
244
+ }
245
+
246
+ if ((s->launch_update.control & VM_LAUNCHUPDATE_CTL_HOST_IGVM) &&
247
+ (s->launch_update.status == VM_LAUNCHUPDATE_SUCCESS)) {
248
+ info_report("vmlaunchupdate: next reset will use host igvm");
249
+ s->host_igvm_on_reset = true;
250
+ }
251
+
252
+ trace_vmlaunch_reset_enter();
253
+}
254
+
255
+static ResettableState *vmlaunch_reset_state(Object *obj)
256
+{
257
+ VMLaunchUpdateState *s = VMLAUNCHUPDATE(obj);
258
+
259
+ return &s->reset_state;
260
+}
261
+
262
+static void vm_launchupdate_realize(DeviceState *dev, Error **errp)
263
+{
264
+ VMLaunchUpdateState *s = VMLAUNCHUPDATE(dev);
265
+ FWCfgState *fw_cfg = fw_cfg_find();
266
+
267
+ /* multiple devices are not supported */
268
+ if (!vm_launchupdate_find()) {
269
+ error_setg(errp, "at most one %s device is permitted",
270
+ TYPE_VMLAUNCHUPDATE);
271
+ return;
272
+ }
273
+
274
+ /* if current machine is not supported, do not initialize */
275
+ if (!vmlaunchupdate_supported()) {
276
+ error_setg(errp,
277
+ "This machine does not support vm-launch-update device");
278
+ return;
279
+ }
280
+
281
+ /* fw_cfg with DMA support is necessary to support this device */
282
+ if (!fw_cfg || !fw_cfg_dma_enabled(fw_cfg)) {
283
+ error_setg(errp, "%s device requires fw_cfg",
284
+ TYPE_VMLAUNCHUPDATE);
285
+ return;
286
+ }
287
+
288
+ fw_cfg_add_file_callback(fw_cfg, FILE_VMLAUNCHUPDATE,
289
+ launch_update_select, launch_update_write, s,
290
+ &s->launch_update,
291
+ sizeof(s->launch_update),
292
+ false);
293
+
294
+ clear_init_vm_launch_update(s);
295
+ /*
296
+ * This device requires to register a global reset because it is
297
+ * not plugged to a bus (which, as its QOM parent, would reset it).
298
+ */
299
+ qemu_register_resettable(OBJECT(s));
300
+}
301
+
302
+static void vm_launchupdate_finalize(Object *obj)
303
+{
304
+ qemu_unregister_resettable(obj);
305
+ trace_vm_launchupdate_finalize();
306
+}
307
+
308
+static void vmlaunchupdate_device_class_init(ObjectClass *klass,
309
+ const void *data)
310
+{
311
+ DeviceClass *dc = DEVICE_CLASS(klass);
312
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
313
+
314
+ /* we are not interested in migration - so no need to populate dc->vmsd */
315
+ dc->desc = "VM launch state update device";
316
+ dc->realize = vm_launchupdate_realize;
317
+ dc->hotpluggable = false;
318
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
319
+ rc->phases.enter = vmlaunch_reset_enter;
320
+ rc->get_state = vmlaunch_reset_state;
321
+}
322
+
323
+static const TypeInfo vmlaunchupdate_device_types[] = {
324
+ {
325
+ .name = TYPE_VMLAUNCHUPDATE,
326
+ .parent = TYPE_DEVICE,
327
+ .instance_size = sizeof(VMLaunchUpdateState),
328
+ .class_init = vmlaunchupdate_device_class_init,
329
+ .instance_finalize = vm_launchupdate_finalize,
330
+ },
331
+};
332
+
333
+DEFINE_TYPES(vmlaunchupdate_device_types)