From 8944f800f82fe132bc1f3e130c582482a2c0c758 Mon Sep 17 00:00:00 2001 From: Rahix Date: Sat, 10 Aug 2019 18:19:38 +0200 Subject: [PATCH 01/12] fix(l0dables): Fix vector table alignment The vector table's alignment requirements depend on the number of interrupts [1]. In our case, we have 0x6E(=110) interrupts and thus an alignment requirement of 0x80(=128). To satisfy this requirement, this commit moves the IVT to the beginning of .text and enforces a 128 byte alignment. Please note that the headers which come before .text will push the IVT to 0x100 instead of having it directly in the beginning at 0x00. [1]: https://developer.arm.com/docs/dui0553/a/cortex-m4-peripherals/system-control-block/vector-table-offset-register Signed-off-by: Rahix --- l0dables/lib/crt.s | 6 +++--- l0dables/lib/l0dable.ld | 3 +++ l0dables/lib/meson.build | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/l0dables/lib/crt.s b/l0dables/lib/crt.s index b811c4e6..57726cb5 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 31fbb773..aa7225cf 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 f2f16295..d2407514 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( -- GitLab From 06371344fffbe5e308e974b38ef4dce09fbdaf3d Mon Sep 17 00:00:00 2001 From: Rahix Date: Sun, 11 Aug 2019 22:37:17 +0200 Subject: [PATCH 02/12] feat(epicardium): Add switch for core 1 debugging Signed-off-by: Rahix --- Documentation/how-to-build.rst | 2 ++ epicardium/main.c | 20 +++++++++++++++++++- meson.build | 7 +++++++ meson_options.txt | 8 ++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Documentation/how-to-build.rst b/Documentation/how-to-build.rst index da17ca98..144c2e1f 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/main.c b/epicardium/main.c index 56f532a6..dc3dfd25 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -6,6 +6,7 @@ #include "max32665.h" #include "uart.h" #include "cdcacm.h" +#include "gpio.h" #include "card10.h" #include "pmic.h" @@ -51,7 +52,24 @@ int main(void) LOG_INFO("startup", "Version " CARD10_VERSION); card10_init(); - card10_diag(); +#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) diff --git a/meson.build b/meson.build index 3dd0578c..f1586ec9 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 bb96608f..0bff7f44 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', -- GitLab From b8aafb41ae03ed87ca123ce740bb50b2d025709e Mon Sep 17 00:00:00 2001 From: Rahix Date: Sun, 11 Aug 2019 14:47:34 +0200 Subject: [PATCH 03/12] feat(api): Add initial argument passing This allows pycardium to learn which script it should start once it boots. Arguments can only be read before any API calls are made. Afterward they are lost. To ensure they won't collide with anything during a core 1 restart, they are offset by 0x20 from the start of the API buffer. Signed-off-by: Rahix --- epicardium/api/caller.c | 22 ++++++++++++++++++++++ epicardium/api/caller.h | 9 +++++++++ epicardium/api/dispatcher.c | 26 ++++++++++++++++++++++---- epicardium/api/dispatcher.h | 8 ++++++-- pycardium/main.c | 19 +++++++++++++++++-- 5 files changed, 76 insertions(+), 8 deletions(-) diff --git a/epicardium/api/caller.c b/epicardium/api/caller.c index f1ba675f..7c52d280 100644 --- a/epicardium/api/caller.c +++ b/epicardium/api/caller.c @@ -53,3 +53,25 @@ void *_api_call_transact(void *buffer) return API_CALL_MEM->buffer; } + +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 d9192c85..93bd3f3d 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/dispatcher.c b/epicardium/api/dispatcher.c index 4675c8d4..73057975 100644 --- a/epicardium/api/dispatcher.c +++ b/epicardium/api/dispatcher.c @@ -1,7 +1,15 @@ -#include -#include "sema.h" #include "api/dispatcher.h" + #include "max32665.h" +#include "sema.h" + +#include +#include + +/* 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() { @@ -20,8 +28,6 @@ int api_dispatcher_init() return ret; } -static bool event_ready = false; - bool api_dispatcher_poll_once() { if (event_ready) { @@ -68,3 +74,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 a67aa0c4..1294592d 100644 --- a/epicardium/api/dispatcher.h +++ b/epicardium/api/dispatcher.h @@ -22,5 +22,9 @@ 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); diff --git a/pycardium/main.c b/pycardium/main.c index 60c86093..78450da0 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,8 +45,13 @@ 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(); } } -- GitLab From 83ef535730cc9a8a733f4b1a6b442cd87ce72f6e Mon Sep 17 00:00:00 2001 From: Rahix Date: Sun, 11 Aug 2019 14:54:22 +0200 Subject: [PATCH 04/12] fix(api): Change interrupt idle value to -1 Previously 0 was used but as 0 is also the ID of the reset interrupt, this could lead to weird behaviors. Signed-off-by: Rahix --- epicardium/api/genapi.py | 3 +++ epicardium/api/interrupt-receiver.c | 2 +- epicardium/api/interrupt-sender.c | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/epicardium/api/genapi.py b/epicardium/api/genapi.py index 5fd546a6..c6cefc92 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 c212a402..7684c6bb 100644 --- a/epicardium/api/interrupt-receiver.c +++ b/epicardium/api/interrupt-receiver.c @@ -10,5 +10,5 @@ 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); } diff --git a/epicardium/api/interrupt-sender.c b/epicardium/api/interrupt-sender.c index cabd3bcb..c20d998e 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,7 +22,7 @@ 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; -- GitLab From 3338d8a53c390c04b2a2d36133926f9522e96e37 Mon Sep 17 00:00:00 2001 From: Rahix Date: Sun, 11 Aug 2019 14:56:03 +0200 Subject: [PATCH 05/12] fix(api): Make reset interrupt non-maskable Signed-off-by: Rahix --- epicardium/api/interrupt-sender.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/epicardium/api/interrupt-sender.c b/epicardium/api/interrupt-sender.c index c20d998e..d531846d 100644 --- a/epicardium/api/interrupt-sender.c +++ b/epicardium/api/interrupt-sender.c @@ -27,6 +27,9 @@ void api_interrupt_init(void) 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) @@ -41,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; } -- GitLab From b78ebdaf14dd438eb981ba1cb1361c4f66aefe68 Mon Sep 17 00:00:00 2001 From: Rahix Date: Sun, 11 Aug 2019 22:11:16 +0200 Subject: [PATCH 06/12] feat(epicardium): Implement basic core 1 lifecycle This commit introduces a way to control core 1. This is archieved by a predefined API-Interrupt: The reset interrupt. When triggered, it will bring the core back into its default state and wait for a new vector address from Epicardium. Once this vector address is transferred, it will start the new payload. This method only works as long as core 1 is responsive to the API interrupts. Cases where this might not be the case: - During times where core 1 has interrupts disabled - When in a higher priority exception handler - When core 1 has corrupted its IVT Signed-off-by: Rahix --- epicardium/api/common.h | 10 +- epicardium/api/control.c | 233 ++++++++++++++++++++++++++++ epicardium/api/dispatcher.c | 8 +- epicardium/api/dispatcher.h | 16 ++ epicardium/api/interrupt-receiver.c | 7 + epicardium/main.c | 8 +- epicardium/meson.build | 3 +- 7 files changed, 278 insertions(+), 7 deletions(-) create mode 100644 epicardium/api/control.c diff --git a/epicardium/api/common.h b/epicardium/api/common.h index c884f37c..ff0c9f3a 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 00000000..daa1ff52 --- /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 73057975..4ffac422 100644 --- a/epicardium/api/dispatcher.c +++ b/epicardium/api/dispatcher.c @@ -15,8 +15,12 @@ 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. diff --git a/epicardium/api/dispatcher.h b/epicardium/api/dispatcher.h index 1294592d..2299f54d 100644 --- a/epicardium/api/dispatcher.h +++ b/epicardium/api/dispatcher.h @@ -28,3 +28,19 @@ api_id_t api_dispatcher_exec(); * 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/interrupt-receiver.c b/epicardium/api/interrupt-receiver.c index 7684c6bb..f9856423 100644 --- a/epicardium/api/interrupt-receiver.c +++ b/epicardium/api/interrupt-receiver.c @@ -12,3 +12,10 @@ void TMR5_IRQHandler(void) __dispatch_isr(API_CALL_MEM->int_id); 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/main.c b/epicardium/main.c index dc3dfd25..84abf8d9 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -148,6 +148,8 @@ int main(void) LOG_INFO("startup", "starting light sensor ..."); epic_light_sensor_run(); + core1_boot(); + /* * See if there's a l0dable.elf to run. If not, run pycardium. * This is temporary until epicardium gets a l0dable API from pycardium. @@ -163,11 +165,11 @@ int main(void) LOG_INFO( "startup", "Starting %s on core1 ...", l0dable ); - core1_start(info.isr_vector); + core1_load(info.isr_vector, ""); } } else { - LOG_INFO("startup", "Starting pycardium on core1 ..."); - core1_start((void *)0x10080000); + LOG_INFO("startup", "Starting pycardium on core 1 ..."); + core1_load((void *)0x10080000, "main.py"); } LOG_INFO("startup", "Starting FreeRTOS ..."); diff --git a/epicardium/meson.build b/epicardium/meson.build index c9b128f7..220f0bae 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], ) ########################################################################## -- GitLab From 79f8b607434de8cdc67213a7fdb80ae81f425fa2 Mon Sep 17 00:00:00 2001 From: Rahix Date: Wed, 14 Aug 2019 22:14:09 +0200 Subject: [PATCH 07/12] chore(epicardium): Move dispatcher into own module Also add a mutex around API calls in preparation for future changes. Signed-off-by: Rahix --- epicardium/main.c | 15 --------------- epicardium/modules/dispatcher.c | 34 +++++++++++++++++++++++++++++++++ epicardium/modules/meson.build | 1 + epicardium/modules/modules.h | 9 ++++++++- 4 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 epicardium/modules/dispatcher.c diff --git a/epicardium/main.c b/epicardium/main.c index 84abf8d9..cd989f4c 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -31,21 +31,6 @@ 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 ..."); diff --git a/epicardium/modules/dispatcher.c b/epicardium/modules/dispatcher.c new file mode 100644 index 00000000..60f1c1f8 --- /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/meson.build b/epicardium/modules/meson.build index b3cf4eb2..9a70957d 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -1,4 +1,5 @@ module_sources = files( + 'dispatcher.c', 'display.c', 'fileops.c', 'leds.c', diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index 4a7bc255..ea2a313f 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -1,12 +1,19 @@ #ifndef MODULES_H #define MODULES_H +#include "FreeRTOS.h" +#include "semphr.h" + #include +/* ---------- Dispatcher --------------------------------------------------- */ +void vApiDispatcher(void *pvParameters); +extern SemaphoreHandle_t api_mutex; +extern TaskHandle_t dispatcher_task_id; + /* ---------- Serial ------------------------------------------------------- */ #define SERIAL_READ_BUFFER_SIZE 128 void vSerialTask(void *pvParameters); - void serial_enqueue_char(char chr); /* ---------- PMIC --------------------------------------------------------- */ -- GitLab From 3133f0da0e6e712cb3017157a47abbefb0a3e0fa Mon Sep 17 00:00:00 2001 From: Rahix Date: Tue, 13 Aug 2019 11:04:02 +0200 Subject: [PATCH 08/12] feat(epicardium): Add module for hardware init Signed-off-by: Rahix --- epicardium/main.c | 5 ++++- epicardium/modules/hardware.c | 40 ++++++++++++++++++++++++++++++++++ epicardium/modules/meson.build | 1 + epicardium/modules/modules.h | 6 +++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 epicardium/modules/hardware.c diff --git a/epicardium/main.c b/epicardium/main.c index cd989f4c..1ae2dde0 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -36,7 +36,8 @@ int main(void) LOG_INFO("startup", "Epicardium startup ..."); LOG_INFO("startup", "Version " CARD10_VERSION); - card10_init(); + hardware_early_init(); + #ifdef CARD10_DEBUG_CORE1 LOG_WARN("startup", "Core 1 Debugger Mode"); static const gpio_cfg_t swclk = { @@ -157,6 +158,8 @@ int main(void) core1_load((void *)0x10080000, "main.py"); } + hardware_init(); + LOG_INFO("startup", "Starting FreeRTOS ..."); vTaskStartScheduler(); diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c new file mode 100644 index 00000000..ee369248 --- /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/meson.build b/epicardium/modules/meson.build index 9a70957d..ba1a4fd4 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -2,6 +2,7 @@ module_sources = files( 'dispatcher.c', 'display.c', 'fileops.c', + 'hardware.c', 'leds.c', 'light_sensor.c', 'log.c', diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index ea2a313f..df1a9d9b 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -11,6 +11,11 @@ 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); + /* ---------- Serial ------------------------------------------------------- */ #define SERIAL_READ_BUFFER_SIZE 128 void vSerialTask(void *pvParameters); @@ -27,4 +32,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 */ -- GitLab From f6665196c30081c686b26c67fbd5d26bf53f297d Mon Sep 17 00:00:00 2001 From: Rahix Date: Thu, 15 Aug 2019 14:05:25 +0200 Subject: [PATCH 09/12] feat(epicardium): Add core 1 lifecycle This commit introduces a lifecycle for core 1. Based on the new loading system, a few APIs are made available to control the payload running on core 1. These are: 1. From core 1 (Pycardium, L0dable): - `epic_exec(name)` API Call: Request loading of a new payload by name. If the file does not exist, the call will return with an error code. Otherwise, control will go to the new payload. - `epic_exit(retcode)` API Call: Return from payload unconditionally. This call should be called whenever a payload is done or when it has hit an unrecoverable error. On `epic_exit`, Epicardium will reset the core back into the menu. 2. From inside Epicardium: - `epic_exec(name)`: This is **not** the same as the API call, as it needs a different implementation underneath. It will load a new payload and wait until this load actually completed (synchroneous). - `return_to_menu()`: Return core 1 to the menu script no matter what it is currently doing. This call is asynchroneous and will return immediately after scheduling the lifecycle task. This task will then take care of actually performing the load. Signed-off-by: Rahix --- epicardium/api/caller.c | 41 +++++ epicardium/epicardium.h | 59 +++++- epicardium/main.c | 41 ++--- epicardium/modules/lifecycle.c | 315 +++++++++++++++++++++++++++++++++ epicardium/modules/meson.build | 1 + epicardium/modules/modules.h | 4 + 6 files changed, 429 insertions(+), 32 deletions(-) create mode 100644 epicardium/modules/lifecycle.c diff --git a/epicardium/api/caller.c b/epicardium/api/caller.c index 7c52d280..f2f5643a 100644 --- a/epicardium/api/caller.c +++ b/epicardium/api/caller.c @@ -54,6 +54,47 @@ 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) { diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 2c65e376..c680d559 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 1ae2dde0..c70ea936 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -12,7 +12,6 @@ #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" @@ -73,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 */ @@ -127,39 +129,22 @@ int main(void) abort(); } - LOG_INFO("startup", "Initializing dispatcher ..."); - api_dispatcher_init(); - /* light sensor */ LOG_INFO("startup", "starting light sensor ..."); epic_light_sensor_run(); - core1_boot(); - - /* - * 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_load(info.isr_vector, ""); - } - } else { - LOG_INFO("startup", "Starting pycardium on core 1 ..."); - core1_load((void *)0x10080000, "main.py"); + /* 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(); } - hardware_init(); - LOG_INFO("startup", "Starting FreeRTOS ..."); vTaskStartScheduler(); diff --git a/epicardium/modules/lifecycle.c b/epicardium/modules/lifecycle.c new file mode 100644 index 00000000..13ba9704 --- /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 +#include +#include + +#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 ba1a4fd4..d7c9f40b 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -4,6 +4,7 @@ module_sources = files( '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 df1a9d9b..35968547 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -16,6 +16,10 @@ 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); -- GitLab From 475830d062450b049112d272017c969a556ad898 Mon Sep 17 00:00:00 2001 From: Rahix Date: Mon, 12 Aug 2019 14:45:26 +0200 Subject: [PATCH 10/12] feat(pycardium): Add a barebones "os" module Signed-off-by: Rahix --- pycardium/meson.build | 1 + pycardium/modules/os.c | 65 ++++++++++++++++++++++++++++++++++++ pycardium/modules/qstrdefs.h | 4 +++ pycardium/mpconfigport.h | 1 + 4 files changed, 71 insertions(+) create mode 100644 pycardium/modules/os.c diff --git a/pycardium/meson.build b/pycardium/meson.build index 8dafd3e0..b43d0fc5 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 00000000..3f84c26e --- /dev/null +++ b/pycardium/modules/os.c @@ -0,0 +1,65 @@ +#include "epicardium.h" + +#include "py/obj.h" +#include "py/runtime.h" + +#include + +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 1c399060..f25307d5 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 38934682..5e4d09e9 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) -- GitLab From 8b17fcdb124b300149cab5785a3685326f94eb6e Mon Sep 17 00:00:00 2001 From: Rahix Date: Wed, 14 Aug 2019 23:14:24 +0200 Subject: [PATCH 11/12] feat(pycardium): Return on HardFault Signed-off-by: Rahix --- pycardium/main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pycardium/main.c b/pycardium/main.c index 78450da0..49f03244 100644 --- a/pycardium/main.c +++ b/pycardium/main.c @@ -56,6 +56,11 @@ int main(void) } } +void HardFault_Handler(void) +{ + epic_exit(255); +} + void gc_collect(void) { void *sp = (void *)__get_MSP(); -- GitLab From aa81020bc74b5ecb254ed2991abddf9478ca4288 Mon Sep 17 00:00:00 2001 From: Rahix Date: Thu, 15 Aug 2019 14:11:42 +0200 Subject: [PATCH 12/12] feat(pmic): Implement proper reset behavior Pressing the power button will now excert the following behavior: - `<400 ms`: Return back to menu - `<1 s`: Reset card10 - `>1 s`: Poweroff Signed-off-by: Rahix --- epicardium/modules/pmic.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/epicardium/modules/pmic.c b/epicardium/modules/pmic.c index 9e5c9b62..492545bb 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; - } } } -- GitLab