diff --git a/Documentation/overview.rst b/Documentation/overview.rst index 55c07dffd20ff97c4682bf4783d8d951072b37bd..ae7506a7f37e5d5f23461b0129f3f1983f75c596 100644 --- a/Documentation/overview.rst +++ b/Documentation/overview.rst @@ -14,31 +14,34 @@ Epicardium Epicardium is based on `FreeRTOS <https://www.freertos.org/>`_. There are a number of tasks that will have been keeping card10 running. These are: -+-------------------+-------------------------------+----------+-------------------------------------------+ -| Name | ID Global | Priority | Description | -+===================+===============================+==========+===========================================+ -| `vPmicTask`_ | ``pmic_task_id`` (static) | +4 | Power Management (and Reset Button) | -+-------------------+-------------------------------+----------+-------------------------------------------+ -| `vLifecycleTask`_ | ``lifecycle_task`` (static) | +3 | Control of the payload running on core 1. | -+-------------------+-------------------------------+----------+-------------------------------------------+ -| `vBleTask`_ | ``ble_task_id`` (static) | +3 | Bluetooth Low Energy Stack | -+-------------------+-------------------------------+----------+-------------------------------------------+ -| `vSerialTask`_ | ``serial_task_id`` | +3 | Serial Output via UART/CDC-ACM/BLE | -+-------------------+-------------------------------+----------+-------------------------------------------+ -| `vApiDispatcher`_ | ``dispatcher_task_id`` | +2 | Epicardium API dispatcher | -+-------------------+-------------------------------+----------+-------------------------------------------+ -| `vLedTask`_ | -/- | +1 | LED Animations | -+-------------------+-------------------------------+----------+-------------------------------------------+ -| `vMAX30001Task`_ | ``max30001_task_id`` (static) | +1 | `MAX30001`_ ECG driver | -+-------------------+-------------------------------+----------+-------------------------------------------+ -| `vBhi160Task`_ | ``bhi160_task_id`` (static) | +1 | `BHI160`_ sensor fusion driver | -+-------------------+-------------------------------+----------+-------------------------------------------+ ++--------------------+-------------------------------+----------+-------------------------------------------+ +| Name | ID Global | Priority | Description | ++====================+===============================+==========+===========================================+ +| `vPmicTask`_ | ``pmic_task_id`` (static) | +4 | Power Management (and Reset Button) | ++--------------------+-------------------------------+----------+-------------------------------------------+ +| `vLifecycleTask`_ | ``lifecycle_task`` (static) | +3 | Control of the payload running on core 1. | ++--------------------+-------------------------------+----------+-------------------------------------------+ +| `vBleTask`_ | ``ble_task_id`` (static) | +3 | Bluetooth Low Energy Stack | ++--------------------+-------------------------------+----------+-------------------------------------------+ +| `vSerialTask`_ | ``serial_task_id`` | +3 | Serial Output via UART/CDC-ACM/BLE | ++--------------------+-------------------------------+----------+-------------------------------------------+ +| `vApiDispatcher`_ | ``dispatcher_task_id`` | +2 | Epicardium API dispatcher | ++--------------------+-------------------------------+----------+-------------------------------------------+ +| `vInterruptsTask`_ | ``interrupts_task`` (static) | +2 | Interrupt dispatcher worker | ++--------------------+-------------------------------+----------+-------------------------------------------+ +| `vLedTask`_ | -/- | +1 | LED Animations | ++--------------------+-------------------------------+----------+-------------------------------------------+ +| `vMAX30001Task`_ | ``max30001_task_id`` (static) | +1 | `MAX30001`_ ECG driver | ++--------------------+-------------------------------+----------+-------------------------------------------+ +| `vBhi160Task`_ | ``bhi160_task_id`` (static) | +1 | `BHI160`_ sensor fusion driver | ++--------------------+-------------------------------+----------+-------------------------------------------+ .. _vPmicTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/pmic.c#L281 .. _vLifecycleTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/lifecycle.c#L361 .. _vBleTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/ble/ble.c#L237 .. _vSerialTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/serial.c#L289 .. _vApiDispatcher: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/dispatcher.c#L25 +.. _vInterruptsTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/interrupts.c#L119 .. _vLedTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/personal_state.c#L58 .. _vMAX30001Task: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/max30001.c#L378 .. _vBhi160Task: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/bhi.c#L419 diff --git a/epicardium/api/control.c b/epicardium/api/control.c index 99c99953344d6b2700e5c657cc4c6ade1fbe9b4e..deff433010bf426c1c8a59bcab5450d7787fe55a 100644 --- a/epicardium/api/control.c +++ b/epicardium/api/control.c @@ -1,6 +1,5 @@ #include "epicardium.h" #include "api/dispatcher.h" -#include "api/interrupt-sender.h" #include "modules/log.h" #include "card10.h" @@ -10,6 +9,7 @@ #include "tmr.h" static void __core1_init(void); +extern void interrupt_trigger_sync(api_int_id_t id); struct core1_info { /* Location of core1's interrupt vector table */ @@ -206,8 +206,13 @@ void core1_boot(void) void core1_trigger_reset(void) { - /* Signal core 1 that we intend to load a new payload. */ - api_interrupt_trigger(EPIC_INT_RESET); + /* + * Signal core 1 that we intend to load a new payload. + * + * This needs to be synchroneous because otherwise we will deadlock + * (Lifecycle task busy-spins and interrupt can never get dispatched). + */ + interrupt_trigger_sync(EPIC_INT_RESET); } void core1_wait_ready(void) diff --git a/epicardium/api/interrupt-sender.c b/epicardium/api/interrupt-sender.c index 5117bea201e2e866e606e42cb8639ed48c162513..fad7ed674ef85f7e0553abbfc86ed1cca6af37d5 100644 --- a/epicardium/api/interrupt-sender.c +++ b/epicardium/api/interrupt-sender.c @@ -1,53 +1,22 @@ #include "api/interrupt-sender.h" #include "api/common.h" #include "tmr_utils.h" - -static bool int_enabled[EPIC_INT_NUM]; - -int api_interrupt_trigger(api_int_id_t id) -{ - if (id >= EPIC_INT_NUM) { - return -EINVAL; - } - - if (int_enabled[id]) { - while (API_CALL_MEM->int_id != (api_int_id_t)(-1)) - ; - - API_CALL_MEM->int_id = id; - TMR_TO_Start(MXC_TMR5, 1, 0); - } - return 0; -} +#include <assert.h> void api_interrupt_init(void) { API_CALL_MEM->int_id = (-1); - - for (int i = 0; i < EPIC_INT_NUM; i++) { - int_enabled[i] = false; - } - - /* Reset interrupt is always enabled */ - int_enabled[EPIC_INT_RESET] = true; } -int epic_interrupt_enable(api_int_id_t int_id) +bool api_interrupt_is_ready(void) { - if (int_id >= EPIC_INT_NUM) { - return -EINVAL; - } - - int_enabled[int_id] = true; - return 0; + return API_CALL_MEM->int_id == (api_int_id_t)(-1); } -int epic_interrupt_disable(api_int_id_t int_id) +void api_interrupt_trigger(api_int_id_t id) { - if (int_id >= EPIC_INT_NUM || int_id == EPIC_INT_RESET) { - return -EINVAL; - } + assert(API_CALL_MEM->int_id == (api_int_id_t)(-1)); - int_enabled[int_id] = false; - return 0; + API_CALL_MEM->int_id = id; + TMR_TO_Start(MXC_TMR5, 1, 0); } diff --git a/epicardium/api/interrupt-sender.h b/epicardium/api/interrupt-sender.h index 419993c72388d15a234d4887a4a71d0a00ccd6d7..d4924c0995b2ed46474f64155db67db9415e7843 100644 --- a/epicardium/api/interrupt-sender.h +++ b/epicardium/api/interrupt-sender.h @@ -2,4 +2,5 @@ #include "api/common.h" void api_interrupt_init(void); -int api_interrupt_trigger(api_int_id_t id); +bool api_interrupt_is_ready(void); +void api_interrupt_trigger(api_int_id_t id); diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index f617054319da5ecc573a3bd29dff011832ef2eaa..8814692a62a06edce3b9db0cf58c6090ba78c86a 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -158,6 +158,10 @@ typedef uint32_t api_int_id_t; * (masked/unmasked) using :c:func:`epic_interrupt_enable` and * :c:func:`epic_interrupt_disable`. * + * These interrupts work similar to hardware interrupts: You will only get a + * single interrupt, even if multiple events occured since the ISR last ran + * (*this behavior is new since version 1.16*). + * * .. warning:: * * Never attempt to call the API from inside an ISR. This might trigger an diff --git a/epicardium/main.c b/epicardium/main.c index c5e2768c37177f97f6a2388bdfdd8b71f10cd591..89054e9e199f9ade4236e7e270a0068ddec67ce9 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -133,6 +133,16 @@ int main(void) &dispatcher_task_id) != pdPASS) { panic("Failed to create %s task!", "API Dispatcher"); } + /* Interrupts */ + if (xTaskCreate( + vInterruptsTask, + (const char *)"Interrupt Dispatcher", + configMINIMAL_STACK_SIZE, + NULL, + tskIDLE_PRIORITY + 2, + NULL) != pdPASS) { + panic("Failed to create %s task!", "Interrupt Dispatcher"); + } /* BLE */ if (ble_shall_start()) { diff --git a/epicardium/modules/bhi.c b/epicardium/modules/bhi.c index 5a5528f5f2ce1c5dde700866847240eb6c69bc5a..baa2d16220be0754bcbb321ef04fa852a43d7a85 100644 --- a/epicardium/modules/bhi.c +++ b/epicardium/modules/bhi.c @@ -11,7 +11,6 @@ #include "task.h" #include "queue.h" -#include "api/interrupt-sender.h" #include "epicardium.h" #include "modules/log.h" #include "modules/modules.h" @@ -320,7 +319,7 @@ bhi160_handle_packet(bhy_data_type_t data_type, bhy_data_generic_t *sensor_data) } if (wakeup) { - api_interrupt_trigger(epic_int); + interrupt_trigger(epic_int); } break; default: diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c index 358154334f1b7f3a2d508e7348e70999aa67257c..6d118fe73ef59007c4e3cb915afdba004e678d56 100644 --- a/epicardium/modules/hardware.c +++ b/epicardium/modules/hardware.c @@ -1,7 +1,6 @@ #include "epicardium.h" #include "api/dispatcher.h" -#include "api/interrupt-sender.h" #include "usb/epc_usb.h" #include "modules/filesystem.h" #include "modules/log.h" @@ -171,7 +170,7 @@ int hardware_early_init(void) /* * API Dispatcher & API Interrupts */ - api_interrupt_init(); + interrupt_init(); api_dispatcher_init(); /* @@ -237,7 +236,7 @@ int hardware_reset(void) /* * API Dispatcher & API Interrupts */ - api_interrupt_init(); + interrupt_init(); api_dispatcher_init(); /* diff --git a/epicardium/modules/interrupts.c b/epicardium/modules/interrupts.c new file mode 100644 index 0000000000000000000000000000000000000000..0ead54fc58b1d6341071cd377a8ab239bbbff8e7 --- /dev/null +++ b/epicardium/modules/interrupts.c @@ -0,0 +1,156 @@ +#include "modules/mutex.h" +#include "modules/log.h" +#include "epicardium.h" +#include "api/interrupt-sender.h" +#include <assert.h> + +struct interrupt_priv { + /* Whether this interrupt can be triggered */ + bool int_enabled[EPIC_INT_NUM]; + /* Whether this interrupt is waiting to be delivered */ + bool int_pending[EPIC_INT_NUM]; + /* Whether any interrupts are currently waiting to be triggered */ + bool has_pending; +}; + +static struct interrupt_priv interrupt_data; +static struct mutex interrupt_mutex; +static TaskHandle_t interrupts_task; + +void interrupt_trigger(api_int_id_t id) +{ + assert(id < EPIC_INT_NUM); + + mutex_lock(&interrupt_mutex); + + if (interrupt_data.int_enabled[id]) { + interrupt_data.int_pending[id] = true; + interrupt_data.has_pending = true; + mutex_unlock(&interrupt_mutex); + xTaskNotifyGive(interrupts_task); + } else { + mutex_unlock(&interrupt_mutex); + } +} + +void interrupt_trigger_sync(api_int_id_t id) +{ + assert(id < EPIC_INT_NUM); + + mutex_lock(&interrupt_mutex); + if (!interrupt_data.int_enabled[id]) + goto out; + + while (!api_interrupt_is_ready()) + ; + + api_interrupt_trigger(id); +out: + mutex_unlock(&interrupt_mutex); +} + +/* + * This function solely exists because of that one use of interrupts that breaks + * the rules: The RTC ALARM interrupt is triggered from a hardware ISR where + * interrupt_trigger_sync() won't work because it needs to lock a mutex. + * + * DO NOT USE THIS FUNCTION IN ANY NEW CODE. + */ +void __attribute__((deprecated)) interrupt_trigger_unsafe(api_int_id_t id) +{ + assert(id < EPIC_INT_NUM); + + if (!interrupt_data.int_enabled[id]) + return; + + while (!api_interrupt_is_ready()) + ; + + api_interrupt_trigger(id); +} + +static void interrupt_set_enabled(api_int_id_t id, bool enabled) +{ + assert(id < EPIC_INT_NUM); + + mutex_lock(&interrupt_mutex); + interrupt_data.int_enabled[id] = enabled; + mutex_unlock(&interrupt_mutex); +} + +void interrupt_init(void) +{ + if (interrupt_mutex.name == NULL) + mutex_create(&interrupt_mutex); + + api_interrupt_init(); + + /* Reset all irqs to disabled */ + for (size_t i = 0; i < EPIC_INT_NUM; i++) { + interrupt_set_enabled(i, false); + } + + /* Reset interrupt is always enabled */ + interrupt_set_enabled(EPIC_INT_RESET, true); +} + +/* Epic-calls {{{ */ +int epic_interrupt_enable(api_int_id_t int_id) +{ + if (int_id >= EPIC_INT_NUM) { + return -EINVAL; + } + + interrupt_set_enabled(int_id, true); + return 0; +} + +int epic_interrupt_disable(api_int_id_t int_id) +{ + if (int_id >= EPIC_INT_NUM || int_id == EPIC_INT_RESET) { + return -EINVAL; + } + + interrupt_set_enabled(int_id, false); + return 0; +} +/* }}} */ + +void vInterruptsTask(void *pvParameters) +{ + interrupts_task = xTaskGetCurrentTaskHandle(); + + while (true) { + mutex_lock(&interrupt_mutex); + + if (!interrupt_data.has_pending) { + /* Wait for a wakeup event from interrupt_trigger() */ + mutex_unlock(&interrupt_mutex); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + mutex_lock(&interrupt_mutex); + } + + while (!api_interrupt_is_ready()) { + mutex_unlock(&interrupt_mutex); + vTaskDelay(pdMS_TO_TICKS(5)); + mutex_lock(&interrupt_mutex); + } + + api_int_id_t current_irq = EPIC_INT_NUM; + for (size_t i = 0; i < EPIC_INT_NUM; i++) { + if (interrupt_data.int_pending[i]) { + current_irq = i; + interrupt_data.int_pending[i] = false; + break; + } + } + + if (current_irq == EPIC_INT_NUM) { + interrupt_data.has_pending = false; + } else if (interrupt_data.int_enabled[current_irq]) { + api_interrupt_trigger(current_irq); + } + + mutex_unlock(&interrupt_mutex); + } +} diff --git a/epicardium/modules/lifecycle.c b/epicardium/modules/lifecycle.c index 2e72880fb892fc38db427c36494b4044d40d1401..76c5648cc20120eca6de28fad2ed4329404943da 100644 --- a/epicardium/modules/lifecycle.c +++ b/epicardium/modules/lifecycle.c @@ -4,7 +4,6 @@ #include "modules/config.h" #include "modules/mutex.h" #include "api/dispatcher.h" -#include "api/interrupt-sender.h" #include "l0der/l0der.h" #include "card10.h" @@ -355,9 +354,17 @@ void vLifecycleTask(void *pvParameters) mutex_unlock(&core1_mutex); - /* If `main.py` exists, start it. Otherwise, start `menu.py`. */ - if (epic_exec("main.py") < 0) { - return_to_menu(); + /* + * If `main.py` exists, start it. Otherwise, start `menu.py`. + * + * We are not using epic_exec() & return_to_menu() here because those + * trigger a reset which is undesirable during startup. + */ + mutex_lock(&core1_mutex); + int ret = load_sync("main.py", false); + mutex_unlock(&core1_mutex); + if (ret < 0) { + load_menu(false); } hardware_init(); diff --git a/epicardium/modules/max30001.c b/epicardium/modules/max30001.c index 5c2ff10f8965e7a9e28c30ab51bbc00fd0af0c45..39379703ec292458a404a63346d4bf6258b04d84 100644 --- a/epicardium/modules/max30001.c +++ b/epicardium/modules/max30001.c @@ -11,7 +11,6 @@ #include "task.h" #include "queue.h" -#include "api/interrupt-sender.h" #include "epicardium.h" #include "modules/log.h" #include "modules/modules.h" @@ -141,7 +140,7 @@ static void max30001_handle_samples(int16_t *sensor_data, int16_t n) LOG_WARN("max30001", "queue full"); } } - api_interrupt_trigger(EPIC_INT_MAX30001_ECG); + interrupt_trigger(EPIC_INT_MAX30001_ECG); } /***** Functions *****/ diff --git a/epicardium/modules/max86150.c b/epicardium/modules/max86150.c index 428d0caf22f8f78511e1e7a0792602af092605e1..03527052c5b372d4a9ca30ae64d767d5ce51a79c 100644 --- a/epicardium/modules/max86150.c +++ b/epicardium/modules/max86150.c @@ -13,7 +13,6 @@ #include "task.h" #include "queue.h" -#include "api/interrupt-sender.h" #include "modules/modules.h" static const gpio_cfg_t max86150_interrupt_pin = { @@ -140,7 +139,10 @@ static int max86150_handle_sample(struct max86150_sensor_data *data) LOG_WARN("max86150", "queue full"); return -EIO; } - return api_interrupt_trigger(EPIC_INT_MAX86150); + + interrupt_trigger(EPIC_INT_MAX86150); + + return 0; } static int max86150_fetch_fifo(void) diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index 474b32930f9b4970fdde4cf65c6891f5eead8a1f..548d8563ea1e7ac2943843931e1733af5fc07dd2 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -9,6 +9,7 @@ module_sources = files( 'gpio.c', 'hardware.c', 'hw-lock.c', + 'interrupts.c', 'leds.c', 'lifecycle.c', 'light_sensor.c', diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index 67557ad4f226aa6ad9a116aa0b940b09889ed2a2..c26a1ce6cf7a2480eb982d8b80ab1215d4c06278 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -4,6 +4,7 @@ #include "FreeRTOS.h" #include "gpio.h" #include "modules/mutex.h" +#include "epicardium.h" #include <stdint.h> #include <stdbool.h> @@ -27,6 +28,15 @@ int hardware_reset(void); void vLifecycleTask(void *pvParameters); void return_to_menu(void); +/* ---------- Interrupts --------------------------------------------------- */ +void interrupt_init(void); +void interrupt_trigger(api_int_id_t id); +void interrupt_trigger_sync(api_int_id_t id); +void interrupt_trigger_unsafe(api_int_id_t id) __attribute__((deprecated( + "interrupt_trigger_unsafe() is racy and only exists for legacy code." +))); +void vInterruptsTask(void *pvParameters); + /* ---------- Serial ------------------------------------------------------- */ #define SERIAL_READ_BUFFER_SIZE 128 #define SERIAL_WRITE_STREAM_BUFFER_SIZE 512 diff --git a/epicardium/modules/rtc.c b/epicardium/modules/rtc.c index b563462dbc242194e7f931721d82fcd21d0b344e..7d75fd169ca88aebb80e3d4def52b586b2684581 100644 --- a/epicardium/modules/rtc.c +++ b/epicardium/modules/rtc.c @@ -1,6 +1,6 @@ #include "epicardium.h" #include "modules/log.h" -#include "api/interrupt-sender.h" +#include "modules/modules.h" #include "FreeRTOS.h" #include "task.h" @@ -84,19 +84,23 @@ void epic_rtc_set_milliseconds(uint64_t milliseconds) monotonic_offset += diff; } +/* We need to use interrupt_trigger_unsafe() here */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" void RTC_IRQHandler(void) { int flags = RTC_GetFlags(); if (flags & MXC_F_RTC_CTRL_ALDF) { RTC_ClearFlags(MXC_F_RTC_CTRL_ALDF); - api_interrupt_trigger(EPIC_INT_RTC_ALARM); + interrupt_trigger_unsafe(EPIC_INT_RTC_ALARM); } else { LOG_WARN("rtc", "Unknown IRQ caught!"); /* Disable IRQ so it does not retrigger */ NVIC_DisableIRQ(RTC_IRQn); } } +#pragma GCC diagnostic pop int epic_rtc_schedule_alarm(uint32_t timestamp) { @@ -107,7 +111,7 @@ int epic_rtc_schedule_alarm(uint32_t timestamp) * immediately. */ if (epic_rtc_get_seconds() >= timestamp) { - api_interrupt_trigger(EPIC_INT_RTC_ALARM); + interrupt_trigger(EPIC_INT_RTC_ALARM); return 0; } diff --git a/epicardium/modules/serial.c b/epicardium/modules/serial.c index 25dd91846c9ea60a2aa26faa67cfce357ada7e9c..e80e30f75f2b718028238dd5270a0d4690b846c0 100644 --- a/epicardium/modules/serial.c +++ b/epicardium/modules/serial.c @@ -1,5 +1,4 @@ #include "epicardium.h" -#include "api/interrupt-sender.h" #include "modules/log.h" #include "modules/modules.h" @@ -280,7 +279,7 @@ void serial_enqueue_char(char chr) { if (chr == 0x3) { /* Control-C */ - api_interrupt_trigger(EPIC_INT_CTRL_C); + interrupt_trigger(EPIC_INT_CTRL_C); } if (xQueueSend(read_queue, &chr, 100) == errQUEUE_FULL) { @@ -288,7 +287,7 @@ void serial_enqueue_char(char chr) vTaskDelay(portTICK_PERIOD_MS * 50); } - api_interrupt_trigger(EPIC_INT_UART_RX); + interrupt_trigger(EPIC_INT_UART_RX); } void vSerialTask(void *pvParameters)