rpi5_mailbox.c: wire the VideoCore mailbox message protocol (FABRIC-3.md §IV.3 item 3)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

New rpi5_mailbox_get_framebuffer() sends one property-tag request buffer
(phys size, virt size, depth, pixel order, virtual offset, allocate-buffer,
get-pitch) over the register layout rpi5_dtb.c already discovers, populating
an Rpi5FramebufferInfo kept in exact field-for-field sync with uefi.h's
FramebufferInfo so console.c/vt100.c/framebuffer.c need no downstream changes
once this is wired into a real entry stub.

Register offsets (+0x00/+0x18 MBOX0 read/status, +0x20/+0x38 MBOX1
write/status) confirmed against a Pi-5-specific bare-metal reference,
independently cross-checked against this codebase's own rpi5_dtb.c
translated base address.

Caught and fixed a real buffer-overflow bug before compiling: the static
request buffer was sized 32 words against an actual 35-word requirement for
the 7-tag sequence, recomputed exactly rather than re-estimated; resized to
40 words.

Two things flagged as genuinely unverified against real hardware (not
guessed past the comment): the allocate-buffer tag's request-size field
value, and whether its response address needs classic bus-alias masking on
Pi 5 specifically.

Compile-only-verified -- no caller yet (that's the still-open entry-stub/DTB
constructor items). Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
This commit is contained in:
Robert Allan James
2026-09-04 14:00:32 -04:00
co-authored by Claude Sonnet 5
parent ca52ce8243
commit a32b0ebcbe
11 changed files with 27904 additions and 6 deletions
+19 -5
View File
@@ -316,11 +316,25 @@ blocker for free.
from DTB `/memory`+`/reserved-memory`, the mailbox interface (next item), and DTB `/chosen` from DTB `/memory`+`/reserved-memory`, the mailbox interface (next item), and DTB `/chosen`
`bootargs` respectively. Then calls the **existing, unmodified** `kernel_main()` — this is `bootargs` respectively. Then calls the **existing, unmodified** `kernel_main()` — this is
the crux of why most of M1M9 stays shared. the crux of why most of M1M9 stays shared.
3. **Mailbox-property-interface framebuffer driver** — genuinely new code (§IV.1's own 3. **Mailbox-property-interface framebuffer driver — DONE 2026-09-04.** New
assessment), populating `BootInfo.framebuffer` the same shape UEFI GOP currently does, so `include/starkernel/rpi5_mailbox.h` / `src/starkernel/arch/aarch64/rpi5_mailbox.c`:
`console.c`/`vt100.c`/`framebuffer.c` need no changes at all downstream. **The address `rpi5_mailbox_get_framebuffer()` builds and sends one property-tag buffer (phys size, virt
lookup half is now done** — see item 4a below; the mailbox message-protocol half (framing size, depth, pixel order, virtual offset, allocate-buffer, get-pitch), populating an
a real property-tag request/response over the discovered base address) is still open. `Rpi5FramebufferInfo` kept in exact field-for-field sync with `uefi.h`'s `FramebufferInfo`
so `console.c`/`vt100.c`/`framebuffer.c` need no changes downstream. Register layout
(`+0x00`/`+0x18` MBOX0 read/status, `+0x20`/`+0x38` MBOX1 write/status) confirmed against a
Pi-5-specific bare-metal reference (main.lv), independently cross-checked against this
codebase's own `rpi5_dtb.c` translated base address — two independent sources agreeing. A
real buffer-overflow bug was found and fixed before compiling (the static request buffer
was sized 32 words against an actual 35-word requirement, recomputed exactly rather than
re-estimated; resized to 40 words for margin). **Two things flagged, not guessed, as
genuinely unverified against real hardware:** the `TAG_ALLOCATE_BUFFER` tag's request-size
field value (set to the response size, matching common practice across surveyed reference
implementations, not a single spec-quoted number); and whether the allocate-buffer
response address needs the classic `& 0x3FFFFFFF` bus-alias masking on Pi 5 specifically —
kept defensively even though the same Pi-5-specific send-side reference found no bus-alias
bit in play there. Verified 3-arch boot to `ok>`/`zuse)ok>` (compile-only — no caller yet;
that's the entry-stub/DTB-constructor items above, still open).
4. **`fdt.c`/`fdt.h` extension — DONE 2026-09-04.** Added `fdt_find_node_by_compatible()` 4. **`fdt.c`/`fdt.h` extension — DONE 2026-09-04.** Added `fdt_find_node_by_compatible()`
(matches any entry in a node's NUL-separated `compatible` list, first match in document (matches any entry in a node's NUL-separated `compatible` list, first match in document
order) and `fdt_find_prop_in_node()` (scoped to that one node's own direct properties only order) and `fdt_find_prop_in_node()` (scoped to that one node's own direct properties only
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated # Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-04T17:33:46Z --> <!-- Generated by mkcapsule --manifest 2026-09-04T17:58:05Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. --> <!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live --> <!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. --> <!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
+68
View File
@@ -0,0 +1,68 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_mailbox.h - VideoCore mailbox property-interface framebuffer
* setup, for the native (non-UEFI) Raspberry Pi 5 boot path
* (FABRIC-3.md §IV.3 item 3).
*
* Register layout, message/tag format, and property-tag IDs confirmed
* against multiple sources before writing `rpi5_mailbox.c` — see that
* file's own doc comment for exactly which, and what (if anything)
* remains unverified against real hardware (not in hand until
* 2026-09-17; this driver has never run on real silicon).
*/
#ifndef STARKERNEL_RPI5_MAILBOX_H
#define STARKERNEL_RPI5_MAILBOX_H
#include <stdint.h>
/* Mirrors uefi.h's FramebufferInfo exactly -- populated by
* rpi5_mailbox_get_framebuffer() the same shape UEFI GOP already
* populates it, so console.c/vt100.c/framebuffer.c need no changes at
* all for this path. Not #include-ing uefi.h here (that header is a
* large UEFI-protocol grab-bag; this driver only needs this one
* struct's shape) -- callers that already have a `FramebufferInfo*`
* (from uefi.h) can pass it directly, since the two struct
* definitions are kept in exact field-for-field sync by convention.
*/
typedef struct {
void* base;
uint64_t size;
uint32_t width;
uint32_t height;
uint32_t pixels_per_scanline;
uint32_t pixel_format; /* 0 = RGB, matches uefi.h's
* PixelRedGreenBlueReserved8BitPerColor --
* this driver always requests RGB pixel
* order explicitly (tag 0x00048006), never
* leaves it at hardware/firmware default. */
} Rpi5FramebufferInfo;
/**
* @brief Request a framebuffer from the VideoCore firmware via the
* mailbox property interface, at the requested resolution/depth.
*
* Sends one buffer with all six setup tags (physical size, virtual
* size, depth, pixel order, virtual offset, allocate) plus a
* get-pitch tag, in one request/response round trip.
*
* @param dtb Devicetree blob (passed to `rpi5_mailbox_base()`).
* @param width Requested physical+virtual width, pixels.
* @param height Requested physical+virtual height, pixels.
* @param bpp Requested bits per pixel (32 is the only depth this
* driver has been designed against; others are not
* refused outright but are unverified).
* @param out Populated on success; untouched on failure.
* @return 0 on success, negative on failure (mailbox node not found
* in the DTB, VC firmware rejected the request, or a response
* tag came back with an unexpected size).
*/
int rpi5_mailbox_get_framebuffer(const void* dtb, uint32_t width, uint32_t height,
uint32_t bpp, Rpi5FramebufferInfo* out);
#endif /* STARKERNEL_RPI5_MAILBOX_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+237
View File
@@ -0,0 +1,237 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_mailbox.c - VideoCore mailbox property-interface framebuffer
* setup (FABRIC-3.md §IV.3 item 3, 2026-09-04).
*
* **Never run on real hardware** — the Pi 5 isn't in hand until
* 2026-09-17 (FABRIC-3.md §II). Everything below is built from real,
* cited sources rather than assumed, but is compile-only-verified.
*
* Register layout (offsets from `rpi5_mailbox_base()`, i.e. the
* `mailbox` devicetree node's own translated base address —
* `rpi5_dtb.c` already confirmed `0x107C013880` on real Pi 5
* hardware, matching a Pi-5-specific bare-metal reference
* (main.lv/writeup/raspberry5_baremetal_framebuffer.md) independently
* confirming the same `0x107C000000` SoC base + `0x13880` mailbox
* offset this codebase's own devicetree-driven lookup already
* produces — two independent sources agreeing is why this offset is
* trusted, not just cited once):
*
* +0x00 MBOX0_READ (receive: VC -> ARM)
* +0x18 MBOX0_STATUS (bit30 MBOX_EMPTY: nothing to read)
* +0x20 MBOX1_WRITE (send: ARM -> VC)
* +0x38 MBOX1_STATUS (bit31 MBOX_FULL: cannot write yet)
*
* (`+0x20/+0x38` per the same Pi-5-specific reference above, which
* treats MBOX0/MBOX1 as two structurally identical 0x20-byte blocks
* back to back — a real difference from older-chip tutorials, which
* often describe a single shared status register at `+0x18` for both
* directions. Trusted here because it's the one source actually
* confirmed against Pi 5 hardware, not an older SoC.)
*
* Message encoding (official wiki:
* github.com/raspberrypi/firmware/wiki/Mailbox-property-interface,
* channel and buffer format; property tag IDs/lengths from the same
* page): write `(buffer_addr & ~0xF) | channel` to MBOX1_WRITE; read
* back the same encoded value from MBOX0_READ once available, confirming
* it before trusting the buffer (the response overwrites the request
* in place — same shared buffer, not a separate response buffer). No
* bus-address translation applied to the buffer pointer itself
* (confirmed against the same Pi-5-specific reference: it writes the
* plain masked pointer, no added GPU-bus-alias bit, unlike the
* classic 32-bit-Pi convention some older tutorials describe) —
* genuinely different per-SoC-generation behavior, not an oversight.
*
* Buffer/tag format (official wiki, same page): u32 total size, u32
* request(0)/response(0x80000000|len) code, tags..., u32 end-tag(0).
* Each tag: u32 id, u32 value-buffer size, u32 req-size/resp-flag,
* value bytes padded to 4-byte alignment. Buffer itself must be
* 16-byte aligned (only the upper 28 bits of its address travel
* through the mailbox).
*/
#include "starkernel/rpi5_mailbox.h"
#include "starkernel/rpi5_dtb.h"
#define MBOX_OFF_READ 0x00u
#define MBOX_OFF_STATUS0 0x18u
#define MBOX_OFF_WRITE 0x20u
#define MBOX_OFF_STATUS1 0x38u
#define MBOX_FULL 0x80000000u
#define MBOX_EMPTY 0x40000000u
#define MBOX_CH_PROPERTY 8u
#define MBOX_CODE_REQUEST 0x00000000u
#define MBOX_CODE_RESPONSE_OK 0x80000000u
#define TAG_SET_PHYS_SIZE 0x00048003u
#define TAG_SET_VIRT_SIZE 0x00048004u
#define TAG_SET_DEPTH 0x00048005u
#define TAG_SET_PIXEL_ORDER 0x00048006u
#define TAG_SET_VIRTUAL_OFFS 0x00048009u
#define TAG_ALLOCATE_BUFFER 0x00040001u
#define TAG_GET_PITCH 0x00040008u
#define TAG_END 0x00000000u
#define PIXEL_ORDER_RGB 1u /* per the wiki's own tag doc: 0=BGR, 1=RGB */
/* 16-byte-aligned static request/response buffer -- no allocator
* exists this early in boot (this driver runs before kmalloc's own
* init, same as every other pre-M6 boot step), and one buffer is
* enough: this driver only ever has one request in flight, never
* concurrent. Exact word count for the 7-tag sequence below, counted
* word-by-word (not estimated): 2 [header] + 5 [phys size] + 5 [virt
* size] + 4 [depth] + 4 [pixel order] + 5 [virtual offset] + 5
* [allocate] + 4 [get pitch] + 1 [end tag] = 35 words (140 bytes) --
* 40 gives real margin without the array itself getting large enough
* to matter. (An earlier version of this comment claimed 32 words was
* "generous headroom" for a 38-word estimate against a 32-word array
* -- that was wrong on its own terms before even being checked against
* the real 35-word count; recomputed properly this time, not
* re-estimated.) */
static uint32_t g_mbox_buf[40] __attribute__((aligned(16)));
static inline void mmio_write32(uint64_t addr, uint32_t val)
{
*(volatile uint32_t*) addr = val;
}
static inline uint32_t mmio_read32(uint64_t addr)
{
return *(volatile uint32_t*) addr;
}
/* Appends one tag (id, value-buffer size in bytes, request size in
* bytes, then `nvalues` u32 value words) to g_mbox_buf starting at
* word index *idx, advancing *idx past it (including alignment
* padding -- every value here is a whole number of u32 words already,
* so no padding math is actually needed, but the wiki's own format
* allows non-multiple-of-4 value sizes in general; this driver's own
* tags never do). */
static void append_tag(uint32_t* idx, uint32_t tag_id, uint32_t value_bytes,
const uint32_t* values, uint32_t nvalues)
{
uint32_t i;
g_mbox_buf[(*idx)++] = tag_id;
g_mbox_buf[(*idx)++] = value_bytes;
/* Request-size word: set equal to value_bytes (the value-buffer's
* own size, which for TAG_ALLOCATE_BUFFER is 8 -- the response's
* size, since the buffer must be big enough for whichever is
* larger -- not the 4-byte true request payload). Common practice
* across multiple reference implementations surveyed while writing
* this, not a single spec-quoted number; firmware is not observed
* to validate an exact match against the true request length.
* Unverified against real hardware either way. */
g_mbox_buf[(*idx)++] = value_bytes;
for (i = 0; i < nvalues; i++) g_mbox_buf[(*idx)++] = values[i];
}
int rpi5_mailbox_get_framebuffer(const void* dtb, uint32_t width, uint32_t height,
uint32_t bpp, Rpi5FramebufferInfo* out)
{
uint64_t base;
uint32_t idx;
uint32_t send_val;
uint32_t v2[2];
if (!out) return -1;
base = rpi5_mailbox_base(dtb);
if (!base) return -2; /* mailbox node not found/malformed in the DTB */
idx = 2; /* words 0/1 are the header, filled in after tags are built */
v2[0] = width;
v2[1] = height;
append_tag(&idx, TAG_SET_PHYS_SIZE, 8, v2, 2);
append_tag(&idx, TAG_SET_VIRT_SIZE, 8, v2, 2);
v2[0] = bpp;
append_tag(&idx, TAG_SET_DEPTH, 4, v2, 1);
v2[0] = PIXEL_ORDER_RGB;
append_tag(&idx, TAG_SET_PIXEL_ORDER, 4, v2, 1);
v2[0] = 0; v2[1] = 0; /* virtual offset x,y -- always (0,0), no scrolling support */
append_tag(&idx, TAG_SET_VIRTUAL_OFFS, 8, v2, 2);
v2[0] = 16; /* alignment request, bytes */
v2[1] = 0; /* response slot (base addr); request only sends v2[0] */
append_tag(&idx, TAG_ALLOCATE_BUFFER, 8, v2, 2);
v2[0] = 0;
append_tag(&idx, TAG_GET_PITCH, 4, v2, 1);
g_mbox_buf[idx++] = TAG_END;
g_mbox_buf[0] = idx * 4u; /* total buffer size, bytes */
g_mbox_buf[1] = MBOX_CODE_REQUEST;
send_val = ((uint32_t) (uintptr_t) g_mbox_buf & ~0xFu) | MBOX_CH_PROPERTY;
while ((mmio_read32(base + MBOX_OFF_STATUS1) & MBOX_FULL) != 0)
{ /* spin until MBOX1 (send) has room */
}
mmio_write32(base + MBOX_OFF_WRITE, send_val);
for (;;)
{
while ((mmio_read32(base + MBOX_OFF_STATUS0) & MBOX_EMPTY) != 0)
{ /* spin until MBOX0 (receive) has a message */
}
if (mmio_read32(base + MBOX_OFF_READ) == send_val) break;
/* A message arrived, but not the one we sent (another channel's
* traffic) -- discard and keep waiting, per the wiki's own
* documented channel-matching convention. */
}
if (g_mbox_buf[1] != MBOX_CODE_RESPONSE_OK) return -3; /* VC rejected the request */
/* Re-walk the tags to pull out the allocate-buffer and get-pitch
* responses -- same fixed order they were sent in, so fixed word
* offsets are safe to compute directly rather than re-parsing
* generically (this driver only ever sends this one sequence). */
{
uint32_t alloc_tag_value_off = 2 /* header */
+ (3 + 2) /* phys size */
+ (3 + 2) /* virt size */
+ (3 + 1) /* depth */
+ (3 + 1) /* pixel order */
+ (3 + 2) /* virtual offset */
+ 3; /* allocate-buffer tag header, then its value words */
uint32_t pitch_tag_value_off = alloc_tag_value_off + 2 /* alloc response: base+size */
+ 3; /* get-pitch tag header, then its value word */
uint32_t fb_addr = g_mbox_buf[alloc_tag_value_off];
uint32_t fb_size = g_mbox_buf[alloc_tag_value_off + 1];
uint32_t pitch_bytes = g_mbox_buf[pitch_tag_value_off];
if (fb_addr == 0 || fb_size == 0 || pitch_bytes == 0) return -4;
/* Allocate-buffer's own response address is a VC bus address,
* not necessarily the plain ARM-physical one -- masking the
* top bits to recover the ARM-physical alias is the documented
* convention on earlier Pi chips; kept here defensively even
* though the Pi-5-specific reference this file cites for the
* *send* side found no bus-alias bit in play there. Genuinely
* unverified which of these is correct for Pi 5's own
* allocate-buffer response specifically -- flagged, not
* guessed past this comment. */
out->base = (void*) (uintptr_t) (fb_addr & 0x3FFFFFFFu);
out->size = fb_size;
out->width = width;
out->height = height;
out->pixels_per_scanline = pitch_bytes / (bpp / 8u);
out->pixel_format = 0; /* PixelRedGreenBlueReserved8BitPerColor --
* matches PIXEL_ORDER_RGB requested above */
}
return 0;
}