PM5 CEP: data_available() doesn't poll CEP, so CMD_BREAK_LOOP over a

Flipper link can't interrupt a running reader loop

Problem: triggering the Flipper FAP's built-in ReadHitag2 demo command
turns the RF field on and it stays on even after backing out of the
app - only an unrelated USB command clears it. data_available()
(armsrc/util.c) is the standard idiom LF/HF reader loops use to notice
an incoming CMD_BREAK_LOOP, but it only polled USB/BWM/FPC. CEP's own
transport PR never wired it in, so a Flipper-sent break is invisible
to any loop that was already running.

Fix: add cep_spi_rx_pending() to armsrc/pm5_cep.c/.h - a genuinely
non-blocking single SPI1 RDBF flag check, not a reuse of the existing
(bounded-wait) cep_spi_data_available(), since this gets called every
iteration of every reader loop in the codebase and needs to cost
nothing when idle. Wire it into data_available()/data_available_fast()
alongside the existing BWM/FPC branches, additive since CEP and BWM
can coexist.

Hardware-confirmed: field now drops promptly every cycle instead of
sticking on, across repeated ReadHitag2 attach/attempt/cancel cycles.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Tom HarknessandClaude Sonnet 5 authored and Philippe Teuwen committed 2026-10-04 14:36:03 +02:00
1 parent dd93406aa0
commit ba4016d7e2
3 files changed
+41 -6

No files matched your search

+11
View File
@@ -343,6 +343,17 @@ static bool cep_spi_read_byte(uint8_t *out) {
return true;
}
// Genuinely non-blocking - a single flag read, no spin-wait at all. For
// data_available()'s use (armsrc/util.c): a tight sampling loop calls that
// every pass purely to notice an incoming CMD_BREAK_LOOP promptly, so it
// needs to cost nothing when idle. cep_spi_data_available() isn't a fit
// here - it waits (bounded, but non-zero) for a first byte to actually
// arrive, which is fine for receive_ng()'s own cadence but would add that
// wait to every iteration of every LF/HF reader loop in the codebase.
bool cep_spi_rx_pending(void) {
return spi_i2s_flag_get(SPI1, SPI_I2S_RDBF_FLAG) != RESET;
}
bool cep_spi_data_available(void) {
uint8_t len_header[2] = {0x00};
size_t discarded = 0;
+7
View File
@@ -58,6 +58,13 @@ void cep_attach_poll(void);
// simple non-blocking peek.
bool cep_spi_data_available(void);
// True if SPI1's receive-data register currently holds an unread byte - a
// single flag check, zero wait. Gate for data_available() (armsrc/util.c),
// which many LF/HF reader loops call every pass to notice an incoming
// CMD_BREAK_LOOP - that call site needs to cost nothing when idle, unlike
// cep_spi_data_available() above which waits (bounded) for a first byte.
bool cep_spi_rx_pending(void);
// Read up to `len` raw NG bytes off SPI1. Returns the number of bytes
// actually read before an internal per-byte timeout gave up early.
uint32_t cep_spi_read_ng(uint8_t *data, size_t len);
+23 -6
View File
@@ -28,6 +28,9 @@
#ifdef WITH_BWM_FORWARD
#include "bwm_forward.h"
#endif
#ifdef WITH_CEP
#include "pm5_cep.h"
#endif
#include "BigBuf.h" // trace_restart_timeline
#ifdef WITH_SMARTCARD
#include "i2c.h" // sc_log_trace_reset
@@ -330,28 +333,42 @@ int BUTTON_HELD(int ms) {
// This function returns false if no data is available or
// the USB connection is invalid.
bool data_available(void) {
bool avail;
#if defined(WITH_BWM_FORWARD)
// The BWM (BLE / WiFi) link is the host connection on a Proxmark5, so it
// has to be polled here too or CMD_BREAK_LOOP is never seen over it.
return usb_poll_validate_length() || (bwm_fwd_rxdata_available() > 0);
avail = usb_poll_validate_length() || (bwm_fwd_rxdata_available() > 0);
#elif defined(WITH_FPC_USART_HOST)
return usb_poll_validate_length() || (usart_rxdata_available() > 0);
avail = usb_poll_validate_length() || (usart_rxdata_available() > 0);
#else
return usb_poll_validate_length();
avail = usb_poll_validate_length();
#endif
#if defined(WITH_CEP)
// CEP is independent of BWM/FPC (both may be live at once) - a Flipper
// sending CMD_BREAK_LOOP into a running reader loop is otherwise
// invisible here, leaving the loop (and its RF field) running until an
// unrelated USB command happens to arrive.
avail = avail || cep_spi_rx_pending();
#endif
return avail;
}
// This function doesn't check if the USB connection is valid.
// In most of the cases, you should use data_available() unless
// the timing is critical.
bool data_available_fast(void) {
bool avail;
#if defined(WITH_BWM_FORWARD)
return usb_available_length() || (bwm_fwd_rxdata_available() > 0);
avail = usb_available_length() || (bwm_fwd_rxdata_available() > 0);
#elif defined(WITH_FPC_USART_HOST)
return usb_available_length() || (usart_rxdata_available() > 0);
avail = usb_available_length() || (usart_rxdata_available() > 0);
#else
return usb_available_length();
avail = usb_available_length();
#endif
#if defined(WITH_CEP)
avail = avail || cep_spi_rx_pending();
#endif
return avail;
}
// Combined function to convert an unsigned int to an array of hex values corresponding to the last three bits of k1