Files
QEMU-S5L8950X/hw/misc/vmlaunchupdate.c
T
Yaya48 cf256aa081 Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git

Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
2026-08-31 02:15:30 +02:00

334 lines
9.1 KiB
C

/*
* Guest driven VM launch component update (using IGVM) device
* For details and specification, please look at docs/specs/vmlaunchupdate.rst.
*
* Copyright (C) 2026 Red Hat, Inc.
*
* Authors: Ani Sinha <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "system/physmem.h"
#include "system/reset.h"
#include "qemu/target-info-qapi.h"
#include "hw/nvram/fw_cfg.h"
#include "hw/core/qdev-properties.h"
#include "hw/i386/pc.h"
#include "exec/cpu-common.h"
#include "hw/misc/vmlaunchupdate.h"
#include "system/igvm.h"
#include "system/igvm-internal.h"
#include "qemu/error-report.h"
#include "trace.h"
/* returns NULL unless there is exactly one device */
static VMLaunchUpdateState *vm_launchupdate_find(void)
{
Object *o = object_resolve_path_type("", TYPE_VMLAUNCHUPDATE, NULL);
return o ? VMLAUNCHUPDATE(o) : NULL;
}
static bool vmlaunchupdate_supported(void)
{
return target_arch() == SYS_EMU_TARGET_X86_64;
}
static void init_vm_launch_update(VMLaunchUpdateState *s)
{
s->launch_update.capabilities = VM_LAUNCHUPDATE_FORMAT_IGVM;
s->launch_update.control = 0;
if (s->disabled) {
s->launch_update.control |= VM_LAUNCHUPDATE_CTL_DISABLE;
}
s->launch_update.version = VM_LAUNCHUPDATE_VERSION;
return;
}
static void clear_init_vm_launch_update(VMLaunchUpdateState *s)
{
memset(&s->launch_update, 0, sizeof(s->launch_update));
init_vm_launch_update(s);
}
static bool no_igvmcfg(X86MachineState *x86m)
{
IgvmCfg *igvmc;
if (!x86m) {
return true;
}
igvmc = x86m->igvm;
if (!igvmc) {
/* The VM was not started with an IGVM, bail */
info_report("guest was not initially started with IGVM, "
"not changing launch state.");
return true;
}
return false;
}
static int process_x86_igvm(VMLaunchUpdateState *s,
uint64_t fw_image_addr, uint64_t fw_image_size)
{
X86MachineState *x86machine = X86_MACHINE(qdev_get_machine());
IgvmCfg *igvmc = x86machine->igvm;
IgvmHandle igvm;
void *image_addr_ptr;
hwaddr len;
if (no_igvmcfg(x86machine)) {
return -2;
}
if (!fw_image_addr || !fw_image_size) {
return -1;
}
len = (hwaddr) fw_image_size;
image_addr_ptr = physical_memory_map((hwaddr) fw_image_addr,
(hwaddr *) &len, 0);
if (!image_addr_ptr || (len < fw_image_size)) {
warn_report("vmlaunchupdate: Invalid guest addresses.");
goto err;
}
igvm = igvm_new_from_binary(image_addr_ptr, fw_image_size);
if (igvm < 0) {
warn_report("vmlaunchupdate: Unable to parse IGVM file %"
PRIx64 ": %" PRIx64, fw_image_addr, fw_image_size);
goto err;
}
/* free previous file context */
if (igvmc->file >= 0) {
igvm_free(igvmc->file);
}
/* set new context */
igvmc->file = igvm;
physical_memory_unmap(image_addr_ptr, len, 0, 0);
info_report("vmlaunchupdate: new IGVM context set.");
return 0;
err:
if (image_addr_ptr) {
physical_memory_unmap(image_addr_ptr, len, 0, 0);
}
return -1;
}
static void restore_host_x86_igvm(void)
{
X86MachineState *x86machine = X86_MACHINE(qdev_get_machine());
IgvmCfg *igvmc = x86machine->igvm;
Error *errp = NULL;
if (no_igvmcfg(x86machine)) {
return;
}
/* free previous file context */
if (igvmc->file >= 0) {
igvm_free(igvmc->file);
}
info_report("restoring original host IGVM: %s", igvmc->filename);
igvmc->file = qigvm_file_init(igvmc->filename, &errp);
assert(!errp);
info_report("vmlaunchupdate: host IGVM context set.");
trace_restore_host_x86_igvm();
return;
}
static bool fw_address_cleared(VMLaunchUpdateState *s)
{
return !s->launch_update.fw_image_addr &&
!s->launch_update.fw_image_size;
}
static void launch_update_write(void *dev, off_t offset, size_t len)
{
VMLaunchUpdateState *s = VMLAUNCHUPDATE(dev);
uint64_t addr;
uint64_t size;
int rc;
s->launch_update.status = VM_LAUNCHUPDATE_SUCCESS;
if (s->disabled) {
goto end;
}
if (s->launch_update.control & VM_LAUNCHUPDATE_CTL_DISABLE) {
s->disabled = true;
goto end;
}
if (fw_address_cleared(s) &&
(s->launch_update.control & VM_LAUNCHUPDATE_CTL_HOST_IGVM)) {
/* restore host IGVM on immediate next reset */
s->host_igvm_on_reset = true;
goto end;
}
if (!(s->launch_update.control & VM_LAUNCHUPDATE_FORMAT_IGVM) &&
!fw_address_cleared(s)) {
/* at least one address provided but the format is not IGVM */
s->launch_update.status = VM_LAUNCHUPDATE_LOAD_FAIL;
goto end;
}
/* process guest provided IGVM image */
if (s->launch_update.control & VM_LAUNCHUPDATE_FORMAT_IGVM) {
if (target_arch() == SYS_EMU_TARGET_X86_64) {
addr = le64_to_cpu(s->launch_update.fw_image_addr);
size = le64_to_cpu(s->launch_update.fw_image_size);
rc = process_x86_igvm(s, addr, size);
if (rc < 0) {
switch (rc) {
case -2:
s->launch_update.status = VM_LAUNCHUPDATE_NOT_IGVM_INIT;
break;
default:
s->launch_update.status = VM_LAUNCHUPDATE_LOAD_FAIL;
}
goto end;
}
}
/* process other machines here when support is added */
}
/* clear the addresses */
s->launch_update.fw_image_addr = 0x0;
s->launch_update.fw_image_size = 0x0;
end:
trace_launch_update_write();
return;
}
static void launch_update_select(void *dev)
{
VMLaunchUpdateState *s = VMLAUNCHUPDATE(dev);
init_vm_launch_update(s);
}
static void vmlaunch_reset_enter(Object *obj, ResetType type)
{
VMLaunchUpdateState *s = VMLAUNCHUPDATE(obj);
if (target_arch() != SYS_EMU_TARGET_X86_64) {
return;
}
if (s->host_igvm_on_reset) {
restore_host_x86_igvm();
s->host_igvm_on_reset = false;
/* restoring host igvm enables the interface again */
s->disabled = false;
/* clear the host IGVM ctrl bit */
s->launch_update.control &= ~VM_LAUNCHUPDATE_CTL_HOST_IGVM;
}
if ((s->launch_update.control & VM_LAUNCHUPDATE_CTL_HOST_IGVM) &&
(s->launch_update.status == VM_LAUNCHUPDATE_SUCCESS)) {
info_report("vmlaunchupdate: next reset will use host igvm");
s->host_igvm_on_reset = true;
}
trace_vmlaunch_reset_enter();
}
static ResettableState *vmlaunch_reset_state(Object *obj)
{
VMLaunchUpdateState *s = VMLAUNCHUPDATE(obj);
return &s->reset_state;
}
static void vm_launchupdate_realize(DeviceState *dev, Error **errp)
{
VMLaunchUpdateState *s = VMLAUNCHUPDATE(dev);
FWCfgState *fw_cfg = fw_cfg_find();
/* multiple devices are not supported */
if (!vm_launchupdate_find()) {
error_setg(errp, "at most one %s device is permitted",
TYPE_VMLAUNCHUPDATE);
return;
}
/* if current machine is not supported, do not initialize */
if (!vmlaunchupdate_supported()) {
error_setg(errp,
"This machine does not support vm-launch-update device");
return;
}
/* fw_cfg with DMA support is necessary to support this device */
if (!fw_cfg || !fw_cfg_dma_enabled(fw_cfg)) {
error_setg(errp, "%s device requires fw_cfg",
TYPE_VMLAUNCHUPDATE);
return;
}
fw_cfg_add_file_callback(fw_cfg, FILE_VMLAUNCHUPDATE,
launch_update_select, launch_update_write, s,
&s->launch_update,
sizeof(s->launch_update),
false);
clear_init_vm_launch_update(s);
/*
* This device requires to register a global reset because it is
* not plugged to a bus (which, as its QOM parent, would reset it).
*/
qemu_register_resettable(OBJECT(s));
}
static void vm_launchupdate_finalize(Object *obj)
{
qemu_unregister_resettable(obj);
trace_vm_launchupdate_finalize();
}
static void vmlaunchupdate_device_class_init(ObjectClass *klass,
const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
ResettableClass *rc = RESETTABLE_CLASS(klass);
/* we are not interested in migration - so no need to populate dc->vmsd */
dc->desc = "VM launch state update device";
dc->realize = vm_launchupdate_realize;
dc->hotpluggable = false;
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
rc->phases.enter = vmlaunch_reset_enter;
rc->get_state = vmlaunch_reset_state;
}
static const TypeInfo vmlaunchupdate_device_types[] = {
{
.name = TYPE_VMLAUNCHUPDATE,
.parent = TYPE_DEVICE,
.instance_size = sizeof(VMLaunchUpdateState),
.class_init = vmlaunchupdate_device_class_init,
.instance_finalize = vm_launchupdate_finalize,
},
};
DEFINE_TYPES(vmlaunchupdate_device_types)