sys/irq_handler: Remove deprecated module
This commit is contained in:
parent
9681c204d6
commit
a2dd6f90e5
@ -1,253 +0,0 @@
|
||||
/*
|
||||
* 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 occurrence 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 occurrence 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 structure, 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
|
||||
* occurrence 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 +0,0 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
||||
@ -1,94 +0,0 @@
|
||||
/*
|
||||
* 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 <assert.h>
|
||||
#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);
|
||||
|
||||
/* initialize 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;
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
include ../Makefile.tests_common
|
||||
|
||||
CFLAGS += -DIRQ_HANDLER_PRIO=THREAD_PRIORITY_MAIN+1
|
||||
|
||||
USEMODULE += irq_handler
|
||||
USEMODULE += xtimer
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
||||
@ -1,10 +0,0 @@
|
||||
BOARD_INSUFFICIENT_MEMORY := \
|
||||
arduino-duemilanove \
|
||||
arduino-leonardo \
|
||||
arduino-nano \
|
||||
arduino-uno \
|
||||
atmega328p \
|
||||
nucleo-f031k6 \
|
||||
nucleo-l011k4 \
|
||||
stm32f030f4-demo \
|
||||
#
|
||||
@ -1,170 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup tests
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Interrupt handler test application
|
||||
*
|
||||
* @author Gunar Schorcht <gunar@schorcht.net>
|
||||
*
|
||||
* This application demonstrates how to use the interrupt handler thread
|
||||
* module `irq_handler`. For that purpose the main thread simulates interrupts
|
||||
* from 2 interrupt sources with different priorities with a period
|
||||
* of a half second. One interrupt source triggers a second interrupt before
|
||||
* the interrupts can be handled by the interrupt handler.
|
||||
*
|
||||
* To be able to simulate the interrupts by the main thread, the interrupt
|
||||
* handler thread has to have a lower priority as the main thread for this
|
||||
* example. Otherwise, the interrupts would be handled immediately when they
|
||||
* are generated by the main thread. Therefore the interrupt handler thread
|
||||
* priority is set to THREAD_PRIORITY_MAIN+1 for this example.
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "irq_handler.h"
|
||||
#include "mutex.h"
|
||||
#include "thread.h"
|
||||
#include "xtimer.h"
|
||||
|
||||
#define TEST_TIME (100 * US_PER_MS)
|
||||
#define TEST_REPETITIONS (10)
|
||||
|
||||
static void _int1_service (void *arg);
|
||||
static void _int2_service (void *arg);
|
||||
|
||||
static char some_stack[THREAD_STACKSIZE_MAIN];
|
||||
static int i = 0;
|
||||
static int n_int1_handled = 0;
|
||||
static int n_int2_handled = 0;
|
||||
static int n_already_pending = 0;
|
||||
static mutex_t mutex = MUTEX_INIT;
|
||||
xtimer_t timer1a = {
|
||||
.callback = _int1_service,
|
||||
.arg = &timer1a
|
||||
};
|
||||
xtimer_t timer1b = {
|
||||
.callback = _int1_service,
|
||||
.arg = &timer1b
|
||||
};
|
||||
xtimer_t timer2 = {
|
||||
.callback = _int2_service,
|
||||
.arg = &timer2
|
||||
};
|
||||
|
||||
/* preallocated and initialized interrupt event objects */
|
||||
static irq_event_t _int1_event = IRQ_EVENT_INIT;
|
||||
static irq_event_t _int2_event = IRQ_EVENT_INIT;
|
||||
|
||||
/* interrupt service routine for a simulated interrupt from source 1 */
|
||||
static void _int1_service (void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
puts("int1 triggered");
|
||||
/* registers just the interrupt event and returns */
|
||||
if (irq_event_add(&_int1_event) == -EALREADY) {
|
||||
puts("int1 is already pending");
|
||||
n_already_pending++;
|
||||
}
|
||||
}
|
||||
|
||||
/* interrupt handler for interrupts from source 1 */
|
||||
static void _int1_handler(void *ctx)
|
||||
{
|
||||
xtimer_t *timer = ctx;
|
||||
xtimer_set(timer, TEST_TIME);
|
||||
puts("int1 handled");
|
||||
mutex_lock(&mutex);
|
||||
n_int1_handled++;
|
||||
mutex_unlock(&mutex);
|
||||
}
|
||||
|
||||
/* interrupt service routine for a simulated interrupt from source 2 */
|
||||
static void _int2_service (void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
puts("int2 triggered");
|
||||
/* registers just the interrupt event and returns */
|
||||
if (irq_event_add(&_int2_event) == -EALREADY) {
|
||||
puts("int2 is already pending");
|
||||
n_already_pending++;
|
||||
}
|
||||
}
|
||||
|
||||
/* interrupt handler for interrupts from source 2 */
|
||||
static void _int2_handler(void *ctx)
|
||||
{
|
||||
xtimer_t *timer = ctx;
|
||||
xtimer_set(timer, TEST_TIME);
|
||||
puts("int2 handled");
|
||||
mutex_lock(&mutex);
|
||||
n_int2_handled++;
|
||||
mutex_unlock(&mutex);
|
||||
}
|
||||
|
||||
static void *some_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
puts("some_thread is starting");
|
||||
|
||||
while (1) {
|
||||
mutex_lock(&mutex);
|
||||
i++;
|
||||
mutex_unlock(&mutex);
|
||||
xtimer_usleep(US_PER_MS);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
_int1_event.isr = _int1_handler;
|
||||
_int1_event.ctx = &timer1a;
|
||||
|
||||
_int2_event.isr = _int2_handler;
|
||||
_int2_event.ctx = &timer2;
|
||||
|
||||
puts("START");
|
||||
|
||||
thread_create(some_stack, sizeof(some_stack),
|
||||
THREAD_PRIORITY_MAIN + 2, THREAD_CREATE_WOUT_YIELD,
|
||||
some_thread, NULL, "some_thread");
|
||||
puts("some_thread created");
|
||||
|
||||
/* wait to let some_thread to start */
|
||||
xtimer_usleep(US_PER_SEC);
|
||||
|
||||
i = 0;
|
||||
|
||||
xtimer_set(&timer1a, TEST_TIME);
|
||||
xtimer_set(&timer1b, TEST_TIME);
|
||||
xtimer_set(&timer2, TEST_TIME);
|
||||
|
||||
xtimer_usleep(TEST_TIME * TEST_REPETITIONS + TEST_TIME/2);
|
||||
|
||||
xtimer_remove(&timer1a);
|
||||
xtimer_remove(&timer1b);
|
||||
xtimer_remove(&timer2);
|
||||
|
||||
mutex_lock(&mutex);
|
||||
if ((i > 0) &&
|
||||
(n_int1_handled == TEST_REPETITIONS) &&
|
||||
(n_int2_handled == TEST_REPETITIONS) &&
|
||||
(n_already_pending == 1)) {
|
||||
puts("[SUCCESS]");
|
||||
}
|
||||
mutex_unlock(&mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user