hw: x86_64: refactor Vm_session_component

On x86, the `Vm_session_component` obscured the differences between SVM
and VMX.

Separate the implementations, factor out common functionality and
address a number of long-standing issues in the process:

- Allocate nested page tables from Core_ram_allocator as a more suitable
  abstraction and account for the required memory, subtract the
  necessary amount of RAM from the session's `Ram_quota` *before*
  constructing the session object, to make sure that the memory
  allocated from the `Core_ram_allocator` is available from the VMM's
  RAM quota.
- Move the allocation of Vcpu_state and Vcpu_data into the Core::Vcpu
  class and use the Core RAM Allocator to allocate memory with a known
  physical address.
- Remove the fixed number of virtual CPUs and the associated reservation
  of memory by using a Registry for a flexible amount of vCPUs.

Issue #5221
This commit is contained in:
Benjamin Lamowski 2025-01-07 16:35:16 +01:00 committed by Christian Helmuth
parent 922fdd1628
commit 05522696c7
13 changed files with 1051 additions and 298 deletions

View File

@ -22,9 +22,6 @@ SRC_CC += kernel/vm_thread_on.cc
SRC_CC += spec/x86_64/virtualization/kernel/vm.cc
SRC_CC += spec/x86_64/virtualization/kernel/svm.cc
SRC_CC += spec/x86_64/virtualization/kernel/vmx.cc
SRC_CC += spec/x86_64/virtualization/vm_session_component.cc
SRC_CC += vm_session_common.cc
SRC_CC += vm_session_component.cc
SRC_CC += kernel/lock.cc
SRC_CC += spec/x86_64/pic.cc
SRC_CC += spec/x86_64/pit.cc

View File

@ -0,0 +1,275 @@
/*
* \brief Guest memory abstraction
* \author Stefan Kalkowski
* \author Benjamin Lamowski
* \date 2024-11-25
*/
/*
* Copyright (C) 2015-2024 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__GUEST_MEMORY_H_
#define _CORE__GUEST_MEMORY_H_
/* base includes */
#include <base/allocator.h>
#include <base/allocator_avl.h>
#include <vm_session/vm_session.h>
#include <dataspace/capability.h>
/* core includes */
#include <dataspace_component.h>
#include <region_map_component.h>
namespace Core { class Guest_memory; }
using namespace Core;
class Core::Guest_memory
{
private:
using Avl_region = Allocator_avl_tpl<Rm_region>;
using Attach_attr = Genode::Vm_session::Attach_attr;
Sliced_heap _sliced_heap;
Avl_region _map { &_sliced_heap };
uint8_t _remaining_print_count { 10 };
void _with_region(addr_t const addr, auto const &fn)
{
Rm_region *region = _map.metadata((void *)addr);
if (region)
fn(*region);
else
if (_remaining_print_count) {
error(__PRETTY_FUNCTION__, " unknown region");
_remaining_print_count--;
}
}
public:
enum class Attach_result {
OK,
INVALID_DS,
OUT_OF_RAM,
OUT_OF_CAPS,
REGION_CONFLICT,
};
Attach_result attach(Region_map_detach &rm_detach,
Dataspace_component &dsc,
addr_t const guest_phys,
Attach_attr attr,
auto const &map_fn)
{
/*
* unsupported - deny otherwise arbitrary physical
* memory can be mapped to a VM
*/
if (dsc.managed())
return Attach_result::INVALID_DS;
if (guest_phys & 0xffful || attr.offset & 0xffful ||
attr.size & 0xffful)
return Attach_result::INVALID_DS;
if (!attr.size) {
attr.size = dsc.size();
if (attr.offset < attr.size)
attr.size -= attr.offset;
}
if (attr.size > dsc.size())
attr.size = dsc.size();
if (attr.offset >= dsc.size() ||
attr.offset > dsc.size() - attr.size)
return Attach_result::INVALID_DS;
using Alloc_error = Range_allocator::Alloc_error;
Attach_result const retval = _map.alloc_addr(attr.size, guest_phys).convert<Attach_result>(
[&] (void *) {
Rm_region::Attr const region_attr
{
.base = guest_phys,
.size = attr.size,
.write = dsc.writeable() && attr.writeable,
.exec = attr.executable,
.off = attr.offset,
.dma = false,
};
/* store attachment info in meta data */
try {
_map.construct_metadata((void *)guest_phys,
dsc, rm_detach, region_attr);
} catch (Allocator_avl_tpl<Rm_region>::Assign_metadata_failed) {
if (_remaining_print_count) {
error("failed to store attachment info");
_remaining_print_count--;
}
return Attach_result::INVALID_DS;
}
Rm_region &region = *_map.metadata((void *)guest_phys);
/* inform dataspace about attachment */
dsc.attached_to(region);
return Attach_result::OK;
},
[&] (Alloc_error error) {
switch (error) {
case Alloc_error::OUT_OF_RAM:
return Attach_result::OUT_OF_RAM;
case Alloc_error::OUT_OF_CAPS:
return Attach_result::OUT_OF_CAPS;
case Alloc_error::DENIED:
{
/*
* Handle attach after partial detach
*/
Rm_region *region_ptr = _map.metadata((void *)guest_phys);
if (!region_ptr)
return Attach_result::REGION_CONFLICT;
Rm_region &region = *region_ptr;
bool conflict = false;
region.with_dataspace([&] (Dataspace_component &dataspace) {
(void)dataspace;
if (!(dsc.cap() == dataspace.cap()))
conflict = true;
});
if (conflict)
return Attach_result::REGION_CONFLICT;
if (guest_phys < region.base() ||
guest_phys > region.base() + region.size() - 1)
return Attach_result::REGION_CONFLICT;
}
};
return Attach_result::OK;
}
);
if (retval == Attach_result::OK) {
addr_t phys_addr = dsc.phys_addr() + attr.offset;
size_t size = attr.size;
map_fn(guest_phys, phys_addr, size);
}
return retval;
}
void detach(addr_t guest_phys,
size_t size,
auto const &unmap_fn)
{
if (!size || (guest_phys & 0xffful) || (size & 0xffful)) {
if (_remaining_print_count) {
warning("vm_session: skipping invalid memory detach addr=",
(void *)guest_phys, " size=", (void *)size);
_remaining_print_count--;
}
return;
}
addr_t const guest_phys_end = guest_phys + (size - 1);
addr_t addr = guest_phys;
do {
Rm_region *region = _map.metadata((void *)addr);
/* walk region holes page-by-page */
size_t iteration_size = 0x1000;
if (region) {
iteration_size = region->size();
detach_at(region->base(), unmap_fn);
}
if (addr >= guest_phys_end - (iteration_size - 1))
break;
addr += iteration_size;
} while (true);
}
Guest_memory(Constrained_ram_allocator &constrained_md_ram_alloc,
Region_map &region_map)
:
_sliced_heap(constrained_md_ram_alloc, region_map)
{
/* configure managed VM area */
_map.add_range(0UL, ~0UL);
}
~Guest_memory()
{
/* detach all regions */
while (true) {
addr_t out_addr = 0;
if (!_map.any_block_addr(&out_addr))
break;
detach_at(out_addr, [](addr_t, size_t) { });
}
}
void detach_at(addr_t addr,
auto const &unmap_fn)
{
_with_region(addr, [&] (Rm_region &region) {
if (!region.reserved())
reserve_and_flush(addr, unmap_fn);
/* free the reserved region */
_map.free(reinterpret_cast<void *>(region.base()));
});
}
void reserve_and_flush(addr_t addr,
auto const &unmap_fn)
{
_with_region(addr, [&] (Rm_region &region) {
/* inform dataspace */
region.with_dataspace([&] (Dataspace_component &dataspace) {
dataspace.detached_from(region);
});
region.mark_as_reserved();
unmap_fn(region.base(), region.size());
});
}
};
#endif /* _CORE__GUEST_MEMORY_H_ */

