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.zig49
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;
}