platform_drv: add IOMMU devices to common

With this change, platform-specific code is able to define factories that
acquire IOMMU devices to be used by the platform driver.

genodelabs/genode#4761
This commit is contained in:
Johannes Schlatow 2023-01-20 18:06:38 +01:00 committed by Christian Helmuth
parent f98466430f
commit f2e63bdd64
7 changed files with 467 additions and 4 deletions

View File

@ -1,5 +1,6 @@
/*
* \brief Platform driver - compound object for all derivate implementations
* \author Johannes Schlatow
* \author Stefan Kalkowski
* \date 2022-05-10
*/
@ -11,11 +12,19 @@
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SRC__DRIVERS__PLATFORM__COMMON_H_
#define _SRC__DRIVERS__PLATFORM__COMMON_H_
#include <base/registry.h>
#include <root.h>
#include <device_owner.h>
#include <io_mmu.h>
namespace Driver { class Common; };
class Driver::Common : Device_reporter
class Driver::Common : Device_reporter,
public Device_owner
{
private:
@ -25,9 +34,14 @@ class Driver::Common : Device_reporter
Attached_rom_dataspace _platform_info { _env, "platform_info" };
Heap _heap { _env.ram(), _env.rm() };
Sliced_heap _sliced_heap { _env.ram(), _env.rm() };
Device_model _devices { _env, _heap, *this };
Device_model _devices { _env, _heap, *this, *this };
Signal_handler<Common> _dev_handler { _env.ep(), *this,
&Common::_handle_devices };
Device::Owner _owner_id { *this };
Io_mmu_devices _io_mmu_devices { };
Registry<Io_mmu_factory> _io_mmu_factories { };
Driver::Root _root;
Constructible<Expanding_reporter> _cfg_reporter { };
@ -44,8 +58,12 @@ class Driver::Common : Device_reporter
Heap & heap() { return _heap; }
Device_model & devices() { return _devices; }
Registry<Io_mmu_factory> & io_mmu_factories() {
return _io_mmu_factories; }
void announce_service();
void handle_config(Xml_node config);
void acquire_io_mmu_devices();
/*********************
@ -53,13 +71,38 @@ class Driver::Common : Device_reporter
*********************/
void update_report() override;
/******************
** Device_owner **
******************/
void disable_device(Device const & device) override;
};
void Driver::Common::acquire_io_mmu_devices()
{
_io_mmu_factories.for_each([&] (Io_mmu_factory & factory) {
_devices.for_each([&] (Device & dev) {
if (dev.owner().valid())
return;
if (factory.matches(dev)) {
dev.acquire(*this);
factory.create(_heap, _io_mmu_devices, dev);
}
});
});
}
void Driver::Common::_handle_devices()
{
_devices_rom.update();
_devices.update(_devices_rom.xml());
acquire_io_mmu_devices();
update_report();
_root.update_policy();
}
@ -83,6 +126,15 @@ void Driver::Common::update_report()
}
void Driver::Common::disable_device(Device const & device)
{
_io_mmu_devices.for_each([&] (Io_mmu & io_mmu) {
if (io_mmu.name() == device.name())
destroy(_heap, &io_mmu);
});
}
void Driver::Common::handle_config(Xml_node config)
{
config.for_each_sub_node("report", [&] (Xml_node const node) {
@ -120,3 +172,5 @@ Driver::Common::Common(Genode::Env & env,
_devices_rom.sigh(_dev_handler);
_handle_devices();
}
#endif /* _SRC__DRIVERS__PLATFORM__COMMON_H_ */

View File

@ -317,6 +317,7 @@ class Driver::Device_model :
Env & _env;
Heap & _heap;
Device_reporter & _reporter;
Device_owner & _owner;
List_model<Device> _model { };
Registry<Shared_interrupt> _shared_irqs { };
Clocks _clocks { };
@ -331,8 +332,9 @@ class Driver::Device_model :
Device_model(Env & env,
Heap & heap,
Device_reporter & reporter)
: _env(env), _heap(heap), _reporter(reporter) { }
Device_reporter & reporter,
Device_owner & owner)
: _env(env), _heap(heap), _reporter(reporter), _owner(owner) { }
~Device_model() {
_model.destroy_all_elements(*this); }

View File

@ -63,6 +63,7 @@ void Device_model::destroy_element(Device & device)
device._reserved_mem_list.destroy_all_elements(policy);
}
device.release(_owner);
Genode::destroy(_heap, &device);
}

View File

