/* * \brief Genode specific VirtualBox SUPLib supplements * \author Alexander Boettcher * \author Norman Feske * \author Christian Helmuth */ /* * Copyright (C) 2013-2019 Genode Labs GmbH * * This file is distributed under the terms of the GNU General Public License * version 2. */ #ifndef _VIRTUALBOX__VCPU_VMX_H_ #define _VIRTUALBOX__VCPU_VMX_H_ /* base includes */ #include #include #include /* libc includes */ #include /* VirtualBox includes */ #include /* Genode's VirtualBox includes */ #include "vcpu.h" #include "vmx.h" class Vcpu_handler_vmx : public Vcpu_handler { private: Genode::Vm_handler _handler; Genode::Vm_connection &_vm_session; Genode::Vm_session_client::Vcpu_id _vcpu; Genode::Attached_dataspace _state_ds; template void _vmx_ept() { Genode::addr_t const exit_qual = _state->qual_primary.value(); Genode::addr_t const exit_addr = _state->qual_secondary.value(); bool const unmap = exit_qual & 0x38; RTGCUINT vbox_errorcode = 0; if (exit_qual & VMX_EXIT_QUALIFICATION_EPT_INSTR_FETCH) vbox_errorcode |= X86_TRAP_PF_ID; if (exit_qual & VMX_EXIT_QUALIFICATION_EPT_DATA_WRITE) vbox_errorcode |= X86_TRAP_PF_RW; if (exit_qual & VMX_EXIT_QUALIFICATION_EPT_ENTRY_PRESENT) vbox_errorcode |= X86_TRAP_PF_P; npt_ept_exit_addr = exit_addr; npt_ept_unmap = unmap; npt_ept_errorcode = vbox_errorcode; _npt_ept(); } void _vmx_default() { _default_handler(); } void _vmx_startup() { /* configure VM exits to get */ /* from src/VBox/VMM/VMMR0/HWVMXR0.cpp of virtualbox sources */ next_utcb.ctrl[0] = VMX_VMCS_CTRL_PROC_EXEC_HLT_EXIT | VMX_VMCS_CTRL_PROC_EXEC_MOV_DR_EXIT | VMX_VMCS_CTRL_PROC_EXEC_UNCOND_IO_EXIT | /* VMX_VMCS_CTRL_PROC_EXEC_MONITOR_EXIT | VMX_VMCS_CTRL_PROC_EXEC_MWAIT_EXIT | */ /* VMX_VMCS_CTRL_PROC_EXEC_CR8_LOAD_EXIT | VMX_VMCS_CTRL_PROC_EXEC_CR8_STORE_EXIT |*/ VMX_VMCS_CTRL_PROC_EXEC_USE_TPR_SHADOW | VMX_VMCS_CTRL_PROC_EXEC_RDPMC_EXIT; /* VMX_VMCS_CTRL_PROC_EXEC_PAUSE_EXIT | */ /* * Disable trapping RDTSC for now as it creates a huge load with * VM guests that execute it frequently. */ // VMX_VMCS_CTRL_PROC_EXEC_RDTSC_EXIT; next_utcb.ctrl[1] = VMX_VMCS_CTRL_PROC_EXEC2_VIRT_APIC | VMX_VMCS_CTRL_PROC_EXEC2_WBINVD_EXIT | VMX_VMCS_CTRL_PROC_EXEC2_UNRESTRICTED_GUEST | VMX_VMCS_CTRL_PROC_EXEC2_VPID | VMX_VMCS_CTRL_PROC_EXEC2_RDTSCP | VMX_VMCS_CTRL_PROC_EXEC2_EPT | VMX_VMCS_CTRL_PROC_EXEC2_INVPCID; } void _vmx_triple() { Genode::error("triple fault - dead"); exit(-1); } void _vmx_irqwin() { _irq_window(); } __attribute__((noreturn)) void _vmx_invalid() { unsigned const dubious = _state->inj_info.value() | _state->intr_state.value() | _state->actv_state.value(); if (dubious) Genode::warning(__func__, " - dubious -" " inj_info=", Genode::Hex(_state->inj_info.value()), " inj_error=", Genode::Hex(_state->inj_error.value()), " intr_state=", Genode::Hex(_state->intr_state.value()), " actv_state=", Genode::Hex(_state->actv_state.value())); Genode::error("invalid guest state - dead"); exit(-1); } void _vmx_mov_crx() { _default_handler(); return; } void _handle_vm_exception() { unsigned const exit = _state->exit_reason; bool recall_wait = true; switch (exit) { case VMX_EXIT_TRIPLE_FAULT: _vmx_triple(); break; case VMX_EXIT_INIT_SIGNAL: _vmx_default(); break; case VMX_EXIT_INT_WINDOW: _vmx_irqwin(); break; case VMX_EXIT_TASK_SWITCH: _vmx_default(); break; case VMX_EXIT_CPUID: _vmx_default(); break; case VMX_EXIT_HLT: _vmx_default(); break; /* we don't support tsc offsetting for now - so let the rdtsc exit */ case VMX_EXIT_RDTSC: _vmx_default(); break; case VMX_EXIT_RDTSCP: _vmx_default(); break; case VMX_EXIT_VMCALL: _vmx_default(); break; case VMX_EXIT_IO_INSTR: _vmx_default(); break; case VMX_EXIT_RDMSR: _vmx_default(); break; case VMX_EXIT_WRMSR: _vmx_default(); break; case VMX_EXIT_ERR_INVALID_GUEST_STATE: _vmx_invalid(); break; case VMX_EXIT_PAUSE: _vmx_default(); break; case VMX_EXIT_WBINVD: _vmx_default(); break; case VMX_EXIT_MOV_CRX: _vmx_mov_crx(); break; case VMX_EXIT_MOV_DRX: _vmx_default(); break; case VMX_EXIT_XSETBV: _vmx_default(); break; case VMX_EXIT_TPR_BELOW_THRESHOLD: _vmx_default(); break; case VMX_EXIT_EPT_VIOLATION: _vmx_ept(); break; case RECALL: recall_wait = Vcpu_handler::_recall_handler(); break; case VCPU_STARTUP: _vmx_startup(); _lock_emt.unlock(); /* pause - no resume */ break; default: Genode::error(__func__, " unknown exit - stop - ", Genode::Hex(exit)); _vm_state = PAUSED; return; } if (exit == RECALL && !recall_wait) { _vm_state = RUNNING; run_vm(); return; } /* wait until EMT thread wake's us up */ _sem_handler.down(); /* resume vCPU */ _vm_state = RUNNING; if (_next_state == RUN) run_vm(); else pause_vm(); /* cause pause exit */ } void run_vm() { _vm_session.run(_vcpu); } void pause_vm() { _vm_session.pause(_vcpu); } int attach_memory_to_vm(RTGCPHYS const gp_attach_addr, RTGCUINT vbox_errorcode) { return map_memory(_vm_session, gp_attach_addr, vbox_errorcode); } void _exit_config(Genode::Vm_state &state, unsigned exit) { switch (exit) { case VMX_EXIT_TRIPLE_FAULT: case VMX_EXIT_INIT_SIGNAL: case VMX_EXIT_INT_WINDOW: case VMX_EXIT_TASK_SWITCH: case VMX_EXIT_CPUID: case VMX_EXIT_HLT: case VMX_EXIT_RDTSC: case VMX_EXIT_RDTSCP: case VMX_EXIT_VMCALL: case VMX_EXIT_IO_INSTR: case VMX_EXIT_RDMSR: case VMX_EXIT_WRMSR: case VMX_EXIT_ERR_INVALID_GUEST_STATE: // case VMX_EXIT_PAUSE: case VMX_EXIT_WBINVD: case VMX_EXIT_MOV_CRX: case VMX_EXIT_MOV_DRX: case VMX_EXIT_TPR_BELOW_THRESHOLD: case VMX_EXIT_EPT_VIOLATION: case VMX_EXIT_XSETBV: case VCPU_STARTUP: case RECALL: /* todo - touch all members */ Genode::memset(&state, ~0U, sizeof(state)); break; default: break; } } public: Vcpu_handler_vmx(Genode::Env &env, size_t stack_size, Genode::Affinity::Location location, unsigned int cpu_id, Genode::Vm_connection &vm_session, Genode::Allocator &alloc) : Vcpu_handler(env, stack_size, location, cpu_id), _handler(_ep, *this, &Vcpu_handler_vmx::_handle_vm_exception, &Vcpu_handler_vmx::_exit_config), _vm_session(vm_session), /* construct vcpu */ _vcpu(_vm_session.with_upgrade([&]() { return _vm_session.create_vcpu(alloc, env, _handler); })), /* get state of vcpu */ _state_ds(env.rm(), _vm_session.cpu_state(_vcpu)) { _state = _state_ds.local_addr(); /* sync with initial startup exception */ _lock_emt.lock(); _vm_session.run(_vcpu); /* sync with initial startup exception */ _lock_emt.lock(); } bool hw_save_state(Genode::Vm_state *state, VM * pVM, PVMCPU pVCpu) { return vmx_save_state(state, pVM, pVCpu); } bool hw_load_state(Genode::Vm_state * state, VM * pVM, PVMCPU pVCpu) { return vmx_load_state(state, pVM, pVCpu); } int vm_exit_requires_instruction_emulation(PCPUMCTX pCtx) { switch (_state->exit_reason) { case VMX_EXIT_HLT: pCtx->rip++; return VINF_EM_HALT; case VMX_EXIT_IO_INSTR: /* EMHandleRCTmpl.h does not distinguish READ/WRITE rc */ return VINF_IOM_R3_IOPORT_WRITE; case VMX_EXIT_RDMSR: return VINF_CPUM_R3_MSR_READ; case VMX_EXIT_WRMSR: return VINF_CPUM_R3_MSR_WRITE; case VMX_EXIT_TPR_BELOW_THRESHOLD: /* the instruction causing the exit has already been executed */ case RECALL: return VINF_SUCCESS; case VMX_EXIT_EPT_VIOLATION: if (_ept_fault_addr_type == PGMPAGETYPE_MMIO) /* EMHandleRCTmpl.h does not distinguish READ/WRITE rc */ return VINF_IOM_R3_MMIO_READ_WRITE; case VMX_EXIT_MOV_DRX: /* looks complicated in original R0 code -> emulate instead */ return VINF_EM_RAW_EMULATE_INSTR; default: return VINF_EM_RAW_EMULATE_INSTR; } } }; #endif /* _VIRTUALBOX__VCPU_VMX_H_ */