Skip to content

Commit cfa55b4

Browse files
projectgusdpgeorge
authored andcommitted
rp2: Fix recursive atomic sections when core1 is active.
mp_thread_begin_atomic_section() is expected to be recursive (i.e. for nested machine.disable_irq() calls, or if Python code calls disable_irq() and then the Python runtime calls mp_handle_pending() which also enters an atomic section to check the scheduler state). On rp2 when not using core1 the atomic sections are recursive. However when core1 was active (i.e. _thread) then there was a bug that caused the core to live-lock if an atomic section recursed. Adds a test case specifically for mutual exclusion and recursive atomic sections when using two threads. Without this fix the test immediately hangs on rp2. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <[email protected]>
1 parent 908ab1c commit cfa55b4

File tree

5 files changed

+68
-10
lines changed

5 files changed

+68
-10
lines changed

ports/rp2/mpthreadport.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,21 @@ static uint32_t *core1_stack = NULL;
4646
static size_t core1_stack_num_words = 0;
4747

4848
// Thread mutex.
49-
static mutex_t atomic_mutex;
49+
static recursive_mutex_t atomic_mutex;
5050

5151
uint32_t mp_thread_begin_atomic_section(void) {
5252
if (core1_entry) {
5353
// When both cores are executing, we also need to provide
5454
// full mutual exclusion.
55-
return mutex_enter_blocking_and_disable_interrupts(&atomic_mutex);
55+
return recursive_mutex_enter_blocking_and_disable_interrupts(&atomic_mutex);
5656
} else {
5757
return save_and_disable_interrupts();
5858
}
5959
}
6060

6161
void mp_thread_end_atomic_section(uint32_t state) {
6262
if (atomic_mutex.owner != LOCK_INVALID_OWNER_ID) {
63-
mutex_exit_and_restore_interrupts(&atomic_mutex, state);
63+
recursive_mutex_exit_and_restore_interrupts(&atomic_mutex, state);
6464
} else {
6565
restore_interrupts(state);
6666
}
@@ -70,7 +70,7 @@ void mp_thread_end_atomic_section(uint32_t state) {
7070
void mp_thread_init(void) {
7171
assert(get_core_num() == 0);
7272

73-
mutex_init(&atomic_mutex);
73+
recursive_mutex_init(&atomic_mutex);
7474

7575
// Allow MICROPY_BEGIN_ATOMIC_SECTION to be invoked from core1.
7676
multicore_lockout_victim_init();

ports/rp2/mutex_extra.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,27 @@
99
// These functions are taken from lib/pico-sdk/src/common/pico_sync/mutex.c and modified
1010
// so that they atomically obtain the mutex and disable interrupts.
1111

12-
uint32_t __time_critical_func(mutex_enter_blocking_and_disable_interrupts)(mutex_t * mtx) {
12+
uint32_t __time_critical_func(recursive_mutex_enter_blocking_and_disable_interrupts)(recursive_mutex_t * mtx) {
1313
lock_owner_id_t caller = lock_get_caller_owner_id();
1414
do {
1515
uint32_t save = spin_lock_blocking(mtx->core.spin_lock);
16-
if (!lock_is_owner_id_valid(mtx->owner)) {
16+
if (mtx->owner == caller || !lock_is_owner_id_valid(mtx->owner)) {
1717
mtx->owner = caller;
18+
uint __unused total = ++mtx->enter_count;
1819
spin_unlock_unsafe(mtx->core.spin_lock);
20+
assert(total); // check for overflow
1921
return save;
2022
}
2123
lock_internal_spin_unlock_with_wait(&mtx->core, save);
2224
} while (true);
2325
}
2426

25-
void __time_critical_func(mutex_exit_and_restore_interrupts)(mutex_t * mtx, uint32_t save) {
27+
void __time_critical_func(recursive_mutex_exit_and_restore_interrupts)(recursive_mutex_t * mtx, uint32_t save) {
2628
spin_lock_unsafe_blocking(mtx->core.spin_lock);
2729
assert(lock_is_owner_id_valid(mtx->owner));
28-
mtx->owner = LOCK_INVALID_OWNER_ID;
30+
assert(mtx->enter_count);
31+
if (!--mtx->enter_count) {
32+
mtx->owner = LOCK_INVALID_OWNER_ID;
33+
}
2934
lock_internal_spin_unlock_with_notify(&mtx->core, save);
3035
}

ports/rp2/mutex_extra.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
#include "pico/mutex.h"
3030

31-
uint32_t mutex_enter_blocking_and_disable_interrupts(mutex_t *mtx);
32-
void mutex_exit_and_restore_interrupts(mutex_t *mtx, uint32_t save);
31+
uint32_t recursive_mutex_enter_blocking_and_disable_interrupts(recursive_mutex_t *mtx);
32+
void recursive_mutex_exit_and_restore_interrupts(recursive_mutex_t *mtx, uint32_t save);
3333

3434
#endif // MICROPY_INCLUDED_RP2_MUTEX_EXTRA_H

tests/thread/disable_irq.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Ensure that disabling IRQs creates mutual exclusion between threads
2+
# (also tests nesting of disable_irq across threads)
3+
import machine
4+
import time
5+
import _thread
6+
7+
if not hasattr(machine, "disable_irq"):
8+
print("SKIP")
9+
raise SystemExit
10+
11+
count = 0
12+
thread_done = False
13+
14+
15+
def inc_count():
16+
global count
17+
a = machine.disable_irq()
18+
try:
19+
count += 1
20+
i = 0
21+
while i < 20:
22+
b = machine.disable_irq()
23+
try:
24+
count += 1
25+
count -= 1
26+
i += 1
27+
finally:
28+
machine.enable_irq(b)
29+
finally:
30+
machine.enable_irq(a)
31+
32+
33+
def inc_count_multiple(times):
34+
for _ in range(times):
35+
inc_count()
36+
37+
38+
def thread_entry(inc_times):
39+
global thread_done
40+
inc_count_multiple(inc_times)
41+
thread_done = True
42+
43+
44+
_thread.start_new_thread(thread_entry, (1000,))
45+
inc_count_multiple(1000)
46+
47+
time.sleep(1)
48+
49+
print("count", count, thread_done)
50+
if count == 2000:
51+
print("PASS")

tests/thread/disable_irq.py.exp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
count 2000 True
2+
PASS

0 commit comments

Comments
 (0)