Fix silent truncation of frames longer than the RX FIFO watermark
Every frame of 16 bytes or more was rejected as LengthMismatch and silently dropped. Found by the new end-to-end suite; neither the host tests nor the sensor verification could reach it, because it needs real bus timing and a frame past the watermark. The watermark interrupt fires mid-frame once 16 bytes are in the FIFO. The ISR drained those, stamped last_byte_at, and started frame_gap. The remaining bytes then sat *below* the watermark and raised no interrupt at all until the PL011's receive timeout eventually fired, so frame_gap measured its 1822 us gap from a mid-frame timestamp, concluded the frame had ended while its tail was still arriving, and parsed a prefix whose LEN field disagreed with its length. Frames under 16 bytes never trip the watermark, so their only interrupt is the receive timeout, by which point every byte is drained and the timestamp is honest. That is why all five real commands worked and only an oversized PING exposed it. frame_gap now drains the FIFO itself before each decision rather than trusting a timestamp the ISR left behind, so a byte that has landed can always push the deadline back. The gap is then measured from when the byte was observed rather than when it arrived, making the reply up to one gap period later than strictly necessary; 1.8 ms is a fair price. Verified on hardware across every payload size from 0 to 59 — frames of 5 to 64 bytes — with the node's error counters accounting exactly for the faults the suite injects deliberately and nothing else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+24
-3
@@ -273,15 +273,36 @@ mod app {
|
||||
/// Wait out the inter-frame gap, then answer the frame.
|
||||
///
|
||||
/// Runs above the sensor so a conversion in progress can never delay a reply,
|
||||
/// and below the UART ISO so incoming bytes are never dropped while we work.
|
||||
/// and below the UART ISR so incoming bytes are never dropped while we work.
|
||||
#[task(priority = 2, shared = [bus, rx, reading, state])]
|
||||
async fn frame_gap(mut cx: frame_gap::Context) {
|
||||
let gap = board::Duration::micros(board::INTER_FRAME_GAP_US as u64);
|
||||
|
||||
// Bytes may still be arriving. Rather than cancelling and respawning a
|
||||
// timer on every byte, re-read the last arrival time and sleep again.
|
||||
// timer on every byte, drain whatever has landed, re-read the last
|
||||
// arrival time, and sleep again.
|
||||
//
|
||||
// Draining here as well as in the ISR is load-bearing, not belt and
|
||||
// braces. The watermark interrupt fires mid-frame once 16 bytes are in
|
||||
// the FIFO; the rest of the frame then sits *below* the watermark and
|
||||
// raises no interrupt at all until the PL011's receive timeout
|
||||
// eventually fires. Measuring the gap from the ISR's mid-frame
|
||||
// timestamp would therefore declare the frame complete while its tail
|
||||
// was still arriving, truncating every frame longer than the watermark.
|
||||
// Draining before each decision means a byte that arrived can always
|
||||
// push the deadline back.
|
||||
//
|
||||
// The cost is that the gap is measured from when we observed the byte
|
||||
// rather than when it landed, so the reply is up to one gap period
|
||||
// later than strictly necessary. Correctness is worth 1.8 ms.
|
||||
loop {
|
||||
let deadline = cx.shared.rx.lock(|rx| rx.last_byte_at) + gap;
|
||||
let last_byte_at = cx.shared.bus.lock(|bus| {
|
||||
cx.shared.rx.lock(|rx| {
|
||||
bus.drain_into(rx, Mono::now());
|
||||
rx.last_byte_at
|
||||
})
|
||||
});
|
||||
let deadline = last_byte_at + gap;
|
||||
if Mono::now() >= deadline {
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user