summaryrefslogtreecommitdiff
path: root/src/pardes/uart.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/pardes/uart.zig')
-rw-r--r--src/pardes/uart.zig47
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