View File

@ -0,0 +1,79 @@
/*
* \brief Allocate an object with a physical address
* \author Norman Feske
* \author Benjamin Lamowski
* \date 2024-12-02
*/
/*
* Copyright (C) 2024 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__PHYS_ALLOCATED_H_
#define _CORE__PHYS_ALLOCATED_H_
/* base includes */
#include <base/allocator.h>
#include <base/attached_ram_dataspace.h>
#include <util/noncopyable.h>
/* core-local includes */
#include <types.h>
namespace Core {
template <typename T>
class Phys_allocated;
}
using namespace Core;
template <typename T>
class Core::Phys_allocated : Genode::Noncopyable
{
private:
Rpc_entrypoint &_ep;
Ram_allocator &_ram;
Region_map &_rm;
Attached_ram_dataspace _ds { _ram, _rm, sizeof(T) };
public:
T &obj = *_ds.local_addr<T>();
Phys_allocated(Rpc_entrypoint &ep,
Ram_allocator &ram,
Region_map &rm)
:
_ep(ep), _ram(ram), _rm(rm)
{
construct_at<T>(&obj);
}
Phys_allocated(Rpc_entrypoint &ep,
Ram_allocator &ram,
Region_map &rm,
auto const &construct_fn)
:
_ep(ep), _ram(ram), _rm(rm)
{
construct_fn(*this, &obj);
}
~Phys_allocated() { obj.~T(); }
addr_t phys_addr() {
addr_t phys_addr { };
_ep.apply(_ds.cap(), [&](Dataspace_component *dsc) {
phys_addr = dsc->phys_addr();
});
return phys_addr;
}
};
#endif /* _CORE__PHYS_ALLOCATED_H_ */

View File

