diff --git a/hypervisor/Makefile b/hypervisor/Makefile index 9100ebf2a..a49015988 100644 --- a/hypervisor/Makefile +++ b/hypervisor/Makefile @@ -292,6 +292,8 @@ VP_BASE_C_SRCS += arch/x86/configs/pci_dev.c VP_BASE_C_SRCS += arch/x86/configs/vacpi.c VP_BASE_C_SRCS += quirks/acrn_vm_fixup.c +VP_BASE_C_SRCS += quirks/smbios.c + # virtual platform device model VP_DM_C_SRCS += dm/vpic.c VP_DM_C_SRCS += dm/vrtc.c diff --git a/hypervisor/arch/x86/guest/vm.c b/hypervisor/arch/x86/guest/vm.c index ef4e5a909..dddf10bc3 100644 --- a/hypervisor/arch/x86/guest/vm.c +++ b/hypervisor/arch/x86/guest/vm.c @@ -40,6 +40,7 @@ #include #include #include +#include /* Local variables */ @@ -149,6 +150,16 @@ bool is_nvmx_configured(const struct acrn_vm *vm) return ((vm_config->guest_flags & GUEST_FLAG_NVMX_ENABLED) != 0U); } +/** + * @pre vm != NULL && vm_config != NULL && vm->vmid < CONFIG_MAX_VM_NUM + */ +bool is_smbios_pt_configured(__unused const struct acrn_vm *vm) +{ + struct acrn_vm_config *vm_config = get_vm_config(vm->vm_id); + + return ((vm_config->guest_flags & GUEST_FLAG_SMBIOS_PASSTHROUGH) != 0U); +} + /** * @brief VT-d PI posted mode can possibly be used for PTDEVs assigned * to this VM if platform supports VT-d PI AND lapic passthru is not configured @@ -592,6 +603,8 @@ int32_t create_vm(uint16_t vm_id, uint64_t pcpu_bitmap, struct acrn_vm_config *v deny_hv_owned_devices(vm); } + try_smbios_passthrough(vm, get_acrn_boot_info()); + init_vpci(vm); enable_iommu(); diff --git a/hypervisor/include/arch/x86/asm/guest/vm.h b/hypervisor/include/arch/x86/asm/guest/vm.h index 74065db25..fa890c2f1 100644 --- a/hypervisor/include/arch/x86/asm/guest/vm.h +++ b/hypervisor/include/arch/x86/asm/guest/vm.h @@ -258,6 +258,7 @@ void vrtc_init(struct acrn_vm *vm); bool is_lapic_pt_configured(const struct acrn_vm *vm); bool is_rt_vm(const struct acrn_vm *vm); bool is_nvmx_configured(const struct acrn_vm *vm); +bool is_smbios_pt_configured(const struct acrn_vm *vm); bool is_pi_capable(const struct acrn_vm *vm); bool has_rt_vm(void); struct acrn_vm *get_highest_severity_vm(bool runtime); diff --git a/hypervisor/include/public/acrn_common.h b/hypervisor/include/public/acrn_common.h index ae4a26771..3f4e437bb 100644 --- a/hypervisor/include/public/acrn_common.h +++ b/hypervisor/include/public/acrn_common.h @@ -53,6 +53,7 @@ #define GUEST_FLAG_HIDE_MTRR (1UL << 3U) /* Whether hide MTRR from VM */ #define GUEST_FLAG_RT (1UL << 4U) /* Whether the vm is RT-VM */ #define GUEST_FLAG_NVMX_ENABLED (1UL << 5U) /* Whether this VM supports nested virtualization */ +#define GUEST_FLAG_SMBIOS_PASSTHROUGH (1UL << 6U) /* Whether to passthrough native SMBIOS information to this VM */ /* TODO: We may need to get this addr from guest ACPI instead of hardcode here */ #define VIRTUAL_SLEEP_CTL_ADDR 0x400U /* Pre-launched VM uses ACPI reduced HW mode and sleep control register */ diff --git a/hypervisor/quirks/smbios.c b/hypervisor/quirks/smbios.c new file mode 100644 index 000000000..62f452f41 --- /dev/null +++ b/hypervisor/quirks/smbios.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2021 Intel Corporation. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SMBIOS_TABLE_GUID {0xeb9d2d31, 0x2d88, 0x11d3, {0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}} +#define SMBIOS3_TABLE_GUID {0xf2fd1544, 0x9794, 0x4a2c, {0x99,0x2e,0xe5,0xbb,0xcf,0x20,0xe3,0x94}} + +struct smbios_entry_point { + char anchor[4]; /* "_SM_" */ + uint8_t checksum; + uint8_t length; /* length of this entry point structure, currently 1Fh */ + uint8_t major_ver; /* version major */ + uint8_t minor_ver; /* version minor */ + uint16_t max_struct_size; /* size of the largest SMBIOS structure */ + uint8_t ep_rev; /* entry point structure revision */ + uint8_t formatted[5]; + char int_anchor[5]; /* "_DMI_" */ + uint8_t int_checksum; /* intermediate checksum */ + uint16_t st_length; /* total length of SMBIOS structure table */ + uint32_t st_addr; /* structure table address */ + uint16_t nstructs; /* number of SMBIOS structures */ + uint8_t bcd_revision; /* BCD revision */ +} __attribute__((packed)); + +struct smbios3_entry_point { + char anchor[5]; /* "_SM3_" */ + uint8_t checksum; + uint8_t length; /* length of this entry point structure, currently 18h */ + uint8_t major_ver; + uint8_t minor_ver; + uint8_t docrev; + uint8_t ep_rev; + uint8_t reserved; + uint32_t max_st_size; /* max structure table size. The actual size is guaranteed to be <= this value. */ + uint64_t st_addr; /* structure table address */ +} __attribute__((packed)); + +typedef struct { + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; +} EFI_GUID; + +typedef struct _EFI_CONFIGURATION_TABLE { + EFI_GUID VendorGuid; + void *VendorTable; +} EFI_CONFIGURATION_TABLE; + +typedef struct _EFI_TABLE_HEADER { + uint64_t Signature; + uint32_t Revision; + uint32_t HeaderSize; + uint32_t CRC32; + uint32_t Reserved; +} EFI_TABLE_HEADER; + +typedef struct _EFI_SYSTEM_TABLE { + EFI_TABLE_HEADER Hdr; + uint16_t *FirmwareVendor; + uint32_t FirmwareRevision; + void *ConsoleInHandle; + void *ConIn; + void *ConsoleOutHandle; + void *ConOut; + void *StandardErrorHandle; + void *StdErr; + void *RuntimeServices; + void *BootServices; + uint64_t NumberOfTableEntries; + EFI_CONFIGURATION_TABLE *ConfigurationTable; +} EFI_SYSTEM_TABLE; + +union { + struct smbios_entry_point eps; + struct smbios3_entry_point eps3; +} smbios_eps; +size_t smbios_eps_size; +void *smbios_table; +size_t smbios_table_size; + +static int memcmp(uint8_t *s1, uint8_t *s2, size_t len) +{ + while (len-- > 0) { + if (*s1++ != *s2++) { + return s1[-1] < s2[-1] ? -1 : 1; + } + } + return 0; +} + +static void *detect_smbios_eps(EFI_SYSTEM_TABLE *efi_system_tab, EFI_GUID *target_guid) +{ + uint64_t i; + void *smbios_eps = NULL; + + /* search EFI system table -> configuration table */ + for (i = 0; i < efi_system_tab->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *conf_tab = &efi_system_tab->ConfigurationTable[i]; + EFI_GUID guid = conf_tab->VendorGuid; + + if (!memcmp((uint8_t *)&guid, (uint8_t *)target_guid, sizeof(EFI_GUID))) { + smbios_eps = hpa2hva((uint64_t)conf_tab->VendorTable); + break; + } + } + + /* We don't search for legacy 0xf0000~0xfffff region because UEFI environment is assumed. */ + return smbios_eps; +} + +static void smbios_table_probe(struct acrn_boot_info *abi) +{ + void *p; + EFI_GUID smbios3_guid = SMBIOS3_TABLE_GUID; + EFI_GUID smbios_guid = SMBIOS_TABLE_GUID; + uint64_t efi_system_tab_paddr = (uint64_t)abi->efi_info.system_table; + EFI_SYSTEM_TABLE *efi_system_tab = (EFI_SYSTEM_TABLE *)hpa2hva(efi_system_tab_paddr); + + stac(); + /* If both are present, SMBIOS3 takes precedence over SMBIOS */ + p = detect_smbios_eps(efi_system_tab, &smbios3_guid); + if (p) { + struct smbios3_entry_point *eps = (struct smbios3_entry_point *)p; + smbios_eps_size = eps->length; + memcpy_s(&smbios_eps, smbios_eps_size, eps, smbios_eps_size); + smbios_table = hpa2hva(eps->st_addr); + smbios_table_size = eps->max_st_size; + } else { + p = detect_smbios_eps(efi_system_tab, &smbios_guid); + if (p) { + struct smbios_entry_point *eps = (struct smbios_entry_point *)p; + smbios_eps_size = eps->length; + memcpy_s(&smbios_eps, smbios_eps_size, eps, smbios_eps_size); + smbios_table = hpa2hva(eps->st_addr); + smbios_table_size = eps->st_length; + } + } + clac(); +} + +/* Recalculate checksum of n bytes starting from byte_start, and write checksum to checksum_pos */ +static void recalc_checksum(uint8_t *byte_start, uint8_t nbytes, uint8_t *checksum_pos) +{ + int i; + uint8_t sum = 0; + for (i = 0; i < nbytes; i++) { + if (byte_start + i != checksum_pos) { + sum += byte_start[i]; + } + } + *checksum_pos = -sum; +} + +static int copy_smbios_to_guest(struct acrn_vm *vm) +{ + uint64_t gpa; + int ret = -1; + + if (smbios_eps_size) { + /* SMBIOS (32-bit entry point) requires the table to be below 4GB, whereas the + * SMBIOS3 (64-bit entry point) requires the table to be in anywhere in 64-bit + * memory address. So we put it below 4GB. + * Note that find_space_from_ve820 does not mark space as reserved as it gives out + * address ranges, so we need to check with other usages of this API (basically + * kernel and module loading). + */ + uint64_t klend = (uint64_t)vm->sw.kernel_info.kernel_load_addr + vm->sw.kernel_info.kernel_size + MEM_2M; + uint64_t mlend = max((uint64_t)vm->sw.ramdisk_info.load_addr + vm->sw.ramdisk_info.size, MEM_1M); + uint64_t high = max(klend, mlend); + gpa = high + 0x1000; + if (gpa != INVALID_GPA) { + ret = copy_to_gpa(vm, smbios_table, gpa, smbios_table_size); + if (ret == 0) { + if (smbios_eps.eps.anchor[3] == '_') { + /* SMBIOS (_SM_) */ + struct smbios_entry_point *eps = &smbios_eps.eps; + eps->st_addr = (uint32_t)gpa; + /* the intermediate checksum covers the structure table field */ + recalc_checksum((uint8_t *)eps->int_anchor, 0xf, &eps->int_checksum); + } else { + /* SMBIOS3 (_SM3_) */ + struct smbios3_entry_point *eps3 = &smbios_eps.eps3; + eps3->st_addr = (uint32_t)gpa; + recalc_checksum((uint8_t *)eps3, eps3->length, &eps3->checksum); + } + + /* The SMBIOS entry point structure should reside in between 0xf0000~0xfffff */ + /* The rsdp starts from 0xf2400 so 0xf1000 should be OK. This structure is at most 31 bytes. */ + gpa = 0xf1000UL; + ret = copy_to_gpa(vm, &smbios_eps, gpa, smbios_eps_size); + } + } + } + + return ret; +} + +void try_smbios_passthrough(struct acrn_vm *vm, struct acrn_boot_info *abi) +{ + /* TODO: Add config and guest flag to disable it by default */ + if (is_prelaunched_vm(vm)) { + smbios_table_probe(abi); + (void)copy_smbios_to_guest(vm); + } +} diff --git a/hypervisor/quirks/smbios.h b/hypervisor/quirks/smbios.h new file mode 100644 index 000000000..fb649fdf2 --- /dev/null +++ b/hypervisor/quirks/smbios.h @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2021 Intel Corporation. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef QUIRKS_SMBIOS_H +#define QUIRKS_SMBIOS_H + +void try_smbios_passthrough(struct acrn_vm *vm, struct acrn_boot_info *abi); + +#endif