-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
rp2: Fix soft timer expiry waking early from lightsleep. #16431
Conversation
Code size report:
|
0c92842
to
9a34859
Compare
@projectgus, I built micropython for RPI_PICO_W with your changes and tested them. As part of that, I ran the attached example running a periodic Timer once per second during the lightsleep. I just happened to have this code lying around for testing sleep_ms and I thought to give it a try. I know interrupts from other sources (EG: Pin) don't work but this one does. |
Thanks for taking a look and testing this, @cpottle9!
Ironically, |
This is a bit hand-wavey as USB data wakes up lightsleep() pretty often, but this modified test passes on PICO builds and fails consistently on PICO_W builds where the 64ms LWIP tick timer always wakes up lightsleep. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
Fixes issue where PICO-W build won't lightsleep() for more than 64ms. This is a regression introduced in 74fb42a when we switched away from the pico-sdk alarm pool for soft timers and accidentally made soft timer expiry a wakeup source. Before 74fb42a, both the pico-sdk alarm pool and the lightsleep wakeup timer uses alarm 3. The lightsleep wakeup would quietly clobber alarm 3's timeout, meaning softtimer wouldn't wake the chip from lightsleep. After 74fb42a, soft timer wakeup happens on timer alarm 2 so this interrupt wakes the chip from light sleep. On PICO-W builds this happens every lwIP tick (64ms). The change is to go back to using the same timer alarm for both lightsleep wakeup and soft timer, but now being explicit about lightsleep wakeup clobbering any soft timer wakeup. Also adds a "catch up" call to soft timer handler as it's currently possible to miss soft timer events after waking up. This also reworks the changes added in 19844b to enable the timer IRQ on CPU1. This is still needed, but have changed the implementation to be more explicit about when it's needed. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
9a34859
to
e9b8a36
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #16431 +/- ##
=======================================
Coverage 98.57% 98.57%
=======================================
Files 164 164
Lines 21349 21349
=======================================
Hits 21045 21045
Misses 304 304 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Having a constant lwIP tick counter of 64ms is probably a waste when it will tell us when it next needs to wake up via sys_timeouts_sleeptime. |
@projectgus and other maintainers of the rp2 port, I've been following changes related to lightsleep and the shoe just dropped for me. I did not see what you are trying to do until now. You are trying to solve world hunger for lightsleep and I don't think that is good use of your time. If I'm running a PICO_W from battery to save power the first thing I need to do is disable the cyw43 (and WIFI) when I don't need it. That includes not using the system LED. I have an application where I do wlan.deinit() and then lightsleep. I propose you need make lightsleep simpler and empower developers(like me) to decide which hardware blocks need to enabled for a given application. |
Ah, thanks @peterharperuk! The rp2 port is the only tickless port in MicroPython at the moment, so it's the only place this really counts. If we can easily make lwIP tickless as well then we have a lot more options. I'd assumed this wasn't simple but sounds like it may well be.
This is a good point, @cpottle9, although it's less "not break functionality" and more "not crash or hang" which is the bottom line here. For example, the reason that USB gets special handling in rp2 lightsleep is because disabling (or suspending) on sleep and re-enabling on wake caused intermittent crashes when either the peripheral or TinyUSB gets in some permanent bad state. Similarly, the workarounds for the second core are to prevent execution stalling indefinitely if the other CPU gets permanently stuck in WFI. If the impact is limited to "feature stops on sleep and starts on wake" or "power consumption is higher when this feature is in use" then these are probably reasonable trade-offs. Permanent hangs or hard faults aren't reasonable trade-offs.
That's a great project, and I think it's a good approach for people who want the absolute most control over the sleep modes. Part of the goal of MicroPython, and the That's not to say that something like RP2-PowerControl isn't a great option for folks who need that fine-grained register-level control, though. |
I realise there is another trade-off we could choose for some of these states, which would be to have There are still footguns we don't want to leave lying around, though. For example, even if someone's battery powered application isn't designed to run with USB in the field then they might want to sometimes plug USB in and do some debugging. If the behaviour in that state is significantly different then it can become quite hard to debug. |
@projectgus, your USB issue is valid. Their application can determine if USB is present and have RP2-PowerCtrl disable USB only when it is not. By the way, I initially coded RP2-PowerCtrl as part of modrp2.c. |
@projectgus one other thing. Prior to commit d1423ef on entry lightsleep saved the current value of sleep_en0 and sleep_en1. Now before exiting lightsleep sets those registers to the power on defaults. That means if someone uses RP2-PowerCtrl and then calls lightsleep their changes to sleep_en0 and sleep_en1 are lost. Should I open an issue? |
That could be one way forward, although if everything works the same in pure Python code then it's less appealing to add binary size for it in modrp2. I also just tagged you in this discussion, where I vaguely outlined another possible approach:
Yes, we can definitely change this back. If possible then suggest you open a Pull Request restoring behaviour that works for RP2-PowerCtrl, and then we can go from there. |
Couple of things have become apparent about this approach:
|
Quick update after discussing with @dpgeorge : Strong preference for #16454 to be the long-term fix. Hopeful that there will be a pico-sdk release with the final alarm pool fix included and we can go direct to that one. So I'm not planning to pick this approach up again for now. If this is a major inconvenience for anyone, please leave a comment. EDIT/note-to-self: If this is eventually closed then should open a new PR with just the test improvements. |
@projectgus, thanks for the update. You prompted me to look at #16454. If I understand correctly it will be a while before the issues in pico-sdk are addressed. |
The issue in pico-sdk is addressed now, but there hasn't yet been a pico-sdk release that includes the fix. Once a release comes out, and assuming no other issues crop up when we update pico-sdk, then we'll be able to proceed with the changes in #16454. |
Closing in favour of the approach from #16454 |
Summary
Fixes #16181 and partial fix for #16180. Specifically: bug where PICO-W build won't lightsleep() for more than 64ms.
Details
This is a regression introduced in 74fb42a (#13329) when we switched away from the pico-sdk alarm pool for soft timers and accidentally made soft timer expiry a wakeup source.
Before 74fb42a (including V1.23), both the pico-sdk alarm pool and the lightsleep wakeup timer uses alarm 3. The lightsleep timer would quietly clobber alarm 3's timeout, meaning softtimer events wouldn't wake the chip from lightsleep.
After 74fb42a (including V1.24), soft timer wakeup happens on timer alarm 2 so this interrupt can wake the chip from light sleep. On PICO-W builds this happens every lwIP tick (64ms) which is unexpected.
The change here is to go back to using the same timer alarm for both lightsleep wakeup and soft timer, but now being explicit about lightsleep wakeup clobbering any soft timer wakeup. Also adds a "catch up" call to soft timer handler as it's not currently there.
This also reworks the changes added in 19844b4 to enable the timer IRQ on CPU1. The functionality is basically the same, just being a bit more explicit and by reworking this and the other code there's no more "magic IRQ number 3" in this code.
Thanks to @cpottle9 for investigating this issue and pinpointing 74fb42a as the regression commit. 🙏
This work was funded through GitHub Sponsors.
Testing
Add unit test verifying that soft timer callbacks resume after waking from lightsleep.EDIT: Can't do this actually, asmachine.Timer()
uses the alarm pool. However, lightsleep and Wi-Fi are compatible so soft timer is resuming OK.Trade-offs and Alternatives
best_effort_wfe_or_timeout
and WFE in SDK 2.0.0 raspberrypi/pico-sdk#1812 is closed, however @dpgeorge mentioned here that the new version of the alarm pool may have another bug - maybe the one fixed in ta_set_timeout can fail to set an alarm raspberrypi/pico-sdk#2127 ?