@ -0,0 +1,128 @@
/*
* \brief Vm_session vCPU
* \author Stefan Kalkowski
* \author Benjamin Lamowski
* \date 2024-11-26
*/
/*
* Copyright (C) 2015-2024 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__VCPU_H_
#define _CORE__VCPU_H_
/* base includes */
#include <base/attached_dataspace.h>
#include <vm_session/vm_session.h>
/* base-hw includes */
#include <hw_native_vcpu/hw_native_vcpu.h>
#include <kernel/vm.h>
/* core includes */
#include <phys_allocated.h>
#include <region_map_component.h>
namespace Core { struct Vcpu; }
class Core::Vcpu : public Rpc_object<Vm_session::Native_vcpu, Vcpu>
{
private:
struct Data_pages {
uint8_t _[Vcpu_data::size()];
};
Kernel::Vm::Identity &_id;
Rpc_entrypoint &_ep;
Vcpu_data _vcpu_data { };
Kernel_object<Kernel::Vm> _kobj { };
Constrained_ram_allocator &_ram;
Ram_dataspace_capability _ds_cap { };
Region_map &_region_map;
Affinity::Location _location;
Phys_allocated<Data_pages> _vcpu_data_pages;
constexpr size_t vcpu_state_size()
{
return align_addr(sizeof(Board::Vcpu_state),
get_page_size_log2());
}
public:
Vcpu(Kernel::Vm::Identity &id,
Rpc_entrypoint &ep,
Constrained_ram_allocator &constrained_ram_alloc,
Region_map &region_map,
Affinity::Location location)
:
_id(id),
_ep(ep),
_ram(constrained_ram_alloc),
_ds_cap( {_ram.alloc(vcpu_state_size(), Cache::UNCACHED)} ),
_region_map(region_map),
_location(location),
_vcpu_data_pages(ep, constrained_ram_alloc, region_map)
{
Region_map::Attr attr { };
attr.writeable = true;
_vcpu_data.vcpu_state = _region_map.attach(_ds_cap, attr).convert<Vcpu_state *>(
[&] (Region_map::Range range) { return (Vcpu_state *)range.start; },
[&] (Region_map::Attach_error) -> Vcpu_state * {
error("failed to attach VCPU data within core");
return nullptr;
});
if (!_vcpu_data.vcpu_state) {
_ram.free(_ds_cap);
throw Attached_dataspace::Region_conflict();
}
_vcpu_data.virt_area = &_vcpu_data_pages.obj;
_vcpu_data.phys_addr = _vcpu_data_pages.phys_addr();
ep.manage(this);
}
~Vcpu()
{
_region_map.detach((addr_t)_vcpu_data.vcpu_state);
_ram.free(_ds_cap);
_ep.dissolve(this);
}
/*******************************
** Native_vcpu RPC interface **
*******************************/
Capability<Dataspace> state() const { return _ds_cap; }
Native_capability native_vcpu() { return _kobj.cap(); }
void exception_handler(Signal_context_capability handler)
{
using Genode::warning;
if (!handler.valid()) {
warning("invalid signal");
return;
}
if (_kobj.constructed()) {
warning("Cannot register vcpu handler twice");
return;
}
unsigned const cpu = _location.xpos();
if (!_kobj.create(cpu, (void *)&_vcpu_data,
Capability_space::capid(handler), _id))
warning("Cannot instantiate vm kernel object, invalid signal context?");
}
};
#endif /* _CORE__VCPU_H_ */

View File

