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