| /* |
| * 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 <sel4vm/guest_vm.h> |
| #include <sel4vm/boot.h> |
| |
| #include "vm.h" |
| #include "i8259/i8259.h" |
| #include "guest_state.h" |
| #include "processor/decode.h" |
| #include "processor/lapic.h" |
| #include "interrupt.h" |
| |
| #define TRAMPOLINE_LENGTH (100) |
| |
| static void resume_guest(vm_vcpu_t *vcpu) |
| { |
| /* Disable exit-for-interrupt in guest state to allow the guest to resume. */ |
| uint32_t state = vm_guest_state_get_control_ppc(vcpu->vcpu_arch.guest_state); |
| state &= ~BIT(2); /* clear the exit for interrupt flag */ |
| vm_guest_state_set_control_ppc(vcpu->vcpu_arch.guest_state, state); |
| } |
| |
| static void inject_irq(vm_vcpu_t *vcpu, int irq) |
| { |
| /* Inject a vectored exception into the guest */ |
| assert(irq >= 16); |
| vm_guest_state_set_control_entry(vcpu->vcpu_arch.guest_state, BIT(31) | irq); |
| } |
| |
| void vm_inject_exception(vm_vcpu_t *vcpu, int exception, int has_error, uint32_t error_code) |
| { |
| assert(exception < 16); |
| // ensure we are not already injecting an interrupt or exception |
| uint32_t int_control = vm_guest_state_get_control_entry(vcpu->vcpu_arch.guest_state); |
| if ((int_control & BIT(31)) != 0) { |
| ZF_LOGF("Cannot inject exception"); |
| } |
| if (has_error) { |
| vm_guest_state_set_entry_exception_error_code(vcpu->vcpu_arch.guest_state, error_code); |
| } |
| vm_guest_state_set_control_entry(vcpu->vcpu_arch.guest_state, BIT(31) | exception | 3 << 8 | (has_error ? BIT(11) : 0)); |
| } |
| |
| void wait_for_guest_ready(vm_vcpu_t *vcpu) |
| { |
| /* Request that the guest exit at the earliest point that we can inject an interrupt. */ |
| uint32_t state = vm_guest_state_get_control_ppc(vcpu->vcpu_arch.guest_state); |
| state |= BIT(2); /* set the exit for interrupt flag */ |
| vm_guest_state_set_control_ppc(vcpu->vcpu_arch.guest_state, state); |
| } |
| |
| int can_inject(vm_vcpu_t *vcpu) |
| { |
| uint32_t rflags = vm_guest_state_get_rflags(vcpu->vcpu_arch.guest_state, vcpu->vcpu.cptr); |
| uint32_t guest_int = vm_guest_state_get_interruptibility(vcpu->vcpu_arch.guest_state, vcpu->vcpu.cptr); |
| uint32_t int_control = vm_guest_state_get_control_entry(vcpu->vcpu_arch.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 vm_have_pending_interrupt(vm_vcpu_t *vcpu) |
| { |
| if (vm_apic_has_interrupt(vcpu) >= 0) { |
| /* there is actually an interrupt to inject */ |
| if (can_inject(vcpu)) { |
| if (vcpu->vcpu_arch.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->vcpu_arch.guest_state->virt.interrupt_halt = 0; |
| } else { |
| int irq = vm_apic_get_interrupt(vcpu); |
| inject_irq(vcpu, irq); |
| /* see if there are more */ |
| if (vm_apic_has_interrupt(vcpu) >= 0) { |
| wait_for_guest_ready(vcpu); |
| } |
| } |
| } else { |
| wait_for_guest_ready(vcpu); |
| if (vcpu->vcpu_arch.guest_state->virt.interrupt_halt) { |
| vcpu->vcpu_arch.guest_state->virt.interrupt_halt = 0; |
| } |
| } |
| } |
| } |
| |
| int vm_pending_interrupt_handler(vm_vcpu_t *vcpu) |
| { |
| /* see if there is actually a pending interrupt */ |
| assert(can_inject(vcpu)); |
| int irq = vm_apic_get_interrupt(vcpu); |
| if (irq == -1) { |
| resume_guest(vcpu); |
| } else { |
| /* inject the interrupt */ |
| inject_irq(vcpu, irq); |
| if (!(vm_apic_has_interrupt(vcpu) >= 0)) { |
| resume_guest(vcpu); |
| } |
| vcpu->vcpu_arch.guest_state->virt.interrupt_halt = 0; |
| } |
| return VM_EXIT_HANDLED; |
| } |
| |
| /* Start an AP vcpu after a sipi with the requested vector */ |
| void vm_start_ap_vcpu(vm_vcpu_t *vcpu, unsigned int sipi_vector) |
| { |
| ZF_LOGD("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->vcpu_arch.guest_state; |
| |
| /* Emulate up to 100 bytes of trampoline code */ |
| uint8_t instr[TRAMPOLINE_LENGTH]; |
| vm_fetch_instruction(vcpu, eip, vm_guest_state_get_cr3(gs, vcpu->vcpu.cptr), |
| TRAMPOLINE_LENGTH, instr); |
| |
| eip = vm_emulate_realmode(vcpu, instr, &segment, eip, |
| TRAMPOLINE_LENGTH, gs); |
| |
| vm_guest_state_set_eip(vcpu->vcpu_arch.guest_state, eip); |
| |
| vm_sync_guest_context(vcpu); |
| vm_sync_guest_vmcs_state(vcpu); |
| |
| assert(!"no tcb"); |
| // seL4_TCB_Resume(vcpu->guest_tcb); |
| } |
| |
| /* Got interrupt(s) from PIC, propagate to relevant vcpu lapic */ |
| void vm_check_external_interrupt(vm_t *vm) |
| { |
| /* TODO if all lapics are enabled, store which lapic |
| (only one allowed) receives extints, and short circuit this */ |
| if (i8259_has_interrupt(vm) != -1) { |
| vm_vcpu_t *vcpu = vm->vcpus[BOOT_VCPU]; |
| if (vm_apic_accept_pic_intr(vcpu)) { |
| vm_vcpu_accept_interrupt(vcpu); |
| } |
| } |
| } |
| |
| void vm_vcpu_accept_interrupt(vm_vcpu_t *vcpu) |
| { |
| if (vm_apic_has_interrupt(vcpu) == -1) { |
| return; |
| } |
| |
| /* in an exit, can call the regular injection method */ |
| vm_have_pending_interrupt(vcpu); |
| } |