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