From 0ca995ad8b0fcc28609703c130c2fadf518ee5d4 Mon Sep 17 00:00:00 2001
From: Rahix <rahix@rahix.de>
Date: Thu, 4 Jul 2019 10:04:23 +0200
Subject: [PATCH] feat(epicardium): Enable tickless idle

---
 epicardium/FreeRTOSConfig.h | 25 +++++++++++-
 epicardium/api/common.h     |  1 +
 epicardium/api/dispatcher.c | 31 +++++++++++++--
 epicardium/api/dispatcher.h | 17 ++++++--
 epicardium/main.c           | 63 ++++++++++++++++++++++++------
 epicardium/serial.c         | 77 ++++++++++++++++++++++++++++---------
 6 files changed, 176 insertions(+), 38 deletions(-)

diff --git a/epicardium/FreeRTOSConfig.h b/epicardium/FreeRTOSConfig.h
index 26e51bc4c..76ae165a9 100644
--- a/epicardium/FreeRTOSConfig.h
+++ b/epicardium/FreeRTOSConfig.h
@@ -1,6 +1,9 @@
 #ifndef FREERTOS_CONFIG_H
 #define FREERTOS_CONFIG_H
 
+#define  MXC_ASSERT_ENABLE
+#include "mxc_assert.h"
+
 #include "max32665.h"
 
 /* CMSIS keeps a global updated with current system clock in Hz */
@@ -22,7 +25,14 @@
 #define configMAX_SYSCALL_INTERRUPT_PRIORITY  ( ( unsigned char ) 5 << ( 8 - configPRIO_BITS) )  
 
 /* We want to use preemption to easier integrate components */
-#define configUSE_PREEMPTION        1
+/* TODO: Figure out why turning this on does not work ... */
+#define configUSE_PREEMPTION        0
+
+/*
+ * Tickless idle from the FreeRTOS port + our own hooks (defined further down in
+ * this file)
+ */
+#define configUSE_TICKLESS_IDLE     1
 
 /* TODO: Adjust */
 #define configUSE_IDLE_HOOK         0
@@ -42,4 +52,17 @@
 #define xPortPendSVHandler    PendSV_Handler
 #define xPortSysTickHandler   SysTick_Handler
 
+/* Assert */
+#define configASSERT(x)       MXC_ASSERT(x)
+
+/* Tickless idle hooks */
+typedef uint32_t TickType_t;
+void pre_idle_sleep(TickType_t xExpectedIdleTime);
+#define configPRE_SLEEP_PROCESSING(xModifiableIdleTime) \
+	pre_idle_sleep(xModifiableIdleTime); xModifiableIdleTime = 0
+
+void post_idle_sleep(TickType_t xExpectedIdleTime);
+#define configPOST_SLEEP_PROCESSING(xExpectedIdleTime) \
+	post_idle_sleep(xExpectedIdleTime)
+
 #endif /* FREERTOS_CONFIG_H */
diff --git a/epicardium/api/common.h b/epicardium/api/common.h
index 70b24a35a..5b435fa58 100644
--- a/epicardium/api/common.h
+++ b/epicardium/api/common.h
@@ -1,4 +1,5 @@
 #include <stdint.h>
+#include <stdbool.h>
 
 /*
  * Semaphore used for API synchronization.
diff --git a/epicardium/api/dispatcher.c b/epicardium/api/dispatcher.c
index d499bd16f..a6e5cf415 100644
--- a/epicardium/api/dispatcher.c
+++ b/epicardium/api/dispatcher.c
@@ -18,20 +18,45 @@ int api_dispatcher_init()
 	return ret;
 }
 
-api_id_t api_dispatcher_poll()
+static bool event_ready = false;
+
+bool api_dispatcher_poll_once()
 {
-	api_id_t id = 0;
+	if (event_ready) {
+		return false;
+	}
+
 	while (SEMA_GetSema(_API_SEMAPHORE) == E_BUSY) {}
 
 	if (API_CALL_MEM->call_flag != _API_FLAG_CALLING) {
 		SEMA_FreeSema(_API_SEMAPHORE);
+		return false;
+	}
+
+	event_ready = true;
+	return true;
+}
+
+bool api_dispatcher_poll()
+{
+	if (event_ready) {
+		return true;
+	}
+
+	return api_dispatcher_poll_once();
+}
+
+api_id_t api_dispatcher_exec()
+{
+	if (!event_ready) {
 		return 0;
 	}
 
-	id = API_CALL_MEM->id;
+	api_id_t id = API_CALL_MEM->id;
 	__api_dispatch_call(id, API_CALL_MEM->buffer);
 	API_CALL_MEM->call_flag = _API_FLAG_RETURNED;
 
+	event_ready = false;
 	SEMA_FreeSema(_API_SEMAPHORE);
 
 	/* Notify the caller that we returned */
