diff options
Diffstat (limited to 'src/esp32p4/uart.zig')
| -rw-r--r-- | src/esp32p4/uart.zig | 50 |
1 files changed, 48 insertions, 2 deletions
diff --git a/src/esp32p4/uart.zig b/src/esp32p4/uart.zig index 0afd145b..53ee29df 100644 --- a/src/esp32p4/uart.zig +++ b/src/esp32p4/uart.zig @@ -30,12 +30,20 @@ //! package's `src/hal/uart.zig:52`). At 115200 the wire costs ~86 us per byte and dwarfs either //! version, so today this is merely free - and it stops being free the moment the divider is raised. //! -//! **UART0's configuration is never touched.** Not the divider, not the format, not the pad +//! **UART0's configuration is never touched, except by `setBaud`.** Not the format, not the pad //! routing, and above all not `reset()`. The second-stage bootloader configured this block, and //! `src/hal/uart.zig:195-211` records what happens if it is reset: UART_CLKDIV returns to its //! power-on value, the console turns to garbage mid-sentence, and the board takes a watchdog reset -//! with nothing readable left to explain it. Everything here touches FIFO offset 0x000 and the +//! with nothing readable left to explain it. Everything else here touches FIFO offset 0x000 and the //! status register, and nothing else. +//! +//! The DIVIDER is the one exception, and it was carved out for the second image rather than for this +//! one: `docs/registry.typ` `BOARD-1`. The editor's console is opened by a human at 115200 and the +//! firmware inherits that divider (which is why the paragraph above used to say "not the divider"); +//! the 9P image (`nine.zig`) has a program on the far end that opens the port at whatever rate the +//! image was built for, and eight times the baud is eight times less latency on every `Tread`. So +//! `setBaud` exists, this file still never calls it, and `app.zig` still never calls it — the only +//! caller is the image whose host side is opened to match. const hal = @import("hal"); const input_rescue = @import("input_rescue.zig"); @@ -71,6 +79,44 @@ pub fn write(bytes: []const u8) void { /// lying about what happened, so it is worth printing. pub var dropped: u32 = 0; +/// Push as much of `bytes` as the transmitter has room for RIGHT NOW, and answer how much. Never +/// spins, never drops, never rescues — because it never waits for anything. +/// +/// THIS IS THE SANS-IO WRITE, and it exists because `write` above is the wrong primitive for a +/// protocol server. `write` is right for a console: a frame of ANSI is atomic to the terminal on the +/// far end, half an escape sequence leaves it in the wrong colour, so blocking until the whole burst +/// is in the FIFO is real backpressure and worth the stall. A 9P reply is not atomic to anything: +/// every message carries its own length, the reader on the far end reassembles, and +/// `9p.Server.wrote(n)` exists precisely so that a partial write costs nothing but another trip +/// round the loop (`src/9p.zig:2248-2257`). So this hands over what fits and returns, and +/// `src/esp32p4_9p.zig`'s loop keeps the remainder queued in the server where it already was. +/// +/// The difference that matters is DROPPING. `write`'s bounded spin gives up after a million status +/// reads and counts the loss, which turns a stalled transmitter into a diagnosable console; the same +/// behaviour on a 9P stream would truncate a reply mid-message and desynchronise the connection for +/// good. A short count cannot desynchronise anything. +pub fn writeSome(bytes: []const u8) usize { + const n = @min(@as(usize, uart0.txFree()), bytes.len); + for (bytes[0..n]) |b| uart0.pushByte(b); + return n; +} + +/// Reprogram UART0's divider, and answer whether the rate is reachable from the clock this block is +/// running on. Nothing is touched when it is not. +/// +/// THE ONE EXCEPTION to this file's rule, and see the header for who may call it: not this file, not +/// `app.zig`, only an image whose host side is opened at the same rate. `docs/registry.typ` +/// `BOARD-1` has the numbers — 921600 is one `UART_CLKDIV_SYNC` write on the existing 40 MHz XTAL, +/// int 43 frag 6, +0.064% error, and it takes a byte from 86.8 µs to 10.85 µs. +/// +/// The hazard is worth restating where the call is: the bytes already in the FIFO go out at the OLD +/// rate, so anything written before this and not yet drained is corrupted, and a host that is not +/// reopened sees garbage from here on with no error to report. The 9P image calls this before it +/// answers its first message, which is the one moment when neither of those can have happened yet. +pub fn setBaud(baud: u32) bool { + return uart0.setBaudrate(baud, uart0.clockSource().nominalHz()); +} + /// 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 { |
