From 3a1bbfad280fb9fd45cd8d8e3cdfcdd866db7b07 Mon Sep 17 00:00:00 2001 From: Alexander Boettcher Date: Wed, 26 Sep 2018 14:10:34 +0200 Subject: [PATCH] nova: implement vm_session interface Issue #3111 --- repos/base-nova/lib/mk/core-nova.inc | 2 +- .../src/core/include/vm_session_component.h | 117 +++ repos/base-nova/src/core/platform_services.cc | 35 + .../src/core/vm_session_component.cc | 370 +++++++++ repos/base-nova/src/lib/base/vm_session.cc | 746 ++++++++++++++++++ 5 files changed, 1269 insertions(+), 1 deletion(-) create mode 100644 repos/base-nova/src/core/include/vm_session_component.h create mode 100644 repos/base-nova/src/core/platform_services.cc create mode 100644 repos/base-nova/src/core/vm_session_component.cc create mode 100644 repos/base-nova/src/lib/base/vm_session.cc diff --git a/repos/base-nova/lib/mk/core-nova.inc b/repos/base-nova/lib/mk/core-nova.inc index cd815fe127..b09b78242d 100644 --- a/repos/base-nova/lib/mk/core-nova.inc +++ b/repos/base-nova/lib/mk/core-nova.inc @@ -41,6 +41,7 @@ SRC_CC += stack_area.cc \ trace_session_component.cc \ signal_transmitter_noinit.cc \ signal_receiver.cc \ + vm_session_component.cc \ heartbeat.cc INC_DIR = $(REP_DIR)/src/core/include \ @@ -72,7 +73,6 @@ vpath core_mem_alloc.cc $(GEN_CORE_DIR) vpath default_log.cc $(GEN_CORE_DIR) vpath dump_alloc.cc $(GEN_CORE_DIR) vpath platform_rom_modules.cc $(GEN_CORE_DIR) -vpath platform_services.cc $(GEN_CORE_DIR)/spec/x86 vpath stack_area.cc $(GEN_CORE_DIR) vpath heartbeat.cc $(GEN_CORE_DIR) vpath %.cc $(REP_DIR)/src/core diff --git a/repos/base-nova/src/core/include/vm_session_component.h b/repos/base-nova/src/core/include/vm_session_component.h new file mode 100644 index 0000000000..791c01ef51 --- /dev/null +++ b/repos/base-nova/src/core/include/vm_session_component.h @@ -0,0 +1,117 @@ +/* + * \brief Core-specific instance of the VM session interface + * \author Alexander Boettcher + * \date 2018-08-26 + */ + +/* + * Copyright (C) 2018 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. + */ + +#ifndef _CORE__VM_SESSION_COMPONENT_H_ +#define _CORE__VM_SESSION_COMPONENT_H_ + +/* Genode includes */ +#include +#include +#include + +namespace Genode { class Vm_session_component; } + +class Genode::Vm_session_component +: + private Ram_quota_guard, + private Cap_quota_guard, + public Rpc_object +{ + private: + + class Vcpu : public List::Element { + + public: + + enum State { INIT, ALIVE }; + + private: + + Constrained_ram_allocator &_ram_alloc; + Cap_quota_guard &_cap_alloc; + Ram_dataspace_capability _ds_cap { }; + addr_t _sel_sm_ec_sc; + addr_t _vm_pt_cnt { 0 }; + Vcpu_id const _id; + State _state { INIT }; + + public: + + Vcpu(Constrained_ram_allocator &ram_alloc, + Cap_quota_guard &cap_alloc, + Vcpu_id const id); + + ~Vcpu(); + + addr_t sm_sel() const { return _sel_sm_ec_sc + 0; } + addr_t ec_sel() const { return _sel_sm_ec_sc + 1; } + addr_t sc_sel() const { return _sel_sm_ec_sc + 2; } + + addr_t new_pt_id(); + + bool match(Vcpu_id const id) const { return id.id == _id.id; } + Ram_dataspace_capability ds_cap() const { return _ds_cap; } + + bool init() const { return _state == State::INIT; } + void alive() { _state = ALIVE; }; + + static addr_t invalid() { return ~0UL; } + }; + + Rpc_entrypoint &_ep; + Constrained_ram_allocator _constrained_md_ram_alloc; + Sliced_heap _sliced_heap; + addr_t _pd_sel { 0 }; + unsigned _id_alloc { 0 }; + unsigned _priority; + + List _vcpus { }; + + Vcpu * _lookup(Vcpu_id const vcpu_id) + { + for (Vcpu * vcpu = _vcpus.first(); vcpu; vcpu = vcpu->next()) + if (vcpu->match(vcpu_id)) return vcpu; + + return nullptr; + } + + protected: + + Ram_quota_guard &_ram_quota_guard() { return *this; } + Cap_quota_guard &_cap_quota_guard() { return *this; } + + public: + + using Ram_quota_guard::upgrade; + using Cap_quota_guard::upgrade; + + Vm_session_component(Rpc_entrypoint &, Resources, Label const &, + Diag, Ram_allocator &ram, Region_map &); + ~Vm_session_component(); + + /************************** + ** Vm session interface ** + **************************/ + + Dataspace_capability _cpu_state(Vcpu_id); + + void _exception_handler(Signal_context_capability, Vcpu_id); + void _run(Vcpu_id); + void _pause(Vcpu_id) { } + void attach(Dataspace_capability, addr_t) override; + void attach_pic(addr_t) override {} + void detach(addr_t, size_t) override { } + void _create_vcpu(Thread_capability); +}; + +#endif /* _CORE__VM_SESSION_COMPONENT_H_ */ diff --git a/repos/base-nova/src/core/platform_services.cc b/repos/base-nova/src/core/platform_services.cc new file mode 100644 index 0000000000..21fd9229ba --- /dev/null +++ b/repos/base-nova/src/core/platform_services.cc @@ -0,0 +1,35 @@ +/* + * \brief Platform specific services for NOVA + * \author Alexander Boettcher + * \date 2018-08-26 + */ + +/* + * Copyright (C) 2018 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. + */ + +/* core includes */ +#include +#include +#include +#include + +/* + * Add x86 specific services + */ +void Genode::platform_add_local_services(Rpc_entrypoint &ep, + Sliced_heap &heap, + Registry &services) +{ + static Vm_root vm_root(ep, heap, core_env().ram_allocator(), + core_env().local_rm()); + static Core_service vm(services, vm_root); + + static Io_port_root io_root(*core_env().pd_session(), + platform().io_port_alloc(), heap); + + static Core_service io_port(services, io_root); +} diff --git a/repos/base-nova/src/core/vm_session_component.cc b/repos/base-nova/src/core/vm_session_component.cc new file mode 100644 index 0000000000..8a26e9cf95 --- /dev/null +++ b/repos/base-nova/src/core/vm_session_component.cc @@ -0,0 +1,370 @@ +/* + * \brief Core-specific instance of the VM session interface + * \author Alexander Boettcher + * \date 2018-08-26 + */ + +/* + * Copyright (C) 2018 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. + */ + +/* Base includes */ +#include +#include +#include + +/* Core includes */ +#include +#include +#include +#include +#include +#include + +#include + +/* NOVA includes */ +#include + +using Genode::addr_t; +using Genode::Vm_session_component; + +enum { CAP_RANGE_LOG2 = 2, CAP_RANGE = 1 << CAP_RANGE_LOG2 }; + +Vm_session_component::Vcpu::Vcpu(Constrained_ram_allocator &ram_alloc, + Cap_quota_guard &cap_alloc, + Vcpu_id const id) +: + _ram_alloc(ram_alloc), + _cap_alloc(cap_alloc), + _sel_sm_ec_sc(invalid()), + _id(id) +{ + /* account caps required to setup vCPU */ + _cap_alloc.withdraw(Cap_quota{CAP_RANGE}); + + /* now try to allocate cap indexes */ + _sel_sm_ec_sc = cap_map().insert(CAP_RANGE_LOG2); + if (_sel_sm_ec_sc == invalid()) { + error("out of caps in core"); + _cap_alloc.replenish(Cap_quota{CAP_RANGE}); + return; + } + + try { + /* create ds for vCPU state */ + _ds_cap = _ram_alloc.alloc(4096, Cache_attribute::CACHED); + } catch (...) { + _cap_alloc.replenish(Cap_quota{CAP_RANGE}); + cap_map().remove(_sel_sm_ec_sc, CAP_RANGE_LOG2); + throw; + } +} + +Vm_session_component::Vcpu::~Vcpu() +{ + if (_ds_cap.valid()) + _ram_alloc.free(_ds_cap); + + if (_sel_sm_ec_sc != invalid()) { + _cap_alloc.replenish(Cap_quota{CAP_RANGE}); + cap_map().remove(_sel_sm_ec_sc, CAP_RANGE_LOG2); + } +} + +addr_t Vm_session_component::Vcpu::new_pt_id() +{ + enum { MAX_VM_EXITS = (1U << Nova::NUM_INITIAL_VCPU_PT_LOG2) }; + if (_vm_pt_cnt >= MAX_VM_EXITS) + return invalid(); + + return MAX_VM_EXITS * _id.id + _vm_pt_cnt ++; +} + + +static Nova::uint8_t map_async_caps(Nova::Obj_crd const src, + Nova::Obj_crd const dst, + addr_t const dst_pd) +{ + using Nova::Utcb; + using Genode::Thread; + + Utcb &utcb = *reinterpret_cast(Thread::myself()->utcb()); + addr_t const src_pd = Genode::platform_specific().core_pd_sel(); + + utcb.set_msg_word(0); + /* ignore return value as one item always fits into the utcb */ + bool const ok = utcb.append_item(src, 0); + (void)ok; + + /* asynchronously map capabilities */ + return Nova::delegate(src_pd, dst_pd, dst); +} + +static Nova::uint8_t kernel_quota_upgrade(addr_t const pd_target) +{ + using Genode::Pager_object; + + return Pager_object::handle_oom(Pager_object::SRC_CORE_PD, pd_target, + "core", "ep", + Pager_object::Policy::UPGRADE_CORE_TO_DST); +} + +template +static Genode::uint8_t _with_kernel_quota_upgrade(addr_t const pd_target, + FUNC const &func) +{ + Genode::uint8_t res; + do { + res = func(); + } while (res == Nova::NOVA_PD_OOM && + Nova::NOVA_OK == kernel_quota_upgrade(pd_target)); + return res; +} + +void Vm_session_component::_create_vcpu(Thread_capability cap) +{ + if (!cap.valid()) + return; + + /* lookup vmm pd and cpu location of handler thread in VMM */ + addr_t kernel_cpu_id = 0; + auto lambda = [&] (Cpu_thread_component *ptr) { + if (!ptr) + return Vcpu::invalid(); + + Cpu_thread_component &thread = *ptr; + + addr_t genode_cpu_id = thread.platform_thread().affinity().xpos(); + kernel_cpu_id = platform_specific().kernel_cpu_id(genode_cpu_id); + + return thread.platform_thread().pager().pd_sel(); + }; + addr_t const vmm_pd_sel = _ep.apply(cap, lambda); + + /* if VMM pd lookup failed then deny to create vCPU */ + if (!vmm_pd_sel || vmm_pd_sel == Vcpu::invalid()) + return; + + /* allocate vCPU object */ + Vcpu &vcpu = *new (_sliced_heap) Vcpu(_constrained_md_ram_alloc, + _cap_quota_guard(), + Vcpu_id {_id_alloc}); + + /* we ran out of caps in core */ + if (!vcpu.ds_cap().valid()) + return; + + /* core PD selector */ + addr_t const core_pd = platform_specific().core_pd_sel(); + + /* setup vCPU resources */ + uint8_t res = _with_kernel_quota_upgrade(_pd_sel, [&] { + return Nova::create_sm(vcpu.sm_sel(), core_pd, 0); + }); + + if (res != Nova::NOVA_OK) { + error("create_sm = ", res); + destroy(_sliced_heap, &vcpu); + return; + } + + addr_t const event_base = (1U << Nova::NUM_INITIAL_VCPU_PT_LOG2) * _id_alloc; + enum { THREAD_GLOBAL = true, NO_UTCB = 0, NO_STACK = 0 }; + res = _with_kernel_quota_upgrade(_pd_sel, [&] { + return Nova::create_ec(vcpu.ec_sel(), _pd_sel, kernel_cpu_id, + NO_UTCB, NO_STACK, event_base, THREAD_GLOBAL); + }); + + if (res != Nova::NOVA_OK) { + error("create_ec = ", res); + destroy(_sliced_heap, &vcpu); + return; + } + + addr_t const dst_sm_ec_sel = Nova::NUM_INITIAL_PT_RESERVED + + _id_alloc * CAP_RANGE; + + res = _with_kernel_quota_upgrade(vmm_pd_sel, [&] { + using namespace Nova; + + enum { CAP_LOG2_COUNT = 1 }; + int permission = Obj_crd::RIGHT_EC_RECALL | Obj_crd::RIGHT_SM_UP | + Obj_crd::RIGHT_SM_DOWN; + Obj_crd const src(vcpu.sm_sel(), CAP_LOG2_COUNT, permission); + Obj_crd const dst(dst_sm_ec_sel, CAP_LOG2_COUNT); + + return map_async_caps(src, dst, vmm_pd_sel); + }); + + if (res != Nova::NOVA_OK) + { + error("map sm ", res, " ", _id_alloc); + destroy(_sliced_heap, &vcpu); + return; + } + + _id_alloc++; + _vcpus.insert(&vcpu); +} + +void Vm_session_component::_run(Vcpu_id const vcpu_id) +{ + Vcpu * ptr = _lookup(vcpu_id); + if (!ptr) + return; + + Vcpu &vcpu = *ptr; + + if (!vcpu.init()) + return; + + addr_t const _priority = 1; /* XXX */ + uint8_t res = _with_kernel_quota_upgrade(_pd_sel, [&] { + return Nova::create_sc(vcpu.sc_sel(), _pd_sel, vcpu.ec_sel(), + Nova::Qpd(Nova::Qpd::DEFAULT_QUANTUM, _priority)); + }); + + if (res == Nova::NOVA_OK) + vcpu.alive(); + else + error("create_sc=", res); +} + +void Vm_session_component::_exception_handler(Signal_context_capability const cap, + Vcpu_id const vcpu_id) +{ + if (!cap.valid()) + return; + + Vcpu * ptr = _lookup(vcpu_id); + if (!ptr) + return; + + Vcpu &vcpu = *ptr; + + addr_t const pt = vcpu.new_pt_id(); + if (pt == Vcpu::invalid()) + return; + + uint8_t res = _with_kernel_quota_upgrade(_pd_sel, [&] { + Nova::Obj_crd const src(cap.local_name(), 0); + Nova::Obj_crd const dst(pt, 0); + + return map_async_caps(src, dst, _pd_sel); + }); + + if (res != Nova::NOVA_OK) + error("map pt ", res, " failed"); +} + +Genode::Dataspace_capability Vm_session_component::_cpu_state(Vcpu_id const vcpu_id) +{ + Vcpu * ptr = _lookup(vcpu_id); + if (!ptr) + return Dataspace_capability(); + + Vcpu &vcpu = *ptr; + return vcpu.ds_cap(); +} + +Vm_session_component::Vm_session_component(Rpc_entrypoint &ep, + Resources resources, + Label const &, + Diag, + Ram_allocator &ram, + Region_map &local_rm) +: + Ram_quota_guard(resources.ram_quota), + Cap_quota_guard(resources.cap_quota), + _ep(ep), + _constrained_md_ram_alloc(ram, _ram_quota_guard(), _cap_quota_guard()), + _sliced_heap(_constrained_md_ram_alloc, local_rm), + _priority(1 /*scale_priority(priority, "VM session")*/) +{ + _cap_quota_guard().withdraw(Cap_quota{1}); + + _pd_sel = cap_map().insert(); + if (!_pd_sel || _pd_sel == Vcpu::invalid()) { + _cap_quota_guard().replenish(Cap_quota{1}); + throw Service_denied(); + } + + addr_t const core_pd = platform_specific().core_pd_sel(); + enum { KEEP_FREE_PAGES_NOT_AVAILABLE_FOR_UPGRADE = 2, UPPER_LIMIT_PAGES = 32 }; + uint8_t res = Nova::create_pd(_pd_sel, core_pd, Nova::Obj_crd(), + KEEP_FREE_PAGES_NOT_AVAILABLE_FOR_UPGRADE, + UPPER_LIMIT_PAGES); + if (res != Nova::NOVA_OK) { + error("create_pd = ", res); + cap_map().remove(_pd_sel, 0, true); + _cap_quota_guard().replenish(Cap_quota{1}); + throw Service_denied(); + } +} + +Vm_session_component::~Vm_session_component() +{ + for (;Vcpu * vcpu = _vcpus.first();) { + _vcpus.remove(vcpu); + destroy(_sliced_heap, vcpu); + } + + if (_pd_sel && _pd_sel != Vcpu::invalid()) { + cap_map().remove(_pd_sel, 0, true); + _cap_quota_guard().replenish(Cap_quota{1}); + } +} + +void Vm_session_component::attach(Dataspace_capability cap, addr_t guest_phys) +{ + if (!cap.valid()) + throw Invalid_dataspace(); + + /* check dataspace validity */ + _ep.apply(cap, [&] (Dataspace_component *ptr) { + if (!ptr) + throw Invalid_dataspace(); + + Dataspace_component &dsc = *ptr; + + /* unsupported - deny otherwise arbitrary physical memory can be mapped to a VM */ + if (dsc.managed()) + throw Invalid_dataspace(); + + using Nova::Utcb; + Utcb & utcb = *reinterpret_cast(Thread::myself()->utcb()); + addr_t const src_pd = platform_specific().core_pd_sel(); + + Flexpage_iterator flex(dsc.phys_addr(), dsc.size(), + guest_phys, dsc.size(), guest_phys); + + Flexpage page = flex.page(); + while (page.valid()) { + Nova::Rights const map_rights (true, dsc.writable(), true); + Nova::Mem_crd const mem(page.addr >> 12, page.log2_order - 12, + map_rights); + + utcb.set_msg_word(0); + /* ignore return value as one item always fits into the utcb */ + bool const ok = utcb.append_item(mem, 0, true, true); + (void)ok; + + /* receive window in destination pd */ + Nova::Mem_crd crd_mem(page.hotspot >> 12, page.log2_order - 12, + map_rights); + + /* asynchronously map memory */ + uint8_t res = _with_kernel_quota_upgrade(_pd_sel, [&] { + return Nova::delegate(src_pd, _pd_sel, crd_mem); }); + + if (res != Nova::NOVA_OK) + error("could not map VM memory ", res); + + page = flex.page(); + } + }); +} diff --git a/repos/base-nova/src/lib/base/vm_session.cc b/repos/base-nova/src/lib/base/vm_session.cc new file mode 100644 index 0000000000..730b6450e2 --- /dev/null +++ b/repos/base-nova/src/lib/base/vm_session.cc @@ -0,0 +1,746 @@ +/* + * \brief Client-side VM session interface + * \author Alexander Boettcher + * \date 2018-08-27 + */ + +/* + * Copyright (C) 2018 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. + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +using namespace Genode; + +struct Vcpu; + +static Genode::Registry > vcpus; +static unsigned vcpu_id = 0; + +struct Vcpu { + + private: + + Signal_dispatcher_base &_obj; + Vm_session_client::Vcpu_id _id; + addr_t _state { 0 }; + void *_ep_handler { nullptr }; + void *_dispatching { nullptr }; + bool _block { true }; + + enum Remote_state_requested { + NONE = 0, + PAUSE = 1, + RUN = 2 + } _remote { NONE }; + + static void _read_nova_state(Nova::Utcb &utcb, Vm_state &state, + unsigned exit_reason) + { + typedef Genode::Vm_state::Segment Segment; + typedef Genode::Vm_state::Range Range; + + state = Vm_state {}; + state.exit_reason = exit_reason; + + if (utcb.mtd & Nova::Mtd::ACDB) { + state.ax.value(utcb.ax); + state.cx.value(utcb.cx); + state.dx.value(utcb.dx); + state.bx.value(utcb.bx); + } + + if (utcb.mtd & Nova::Mtd::EBSD) { + state.di.value(utcb.di); + state.si.value(utcb.si); + state.bp.value(utcb.bp); + } + + if (utcb.mtd & Nova::Mtd::EFL) state.flags.value(utcb.flags); + if (utcb.mtd & Nova::Mtd::ESP) state.sp.value(utcb.sp); + if (utcb.mtd & Nova::Mtd::DR) state.dr7.value(utcb.dr7); + + if (utcb.mtd & Nova::Mtd::EIP) { + state.ip.value(utcb.ip); + state.ip_len.value(utcb.instr_len); + } + + if (utcb.mtd & Nova::Mtd::R8_R15) { + state. r8.value(utcb.read_r8()); + state. r9.value(utcb.read_r9()); + state.r10.value(utcb.read_r10()); + state.r11.value(utcb.read_r11()); + state.r12.value(utcb.read_r12()); + state.r13.value(utcb.read_r13()); + state.r14.value(utcb.read_r14()); + state.r15.value(utcb.read_r15()); + } + + if (utcb.mtd & Nova::Mtd::CR) { + state.cr0.value(utcb.cr0); + state.cr2.value(utcb.cr2); + state.cr3.value(utcb.cr3); + state.cr4.value(utcb.cr4); + } + if (utcb.mtd & Nova::Mtd::CSSS) { + state.cs.value(Segment{utcb.cs.sel, utcb.cs.ar, utcb.cs.limit, + utcb.cs.base}); + state.ss.value(Segment{utcb.ss.sel, utcb.ss.ar, utcb.ss.limit, + utcb.ss.base}); + } + + if (utcb.mtd & Nova::Mtd::ESDS) { + state.es.value(Segment{utcb.es.sel, utcb.es.ar, utcb.es.limit, + utcb.es.base}); + state.ds.value(Segment{utcb.ds.sel, utcb.ds.ar, utcb.ds.limit, + utcb.ds.base}); + } + + if (utcb.mtd & Nova::Mtd::FSGS) { + state.fs.value(Segment{utcb.fs.sel, utcb.fs.ar, utcb.fs.limit, + utcb.fs.base}); + state.gs.value(Segment{utcb.gs.sel, utcb.gs.ar, utcb.gs.limit, + utcb.gs.base}); + } + + if (utcb.mtd & Nova::Mtd::TR) { + state.tr.value(Segment{utcb.tr.sel, utcb.tr.ar, utcb.tr.limit, + utcb.tr.base}); + } + + if (utcb.mtd & Nova::Mtd::LDTR) { + state.ldtr.value(Segment{utcb.ldtr.sel, utcb.ldtr.ar, + utcb.ldtr.limit, utcb.ldtr.base}); + } + + if (utcb.mtd & Nova::Mtd::GDTR) { + state.gdtr.value(Range{utcb.gdtr.base, utcb.gdtr.limit}); + } + + if (utcb.mtd & Nova::Mtd::IDTR) { + state.idtr.value(Range{utcb.idtr.base, utcb.idtr.limit}); + } + + if (utcb.mtd & Nova::Mtd::SYS) { + state.sysenter_cs.value(utcb.sysenter_cs); + state.sysenter_sp.value(utcb.sysenter_sp); + state.sysenter_ip.value(utcb.sysenter_ip); + } + + if (utcb.mtd & Nova::Mtd::QUAL) { + state.qual_primary.value(utcb.qual[0]); + state.qual_secondary.value(utcb.qual[1]); + } + + if (utcb.mtd & Nova::Mtd::CTRL) { + state.ctrl_primary.value(utcb.ctrl[0]); + state.ctrl_secondary.value(utcb.ctrl[1]); + } + + if (utcb.mtd & Nova::Mtd::INJ) { + state.inj_info.value(utcb.inj_info); + state.inj_error.value(utcb.inj_error); + } + + if (utcb.mtd & Nova::Mtd::STA) { + state.intr_state.value(utcb.intr_state); + state.actv_state.value(utcb.actv_state); + } + + if (utcb.mtd & Nova::Mtd::TSC) { + state.tsc.value(utcb.tsc_val); + state.tsc_offset.value(utcb.tsc_off); + } + + if (utcb.mtd & Nova::Mtd::EFER) { + state.efer.value(utcb.read_efer()); + } + + if (utcb.mtd & Nova::Mtd::PDPTE) { + state.pdpte_0.value(utcb.pdpte[0]); + state.pdpte_1.value(utcb.pdpte[1]); + state.pdpte_2.value(utcb.pdpte[2]); + state.pdpte_3.value(utcb.pdpte[3]); + } + + if (utcb.mtd & Nova::Mtd::SYSCALL_SWAPGS) { + state.star.value(utcb.read_star()); + state.lstar.value(utcb.read_lstar()); + state.fmask.value(utcb.read_fmask()); + state.kernel_gs_base.value(utcb.read_kernel_gs_base()); + } + + if (utcb.mtd & Nova::Mtd::TPR) { + state.tpr.value(utcb.read_tpr()); + state.tpr_threshold.value(utcb.read_tpr_threshold()); + } + + /* XXX FPU state missing */ + } + + static void _write_nova_state(Nova::Utcb &utcb, Vm_state &state) + { + utcb.items = 0; + utcb.mtd = 0; + + if (state.ax.valid() || state.cx.valid() || + state.dx.valid() || state.bx.valid()) { + utcb.mtd |= Nova::Mtd::ACDB; + utcb.ax = state.ax.value(); + utcb.cx = state.cx.value(); + utcb.dx = state.dx.value(); + utcb.bx = state.bx.value(); + } + + if (state.bp.valid() || state.di.valid() || state.si.valid()) { + utcb.mtd |= Nova::Mtd::EBSD; + utcb.di = state.di.value(); + utcb.si = state.si.value(); + utcb.bp = state.bp.value(); + } + + if (state.flags.valid()) { + utcb.mtd |= Nova::Mtd::EFL; + utcb.flags = state.flags.value(); + } + + if (state.sp.valid()) { + utcb.mtd |= Nova::Mtd::ESP; + utcb.sp = state.sp.value(); + } + + if (state.ip.valid()) { + utcb.mtd |= Nova::Mtd::EIP; + utcb.ip = state.ip.value(); + } + + if (state.dr7.valid()) { + utcb.mtd |= Nova::Mtd::DR; + utcb.dr7 = state.dr7.value(); + } + + if (state.r8.valid() || state.r9.valid() || + state.r10.valid() || state.r11.valid() || + state.r12.valid() || state.r13.valid() || + state.r14.valid() || state.r15.valid()) { + + utcb.mtd |= Nova::Mtd::R8_R15; + utcb.write_r8 (state.r8.value()); + utcb.write_r9 (state.r9.value()); + utcb.write_r10(state.r10.value()); + utcb.write_r11(state.r11.value()); + utcb.write_r12(state.r12.value()); + utcb.write_r13(state.r13.value()); + utcb.write_r14(state.r14.value()); + utcb.write_r15(state.r15.value()); + } + + if (state.cr0.valid() || state.cr2.valid() || state.cr3.valid() || + state.cr4.valid()) { + utcb.mtd |= Nova::Mtd::CR; + utcb.cr0 = state.cr0.value(); + utcb.cr2 = state.cr2.value(); + utcb.cr3 = state.cr3.value(); + utcb.cr4 = state.cr4.value(); + } + + if (state.cs.valid() || state.ss.valid()) { + utcb.mtd |= Nova::Mtd::CSSS; + utcb.cs.sel = state.cs.value().sel; + utcb.cs.ar = state.cs.value().ar; + utcb.cs.limit = state.cs.value().limit; + utcb.cs.base = state.cs.value().base; + + utcb.ss.sel = state.ss.value().sel; + utcb.ss.ar = state.ss.value().ar; + utcb.ss.limit = state.ss.value().limit; + utcb.ss.base = state.ss.value().base; + } + + if (state.es.valid() || state.ds.valid()) { + utcb.mtd |= Nova::Mtd::ESDS; + utcb.es.sel = state.es.value().sel; + utcb.es.ar = state.es.value().ar; + utcb.es.limit = state.es.value().limit; + utcb.es.base = state.es.value().base; + + utcb.ds.sel = state.ds.value().sel; + utcb.ds.ar = state.ds.value().ar; + utcb.ds.limit = state.ds.value().limit; + utcb.ds.base = state.ds.value().base; + } + + if (state.fs.valid() || state.gs.valid()) { + utcb.mtd |= Nova::Mtd::FSGS; + utcb.fs.sel = state.fs.value().sel; + utcb.fs.ar = state.fs.value().ar; + utcb.fs.limit = state.fs.value().limit; + utcb.fs.base = state.fs.value().base; + + utcb.gs.sel = state.gs.value().sel; + utcb.gs.ar = state.gs.value().ar; + utcb.gs.limit = state.gs.value().limit; + utcb.gs.base = state.gs.value().base; + } + + if (state.tr.valid()) { + utcb.mtd |= Nova::Mtd::TR; + utcb.tr.sel = state.tr.value().sel; + utcb.tr.ar = state.tr.value().ar; + utcb.tr.limit = state.tr.value().limit; + utcb.tr.base = state.tr.value().base; + } + + if (state.ldtr.valid()) { + utcb.mtd |= Nova::Mtd::LDTR; + utcb.ldtr.sel = state.ldtr.value().sel; + utcb.ldtr.ar = state.ldtr.value().ar; + utcb.ldtr.limit = state.ldtr.value().limit; + utcb.ldtr.base = state.ldtr.value().base; + } + + if (state.gdtr.valid()) { + utcb.mtd |= Nova::Mtd::GDTR; + utcb.gdtr.limit = state.gdtr.value().limit; + utcb.gdtr.base = state.gdtr.value().base; + } + + if (state.idtr.valid()) { + utcb.mtd |= Nova::Mtd::IDTR; + utcb.idtr.limit = state.idtr.value().limit; + utcb.idtr.base = state.idtr.value().base; + } + + if (state.sysenter_cs.valid() || state.sysenter_sp.valid() || + state.sysenter_ip.valid()) { + utcb.mtd |= Nova::Mtd::SYS; + utcb.sysenter_cs = state.sysenter_cs.value(); + utcb.sysenter_sp = state.sysenter_sp.value(); + utcb.sysenter_ip = state.sysenter_ip.value(); + } + + if (state.ctrl_primary.valid() || state.ctrl_secondary.valid()) { + utcb.mtd |= Nova::Mtd::CTRL; + utcb.ctrl[0] = state.ctrl_primary.value(); + utcb.ctrl[1] = state.ctrl_secondary.value(); + } + + if (state.inj_info.valid() || state.inj_error.valid()) { + utcb.mtd |= Nova::Mtd::INJ; + utcb.inj_info = state.inj_info.value(); + utcb.inj_error = state.inj_error.value(); + } + + if (state.intr_state.valid() || state.actv_state.valid()) { + utcb.mtd |= Nova::Mtd::STA; + utcb.intr_state = state.intr_state.value(); + utcb.actv_state = state.actv_state.value(); + } + + if (state.tsc.valid() || state.tsc_offset.valid()) { + utcb.mtd |= Nova::Mtd::TSC; + utcb.tsc_val = state.tsc.value(); + utcb.tsc_off = state.tsc_offset.value(); + } + + if (state.efer.valid()) { + utcb.mtd |= Nova::Mtd::EFER; + utcb.write_efer(state.efer.value()); + } + + if (state.pdpte_0.valid() || state.pdpte_1.valid() || + state.pdpte_2.valid() || state.pdpte_3.valid()) { + + utcb.mtd |= Nova::Mtd::PDPTE; + utcb.pdpte[0] = state.pdpte_0.value(); + utcb.pdpte[1] = state.pdpte_1.value(); + utcb.pdpte[2] = state.pdpte_2.value(); + utcb.pdpte[3] = state.pdpte_3.value(); + } + + if (state.star.valid() || state.lstar.valid() || + state.fmask.valid() || state.kernel_gs_base.valid()) { + + utcb.mtd |= Nova::Mtd::SYSCALL_SWAPGS; + utcb.write_star(state.star.value()); + utcb.write_lstar(state.lstar.value()); + utcb.write_fmask(state.fmask.value()); + utcb.write_kernel_gs_base(state.kernel_gs_base.value()); + } + + if (state.tpr.valid() || state.tpr_threshold.valid()) { + utcb.mtd |= Nova::Mtd::TPR; + utcb.write_tpr(state.tpr.value()); + utcb.write_tpr_threshold(state.tpr_threshold.value()); + } + } + + void _dispatch() + { + try { + _dispatching = Thread::myself(); + /* call dispatch handler */ + _obj.dispatch(1); + _dispatching = nullptr; + } catch (...) { + _dispatching = nullptr; + throw; + } + } + + addr_t _sm_sel() const { + return Nova::NUM_INITIAL_PT_RESERVED + _id.id * 4; } + + addr_t _ec_sel() const { return _sm_sel() + 1; } + + Vcpu(const Vcpu&); + Vcpu &operator = (Vcpu const &); + + public: + + Vcpu(Vm_handler_base &o, unsigned id) : _obj(o), _id({id}) { } + + virtual ~Vcpu() { } + + addr_t badge(uint16_t exit) const { + return ((0UL + _id.id) << (sizeof(exit) * 8)) | exit; } + + Vm_session_client::Vcpu_id id() const { return _id; } + + __attribute__((regparm(1))) static void exit_entry(addr_t o) + { + Thread &myself = *Thread::myself(); + Nova::Utcb &utcb = *reinterpret_cast(myself.utcb()); + + uint16_t const exit_reason = o; + unsigned const vcpu_id = o >> (sizeof(exit_reason) * 8); + Vcpu * vcpu = nullptr; + + vcpus.for_each([&] (Vcpu &vc) { + if (vcpu_id == vc._id.id) + vcpu = &vc; + }); + + if (!vcpu) { + /* somebody called us directly ? ... ignore/deny */ + utcb.items = 0; + utcb.mtd = 0; + Nova::reply(myself.stack_top()); + } + + /* reset blocking state */ + bool const previous_blocked = vcpu->_block; + vcpu->_block = true; + + /* NOVA specific exit reasons */ + enum { VM_EXIT_STARTUP = 0xfe, VM_EXIT_RECALL = 0xff }; + + if (exit_reason == VM_EXIT_STARTUP) + vcpu->_ep_handler = &myself; + + Vm_state &state = *reinterpret_cast(vcpu->_state); + /* transform state from NOVA to Genode */ + if (exit_reason != VM_EXIT_RECALL) + _read_nova_state(utcb, state, exit_reason); + else { + /* consume potential multiple sem ups */ + Nova::sm_ctrl(vcpu->_sm_sel(), Nova::SEMAPHORE_UP); + Nova::sm_ctrl(vcpu->_sm_sel(), Nova::SEMAPHORE_DOWNZERO); + + if (!previous_blocked) + _read_nova_state(utcb, state, exit_reason); + else + state.exit_reason = exit_reason; + + if (vcpu->_remote == PAUSE) { + vcpu->_remote = NONE; + } else { + if (vcpu->_remote == RUN) { + vcpu->_remote = NONE; + if (!previous_blocked) { + /* still running - reply without state transfer */ + vcpu->_block = false; + utcb.items = 0; + utcb.mtd = 0; + Nova::reply(myself.stack_top()); + } + } + + if (previous_blocked) { + /* resume vCPU - with vCPU state update */ + vcpu->_block = false; + _write_nova_state(utcb, state); + Nova::reply(myself.stack_top()); + } + } + } + + vcpu->_dispatch(); + + if (vcpu->_block) { + /* block vCPU in kernel - no vCPU state update */ + utcb.items = 0; + utcb.mtd = 0; + Nova::reply(myself.stack_top(), vcpu->_sm_sel()); + } + + /* reply to NOVA and transfer vCPU state */ + _write_nova_state(utcb, state); + Nova::reply(myself.stack_top()); + } + + bool resume() + { + if (!_ep_handler) { + /* not started yet */ + return true; + } + + Thread * const current = Thread::myself(); + + if (_dispatching == current) { + _block = false; + return false; + } + + if ((_ep_handler == current) && !_block) + return false; + + if (_ep_handler != current) + _remote = RUN; + + Nova::ec_ctrl(Nova::EC_RECALL, _ec_sel()); + Nova::sm_ctrl(_sm_sel(), Nova::SEMAPHORE_UP); + + return false; + } + + void pause() + { + Thread * const current = Thread::myself(); + + if (_dispatching == current) { + /* current thread is already dispatching */ + _block = true; + return; + } + + if ((_ep_handler == current) && _block) { + _remote = PAUSE; + /* already blocked */ + } + + if (_ep_handler != current) + _remote = PAUSE; + + if (!_ep_handler) { + /* not started yet - let startup handler issue the recall */ + return; + } + + Nova::ec_ctrl(Nova::EC_RECALL, _ec_sel()); + Nova::sm_ctrl(_sm_sel(), Nova::SEMAPHORE_UP); + } + + void assign_ds_state(Region_map &rm, Dataspace_capability cap) { + _state = rm.attach(cap); } + + Nova::Mtd portal_mtd(unsigned exit, Vm_handler_base &handler) + { + Vm_state &state = *reinterpret_cast(_state); + state = Vm_state {}; + + if (!handler.config_vm_event(state, exit)) + return Nova::Mtd(Nova::Mtd::ALL); + + Genode::addr_t mtd = 0; + + if (state.ax.valid() || state.cx.valid() || + state.dx.valid() || state.bx.valid()) + mtd |= Nova::Mtd::ACDB; + + if (state.bp.valid() || state.di.valid() || state.si.valid()) + mtd |= Nova::Mtd::EBSD; + + if (state.flags.valid()) + mtd |= Nova::Mtd::EFL; + + if (state.sp.valid()) + mtd |= Nova::Mtd::ESP; + + if (state.ip.valid()) + mtd |= Nova::Mtd::EIP; + + if (state.dr7.valid()) + mtd |= Nova::Mtd::DR; + + if (state.r8.valid() || state.r9.valid() || state.r10.valid() || + state.r11.valid() || state.r12.valid() || state.r13.valid() || + state.r14.valid() || state.r15.valid()) + mtd |= Nova::Mtd::R8_R15; + + if (state.cr0.valid() || state.cr2.valid() || state.cr3.valid() || + state.cr4.valid()) + mtd |= Nova::Mtd::CR; + + if (state.cs.valid() || state.ss.valid()) + mtd |= Nova::Mtd::CSSS; + + if (state.es.valid() || state.ds.valid()) + mtd |= Nova::Mtd::ESDS; + + if (state.fs.valid() || state.gs.valid()) + mtd |= Nova::Mtd::FSGS; + + if (state.tr.valid()) + mtd |= Nova::Mtd::TR; + + if (state.ldtr.valid()) + mtd |= Nova::Mtd::LDTR; + + if (state.gdtr.valid()) + mtd |= Nova::Mtd::GDTR; + + if (state.idtr.valid()) + mtd |= Nova::Mtd::IDTR; + + if (state.sysenter_cs.valid() || state.sysenter_sp.valid() || + state.sysenter_ip.valid()) + mtd |= Nova::Mtd::SYS; + + if (state.ctrl_primary.valid() || state.ctrl_secondary.valid()) + mtd |= Nova::Mtd::CTRL; + + if (state.inj_info.valid() || state.inj_error.valid()) + mtd |= Nova::Mtd::INJ; + + if (state.intr_state.valid() || state.actv_state.valid()) + mtd |= Nova::Mtd::STA; + + if (state.tsc.valid() || state.tsc_offset.valid()) + mtd |= Nova::Mtd::TSC; + + if (state.efer.valid()) + mtd |= Nova::Mtd::EFER; + + if (state.pdpte_0.valid() || state.pdpte_1.valid() || + state.pdpte_2.valid() || state.pdpte_3.valid()) + mtd |= Nova::Mtd::PDPTE; + + if (state.star.valid() || state.lstar.valid() || + state.fmask.valid() || state.kernel_gs_base.valid()) + mtd |= Nova::Mtd::SYSCALL_SWAPGS; + + if (state.tpr.valid() || state.tpr_threshold.valid()) + mtd |= Nova::Mtd::TPR; + + if (state.qual_primary.valid() || state.qual_secondary.valid()) + mtd |= Nova::Mtd::QUAL; + + /* XXX FPU missing */ + + state = Vm_state {}; + + return Nova::Mtd(mtd); + } +}; + +Signal_context_capability +static create_exit_handler(Pd_session &pd, Rpc_entrypoint &ep, + Vcpu &vcpu, unsigned exit_reason, + Nova::Mtd &mtd) +{ + Thread * tep = reinterpret_cast(&ep); + + Native_capability thread_cap = Capability_space::import(tep->native_thread().ec_sel); + + Nova_native_pd_client native_pd { pd.native_pd() }; + Native_capability vm_exit_cap = native_pd.alloc_rpc_cap(thread_cap, + (addr_t)Vcpu::exit_entry, + mtd.value()); + native_pd.imprint_rpc_cap(vm_exit_cap, vcpu.badge(exit_reason)); + + return reinterpret_cap_cast(vm_exit_cap); +} + +Vm_session_client::Vcpu_id +Vm_session_client::create_vcpu(Allocator &alloc, Env &env, + Vm_handler_base &handler) +{ + Thread * ep = reinterpret_cast(&handler._rpc_ep); + call(ep->cap()); + + Vcpu * vcpu = new (alloc) Registered (vcpus, handler, vcpu_id++); + vcpu->assign_ds_state(env.rm(), call(vcpu->id())); + + Signal_context_capability dontcare_exit; + + enum { MAX_VM_EXITS = (1U << Nova::NUM_INITIAL_VCPU_PT_LOG2) }; + for (unsigned i = 0; i < MAX_VM_EXITS; i++) { + Signal_context_capability signal_exit; + + Nova::Mtd mtd = vcpu->portal_mtd(i, handler); + if (mtd.value()) { + signal_exit = create_exit_handler(env.pd(), handler._rpc_ep, + *vcpu, i, mtd); + } else { + if (!dontcare_exit.valid()) { + Nova::Mtd mtd_ip(Nova::Mtd::EIP); + dontcare_exit = create_exit_handler(env.pd(), handler._rpc_ep, + *vcpu, 0x100, mtd_ip); + } + signal_exit = dontcare_exit; + } + + call(signal_exit, vcpu->id()); + } + + return vcpu->id(); +} + +void Vm_session_client::run(Vm_session_client::Vcpu_id vcpu_id) +{ + vcpus.for_each([&] (Vcpu &vcpu) { + if (vcpu.id().id != vcpu_id.id) + return; + + if (vcpu.resume()) + call(vcpu.id()); + }); +} + +void Vm_session_client::pause(Vm_session_client::Vcpu_id vcpu_id) +{ + vcpus.for_each([&] (Vcpu &vcpu) { + if (vcpu.id().id != vcpu_id.id) + return; + + vcpu.pause(); + }); +} + +Dataspace_capability Vm_session_client::cpu_state(Vcpu_id vcpu_id) +{ + Dataspace_capability cap; + + vcpus.for_each([&] (Vcpu &vcpu) { + if (vcpu.id().id == vcpu_id.id) + cap = call(vcpu_id); + }); + + return cap; +}