diff --git a/components/bootloader/subproject/Makefile b/components/bootloader/subproject/Makefile index 8547e3ecb0..6acc7bd29b 100644 --- a/components/bootloader/subproject/Makefile +++ b/components/bootloader/subproject/Makefile @@ -8,7 +8,7 @@ endif PROJECT_NAME := bootloader -COMPONENTS := esp_hw_support esptool_py bootloader_support log spi_flash micro-ecc soc main efuse esp_rom hal +COMPONENTS := esp_hw_support esptool_py bootloader_support log spi_flash micro-ecc soc main efuse esp_rom hal xtensa # Clear C and CXX from top level project CFLAGS = diff --git a/components/esp_rom/CMakeLists.txt b/components/esp_rom/CMakeLists.txt index ce7fa9ba74..38e502a204 100644 --- a/components/esp_rom/CMakeLists.txt +++ b/components/esp_rom/CMakeLists.txt @@ -1,8 +1,14 @@ idf_build_get_property(target IDF_TARGET) -idf_component_register(SRCS "patches/esp_rom_crc.c" - "patches/esp_rom_sys.c" - "patches/esp_rom_uart.c" +set(sources "patches/esp_rom_crc.c" + "patches/esp_rom_sys.c" + "patches/esp_rom_uart.c") + +if(CONFIG_IDF_TARGET_ARCH_XTENSA) + list(APPEND sources "patches/esp_rom_longjmp.S") +endif() + +idf_component_register(SRCS ${sources} INCLUDE_DIRS include "${target}" PRIV_REQUIRES soc hal) @@ -98,6 +104,9 @@ else() # Regular app build endif() endif() + if(CONFIG_IDF_TARGET_ARCH_XTENSA) + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=longjmp") + endif() endif() if(target STREQUAL "esp32s2") diff --git a/components/esp_rom/component.mk b/components/esp_rom/component.mk index 05e486568e..529abafdd2 100644 --- a/components/esp_rom/component.mk +++ b/components/esp_rom/component.mk @@ -39,5 +39,6 @@ endif COMPONENT_ADD_LDFLAGS += -L $(COMPONENT_PATH)/esp32/ld \ $(addprefix -T ,$(LINKER_SCRIPTS)) \ + -l$(COMPONENT_NAME) -Wl,--wrap=longjmp \ COMPONENT_ADD_LINKER_DEPS += $(addprefix esp32/ld/, $(LINKER_SCRIPTS)) diff --git a/components/esp_rom/patches/esp_rom_longjmp.S b/components/esp_rom/patches/esp_rom_longjmp.S new file mode 100644 index 0000000000..7f2de71737 --- /dev/null +++ b/components/esp_rom/patches/esp_rom_longjmp.S @@ -0,0 +1,71 @@ +/* + Copyright (c) 2001-2006 by Tensilica Inc. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + This file contains a modified version of the original Xtensa longjmp implementation. + In this modified version, setting WINDOWSTART = 1 << WINDOWBASE is done inside a critical section. + This is necessary because after a FreeRTOS context switch in IDF, the values of WINDOWBASE and WINDOWSTART + are not guaranteed to be the same as before the context switch. +*/ + +#include + +/* + Replacement of the first instructions of void longjmp (jmp_buf env, int val) +*/ + + .align 4 + .literal_position + .global __wrap_longjmp + .type __wrap_longjmp, @function +__wrap_longjmp: + entry sp, 16 + + /* Deactivate interrupts in order to modify WINDOWBASE and WINDOWSTART. */ + rsr a7, PS /* to be restored after SPILL_ALL_WINDOWS */ + movi a5, PS_EXCM /* PS_INTLEVEL_MASK */ + or a5, a7, a5 /* get the current INTLEVEL */ + wsr a5, PS + + /* Invalidate all but the current window; + set WindowStart to (1 << WindowBase). */ + rsr a5, WINDOWBASE + movi a4, 1 + ssl a5 + sll a4, a4 + wsr a4, WINDOWSTART + rsync + + /* Activate interrupts again after modifying WINDOWBASE and WINDOWSTART. */ + wsr a7, PS + + /* Jump back to original longjmp implementation. + The jump target is the instrucion + l32i a0, a2, 64 + of the original code. Hence, the original code's entry instruction and windowstart modification are left + out. + */ + movi a0, __real_longjmp + 20 + jx a0 + + .size __wrap_longjmp, . - __wrap_longjmp diff --git a/components/xtensa/component.mk b/components/xtensa/component.mk index 524610edb6..35e30f5384 100644 --- a/components/xtensa/component.mk +++ b/components/xtensa/component.mk @@ -5,3 +5,7 @@ COMPONENT_ADD_LDFLAGS += $(COMPONENT_PATH)/esp32/libxt_hal.a COMPONENT_ADD_LDFRAGMENTS += linker.lf COMPONENT_SRCDIRS := . esp32 + +ifdef IS_BOOTLOADER_BUILD + COMPONENT_OBJEXCLUDE := xtensa_intr.o xtensa_intr_asm.o expression_with_stack_xtensa.o expression_with_stack_xtensa_asm.o +endif diff --git a/tools/test_apps/system/longjmp_test/CMakeLists.txt b/tools/test_apps/system/longjmp_test/CMakeLists.txt new file mode 100644 index 0000000000..8a022b8486 --- /dev/null +++ b/tools/test_apps/system/longjmp_test/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(hello-world) diff --git a/tools/test_apps/system/longjmp_test/Makefile b/tools/test_apps/system/longjmp_test/Makefile new file mode 100644 index 0000000000..e43bc1f177 --- /dev/null +++ b/tools/test_apps/system/longjmp_test/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := longjmp_test + +include $(IDF_PATH)/make/project.mk diff --git a/tools/test_apps/system/longjmp_test/README.md b/tools/test_apps/system/longjmp_test/README.md new file mode 100644 index 0000000000..5e9f1561f5 --- /dev/null +++ b/tools/test_apps/system/longjmp_test/README.md @@ -0,0 +1,13 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | + +# Building +Example building for ESP32: +``` +idf.py set-target esp32 +cp sdkconfig.defaults sdkconfig +idf.py build +``` + +# Running +All the setup needs to be done as described in the [test apps README](../../README.md). diff --git a/tools/test_apps/system/longjmp_test/app_test.py b/tools/test_apps/system/longjmp_test/app_test.py new file mode 100644 index 0000000000..0e4c2b6139 --- /dev/null +++ b/tools/test_apps/system/longjmp_test/app_test.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +import ttfw_idf +from tiny_test_fw import Utility + + +@ttfw_idf.idf_custom_test(env_tag='Example_GENERIC', target=['esp32', 'esp32s2'], group='test-apps') +def test_longjmp(env, _): + + dut = env.get_dut('longjmp_test', 'tools/test_apps/system/longjmp_test') + dut.start_app() + dut.expect('Test successful', 15) + + Utility.console_log('longjmp test done.') + + +if __name__ == '__main__': + test_longjmp() diff --git a/tools/test_apps/system/longjmp_test/main/CMakeLists.txt b/tools/test_apps/system/longjmp_test/main/CMakeLists.txt new file mode 100644 index 0000000000..07686dc8e1 --- /dev/null +++ b/tools/test_apps/system/longjmp_test/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "hello_world_main.c" + INCLUDE_DIRS "") diff --git a/tools/test_apps/system/longjmp_test/main/component.mk b/tools/test_apps/system/longjmp_test/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/tools/test_apps/system/longjmp_test/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/tools/test_apps/system/longjmp_test/main/hello_world_main.c b/tools/test_apps/system/longjmp_test/main/hello_world_main.c new file mode 100644 index 0000000000..c358efa334 --- /dev/null +++ b/tools/test_apps/system/longjmp_test/main/hello_world_main.c @@ -0,0 +1,118 @@ +/* test longjmp + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_spi_flash.h" +#include + +#include + +#define LUAI_NOIPA __attribute__((__noipa__)) +#define LUAI_THROW(c) longjmp((c)->b, 1) +#define LUAI_TRY(c,a) if (setjmp((c)->b) == 0) { a } + +#define TIMEOUT 50 + +#define RECURSION 19 + +static esp_timer_handle_t crash_timer; + +static uint32_t result = 0; + +uint32_t calc_fac(uint32_t n) { + if (n == 1 || n == 0) { + return 1; + } else { + return n * calc_fac(n - 1); + } +} + +static void timer_cb(void *arg) { + result = calc_fac(RECURSION); +} + +typedef struct { + jmp_buf b; +} jmp_ctx; + +LUAI_NOIPA +static void pret(jmp_ctx *jc) { + LUAI_THROW(jc); +} + +LUAI_NOIPA +static void precurse(jmp_ctx *jc, int n) { + if (n) precurse(jc, n - 1); + else pret(jc); +} + +LUAI_NOIPA +static void ptest(jmp_ctx *jc) { + precurse(jc, 64); +} + +LUAI_NOIPA +void pcall(void (*func)(jmp_ctx *ctx)) { + jmp_ctx jc; + LUAI_TRY(&jc, + ptest(&jc); + ); +} + +static void sjlj_task(void *ctx) { + uint32_t start = xTaskGetTickCount(); + for (;;) { + pcall(ptest); + uint32_t end = xTaskGetTickCount(); + + uint32_t dt = end - start; + if (dt >= 1000) { + start = end; + + printf("[%u] sjlj tick %d\n", end, (int)ctx); + } + + if (end > 9800) { + break; + } + } + + vTaskDelete(NULL); +} + +void app_main(void) +{ + const esp_timer_create_args_t timer_args = { + timer_cb, + NULL, + ESP_TIMER_TASK, + "crash_timer", + true, + }; + + esp_timer_create(&timer_args, &crash_timer); + esp_timer_start_periodic(crash_timer, TIMEOUT); + + printf("Hello world!\n"); + printf("Free heap: %d\n", esp_get_free_heap_size()); + + for (size_t i = 0; i < 16; i++) { + xTaskCreate(sjlj_task, "sjlj_task", 4096, (void *) i, tskIDLE_PRIORITY + 0, NULL); + } + + vTaskDelay(10000); + printf("stopping timers...\n"); + esp_timer_stop(crash_timer); + esp_timer_delete(crash_timer); + + printf("Test successful\n"); +} diff --git a/tools/test_apps/system/longjmp_test/sdkconfig.defaults b/tools/test_apps/system/longjmp_test/sdkconfig.defaults new file mode 100644 index 0000000000..23bffad44f --- /dev/null +++ b/tools/test_apps/system/longjmp_test/sdkconfig.defaults @@ -0,0 +1,11 @@ +CONFIG_COMPILER_OPTIMIZATION_PERF=y + +CONFIG_ESP_INT_WDT=n +CONFIG_ESP_TASK_WDT=n + +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y + +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 + +CONFIG_FREERTOS_UNICORE=y +CONFIG_FREERTOS_HZ=1000