@ -0,0 +1,131 @@
/*
* \brief Platform driver - DMA allocator
* \author Johannes Schlatow
* \date 2023-03-23
*/
/*
* 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.
*/
/* Genode includes */
#include <base/log.h>
/* local includes */
#include <dma_allocator.h>
using namespace Driver;
addr_t Dma_allocator::_alloc_dma_addr(addr_t const phys_addr,
size_t const size,
bool const force_phys_addr)
{
using Alloc_error = Allocator::Alloc_error;
if (!_iommu) return phys_addr;
/*
* 1:1 mapping (allocate at specified range from DMA memory allocator)
*/
if (force_phys_addr) {
return _dma_alloc.alloc_addr(size, phys_addr).convert<addr_t>(
[&] (void *) -> addr_t { return phys_addr; },
[&] (Alloc_error err) -> addr_t {
switch (err) {
case Alloc_error::OUT_OF_RAM: throw Out_of_ram();
case Alloc_error::OUT_OF_CAPS: throw Out_of_caps();
case Alloc_error::DENIED:
error("Could not attach DMA range at ",
Hex_range(phys_addr, size), " (error: ", err, ")");
break;
}
return 0UL;
});
}
/* natural size align (to some limit) for better IOMMU TLB usage */
unsigned size_align_log2 = unsigned(log2(size));
if (size_align_log2 < 12) /* 4 kB */
size_align_log2 = 12;
if (size_align_log2 > 24) /* 16 MB */
size_align_log2 = 24;
return _dma_alloc.alloc_aligned(size, size_align_log2).convert<addr_t>(
[&] (void *ptr) { return (addr_t)ptr; },
[&] (Alloc_error err) -> addr_t {
switch (err) {
case Alloc_error::OUT_OF_RAM: throw Out_of_ram();
case Alloc_error::OUT_OF_CAPS: throw Out_of_caps();
case Alloc_error::DENIED:
error("Could not allocate DMA area of size: ", size,
" alignment: ", size_align_log2,
" total avail: ", _dma_alloc.avail(),
" (error: ", err, ")");
break;
};
return 0;
});
}
bool Dma_allocator::reserve(addr_t phys_addr, size_t size)
{
return _alloc_dma_addr(phys_addr, size, true) == phys_addr;
}
void Dma_allocator::unreserve(addr_t phys_addr, size_t) { _free_dma_addr(phys_addr); }
Dma_buffer & Dma_allocator::alloc_buffer(Ram_dataspace_capability cap,
addr_t phys_addr,
size_t size)
{
addr_t dma_addr = _alloc_dma_addr(phys_addr, size, false);
try {
return * new (_md_alloc) Dma_buffer(_registry, *this, cap, dma_addr, size);
} catch (Out_of_ram) {
_free_dma_addr(dma_addr);
throw;
} catch (Out_of_caps) {
_free_dma_addr(dma_addr);
throw;
}
}
void Dma_allocator::_free_dma_addr(addr_t dma_addr)
{
if (_iommu)
_dma_alloc.free((void *)dma_addr);
}
Dma_allocator::Dma_allocator(Allocator & md_alloc,
bool const iommu)
:
_md_alloc(md_alloc), _iommu(iommu)
{
/* 0x1000 - 4GB */
enum { DMA_SIZE = 0xffffe000 };
_dma_alloc.add_range(0x1000, DMA_SIZE);
/*
* Interrupt address range is special handled and in general not
* usable for normal DMA translations, see chapter 3.15
* of "Intel Virtualization Technology for Directed I/O"
* (March 2023, Revision 4.1)
*/
enum { IRQ_RANGE_BASE = 0xfee00000u, IRQ_RANGE_SIZE = 0x100000 };
_dma_alloc.remove_range(IRQ_RANGE_BASE, IRQ_RANGE_SIZE);
}
Dma_buffer::~Dma_buffer()
{
dma_alloc._free_dma_addr(dma_addr);
}

View File

@ -0,0 +1,76 @@
/*
* \brief Platform driver - DMA allocator
* \author Johannes Schlatow
* \date 2023-03-23
*/
/*
* 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__DMA_ALLOCATOR_H_
#define _SRC__DRIVERS__PLATFORM__DMA_ALLOCATOR_H_
/* Genode includes */
#include <base/allocator_avl.h>
#include <base/registry.h>
namespace Driver {
using namespace Genode;
struct Dma_buffer;
class Dma_allocator;
}
struct Driver::Dma_buffer : Registry<Dma_buffer>::Element
{
Ram_dataspace_capability const cap;
addr_t dma_addr;
size_t size;
Dma_allocator & dma_alloc;
Dma_buffer(Registry<Dma_buffer> & registry,
Dma_allocator & dma_alloc,
Ram_dataspace_capability const cap,
addr_t dma_addr,
size_t size)
: Registry<Dma_buffer>::Element(registry, *this),
cap(cap), dma_addr(dma_addr), size(size), dma_alloc(dma_alloc)
{ }
~Dma_buffer();
};
class Driver::Dma_allocator
{
private:
friend class Dma_buffer;
Allocator & _md_alloc;
bool const _iommu;
Allocator_avl _dma_alloc { &_md_alloc };
Registry<Dma_buffer> _registry { };
addr_t _alloc_dma_addr(addr_t phys_addr, size_t size, bool const force_phys_addr);
void _free_dma_addr(addr_t dma_addr);
public:
bool reserve(addr_t phys_addr, size_t size);
void unreserve(addr_t phys_addr, size_t size);
Dma_buffer & alloc_buffer(Ram_dataspace_capability cap, addr_t phys_addr, size_t size);
Registry<Dma_buffer> & buffer_registry() { return _registry; }
Registry<Dma_buffer> const & buffer_registry() const { return _registry; }
Dma_allocator(Allocator & md_alloc, bool const iommu);
};
#endif /* _SRC__DRIVERS__PLATFORM__DMA_ALLOCATOR_H */

