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: Rahix's avatarRahix <rahix@rahix.de>
parents 14930b38 d3c52d1c
......@@ -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);