diff --git a/epicardium/api/dispatcher.h b/epicardium/api/dispatcher.h
index 1385b7194..4098b162b 100644
--- a/epicardium/api/dispatcher.h
+++ b/epicardium/api/dispatcher.h
@@ -7,11 +7,20 @@
 int api_dispatcher_init();
 
 /*
- * Attempt to dispatch a call, if the caller has requested one.
- * Will return 0 if no call was dispatched and the ID of the dispatched
- * call otherwise.
+ * Check whether the other core requested a call.  If this function returns
+ * true, the dispatcher should call api_dispatcher_exec() to actually dispatch
+ * the call.  Consecutive calls to this function will return false.  Use
+ * api_dispatcher_poll() if your need to recheck.
  */
-api_id_t api_dispatcher_poll();
+bool api_dispatcher_poll_once();
+bool api_dispatcher_poll();
+
+/*
+ * Attempt to dispatch a call, if one had been polled using
+ * api_dispatcher_poll().  Will return 0 if no call was dispatched or the ID of
+ * the dispatched call otherwise.
+ */
+api_id_t api_dispatcher_exec();
 
 /* This function is defined by the generated dispatcher code */
 void __api_dispatch_call(api_id_t id, void*buffer);
diff --git a/epicardium/main.c b/epicardium/main.c
index 3490b026d..cc2d0a4f3 100644
--- a/epicardium/main.c
+++ b/epicardium/main.c
@@ -12,17 +12,65 @@
 #include "FreeRTOS.h"
 #include "task.h"
 
+static TaskHandle_t dispatcher_task_id;
+
+/* TODO: Move out of main.c */
 void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b)
 {
 	leds_set(led, r, g, b);
 	leds_update();
 }
 
+/*
+ * This hook is called before FreeRTOS enters tickless idle.
+ */
+void pre_idle_sleep(TickType_t xExpectedIdleTime)
+{
+	if (xExpectedIdleTime > 0) {
+		/*
+		 * WFE because the other core should be able to notify
+		 * epicardium if it wants to issue an API call.
+		 */
+		__asm volatile( "dsb" ::: "memory" );
+		__asm volatile( "wfe" );
+		__asm volatile( "isb" );
+	}
+}
+
+/*
+ * This hook is called after FreeRTOS exits tickless idle.
+ */
+void post_idle_sleep(TickType_t xExpectedIdleTime)
+{
+	/* Check whether a new API call was issued. */
+	if (api_dispatcher_poll_once()) {
+		xTaskNotifyGive(dispatcher_task_id);
+	}
+}
+
+#if 0
+void vPortSuppressTicksAndSleep(TickType_t xExpectedIdleTime)
+{
+	if (xExpectedIdleTime > 0) {
+		__WFE();
+		if (api_dispatcher_poll()) {
+			xTaskNotifyGive(dispatcher_task_id);
+		}
+	}
+}
+#endif
+
+/*
+ * API dispatcher task.  This task will sleep until an API call is issued and
+ * then wake up to dispatch it.
+ */
 void vApiDispatcher(void*pvParameters)
 {
 	while (1) {
-		api_dispatcher_poll();
-		vTaskDelay(portTICK_PERIOD_MS * 10);
+		if (api_dispatcher_poll()) {
+			api_dispatcher_exec();
+		}
+		ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
 	}
 }
 
@@ -84,7 +132,7 @@ int main(void)
 		configMINIMAL_STACK_SIZE,
 		NULL,
 		tskIDLE_PRIORITY  + 2,
-		NULL
+		&dispatcher_task_id
 	) != pdPASS) {
 		printf("Failed to create api dispatcher task!\n");
 		abort();
@@ -97,12 +145,5 @@ int main(void)
 	core1_start();
 
 	vTaskStartScheduler();
-	printf("ERROR: FreeRTOS did not start due to above error!\n");
-
-#if 0
-	while(1) {
-		__WFE();
-		api_dispatcher_poll();
-	}
-#endif
+	printf("ERROR: FreeRTOS did not start due to unknown error!\n");
 }
