Skip to content

Commit eced9d8

Browse files
projectgusdpgeorge
authored andcommitted
rp2: Fix power consumption when sleeping with a timeout.
Fixes a regression introduced in 3af006e where WFE never blocked in `mp_wfe_or_timeout()` function and would busy-wait instead. This increases power consumption measurably. Root cause is that `mp_wfe_or_timeout()` calls soft timer functions that (after the regression) call `recursive_mutex_enter()` and `recursive_mutex_exit()`. The exit calls `lock_internal_spin_unlock_with_notify()` and the default pico-sdk implementation of this macro issues a SEV which negates the WFE that follows it, meaning the CPU never suspends. See https://forums.raspberrypi.com/viewtopic.php?p=2233908 for more details. The fix in this comment adds a custom "nowait" variant mutex that doesn't do WFE/SEV, and uses this one for PendSV. This will use more power when there's contention for the PendSV mutex as the other core will spin, but this shouldn't happen very often. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <[email protected]>
1 parent 44527ad commit eced9d8

File tree

3 files changed

+56
-9
lines changed

3 files changed

+56
-9
lines changed

ports/rp2/mutex_extra.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,22 @@ void __time_critical_func(recursive_mutex_exit_and_restore_interrupts)(recursive
3333
}
3434
lock_internal_spin_unlock_with_notify(&mtx->core, save);
3535
}
36+
37+
void __time_critical_func(recursive_mutex_nowait_enter_blocking)(recursive_mutex_nowait_t * mtx) {
38+
while (!recursive_mutex_try_enter(&mtx->mutex, NULL)) {
39+
tight_loop_contents();
40+
}
41+
}
42+
43+
void __time_critical_func(recursive_mutex_nowait_exit)(recursive_mutex_nowait_t * wrapper) {
44+
recursive_mutex_t *mtx = &wrapper->mutex;
45+
// Rest of this function is a copy of recursive_mutex_exit(), with
46+
// lock_internal_spin_unlock_with_notify() removed.
47+
uint32_t save = spin_lock_blocking(mtx->core.spin_lock);
48+
assert(lock_is_owner_id_valid(mtx->owner));
49+
assert(mtx->enter_count);
50+
if (!--mtx->enter_count) {
51+
mtx->owner = LOCK_INVALID_OWNER_ID;
52+
}
53+
spin_unlock(mtx->core.spin_lock, save);
54+
}

ports/rp2/mutex_extra.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,28 @@
3131
uint32_t recursive_mutex_enter_blocking_and_disable_interrupts(recursive_mutex_t *mtx);
3232
void recursive_mutex_exit_and_restore_interrupts(recursive_mutex_t *mtx, uint32_t save);
3333

34+
// Alternative version of recursive_mutex_t that doesn't issue WFE and SEV
35+
// instructions. This means it will use more power (busy-waits), but exiting
36+
// this mutex doesn't disrupt the calling CPU's event state in the same way a
37+
// recursive mutex does (because recurse_mutex_exit() executes SEV each time the
38+
// mutex is released.)
39+
//
40+
// Implement as a wrapper type because no longer compatible with the normal
41+
// recursive_mutex functions.
42+
43+
typedef struct {
44+
recursive_mutex_t mutex;
45+
} recursive_mutex_nowait_t;
46+
47+
inline static void recursive_mutex_nowait_init(recursive_mutex_nowait_t *mtx) {
48+
recursive_mutex_init(&mtx->mutex);
49+
}
50+
51+
inline static bool recursive_mutex_nowait_try_enter(recursive_mutex_nowait_t *mtx, uint32_t *owner_out) {
52+
return recursive_mutex_try_enter(&mtx->mutex, owner_out);
53+
}
54+
55+
void recursive_mutex_nowait_enter_blocking(recursive_mutex_nowait_t *mtx);
56+
void recursive_mutex_nowait_exit(recursive_mutex_nowait_t *mtx);
57+
3458
#endif // MICROPY_INCLUDED_RP2_MUTEX_EXTRA_H

ports/rp2/pendsv.c

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
*/
2626

2727
#include <assert.h>
28-
#include "pico/mutex.h"
2928
#include "py/mpconfig.h"
29+
#include "mutex_extra.h"
3030
#include "pendsv.h"
3131
#include "RP2040.h"
3232

@@ -35,21 +35,25 @@
3535
#endif
3636

3737
static pendsv_dispatch_t pendsv_dispatch_table[PENDSV_DISPATCH_NUM_SLOTS];
38-
static recursive_mutex_t pendsv_mutex;
38+
39+
// Using the nowait variant here as softtimer updates PendSV from the loop of mp_wfe_or_timeout(),
40+
// where we don't want the CPU event bit to be set.
41+
static recursive_mutex_nowait_t pendsv_mutex;
3942

4043
void pendsv_init(void) {
41-
recursive_mutex_init(&pendsv_mutex);
44+
recursive_mutex_nowait_init(&pendsv_mutex);
4245
}
4346

4447
void pendsv_suspend(void) {
4548
// Recursive Mutex here as either core may call pendsv_suspend() and expect
4649
// both mutual exclusion (other core can't enter pendsv_suspend() at the
4750
// same time), and that no PendSV handler will run.
48-
recursive_mutex_enter_blocking(&pendsv_mutex);
51+
recursive_mutex_nowait_enter_blocking(&pendsv_mutex);
4952
}
5053

5154
void pendsv_resume(void) {
52-
recursive_mutex_exit(&pendsv_mutex);
55+
recursive_mutex_nowait_exit(&pendsv_mutex);
56+
5357
// Run pendsv if needed. Find an entry with a dispatch and call pendsv dispatch
5458
// with it. If pendsv runs it will service all slots.
5559
int count = PENDSV_DISPATCH_NUM_SLOTS;
@@ -63,7 +67,7 @@ void pendsv_resume(void) {
6367

6468
void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) {
6569
pendsv_dispatch_table[slot] = f;
66-
if (pendsv_mutex.enter_count == 0) {
70+
if (pendsv_mutex.mutex.enter_count == 0) {
6771
// There is a race here where other core calls pendsv_suspend() before
6872
// ISR can execute, but dispatch will happen later when other core
6973
// calls pendsv_resume().
@@ -78,13 +82,13 @@ void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) {
7882
// PendSV interrupt handler to perform background processing.
7983
void PendSV_Handler(void) {
8084

81-
if (!recursive_mutex_try_enter(&pendsv_mutex, NULL)) {
85+
if (!recursive_mutex_nowait_try_enter(&pendsv_mutex, NULL)) {
8286
// Failure here means core 1 holds pendsv_mutex. ISR will
8387
// run again after core 1 calls pendsv_resume().
8488
return;
8589
}
8690
// Core 0 should not already have locked pendsv_mutex
87-
assert(pendsv_mutex.enter_count == 1);
91+
assert(pendsv_mutex.mutex.enter_count == 1);
8892

8993
#if MICROPY_PY_NETWORK_CYW43
9094
CYW43_STAT_INC(PENDSV_RUN_COUNT);
@@ -98,5 +102,5 @@ void PendSV_Handler(void) {
98102
}
99103
}
100104

101-
recursive_mutex_exit(&pendsv_mutex);
105+
recursive_mutex_nowait_exit(&pendsv_mutex);
102106
}

0 commit comments

Comments
 (0)