mirror of
https://git.hardenedbsd.org/hardenedbsd/HardenedBSD.git
synced 2024-11-22 11:14:18 +01:00
d3916eace5
Add kernel code for 'H' — Hypervisor Extension[1] to support virtualization on RISC-V ISA. This comes with a separate userspace patch allowing us to boot unmodified freebsd/riscv guest. Other operating systems are untested. This also comes with a U-Boot port that is configured to run in bhyve guest environment — in RISC-V virtual supervisor mode. The vmm SBI code emulates RISC-V machine-mode for the guest, handling SBI calls partly in vmm kernel and partly in bhyve userspace. Developed in Spike simulator during short period of time, the support is considered experimental. The first real hardware with hypervisor spec included should have just reached the market, so this was tested in Spike and QEMU only. Note that this depends on Sstc extension presence in the hardware (both Spike and QEMU have it). Note that booting multiple guests at the same time is not tested and may require additional work. Some TODOs are indicated within the code, and some listed in the project's home page[2]. Many thanks to Jessica Clarke, Mitchell Horne and Mark Johnston for help with parts, test and review. 1. https://riscv.org/technical/specifications/ 2. https://wiki.freebsd.org/riscv/bhyve Sponsored by: UK Research and Innovation Differential Revision: https://reviews.freebsd.org/D45553
923 lines
20 KiB
C
923 lines
20 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (c) 2024 Ruslan Bukin <br@bsdpad.com>
|
|
*
|
|
* This software was developed by the University of Cambridge Computer
|
|
* Laboratory (Department of Computer Science and Technology) under Innovate
|
|
* UK project 105694, "Digital Security by Design (DSbD) Technology Platform
|
|
* Prototype".
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/smp.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/pcpu.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/vmem.h>
|
|
#include <sys/bus.h>
|
|
|
|
#include <vm/vm.h>
|
|
#include <vm/pmap.h>
|
|
#include <vm/vm_extern.h>
|
|
#include <vm/vm_map.h>
|
|
#include <vm/vm_page.h>
|
|
#include <vm/vm_param.h>
|
|
|
|
#include <machine/md_var.h>
|
|
#include <machine/riscvreg.h>
|
|
#include <machine/vm.h>
|
|
#include <machine/cpufunc.h>
|
|
#include <machine/cpu.h>
|
|
#include <machine/machdep.h>
|
|
#include <machine/vmm.h>
|
|
#include <machine/vmm_dev.h>
|
|
#include <machine/atomic.h>
|
|
#include <machine/pmap.h>
|
|
#include <machine/intr.h>
|
|
#include <machine/encoding.h>
|
|
#include <machine/db_machdep.h>
|
|
|
|
#include "riscv.h"
|
|
#include "vmm_aplic.h"
|
|
#include "vmm_stat.h"
|
|
|
|
MALLOC_DEFINE(M_HYP, "RISC-V VMM HYP", "RISC-V VMM HYP");
|
|
|
|
DPCPU_DEFINE_STATIC(struct hypctx *, vcpu);
|
|
|
|
static int
|
|
m_op(uint32_t insn, int match, int mask)
|
|
{
|
|
|
|
if (((insn ^ match) & mask) == 0)
|
|
return (1);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static inline void
|
|
riscv_set_active_vcpu(struct hypctx *hypctx)
|
|
{
|
|
|
|
DPCPU_SET(vcpu, hypctx);
|
|
}
|
|
|
|
struct hypctx *
|
|
riscv_get_active_vcpu(void)
|
|
{
|
|
|
|
return (DPCPU_GET(vcpu));
|
|
}
|
|
|
|
int
|
|
vmmops_modinit(void)
|
|
{
|
|
|
|
if (!has_hyp) {
|
|
printf("vmm: riscv hart doesn't support H-extension.\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
if (!has_sstc) {
|
|
printf("vmm: riscv hart doesn't support SSTC extension.\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
vmmops_modcleanup(void)
|
|
{
|
|
|
|
return (0);
|
|
}
|
|
|
|
void *
|
|
vmmops_init(struct vm *vm, pmap_t pmap)
|
|
{
|
|
struct hyp *hyp;
|
|
vm_size_t size;
|
|
|
|
size = round_page(sizeof(struct hyp) +
|
|
sizeof(struct hypctx *) * vm_get_maxcpus(vm));
|
|
hyp = malloc_aligned(size, PAGE_SIZE, M_HYP, M_WAITOK | M_ZERO);
|
|
hyp->vm = vm;
|
|
hyp->aplic_attached = false;
|
|
|
|
aplic_vminit(hyp);
|
|
|
|
return (hyp);
|
|
}
|
|
|
|
static void
|
|
vmmops_delegate(void)
|
|
{
|
|
uint64_t hedeleg;
|
|
uint64_t hideleg;
|
|
|
|
hedeleg = (1UL << SCAUSE_INST_MISALIGNED);
|
|
hedeleg |= (1UL << SCAUSE_ILLEGAL_INSTRUCTION);
|
|
hedeleg |= (1UL << SCAUSE_BREAKPOINT);
|
|
hedeleg |= (1UL << SCAUSE_ECALL_USER);
|
|
hedeleg |= (1UL << SCAUSE_INST_PAGE_FAULT);
|
|
hedeleg |= (1UL << SCAUSE_LOAD_PAGE_FAULT);
|
|
hedeleg |= (1UL << SCAUSE_STORE_PAGE_FAULT);
|
|
csr_write(hedeleg, hedeleg);
|
|
|
|
hideleg = (1UL << IRQ_SOFTWARE_HYPERVISOR);
|
|
hideleg |= (1UL << IRQ_TIMER_HYPERVISOR);
|
|
hideleg |= (1UL << IRQ_EXTERNAL_HYPERVISOR);
|
|
csr_write(hideleg, hideleg);
|
|
}
|
|
|
|
static void
|
|
vmmops_vcpu_restore_csrs(struct hypctx *hypctx)
|
|
{
|
|
struct hypcsr *csrs;
|
|
|
|
csrs = &hypctx->guest_csrs;
|
|
|
|
csr_write(vsstatus, csrs->vsstatus);
|
|
csr_write(vsie, csrs->vsie);
|
|
csr_write(vstvec, csrs->vstvec);
|
|
csr_write(vsscratch, csrs->vsscratch);
|
|
csr_write(vsepc, csrs->vsepc);
|
|
csr_write(vscause, csrs->vscause);
|
|
csr_write(vstval, csrs->vstval);
|
|
csr_write(hvip, csrs->hvip);
|
|
csr_write(vsatp, csrs->vsatp);
|
|
}
|
|
|
|
static void
|
|
vmmops_vcpu_save_csrs(struct hypctx *hypctx)
|
|
{
|
|
struct hypcsr *csrs;
|
|
|
|
csrs = &hypctx->guest_csrs;
|
|
|
|
csrs->vsstatus = csr_read(vsstatus);
|
|
csrs->vsie = csr_read(vsie);
|
|
csrs->vstvec = csr_read(vstvec);
|
|
csrs->vsscratch = csr_read(vsscratch);
|
|
csrs->vsepc = csr_read(vsepc);
|
|
csrs->vscause = csr_read(vscause);
|
|
csrs->vstval = csr_read(vstval);
|
|
csrs->hvip = csr_read(hvip);
|
|
csrs->vsatp = csr_read(vsatp);
|
|
}
|
|
|
|
void *
|
|
vmmops_vcpu_init(void *vmi, struct vcpu *vcpu1, int vcpuid)
|
|
{
|
|
struct hypctx *hypctx;
|
|
struct hyp *hyp;
|
|
vm_size_t size;
|
|
|
|
hyp = vmi;
|
|
|
|
dprintf("%s: hyp %p\n", __func__, hyp);
|
|
|
|
KASSERT(vcpuid >= 0 && vcpuid < vm_get_maxcpus(hyp->vm),
|
|
("%s: Invalid vcpuid %d", __func__, vcpuid));
|
|
|
|
size = round_page(sizeof(struct hypctx));
|
|
|
|
hypctx = malloc_aligned(size, PAGE_SIZE, M_HYP, M_WAITOK | M_ZERO);
|
|
hypctx->hyp = hyp;
|
|
hypctx->vcpu = vcpu1;
|
|
hypctx->guest_scounteren = HCOUNTEREN_CY | HCOUNTEREN_TM;
|
|
|
|
/* sstatus */
|
|
hypctx->guest_regs.hyp_sstatus = SSTATUS_SPP | SSTATUS_SPIE;
|
|
hypctx->guest_regs.hyp_sstatus |= SSTATUS_FS_INITIAL;
|
|
|
|
/* hstatus */
|
|
hypctx->guest_regs.hyp_hstatus = HSTATUS_SPV | HSTATUS_VTW;
|
|
hypctx->guest_regs.hyp_hstatus |= HSTATUS_SPVP;
|
|
|
|
hypctx->cpu_id = vcpuid;
|
|
hyp->ctx[vcpuid] = hypctx;
|
|
|
|
aplic_cpuinit(hypctx);
|
|
|
|
return (hypctx);
|
|
}
|
|
|
|
static int
|
|
riscv_vmm_pinit(pmap_t pmap)
|
|
{
|
|
|
|
dprintf("%s: pmap %p\n", __func__, pmap);
|
|
|
|
pmap_pinit_stage(pmap, PM_STAGE2);
|
|
|
|
return (1);
|
|
}
|
|
|
|
struct vmspace *
|
|
vmmops_vmspace_alloc(vm_offset_t min, vm_offset_t max)
|
|
{
|
|
|
|
return (vmspace_alloc(min, max, riscv_vmm_pinit));
|
|
}
|
|
|
|
void
|
|
vmmops_vmspace_free(struct vmspace *vmspace)
|
|
{
|
|
|
|
pmap_remove_pages(vmspace_pmap(vmspace));
|
|
vmspace_free(vmspace);
|
|
}
|
|
|
|
static void
|
|
riscv_unpriv_read(struct hypctx *hypctx, uintptr_t guest_addr, uint64_t *data,
|
|
struct hyptrap *trap)
|
|
{
|
|
register struct hyptrap * htrap asm("a0");
|
|
uintptr_t old_hstatus;
|
|
uintptr_t old_stvec;
|
|
uintptr_t entry;
|
|
uint64_t val;
|
|
uint64_t tmp;
|
|
int intr;
|
|
|
|
entry = (uintptr_t)&vmm_unpriv_trap;
|
|
htrap = trap;
|
|
|
|
intr = intr_disable();
|
|
|
|
old_hstatus = csr_swap(hstatus, hypctx->guest_regs.hyp_hstatus);
|
|
/*
|
|
* Setup a temporary exception vector, so that if hlvx.hu raises
|
|
* an exception we catch it in the vmm_unpriv_trap().
|
|
*/
|
|
old_stvec = csr_swap(stvec, entry);
|
|
|
|
/*
|
|
* Read first two bytes of instruction assuming it could be a
|
|
* compressed one.
|
|
*/
|
|
__asm __volatile(".option push\n"
|
|
".option norvc\n"
|
|
"hlvx.hu %[val], (%[addr])\n"
|
|
".option pop\n"
|
|
: [val] "=r" (val)
|
|
: [addr] "r" (guest_addr), "r" (htrap)
|
|
: "a1", "memory");
|
|
|
|
/*
|
|
* Check if previous hlvx.hu did not raise an exception, and then
|
|
* read the rest of instruction if it is a full-length one.
|
|
*/
|
|
if (trap->scause == -1 && (val & 0x3) == 0x3) {
|
|
guest_addr += 2;
|
|
__asm __volatile(".option push\n"
|
|
".option norvc\n"
|
|
"hlvx.hu %[tmp], (%[addr])\n"
|
|
".option pop\n"
|
|
: [tmp] "=r" (tmp)
|
|
: [addr] "r" (guest_addr), "r" (htrap)
|
|
: "a1", "memory");
|
|
val |= (tmp << 16);
|
|
}
|
|
|
|
csr_write(hstatus, old_hstatus);
|
|
csr_write(stvec, old_stvec);
|
|
|
|
intr_restore(intr);
|
|
|
|
*data = val;
|
|
}
|
|
|
|
static int
|
|
riscv_gen_inst_emul_data(struct hypctx *hypctx, struct vm_exit *vme_ret,
|
|
struct hyptrap *trap)
|
|
{
|
|
uintptr_t guest_addr;
|
|
struct vie *vie;
|
|
uint64_t insn;
|
|
int reg_num;
|
|
int rs2, rd;
|
|
int direction;
|
|
int sign_extend;
|
|
int access_size;
|
|
|
|
guest_addr = vme_ret->sepc;
|
|
|
|
KASSERT(vme_ret->scause == SCAUSE_FETCH_GUEST_PAGE_FAULT ||
|
|
vme_ret->scause == SCAUSE_LOAD_GUEST_PAGE_FAULT ||
|
|
vme_ret->scause == SCAUSE_STORE_GUEST_PAGE_FAULT,
|
|
("Invalid scause"));
|
|
|
|
direction = vme_ret->scause == SCAUSE_STORE_GUEST_PAGE_FAULT ?
|
|
VM_DIR_WRITE : VM_DIR_READ;
|
|
|
|
sign_extend = 1;
|
|
|
|
bzero(trap, sizeof(struct hyptrap));
|
|
trap->scause = -1;
|
|
riscv_unpriv_read(hypctx, guest_addr, &insn, trap);
|
|
if (trap->scause != -1)
|
|
return (-1);
|
|
|
|
if ((insn & 0x3) == 0x3) {
|
|
rs2 = (insn & RS2_MASK) >> RS2_SHIFT;
|
|
rd = (insn & RD_MASK) >> RD_SHIFT;
|
|
|
|
if (direction == VM_DIR_WRITE) {
|
|
if (m_op(insn, MATCH_SB, MASK_SB))
|
|
access_size = 1;
|
|
else if (m_op(insn, MATCH_SH, MASK_SH))
|
|
access_size = 2;
|
|
else if (m_op(insn, MATCH_SW, MASK_SW))
|
|
access_size = 4;
|
|
else if (m_op(insn, MATCH_SD, MASK_SD))
|
|
access_size = 8;
|
|
else {
|
|
printf("unknown store instr at %lx",
|
|
guest_addr);
|
|
return (-2);
|
|
}
|
|
reg_num = rs2;
|
|
} else {
|
|
if (m_op(insn, MATCH_LB, MASK_LB))
|
|
access_size = 1;
|
|
else if (m_op(insn, MATCH_LH, MASK_LH))
|
|
access_size = 2;
|
|
else if (m_op(insn, MATCH_LW, MASK_LW))
|
|
access_size = 4;
|
|
else if (m_op(insn, MATCH_LD, MASK_LD))
|
|
access_size = 8;
|
|
else if (m_op(insn, MATCH_LBU, MASK_LBU)) {
|
|
access_size = 1;
|
|
sign_extend = 0;
|
|
} else if (m_op(insn, MATCH_LHU, MASK_LHU)) {
|
|
access_size = 2;
|
|
sign_extend = 0;
|
|
} else if (m_op(insn, MATCH_LWU, MASK_LWU)) {
|
|
access_size = 4;
|
|
sign_extend = 0;
|
|
} else {
|
|
printf("unknown load instr at %lx",
|
|
guest_addr);
|
|
return (-3);
|
|
}
|
|
reg_num = rd;
|
|
}
|
|
vme_ret->inst_length = 4;
|
|
} else {
|
|
rs2 = (insn >> 7) & 0x7;
|
|
rs2 += 0x8;
|
|
rd = (insn >> 2) & 0x7;
|
|
rd += 0x8;
|
|
|
|
if (direction == VM_DIR_WRITE) {
|
|
if (m_op(insn, MATCH_C_SW, MASK_C_SW))
|
|
access_size = 4;
|
|
else if (m_op(insn, MATCH_C_SD, MASK_C_SD))
|
|
access_size = 8;
|
|
else {
|
|
printf("unknown compressed store instr at %lx",
|
|
guest_addr);
|
|
return (-4);
|
|
}
|
|
} else {
|
|
if (m_op(insn, MATCH_C_LW, MASK_C_LW))
|
|
access_size = 4;
|
|
else if (m_op(insn, MATCH_C_LD, MASK_C_LD))
|
|
access_size = 8;
|
|
else {
|
|
printf("unknown load instr at %lx", guest_addr);
|
|
return (-5);
|
|
}
|
|
}
|
|
reg_num = rd;
|
|
vme_ret->inst_length = 2;
|
|
}
|
|
|
|
vme_ret->u.inst_emul.gpa = (vme_ret->htval << 2) |
|
|
(vme_ret->stval & 0x3);
|
|
|
|
dprintf("guest_addr %lx insn %lx, reg %d, gpa %lx\n", guest_addr, insn,
|
|
reg_num, vme_ret->u.inst_emul.gpa);
|
|
|
|
vie = &vme_ret->u.inst_emul.vie;
|
|
vie->dir = direction;
|
|
vie->reg = reg_num;
|
|
vie->sign_extend = sign_extend;
|
|
vie->access_size = access_size;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static bool
|
|
riscv_handle_world_switch(struct hypctx *hypctx, struct vm_exit *vme,
|
|
pmap_t pmap)
|
|
{
|
|
struct hyptrap trap;
|
|
uint64_t insn;
|
|
uint64_t gpa;
|
|
bool handled;
|
|
bool retu;
|
|
int ret;
|
|
int i;
|
|
|
|
handled = false;
|
|
|
|
if (vme->scause & SCAUSE_INTR) {
|
|
/*
|
|
* Host interrupt? Leave critical section to handle.
|
|
*/
|
|
vmm_stat_incr(hypctx->vcpu, VMEXIT_IRQ, 1);
|
|
vme->exitcode = VM_EXITCODE_BOGUS;
|
|
vme->inst_length = 0;
|
|
return (handled);
|
|
}
|
|
|
|
switch (vme->scause) {
|
|
case SCAUSE_FETCH_GUEST_PAGE_FAULT:
|
|
case SCAUSE_LOAD_GUEST_PAGE_FAULT:
|
|
case SCAUSE_STORE_GUEST_PAGE_FAULT:
|
|
gpa = (vme->htval << 2) | (vme->stval & 0x3);
|
|
if (vm_mem_allocated(hypctx->vcpu, gpa)) {
|
|
vme->exitcode = VM_EXITCODE_PAGING;
|
|
vme->inst_length = 0;
|
|
vme->u.paging.gpa = gpa;
|
|
} else {
|
|
ret = riscv_gen_inst_emul_data(hypctx, vme, &trap);
|
|
if (ret != 0) {
|
|
vme->exitcode = VM_EXITCODE_HYP;
|
|
vme->u.hyp.scause = trap.scause;
|
|
break;
|
|
}
|
|
vme->exitcode = VM_EXITCODE_INST_EMUL;
|
|
}
|
|
break;
|
|
case SCAUSE_ILLEGAL_INSTRUCTION:
|
|
/*
|
|
* TODO: handle illegal instruction properly.
|
|
*/
|
|
printf("%s: Illegal instruction at %lx stval 0x%lx htval "
|
|
"0x%lx\n", __func__, vme->sepc, vme->stval, vme->htval);
|
|
vmm_stat_incr(hypctx->vcpu, VMEXIT_UNHANDLED, 1);
|
|
vme->exitcode = VM_EXITCODE_BOGUS;
|
|
handled = false;
|
|
break;
|
|
case SCAUSE_VIRTUAL_SUPERVISOR_ECALL:
|
|
retu = false;
|
|
vmm_sbi_ecall(hypctx->vcpu, &retu);
|
|
if (retu == false) {
|
|
handled = true;
|
|
break;
|
|
}
|
|
for (i = 0; i < nitems(vme->u.ecall.args); i++)
|
|
vme->u.ecall.args[i] = hypctx->guest_regs.hyp_a[i];
|
|
vme->exitcode = VM_EXITCODE_ECALL;
|
|
handled = false;
|
|
break;
|
|
case SCAUSE_VIRTUAL_INSTRUCTION:
|
|
insn = vme->stval;
|
|
if (m_op(insn, MATCH_WFI, MASK_WFI))
|
|
vme->exitcode = VM_EXITCODE_WFI;
|
|
else
|
|
vme->exitcode = VM_EXITCODE_BOGUS;
|
|
handled = false;
|
|
break;
|
|
default:
|
|
printf("unknown scause %lx\n", vme->scause);
|
|
vmm_stat_incr(hypctx->vcpu, VMEXIT_UNHANDLED, 1);
|
|
vme->exitcode = VM_EXITCODE_BOGUS;
|
|
handled = false;
|
|
break;
|
|
}
|
|
|
|
return (handled);
|
|
}
|
|
|
|
int
|
|
vmmops_gla2gpa(void *vcpui, struct vm_guest_paging *paging, uint64_t gla,
|
|
int prot, uint64_t *gpa, int *is_fault)
|
|
{
|
|
|
|
/* Implement me. */
|
|
|
|
return (ENOSYS);
|
|
}
|
|
|
|
void
|
|
riscv_send_ipi(struct hypctx *hypctx, int hart_id)
|
|
{
|
|
struct hyp *hyp;
|
|
struct vm *vm;
|
|
|
|
hyp = hypctx->hyp;
|
|
vm = hyp->vm;
|
|
|
|
atomic_set_32(&hypctx->ipi_pending, 1);
|
|
|
|
vcpu_notify_event(vm_vcpu(vm, hart_id));
|
|
}
|
|
|
|
int
|
|
riscv_check_ipi(struct hypctx *hypctx, bool clear)
|
|
{
|
|
int val;
|
|
|
|
if (clear)
|
|
val = atomic_swap_32(&hypctx->ipi_pending, 0);
|
|
else
|
|
val = hypctx->ipi_pending;
|
|
|
|
return (val);
|
|
}
|
|
|
|
static void
|
|
riscv_sync_interrupts(struct hypctx *hypctx)
|
|
{
|
|
int pending;
|
|
|
|
pending = aplic_check_pending(hypctx);
|
|
|
|
if (pending)
|
|
hypctx->guest_csrs.hvip |= HVIP_VSEIP;
|
|
else
|
|
hypctx->guest_csrs.hvip &= ~HVIP_VSEIP;
|
|
|
|
csr_write(hvip, hypctx->guest_csrs.hvip);
|
|
}
|
|
|
|
static void
|
|
riscv_sync_ipi(struct hypctx *hypctx)
|
|
{
|
|
|
|
/* Guest clears VSSIP bit manually. */
|
|
if (riscv_check_ipi(hypctx, true))
|
|
hypctx->guest_csrs.hvip |= HVIP_VSSIP;
|
|
|
|
csr_write(hvip, hypctx->guest_csrs.hvip);
|
|
}
|
|
|
|
int
|
|
vmmops_run(void *vcpui, register_t pc, pmap_t pmap, struct vm_eventinfo *evinfo)
|
|
{
|
|
struct hypctx *hypctx;
|
|
struct vm_exit *vme;
|
|
struct vcpu *vcpu;
|
|
register_t val;
|
|
bool handled;
|
|
|
|
hypctx = (struct hypctx *)vcpui;
|
|
vcpu = hypctx->vcpu;
|
|
vme = vm_exitinfo(vcpu);
|
|
|
|
hypctx->guest_regs.hyp_sepc = (uint64_t)pc;
|
|
|
|
vmmops_delegate();
|
|
|
|
/*
|
|
* From The RISC-V Instruction Set Manual
|
|
* Volume II: RISC-V Privileged Architectures
|
|
*
|
|
* If the new virtual machine's guest physical page tables
|
|
* have been modified, it may be necessary to execute an HFENCE.GVMA
|
|
* instruction (see Section 5.3.2) before or after writing hgatp.
|
|
*/
|
|
__asm __volatile("hfence.gvma" ::: "memory");
|
|
|
|
csr_write(hgatp, pmap->pm_satp);
|
|
csr_write(henvcfg, HENVCFG_STCE);
|
|
csr_write(hie, HIE_VSEIE | HIE_VSSIE | HIE_SGEIE);
|
|
/* TODO: should we trap rdcycle / rdtime? */
|
|
csr_write(hcounteren, HCOUNTEREN_CY | HCOUNTEREN_TM);
|
|
|
|
vmmops_vcpu_restore_csrs(hypctx);
|
|
|
|
for (;;) {
|
|
dprintf("%s: pc %lx\n", __func__, pc);
|
|
|
|
if (hypctx->has_exception) {
|
|
hypctx->has_exception = false;
|
|
/*
|
|
* TODO: implement exception injection.
|
|
*/
|
|
}
|
|
|
|
val = intr_disable();
|
|
|
|
/* Check if the vcpu is suspended */
|
|
if (vcpu_suspended(evinfo)) {
|
|
intr_restore(val);
|
|
vm_exit_suspended(vcpu, pc);
|
|
break;
|
|
}
|
|
|
|
if (vcpu_debugged(vcpu)) {
|
|
intr_restore(val);
|
|
vm_exit_debug(vcpu, pc);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* TODO: What happens if a timer interrupt is asserted exactly
|
|
* here, but for the previous VM?
|
|
*/
|
|
riscv_set_active_vcpu(hypctx);
|
|
aplic_flush_hwstate(hypctx);
|
|
|
|
riscv_sync_interrupts(hypctx);
|
|
riscv_sync_ipi(hypctx);
|
|
|
|
dprintf("%s: Entering guest VM, vsatp %lx, ss %lx hs %lx\n",
|
|
__func__, csr_read(vsatp), hypctx->guest_regs.hyp_sstatus,
|
|
hypctx->guest_regs.hyp_hstatus);
|
|
|
|
vmm_switch(hypctx);
|
|
|
|
dprintf("%s: Leaving guest VM, hstatus %lx\n", __func__,
|
|
hypctx->guest_regs.hyp_hstatus);
|
|
|
|
aplic_sync_hwstate(hypctx);
|
|
riscv_sync_interrupts(hypctx);
|
|
|
|
/*
|
|
* TODO: deactivate stage 2 pmap here if needed.
|
|
*/
|
|
|
|
vme->scause = csr_read(scause);
|
|
vme->sepc = csr_read(sepc);
|
|
vme->stval = csr_read(stval);
|
|
vme->htval = csr_read(htval);
|
|
vme->htinst = csr_read(htinst);
|
|
|
|
intr_restore(val);
|
|
|
|
vmm_stat_incr(vcpu, VMEXIT_COUNT, 1);
|
|
vme->pc = hypctx->guest_regs.hyp_sepc;
|
|
vme->inst_length = INSN_SIZE;
|
|
|
|
handled = riscv_handle_world_switch(hypctx, vme, pmap);
|
|
if (handled == false)
|
|
/* Exit loop to emulate instruction. */
|
|
break;
|
|
else {
|
|
/* Resume guest execution from the next instruction. */
|
|
hypctx->guest_regs.hyp_sepc += vme->inst_length;
|
|
}
|
|
}
|
|
|
|
vmmops_vcpu_save_csrs(hypctx);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
riscv_pcpu_vmcleanup(void *arg)
|
|
{
|
|
struct hyp *hyp;
|
|
int i, maxcpus;
|
|
|
|
hyp = arg;
|
|
maxcpus = vm_get_maxcpus(hyp->vm);
|
|
for (i = 0; i < maxcpus; i++) {
|
|
if (riscv_get_active_vcpu() == hyp->ctx[i]) {
|
|
riscv_set_active_vcpu(NULL);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
vmmops_vcpu_cleanup(void *vcpui)
|
|
{
|
|
struct hypctx *hypctx;
|
|
|
|
hypctx = vcpui;
|
|
|
|
dprintf("%s\n", __func__);
|
|
|
|
aplic_cpucleanup(hypctx);
|
|
|
|
free(hypctx, M_HYP);
|
|
}
|
|
|
|
void
|
|
vmmops_cleanup(void *vmi)
|
|
{
|
|
struct hyp *hyp;
|
|
|
|
hyp = vmi;
|
|
|
|
dprintf("%s\n", __func__);
|
|
|
|
aplic_vmcleanup(hyp);
|
|
|
|
smp_rendezvous(NULL, riscv_pcpu_vmcleanup, NULL, hyp);
|
|
|
|
free(hyp, M_HYP);
|
|
}
|
|
|
|
/*
|
|
* Return register value. Registers have different sizes and an explicit cast
|
|
* must be made to ensure proper conversion.
|
|
*/
|
|
static uint64_t *
|
|
hypctx_regptr(struct hypctx *hypctx, int reg)
|
|
{
|
|
|
|
switch (reg) {
|
|
case VM_REG_GUEST_RA:
|
|
return (&hypctx->guest_regs.hyp_ra);
|
|
case VM_REG_GUEST_SP:
|
|
return (&hypctx->guest_regs.hyp_sp);
|
|
case VM_REG_GUEST_GP:
|
|
return (&hypctx->guest_regs.hyp_gp);
|
|
case VM_REG_GUEST_TP:
|
|
return (&hypctx->guest_regs.hyp_tp);
|
|
case VM_REG_GUEST_T0:
|
|
return (&hypctx->guest_regs.hyp_t[0]);
|
|
case VM_REG_GUEST_T1:
|
|
return (&hypctx->guest_regs.hyp_t[1]);
|
|
case VM_REG_GUEST_T2:
|
|
return (&hypctx->guest_regs.hyp_t[2]);
|
|
case VM_REG_GUEST_S0:
|
|
return (&hypctx->guest_regs.hyp_s[0]);
|
|
case VM_REG_GUEST_S1:
|
|
return (&hypctx->guest_regs.hyp_s[1]);
|
|
case VM_REG_GUEST_A0:
|
|
return (&hypctx->guest_regs.hyp_a[0]);
|
|
case VM_REG_GUEST_A1:
|
|
return (&hypctx->guest_regs.hyp_a[1]);
|
|
case VM_REG_GUEST_A2:
|
|
return (&hypctx->guest_regs.hyp_a[2]);
|
|
case VM_REG_GUEST_A3:
|
|
return (&hypctx->guest_regs.hyp_a[3]);
|
|
case VM_REG_GUEST_A4:
|
|
return (&hypctx->guest_regs.hyp_a[4]);
|
|
case VM_REG_GUEST_A5:
|
|
return (&hypctx->guest_regs.hyp_a[5]);
|
|
case VM_REG_GUEST_A6:
|
|
return (&hypctx->guest_regs.hyp_a[6]);
|
|
case VM_REG_GUEST_A7:
|
|
return (&hypctx->guest_regs.hyp_a[7]);
|
|
case VM_REG_GUEST_S2:
|
|
return (&hypctx->guest_regs.hyp_s[2]);
|
|
case VM_REG_GUEST_S3:
|
|
return (&hypctx->guest_regs.hyp_s[3]);
|
|
case VM_REG_GUEST_S4:
|
|
return (&hypctx->guest_regs.hyp_s[4]);
|
|
case VM_REG_GUEST_S5:
|
|
return (&hypctx->guest_regs.hyp_s[5]);
|
|
case VM_REG_GUEST_S6:
|
|
return (&hypctx->guest_regs.hyp_s[6]);
|
|
case VM_REG_GUEST_S7:
|
|
return (&hypctx->guest_regs.hyp_s[7]);
|
|
case VM_REG_GUEST_S8:
|
|
return (&hypctx->guest_regs.hyp_s[8]);
|
|
case VM_REG_GUEST_S9:
|
|
return (&hypctx->guest_regs.hyp_s[9]);
|
|
case VM_REG_GUEST_S10:
|
|
return (&hypctx->guest_regs.hyp_s[10]);
|
|
case VM_REG_GUEST_S11:
|
|
return (&hypctx->guest_regs.hyp_s[11]);
|
|
case VM_REG_GUEST_T3:
|
|
return (&hypctx->guest_regs.hyp_t[3]);
|
|
case VM_REG_GUEST_T4:
|
|
return (&hypctx->guest_regs.hyp_t[4]);
|
|
case VM_REG_GUEST_T5:
|
|
return (&hypctx->guest_regs.hyp_t[5]);
|
|
case VM_REG_GUEST_T6:
|
|
return (&hypctx->guest_regs.hyp_t[6]);
|
|
case VM_REG_GUEST_SEPC:
|
|
return (&hypctx->guest_regs.hyp_sepc);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
int
|
|
vmmops_getreg(void *vcpui, int reg, uint64_t *retval)
|
|
{
|
|
uint64_t *regp;
|
|
int running, hostcpu;
|
|
struct hypctx *hypctx;
|
|
|
|
hypctx = vcpui;
|
|
|
|
running = vcpu_is_running(hypctx->vcpu, &hostcpu);
|
|
if (running && hostcpu != curcpu)
|
|
panic("%s: %s%d is running", __func__, vm_name(hypctx->hyp->vm),
|
|
vcpu_vcpuid(hypctx->vcpu));
|
|
|
|
if (reg == VM_REG_GUEST_ZERO) {
|
|
*retval = 0;
|
|
return (0);
|
|
}
|
|
|
|
regp = hypctx_regptr(hypctx, reg);
|
|
if (regp == NULL)
|
|
return (EINVAL);
|
|
|
|
*retval = *regp;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
vmmops_setreg(void *vcpui, int reg, uint64_t val)
|
|
{
|
|
struct hypctx *hypctx;
|
|
int running, hostcpu;
|
|
uint64_t *regp;
|
|
|
|
hypctx = vcpui;
|
|
|
|
running = vcpu_is_running(hypctx->vcpu, &hostcpu);
|
|
if (running && hostcpu != curcpu)
|
|
panic("%s: %s%d is running", __func__, vm_name(hypctx->hyp->vm),
|
|
vcpu_vcpuid(hypctx->vcpu));
|
|
|
|
regp = hypctx_regptr(hypctx, reg);
|
|
if (regp == NULL)
|
|
return (EINVAL);
|
|
|
|
*regp = val;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
vmmops_exception(void *vcpui, uint64_t scause)
|
|
{
|
|
struct hypctx *hypctx;
|
|
int running, hostcpu;
|
|
|
|
hypctx = vcpui;
|
|
|
|
running = vcpu_is_running(hypctx->vcpu, &hostcpu);
|
|
if (running && hostcpu != curcpu)
|
|
panic("%s: %s%d is running", __func__, vm_name(hypctx->hyp->vm),
|
|
vcpu_vcpuid(hypctx->vcpu));
|
|
|
|
/* TODO: implement me. */
|
|
|
|
return (ENOSYS);
|
|
}
|
|
|
|
int
|
|
vmmops_getcap(void *vcpui, int num, int *retval)
|
|
{
|
|
int ret;
|
|
|
|
ret = ENOENT;
|
|
|
|
switch (num) {
|
|
case VM_CAP_UNRESTRICTED_GUEST:
|
|
*retval = 1;
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
vmmops_setcap(void *vcpui, int num, int val)
|
|
{
|
|
|
|
return (ENOENT);
|
|
}
|