@ -22,7 +22,6 @@
#include <cpu.h>
#include <cpu/vcpu_state_virtualization.h>
#include <hw/spec/x86_64/x86_64.h>
#include <spec/x86_64/virtualization/vm_page_table.h>
#include <spec/x86_64/virtualization/svm.h>
#include <spec/x86_64/virtualization/vmx.h>
@ -34,10 +33,6 @@ namespace Board {
using Vcpu_data = Genode::Vcpu_data;
using Vcpu_state = Genode::Vcpu_state;
enum {
VCPU_MAX = 16
};
enum Platform_exitcodes : uint64_t {
EXIT_NPF = 0xfc,
EXIT_INIT = 0xfd,

View File

@ -37,7 +37,7 @@ void Core::platform_add_local_services(Rpc_entrypoint &ep,
static Vm_root vm_root(ep, sliced_heap, core_ram, core_rm, trace_sources);
static Core_service<Vm_session_component> vm_service(local_services, vm_root);
static Core_service<Session_object<Vm_session>> vm_service(local_services, vm_root);
static Core_service<Io_port_session_component> io_port_ls(local_services, io_port_root);
}

View File

@ -0,0 +1,234 @@
/*
* \brief SVM VM session component for 'base-hw'
* \author Stefan Kalkowski
* \author Benjamin Lamowski
* \date 2024-09-20
*/
/*
* Copyright (C) 2015-2024 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__SVM_VM_SESSION_COMPONENT_H_
#define _CORE__SVM_VM_SESSION_COMPONENT_H_
/* base includes */
#include <base/allocator.h>
#include <base/session_object.h>
#include <base/registry.h>
#include <vm_session/vm_session.h>
#include <dataspace/capability.h>
/* base-hw includes */
#include <spec/x86_64/virtualization/hpt.h>
/* core includes */
#include <cpu_thread_component.h>
#include <region_map_component.h>
#include <kernel/vm.h>
#include <trace/source_registry.h>
#include <vcpu.h>
#include <vmid_allocator.h>
#include <guest_memory.h>
#include <phys_allocated.h>
namespace Core { class Svm_session_component; }
class Core::Svm_session_component
:
public Session_object<Vm_session>
{
private:
using Vm_page_table = Hw::Hpt;
using Vm_page_table_array =
Vm_page_table::Allocator::Array<Kernel::DEFAULT_TRANSLATION_TABLE_MAX>;
/*
* Noncopyable
*/
Svm_session_component(Svm_session_component const &);
Svm_session_component &operator = (Svm_session_component const &);
struct Detach : Region_map_detach
{
Svm_session_component &_session;
Detach(Svm_session_component &session) : _session(session)
{ }
void detach_at(addr_t at) override
{
_session._detach_at(at);
}
void reserve_and_flush(addr_t at) override
{
_session._reserve_and_flush(at);
}
void unmap_region(addr_t base, size_t size) override
{
Genode::error(__func__, " unimplemented ", base, " ", size);
}
} _detach { *this };
Registry<Registered<Vcpu>> _vcpus { };
Rpc_entrypoint &_ep;
Constrained_ram_allocator _constrained_ram_alloc;
Region_map &_region_map;
Heap _heap;
Phys_allocated<Vm_page_table> _table;
Phys_allocated<Vm_page_table_array> _table_array;
Guest_memory _memory;
Vmid_allocator &_vmid_alloc;
Kernel::Vm::Identity _id;
uint8_t _remaining_print_count { 10 };
void _detach_at(addr_t addr)
{
_memory.detach_at(addr,
[&](addr_t vm_addr, size_t size) {
_table.obj.remove_translation(vm_addr, size, _table_array.obj.alloc()); });
}
void _reserve_and_flush(addr_t addr)
{
_memory.reserve_and_flush(addr, [&](addr_t vm_addr, size_t size) {
_table.obj.remove_translation(vm_addr, size, _table_array.obj.alloc()); });
}
public:
Svm_session_component(Vmid_allocator & vmid_alloc,
Rpc_entrypoint &ds_ep,
Resources resources,
Label const &label,
Diag diag,
Ram_allocator &ram_alloc,
Region_map &region_map,
Trace::Source_registry &)
:
Session_object(ds_ep, resources, label, diag),
_ep(ds_ep),
_constrained_ram_alloc(ram_alloc, _ram_quota_guard(), _cap_quota_guard()),
_region_map(region_map),
_heap(_constrained_ram_alloc, region_map),
_table(_ep, _constrained_ram_alloc, _region_map),
_table_array(_ep, _constrained_ram_alloc, _region_map,
[] (Phys_allocated<Vm_page_table_array> &table_array, auto *obj_ptr) {
construct_at<Vm_page_table_array>(obj_ptr, [&] (void *virt) {
return table_array.phys_addr() + ((addr_t) obj_ptr - (addr_t)virt);
});
}),
_memory(_constrained_ram_alloc, region_map),
_vmid_alloc(vmid_alloc),
_id({(unsigned)_vmid_alloc.alloc(), (void *)_table.phys_addr()})
{ }
~Svm_session_component()
{
_vcpus.for_each([&] (Registered<Vcpu> &vcpu) {
destroy(_heap, &vcpu); });
_vmid_alloc.free(_id.id);
}
/**************************
** Vm session interface **
**************************/
void attach(Dataspace_capability cap, addr_t guest_phys, Attach_attr attr) override
{
bool out_of_tables = false;
bool invalid_mapping = false;
auto const &map_fn = [&](addr_t vm_addr, addr_t phys_addr, size_t size) {
Page_flags const pflags { RW, EXEC, USER, NO_GLOBAL, RAM, CACHED };
try {
_table.obj.insert_translation(vm_addr, phys_addr, size, pflags, _table_array.obj.alloc());
} catch(Hw::Out_of_tables &) {
if (_remaining_print_count) {
Genode::error("Translation table needs too much RAM");
_remaining_print_count--;
}
out_of_tables = true;
} catch(...) {
if (_remaining_print_count) {
Genode::error("Invalid mapping ", Genode::Hex(phys_addr), " -> ",
Genode::Hex(vm_addr), " (", size, ")");
}
invalid_mapping = true;
}
};
if (!cap.valid())
throw Invalid_dataspace();
/* check dataspace validity */
_ep.apply(cap, [&] (Dataspace_component *ptr) {
if (!ptr)
throw Invalid_dataspace();
Dataspace_component &dsc = *ptr;
Guest_memory::Attach_result result =
_memory.attach(_detach, dsc, guest_phys, attr, map_fn);
if (out_of_tables)
throw Out_of_ram();
if (invalid_mapping)
throw Invalid_dataspace();
switch (result) {
case Guest_memory::Attach_result::OK : break;
case Guest_memory::Attach_result::INVALID_DS : throw Invalid_dataspace(); break;
case Guest_memory::Attach_result::OUT_OF_RAM : throw Out_of_ram(); break;
case Guest_memory::Attach_result::OUT_OF_CAPS : throw Out_of_caps(); break;
case Guest_memory::Attach_result::REGION_CONFLICT: throw Region_conflict(); break;
}
});
}
void attach_pic(addr_t) override
{ }
void detach(addr_t guest_phys, size_t size) override
{
_memory.detach(guest_phys, size, [&](addr_t vm_addr, size_t size) {
_table.obj.remove_translation(vm_addr, size, _table_array.obj.alloc()); });
}
Capability<Native_vcpu> create_vcpu(Thread_capability tcap) override
{
Affinity::Location vcpu_location;
_ep.apply(tcap, [&] (Cpu_thread_component *ptr) {
if (!ptr) return;
vcpu_location = ptr->platform_thread().affinity();
});
Vcpu &vcpu = *new (_heap)
Registered<Vcpu>(_vcpus,
_id,
_ep,
_constrained_ram_alloc,
_region_map,
vcpu_location);
return vcpu.cap();
}
};
#endif /* _CORE__SVM_VM_SESSION_COMPONENT_H_ */

View File

@ -1,106 +0,0 @@
/*
* \brief VM page table abstraction between VMX and SVM for x86
* \author Benjamin Lamowski
* \date 2024-04-23
*/
/*
* Copyright (C) 2024 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__SPEC__PC__VIRTUALIZATION__VM_PAGE_TABLE_H_
#define _CORE__SPEC__PC__VIRTUALIZATION__VM_PAGE_TABLE_H_
#include <base/log.h>
#include <util/construct_at.h>
#include <spec/x86_64/virtualization/ept.h>
#include <spec/x86_64/virtualization/hpt.h>
namespace Board {
using namespace Genode;
struct Vm_page_table
{
/* Both Ept and Hpt need to actually use this allocator */
using Allocator = Genode::Page_table_allocator<1UL << SIZE_LOG2_4KB>;
template <class T, class U>
struct is_same {
static const bool value = false;
};
template <class T>
struct is_same <T, T> {
static const bool value = true;
};
static_assert(is_same<Allocator, Hw::Ept::Allocator>::value,
"Ept uses different allocator");
static_assert(is_same<Allocator, Hw::Hpt::Allocator>::value,
"Hpt uses different allocator");
static constexpr size_t ALIGNM_LOG2 = Hw::SIZE_LOG2_4KB;
enum Virt_type {
VIRT_TYPE_NONE,
VIRT_TYPE_VMX,
VIRT_TYPE_SVM
};
union {
Hw::Ept ept;
Hw::Hpt hpt;
};
void insert_translation(addr_t vo,
addr_t pa,
size_t size,
Page_flags const & flags,
Allocator & alloc)
{
if (virt_type() == VIRT_TYPE_VMX)
ept.insert_translation(vo, pa, size, flags, alloc);
else if (virt_type() == VIRT_TYPE_SVM)
hpt.insert_translation(vo, pa, size, flags, alloc);
}
void remove_translation(addr_t vo, size_t size, Allocator & alloc)
{
if (virt_type() == VIRT_TYPE_VMX)
ept.remove_translation(vo, size, alloc);
else if (virt_type() == VIRT_TYPE_SVM)
hpt.remove_translation(vo, size, alloc);
}
static Virt_type virt_type() {
static Virt_type virt_type { VIRT_TYPE_NONE };
if (virt_type == VIRT_TYPE_NONE) {
if (Hw::Virtualization_support::has_vmx())
virt_type = VIRT_TYPE_VMX;
else if (Hw::Virtualization_support::has_svm())
virt_type = VIRT_TYPE_SVM;
else
error("Failed to detect Virtualization technology");
}
return virt_type;
}
Vm_page_table()
{
if (virt_type() == VIRT_TYPE_VMX)
Genode::construct_at<Hw::Ept>(this);
else if (virt_type() == VIRT_TYPE_SVM)
Genode::construct_at<Hw::Hpt>(this);
}
};
using Vm_page_table_array =
Vm_page_table::Allocator::Array<Kernel::DEFAULT_TRANSLATION_TABLE_MAX>;
};
#endif /* _CORE__SPEC__PC__VIRTUALIZATION__VM_PAGE_TABLE_H_ */

