blob: cbefeb4988f3ddb517b965c66d1cd4e2460ea0f8 [file] [log] [blame]
/*
* Copyright 2017, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the GNU General Public License version 2. Note that NO WARRANTY is provided.
* See "LICENSE_GPLv2.txt" for details.
*
* @TAG(DATA61_GPL)
*/
/* vm exits and general handling of interrupt injection */
#include <stdio.h>
#include <stdlib.h>
#include <sel4/sel4.h>
#include "vmm/debug.h"
#include "vmm/vmm.h"
#include "vmm/processor/decode.h"
#define TRAMPOLINE_LENGTH (100)
static void resume_guest(vmm_vcpu_t *vcpu) {
/* Disable exit-for-interrupt in guest state to allow the guest to resume. */
uint32_t state = vmm_guest_state_get_control_ppc(&vcpu->guest_state);
state &= ~BIT(2); /* clear the exit for interrupt flag */
vmm_guest_state_set_control_ppc(&vcpu->guest_state, state);
}
static void inject_irq(vmm_vcpu_t *vcpu, int irq) {
/* Inject a vectored exception into the guest */
assert(irq >= 16);
vmm_guest_state_set_control_entry(&vcpu->guest_state, BIT(31) | irq);
}
void wait_for_guest_ready(vmm_vcpu_t *vcpu) {
/* Request that the guest exit at the earliest point that we can inject an interrupt. */
uint32_t state = vmm_guest_state_get_control_ppc(&vcpu->guest_state);
state |= BIT(2); /* set the exit for interrupt flag */
vmm_guest_state_set_control_ppc(&vcpu->guest_state, state);
}
int can_inject(vmm_vcpu_t *vcpu) {
uint32_t rflags = vmm_guest_state_get_rflags(&vcpu->guest_state, vcpu->guest_vcpu);
uint32_t guest_int = vmm_guest_state_get_interruptibility(&vcpu->guest_state, vcpu->guest_vcpu);
uint32_t int_control = vmm_guest_state_get_control_entry(&vcpu->guest_state);
/* we can only inject if the interrupt mask flag is not set in flags,
guest is not in an uninterruptable state and we are not already trying to
inject an interrupt */
if ( (rflags & BIT(9)) && (guest_int & 0xF) == 0 && (int_control & BIT(31)) == 0) {
return 1;
}
return 0;
}
/* This function is called by the local apic when a new interrupt has occured. */
void vmm_have_pending_interrupt(vmm_vcpu_t *vcpu) {
if (vmm_apic_has_interrupt(vcpu) >= 0) {
/* there is actually an interrupt to inject */
if (can_inject(vcpu)) {
if (vcpu->guest_state.virt.interrupt_halt) {
/* currently halted. need to put the guest
* in a state where it can inject again */
wait_for_guest_ready(vcpu);
vcpu->guest_state.virt.interrupt_halt = 0;
vmm_sync_guest_state(vcpu);
vmm_reply_vm_exit(vcpu); /* unblock the guest */
} else {
int irq = vmm_apic_get_interrupt(vcpu);
inject_irq(vcpu, irq);
/* see if there are more */
if (vmm_apic_has_interrupt(vcpu) >= 0) {
wait_for_guest_ready(vcpu);
}
}
} else {
wait_for_guest_ready(vcpu);
if (vcpu->guest_state.virt.interrupt_halt) {
vcpu->guest_state.virt.interrupt_halt = 0;
vmm_sync_guest_state(vcpu);
vmm_reply_vm_exit(vcpu); /* unblock the guest */
}
}
}
}
int vmm_pending_interrupt_handler(vmm_vcpu_t *vcpu) {
/* see if there is actually a pending interrupt */
assert(can_inject(vcpu));
int irq = vmm_apic_get_interrupt(vcpu);
if (irq == -1) {
resume_guest(vcpu);
} else {
/* inject the interrupt */
inject_irq(vcpu, irq);
if (!(vmm_apic_has_interrupt(vcpu) >= 0)) {
resume_guest(vcpu);
}
vcpu->guest_state.virt.interrupt_halt = 0;
}
return 0;
}
/* Start an AP vcpu after a sipi with the requested vector */
void vmm_start_ap_vcpu(vmm_vcpu_t *vcpu, unsigned int sipi_vector)
{
DPRINTF(1, "trying to start vcpu %d\n", vcpu->vcpu_id);
uint16_t segment = sipi_vector * 0x100;
uintptr_t eip = sipi_vector * 0x1000;
guest_state_t *gs = &vcpu->guest_state;
/* Emulate up to 100 bytes of trampoline code */
uint8_t instr[TRAMPOLINE_LENGTH];
vmm_fetch_instruction(vcpu, eip, vmm_guest_state_get_cr3(gs, vcpu->guest_vcpu),
TRAMPOLINE_LENGTH, instr);
eip = vmm_emulate_realmode(&vcpu->vmm->guest_mem, instr, &segment, eip,
TRAMPOLINE_LENGTH, gs);
vmm_guest_state_set_eip(&vcpu->guest_state, eip);
vmm_sync_guest_context(vcpu);
vmm_sync_guest_state(vcpu);
assert(!"no tcb");
// seL4_TCB_Resume(vcpu->guest_tcb);
}
/* Got interrupt(s) from PIC, propagate to relevant vcpu lapic */
void vmm_check_external_interrupt(vmm_t *vmm)
{
/* TODO if all lapics are enabled, store which lapic
(only one allowed) receives extints, and short circuit this */
if (vmm->plat_callbacks.has_interrupt() != -1) {
for (int i = 0; i < vmm->num_vcpus; i++) {
vmm_vcpu_t *vcpu = &vmm->vcpus[i];
if (vmm_apic_accept_pic_intr(vcpu)) {
vmm_vcpu_accept_interrupt(vcpu);
break; /* Only one VCPU can take a PIC interrupt */
}
}
}
}
void vmm_vcpu_accept_interrupt(vmm_vcpu_t *vcpu)
{
if (vmm_apic_has_interrupt(vcpu) == -1) {
return;
}
/* in an exit, can call the regular injection method */
vmm_have_pending_interrupt(vcpu);
}