diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-26 01:13:28 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-26 01:13:28 -0300 |
| commit | e1526395233aad15dc9e2fdb49d5842de3e7e77b (patch) | |
| tree | a5ff0bdaf693ab2189ab7ea4a0219aa9262e26af /examples/echo.zig | |
| parent | 110eeeb80181b990901c22d28fa4fd41dd10cdd7 (diff) | |
| download | esp32p4-e1526395233aad15dc9e2fdb49d5842de3e7e77b.tar.gz esp32p4-e1526395233aad15dc9e2fdb49d5842de3e7e77b.zip | |
Drain the receiver while the transmitter is full: keystrokes were being lost
Reported as "a key is stuck and is only sent when I send a new event". It was neither
stuck nor late - it was gone, and a later frame repainting those cells is what made it
look like it arrived eventually.
The loop is read, apply, render, write, and `uart.write` blocks while the transmit FIFO is
full. That wait is real backpressure and should stay: dropping half an escape sequence
leaves the host terminal in the wrong colour for the rest of the session. But NOTHING
drained the receive FIFO during it, and that FIFO is 128 bytes - 11 ms of wire at 115200.
Measured on the die, typing a burst in one host write and counting what the firmware's
loop actually took off the UART:
burst before after after + chunked input
128 128 128 128
200 197 200 200
300 257 300 300
600 478 600 600
1200 - 1200 1200
2400 - 2316 2400
4096 - 3611 4096
Two windows had to close, and the second was only visible once the first was shut.
`input_rescue.pump` drains the receiver on every iteration of the wait for transmitter
room. That is the big one, and it is the whole reason this policy lives in its own file:
`uart.zig` cannot be tested without the chip because every line of it is an MMIO access,
while `pump` takes its port as `anytype` and runs against a fake with a two-byte transmit
FIFO and an eight-byte receive FIFO in `zig build test`. The fake models the receive FIFO
the way the hardware behaves - a byte arriving into a full FIFO is simply gone - so the
test fails by 67 lost bytes with the rescue removed, which is the die's 88-of-200 in
miniature. It also caught a flaw in its own first draft: a fake whose transmit FIFO drains
as fast as it fills never blocks, so `pump` never waits and the test proves nothing.
The second window was APPLYING the input. A keystroke costs 44 us on an empty line and
63 us at 640 characters, so handing the editor a full 128-byte batch is up to 8 ms in
which nothing drains the receiver - against 11 ms of FIFO. The loop now feeds the editor
eight bytes at a time and rescues between chunks. Splitting a burst at an arbitrary byte
is already safe, because `pardes_p4_input` keeps whatever it could not parse; that is how
it survives an escape sequence split across two UART reads. One render still happens per
loop iteration, so this costs no extra wire. Eight rather than thirty-two by measurement:
32 left 2400 and 4096 lossy, 8 does not.
Beyond 4096 bytes in one burst the editor genuinely cannot keep up, and the ring reports
what it abandoned instead of losing it silently - `rxdrop` in the PROF line, alongside a
running count of received bytes. That counter is the other lesson here: the first attempt
at measuring this counted characters on the reconstructed screen, which cannot distinguish
"never arrived" from "arrived but off the edge of the viewport", and it disagreed with the
hardware in both directions.
No cost to latency: round trip median 3687 us over 60 trials against 3687 before, maximum
3866, screen byte-identical to the vaxis reference, host tests green.
Diffstat (limited to 'examples/echo.zig')
0 files changed, 0 insertions, 0 deletions
