From 695e09a194414e77ba4b0e70c59cfe68389625df Mon Sep 17 00:00:00 2001 From: Victor Sun Date: Mon, 7 Jun 2021 11:25:18 +0800 Subject: [PATCH] HV: relocate SOS kernel before boot modules for SBL SBL would load multiboot modules to the maximum usable ram below 4GB, in this case the SOS kernel would be failed to load because no room for kernel in the range of end of modules and MEM_4G. So kernel must be relocated to the range of MEM_1M and start of modules, or other appropriate place according to modules layout. Tracked-On: #6162 Signed-off-by: Victor Sun --- hypervisor/boot/boot.c | 21 +++++ hypervisor/boot/guest/vboot_info.c | 119 +++++++++++++++-------------- hypervisor/boot/include/boot.h | 2 + 3 files changed, 86 insertions(+), 56 deletions(-) diff --git a/hypervisor/boot/boot.c b/hypervisor/boot/boot.c index 919fb7662..ec3c4e672 100644 --- a/hypervisor/boot/boot.c +++ b/hypervisor/boot/boot.c @@ -14,6 +14,27 @@ static struct acrn_boot_info acrn_bi = { 0U }; static char boot_protocol_name[16U] = { 0 }; +/** + * @pre (p_start != NULL) && (p_end != NULL) + */ +void get_boot_mods_range(uint64_t *p_start, uint64_t *p_end) +{ + uint32_t i; + uint64_t start = ~0UL, end = 0UL; + struct acrn_boot_info *abi = get_acrn_boot_info(); + + for (i = 0; i < abi->mods_count; i++) { + if (hva2hpa(abi->mods[i].start) < start) { + start = hva2hpa(abi->mods[i].start); + } + if (hva2hpa(abi->mods[i].start + abi->mods[i].size) > end) { + end = hva2hpa(abi->mods[i].start + abi->mods[i].size); + } + } + *p_start = start; + *p_end = end; +} + void init_acrn_boot_info(uint32_t *registers) { if (init_multiboot_info(registers) == 0) { diff --git a/hypervisor/boot/guest/vboot_info.c b/hypervisor/boot/guest/vboot_info.c index 376ec3656..ca8c262d0 100644 --- a/hypervisor/boot/guest/vboot_info.c +++ b/hypervisor/boot/guest/vboot_info.c @@ -28,12 +28,17 @@ * but this should be configurable for different OS. */ #define DEFAULT_RAMDISK_GPA_MAX 0x37ffffffUL +#define PRE_VM_MAX_RAM_ADDR_BELOW_4GB (VIRT_ACPI_DATA_ADDR - 1U) + /** * @pre vm != NULL && mod != NULL */ static void init_vm_ramdisk_info(struct acrn_vm *vm, const struct abi_module *mod) { uint64_t ramdisk_load_gpa = INVALID_GPA; + uint64_t ramdisk_gpa_max = DEFAULT_RAMDISK_GPA_MAX; + uint64_t kernel_start = (uint64_t)vm->sw.kernel_info.kernel_load_addr; + uint64_t kernel_end = kernel_start + vm->sw.kernel_info.kernel_size; struct acrn_vm_config *vm_config = get_vm_config(vm->vm_id); if (mod->start != NULL) { @@ -41,10 +46,30 @@ static void init_vm_ramdisk_info(struct acrn_vm *vm, const struct abi_module *mo vm->sw.ramdisk_info.size = mod->size; } - if (is_sos_vm(vm)) { + /* Per Linux boot protocol, the Kernel need a size of contiguous + * memory(i.e. init_size field in zeropage) from its extract address to boot, + * and initrd_addr_max field specifies the maximum address of the ramdisk. + * Per kernel src head_64.S, decompressed kernel start at 2M aligned to the + * compressed kernel load address. + */ + if (vm->sw.kernel_type == KERNEL_BZIMAGE) { struct zero_page *zeropage = (struct zero_page *)vm->sw.kernel_info.kernel_src_addr; + uint32_t kernel_init_size = zeropage->hdr.init_size; uint32_t initrd_addr_max = zeropage->hdr.initrd_addr_max; + kernel_end = kernel_start + MEM_2M + kernel_init_size; + if (initrd_addr_max != 0U) { + ramdisk_gpa_max = initrd_addr_max; + } + } + + if (is_sos_vm(vm)) { + uint64_t mods_start, mods_end; + + get_boot_mods_range(&mods_start, &mods_end); + mods_start = sos_vm_hpa2gpa(mods_start); + mods_end = sos_vm_hpa2gpa(mods_end); + if (vm->sw.ramdisk_info.src_addr != NULL) { ramdisk_load_gpa = sos_vm_hpa2gpa((uint64_t)vm->sw.ramdisk_info.src_addr); } @@ -52,46 +77,33 @@ static void init_vm_ramdisk_info(struct acrn_vm *vm, const struct abi_module *mo /* For SOS VM, the ramdisk has been loaded by bootloader, so in most cases * there is no need to do gpa copy again. But in the case that the ramdisk is * loaded by bootloader at a address higher than its limit, we should do gpa - * copy then. Given the kernel is relocated to after all modules, we find - * the space from MEM_1M to min(ramdisk_load_gpa, initrd_addr_max). + * copy then. */ - if ((ramdisk_load_gpa + vm->sw.ramdisk_info.size) > initrd_addr_max) { + if ((ramdisk_load_gpa + vm->sw.ramdisk_info.size) > ramdisk_gpa_max) { + /* In this case, mods_end must be higher than ramdisk_gpa_max, + * so try to locate ramdisk between MEM_1M and mods_start/kernel_start, + * or try the range between kernel_end and mods_start; + */ ramdisk_load_gpa = find_space_from_ve820(vm, vm->sw.ramdisk_info.size, - MEM_1M, min(ramdisk_load_gpa, initrd_addr_max)); - } - } else { - /* For pre-launched VM, the ramdisk would be put after guest kernel. - * Its GPA should be got by searching ve820 table. - */ - uint64_t kernel_end, ramdisk_gpa_max; - - kernel_end = (uint64_t)(vm->sw.kernel_info.kernel_load_addr + vm->sw.kernel_info.kernel_size); - ramdisk_gpa_max = min((VIRT_ACPI_DATA_ADDR - 1U), DEFAULT_RAMDISK_GPA_MAX); - - /* Per Linux boot protocol, the Kernel need a size of contiguous - * memory(i.e. init_size field in zeropage) from its extract address to boot, - * and initrd_addr_max field specifies the maximum address of the ramdisk. - * Per kernel src head_64.S, decompressed kernel start at 2M aligned to the - * compressed kernel load address. - */ - if (vm->sw.kernel_type == KERNEL_BZIMAGE) { - struct zero_page *zeropage = (struct zero_page *)vm->sw.kernel_info.kernel_src_addr; - uint32_t kernel_init_size = zeropage->hdr.init_size; - uint32_t initrd_addr_max = zeropage->hdr.initrd_addr_max; - - kernel_end = (uint64_t)(vm->sw.kernel_info.kernel_load_addr + MEM_2M + kernel_init_size); - if (initrd_addr_max != 0U) { - ramdisk_gpa_max = min((VIRT_ACPI_DATA_ADDR - 1U), initrd_addr_max); + MEM_1M, min(min(mods_start, kernel_start), ramdisk_gpa_max)); + if ((ramdisk_load_gpa == INVALID_GPA) && (kernel_end < min(mods_start, ramdisk_gpa_max))) { + ramdisk_load_gpa = find_space_from_ve820(vm, vm->sw.ramdisk_info.size, + kernel_end, min(mods_start, ramdisk_gpa_max)); } } + } else { + /* For pre-launched VM, the ramdisk would be put by searching ve820 table. + */ + ramdisk_gpa_max = min(PRE_VM_MAX_RAM_ADDR_BELOW_4GB, ramdisk_gpa_max); - if (kernel_end > ramdisk_gpa_max) { - ramdisk_load_gpa = find_space_from_ve820(vm, vm->sw.ramdisk_info.size, - MEM_1M, (uint64_t)vm->sw.kernel_info.kernel_load_addr); - } else { + if (kernel_end < ramdisk_gpa_max) { ramdisk_load_gpa = find_space_from_ve820(vm, vm->sw.ramdisk_info.size, kernel_end, ramdisk_gpa_max); } + if (ramdisk_load_gpa == INVALID_GPA) { + ramdisk_load_gpa = find_space_from_ve820(vm, vm->sw.ramdisk_info.size, + MEM_1M, min(kernel_start, ramdisk_gpa_max)); + } } if (ramdisk_load_gpa == INVALID_GPA) { @@ -122,20 +134,6 @@ static void init_vm_acpi_info(struct acrn_vm *vm, const struct abi_module *mod) vm->sw.acpi_info.size = ACPI_MODULE_SIZE; } -static uint64_t get_boot_mods_end_addr(void) -{ - uint32_t i; - uint64_t addr = 0UL; - struct acrn_boot_info *abi = get_acrn_boot_info(); - - for (i = 0; i < abi->mods_count; i++) { - if (hva2hpa(abi->mods[i].start + abi->mods[i].size) > addr) { - addr = hva2hpa(abi->mods[i].start + abi->mods[i].size); - } - } - return addr; -} - /** * @pre vm != NULL */ @@ -157,20 +155,29 @@ static void *get_kernel_load_addr(struct acrn_vm *vm) zeropage = (struct zero_page *)sw_info->kernel_info.kernel_src_addr; if ((is_sos_vm(vm)) && (zeropage->hdr.relocatable_kernel != 0U)) { + uint64_t mods_start, mods_end; + uint64_t kernel_load_gpa = INVALID_GPA; uint32_t kernel_align = zeropage->hdr.kernel_alignment; uint32_t kernel_init_size = zeropage->hdr.init_size; - uint64_t hv_end_addr, mods_end_addr, kernel_load_gpa; - - hv_end_addr = sos_vm_hpa2gpa(get_hv_image_base() + CONFIG_HV_RAM_SIZE); - mods_end_addr = sos_vm_hpa2gpa(get_boot_mods_end_addr()); - - /* The kernel will be put after hypervisor and all boot modules before 4GB. - * Because the kernel load address need to be up aligned to kernel_align size + /* Because the kernel load address need to be up aligned to kernel_align size * whereas find_space_from_ve820() can only return page aligned address, * we enlarge the needed size to (kernel_init_size + 2 * kernel_align). */ - kernel_load_gpa = find_space_from_ve820(vm, (kernel_init_size + 2 * kernel_align), - max(hv_end_addr, mods_end_addr), MEM_4G); + uint32_t kernel_size = kernel_init_size + 2 * kernel_align; + + get_boot_mods_range(&mods_start, &mods_end); + mods_start = sos_vm_hpa2gpa(mods_start); + mods_end = sos_vm_hpa2gpa(mods_end); + + /* TODO: support load kernel when modules are beyond 4GB space. */ + if (mods_end < MEM_4G) { + kernel_load_gpa = find_space_from_ve820(vm, kernel_size, MEM_1M, mods_start); + + if (kernel_load_gpa == INVALID_GPA) { + kernel_load_gpa = find_space_from_ve820(vm, kernel_size, mods_end, MEM_4G); + } + } + if (kernel_load_gpa != INVALID_GPA) { load_addr = (void *)roundup((uint64_t)kernel_load_gpa, kernel_align); } diff --git a/hypervisor/boot/include/boot.h b/hypervisor/boot/include/boot.h index 6c89a5bfd..117f9caf0 100644 --- a/hypervisor/boot/include/boot.h +++ b/hypervisor/boot/include/boot.h @@ -70,6 +70,8 @@ static inline bool boot_from_uefi(struct acrn_boot_info *abi) return (abi->efi_info.system_table != NULL); } +void get_boot_mods_range(uint64_t *p_start, uint64_t *p_end); + int32_t init_multiboot_info(uint32_t *registers); int32_t init_multiboot2_info(uint32_t *registers);