View File

@ -1,181 +0,0 @@
/*
* \brief VM session component for 'base-hw'
* \author Stefan Kalkowski
* \author Benjamin Lamowski
* \date 2015-02-17
*/
/*
* Copyright (C) 2015-2024 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.
*/
/* Genode includes */
#include <util/construct_at.h>
/* base internal includes */
#include <base/internal/unmanaged_singleton.h>
/* core includes */
#include <kernel/core_interface.h>
#include <vm_session_component.h>
#include <platform.h>
#include <cpu_thread_component.h>
using namespace Core;
static Core_mem_allocator & cma() {
return static_cast<Core_mem_allocator&>(platform().core_mem_alloc()); }
void Vm_session_component::_attach(addr_t phys_addr, addr_t vm_addr, size_t size)
{
using namespace Hw;
Page_flags pflags { RW, EXEC, USER, NO_GLOBAL, RAM, CACHED };
try {
_table.insert_translation(vm_addr, phys_addr, size, pflags,
_table_array.alloc());
return;
} catch(Hw::Out_of_tables &) {
Genode::error("Translation table needs to much RAM");
} catch(...) {
Genode::error("Invalid mapping ", Genode::Hex(phys_addr), " -> ",
Genode::Hex(vm_addr), " (", size, ")");
}
}
void Vm_session_component::_attach_vm_memory(Dataspace_component &dsc,
addr_t const vm_addr,
Attach_attr const attribute)
{
_attach(dsc.phys_addr() + attribute.offset, vm_addr, attribute.size);
}
void Vm_session_component::attach_pic(addr_t )
{ }
void Vm_session_component::_detach_vm_memory(addr_t vm_addr, size_t size)
{
_table.remove_translation(vm_addr, size, _table_array.alloc());
}
void * Vm_session_component::_alloc_table()
{
/* get some aligned space for the translation table */
return cma().alloc_aligned(sizeof(Board::Vm_page_table),
Board::Vm_page_table::ALIGNM_LOG2).convert<void *>(
[&] (void *table_ptr) {
return table_ptr; },
[&] (Range_allocator::Alloc_error) -> void * {
/* XXX handle individual error conditions */
error("failed to allocate kernel object");
throw Insufficient_ram_quota(); }
);
}
Genode::addr_t Vm_session_component::_alloc_vcpu_data(Genode::addr_t ds_addr)
{
/*
* XXX these allocations currently leak memory on VM Session
* destruction. This cannot be easily fixed because the
* Core Mem Allocator does not implement free().
*
* Normally we would use constrained_md_ram_alloc to make the allocation,
* but to get the physical address of the pages in virt_area, we need
* to use the Core Mem Allocator.
*/
Vcpu_data * vcpu_data = (Vcpu_data *) cma()
.try_alloc(sizeof(Board::Vcpu_data))
.convert<void *>(
[&](void *ptr) { return ptr; },
[&](Range_allocator::Alloc_error) -> void * {
/* XXX handle individual error conditions */
error("failed to allocate kernel object");
throw Insufficient_ram_quota();
});
vcpu_data->virt_area = cma()
.alloc_aligned(Vcpu_data::size(), 12)
.convert<void *>(
[&](void *ptr) { return ptr; },
[&](Range_allocator::Alloc_error) -> void * {
/* XXX handle individual error conditions */
error("failed to allocate kernel object");
throw Insufficient_ram_quota();
});
vcpu_data->vcpu_state = (Vcpu_state *) ds_addr;
vcpu_data->phys_addr = (addr_t)cma().phys_addr(vcpu_data->virt_area);
return (Genode::addr_t) vcpu_data;
}
Vm_session_component::Vm_session_component(Vmid_allocator & vmid_alloc,
Rpc_entrypoint &ds_ep,
Resources resources,
Label const &,
Diag,
Ram_allocator &ram_alloc,
Region_map &region_map,
unsigned,
Trace::Source_registry &)
:
Ram_quota_guard(resources.ram_quota),
Cap_quota_guard(resources.cap_quota),
_ep(ds_ep),
_constrained_md_ram_alloc(ram_alloc, _ram_quota_guard(), _cap_quota_guard()),
_sliced_heap(_constrained_md_ram_alloc, region_map),
_region_map(region_map),
_table(*construct_at<Board::Vm_page_table>(_alloc_table())),
_table_array(*(new (cma()) Board::Vm_page_table_array([] (void * virt) {
return (addr_t)cma().phys_addr(virt);}))),
_vmid_alloc(vmid_alloc),
_id({(unsigned)_vmid_alloc.alloc(), cma().phys_addr(&_table)})
{
/* configure managed VM area */
_map.add_range(0UL, ~0UL);
}
Vm_session_component::~Vm_session_component()
{
/* detach all regions */
while (true) {
addr_t out_addr = 0;
if (!_map.any_block_addr(&out_addr))
break;
detach_at(out_addr);
}
/* free region in allocator */
for (unsigned i = 0; i < _vcpu_id_alloc; i++) {
if (!_vcpus[i].constructed())
continue;
Vcpu & vcpu = *_vcpus[i];
if (vcpu.ds_cap.valid()) {
_region_map.detach(vcpu.ds_addr);
_constrained_md_ram_alloc.free(vcpu.ds_cap);
}
}
/* free guest-to-host page tables */
destroy(platform().core_mem_alloc(), &_table);
destroy(platform().core_mem_alloc(), &_table_array);
_vmid_alloc.free(_id.id);
}

