/* * \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 #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, Affinity::Location const location, Session_label const &label, Trace::Control_area &trace_control_area, Trace::Source_registry &trace_sources) : _ram_alloc(ram_alloc), _cap_alloc(cap_alloc), _trace_sources(trace_sources), _sel_sm_ec_sc(invalid()), _id(id), _location(location), _label(label), _trace_control_slot(trace_control_area) { /* 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; } _trace_sources.insert(&_trace_source); } Vm_session_component::Vcpu::~Vcpu() { _trace_sources.remove(&_trace_source); 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 ++; } Genode::Trace::Source::Info Vm_session_component::Vcpu::trace_source_info() const { Genode::uint64_t sc_time = 0; uint8_t res = Nova::sc_ctrl(sc_sel(), sc_time); if (res != Nova::NOVA_OK) warning("sc_ctrl failed res=", res); return { _label, String<5>("vCPU"), Trace::Execution_time(sc_time, sc_time), _location }; } 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; Affinity::Location vcpu_location; auto lambda = [&] (Cpu_thread_component *ptr) { if (!ptr) return Vcpu::invalid(); Cpu_thread_component &thread = *ptr; vcpu_location = thread.platform_thread().affinity(); 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 (_heap) Vcpu(_constrained_md_ram_alloc, _cap_quota_guard(), Vcpu_id {_id_alloc}, vcpu_location, _session_label, _trace_control_area, _trace_sources); /* 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(_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(_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(_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; 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 &label, Diag, Ram_allocator &ram, Region_map &local_rm, unsigned const priority, Trace::Source_registry &trace_sources) : Ram_quota_guard(resources.ram_quota), Cap_quota_guard(resources.cap_quota), _ep(ep), _trace_control_area(ram, local_rm), _trace_sources(trace_sources), _constrained_md_ram_alloc(ram, _ram_quota_guard(), _cap_quota_guard()), _heap(_constrained_md_ram_alloc, local_rm), _priority(scale_priority(priority, "VM session")), _session_label(label) { _cap_quota_guard().withdraw(Cap_quota{1}); _pd_sel = cap_map().insert(); if (!_pd_sel || _pd_sel == Vcpu::invalid()) 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); throw Service_denied(); } /* configure managed VM area */ _map.add_range(0, 0UL - 0x1000); _map.add_range(0UL - 0x1000, 0x1000); } Vm_session_component::~Vm_session_component() { for (;Vcpu * vcpu = _vcpus.first();) { _vcpus.remove(vcpu); destroy(_heap, vcpu); } /* detach all regions */ while (true) { addr_t out_addr = 0; if (!_map.any_block_addr(&out_addr)) break; detach(out_addr); } if (_pd_sel && _pd_sel != Vcpu::invalid()) cap_map().remove(_pd_sel, 0, true); } void Vm_session_component::_attach_vm_memory(Dataspace_component &dsc, addr_t const guest_phys, Attach_attr const attribute) { 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() + attribute.offset, attribute.size, guest_phys, attribute.size, guest_phys); Flexpage page = flex.page(); while (page.valid()) { Nova::Rights const map_rights (true, dsc.writable() && attribute.writeable, attribute.executable); 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(); } } void Vm_session_component::_detach_vm_memory(addr_t guest_phys, size_t size) { Nova::Rights const revoke_rwx(true, true, true); Flexpage_iterator flex(guest_phys, size, guest_phys, size, 0); Flexpage page = flex.page(); while (page.valid()) { Nova::Mem_crd mem(page.addr >> 12, page.log2_order - 12, revoke_rwx); Nova::revoke(mem, true, true, _pd_sel); page = flex.page(); } }