Skip to content

rp2: Fix soft timer expiry waking early from lightsleep. #16431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 25 additions & 16 deletions ports/rp2/modmachine.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,17 +196,8 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {
#endif
xosc_dormant();
} else {
bool timer3_enabled = irq_is_enabled(3);

const uint32_t alarm_num = 3;
const uint32_t irq_num = TIMER_ALARM_IRQ_NUM(timer_hw, alarm_num);
uint32_t timer_irq_num;
if (use_timer_alarm) {
// Make sure ALARM3/IRQ3 is enabled on _this_ core
if (!timer3_enabled) {
irq_set_enabled(irq_num, true);
}
hw_set_bits(&timer_hw->inte, 1u << alarm_num);
// Use timer alarm to wake.
clocks_hw->sleep_en0 = 0x0;
#if PICO_RP2040
clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_SYS_TIMER_BITS;
Expand All @@ -215,8 +206,18 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {
#else
#error Unknown processor
#endif
timer_hw->intr = 1u << alarm_num; // clear any IRQ
timer_hw->alarm[alarm_num] = timer_hw->timerawl + delay_ms * 1000;

// Clobber the soft timer hardware alarm (as soft timer doesn't wake lightsleep)
// and repurpose it for the lightsleep wakeup
//
// The timer peripheral will already be configured via softtimer_init().
if (get_core_num() == 1) {
// On CPU0 the hardware alarm interrupt is already enabled, but IRQs won't be serviced
// due to the critical section so we also need to temporarily enable this IRQ on our CPU
timer_irq_num = hardware_alarm_get_irq_num(PICO_DEFAULT_TIMER_INSTANCE(), MICROPY_HW_SOFT_TIMER_ALARM_NUM);
irq_set_enabled(timer_irq_num, true);
}
hardware_alarm_set_target(MICROPY_HW_SOFT_TIMER_ALARM_NUM, delayed_by_ms(get_absolute_time(), delay_ms));
} else {
// TODO: Use RTC alarm to wake.
clocks_hw->sleep_en0 = 0x0;
Expand Down Expand Up @@ -247,17 +248,25 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {

// Go into low-power mode.
__wfi();

if (!timer3_enabled) {
irq_set_enabled(irq_num, false);
}
clocks_hw->sleep_en0 |= ~(0u);
clocks_hw->sleep_en1 |= ~(0u);

if (use_timer_alarm && get_core_num() == 1) {
// We don't have an easy way to clear any pending IRQ on this CPU,
// so we might get a spurious soft timer interrupt handler run on
// CPU1 unfortunately - but this should be harmless
irq_set_enabled(timer_irq_num, false);
}
}

// Enable ROSC.
rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB;

// we might have missed at least one soft timer expiry that hasn't been processed
// depending on when we woke up, so force soft timer processing for when we exit
// the atomic section
hardware_alarm_force_irq(MICROPY_HW_SOFT_TIMER_ALARM_NUM);

// Bring back all clocks.
runtime_init_clocks_optional_usb(disable_usb);
MICROPY_END_ATOMIC_SECTION(my_interrupts);
Expand Down
10 changes: 7 additions & 3 deletions ports/rp2/mphalport.c
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,13 @@ static void soft_timer_hardware_callback(unsigned int alarm_num) {
// a second ISR, as PendSV may be currently suspended by the other CPU.
pendsv_schedule_dispatch(PENDSV_DISPATCH_SOFT_TIMER, soft_timer_handler);

// This ISR only runs on core0, but if core1 is running Python code then it
// may be blocked in WFE so wake it up as well. Unfortunately this also sets
// the event flag on core0, so a subsequent WFE on this core will not suspend
// This ISR mostly only runs on core0 but may run on core1 if core1 calls
// machine.lightsleep().
//
// Either way, if the other core is running Python code then it may be
// blocked in WFE so wake it up as well. Unfortunately this also sets the
// event flag on this core, so a subsequent WFE on this core will not
// suspend
#if MICROPY_PY_THREAD
if (core1_entry != NULL) {
__sev();
Expand Down
22 changes: 20 additions & 2 deletions tests/ports/rp2/rp2_lightsleep.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# A range of sleep periods (1 to 512ms) are tested. The total nominal sleep time
# is 10.23 seconds, but on most ports this will finish much earlier as interrupts
# happen before each timeout expires.
import sys
import sys, time

try:
from machine import lightsleep, Pin
Expand All @@ -27,10 +27,28 @@
except AttributeError:
led = None

t0 = time.ticks_ms()

for n in range(100):
if led:
led.toggle()
sys.stdout.write(chr(ord("a") + (n % 26)))
lightsleep(2 ** (n % 10))

print("\nDONE")
print() # end of line

delta = time.ticks_diff(time.ticks_ms(), t0)

if delta < 2500:
# As per note above, we don't expect the full 10.23 seconds
# but a very short total runtime may indicate a bug with
# lightsleep
print("Unexpected short test time", delta, "ms")
elif delta > 11000:
# Similarly, the test shouldn't take much longer than the max lightsleep
# time
print("Unexpected long test time", delta, "ms")
else:
print("Test time OK")

print("DONE")
1 change: 1 addition & 0 deletions tests/ports/rp2/rp2_lightsleep.py.exp
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv
Test time OK
DONE
60 changes: 60 additions & 0 deletions tests/ports/rp2/rp2_lightsleep_thread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Verify that a thread running on CPU1 can go to lightsleep
# and wake up in the expected timeframe
import _thread
import time
import unittest
from machine import lightsleep, Pin

try:
led = Pin(Pin.board.LED, Pin.OUT)
except AttributeError:
led = None

N_SLEEPS = 5
SLEEP_MS = 250

IDEAL_RUNTIME = N_SLEEPS * SLEEP_MS
MAX_RUNTIME = (N_SLEEPS + 1) * SLEEP_MS


class LightSleepInThread(unittest.TestCase):
def thread_entry(self, is_thread=True):
led.toggle()
for _ in range(N_SLEEPS):
lightsleep(SLEEP_MS)
led.toggle()
if is_thread:
self.thread_done = True

def elapsed_ms(self):
return time.ticks_diff(time.ticks_ms(), self.t0)

def setUp(self):
self.thread_done = False
self.t0 = time.ticks_ms()

def test_cpu0_busy(self):
_thread.start_new_thread(self.thread_entry, ())
# CPU0 is busy-waiting not asleep itself
while not self.thread_done:
self.assertLessEqual(self.elapsed_ms(), MAX_RUNTIME)
self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME, delta=IDEAL_RUNTIME / 2)

def test_cpu0_sleeping(self):
_thread.start_new_thread(self.thread_entry, ())
time.sleep_ms(MAX_RUNTIME)
self.assertTrue(self.thread_done)
self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME, delta=IDEAL_RUNTIME / 2)

def test_cpu0_also_lightsleep(self):
_thread.start_new_thread(self.thread_entry, ())
time.sleep(0.050) # account for any delay in starting the thread
self.thread_entry(False) # does the same lightsleep loop, doesn't set the done flag
self.assertTrue(self.thread_done)
# only one thread can actually be in lightsleep at a time to avoid races, so the total
# runtime is doubled by doing it on both CPUs
self.assertAlmostEqual(self.elapsed_ms(), IDEAL_RUNTIME * 2, delta=IDEAL_RUNTIME)


if __name__ == "__main__":
unittest.main()
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy