acrn-hypervisor/hypervisor/arch/x86/guest/vm.c
Sainath Grandhi 022ef92b62 hv: Add vrtc emulation support for ACRN partition mode
This patch adds code to support read-only RTC support for guests
run by partition mode ACRN. It supports RW for CMOS address port 0x70
and RO for CMOS data port 0x71. Reads to CMOS RAM offsets are fetched
by reading CMOS h/w directly and writes to CMOS offsets are discarded.

Signed-off-by: Sainath Grandhi <sainath.grandhi@intel.com>
2018-08-16 16:23:11 +08:00

566 lines
11 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>
/* Local variables */
/* VMs list */
struct list_head vm_list = {
.next = &vm_list,
.prev = &vm_list,
};
/* Lock for VMs list */
spinlock_t vm_list_lock = {
.head = 0U,
.tail = 0U
};
#ifndef CONFIG_PARTITION_MODE
/* used for vmid allocation. And this means the max vm number is 64 */
static uint64_t vmid_bitmap;
static inline uint16_t alloc_vm_id(void)
{
uint16_t id = ffz64(vmid_bitmap);
while (id < (size_t)(sizeof(vmid_bitmap) * 8U)) {
if (!bitmap_test_and_set_lock(id, &vmid_bitmap)) {
return id;
}
id = ffz64(vmid_bitmap);
}
return INVALID_VM_ID;
}
static inline void free_vm_id(struct vm *vm)
{
bitmap_clear_lock(vm->vm_id, &vmid_bitmap);
}
#endif
static void init_vm(struct vm_description *vm_desc,
struct vm *vm_handle)
{
/* Populate VM attributes from VM description */
#ifdef CONFIG_VM0_DESC
if (is_vm0(vm_handle)) {
/* Allocate all cpus to vm0 at the beginning */
vm_handle->hw.num_vcpus = phys_cpu_num;
vm_handle->hw.exp_num_vcpus = vm_desc->vm_hw_num_cores;
} else {
vm_handle->hw.num_vcpus = vm_desc->vm_hw_num_cores;
vm_handle->hw.exp_num_vcpus = vm_desc->vm_hw_num_cores;
}
#else
vm_handle->hw.num_vcpus = vm_desc->vm_hw_num_cores;
#endif
#ifdef CONFIG_PARTITION_MODE
vm_handle->vm_desc = vm_desc;
#endif
}
/* return a pointer to the virtual machine structure associated with
* this VM ID
*/
struct vm *get_vm_from_vmid(uint16_t vm_id)
{
struct vm *vm = NULL;
struct list_head *pos;
spinlock_obtain(&vm_list_lock);
list_for_each(pos, &vm_list) {
vm = list_entry(pos, struct vm, list);
if (vm->vm_id == vm_id) {
spinlock_release(&vm_list_lock);
return vm;
}
}
spinlock_release(&vm_list_lock);
return NULL;
}
int create_vm(struct vm_description *vm_desc, struct vm **rtn_vm)
{
struct vm *vm;
int status;
if ((vm_desc == NULL) || (rtn_vm == NULL)) {
pr_err("%s, invalid paramater\n", __func__);
return -EINVAL;
}
/* Allocate memory for virtual machine */
vm = calloc(1U, sizeof(struct vm));
if (vm == NULL) {
pr_err("%s, vm allocation failed\n", __func__);
return -ENOMEM;
}
/*
* Map Virtual Machine to its VM Description
*/
init_vm(vm_desc, vm);
/* Init mmio list */
INIT_LIST_HEAD(&vm->mmio_list);
if (vm->hw.num_vcpus == 0U) {
vm->hw.num_vcpus = phys_cpu_num;
}
vm->hw.vcpu_array =
calloc(1U, sizeof(struct vcpu *) * vm->hw.num_vcpus);
if (vm->hw.vcpu_array == NULL) {
pr_err("%s, vcpu_array allocation failed\n", __func__);
status = -ENOMEM;
goto err;
}
#ifdef CONFIG_PARTITION_MODE
vm->vm_id = vm_desc->vm_id;
#else
vm->vm_id = alloc_vm_id();
if (vm->vm_id == INVALID_VM_ID) {
pr_err("%s, no more VMs can be supported\n", __func__);
status = -ENODEV;
goto err;
}
#endif
atomic_store16(&vm->hw.created_vcpus, 0U);
/* gpa_lowtop are used for system start up */
vm->hw.gpa_lowtop = 0UL;
vm->arch_vm.nworld_eptp = alloc_paging_struct();
vm->arch_vm.m2p = alloc_paging_struct();
if ((vm->arch_vm.nworld_eptp == NULL) ||
(vm->arch_vm.m2p == NULL)) {
pr_fatal("%s, alloc memory for EPTP failed\n", __func__);
status = -ENOMEM;
goto err;
}
/* Only for SOS: Configure VM software information */
/* For UOS: This VM software information is configure in DM */
if (is_vm0(vm)) {
status = prepare_vm0_memmap_and_e820(vm);
if (status != 0) {
goto err;
}
#ifndef CONFIG_EFI_STUB
status = init_vm_boot_info(vm);
if (status != 0) {
goto err;
}
#endif
} else {
/* populate UOS vm fields according to vm_desc */
vm->sworld_control.flag.supported =
vm_desc->sworld_supported;
(void)memcpy_s(&vm->GUID[0], sizeof(vm->GUID),
&vm_desc->GUID[0],
sizeof(vm_desc->GUID));
#ifdef CONFIG_PARTITION_MODE
ept_mr_add(vm, vm_desc->start_hpa,
0UL, vm_desc->mem_size, EPT_RWX|EPT_WB);
init_vm_boot_info(vm);
#endif
}
INIT_LIST_HEAD(&vm->list);
spinlock_obtain(&vm_list_lock);
list_add(&vm->list, &vm_list);
spinlock_release(&vm_list_lock);
/* 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_vm0(vm)) {
/* Load pm S state data */
if (vm_load_pm_s_state(vm) == 0) {
register_pm1ab_handler(vm);
}
/* Create virtual uart */
vm->vuart = vuart_init(vm);
}
vm->vpic = vpic_init(vm);
#ifdef CONFIG_PARTITION_MODE
/* Create virtual uart */
if (vm_desc->vm_vuart) {
vm->vuart = vuart_init(vm);
}
vrtc_init(vm);
vpci_init(vm);
#endif
/* vpic wire_mode default is INTR */
vm->wire_mode = VPIC_WIRE_INTR;
/* Allocate full emulated vIOAPIC instance */
vm->arch_vm.virt_ioapic = vioapic_init(vm);
if (vm->arch_vm.virt_ioapic == NULL) {
status = -ENODEV;
goto err;
}
/* Populate return VM handle */
*rtn_vm = vm;
vm->sw.io_shared_page = NULL;
status = set_vcpuid_entries(vm);
if (status != 0) {
goto err;
}
vm->state = VM_CREATED;
return 0;
err:
if (vm->arch_vm.virt_ioapic != NULL) {
vioapic_cleanup(vm->arch_vm.virt_ioapic);
}
if (vm->vpic != NULL) {
vpic_cleanup(vm);
}
if (vm->arch_vm.m2p != NULL) {
free(vm->arch_vm.m2p);
}
if (vm->arch_vm.nworld_eptp != NULL) {
free(vm->arch_vm.nworld_eptp);
}
if (vm->hw.vcpu_array != NULL) {
free(vm->hw.vcpu_array);
}
free(vm);
return status;
}
int shutdown_vm(struct vm *vm)
{
int status = 0;
uint16_t i;
struct vcpu *vcpu = NULL;
if (vm == NULL) {
return -EINVAL;
}
pause_vm(vm);
/* Only allow shutdown paused vm */
if (vm->state != VM_PAUSED) {
return -EINVAL;
}
foreach_vcpu(i, vm, vcpu) {
reset_vcpu(vcpu);
destroy_vcpu(vcpu);
}
spinlock_obtain(&vm_list_lock);
list_del_init(&vm->list);
spinlock_release(&vm_list_lock);
ptdev_release_all_entries(vm);
/* cleanup and free vioapic */
vioapic_cleanup(vm->arch_vm.virt_ioapic);
/* Destroy secure world */
if (vm->sworld_control.flag.active) {
destroy_secure_world(vm, true);
}
/* Free EPT allocated resources assigned to VM */
destroy_ept(vm);
/* Free MSR bitmap */
free(vm->arch_vm.msr_bitmap);
/* TODO: De-initialize I/O Emulation */
free_io_emulation_resource(vm);
/* Free iommu */
if (vm->iommu != NULL) {
destroy_iommu_domain(vm->iommu);
}
#ifndef CONFIG_PARTITION_MODE
/* Free vm id */
free_vm_id(vm);
#endif
if (vm->vpic != NULL) {
vpic_cleanup(vm);
}
#ifdef CONFIG_PARTITION_MODE
vpci_cleanup(vm);
#endif
free(vm->hw.vcpu_array);
/* TODO: De-Configure HV-SW */
/* Deallocate VM */
free(vm);
/* Return status to caller */
return status;
}
/**
* * @pre vm != NULL
*/
int start_vm(struct vm *vm)
{
struct vcpu *vcpu = NULL;
vm->state = VM_STARTED;
/* Only start BSP (vid = 0) and let BSP start other APs */
vcpu = vcpu_from_vid(vm, 0U);
ASSERT(vcpu != NULL, "vm%d, vcpu0", vm->vm_id);
schedule_vcpu(vcpu);
return 0;
}
/**
* * @pre vm != NULL
*/
int reset_vm(struct vm *vm)
{
int i;
struct vcpu *vcpu = NULL;
if (vm->state != VM_PAUSED)
return -1;
foreach_vcpu(i, vm, vcpu) {
reset_vcpu(vcpu);
if (is_vcpu_bsp(vcpu))
vm_sw_loader(vm, vcpu);
vcpu->arch_vcpu.cpu_mode = CPU_MODE_REAL;
}
vioapic_reset(vm->arch_vm.virt_ioapic);
destroy_secure_world(vm, false);
vm->sworld_control.flag.active = 0UL;
start_vm(vm);
return 0;
}
/**
* * @pre vm != NULL
*/
void pause_vm(struct vm *vm)
{
uint16_t i;
struct vcpu *vcpu = NULL;
if (vm->state == VM_PAUSED) {
return;
}
vm->state = VM_PAUSED;
foreach_vcpu(i, vm, vcpu) {
pause_vcpu(vcpu, VCPU_ZOMBIE);
}
}
/**
* * @pre vm != NULL
*/
void resume_vm(struct vm *vm)
{
uint16_t i;
struct 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 vm *vm, uint32_t wakeup_vec)
{
struct vcpu *bsp = vcpu_from_vid(vm, 0U);
vm->state = VM_STARTED;
reset_vcpu(bsp);
bsp->entry_addr = (void *)(uint64_t)wakeup_vec;
bsp->arch_vcpu.cpu_mode = CPU_MODE_REAL;
init_vmcs(bsp);
schedule_vcpu(bsp);
}
#ifdef CONFIG_PARTITION_MODE
/* Create vm/vcpu for vm */
int prepare_vm(uint16_t pcpu_id)
{
int ret = 0;
uint16_t i;
struct vm *vm = NULL;
const struct vm_description *vm_desc = NULL;
bool is_vm_bsp;
vm_desc = pcpu_vm_desc_map[pcpu_id].vm_desc_ptr;
is_vm_bsp = pcpu_vm_desc_map[pcpu_id].is_bsp;
if (is_vm_bsp) {
ret = create_vm(vm_desc, &vm);
ASSERT(ret == 0, "VM creation failed!");
mptable_build(vm);
prepare_vcpu(vm, vm_desc->vm_pcpu_ids[0]);
/* Prepare the AP for vm */
for (i = 1U; i < vm_desc->vm_hw_num_cores; i++)
prepare_vcpu(vm, vm_desc->vm_pcpu_ids[i]);
/* start vm BSP automatically */
start_vm(vm);
pr_acrnlog("Start VM%x", vm_desc->vm_id);
}
return ret;
}
#else
/* Create vm/vcpu for vm0 */
int prepare_vm0(void)
{
int err;
uint16_t i;
struct vm *vm = NULL;
struct vm_description *vm_desc = &vm0_desc;
#ifndef CONFIG_VM0_DESC
vm_desc->vm_hw_num_cores = phys_cpu_num;
#endif
err = create_vm(vm_desc, &vm);
if (err != 0) {
return err;
}
/* Allocate all cpus to vm0 at the beginning */
for (i = 0U; i < phys_cpu_num; i++) {
err = prepare_vcpu(vm, i);
if (err != 0) {
return err;
}
}
/* start vm0 BSP automatically */
err = start_vm(vm);
pr_acrnlog("Start VM0");
return err;
}
int prepare_vm(uint16_t pcpu_id)
{
int err = 0;
/* prepare vm0 if pcpu_id is BOOT_CPU_ID */
if (pcpu_id == BOOT_CPU_ID) {
err = prepare_vm0();
}
return err;
}
#endif
#ifdef CONFIG_VM0_DESC
static inline bool vcpu_in_vm_desc(struct vcpu *vcpu,
struct vm_description *vm_desc)
{
int i;
for (i = 0; i < vm_desc->vm_hw_num_cores; i++) {
if (vcpu->pcpu_id == vm_desc->vm_pcpu_ids[i]) {
return true;
}
}
return false;
}
/*
* fixup vm0 for expected vcpu:
* vm0 is starting with all physical cpus, it's mainly for UEFI boot to
* handle all physical mapped APs wakeup during boot service exit.
* this fixup is used to pause then destroy non-expect-enabled vcpus from VM0.
*
* NOTE: if you want to enable mult-vpucs for vm0, please make sure the pcpu_id
* is in order, for example:
* - one vcpu: VM0_CPUS[VM0_NUM_CPUS] = {0};
* - two vcpus: VM0_CPUS[VM0_NUM_CPUS] = {0, 1};
* - three vcpus: VM0_CPUS[VM0_NUM_CPUS] = {0, 1, 2};
*/
void vm_fixup(struct vm *vm)
{
if (is_vm0(vm) && (vm->hw.exp_num_vcpus < vm->hw.num_vcpus)) {
struct vm_description *vm_desc = &vm0_desc;
struct vcpu *vcpu;
uint16_t i;
foreach_vcpu(i, vm, vcpu) {
if (!vcpu_in_vm_desc(vcpu, vm_desc)) {
pause_vcpu(vcpu, VCPU_ZOMBIE);
reset_vcpu(vcpu);
destroy_vcpu(vcpu);
}
}
vm->hw.num_vcpus = vm->hw.exp_num_vcpus;
}
}
#endif