diff --git a/cpu/esp_common/esp-riscv/exceptions.c b/cpu/esp_common/esp-riscv/exceptions.c new file mode 100644 index 0000000000..5099dec321 --- /dev/null +++ b/cpu/esp_common/esp-riscv/exceptions.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_esp_common + * @{ + * + * @file + * @brief Exception handling for RISC-V-based ESP SoCs + * + * @author Gunar Schorcht + * @} + */ + +#include + +#include "kernel_defines.h" +#include "panic.h" +#include "periph/pm.h" + +#include "esp_attr.h" +#include "riscv/rvruntime-frames.h" +#include "rom/ets_sys.h" + +static const char *exceptions[] = { + "nil", + "0x1: PMP Instruction access fault", + "0x2: Illegal Instruction", + "0x3: Hardware Breakpoint/Watchpoint or EBREAK", + "nil", + "0x5: PMP Load access fault", + "nil", + "0x7: PMP Store access fault", + "0x8: ECALL from U mode", + "nil", + "nil", + "0xb: ECALL from M mode", +}; + +static RvExcFrame *_frame = NULL; + +void init_exceptions (void) +{ +} + +void IRAM_ATTR xt_unhandled_exception(RvExcFrame *frame) +{ + _frame = frame; + core_panic(PANIC_GENERAL_ERROR, "Unhandled exception"); +} + +void IRAM_ATTR panicHandler(RvExcFrame *frame) +{ + _frame = frame; + core_panic(PANIC_GENERAL_ERROR, "Panic handler"); +} + +extern void heap_stats(void); + +void panic_arch(void) +{ + if (_frame) { + /* TODO */ + ets_printf("Exception @0x%08"PRIx32", cause %s\n", + _frame->mepc, exceptions[_frame->mcause]); + } +#if defined(DEVELHELP) + heap_stats(); +#endif +} diff --git a/cpu/esp_common/esp-riscv/irq_arch.c b/cpu/esp_common/esp-riscv/irq_arch.c new file mode 100644 index 0000000000..1f152d4aa8 --- /dev/null +++ b/cpu/esp_common/esp-riscv/irq_arch.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_esp32 + * @{ + * + * @file + * @brief Implementation of the kernels irq interface + * + * @author Gunar Schorcht + * + * @} + */ + +#include "irq_arch.h" + +#include "esp_attr.h" +#include "hal/interrupt_controller_types.h" +#include "hal/interrupt_controller_ll.h" +#include "soc/periph_defs.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define RVHAL_EXCM_LEVEL 4 + +/** + * @brief Disable all maskable interrupts + */ +unsigned int IRAM_ATTR irq_disable(void) +{ + uint32_t mstatus; + /* clear MIE bit in register mstatus */ + __asm__ volatile ("csrrc %0, mstatus, %1" : "=r"(mstatus) : "rK"(MSTATUS_MIE) : "memory"); + + /* save interrupt priority level threshold */ + uint32_t state = *((volatile uint32_t *)INTERRUPT_CORE0_CPU_INT_THRESH_REG); + /* set interrupt priority level threshold to exception level */ + *((volatile uint32_t *)INTERRUPT_CORE0_CPU_INT_THRESH_REG) = RVHAL_EXCM_LEVEL; + + /* set MIE bit in register mstatus */ + __asm__ volatile ("csrrs %0, mstatus, %1" : "=r"(mstatus) : "rK"(MSTATUS_MIE) : "memory"); + + DEBUG("%s %02x(%02x)\n", __func__, RVHAL_EXCM_LEVEL, (unsigned)state); + return state; +} + +/** + * @brief Enable all maskable interrupts + */ +unsigned int IRAM_ATTR irq_enable(void) +{ + uint32_t state = *((volatile uint32_t *)INTERRUPT_CORE0_CPU_INT_THRESH_REG); + + /* set interrupt priority level threshold to 0 */ + *((volatile uint32_t *)INTERRUPT_CORE0_CPU_INT_THRESH_REG) = 0; + + /* small delay needed here */ + __asm__ volatile ( "nop" ); + __asm__ volatile ( "nop" ); + __asm__ volatile ( "nop" ); + + DEBUG("%s %02x(%02x)\n", __func__, 0, (unsigned)state); + return state; +} + +/** + * @brief Restore the state of the IRQ flags + */ +void IRAM_ATTR irq_restore(unsigned int state) +{ + uint32_t old = *((volatile uint32_t *)INTERRUPT_CORE0_CPU_INT_THRESH_REG); + + /* set interrupt priority level threshold to old level */ + *((volatile uint32_t *)INTERRUPT_CORE0_CPU_INT_THRESH_REG) = state; + + /* small delay needed here */ + __asm__ volatile ( "nop" ); + __asm__ volatile ( "nop" ); + __asm__ volatile ( "nop" ); + + DEBUG("%s %02x(%02x)\n", __func__, (unsigned)state, (unsigned)old); +} + +/** + * @brief Test if IRQs are currently enabled + */ +bool IRAM_ATTR irq_is_enabled(void) +{ + return *((volatile uint32_t *)INTERRUPT_CORE0_CPU_INT_THRESH_REG) == 0; +} diff --git a/cpu/esp_common/esp-riscv/thread_arch.c b/cpu/esp_common/esp-riscv/thread_arch.c new file mode 100644 index 0000000000..f6a43b80c4 --- /dev/null +++ b/cpu/esp_common/esp-riscv/thread_arch.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_esp_common + * @{ + * + * @file + * @brief Implementation of kernel's architecture dependent interface + * + * This file implements kernel's architecture dependent interface for RISC-V + * based ESP32x SoCs + * + * @note The implementation in `$(RIOTCPU)/risc_common/thread_arch.c` cannot + * be used because + * - it requires some modifications for compatibility with ESP-IDF code + * - the ESP-IDF uses a different context frame + * + * @author Gunar Schorcht + * + * @} + */ + +#include + +#include "esp_attr.h" + +#include "irq.h" +#include "thread.h" +#include "sched.h" +#include "syscalls.h" + +#include "freertos/FreeRTOS.h" +#include "riscv/rvruntime-frames.h" +#include "soc/soc.h" +#include "soc/system_reg.h" + +/* + * The FreeRTOS implementation for interrupt/exception handling of ESP-IDF, + * which is also used for RIOT to ensure compatibility with other ESP-IDF code, + * uses a separate stack in the interrupt context. Therefore, the stack and + * a pointer to the top of this stack are defined here. + * + * For compatibility reasons with xtensa implementation, we call them + * port_IntStack and port_IntStackTop. + */ + +/* bottom and top of ISR stack */ +extern uint8_t port_IntStack; +extern uint8_t port_IntStackTop; + +/* pointer the top of the ISR stack as required by ESP-IDF */ +uint8_t *xIsrStackTop = &port_IntStackTop; + +/* context frame as used by the ESP-IDF */ +typedef struct context_switch_frame { + uint32_t mepc; /**< machine exception program counter instead of x0 */ + uint32_t ra; /**< x1 - return address (caller saved) */ + uint32_t gp; /**< x3 - global pointer (-) */ + uint32_t sp; /**< x2 - stack pointer (callee saved) */ + uint32_t tp; /**< x4 - thread pointer (-) */ + uint32_t t0; /**< x5 - temporary register (caller saved) */ + uint32_t t1; /**< x6 - temporary register (caller saved) */ + uint32_t t2; /**< x7 - temporary register (caller saved) */ + uint32_t s0; /**< x8 - saved register / frame pointer (callee saved) */ + uint32_t s1; /**< x9 - saved register (callee saved) */ + uint32_t a0; /**< x10 - function argument / return value (caller saved) */ + uint32_t a1; /**< x11 - function argument / return value (caller saved) */ + uint32_t a2; /**< x12 - function argument (caller saved) */ + uint32_t a3; /**< x13 - function argument (caller saved) */ + uint32_t a4; /**< x14 - function argument (caller saved) */ + uint32_t a5; /**< x15 - function argument (caller saved) */ + uint32_t a6; /**< x16 - function argument (caller saved) */ + uint32_t a7; /**< x17 - function argument (caller saved) */ + uint32_t s2; /**< x18 - saved register (callee saved) */ + uint32_t s3; /**< x19 - saved register (callee saved) */ + uint32_t s4; /**< x20 - saved register (callee saved) */ + uint32_t s5; /**< x21 - saved register (callee saved) */ + uint32_t s6; /**< x22 - saved register (callee saved) */ + uint32_t s7; /**< x23 - saved register (callee saved) */ + uint32_t s8; /**< x24 - saved register (callee saved) */ + uint32_t s9; /**< x25 - saved register (callee saved) */ + uint32_t s10; /**< x26 - saved register (callee saved) */ + uint32_t s11; /**< x27 - saved register (callee saved) */ + uint32_t t3; /**< x28 - temporary register (caller saved) */ + uint32_t t4; /**< x29 - temporary register (caller saved) */ + uint32_t t5; /**< x30 - temporary register (caller saved) */ + uint32_t t6; /**< x31 - temporary register (caller saved) */ +} context_switch_frame_t; + +/* + * The following function is a modified copy of function `thread_stack_init` + * in `$(RIOTCPU)/risc_common/thread_arch.c`, which is under the following + * copyright: + * + * Copyright (C) 2017, 2019 Ken Rabold, JP Bonn + * + * Modifications: + * - For compatibility with ESP-IDF, the context frame is defined as + * used by the ESP-IDF. + * - The `STACK_MARKER` is not used to identify the top of the stack. + * Instead, the stack is initialized in the `thread_create` function in + * `$(RIOTBASE)/core/thread.c` as expected in the `thread_measure_stack_free` + * function. + * - Support for `__global_pointer$` and TLS has been added. + */ +char* thread_stack_init(thread_task_func_t task_func, void *arg, void *stack_start, int stack_size) +{ + _Static_assert(sizeof(context_switch_frame_t) == 32 * sizeof(uint32_t), + "context frame has to store 32 registers"); + + context_switch_frame_t *sf; + uint8_t *stk_top; + + /* calculate the top of the stack */ + stk_top = (uint8_t *)stack_start + stack_size; + /* per ABI align stack pointer to 16 byte boundary. */ + stk_top = (uint8_t *)((uintptr_t)stk_top & ~((uintptr_t)0xf)); + + /* prepare thread local storage and the thread pointer */ + extern uint32_t __global_pointer$; + +#if !defined(RISCV_NO_RELAX) + extern char _thread_local_start, _thread_local_end, _flash_rodata_start; + + uint8_t *_local_start; + uint32_t _local_size = (uint32_t)(&_thread_local_end - &_thread_local_start); + + _local_size = ALIGNUP(0x10, _local_size); + stk_top -= _local_size; + + _local_start = stk_top; + memcpy(_local_start, &_thread_local_start, _local_size); + + uint8_t *tp; + tp = _local_start - (&_thread_local_start - &_flash_rodata_start); +#else + uint8_t *tp = NULL; +#endif + + /* reserve space for the stack frame. */ + stk_top = stk_top - sizeof(*sf); + + /* populate the stack frame with default values for starting the thread. */ + sf = (struct context_switch_frame *)((uintptr_t)stk_top); + + /* Clear stack frame */ + memset(sf, 0, sizeof(*sf)); + + /* set initial reg values */ + sf->mepc = (uint32_t)task_func; + sf->a0 = (uint32_t)arg; + sf->gp = (uint32_t)__global_pointer$; + sf->tp = (uint32_t)tp; + /* if the thread exits go to sched_task_exit() */ + sf->ra = (uint32_t)sched_task_exit; + + return (char *)stk_top; +} + +void IRAM_ATTR thread_yield_isr(void* arg) +{ + (void)arg; + /** + * Context switches are realized using software interrupts since interrupt + * entry and exit functions are used save and restore complete + * context. + */ + /* clear the interrupt first */ + WRITE_PERI_REG(SYSTEM_CPU_INTR_FROM_CPU_0_REG, 0); + /* set the context switch flag (indicates that context has to be switched + is switch on exit from interrupt in rtos_int_exit */ + sched_context_switch_request = 1; +} + +void IRAM_ATTR thread_yield_higher(void) +{ + /* reset hardware watchdog */ + system_wdt_feed(); + + /** + * If we are already in an interrupt handler, the function simply sets the + * context switch flag, which indicates that the context has to be switched + * in the rtos_int_exit function when exiting the interrupt. Otherwise, we + * will generate a software interrupt to force the context switch when + * exiting from the software interrupt (see thread_yield_isr). + */ + if (irq_is_in()) { + /* if already in ISR, only set the sched_context_switch_request flag */ + sched_context_switch_request = 1; + } + else { + /* otherwise trigger a software interrupt for context switch */ + WRITE_PERI_REG(SYSTEM_CPU_INTR_FROM_CPU_0_REG, 1); + /* small delay of 3-4 instructions required here before we return */ + __asm__ volatile ( "nop" ); + __asm__ volatile ( "nop" ); + __asm__ volatile ( "nop" ); + __asm__ volatile ( "nop" ); + } +} + +NORETURN void cpu_switch_context_exit(void) +{ + /* enable interrupts */ + irq_enable(); + + /* force a context switch to another thread */ + thread_yield_higher(); + UNREACHABLE(); +}