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.
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.
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.
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.
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.
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
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.