Skip to content
Snippets Groups Projects
Verified Commit 604a5036 authored by rahix's avatar rahix
Browse files

Merge branch 'rahix/lifecycle'


This changeset implements a proper lifecycle of the core 1 payload.
The implementation faces some difficulties because there is no way to
properly reset just core 1.  Instead, we chose a cooperative approach:

To perform a reset, core 1 jumps to a reset-stub and is then fed a new
IVT location which it uses to "restart" with a new payload.  The jump to
the reset-stub is either voluntary by a call to epic_exit() or epix_exec(),
or involuntary by Epicardium issuing a "reset" interrupt.  This is a
special API interrupt whose handler is pre-implemented in the API-caller
lib.

Additionally, this changeset contains support for defining which python
script Pycardium should execute on startup.  This way, scripts can issue
the start of one another using `epic_exec("script-name.py")`.

Finally, this changeset also changes the power-button 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: default avatarRahix <rahix@rahix.de>
parents 14930b38 d3c52d1c
No related branches found
No related tags found
No related merge requests found
Showing
with 906 additions and 83 deletions
......@@ -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::
......
......@@ -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;
}
......@@ -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);
......@@ -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
......
#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();
}
#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];
}
}
......@@ -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);
......@@ -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;
......
......@@ -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();
}
......@@ -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;
}
......
......@@ -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
......
......@@ -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 ...");
......
......@@ -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],
)
##########################################################################
......
#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);
}
}
#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;
}
#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);
}
}
module_sources = files(
'dispatcher.c',
'display.c',
'fileops.c',
'hardware.c',
'leds.c',
'lifecycle.c',
'light_sensor.c',
'log.c',
'pmic.c',
......
#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 */
......@@ -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;
}
}
}
......@@ -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 */
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment