diff --git a/repos/base-sel4/src/lib/base/x86/vm.cc b/repos/base-sel4/src/lib/base/x86/vm.cc index 0ced3693af..85513ed07a 100644 --- a/repos/base-sel4/src/lib/base/x86/vm.cc +++ b/repos/base-sel4/src/lib/base/x86/vm.cc @@ -1,11 +1,12 @@ /* * \brief Client-side VM session interface * \author Alexander Boettcher + * \author Benjamin Lamowski * \date 2018-08-27 */ /* - * Copyright (C) 2018-2021 Genode Labs GmbH + * Copyright (C) 2018-2023 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -16,6 +17,7 @@ #include #include #include +#include #include #include @@ -36,6 +38,7 @@ using namespace Genode; using Exit_config = Vm_connection::Exit_config; +using Call_with_state = Vm_connection::Call_with_state; struct Sel4_vcpu; @@ -71,11 +74,16 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable private: Vcpu_handler_base &_vcpu_handler; + Vcpu_handler _exit_handler; Vcpu_state _state __attribute__((aligned(0x10))) { }; Semaphore _wake_up { 0 }; Blockade _startup { }; addr_t _recall { 0 }; uint64_t _tsc_offset { 0 }; + Semaphore _state_ready { 0 }; + bool _dispatching { false }; + bool _extra_dispatch_up { false }; + void *_ep_handler { nullptr }; Constructible _rpc { }; @@ -133,7 +141,7 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable /* get selector to call back a vCPU into VMM */ _recall = _stack->utcb().lock_sel(); - Vcpu_state &state = this->state(); + Vcpu_state &state = _state; state.discharge(); /* wait for first user resume() */ @@ -148,9 +156,10 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable state.exit_reason = VMEXIT_STARTUP; _read_sel4_state(service, state); - Genode::Signal_transmitter(_vcpu_handler.signal_cap()).submit(); + _state_ready.up(); + Signal_transmitter(_exit_handler.signal_cap()).submit(); - _vcpu_handler.ready_semaphore().down(); + _exit_handler.ready_semaphore().down(); _wake_up.down(); State local_state { NONE }; @@ -163,7 +172,7 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable { Mutex::Guard guard(_remote_mutex); - local_state = _remote; + local_state = _remote; _remote = NONE; if (local_state == PAUSE) { @@ -192,14 +201,13 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable _read_sel4_state(service, state); - /* notify VM handler */ - Genode::Signal_transmitter(_vcpu_handler.signal_cap()).submit(); + _state_ready.up(); + + if (_extra_dispatch_up) { + _extra_dispatch_up = false; + _exit_handler.ready_semaphore().down(); + } - /* - * Wait until VM handler is really really done, - * otherwise we lose state. - */ - _vcpu_handler.ready_semaphore().down(); continue; } @@ -219,8 +227,18 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable state.discharge(); - if (res != SEL4_VMENTER_RESULT_FAULT) + /* + * If a VMEXIT_RECALL is dispatched here, it comes from a + * pause request sent by an already running asynchronous signal + * handler. + * In that case, don't dispatch an extra exit signal. + */ + bool skip_dispatch = false; + + if (res != SEL4_VMENTER_RESULT_FAULT) { state.exit_reason = VMEXIT_RECALL; + skip_dispatch = true; + } else state.exit_reason = (unsigned)seL4_GetMR(SEL4_VMENTER_FAULT_REASON_MR); @@ -233,15 +251,18 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable _wake_up.down(); } } + _state_ready.up(); + if (skip_dispatch) + continue; /* notify VM handler */ - Genode::Signal_transmitter(_vcpu_handler.signal_cap()).submit(); + Genode::Signal_transmitter(_exit_handler.signal_cap()).submit(); /* * Wait until VM handler is really really done, * otherwise we lose state. */ - _vcpu_handler.ready_semaphore().down(); + _exit_handler.ready_semaphore().down(); } } @@ -747,6 +768,13 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable return ep->affinity(); } + void _wrapper_dispatch() + { + _dispatching = true; + _vcpu_handler.dispatch(1); + _dispatching = false; + } + public: Sel4_vcpu(Env &env, Vm_connection &vm, @@ -754,12 +782,14 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable : Thread(env, "vcpu_thread", STACK_SIZE, _location(handler), Weight(), env.cpu()), - _vcpu_handler(handler) + _vcpu_handler(handler), + _exit_handler(handler.ep(), *this, &Sel4_vcpu::_wrapper_dispatch) { Thread::start(); /* wait until thread is alive, e.g. Thread::cap() is valid */ _startup.block(); + _ep_handler = reinterpret_cast(&handler.rpc_ep()); _rpc.construct(vm, this->cap(), *this); @@ -767,6 +797,9 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable _wake_up.up(); } + const Sel4_vcpu& operator=(const Sel4_vcpu &) = delete; + Sel4_vcpu(const Sel4_vcpu&) = delete; + void resume() { Mutex::Guard guard(_remote_mutex); @@ -778,21 +811,52 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable _wake_up.up(); } - void pause() + void with_state(Call_with_state &cw) { - Mutex::Guard guard(_remote_mutex); + if (!_dispatching) { + if (Thread::myself() != _ep_handler) { + error("vCPU state requested outside of vcpu_handler EP"); + sleep_forever(); + } - if (_remote == PAUSE) - return; + _remote_mutex.acquire(); - _remote = PAUSE; + /* Trigger pause exit */ + _remote = PAUSE; + seL4_Signal(_recall); + _wake_up.up(); - seL4_Signal(_recall); + _remote_mutex.release(); + _state_ready.down(); - _wake_up.up(); + /* + * We're in the async dispatch, yet processing a non-pause exit. + * Signal that we have to wrap the dispatch loop around. + */ + if (_state.exit_reason != VMEXIT_RECALL) { + _extra_dispatch_up = true; + } + } else { + _state_ready.down(); + } + + if (cw.call_with_state(_state) + || _extra_dispatch_up) + resume(); + + /* + * The regular exit was handled by the asynchronous dispatch handler + * triggered by the pause request. + * + * Fake finishing the exit dispatch so that the vCPU loop + * processes the asynchronously dispatched exit and provides + * the VMEXIT_RECALL to the already pending dispatch function + * for the exit code. + */ + if (!_dispatching && _extra_dispatch_up) + _exit_handler.ready_semaphore().up(); } - Vcpu_state & state() { return _state; } Sel4_native_rpc * rpc() { return &*_rpc; } }; @@ -800,13 +864,13 @@ struct Sel4_vcpu : Genode::Thread, Noncopyable ** vCPU API ** **************/ -void Vm_connection::Vcpu::run() { static_cast(_native_vcpu).vcpu.resume(); } -void Vm_connection::Vcpu::pause() { static_cast(_native_vcpu).vcpu.pause(); } -Vcpu_state & Vm_connection::Vcpu::state() { return static_cast(_native_vcpu).vcpu.state(); } +void Vm_connection::Vcpu::_with_state(Call_with_state &cw) { static_cast(_native_vcpu).vcpu.with_state(cw); } Vm_connection::Vcpu::Vcpu(Vm_connection &vm, Allocator &alloc, Vcpu_handler_base &handler, Exit_config const &exit_config) : _native_vcpu(*((new (alloc) Sel4_vcpu(vm._env, vm, handler, exit_config))->rpc())) -{ } +{ + static_cast(_native_vcpu).vcpu.resume(); +}