From 1bffa9f163552f657fc159c9cec36935df2207c5 Mon Sep 17 00:00:00 2001 From: Carl Pottle Date: Fri, 13 Dec 2024 21:55:24 -0800 Subject: [PATCH 1/2] rp2/modmachine: Fix lightsleep returning early. fixes #16181 Commit 74fb42a added code using hardware timer 2. Prior to that only lightsleep used hardware timers specifically timer 3. lightsleep now loops calling __wfi() until timer 3 has fired. Without the loop lightsleep would return very early. Tested on PICO W with code from the issue. Works fine. Signed-off-by: Carl Pottle --- ports/rp2/modmachine.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index cd73552dd9874..11990f0a94dec 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -245,8 +245,11 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif #endif - // Go into low-power mode. - __wfi(); + // A timer other than #3 could fire. + // Repeatedly go into low-power mode until timer 3 is no longer armed. + while (timer_hw->armed & (1u << alarm_num)) { + __wfi(); + } if (!timer3_enabled) { irq_set_enabled(irq_num, false); From e514743b08eccee8a4326ea15ee57cb28efdeb35 Mon Sep 17 00:00:00 2001 From: Carl Pottle Date: Mon, 16 Dec 2024 13:06:24 -0800 Subject: [PATCH 2/2] rp2/modmachine: Resolve race condition in lightsleep. fixes #16181 @dpgeorge noted a race condition in my initial commit for this pull request. This commit masks the race condition by changing the condition in the while() loop. Now the while loop continues as long as timer 3 is armed and there are at least 50 microseconds left to run. 50 microseconds is a magic number I chose. It could be smaller. It is more than long enough to ensure timer 3 interrupt won't occur before __wfi() is called. And it is small enough someone calling lightsleep() won't notice it returning early. I tested using a modified version of test/ports/rp2/rp2_lightsleep.py. In my modified version of the test lightsleep was called for 1, 2, 4, 8, 16, ..., 4096, 8192 milliseconds. Using time.ticks_us() I measured the time to execute lightsleep(). Pretty consistently it was 800-900 microseconds longer than requested. I suspect this is one or more interrupts being serviced when lightsleep re-enable interrupts before returning. I did my testing on a PICO W connected to a PI 4 running thonny. Signed-off-by: Carl Pottle --- ports/rp2/modmachine.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 11990f0a94dec..82be2bbbbcfd3 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -197,6 +197,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { xosc_dormant(); } else { bool timer3_enabled = irq_is_enabled(3); + uint64_t endtime_us_64 = timer_time_us_64(timer_hw) + delay_ms * 1000; const uint32_t alarm_num = 3; const uint32_t irq_num = TIMER_ALARM_IRQ_NUM(timer_hw, alarm_num); @@ -216,7 +217,7 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #error Unknown processor #endif timer_hw->intr = 1u << alarm_num; // clear any IRQ - timer_hw->alarm[alarm_num] = timer_hw->timerawl + delay_ms * 1000; + timer_hw->alarm[alarm_num] = ((uint32_t)((endtime_us_64) & 0xffffffff)); } else { // TODO: Use RTC alarm to wake. clocks_hw->sleep_en0 = 0x0; @@ -245,10 +246,23 @@ static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) { #endif #endif + // A timer other than #3 could fire. - // Repeatedly go into low-power mode until timer 3 is no longer armed. - while (timer_hw->armed & (1u << alarm_num)) { + // Repeatedly go into low-power mode until timer 3 is no longer armed or + // there is less that 30 microseconds left to go. + // + // This means lightsleep can return 50 microseconds early. + // That is still close, even for a 1 millisecond sleep. + // But this masks a race condition where and interrupt occurs after the armed bit + // is checked but before __wfi() is called. + // Note: 50 microseconds is a magic number. + // Given that all interrupts are masked except the interrupt for timer_hw + // This should be more than enough to mask the race condition. + + uint64_t currenttime_us_64 = timer_time_us_64(timer_hw); + while (timer_hw->armed & (1u << alarm_num) && (endtime_us_64 - currenttime_us_64) > 50) { __wfi(); + currenttime_us_64 = timer_time_us_64(timer_hw); } if (!timer3_enabled) { 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