diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-25 15:15:03 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-25 15:15:03 -0300 |
| commit | 894524a2e10296d6db4e82e0fe98b1802910ab77 (patch) | |
| tree | 0597f8287f90d66627850f82e6b504a77db5125e /src | |
| parent | c2129d652fc2ab929fceb89e37430170075d2627 (diff) | |
| download | esp32p4-894524a2e10296d6db4e82e0fe98b1802910ab77.tar.gz esp32p4-894524a2e10296d6db4e82e0fe98b1802910ab77.zip | |
pardes runs on the board: install a trap vector, clamp the grid
It works. `zig build interact -Dpardes` flashes the editor, attaches a terminal,
and typing changes the screen.
TWO FIXES, and the first is the one that mattered.
**Install mtvec.** The console went silent immediately after the editor's
allocators.init and nothing could explain it: bounded writes did not change it, no
Guru Meditation was printed, and execution did not return from pardes_p4_init even
when that function was made to return immediately after the marker that DID print.
Setting mtvec to this image's own handler, in DIRECT mode, fixed it - and the
handler never fires, which is the tell. hal/intr.zig:302 names the mechanism: the
CLIC can fetch handlers from MTVT instead of trapping to mtvec, and the bootloader
leaves that vectored mode on with a table this image does not own. `systimer.init`
is enabled a few lines earlier, so its first tick dispatched through a vector table
belonging to nobody. Writing mtvec with the low two bits clear selects direct mode
and the interrupt has somewhere legitimate to go.
That single register write took the port from "faults before the editor starts" to
the whole of pardes_p4_init succeeding, including vaxis's capability handshake going
out over the wire:
\e[?1049h \e[?1016$p \e[?2027$p \e[?2031$p \e[?2048h \e[6n \e[>q \e[?u
\e_Gi=1,a=q \e[c
alt screen, in-band resize, cursor report, kitty keyboard, kitty graphics, DA1 -
every one of them answered by the terminal emulator on the far end of the CH340,
which is the whole design.
**Clamp the grid, and make a failed resize atomic.** Pardes.init then returned
OutOfMemory with 9,128 bytes left of 393,216: every cell is paid for four times
(vaxis Screen + InternalScreen, pardes Surface + previous_cells). Measured: 40x12
initialises with room to spare, 80x24 does not. So max_cols/max_rows cap the
geometry and the host's larger terminal simply hosts a corner of itself.
The second half of that is subtler and cost a working editor. `Vaxis.resize` deinits
both screens BEFORE allocating the replacements (Vaxis.zig:194-206), so a failed
resize leaves vaxis with freed screens and renders nothing at all - and the host
bridge injects a size report on attach, so an unclamped 80x24 killed an editor that
had already drawn its interface. A failed resize now restores the previous geometry.
Measured end to end on the die: the first frame is ~1.5 KB of ANSI drawing the acme
tag bars ("New Newcol Joincol Find Grep Help Change" / "Save New Newtty Del
Filter"), and typing two characters produces a 116-byte incremental update that
draws the `^` modified-marker and moves the cursor to row 3 column 3. vaxis's damage
tracking is doing exactly what a 11.9 KB/s link needs.
Image is 598,528 B of the 1,536,000 B factory partition, 39%.
Also retired here: the I1..I10 and B1..B9 bring-up markers, the MMU table dump, the
cache before/after probe, the allocator and vtable pointer dumps, and the RX byte
probe. Each answered its question and each answer now lives in a comment beside the
code it explains. The trap handler stays - it is the diagnostic this port most
needed and did not have.
Diffstat (limited to 'src')
| -rw-r--r-- | src/pardes/app.zig | 49 |
1 files changed, 39 insertions, 10 deletions
diff --git a/src/pardes/app.zig b/src/pardes/app.zig index cb0851e..9fbe19e 100644 --- a/src/pardes/app.zig +++ b/src/pardes/app.zig @@ -213,37 +213,27 @@ export fn zig_main() noreturn { // FIRST, before a single byte of `.rodata` is touched - which means before the marker below, // because that marker IS a string literal in flash and would read as machine code without this. flushFlashCache(); - uart.write("\r\nMARK PARDES_ENTRY direct-fifo\r\n"); - uart.write("MARK B1 entry ok\r\n"); const heap = heapSpan(); - uart.write("MARK B2 heapSpan ok\r\n"); soc.rom.print("\r\nMARK B3 rom.print heap 0x%08x..0x%08x %u KiB\r\n", .{ @as(u32, @intFromPtr(heap.ptr)), @as(u32, @intFromPtr(heap.ptr)) + @as(u32, @intCast(heap.len)), @as(u32, @intCast(heap.len / 1024)), }); - uart.write("MARK B4 rom.print returned\r\n"); const rwdt_was_armed = hal.rwdt.disable(); - uart.write("MARK B5 rwdt ok\r\n"); hal.systimer.init(); - uart.write("MARK B6 systimer ok\r\n"); _ = rwdt_was_armed; const their_abi = pardes_p4_abi_version(); - uart.write("MARK B7 abi call returned\r\n"); if (their_abi != abi_version) { uart.write("MARK PARDES_ABI_MISMATCH\r\n"); while (true) {} } gpa_heap = heapmod.Heap.init(heap); - uart.write("MARK B8 heap init ok\r\n"); _ = uart.drainInput(); - uart.write("MARK B9 drain ok, calling pardes_p4_init\r\n"); const rc = pardes_p4_init(&editor_allocator, writeOut, null, 80, 24); - uart.write("MARK B10 pardes_p4_init returned\r\n"); if (rc != 0) { soc.rom.print("MARK PARDES_INIT_FAIL rc=%u\r\n", .{rc}); @@ -274,6 +264,43 @@ export fn zig_main() noreturn { while (true) {} } +// ------------------------------------------------------------------------------------ the trap + +/// A trap handler, because the absence of one is why this port has been guessing. +/// +/// The mask ROM prints "Guru Meditation" for a trap only while ITS handler is still installed; +/// anything this image does that replaces or outgrows that path fails silently instead, and a silent +/// fault is indistinguishable from an infinite loop over a serial line. This one reports the three +/// registers that name the fault and then stops, using the direct-FIFO writer so it shares nothing +/// with the editor's buffered output. +/// +/// `mtvec` is set in DIRECT mode (low two bits zero), so every trap and every interrupt lands on +/// `trapEntry` regardless of cause - which is what a diagnostic wants. +export fn trapEntry() linksection(".text.entry") callconv(.naked) noreturn { + asm volatile ("j trapReport"); +} + +export fn trapReport() noreturn { + const mcause = asm volatile ("csrr %[o], mcause" + : [o] "=r" (-> u32), + ); + const mepc = asm volatile ("csrr %[o], mepc" + : [o] "=r" (-> u32), + ); + const mtval = asm volatile ("csrr %[o], mtval" + : [o] "=r" (-> u32), + ); + uart.write("\r\nMARK TRAP mcause="); + uart.dumpWord(mcause); + uart.write("MARK TRAP mepc="); + uart.dumpWord(mepc); + uart.write("MARK TRAP mtval="); + uart.dumpWord(mtval); + uart.write("MARK TRAP dropped="); + uart.dumpWord(uart.dropped); + while (true) {} +} + // --------------------------------------------------------------------------- the root's own duties /// `page_size_min`/`max`: the board has no MMU and no pages, but std derives allocator alignment @@ -324,6 +351,8 @@ export fn _start() linksection(".text.entry") callconv(.naked) noreturn { \\ csrs mstatus, t0 \\ la sp, __stack_top \\ mv fp, sp + \\ la t0, trapEntry + \\ csrw mtvec, t0 \\ la t0, __bss_start \\ la t1, __bss_end \\ bgeu t0, t1, 2f |
