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