tud_task() inhibits wfe on rp2350, turns low power loops into busy loops #2495
Labels
No labels
bazel
bluetooth
board settings
boot_stage2
bug
build
c++
can't reproduce
clang
clib-interface
cmsis
code cleanliness
code size
compiler version issues
cyw43_driver
debug
discussion
documentation
duplicate
dynamic_clock
elf2uf2
enhancement
errata
filesystem
flash layout
floating point
FreeRTOS
hardware_adc
hardware_claim
hardware_clocks
hardware_dma
hardware_exception
hardware_flash
hardware_gpio
hardware_i2c
hardware_irq
hardware_pio
hardware_pwm
hardware_rcp
hardware_rtc
hardware_spi
hardware_sync
hardware_uart
hardware_vreg
hardware_watchdog
hardware_xosc
help wanted
host
IAR
installation
invalid
linker scripts
lto
lwip
mbedtls
missing API
needs reporter detail
networking
newlib
newlib-multicore
non-c
non-cmake
non-gcc
openocd
orphan PR
OS integration
pico_async_context
pico_base
pico_binary_info
pico_flash
picolibc
pico_lwip
pico_malloc
pico_multicore
pico_printf
pico_runtime
pico_stdio
pico_stdio_usb
pico_sync
pico_time
pico_util
pioasm
port
question
review
review2
rtos interop
runtime init
static library
svd
test
tinyusb
vscode
wifi integration
wontfix
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-raspberrypi/pico-sdk#2495
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The common pattern of
which should put the processor to sleep between interrupts, becomes a busy loop by default on RP2350 due to the use of pico-sdk spinlocks within
tud_task(), which behave likesev()due to reasons discussed in https://github.com/raspberrypi/pico-sdk/issues/1812. Mitigations include disabling EXTEXCALL (which modifies the behaviour of ALL atomics and other exclusive load/stores between cores), or puttingin tusb_config.h, disabling the pico-specific code paths in the TinyUSB OS abstraction layer. The latter is less of a big hammer but still has consequences wr interacting with TinyUSB from both cores.
Maybe you should do something like this instead?
This could work, but code comments within the function it calls indicate that it's meant to be called with interrupts disabled, due to not being protected by a locking primitive. It's the lack of being protected by a locking primitive that means there is no problematic implicit
sev, but I'm not sure whether it's actually safe in all cases.If it is indeed safe, then it should be possible to modify
tud_task()itself so that the original idiomatic usage of it is low power, as intended.added, then deleted my totally incorrect comment
Ok, trying again:
Note we can't really do
__sev(); __wfe()in the spin lock code, as eating events is certainly a non-desirable side effect in many cases.However I'd suggest that someone calling
tud_task()should not reasonably expect any guarantees related to processor events. For this reason I'd suggest that callingtud_task()followed by__wfe()is a poor idiom in general, that has just happened to work up until now.arguably there should be a
tud_block_until_event()or similarnote also that
tud_task_event_ready()should probably be documented as safe to call without holding IRQs. it isn't guaranteed to be correct in that case, but it won't crash. The races are safe:@rlcamp could you try my suggested fix
Willing to try things, but what is your suggested fix? Adding a sev wfe to consume the event is, as you point out above, a non starter.
The code at the top of https://github.com/raspberrypi/pico-sdk/issues/2495#issuecomment-3067323222 - i was suggesting you CAN reasonably do a SEV/WFE here
I don't think you can. In any code that is trying to sleep until either TinyUSB or unrelated things wake it up, it would be very bad for the calling code to reset the ER under the assumption that tud_task is the only thing that set it. That's certainly the case for the code I was prototyping when I ran into this.
The original intention seems to be that this usage should be okay:
Originally posted by @hathach in #52
However,
tud_task_event_ready()did not yet exist when the above comment was written, so it seems like the future-proof solution is to chase down loops in the wild that contain both an unguardedtud_task()and__wfe()and add the necessarytud_task_event_ready()guard. I'm still not sure this makes it safe to interact with TinyUSB from both cores, if one had a reason to do so.Yeah, as i say any code relying on the CPU event side effects (present or not) of
tud_task()is probably built on shaky foundations.If you need to sleep waiting on one of multiple conditions to be true, you should probably use an SDK semaphore. As of last checking TinyUSB does not provide a hook where you could set the semaphore, however the code in
pico_stdio_usbgithub.com/raspberrypi/pico-sdk@9a4113fbba/src/rp2_common/pico_stdio_usb/stdio_usb.c (L221)adds a shared IRQ handler to catch possible work required situations, and you could do the same thingmoving to 2.3.0 as there will be no SDK change for 2.2.0 - it is still worth working towards a TinyUSB based solution
One possible TinyUSB based solution would simply to be to factor out the use of
critical_section_twithinosal_pico.hand document that the calls totud_taskmust be performed by the same core as the interrupt handler (which the SDK can enforce, in its own usage of TinyUSB), thereby eliminating the original issue?Yeah, another potential solution is to use an
async_contextfor all tinyusb stuff, which is the direction pico_stdio_usb ought to go; just happens to predate theasync_contextcode. Theasync_contextcan make sure the calls are all made from the same place, and is also design to wake up only when there is work to do