nova: FPU handling for vCPUs in VM session

- request FPU state on VM exit in portal config

- transfer FPU state on VM entry

- save fpu state early

  Avoid any FPU instructions (for example during base API calls), which
  use the FPU and overwrite FPU registers holding the guest vCPU FPU
  state.

- don't save fpu state of EP

Issue #4313
This commit is contained in:
Alexander Boettcher 2021-10-20 09:40:13 +02:00 committed by Christian Helmuth
parent 1a79bf2be2
commit 7a06633173

View File

@ -64,17 +64,16 @@ struct Nova_vcpu : Rpc_client<Vm_session::Native_vcpu>, Noncopyable
Vcpu_state _vcpu_state __attribute__((aligned(0x10))) { };
uint8_t _fpu_ep[512] __attribute__((aligned(0x10)));
enum Remote_state_requested {
NONE = 0,
PAUSE = 1,
RUN = 2
} _remote { NONE };
inline void _read_nova_state(Nova::Utcb &utcb, unsigned exit_reason);
inline void _read_nova_state(Nova::Utcb &, unsigned exit_reason,
uint8_t const &);
inline void _write_nova_state(Nova::Utcb &utcb);
inline void _write_nova_state(Nova::Utcb &);
addr_t _sm_sel() const {
return Nova::NUM_INITIAL_PT_RESERVED + _id_elem.id().value * 4; }
@ -82,7 +81,8 @@ struct Nova_vcpu : Rpc_client<Vm_session::Native_vcpu>, Noncopyable
addr_t _ec_sel() const { return _sm_sel() + 1; }
/**
* NOVA badge with 16-bit exit reason and 16-bit artificial vCPU I
* NOVA badge with 15-bit exit reason, 1-bit fpu usage and
* 16-bit artificial vCPU I
*/
struct Badge
{
@ -91,15 +91,19 @@ struct Nova_vcpu : Rpc_client<Vm_session::Native_vcpu>, Noncopyable
Badge(unsigned long value)
: _value((uint32_t)value) { }
Badge(uint16_t vcpu_id, uint16_t exit_reason)
: _value((uint32_t)(vcpu_id << 16) | exit_reason) { }
Badge(uint16_t vcpu_id, uint16_t exit_reason, bool fpu_usage)
: _value((uint32_t)(vcpu_id << 16) |
(exit_reason & 0x7fffu) |
(fpu_usage ? 0x8000u : 0u)
) { }
uint16_t exit_reason() const { return (uint16_t)( _value & 0xffff); }
uint16_t exit_reason() const { return (uint16_t)( _value & 0x7fff); }
uint16_t vcpu_id() const { return (uint16_t)((_value >> 16) & 0xffff); }
bool fpu_usage() const { return _value & 0x8000; }
uint32_t value() const { return _value; }
};
bool _handle_exit(Nova::Utcb &utcb, uint16_t exit_reason);
bool _handle_exit(Nova::Utcb &, uint16_t exit_reason, uint8_t const &);
__attribute__((regparm(1))) static void _exit_entry(addr_t badge);
@ -109,8 +113,6 @@ struct Nova_vcpu : Rpc_client<Vm_session::Native_vcpu>, Noncopyable
(void)exit;
(void)config;
return Nova::Mtd(Nova::Mtd::ALL);
Genode::addr_t mtd = 0;
mtd |= Nova::Mtd::ACDB;
@ -172,7 +174,8 @@ struct Nova_vcpu : Rpc_client<Vm_session::Native_vcpu>, Noncopyable
};
void Nova_vcpu::_read_nova_state(Nova::Utcb &utcb, unsigned exit_reason)
void Nova_vcpu::_read_nova_state(Nova::Utcb &utcb, unsigned exit_reason,
uint8_t const &fpu_at_exit)
{
typedef Genode::Vcpu_state::Segment Segment;
typedef Genode::Vcpu_state::Range Range;
@ -181,10 +184,9 @@ void Nova_vcpu::_read_nova_state(Nova::Utcb &utcb, unsigned exit_reason)
state().exit_reason = exit_reason;
if (utcb.mtd & Nova::Mtd::FPU) {
state().fpu.charge([] (Vcpu_state::Fpu::State &fpu) {
asm volatile ("fxsave %0" : "=m" (fpu) :: "memory");
state().fpu.charge([&] (Vcpu_state::Fpu::State &fpu) {
memcpy(&fpu, &fpu_at_exit, sizeof(fpu));
});
asm volatile ("fxrstor %0" : : "m" (*_fpu_ep) : "memory");
}
if (utcb.mtd & Nova::Mtd::ACDB) {
@ -539,11 +541,8 @@ void Nova_vcpu::_write_nova_state(Nova::Utcb &utcb)
utcb.write_tpr_threshold(state().tpr_threshold.value());
}
if (_use_guest_fpu || state().fpu.charged()) {
asm volatile ("fxsave %0" : "=m" (*_fpu_ep) :: "memory");
}
if (state().fpu.charged()) {
utcb.mtd |= Nova::Mtd::FPU;
state().fpu.with_state([] (Vcpu_state::Fpu::State const &fpu) {
asm volatile ("fxrstor %0" : : "m" (fpu) : "memory");
});
@ -581,7 +580,7 @@ void Nova_vcpu::run()
* Do not touch the UTCB before _read_nova_state() and after
* _write_nova_state(), particularly not by logging diagnostics.
*/
bool Nova_vcpu::_handle_exit(Nova::Utcb &utcb, uint16_t exit_reason)
bool Nova_vcpu::_handle_exit(Nova::Utcb &utcb, uint16_t exit_reason, uint8_t const &fpu)
{
/* reset blocking state */
bool const previous_blocked = _block;
@ -595,7 +594,7 @@ bool Nova_vcpu::_handle_exit(Nova::Utcb &utcb, uint16_t exit_reason)
/* transform state from NOVA to Genode */
if (exit_reason != VM_EXIT_RECALL || !previous_blocked)
_read_nova_state(utcb, exit_reason);
_read_nova_state(utcb, exit_reason, fpu);
if (exit_reason == VM_EXIT_RECALL) {
if (previous_blocked)
@ -653,6 +652,10 @@ bool Nova_vcpu::_handle_exit(Nova::Utcb &utcb, uint16_t exit_reason)
void Nova_vcpu::_exit_entry(addr_t badge)
{
uint8_t _fpu_at_exit[512] __attribute__((aligned(0x10)));
if (Badge(badge).fpu_usage())
asm volatile ("fxsave %0" : "=m" (*_fpu_at_exit) :: "memory");
Thread &myself = *Thread::myself();
Nova::Utcb &utcb = *reinterpret_cast<Nova::Utcb *>(myself.utcb());
@ -662,7 +665,8 @@ void Nova_vcpu::_exit_entry(addr_t badge)
try {
_vcpu_space().apply<Nova_vcpu>(vcpu_id, [&] (Nova_vcpu &vcpu)
{
bool const block = vcpu._handle_exit(utcb, exit_reason);
bool const block = vcpu._handle_exit(utcb, exit_reason,
*_fpu_at_exit);
if (block) {
Nova::reply(myself.stack_top(), vcpu._sm_sel());
@ -729,7 +733,7 @@ Signal_context_capability Nova_vcpu::_create_exit_handler(Pd_session &pd,
Native_capability vm_exit_cap =
native_pd.alloc_rpc_cap(thread_cap, (addr_t)Nova_vcpu::_exit_entry, mtd.value());
Badge const badge { vcpu_id, exit_reason };
Badge const badge { vcpu_id, exit_reason, !!(mtd.value() & Nova::Mtd::FPU) };
native_pd.imprint_rpc_cap(vm_exit_cap, badge.value());
return reinterpret_cap_cast<Signal_context>(vm_exit_cap);