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

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: default avatarRahix <rahix@rahix.de>
parent be09c127
No related branches found
No related tags found
No related merge requests found
......@@ -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) {
......
......@@ -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
......
......@@ -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();
......
#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);
}
}
......@@ -4,6 +4,7 @@ module_sources = files(
'fileops.c',
'hardware.c',
'leds.c',
'lifecycle.c',
'light_sensor.c',
'log.c',
'pmic.c',
......
......@@ -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);
......
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