| /* | |
| * Copyright 2014, NICTA | |
| * | |
| * 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(NICTA_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); | |
| } | |