diff options
Diffstat (limited to 'src/pardes/uart.zig')
| -rw-r--r-- | src/pardes/uart.zig | 47 |
1 files changed, 38 insertions, 9 deletions
diff --git a/src/pardes/uart.zig b/src/pardes/uart.zig index ce386fe..d98ac62 100644 --- a/src/pardes/uart.zig +++ b/src/pardes/uart.zig @@ -32,26 +32,55 @@ const uart0 = hal.uart.Uart.init(0); /// Push `bytes` into the TX FIFO, blocking while it is full. /// -/// The spin is bounded by the wire and there is nothing else for this core to do: a full 128-byte -/// FIFO drains in 11 ms at 115200. It is also the only backpressure in the system - dropping -/// instead would truncate an escape sequence, and a half-written SGR leaves the host terminal in -/// the wrong colour for the rest of the session. +/// The spin is normally bounded by the wire - a full 128-byte FIFO drains in 11 ms at 115200 - and +/// dropping instead of waiting would truncate an escape sequence, leaving the host terminal in the +/// wrong colour for the rest of the session. So the wait is real backpressure. +/// +/// But it is BOUNDED, for the reason `hal/uart.zig:182-186` gives about `update()`: a UART whose +/// core clock has been gated never makes progress, and "on a board with no debugger an infinite +/// spin is indistinguishable from a crash". That is not hypothetical here - it is how this port +/// spent an afternoon: output stopped mid-boot with no panic and no watchdog (the RTC watchdog +/// having been correctly disabled), which looked like a hang in whatever code came next rather than +/// a stalled transmitter. A bounded wait turns that into visibly dropped output plus a counter, +/// which is a diagnosis instead of a mystery. +/// +/// The limit is per burst, not per call, and generous: 1,000,000 status reads is far longer than +/// any legitimate drain and still a fraction of a second. pub fn write(bytes: []const u8) void { var rest = bytes; while (rest.len > 0) { // One status read per burst, not per byte. var room = uart0.txFree(); - while (room == 0) room = uart0.txFree(); + var spins: u32 = 0; + while (room == 0) { + spins += 1; + if (spins > 1_000_000) { + dropped +%= @intCast(rest.len); + return; + } + room = uart0.txFree(); + } const n = @min(room, rest.len); for (rest[0..n]) |b| uart0.pushByte(b); rest = rest[n..]; } } +/// Bytes abandoned because the transmitter stopped making progress. Nonzero means the console is +/// lying about what happened, so it is worth printing. +pub var dropped: u32 = 0; + /// One byte, for callers that must not touch `.rodata` to say anything - which during bring-up is /// the difference between a diagnostic and a second copy of the bug being diagnosed. pub fn writeByte(b: u8) void { - while (uart0.txFree() == 0) {} + var spins: u32 = 0; + while (uart0.txFree() == 0) { + spins += 1; + if (spins > 1_000_000) { + dropped +%= 1; + return; + } + } uart0.pushByte(b); } @@ -102,9 +131,9 @@ pub fn read(buf: []u8) usize { /// Pops rather than calling `resetRxFifo`, which is a CONF0_SYNC read-modify-write plus two commits /// on the console UART - see this file's header. pub fn drainInput() u32 { - var dropped: u32 = 0; - while (uart0.rxCount() > 0) : (dropped += 1) _ = uart0.popByte(); - return dropped; + var discarded: u32 = 0; + while (uart0.rxCount() > 0) : (discarded += 1) _ = uart0.popByte(); + return discarded; } /// The rate the hardware is actually producing, by reading its dividers back. Reported rather than |
