From 79a0d3d56952fbea42d96a118a53f839dceaa21f Mon Sep 17 00:00:00 2001
From: Rahix <rahix@rahix.de>
Date: Fri, 3 Apr 2020 13:13:07 +0200
Subject: [PATCH] feat(interrupts): Dispatch interrupts asynchroneously

Instead of blocking the triggering task when core 1 is still busy
handling the previous interrupt, offload interrupt dispatching into
a separate task.  This is the first step towards making API-calls
interrupt safe.

Next to the async triggering, the synchroneous mechanism is retained for
special cases where async does not work (e.g. because of spin-locks).
Currently, there is only one such case when resetting core 1 (triggering
EPIC_INT_RESET).

Signed-off-by: Rahix <rahix@rahix.de>
---
 epicardium/api/control.c        | 11 +++++--
 epicardium/main.c               | 10 ++++++
 epicardium/modules/interrupts.c | 57 +++++++++++++++++++++++++++++++++
 epicardium/modules/modules.h    |  2 ++
 4 files changed, 77 insertions(+), 3 deletions(-)

diff --git a/epicardium/api/control.c b/epicardium/api/control.c
index f59978842..deff43301 100644
--- a/epicardium/api/control.c
+++ b/epicardium/api/control.c
@@ -9,7 +9,7 @@
 #include "tmr.h"
 
 static void __core1_init(void);
-extern void interrupt_trigger(api_int_id_t id);
+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. */
-	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/main.c b/epicardium/main.c
index c5e2768c3..89054e9e1 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/interrupts.c b/epicardium/modules/interrupts.c
index e33e35769..0ead54fc5 100644
--- a/epicardium/modules/interrupts.c
+++ b/epicardium/modules/interrupts.c
@@ -1,4 +1,5 @@
 #include "modules/mutex.h"
+#include "modules/log.h"
 #include "epicardium.h"
 #include "api/interrupt-sender.h"
 #include <assert.h>
@@ -14,11 +15,28 @@ struct interrupt_priv {
 
 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;
@@ -97,3 +115,42 @@ int epic_interrupt_disable(api_int_id_t int_id)
 	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/modules.h b/epicardium/modules/modules.h
index 534901c5c..c26a1ce6c 100644
--- a/epicardium/modules/modules.h
+++ b/epicardium/modules/modules.h
@@ -31,9 +31,11 @@ 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
-- 
GitLab