Skip to content

Commit 446528d

Browse files
committed
feat(ulp): implement ulp lp core spinlock
1 parent a760bd2 commit 446528d

File tree

12 files changed

+431
-2
lines changed

12 files changed

+431
-2
lines changed

components/ulp/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ endif()
5252
if(CONFIG_ULP_COPROC_TYPE_LP_CORE)
5353
list(APPEND srcs
5454
"lp_core/lp_core.c"
55-
"lp_core/shared/ulp_lp_core_memory_shared.c")
55+
"lp_core/shared/ulp_lp_core_memory_shared.c"
56+
"lp_core/shared/ulp_lp_core_critical_section_shared.c")
5657

5758
if(CONFIG_SOC_ULP_LP_UART_SUPPORTED)
5859
list(APPEND srcs "lp_core/lp_core_uart.c")

components/ulp/cmake/IDFULPProject.cmake

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ function(ulp_apply_default_sources ulp_app_name)
125125
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_i2c.c"
126126
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_spi.c"
127127
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_ubsan.c"
128-
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c")
128+
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c"
129+
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_critical_section_shared.c")
129130

130131
set(target_folder ${IDF_TARGET})
131132

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#pragma once
8+
9+
#include "sdkconfig.h"
10+
11+
#ifdef __cplusplus
12+
extern "C" {
13+
#endif
14+
15+
typedef enum {
16+
LOCK_CANDIDATE_LP_CORE = 0,
17+
LOCK_CANDIDATE_HP_CORE_0,
18+
#if CONFIG_FREERTOS_NUMBER_OF_CORES > 1
19+
LOCK_CANDIDATE_HP_CORE_1,
20+
#endif
21+
LOCK_CANDIDATE_NUM_MAX,
22+
} ulp_lp_core_spinlock_candidate_t;
23+
24+
typedef struct {
25+
volatile int level[LOCK_CANDIDATE_NUM_MAX];
26+
volatile int last_to_enter[LOCK_CANDIDATE_NUM_MAX - 1];
27+
volatile unsigned int prev_int_level;
28+
} ulp_lp_core_spinlock_t;
29+
30+
/**
31+
* @brief Initialize the spinlock that protects shared resources between main CPU and LP CPU.
32+
*
33+
* @note The spinlock can be initialized in either main program or LP program.
34+
*
35+
* @param lock Pointer to lock struct
36+
*/
37+
void ulp_lp_core_spinlock_init(ulp_lp_core_spinlock_t *lock);
38+
39+
/**
40+
* @brief Enter the critical section that protects shared resources between main CPU and LP CPU.
41+
*
42+
* @note This critical section is designed for being used by multiple threads, it is safe to try to enter it
43+
* simultaneously from multiple threads on multiple main CPU cores and LP CPU.
44+
*
45+
* @note This critical section does not support nesting entering and exiting.
46+
*
47+
* @param lock Pointer to lock struct
48+
*/
49+
void ulp_lp_core_enter_critical(ulp_lp_core_spinlock_t *lock);
50+
51+
/**
52+
* @brief Exit the critical section that protect shared resource between main CPU and LP CPU.
53+
*
54+
* @note This critical section does not support nesting entering and exiting.
55+
*
56+
* @param lock Pointer to lock struct
57+
*/
58+
void ulp_lp_core_exit_critical(ulp_lp_core_spinlock_t *lock);
59+
60+
#ifdef __cplusplus
61+
}
62+
#endif
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include "ulp_lp_core_critical_section_shared.h"
8+
#include "esp_cpu.h"
9+
#if IS_ULP_COCPU
10+
#include "ulp_lp_core_interrupts.h"
11+
#endif
12+
13+
/* Masked interrupt threshold varies between different types of interrupt controller */
14+
#if !SOC_INT_CLIC_SUPPORTED
15+
#define INT_MASK_THRESHOLD RVHAL_EXCM_LEVEL
16+
#else /* SOC_INT_CLIC_SUPPORTED */
17+
#define INT_MASK_THRESHOLD RVHAL_EXCM_LEVEL_CLIC
18+
#endif /* !SOC_INIT_CLIC_SUPPORTED */
19+
20+
void ulp_lp_core_spinlock_init(ulp_lp_core_spinlock_t *lock)
21+
{
22+
int i = 0;
23+
for (i = 0; i < LOCK_CANDIDATE_NUM_MAX; i++) {
24+
lock->level[i] = -1;
25+
}
26+
27+
for (i = 0; i < LOCK_CANDIDATE_NUM_MAX - 1; i++) {
28+
lock->last_to_enter[i] = 0;
29+
}
30+
}
31+
32+
#if RTC_MEM_AMO_INSTRUCTIONS_VERIFIED
33+
/* ULP spinlock ACQ/REL functions also have a hardware implementation base on AMOSWAP instructions,
34+
which has much better performance but have not been thoroughly verified yet on RTC memory. This set
35+
of implementation will be adapted once it's verified.
36+
*/
37+
static void ulp_lp_core_spinlock_acquire(ulp_lp_core_spinlock_t *lock)
38+
{
39+
40+
/* Based on sample code for AMOSWAP from RISCV specs v2.1 */
41+
asm volatile(
42+
"li t0, 1\n" // Initialize swap value.
43+
"1:\n"
44+
"lw t1, (%0)\n" // Check if lock is held.
45+
"bnez t1, 1b\n" // Retry if held.
46+
"amoswap.w.aq t1, t0, (%0)\n" // Attempt to acquire lock.
47+
"bnez t1, 1b\n" // Retry if held.
48+
:
49+
: "r"(lock)
50+
: "t0", "t1", "memory"
51+
);
52+
}
53+
54+
static void ulp_lp_core_spinlock_release(ulp_lp_core_spinlock_t *lock)
55+
{
56+
asm volatile(
57+
"amoswap.w.rl x0, x0, (%0)\n" // Release lock by storing 0
58+
:
59+
: "r"(lock)
60+
: "memory"
61+
);
62+
}
63+
#else // !RTC_MEM_AMO_INSTRUCTIONS_VERIFIED
64+
/**
65+
* @brief Obtain the id of current lock candidate.
66+
*
67+
* @return lock candidate id
68+
*/
69+
static int ulp_lp_core_spinlock_get_candidate_id(void)
70+
{
71+
int lock_candidate_id = 0;
72+
#if !IS_ULP_COCPU
73+
/* Refer to ulp_lp_core_spinlock_candidate_t, each HP_CORE lock candidate's index is the core id plus 1 */
74+
lock_candidate_id = esp_cpu_get_core_id() + 1;
75+
#else
76+
lock_candidate_id = LOCK_CANDIDATE_LP_CORE;
77+
#endif
78+
return lock_candidate_id;
79+
}
80+
81+
/**
82+
* @brief Software lock implementation is based on the filter algorithm, which is a generalization of Peterson's algorithm, https://en.wikipedia.org/wiki/Peterson%27s_algorithm#Filter_algorithm:_Peterson's_algorithm_for_more_than_two_processes
83+
*
84+
*/
85+
86+
/**
87+
* @brief Attempt to acquire the lock. Spins until lock is acquired.
88+
*
89+
* @note This lock is designed for being used by multiple threads, it is safe to try to acquire it
90+
* simultaneously from multiple threads on multiple main CPU cores and LP CPU.
91+
*
92+
* @note This function is private to ulp lp core critical section and shall not be called from anywhere else.
93+
*
94+
* @param lock Pointer to lock struct, shared with ULP
95+
*/
96+
static void ulp_lp_core_spinlock_acquire(ulp_lp_core_spinlock_t *lock)
97+
{
98+
/* Level index */
99+
int lv = 0;
100+
/* Candidate index */
101+
int candidate = 0;
102+
103+
/* Index of the current lock candidate */
104+
int lock_candidate_id = ulp_lp_core_spinlock_get_candidate_id();
105+
106+
for (lv = 0; lv < (int)LOCK_CANDIDATE_NUM_MAX - 1; lv++) {
107+
/* Each candidate has to go through all the levels in order to get the spinlock. Start by notifying other candidates, we have reached level `lv` */
108+
lock->level[lock_candidate_id] = lv;
109+
/* Notify other candidates we are the latest one who entered level `lv` */
110+
lock->last_to_enter[lv] = lock_candidate_id;
111+
/* If there is any candidate that reached the same or a higher level than this candidate, wait for it to finish. Advance to the next level if another candidate becomes the latest one to arrive at our current level */
112+
for (candidate = 0; candidate < (int)LOCK_CANDIDATE_NUM_MAX; candidate++) {
113+
while ((candidate != lock_candidate_id) && (lock->level[candidate] >= lv && lock->last_to_enter[lv] == lock_candidate_id)) {
114+
}
115+
}
116+
}
117+
}
118+
119+
/**
120+
* @brief Release the lock.
121+
*
122+
* @note This function is private to ulp lp core critical section and shall not be called from anywhere else.
123+
*
124+
* @param lock Pointer to lock struct, shared with ULP
125+
*/
126+
static void ulp_lp_core_spinlock_release(ulp_lp_core_spinlock_t *lock)
127+
{
128+
int lock_candidate_id = ulp_lp_core_spinlock_get_candidate_id();
129+
130+
lock->level[lock_candidate_id] = -1;
131+
}
132+
#endif
133+
134+
void ulp_lp_core_enter_critical(ulp_lp_core_spinlock_t *lock)
135+
{
136+
/* disable interrupt */
137+
#if !IS_ULP_COCPU // HP core
138+
unsigned prev_int_level = rv_utils_set_intlevel_regval(INT_MASK_THRESHOLD);
139+
lock->prev_int_level = prev_int_level;
140+
#else // LP core
141+
ulp_lp_core_intr_disable();
142+
#endif
143+
/* Busy-wait to acquire the spinlock. Use caution when deploying this lock in time-sensitive scenarios. */
144+
ulp_lp_core_spinlock_acquire(lock);
145+
}
146+
147+
void ulp_lp_core_exit_critical(ulp_lp_core_spinlock_t *lock)
148+
{
149+
ulp_lp_core_spinlock_release(lock);
150+
151+
/* re-enable interrupt */
152+
#if !IS_ULP_COCPU // HP core
153+
unsigned prev_int_level = lock->prev_int_level;
154+
rv_utils_restore_intlevel_regval(prev_int_level);
155+
#else // LP core
156+
ulp_lp_core_intr_enable();
157+
#endif
158+
}

