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();
+}