diff --git a/Makefile.dep b/Makefile.dep index b6df84cb01..c4d9b80450 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -884,6 +884,10 @@ ifneq (,$(filter riotboot_hdr, $(USEMODULE))) USEMODULE += riotboot endif +ifneq (,$(filter irq_handler,$(USEMODULE))) + USEMODULE += event +endif + # Enable periph_gpio when periph_gpio_irq is enabled ifneq (,$(filter periph_gpio_irq,$(USEMODULE))) FEATURES_REQUIRED += periph_gpio diff --git a/sys/include/irq_handler.h b/sys/include/irq_handler.h new file mode 100644 index 0000000000..97f79e9f77 --- /dev/null +++ b/sys/include/irq_handler.h @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2019 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_irq_handler Interrupt handler thread + * @ingroup sys + * @brief Single thread for handling interrupts that may trigger blocking + * functions and therefore may only be called in thread context. + * + * @author Gunar Schorcht + * + * ## Interrupt Context Problem + * + * There are many devices connected to the host CPU via a bus system such as + * I2C or SPI. Such devices are, for example, sensors and actuators. Since + * these devices share the bus system, their access to the bus system has + * to be synchronized. + * + * In addition, such devices often use interrupts to trigger the execution + * of certain functions, such as reading the sensor values. That is, when an + * interrupt occurs, the driver requires often access to the bus system, for + * example, to read the status registers. + * + * The access to SPI and I2C interfaces is synchronized by mutual exclusion + * using mutexes. If one thread tries to access such an interface that is + * already being used by another thread, it will be blocked until the interface + * becomes available. Although this synchronization works in the thread + * context, it does not work in the interrupt context. Accessing such an + * interface within an ISR would interfere with an already existing interface + * access. This problem is called [interrupt context problem] + * (http://api.riot-os.org/group__drivers__netdev__api.html). + * + * The only solution to this problem is *not to call any function that + * interacts with a device directly from interrupt context*. Rather, the ISR + * should only indicate the occurrence of the interrupt. The interrupt is + * then handled asynchronously by a normal function within a thread context. + * + * The problem now is that driver modules usually do not use their own thread, + * but run in the context of the calling thread. However, it can not be left + * to the application thread to handle the interrupts of driver modules. The + * only solution would be to have a separate interrupt handler thread for each + * driver module that uses interrupts along with SPI or I2C interfaces. + * However, as the number of such modules increases, many resources (thread + * contexts, including their thread stacks) are allocated only for interrupt + * handling. + * + * ## Solution + * + * The solution is to have a single interrupt handler thread which serializes + * the interrupts of such driver modules and calls the functions of the driver + * modules to handle the interrupts from its thread context. + * + * For this purpose, each driver module that wants to use this interrupt + * handler thread has to define an interrupt event of type #irq_event_t + * for each of its interrupt sources. The interrupt event contains a + * reference to the function to be called to handle the interrupt. + * + * When an interrupt of the corresponding source occurs, the ISR of the + * driver module registers only the interrupt event associated with the + * interrupt source with the #irq_event_add function on the handler. The + * handler places the interrupt event in an pending interrupt queue. + * + * Each interrupt event can be registered on the handler only once. That is, + * if the same interrupt occurs multiple times, only its first occurence is + * placed to the pending interrupt queue and is handled. + * + * When the interrupt handler thread gets the CPU, it processes all pending + * interrupt events in the order of their occurence before it yields. + * + * ## Usage + * + * The single-interrupt handler thread can be used not only for driver + * modules with bus access, but for any interrupt handling that may + * trigger a blocking function and therefore cannot be called in an + * interrupt context. + * + * @note All interrupts handled by the single interrupt handler thread are + * serialized. + * + * To use the interrupt handler thread, using modules have to define a static + * interrupt event of type #irq_event_t for each of their interrupt sources. + * These static interrupt events have to be initialized with the static + * initializer #IRQ_EVENT_INIT. Furthermore, the interrupt handling function + * and optionally an argument, the context, have to be set. + * + * Once the interrupt events have been initialized, they can be added to the + * pending interrupts queue of the interrupt handler thread by an ISR using + * the #irq_event_add function which indicates that an interrupt has occurred + * and needs to be handled. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~ {.c} + * #include "foo_device.h" + * #include "irq_handler.h" + * + * // interrupt event structure with static initializer + * static irq_event_t _int_event = IRQ_EVENT_INIT; + * + * ... + * + * // non blocking ISR just adds the event and returns + * static void _int_request(void *arg) + * { + * irq_event_add(&_int_event); + * } + * + * // example handler for the interrupt including blocking functions + * static void _int_handler(void *ctx) + * { + * foo_device_t dev = (foo_device_t*)ctx; + * uint8_t status; + * + * // blocking access to the I2C + * i2c_aquire(dev->i2c_device); + * i2c_read_reg(dev->i2c_device, FOO_DEVICE_REG_STATUS, &status, 1); + * i2c_release(dev->i2c_device); + * + * // application thread callbacks + * switch (status) { + * case FOO_INT_TYPE1: dev->int_type1.isr(dev->int_type1.arg); + * break; + * case FOO_INT_TYPE2: dev->int_type2.isr(dev->int_type2.arg); + * break; + * ... + * } + * ... + * } + * + * ... + * + * int foo_device_init(foo_device_t *dev, foo_device_params_t *params) + * { + * // set the handler for the interrupt + * _int_event.isr = _int_handler; + * _int_event.ctx = (void*)dev; + * + * // initialize the GPIO with ISR + * gpio_init_int(GPIO_PIN(0, 1), GPIO_IN, GPIO_BOTH, int_request, 0); + * ... + * } + * + * ~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @{ + * @file + */ + +#ifndef IRQ_HANDLER_H +#define IRQ_HANDLER_H + +#include + +#include "assert.h" +#include "event.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Default priority of the interrupt handler thread + * + * The priority of the interrupt handler thread has to be high enough that all + * pending interrupts are handled before other threads are executed. + */ +#ifndef IRQ_HANDLER_PRIO +#define IRQ_HANDLER_PRIO 0 +#endif + +/** + * @brief Interrupt handling function prototype + * + * Defines the prototype of the function that is registered together + * with an interrupt event and to be called when the interrupt is handled. + */ +typedef void (*irq_isr_t)(void *ctx); + +/** + * @brief Interrupt event structure + * + * Using modules have to define a structure of this type for each interrupt + * source used by the modules. Structures of this type are used to put + * them in a pending interrupt queue indicating that an interrupt of the + * corresponding source has occurred and needs to be handled. Each interrupt + * event can only be pending once. + * + * Interrupt event structures have to be pre-allocated to use them. + */ +typedef struct { + event_t event; /**< Event structure */ + bool pending; /**< Indicates whether the same interrupt request event + is already pending */ + irq_isr_t isr; /**< Function to be called to handle the interrupt */ + void *ctx; /**< Context used by the function */ +} irq_event_t; + +/** + * @brief Static initializer for #irq_event_t. + */ +#define IRQ_EVENT_INIT { \ + .event.handler = NULL, \ + .event.list_node.next = NULL, \ + .pending = false, \ + .isr = NULL, \ + .ctx = NULL, \ + } + +/** + * @brief Initialize an interrupt event + * + * Initializes the given interrupt event structure. + * + * Only use this function for dynamically allocated interrupt event + * structures. For the initialization of static interrupt event structures + * use #IRQ_EVENT_INIT instead. + * + * @param[out] irq Pre-allocated #irq_event_t sturcture, must not be NULL + */ + +static inline void irq_event_init(irq_event_t *irq) +{ + assert(irq != NULL); + irq_event_t tmp = IRQ_EVENT_INIT; + *irq = tmp; +} + +/** + * @brief Add an interrupt event to the pending interrupt queue + * + * The interrupt event given by parameter \p irq will be placed at the end of + * the pending interrupt queue. + * + * Each interrupt event can be added only once to the pending interrupt queue. + * That is, if the same interrupt occurs multiple times, only its first + * occurence is placed to the pending interrupt queue and is handled. + * + * @param[in] irq Preallocated interrupt event + * + * @retval 0 on success + * @retval -EALREADY if the given interrupt event is already pending + */ +int irq_event_add(irq_event_t *irq); + +#ifdef __cplusplus +} +#endif + +#endif /* IRQ_HANDLER_H */ +/** @} */ diff --git a/sys/irq_handler/Makefile b/sys/irq_handler/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/irq_handler/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/irq_handler/irq_handler.c b/sys/irq_handler/irq_handler.c new file mode 100644 index 0000000000..34370beb7a --- /dev/null +++ b/sys/irq_handler/irq_handler.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2019 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include +#include + +#include "irq_handler.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* Stack for the interrupt event handler thread */ +static char _irq_handler_stack[THREAD_STACKSIZE_DEFAULT]; + +/* PID of the interrupt handler thread, KERNEL_PID_UNDEF if not created yet */ +static kernel_pid_t _irq_handler_thread = KERNEL_PID_UNDEF; + +/* Interrupt event queue */ +static event_queue_t irq_queue = EVENT_QUEUE_INIT_DETACHED; + +static void _irq_handler(event_t *event) +{ + irq_event_t *irq = (irq_event_t *)event; + assert(irq != NULL); + + /* handle the pending interrupt */ + irq->pending = false; + irq->isr(irq->ctx); +} + +static void *_irq_loop(void *arg) +{ + (void)arg; + + DEBUG("[%s] starts\n", __func__); + + /* bind the queue */ + event_queue_claim(&irq_queue); + + /* doesn't return */ + event_loop(&irq_queue); + + return NULL; +} + +int irq_event_add(irq_event_t * irq) +{ + assert(irq != NULL); + assert(irq->isr != NULL); + + DEBUG("[%s] irq %p\n", __func__, (void *)irq); + + if (irq->pending) { + DEBUG("[%s] interrupt event %p is already pending\n", + __func__, (void *)irq); + return -EALREADY; + } + + /* disable interrupts */ + unsigned state = irq_disable(); + + /* create the handler thread if not created yet */ + if (_irq_handler_thread == KERNEL_PID_UNDEF) { + DEBUG("[%s] create irq_handler thread\n", __func__); + _irq_handler_thread = thread_create(_irq_handler_stack, + sizeof(_irq_handler_stack), + IRQ_HANDLER_PRIO, + THREAD_CREATE_WOUT_YIELD | + THREAD_CREATE_STACKTEST, + _irq_loop, NULL, "irq_handler"); + assert(_irq_handler_thread != KERNEL_PID_UNDEF); + + /* intialize the queue unbind */ + event_queue_init_detached(&irq_queue); + } + + /* initialize the interrupt event */ + irq->event.handler = _irq_handler; + irq->pending = true; + + /* restore previous interrupt state */ + irq_restore(state); + + /* queue the interrupt event */ + event_post(&irq_queue, (event_t *)irq); + + return 0; +}