hw: simplify x86 vCPU initialization

Initializing a vCPU on base-hw involved a round-trip to the kernel to
send the initial startup exit signal and setting up the vCPU on first
run of the vCPU thread.

Signal the vCPU handler directly from the base libary. This removes the
need to call `Vm::run()` during construction of the vCPU, possibly from
a remote core. Check that `Vm::run()` and `Vm::pause()` are only called
from the local CPU core. Finally, move the initialization of the vCPU
from `Vm::proceed()` to `Vm::run()`, so that the `Vcpu_state` can be
synced from the VMM directly after initialization.

Fixes #5483
This commit is contained in:
Benjamin Lamowski 2025-03-24 14:48:28 +01:00 committed by Norman Feske
parent dd8b78575e
commit c3c719fce7
7 changed files with 79 additions and 86 deletions

View File

@ -16,6 +16,7 @@
#define _CORE__KERNEL__VM_H_
/* core includes */
#include <kernel/cpu.h>
#include <kernel/cpu_context.h>
#include <kernel/pd.h>
#include <kernel/signal.h>
@ -61,8 +62,6 @@ class Kernel::Vm : private Kernel::Object, public Cpu_context
Scheduler_state _scheduled = INACTIVE;
Board::Vcpu_context _vcpu_context;
void _sync_to_vmm();
void _sync_from_vmm();
void _pause_vcpu()
{
if (_scheduled != INACTIVE)
@ -134,12 +133,7 @@ class Kernel::Vm : private Kernel::Object, public Cpu_context
void run();
void pause()
{
_pause_vcpu();
_sync_to_vmm();
}
void pause();
/*****************
** Cpu_context **

View File

@ -86,15 +86,16 @@ void Vm::proceed()
void Vm::run()
{
_sync_from_vmm();
if (_scheduled != ACTIVE) Cpu_context::_activate();
_scheduled = ACTIVE;
}
void Vm::_sync_to_vmm()
{}
void Vm::_sync_from_vmm()
{}
void Vm::pause()
{
if (_cpu().id() != Cpu::executing_id()) {
Genode::error("vCPU pause called from remote core.");
return;
}
_pause_vcpu();
}

View File

@ -207,18 +207,18 @@ void Kernel::Vm::proceed()
void Vm::run()
{
_sync_from_vmm();
if (_scheduled != ACTIVE) Cpu_context::_activate();
_scheduled = ACTIVE;
}
void Vm::_sync_to_vmm()
{}
void Vm::_sync_from_vmm()
{}
void Vm::pause()
{
if (_cpu().id() != Cpu::executing_id()) {
Genode::error("vCPU pause called from remote core.");
return;
}
_pause_vcpu();
}
void Vm::inject_irq(unsigned irq)

View File

@ -214,18 +214,19 @@ void Vm::proceed()
void Vm::run()
{
_sync_from_vmm();
if (_scheduled != ACTIVE) Cpu_context::_activate();
_scheduled = ACTIVE;
}
void Vm::_sync_to_vmm()
{}
void Vm::_sync_from_vmm()
{}
void Vm::pause()
{
if (_cpu().id() != Cpu::executing_id()) {
Genode::error("vCPU pause called from remote core.");
return;
}
_pause_vcpu();
}
void Vm::inject_irq(unsigned irq)

View File

@ -35,7 +35,6 @@ namespace Board {
enum Platform_exitcodes : uint64_t {
EXIT_NPF = 0xfc,
EXIT_STARTUP = 0xfe,
EXIT_PAUSED = 0xff,
};
@ -55,7 +54,6 @@ struct Board::Vcpu_context
{
enum class Init_state {
CREATED,
INITIALIZING,
STARTED
};

View File

@ -55,36 +55,57 @@ Vm::~Vm()
void Vm::run()
{
if (_vcpu_context.init_state == Board::Vcpu_context::Init_state::CREATED) {
_vcpu_context.exit_reason = Board::EXIT_STARTUP;
_vcpu_context.init_state = Board::Vcpu_context::Init_state::INITIALIZING;
_context.submit(1);
if (_cpu().id() != Cpu::executing_id()) {
error("vCPU run called from remote core.");
return;
}
_sync_from_vmm();
/*
* On first start, initialize the vCPU
*/
if (_vcpu_context.init_state == Board::Vcpu_context::Init_state::CREATED) {
_vcpu_context.initialize(_cpu(),
reinterpret_cast<addr_t>(_id.table));
_vcpu_context.tsc_aux_host = _cpu().id();
_vcpu_context.init_state = Board::Vcpu_context::Init_state::STARTED;
}
_vcpu_context.read_vcpu_state(_state);
if (_scheduled != ACTIVE) Cpu_context::_activate();
_scheduled = ACTIVE;
}
void Vm::proceed()
void Vm::pause()
{
using namespace Board;
if (_vcpu_context.init_state == Board::Vcpu_context::Init_state::INITIALIZING) {
_vcpu_context.initialize(_cpu(),
reinterpret_cast<addr_t>(_id.table));
_vcpu_context.tsc_aux_host = _cpu().id();
_vcpu_context.init_state = Board::Vcpu_context::Init_state::STARTED;
/*
* Sync the initial state from the VMM that was skipped due to
* the vCPU being uninitialized.
*/
_sync_from_vmm();
if (_cpu().id() != Cpu::executing_id()) {
Genode::error("vCPU pause called from remote core.");
return;
}
/*
* The vCPU isn't initialized yet when the VMM first queries the state.
* Just return so that the VMM gets presented with the default startup
* exit code set at construction.
*/
if (_vcpu_context.init_state != Board::Vcpu_context::Init_state::STARTED)
return;
_pause_vcpu();
_vcpu_context.write_vcpu_state(_state);
/*
* Set exit code so that if _run() was not called after an exit, the
* next exit due to a signal will be interpreted as PAUSE request.
*/
_vcpu_context.exit_reason = Board::EXIT_PAUSED;
}
void Vm::proceed()
{
Cpu::Ia32_tsc_aux::write(
(Cpu::Ia32_tsc_aux::access_t)_vcpu_context.tsc_aux_guest);
@ -165,40 +186,6 @@ void Vm::exception(Genode::Cpu_state &state)
}
void Vm::_sync_to_vmm()
{
/*
* If the vCPU isn't initialized, sync instructions such as vmread may fail.
* Just sync the startup exit and skip the rest of the synchronization.
*/
if (_vcpu_context.init_state != Board::Vcpu_context::Init_state::STARTED) {
_state.exit_reason = (unsigned) Board::EXIT_STARTUP;
return;
}
_vcpu_context.write_vcpu_state(_state);
/*
* Set exit code so that if _run() was not called after an exit, the
* next exit due to a signal will be interpreted as PAUSE request.
*/
_vcpu_context.exit_reason = Board::EXIT_PAUSED;
}
void Vm::_sync_from_vmm()
{
/*
* Syncing the state to an unitialized vCPU may fail.
* The inial state from the VMM will be synced after vCPU initialization.
*/
if (_vcpu_context.init_state != Board::Vcpu_context::Init_state::STARTED)
return;
_vcpu_context.read_vcpu_state(_state);
}
Board::Vcpu_context::Vcpu_context(unsigned id, Vcpu_data &vcpu_data)
:
regs(1),

View File

@ -19,8 +19,11 @@
#include <base/signal.h>
#include <base/sleep.h>
#include <base/internal/capability_space.h>
#include <kernel/interface.h>
#include <spec/x86/cpu/vcpu_state.h>
#include <vm_session/connection.h>
#include <vm_session/handler.h>
@ -38,6 +41,7 @@ using Exit_config = Vm_connection::Exit_config;
struct Hw_vcpu : Rpc_client<Vm_session::Native_vcpu>, Noncopyable
{
private:
enum Initial_exitcode : unsigned { EXIT_STARTUP = 0xfe };
Attached_dataspace _state;
Native_capability _kernel_vcpu { };
@ -73,6 +77,11 @@ Hw_vcpu::Hw_vcpu(Env &env, Vm_connection &vm, Vcpu_handler_base &handler)
_kernel_vcpu = call<Rpc_native_vcpu>();
_id = counter++;
_ep_handler = reinterpret_cast<Thread *>(&handler.rpc_ep());
/*
* Set the startup exit for the initial signal to the VMM's Vcpu_handler
*/
_local_state().exit_reason = EXIT_STARTUP;
}
@ -119,5 +128,8 @@ Vm_connection::Vcpu::Vcpu(Vm_connection &vm, Allocator &alloc,
:
_native_vcpu(*new (alloc) Hw_vcpu(vm._env, vm, handler))
{
static_cast<Hw_vcpu &>(_native_vcpu).run();
/*
* Send the initial startup signal to the vCPU handler.
*/
Signal_transmitter(handler.signal_cap()).submit();
}