acrn-hypervisor/hypervisor/boot/guest/vboot_info.c
Victor Sun db690e0967 HV: enable multiboot module string as kernel bootargs
Previously the VM kernel bootargs for pre-launched VMs and direct boot mode
of SOS VM are built-in hypervisor binary so end users have no way to change
it. Now we provide another option that the multiboot module string could be
used as bootargs also. This would bring convenience to end users when they
use GRUB as bootloader because the bootargs could be configurable in GRUB
menu.

The usage is if there is any string follows configured kernel_mod_tag in
module string, the string will be used as new kernel bootargs instead of
built-in kernel bootargs. If there is no string follows kernel_mod_tag,
then the built-in bootargs will be the default kernel bootargs.

Please note kernel_mod_tag must be the first word in module string in any
case, it is used to specify the module for which VM.

Tracked-On: #4885

Signed-off-by: Victor Sun <victor.sun@intel.com>
Reviewed-by: Yin Fengwei <fengwei.yin@intel.com>
Reviewed-by: Eddie Dong <eddie.dong@intel.com>
2020-06-08 13:30:04 +08:00

312 lines
9.6 KiB
C

/*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <types.h>
#include <rtl.h>
#include <errno.h>
#include <per_cpu.h>
#include <irq.h>
#include <boot_context.h>
#include <boot.h>
#include <pgtable.h>
#include <zeropage.h>
#include <seed.h>
#include <mmu.h>
#include <vm.h>
#include <logmsg.h>
#include <deprivilege_boot.h>
#include <vboot_info.h>
#define DBG_LEVEL_BOOT 6U
#define INVALID_MOD_IDX 0xFFFFU
/**
* @pre vm != NULL && mbi != NULL
*/
static void init_vm_ramdisk_info(struct acrn_vm *vm, const struct multiboot_module *mod)
{
void *mod_addr = hpa2hva((uint64_t)mod->mm_mod_start);
if ((mod_addr != NULL) && (mod->mm_mod_end > mod->mm_mod_start)) {
vm->sw.ramdisk_info.src_addr = mod_addr;
vm->sw.ramdisk_info.load_addr = vm->sw.kernel_info.kernel_load_addr + vm->sw.kernel_info.kernel_size;
vm->sw.ramdisk_info.load_addr = (void *)round_page_up((uint64_t)vm->sw.ramdisk_info.load_addr);
vm->sw.ramdisk_info.size = mod->mm_mod_end - mod->mm_mod_start;
}
}
/**
* @pre vm != NULL
*/
static void *get_kernel_load_addr(struct acrn_vm *vm)
{
void *load_addr = NULL;
struct vm_sw_info *sw_info = &vm->sw;
struct zero_page *zeropage;
struct acrn_vm_config *vm_config = get_vm_config(vm->vm_id);
switch (sw_info->kernel_type) {
case KERNEL_BZIMAGE:
/* According to the explaination for pref_address
* in Documentation/x86/boot.txt, a relocating
* bootloader should attempt to load kernel at pref_address
* if possible. A non-relocatable kernel will unconditionally
* move itself and to run at this address, so no need to copy
* kernel to perf_address by bootloader, if kernel is
* non-relocatable.
*/
zeropage = (struct zero_page *)sw_info->kernel_info.kernel_src_addr;
if (zeropage->hdr.relocatable_kernel != 0U) {
zeropage = (struct zero_page *)zeropage->hdr.pref_addr;
}
load_addr = (void *)zeropage;
break;
case KERNEL_ZEPHYR:
load_addr = (void *)vm_config->os_config.kernel_load_addr;
break;
default:
pr_err("Unsupported Kernel type.");
break;
}
if (load_addr == NULL) {
pr_err("Could not get kernel load addr of VM %d .", vm->vm_id);
}
return load_addr;
}
/**
* @pre vm != NULL && mod != NULL
*/
static int32_t init_vm_kernel_info(struct acrn_vm *vm, const struct multiboot_module *mod)
{
struct acrn_vm_config *vm_config = get_vm_config(vm->vm_id);
dev_dbg(DBG_LEVEL_BOOT, "kernel mod start=0x%x, end=0x%x",
mod->mm_mod_start, mod->mm_mod_end);
vm->sw.kernel_type = vm_config->os_config.kernel_type;
vm->sw.kernel_info.kernel_src_addr = hpa2hva((uint64_t)mod->mm_mod_start);
if ((vm->sw.kernel_info.kernel_src_addr != NULL) && (mod->mm_mod_end > mod->mm_mod_start)){
vm->sw.kernel_info.kernel_size = mod->mm_mod_end - mod->mm_mod_start;
vm->sw.kernel_info.kernel_load_addr = get_kernel_load_addr(vm);
}
return (vm->sw.kernel_info.kernel_load_addr == NULL) ? (-EINVAL) : 0;
}
/* cmdline parsed from multiboot module string, for pre-launched VMs and SOS VM only. */
static char mod_cmdline[PRE_VM_NUM + SOS_VM_NUM][MAX_BOOTARGS_SIZE] = { '\0' };
/**
* @pre vm != NULL && mbi != NULL
*/
static void init_vm_bootargs_info(struct acrn_vm *vm, const struct acrn_multiboot_info *mbi)
{
struct acrn_vm_config *vm_config = get_vm_config(vm->vm_id);
char *bootargs = vm_config->os_config.bootargs;
if ((vm_config->load_order == PRE_LAUNCHED_VM) || (vm_config->load_order == SOS_VM)) {
if (mod_cmdline[vm->vm_id][0] == '\0') {
vm->sw.bootargs_info.src_addr = bootargs;
} else {
/* override build-in bootargs with multiboot module string which is configurable
* at bootloader boot time. e.g. GRUB menu
*/
vm->sw.bootargs_info.src_addr = &mod_cmdline[vm->vm_id][0];
}
}
if (vm_config->load_order == SOS_VM) {
if (strncat_s((char *)vm->sw.bootargs_info.src_addr, MAX_BOOTARGS_SIZE, " ", 1U) == 0) {
char seed_args[MAX_SEED_ARG_SIZE] = "";
fill_seed_arg(seed_args, true);
/* Fill seed argument for SOS
* seed_args string ends with a white space and '\0', so no aditional delimiter is needed
*/
if (strncat_s((char *)vm->sw.bootargs_info.src_addr, MAX_BOOTARGS_SIZE,
seed_args, (MAX_BOOTARGS_SIZE - 1U)) != 0) {
pr_err("failed to fill seed arg to SOS bootargs!");
}
/* If there is cmdline from mbi->mi_cmdline, merge it with configured SOS bootargs. */
if (((mbi->mi_flags & MULTIBOOT_INFO_HAS_CMDLINE) != 0U) && (*(mbi->mi_cmdline) != '\0')) {
if (strncat_s((char *)vm->sw.bootargs_info.src_addr, MAX_BOOTARGS_SIZE,
mbi->mi_cmdline, (MAX_BOOTARGS_SIZE - 1U)) != 0) {
pr_err("failed to merge mbi cmdline to SOS bootargs!");
}
}
} else {
pr_err("no space to append SOS bootargs!");
}
}
vm->sw.bootargs_info.size = strnlen_s((const char *)vm->sw.bootargs_info.src_addr, MAX_BOOTARGS_SIZE);
/* Kernel bootarg and zero page are right before the kernel image */
if (vm->sw.bootargs_info.size > 0U) {
vm->sw.bootargs_info.load_addr = vm->sw.kernel_info.kernel_load_addr - (MEM_1K * 8U);
} else {
vm->sw.bootargs_info.load_addr = NULL;
}
}
/* @pre mods != NULL
*/
static uint32_t get_mod_idx_by_tag(const struct multiboot_module *mods, uint32_t mods_count, const char *tag)
{
uint32_t i, ret = INVALID_MOD_IDX;
uint32_t tag_len = strnlen_s(tag, MAX_MOD_TAG_LEN);
for (i = 0U; i < mods_count; i++) {
const char *mm_string = (char *)hpa2hva((uint64_t)mods[i].mm_string);
uint32_t mm_str_len = strnlen_s(mm_string, MAX_MOD_TAG_LEN);
const char *p_chr = mm_string + tag_len; /* point to right after the end of tag */
/* The tag must be located at the first word in mm_string and end with SPACE/TAB or EOL since
* when do file stitch by tool, the tag in mm_string might be followed by EOL(0x0d/0x0a).
*/
if ((mm_str_len >= tag_len) && (strncmp(mm_string, tag, tag_len) == 0)
&& (is_space(*p_chr) || is_eol(*p_chr))) {
ret = i;
break;
}
}
return ret;
}
/* @pre vm != NULL && mbi != NULL
*/
static int32_t init_vm_sw_load(struct acrn_vm *vm, const struct acrn_multiboot_info *mbi)
{
struct acrn_vm_config *vm_config = get_vm_config(vm->vm_id);
struct multiboot_module *mods = (struct multiboot_module *)(&mbi->mi_mods[0]);
uint32_t mod_idx;
int32_t ret = -EINVAL;
dev_dbg(DBG_LEVEL_BOOT, "mod counts=%d\n", mbi->mi_mods_count);
if (mods != NULL) {
/* find kernel module first */
mod_idx = get_mod_idx_by_tag(mods, mbi->mi_mods_count, vm_config->os_config.kernel_mod_tag);
if (mod_idx != INVALID_MOD_IDX) {
const char *mm_string = (char *)hpa2hva((uint64_t)mods[mod_idx].mm_string);
uint32_t mm_str_len = strnlen_s(mm_string, MAX_BOOTARGS_SIZE);
uint32_t tag_len = strnlen_s(vm_config->os_config.kernel_mod_tag, MAX_MOD_TAG_LEN);
const char *p_chr = mm_string + tag_len + 1; /* point to the possible start of cmdline */
/* check whether there is a cmdline configured in module string */
if (((mm_str_len > (tag_len + 1U))) && (is_space(*(p_chr - 1))) && (!is_eol(*p_chr))) {
(void)strncpy_s(&mod_cmdline[vm->vm_id][0], MAX_BOOTARGS_SIZE,
p_chr, (MAX_BOOTARGS_SIZE - 1U));
}
ret = init_vm_kernel_info(vm, &mods[mod_idx]);
}
}
if (ret == 0) {
init_vm_bootargs_info(vm, mbi);
/* check whether there is a ramdisk module */
mod_idx = get_mod_idx_by_tag(mods, mbi->mi_mods_count, vm_config->os_config.ramdisk_mod_tag);
if (mod_idx != INVALID_MOD_IDX) {
init_vm_ramdisk_info(vm, &mods[mod_idx]);
/* TODO: prepare other modules like firmware, seedlist */
}
} else {
pr_err("failed to load VM %d kernel module", vm->vm_id);
}
return ret;
}
/**
* @pre vm != NULL
*/
static int32_t init_general_vm_boot_info(struct acrn_vm *vm)
{
struct acrn_multiboot_info *mbi = get_multiboot_info();
int32_t ret = -EINVAL;
stac();
if ((mbi->mi_flags & MULTIBOOT_INFO_HAS_MODS) == 0U) {
panic("no multiboot module info found");
} else {
ret = init_vm_sw_load(vm, mbi);
}
clac();
return ret;
}
static void depri_boot_spurious_handler(uint32_t vector)
{
if (get_pcpu_id() == BSP_CPU_ID) {
struct acrn_vcpu *vcpu = vcpu_from_vid(get_sos_vm(), BSP_CPU_ID);
if (vcpu != NULL) {
vlapic_set_intr(vcpu, vector, LAPIC_TRIG_EDGE);
} else {
pr_err("%s vcpu or vlapic is not ready, interrupt lost\n", __func__);
}
}
}
static int32_t depri_boot_sw_loader(struct acrn_vm *vm)
{
int32_t ret = 0;
/* get primary vcpu */
struct acrn_vcpu *vcpu = vcpu_from_vid(vm, BSP_CPU_ID);
struct acrn_vcpu_regs *vcpu_regs = &boot_context;
const struct depri_boot_context *depri_boot_ctx = get_depri_boot_ctx();
const struct lapic_regs *depri_boot_lapic_regs = get_depri_boot_lapic_regs();
pr_dbg("Loading guest to run-time location");
vlapic_restore(vcpu_vlapic(vcpu), depri_boot_lapic_regs);
/* For UEFI platform, the bsp init regs come from two places:
* 1. saved in depri_boot: gpregs, rip
* 2. saved when HV started: other registers
* We copy the info saved in depri_boot to boot_context and
* init bsp with boot_context.
*/
(void)memcpy_s((void *)&(vcpu_regs->gprs), sizeof(struct acrn_gp_regs),
&(depri_boot_ctx->vcpu_regs.gprs), sizeof(struct acrn_gp_regs));
vcpu_regs->rip = depri_boot_ctx->vcpu_regs.rip;
set_vcpu_regs(vcpu, vcpu_regs);
/* defer irq enabling till vlapic is ready */
spurious_handler = depri_boot_spurious_handler;
CPU_IRQ_ENABLE();
return ret;
}
/**
* @param[inout] vm pointer to a vm descriptor
*
* @retval 0 on success
* @retval -EINVAL on invalid parameters
*
* @pre vm != NULL
*/
int32_t init_vm_boot_info(struct acrn_vm *vm)
{
int32_t ret = 0;
if (is_sos_vm(vm) && (get_sos_boot_mode() == DEPRI_BOOT_MODE)) {
vm_sw_loader = depri_boot_sw_loader;
} else {
vm_sw_loader = direct_boot_sw_loader;
ret = init_general_vm_boot_info(vm);
}
return ret;
}