diff options
Diffstat (limited to 'src/pardes/uart.zig')
| -rw-r--r-- | src/pardes/uart.zig | 49 |
1 files changed, 29 insertions, 20 deletions
diff --git a/src/pardes/uart.zig b/src/pardes/uart.zig index d98ac62..7696742 100644 --- a/src/pardes/uart.zig +++ b/src/pardes/uart.zig @@ -26,10 +26,15 @@ //! register, and nothing else. const hal = @import("hal"); +const input_rescue = @import("input_rescue.zig"); /// UART0: the instance the CH340 is wired to, and the one the ROM and bootloader configured. const uart0 = hal.uart.Uart.init(0); +/// Keystrokes taken off the receiver while the transmitter was full. See `input_rescue`: without +/// this, anything typed into a frame longer than the 128-byte FIFO was silently gone. +var rescued: input_rescue.Ring = .{}; + /// Push `bytes` into the TX FIFO, blocking while it is full. /// /// The spin is normally bounded by the wire - a full 128-byte FIFO drains in 11 ms at 115200 - and @@ -47,23 +52,7 @@ const uart0 = hal.uart.Uart.init(0); /// 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(); - 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..]; - } + dropped +%= input_rescue.pump(uart0, &rescued, bytes, 1_000_000); } /// Bytes abandoned because the transmitter stopped making progress. Nonzero means the console is @@ -119,9 +108,27 @@ pub fn dumpWord(v: u32) void { /// not stall on a keystroke that may never come. `rxCount` is read once per call and the FIFO /// drained to that mark, so a fast typist or a pasted buffer cannot hold the loop here. pub fn read(buf: []u8) usize { - const waiting = @min(uart0.rxCount(), buf.len); - for (buf[0..waiting]) |*slot| slot.* = uart0.popByte(); - return waiting; + // RESCUED BYTES FIRST. They arrived before anything still sitting in the FIFO, and an editor + // that reorders keystrokes is worse than one that drops them. + var n = rescued.pop(buf); + const waiting = @min(uart0.rxCount(), buf.len - n); + for (buf[n..][0..waiting]) |*slot| slot.* = uart0.popByte(); + n += waiting; + return n; +} + +/// Take whatever has arrived off the receiver right now, without waiting and without handing it to +/// anyone. For callers that are about to spend a while not reading: `write` does this while the +/// transmitter is full, and the loop does it between chunks of input, because applying a keystroke +/// gets more expensive as the line grows and 128 bytes of FIFO is only 11 ms at 115200. +pub fn rescueNow() void { + input_rescue.rescue(uart0, &rescued); +} + +/// Input abandoned because even the rescue buffer overflowed. Distinct from `dropped`, which is +/// OUTPUT abandoned by a stalled transmitter. +pub fn inputDropped() u32 { + return rescued.dropped; } /// Discard anything already received, returning how much. Used once at startup: the host-side @@ -133,6 +140,8 @@ pub fn read(buf: []u8) usize { pub fn drainInput() u32 { var discarded: u32 = 0; while (uart0.rxCount() > 0) : (discarded += 1) _ = uart0.popByte(); + discarded += @intCast(rescued.len); + rescued.clear(); return discarded; } |