View File

@ -0,0 +1,198 @@
/*
* \brief Platform driver - IO MMU interface
* \author Johannes Schlatow
* \date 2023-01-20
*/
/*
* 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_H_
#define _SRC__DRIVERS__PLATFORM__IO_MMU_H_
/* Genode includes */
#include <base/registry.h>
#include <base/quota_guard.h>
#include <pci/types.h>
/* local includes */
#include <device.h>
#include <dma_allocator.h>
namespace Driver
{
using namespace Genode;
class Io_mmu;
class Io_mmu_factory;
using Io_mmu_devices = Registry<Io_mmu>;
}
class Driver::Io_mmu : private Io_mmu_devices::Element
{
public:
using Range = Platform::Device_interface::Range;
class Domain : private Registry<Domain>::Element
{
private:
friend class Io_mmu;
Io_mmu & _io_mmu;
Allocator & _md_alloc;
unsigned _active_devices { 0 };
public:
Allocator & md_alloc() { return _md_alloc; }
Device::Name const & device_name() const { return _io_mmu.name(); }
void enable_device()
{
_active_devices++;
if (_active_devices == 1)
_io_mmu._enable_domain();
}
void disable_device()
{
if (_active_devices > 0) {
_active_devices--;
if (_active_devices == 0)
_io_mmu._disable_domain();
}
}
unsigned devices() const { return _active_devices; }
/* interface for (un)assigning a pci device */
virtual void enable_pci_device(Io_mem_dataspace_capability const,
Pci::Bdf const) = 0;
virtual void disable_pci_device(Io_mem_dataspace_capability const,
Pci::Bdf const) = 0;
/* interface for adding/removing DMA buffers */
virtual void add_range(Range const &, Dataspace_capability const) = 0;
virtual void remove_range(Range const &) = 0;
Domain(Io_mmu & io_mmu,
Allocator & md_alloc,
Registry<Dma_buffer> const & buffer_registry)
: Registry<Domain>::Element(io_mmu._domains, *this),
_io_mmu(io_mmu), _md_alloc(md_alloc)
{
/* we always need to add existing buffers when creating a new domain */
buffer_registry.for_each([&] (Dma_buffer const & buf) {
add_range({ buf.dma_addr, buf.size }, buf.cap); });
}
virtual ~Domain() { }
};
protected:
friend class Domain;
Device::Name _name;
Registry<Domain> _domains { };
unsigned _active_domains { 0 };
virtual void _enable() { };
virtual void _disable() { };
void _enable_domain()
{
if (!_active_domains)
_enable();
_active_domains++;
};
void _disable_domain()
{
if (_active_domains > 0)
_active_domains--;
if (!_active_domains)
_disable();
};
void _destroy_domains()
{
_domains.for_each([&] (Domain & domain) {
destroy(domain.md_alloc(), &domain); });
}
public:
Device::Name const & name() const { return _name; }
bool domain_owner(Domain const & domain) const {
return &domain._io_mmu == this; }
/* Return true if device requires physical addressing */
virtual bool mpu() { return false; };
/* Create a Io_mmu::Domain object */
virtual Domain & create_domain(Allocator &,
Registry<Dma_buffer> const &,
Ram_quota_guard &,
Cap_quota_guard &) = 0;
Io_mmu(Io_mmu_devices & io_mmu_devices,
Device::Name const & name)
: Io_mmu_devices::Element(io_mmu_devices, *this),
_name(name)
{ }
virtual ~Io_mmu()
{
/**
* destroying domain objects
* any derived class that overrides any virtual method must
* call this at the very beginning of its destruction
*/
_destroy_domains();
}
};
class Driver::Io_mmu_factory : private Genode::Registry<Io_mmu_factory>::Element
{
protected:
Device::Type _type;
public:
Io_mmu_factory(Registry<Io_mmu_factory> & registry,
Device::Type const & type)
: Registry<Io_mmu_factory>::Element(registry, *this),
_type(type)
{ }
virtual ~Io_mmu_factory() { }
bool matches(Device const & dev) {
return dev.type() == _type; }
virtual void create(Allocator &,
Io_mmu_devices &,
Device const &) = 0;
};
#endif /* _SRC__DRIVERS__PLATFORM__IO_MMU_H_ */

View File

@ -7,6 +7,7 @@ SRC_CC += pci.cc
SRC_CC += root.cc
SRC_CC += session_component.cc
SRC_CC += shared_irq.cc
SRC_CC += dma_allocator.cc
GENERIC_DIR := $(dir $(call select_from_repositories,src/drivers/platform/target.inc))