From 6dd9d349fc0341ce630ddf517d3cccdf65a9bcbf Mon Sep 17 00:00:00 2001 From: Alexander Boettcher Date: Wed, 25 Mar 2015 14:51:42 +0100 Subject: [PATCH] pci: support shared irqs (x86) Step to move shared irq handling out of core in the long run. So, use irq_proxy implementation from base in os and implement shared irq handling in platform driver of x86 (pci_drv). Fixes #1471 --- repos/os/include/platform/irq_proxy.h | 242 ++++++++++++++++++ repos/os/src/drivers/pci/irq.cc | 241 +++++++++++++++++ repos/os/src/drivers/pci/irq.h | 49 ++++ .../os/src/drivers/pci/pci_device_component.h | 185 ++++++------- .../src/drivers/pci/pci_session_component.h | 2 +- repos/os/src/drivers/pci/x86/target.mk | 4 +- 6 files changed, 630 insertions(+), 93 deletions(-) create mode 100644 repos/os/include/platform/irq_proxy.h create mode 100644 repos/os/src/drivers/pci/irq.cc create mode 100644 repos/os/src/drivers/pci/irq.h diff --git a/repos/os/include/platform/irq_proxy.h b/repos/os/include/platform/irq_proxy.h new file mode 100644 index 0000000000..11d6c04c1b --- /dev/null +++ b/repos/os/include/platform/irq_proxy.h @@ -0,0 +1,242 @@ +/** + * \brief Shared-interrupt support + * \author Christian Helmuth + * \author Sebastian Sumpf + * \date 2009-12-15 + */ + +#ifndef _CORE__INCLUDE__IRQ_PROXY_H_ +#define _CORE__INCLUDE__IRQ_PROXY_H_ + +#include + + +namespace Genode { + class Irq_sigh; + template class Irq_proxy; +} + + +class Genode::Irq_sigh : public Genode::Signal_context_capability, + public Genode::List::Element +{ + public: + + inline Irq_sigh * operator= (const Signal_context_capability &cap) + { + Signal_context_capability::operator=(cap); + return this; + } + + Irq_sigh() { } + + void notify() { Genode::Signal_transmitter(*this).submit(1); } +}; + + +/* + * Proxy thread that associates to the interrupt and unblocks waiting irqctrl + * threads. + * + * XXX resources are not accounted as the interrupt is shared + */ +template +class Genode::Irq_proxy : public THREAD, + public Genode::List >::Element +{ + protected: + + char _name[32]; + Lock _startup_lock; + + long _irq_number; + + Lock _mutex; /* protects this object */ + int _num_sharers; /* number of clients sharing this IRQ */ + Semaphore _sleep; /* wake me up if aspired blockers return */ + List _sigh_list; + int _num_acknowledgers; /* number of currently blocked clients */ + bool _woken_up; /* client decided to wake me up - + this prevents multiple wakeups + to happen during initialization */ + + + /*************** + ** Interface ** + ***************/ + + /** + * Request interrupt + * + * \return true on success + */ + virtual bool _associate() = 0; + + /** + * Wait for associated interrupt + */ + virtual void _wait_for_irq() = 0; + + /** + * Acknowledge interrupt + */ + virtual void _ack_irq() = 0; + + /******************** + ** Implementation ** + ********************/ + + const char *_construct_name(long irq_number) + { + snprintf(_name, sizeof(_name), "irqproxy%02lx", irq_number); + return _name; + } + + void _loop() + { + /* wait for first blocker */ + _sleep.down(); + + while (1) { + _wait_for_irq(); + + /* notify all */ + notify_about_irq(1); + + /* + * We must wait for all clients to ack their interrupt, + * otherwise level-triggered interrupts will occur immediately + * after acknowledgement. That's an inherent security problem + * with shared IRQs and induces problems with dynamic driver + * load and unload. + */ + _sleep.down(); + + /* acknowledge previous interrupt */ + _ack_irq(); + } + } + + /** + * Start this thread, should be called externally from derived class + */ + virtual void _start() + { + THREAD::start(); + _startup_lock.lock(); + } + + public: + + Irq_proxy(long irq_number) + : + THREAD(_construct_name(irq_number)), + _startup_lock(Lock::LOCKED), _irq_number(irq_number), + _mutex(Lock::UNLOCKED), _num_sharers(0), _num_acknowledgers(0), _woken_up(false) + + { } + + /** + * Thread interface + */ + void entry() + { + bool const associate_suceeded = _associate(); + + _startup_lock.unlock(); + + if (associate_suceeded) + _loop(); + } + + /** + * Acknowledgements of clients + */ + virtual bool ack_irq() + { + Lock::Guard lock_guard(_mutex); + + _num_acknowledgers++; + + /* + * The proxy thread has to be woken up if no client woke it up + * before and this client is the last aspired acknowledger. + */ + if (!_woken_up && _num_acknowledgers == _num_sharers) { + _sleep.up(); + _woken_up = true; + } + + return _woken_up; + } + + /** + * Notify all clients about irq + */ + void notify_about_irq(unsigned) + { + Lock::Guard lock_guard(_mutex); + + /* reset acknowledger state */ + _num_acknowledgers = 0; + _woken_up = false; + + /* inform blocked clients */ + for (Irq_sigh * s = _sigh_list.first(); s ; s = s->next()) + s->notify(); + } + + long irq_number() const { return _irq_number; } + + virtual bool add_sharer(Irq_sigh *s) + { + Lock::Guard lock_guard(_mutex); + + ++_num_sharers; + _sigh_list.insert(s); + + return true; + } + + virtual bool remove_sharer(Irq_sigh *s) + { + Lock::Guard lock_guard(_mutex); + + _sigh_list.remove(s); + --_num_sharers; + + if (_woken_up) + return _num_sharers == 0; + + if (_num_acknowledgers == _num_sharers) { + _sleep.up(); + _woken_up = true; + } + + return _num_sharers == 0; + } + + template + static PROXY *get_irq_proxy(long irq_number, Range_allocator *irq_alloc = 0) + { + static List proxies; + static Lock proxies_lock; + + Lock::Guard lock_guard(proxies_lock); + + /* lookup proxy in database */ + for (Irq_proxy *p = proxies.first(); p; p = p->next()) + if (p->irq_number() == irq_number) + return static_cast(p); + + /* try to create proxy */ + if (!irq_alloc || irq_alloc->alloc_addr(1, irq_number).is_error()) + return 0; + + PROXY *new_proxy = new (env()->heap()) PROXY(irq_number); + proxies.insert(new_proxy); + return new_proxy; + } +}; + +#endif /* _CORE__INCLUDE__IRQ_PROXY_H_ */ diff --git a/repos/os/src/drivers/pci/irq.cc b/repos/os/src/drivers/pci/irq.cc new file mode 100644 index 0000000000..3c4a4a5c03 --- /dev/null +++ b/repos/os/src/drivers/pci/irq.cc @@ -0,0 +1,241 @@ +/* + * \brief Implementation of shared IRQs in PCI driver + * \author Alexander Boettcher + * \date 2015-03-27 + */ + +/* + * Copyright (C) 2015 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include + +/* Genode OS includes */ +#include + +/* PCI driver include */ +#include "irq.h" + + +namespace Pci { + class Irq_component; + class Irq_allocator; + class Irq_thread; +} + + +using namespace Genode; + + +/** + * A simple range allocator implementation used by the Irq_proxy + */ +class Pci::Irq_allocator : public Range_allocator, Bit_allocator<256> +{ + 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; + } + } + + /* 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; } +}; + + +/** + * Required by Irq_proxy if we would like to have a thread per IRQ, + * which we don't want to in the PCI driver - one thread is sufficient. + */ +class NoThread +{ + public: + + NoThread(const char *) { } + + void start(void) { } +}; + + +/** + * Thread waiting for signals caused by IRQs + */ +class Pci::Irq_thread : public Genode::Thread<4096> +{ + private: + + Signal_receiver _sig_rec; + + public: + + Irq_thread() : Thread<4096>("irq_sig_recv") { start(); } + + Signal_receiver & sig_rec() { return _sig_rec; } + + void entry() { + + typedef Genode::Signal_dispatcher_base Sdb; + + while (1) { + Genode::Signal sig = _sig_rec.wait_for_signal(); + + Sdb *dispatcher = dynamic_cast(sig.context()); + + if (!dispatcher) { + PERR("dispatcher missing for signal %p, %u", + sig.context(), sig.num()); + continue; + } + dispatcher->dispatch(sig.num()); + } + } +}; + + +/** + * One allocator for managing in use IRQ numbers and one IRQ thread waiting + * for Genode signals of all hardware IRQs. + */ +static Pci::Irq_allocator irq_alloc; +static Pci::Irq_thread irq_thread; + + +/** + * Irq_proxy interface implementation + */ +typedef Genode::Irq_proxy Proxy; + +class Pci::Irq_component : public Proxy +{ + private: + + Genode::Irq_connection _irq; + Genode::Signal_dispatcher _irq_dispatcher; + + bool _associated; + + public: + + void _ack_irq() { + /* + * Associate handler only when required, because our partner may + * also implement shared irq and would expect to get ack_irq() + * form us even if we have no client ... + */ + if (!_associated) { + _associated = true; + /* register signal handler on irq_session */ + _irq.sigh(_irq_dispatcher); + } + + _irq.ack_irq(); + } + + bool _associate() { return _associated; } + void _wait_for_irq() { } + + virtual bool remove_sharer(Irq_sigh *s) override { + if (!Proxy::remove_sharer(s)) + return false; + + /* De-associate handler. */ + _associated = false; + _irq.sigh(Genode::Signal_context_capability()); + return true; + } + + public: + + Irq_component(unsigned gsi) + : + Proxy(gsi), _irq(gsi), _irq_dispatcher(irq_thread.sig_rec(), *this, + &::Proxy::notify_about_irq), + _associated(false) + { } +}; + + + +/******************************* + ** PCI IRQ session component ** + *******************************/ + +void Pci::Irq_session_component::ack_irq() +{ + Irq_component *irq_obj = Proxy::get_irq_proxy(_gsi); + if (!irq_obj) { + PERR("Expected to find IRQ proxy for IRQ %02x", _gsi); + return; + } + + if (irq_obj->ack_irq()) + irq_obj->_ack_irq(); +} + + +Pci::Irq_session_component::Irq_session_component(unsigned irq) : _gsi(irq) +{ + bool valid = false; + + /* invalid irq number for pci devices */ + if (irq == 0xFF) + return; + + try { + /* check if IRQ object was used before */ + valid = Proxy::get_irq_proxy(_gsi, &irq_alloc); + } catch (Genode::Parent::Service_denied) { } + + if (!valid) + PERR("unavailable IRQ object 0x%x requested", _gsi); +} + + +Pci::Irq_session_component::~Irq_session_component() +{ + Irq_component *irq_obj = Proxy::get_irq_proxy(_gsi); + if (!irq_obj) return; + + if (_irq_sigh.valid()) + irq_obj->remove_sharer(&_irq_sigh); +} + + +void Pci::Irq_session_component::sigh(Genode::Signal_context_capability sigh) +{ + Irq_component *irq_obj = Proxy::get_irq_proxy(_gsi); + if (!irq_obj) { + PERR("signal handler got not registered - irq object unavailable"); + return; + } + + Genode::Signal_context_capability old = _irq_sigh; + + if (old.valid() && !sigh.valid()) + irq_obj->remove_sharer(&_irq_sigh); + + _irq_sigh = sigh; + + if (!old.valid() && sigh.valid()) + irq_obj->add_sharer(&_irq_sigh); +} diff --git a/repos/os/src/drivers/pci/irq.h b/repos/os/src/drivers/pci/irq.h new file mode 100644 index 0000000000..1f9c93b03b --- /dev/null +++ b/repos/os/src/drivers/pci/irq.h @@ -0,0 +1,49 @@ +/* + * \brief IRQ session interface + * \author Alexander Boettcher + * \date 2015-03-25 + */ + +/* + * Copyright (C) 2015 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#pragma once + +#include + +#include + +#include +#include + +/* PCI local includes */ +#include + + +namespace Pci { class Irq_session_component; } + + +class Pci::Irq_session_component : public Genode::Rpc_object, + public Genode::List::Element +{ + private: + + unsigned _gsi; + Genode::Irq_sigh _irq_sigh; + + public: + + Irq_session_component(unsigned); + ~Irq_session_component(); + + /*************************** + ** Irq session interface ** + ***************************/ + + void ack_irq() override; + void sigh(Genode::Signal_context_capability) override; +}; diff --git a/repos/os/src/drivers/pci/pci_device_component.h b/repos/os/src/drivers/pci/pci_device_component.h index 878618405e..23f15fdd90 100644 --- a/repos/os/src/drivers/pci/pci_device_component.h +++ b/repos/os/src/drivers/pci/pci_device_component.h @@ -20,106 +20,111 @@ #include #include -#include #include "pci_device_config.h" -namespace Pci { +#include "irq.h" - class Device_component : public Genode::Rpc_object, - public Genode::List::Element - { - private: +namespace Pci { class Device_component; } - Device_config _device_config; - Genode::addr_t _config_space; - Genode::Io_mem_connection *_io_mem; - Config_access _config_access; - Genode::Irq_connection _irq; +class Pci::Device_component : public Genode::Rpc_object, + public Genode::List::Element +{ + private: - enum { PCI_IRQ = 0x3c }; + Device_config _device_config; + Genode::addr_t _config_space; + Genode::Io_mem_connection *_io_mem; + Config_access _config_access; + Genode::Rpc_entrypoint *_ep; + Irq_session_component _irq_session; - public: + enum { PCI_IRQ = 0x3c }; - /** - * Constructor + public: + + /** + * Constructor + */ + Device_component(Device_config device_config, Genode::addr_t addr, + Genode::Rpc_entrypoint *ep) + : + _device_config(device_config), _config_space(addr), + _io_mem(0), _ep(ep), + _irq_session(_device_config.read(&_config_access, PCI_IRQ, + Pci::Device::ACCESS_8BIT)) + { + _ep->manage(&_irq_session); + } + + /** + * De-constructor + */ + ~Device_component() + { + _ep->dissolve(&_irq_session); + } + + /**************************************** + ** Methods used solely by pci session ** + ****************************************/ + + Device_config config() { return _device_config; } + + Genode::addr_t config_space() { return _config_space; } + + void set_config_space(Genode::Io_mem_connection * io_mem) { + _io_mem = io_mem; } + + Genode::Io_mem_connection * get_config_space() { return _io_mem; } + + /************************** + ** PCI-device interface ** + **************************/ + + void bus_address(unsigned char *bus, unsigned char *dev, + unsigned char *fn) + { + *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 device_id() { return _device_config.device_id(); } + + unsigned class_code() { return _device_config.class_code(); } + + Resource resource(int resource_id) + { + /* return invalid resource if device is invalid */ + if (!_device_config.valid()) + Resource(0, 0); + + return _device_config.resource(resource_id); + } + + unsigned config_read(unsigned char address, Access_size size) + { + return _device_config.read(&_config_access, address, size); + } + + void config_write(unsigned char address, unsigned value, Access_size size) + { + /* + * XXX implement policy to prevent write access to base-address registers */ - Device_component(Device_config device_config, Genode::addr_t addr) - : - _device_config(device_config), _config_space(addr), - _io_mem(0), - _irq(_device_config.read(&_config_access, PCI_IRQ, - Pci::Device::ACCESS_8BIT)) - { } - /**************************************** - ** Methods used solely by pci session ** - ****************************************/ + _device_config.write(&_config_access, address, value, size); + } - Device_config config() { return _device_config; } - - Genode::addr_t config_space() { return _config_space; } - - void set_config_space(Genode::Io_mem_connection * io_mem) { - _io_mem = io_mem; } - - Genode::Io_mem_connection * get_config_space() { return _io_mem; } - - /************************** - ** PCI-device interface ** - **************************/ - - void bus_address(unsigned char *bus, unsigned char *dev, - unsigned char *fn) - { - *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 device_id() { - return _device_config.device_id(); } - - unsigned class_code() { - return _device_config.class_code(); } - - Resource resource(int resource_id) - { - /* return invalid resource if device is invalid */ - if (!_device_config.valid()) - Resource(0, 0); - - return _device_config.resource(resource_id); - } - - unsigned config_read(unsigned char address, Access_size size) - { - Config_access config_access; - - return _device_config.read(&config_access, address, size); - } - - void config_write(unsigned char address, unsigned value, Access_size size) - { - Config_access config_access; - - /* - * XXX implement policy to prevent write access to base-address registers - */ - - _device_config.write(&config_access, address, value, size); - } - - Genode::Irq_session_capability irq(Genode::uint8_t id) override - { - if (id != 0) - return Genode::Irq_session_capability(); - return _irq_session.cap(); - } - }; -} + Genode::Irq_session_capability irq(Genode::uint8_t id) override + { + if (id != 0) + return Genode::Irq_session_capability(); + return _irq_session.cap(); + } +}; #endif /* _PCI_DEVICE_COMPONENT_H_ */ diff --git a/repos/os/src/drivers/pci/pci_session_component.h b/repos/os/src/drivers/pci/pci_session_component.h index 87b11ae560..c32bea8ca1 100644 --- a/repos/os/src/drivers/pci/pci_session_component.h +++ b/repos/os/src/drivers/pci/pci_session_component.h @@ -253,7 +253,7 @@ namespace Pci { * FIXME: check and adjust session quota */ Device_component *device_component = - new (_md_alloc) Device_component(config, config_space); + new (_md_alloc) Device_component(config, config_space, _ep); if (!device_component) return Device_capability(); diff --git a/repos/os/src/drivers/pci/x86/target.mk b/repos/os/src/drivers/pci/x86/target.mk index 2e67d748f1..b3c3c97e21 100644 --- a/repos/os/src/drivers/pci/x86/target.mk +++ b/repos/os/src/drivers/pci/x86/target.mk @@ -1,8 +1,8 @@ TARGET = pci_drv REQUIRES = x86 -SRC_CC = main.cc +SRC_CC = main.cc irq.cc LIBS = base config INC_DIR = $(PRG_DIR)/.. -vpath main.cc $(PRG_DIR)/.. +vpath %.cc $(PRG_DIR)/..