computedriven computedriven/docs/rv32-mailbox-carrier.md
Status: design only. Ruled 2026-09-04 with the R5 adjudication: the RV32 ecall interface (cd-kernellet/src/lower/riscv32_hostcall.rs, Linux rv32 numbers 63/64/93) is the test/ho…

RV32 bare-metal carrier: a memory-mapped mailbox (design, R5; not built)

Status: design only. Ruled 2026-09-04 with the R5 adjudication: the RV32 ecall interface (cd-kernellet/src/lower/riscv32_hostcall.rs, Linux rv32 numbers 63/64/93) is the test/hostcall lowering — chosen so that the in-repo interpreter and QEMU user-mode both service the same image — and is not the canonical bare-metal carrier. This document is the carrier that is. Nothing here is implemented; nothing here changes what a transition means.

1. What a carrier is, and is not

The 48-byte command frame and the 32-byte outcome frame are the ABI. A carrier moves those bytes and says when they are ready. It does not decide anything: admission does (R0.4.6/R0.4.7), and evidence egress is an orthogonal obligation with its own channel (R3). A carrier that could refuse a transition, or that carried evidence in the response, would be a semantic change and is out of scope by construction.

2. The mailbox

A fixed block of memory at a link-time address (__cd_mailbox, placed by link.ld in the RAM region so the same 64 KiB map governs it), owned half by the host and half by the floor:

offset  size  field             writer   meaning
0       4     magic             host     "CDMB" — the host has mapped the block
4       4     version           host     carrier version, 1
8       4     command_status    host→floor→host
                                  0 EMPTY   host may write command[]
                                  1 READY   host has written a complete 48-byte command; floor may consume
                                  2 TAKEN   floor has copied command[] out; host may write the next
16      48    command           host     one COMMAND frame, byte-for-byte the fd-0 frame
64      4     response_status   floor→host→floor
                                  0 EMPTY   floor may write response[]
                                  1 READY   floor has written a complete 32-byte outcome; host may read
                                  2 TAKEN   host has copied response[] out; floor may write the next
80      32    response          floor    one OUTCOME frame, byte-for-byte the fd-1 frame
112     4     control           host     bit 0 STOP — the floor exits its loop at the next EMPTY
116     4     exit_code         floor    written once, when the floor leaves the loop
120     4     fault             floor    0, or a fault code if the floor panicked (the panic handler writes it)
124     4     reserved

128 bytes. Every status field is a whole aligned word written with a single sw after the payload it announces (release order: payload, then status); the reader polls the status word and reads the payload after seeing it. On a single hart with no caches this is sufficient; on anything else a fence sits between the payload and the status store (fence w,w) and between the status load and the payload load (fence r,r). Those two fences are the whole memory-model surface of the carrier.

3. The floor's loop

loop:
    wait until command_status == READY        (poll; wfi where the target has it)
    copy command[48] out                      (the same read_exact the hostcall lowering has, over memory)
    command_status = TAKEN                    (release)
    dispatch                                  (unchanged)
    wait until response_status != READY       (the host has drained the previous outcome)
    write response[32]; response_status = READY
    if control & STOP: exit_code = 0; break

lower/riscv32_mailbox.rs supplies exactly what lower/mod.rs names — _start, sys_read, sys_write_fd, exit, stack_ptr, tsc — and sys_read/sys_write_fd are the two waits above. main.rs does not change. sys_write_fd for any fd other than 1 returns EBADF, as the hostcall one does: evidence egress does not ride this mailbox.

4. Evidence egress stays orthogonal

R3's ruling stands: egress exports truth without gaining authority over it, and a committed transition whose evidence cannot cross the boundary poisons the floor. On this carrier that boundary is a second block — an egress ring of N × 52-byte LifecycleEgressV1 frames with a producer index the floor owns and a consumer index the host owns — so that command latency never couples to evidence throughput (the same reason fd 3 is not fd 1). A full ring is the poison condition, exactly as EAGAIN/ENOTCONN is on Linux. Not designed further here.

5. Where it maps

QEMU virt machine     the block in DRAM; the host side is a QEMU test device or a second hart
ESP32-P4              shared SRAM between the HP core running the floor and the host core
FPGA                  a BRAM with the host side on the fabric; status words become ready/valid handshakes
CD1 hardware queue    the same 128-byte contract as a register file

6. What this design does not claim

  • No boot: the ROM→RAM copy of .data and zeroing of .bss (R5-OPEN.md

F76) belong to _start of this lowering and are not written.

  • No timing: tsc() is the cycle CSR where it exists.

  • No multi-hart claim: the fences above are the only concurrency statement.

  • No egress implementation.

Open in the interactive atlas