diff --git a/Documentation/how-to-build.rst b/Documentation/how-to-build.rst index da17ca9829536730a7b2134a5650f38d0c604d13..144c2e1f757b66fca5aff56255e2f851ec0b7ef7 100644 --- a/Documentation/how-to-build.rst +++ b/Documentation/how-to-build.rst @@ -68,6 +68,8 @@ firmware features: - ``-Ddebug_prints=true``: Print more verbose debugging log messages - ``-Dble_trace=true``: Enable BLE tracing. This will output lots of status info related to BLE. +- ``-Ddebug_core1=true``: Enable the core 1 SWD lines which are exposed on the + SAO connector. Only use this if you have a debugger which is modified for core 1. .. warning:: diff --git a/epicardium/api/caller.c b/epicardium/api/caller.c index f1ba675fa98b03d0ee6411e2a0271829174d809d..f2f5643abb62e6497c19e7e831ccd1ad1a60525f 100644 --- a/epicardium/api/caller.c +++ b/epicardium/api/caller.c @@ -53,3 +53,66 @@ void *_api_call_transact(void *buffer) return API_CALL_MEM->buffer; } + +__attribute__((noreturn)) void epic_exit(int ret) +{ + /* + * Call __epic_exit() and then jump to the reset routine/ + */ + void *buffer; + + buffer = _api_call_start(API_SYSTEM_EXIT, sizeof(int)); + *(int *)buffer = ret; + _api_call_transact(buffer); + + API_CALL_MEM->reset_stub(); + + /* unreachable */ + while (1) + ; +} + +int epic_exec(char *name) +{ + /* + * Call __epic_exec(). If it succeeds, jump to the reset routine. + * Otherwise, return the error code. + */ + void *buffer; + + buffer = _api_call_start(API_SYSTEM_EXEC, sizeof(char *)); + *(char **)buffer = name; + int ret = *(int *)_api_call_transact(buffer); + + if (ret < 0) { + return ret; + } + + API_CALL_MEM->reset_stub(); + + /* unreachable */ + while (1) + ; +} + +int api_fetch_args(char *buf, size_t cnt) +{ + if (API_CALL_MEM->id != 0) { + /* + * When any call happened before the args are fetched, they are + * overwritten and no longer accessible. + */ + return (-1); + } + + if (API_CALL_MEM->buffer[0x20] == '\0') { + return 0; + } + + int i; + for (i = 0; i < cnt && API_CALL_MEM->buffer[i + 0x20] != '\0'; i++) { + buf[i] = API_CALL_MEM->buffer[i + 0x20]; + } + + return i - 1; +} diff --git a/epicardium/api/caller.h b/epicardium/api/caller.h index d9192c85e133502c005d2e868eebbf39598dd7ff..93bd3f3d71fb88cc81481c939af6b18fb6e496b0 100644 --- a/epicardium/api/caller.h +++ b/epicardium/api/caller.h @@ -27,3 +27,12 @@ void *_api_call_start(api_id_t id, uintptr_t size); * - Pointer to a buffer containing the return value */ void *_api_call_transact(void *buffer); + +/* + * Fetch arguments from the API buffer. This function will only work properly + * directly after startup of core 1. If api_fetch_args() is called after other + * calls have already happened, it will return -1. + * + * Otherwise it will return the length of data which was read. + */ +int api_fetch_args(char *buf, size_t cnt); diff --git a/epicardium/api/common.h b/epicardium/api/common.h index c884f37c48aaa7c6b26b64d5961f99df7a07c6f2..ff0c9f3abf4da6822ca89c890c3a6bb55fab7ce6 100644 --- a/epicardium/api/common.h +++ b/epicardium/api/common.h @@ -8,7 +8,8 @@ * Semaphore used for API synchronization. * TODO: Replace this with a LDREX/STREX based implementation */ -#define _API_SEMAPHORE 0 +#define _API_SEMAPHORE 0 +#define _CONTROL_SEMAPHORE 1 /* Type of API IDs */ typedef uint32_t api_id_t; @@ -19,6 +20,13 @@ typedef uint32_t api_id_t; /* Layout of the shared memory for API calls */ struct api_call_mem { + /* + * Reset stub. The reset stub is a small function provided by + * epicardium that should be called by a payload when receiving the + * reset interrupt. + */ + void (*reset_stub)(); + /* * Flag for synchronization of API calls. When this flag * is set, the caller has issued a call and is waiting for diff --git a/epicardium/api/control.c b/epicardium/api/control.c new file mode 100644 index 0000000000000000000000000000000000000000..daa1ff52ea81cb897e8c22efc1c024d6cc4d96ec --- /dev/null +++ b/epicardium/api/control.c @@ -0,0 +1,233 @@ +#include "epicardium.h" +#include "api/dispatcher.h" +#include "api/interrupt-sender.h" +#include "modules/log.h" + +#include "card10.h" + +#include "max32665.h" +#include "sema.h" +#include "tmr.h" + +static void __core1_init(void); + +struct core1_info { + /* Location of core1's interrupt vector table */ + volatile uintptr_t ivt_addr; + /* Whether core 1 is ready for a new IVT */ + volatile bool ready; +}; + +/* + * Information passing structure for controlling core 1. + */ +static volatile struct core1_info core1_info = { + .ivt_addr = 0x00, + .ready = false, +}; + +/* + * Minimal IVT needed for initial startup. This IVT only contains the initial + * stack pointer and reset-handler and is used to startup core 1. Afterwards, + * the payload's IVT is loaded into VTOR and used from then on. + */ +static uintptr_t core1_initial_ivt[] = { + /* Initial Stack Pointer */ + 0x20080000, + /* Reset Handler */ + (uintptr_t)__core1_reset, +}; + +/* + * Reset Handler + * + * Calls __core1_init() to reset & prepare the core for loading a new payload. + */ +__attribute__((naked)) void __core1_reset(void) +{ + __asm volatile("mov sp, %0\n\t" + : /* No Outputs */ + : "r"(core1_initial_ivt[0])); + __core1_init(); +} + +/* + * Init core 1. This function will reset the core and wait for a new IVT + * address from Epicardium. Once this address is received, it will start + * execution with the supplied reset handler. + */ +void __core1_init(void) +{ + /* + * Clear any pending API interrupts. + */ + TMR_IntClear(MXC_TMR5); + + /* + * Reset Interrupts + * + * To ensure proper operation of the new payload, disable all interrupts + * and clear all pending ones. + */ + for (int i = 0; i < MXC_IRQ_EXT_COUNT; i++) { + NVIC_DisableIRQ(i); + NVIC_ClearPendingIRQ(i); + NVIC_SetPriority(i, 0); + } + + /* + * Check whether we catched the core during an interrupt. If this is + * the case, try returning from the exception handler first and call + * __core1_reset() again in thread context. + */ + if ((SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0) { + /* + * Construct an exception frame so the CPU will jump back to our + * __core1_reset() function once we exit from the exception + * handler. + * + * To exit the exception, a special "EXC_RETURN" value is loaded + * into the link register and then branched to. + */ + __asm volatile( + "ldr r0, =0x41000000\n\t" + "ldr r1, =0\n\t" + "push { r0 }\n\t" /* xPSR */ + "push { %0 }\n\t" /* PC */ + "push { %0 }\n\t" /* LR */ + "push { r1 }\n\t" /* R12 */ + "push { r1 }\n\t" /* R3 */ + "push { r1 }\n\t" /* R2 */ + "push { r1 }\n\t" /* R1 */ + "push { r1 }\n\t" /* R0 */ + + "ldr lr, =0xFFFFFFF9\n\t" + "bx lr\n\t" + : /* No Outputs */ + : "r"((uintptr_t)__core1_reset) + : "pc", "lr"); + + /* unreachable */ + while (1) + ; + } + + /* Wait for the IVT address */ + while (1) { + while (SEMA_GetSema(_CONTROL_SEMAPHORE) == E_BUSY) { + } + + __DMB(); + __ISB(); + + /* + * The IVT address is reset to 0 by Epicardium before execution + * gets here. Once a new address has been set, core 1 can use + * the new IVT. + */ + if (core1_info.ivt_addr != 0x00) { + break; + } + + /* Signal that we are ready for an IVT address */ + core1_info.ready = true; + + SEMA_FreeSema(_CONTROL_SEMAPHORE); + + __WFE(); + } + + uintptr_t *ivt = (uintptr_t *)core1_info.ivt_addr; + core1_info.ivt_addr = 0x00; + + SEMA_FreeSema(_CONTROL_SEMAPHORE); + + /* + * Reset the call-flag before entering the payload so API calls behave + * properly. This is necessary because epic_exec() will set the flag + * to "returning" on exit. + */ + API_CALL_MEM->call_flag = _API_FLAG_IDLE; + + /* + * Set the IVT + */ + SCB->VTOR = (uintptr_t)ivt; + + /* + * Clear any pending API interrupts. + */ + TMR_IntClear(MXC_TMR5); + NVIC_ClearPendingIRQ(TMR5_IRQn); + + /* + * Jump to payload's reset handler + */ + __asm volatile( + "ldr r0, %0\n\t" + "blx r0\n\r" + : /* No Outputs */ + : "m"(*(ivt + 1)) + : "r0"); +} + +void core1_boot(void) +{ + /* + * Boot using the initial IVT. This will place core 1 into a loop, + * waiting for a payload. + */ + core1_start(&core1_initial_ivt); +} + +void core1_reset(void) +{ + /* Signal core 1 that we intend to load a new payload. */ + api_interrupt_trigger(EPIC_INT_RESET); + + /* Wait for the core to accept */ + while (1) { + while (SEMA_GetSema(_CONTROL_SEMAPHORE) == E_BUSY) { + } + + /* + * core 1 will set the ready flag once it is spinning in the + * above loop, waiting for a new IVT. + */ + if (core1_info.ready) { + break; + } + + SEMA_FreeSema(_CONTROL_SEMAPHORE); + + for (int i = 0; i < 10000; i++) { + } + } + + /* + * TODO: If the other core does not respond within a certain grace + * period, we need to force it into our desired state by overwriting + * all of its memory. Yes, I don't like this method either ... + */ +} + +void core1_load(void *ivt, char *args) +{ + /* If the core is currently in an API call, reset it. */ + API_CALL_MEM->call_flag = _API_FLAG_IDLE; + API_CALL_MEM->id = 0; + API_CALL_MEM->int_id = (-1); + + api_prepare_args(args); + + core1_info.ivt_addr = (uintptr_t)ivt; + core1_info.ready = false; + + __DMB(); + __ISB(); + + SEMA_FreeSema(_CONTROL_SEMAPHORE); + + __SEV(); + __WFE(); +} diff --git a/epicardium/api/dispatcher.c b/epicardium/api/dispatcher.c index 4675c8d4f53f1e90b2d0809cb5979589e7a58af1..4ffac4220a9aa0ed473a5e7cf911cb092a7135d0 100644 --- a/epicardium/api/dispatcher.c +++ b/epicardium/api/dispatcher.c @@ -1,14 +1,26 @@ -#include <stdlib.h> -#include "sema.h" #include "api/dispatcher.h" + #include "max32665.h" +#include "sema.h" + +#include <stdlib.h> +#include <string.h> + +/* This function is defined by the generated dispatcher code */ +void __api_dispatch_call(api_id_t id, void *buffer); + +static volatile bool event_ready = false; int api_dispatcher_init() { int ret; - ret = SEMA_Init(NULL); - API_CALL_MEM->call_flag = _API_FLAG_IDLE; + ret = SEMA_Init(NULL); + SEMA_FreeSema(_API_SEMAPHORE); + API_CALL_MEM->reset_stub = __core1_reset; + API_CALL_MEM->call_flag = _API_FLAG_IDLE; + API_CALL_MEM->id = 0; + API_CALL_MEM->int_id = (-1); /* * Enable TX events for both cores. @@ -20,8 +32,6 @@ int api_dispatcher_init() return ret; } -static bool event_ready = false; - bool api_dispatcher_poll_once() { if (event_ready) { @@ -68,3 +78,15 @@ api_id_t api_dispatcher_exec() return id; } + +void api_prepare_args(char *args) +{ + /* + * The args are stored with an offset of 0x20 to make sure they won't + * collide with any integer return value of API calls like epic_exec(). + */ + API_CALL_MEM->id = 0; + for (int i = 0; i <= strlen(args); i++) { + API_CALL_MEM->buffer[i + 0x20] = args[i]; + } +} diff --git a/epicardium/api/dispatcher.h b/epicardium/api/dispatcher.h index a67aa0c40df25968f51809da9c4401b5c3ef1a7a..2299f54df34f5ac0d13e187de1bb712ae2af1b34 100644 --- a/epicardium/api/dispatcher.h +++ b/epicardium/api/dispatcher.h @@ -22,5 +22,25 @@ bool api_dispatcher_poll(); */ 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); +/* + * Fill the API buffer with data for l0dable/pycardium startup. + * + * The data is a NULL-terminated string. + */ +void api_prepare_args(char *args); + +/********************************************************************* + * core 1 control * + *********************************************************************/ + +/* Startup core1 into a state where it is ready to receive a payload. */ +void core1_boot(void); + +/* Reset core 1 into a state where it can accept a new payload */ +void core1_reset(void); + +/* Load a payload into core 1 */ +void core1_load(void *ivt, char *args); + +/* core 1 reset stub. See epicardium/api/control.c for details. */ +void __core1_reset(void); diff --git a/epicardium/api/genapi.py b/epicardium/api/genapi.py index 5fd546a6f2bfcaea5d50fc2d5fd890e460454f0a..c6cefc927bd7a1bac3729208302f91a9c57811d5 100644 --- a/epicardium/api/genapi.py +++ b/epicardium/api/genapi.py @@ -231,6 +231,9 @@ void __dispatch_isr(api_int_id_t id) f_client.write(tmp.format(**isr)) tmp = """\ + case (-1): + /* Ignore a spurious interrupt */ + break; default: epic_isr_default_handler(id); break; diff --git a/epicardium/api/interrupt-receiver.c b/epicardium/api/interrupt-receiver.c index c212a402d0a564375bddc193fd7ccf7c50507a81..f9856423fd1a51211752dcd810b608fb549f9fcd 100644 --- a/epicardium/api/interrupt-receiver.c +++ b/epicardium/api/interrupt-receiver.c @@ -10,5 +10,12 @@ void TMR5_IRQHandler(void) { TMR_IntClear(MXC_TMR5); __dispatch_isr(API_CALL_MEM->int_id); - API_CALL_MEM->int_id = 0; + API_CALL_MEM->int_id = (-1); +} + +/* Reset Handler */ +void __epic_isr_reset(void) +{ + API_CALL_MEM->int_id = (-1); + API_CALL_MEM->reset_stub(); } diff --git a/epicardium/api/interrupt-sender.c b/epicardium/api/interrupt-sender.c index cabd3bcb2b96f6f79c65a0a9722bd05860cf860e..d531846d89fcdfaf634d1d0fa355d057a9125db3 100644 --- a/epicardium/api/interrupt-sender.c +++ b/epicardium/api/interrupt-sender.c @@ -11,8 +11,9 @@ int api_interrupt_trigger(api_int_id_t id) } if (int_enabled[id]) { - while (API_CALL_MEM->int_id) + while (API_CALL_MEM->int_id != (-1)) ; + API_CALL_MEM->int_id = id; TMR_TO_Start(MXC_TMR5, 1, 0); } @@ -21,11 +22,14 @@ int api_interrupt_trigger(api_int_id_t id) void api_interrupt_init(void) { - API_CALL_MEM->int_id = 0; + 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) @@ -40,7 +44,7 @@ int epic_interrupt_enable(api_int_id_t int_id) int epic_interrupt_disable(api_int_id_t int_id) { - if (int_id >= EPIC_INT_NUM) { + if (int_id >= EPIC_INT_NUM || int_id == EPIC_INT_RESET) { return -EINVAL; } diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 2c65e376d4f3741592d34e1e64f57cd3c9714db2..c680d55926adf7af5202c7e498873e7ea6d10472 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -29,8 +29,8 @@ typedef _Bool bool; */ /* clang-format off */ -#define API_SYSTEM_EXIT 0x1 /* TODO */ -#define API_SYSTEM_EXEC 0x2 /* TODO */ +#define API_SYSTEM_EXIT 0x1 +#define API_SYSTEM_EXEC 0x2 #define API_INTERRUPT_ENABLE 0xA #define API_INTERRUPT_DISABLE 0xB @@ -116,7 +116,7 @@ API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id)); */ /* clang-format off */ -/** Reset Handler? **TODO** */ +/** Reset Handler */ #define EPIC_INT_RESET 0 /** ``^C`` interrupt. See :c:func:`epic_isr_ctrl_c` for details. */ #define EPIC_INT_CTRL_C 1 @@ -129,8 +129,59 @@ API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id)); #define EPIC_INT_NUM 4 /* clang-format on */ -API_ISR(EPIC_INT_RESET, epic_isr_reset); +/* + * "Reset Handler*. This isr is implemented by the API caller and is used to + * reset the core for loading a new payload. + * + * Just listed here for completeness. You don't need to implement this yourself. + */ +API_ISR(EPIC_INT_RESET, __epic_isr_reset); + +/** + * Core API + * ======== + * The following functions control execution of code on core 1. + */ + +/** + * Stop execution of the current payload and return to the menu. + * + * :param int ret: Return code. + * :return: :c:func:`epic_exit` will never return. + */ +void epic_exit(int ret) __attribute__((noreturn)); + +/* + * The actual epic_exit() function is not an API call because it needs special + * behavior. The underlying call is __epic_exit() which returns. After calling + * this API function, epic_exit() will enter the reset handler. + */ +API(API_SYSTEM_EXIT, void __epic_exit(int ret)); +/** + * Stop execution of the current payload and immediately start another payload. + * + * :param char* name: Name (path) of the new payload to start. This can either + * be: + * + * - A path to an ``.elf`` file (l0dable). + * - A path to a ``.py`` file (will be loaded using Pycardium). + * - A path to a directory (assumed to be a Python module, execution starts + * with ``__init__.py`` in this folder). + * + * :return: :c:func:`epic_exec` will only return in case loading went wrong. + * The following error codes can be returned: + * + * - ``-ENOENT``: File not found. + * - ``-ENOEXEC``: File not a loadable format. + */ +int epic_exec(char *name); + +/* + * Underlying API call for epic_exec(). The function is not an API call itself + * because it needs special behavior when loading a new payload. + */ +API(API_SYSTEM_EXEC, int __epic_exec(char *name)); /** * UART/Serial Interface diff --git a/epicardium/main.c b/epicardium/main.c index 56f532a675960a4f86c375356ab795603a8f5c7b..c70ea936532c0bf85ce913186e90ca0241fcf8b3 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -6,12 +6,12 @@ #include "max32665.h" #include "uart.h" #include "cdcacm.h" +#include "gpio.h" #include "card10.h" #include "pmic.h" #include "leds.h" #include "api/dispatcher.h" -#include "l0der/l0der.h" #include "modules/modules.h" #include "modules/log.h" #include "modules/stream.h" @@ -30,28 +30,31 @@ TaskHandle_t dispatcher_task_id; void vBleTask(void *pvParameters); -/* - * API dispatcher task. This task will sleep until an API call is issued and - * then wake up to dispatch it. - */ -void vApiDispatcher(void *pvParameters) -{ - LOG_DEBUG("dispatcher", "Ready."); - while (1) { - if (api_dispatcher_poll()) { - api_dispatcher_exec(); - } - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - } -} - int main(void) { LOG_INFO("startup", "Epicardium startup ..."); LOG_INFO("startup", "Version " CARD10_VERSION); - card10_init(); - card10_diag(); + hardware_early_init(); + +#ifdef CARD10_DEBUG_CORE1 + LOG_WARN("startup", "Core 1 Debugger Mode"); + static const gpio_cfg_t swclk = { + PORT_0, + PIN_7, + GPIO_FUNC_ALT3, + GPIO_PAD_NONE, + }; + static const gpio_cfg_t swdio = { + PORT_0, + PIN_6, + GPIO_FUNC_ALT3, + GPIO_PAD_NONE, + }; + + GPIO_Config(&swclk); + GPIO_Config(&swdio); +#endif /* CARD10_DEBUG_CORE1 */ gfx_copy_region_raw( &display_screen, 0, 0, 160, 80, 2, (const void *)(Heart) @@ -69,6 +72,9 @@ int main(void) api_interrupt_init(); stream_init(); + LOG_INFO("startup", "Initializing dispatcher ..."); + api_dispatcher_init(); + LOG_INFO("startup", "Initializing tasks ..."); /* Serial */ @@ -123,33 +129,20 @@ int main(void) abort(); } - LOG_INFO("startup", "Initializing dispatcher ..."); - api_dispatcher_init(); - /* light sensor */ LOG_INFO("startup", "starting light sensor ..."); epic_light_sensor_run(); - /* - * See if there's a l0dable.elf to run. If not, run pycardium. - * This is temporary until epicardium gets a l0dable API from pycardium. - */ - const char *l0dable = "l0dable.elf"; - if (f_stat(l0dable, NULL) == FR_OK) { - LOG_INFO("startup", "Running %s ...", l0dable); - struct l0dable_info info; - int res = l0der_load_path(l0dable, &info); - if (res != 0) { - LOG_ERR("startup", "l0der failed: %d\n", res); - } else { - LOG_INFO( - "startup", "Starting %s on core1 ...", l0dable - ); - core1_start(info.isr_vector); - } - } else { - LOG_INFO("startup", "Starting pycardium on core1 ..."); - core1_start((void *)0x10080000); + /* Lifecycle */ + if (xTaskCreate( + vLifecycleTask, + (const char *)"Lifecycle", + configMINIMAL_STACK_SIZE * 4, + NULL, + tskIDLE_PRIORITY + 1, + NULL) != pdPASS) { + LOG_CRIT("startup", "Failed to create %s task!", "Lifecycle"); + abort(); } LOG_INFO("startup", "Starting FreeRTOS ..."); diff --git a/epicardium/meson.build b/epicardium/meson.build index c9b128f7289f791bc4c8bbd2040d7b8d10e1d710..220f0bae63c7f84e60aee6002760c8271c7587de 100644 --- a/epicardium/meson.build +++ b/epicardium/meson.build @@ -37,8 +37,9 @@ api_dispatcher_lib = static_library( 'api-dispatcher', 'api/dispatcher.c', 'api/interrupt-sender.c', + 'api/control.c', api[1], # Dispatcher - dependencies: periphdriver, + dependencies: [libcard10, periphdriver], ) ########################################################################## diff --git a/epicardium/modules/dispatcher.c b/epicardium/modules/dispatcher.c new file mode 100644 index 0000000000000000000000000000000000000000..60f1c1f818dcd8f41ddeb1f4b4a11b2e2400e938 --- /dev/null +++ b/epicardium/modules/dispatcher.c @@ -0,0 +1,34 @@ +#include "modules/log.h" + +#include "api/dispatcher.h" + +#include "FreeRTOS.h" +#include "task.h" +#include "semphr.h" + +#define TIMEOUT pdMS_TO_TICKS(2000) + +static StaticSemaphore_t api_mutex_data; +SemaphoreHandle_t api_mutex = NULL; + +/* + * API dispatcher task. This task will sleep until an API call is issued and + * then wake up to dispatch it. + */ +void vApiDispatcher(void *pvParameters) +{ + api_mutex = xSemaphoreCreateMutexStatic(&api_mutex_data); + + LOG_DEBUG("dispatcher", "Ready."); + while (1) { + if (api_dispatcher_poll()) { + if (xSemaphoreTake(api_mutex, TIMEOUT) != pdTRUE) { + LOG_ERR("dispatcher", "API mutex blocked"); + continue; + } + api_dispatcher_exec(); + xSemaphoreGive(api_mutex); + } + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } +} diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c new file mode 100644 index 0000000000000000000000000000000000000000..ee369248a17788907875d346fb5f3b4e9de9dc52 --- /dev/null +++ b/epicardium/modules/hardware.c @@ -0,0 +1,40 @@ +#include "modules/modules.h" + +#include "card10.h" + +/* + * Early init is called at the very beginning and is meant for modules which + * absolutely need to start as soon as possible. hardware_early_init() blocks + * which means code in here should be fast. + */ +int hardware_early_init(void) +{ + card10_init(); + return 0; +} + +/* + * hardware_init() is called after the core has been bootstrapped and is meant + * for less critical initialization steps. Modules which initialize here should + * be robust against a l0dable using their API before initialization is done. + * + * Ideally, acquire a lock in hardware_early_init() and release it in + * hardware_init() once initialization is done. + */ +int hardware_init(void) +{ + return 0; +} + +/* + * hardware_reset() is called whenever a new l0dable is started. hardware_reset() + * should bring all peripherals back into a known initial state. This does not + * necessarily mean resetting the peripheral entirely but hardware_reset() + * should at least bring the API facing part of a peripheral back into the state + * a fresh booted l0dable expects. + */ +int hardware_reset(void) +{ + card10_init(); + return 0; +} diff --git a/epicardium/modules/lifecycle.c b/epicardium/modules/lifecycle.c new file mode 100644 index 0000000000000000000000000000000000000000..13ba9704e5d068edce4dc85fb3a26c610b662964 --- /dev/null +++ b/epicardium/modules/lifecycle.c @@ -0,0 +1,315 @@ +#include "epicardium.h" +#include "modules/log.h" +#include "modules/modules.h" +#include "api/dispatcher.h" +#include "l0der/l0der.h" + +#include "card10.h" + +#include "FreeRTOS.h" +#include "task.h" +#include "semphr.h" + +#include <string.h> +#include <stdbool.h> +#include <stdbool.h> + +#define PYCARDIUM_IVT (void *)0x10080000 +#define BLOCK_WAIT pdMS_TO_TICKS(1000) +/* + * Loading an empty filename into Pycardium will drop straight into the + * interpreter. This define is used to make it more clear when we intend + * to go into the interpreter. + */ +#define PYINTERPRETER "" + +static TaskHandle_t lifecycle_task = NULL; +static StaticSemaphore_t core1_mutex_data; +static SemaphoreHandle_t core1_mutex = NULL; + +enum payload_type { + PL_INVALID = 0, + PL_PYTHON_SCRIPT = 1, + PL_PYTHON_DIR = 2, + PL_PYTHON_INTERP = 3, + PL_L0DABLE = 4, +}; + +struct load_info { + bool do_reset; + enum payload_type type; + char name[256]; +}; +static volatile struct load_info async_load = { + .do_reset = false, + .name = { 0 }, + .type = PL_INVALID, +}; + +/* Helpers {{{ */ + +/* + * Check if the payload is a valid file (or module) and if so, return its type. + */ +static int load_stat(char *name) +{ + size_t name_len = strlen(name); + + if (name_len == 0) { + return PL_PYTHON_INTERP; + } + + struct epic_stat stat; + if (epic_file_stat(name, &stat) < 0) { + return -ENOENT; + } + + if (stat.type == EPICSTAT_DIR) { + /* This might be a python module. */ + return PL_PYTHON_DIR; + } + + if (strcmp(name + name_len - 3, ".py") == 0) { + /* A python script */ + return PL_PYTHON_SCRIPT; + } else if (strcmp(name + name_len - 4, ".elf") == 0) { + return PL_L0DABLE; + } + + return -ENOEXEC; +} + +/* + * Actually load a payload into core 1. Optionally reset the core first. + */ +static int do_load(struct load_info *info) +{ + if (*info->name == '\0') { + LOG_INFO("lifecycle", "Loading Python interpreter ..."); + } else { + LOG_INFO("lifecycle", "Loading \"%s\" ...", info->name); + } + + if (xSemaphoreTake(api_mutex, BLOCK_WAIT) != pdTRUE) { + LOG_ERR("lifecycle", "API blocked"); + return -EBUSY; + } + + if (info->do_reset) { + LOG_DEBUG("lifecycle", "Triggering core 1 reset."); + core1_reset(); + + api_dispatcher_init(); + } + + switch (info->type) { + case PL_PYTHON_SCRIPT: + case PL_PYTHON_DIR: + case PL_PYTHON_INTERP: + core1_load(PYCARDIUM_IVT, info->name); + break; + case PL_L0DABLE: + /* + * Always reset when loading a l0dable to make sure the memory + * space is absolutely free. + */ + core1_reset(); + + struct l0dable_info l0dable; + int res = l0der_load_path(info->name, &l0dable); + if (res != 0) { + LOG_ERR("lifecycle", "l0der failed: %d\n", res); + xSemaphoreGive(api_mutex); + return -ENOEXEC; + } + core1_load(l0dable.isr_vector, ""); + + break; + default: + LOG_ERR("lifecyle", + "Attempted to load invalid payload (%s)", + info->name); + xSemaphoreGive(api_mutex); + return -EINVAL; + } + + xSemaphoreGive(api_mutex); + return 0; +} + +/* + * Do a synchroneous load. + */ +static int load_sync(char *name, bool reset) +{ + int ret = load_stat(name); + if (ret < 0) { + return ret; + } + + struct load_info info = { + .name = { 0 }, + .type = ret, + .do_reset = reset, + }; + + strncpy(info.name, name, sizeof(info.name)); + + return do_load(&info); +} + +/* + * Do an asynchroneous load. This will return immediately if the payload seems + * valid and call the lifecycle task to actually perform the load later. + */ +static int load_async(char *name, bool reset) +{ + int ret = load_stat(name); + if (ret < 0) { + return ret; + } + + async_load.type = ret; + async_load.do_reset = reset; + + strncpy((char *)async_load.name, name, sizeof(async_load.name)); + + if (lifecycle_task != NULL) { + xTaskNotifyGive(lifecycle_task); + } + + return 0; +} + +/* + * Go back to the menu. + */ +static void load_menu(bool reset) +{ + LOG_INFO("lifecycle", "Into the menu"); + + if (xSemaphoreTake(core1_mutex, BLOCK_WAIT) != pdTRUE) { + LOG_ERR("lifecycle", + "Can't load because mutex is blocked (menu)."); + return; + } + + int ret = load_async("menu.py", reset); + if (ret < 0) { + /* TODO: Write default menu */ + LOG_WARN("lifecycle", "No menu script found."); + load_async(PYINTERPRETER, reset); + } + + xSemaphoreGive(core1_mutex); +} +/* Helpers }}} */ + +/* API {{{ */ +/* + * This is NOT the epic_exec() called from Pycardium, but an implementation of + * the same call for use in Epicardium. This function is synchroneous and will + * wait until the call returns. + */ +int epic_exec(char *name) +{ + if (xSemaphoreTake(core1_mutex, BLOCK_WAIT) != pdTRUE) { + LOG_ERR("lifecycle", + "Can't load because mutex is blocked (epi exec)."); + return -EBUSY; + } + + int ret = load_sync(name, true); + xSemaphoreGive(core1_mutex); + return ret; +} + +/* + * This is the underlying call for epic_exec() from Pycardium. It is + * asynchroneous and will return early to allow Pycardium (or a l0dable) to jump + * to the reset handler. + * + * The lifecycle task will deal with actually loading the new payload. + */ +int __epic_exec(char *name) +{ + if (xSemaphoreTake(core1_mutex, BLOCK_WAIT) != pdTRUE) { + LOG_ERR("lifecycle", + "Can't load because mutex is blocked (1 exec)."); + return -EBUSY; + } + int ret = load_async(name, false); + xSemaphoreGive(core1_mutex); + return ret; +} + +/* + * This is the underlying call for epic_exit() from Pycardium. It is + * asynchroneous and will return early to allow Pycardium (or a l0dable) to jump + * to the reset handler. + * + * The lifecycle task will deal with actually loading the new payload. + */ +void __epic_exit(int ret) +{ + if (ret == 0) { + LOG_INFO("lifecycle", "Payload returned successfully"); + } else { + LOG_WARN("lifecycle", "Payload returned with %d.", ret); + } + + load_menu(false); +} + +/* + * This function can be used in Epicardium to jump back to the menu. + * + * It is asynchroneous and will return immediately. The lifecycle task will + * take care of actually jumping back. + */ +void return_to_menu(void) +{ + load_menu(true); +} +/* API }}} */ + +void vLifecycleTask(void *pvParameters) +{ + lifecycle_task = xTaskGetCurrentTaskHandle(); + core1_mutex = xSemaphoreCreateMutexStatic(&core1_mutex_data); + + if (xSemaphoreTake(core1_mutex, 0) != pdTRUE) { + LOG_CRIT( + "lifecycle", "Failed to acquire mutex after creation." + ); + vTaskDelay(portMAX_DELAY); + } + + LOG_INFO("lifecycle", "Booting core 1 ..."); + core1_boot(); + vTaskDelay(pdMS_TO_TICKS(10)); + + xSemaphoreGive(core1_mutex); + + /* If `main.py` exists, start it. Otherwise, start `menu.py`. */ + if (epic_exec("main.py") < 0) { + return_to_menu(); + } + + hardware_init(); + + /* When triggered, reset core 1 to menu */ + while (1) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + if (xSemaphoreTake(core1_mutex, BLOCK_WAIT) != pdTRUE) { + LOG_ERR("lifecycle", + "Can't load because mutex is blocked (task)."); + continue; + } + + do_load((struct load_info *)&async_load); + + xSemaphoreGive(core1_mutex); + } +} diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index b3cf4eb2ee679963768a76a54834208de0a30ad8..d7c9f40b90fe174bc81cba76cd928a210a4c2b75 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -1,7 +1,10 @@ module_sources = files( + 'dispatcher.c', 'display.c', 'fileops.c', + 'hardware.c', 'leds.c', + 'lifecycle.c', 'light_sensor.c', 'log.c', 'pmic.c', diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index 4a7bc2557aea29d2d79a96e0c00cefc786f917f5..35968547f77fb7180548ec178e3484bff37ca08e 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -1,12 +1,28 @@ #ifndef MODULES_H #define MODULES_H +#include "FreeRTOS.h" +#include "semphr.h" + #include <stdint.h> +/* ---------- Dispatcher --------------------------------------------------- */ +void vApiDispatcher(void *pvParameters); +extern SemaphoreHandle_t api_mutex; +extern TaskHandle_t dispatcher_task_id; + +/* ---------- Hardware Init & Reset ---------------------------------------- */ +int hardware_early_init(void); +int hardware_init(void); +int hardware_reset(void); + +/* ---------- Lifecycle ---------------------------------------------------- */ +void vLifecycleTask(void *pvParameters); +void return_to_menu(void); + /* ---------- Serial ------------------------------------------------------- */ #define SERIAL_READ_BUFFER_SIZE 128 void vSerialTask(void *pvParameters); - void serial_enqueue_char(char chr); /* ---------- PMIC --------------------------------------------------------- */ @@ -20,4 +36,5 @@ void ble_uart_write(uint8_t *pValue, uint8_t len); // Forces an unlock of the display. Only to be used in epicardium void disp_forcelock(); + #endif /* MODULES_H */ diff --git a/epicardium/modules/pmic.c b/epicardium/modules/pmic.c index 9e5c9b62412ea6ebe685af5bd55d57f859f077ab..492545bb3b47c33b5494351a6f321d0af376df2c 100644 --- a/epicardium/modules/pmic.c +++ b/epicardium/modules/pmic.c @@ -27,19 +27,21 @@ void pmic_interrupt_callback(void *_) void vPmicTask(void *pvParameters) { - int count = 0; - portTickType delay = portMAX_DELAY; - pmic_task_id = xTaskGetCurrentTaskHandle(); + pmic_task_id = xTaskGetCurrentTaskHandle(); - while (1) { - ulTaskNotifyTake(pdTRUE, delay); + TickType_t button_start_tick = 0; - if (count == PMIC_PRESS_SLEEP) { - LOG_ERR("pmic", "Sleep [[ Unimplemented ]]"); + while (1) { + if (button_start_tick == 0) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } else { + ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100)); } - if (count == PMIC_PRESS_POWEROFF) { - LOG_INFO("pmic", "Poweroff"); + TickType_t duration = xTaskGetTickCount() - button_start_tick; + + if (button_start_tick != 0 && duration > pdMS_TO_TICKS(1000)) { + LOG_WARN("pmic", "Poweroff"); MAX77650_setSFT_RST(0x2); } @@ -47,17 +49,17 @@ void vPmicTask(void *pvParameters) if (int_flag & MAX77650_INT_nEN_F) { /* Button was pressed */ - count = 0; - delay = portTICK_PERIOD_MS * 100; + button_start_tick = xTaskGetTickCount(); } if (int_flag & MAX77650_INT_nEN_R) { - /* Button was pressed */ - if (count < PMIC_PRESS_SLEEP) { + /* Button was released */ + button_start_tick = 0; + if (duration < pdMS_TO_TICKS(400)) { + return_to_menu(); + } else { + LOG_WARN("pmic", "Resetting ..."); card10_reset(); } - - count = 0; - delay = portMAX_DELAY; } /* TODO: Remove when all interrupts are handled */ @@ -68,9 +70,5 @@ void vPmicTask(void *pvParameters) int_flag ); } - - if (delay != portMAX_DELAY) { - count += 1; - } } } diff --git a/l0dables/lib/crt.s b/l0dables/lib/crt.s index b811c4e6cb0c732b0de7091e596da94fce9bd1ca..57726cb566590e7b6ed90f9b5f71ae095ca55fa7 100644 --- a/l0dables/lib/crt.s +++ b/l0dables/lib/crt.s @@ -18,10 +18,10 @@ * * All of the following (apart from Reset_Handler, which calls main()) * are backed by weak referenced symbols, which you can override just - * by defining them in C code. + * by defining them in C code. */ - .section .data - .align 2 + .section .text.isr_vector + .align 7 .globl __isr_vector __isr_vector: .long 0 /* Top of Stack, overriden by l0der at load time */ diff --git a/l0dables/lib/l0dable.ld b/l0dables/lib/l0dable.ld index 31fbb773de00115fecb9a49d7a449a38cbfc02b6..aa7225cfecc230c6e45017d009771e8e7fdaaf0d 100644 --- a/l0dables/lib/l0dable.ld +++ b/l0dables/lib/l0dable.ld @@ -33,6 +33,9 @@ SECTIONS { .text : { + /* The vector table needs 128 byte alignment */ + . = ALIGN(128); + KEEP(*(.text.isr_vector)) *(.text*) *(.rodata*) diff --git a/l0dables/lib/meson.build b/l0dables/lib/meson.build index f2f16295ae51a3d5ba0680ae14c220911dd540d1..d2407514569cdb380817abed63ac20a19384f78f 100644 --- a/l0dables/lib/meson.build +++ b/l0dables/lib/meson.build @@ -3,7 +3,7 @@ l0dable_startup_lib = static_library( 'crt.s', 'hardware.c', dependencies: [api_caller], - pic: true, + c_args: ['-fpie'], ) l0dable_startup = declare_dependency( diff --git a/meson.build b/meson.build index 3dd0578c22938b2bafa857aca91a2625f3b2ac0e..f1586ec93aa9aae174a8a26bc95d1cc708e0cea5 100644 --- a/meson.build +++ b/meson.build @@ -28,6 +28,13 @@ if get_option('debug_prints') ) endif +if get_option('debug_core1') + add_global_arguments( + ['-DCARD10_DEBUG_CORE1=1'], + language: 'c', + ) +endif + add_global_link_arguments( '-Wl,--gc-sections', '-lm', diff --git a/meson_options.txt b/meson_options.txt index bb96608f53ba3363e9e5a3b96dfb6eb6e11086df..0bff7f441774f3f6eb695dd2f29fc308af3a0d44 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -6,6 +6,14 @@ option( description: 'Whether to print debug messages on the serial console' ) +option( + 'debug_core1', + type: 'boolean', + value: false, + + description: 'Enable core 1 debugging interface' +) + option( 'ble_trace', type: 'boolean', diff --git a/pycardium/main.c b/pycardium/main.c index 60c86093af0e319a6367cb05957aec40dc8d7eb8..49f03244d24ce38116d01334c0598cacb9c33918 100644 --- a/pycardium/main.c +++ b/pycardium/main.c @@ -1,4 +1,5 @@ #include "epicardium.h" +#include "api/caller.h" #include "mphalport.h" #include "card10-version.h" @@ -24,10 +25,19 @@ static const char header[] = int main(void) { - epic_uart_write_str(header, sizeof(header)); + char script_name[128] = { 0 }; + int cnt = api_fetch_args(script_name, sizeof(script_name)); pycardium_hal_init(); + epic_uart_write_str(header, sizeof(header)); + + if (cnt < 0) { + printf("pycardium: Error fetching args: %d\n", cnt); + } else if (cnt > 0) { + printf(" Loading %s ...\n", script_name); + } + mp_stack_set_top(&__StackTop); mp_stack_set_limit((mp_int_t)&__StackLimit); @@ -35,12 +45,22 @@ int main(void) gc_init(&__HeapBase + 1024 * 10, &__HeapLimit); mp_init(); - pyexec_file_if_exists("main.py"); + + if (cnt > 0) { + pyexec_file_if_exists(script_name); + } + pyexec_friendly_repl(); + mp_deinit(); } } +void HardFault_Handler(void) +{ + epic_exit(255); +} + void gc_collect(void) { void *sp = (void *)__get_MSP(); diff --git a/pycardium/meson.build b/pycardium/meson.build index 8dafd3e0afa2341d913823719efffb888bea738e..b43d0fc5cd7668e6db7e0bd3fe8a5301c954cb8a 100644 --- a/pycardium/meson.build +++ b/pycardium/meson.build @@ -6,6 +6,7 @@ modsrc = files( 'modules/interrupt.c', 'modules/sys_leds.c', 'modules/light_sensor.c', + 'modules/os.c', 'modules/sys_display.c', 'modules/utime.c', 'modules/vibra.c', diff --git a/pycardium/modules/os.c b/pycardium/modules/os.c new file mode 100644 index 0000000000000000000000000000000000000000..3f84c26eea85efd6208a79f8356e482395bb4216 --- /dev/null +++ b/pycardium/modules/os.c @@ -0,0 +1,65 @@ +#include "epicardium.h" + +#include "py/obj.h" +#include "py/runtime.h" + +#include <string.h> + +static mp_obj_t mp_os_exit(size_t n_args, const mp_obj_t *args) +{ + int ret = 0; + if (n_args == 1) { + ret = mp_obj_get_int(args[0]); + } + + epic_exit(ret); + + /* unreachable */ + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(exit_obj, 0, 1, mp_os_exit); + +static mp_obj_t mp_os_exec(mp_obj_t name_in) +{ + const char *name_ptr; + char name_str[256]; + size_t len, maxlen; + + name_ptr = mp_obj_str_get_data(name_in, &len); + + /* + * The string retrieved from MicroPython is not NULL-terminated so we + * first need to copy it and add a NULL-byte. + */ + maxlen = len < (sizeof(name_str) - 1) ? len : (sizeof(name_str) - 1); + memcpy(name_str, name_ptr, maxlen); + name_str[maxlen] = '\0'; + + int ret = epic_exec(name_str); + + /* + * If epic_exec() returns, something went wrong. We can raise an + * exception in all cases. + */ + mp_raise_OSError(-ret); + + /* unreachable */ + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(exec_obj, mp_os_exec); + +static const mp_rom_map_elem_t os_module_gobals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_os) }, + { MP_ROM_QSTR(MP_QSTR_exit), MP_ROM_PTR(&exit_obj) }, + { MP_ROM_QSTR(MP_QSTR_exec), MP_ROM_PTR(&exec_obj) }, +}; +static MP_DEFINE_CONST_DICT(os_module_globals, os_module_gobals_table); + +const mp_obj_module_t os_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&os_module_globals, +}; + +/* This is a special macro that will make MicroPython aware of this module */ +/* clang-format off */ +MP_REGISTER_MODULE(MP_QSTR_os, os_module, MODULE_OS_ENABLED); diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h index 1c3990607704dae3ea7ff4cb7d60b8d669b1fa42..f25307d58b4ca4b9f46a040daa9951bfe84f79b6 100644 --- a/pycardium/modules/qstrdefs.h +++ b/pycardium/modules/qstrdefs.h @@ -85,3 +85,7 @@ Q(tell) Q(TextIOWrapper) Q(write) +/* os */ +Q(os) +Q(exit) +Q(exec) diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h index 38934682aff55c4a4c8522b5715eaa900ac3c355..5e4d09e907b1f7ee5c7abd191b5a4e7f2b2c1ac7 100644 --- a/pycardium/mpconfigport.h +++ b/pycardium/mpconfigport.h @@ -43,6 +43,7 @@ #define MODULE_INTERRUPT_ENABLED (1) #define MODULE_LEDS_ENABLED (1) #define MODULE_LIGHT_SENSOR_ENABLED (1) +#define MODULE_OS_ENABLED (1) #define MODULE_UTIME_ENABLED (1) #define MODULE_VIBRA_ENABLED (1)