diff --git a/CMakeLists.txt b/CMakeLists.txt index 19405d42..bb661f9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,6 +152,16 @@ get_filename_component(ProjectId ${CMAKE_CURRENT_LIST_DIR} NAME) string(REPLACE " " "_" ProjectId ${ProjectId}) project(${ProjectId}) +# Fixed-address section for the native library C interface table. The address +# is target-specific and must match VESC_IF in main/c_libs/vesc_c_if.h. +idf_build_get_property(LIBIF_IDF_TARGET IDF_TARGET) +set(LIBIF_LD "${CMAKE_CURRENT_SOURCE_DIR}/main/linker_libif_${LIBIF_IDF_TARGET}.ld") +if(NOT EXISTS ${LIBIF_LD}) + message(FATAL_ERROR "No native lib interface linker script for target ${LIBIF_IDF_TARGET} (expected ${LIBIF_LD})") +endif() +target_link_libraries(${PROJECT_NAME}.elf PRIVATE "-T${LIBIF_LD}") +set_property(TARGET ${PROJECT_NAME}.elf APPEND PROPERTY LINK_DEPENDS ${LIBIF_LD}) + include(cmake/git_rev_parse.cmake) git_describe(GIT_COMMIT_HASH ".") diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index df8c14e5..b5faf49c 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -29,6 +29,7 @@ set(COMPONENT_SRCS "hwconf/hw.c" "lispif.c" +"lispif_c_lib.c" "lispif_events.c" "lbm_vesc_utils.c" "lispif_vesc_extensions.c" diff --git a/main/c_libs/vesc_c_if.h b/main/c_libs/vesc_c_if.h new file mode 100644 index 00000000..c045630d --- /dev/null +++ b/main/c_libs/vesc_c_if.h @@ -0,0 +1,277 @@ +/* + Copyright 2026 Benjamin Vedder benjamin@vedder.se + + This file is part of the VESC firmware. + + The VESC firmware is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The VESC firmware is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +/* + * Native library C interface for the VESC Express. + * + * This is a NEW interface, separate from the bldc (STM32 motor controller) + * vesc_c_if. It starts from the platform-neutral core - LispBM access, + * threads, timing, mutexes/semaphores, memory and printf - and contains no + * motor, CAN or peripheral functions. More slots will be appended over time. + * + * Compatibility rules: + * - New function pointers are ONLY ever added at the END of the struct, so + * libs built against older headers keep working on newer firmware. Added + * slots are null-pointers on firmware that predates them - a lib that + * wants to run on older firmware can check them for NULL. + * - VESC_C_IF_VERSION is bumped ONLY on a breaking layout change. INIT_START + * compares it against the firmware's table (first slot, read before any + * function in the table is called) and fails the lib init on mismatch. + */ + +#ifndef VESC_C_IF_H +#define VESC_C_IF_H + +#include +#include +#include + +// Layout version of the vesc_c_if struct below. Only bumped on breaking +// changes; appended slots do NOT bump it. +#define VESC_C_IF_VERSION 1 + +#define NATIVE_LIB_MAGIC 0xCAFEBABE + +// Container magic for relocatable libs that the firmware copies into RAM +// and patches at load time. Used on targets that cannot run position- +// independent code in place (ESP32-S3 / Xtensa). Layout: magic, +// version, code_size, data_size, entry_offset, reloc_count, relocs[], +// code[], data[]. +#define NATIVE_LIB_RELOC_MAGIC 0xCAFEBABF + +// System tick, as returned by system_time_ticks +typedef uint32_t systime_t; + +#ifdef IS_VESC_LIB +// LBM types, provided by lispbm.h when compiling the firmware itself. +typedef uint32_t lbm_value; +typedef uint32_t lbm_type; +typedef uint32_t lbm_cid; + +typedef uint32_t lbm_uint; +typedef int32_t lbm_int; +typedef float lbm_float; + +typedef struct { + uint8_t *buf; + lbm_uint buf_size; + lbm_uint buf_pos; +} lbm_flat_value_t; + +typedef struct { + lbm_uint size; /// Number of elements + lbm_uint *data; /// pointer to lbm_memory array or C array. +} lbm_array_header_t; + +typedef lbm_value (*extension_fptr)(lbm_value*,lbm_uint); + +// For double precision literals +#define D(x) ((double)x##L) +#endif + +typedef bool (*load_extension_fptr)(char*,extension_fptr); + +typedef void* lib_thread; +typedef void* lib_mutex; +typedef void* lib_semaphore; + +/* + * Function pointer struct. Always add new function pointers to the end in + * order to not break compatibility with old binaries. If a function is not + * available (e.g. in an old firmware) it will be a null-pointer. + */ +typedef struct { + // Interface layout version, always the first slot. Read by INIT_START + // before anything else in the table is used. + uint32_t if_version; + + // LBM: extensions, symbols and errors + load_extension_fptr lbm_add_extension; + int (*lbm_set_error_reason)(char *str); + int (*lbm_add_symbol_const)(const char *, lbm_uint *); + int (*lbm_get_symbol_by_name)(const char *name, lbm_uint* id); + + // LBM: evaluator control and messaging + void (*lbm_block_ctx_from_extension)(void); + bool (*lbm_unblock_ctx)(lbm_cid, lbm_flat_value_t*); + bool (*lbm_unblock_ctx_unboxed)(lbm_cid cid, lbm_value unboxed); + lbm_cid (*lbm_get_current_cid)(void); + int (*lbm_send_message)(lbm_cid cid, lbm_value msg); + void (*lbm_pause_eval_with_gc)(uint32_t num_free); + void (*lbm_continue_eval)(void); + bool (*lbm_eval_is_paused)(void); + + // LBM: heap and values + lbm_value (*lbm_cons)(lbm_value car, lbm_value cdr); + lbm_value (*lbm_car)(lbm_value val); + lbm_value (*lbm_cdr)(lbm_value val); + lbm_value (*lbm_list_destructive_reverse)(lbm_value list); + bool (*lbm_create_byte_array)(lbm_value *value, lbm_uint num_elt); + + // LBM: encoding + lbm_value (*lbm_enc_i)(lbm_int x); + lbm_value (*lbm_enc_u)(lbm_uint x); + lbm_value (*lbm_enc_char)(uint8_t x); + lbm_value (*lbm_enc_float)(float f); + lbm_value (*lbm_enc_u32)(uint32_t u); + lbm_value (*lbm_enc_i32)(int32_t i); + lbm_value (*lbm_enc_sym)(lbm_uint s); + + // LBM: decoding + float (*lbm_dec_as_float)(lbm_value val); + uint32_t (*lbm_dec_as_u32)(lbm_value val); + int32_t (*lbm_dec_as_i32)(lbm_value val); + uint8_t (*lbm_dec_char)(lbm_value x); + char* (*lbm_dec_str)(lbm_value); + lbm_uint (*lbm_dec_sym)(lbm_value x); + + // LBM: type checks + bool (*lbm_is_byte_array)(lbm_value val); + bool (*lbm_is_cons)(lbm_value x); + bool (*lbm_is_number)(lbm_value x); + bool (*lbm_is_char)(lbm_value x); + bool (*lbm_is_symbol)(lbm_value x); + bool (*lbm_is_symbol_nil)(lbm_uint); + bool (*lbm_is_symbol_true)(lbm_uint); + + // LBM: symbol constants + lbm_uint lbm_enc_sym_nil; + lbm_uint lbm_enc_sym_true; + lbm_uint lbm_enc_sym_terror; + lbm_uint lbm_enc_sym_eerror; + lbm_uint lbm_enc_sym_merror; + + // LBM: flat values + bool (*lbm_start_flatten)(lbm_flat_value_t *v, size_t buffer_size); + bool (*lbm_finish_flatten)(lbm_flat_value_t *v); + bool (*f_cons)(lbm_flat_value_t *v); + bool (*f_sym)(lbm_flat_value_t *v, lbm_uint sym); + bool (*f_i)(lbm_flat_value_t *v, lbm_int i); + bool (*f_b)(lbm_flat_value_t *v, uint8_t b); + bool (*f_i32)(lbm_flat_value_t *v, int32_t w); + bool (*f_u32)(lbm_flat_value_t *v, uint32_t w); + bool (*f_float)(lbm_flat_value_t *v, float f); + bool (*f_i64)(lbm_flat_value_t *v, int64_t w); + bool (*f_u64)(lbm_flat_value_t *v, uint64_t w); + bool (*f_lbm_array)(lbm_flat_value_t *v, uint32_t num_elts, uint8_t *data); + + // Os: time and sleep + void (*sleep_ms)(uint32_t ms); + void (*sleep_us)(uint32_t us); + float (*system_time)(void); // Time since boot in seconds + float (*ts_to_age_s)(systime_t ts); // Age of timestamp in seconds + // Time since boot in system ticks (see SYSTEM_TICK_RATE_HZ). Use + // ts_to_age_s to get the age of a timestamp in seconds; it handles + // overflows. + systime_t (*system_time_ticks)(void); + void (*sleep_ticks)(systime_t ticks); + // High resolution timer for short busy-wait sleeps and time + // measurement, in microseconds. + uint32_t (*timer_time_now)(void); + float (*timer_seconds_elapsed_since)(uint32_t time); + void (*timer_sleep)(float seconds); + + // Os: memory and IO + int (*printf)(const char *str, ...); + void* (*malloc)(size_t bytes); + void (*free)(void *ptr); + + // Os: threads + lib_thread (*spawn)(void (*fun)(void *arg), size_t stack_size, const char *name, void *arg); + void (*request_terminate)(lib_thread thd); + bool (*should_terminate)(void); + // Set priority of current thread. + // Range: -5 to 5, -5 is lowest, 0 is normal, 5 is highest + void (*thread_set_priority)(int priority); + void** (*get_arg)(uint32_t prog_addr); + + // Os: mutex + lib_mutex (*mutex_create)(void); // Use VESC_IF->free on the mutex when done with it + void (*mutex_lock)(lib_mutex); + void (*mutex_unlock)(lib_mutex); + + // Os: semaphore + lib_semaphore (*sem_create)(void); // Use VESC_IF->free on the semaphore when done with it + void (*sem_wait)(lib_semaphore); + void (*sem_signal)(lib_semaphore); + bool (*sem_wait_to)(lib_semaphore, systime_t); // Returns false on timeout + void (*sem_reset)(lib_semaphore); +} vesc_c_if; + +typedef struct { + void (*stop_fun)(void *arg); + void *arg; + uint32_t base_addr; +} lib_info; + +// System tick rate. Can be used to convert system ticks to time +#define SYSTEM_TICK_RATE_HZ 1000 + +/* + * Address of the firmware-side C interface table. It must match the address + * of the .libif section in main/linker_libif_.ld for the target the + * firmware (and the native library) is built for. + * + * The firmware build picks the target up from sdkconfig automatically. When + * building a native library out of tree, define the CONFIG_IDF_TARGET_* + * macro matching the hardware you are building for, e.g. + * -DCONFIG_IDF_TARGET_ESP32C3=1. A library only works on the target it was + * built for. + */ +#if defined(__has_include) +#if __has_include("sdkconfig.h") +#include "sdkconfig.h" +#endif +#endif + +#if defined(CONFIG_IDF_TARGET_ESP32C3) +#define VESC_IF ((vesc_c_if*)(0x3FCDBE00)) +#elif defined(CONFIG_IDF_TARGET_ESP32S3) +#define VESC_IF ((vesc_c_if*)(0x3FCE8800)) +#elif defined(CONFIG_IDF_TARGET_ESP32C6) +#define VESC_IF ((vesc_c_if*)(0x4087B800)) +#elif defined(CONFIG_IDF_TARGET_ESP32P4) +#define VESC_IF ((vesc_c_if*)(0x4FF3A000)) +#else +#error "Unknown ESP target. Define CONFIG_IDF_TARGET_ESP32C3, -S3, -C6 or -P4 when building a native library." +#endif + +// Put this at the beginning of your source file +#define HEADER volatile int __attribute__((__section__(".program_ptr"))) prog_ptr; + +// Init function +#define INIT_FUN bool __attribute__((__section__(".init_fun"))) init + +// Put this at the start of the init function. The version check reads the +// first table slot only - safe even when the firmware carries a different +// interface layout. +#define INIT_START (void)prog_ptr; \ + if (VESC_IF->if_version != VESC_C_IF_VERSION) { \ + return false; \ + } + +// Address of this program in memory +#define PROG_ADDR ((uint32_t)&prog_ptr) + +// The argument that was set in the init function (same as the one you get in stop_fun) +#define ARG (*VESC_IF->get_arg(PROG_ADDR)) + +extern volatile int prog_ptr; + +#endif // VESC_C_IF_H diff --git a/main/linker_libif_esp32c3.ld b/main/linker_libif_esp32c3.ld new file mode 100644 index 00000000..6e69ce08 --- /dev/null +++ b/main/linker_libif_esp32c3.ld @@ -0,0 +1,24 @@ +/* Places the native library C interface table (the .libif section, defined in + main/lispif_c_lib.c) at a fixed address so that + out-of-tree native libraries can find it through the VESC_IF macro in + main/c_libs/vesc_c_if.h. + + The address here must match VESC_IF for this target, and the block is + excluded from the heap with SOC_RESERVE_MEMORY_REGION() in vesc_c_if.c. + + ESP32-C3: top of the 0x3FCC0000 D/IRAM region, directly below the + ROM-reserved area (APP_USABLE_DRAM_END = 0x3FCDC710). Placing it at the + very top keeps the main DRAM heap region contiguous - a block in the + middle would split it and make large allocations (e.g. the LispBM + memory pool) fail even with plenty of total free heap. */ + +SECTIONS +{ + .libif 0x3FCDBE00 (NOLOAD) : + { + _libif_start = ABSOLUTE(.); + KEEP(*(.libif)) + . = ALIGN(4); + _libif_end = ABSOLUTE(.); + } +} diff --git a/main/linker_libif_esp32c6.ld b/main/linker_libif_esp32c6.ld new file mode 100644 index 00000000..bbd15d42 --- /dev/null +++ b/main/linker_libif_esp32c6.ld @@ -0,0 +1,21 @@ +/* Places the native library C interface table (the .libif section, defined in + main/lispif_c_lib.c) at a fixed address so that + out-of-tree native libraries can find it through the VESC_IF macro in + main/c_libs/vesc_c_if.h. + + The address here must match VESC_IF for this target, and the block is + excluded from the heap with SOC_RESERVE_MEMORY_REGION() in vesc_c_if.c. + + ESP32-C6: top of the 0x40860000 HP SRAM region, below the ROM-reserved + area (APP_USABLE_DRAM_END = 0x4087C610). */ + +SECTIONS +{ + .libif 0x4087B800 (NOLOAD) : + { + _libif_start = ABSOLUTE(.); + KEEP(*(.libif)) + . = ALIGN(4); + _libif_end = ABSOLUTE(.); + } +} diff --git a/main/linker_libif_esp32p4.ld b/main/linker_libif_esp32p4.ld new file mode 100644 index 00000000..906af2ac --- /dev/null +++ b/main/linker_libif_esp32p4.ld @@ -0,0 +1,22 @@ +/* Places the native library C interface table (the .libif section, defined in + main/lispif_c_lib.c) at a fixed address so that + out-of-tree native libraries can find it through the VESC_IF macro in + main/c_libs/vesc_c_if.h. + + The address here must match VESC_IF for this target, and the block is + excluded from the heap with SOC_RESERVE_MEMORY_REGION() in vesc_c_if.c. + + ESP32-P4: below the lowest usable-DRAM end across chip revisions + (APP_USABLE_DIRAM_END = 0x4FF3AFC0 for rev < 3), so the same address + works on every revision. */ + +SECTIONS +{ + .libif 0x4FF3A000 (NOLOAD) : + { + _libif_start = ABSOLUTE(.); + KEEP(*(.libif)) + . = ALIGN(4); + _libif_end = ABSOLUTE(.); + } +} diff --git a/main/linker_libif_esp32s3.ld b/main/linker_libif_esp32s3.ld new file mode 100644 index 00000000..bd877d20 --- /dev/null +++ b/main/linker_libif_esp32s3.ld @@ -0,0 +1,21 @@ +/* Places the native library C interface table (the .libif section, defined in + main/lispif_c_lib.c) at a fixed address so that + out-of-tree native libraries can find it through the VESC_IF macro in + main/c_libs/vesc_c_if.h. + + The address here must match VESC_IF for this target, and the block is + excluded from the heap with SOC_RESERVE_MEMORY_REGION() in vesc_c_if.c. + + ESP32-S3: top of the 0x3FCE0000 D/IRAM region, below the ROM-reserved + area (APP_USABLE_DRAM_END = 0x3FCE9710). */ + +SECTIONS +{ + .libif 0x3FCE8800 (NOLOAD) : + { + _libif_start = ABSOLUTE(.); + KEEP(*(.libif)) + . = ALIGN(4); + _libif_end = ABSOLUTE(.); + } +} diff --git a/main/lispif.c b/main/lispif.c index 6ad77caf..c3deeff4 100644 --- a/main/lispif.c +++ b/main/lispif.c @@ -42,7 +42,7 @@ #define EXTENSION_STORAGE_SIZE 364 #endif #ifndef USER_EXTENSION_STORAGE_SIZE -#define USER_EXTENSION_STORAGE_SIZE 0 +#define USER_EXTENSION_STORAGE_SIZE 128 #endif #define PROF_DATA_NUM 30 #define EXT_LOAD_CALLBACK_LEN 10 @@ -777,6 +777,8 @@ void lispif_stop(void) { return; } + lispif_stop_lib(); + lispif_lock_lbm(); lbm_kill_eval(); @@ -932,9 +934,40 @@ bool lispif_restart(bool print, bool load_code, bool load_imports) { int32_t offset = buffer_get_int32((uint8_t*)code_data, &ind); int32_t len = buffer_get_int32((uint8_t*)code_data, &ind); + // Bounds check first + if (offset < 0 || len < 0 + || (int64_t)offset + len > (int64_t)code_len) { + continue; + } + lbm_value val; - if (lbm_share_array_const(&val, code_data + offset, len)) { - lbm_define(name, val); + bool handled = false; + + // Try native lib path if it's big enough to have a header + if (len > 12) { + uint32_t magic_be = 0; + memcpy( + &magic_be, (const uint8_t *)code_data + offset, + sizeof(magic_be) + ); + + if (magic_be == __builtin_bswap32(NATIVE_LIB_MAGIC) || + magic_be == __builtin_bswap32(NATIVE_LIB_RELOC_MAGIC)) { + uint8_t *irom_base = + (uint8_t *)utils_drom_to_irom(code_data) + + offset; + + lbm_value lib_addr = lbm_enc_u32((uint32_t)irom_base); + lbm_define(name, lib_addr); + handled = true; + } + } + + // Fallback: normal Lisp import + if (!handled) { + if (lbm_share_array_const(&val, code_data + offset, len)) { + lbm_define(name, val); + } } } } diff --git a/main/lispif.h b/main/lispif.h index fe59b264..6584ca1f 100644 --- a/main/lispif.h +++ b/main/lispif.h @@ -26,12 +26,16 @@ #include #include "lispbm.h" +#define NATIVE_LIB_MAGIC 0xCAFEBABE +#define NATIVE_LIB_RELOC_MAGIC 0xCAFEBABF + // Functions void lispif_init(void); int lispif_get_restart_cnt(void); void lispif_lock_lbm(void); void lispif_unlock_lbm(void); void lispif_stop(void); +void lispif_stop_lib(void); bool lispif_restart(bool print, bool load_code, bool load_imports); void lispif_disable_all_events(void); void lispif_free(void *ptr); diff --git a/main/lispif_c_lib.c b/main/lispif_c_lib.c new file mode 100644 index 00000000..a8e3dc22 --- /dev/null +++ b/main/lispif_c_lib.c @@ -0,0 +1,786 @@ +/* + Copyright 2022‑2026 Benjamin Vedder + Copyright 2022 Joel Svensson + + This file is part of the VESC firmware and is released under the + terms of the GNU General Public License, version 3 (or any later). + + ----------------------------------------------------------------- + FreeRTOS / ESP‑IDF PORT + ----------------------------------------------------------------- + All ChibiOS threading primitives have been replaced with their + FreeRTOS counterparts so that `(spawn …)` and related Lisp helpers + work unmodified on ESP32 targets. + + This implements the VESC Express native lib interface (see + c_libs/vesc_c_if.h) - a fresh interface separate from the bldc + one, starting from the platform-neutral core: LispBM access, + threads, timing, mutexes/semaphores, memory and printf. New slots + are appended to the struct; VESC_C_IF_VERSION is only bumped on + breaking layout changes. +*/ + +#pragma GCC optimize("Os") +#include +#include +#include +#include "esp_timer.h" +#include "esp_rom_sys.h" +#include "esp_heap_caps.h" +#include "esp_memory_utils.h" +#include "heap_memory_layout.h" +#include "commands.h" +#include "extensions.h" +#include "lbm_flat_value.h" +#include "lispif.h" +#include "lispbm.h" +#include "utils.h" +#include "c_libs/vesc_c_if.h" +#include "freertos/task.h" + +_Static_assert(sizeof(vesc_c_if) <= 2048, "cif pad too small"); + +typedef struct { + const char *name; + void *arg; + void (*func)(void*); + volatile bool should_terminate; + TaskHandle_t handle; + UBaseType_t base_prio; +} lib_thd_info; + +#define LIB_MAX_THREADS 20 +static lib_thd_info *lib_thread_infos[LIB_MAX_THREADS] = {0}; +static size_t lib_thread_infos_cnt = 0; + +// Optional: protect lib_thread_infos[] edits/reads if accessed from multiple tasks +static portMUX_TYPE lib_thr_mux = portMUX_INITIALIZER_UNLOCKED; +#define LIB_THR_LOCK() portENTER_CRITICAL(&lib_thr_mux) +#define LIB_THR_UNLOCK() portEXIT_CRITICAL(&lib_thr_mux) + +#define LIB_NUM_MAX 10 + +static lib_info loaded_libs[LIB_NUM_MAX] = {0}; + +// The flash (IROM) address of each loaded lib's container, i.e. the value +// the lisp code passes to load-native-lib / unload-native-lib. For XIP libs +// this equals base_addr; for RAM-loaded (relocated) libs it differs. +static uint32_t lib_flash_addr[LIB_NUM_MAX] = {0}; + +// Heap allocation backing a RAM-loaded lib, NULL for XIP libs. +static void *lib_ram_alloc[LIB_NUM_MAX] = {0}; + +// Second allocation backing a RAM-loaded lib's data region (S3), NULL +// otherwise. +static void *lib_ram_data[LIB_NUM_MAX] = {0}; + +__attribute__((section(".libif"))) static volatile union { + vesc_c_if cif; + char pad[2048]; +} cif; + +// The .libif section is placed at a fixed, target-specific address by +// main/linker_libif_.ld so that native libs can find the interface +// table through the VESC_IF macro. Keep the heap allocator away from it. +SOC_RESERVE_MEMORY_REGION((intptr_t)&cif, (intptr_t)&cif + sizeof(cif), vesc_libif); + +static bool lib_init_done = false; + +static bool lib_is_func_valid(void *func) { + return esp_ptr_executable(func); +} + +static void lib_sleep_ms(uint32_t ms) { + vTaskDelay(pdMS_TO_TICKS(ms)); +} + +static void lib_sleep_us(uint32_t us) { + if (us >= 1000) { + vTaskDelay(pdMS_TO_TICKS(us / 1000)); + us %= 1000; + } + if (us) + esp_rom_delay_us(us); +} + +static float lib_system_time(void) { + return UTILS_AGE_S(0); +} + +static float lib_ts_to_age_s(TickType_t ts) { + return UTILS_AGE_S(ts); +} + +static void lib_thd(void *arg) { + lib_thd_info *t = (lib_thd_info*)arg; + + // Set thread-local storage for should_terminate check + vTaskSetThreadLocalStoragePointer(NULL, 0, t); + + t->func(t->arg); + + // Task finished, remove from global tracking + for (size_t i = 0; i < lib_thread_infos_cnt; i++) { + if (lib_thread_infos[i] == t) { + // Shift down remaining entries + for (size_t j = i; j < lib_thread_infos_cnt - 1; j++) { + lib_thread_infos[j] = lib_thread_infos[j + 1]; + } + lib_thread_infos[--lib_thread_infos_cnt] = NULL; + break; + } + } + + lbm_free(t); + vTaskDelete(NULL); // clean self-termination +} + + +static bool lib_should_terminate(void) { + lib_thd_info *info = (lib_thd_info*) pvTaskGetThreadLocalStoragePointer(NULL, 0); + return info && info->should_terminate; +} +_Static_assert( + configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0, + "Need ≥1 TLS pointer for lib thread bookkeeping" +); +lib_thread lispif_spawn(void (*func)(void*), size_t stack_size, const char *name, void *arg) { + if (!lib_is_func_valid(func)) { + commands_printf_lisp("Invalid function address. Must be static."); + return 0; + } + + if (lib_thread_infos_cnt >= LIB_MAX_THREADS) { + commands_printf_lisp("Thread limit reached."); + return 0; + } + + lib_thd_info *info = lbm_malloc_reserve(sizeof(lib_thd_info)); + if (!info) { + commands_printf_lisp("Failed to allocate thread info"); + return 0; + } + + info->arg = arg; + info->func = func; + info->name = name; + info->should_terminate = false; + + TaskHandle_t thd = NULL; + BaseType_t result = xTaskCreatePinnedToCore( + lib_thd, + name ? name : "lib-thd", + stack_size, + info, + tskIDLE_PRIORITY + 5, + &thd, + tskNO_AFFINITY + ); + + if (result == pdPASS && thd != NULL) { + info->handle = thd; + info->base_prio = uxTaskPriorityGet(thd); + lib_thread_infos[lib_thread_infos_cnt++] = info; + return (lib_thread)thd; + } else { + commands_printf_lisp("Thread creation failed"); + lbm_free(info); + return 0; + } +} + + +static void lib_request_terminate(lib_thread thd) { + TaskHandle_t handle = (TaskHandle_t)thd; + + for (size_t i = 0; i < lib_thread_infos_cnt; i++) { + if (lib_thread_infos[i]->handle == handle) { + lib_thread_infos[i]->should_terminate = true; + + // Wait for task to self-terminate + int timeout = 2000; + while (eTaskGetState(handle) != eDeleted && timeout-- > 0) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + + if (timeout <= 0) { + commands_printf_lisp("Thread did not exit. Crashing..."); + vTaskDelay(pdMS_TO_TICKS(20)); + abort(); + } + + return; + } + } + + commands_printf_lisp("Thread handle not found"); +} + + +static inline UBaseType_t clamp_prio(int p) { + // ESP-IDF sets FREERTOS_MAX_PRIORITIES via sdkconfig; typical is 25. + const UBaseType_t maxp = configMAX_PRIORITIES - 1; + const UBaseType_t minp = tskIDLE_PRIORITY + 1; + if (p < (int)minp) + return minp; + if (p > (int)maxp) + return maxp; + return (UBaseType_t)p; +} + +static void lib_thread_set_priority(int delta /* -5..5 */) { + // Find our bookkeeping record for the CURRENT task + lib_thd_info *info = + (lib_thd_info *)pvTaskGetThreadLocalStoragePointer(NULL, 0); + if (!info || info->handle != xTaskGetCurrentTaskHandle()) { + lbm_set_error_reason( + "thread_set_priority must be called from a lib thread" + ); + return; + } + + // Normalize the requested delta into absolute target priority + // 0 => baseline, +1 => one level higher than baseline, etc. + int target = (int)info->base_prio + delta; + UBaseType_t newp = clamp_prio(target); + + vTaskPrioritySet(info->handle, newp); +} + +static void **lib_get_arg(uint32_t prog_addr) { + uint32_t p = (uint32_t)utils_drom_to_irom((void *)prog_addr); + + for (int i = 0; i < LIB_NUM_MAX; i++) { + uint32_t base = loaded_libs[i].base_addr; + if (!base) + continue; + + if (p == base + 4u) { + return &loaded_libs[i].arg; + } + } + return NULL; +} + +static bool lib_create_byte_array(lbm_value *value, lbm_uint num_elt) { + return lbm_heap_allocate_array(value, num_elt); +} + +static bool lib_eval_is_paused(void) { + return lbm_get_eval_state() == EVAL_CPS_STATE_PAUSED; +} + +static lib_mutex lib_mutex_create(void) { + SemaphoreHandle_t *m = lbm_malloc_reserve(sizeof(SemaphoreHandle_t)); + if (!m) + return NULL; + *m = xSemaphoreCreateMutex(); + if (!*m) { + lbm_free(m); + return NULL; + } + return (lib_mutex)m; +} + +static void lib_mutex_lock(lib_mutex m) { + xSemaphoreTake(*((SemaphoreHandle_t *)m), portMAX_DELAY); +} + +static void lib_mutex_unlock(lib_mutex m) { + xSemaphoreGive(*((SemaphoreHandle_t *)m)); +} + +static lib_semaphore lib_sem_create(void) { + SemaphoreHandle_t *s = lbm_malloc_reserve(sizeof(SemaphoreHandle_t)); + if (!s) + return NULL; + *s = xSemaphoreCreateCounting(0xFFFF, 0); + if (!*s) { + lbm_free(s); + return NULL; + } + return (lib_semaphore)s; +} + +static void lib_sem_wait(lib_semaphore s) { + xSemaphoreTake(*((SemaphoreHandle_t *)s), portMAX_DELAY); +} + +static void lib_sem_signal(lib_semaphore s) { + xSemaphoreGive(*((SemaphoreHandle_t *)s)); +} + +static bool lib_sem_wait_to(lib_semaphore s, TickType_t timeout_ticks) { + return xSemaphoreTake(*((SemaphoreHandle_t *)s), timeout_ticks) == pdPASS; +} + +static void lib_sem_reset(lib_semaphore s) { + SemaphoreHandle_t h = *((SemaphoreHandle_t *)s); + while (xSemaphoreTake(h, 0) == pdPASS) { /* drain */ + } +} + +static bool lib_add_extension(char *sym_str, extension_fptr ext) { + if (sym_str[0] != 'e' || sym_str[1] != 'x' || sym_str[2] != 't' + || sym_str[3] != '-') { + lbm_set_error_reason("Error: Extensions must start with ext-"); + return false; + } + + return lbm_add_extension(sym_str, ext); +} + +static int lib_lbm_set_error_reason(char *str) { + lbm_set_error_reason(str); + return 1; +} + +// High resolution timer for short busy-wait sleeps and time measurement +uint32_t lib_timer_time_now() { + return (uint32_t)(esp_timer_get_time()); // microseconds +} + +float lib_timer_seconds_elapsed_since(uint32_t time_us) { + uint32_t now_us = lib_timer_time_now(); + return (now_us - time_us) / 1000000.0f; +} + +void lib_timer_sleep(float seconds) { + if (seconds <= 0) + return; + uint32_t us = (uint32_t)(seconds * 1000000.0f); + while (us >= 2000) { + vTaskDelay(pdMS_TO_TICKS(1)); + us -= 1000; + } + if (us) + esp_rom_delay_us(us); +} + +void lispif_stop_lib(void) { + // 1) Call stop_fun for all loaded libs (mirrors STM32) + for (int i = 0; i < LIB_NUM_MAX; i++) { + if (loaded_libs[i].stop_fun) { + if (lib_is_func_valid(loaded_libs[i].stop_fun)) { + loaded_libs[i].stop_fun(loaded_libs[i].arg); + } + loaded_libs[i].stop_fun = NULL; + loaded_libs[i].base_addr = 0; + loaded_libs[i].arg = NULL; + lib_flash_addr[i] = 0; + } + } + // 2) Terminate remaining lib threads. Snapshot the handles under the + // lock, but request termination outside of it as lib_request_terminate + // blocks and blocking is not allowed in a critical section. + TaskHandle_t handles[LIB_MAX_THREADS]; + size_t handle_cnt = 0; + + LIB_THR_LOCK(); + for (size_t i = 0; i < lib_thread_infos_cnt; i++) { + if (lib_thread_infos[i] && lib_thread_infos[i]->handle) { + handles[handle_cnt++] = lib_thread_infos[i]->handle; + } + } + LIB_THR_UNLOCK(); + + for (size_t i = 0; i < handle_cnt; i++) { + lib_request_terminate(handles[i]); + } + + // 3) Free RAM-loaded lib images. Done last, after every lib thread is + // gone, as their code lives in these allocations. + for (int i = 0; i < LIB_NUM_MAX; i++) { + if (lib_ram_alloc[i]) { + heap_caps_free(lib_ram_alloc[i]); + lib_ram_alloc[i] = NULL; + } + if (lib_ram_data[i]) { + heap_caps_free(lib_ram_data[i]); + lib_ram_data[i] = NULL; + } + } +} + +lbm_value ext_load_native_lib(lbm_value *args, lbm_uint argn) { + lbm_value res = lbm_enc_sym(SYM_EERROR); + + // Expect a single numeric argument containing the IROM base address + if (argn != 1 || !lbm_is_number(args[0])) { + return res; + } + + // The linker script and VESC_IF must agree on where the interface table + // lives, otherwise libs would read garbage function pointers. + if ((uintptr_t)&cif != (uintptr_t)VESC_IF) { + lbm_set_error_reason("Native lib interface address mismatch (firmware bug)"); + return res; + } + + if (!lib_init_done) { + // Zero the padding beyond the struct; slots appended to the + // interface after this firmware was built read as NULL. + memset((char *)cif.pad, 0, 2048); + + cif.cif.if_version = VESC_C_IF_VERSION; + + // LBM + cif.cif.lbm_add_extension = lib_add_extension; + cif.cif.lbm_block_ctx_from_extension = lbm_block_ctx_from_extension; + cif.cif.lbm_unblock_ctx = lbm_unblock_ctx; + cif.cif.lbm_get_current_cid = lbm_get_current_cid; + cif.cif.lbm_set_error_reason = lib_lbm_set_error_reason; + cif.cif.lbm_pause_eval_with_gc = lbm_pause_eval_with_gc; + cif.cif.lbm_continue_eval = lbm_continue_eval; + cif.cif.lbm_send_message = lbm_send_message; + cif.cif.lbm_eval_is_paused = lib_eval_is_paused; + + cif.cif.lbm_cons = lbm_cons; + cif.cif.lbm_car = lbm_car; + cif.cif.lbm_cdr = lbm_cdr; + cif.cif.lbm_list_destructive_reverse = lbm_list_destructive_reverse; + cif.cif.lbm_create_byte_array = lib_create_byte_array; + + cif.cif.lbm_add_symbol_const = lbm_add_symbol_const; + cif.cif.lbm_get_symbol_by_name = lbm_get_symbol_by_name; + + cif.cif.lbm_enc_i = lbm_enc_i; + cif.cif.lbm_enc_u = lbm_enc_u; + cif.cif.lbm_enc_char = lbm_enc_char; + cif.cif.lbm_enc_float = lbm_enc_float; + cif.cif.lbm_enc_u32 = lbm_enc_u32; + cif.cif.lbm_enc_i32 = lbm_enc_i32; + cif.cif.lbm_enc_sym = lbm_enc_sym; + + cif.cif.lbm_dec_as_float = lbm_dec_as_float; + cif.cif.lbm_dec_as_u32 = lbm_dec_as_u32; + cif.cif.lbm_dec_as_i32 = lbm_dec_as_i32; + cif.cif.lbm_dec_char = lbm_dec_char; + cif.cif.lbm_dec_str = lbm_dec_str; + cif.cif.lbm_dec_sym = lbm_dec_sym; + + cif.cif.lbm_is_byte_array = lbm_is_array_r; + cif.cif.lbm_is_cons = lbm_is_cons; + cif.cif.lbm_is_number = lbm_is_number; + cif.cif.lbm_is_char = lbm_is_char; + cif.cif.lbm_is_symbol = lbm_is_symbol; + + cif.cif.lbm_enc_sym_nil = ENC_SYM_NIL; + cif.cif.lbm_enc_sym_true = ENC_SYM_TRUE; + cif.cif.lbm_enc_sym_terror = ENC_SYM_TERROR; + cif.cif.lbm_enc_sym_eerror = ENC_SYM_EERROR; + cif.cif.lbm_enc_sym_merror = ENC_SYM_MERROR; + + cif.cif.lbm_is_symbol_nil = lbm_is_symbol_nil; + cif.cif.lbm_is_symbol_true = lbm_is_symbol_true; + + // Os + cif.cif.sleep_ms = lib_sleep_ms; + cif.cif.sleep_us = lib_sleep_us; + cif.cif.system_time = lib_system_time; + cif.cif.ts_to_age_s = lib_ts_to_age_s; + cif.cif.printf = commands_printf_lisp; + cif.cif.malloc = lbm_malloc_reserve; + cif.cif.free = lbm_free; + cif.cif.spawn = lispif_spawn; + cif.cif.request_terminate = lib_request_terminate; + cif.cif.should_terminate = lib_should_terminate; + cif.cif.get_arg = lib_get_arg; + + // Mutex + cif.cif.mutex_create = lib_mutex_create; + cif.cif.mutex_lock = lib_mutex_lock; + cif.cif.mutex_unlock = lib_mutex_unlock; + + // High resolution timer for short busy-wait sleeps and time measurement + cif.cif.timer_time_now = lib_timer_time_now; + cif.cif.timer_seconds_elapsed_since = lib_timer_seconds_elapsed_since; + cif.cif.timer_sleep = lib_timer_sleep; + + // Flat values + cif.cif.lbm_start_flatten = lbm_start_flatten; + cif.cif.lbm_finish_flatten = lbm_finish_flatten; + cif.cif.f_b = f_b; + cif.cif.f_cons = f_cons; + cif.cif.f_float = f_float; + cif.cif.f_i = f_i; + cif.cif.f_i32 = f_i32; + cif.cif.f_i64 = f_i64; + cif.cif.f_lbm_array = f_lbm_array; + cif.cif.f_sym = f_sym; + cif.cif.f_u32 = f_u32; + cif.cif.f_u64 = f_u64; + + // Unblock unboxed + cif.cif.lbm_unblock_ctx_unboxed = lbm_unblock_ctx_unboxed; + + // System time + cif.cif.system_time_ticks = xTaskGetTickCount; + cif.cif.sleep_ticks = vTaskDelay; + + // Semaphores + cif.cif.sem_create = lib_sem_create; + cif.cif.sem_wait = lib_sem_wait; + cif.cif.sem_signal = lib_sem_signal; + cif.cif.sem_wait_to = lib_sem_wait_to; + cif.cif.sem_reset = lib_sem_reset; + + cif.cif.thread_set_priority = lib_thread_set_priority; + + lib_init_done = true; + } + + // Read IROM header base directly + uint32_t irom_base = lbm_dec_as_u32(args[0]); + + // Basic pointer/alignment sanity + if (irom_base == 0 || (irom_base & 0x3) != 0) { + lbm_set_error_reason("Invalid IROM base pointer"); + return res; + } + + // Validate native header magic. Read through the DROM alias, as data + // reads through the instruction bus fault on some targets. + const uint8_t *container_drom = utils_irom_to_drom((void *)irom_base); + uint32_t magic_be = 0; + memcpy(&magic_be, container_drom, sizeof(magic_be)); + + bool is_reloc = magic_be == __builtin_bswap32(NATIVE_LIB_RELOC_MAGIC); + if (!is_reloc && magic_be != __builtin_bswap32(NATIVE_LIB_MAGIC)) { + lbm_set_error_reason("Magic number not found at IROM address"); + return res; + } + + // Duplicate check by container flash address + for (int i = 0; i < LIB_NUM_MAX; i++) { + if (loaded_libs[i].stop_fun != NULL + && lib_flash_addr[i] == irom_base) { + lbm_set_error_reason("Library already loaded"); + return res; + } + } + + int slot = -1; + for (int i = 0; i < LIB_NUM_MAX; i++) { + if (loaded_libs[i].stop_fun == NULL) { + slot = i; + break; + } + } + if (slot < 0) { + lbm_set_error_reason("Library table full"); + return res; + } + + // base_addr is where the lib image lives at runtime (prog_ptr at +4), + // entry_addr is the init function. + uint32_t base_addr; + uint32_t entry_addr; + void *ram_alloc = NULL; + void *ram_data = NULL; + + if (is_reloc) { +#if CONFIG_IDF_TARGET_ESP32S3 && CONFIG_ESP_SYSTEM_MEMPROT_FEATURE + // The exec-heap allocation below can never succeed with memory + // protection enabled, so fail with a message that says exactly + // what is wrong with this firmware build. + lbm_set_error_reason("This firmware was built with " + "CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=y - native libs on the " + "ESP32-S3 need a build with it disabled"); + return res; +#elif CONFIG_IDF_TARGET_ESP32S3 + // Xtensa cannot run position-independent code in place, so the + // container carries region-relative relocations and the image is + // copied to RAM in two parts: the code region goes to executable + // memory (any exec block works - including pure-IRAM without a + // data alias, since code is only word-accessed) and the data + // region to any byte-accessible internal RAM (including the + // DRAM-only spare). This keeps native libs out of the contested + // D/IRAM that LispBM needs. Container layout after the magic: + // version, code_size, data_size, entry_offset, reloc_count (all + // LE u32), relocs[], code[], data[]. + uint32_t version, code_size, data_size, entry_offset, reloc_count; + memcpy(&version, container_drom + 4, 4); + memcpy(&code_size, container_drom + 8, 4); + memcpy(&data_size, container_drom + 12, 4); + memcpy(&entry_offset, container_drom + 16, 4); + memcpy(&reloc_count, container_drom + 20, 4); + + if (version != 2) { + lbm_set_error_reason("Native lib container version mismatch - " + "rebuild the lib with the current vesc_pkg c_libs"); + return res; + } + + if (code_size < 4 || code_size > 0x40000 || (code_size & 3) + || data_size < 8 || data_size > 0x40000 || (data_size & 3) + || entry_offset >= code_size || (entry_offset & 3) + || reloc_count > (code_size + data_size) / 4) { + lbm_set_error_reason("Invalid native lib container"); + return res; + } + + uint32_t *code_ram = heap_caps_malloc( + code_size, MALLOC_CAP_EXEC | MALLOC_CAP_INTERNAL); + uint8_t *data_ram = heap_caps_malloc( + data_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (!code_ram || !data_ram) { + static char err_buf[96]; + snprintf(err_buf, sizeof(err_buf), + "Out of memory for lib: code %u (largest %u), data %u (largest %u)", + (unsigned)code_size, + (unsigned)heap_caps_get_largest_free_block( + MALLOC_CAP_EXEC | MALLOC_CAP_INTERNAL), + (unsigned)data_size, + (unsigned)heap_caps_get_largest_free_block( + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); + if (code_ram) heap_caps_free(code_ram); + if (data_ram) heap_caps_free(data_ram); + lbm_set_error_reason(err_buf); + return res; + } + + const uint8_t *relocs = container_drom + 24; + const uint8_t *code_src = relocs + reloc_count * 4; + const uint8_t *data_src = code_src + code_size; + + // The code block may be pure IRAM, which only allows aligned + // 32-bit accesses - copy and patch it word-wise. + for (uint32_t i = 0; i < code_size / 4; i++) { + uint32_t w; + memcpy(&w, code_src + i * 4, 4); + code_ram[i] = w; + } + memcpy(data_ram, data_src, data_size); + + // Relocation entries: bit31 = target is code, bit30 = the word + // sits in the data region, low bits = region-relative offset of + // the word. Stored words are region-relative target offsets. + bool patch_ok = true; + for (uint32_t r = 0; r < reloc_count; r++) { + uint32_t e; + memcpy(&e, relocs + r * 4, 4); + uint32_t off = e & 0x3FFFFFFF; + uint32_t add = (e & 0x80000000) + ? (uint32_t)code_ram : (uint32_t)data_ram; + + if (e & 0x40000000) { + if ((off & 3) || off + 4 > data_size) { + patch_ok = false; + break; + } + uint32_t word; + memcpy(&word, data_ram + off, 4); + word += add; + memcpy(data_ram + off, &word, 4); + } else { + if ((off & 3) || off + 4 > code_size) { + patch_ok = false; + break; + } + code_ram[off / 4] += add; + } + } + + uint32_t inner_magic = 0; + memcpy(&inner_magic, data_ram, 4); + if (!patch_ok || inner_magic != __builtin_bswap32(NATIVE_LIB_MAGIC)) { + heap_caps_free(code_ram); + heap_caps_free(data_ram); + lbm_set_error_reason("Invalid relocation table in native lib"); + return res; + } + + // Make the copied and patched code visible to instruction fetch. + __asm__ __volatile__("memw\n\tisync\n\t" ::: "memory"); + + base_addr = (uint32_t)data_ram; + entry_addr = (uint32_t)code_ram + entry_offset; + ram_alloc = code_ram; + ram_data = data_ram; +#else + lbm_set_error_reason( + "Relocatable libs are only supported on the ESP32-S3"); + return res; +#endif + } else { + // XIP: runs in place from flash. Entry is after the header: + // magic(4) + prog_addr(4) = 8 bytes. + base_addr = irom_base; + entry_addr = irom_base + 8; + } + + loaded_libs[slot].base_addr = base_addr; + lib_flash_addr[slot] = irom_base; + lib_ram_alloc[slot] = ram_alloc; + lib_ram_data[slot] = ram_data; + + bool ok = ((bool (*)(lib_info *))entry_addr)(&loaded_libs[slot]); + + if (ok && loaded_libs[slot].stop_fun != NULL) { + void *stop_fun_irom = utils_drom_to_irom(loaded_libs[slot].stop_fun); + if (lib_is_func_valid(stop_fun_irom)) { + loaded_libs[slot].stop_fun = stop_fun_irom; + return lbm_enc_sym(SYM_TRUE); + } + lbm_set_error_reason("Invalid stop function. Must be static."); + } else if (ok) { + lbm_set_error_reason("Library init failed - no stop function set"); + } else { + lbm_set_error_reason("Library init failed"); + } + + // Rollback + loaded_libs[slot].stop_fun = NULL; + loaded_libs[slot].base_addr = 0; + loaded_libs[slot].arg = NULL; + lib_flash_addr[slot] = 0; + if (lib_ram_alloc[slot]) { + heap_caps_free(lib_ram_alloc[slot]); + lib_ram_alloc[slot] = NULL; + } + if (lib_ram_data[slot]) { + heap_caps_free(lib_ram_data[slot]); + lib_ram_data[slot] = NULL; + } + + return res; +} + +lbm_value ext_unload_native_lib(lbm_value *args, lbm_uint argn) { + lbm_value res = lbm_enc_sym(SYM_EERROR); + + if (argn != 1 || !lbm_is_number(args[0])) { + return res; + } + + uint32_t irom_base = lbm_dec_as_u32(args[0]); + + for (int i = 0; i < LIB_NUM_MAX; i++) { + if (loaded_libs[i].stop_fun != NULL + && lib_flash_addr[i] == irom_base) { + // The stop function must stop everything the lib started, + // including its threads, before returning. + if (lib_is_func_valid(loaded_libs[i].stop_fun)) { + loaded_libs[i].stop_fun(loaded_libs[i].arg); + } + loaded_libs[i].stop_fun = NULL; + loaded_libs[i].base_addr = 0; + loaded_libs[i].arg = NULL; + lib_flash_addr[i] = 0; + if (lib_ram_alloc[i]) { + heap_caps_free(lib_ram_alloc[i]); + lib_ram_alloc[i] = NULL; + } + if (lib_ram_data[i]) { + heap_caps_free(lib_ram_data[i]); + lib_ram_data[i] = NULL; + } + + return lbm_enc_sym(SYM_TRUE); + } + } + + lbm_set_error_reason("Library not loaded"); + return res; +} diff --git a/main/lispif_vesc_extensions.c b/main/lispif_vesc_extensions.c index dd612a1a..99f0cb90 100644 --- a/main/lispif_vesc_extensions.c +++ b/main/lispif_vesc_extensions.c @@ -111,6 +111,10 @@ #error "Unsupported target" #endif +// Declare native lib extension +lbm_value ext_load_native_lib(lbm_value *args, lbm_uint argn); +lbm_value ext_unload_native_lib(lbm_value *args, lbm_uint argn); + typedef struct { // BMS lbm_uint v_tot; @@ -158,6 +162,7 @@ typedef struct { lbm_uint fw_ver; lbm_uint uuid; lbm_uint hw_type; + lbm_uint hw_target; lbm_uint part_running; lbm_uint git_branch; lbm_uint git_hash; @@ -294,6 +299,8 @@ static bool compare_symbol(lbm_uint sym, lbm_uint *comp) { lbm_add_symbol_const("uuid", comp); } else if (comp == &syms_vesc.hw_type) { lbm_add_symbol_const("hw-type", comp); + } else if (comp == &syms_vesc.hw_target) { + lbm_add_symbol_const("hw-target", comp); } else if (comp == &syms_vesc.part_running) { lbm_add_symbol_const("part-running", comp); } else if (comp == &syms_vesc.git_branch) { @@ -1160,6 +1167,18 @@ static lbm_value ext_sysinfo(lbm_value *args, lbm_uint argn) { res = lbm_cons(lbm_enc_i(FW_VERSION_MAJOR), res); } else if (compare_symbol(name, &syms_vesc.hw_type)) { res = lbm_enc_sym(sym_hw_express); + } else if (compare_symbol(name, &syms_vesc.hw_target)) { + // Chip this firmware runs on, e.g. "esp32c3". Native libs only run + // on the chip they were built for, so multi-target packages use + // this to pick the right binary. + lbm_value lbm_res; + if (lbm_create_array(&lbm_res, strlen(CONFIG_IDF_TARGET) + 1)) { + lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(lbm_res); + strcpy((char*)arr->data, CONFIG_IDF_TARGET); + res = lbm_res; + } else { + res = ENC_SYM_MERROR; + } } else if (compare_symbol(name, &syms_vesc.part_running)) { const esp_partition_t *running = esp_ota_get_running_partition(); if (running != NULL) { @@ -6753,6 +6772,7 @@ static bool dynamic_loader(const char *str, const char **code) { } void lispif_load_vesc_extensions(bool main_found) { + lispif_stop_lib(); if (!i2c_mutex_init_done) { i2c_mutex = xSemaphoreCreateMutex(); i2c_mutex_init_done = true; @@ -6948,6 +6968,10 @@ void lispif_load_vesc_extensions(bool main_found) { lbm_add_extension("sleep-config-wakeup-pin", ext_sleep_config_wakeup_pin); lbm_add_extension("rtc-data", ext_rtc_data); + // Native libraries + lbm_add_extension("load-native-lib", ext_load_native_lib); + lbm_add_extension("unload-native-lib", ext_unload_native_lib); + lispif_load_rgbled_extensions(); lispif_load_disp_extensions(); @@ -7098,6 +7122,8 @@ void lispif_disable_all_events(void) { xSemaphoreGive(rmsg_mutex); } + lispif_stop_lib(); + event_can_sid_en = false; event_can_eid_en = false; event_can2_sid_en = false; @@ -7147,6 +7173,8 @@ void lispif_disable_all_events(void) { cmds_running = false; cmds_state = 0; + + vTaskDelay(pdMS_TO_TICKS(5)); } void lispif_process_can(uint32_t can_id, uint8_t *data8, int len, bool is_ext) { diff --git a/main/utils.h b/main/utils.h index 0ccfa77d..4da882ba 100644 --- a/main/utils.h +++ b/main/utils.h @@ -24,6 +24,7 @@ #include #include #include +#include "soc/soc.h" // Global variables extern char *string_pin_invalid; @@ -112,4 +113,28 @@ static inline void utils_norm_angle_rad(float *angle) { while (*angle >= M_PI) { *angle -= 2.0 * M_PI; } } +// Translate between the flash data bus (DROM) and instruction bus (IROM) +// mappings of the same flash page. The flash MMU maps each page at the same +// offset on both buses, so this is a constant-offset translation. On chips +// with a unified bus (C6, P4) both mappings are the same address. +static inline void* utils_drom_to_irom(void* drom_addr) { + uintptr_t addr = (uintptr_t)drom_addr; +#if SOC_DROM_LOW != SOC_IROM_LOW + if (addr >= SOC_DROM_LOW && addr < SOC_DROM_HIGH) { + return (void*)(addr - SOC_DROM_LOW + SOC_IROM_LOW); + } +#endif + return (void*)addr; +} + +static inline void* utils_irom_to_drom(void* irom_addr) { + uintptr_t addr = (uintptr_t)irom_addr; +#if SOC_DROM_LOW != SOC_IROM_LOW + if (addr >= SOC_IROM_LOW && addr < SOC_IROM_HIGH) { + return (void*)(addr - SOC_IROM_LOW + SOC_DROM_LOW); + } +#endif + return (void*)addr; +} + #endif /* MAIN_UTILS_H_ */ diff --git a/sdkconfig.defaults.esp32s3_fh4 b/sdkconfig.defaults.esp32s3_fh4 index 767e66ce..3b0af0c4 100644 --- a/sdkconfig.defaults.esp32s3_fh4 +++ b/sdkconfig.defaults.esp32s3_fh4 @@ -1454,8 +1454,8 @@ CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y # # Memory protection # -CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=y -CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y +# Memory protection must stay off so native libs can execute from RAM +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n # end of Memory protection CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 diff --git a/sdkconfig.defaults.esp32s3_fh4r2 b/sdkconfig.defaults.esp32s3_fh4r2 index 4d7435be..ea191548 100644 --- a/sdkconfig.defaults.esp32s3_fh4r2 +++ b/sdkconfig.defaults.esp32s3_fh4r2 @@ -1491,8 +1491,8 @@ CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y # # Memory protection # -CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=y -CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y +# Memory protection must stay off so native libs can execute from RAM +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n # end of Memory protection CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 diff --git a/sdkconfig.defaults.esp32s3_n16r8 b/sdkconfig.defaults.esp32s3_n16r8 index 9add38dd..6ce256af 100644 --- a/sdkconfig.defaults.esp32s3_n16r8 +++ b/sdkconfig.defaults.esp32s3_n16r8 @@ -1489,8 +1489,8 @@ CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y # # Memory protection # -CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=y -CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y +# Memory protection must stay off so native libs can execute from RAM +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n # end of Memory protection CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32