Count and surface RX-queue drops on the ESP32-C5's promiscuous path
wifi_promisc_rx_cb() fed s_rx_queue with a 0-timeout xQueueSend() and never checked whether it succeeded, so a burst of captured frames arriving faster than rx_forward_task could drain them vanished with no counter anywhere - none of oversizeDrops/txFailures/rxCrcErrors caught it. Added a rxQueueDrops counter, threaded it through the STATUS heartbeat as a new trailing uint16 (old firmware/app on either side still parse fine), and surfaced it on the CAM Pinger card. Confirmed on the bench: flashed to the production OBU (COM3) and installed the matching app build on the phone, then watched the counter over logcat against obu-cam-transmistter's ~3.3 Hz beacon - it is real (0 -> 89 -> 90 across two sessions) but bursty around connect/reconnect rather than a continuous overflow under steady single-station traffic. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
21e01499d8
commit
71dde3364d
@@ -259,8 +259,13 @@ static void wifi_promisc_rx_cb(void *recv_buf, wifi_promiscuous_pkt_type_t type)
|
||||
s_cb_item.rssi = packet->rx_ctrl.rssi;
|
||||
|
||||
// 0 timeout: never block the WiFi driver's own task waiting for queue space. xQueueSend copies
|
||||
// the struct out before returning, so reusing s_cb_item on the next callback is fine.
|
||||
xQueueSend(s_rx_queue, &s_cb_item, 0);
|
||||
// the struct out before returning, so reusing s_cb_item on the next callback is fine. The
|
||||
// return value used to go unchecked, so a full queue (rx_forward_task still draining a
|
||||
// previous burst) silently ate frames with no counter anywhere - see
|
||||
// serial_link_note_rx_queue_drop()'s KDoc.
|
||||
if (xQueueSend(s_rx_queue, &s_cb_item, 0) != pdTRUE) {
|
||||
serial_link_note_rx_queue_drop();
|
||||
}
|
||||
}
|
||||
|
||||
static void rx_forward_task(void *arg)
|
||||
|
||||
@@ -21,6 +21,7 @@ static serial_link_cam_tx_pv_cb_t s_on_cam_tx_pv;
|
||||
static uint16_t s_oversize_drops;
|
||||
static uint16_t s_tx_failures;
|
||||
static uint16_t s_rx_crc_errors;
|
||||
static uint16_t s_rx_queue_drops;
|
||||
|
||||
// Serializes send_frame(): it writes a frame as four separate usb_serial_jtag_write_bytes() calls
|
||||
// and shares one static CRC scratch buffer, and it's now called from three tasks (rx_forward for
|
||||
@@ -45,6 +46,12 @@ void serial_link_note_oversize_drop(uint16_t btp_dest_port)
|
||||
btp_dest_port, s_oversize_drops);
|
||||
}
|
||||
|
||||
void serial_link_note_rx_queue_drop(void)
|
||||
{
|
||||
bump(&s_rx_queue_drops);
|
||||
ESP_LOGW(TAG, "rx queue full, dropped a captured frame, total rx queue drops %u", s_rx_queue_drops);
|
||||
}
|
||||
|
||||
// ---- CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF, no reflect, no xorout) ----
|
||||
// Bytewise (no table) - frames here are at most SERIAL_LINK_MAX_PAYLOAD + 3 bytes, so table
|
||||
// lookup isn't worth the flash/RAM tradeoff. MUST match the Kotlin-side implementation exactly
|
||||
@@ -165,9 +172,10 @@ bool serial_link_send_v2x_rx(uint16_t btp_dest_port, int8_t rssi,
|
||||
|
||||
bool serial_link_send_status(uint8_t status)
|
||||
{
|
||||
// [status:1][oversize_drops:2 LE][tx_failures:2 LE][rx_crc_errors:2 LE][capabilities:1] -
|
||||
// keep in lockstep with EspLinkStatus.parse() in the app's SerialFrame.kt.
|
||||
uint8_t payload[8];
|
||||
// [status:1][oversize_drops:2 LE][tx_failures:2 LE][rx_crc_errors:2 LE][capabilities:1]
|
||||
// [rx_queue_drops:2 LE] - keep in lockstep with EspLinkStatus.parse() in the app's
|
||||
// SerialFrame.kt.
|
||||
uint8_t payload[10];
|
||||
payload[0] = status;
|
||||
payload[1] = (uint8_t)(s_oversize_drops & 0xFF);
|
||||
payload[2] = (uint8_t)((s_oversize_drops >> 8) & 0xFF);
|
||||
@@ -178,6 +186,8 @@ bool serial_link_send_status(uint8_t status)
|
||||
// What this firmware accepts. The app reads it to decide whether it may send CAM_TX_PV, which
|
||||
// is what lets a new app keep working against firmware that predates that message.
|
||||
payload[7] = SERIAL_CAP_CAM_TX_PV;
|
||||
payload[8] = (uint8_t)(s_rx_queue_drops & 0xFF);
|
||||
payload[9] = (uint8_t)((s_rx_queue_drops >> 8) & 0xFF);
|
||||
return send_frame(SERIAL_MSG_STATUS, payload, sizeof(payload));
|
||||
}
|
||||
|
||||
|
||||
@@ -52,11 +52,14 @@
|
||||
// message's own ItsPduHeader.stationID is the meaningful identifier.
|
||||
// SERIAL_MSG_STATUS (0x03), ESP32 -> phone: heartbeat + counters, sent at 1 Hz so the phone can
|
||||
// distinguish "link idle" from "link dead" independent of CAM traffic (the app's watchdog in
|
||||
// UsbSerialTransport.kt declares the link dead after 3 missed beats). Payload is 8 bytes:
|
||||
// UsbSerialTransport.kt declares the link dead after 3 missed beats). Payload is 10 bytes:
|
||||
// [status:1][oversize_drops:2 LE][tx_failures:2 LE][rx_crc_errors:2 LE][capabilities:1]
|
||||
// [rx_queue_drops:2 LE]
|
||||
// status 0 = ok. The counters are free-running totals since boot, saturating at 0xFFFF.
|
||||
// capabilities is a bitmask of the SERIAL_CAP_* flags below. It was appended as byte 7 rather
|
||||
// than inserted, so an app that predates it, and reads only the first 7 bytes, is unaffected.
|
||||
// than inserted, so an app that predates it, and reads only the first 7 bytes, is unaffected;
|
||||
// rx_queue_drops (bytes 8-9) follows the same rule for an app that predates it. Either side
|
||||
// reading a payload shorter than the field it wants should treat that field as 0, not error.
|
||||
// They exist because the alternative - ESP_LOGW on the flashing port - is invisible to the
|
||||
// phone, which is the only thing watching during a bench session. Mirrored by EspLinkStatus
|
||||
// in the app's SerialFrame.kt.
|
||||
@@ -165,4 +168,13 @@ void serial_link_note_tx_failure(void);
|
||||
// whose capture buffer is smaller than the largest frames on air.
|
||||
void serial_link_note_oversize_drop(uint16_t btp_dest_port);
|
||||
|
||||
// Counts a promiscuously-captured frame that main.c's wifi_promisc_rx_cb() could not hand to
|
||||
// rx_forward_task because s_rx_queue was full - i.e. frames arrived faster than the forward task
|
||||
// (gn_unwrap + a blocking USB write, up to SERIAL_LINK_WRITE_TIMEOUT_MS x 4 per frame under
|
||||
// contention) could drain them. Unlike oversize_drop this is not about one frame's size; it is
|
||||
// about a burst of otherwise-forwardable frames. Previously silent - xQueueSend's return value
|
||||
// was not even checked - so a run of these had no visible symptom beyond "that station's CAM
|
||||
// count looked a little low."
|
||||
void serial_link_note_rx_queue_drop(void);
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user