diff --git a/repos/os/src/drivers/platform/common.h b/repos/os/src/drivers/platform/common.h
index bcbf881105..06af2e2ae6 100644
--- a/repos/os/src/drivers/platform/common.h
+++ b/repos/os/src/drivers/platform/common.h
@@ -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
+
#include
+#include
+#include
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 _dev_handler { _env.ep(), *this,
&Common::_handle_devices };
+ Device::Owner _owner_id { *this };
+
+ Io_mmu_devices _io_mmu_devices { };
+ Registry _io_mmu_factories { };
+
Driver::Root _root;
Constructible _cfg_reporter { };
@@ -44,8 +58,12 @@ class Driver::Common : Device_reporter
Heap & heap() { return _heap; }
Device_model & devices() { return _devices; }
+ Registry & 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_ */
diff --git a/repos/os/src/drivers/platform/device.h b/repos/os/src/drivers/platform/device.h
index e378c7f792..9871f491e7 100644
--- a/repos/os/src/drivers/platform/device.h
+++ b/repos/os/src/drivers/platform/device.h
@@ -317,6 +317,7 @@ class Driver::Device_model :
Env & _env;
Heap & _heap;
Device_reporter & _reporter;
+ Device_owner & _owner;
List_model _model { };
Registry _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); }
diff --git a/repos/os/src/drivers/platform/device_model_policy.cc b/repos/os/src/drivers/platform/device_model_policy.cc
index cb8745395a..8dbfd9e394 100644
--- a/repos/os/src/drivers/platform/device_model_policy.cc
+++ b/repos/os/src/drivers/platform/device_model_policy.cc
@@ -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);
}
diff --git a/repos/os/src/drivers/platform/dma_allocator.cc b/repos/os/src/drivers/platform/dma_allocator.cc
new file mode 100644
index 0000000000..13a05bf348
--- /dev/null
+++ b/repos/os/src/drivers/platform/dma_allocator.cc
@@ -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
+
+/* local includes */
+#include
+
+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(
+ [&] (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(
+ [&] (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);
+}
diff --git a/repos/os/src/drivers/platform/dma_allocator.h b/repos/os/src/drivers/platform/dma_allocator.h
new file mode 100644
index 0000000000..1a6ea21828
--- /dev/null
+++ b/repos/os/src/drivers/platform/dma_allocator.h
@@ -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
+#include
+
+namespace Driver {
+ using namespace Genode;
+
+ struct Dma_buffer;
+ class Dma_allocator;
+}
+
+
+struct Driver::Dma_buffer : Registry::Element
+{
+ Ram_dataspace_capability const cap;
+ addr_t dma_addr;
+ size_t size;
+ Dma_allocator & dma_alloc;
+
+ Dma_buffer(Registry & registry,
+ Dma_allocator & dma_alloc,
+ Ram_dataspace_capability const cap,
+ addr_t dma_addr,
+ size_t size)
+ : Registry::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 _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 & buffer_registry() { return _registry; }
+ Registry const & buffer_registry() const { return _registry; }
+
+ Dma_allocator(Allocator & md_alloc, bool const iommu);
+};
+
+#endif /* _SRC__DRIVERS__PLATFORM__DMA_ALLOCATOR_H */
diff --git a/repos/os/src/drivers/platform/io_mmu.h b/repos/os/src/drivers/platform/io_mmu.h
new file mode 100644
index 0000000000..c2f5f0fcae
--- /dev/null
+++ b/repos/os/src/drivers/platform/io_mmu.h
@@ -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
+#include
+#include
+
+/* local includes */
+#include
+#include
+
+namespace Driver
+{
+ using namespace Genode;
+
+ class Io_mmu;
+ class Io_mmu_factory;
+
+ using Io_mmu_devices = Registry;
+}
+
+
+class Driver::Io_mmu : private Io_mmu_devices::Element
+{
+ public:
+
+ using Range = Platform::Device_interface::Range;
+
+ class Domain : private Registry::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 const & buffer_registry)
+ : Registry::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 _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 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::Element
+{
+ protected:
+
+ Device::Type _type;
+
+ public:
+
+ Io_mmu_factory(Registry & registry,
+ Device::Type const & type)
+ : Registry::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_ */
diff --git a/repos/os/src/drivers/platform/target.inc b/repos/os/src/drivers/platform/target.inc
index f6ea60c1aa..61e10df342 100644
--- a/repos/os/src/drivers/platform/target.inc
+++ b/repos/os/src/drivers/platform/target.inc
@@ -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))