Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sys: single interrupt handler thread for interrupts in modules with blocking functions #10555

Merged
merged 2 commits into from
Aug 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
253 changes: 253 additions & 0 deletions sys/include/irq_handler.h
Original file line number Diff line number Diff line change
@@ -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 <gunar@schorcht.net>
*
* ## 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 <stdbool.h>

#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 */
/** @} */
1 change: 1 addition & 0 deletions sys/irq_handler/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base
93 changes: 93 additions & 0 deletions sys/irq_handler/irq_handler.c
Original file line number Diff line number Diff line change
@@ -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 <inttypes.h>
#include <errno.h>

#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;
}
11 changes: 11 additions & 0 deletions tests/sys_irq_handler/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
include ../Makefile.tests_common

BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-nano \
arduino-uno nucleo-f031k6

CFLAGS += -DIRQ_HANDLER_PRIO=THREAD_PRIORITY_MAIN+1

USEMODULE += irq_handler
USEMODULE += xtimer

include $(RIOTBASE)/Makefile.include
Loading