diff --git a/epicardium/serial.c b/epicardium/serial.c
index a891f5103..0b956422a 100644
--- a/epicardium/serial.c
+++ b/epicardium/serial.c
@@ -3,6 +3,7 @@
 
 #include "serial.h"
 
+#include "max32665.h"
 #include "cdcacm.h"
 #include "uart.h"
 #include "tmr_utils.h"
@@ -11,16 +12,26 @@
 #include "task.h"
 #include "queue.h"
 
-extern mxc_uart_regs_t * ConsoleUart;
+/* Task ID for the serial handler */
+TaskHandle_t serial_task_id;
 
+/* The serial console in use (UART0) */
+extern mxc_uart_regs_t* ConsoleUart;
+/* Read queue, filled by both UART and CDCACM */
 static QueueHandle_t read_queue;
 
+/*
+ * API-call to write a string.  Output goes to both CDCACM and UART
+ */
 void epic_uart_write_str(char*str, intptr_t length)
 {
 	UART_Write(ConsoleUart, (uint8_t*)str, length);
 	cdcacm_write((uint8_t*)str, length);
 }
 
+/*
+ * Blocking API-call to read a character from the queue.
+ */
 char epic_uart_read_chr(void)
 {
 	char chr;
@@ -28,11 +39,39 @@ char epic_uart_read_chr(void)
 	return chr;
 }
 
+/* Interrupt handler needed for SDK UART implementation */
+void UART0_IRQHandler(void)
+{
+	UART_Handler(ConsoleUart);
+}
+
+static void uart_callback(uart_req_t*req, int error)
+{
+	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+	vTaskNotifyGiveFromISR(serial_task_id, &xHigherPriorityTaskWoken);
+	portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+}
+
+static void enqueue_char(char chr)
+{
+	if (chr == 0x3) {
+		/* Control-C */
+		TMR_TO_Start(MXC_TMR5, 1, 0);
+	}
+
+	if (xQueueSend(read_queue, &chr, 100) == errQUEUE_FULL) {
+		/* Queue overran, wait a bit */
+		vTaskDelay(portTICK_PERIOD_MS * 50);
+	}
+}
+
 void vSerialTask(void*pvParameters)
 {
 	static uint8_t buffer[sizeof(char) * SERIAL_READ_BUFFER_SIZE];
 	static StaticQueue_t read_queue_data;
 
+	serial_task_id = xTaskGetCurrentTaskHandle();
+
 	/* Setup read queue */
 	read_queue = xQueueCreateStatic(
 		SERIAL_READ_BUFFER_SIZE,
@@ -44,31 +83,31 @@ void vSerialTask(void*pvParameters)
 	/* Setup UART interrupt */
 	NVIC_ClearPendingIRQ(UART0_IRQn);
 	NVIC_DisableIRQ(UART0_IRQn);
-	NVIC_SetPriority(UART0_IRQn, 1);
+	NVIC_SetPriority(UART0_IRQn, 6);
 	NVIC_EnableIRQ(UART0_IRQn);
 
-	while (1) {
-		char chr;
-
-		/* TODO: Wait for interrupt on either device */
-		vTaskDelay(portTICK_PERIOD_MS * 10);
+	unsigned char data;
+	uart_req_t read_req = {
+		.data     = &data,
+		.len      = 1,
+		.callback = uart_callback,
+	};
 
-		if(UART_NumReadAvail(ConsoleUart) > 0) {
-			chr = UART_ReadByte(ConsoleUart);
-		} else if(cdcacm_num_read_avail() > 0) {
-			chr = cdcacm_read();
-		} else {
-			continue;
+	while (1) {
+		int ret = UART_ReadAsync(ConsoleUart, &read_req);
+		if (ret != E_NO_ERROR && ret != E_BUSY) {
+			printf("error reading uart: %d\n", ret);
+			vTaskDelay(portMAX_DELAY);
 		}
 
-		if (chr == 0x3) {
-			/* Control-C */
-			TMR_TO_Start(MXC_TMR5, 1, 0);
+		ulTaskNotifyTake(pdTRUE, portTICK_PERIOD_MS * 1000);
+
+		if (read_req.num > 0) {
+			enqueue_char(*read_req.data);
 		}
 
-		if (xQueueSend(read_queue, &chr, 100) == errQUEUE_FULL) {
-			/* Queue overran, wait a bit */
-			vTaskDelay(portTICK_PERIOD_MS * 50);
+		while (UART_NumReadAvail(ConsoleUart) > 0) {
+			enqueue_char(UART_ReadByte(ConsoleUart));
 		}
 	}
 }
-- 
GitLab