From ffed60cc93db8149ca7c08a1170d2a976a617a0a Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Wed, 3 Aug 2022 13:59:44 +0800 Subject: [PATCH] ulp-riscv: added lock API to provide mutual exclusion when sharing variables between the main CPU and the ULP. --- components/ulp/CMakeLists.txt | 4 +- components/ulp/cmake/CMakeLists.txt | 4 +- .../test_apps/ulp_riscv/main/CMakeLists.txt | 1 + .../test_apps/ulp_riscv/main/test_ulp_riscv.c | 45 ++++++++++++------- .../test_apps/ulp_riscv/main/ulp/test_main.c | 36 ++++++++------- .../ulp_riscv/main/ulp/ulp_test_shared.h | 24 ++++++++++ .../ulp/ulp_riscv/include/ulp_riscv_lock.h | 38 ++++++++++++++++ .../shared/include/ulp_riscv_lock_shared.h | 35 +++++++++++++++ .../include/ulp_riscv_lock_ulp_core.h | 39 ++++++++++++++++ .../ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c | 21 +++++++++ components/ulp/ulp_riscv/ulp_riscv_lock.c | 27 +++++++++++ docs/doxygen/Doxyfile_esp32s2 | 2 + docs/doxygen/Doxyfile_esp32s3 | 2 + docs/en/api-reference/system/ulp-risc-v.rst | 15 ++++++- 14 files changed, 260 insertions(+), 33 deletions(-) create mode 100644 components/ulp/test_apps/ulp_riscv/main/ulp/ulp_test_shared.h create mode 100644 components/ulp/ulp_riscv/include/ulp_riscv_lock.h create mode 100644 components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h create mode 100644 components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_lock_ulp_core.h create mode 100644 components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c create mode 100644 components/ulp/ulp_riscv/ulp_riscv_lock.c diff --git a/components/ulp/CMakeLists.txt b/components/ulp/CMakeLists.txt index ebbab38890..3e5c1f71e1 100644 --- a/components/ulp/CMakeLists.txt +++ b/components/ulp/CMakeLists.txt @@ -24,10 +24,12 @@ if(CONFIG_SOC_ULP_SUPPORTED OR CONFIG_SOC_RISCV_COPROC_SUPPORTED) elseif(CONFIG_ULP_COPROC_TYPE_RISCV) list(APPEND srcs "ulp_riscv/ulp_riscv.c" + "ulp_riscv/ulp_riscv_lock.c" "ulp_riscv/ulp_riscv_adc.c") list(APPEND includes - ulp_riscv/include) + ulp_riscv/include + ulp_riscv/shared/include) endif() endif() diff --git a/components/ulp/cmake/CMakeLists.txt b/components/ulp/cmake/CMakeLists.txt index 028dfdeb98..db665973fe 100644 --- a/components/ulp/cmake/CMakeLists.txt +++ b/components/ulp/cmake/CMakeLists.txt @@ -75,6 +75,7 @@ if(ULP_COCPU_IS_RISCV) list(APPEND ULP_S_SOURCES "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/start.S" "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_adc.c" + "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c" "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_uart.c" "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_print.c" "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/ulp_riscv_utils.c") @@ -100,7 +101,8 @@ if(ULP_COCPU_IS_RISCV) list(APPEND EXTRA_LINKER_ARGS "-Wl,--gc-sections") list(APPEND EXTRA_LINKER_ARGS "-Wl,-Map=\"${CMAKE_CURRENT_BINARY_DIR}/${ULP_APP_NAME}.map\"") #Makes the csr utillies for riscv visible: - target_include_directories(${ULP_APP_NAME} PRIVATE "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/include") + target_include_directories(${ULP_APP_NAME} PRIVATE "${IDF_PATH}/components/ulp/ulp_riscv/ulp_core/include" + "${IDF_PATH}/components/ulp/ulp_riscv/shared/include") target_link_libraries(${ULP_APP_NAME} "-T \"${IDF_PATH}/components/ulp/ld/${IDF_TARGET}.periperals.ld\"") target_compile_definitions(${ULP_APP_NAME} PRIVATE IS_ULP_COCPU) diff --git a/components/ulp/test_apps/ulp_riscv/main/CMakeLists.txt b/components/ulp/test_apps/ulp_riscv/main/CMakeLists.txt index 3535f63068..3e153055b5 100644 --- a/components/ulp/test_apps/ulp_riscv/main/CMakeLists.txt +++ b/components/ulp/test_apps/ulp_riscv/main/CMakeLists.txt @@ -2,6 +2,7 @@ set(app_sources "test_app_main.c" "test_ulp_riscv.c") set(ulp_sources "ulp/test_main.c") idf_component_register(SRCS ${app_sources} + INCLUDE_DIRS "ulp" REQUIRES ulp unity WHOLE_ARCHIVE) diff --git a/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv.c b/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv.c index 16c0211444..4f49eb7596 100644 --- a/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv.c +++ b/components/ulp/test_apps/ulp_riscv/main/test_ulp_riscv.c @@ -12,27 +12,14 @@ #include "soc/sens_reg.h" #include "soc/rtc_periph.h" #include "ulp_riscv.h" +#include "ulp_riscv_lock.h" #include "ulp_test_app.h" +#include "ulp_test_shared.h" #include "unity.h" #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -typedef enum{ - RISCV_READ_WRITE_TEST = 1, - RISCV_DEEP_SLEEP_WAKEUP_TEST, - RISCV_LIGHT_SLEEP_WAKEUP_TEST, - RISCV_STOP_TEST, - RISCV_NO_COMMAND, -} riscv_test_commands_t; - -typedef enum { - RISCV_COMMAND_OK = 1, - RISCV_COMMAND_NOK, - RISCV_COMMAND_INVALID, -} riscv_test_command_reply_t; - -#define XOR_MASK 0xDEADBEEF #define ULP_WAKEUP_PERIOD 1000000 // 1 second extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_test_app_bin_start"); @@ -212,3 +199,31 @@ TEST_CASE("ULP-RISC-V is able to wakeup main CPU from deep sleep", "[ulp][reset= esp_deep_sleep_start(); UNITY_TEST_FAIL(__LINE__, "Should not get here!"); } + +TEST_CASE("ULP-RISC-V mutex", "[ulp]") +{ + /* Load ULP RISC-V firmware and start the ULP RISC-V Coprocessor */ + load_and_start_ulp_firmware(); + + /* Setup test data */ + ulp_riscv_incrementer = 0; + ulp_main_cpu_reply = RISCV_NO_COMMAND; + ulp_main_cpu_command = RISCV_MUTEX_TEST; + + ulp_riscv_lock_t *lock = (ulp_riscv_lock_t*)&ulp_lock; + + for (int i = 0; i < MUTEX_TEST_ITERATIONS; i++) { + ulp_riscv_lock_acquire(lock); + ulp_riscv_incrementer++; + ulp_riscv_lock_release(lock); + } + + while(ulp_main_cpu_reply != RISCV_COMMAND_OK) { + // Wait for ULP to finish + } + + /* If the variable is protected there should be no race conditions + results should be the sum of increments made by ULP and by main CPU + */ + TEST_ASSERT_EQUAL(2*MUTEX_TEST_ITERATIONS, ulp_riscv_incrementer); +} diff --git a/components/ulp/test_apps/ulp_riscv/main/ulp/test_main.c b/components/ulp/test_apps/ulp_riscv/main/ulp/test_main.c index 16a432571c..1a531cfac2 100644 --- a/components/ulp/test_apps/ulp_riscv/main/ulp/test_main.c +++ b/components/ulp/test_apps/ulp_riscv/main/ulp/test_main.c @@ -9,22 +9,9 @@ #include #include "ulp_riscv_utils.h" #include "ulp_riscv_gpio.h" +#include "ulp_riscv_lock_ulp_core.h" +#include "ulp_test_shared.h" -typedef enum{ - RISCV_READ_WRITE_TEST = 1, - RISCV_DEEP_SLEEP_WAKEUP_TEST, - RISCV_LIGHT_SLEEP_WAKEUP_TEST, - RISCV_STOP_TEST, - RISCV_NO_COMMAND, -} riscv_test_commands_t; - -typedef enum { - RISCV_COMMAND_OK = 1, - RISCV_COMMAND_NOK, - RISCV_COMMAND_INVALID, -} riscv_test_command_reply_t; - -#define XOR_MASK 0xDEADBEEF volatile riscv_test_commands_t main_cpu_command = RISCV_NO_COMMAND; volatile riscv_test_command_reply_t main_cpu_reply = RISCV_COMMAND_INVALID; @@ -33,6 +20,9 @@ volatile uint32_t riscv_test_data_in = 0; volatile uint32_t riscv_test_data_out = 0; volatile uint32_t riscv_counter = 0; +volatile uint32_t riscv_incrementer = 0; +ulp_riscv_lock_t lock; + void handle_commands(riscv_test_commands_t cmd) { riscv_counter++; @@ -87,6 +77,21 @@ void handle_commands(riscv_test_commands_t cmd) break; + case RISCV_MUTEX_TEST: + /* Echo the command ID back to the main CPU */ + command_resp = RISCV_MUTEX_TEST; + + for (int i = 0; i < MUTEX_TEST_ITERATIONS; i++) { + ulp_riscv_lock_acquire(&lock); + riscv_incrementer++; + ulp_riscv_lock_release(&lock); + } + /* Set the command reply status */ + main_cpu_reply = RISCV_COMMAND_OK; + main_cpu_command = RISCV_NO_COMMAND; + + break; + case RISCV_NO_COMMAND: main_cpu_reply = RISCV_COMMAND_OK; break; @@ -99,6 +104,7 @@ void handle_commands(riscv_test_commands_t cmd) int main (void) { + while (1) { handle_commands(main_cpu_command); break; diff --git a/components/ulp/test_apps/ulp_riscv/main/ulp/ulp_test_shared.h b/components/ulp/test_apps/ulp_riscv/main/ulp/ulp_test_shared.h new file mode 100644 index 0000000000..c12f828ad5 --- /dev/null +++ b/components/ulp/test_apps/ulp_riscv/main/ulp/ulp_test_shared.h @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#pragma once + +#define MUTEX_TEST_ITERATIONS 100000 +#define XOR_MASK 0xDEADBEEF + +typedef enum{ + RISCV_READ_WRITE_TEST = 1, + RISCV_DEEP_SLEEP_WAKEUP_TEST, + RISCV_LIGHT_SLEEP_WAKEUP_TEST, + RISCV_STOP_TEST, + RISCV_MUTEX_TEST, + RISCV_NO_COMMAND, +} riscv_test_commands_t; + +typedef enum { + RISCV_COMMAND_OK = 1, + RISCV_COMMAND_NOK, + RISCV_COMMAND_INVALID, +} riscv_test_command_reply_t; diff --git a/components/ulp/ulp_riscv/include/ulp_riscv_lock.h b/components/ulp/ulp_riscv/include/ulp_riscv_lock.h new file mode 100644 index 0000000000..597e3ed463 --- /dev/null +++ b/components/ulp/ulp_riscv/include/ulp_riscv_lock.h @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ulp_riscv_lock_shared.h" + +/** + * @brief Locks are based on the Peterson's algorithm, https://en.wikipedia.org/wiki/Peterson%27s_algorithm + * + */ + +/** + * @brief Acquire the lock, preventing the ULP from taking until released. Spins until lock is acquired. + * + * @note The lock is only designed for being used by a single thread on the main CPU, + * it is not safe to try to acquire it from multiple threads. + * + * @param lock Pointer to lock struct, shared with ULP + */ +void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock); + +/** + * @brief Release the lock + * + * @param lock Pointer to lock struct, shared with ULP + */ +void ulp_riscv_lock_release(ulp_riscv_lock_t *lock); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h b/components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h new file mode 100644 index 0000000000..67c12dac8c --- /dev/null +++ b/components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enum representing which processor is allowed to enter the critical section + * + */ +typedef enum { + ULP_RISCV_LOCK_TURN_ULP, /*!< ULP's turn to enter the critical section */ + ULP_RISCV_LOCK_TURN_MAIN_CPU, /*!< Main CPU's turn to enter the critical section */ +} ulp_riscv_lock_turn_t; + +/** + * @brief Structure representing a lock shared between ULP and main CPU + * + */ +typedef struct { + volatile bool critical_section_flag_ulp; /*!< ULP wants to enter the critical sections */ + volatile bool critical_section_flag_main_cpu; /*!< Main CPU wants to enter the critical sections */ + volatile ulp_riscv_lock_turn_t turn; /*!< Which CPU is allowed to enter the critical section */ +} ulp_riscv_lock_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_lock_ulp_core.h b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_lock_ulp_core.h new file mode 100644 index 0000000000..4f403058a1 --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_lock_ulp_core.h @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "ulp_riscv_lock_shared.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Locks are based on the Peterson's algorithm, https://en.wikipedia.org/wiki/Peterson%27s_algorithm + * + */ + +/** + * @brief Acquire the lock, preventing the main CPU from taking until released. Spins until lock is acquired. + * + * @note The lock is only designed for being used by a single thread on the ULP, + * it is not safe to try to acquire it from multiple threads. + * + * @param lock Pointer to lock struct, shared with main CPU + */ +void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock); + +/** + * @brief Release the lock + * + * @param lock Pointer to lock struct, shared with main CPU + */ +void ulp_riscv_lock_release(ulp_riscv_lock_t *lock); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c new file mode 100644 index 0000000000..d6f3b95dbd --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_lock.c @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "ulp_riscv_lock.h" +#include "ulp_riscv_lock_shared.h" + +void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock) +{ + lock->critical_section_flag_ulp = true; + lock->turn = ULP_RISCV_LOCK_TURN_MAIN_CPU; + + while (lock->critical_section_flag_main_cpu && (lock->turn == ULP_RISCV_LOCK_TURN_MAIN_CPU)) { + } +} + +void ulp_riscv_lock_release(ulp_riscv_lock_t *lock) +{ + lock->critical_section_flag_ulp = false; +} diff --git a/components/ulp/ulp_riscv/ulp_riscv_lock.c b/components/ulp/ulp_riscv/ulp_riscv_lock.c new file mode 100644 index 0000000000..cab59a8d0e --- /dev/null +++ b/components/ulp/ulp_riscv/ulp_riscv_lock.c @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "ulp_riscv_lock.h" +#include "ulp_riscv_lock_shared.h" + +#include + +void ulp_riscv_lock_acquire(ulp_riscv_lock_t *lock) +{ + assert(lock); + + lock->critical_section_flag_main_cpu = true; + lock->turn = ULP_RISCV_LOCK_TURN_ULP; + + while (lock->critical_section_flag_ulp && (lock->turn == ULP_RISCV_LOCK_TURN_ULP)) { + } +} + +void ulp_riscv_lock_release(ulp_riscv_lock_t *lock) +{ + assert(lock); + + lock->critical_section_flag_main_cpu = false; +} diff --git a/docs/doxygen/Doxyfile_esp32s2 b/docs/doxygen/Doxyfile_esp32s2 index f74884ca20..0aa2a6e963 100644 --- a/docs/doxygen/Doxyfile_esp32s2 +++ b/docs/doxygen/Doxyfile_esp32s2 @@ -25,6 +25,8 @@ INPUT += \ $(PROJECT_PATH)/components/touch_element/include/touch_element/touch_slider.h \ $(PROJECT_PATH)/components/ulp/ulp_common/include/$(IDF_TARGET)/ulp_common_defs.h \ $(PROJECT_PATH)/components/ulp/ulp_fsm/include/$(IDF_TARGET)/ulp.h \ + $(PROJECT_PATH)/components/ulp/ulp_riscv/include/ulp_riscv_lock.h \ + $(PROJECT_PATH)/components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_helpers.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_host.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_types_ch9.h \ diff --git a/docs/doxygen/Doxyfile_esp32s3 b/docs/doxygen/Doxyfile_esp32s3 index 4c247aaffa..1f6738be6d 100644 --- a/docs/doxygen/Doxyfile_esp32s3 +++ b/docs/doxygen/Doxyfile_esp32s3 @@ -31,6 +31,8 @@ INPUT += \ $(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/touch_sensor_channel.h \ $(PROJECT_PATH)/components/ulp/ulp_common/include/$(IDF_TARGET)/ulp_common_defs.h \ $(PROJECT_PATH)/components/ulp/ulp_fsm/include/$(IDF_TARGET)/ulp.h \ + $(PROJECT_PATH)/components/ulp/ulp_riscv/include/ulp_riscv_lock.h \ + $(PROJECT_PATH)/components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_helpers.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_host.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_types_ch9.h \ diff --git a/docs/en/api-reference/system/ulp-risc-v.rst b/docs/en/api-reference/system/ulp-risc-v.rst index a3088a0954..11b444be07 100644 --- a/docs/en/api-reference/system/ulp-risc-v.rst +++ b/docs/en/api-reference/system/ulp-risc-v.rst @@ -98,6 +98,18 @@ To access the ULP RISC-V program variables from the main program, the generated ulp_measurement_count = 64; } +Mutual Exclusion +^^^^^^^^^^^^^^^^ + +If mutual exclusion is needed when accessing a variable shared between the main program and ULP then this can be achieved by using the ULP RISC-V lock API: + + * :cpp:func:`ulp_riscv_lock_acquire` + * :cpp:func:`ulp_riscv_lock_release` + +The ULP does not have any hardware instructions to facilitate mutual exclusion so the lock API achieves this through a software algorithm (`Peterson's algorithm `_). + +The locks are intended to only be called from a single thread in the main program, and will not provide mutual exclusion if used simultaneously from multiple threads. + Starting the ULP RISC-V Program ------------------------------- @@ -151,7 +163,6 @@ Keeping this in mind, here are some ways that may help you debug you ULP RISC-V * Trap signal: the ULP RISC-V has a hardware trap that will trigger under certain conditions, e.g. illegal instruction. This will cause the main CPU to be woken up with the wake-up cause :cpp:enumerator:`ESP_SLEEP_WAKEUP_COCPU_TRAP_TRIG`. - Application Examples -------------------- @@ -163,3 +174,5 @@ API Reference ------------- .. include-build-file:: inc/ulp_riscv.inc +.. include-build-file:: inc/ulp_riscv_lock_shared.inc +.. include-build-file:: inc/ulp_riscv_lock.inc