examples/system/.build-test-rules.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,12 @@ examples/system/ulp/lp_core/gpio_intr_pulse_counter:
301301
depends_components:
302302
- ulp
303303

304+
examples/system/ulp/lp_core/inter_cpu_critical_section/:
305+
enable:
306+
- if: SOC_LP_CORE_SUPPORTED == 1
307+
depends_components:
308+
- ulp
309+
304310
examples/system/ulp/lp_core/interrupt:
305311
enable:
306312
- if: SOC_LP_CORE_SUPPORTED == 1
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# This is the project CMakeLists.txt file for the test subproject
2+
cmake_minimum_required(VERSION 3.16)
3+
4+
list(APPEND SDKCONFIG_DEFAULTS "sdkconfig.defaults")
5+
6+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
7+
project(lp_inter_cpu_critical_section_example)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-P4 |
2+
| ----------------- | -------- | -------- | -------- |
3+
4+
# LP Core simple example with inter-CPU critical section:
5+
6+
(See the README.md file in the upper level 'examples' directory for more information about examples.)
7+
8+
## Overview
9+
This example demonstrates the use of an inter-CPU critical section to safeguard shared resources between the main program and the ULP (Ultra-Low-Power) processor.
10+
11+
In this example, a global shared counter `shared_cnt` is incremented by both the high-performance CPU (HP CPU) and the low-power CPU (LP CPU) in turn. To ensure mutual exclusion, a global inter-CPU spinlock is used. With this protection in place, both the HP and LP CPUs attempt to increment the shared counter 100,000 times each.
12+
13+
The inter-CPU critical section is implemented using a global spinlock of type ulp_lp_core_spinlock_t. This type of spinlock is especially designed to protect shared resources between main program and ULP program. It supports multiple threads to attempt to enter critical section simultaneously, while eventually only one thread can succeed. This spinlock must be declared within the ULP program and shall be initialized in either the main program or the ULP program before using it. Note that this critical section doesn't support nested entering and exiting.
14+
15+
## How to use this example
16+
17+
### Build and Flash
18+
19+
Enter `idf.py -p PORT flash monitor` to build, flash and monitor the project.
20+
21+
(To exit the serial monitor, type ``Ctrl-]``.)
22+
23+
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
24+
25+
To observe the mutual exclusion between HP and LP CPUs straight forward, the output of LP CPU is routed to HP CPU‘s console.
26+
27+
## Example output
28+
```
29+
LP CPU's increment starts, shared counter = 0
30+
core 0 started, cnt = 5868
31+
HP CPU's increment ends, shared counter = 165882
32+
LP CPU's increment ends, shared counter = 250000
33+
...
34+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Register the component
2+
idf_component_register(SRCS "lp_inter_cpu_critical_section_main.c"
3+
INCLUDE_DIRS ""
4+
REQUIRES ulp)
5+
#
6+
# ULP support additions to component CMakeLists.txt.
7+
#
8+
# 1. The LP Core app name must be unique (if multiple components use LP Core).
9+
set(ulp_app_name lp_core_${COMPONENT_NAME})
10+
#
11+
# 2. Specify all C files.
12+
# Files should be placed into a separate directory (in this case, lp_core/),
13+
# which should not be added to COMPONENT_SRCS.
14+
set(ulp_lp_core_sources "lp_core/main.c")
15+
16+
#
17+
# 3. List all the component source files which include automatically
18+
# generated LP Core export file, ${ulp_app_name}.h:
19+
set(ulp_exp_dep_srcs "lp_inter_cpu_critical_section_main..c")
20+
21+
#
22+
# 4. Call function to build ULP binary and embed in project using the argument
23+
# values above.
24+
ulp_embed_binary(${ulp_app_name} "${ulp_lp_core_sources}" "${ulp_exp_dep_srcs}")
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include "ulp_lp_core_print.h"
8+
#include "ulp_lp_core_utils.h"
9+
#include "ulp_lp_core_critical_section_shared.h"
10+
11+
volatile uint32_t shared_cnt = 0;
12+
ulp_lp_core_spinlock_t lp_spinlock;
13+
14+
int main (void)
15+
{
16+
/* Initialize the inter-processor spinlock. This must be done on either of HP core and LP core */
17+
ulp_lp_core_spinlock_init(&lp_spinlock);
18+
19+
/* Delay 10ms in case of interleaved console output */
20+
ulp_lp_core_delay_us(10000);
21+
22+
lp_core_printf("LP CPU's increment starts, shared counter = %d\r\n", shared_cnt);
23+
24+
for (int i = 0; i < 100000; i++) {
25+
ulp_lp_core_enter_critical(&lp_spinlock);
26+
shared_cnt++;
27+
ulp_lp_core_exit_critical(&lp_spinlock);
28+
}
29+
30+
lp_core_printf("LP CPU's increment ends, shared counter = %d\r\n", shared_cnt);
31+
return 0;
32+
}

0 commit comments

Comments
 (0)