acrn-hypervisor/hypervisor/arch/x86/guest/vm.c
Shiqing Gao 49623fc073 hv: refine guest.c
- move `vcpumask2pcpumask` from `guest.c` to `vcpu.c`
- move `prepare_sos_vm_memmap` from `guest.c` to `vm.c`
- rename `guest.c` to `guest_memory.c`

Tracked-On: #2484
Signed-off-by: Shiqing Gao <shiqing.gao@intel.com>
Acked-by: Eddie Dong <eddie.dong@intel.com>
2019-02-01 16:30:55 +08:00

563 lines
12 KiB
C

/*
* Copyright (C) 2018 Intel Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <hypervisor.h>
#include <bsp_extern.h>
#include <multiboot.h>
#include <e820.h>
#include <vtd.h>
#include <reloc.h>
vm_sw_loader_t vm_sw_loader;
/* Local variables */
static struct acrn_vm vm_array[CONFIG_MAX_VM_NUM] __aligned(PAGE_SIZE);
static struct acrn_vm *sos_vm_ptr = NULL;
uint16_t find_free_vm_id(void)
{
uint16_t id;
struct acrn_vm_config *vm_config;
for (id = 0U; id < CONFIG_MAX_VM_NUM; id++) {
vm_config = get_vm_config(id);
if (vm_config->type == UNDEFINED_VM) {
break;
}
}
return (vm_config->type == UNDEFINED_VM) ? id : INVALID_VM_ID;
}
static inline void free_vm_id(const struct acrn_vm *vm)
{
struct acrn_vm_config *vm_config = get_vm_config(vm->vm_id);
if (vm_config != NULL) {
vm_config->type = UNDEFINED_VM;
}
}
bool is_sos_vm(const struct acrn_vm *vm)
{
return (vm != NULL) && (vm == sos_vm_ptr);
}
/**
* @brief Initialize the I/O bitmap for \p vm
*
* @param vm The VM whose I/O bitmap is to be initialized
*/
static void setup_io_bitmap(struct acrn_vm *vm)
{
if (is_sos_vm(vm)) {
(void)memset(vm->arch_vm.io_bitmap, 0x00U, PAGE_SIZE * 2U);
} else {
/* block all IO port access from Guest */
(void)memset(vm->arch_vm.io_bitmap, 0xFFU, PAGE_SIZE * 2U);
}
}
/**
* return a pointer to the virtual machine structure associated with
* this VM ID
*
* @pre vm_id < CONFIG_MAX_VM_NUM
*/
struct acrn_vm *get_vm_from_vmid(uint16_t vm_id)
{
return &vm_array[vm_id];
}
/* return a pointer to the virtual machine structure of SOS VM */
struct acrn_vm *get_sos_vm(void)
{
return sos_vm_ptr;
}
/**
* @pre vm_config != NULL
*/
static inline uint16_t get_vm_bsp_pcpu_id(const struct acrn_vm_config *vm_config)
{
uint16_t cpu_id = INVALID_CPU_ID;
cpu_id = ffs64(vm_config->pcpu_bitmap);
return (cpu_id < get_pcpu_nums()) ? cpu_id : INVALID_CPU_ID;
}
#ifdef CONFIG_PARTITION_MODE
/**
* @pre vm_config != NULL
*/
uint16_t get_vm_pcpu_nums(struct acrn_vm_config *vm_config)
{
uint16_t i, host_pcpu_num, pcpu_num = 0U;
uint64_t cpu_bitmap = vm_config->pcpu_bitmap;
host_pcpu_num = get_pcpu_nums();
for (i = 0U; i < host_pcpu_num ; i++) {
if (bitmap_test(i, &cpu_bitmap)) {
pcpu_num++;
}
}
return pcpu_num;
}
#endif
/**
* @param[inout] vm pointer to a vm descriptor
*
* @retval 0 on success
*
* @pre vm != NULL
* @pre is_sos_vm(vm) == true
*/
static void prepare_sos_vm_memmap(struct acrn_vm *vm)
{
uint32_t i;
uint64_t attr_uc = (EPT_RWX | EPT_UNCACHED);
uint64_t hv_hpa;
uint64_t *pml4_page = (uint64_t *)vm->arch_vm.nworld_eptp;
const struct e820_entry *entry;
uint32_t entries_count = get_e820_entries_count();
const struct e820_entry *p_e820 = get_e820_entry();
const struct e820_mem_params *p_e820_mem_info = get_e820_mem_info();
pr_dbg("sos_vm: bottom memory - 0x%llx, top memory - 0x%llx\n",
p_e820_mem_info->mem_bottom, p_e820_mem_info->mem_top);
if (p_e820_mem_info->mem_top > EPT_ADDRESS_SPACE(CONFIG_SOS_RAM_SIZE)) {
panic("Please configure SOS_VM_ADDRESS_SPACE correctly!\n");
}
/* create real ept map for all ranges with UC */
ept_mr_add(vm, pml4_page, p_e820_mem_info->mem_bottom, p_e820_mem_info->mem_bottom,
(p_e820_mem_info->mem_top - p_e820_mem_info->mem_bottom), attr_uc);
/* update ram entries to WB attr */
for (i = 0U; i < entries_count; i++) {
entry = p_e820 + i;
if (entry->type == E820_TYPE_RAM) {
ept_mr_modify(vm, pml4_page, entry->baseaddr, entry->length, EPT_WB, EPT_MT_MASK);
}
}
pr_dbg("SOS_VM e820 layout:\n");
for (i = 0U; i < entries_count; i++) {
entry = p_e820 + i;
pr_dbg("e820 table: %d type: 0x%x", i, entry->type);
pr_dbg("BaseAddress: 0x%016llx length: 0x%016llx\n", entry->baseaddr, entry->length);
}
/* unmap hypervisor itself for safety
* will cause EPT violation if sos accesses hv memory
*/
hv_hpa = get_hv_image_base();
ept_mr_del(vm, pml4_page, hv_hpa, CONFIG_HV_RAM_SIZE);
}
/**
* @pre vm_id < CONFIG_MAX_VM_NUM && vm_config != NULL && rtn_vm != NULL
*/
int32_t create_vm(uint16_t vm_id, struct acrn_vm_config *vm_config, struct acrn_vm **rtn_vm)
{
struct acrn_vm *vm = NULL;
int32_t status = 0;
bool need_cleanup = false;
/* Allocate memory for virtual machine */
vm = &vm_array[vm_id];
(void)memset((void *)vm, 0U, sizeof(struct acrn_vm));
vm->vm_id = vm_id;
vm->hw.created_vcpus = 0U;
vm->emul_mmio_regions = 0U;
vm->snoopy_mem = true;
/* gpa_lowtop are used for system start up */
vm->hw.gpa_lowtop = 0UL;
init_ept_mem_ops(vm);
vm->arch_vm.nworld_eptp = vm->arch_vm.ept_mem_ops.get_pml4_page(vm->arch_vm.ept_mem_ops.info);
sanitize_pte((uint64_t *)vm->arch_vm.nworld_eptp);
/* Only for SOS: Configure VM software information */
/* For UOS: This VM software information is configure in DM */
if (is_sos_vm(vm)) {
vm->snoopy_mem = false;
rebuild_sos_vm_e820();
prepare_sos_vm_memmap(vm);
status = init_vm_boot_info(vm);
if (status == 0) {
init_iommu_sos_vm_domain(vm);
} else {
need_cleanup = true;
}
} else {
/* populate UOS vm fields according to vm_config */
if ((vm_config->guest_flags & SECURE_WORLD_ENABLED) != 0U) {
vm->sworld_control.flag.supported = 1U;
}
if (vm->sworld_control.flag.supported != 0UL) {
struct memory_ops *ept_mem_ops = &vm->arch_vm.ept_mem_ops;
ept_mr_add(vm, (uint64_t *)vm->arch_vm.nworld_eptp,
hva2hpa(ept_mem_ops->get_sworld_memory_base(ept_mem_ops->info)),
TRUSTY_EPT_REBASE_GPA, TRUSTY_RAM_SIZE, EPT_WB | EPT_RWX);
}
if (vm_config->name[0] == '\0') {
/* if VM name is not configured, specify with VM ID */
snprintf(vm_config->name, 16, "ACRN VM_%d", vm_id);
}
(void)memcpy_s(&vm->GUID[0], sizeof(vm->GUID),
&vm_config->GUID[0], sizeof(vm_config->GUID));
#ifdef CONFIG_PARTITION_MODE
ept_mr_add(vm, (uint64_t *)vm->arch_vm.nworld_eptp,
vm_config->memory.start_hpa, 0UL, vm_config->memory.size,
EPT_RWX|EPT_WB);
init_vm_boot_info(vm);
#endif
}
if (status == 0) {
enable_iommu();
INIT_LIST_HEAD(&vm->softirq_dev_entry_list);
spinlock_init(&vm->softirq_dev_lock);
vm->intr_inject_delay_delta = 0UL;
/* Set up IO bit-mask such that VM exit occurs on
* selected IO ranges
*/
setup_io_bitmap(vm);
vm_setup_cpu_state(vm);
if (is_sos_vm(vm)) {
/* Load pm S state data */
if (vm_load_pm_s_state(vm) == 0) {
register_pm1ab_handler(vm);
}
/* Create virtual uart; just when uart enabled, vuart can work */
if (is_dbg_uart_enabled()) {
vuart_init(vm);
}
}
vpic_init(vm);
#ifdef CONFIG_PARTITION_MODE
/* Create virtual uart; just when uart enabled, vuart can work */
if (vm_config->vm_vuart && is_dbg_uart_enabled()) {
vuart_init(vm);
}
vrtc_init(vm);
#endif
vpci_init(vm);
/* vpic wire_mode default is INTR */
vm->wire_mode = VPIC_WIRE_INTR;
/* Init full emulated vIOAPIC instance */
vioapic_init(vm);
/* Populate return VM handle */
*rtn_vm = vm;
vm->sw.io_shared_page = NULL;
if ((vm_config->guest_flags & IO_COMPLETION_POLLING) != 0U) {
/* enable IO completion polling mode per its guest flags in vm_config. */
vm->sw.is_completion_polling = true;
}
status = set_vcpuid_entries(vm);
if (status == 0) {
vm->state = VM_CREATED;
} else {
need_cleanup = true;
}
}
if (need_cleanup && (vm != NULL)) {
if (vm->arch_vm.nworld_eptp != NULL) {
(void)memset(vm->arch_vm.nworld_eptp, 0U, PAGE_SIZE);
}
free_vm_id(vm);
}
return status;
}
/*
* @pre vm != NULL
*/
int32_t shutdown_vm(struct acrn_vm *vm)
{
uint16_t i;
struct acrn_vcpu *vcpu = NULL;
int32_t ret;
pause_vm(vm);
/* Only allow shutdown paused vm */
if (vm->state == VM_PAUSED) {
foreach_vcpu(i, vm, vcpu) {
reset_vcpu(vcpu);
offline_vcpu(vcpu);
}
ptdev_release_all_entries(vm);
/* Free EPT allocated resources assigned to VM */
destroy_ept(vm);
/* Free iommu */
if (vm->iommu != NULL) {
destroy_iommu_domain(vm->iommu);
}
/* Free vm id */
free_vm_id(vm);
vpci_cleanup(vm);
ret = 0;
} else {
ret = -EINVAL;
}
/* Return status to caller */
return ret;
}
/**
* * @pre vm != NULL
*/
void start_vm(struct acrn_vm *vm)
{
struct acrn_vcpu *vcpu = NULL;
vm->state = VM_STARTED;
/* Only start BSP (vid = 0) and let BSP start other APs */
vcpu = vcpu_from_vid(vm, 0U);
schedule_vcpu(vcpu);
}
/**
* * @pre vm != NULL
*/
int32_t reset_vm(struct acrn_vm *vm)
{
uint16_t i;
struct acrn_vcpu *vcpu = NULL;
int32_t ret;
if (vm->state == VM_PAUSED) {
foreach_vcpu(i, vm, vcpu) {
reset_vcpu(vcpu);
}
if (is_sos_vm(vm)) {
(void )vm_sw_loader(vm);
}
reset_vm_ioreqs(vm);
vioapic_reset(vm);
destroy_secure_world(vm, false);
vm->sworld_control.flag.active = 0UL;
ret = 0;
} else {
ret = -1;
}
return ret;
}
/**
* * @pre vm != NULL
*/
void pause_vm(struct acrn_vm *vm)
{
uint16_t i;
struct acrn_vcpu *vcpu = NULL;
if (vm->state != VM_PAUSED) {
vm->state = VM_PAUSED;
foreach_vcpu(i, vm, vcpu) {
pause_vcpu(vcpu, VCPU_ZOMBIE);
}
}
}
/**
* * @pre vm != NULL
*/
void resume_vm(struct acrn_vm *vm)
{
uint16_t i;
struct acrn_vcpu *vcpu = NULL;
foreach_vcpu(i, vm, vcpu) {
resume_vcpu(vcpu);
}
vm->state = VM_STARTED;
}
/**
* @brief Resume vm from S3 state
*
* To resume vm after guest enter S3 state:
* - reset BSP
* - BSP will be put to real mode with entry set as wakeup_vec
* - init_vmcs BSP. We could call init_vmcs here because we know current
* pcpu is mapped to BSP of vm.
*
* @vm[in] vm pointer to vm data structure
* @wakeup_vec[in] The resume address of vm
*
* @pre vm != NULL
*/
void resume_vm_from_s3(struct acrn_vm *vm, uint32_t wakeup_vec)
{
struct acrn_vcpu *bsp = vcpu_from_vid(vm, 0U);
vm->state = VM_STARTED;
reset_vcpu(bsp);
/* When SOS resume from S3, it will return to real mode
* with entry set to wakeup_vec.
*/
set_ap_entry(bsp, wakeup_vec);
init_vmcs(bsp);
schedule_vcpu(bsp);
}
/**
* Prepare to create vm/vcpu for vm
*
* @pre vm_id < CONFIG_MAX_VM_NUM && vm_config != NULL
*/
void prepare_vm(uint16_t vm_id, struct acrn_vm_config *vm_config)
{
int32_t err = 0;
uint16_t i;
struct acrn_vm *vm = NULL;
err = create_vm(vm_id, vm_config, &vm);
if (err == 0) {
#ifdef CONFIG_PARTITION_MODE
mptable_build(vm);
#endif
for (i = 0U; i < get_pcpu_nums(); i++) {
if (bitmap_test(i, &vm_config->pcpu_bitmap)) {
err = prepare_vcpu(vm, i);
if (err != 0) {
break;
}
}
}
}
if (err == 0) {
if (vm_sw_loader == NULL) {
vm_sw_loader = general_sw_loader;
}
(void )vm_sw_loader(vm);
/* start vm BSP automatically */
start_vm(vm);
pr_acrnlog("Start VM id: %x name: %s", vm_id, vm_config->name);
}
}
/**
* @pre vm_config != NULL
*/
int32_t sanitize_vm_config(void)
{
int32_t ret = 0;
uint16_t vm_id;
uint64_t sos_pcpu_bitmap, pre_launch_pcpu_bitmap;
struct acrn_vm_config *vm_config;
sos_pcpu_bitmap = (uint64_t)((((uint64_t)1U) << get_pcpu_nums()) - 1U);
pre_launch_pcpu_bitmap = 0U;
/* All physical CPUs except ocuppied by Pre-launched VMs are all
* belong to SOS_VM. i.e. The pcpu_bitmap of a SOS_VM is decided
* by pcpu_bitmap status in PRE_LAUNCHED_VMs.
* We need to setup a rule, that the vm_configs[] array should follow
* the order of PRE_LAUNCHED_VM first, and then SOS_VM.
*/
for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
vm_config = get_vm_config(vm_id);
switch (vm_config->type) {
case PRE_LAUNCHED_VM:
if (vm_config->pcpu_bitmap == 0U) {
ret = -EINVAL;
} else {
pre_launch_pcpu_bitmap |= vm_config->pcpu_bitmap;
}
break;
case SOS_VM:
/* Deduct pcpus of PRE_LAUNCHED_VMs */
sos_pcpu_bitmap ^= pre_launch_pcpu_bitmap;
if ((sos_pcpu_bitmap == 0U) || ((vm_config->guest_flags & LAPIC_PASSTHROUGH) != 0U)) {
ret = -EINVAL;
} else {
vm_config->pcpu_bitmap = sos_pcpu_bitmap;
}
break;
case NORMAL_VM:
ret = -EINVAL;
break;
default:
/* Nothing to do for a UNDEFINED_VM, break directly. */
break;
}
if (ret != 0) {
break;
}
}
return ret;
}
/**
* @pre vm_config != NULL
*/
void launch_vms(uint16_t pcpu_id)
{
uint16_t vm_id, bsp_id;
struct acrn_vm_config *vm_config;
for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
vm_config = get_vm_config(vm_id);
if ((vm_config->type == SOS_VM) || (vm_config->type == PRE_LAUNCHED_VM)) {
if (vm_config->type == SOS_VM) {
sos_vm_ptr = &vm_array[vm_id];
}
bsp_id = get_vm_bsp_pcpu_id(vm_config);
if (pcpu_id == bsp_id) {
prepare_vm(vm_id, vm_config);
}
}
}
}