mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-20 17:52:52 +00:00
platform_drv: use IOMMU devices
Every session component manages a registry of Io_mmu::Domain objects that it creates on demand depending on the acquired devices (i.e. the IOMMU devices referenced by the acquired devices). Via the domain objects, a session component adds/removes the address ranges of the allocated DMA buffers. Additionally, domain objects provide an interface for enabling/disabling pci devices. Domain objects get destroyed with the corresponding control device. Moreover, on devices/policy ROM updates, domain objects of control devices that are not referenced by any acquired device anymore get destroyed. genodelabs/genode#4761
This commit is contained in:
parent
d3357b4c53
commit
9b5944b90c
@ -31,8 +31,20 @@ void Driver::Device_component::_release_resources()
|
||||
_io_port_range_registry.for_each([&] (Io_port_range & iop) {
|
||||
destroy(_session.heap(), &iop); });
|
||||
|
||||
/* remove reserved memory ranges from IOMMU domains */
|
||||
_session.domain_registry().for_each_domain(
|
||||
[&] (Driver::Io_mmu::Domain & domain) {
|
||||
_reserved_mem_registry.for_each([&] (Io_mem & iomem) {
|
||||
domain.remove_range(iomem.range);
|
||||
});
|
||||
});
|
||||
|
||||
_reserved_mem_registry.for_each([&] (Io_mem & iomem) {
|
||||
destroy(_session.heap(), &iomem); });
|
||||
/* unreserve at dma allocator */
|
||||
_session.dma_allocator().unreserve(iomem.range.start, iomem.range.size);
|
||||
|
||||
destroy(_session.heap(), &iomem);
|
||||
});
|
||||
|
||||
if (_pci_config.constructed()) _pci_config.destruct();
|
||||
|
||||
@ -219,9 +231,30 @@ Device_component::Device_component(Registry<Device_component> & registry,
|
||||
Io_mem(_reserved_mem_registry, {0}, idx, range, false));
|
||||
iomem.io_mem.construct(_env, iomem.range.start,
|
||||
iomem.range.size, false);
|
||||
session.device_pd().attach_dma_mem(iomem.io_mem->dataspace(),
|
||||
iomem.range.start, true);
|
||||
|
||||
/* reserve memory at dma allocator */
|
||||
session.dma_allocator().reserve(iomem.range.start, iomem.range.size);
|
||||
});
|
||||
|
||||
auto add_range_fn = [&] (Driver::Io_mmu::Domain & domain) {
|
||||
_reserved_mem_registry.for_each([&] (Io_mem & iomem) {
|
||||
domain.add_range(iomem.range, iomem.io_mem->dataspace());
|
||||
});
|
||||
};
|
||||
|
||||
/* attach reserved memory ranges to IOMMU domains */
|
||||
device.for_each_io_mmu(
|
||||
/* non-empty list fn */
|
||||
[&] (Driver::Device::Io_mmu const &io_mmu) {
|
||||
session.domain_registry().with_domain(io_mmu.name,
|
||||
add_range_fn,
|
||||
[&] () { }); },
|
||||
|
||||
/* empty list fn */
|
||||
[&] () {
|
||||
session.domain_registry().with_default_domain(add_range_fn); }
|
||||
);
|
||||
|
||||
} catch(...) {
|
||||
_release_resources();
|
||||
throw;
|
||||
|
121
repos/os/src/drivers/platform/io_mmu_domain_registry.h
Normal file
121
repos/os/src/drivers/platform/io_mmu_domain_registry.h
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* \brief Platform driver - IO MMU domain wrapper and registry
|
||||
* \author Johannes Schlatow
|
||||
* \date 2023-03-27
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 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 _SRC__DRIVERS__PLATFORM__IO_MMU_DOMAIN_REGISTRY_H_
|
||||
#define _SRC__DRIVERS__PLATFORM__IO_MMU_DOMAIN_REGISTRY_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/allocator.h>
|
||||
#include <base/registry.h>
|
||||
#include <base/quota_guard.h>
|
||||
|
||||
/* local includes */
|
||||
#include <io_mmu.h>
|
||||
|
||||
namespace Driver
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
class Io_mmu_domain_wrapper;
|
||||
class Io_mmu_domain;
|
||||
class Io_mmu_domain_registry;
|
||||
}
|
||||
|
||||
|
||||
struct Driver::Io_mmu_domain_wrapper
|
||||
{
|
||||
Io_mmu::Domain & domain;
|
||||
|
||||
Io_mmu_domain_wrapper(Io_mmu & io_mmu,
|
||||
Allocator & md_alloc,
|
||||
Registry<Dma_buffer> const & dma_buffers,
|
||||
Ram_quota_guard & ram_guard,
|
||||
Cap_quota_guard & cap_guard)
|
||||
: domain(io_mmu.create_domain(md_alloc, dma_buffers, ram_guard, cap_guard))
|
||||
{ }
|
||||
|
||||
~Io_mmu_domain_wrapper() { destroy(domain.md_alloc(), &domain); }
|
||||
};
|
||||
|
||||
|
||||
struct Driver::Io_mmu_domain : private Registry<Io_mmu_domain>::Element,
|
||||
public Io_mmu_domain_wrapper
|
||||
{
|
||||
Io_mmu_domain(Registry<Io_mmu_domain> & registry,
|
||||
Io_mmu & io_mmu,
|
||||
Allocator & md_alloc,
|
||||
Registry<Dma_buffer> const & dma_buffers,
|
||||
Ram_quota_guard & ram_guard,
|
||||
Cap_quota_guard & cap_guard)
|
||||
: Registry<Io_mmu_domain>::Element(registry, *this),
|
||||
Io_mmu_domain_wrapper(io_mmu, md_alloc, dma_buffers, ram_guard, cap_guard)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
class Driver::Io_mmu_domain_registry : public Registry<Io_mmu_domain>
|
||||
{
|
||||
protected:
|
||||
|
||||
Constructible<Io_mmu_domain_wrapper> _default_domain { };
|
||||
|
||||
public:
|
||||
|
||||
void default_domain(Io_mmu & io_mmu,
|
||||
Allocator & md_alloc,
|
||||
Registry<Dma_buffer> const & dma_buffers,
|
||||
Ram_quota_guard & ram_quota_guard,
|
||||
Cap_quota_guard & cap_quota_guard)
|
||||
{
|
||||
_default_domain.construct(io_mmu, md_alloc, dma_buffers,
|
||||
ram_quota_guard, cap_quota_guard);
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void for_each_domain(FN && fn)
|
||||
{
|
||||
for_each([&] (Io_mmu_domain & wrapper) {
|
||||
fn(wrapper.domain); });
|
||||
|
||||
if (_default_domain.constructed())
|
||||
fn(_default_domain->domain);
|
||||
}
|
||||
|
||||
template <typename MATCH_FN, typename NONMATCH_FN>
|
||||
void with_domain(Device::Name const & name,
|
||||
MATCH_FN && match_fn,
|
||||
NONMATCH_FN && nonmatch_fn)
|
||||
{
|
||||
bool exists = false;
|
||||
|
||||
for_each_domain([&] (Io_mmu::Domain & domain) {
|
||||
if (domain.device_name() == name) {
|
||||
match_fn(domain);
|
||||
exists = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!exists)
|
||||
nonmatch_fn();
|
||||
}
|
||||
|
||||
template <typename FN>
|
||||
void with_default_domain(FN && fn)
|
||||
{
|
||||
if (_default_domain.constructed())
|
||||
fn(_default_domain->domain);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif /* _SRC__DRIVERS__PLATFORM__IO_MMU_DOMAIN_REGISTRY_H_ */
|
@ -23,8 +23,40 @@ using Driver::Session_component;
|
||||
Genode::Capability<Platform::Device_interface>
|
||||
Session_component::_acquire(Device & device)
|
||||
{
|
||||
/**
|
||||
* add IOMMU domains if they don't exist yet
|
||||
*
|
||||
* note: must be done before device.acquire and Device_component construction
|
||||
* because both may access the domain registry
|
||||
*/
|
||||
device.for_each_io_mmu([&] (Device::Io_mmu const & io_mmu) {
|
||||
_domain_registry.with_domain(io_mmu.name,
|
||||
[&] (Io_mmu::Domain &) { },
|
||||
[&] () {
|
||||
_io_mmu_devices.for_each([&] (Io_mmu & io_mmu_dev) {
|
||||
if (io_mmu_dev.name() == io_mmu.name) {
|
||||
if (io_mmu_dev.mpu() && _iommu)
|
||||
error("Unable to create domain for MPU device ",
|
||||
io_mmu_dev.name(), " for an IOMMU-enabled session.");
|
||||
else
|
||||
new (heap()) Io_mmu_domain(_domain_registry,
|
||||
io_mmu_dev,
|
||||
heap(),
|
||||
_dma_allocator.buffer_registry(),
|
||||
_ram_quota_guard(),
|
||||
_cap_quota_guard());
|
||||
}
|
||||
});
|
||||
}
|
||||
);},
|
||||
|
||||
/* empty list fn */
|
||||
[&] () { }
|
||||
);
|
||||
|
||||
Device_component * dc = new (heap())
|
||||
Device_component(_device_registry, _env, *this, _devices, device);
|
||||
|
||||
device.acquire(*this);
|
||||
return _env.ep().rpc_ep().manage(dc);
|
||||
};
|
||||
@ -38,13 +70,23 @@ void Session_component::_release_device(Device_component & dc)
|
||||
|
||||
_devices.for_each([&] (Device & dev) {
|
||||
if (name == dev.name()) dev.release(*this); });
|
||||
|
||||
/* destroy unused domains */
|
||||
_domain_registry.for_each([&] (Io_mmu_domain & wrapper) {
|
||||
if (wrapper.domain.devices() == 0)
|
||||
destroy(heap(), &wrapper);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void Session_component::_free_dma_buffer(Dma_buffer & buf)
|
||||
{
|
||||
Ram_dataspace_capability cap = buf.cap;
|
||||
_device_pd.free_dma_mem(buf.dma_addr);
|
||||
|
||||
_domain_registry.for_each_domain([&] (Io_mmu::Domain & domain) {
|
||||
domain.remove_range({ buf.dma_addr, buf.size });
|
||||
});
|
||||
|
||||
destroy(heap(), &buf);
|
||||
_env_ram.free(cap);
|
||||
}
|
||||
@ -72,11 +114,64 @@ bool Session_component::matches(Device const & dev) const
|
||||
};
|
||||
|
||||
|
||||
void Session_component::update_io_mmu_devices()
|
||||
{
|
||||
_io_mmu_devices.for_each([&] (Io_mmu & io_mmu_dev) {
|
||||
|
||||
/* determine whether IOMMU is used by any owned/acquire device */
|
||||
bool used_by_owned_device = false;
|
||||
_devices.for_each([&] (Device const & dev) {
|
||||
if (!(dev.owner() == _owner_id))
|
||||
return;
|
||||
|
||||
if (used_by_owned_device)
|
||||
return;
|
||||
|
||||
dev.for_each_io_mmu(
|
||||
[&] (Device::Io_mmu const & io_mmu) {
|
||||
if (io_mmu.name == io_mmu_dev.name())
|
||||
used_by_owned_device = true;
|
||||
},
|
||||
[&] () { });
|
||||
});
|
||||
|
||||
/* synchronise with IOMMU domains */
|
||||
bool domain_exists = false;
|
||||
_domain_registry.for_each([&] (Io_mmu_domain & wrapper) {
|
||||
if (io_mmu_dev.domain_owner(wrapper.domain)) {
|
||||
domain_exists = true;
|
||||
|
||||
/* remove domain if not used by any owned device */
|
||||
if (!used_by_owned_device)
|
||||
destroy(heap(), &wrapper);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* If an IOMMU is used but there is no domain (because the IOMMU
|
||||
* device was just added), we need to create (i.e. allocate) a domain for
|
||||
* it. However, since we are in context of a ROM update at this point,
|
||||
* we are not able to propagate any Out_of_ram exception to the client.
|
||||
* Since this is supposedly a very rare and not even practical corner-case,
|
||||
* we print a warning instead.
|
||||
*/
|
||||
if (used_by_owned_device && !domain_exists) {
|
||||
warning("Unable to configure DMA ranges properly because ",
|
||||
"IO MMU'", io_mmu_dev.name(),
|
||||
"' was added to an already acquired device.");
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void Session_component::update_policy(bool info, Policy_version version)
|
||||
{
|
||||
_info = info;
|
||||
_version = version;
|
||||
|
||||
update_io_mmu_devices();
|
||||
|
||||
enum Device_state { AWAY, CHANGED, UNCHANGED };
|
||||
|
||||
_device_registry.for_each([&] (Device_component & dc) {
|
||||
@ -116,7 +211,16 @@ void Session_component::produce_xml(Xml_generator &xml)
|
||||
Genode::Heap & Session_component::heap() { return _md_alloc; }
|
||||
|
||||
|
||||
Driver::Device_pd & Session_component::device_pd() { return _device_pd; }
|
||||
Driver::Io_mmu_domain_registry & Session_component::domain_registry()
|
||||
{
|
||||
return _domain_registry;
|
||||
}
|
||||
|
||||
|
||||
Driver::Dma_allocator & Session_component::dma_allocator()
|
||||
{
|
||||
return _dma_allocator;
|
||||
}
|
||||
|
||||
|
||||
void Session_component::update_devices_rom()
|
||||
@ -128,12 +232,40 @@ void Session_component::update_devices_rom()
|
||||
void Session_component::enable_device(Device const & device)
|
||||
{
|
||||
pci_enable(_env, device_pd(), device);
|
||||
|
||||
auto fn = [&] (Driver::Io_mmu::Domain & domain) {
|
||||
domain.enable_device();
|
||||
};
|
||||
|
||||
device.for_each_io_mmu(
|
||||
/* non-empty list fn */
|
||||
[&] (Device::Io_mmu const & io_mmu) {
|
||||
_domain_registry.with_domain(io_mmu.name, fn, [&] () { }); },
|
||||
|
||||
/* empty list fn */
|
||||
[&] () {
|
||||
_domain_registry.with_default_domain(fn); }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void Session_component::disable_device(Device const & device)
|
||||
{
|
||||
pci_disable(_env, device);
|
||||
|
||||
auto fn = [&] (Driver::Io_mmu::Domain & domain) {
|
||||
domain.disable_device();
|
||||
};
|
||||
|
||||
device.for_each_io_mmu(
|
||||
/* non-empty list fn */
|
||||
[&] (Device::Io_mmu const & io_mmu) {
|
||||
_domain_registry.with_domain(io_mmu.name, fn, [&] () { }); },
|
||||
|
||||
/* empty list fn */
|
||||
[&] () {
|
||||
_domain_registry.with_default_domain(fn); }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -208,25 +340,18 @@ Session_component::alloc_dma_buffer(size_t const size, Cache cache)
|
||||
|
||||
if (!ram_cap.valid()) return ram_cap;
|
||||
|
||||
Dma_buffer *buf { nullptr };
|
||||
try {
|
||||
buf = new (heap()) Dma_buffer(_buffer_registry, ram_cap);
|
||||
} catch (Out_of_ram) {
|
||||
_env_ram.free(ram_cap);
|
||||
throw;
|
||||
} catch (Out_of_caps) {
|
||||
_env_ram.free(ram_cap);
|
||||
throw;
|
||||
}
|
||||
Dma_buffer & buf = _dma_allocator.alloc_buffer(ram_cap,
|
||||
_env.pd().dma_addr(ram_cap),
|
||||
size);
|
||||
|
||||
try {
|
||||
buf->dma_addr = _device_pd.attach_dma_mem(ram_cap, _env.pd().dma_addr(buf->cap), false);
|
||||
_domain_registry.for_each_domain([&] (Io_mmu::Domain & domain) {
|
||||
domain.add_range({ buf.dma_addr, buf.size }, buf.cap);
|
||||
});
|
||||
} catch (Out_of_ram) {
|
||||
destroy(heap(), buf);
|
||||
_env_ram.free(ram_cap);
|
||||
throw;
|
||||
} catch (Out_of_caps) {
|
||||
destroy(heap(), buf);
|
||||
_env_ram.free(ram_cap);
|
||||
throw;
|
||||
}
|
||||
@ -239,7 +364,7 @@ void Session_component::free_dma_buffer(Ram_dataspace_capability ram_cap)
|
||||
{
|
||||
if (!ram_cap.valid()) { return; }
|
||||
|
||||
_buffer_registry.for_each([&] (Dma_buffer & buf) {
|
||||
_dma_allocator.buffer_registry().for_each([&] (Dma_buffer & buf) {
|
||||
if (buf.cap.local_name() == ram_cap.local_name())
|
||||
_free_dma_buffer(buf); });
|
||||
}
|
||||
@ -252,7 +377,7 @@ Genode::addr_t Session_component::dma_addr(Ram_dataspace_capability ram_cap)
|
||||
if (!ram_cap.valid())
|
||||
return ret;
|
||||
|
||||
_buffer_registry.for_each([&] (Dma_buffer const & buf) {
|
||||
_dma_allocator.buffer_registry().for_each([&] (Dma_buffer const & buf) {
|
||||
if (buf.cap.local_name() == ram_cap.local_name())
|
||||
ret = buf.dma_addr; });
|
||||
|
||||
@ -290,16 +415,35 @@ Session_component::Session_component(Env & env,
|
||||
*/
|
||||
_cap_quota_guard().withdraw(Cap_quota{Rom_session::CAP_QUOTA});
|
||||
_ram_quota_guard().withdraw(Ram_quota{5*1024});
|
||||
|
||||
/**
|
||||
* Until we integrated IOMMU support within the platform driver, we assume
|
||||
* there is a kernel_iommu used by each device if _iommu is set. We therefore
|
||||
* construct a corresponding domain object at session construction.
|
||||
*/
|
||||
if (_iommu)
|
||||
_io_mmu_devices.for_each([&] (Io_mmu & io_mmu_dev) {
|
||||
if (io_mmu_dev.name() == "kernel_iommu") {
|
||||
_domain_registry.default_domain(io_mmu_dev,
|
||||
heap(),
|
||||
_dma_allocator.buffer_registry(),
|
||||
_ram_quota_guard(),
|
||||
_cap_quota_guard());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Session_component::~Session_component()
|
||||
{
|
||||
_domain_registry.for_each([&] (Io_mmu_domain & wrapper) {
|
||||
destroy(heap(), &wrapper); });
|
||||
|
||||
_device_registry.for_each([&] (Device_component & dc) {
|
||||
_release_device(dc); });
|
||||
|
||||
/* free up dma buffers */
|
||||
_buffer_registry.for_each([&] (Dma_buffer & buf) {
|
||||
_dma_allocator.buffer_registry().for_each([&] (Dma_buffer & buf) {
|
||||
_free_dma_buffer(buf); });
|
||||
|
||||
/* replenish quota for rom sessions, see constructor for explanation */
|
||||
|
@ -25,9 +25,9 @@
|
||||
#include <platform_session/platform_session.h>
|
||||
|
||||
#include <device_component.h>
|
||||
#include <device_pd.h>
|
||||
#include <device_owner.h>
|
||||
#include <io_mmu.h>
|
||||
#include <io_mmu_domain_registry.h>
|
||||
|
||||
namespace Driver {
|
||||
class Session_component;
|
||||
@ -61,14 +61,16 @@ class Driver::Session_component
|
||||
|
||||
~Session_component();
|
||||
|
||||
Heap & heap();
|
||||
Device_pd & device_pd();
|
||||
Heap & heap();
|
||||
Io_mmu_domain_registry & domain_registry();
|
||||
Dma_allocator & dma_allocator();
|
||||
|
||||
bool matches(Device const &) const;
|
||||
|
||||
Ram_quota_guard & ram_quota_guard() { return _ram_quota_guard(); }
|
||||
Cap_quota_guard & cap_quota_guard() { return _cap_quota_guard(); }
|
||||
|
||||
void update_io_mmu_devices();
|
||||
void update_policy(bool info, Policy_version version);
|
||||
|
||||
/**************************
|
||||
@ -98,16 +100,6 @@ class Driver::Session_component
|
||||
|
||||
friend class Root;
|
||||
|
||||
struct Dma_buffer : Registry<Dma_buffer>::Element
|
||||
{
|
||||
Ram_dataspace_capability const cap;
|
||||
addr_t dma_addr { 0 };
|
||||
|
||||
Dma_buffer(Registry<Dma_buffer> & registry,
|
||||
Ram_dataspace_capability const cap)
|
||||
: Registry<Dma_buffer>::Element(registry, *this), cap(cap) {}
|
||||
};
|
||||
|
||||
Env & _env;
|
||||
Attached_rom_dataspace const & _config;
|
||||
Device_model & _devices;
|
||||
@ -119,7 +111,7 @@ class Driver::Session_component
|
||||
_cap_quota_guard() };
|
||||
Heap _md_alloc { _env_ram, _env.rm() };
|
||||
Registry<Device_component> _device_registry { };
|
||||
Registry<Dma_buffer> _buffer_registry { };
|
||||
Io_mmu_domain_registry _domain_registry { };
|
||||
Dynamic_rom_session _rom_session { _env.ep(), _env.ram(),
|
||||
_env.rm(), *this };
|
||||
bool _info;
|
||||
@ -130,18 +122,18 @@ class Driver::Session_component
|
||||
_ram_quota_guard(),
|
||||
_cap_quota_guard(),
|
||||
_iommu };
|
||||
Dma_allocator _dma_allocator { _md_alloc, _iommu };
|
||||
|
||||
Device_capability _acquire(Device & device);
|
||||
void _release_device(Device_component & dc);
|
||||
void _free_dma_buffer(Dma_buffer & buf);
|
||||
|
||||
|
||||
/*
|
||||
* Noncopyable
|
||||
*/
|
||||
Session_component(Session_component const &);
|
||||
Session_component &operator = (Session_component const &);
|
||||
|
||||
|
||||
/*******************************************
|
||||
** Dynamic_rom_session::Xml_producer API **
|
||||
*******************************************/
|
||||
|
Loading…
x
Reference in New Issue
Block a user