From dd93406aa015e187bfe57e4816c6660c9c4e20aa Mon Sep 17 00:00:00 2001 From: Tom Harkness Date: Sat, 3 Oct 2026 11:39:29 +1000 Subject: [PATCH] PM5: skip WFI idle sleep while a Flipper is CEP-attached Problem pm5_power_idle()'s WFI sleep adds ~40-50ms of latency before the main loop notices a fresh byte on CEP's SPI1/USART1. At idle this is the whole point of WFI - but it's also what made the original attach handshake miss bytes (GH #3667): cep_attach_poll() runs every 50ms, and WFI's own latency on top of that was enough to lose bytes of the Flipper's one-shot handshake string often enough to require a physical cable replug to recover. Change armsrc/appmain.c: skip pm5_power_idle() entirely once cep_is_attached() is true, so the main loop polls CEP as fast as the hardware allows for as long as a Flipper is physically present. armsrc/pm5_cep.c/.h: new cep_is_attached(), true as soon as the CC controller reports a physical attach - independent of whether the handshake/SPI transport is actually up yet, so the loop stops sleeping before the handshake even starts, not after. Paired with a CEP_ACTIVITY_TIMEOUT_MS (3000ms) idle-detach: the FAP quitting doesn't touch the CC controller's attach bit (the cable stays connected), so losing its normal 20-120ms SPI poll cadence for this long is what actually means it's gone - at that point cep_is_attached() still reflects the physical attach correctly, but cep_is_active() (and therefore this PR's WFI-skip, which gates on attachment rather than a completed handshake) only backs off once the FAP itself has clearly quit rather than just being between polls. Built on top of the AT32 SPI1 errata fix (bounded waits + resync) from the companion CEP-reliability PR - running the main loop flat-out against an unbounded wait would have just hit that hang faster, which is why this shipped after rather than alongside it. Behaviour change PM5 no longer downclocks to WFI between main-loop iterations while a Flipper is attached over CEP (idle power draw increases for the duration of the attach; unaffected when no Flipper is present, and unaffected on RDV4/non-CEP builds). Testing - make clean && make PLATFORM=PM5 PLATFORM_EXTRAS=BWM and make clean && make PLATFORM=PM3RDV4 - both zero warnings. - Hardware: this flag was active throughout all of the companion CEP- reliability PR's hardware testing (60s/110s/240s captures, 18x `hw status`, 5x attach/detach + `hf search` cycles against a real card) - all passed with WFI-skip engaged the whole time. Additionally reflashed this exact branch standalone and reconfirmed a live attach/detach cycle completes cleanly. - Not tested: clang toolchain (firmware-only diff); idle power draw was not independently measured (no direct ammeter in this session's bench setup, only observed BWM telemetry prior to this change). Checklist - [x] Proper style of own code, no unrelated reformatting included - [x] Builds warning-free with gcc (PM5 and RDV4) - [ ] clang not tried (firmware-only diff) Co-Authored-By: Claude Sonnet 5 --- armsrc/appmain.c | 13 ++++++++++++- armsrc/pm5_cep.c | 25 +++++++++++++++++++++++++ armsrc/pm5_cep.h | 7 +++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 1678a10cb..b67974745 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -4859,7 +4859,18 @@ void __attribute__((noreturn)) AppMain(void) { } #ifdef PM5 - pm5_power_idle(); + // WFI sleep adds ~40-50ms of latency before the main loop notices a + // fresh byte on CEP's SPI1 - fine at idle, but it's what made the + // original attach handshake miss bytes (see cep_attach_poll()'s own + // comment on the attach-edge bug this caused, GH #3667). Skip the + // sleep entirely while a Flipper is attached so the loop polls as + // fast as the hardware allows; cep_is_attached() drops back to false + // within CEP_ACTIVITY_TIMEOUT_MS of the FAP going away, so this + // isn't a permanent always-on cost. +#ifdef WITH_CEP + if (!cep_is_attached()) +#endif + pm5_power_idle(); #endif } } diff --git a/armsrc/pm5_cep.c b/armsrc/pm5_cep.c index ac9f425dc..cd5648fec 100644 --- a/armsrc/pm5_cep.c +++ b/armsrc/pm5_cep.c @@ -55,12 +55,23 @@ #define CEP_SYNC_BYTE 0x55 #define CEP_SYNC_MAX_DISCARD 600 +// How long cep_attach_poll() will go without seeing a real SPI poll from the +// Flipper before deciding the FAP has quit (see the comment at its one use +// below) and letting AppMain() go back to sleeping between iterations. +#define CEP_ACTIVITY_TIMEOUT_MS 3000 + static bool s_cep_active = false; // handshake completed, SPI transport live +static bool s_cep_attached = false; // CC controller reports a physical attach +static uint32_t s_last_spi_activity_tick = 0; // last time a real SPI poll was seen bool cep_is_active(void) { return s_cep_active; } +bool cep_is_attached(void) { + return s_cep_attached; +} + // --------------------------------------------------------------------------- // One-time peripheral bring-up // --------------------------------------------------------------------------- @@ -279,6 +290,7 @@ void cep_attach_poll(void) { } bool attached = ((cc_ctrl_data >> 6 & 0x03) != 0); + s_cep_attached = attached; if (!attached) { s_cep_active = false; @@ -294,6 +306,18 @@ void cep_attach_poll(void) { // can't hang the main loop. if (usart_flag_get(USART1, USART_RDBF_FLAG) != RESET) { s_cep_active = cep_do_handshake(); + if (s_cep_active) { + s_last_spi_activity_tick = GetTickCount(); + } + } + + // The FAP quitting doesn't touch the CC controller's attach bit - the + // cable stays connected, so `attached` alone can't tell us it's gone. + // Losing its SPI poll cadence (normally every 20-120ms) is what + // actually means it has - fall back to the slower/idle main loop + // once that cadence has been missing this long. + if (s_cep_active && (GetTickCountDelta(s_last_spi_activity_tick) > CEP_ACTIVITY_TIMEOUT_MS)) { + s_cep_active = false; } } @@ -336,6 +360,7 @@ bool cep_spi_data_available(void) { if (!cep_spi_read_byte(&len_header[1])) { return false; } + s_last_spi_activity_tick = GetTickCount(); // FAP is still polling, valid frame or not uint16_t data_len = (len_header[1] << 8) | len_header[0]; if (data_len > PM3_CMD_DATA_SIZE * 2) { return false; diff --git a/armsrc/pm5_cep.h b/armsrc/pm5_cep.h index d040bd1ef..28d7bb0d9 100644 --- a/armsrc/pm5_cep.h +++ b/armsrc/pm5_cep.h @@ -39,6 +39,13 @@ void cep_init(void); // Flipper is attached. bool cep_is_active(void); +// True once the CC controller reports a Flipper physically attached, +// independent of whether the handshake/SPI transport is up yet. Gate for +// AppMain()'s WFI-skip (see appmain.c) - a Flipper merely being plugged in +// is reason enough to stop sleeping between main-loop iterations, before +// any handshake has even started. +bool cep_is_attached(void); + // Rate-limited (see PM5_CEP_ATTACH_POLL_MS), non-blocking. Call once per // AppMain() main-loop iteration, alongside bwm_autooff_check(). Detects the // Flipper attach/detach transition via the CC controller and, on a fresh