mirror of
https://gitlab.com/qemu-project/qemu.git
synced 2026-08-26 22:23:12 -04:00
igvm + vmlaunchupdate: allow guests supply their own igvm firmware images.
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEoDKM/7k6F6eZAf59TLbY7tPocTgFAmqIeGcACgkQTLbY7tPo cTi6eA/+LTgcwLrRIRlZBJ1ExwYBAVeeI2AwfoVvL7LrNrVTeW5O3HBkkd5iW3u1 O5GGabV/8dQsrt3cTbQ6Tj1FuTaAFluNgykok4uCb9kavji/WQofkBgoek6PmDBi 4CPwseO12eh9i/OV1yyCRdKqBuUjwnk1LbJIaBRDhz+AOhvTrPZYtxZMC1XZUY/4 QPTHzo4lDyRphdq/puXe+Z4WPRMpt0tEUzSq1ev57W2Pwu3Rrf6BNM5X+uH8vTXQ 3OwzmpNDsJusMKpo0/+gc1pGuLrNSSVWfbrEzsKot6jUg3hfkqmonTEFT+ArZNSn cqBCulWVlopJttRcnbUUSdjTaoBHIfS2yX2w10K2eVX7s2DbXy49GERhlfC23BHi zMrBQ0JGU4njdZXobFnR8HtCqwAlaTgFHiTHIjk4+IZHRPHUCAOVAIb0rkCeFSX3 qgE1vHJhEBHLIsCv5F6Kb3aUbTiqWSzfFR67FRZctTzJp0nvJ06EiTlKURDk9CyG krInt79qeTHgNu8zNTDwoy8WbHlbDepv0aL/S+liHcE8Drvr5X0pWA0vMWE6mgvj Ibd8BEpw6VbLrGe/cz0eLRdu2/6tIDEKYmlAReuRX9ifP3kT41fT9aF/h9BOuJae zKWRSDgNfPB6MNf2r/6jvOHNCVaaG69jd0HJH/9cbzxHqinMe54= =9hRx -----END PGP SIGNATURE----- Merge tag 'igvm-20260821-pull-request' of https://gitlab.com/kraxel/qemu into staging igvm + vmlaunchupdate: allow guests supply their own igvm firmware images. # -----BEGIN PGP SIGNATURE----- # # iQIzBAABCgAdFiEEoDKM/7k6F6eZAf59TLbY7tPocTgFAmqIeGcACgkQTLbY7tPo # cTi6eA/+LTgcwLrRIRlZBJ1ExwYBAVeeI2AwfoVvL7LrNrVTeW5O3HBkkd5iW3u1 # O5GGabV/8dQsrt3cTbQ6Tj1FuTaAFluNgykok4uCb9kavji/WQofkBgoek6PmDBi # 4CPwseO12eh9i/OV1yyCRdKqBuUjwnk1LbJIaBRDhz+AOhvTrPZYtxZMC1XZUY/4 # QPTHzo4lDyRphdq/puXe+Z4WPRMpt0tEUzSq1ev57W2Pwu3Rrf6BNM5X+uH8vTXQ # 3OwzmpNDsJusMKpo0/+gc1pGuLrNSSVWfbrEzsKot6jUg3hfkqmonTEFT+ArZNSn # cqBCulWVlopJttRcnbUUSdjTaoBHIfS2yX2w10K2eVX7s2DbXy49GERhlfC23BHi # zMrBQ0JGU4njdZXobFnR8HtCqwAlaTgFHiTHIjk4+IZHRPHUCAOVAIb0rkCeFSX3 # qgE1vHJhEBHLIsCv5F6Kb3aUbTiqWSzfFR67FRZctTzJp0nvJ06EiTlKURDk9CyG # krInt79qeTHgNu8zNTDwoy8WbHlbDepv0aL/S+liHcE8Drvr5X0pWA0vMWE6mgvj # Ibd8BEpw6VbLrGe/cz0eLRdu2/6tIDEKYmlAReuRX9ifP3kT41fT9aF/h9BOuJae # zKWRSDgNfPB6MNf2r/6jvOHNCVaaG69jd0HJH/9cbzxHqinMe54= # =9hRx # -----END PGP SIGNATURE----- # gpg: Signature made Fri 21 Aug 2026 09:10:15 AM PDT # gpg: using RSA key A0328CFFB93A17A79901FE7D4CB6D8EED3E87138 # gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [unknown] # gpg: aka "Gerd Hoffmann <gerd@kraxel.org>" [unknown] # gpg: aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [unknown] # gpg: WARNING: This key is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: A032 8CFF B93A 17A7 9901 FE7D 4CB6 D8EE D3E8 7138 * tag 'igvm-20260821-pull-request' of https://gitlab.com/kraxel/qemu: Update MAINTAINERS Add functional and unit tests for the vm-launch-update device tests/qtest: Add small igvm files for testing purpose docs/spec: Add a specification document for vm-launch-update device hw/misc/vmlaunchupdate: Introduce hypervisor fw-cfg interface support hw/misc/vmlaunchupdate: add api header backends/igvm: add a tracepoint for qigvm_cleanup_memory system/memory: add a tracepoint for memory_region_finalize igvm: cleanup memory regions igvm: track memory regions igvm: store IgvmCfg pointer in QIgvm MAINTAINERS: elevating myself to be a maintainer for igvm Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
commit
eea8fe61b8
24 changed files with 1535 additions and 34 deletions
15
MAINTAINERS
15
MAINTAINERS
|
|
@ -2852,6 +2852,19 @@ F: include/hw/acpi/vmgenid.h
|
|||
F: docs/specs/vmgenid.rst
|
||||
F: tests/qtest/vmgenid-test.c
|
||||
|
||||
VM Launch Update
|
||||
M: Ani Sinha <anisinha@redhat.com>
|
||||
M: Gerd Hoffman <kraxel@redhat.com>
|
||||
S: Maintained
|
||||
F: hw/misc/vmlaunchupdate.c
|
||||
F: include/hw/misc/vmlaunchupdate.h
|
||||
F: include/standard-headers/misc/vmlaunchupdate.h
|
||||
F: docs/specs/vmlaunchupdate.rst
|
||||
F: tests/functional/aarch64/test_vm_launch_update_aarch.py
|
||||
F: tests/functional/x86_64/test_vm_launch_update.py
|
||||
F: tests/qtest/launchupdate-test.c
|
||||
F: tests/data/igvm/*
|
||||
|
||||
LED
|
||||
M: Philippe Mathieu-Daudé <philmd@mailo.com>
|
||||
S: Maintained
|
||||
|
|
@ -4119,7 +4132,7 @@ F: tests/uefi-test-tools/
|
|||
IGVM Firmware
|
||||
M: Gerd Hoffmann <kraxel@redhat.com>
|
||||
M: Stefano Garzarella <sgarzare@redhat.com>
|
||||
R: Ani Sinha <anisinha@redhat.com>
|
||||
M: Ani Sinha <anisinha@redhat.com>
|
||||
S: Maintained
|
||||
F: backends/igvm*.c
|
||||
F: docs/system/igvm.rst
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ static void igvm_reset_hold(Object *obj, ResetType type)
|
|||
|
||||
trace_igvm_reset_hold(type);
|
||||
|
||||
/* cleanup existing memory regions first */
|
||||
qigvm_cleanup_memory(igvm);
|
||||
qigvm_process_file(igvm, ms, false, &error_fatal);
|
||||
}
|
||||
|
||||
|
|
@ -65,6 +67,7 @@ static void igvm_complete(UserCreatable *uc, Error **errp)
|
|||
IgvmCfg *igvm = IGVM_CFG(uc);
|
||||
|
||||
igvm->file = qigvm_file_init(igvm->filename, errp);
|
||||
QTAILQ_INIT(&igvm->memory_regions);
|
||||
}
|
||||
|
||||
OBJECT_DEFINE_TYPE_WITH_INTERFACES(IgvmCfg, igvm_cfg, IGVM_CFG, OBJECT,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/target-info-qapi.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "system/igvm.h"
|
||||
#include "system/igvm-cfg.h"
|
||||
#include "system/igvm-internal.h"
|
||||
|
|
@ -178,7 +179,8 @@ static int qigvm_handler(QIgvm *ctx, IgvmVariableHeaderType raw_type,
|
|||
if (handlers[handler].type != type) {
|
||||
continue;
|
||||
}
|
||||
header_handle = igvm_get_header(ctx->file, handlers[handler].section,
|
||||
header_handle = igvm_get_header(ctx->cfg->file,
|
||||
handlers[handler].section,
|
||||
ctx->current_header_index);
|
||||
if (header_handle < 0) {
|
||||
error_setg(
|
||||
|
|
@ -187,7 +189,7 @@ static int qigvm_handler(QIgvm *ctx, IgvmVariableHeaderType raw_type,
|
|||
(int)header_handle);
|
||||
return -1;
|
||||
}
|
||||
header_data = igvm_get_buffer(ctx->file, header_handle);
|
||||
header_data = igvm_get_buffer(ctx->cfg->file, header_handle);
|
||||
if (header_data != NULL) {
|
||||
header_data += sizeof(IGVM_VHS_VARIABLE_HEADER);
|
||||
result = handlers[handler].handler(ctx, header_data, errp);
|
||||
|
|
@ -198,7 +200,7 @@ static int qigvm_handler(QIgvm *ctx, IgvmVariableHeaderType raw_type,
|
|||
header_handle, type);
|
||||
result = -1;
|
||||
}
|
||||
igvm_free_buffer(ctx->file, header_handle);
|
||||
igvm_free_buffer(ctx->cfg->file, header_handle);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -219,7 +221,7 @@ static void *qigvm_prepare_memory(QIgvm *ctx, uint64_t addr, uint64_t size,
|
|||
int region_identifier, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
MemoryRegion *igvm_pages = NULL;
|
||||
IgvmMemoryRegion *imr = NULL;
|
||||
Int128 gpa_region_size;
|
||||
MemoryRegionSection mrs =
|
||||
memory_region_find(get_system_memory(), addr, size);
|
||||
|
|
@ -253,23 +255,27 @@ static void *qigvm_prepare_memory(QIgvm *ctx, uint64_t addr, uint64_t size,
|
|||
*/
|
||||
g_autofree char *region_name =
|
||||
g_strdup_printf("igvm.%X", region_identifier);
|
||||
igvm_pages = g_new0(MemoryRegion, 1);
|
||||
imr = g_new0(IgvmMemoryRegion, 1);
|
||||
imr->mr = g_new0(MemoryRegion, 1);
|
||||
if (ctx->machine_state->cgs &&
|
||||
ctx->machine_state->cgs->require_guest_memfd) {
|
||||
if (!memory_region_init_ram_guest_memfd(igvm_pages, NULL,
|
||||
if (!memory_region_init_ram_guest_memfd(imr->mr, NULL,
|
||||
region_name, size, errp)) {
|
||||
g_free(igvm_pages);
|
||||
g_free(imr->mr);
|
||||
g_free(imr);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
if (!memory_region_init_ram(igvm_pages, NULL, region_name, size,
|
||||
if (!memory_region_init_ram(imr->mr, NULL, region_name, size,
|
||||
errp)) {
|
||||
g_free(igvm_pages);
|
||||
g_free(imr->mr);
|
||||
g_free(imr);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
memory_region_add_subregion(get_system_memory(), addr, igvm_pages);
|
||||
return memory_region_get_ram_ptr(igvm_pages);
|
||||
memory_region_add_subregion(get_system_memory(), addr, imr->mr);
|
||||
QTAILQ_INSERT_TAIL(&ctx->cfg->memory_regions, imr, next);
|
||||
return memory_region_get_ram_ptr(imr->mr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -344,7 +350,8 @@ static int qigvm_process_mem_region(QIgvm *ctx, unsigned start_index,
|
|||
|
||||
for (page_index = 0; page_index < page_count; page_index++) {
|
||||
data_handle = igvm_get_header_data(
|
||||
ctx->file, IGVM_HEADER_SECTION_DIRECTIVE, page_index + start_index);
|
||||
ctx->cfg->file, IGVM_HEADER_SECTION_DIRECTIVE,
|
||||
page_index + start_index);
|
||||
if (data_handle == IGVMAPI_NO_DATA) {
|
||||
/* No data indicates a zero page */
|
||||
memset(®ion[page_index * page_size], 0, page_size);
|
||||
|
|
@ -357,7 +364,7 @@ static int qigvm_process_mem_region(QIgvm *ctx, unsigned start_index,
|
|||
return -1;
|
||||
} else {
|
||||
zero = false;
|
||||
data_size = igvm_get_buffer_size(ctx->file, data_handle);
|
||||
data_size = igvm_get_buffer_size(ctx->cfg->file, data_handle);
|
||||
if (data_size < page_size) {
|
||||
memset(®ion[page_index * page_size], 0, page_size);
|
||||
} else if (data_size > page_size) {
|
||||
|
|
@ -367,14 +374,14 @@ static int qigvm_process_mem_region(QIgvm *ctx, unsigned start_index,
|
|||
page_index + start_index);
|
||||
return -1;
|
||||
}
|
||||
data = igvm_get_buffer(ctx->file, data_handle);
|
||||
data = igvm_get_buffer(ctx->cfg->file, data_handle);
|
||||
if (data == NULL) {
|
||||
error_setg(errp, "IGVM: No buffer for handle %d", data_handle);
|
||||
igvm_free_buffer(ctx->file, data_handle);
|
||||
igvm_free_buffer(ctx->cfg->file, data_handle);
|
||||
return -1;
|
||||
}
|
||||
memcpy(®ion[page_index * page_size], data, data_size);
|
||||
igvm_free_buffer(ctx->file, data_handle);
|
||||
igvm_free_buffer(ctx->cfg->file, data_handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -411,7 +418,8 @@ static int qigvm_process_mem_page(QIgvm *ctx,
|
|||
ctx->region_start = page_data->gpa;
|
||||
ctx->region_start_index = ctx->current_header_index;
|
||||
} else {
|
||||
if (!qigvm_page_attrs_equal(ctx->file, ctx->current_header_index,
|
||||
if (!qigvm_page_attrs_equal(ctx->cfg->file,
|
||||
ctx->current_header_index,
|
||||
page_data,
|
||||
&ctx->region_prev_page_data) ||
|
||||
((ctx->region_prev_page_data.gpa +
|
||||
|
|
@ -474,7 +482,8 @@ static int qigvm_directive_vp_context(QIgvm *ctx, const uint8_t *header_data,
|
|||
return 0;
|
||||
}
|
||||
|
||||
data_handle = igvm_get_header_data(ctx->file, IGVM_HEADER_SECTION_DIRECTIVE,
|
||||
data_handle = igvm_get_header_data(ctx->cfg->file,
|
||||
IGVM_HEADER_SECTION_DIRECTIVE,
|
||||
ctx->current_header_index);
|
||||
if (data_handle < 0) {
|
||||
error_setg(errp, "Invalid VP context in IGVM file. Error code: %X",
|
||||
|
|
@ -482,7 +491,7 @@ static int qigvm_directive_vp_context(QIgvm *ctx, const uint8_t *header_data,
|
|||
return -1;
|
||||
}
|
||||
|
||||
data = (uint8_t *)igvm_get_buffer(ctx->file, data_handle);
|
||||
data = (uint8_t *)igvm_get_buffer(ctx->cfg->file, data_handle);
|
||||
if (data == NULL) {
|
||||
error_setg(errp, "IGVM: No buffer for handle %d", data_handle);
|
||||
result = -1;
|
||||
|
|
@ -491,7 +500,8 @@ static int qigvm_directive_vp_context(QIgvm *ctx, const uint8_t *header_data,
|
|||
|
||||
if (ctx->machine_state->cgs) {
|
||||
result = ctx->cgsc->set_guest_state(
|
||||
vp_context->gpa, data, igvm_get_buffer_size(ctx->file, data_handle),
|
||||
vp_context->gpa, data,
|
||||
igvm_get_buffer_size(ctx->cfg->file, data_handle),
|
||||
CGS_PAGE_TYPE_VMSA, vp_context->vp_index, errp);
|
||||
} else if (target_arch() == SYS_EMU_TARGET_X86_64) {
|
||||
result = qigvm_x86_set_vp_context(data, vp_context->vp_index, errp);
|
||||
|
|
@ -504,7 +514,7 @@ static int qigvm_directive_vp_context(QIgvm *ctx, const uint8_t *header_data,
|
|||
}
|
||||
|
||||
exit:
|
||||
igvm_free_buffer(ctx->file, data_handle);
|
||||
igvm_free_buffer(ctx->cfg->file, data_handle);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
}
|
||||
|
|
@ -863,7 +873,8 @@ static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp)
|
|||
uint32_t compatibility_mask_sev_snp = 0;
|
||||
uint32_t compatibility_mask = 0;
|
||||
|
||||
header_count = igvm_header_count(ctx->file, IGVM_HEADER_SECTION_PLATFORM);
|
||||
header_count = igvm_header_count(ctx->cfg->file,
|
||||
IGVM_HEADER_SECTION_PLATFORM);
|
||||
if (header_count < 0) {
|
||||
error_setg(errp,
|
||||
"Invalid platform header count in IGVM file. Error code: %X",
|
||||
|
|
@ -874,11 +885,11 @@ static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp)
|
|||
for (header_index = 0; header_index < (unsigned)header_count;
|
||||
header_index++) {
|
||||
IgvmVariableHeaderType typ = igvm_get_header_type(
|
||||
ctx->file, IGVM_HEADER_SECTION_PLATFORM, header_index);
|
||||
ctx->cfg->file, IGVM_HEADER_SECTION_PLATFORM, header_index);
|
||||
typ = igvm_vht_type(typ);
|
||||
if (typ == IGVM_VHT_SUPPORTED_PLATFORM) {
|
||||
header_handle = igvm_get_header(
|
||||
ctx->file, IGVM_HEADER_SECTION_PLATFORM, header_index);
|
||||
ctx->cfg->file, IGVM_HEADER_SECTION_PLATFORM, header_index);
|
||||
if (header_handle < 0) {
|
||||
error_setg(errp,
|
||||
"Invalid platform header in IGVM file. "
|
||||
|
|
@ -887,11 +898,11 @@ static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp)
|
|||
return -1;
|
||||
}
|
||||
platform =
|
||||
(IGVM_VHS_SUPPORTED_PLATFORM *)(igvm_get_buffer(ctx->file,
|
||||
(IGVM_VHS_SUPPORTED_PLATFORM *)(igvm_get_buffer(ctx->cfg->file,
|
||||
header_handle));
|
||||
if (platform == NULL) {
|
||||
error_setg(errp, "IGVM: No buffer for handle %d", header_handle);
|
||||
igvm_free_buffer(ctx->file, header_handle);
|
||||
igvm_free_buffer(ctx->cfg->file, header_handle);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -922,7 +933,7 @@ static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp)
|
|||
} else if (platform->platform_type == IGVM_PLATFORM_TYPE_NATIVE) {
|
||||
compatibility_mask = platform->compatibility_mask;
|
||||
}
|
||||
igvm_free_buffer(ctx->file, header_handle);
|
||||
igvm_free_buffer(ctx->cfg->file, header_handle);
|
||||
}
|
||||
}
|
||||
/* Choose the strongest supported isolation technology */
|
||||
|
|
@ -999,7 +1010,7 @@ int qigvm_process_file(IgvmCfg *cfg, MachineState *machine_state,
|
|||
error_setg(errp, "No IGVM file loaded.");
|
||||
return -1;
|
||||
}
|
||||
ctx.file = cfg->file;
|
||||
ctx.cfg = cfg;
|
||||
trace_igvm_process_file(cfg->file, onlyVpContext);
|
||||
|
||||
ctx.machine_state = machine_state;
|
||||
|
|
@ -1021,7 +1032,8 @@ int qigvm_process_file(IgvmCfg *cfg, MachineState *machine_state,
|
|||
goto cleanup;
|
||||
}
|
||||
|
||||
header_count = igvm_header_count(ctx.file, IGVM_HEADER_SECTION_DIRECTIVE);
|
||||
header_count = igvm_header_count(ctx.cfg->file,
|
||||
IGVM_HEADER_SECTION_DIRECTIVE);
|
||||
if (header_count <= 0) {
|
||||
error_setg(
|
||||
errp, "Invalid directive header count in IGVM file. Error code: %X",
|
||||
|
|
@ -1035,7 +1047,8 @@ int qigvm_process_file(IgvmCfg *cfg, MachineState *machine_state,
|
|||
ctx.current_header_index < (unsigned)header_count;
|
||||
ctx.current_header_index++) {
|
||||
IgvmVariableHeaderType raw_type = igvm_get_header_type(
|
||||
ctx.file, IGVM_HEADER_SECTION_DIRECTIVE, ctx.current_header_index);
|
||||
ctx.cfg->file, IGVM_HEADER_SECTION_DIRECTIVE,
|
||||
ctx.current_header_index);
|
||||
if (!onlyVpContext || igvm_vht_type(raw_type) == IGVM_VHT_VP_CONTEXT) {
|
||||
if (qigvm_handler(&ctx, raw_type, errp) < 0) {
|
||||
goto cleanup_parameters;
|
||||
|
|
@ -1053,7 +1066,7 @@ int qigvm_process_file(IgvmCfg *cfg, MachineState *machine_state,
|
|||
}
|
||||
|
||||
header_count =
|
||||
igvm_header_count(ctx.file, IGVM_HEADER_SECTION_INITIALIZATION);
|
||||
igvm_header_count(ctx.cfg->file, IGVM_HEADER_SECTION_INITIALIZATION);
|
||||
if (header_count < 0) {
|
||||
error_setg(
|
||||
errp,
|
||||
|
|
@ -1066,7 +1079,8 @@ int qigvm_process_file(IgvmCfg *cfg, MachineState *machine_state,
|
|||
ctx.current_header_index < (unsigned)header_count;
|
||||
ctx.current_header_index++) {
|
||||
IgvmVariableHeaderType type =
|
||||
igvm_get_header_type(ctx.file, IGVM_HEADER_SECTION_INITIALIZATION,
|
||||
igvm_get_header_type(ctx.cfg->file,
|
||||
IGVM_HEADER_SECTION_INITIALIZATION,
|
||||
ctx.current_header_index);
|
||||
if (qigvm_handler(&ctx, type, errp) < 0) {
|
||||
goto cleanup_parameters;
|
||||
|
|
@ -1096,3 +1110,22 @@ cleanup_parameters:
|
|||
cleanup:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* cleanup any memory regions created by qigvm_prepare_memory()
|
||||
*/
|
||||
void qigvm_cleanup_memory(IgvmCfg *cfg)
|
||||
{
|
||||
IgvmMemoryRegion *imr, *tmp;
|
||||
|
||||
QTAILQ_FOREACH_SAFE(imr, &cfg->memory_regions, next, tmp)
|
||||
{
|
||||
trace_qigvm_cleanup_memory(imr->mr->name);
|
||||
memory_region_del_subregion(get_system_memory(), imr->mr);
|
||||
vmstate_unregister_ram(imr->mr, NULL);
|
||||
QTAILQ_REMOVE(&cfg->memory_regions, imr, next);
|
||||
/* this triggers MemoryRegion cleanup */
|
||||
object_unparent(OBJECT(imr->mr));
|
||||
g_free(imr);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,3 +33,4 @@ igvm_reset_hold(int type) "type=%u"
|
|||
igvm_reset_exit(int type) "type=%u"
|
||||
igvm_file_loaded(const char *fn, int32_t handle) "fn=%s, handle=0x%x"
|
||||
igvm_process_file(int32_t handle, bool context_only) "handle=0x%x context-only=%d"
|
||||
qigvm_cleanup_memory(const char* name) "freeing mr %s"
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ guest hardware that is specific to QEMU.
|
|||
virt-ctlr
|
||||
vmcoreinfo
|
||||
vmgenid
|
||||
vmlaunchupdate
|
||||
rapl-msr
|
||||
rocker
|
||||
riscv-iommu
|
||||
|
|
|
|||
198
docs/specs/vmlaunchupdate.rst
Normal file
198
docs/specs/vmlaunchupdate.rst
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
VMLAUNCHUPDATE Interface Specification
|
||||
######################################
|
||||
|
||||
Introduction
|
||||
************
|
||||
|
||||
``VmLaunchUpdate`` is an extension to ``fw-cfg`` that allows guests to replace
|
||||
boot state in their virtual machine using IGVM file container. Through a combination
|
||||
of this ``fw-cfg`` hypervisor interface, an IGVM file containing specific directives
|
||||
and with hypervisor stack knowledge, guests can deterministically replace the launch
|
||||
state for guests. This is useful for environments like SEV-SNP where the
|
||||
launch payload becomes the launch digest. Guests can use vm-launch-update device to
|
||||
provide a measured, full guest payload (BIOS image, kernel, initramfs, kernel
|
||||
command line) to the virtual machine which enables them to easily reason about
|
||||
integrity of the resulting system.
|
||||
It is also to be noted that this mechanism currently works only when the guest was
|
||||
already started with an IGVM file defining its initial launch state. Subsequent
|
||||
guest resets will use the launch state as defined in the guest provided IGVM file,
|
||||
not the file with which the guest was initially started. If the guest was not started
|
||||
with IGVM, writing a new bundle through the ``fw-cfg`` interface has no effect.
|
||||
|
||||
For more information, please see the `KVM Forum 2024 presentation <KVMFORUM_>`__
|
||||
about this work.
|
||||
|
||||
|
||||
.. _KVMFORUM: https://www.youtube.com/watch?v=VCMBxU6tAto
|
||||
|
||||
Base Requirements
|
||||
*****************
|
||||
|
||||
#. **fw-cfg**:
|
||||
The target system must provide a ``fw-cfg`` interface. For x86 based
|
||||
environments, this ``fw-cfg`` interface must be accessible through PIO ports
|
||||
0x510 and 0x511. The ``fw-cfg`` interface does not need to be announced as part
|
||||
of system device tables such as DSDT. The ``fw-cfg`` interface must support the
|
||||
DMA interface. It may only support the DMA interface for write operations.
|
||||
|
||||
#. **IGVM support**:
|
||||
The hypervisor must provide support for parsing and executing the IGVM file bundle.
|
||||
|
||||
#. **Confidential guests**:
|
||||
For confidential guests, the hypervisor must support guest reset. Otherwise, the new
|
||||
boot state provided through IGVM will not be applied.
|
||||
|
||||
The Fw-cfg File
|
||||
***************
|
||||
|
||||
Guests drive vmlaunchupdate through special ``fw-cfg`` files that control its flow
|
||||
followed by a standard system reset operation. When the ``vm-launch-update`` device
|
||||
is available, it provides the following ``fw-cfg`` file:
|
||||
|
||||
* ``etc/vmlaunchupdate`` - It exposes a structure of the following type, all in
|
||||
little-endian format:
|
||||
|
||||
.. code-block:: c
|
||||
:linenos:
|
||||
|
||||
typedef struct {
|
||||
uint16_t version;
|
||||
uint16_t status;
|
||||
|
||||
uint32_t _padding;
|
||||
|
||||
uint64_t capabilities;
|
||||
uint64_t control;
|
||||
|
||||
uint64_t fw_image_addr;
|
||||
uint64_t fw_image_size;
|
||||
|
||||
uint64_t opaque_addr;
|
||||
uint64_t opaque_size;
|
||||
|
||||
} VMLaunchUpdate;
|
||||
|
||||
|
||||
Currently, the ``version`` number (line 2 above) is initialized to the value ``1``.
|
||||
Only IGVM files are supported at present. The ``capabilities`` (line 7) and ``control`` (line 8) both support
|
||||
the following single value:
|
||||
|
||||
* ``VM_LAUNCHUPDATE_FORMAT_IGVM``
|
||||
|
||||
This value is used by the hypervisor to indicate that only IGVM container files are supported.
|
||||
This is set as a part of ``capabilities`` parameter (line 7) in the above structure. This same value
|
||||
is passed by the guest to the hypervisor in the ``control`` parameter (line 8) in the above structure
|
||||
to indicate that the guest passed IGVM file in memory to the hypervisor. The starting guest physical
|
||||
address of the IGVM file in memory is specified in ``fw_image_addr`` and it's length is specified in
|
||||
``fw_image_size`` by the guest. If any other value is passed by the guest in the ``control`` parameter,
|
||||
the write is ignored by the hypervisor.
|
||||
|
||||
Following ``control`` parameters are supported:
|
||||
|
||||
* ``VM_LAUNCHUPDATE_CTL_DISABLE``
|
||||
|
||||
This value is set in the ``control`` parameter by the guest in order to disable this ``fw-cfg``
|
||||
hypervisor interface from further updating the guest launch state with a new IGVM file.
|
||||
|
||||
* ``VM_LAUNCHUPDATE_CTL_HOST_IGVM``
|
||||
|
||||
This value is set in the ``control`` parameter by the guest in order to send request to the
|
||||
hypervisor to initialize the guest using the original host provided IGVM file.
|
||||
It is useful if the guest wanted to update the UKIs present in the ESP and upon
|
||||
reset, use one of the updated UKIs present there. If the guest passed addresses in memory
|
||||
where its own IGVM file is loaded (see below) while also setting this control value, the next
|
||||
reset will load the guest provided IGVM file and a subsequent second reset will restore the original
|
||||
host IGVM. If the guest did not provide any addresses of its own IGVM (the address values are
|
||||
cleared) while setting this control parameter, the immediate next guest reset will load the
|
||||
original host provided IGVM file.
|
||||
|
||||
The combination of the above two ctl interfaces work as
|
||||
follows:
|
||||
|
||||
A) ``CTL_HOST_IGVM`` = off ``CTL_DISABLE`` = off
|
||||
|
||||
Supplied IGVM file replaces the firmware permanently. Updating the
|
||||
firmware again is possible.
|
||||
|
||||
B) ``CTL_HOST_IGVM`` = off ``CTL_DISABLE`` = on
|
||||
|
||||
Supplied IGVM file replaces the firmware permanently. Updating the
|
||||
firmware again is not possible.
|
||||
|
||||
C) ``CTL_HOST_IGVM`` = on ``CTL_DISABLE`` = off
|
||||
|
||||
Supplied IGVM file replaces the firmware for one reset. Resetting
|
||||
again will switch back to the original firmware. Updating the
|
||||
firmware again is possible.
|
||||
|
||||
D) ``CTL_HOST_IGVM`` = on ``CTL_DISABLE`` = on
|
||||
|
||||
Supplied IGVM file replaces the firmware for one reset. Resetting
|
||||
again will switch back to the original firmware. Updating the
|
||||
firmware again is NOT possible.
|
||||
|
||||
``fw_image_addr`` (line 10) is the base guest physical address of the guest memory where the IGVM file of size
|
||||
``fw_image_size`` (line 11) is loaded. ``opaque_addr`` (line 13) and ``opaque_size`` (line 14) are used by
|
||||
the guest for passing data across resets. The contents of this guest memory are preserved across the
|
||||
reset. For confidential guests, this memory region must come from guest shared unencrypted memory.
|
||||
|
||||
``status`` (line 3) is written by the hypervisor and it indicates the result of the IGVM loading operation.
|
||||
A success indicates status code 0. Otherwise a non-zero status code indicates failure. The nature of the
|
||||
failure is indicated by the value of the code.
|
||||
|
||||
Triggering the Launch State Update using IGVM
|
||||
*********************************************
|
||||
|
||||
To initiate the launch update process, the guest issues a standard system reset
|
||||
operation through any of the means implemented by the machine model.
|
||||
|
||||
On a write to the ``etc/vmlaunchupdate`` interface, the hypervisor evaluates whether this
|
||||
hypervisor interface is disabled. If it is, it ignores any writes to this ``fw-cfg`` file
|
||||
by the guest. No updates to initial launch state is performed.
|
||||
|
||||
If the hypervisor interface is enabled, upon write to the ``etc/vmlaunchupdate`` interface,
|
||||
the hypervisor parses the IGVM file bundle passed to it in memory, with starting guest physical
|
||||
address at ``fw_image_addr`` and length ``fw_image_size``. If parsing is successful, it creates
|
||||
a context handle to the IGVM file. If parsing and context loading is successful and there are no
|
||||
errors, ``fw_image_addr`` and ``fw_image_size`` are cleared. The guest can check this in order
|
||||
to determine if the IGVM was successfully parsed and the new context was loaded. If not, the
|
||||
guest can throw error and abort rebooting to new IGVM boot state. Alternatively, the guest can
|
||||
also check the ``status`` code from the ``fw-cfg`` file. A status code of 0 indicates success
|
||||
of the operation. Non-zero status code indicates failure. Exact nature of the failure is
|
||||
indicated by the value of the code. Currently, only two error values are supported:
|
||||
|
||||
* ``VM_LAUNCHUPDATE_LOAD_FAIL`` - defined as value 1 and is set when loading of the IGVM file failed.
|
||||
* ``VM_LAUNCHUPDATE_NOT_IGVM_INIT`` - defined as value 2 and is set when the guest was not started with
|
||||
IGVM file.
|
||||
|
||||
Upon guest reset, the hypervisor executes the IGVM bundle using
|
||||
the context handle, setting the initial launch state of the guest accordingly.
|
||||
If an invalid IGVM file is passed, parsing the file fails and the hypervisor ignores it
|
||||
when ``fw-cfg`` files are written. In this case, the initial launch state
|
||||
is not modified. If invalid addresses are passed, the hypervisor ignores them as well and no
|
||||
new launch state is set.
|
||||
|
||||
The launch state update mechanism works both for confidential and non-confidential
|
||||
guests. In confidential guests, as a part of the reset operation, all existing
|
||||
guest shared memory (shared with the hypervisor) as well as the guest memory region
|
||||
starting with ``opaque_addr`` and length ``opaque_size`` are preserved.
|
||||
The reset causes recreation of the VM context which triggers a fresh
|
||||
measurement of the replaced BIOS region and reset CPU state.
|
||||
|
||||
For non-confidential guests, there is no concept of guest private memory and all the existing
|
||||
guest memory is preserved (this is the default behaviour today - QEMU does not reset/clear
|
||||
guest memory upon reset).
|
||||
|
||||
In both confidential and non-confidential cases, CPU and device state are reset to
|
||||
the reset states specified in IGVM. In confidential environments, the guest
|
||||
always resumes operation in the highest privileged mode available to it (VMPL0 in SEV-SNP).
|
||||
|
||||
Closing Remarks
|
||||
***************
|
||||
The exact content of the memory region specified by starting address ``opaque_addr``
|
||||
and length ``opaque_size`` is guest specific and is hypervisor agnostic. The hypervisor does
|
||||
not care about the contents of this memory region. Therefore, it is not included in this
|
||||
specification. As of writing this document, TDX guests on QEMU does not support IGVM.
|
||||
Therefore, this mechanism cannot be used to change launch state of TDX guests.
|
||||
|
|
@ -164,6 +164,9 @@ specific_ss.add(when: 'CONFIG_MIPS_ITU', if_true: files('mips_itu.c'))
|
|||
|
||||
specific_ss.add(when: 'CONFIG_RISCV_MIPS_CMGCR', if_true: files('riscv_cmgcr.c'))
|
||||
specific_ss.add(when: 'CONFIG_RISCV_MIPS_CPC', if_true: files('riscv_cpc.c'))
|
||||
if igvm.found()
|
||||
specific_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmlaunchupdate.c'))
|
||||
endif
|
||||
|
||||
system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
|
||||
|
||||
|
|
|
|||
|
|
@ -442,3 +442,9 @@ iommu_testdev_dma_read(uint64_t gva, uint32_t len) "gva=0x%" PRIx64 " len=%u"
|
|||
iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x actual=0x%x"
|
||||
iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
|
||||
iommu_testdev_dma_armed(bool armed) "armed=%d"
|
||||
|
||||
# vmlaunchupdate.c
|
||||
launch_update_write(void) ""
|
||||
vmlaunch_reset_enter(void) ""
|
||||
vm_launchupdate_finalize(void) ""
|
||||
restore_host_x86_igvm(void) ""
|
||||
|
|
|
|||
333
hw/misc/vmlaunchupdate.c
Normal file
333
hw/misc/vmlaunchupdate.c
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
/*
|
||||
* 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 <anisinha@redhat.com>
|
||||
*
|
||||
* 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)
|
||||
38
include/hw/misc/vmlaunchupdate.h
Normal file
38
include/hw/misc/vmlaunchupdate.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Guest driven VM launch state update device via IGVM.
|
||||
* For details and specification, please look at docs/specs/vmlaunchupdate.rst.
|
||||
*
|
||||
* Copyright (C) 2026 Red Hat, Inc.
|
||||
*
|
||||
* Authors: Ani Sinha <anisinha@redhat.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
*/
|
||||
#ifndef VMLAUNCHUPDATE_H
|
||||
#define VMLAUNCHUPDATE_H
|
||||
|
||||
#include "hw/core/qdev.h"
|
||||
#include "qom/object.h"
|
||||
#include "qemu/units.h"
|
||||
#include "system/igvm-cfg.h"
|
||||
#include "standard-headers/misc/vmlaunchupdate.h"
|
||||
|
||||
#define TYPE_VMLAUNCHUPDATE "vm-launch-update"
|
||||
|
||||
typedef struct VMLaunchUpdateState {
|
||||
DeviceState parent_obj;
|
||||
VMLaunchUpdate launch_update;
|
||||
bool disabled;
|
||||
bool host_igvm_on_reset;
|
||||
ResettableState reset_state;
|
||||
} VMLaunchUpdateState;
|
||||
|
||||
|
||||
typedef struct VMLaunchUpdateStateClass {
|
||||
ObjectClass parent_class;
|
||||
} VMLaunchUpdateStateClass;
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(VMLaunchUpdateState, VMLAUNCHUPDATE);
|
||||
|
||||
#endif
|
||||
102
include/standard-headers/misc/vmlaunchupdate.h
Normal file
102
include/standard-headers/misc/vmlaunchupdate.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Guest driven VM launch state update device via IGVM.
|
||||
* The definitions in this header defines the API for the hypervisor interface.
|
||||
* For details and specification, please look at docs/specs/vmlaunchupdate.rst.
|
||||
*
|
||||
* Copyright (C) 2026 Red Hat, Inc.
|
||||
*
|
||||
* Authors: Ani Sinha <anisinha@redhat.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
*/
|
||||
#ifndef VMLAUNCHUPDATE_API_H
|
||||
#define VMLAUNCHUPDATE_API_H
|
||||
|
||||
/* fw-cfg file definition */
|
||||
#define FILE_VMLAUNCHUPDATE "etc/vmlaunchupdate"
|
||||
|
||||
/* version */
|
||||
#define VM_LAUNCHUPDATE_VERSION 0x01
|
||||
|
||||
/* format bits, used by both 'capabilities' and 'control' */
|
||||
|
||||
/* igvm */
|
||||
#define VM_LAUNCHUPDATE_FORMAT_IGVM (1ULL << 32)
|
||||
|
||||
/* 'control' field bits */
|
||||
|
||||
/* disable vmlaunchupdate interface */
|
||||
#define VM_LAUNCHUPDATE_CTL_DISABLE (1 << 0)
|
||||
/* revert to the original host provided igvm */
|
||||
#define VM_LAUNCHUPDATE_CTL_HOST_IGVM (1 << 1)
|
||||
|
||||
/* The combination of the above two ctl interfaces work as
|
||||
* follows:
|
||||
*
|
||||
* A) CTL_HOST_IGVM=off CTL_DISABLE=off
|
||||
*
|
||||
* Supplied IGVM file replaces the firmware permanently. Updating the
|
||||
* firmware again is possible.
|
||||
*
|
||||
* B) CTL_HOST_IGVM=off CTL_DISABLE=on
|
||||
*
|
||||
* Supplied IGVM file replaces the firmware permanently. Updating the
|
||||
* firmware again is not possible.
|
||||
*
|
||||
* C) CTL_HOST_IGVM=on CTL_DISABLE=off
|
||||
*
|
||||
* Supplied IGVM file replaces the firmware for one reset. Resetting
|
||||
* again will switch back to the original firmware. Updating the
|
||||
* firmware again is possible.
|
||||
*
|
||||
* D) CTL_HOST_IGVM=on CTL_DISABLE=on
|
||||
*
|
||||
* Supplied IGVM file replaces the firmware for one reset. Resetting
|
||||
* again will switch back to the original firmware. Updating the
|
||||
* firmware again is NOT possible.
|
||||
*
|
||||
*/
|
||||
|
||||
/* status code */
|
||||
enum VMLaunchUpdateStatus {
|
||||
VM_LAUNCHUPDATE_SUCCESS = 0,
|
||||
VM_LAUNCHUPDATE_LOAD_FAIL = 1,
|
||||
VM_LAUNCHUPDATE_NOT_IGVM_INIT = 2,
|
||||
};
|
||||
|
||||
typedef struct QEMU_PACKED {
|
||||
/* api version */
|
||||
uint16_t version;
|
||||
|
||||
/*
|
||||
* The guest can read this in order to determine if loading new IGVM
|
||||
* succeeded.
|
||||
*/
|
||||
uint16_t status;
|
||||
|
||||
uint32_t _padding;
|
||||
|
||||
/* VMM capabilities, read-only. */
|
||||
uint64_t capabilities;
|
||||
/* control bits, see VMFWUPDATE_CTL_* */
|
||||
uint64_t control;
|
||||
|
||||
/*
|
||||
* address and size of the IGVM image. Will be cleared when
|
||||
* the write completes successfully and IGVM file is correctly parsed.
|
||||
*/
|
||||
uint64_t fw_image_addr;
|
||||
uint64_t fw_image_size;
|
||||
|
||||
/*
|
||||
* address + size of opaque blob. The guest can use this to pass on
|
||||
* information, for example which memory region the linux kernel has been
|
||||
* loaded to. writable, will be kept intact on firmware update.
|
||||
*/
|
||||
uint64_t opaque_addr;
|
||||
uint64_t opaque_size;
|
||||
|
||||
} VMLaunchUpdate;
|
||||
|
||||
#endif
|
||||
|
|
@ -18,6 +18,11 @@
|
|||
#include "system/confidential-guest-support.h"
|
||||
#include <igvm/igvm.h>
|
||||
|
||||
typedef struct IgvmMemoryRegion {
|
||||
QTAILQ_ENTRY(IgvmMemoryRegion) next;
|
||||
MemoryRegion *mr;
|
||||
} IgvmMemoryRegion;
|
||||
|
||||
struct IgvmCfg {
|
||||
Object parent_obj;
|
||||
|
||||
|
|
@ -29,6 +34,7 @@ struct IgvmCfg {
|
|||
char *filename;
|
||||
IgvmHandle file;
|
||||
ResettableState reset_state;
|
||||
QTAILQ_HEAD(, IgvmMemoryRegion) memory_regions;
|
||||
};
|
||||
|
||||
typedef struct QIgvmParameterData {
|
||||
|
|
@ -43,7 +49,7 @@ typedef struct QIgvmParameterData {
|
|||
* file.
|
||||
*/
|
||||
struct QIgvm {
|
||||
IgvmHandle file;
|
||||
IgvmCfg *cfg;
|
||||
MachineState *machine_state;
|
||||
ConfidentialGuestSupportClass *cgsc;
|
||||
uint32_t compatibility_mask;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ typedef struct QIgvm QIgvm;
|
|||
|
||||
int qigvm_process_file(IgvmCfg *igvm, MachineState *machine_state,
|
||||
bool onlyVpContext, Error **errp);
|
||||
void qigvm_cleanup_memory(IgvmCfg *igvm);
|
||||
|
||||
/* x86 native */
|
||||
int qigvm_x86_get_mem_map_entry(int index,
|
||||
|
|
|
|||
|
|
@ -1691,6 +1691,7 @@ static void memory_region_finalize(Object *obj)
|
|||
{
|
||||
MemoryRegion *mr = MEMORY_REGION(obj);
|
||||
|
||||
trace_memory_region_finalize(mr->name);
|
||||
/*
|
||||
* Each memory region (that can be freed) must have an owner, and it
|
||||
* always has the same lifecycle of its owner. It means when reaching
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ flatview_new(void *view, void *root) "%p (root %p)"
|
|||
flatview_destroy(void *view, void *root) "%p (root %p)"
|
||||
flatview_destroy_rcu(void *view, void *root) "%p (root %p)"
|
||||
global_dirty_changed(unsigned int bitmask) "bitmask 0x%"PRIx32
|
||||
memory_region_finalize(const char* name) "mr %s"
|
||||
|
||||
# physmem.c
|
||||
address_space_map(void *as, uint64_t addr, uint64_t len, bool is_write, uint32_t attrs) "as:%p addr 0x%"PRIx64":%"PRIx64" write:%d attrs:0x%x"
|
||||
|
|
|
|||
45
tests/data/igvm/README
Normal file
45
tests/data/igvm/README
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
=== This is the readme file for all IGVM file bundles provided ===
|
||||
=== All IGVM files are covered by GPL v2 or later ===
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
Small IGVM file bundles required for tesing vm-launch-update device
|
||||
===================================================================
|
||||
|
||||
This directory contains IGVM files required for launchupdate-test.c.
|
||||
These IGVM bundles can be built from the following repository:
|
||||
|
||||
https://gitlab.com/anisinha/virt-firmware-rs
|
||||
|
||||
Just type "make" in the top of the repository. The built IGVM files
|
||||
can be found in the target/x86_64-unknown-none/debug directory.
|
||||
|
||||
$ ls -l ./target/x86_64-unknown-none/debug/*.igvm
|
||||
-rw-r--r--. 1 anisinha anisinha 153624 Jun 8 10:05 ./target/x86_64-unknown-none/debug/hello.igvm
|
||||
-rw-r--r--. 1 anisinha anisinha 157848 Jun 8 10:05 ./target/x86_64-unknown-none/debug/igvmtest.igvm
|
||||
-rw-r--r--. 1 anisinha anisinha 987384 Jun 8 10:05 ./target/x86_64-unknown-none/debug/mefisto.igvm
|
||||
-rw-r--r--. 1 anisinha anisinha 368312 Jun 8 10:05 ./target/x86_64-unknown-none/debug/qemuinit.igvm
|
||||
-rw-r--r--. 1 anisinha anisinha 223944 Jun 8 10:05 ./target/x86_64-unknown-none/debug/snptest.igvm
|
||||
|
||||
The IGVM bundles for sev-snp testing are also kept in a seperate directory /snp_igvm_bundles in the
|
||||
above repository for use without requiring to build from source:
|
||||
|
||||
$ virtfirmware-rs/snp_igvm_bundles$ ls -l
|
||||
total 388
|
||||
-rw-r--r-- 1 anisinha anisinha 199176 Aug 7 00:58 snptest.igvm
|
||||
-rw-r--r-- 1 anisinha anisinha 195048 Aug 6 06:10 snptest-nohello.igvm
|
||||
|
||||
Following files are used by the tests/qtest/launchupdate-test.c for non-coco case:
|
||||
|
||||
hello.igvm
|
||||
qemuinit.igvm
|
||||
|
||||
For confidential case (that is when COCO=1 is passed in the environment), the following
|
||||
IGVM bundles are used:
|
||||
|
||||
snptest-nohello.igvm
|
||||
snptest.igvm
|
||||
|
||||
The IGVM bundles used by the confidential case are not checked into the QEMU
|
||||
repository. Users who wish to test the confidential case can use the IGVM
|
||||
bundles from https://gitlab.com/anisinha/virt-firmware-rs as mentioned above.
|
||||
BIN
tests/data/igvm/hello.igvm
Normal file
BIN
tests/data/igvm/hello.igvm
Normal file
Binary file not shown.
BIN
tests/data/igvm/qemuinit.igvm
Normal file
BIN
tests/data/igvm/qemuinit.igvm
Normal file
Binary file not shown.
|
|
@ -25,6 +25,10 @@ tests_aarch64_system_quick = [
|
|||
'vmstate',
|
||||
]
|
||||
|
||||
if igvm.found()
|
||||
tests_aarch64_system_quick += [ 'vm_launch_update_aarch' ]
|
||||
endif
|
||||
|
||||
tests_aarch64_system_thorough = [
|
||||
'aspeed_ast2700a1',
|
||||
'aspeed_ast2700a2',
|
||||
|
|
|
|||
33
tests/functional/aarch64/test_vm_launch_update_aarch.py
Executable file
33
tests/functional/aarch64/test_vm_launch_update_aarch.py
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# Check for vm-launch-update device.
|
||||
#
|
||||
# Copyright (c) 2026 Red Hat, Inc.
|
||||
#
|
||||
# Author:
|
||||
# Ani Sinha <anisinha@redhat.com>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from qemu_test import QemuSystemTest
|
||||
|
||||
class VmLaunchUpdateDeviceCheck(QemuSystemTest):
|
||||
|
||||
def aarch64_fail_test(self):
|
||||
"""
|
||||
Currently the device is only supported for pc platforms.
|
||||
"""
|
||||
self.vm.add_args('-machine', 'virt', '-device',
|
||||
'vm-launch-update,id=fwupd1')
|
||||
self.vm.set_qmp_monitor(enabled=False)
|
||||
self.vm.launch()
|
||||
self.vm.wait()
|
||||
self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1")
|
||||
self.assertRegex(self.vm.get_log(),
|
||||
r'This machine does not support vm-launch-update device')
|
||||
|
||||
def test_vm_launch_update(self):
|
||||
self.aarch64_fail_test()
|
||||
|
||||
if __name__ == '__main__':
|
||||
QemuSystemTest.main()
|
||||
|
|
@ -28,6 +28,10 @@ if not get_option('asan')
|
|||
tests_x86_64_system_quick += [ 'memlock' ]
|
||||
endif
|
||||
|
||||
if igvm.found()
|
||||
tests_x86_64_system_quick += [ 'vm_launch_update' ]
|
||||
endif
|
||||
|
||||
tests_x86_64_system_thorough = [
|
||||
'acpi_bits',
|
||||
'hotplug_blk',
|
||||
|
|
|
|||
48
tests/functional/x86_64/test_vm_launch_update.py
Executable file
48
tests/functional/x86_64/test_vm_launch_update.py
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# Check for vm-launch-update device.
|
||||
#
|
||||
# Copyright (c) 2026 Red Hat, Inc.
|
||||
#
|
||||
# Author:
|
||||
# Ani Sinha <anisinha@redhat.com>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from qemu_test import QemuSystemTest
|
||||
import time
|
||||
|
||||
class VmLaunchUpdateDeviceCheck(QemuSystemTest):
|
||||
DELAY_BOOT_SEQUENCE = 1
|
||||
|
||||
def vm_launch_update_pass(self):
|
||||
"""
|
||||
Basic test to make sure vm-launch-update device can be instantiated.
|
||||
"""
|
||||
self.vm.add_args('-device', 'vm-launch-update,id=fwupd1')
|
||||
self.vm.set_qmp_monitor(enabled=False)
|
||||
self.vm.launch()
|
||||
time.sleep(self.DELAY_BOOT_SEQUENCE)
|
||||
self.vm.shutdown()
|
||||
self.assertEqual(self.vm.exitcode(), 0, "QEMU exit code should be 0")
|
||||
|
||||
def multiple_device_fail(self):
|
||||
"""
|
||||
Only one vm-launch-update device can be instantiated. Ensure failure if
|
||||
user tries to create more than one device.
|
||||
"""
|
||||
self.vm.add_args('-device', 'vm-launch-update,id=fw1',
|
||||
'-device', 'vm-launch-update,id=fw2')
|
||||
self.vm.set_qmp_monitor(enabled=False)
|
||||
self.vm.launch()
|
||||
self.vm.wait()
|
||||
self.assertEqual(self.vm.exitcode(), 1, "QEMU exit code should be 1")
|
||||
self.assertRegex(self.vm.get_log(),
|
||||
r'at most one vm-launch-update device is permitted')
|
||||
|
||||
def test_vm_launch_update(self):
|
||||
self.vm_launch_update_pass()
|
||||
self.multiple_device_fail()
|
||||
|
||||
if __name__ == '__main__':
|
||||
QemuSystemTest.main()
|
||||
625
tests/qtest/launchupdate-test.c
Normal file
625
tests/qtest/launchupdate-test.c
Normal file
|
|
@ -0,0 +1,625 @@
|
|||
/*
|
||||
* vmlaunchupdate device fwcfg test.
|
||||
*
|
||||
* Copyright (c) 2026 Red Hat, Inc.
|
||||
*
|
||||
* Author:
|
||||
* Ani Sinha <anisinha@redhat.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "libqos/libqos-pc.h"
|
||||
#include "libqtest.h"
|
||||
#include "standard-headers/linux/qemu_fw_cfg.h"
|
||||
#include "libqos/fw_cfg.h"
|
||||
#include "qemu/bswap.h"
|
||||
#include "hw/misc/vmlaunchupdate.h"
|
||||
|
||||
#define WAIT_SEC 10
|
||||
static bool debug;
|
||||
static bool trace;
|
||||
static bool confidential;
|
||||
|
||||
static void test_vm_launch_update_capability(void)
|
||||
{
|
||||
QFWCFG *fw_cfg;
|
||||
QTestState *s;
|
||||
VMLaunchUpdate launch_update;
|
||||
size_t filesize;
|
||||
uint64_t capabilities;
|
||||
|
||||
if (!qtest_has_device("vm-launch-update")) {
|
||||
g_test_skip("Device vm-launch-update is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
s = qtest_init("-device vm-launch-update");
|
||||
fw_cfg = pc_fw_cfg_init(s);
|
||||
|
||||
filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
capabilities = le64_to_cpu(launch_update.capabilities);
|
||||
g_assert_cmpint(capabilities, ==, VM_LAUNCHUPDATE_FORMAT_IGVM);
|
||||
pc_fw_cfg_uninit(fw_cfg);
|
||||
qtest_quit(s);
|
||||
}
|
||||
|
||||
|
||||
static void test_vm_launch_update_disable(void)
|
||||
{
|
||||
QFWCFG *fw_cfg;
|
||||
QOSState *qs;
|
||||
VMLaunchUpdate launch_update;
|
||||
uint64_t control;
|
||||
size_t filesize;
|
||||
|
||||
if (!qtest_has_device("vm-launch-update")) {
|
||||
g_test_skip("Device vm-launch-update is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
/* use default accelerator */
|
||||
qs = qtest_pc_boot("-device vm-launch-update");
|
||||
|
||||
fw_cfg = pc_fw_cfg_init(qs->qts);
|
||||
|
||||
filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
control = le64_to_cpu(launch_update.control);
|
||||
g_assert_cmpint(VM_LAUNCHUPDATE_CTL_DISABLE & control, ==, 0);
|
||||
|
||||
/* disable the device */
|
||||
memset(&launch_update, 0, sizeof(launch_update));
|
||||
launch_update.control |= VM_LAUNCHUPDATE_CTL_DISABLE;
|
||||
|
||||
filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
|
||||
/* try to clear the dsable flag */
|
||||
memset(&launch_update, 0, sizeof(launch_update));
|
||||
|
||||
filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
|
||||
/* check if the device is still disabled */
|
||||
filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
control = le64_to_cpu(launch_update.control);
|
||||
g_assert_cmpint(VM_LAUNCHUPDATE_CTL_DISABLE & control, ==, 1);
|
||||
|
||||
pc_fw_cfg_uninit(fw_cfg);
|
||||
qtest_shutdown(qs);
|
||||
}
|
||||
|
||||
static void check_error(void)
|
||||
{
|
||||
QFWCFG *fw_cfg;
|
||||
QOSState *qs;
|
||||
VMLaunchUpdate launch_update;
|
||||
uint16_t status;
|
||||
size_t filesize;
|
||||
|
||||
if (!qtest_has_device("vm-launch-update")) {
|
||||
g_test_skip("Device vm-launch-update is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
/* guest not started with IGVM and with default accelerator */
|
||||
qs = qtest_pc_boot("-device vm-launch-update");
|
||||
|
||||
fw_cfg = pc_fw_cfg_init(qs->qts);
|
||||
|
||||
memset(&launch_update, 0, sizeof(launch_update));
|
||||
launch_update.fw_image_size = 50;
|
||||
launch_update.fw_image_addr = cpu_to_le64(0xdeadbeef);
|
||||
launch_update.control |= VM_LAUNCHUPDATE_FORMAT_IGVM;
|
||||
|
||||
filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
|
||||
memset(&launch_update, 0, sizeof(launch_update));
|
||||
filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
status = le64_to_cpu(launch_update.status);
|
||||
/* should fail with NOT_IGVM_INIT */
|
||||
g_assert_cmpint(status, ==, VM_LAUNCHUPDATE_NOT_IGVM_INIT);
|
||||
|
||||
memset(&launch_update, 0, sizeof(launch_update));
|
||||
launch_update.fw_image_size = 50;
|
||||
launch_update.fw_image_addr = cpu_to_le64(0xdeadbeef);
|
||||
/* control set to 0, not VM_LAUNCHUPDATE_FORMAT_IGVM */
|
||||
|
||||
filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
|
||||
memset(&launch_update, 0, sizeof(launch_update));
|
||||
filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
status = le64_to_cpu(launch_update.status);
|
||||
/* should fail with LOAD_FAIL since it was not IGVM format */
|
||||
g_assert_cmpint(status, ==, VM_LAUNCHUPDATE_LOAD_FAIL);
|
||||
}
|
||||
|
||||
static int64_t get_image_size(const char *filename)
|
||||
{
|
||||
int fd;
|
||||
int64_t size;
|
||||
fd = open(filename, O_RDONLY | O_BINARY);
|
||||
g_assert_true(fd > 0);
|
||||
size = lseek(fd, 0, SEEK_END);
|
||||
close(fd);
|
||||
return size;
|
||||
}
|
||||
|
||||
static ssize_t load_image(const char *igvm_f, void **addr, size_t *size)
|
||||
{
|
||||
ssize_t actsize = 0, l = 0;
|
||||
int f_igvm_f;
|
||||
size_t l_size;
|
||||
|
||||
f_igvm_f = open(igvm_f, O_RDONLY | O_BINARY);
|
||||
g_assert_true(f_igvm_f);
|
||||
l_size = get_image_size(igvm_f);
|
||||
g_assert_true(l_size > 0);
|
||||
*addr = g_malloc0(l_size);
|
||||
g_assert_true(*addr);
|
||||
|
||||
while (l < l_size) {
|
||||
actsize = read(f_igvm_f, *addr + l, 1);
|
||||
if (actsize < 0) {
|
||||
break;
|
||||
}
|
||||
l += actsize;
|
||||
}
|
||||
|
||||
close(f_igvm_f);
|
||||
*size = l_size;
|
||||
return actsize < 0 ? -1 : l;
|
||||
}
|
||||
|
||||
static guint32 match_string(char *serial_f, const char *exp_out)
|
||||
{
|
||||
GError *error = NULL;
|
||||
g_autofree gchar *f_contents = NULL;
|
||||
g_autofree GRegex *regex = NULL;
|
||||
g_autofree GMatchInfo *match_info = NULL;
|
||||
gsize len;
|
||||
guint32 count = 0;
|
||||
gboolean ret;
|
||||
|
||||
ret = g_file_get_contents(serial_f, &f_contents, &len, &error);
|
||||
g_assert(ret);
|
||||
g_assert_no_error(error);
|
||||
|
||||
regex = g_regex_new(exp_out, G_REGEX_CASELESS, 0, &error);
|
||||
g_assert_no_error(error);
|
||||
|
||||
ret = g_regex_match_full(regex, f_contents, -1, 0, 0, &match_info, &error);
|
||||
g_assert_no_error(error);
|
||||
|
||||
while (g_match_info_matches(match_info)) {
|
||||
gchar *word = g_match_info_fetch(match_info, 0);
|
||||
g_free(word);
|
||||
g_match_info_next(match_info, &error);
|
||||
count++;
|
||||
}
|
||||
g_regex_unref(regex);
|
||||
return count;
|
||||
}
|
||||
|
||||
static int wait_for_match(char *serial_f,
|
||||
const char *exp_out, int64_t timeout_s,
|
||||
guint32 count)
|
||||
{
|
||||
time_t start, delta;
|
||||
int ret = -1;
|
||||
|
||||
start = time(NULL);
|
||||
while (1) {
|
||||
if (match_string(serial_f, exp_out) == count) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
delta = time(NULL) - start;
|
||||
if (delta >= timeout_s) {
|
||||
fprintf(stderr, "timed out waiting to read serial output\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/* wait 20 ms before trying again */
|
||||
if (false) {
|
||||
fprintf(stderr,
|
||||
"sleeping 20 ms before checking serial output again.\n");
|
||||
}
|
||||
g_usleep(20000);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void set_test_params(const char **igvm_f, const char **igvm_init,
|
||||
const char **snp, const char **cgs)
|
||||
{
|
||||
if (confidential) {
|
||||
*snp = "-object \'{\"qom-type\":\"sev-snp-guest\",\"id\":\"lsec0\","
|
||||
"\"cbitpos\":51,\"reduced-phys-bits\":1,\"policy\":196608}\'";
|
||||
*cgs = "confidential-guest-support=lsec0";
|
||||
/*
|
||||
* The following two IGVM files can be built from the source
|
||||
* present in https://gitlab.com/anisinha/virt-firmware-rs .
|
||||
* Typing 'make' from the top of this repository will build the
|
||||
* IGVM files for both confidential and
|
||||
* non-confidential tests. The IGVM files for the non-coco
|
||||
* case has been checked-in into the QEMU repository for
|
||||
* convenience and easy CI pipeline testing.
|
||||
*/
|
||||
*igvm_f = "tests/data/igvm/snptest.igvm"; /* prints 'hello world' */
|
||||
*igvm_init = "tests/data/igvm/snptest-nohello.igvm";
|
||||
} else {
|
||||
*igvm_f = "tests/data/igvm/hello.igvm";
|
||||
*igvm_init = "tests/data/igvm/qemuinit.igvm";
|
||||
*snp = "";
|
||||
*cgs = "";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void set_expected_out(const char **exp_out, const char **exp_out2,
|
||||
const char **exp_out3)
|
||||
{
|
||||
*exp_out = "Hello world!";
|
||||
*exp_out2 = "Test succeeded!";
|
||||
*exp_out3 = "boot process complete with initial igvm";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static QOSState *set_qemu_args(const char *cgs, const char *tp, char *serialf,
|
||||
const char *igvm_init, const char *snp)
|
||||
{
|
||||
QOSState *qs;
|
||||
|
||||
if (tp) {
|
||||
qs = qtest_pc_boot("-machine q35,igvm-cfg=igvm0,%s -m 1G -accel kvm "
|
||||
"-device vm-launch-update %s "
|
||||
"-chardev file,id=serial0,path=%s "
|
||||
"-serial chardev:serial0 "
|
||||
"-object igvm-cfg,id=igvm0,file=%s %s",
|
||||
cgs, tp, serialf, igvm_init, snp);
|
||||
} else {
|
||||
qs = qtest_pc_boot("-machine q35,igvm-cfg=igvm0,%s -m 1G -accel kvm "
|
||||
"-device vm-launch-update "
|
||||
"-chardev file,id=serial0,path=%s "
|
||||
"-serial chardev:serial0 "
|
||||
"-object igvm-cfg,id=igvm0,file=%s %s",
|
||||
cgs, serialf, igvm_init, snp);
|
||||
}
|
||||
|
||||
return qs;
|
||||
}
|
||||
|
||||
static void test_load_igvm(void)
|
||||
{
|
||||
const char *igvm_f;
|
||||
const char *igvm_init;
|
||||
int ser_fd;
|
||||
g_autofree void *igvm_blob = NULL;
|
||||
g_autofree char *serialtmp = NULL;
|
||||
const char *exp_out;
|
||||
const char *exp_out2;
|
||||
const char *exp_out3;
|
||||
const char *tracepoints = "--trace memory_region_finalize "
|
||||
"--trace qigvm_cleanup_memory -D /tmp/qemu-debug.log ";
|
||||
const char *snp, *cgs;
|
||||
uint64_t gaddr;
|
||||
size_t igvm_sz;
|
||||
size_t filesize;
|
||||
QFWCFG *fw_cfg;
|
||||
QOSState *qs;
|
||||
VMLaunchUpdate launch_update;
|
||||
|
||||
if (!trace) {
|
||||
tracepoints = "";
|
||||
}
|
||||
|
||||
if (!qtest_has_machine("q35")) {
|
||||
g_test_skip("q35 machine not available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!qtest_has_accel("kvm")) {
|
||||
g_test_skip("No KVM accelerator available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!qtest_has_device("vm-launch-update")) {
|
||||
g_test_skip("Device vm-launch-update is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
set_test_params(&igvm_f, &igvm_init, &snp, &cgs);
|
||||
set_expected_out(&exp_out, &exp_out2, &exp_out3);
|
||||
|
||||
if (!g_file_test(igvm_f, G_FILE_TEST_EXISTS) ||
|
||||
!g_file_test(igvm_init, G_FILE_TEST_EXISTS)) {
|
||||
g_test_skip("igvm file bundle(s) does not exist!");
|
||||
return;
|
||||
}
|
||||
|
||||
ser_fd = g_file_open_tmp("launchupdate-qtest-serial-sXXXXXX",
|
||||
&serialtmp, NULL);
|
||||
g_assert_true(ser_fd != -1);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "serial console file is %s\n", serialtmp);
|
||||
}
|
||||
|
||||
qs = set_qemu_args(cgs, tracepoints, serialtmp, igvm_init, snp);
|
||||
|
||||
fw_cfg = pc_fw_cfg_init(qs->qts);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "target endianness: %s\n",
|
||||
qtest_big_endian(qs->qts) ? "big" : "little");
|
||||
}
|
||||
|
||||
/* exp_out3 should be printed once from initial boot */
|
||||
g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 1) == 0);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "initially booted with host igvm\n");
|
||||
}
|
||||
|
||||
g_assert_true(load_image(igvm_f, &igvm_blob, &igvm_sz) == igvm_sz);
|
||||
|
||||
/* create a data buffer in guest memory */
|
||||
gaddr = guest_alloc(&qs->alloc, igvm_sz);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "guest paddr: %"PRIx64 " igvm size: %lu\n",
|
||||
gaddr, igvm_sz);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "writing igvm file into the guest memory\n");
|
||||
}
|
||||
|
||||
qtest_bufwrite(qs->qts, gaddr, igvm_blob, igvm_sz);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr,
|
||||
"tell hypervisor where igvm is loaded in guest memory\n");
|
||||
}
|
||||
|
||||
/* now tell hypervisor where we loaded the bios */
|
||||
memset(&launch_update, 0, sizeof(launch_update));
|
||||
launch_update.fw_image_size = cpu_to_le64(igvm_sz);
|
||||
launch_update.fw_image_addr = cpu_to_le64(gaddr);
|
||||
launch_update.control |= VM_LAUNCHUPDATE_FORMAT_IGVM;
|
||||
|
||||
filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "resetting the virtual machine now\n");
|
||||
}
|
||||
|
||||
qtest_system_reset(qs->qts);
|
||||
|
||||
/* expected string should be printed on the console */
|
||||
g_assert_true(wait_for_match(serialtmp, exp_out, WAIT_SEC, 1) == 0);
|
||||
g_assert_true(wait_for_match(serialtmp, exp_out2, WAIT_SEC, 1) == 0);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "hello world found on console\n");
|
||||
}
|
||||
|
||||
/* check if VM_LAUNCHUPDATE_CTL_HOST_IGVM function works */
|
||||
|
||||
/* set only VM_LAUNCHUPDATE_CTL_HOST_IGVM control without IGVM bundle */
|
||||
memset(&launch_update, 0, sizeof(launch_update));
|
||||
launch_update.control |= VM_LAUNCHUPDATE_CTL_HOST_IGVM;
|
||||
|
||||
filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
|
||||
/* now reset the guest */
|
||||
if (debug) {
|
||||
fprintf(stderr,
|
||||
"resetting again in order to restore host provided IGVM\n");
|
||||
}
|
||||
qtest_system_reset(qs->qts);
|
||||
|
||||
/*
|
||||
* exp_out3 should be printed twice, once from initial boot,
|
||||
* once from restoring host igvm.
|
||||
*/
|
||||
g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 2) == 0);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "booted with host igvm again\n");
|
||||
}
|
||||
|
||||
close(ser_fd);
|
||||
guest_free(&qs->alloc, gaddr);
|
||||
pc_fw_cfg_uninit(fw_cfg);
|
||||
/* qtest_quit() kils QEMU, first by sending SIGTERM, then SIGKILL */
|
||||
qtest_quit(qs->qts);
|
||||
}
|
||||
|
||||
static void test_set_ctrl_once_and_reset_to_host_igvm(void)
|
||||
{
|
||||
const char *igvm_f;
|
||||
const char *igvm_init;
|
||||
int ser_fd;
|
||||
g_autofree void *igvm_blob = NULL;
|
||||
g_autofree char *serialtmp = NULL;
|
||||
const char *exp_out;
|
||||
const char *exp_out2;
|
||||
const char *exp_out3;
|
||||
const char *snp, *cgs;
|
||||
uint64_t gaddr;
|
||||
size_t igvm_sz;
|
||||
size_t filesize;
|
||||
QFWCFG *fw_cfg;
|
||||
QOSState *qs;
|
||||
VMLaunchUpdate launch_update;
|
||||
|
||||
if (!qtest_has_machine("q35")) {
|
||||
g_test_skip("q35 machine not available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!qtest_has_accel("kvm")) {
|
||||
g_test_skip("No KVM accelerator available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!qtest_has_device("vm-launch-update")) {
|
||||
g_test_skip("Device vm-launch-update is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
set_test_params(&igvm_f, &igvm_init, &snp, &cgs);
|
||||
set_expected_out(&exp_out, &exp_out2, &exp_out3);
|
||||
|
||||
if (!g_file_test(igvm_f, G_FILE_TEST_EXISTS) ||
|
||||
!g_file_test(igvm_init, G_FILE_TEST_EXISTS)) {
|
||||
g_test_skip("igvm file bundle(s) does not exist!");
|
||||
return;
|
||||
}
|
||||
|
||||
ser_fd = g_file_open_tmp("launchupdate-qtest-serial-sXXXXXX",
|
||||
&serialtmp, NULL);
|
||||
g_assert_true(ser_fd != -1);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "serial console file is %s\n", serialtmp);
|
||||
}
|
||||
|
||||
qs = set_qemu_args(cgs, NULL, serialtmp, igvm_init, snp);
|
||||
|
||||
fw_cfg = pc_fw_cfg_init(qs->qts);
|
||||
|
||||
g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 1) == 0);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "initially booted with host igvm\n");
|
||||
}
|
||||
|
||||
g_assert_true(load_image(igvm_f, &igvm_blob, &igvm_sz) == igvm_sz);
|
||||
|
||||
/* create a data buffer in guest memory */
|
||||
gaddr = guest_alloc(&qs->alloc, igvm_sz);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "guest paddr: %"PRIx64 " igvm size: %lu\n",
|
||||
gaddr, igvm_sz);
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "writing igvm file into the guest memory\n");
|
||||
}
|
||||
|
||||
qtest_bufwrite(qs->qts, gaddr, igvm_blob, igvm_sz);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr,
|
||||
"tell hypervisor where igvm is loaded in guest memory\n");
|
||||
}
|
||||
|
||||
/* now tell hypervisor where we loaded the bios */
|
||||
memset(&launch_update, 0, sizeof(launch_update));
|
||||
launch_update.fw_image_size = cpu_to_le64(igvm_sz);
|
||||
launch_update.fw_image_addr = cpu_to_le64(gaddr);
|
||||
|
||||
/* set both host ctrl and format_igvm ctrl once */
|
||||
launch_update.control |= VM_LAUNCHUPDATE_FORMAT_IGVM;
|
||||
launch_update.control |= VM_LAUNCHUPDATE_CTL_HOST_IGVM;
|
||||
|
||||
filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
|
||||
&launch_update, sizeof(launch_update));
|
||||
g_assert_cmpint(filesize, ==, sizeof(launch_update));
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "resetting the virtual machine. This should load "
|
||||
"user provided igvm.\n");
|
||||
}
|
||||
|
||||
qtest_system_reset(qs->qts);
|
||||
|
||||
/* expected string should be printed on the console */
|
||||
g_assert_true(wait_for_match(serialtmp, exp_out, WAIT_SEC, 1) == 0);
|
||||
g_assert_true(wait_for_match(serialtmp, exp_out2, WAIT_SEC, 1) == 0);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "hello world found on console\n");
|
||||
fprintf(stderr, "Now resetting again in order to reset to host igvm\n");
|
||||
}
|
||||
|
||||
qtest_system_reset(qs->qts);
|
||||
|
||||
/*
|
||||
* exp_out3 should be printed twice, once from initial boot,
|
||||
* once from restoring host igvm.
|
||||
*/
|
||||
g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 2) == 0);
|
||||
|
||||
if (debug) {
|
||||
fprintf(stderr, "booted with host igvm\n");
|
||||
}
|
||||
|
||||
close(ser_fd);
|
||||
guest_free(&qs->alloc, gaddr);
|
||||
pc_fw_cfg_uninit(fw_cfg);
|
||||
/* qtest_quit() kils QEMU, first by sending SIGTERM, then SIGKILL */
|
||||
qtest_quit(qs->qts);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *arch = qtest_get_arch();
|
||||
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
|
||||
if (strcmp(arch, "x86_64")) {
|
||||
g_test_skip("vmlaunchupdate tests are only available on x86_64\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_test_add_func("/vm-launch-update/cap", test_vm_launch_update_capability);
|
||||
g_test_add_func("/vm-launch-update/disabled",
|
||||
test_vm_launch_update_disable);
|
||||
|
||||
g_test_add_func("/vm-launch-update/errorcheck", check_error);
|
||||
g_test_add_func("/vm-launch-update/load_igvm",
|
||||
test_load_igvm);
|
||||
g_test_add_func("/vm-launch-update/ctrl_set_once",
|
||||
test_set_ctrl_once_and_reset_to_host_igvm);
|
||||
|
||||
if (getenv("LAUNCHUPDATE_DEBUG")) {
|
||||
debug = true;
|
||||
}
|
||||
if (getenv("LAUNCHUPDATE_TRACE")) {
|
||||
trace = true;
|
||||
}
|
||||
if (getenv("COCO")) {
|
||||
confidential = true;
|
||||
}
|
||||
|
||||
return g_test_run();
|
||||
}
|
||||
|
|
@ -61,6 +61,8 @@ qtests_i386 = \
|
|||
(config_all_devices.has_key('CONFIG_Q35') ? ['e820-test'] : []) + \
|
||||
(config_all_devices.has_key('CONFIG_FW_CFG_DMA') ? ['vmcoreinfo-test'] : []) + \
|
||||
(config_all_devices.has_key('CONFIG_Q35') ? ['dump-test'] : []) + \
|
||||
(igvm.found() and
|
||||
config_all_devices.has_key('CONFIG_FW_CFG_DMA') ? ['launchupdate-test'] : []) + \
|
||||
(config_all_devices.has_key('CONFIG_I440FX') ? ['i440fx-test'] : []) + \
|
||||
(config_all_devices.has_key('CONFIG_I440FX') ? ['ide-test'] : []) + \
|
||||
(config_all_devices.has_key('CONFIG_I440FX') ? ['numa-test'] : []) + \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue