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))