View File

@ -0,0 +1,234 @@
/*
* \brief VMX VM session component for 'base-hw'
* \author Stefan Kalkowski
* \author Benjamin Lamowski
* \date 2024-09-20
*/
/*
* Copyright (C) 2015-2024 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__VMX_VM_SESSION_COMPONENT_H_
#define _CORE__VMX_VM_SESSION_COMPONENT_H_
/* base includes */
#include <base/allocator.h>
#include <base/session_object.h>
#include <base/registry.h>
#include <vm_session/vm_session.h>
#include <dataspace/capability.h>
/* base-hw includes */
#include <spec/x86_64/virtualization/ept.h>
/* core includes */
#include <cpu_thread_component.h>
#include <region_map_component.h>
#include <kernel/vm.h>
#include <trace/source_registry.h>
#include <vcpu.h>
#include <vmid_allocator.h>
#include <guest_memory.h>
#include <phys_allocated.h>
namespace Core { class Vmx_session_component; }
class Core::Vmx_session_component
:
public Session_object<Vm_session>
{
private:
using Vm_page_table = Hw::Ept;
using Vm_page_table_array =
Vm_page_table::Allocator::Array<Kernel::DEFAULT_TRANSLATION_TABLE_MAX>;
/*
* Noncopyable
*/
Vmx_session_component(Vmx_session_component const &);
Vmx_session_component &operator = (Vmx_session_component const &);
struct Detach : Region_map_detach
{
Vmx_session_component &_session;
Detach(Vmx_session_component &session) : _session(session)
{ }
void detach_at(addr_t at) override
{
_session._detach_at(at);
}
void reserve_and_flush(addr_t at) override
{
_session._reserve_and_flush(at);
}
void unmap_region(addr_t base, size_t size) override
{
Genode::error(__func__, " unimplemented ", base, " ", size);
}
} _detach { *this };
Registry<Registered<Vcpu>> _vcpus { };
Rpc_entrypoint &_ep;
Constrained_ram_allocator _constrained_ram_alloc;
Region_map &_region_map;
Heap _heap;
Phys_allocated<Vm_page_table> _table;
Phys_allocated<Vm_page_table_array> _table_array;
Guest_memory _memory;
Vmid_allocator &_vmid_alloc;
Kernel::Vm::Identity _id;
uint8_t _remaining_print_count { 10 };
void _detach_at(addr_t addr)
{
_memory.detach_at(addr,
[&](addr_t vm_addr, size_t size) {
_table.obj.remove_translation(vm_addr, size, _table_array.obj.alloc()); });
}
void _reserve_and_flush(addr_t addr)
{
_memory.reserve_and_flush(addr, [&](addr_t vm_addr, size_t size) {
_table.obj.remove_translation(vm_addr, size, _table_array.obj.alloc()); });
}
public:
Vmx_session_component(Vmid_allocator & vmid_alloc,
Rpc_entrypoint &ds_ep,
Resources resources,
Label const &label,
Diag diag,
Ram_allocator &ram_alloc,
Region_map &region_map,
Trace::Source_registry &)
:
Session_object(ds_ep, resources, label, diag),
_ep(ds_ep),
_constrained_ram_alloc(ram_alloc, _ram_quota_guard(), _cap_quota_guard()),
_region_map(region_map),
_heap(_constrained_ram_alloc, region_map),
_table(_ep, _constrained_ram_alloc, _region_map),
_table_array(_ep, _constrained_ram_alloc, _region_map,
[] (Phys_allocated<Vm_page_table_array> &table_array, auto *obj_ptr) {
construct_at<Vm_page_table_array>(obj_ptr, [&] (void *virt) {
return table_array.phys_addr() + ((addr_t) obj_ptr - (addr_t)virt);
});
}),
_memory(_constrained_ram_alloc, region_map),
_vmid_alloc(vmid_alloc),
_id({(unsigned)_vmid_alloc.alloc(), (void *)_table.phys_addr()})
{ }
~Vmx_session_component()
{
_vcpus.for_each([&] (Registered<Vcpu> &vcpu) {
destroy(_heap, &vcpu); });
_vmid_alloc.free(_id.id);
}
/**************************
** Vm session interface **
**************************/
void attach(Dataspace_capability cap, addr_t guest_phys, Attach_attr attr) override
{
bool out_of_tables = false;
bool invalid_mapping = false;
auto const &map_fn = [&](addr_t vm_addr, addr_t phys_addr, size_t size) {
Page_flags const pflags { RW, EXEC, USER, NO_GLOBAL, RAM, CACHED };
try {
_table.obj.insert_translation(vm_addr, phys_addr, size, pflags, _table_array.obj.alloc());
} catch(Hw::Out_of_tables &) {
if (_remaining_print_count) {
Genode::error("Translation table needs too much RAM");
_remaining_print_count--;
}
out_of_tables = true;
} catch(...) {
if (_remaining_print_count) {
Genode::error("Invalid mapping ", Genode::Hex(phys_addr), " -> ",
Genode::Hex(vm_addr), " (", size, ")");
}
invalid_mapping = true;
}
};
if (!cap.valid())
throw Invalid_dataspace();
/* check dataspace validity */
_ep.apply(cap, [&] (Dataspace_component *ptr) {
if (!ptr)
throw Invalid_dataspace();
Dataspace_component &dsc = *ptr;
Guest_memory::Attach_result result =
_memory.attach(_detach, dsc, guest_phys, attr, map_fn);
if (out_of_tables)
throw Out_of_ram();
if (invalid_mapping)
throw Invalid_dataspace();
switch (result) {
case Guest_memory::Attach_result::OK : break;
case Guest_memory::Attach_result::INVALID_DS : throw Invalid_dataspace(); break;
case Guest_memory::Attach_result::OUT_OF_RAM : throw Out_of_ram(); break;
case Guest_memory::Attach_result::OUT_OF_CAPS : throw Out_of_caps(); break;
case Guest_memory::Attach_result::REGION_CONFLICT: throw Region_conflict(); break;
}
});
}
void attach_pic(addr_t) override
{ }
void detach(addr_t guest_phys, size_t size) override
{
_memory.detach(guest_phys, size, [&](addr_t vm_addr, size_t size) {
_table.obj.remove_translation(vm_addr, size, _table_array.obj.alloc()); });
}
Capability<Native_vcpu> create_vcpu(Thread_capability tcap) override
{
Affinity::Location vcpu_location;
_ep.apply(tcap, [&] (Cpu_thread_component *ptr) {
if (!ptr) return;
vcpu_location = ptr->platform_thread().affinity();
});
Vcpu &vcpu = *new (_heap)
Registered<Vcpu>(_vcpus,
_id,
_ep,
_constrained_ram_alloc,
_region_map,
vcpu_location);
return vcpu.cap();
}
};
#endif /* _CORE__VMX_VM_SESSION_COMPONENT_H_ */

