From 25bd8e84516f9b2500f541213194107213f0c267 Mon Sep 17 00:00:00 2001 From: Sergiu Moga Date: Tue, 21 Mar 2023 15:31:02 +0200 Subject: [PATCH] plat/kvm: Implement architecture generic EFI stub Add an architecture generic EFI stub that sets up a `struct ukplat_bootinfo`'s memory region descriptors, `bootloader` and `bootprotocol` fields. Furthermore, the memory region descriptors corresponding to the UEFI `Runtime Services` are gathered separately, through UEFI's `Memory Attribute Table`, since they have to be treated differently, in order for the `Runtime Services` to be used properly after `exit_boot_services`. At the end, the stub calls `uk_efi_jmp_to_kern` which each architecture is supposed to independently implement the remaining setup for its platform. Signed-off-by: Sergiu Moga Reviewed-by: Michalis Pappas Approved-by: Razvan Deaconescu Tested-by: Unikraft CI GitHub-Closes: #909 --- plat/kvm/Config.uk | 8 + plat/kvm/efi.c | 375 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 383 insertions(+) create mode 100644 plat/kvm/efi.c diff --git a/plat/kvm/Config.uk b/plat/kvm/Config.uk index 8fe2ae52f..27255f14a 100644 --- a/plat/kvm/Config.uk +++ b/plat/kvm/Config.uk @@ -39,6 +39,14 @@ config KVM_BOOT_EFI_STUB endchoice +if KVM_BOOT_EFI_STUB + +config KVM_BOOT_EFI_STUB_DEBUG + bool "Enable EFI stub crash messages" + default n + +endif + choice prompt "Virtual Machine Monitor" default KVM_VMM_QEMU diff --git a/plat/kvm/efi.c b/plat/kvm/efi.c new file mode 100644 index 000000000..adbb6dea7 --- /dev/null +++ b/plat/kvm/efi.c @@ -0,0 +1,375 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* Copyright (c) 2023, Unikraft GmbH and The Unikraft Authors. + * Licensed under the BSD-3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + */ +#include +#include + +static struct uk_efi_runtime_services *uk_efi_rs; +static struct uk_efi_boot_services *uk_efi_bs; +static struct uk_efi_sys_tbl *uk_efi_st; +static uk_efi_hndl_t uk_efi_sh; + +static uk_efi_uintn_t uk_efi_map_key; +static __u8 uk_efi_mat_present; + +/* As per UEFI specification, the call to the GetMemoryMap routine following + * the dummy one, must have a surplus amount of memory region descriptors in + * size. Usually, 2 to 4 is enough, but allocate 10, just in case. + */ +#define UK_EFI_SURPLUS_MEM_DESC_COUNT 10 + +void uk_efi_jmp_to_kern(void) __noreturn; + +/* Overlysimplified conversion from ASCII to UTF-16 */ +static __sz ascii_to_utf16(const char *str, char *str16, __sz max_len16) +{ + __sz i = 0; + + while (str[i >> 1]) { + if (unlikely(i == max_len16)) + return __SZ_MAX; + + str16[i] = str[i >> 1]; + str16[i + 1] = '\0'; + i += 2; + } + + str16[i] = str16[i + 1] = '\0'; + + return i + 2; +} + +/* Overlysimplified conversion from UTF-16 to ASCII */ +static __sz utf16_to_ascii(const char *str16, char *str, __sz max_len) +{ + __sz i = 0; + + while (*str16) { + if (unlikely(i == max_len)) + return __SZ_MAX; + + str[i++] = *str16; + str16 += 2; + } + + str[i] = '\0'; + + return i + 1; +} + +static void _uk_efi_crash(void) +{ + const char reset_data[] = "UK EFI SYSTEM CRASH"; + + uk_efi_rs->reset_system(UK_EFI_RESET_SHUTDOWN, UK_EFI_SUCCESS, + sizeof(reset_data), (void *)reset_data); +} + +#ifdef CONFIG_KVM_BOOT_EFI_STUB_DEBUG +#define UK_EFI_MAX_CRASH_STR_LEN 256 +/* UEFI for proper \n, we must also use CRLF */ +#define uk_efi_crash(str) \ + do { \ + __s16 str16[UK_EFI_MAX_CRASH_STR_LEN]; \ + \ + ascii_to_utf16("[uk_efi]: "str"\r", (char *)str16, \ + UK_EFI_MAX_CRASH_STR_LEN - 1); \ + uk_efi_st->con_out->output_string(uk_efi_st->con_out, \ + str16); \ + _uk_efi_crash(); \ + } while (0) +#else +#define uk_efi_crash(str) _uk_efi_crash() +#endif + +static void uk_efi_cls(void) +{ + uk_efi_st->con_out->clear_screen(uk_efi_st->con_out); +} + +/* Initialize global variables */ +static void uk_efi_init_vars(uk_efi_hndl_t self_hndl, + struct uk_efi_sys_tbl *sys_tbl) +{ + uk_efi_st = sys_tbl; + uk_efi_bs = sys_tbl->boot_services; + uk_efi_rs = sys_tbl->runtime_services; + uk_efi_sh = self_hndl; +} + +/* Convert an EFI Memory Descriptor to a ukplat_memregion_desc */ +static int uk_efi_md_to_bi_mrd(struct uk_efi_mem_desc *const md, + struct ukplat_memregion_desc *const mrd) +{ + __paddr_t start, end; + + switch (md->type) { + case UK_EFI_RESERVED_MEMORY_TYPE: + case UK_EFI_ACPI_RECLAIM_MEMORY: + case UK_EFI_UNUSABLE_MEMORY: + case UK_EFI_ACPI_MEMORY_NVS: + case UK_EFI_PAL_CODE: + case UK_EFI_PERSISTENT_MEMORY: + mrd->type = UKPLAT_MEMRT_RESERVED; + mrd->flags = UKPLAT_MEMRF_READ | UKPLAT_MEMRF_MAP; + + break; + case UK_EFI_MEMORY_MAPPED_IO: + case UK_EFI_MEMORY_MAPPED_IO_PORT_SPACE: + mrd->type = UKPLAT_MEMRT_RESERVED; + mrd->flags = UKPLAT_MEMRF_READ | UKPLAT_MEMRF_WRITE | + UKPLAT_MEMRF_MAP; + + break; + case UK_EFI_RUNTIME_SERVICES_CODE: + case UK_EFI_RUNTIME_SERVICES_DATA: + /* Already added through uk_efi_rt_md_to_bi_mrds() if a MAT + * has been found, dictated by whether uk_efi_mat_present != 0. + * Otherwise, add these instead. + */ + if (uk_efi_mat_present) + return -EEXIST; + + mrd->type = UKPLAT_MEMRT_RESERVED; + /* A MAT would have provided us with proper, high granularity + * memory attributes, but now we cannot be sure of anything as + * Runtime Services related memory descriptors usually have + * useless and inaccurate flags. Therefore, just give all + * permissions to avoid crashes generated by explicit firmware + * calls. + */ + mrd->flags = UKPLAT_MEMRF_READ | UKPLAT_MEMRF_WRITE | + UKPLAT_MEMRF_MAP; + + break; + case UK_EFI_LOADER_CODE: + case UK_EFI_LOADER_DATA: + /* Already added through mkbootinfo.py and relocated through + * do_uk_reloc + */ + return -EEXIST; + case UK_EFI_BOOT_SERVICES_CODE: + case UK_EFI_BOOT_SERVICES_DATA: + case UK_EFI_CONVENTIONAL_MEMORY: + /* These are freed after ExitBootServices is called */ + mrd->type = UKPLAT_MEMRT_FREE; + + mrd->flags = UKPLAT_MEMRF_READ | UKPLAT_MEMRF_WRITE; + + break; + default: + /* Memory type unknown */ + return -EINVAL; + } + + /* Ignore zero-page */ + start = MAX(md->physical_start, __PAGE_SIZE); + end = md->physical_start + md->number_of_pages * UK_EFI_PAGE_SIZE; + if (unlikely(end <= start || end - start < __PAGE_SIZE)) + return -ENOMEM; + + mrd->pbase = start; + mrd->vbase = start; + mrd->len = end - start; + + return 0; +} + +static void uk_efi_get_mmap(struct uk_efi_mem_desc **map, uk_efi_uintn_t *map_sz, + uk_efi_uintn_t *desc_sz) +{ + uk_efi_status_t status; + __u32 desc_ver; + + /* As the UEFI Spec says: + * If the MemoryMap buffer is too small, the EFI_BUFFER_TOO_SMALL + * error code is returned and the MemoryMapSize value contains the + * size of the buffer needed to contain the current memory map. The + * actual size of the buffer allocated for the consequent call to + * GetMemoryMap() should be bigger then the value returned in + * MemoryMapSize, since allocation of the new buffer may potentially + * increase memory map size. + */ + *map_sz = 0; /* force EFI_BUFFER_TOO_SMALL */ + *map = NULL; + status = uk_efi_bs->get_memory_map(map_sz, *map, &uk_efi_map_key, + desc_sz, &desc_ver); + if (unlikely(status != UK_EFI_BUFFER_TOO_SMALL)) + uk_efi_crash("Failed to call initial dummy get_memory_map\n"); + + /* Make sure the actual allocated buffer is bigger */ + *map_sz += *desc_sz * UK_EFI_SURPLUS_MEM_DESC_COUNT; + status = uk_efi_bs->allocate_pool(UK_EFI_LOADER_DATA, *map_sz, + (void **)map); + if (unlikely(status != UK_EFI_SUCCESS)) + uk_efi_crash("Failed to allocate memory for map\n"); + + /* Now we call it for real */ + status = uk_efi_bs->get_memory_map(map_sz, *map, &uk_efi_map_key, + desc_sz, &desc_ver); + if unlikely((status != UK_EFI_SUCCESS)) + uk_efi_crash("Failed to get memory map\n"); +} + +/* Runtime Services memory regions in the Memory Attribute Table have a higher + * granularity regarding sizes and permissions: the ones resulted from + * GetMemoryMap only differentiate between Runtime Services Data/Code, while + * the MAT also differentiates between permissions of the Runtime Services' + * PE sections (Runtime Services can basically be thought of as loaded Portable + * Executable format drivers). + * + * NOTE: Apparently, MAT is somewhat optional, so if none is found, we fallback + * on the Runtime Services memory descriptors we got from GetMemoryMap(). + */ +static void uk_efi_rt_md_to_bi_mrds(struct ukplat_memregion_desc **rt_mrds, + __u32 *const rt_mrds_count) +{ + struct uk_efi_mem_attr_tbl *mat = NULL; + struct ukplat_memregion_desc *rt_mrd; + struct uk_efi_mem_desc *mat_md; + struct uk_efi_cfg_tbl *ct; + uk_efi_status_t status; + uk_efi_uintn_t i; + __sz desc_sz; + + /* Search for the MAT in UEFI System Table's Configuration Tables */ + for (i = 0; i < uk_efi_st->number_of_table_entries; i++) { + ct = &uk_efi_st->configuration_table[i]; + + if (!memcmp(&ct->vendor_guid, + UK_EFI_MEMORY_ATTRIBUTES_TABLE_GUID, + sizeof(ct->vendor_guid))) { + mat = ct->vendor_table; + uk_efi_mat_present = 1; + + break; + } + } + if (!mat) + return; + + desc_sz = mat->descriptor_size; + *rt_mrds_count = mat->number_of_entries; + status = uk_efi_bs->allocate_pool(UK_EFI_LOADER_DATA, + *rt_mrds_count * sizeof(**rt_mrds), + (void **)rt_mrds); + if (unlikely(status != UK_EFI_SUCCESS)) + uk_efi_crash("Failed to allocate memory for Memory Sub-region Descriptors\n"); + + /* Convert the EFI Runtime Services Memory descriptors to + * ukplat_memregion_desc's + */ + mat_md = (struct uk_efi_mem_desc *)mat->entry; + for (i = 0; i < *rt_mrds_count; i++) { + if (!(mat_md->attribute & UK_EFI_MEMORY_RUNTIME)) + continue; + + rt_mrd = *rt_mrds + i; + rt_mrd->pbase = mat_md->physical_start; + rt_mrd->len = mat_md->number_of_pages * UK_EFI_PAGE_SIZE; + rt_mrd->vbase = rt_mrd->pbase; + rt_mrd->type = UKPLAT_MEMRT_RESERVED; + rt_mrd->flags = UKPLAT_MEMRF_MAP; + if (mat_md->attribute & UK_EFI_MEMORY_XP) + if (mat_md->attribute & UK_EFI_MEMORY_RO) + rt_mrd->flags |= UKPLAT_MEMRF_READ; + else + rt_mrd->flags |= UKPLAT_MEMRF_READ | + UKPLAT_MEMRF_WRITE; + else + rt_mrd->flags |= UKPLAT_MEMRF_READ | + UKPLAT_MEMRF_EXECUTE; + + mat_md = (struct uk_efi_mem_desc *)((__u8 *)mat_md + desc_sz); + } +} + +static void uk_efi_setup_bootinfo_mrds(struct ukplat_bootinfo *bi) +{ + struct ukplat_memregion_desc mrd = {0}, *rt_mrds; + struct uk_efi_mem_desc *map_start, *map_end, *md; + uk_efi_uintn_t map_sz, desc_sz; + __u32 rt_mrds_count = 0, i; + uk_efi_status_t status; + int rc; + +#if defined(__X86_64__) + rc = ukplat_memregion_list_insert_legacy_hi_mem(&bi->mrds); + if (unlikely(rc < 0)) + uk_efi_crash("Failed to insert legacy high memory region\n"); +#endif + + /* Fetch the Runtime Services memory regions from the MAT */ + uk_efi_rt_md_to_bi_mrds(&rt_mrds, &rt_mrds_count); + for (i = 0; i < rt_mrds_count; i++) { + rc = ukplat_memregion_list_insert(&bi->mrds, &rt_mrds[i]); + if (unlikely(rc < 0)) + uk_efi_crash("Failed to insert rt_mrd\n"); + } + + /* We no longer need the list of Runtime Services memory regions */ + status = uk_efi_bs->free_pool(rt_mrds); + if (unlikely(status != UK_EFI_SUCCESS)) + uk_efi_crash("Failed to free rt_mrds\n"); + + /* Get memory map through GetMemoryMap */ + uk_efi_get_mmap(&map_start, &map_sz, &desc_sz); + + map_end = (struct uk_efi_mem_desc *)((__u8 *)map_start + map_sz); + for (md = map_start; md < map_end; + md = (struct uk_efi_mem_desc *)((__u8 *)md + desc_sz)) { + if (uk_efi_md_to_bi_mrd(md, &mrd) < 0) + continue; + + rc = ukplat_memregion_list_insert(&bi->mrds, &mrd); + if (unlikely(rc < 0)) + uk_efi_crash("Failed to insert mrd\n"); + } + + ukplat_memregion_list_coalesce(&bi->mrds); + +#if defined(__X86_64__) + ukplat_memregion_alloc_sipi_vect(); +#endif +} + +static void uk_efi_setup_bootinfo(void) +{ + const char bl[] = "EFI_STUB"; + struct ukplat_bootinfo *bi; + const char bp[] = "EFI"; + + bi = ukplat_bootinfo_get(); + if (unlikely(!bi)) + uk_efi_crash("Failed to get bootinfo\n"); + + memcpy(bi->bootloader, bl, sizeof(bl)); + memcpy(bi->bootprotocol, bp, sizeof(bp)); + + uk_efi_setup_bootinfo_mrds(bi); + + bi->efi_st = (__u64)uk_efi_st; +} + +static void uk_efi_exit_bs(void) +{ + uk_efi_status_t status; + + status = uk_efi_bs->exit_boot_services(uk_efi_sh, uk_efi_map_key); + if (unlikely(status != UK_EFI_SUCCESS)) + uk_efi_crash("Failed to to exit Boot Services\n"); +} + +void __uk_efi_api __noreturn uk_efi_main(uk_efi_hndl_t self_hndl, + struct uk_efi_sys_tbl *sys_tbl) +{ + uk_efi_init_vars(self_hndl, sys_tbl); + uk_efi_cls(); + uk_efi_setup_bootinfo(); + uk_efi_exit_bs(); + + /* Jump to arch specific post-EFI entry */ + uk_efi_jmp_to_kern(); +} -- 2.39.5