| /* |
| * Copyright 2019, 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 BSD 2-Clause license. Note that NO WARRANTY is provided. |
| * See "LICENSE_BSD2.txt" for details. |
| * |
| * @TAG(DATA61_BSD) |
| */ |
| |
| #include <autoconf.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include <sel4/sel4.h> |
| #include <sel4/messages.h> |
| |
| #include <sel4vm/guest_vm.h> |
| #include <sel4vm/guest_vm_util.h> |
| #include <sel4vm/boot.h> |
| #include <sel4vm/guest_vm_exits.h> |
| #include <sel4vm/guest_irq_controller.h> |
| #include <sel4vm/sel4_arch/processor.h> |
| #include <sel4vm/arch/guest_arm_context.h> |
| |
| #include "vm.h" |
| #include "arm_vm.h" |
| #include "arm_vm_exits.h" |
| #include "fault.h" |
| |
| #include "vgic/vgic.h" |
| #include "syscalls.h" |
| #include "mem_abort.h" |
| |
| static int vm_user_exception_handler(vm_vcpu_t *vcpu); |
| static int vm_vcpu_handler(vm_vcpu_t *vcpu); |
| static int vm_unknown_exit_handler(vm_vcpu_t *vcpu); |
| static int vm_vppi_event_handler(vm_vcpu_t *vcpu); |
| |
| static vm_exit_handler_fn_t arm_exit_handlers[] = { |
| [VM_GUEST_ABORT_EXIT] = vm_guest_mem_abort_handler, |
| [VM_SYSCALL_EXIT] = vm_syscall_handler, |
| [VM_USER_EXCEPTION_EXIT] = vm_user_exception_handler, |
| [VM_VGIC_MAINTENANCE_EXIT] = vm_vgic_maintenance_handler, |
| [VM_VCPU_EXIT] = vm_vcpu_handler, |
| [VM_VPPI_EXIT] = vm_vppi_event_handler, |
| [VM_UNKNOWN_EXIT] = vm_unknown_exit_handler |
| }; |
| |
| static int vm_decode_exit(seL4_Word label) |
| { |
| int exit_reason = VM_UNKNOWN_EXIT; |
| |
| switch (label) { |
| case seL4_Fault_VMFault: |
| exit_reason = VM_GUEST_ABORT_EXIT; |
| break; |
| case seL4_Fault_UnknownSyscall: |
| exit_reason = VM_SYSCALL_EXIT; |
| break; |
| case seL4_Fault_UserException: |
| exit_reason = VM_USER_EXCEPTION_EXIT; |
| break; |
| case seL4_Fault_VGICMaintenance: |
| exit_reason = VM_VGIC_MAINTENANCE_EXIT; |
| break; |
| case seL4_Fault_VCPUFault: |
| exit_reason = VM_VCPU_EXIT; |
| break; |
| case seL4_Fault_VPPIEvent: |
| exit_reason = VM_VPPI_EXIT; |
| break; |
| default: |
| exit_reason = VM_UNKNOWN_EXIT; |
| } |
| return exit_reason; |
| } |
| |
| static int handle_exception(vm_vcpu_t *vcpu, seL4_Word ip) |
| { |
| seL4_UserContext regs; |
| seL4_CPtr tcb = vm_get_vcpu_tcb(vcpu); |
| int err; |
| ZF_LOGE("%sInvalid instruction from [%s] at PC: 0x"XFMT"%s\n", |
| ANSI_COLOR(RED, BOLD), vcpu->vm->vm_name, seL4_GetMR(0), ANSI_COLOR(RESET)); |
| err = seL4_TCB_ReadRegisters(tcb, false, 0, sizeof(regs) / sizeof(regs.pc), ®s); |
| assert(!err); |
| print_ctx_regs(®s); |
| return VM_EXIT_HANDLED; |
| } |
| |
| static int vm_vppi_event_handler(vm_vcpu_t *vcpu) |
| { |
| int err; |
| seL4_Word ppi_irq; |
| ppi_irq = seL4_GetMR(0); |
| /* We directly inject the interrupt assuming it has been previously registered |
| * If not the interrupt will dropped by the VM */ |
| err = vm_inject_irq(vcpu, ppi_irq); |
| if (err) { |
| ZF_LOGE("VPPI IRQ %d dropped on vcpu %d", ppi_irq, vcpu->vcpu_id); |
| /* Acknowledge to unmask it as our guest will not use the interrupt */ |
| seL4_Error ack_err = seL4_ARM_VCPU_AckVPPI(vcpu->vcpu.cptr, ppi_irq); |
| if (ack_err) { |
| ZF_LOGE("Failed to ACK VPPI: VPPI Ack invocation failed"); |
| return -1; |
| } |
| } |
| seL4_MessageInfo_t reply; |
| reply = seL4_MessageInfo_new(0, 0, 0, 0); |
| seL4_Reply(reply); |
| return 0; |
| } |
| |
| static int vm_user_exception_handler(vm_vcpu_t *vcpu) |
| { |
| seL4_Word ip; |
| int err; |
| ip = seL4_GetMR(0); |
| err = handle_exception(vcpu, ip); |
| assert(!err); |
| if (!err) { |
| seL4_MessageInfo_t reply; |
| reply = seL4_MessageInfo_new(0, 0, 0, 0); |
| seL4_Reply(reply); |
| } |
| return VM_EXIT_HANDLED; |
| } |
| |
| static void print_unhandled_vcpu_hsr(vm_vcpu_t *vcpu, uint32_t hsr) |
| { |
| printf("======= Unhandled VCPU fault from [%s] =======\n", vcpu->vm->vm_name); |
| printf("HSR Value: 0x%08x\n", hsr); |
| printf("HSR Exception Class: %s [0x%x]\n", hsr_reasons[HSR_EXCEPTION_CLASS(hsr)], HSR_EXCEPTION_CLASS(hsr)); |
| printf("Instruction Length: %d\n", HSR_IL(hsr)); |
| printf("ISS Value: 0x%x\n", hsr & HSR_ISS_MASK); |
| printf("==============================================\n"); |
| } |
| |
| static int vm_vcpu_handler(vm_vcpu_t *vcpu) |
| { |
| uint32_t hsr; |
| int err; |
| fault_t *fault; |
| fault = vcpu->vcpu_arch.fault; |
| hsr = seL4_GetMR(seL4_UnknownSyscall_ARG0); |
| if (vcpu->vcpu_arch.unhandled_vcpu_callback) { |
| /* Pass the vcpu fault to library user in case they can handle it */ |
| err = new_vcpu_fault(fault, hsr); |
| if (err) { |
| ZF_LOGE("Failed to create new fault"); |
| return VM_EXIT_HANDLE_ERROR; |
| } |
| err = vcpu->vcpu_arch.unhandled_vcpu_callback(vcpu, hsr, vcpu->vcpu_arch.unhandled_vcpu_callback_cookie); |
| if (!err) { |
| return VM_EXIT_HANDLED; |
| } |
| } |
| print_unhandled_vcpu_hsr(vcpu, hsr); |
| return VM_EXIT_HANDLE_ERROR; |
| } |
| |
| static int vm_unknown_exit_handler(vm_vcpu_t *vcpu) |
| { |
| /* What? Why are we here? What just happened? */ |
| ZF_LOGE("Unknown fault from [%s]", vcpu->vm->vm_name); |
| vcpu->vm->run.exit_reason = VM_GUEST_UNKNOWN_EXIT; |
| return VM_EXIT_HANDLE_ERROR; |
| } |
| |
| static int vcpu_stop(vm_vcpu_t *vcpu) |
| { |
| vcpu->vcpu_online = false; |
| return seL4_TCB_Suspend(vm_get_vcpu_tcb(vcpu)); |
| } |
| |
| int vcpu_start(vm_vcpu_t *vcpu) |
| { |
| int err; |
| vcpu->vcpu_online = true; |
| seL4_Word vmpidr_val; |
| seL4_Word vmpidr_reg; |
| |
| #if CONFIG_MAX_NUM_NODES > 1 |
| #ifdef CONFIG_ARCH_AARCH64 |
| vmpidr_reg = seL4_VCPUReg_VMPIDR_EL2; |
| #else |
| vmpidr_reg = seL4_VCPUReg_VMPIDR; |
| #endif |
| if (vcpu->vcpu_id == BOOT_VCPU) { |
| /* VMPIDR Bit Assignments [G8.2.167, Arm Architecture Reference Manual Armv8] |
| * - BIT(24): Performance of PEs (processing element) at the lowest affinity level is very interdependent |
| * - BIT(31): This implementation includes the ARMv7 Multiprocessing Extensions functionality |
| */ |
| vmpidr_val = BIT(24) | BIT(31); |
| } else { |
| vmpidr_val = vcpu->target_cpu; |
| } |
| err = vm_set_arm_vcpu_reg(vcpu, vmpidr_reg, vmpidr_val); |
| if (err) { |
| ZF_LOGE("Failed to set VMPIDR register"); |
| return -1; |
| } |
| #endif |
| return seL4_TCB_Resume(vm_get_vcpu_tcb(vcpu)); |
| } |
| |
| int vm_register_unhandled_vcpu_fault_callback(vm_vcpu_t *vcpu, unhandled_vcpu_fault_callback_fn vcpu_fault_callback, |
| void *cookie) |
| { |
| if (!vcpu) { |
| ZF_LOGE("Failed to register fault callback: Invalid VCPU handle"); |
| return -1; |
| } |
| |
| if (!vcpu_fault_callback) { |
| ZF_LOGE("Failed to register vcpu fault callback: Invalid callback"); |
| return -1; |
| } |
| vcpu->vcpu_arch.unhandled_vcpu_callback = vcpu_fault_callback; |
| vcpu->vcpu_arch.unhandled_vcpu_callback_cookie = cookie; |
| return 0; |
| |
| } |
| |
| int vm_run_arch(vm_t *vm) |
| { |
| int err; |
| int ret; |
| |
| ret = 1; |
| /* Loop, handling events */ |
| while (ret > 0) { |
| seL4_MessageInfo_t tag; |
| seL4_Word sender_badge; |
| seL4_Word label; |
| int vm_exit_reason; |
| |
| tag = seL4_Recv(vm->host_endpoint, &sender_badge); |
| label = seL4_MessageInfo_get_label(tag); |
| if (sender_badge >= MIN_VCPU_BADGE && sender_badge <= MAX_VCPU_BADGE) { |
| seL4_Word vcpu_idx = VCPU_BADGE_IDX(sender_badge); |
| if (vcpu_idx >= vm->num_vcpus) { |
| ZF_LOGE("Invalid VCPU index. Exiting"); |
| ret = -1; |
| } else { |
| vm_exit_reason = vm_decode_exit(label); |
| ret = arm_exit_handlers[vm_exit_reason](vm->vcpus[vcpu_idx]); |
| if (ret == VM_EXIT_HANDLE_ERROR) { |
| vm->run.exit_reason = VM_GUEST_ERROR_EXIT; |
| } |
| } |
| } else { |
| if (vm->run.notification_callback) { |
| err = vm->run.notification_callback(vm, sender_badge, tag, |
| vm->run.notification_callback_cookie); |
| } else { |
| ZF_LOGE("Unable to handle VM notification. Exiting"); |
| err = -1; |
| } |
| if (err) { |
| ret = -1; |
| vm->run.exit_reason = VM_GUEST_ERROR_EXIT; |
| } |
| } |
| } |
| |
| return ret; |
| } |