View File

@ -0,0 +1,99 @@
/*
* \brief x86_64 specific Vm root interface
* \author Stefan Kalkowski
* \author Benjamin Lamowski
* \date 2012-10-08
*/
/*
* Copyright (C) 2012-2024 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__INCLUDE__VM_ROOT_H_
#define _CORE__INCLUDE__VM_ROOT_H_
/* Genode includes */
#include <root/component.h>
/* Hw includes */
#include <hw/spec/x86_64/x86_64.h>
/* core includes */
#include <virtualization/vmx_session_component.h>
#include <virtualization/svm_session_component.h>
#include <vmid_allocator.h>
namespace Core { class Vm_root; }
class Core::Vm_root : public Root_component<Session_object<Vm_session>>
{
private:
Ram_allocator &_ram_allocator;
Region_map &_local_rm;
Trace::Source_registry &_trace_sources;
Vmid_allocator _vmid_alloc { };
protected:
Session_object<Vm_session> *_create_session(const char *args) override
{
Session::Resources resources = session_resources_from_args(args);
if (Hw::Virtualization_support::has_svm())
return new (md_alloc())
Svm_session_component(_vmid_alloc,
*ep(),
resources,
session_label_from_args(args),
session_diag_from_args(args),
_ram_allocator, _local_rm,
_trace_sources);
if (Hw::Virtualization_support::has_vmx())
return new (md_alloc())
Vmx_session_component(_vmid_alloc,
*ep(),
session_resources_from_args(args),
session_label_from_args(args),
session_diag_from_args(args),
_ram_allocator, _local_rm,
_trace_sources);
Genode::error( "No virtualization support detected.");
throw Core::Service_denied();
}
void _upgrade_session(Session_object<Vm_session> *vm, const char *args) override
{
vm->upgrade(ram_quota_from_args(args));
vm->upgrade(cap_quota_from_args(args));
}
public:
/**
* Constructor
*
* \param session_ep entrypoint managing vm_session components
* \param md_alloc meta-data allocator to be used by root component
*/
Vm_root(Rpc_entrypoint &session_ep,
Allocator &md_alloc,
Ram_allocator &ram_alloc,
Region_map &local_rm,
Trace::Source_registry &trace_sources)
:
Root_component<Session_object<Vm_session>>(&session_ep, &md_alloc),
_ram_allocator(ram_alloc),
_local_rm(local_rm),
_trace_sources(trace_sources)
{ }
};
#endif /* _CORE__INCLUDE__VM_ROOT_H_ */

View File

@ -17,7 +17,6 @@
/* Genode includes */
#include <root/component.h>
#include <base/heap.h>
/* core includes */
#include <vm_session_component.h>

View File

@ -99,7 +99,7 @@ struct Genode::Vm_connection : Connection<Vm_session>, Rpc_client<Vm_session>
long priority = Cpu_session::DEFAULT_PRIORITY,
unsigned long affinity = 0)
:
Connection<Vm_session>(env, label, Ram_quota { 16*1024 }, Affinity(),
Connection<Vm_session>(env, label, Ram_quota { 5*1024*1024 }, Affinity(),
Args("priority=", Hex(priority), ", "
"affinity=", Hex(affinity))),
Rpc_client<Vm_session>(cap())