From ecc9007e84715a65b2ceca16c5bb258a24e195db Mon Sep 17 00:00:00 2001 From: Alexander Boettcher Date: Mon, 27 Apr 2015 11:59:58 +0200 Subject: [PATCH] pci: add msi support to platform driver Fixes #1216 --- repos/os/src/drivers/acpi/main.cc | 9 +- repos/os/src/drivers/pci/irq.cc | 150 ++++++++--- repos/os/src/drivers/pci/irq.h | 21 +- .../os/src/drivers/pci/pci_device_component.h | 245 +++++++++++++----- 4 files changed, 324 insertions(+), 101 deletions(-) diff --git a/repos/os/src/drivers/acpi/main.cc b/repos/os/src/drivers/acpi/main.cc index 8c64a458b7..29a50b94e9 100644 --- a/repos/os/src/drivers/acpi/main.cc +++ b/repos/os/src/drivers/acpi/main.cc @@ -82,14 +82,19 @@ namespace Irq { if (!args.is_valid_string()) throw Root::Invalid_args(); long irq_number = Arg_string::find_arg(args.string(), "irq_number").long_value(-1); + long msi = Arg_string::find_arg(args.string(), "device_config_phys").long_value(0); /* check for 'MADT' overrides */ unsigned mode; - irq_number = Acpi::override(irq_number, &mode); + long irq_legacy = Acpi::override(irq_number, &mode); + /* rewrite IRQ solely if this is not a MSI request */ + if (!msi) + irq_number = irq_legacy; /* allocate IRQ at parent*/ try { - Irq_connection irq(irq_number, _mode2trigger(mode), _mode2polarity(mode)); + Irq_connection irq(irq_number, _mode2trigger(mode), + _mode2polarity(mode), msi); irq.on_destruction(Irq_connection::KEEP_OPEN); return irq.cap(); } catch (...) { throw Root::Unavailable(); } diff --git a/repos/os/src/drivers/pci/irq.cc b/repos/os/src/drivers/pci/irq.cc index 3c4a4a5c03..23df9b4878 100644 --- a/repos/os/src/drivers/pci/irq.cc +++ b/repos/os/src/drivers/pci/irq.cc @@ -29,37 +29,65 @@ namespace Pci { } -using namespace Genode; - +using Genode::size_t; +using Genode::addr_t; /** * A simple range allocator implementation used by the Irq_proxy */ -class Pci::Irq_allocator : public Range_allocator, Bit_allocator<256> +class Pci::Irq_allocator : public Genode::Range_allocator { - Alloc_return alloc_addr(size_t size, addr_t addr) override - { - try { - _array.set(addr, size); - return Alloc_return::OK; - } catch (...) { - return Alloc_return::RANGE_CONFLICT; + private: + + /* + * We partition the IRQ space (128 GSIs) into 40 legacy IRQs and 64 MSIs (and + * hope the partitions will never overlap on any bizarre platform.) + */ + enum { LEGACY = 40, MSI = 64, LEGACY_ARRAY = 64 }; + + Genode::Bit_array _legacy; + Genode::Bit_allocator _msi; + + public: + + Irq_allocator() + { + /* reserve the last 24 legacy IRQs - 40 IRQs remaining */ + _legacy.set(LEGACY, LEGACY_ARRAY - LEGACY); } - } - /* unused methods */ - int remove_range(addr_t, size_t) override { return 0; } - int add_range(addr_t, size_t) override { return 0; } - bool valid_addr(addr_t) const override { return false; } - size_t avail() const override { return 0; } - bool alloc(size_t, void **) override { return false; } - void free(void *) override { } - void free(void *, size_t) override { } - size_t overhead(size_t) const override { return 0; } - bool need_size_for_free() const override { return 0; } + unsigned alloc_msi() + { + try { + return _msi.alloc(); + } catch (Genode::Bit_allocator::Out_of_indices) { return ~0U; } + } - Alloc_return alloc_aligned(size_t, void **, int, addr_t, addr_t) override { - return Alloc_return::RANGE_CONFLICT; } + void free_msi(unsigned msi) { _msi.free(msi); } + + Alloc_return alloc_addr(size_t size, addr_t addr) override + { + try { + _legacy.set(addr, size); + return Alloc_return::OK; + } catch (...) { + return Alloc_return::RANGE_CONFLICT; + } + } + + /* unused methods */ + int remove_range(addr_t, size_t) override { return 0; } + int add_range(addr_t, size_t) override { return 0; } + bool valid_addr(addr_t) const override { return false; } + size_t avail() const override { return 0; } + bool alloc(size_t, void **) override { return false; } + void free(void *) override { } + void free(void *, size_t) override { } + size_t overhead(size_t) const override { return 0; } + bool need_size_for_free() const override { return 0; } + + Alloc_return alloc_aligned(size_t, void **, int, addr_t, addr_t) override { + return Alloc_return::RANGE_CONFLICT; } }; @@ -84,13 +112,13 @@ class Pci::Irq_thread : public Genode::Thread<4096> { private: - Signal_receiver _sig_rec; + Genode::Signal_receiver _sig_rec; public: Irq_thread() : Thread<4096>("irq_sig_recv") { start(); } - Signal_receiver & sig_rec() { return _sig_rec; } + Genode::Signal_receiver & sig_rec() { return _sig_rec; } void entry() { @@ -154,7 +182,7 @@ class Pci::Irq_component : public Proxy bool _associate() { return _associated; } void _wait_for_irq() { } - virtual bool remove_sharer(Irq_sigh *s) override { + virtual bool remove_sharer(Genode::Irq_sigh *s) override { if (!Proxy::remove_sharer(s)) return false; @@ -182,6 +210,13 @@ class Pci::Irq_component : public Proxy void Pci::Irq_session_component::ack_irq() { + if (msi()) { + Genode::Irq_session_client irq_msi(_irq_cap); + irq_msi.ack_irq(); + return; + } + + /* shared irq handling */ Irq_component *irq_obj = Proxy::get_irq_proxy(_gsi); if (!irq_obj) { PERR("Expected to find IRQ proxy for IRQ %02x", _gsi); @@ -193,26 +228,63 @@ void Pci::Irq_session_component::ack_irq() } -Pci::Irq_session_component::Irq_session_component(unsigned irq) : _gsi(irq) +Pci::Irq_session_component::Irq_session_component(unsigned irq, + addr_t pci_config_space) +: + _gsi(irq) { - bool valid = false; - - /* invalid irq number for pci devices */ - if (irq == 0xFF) + /* invalid irq number for pci_devices */ + if (irq >= INVALID_IRQ) return; + if (pci_config_space != ~0UL) { + /* msi way */ + unsigned msi = irq_alloc.alloc_msi(); + if (msi != ~0U) { + try { + using namespace Genode; + + Irq_connection conn(msi, Irq_session::TRIGGER_UNCHANGED, + Irq_session::POLARITY_UNCHANGED, + pci_config_space); + + _msi_info = conn.info(); + if (_msi_info.type == Irq_session::Info::Type::MSI) { + _gsi = msi; + _irq_cap = conn; + + conn.on_destruction(Irq_connection::KEEP_OPEN); + return; + } + } catch (Genode::Parent::Service_denied) { } + + irq_alloc.free_msi(msi); + } + } + try { - /* check if IRQ object was used before */ - valid = Proxy::get_irq_proxy(_gsi, &irq_alloc); + /* check if shared IRQ object was used before */ + if (Proxy::get_irq_proxy(_gsi, &irq_alloc)) + return; } catch (Genode::Parent::Service_denied) { } - if (!valid) - PERR("unavailable IRQ object 0x%x requested", _gsi); + PERR("unavailable IRQ 0x%x requested", _gsi); } Pci::Irq_session_component::~Irq_session_component() { + if (msi()) { + Genode::Irq_session_client irq_msi(_irq_cap); + irq_msi.sigh(Genode::Signal_context_capability()); + + Genode::env()->parent()->close(_irq_cap); + + irq_alloc.free_msi(_gsi); + return; + } + + /* shared irq handling */ Irq_component *irq_obj = Proxy::get_irq_proxy(_gsi); if (!irq_obj) return; @@ -223,6 +295,14 @@ Pci::Irq_session_component::~Irq_session_component() void Pci::Irq_session_component::sigh(Genode::Signal_context_capability sigh) { + if (msi()) { + /* register signal handler for msi directly at parent */ + Genode::Irq_session_client irq_msi(_irq_cap); + irq_msi.sigh(sigh); + return; + } + + /* shared irq handling */ Irq_component *irq_obj = Proxy::get_irq_proxy(_gsi); if (!irq_obj) { PERR("signal handler got not registered - irq object unavailable"); diff --git a/repos/os/src/drivers/pci/irq.h b/repos/os/src/drivers/pci/irq.h index 2d05a78403..5984c60f4d 100644 --- a/repos/os/src/drivers/pci/irq.h +++ b/repos/os/src/drivers/pci/irq.h @@ -32,14 +32,29 @@ class Pci::Irq_session_component : public Genode::Rpc_object -#include +/* base */ #include -#include - #include +#include +#include +/* os */ +#include + +/* local */ #include "pci_device_config.h" - #include "irq.h" namespace Pci { class Device_component; class Session_component; } @@ -32,19 +34,25 @@ class Pci::Device_component : public Genode::Rpc_object, { private: - Device_config _device_config; - Genode::addr_t _config_space; - Genode::Io_mem_connection *_io_mem; - Config_access _config_access; - Genode::Rpc_entrypoint *_ep; - Pci::Session_component *_session; - Irq_session_component _irq_session; - bool _rewrite_irq_line; + Device_config _device_config; + Genode::addr_t _config_space; + Genode::Io_mem_session_capability _io_mem_config_extended; + Config_access _config_access; + Genode::Rpc_entrypoint *_ep; + Pci::Session_component *_session; + unsigned short _irq_line; + Irq_session_component _irq_session; + bool _rewrite_irq_line; enum { IO_BLOCK_SIZE = sizeof(Genode::Io_port_connection) * - Device::NUM_RESOURCES + 32 + 8 * sizeof(void *) + Device::NUM_RESOURCES + 32 + 8 * sizeof(void *), + PCI_CMD_REG = 0x4, + PCI_CMD_DMA = 0x4, + PCI_IRQ_LINE = 0x3c, + PCI_IRQ_PIN = 0x3d }; + Genode::Tslab _slab_ioport; Genode::Slab_block _slab_ioport_block; char _slab_ioport_block_data[IO_BLOCK_SIZE]; @@ -56,7 +64,95 @@ class Pci::Device_component : public Genode::Rpc_object, Genode::Io_port_connection *_io_port_conn [Device::NUM_RESOURCES]; Genode::Io_mem_connection *_io_mem_conn [Device::NUM_RESOURCES]; - enum { PCI_IRQ_LINE = 0x3c }; + struct Status : Genode::Register<8> { + struct Capabilities : Bitfield<4,1> { }; + + inline static access_t read(Genode::uint8_t t) { return t; }; + }; + + + /** + * Read out msi capabilities of the device. + */ + Genode::uint16_t _msi_cap() + { + enum { PCI_STATUS = 0x6, PCI_CAP_OFFSET = 0x34, CAP_MSI = 0x5 }; + + Status::access_t status = Status::read(_device_config.read(&_config_access, + PCI_STATUS, + Pci::Device::ACCESS_16BIT)); + if (!Status::Capabilities::get(status)) + return 0; + + Genode::uint8_t cap = _device_config.read(&_config_access, + PCI_CAP_OFFSET, + Pci::Device::ACCESS_8BIT); + + for (Genode::uint16_t val = 0; cap; cap = val >> 8) { + val = _device_config.read(&_config_access, cap, + Pci::Device::ACCESS_16BIT); + if ((val & 0xff) != CAP_MSI) + continue; + + return cap; + } + + return 0; + } + + + /** + * Disable MSI if already enabled. + */ + unsigned _disable_msi(unsigned irq) + { + using Genode::uint16_t; + using Genode::uint8_t; + + uint8_t has_irq = _device_config.read(&_config_access, PCI_IRQ_PIN, + Pci::Device::ACCESS_8BIT); + if (!has_irq) + return Irq_session_component::INVALID_IRQ; + + uint16_t cap = _msi_cap(); + if (!cap) + return irq; + + uint16_t msi = _device_config.read(&_config_access, cap + 2, + Pci::Device::ACCESS_16BIT); + + enum { MSI_ENABLED = 0x1 }; + + if (msi & MSI_ENABLED) + /* disable MSI */ + _device_config.write(&_config_access, cap + 2, + msi ^ MSI_ENABLED, + Pci::Device::ACCESS_8BIT); + + return irq; + } + + + /** + * Disable bus master dma if already enabled. + */ + void _disable_bus_master_dma() { + + /* + * Disabling a bridge may make the devices behind non-functional, + * as we have no driver which will switch it on again + */ + if (_device_config.is_pci_bridge()) + return; + + unsigned cmd = _device_config.read(&_config_access, PCI_CMD_REG, + Pci::Device::ACCESS_16BIT); + if (cmd & PCI_CMD_DMA) + _device_config.write(&_config_access, PCI_CMD_REG, + cmd ^ PCI_CMD_DMA, + Pci::Device::ACCESS_16BIT); + } + public: @@ -69,13 +165,22 @@ class Pci::Device_component : public Genode::Rpc_object, bool rewrite_irq_line) : _device_config(device_config), _config_space(addr), - _io_mem(0), _ep(ep), _session(session), - _irq_session(_device_config.read(&_config_access, PCI_IRQ_LINE, - Pci::Device::ACCESS_8BIT)), + _ep(ep), _session(session), + _irq_line(_device_config.read(&_config_access, PCI_IRQ_LINE, + Pci::Device::ACCESS_8BIT)), + _irq_session(_disable_msi(_irq_line), _msi_cap() ? _config_space : ~0UL), _rewrite_irq_line(rewrite_irq_line), _slab_ioport(0, &_slab_ioport_block), _slab_iomem(0, &_slab_iomem_block) { + if (_config_space != ~0UL) { + try { + Genode::Io_mem_connection conn(_config_space, 0x1000); + conn.on_destruction(Genode::Io_mem_connection::KEEP_OPEN); + _io_mem_config_extended = conn; + } catch (Genode::Parent::Service_denied) { } + } + _ep->manage(&_irq_session); for (unsigned i = 0; i < Device::NUM_RESOURCES; i++) { @@ -87,6 +192,43 @@ class Pci::Device_component : public Genode::Rpc_object, PERR("incorrect amount of space for io port resources"); if (_slab_iomem.num_elem() != Device::NUM_RESOURCES) PERR("incorrect amount of space for io mem resources"); + + _disable_bus_master_dma(); + + if (!_irq_session.msi()) + return; + + Genode::addr_t msi_address = _irq_session.msi_address(); + Genode::uint32_t msi_value = _irq_session.msi_data(); + Genode::uint16_t msi_cap = _msi_cap(); + + enum { CAP_MSI_64 = 0x80, MSI_ENABLED = 0x1 }; + Genode::uint16_t msi = _device_config.read(&_config_access, + msi_cap + 2, + Pci::Device::ACCESS_16BIT); + + _device_config.write(&_config_access, msi_cap + 0x4, msi_address, + Pci::Device::ACCESS_32BIT); + + if (msi & CAP_MSI_64) { + Genode::uint32_t upper_address = sizeof(msi_address) > 4 ? + msi_address >> 32 : 0UL; + + _device_config.write(&_config_access, msi_cap + 0x8, + upper_address, + Pci::Device::ACCESS_32BIT); + _device_config.write(&_config_access, msi_cap + 0xc, + msi_value, + Pci::Device::ACCESS_16BIT); + } + else + _device_config.write(&_config_access, msi_cap + 0x8, msi_value, + Pci::Device::ACCESS_16BIT); + + /* enable MSI */ + _device_config.write(&_config_access, msi_cap + 2, + msi ^ MSI_ENABLED, + Pci::Device::ACCESS_8BIT); } /** @@ -95,8 +237,9 @@ class Pci::Device_component : public Genode::Rpc_object, Device_component(Genode::Rpc_entrypoint * ep, Pci::Session_component * session, unsigned irq) : - _config_space(~0UL), _io_mem(0), _ep(ep), _session(session), - _irq_session(irq), + _config_space(~0UL), _ep(ep), _session(session), + _irq_line(irq), + _irq_session(_irq_line, _config_space), _slab_ioport(0, &_slab_ioport_block), _slab_iomem(0, &_slab_iomem_block) { @@ -121,6 +264,12 @@ class Pci::Device_component : public Genode::Rpc_object, if (_io_mem_conn[i]) Genode::destroy(_slab_iomem, _io_mem_conn[i]); } + + if (_io_mem_config_extended.valid()) + Genode::env()->parent()->close(_io_mem_config_extended); + + if (_device_config.valid()) + _disable_bus_master_dma(); } /**************************************** @@ -129,32 +278,34 @@ class Pci::Device_component : public Genode::Rpc_object, Device_config config() { return _device_config; } - Genode::addr_t config_space() { return _config_space; } + Genode::Io_mem_dataspace_capability get_config_space() + { + if (!_io_mem_config_extended.valid()) + return Genode::Io_mem_dataspace_capability(); - void set_config_space(Genode::Io_mem_connection * io_mem) { - _io_mem = io_mem; } - - Genode::Io_mem_connection * get_config_space() { return _io_mem; } + Genode::Io_mem_session_client client(_io_mem_config_extended); + return client.dataspace(); + } /************************** ** PCI-device interface ** **************************/ void bus_address(unsigned char *bus, unsigned char *dev, - unsigned char *fn) + unsigned char *fn) override { *bus = _device_config.bus_number(); *dev = _device_config.device_number(); *fn = _device_config.function_number(); } - unsigned short vendor_id() { return _device_config.vendor_id(); } + unsigned short vendor_id() override { return _device_config.vendor_id(); } - unsigned short device_id() { return _device_config.device_id(); } + unsigned short device_id() override { return _device_config.device_id(); } - unsigned class_code() { return _device_config.class_code(); } + unsigned class_code() override { return _device_config.class_code(); } - Resource resource(int resource_id) + Resource resource(int resource_id) override { /* return invalid resource if device is invalid */ if (!_device_config.valid()) @@ -163,41 +314,13 @@ class Pci::Device_component : public Genode::Rpc_object, return _device_config.resource(resource_id); } - unsigned config_read(unsigned char address, Access_size size) + unsigned config_read(unsigned char address, Access_size size) override { return _device_config.read(&_config_access, address, size); } - void config_write(unsigned char address, unsigned value, Access_size size) - { - /* white list of ports which we permit to write */ - switch(address) { - case 0x40 ... 0xFF: - /* all device specific registers are permitted */ - break; - case 0x4: /* COMMAND register - first byte */ - if (size == Access_size::ACCESS_16BIT) - break; - case 0x5: /* COMMAND register - second byte */ - case 0xd: /* Latency timer */ - if (size == Access_size::ACCESS_8BIT) - break; - case PCI_IRQ_LINE: - /* permitted up to now solely for acpi driver */ - if (address == PCI_IRQ_LINE && _rewrite_irq_line && - size == Access_size::ACCESS_8BIT) - break; - default: - PWRN("%x:%x:%x write access to address=%x value=0x%x " - " size=0x%x got dropped", _device_config.bus_number(), - _device_config.device_number(), - _device_config.function_number(), - address, value, size); - return; - } - - _device_config.write(&_config_access, address, value, size); - } + void config_write(unsigned char address, unsigned value, + Access_size size) override; Genode::Irq_session_capability irq(Genode::uint8_t id) override {