Skip to content

Raspberry Pi 5: native kernel for BCM2712 and RP1 - #1046

Merged
pdfinn merged 66 commits into
masterfrom
feat/pi5-port
Nov 8, 2026
Merged

pdfinn merged 66 commits into
masterfrom
feat/pi5-port

Conversation

@pdfinn

@pdfinn pdfinn commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

InferNode on the Raspberry Pi 5 (BCM2712 and RP1), native, no host OS. os/bcm2712/README.md is the guide and os/bcm2712/INVENTORY.md says what is proved, feature by feature.

On the bench board (Pi 5 8GB, rev 1.0): four cores, the JIT, all 8GB, multi-core Dis, SD, Ethernet at 10/100/1000, USB, Wi-Fi, Bluetooth (pairing, L2CAP, LE scan), GPIO with edges, I2C (#J), SPI (#Q), PWM (#W), the real-time clock, the fan, the power button, NVMe at gen 2, boot from the drive, the release image, Lucifer on either HDMI port.

Pi 5 Pi 4
Ethernet in / out, Mbit/s 946 / 930 935 / 863
Wi-Fi in / out, Mbit/s 52 / 51 47 / not measured
SD read, MB/s 19 19
NVMe read / write, MB/s 370 / 297 none
jitbench, s 6.0 10.4

Bugs this found in shared code

  • arm64: core 0 idled in wfi, and ready() wakes cores with sev, which does not end that. A process that had to run on core 0 waited for a clock tick each time. The IP hardening in fix(security): complete report hardening and 9front audit #1032 removed a lock that had been hiding it: gigabit input fell to 300 Mbit/s with nothing lost. All cores now wfe: 946, steady, with that hardening in place.
  • runproc spun taking and freeing the run queue lock at splhi on a proc still marked with a core, starving ready(). It now lets go, with interrupts on.
  • lock(): a proc preempted between winning a lock and raising its priority, pinned to a core with a spinner at PriLock, deadlocked (lock loop ... held by pc 0x0, within 6 to 70 minutes of the all-devices soak). lock() and canlock() now keep interrupts off across those instructions.
  • Audio: the jack's fuzz was a fractional clock divider and dropped rounding error; fixed, with a spectrum test.
  • kfs marks itself clean when idle, so a reboot or an idle power cut needs no check.
  • devletters.py --check failed on master for #K, #x, #X, #h, #z.

Gated to this board: NVME0, RAMDISCOVER (also BCM2711), SDCARD_HISPEED, WORDMEM, POOLBYNEED. Not gated: the two fixes above, poolsize taking a ulong, the heapreserve test, and osinit's button watcher and drive fallback.

Tests: QEMU bcm2837 + bcm2711 330/0/0 with the lock fix; audiolevel, wordmem, kernelpins and devletters pass. On the Pi 5, 24 hours with every device busy on a diagnostic build of this code: 0 panics, 0 failed compares, 0 media errors, kernel memory 14-17 MB. A 24-hour run on the release kernel is in progress. No QEMU models a Pi 5, so CI only builds it. The 3B+ booted and carried traffic on these fixes before the lock change, from a USB disk, and has not soaked them; the lock change has not run on a real Pi 3 or Pi 4.

Known limits

  • D0 stepping pin tables have run only as a dry run; RAM above 4GB on a Pi 4 has not run; 1 to 4GB Pi 5s are simulated with memlimit=.
  • Pools keep what they grow to, so after one VM's runaway the others have little.
  • Bluetooth throughput is unmeasured; I2C and SPI have no real device on them yet.
  • The bench Pi 5 logs under-voltage at boot.
  • main and image ceilings come from a few measured pages, not a survey; pools keep what they grow to.
  • Charon's heap is badly fragmented by something that grows in small steps; not in this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx

xxxxxxxxxxxxx and others added 30 commits October 31, 2026 21:56
The native kernel on a Pi 5 Model B rev 1.0 (8GB). No emulator models one,
so this went to the board first: os/bcm2712/loader is a serial loader that
reports what the firmware left, and each step asked the hardware.

On that board: all four cores through PSCI, the Dis JIT, the SD card, read
and written; gigabit Ethernet on RP1's GEM, 927 Mbit/s in and 903 out, a
45-round half-hour run clean; USB host on RP1's DWC3s; Wi-Fi joined with
WPA2; Bluetooth inquiry; GPIO; RP1 MSI-X; the external PCIe connector and
an NVMe drive as #S/nvme0; the real-time clock as #r; fan, temperature,
ACT LED, power button level in #P. The console is on RP1's UART0 and UART4
both, because an M.2 board sits on header pins 1-12.

Not done, or not proved: the display (no monitor yet); GPIO edges never
fired; PCIe gen 2, where the link drops 1.6s in, under Linux too, so gen 1
is the default; NVMe writes are fenced to the last 2GB of the disk; audio;
memory above 4GB; no long soak. os/bcm2712/INVENTORY.md has all of it.

Changes to shared code are under compile-time flags: bcm2837 and bcm2711
kernels were byte-identical against master with the shared files reverted,
before the last additions to devarch.c, devcons.c and devsd.c, also gated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
RP1's UART0 and UART4 take their input by MSI-X now, which ends the
polling on RP1. The firmware answers a 32-bit framebuffer request with 16
unless config.txt says framebuffer_depth=32, and says nothing, so the
kernel checks the pitch it was given, and the card's config sets it. With
that Lucifer draws on HDMI, and a keyboard and a mouse work on it.

The screen's size comes from the display's EDID where the firmware has it.
On the Pi 5 it does not: it reports 1920x1080 whatever is attached, and a
1024x768 monitor overflows. That is not solved.

GPIO edges fired for the first time, across a jumper. INVENTORY.md says
where each of these stands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
A Pi 5's firmware sends 1920x1080 whatever is attached and ignores
hdmi_group, hdmi_mode and framebuffer_width, because the OS is expected to
set the mode. So the kernel does, through the tags Linux's firmware-KMS
driver uses: the EDID by the tag that names a display, its first detailed
timing to SET_TIMING, and then SET_PLANE on the framebuffer, because
setting the timing takes the firmware's plane off the screen and leaves it
lit and black.

The bench monitor is 1024x768 and was being sent 1080p, which it scaled
past its edges. It now shows Lucifer at its own size. The buffer keeps the
firmware's 1920-wide pitch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
The inventory and README say so. The firmware calls them displays 2 and 7,
and offers 640x480 on the second; the EDID path set 1024x768 on both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
The write fence is gone; the drive is a test device. nvmewrite now checks
writes at the start, middle and end of the disk. #S's named ranges work on
NVMe too, with nvmepart on sdctl, so a file system can have part of it.

kfs in a 4GB range holds a tree across a reboot. On the whole 2TB it does
not finish reaming in any useful time, so it is not the file system for
the whole disk. devsd.c's changes are all under NVME0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
#J, #Q and #W, the letters the device registry in #1036 reserves for
them; its rows are to be marked in use when that lands. All polled.

Timed on the board: an I2C address byte through a jumper at 10.0us a
bit (the block adds nothing to its counts, so the first values ran at
115kHz); 8192 bytes through SPI's loopback intact at 1 and 10MHz; PWM
at 10ms and 2.5ms to the microsecond at the pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
Asked to advertise less (ether100, ether10), the link came up at 1000
with nothing received: the speed came from what the partner offers, not
what both do. 95 in and 94 out at 100, 10 and 9 at 10, wired, with
Wi-Fi off: with both on one subnet, outbound takes the radio.

The MAC divides its own tx clock, as Linux's CLK_HW_CHG says. Dividing
RP1's clock too counts frames out that nothing receives.

uart10 carries the console's output. No input: with nothing plugged in
its receiver fills with noise.

etherusb no longer says the clock stays at 1970, which on a board with
a clock it does not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
CMD6 to 50MHz, gated to this board: 19 MB/s read off a card that gave
9.3 at 25MHz, and a 64MB stable region of it hashes the same from here
as from Linux on the same card.

No QEMU models a Pi 5, so the workflow only builds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
What a driver owes a disk that is going to hold something. A command
with no answer, a controller fatal status or a dropped link resets the
controller and tries the transfer again: shown with a lost command. A
process killed with a command under way used to leave its completion
to answer the next one; proved over five kills, the same hash after.
The drive is shut down on the way down, and its unsafe-shutdown count
stayed still. The transfer size honours the drive's. Partitions come
from the disk's GPT or MBR, as nvme0p1 and on. #S/nvmestat is what it
says of itself.

Data moves through a cached buffer, not uncached memory: reads through
dd go from 52 to 161 MB/s.

kfs at 16KB blocks reams the whole 1.8TB in 30 seconds and survives a
reboot. With nvmeroot, or no card, init takes /n/dos from nvme0p1, and
#S attaches without a card when there is a drive. Linux's FAT there
reads, and the system ran from it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
The link drops once about 1.6s after training and comes back, and the
reset in nvme.c catches that: proved on the real thing, then six
minutes of SD, NVMe, kfs, SPI, I2C, PWM, Bluetooth LE and Ethernet at
once with no further reset, no failed compare and a flat heap. Reads
at 370 MB/s against 191.

A link that keeps dropping goes to gen 1 after four resets; pciegen1
asks for that, and nvmeslow in sdctl does it by hand. Retraining down
under a running drive is proved.

MBR tables read too. An unmounted kfs comes back clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
Firmware boot from NVMe, BOOT_ORDER 0xf416 and no PCIE_PROBE, reaches a
shell in 16s with userland from nvme0p1, and a tryboot reset returns to
Linux from a kernel on the same partition, which is how the test got
home without hands.

A shutdown after the link's first drop with no I/O since found a drive
that had reset: it now says so and goes on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
Forcing each write to the medium cost kfs nearly half its speed: 18
MB/s against 10, and raw writes 214 against 167. A process flushes
every two seconds instead, and at shutdown; nvmefua asks for the old
way.

halt is PSCI SYSTEM_OFF here, after shutting the drive down. Init reads
#P/button and halts on a press, the kernel saying what happened and init
what it means. Written; a press is still to come.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
The third time this has come up, so nothing in the build says any more.
The firmware's device tree lists the banks; each gigabyte above 4GB is
one level-1 block, needing no tables of its own, so a bigger board costs
no constant; poolsize takes a ulong, the ceilings being fractions of
what was found. The revision code stands in for a missing tree.

On the 8GB board: 7.1GB free, ceilings of 2, 4 and 2GB, 3.76GB of Dis
heap touched and read back, jitbench unchanged. memlimit=N runs as a
smaller board: 4, 2 and 1GB each boot to network, drive and shell from
one image. The BCM2711 takes the same path and has not run it above
4GB: the bench Pi 4 has no such memory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
D0 keeps the pin mux in other registers with other functions; both
tables are generated from Linux's pinctrl-bcm2712.c, and the device tree
says which applies. A dry run on this C1 board with D0's tree writes what
Linux's table says; it has not run on a D0 chip.

Below 4GB the tree now overrules the revision code, whose size field
cannot say 3GB and would have mapped memory that is not there.

The heap ceiling was half of memory, so programs could not reach the
rest however idle it was. Ceilings are not reservations, so they may
overlap: three quarters, with 5.9GB touched on the 8GB board.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
Network load panicked the Pi 5 with 'lock loop' on the run queue's lock,
in most boots of one kernel and no boots of another that differed only
in where that lock landed: 0x4685e8 failed, 0x4685d0 and 0x468600 did
not. The lock was truly stuck: no free reading in 20 million, its last
holder having released it, every other core waiting.

The address is what matters, and this keeps it off shared lines. What
exactly goes wrong on a shared line is not understood.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
…y moved to

devletters.py --check failed on master: #1037 and #1039 moved the
devices and #1036, landing after, had them only as planned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
… memset and memmove by words on the Pi 5

Core 0 idled in wfi and ready() wakes cores with sev, which does not end
that, so a process that had to run on core 0 waited for a clock tick
every time. Ethernet input ran at 300 Mbit/s with nothing lost once the
IP hardening took out a lock that had been hiding it; now 946, steady,
with that hardening in place and the desktop up.

runproc went straight round holding and freeing the run queue lock at
splhi when it found a proc still marked with a core, faster than a core
spinning in ready() could win it. The lock read as held 20 million times
running, every holder having released it, until lock() called it a loop
and panicked. Whether that happened hung on where runq landed, so the
alignment that seemed to fix it is gone: the loop now lets go, with
interrupts on. 0 panics in 4 on the kernel and address that failed in
most boots.

memset and memmove were byte loops, whose speed moved 47% with text
layout. By words where alignment allows, under WORDMEM, this board only:
allocation 2.4 times as fast, NVMe writes 214 to 297 MB/s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
…t them

devletters.py --check was failing on master for #X, #h and #z too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
The ceilings were fractions that added up to 125%, and with one heap a
VM, to 325%. main and image used about 10MB each with a desktop and every
device busy; each now has an eighth of memory, within limits, and
programs have the rest. No heap may grow into what those two could still
need, however many VMs there are. On 8GB: 512MB, 1GB and 6.4GB.

A runaway program reached 6.4GB, got "out of memory", and the system
went on. Pools still keep what they grew to: after that the other VMs
had none.

A shutdown with a transfer under way no longer adds a command to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
By ear on the 3B+: a pure tone had a fuzz on it at any volume, the
11-bit level being the 16-bit sample cut short, an error that repeats
with the waveform. First-order error feedback turns that into noise
above the tone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
A fractional divider gets its average by dithering the clock, which is
jitter on every pulse edge. By ear on the 3B+, that was the fuzz left
after the rounding error went.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
xxxxxxxxxxxxx and others added 23 commits November 8, 2026 00:03
…pped (macb_tx_restart); the watchdog stays as the backstop

Under 4 minutes of gigabit traffic both ways the interrupt restarted it
6884 times and the watchdog never had to; every frame given was sent;
out went from ~904 to 834-944 Mbit/s.
…file server benchmark

kfscmd check printed the whole report and never returned: the read it
had waiting for more was never answered when the check ended. On a Pi 5
console that looked like a frozen machine. tests/host/kfscheck_test.sh
checks twice and fails on the old kfs.

fsbench is the measurement kfs is held to, and gefs will be: on a Pi 5's
NVMe, kfs gives 22.9 MB/s write, 47.8 read, 229 small creates/s, and
124 opens/s of the last of 5000 entries in one directory.
…es, and its hash, matching the C on 21 vectors
…eam, load, 9P server, ctl, check (compiles; untested)
…unsigned qid limits, internal errors that say where
…ader and ticker

Snapshots tested hosted: snap main s1 keeps the old contents and a file
removed after it, delsnap removes it, check ok, cron minute/hour/day
snapshots present.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
The NVMe driver flushed the drive's write cache every two seconds on its
own, which gives no order: a commit protocol (gefs) needs what it wrote
before a barrier on the medium before it writes what comes after. sdctl
now takes "flush", which returns when it is; a recovery and retry as a
transfer has; nvmestat counts them. On a card, which has no such cache,
it does nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
…r recovery

Power-cut tests (tests/host/gefs_crash_test.sh: a journal of every
block written and every flush, -W, made into the disks a cut could
leave, torn 4 KB sectors included) lost whole disks: the log's tail is
rewritten in place after a commit holds it, and a cut that tears it
leaves a block whose hash fails, so the commit's log cannot be read.
9front writes it the same way. Now it goes in two steps with a flush
between, the new entries under the old header and then the new header,
and is cut back to the commit once at mount; every mixture of
neighbouring versions is a valid block. The superblocks get a flush
between them, so one cut cannot tear both. Disk format unchanged.

Also found by the tests:
- check after a crash read the stale on-disk log tail, which chains on
  to blocks the lost commit allocated, and cached a free block; the next
  allocation of it broke the file system as a double alloc. getblk now
  returns the tail in memory.
- ctl halt replied before its commits, so whatever ran next could see a
  disk mid-sync; sync and halt now finish before the reply.
- a sync that fails part way leaves the file system read only, as
  9front's does, rather than retrying from a half-advanced state; a
  failed device flush is such a failure.

And: built by mk (appl/cmd/disk/gefs/mkfile), in the dis manifest,
gefs(4), sdctl flush in sd(3), gefs_hash_test (MetroHash64 and ihash
pinned), gefs_test.sh (ream, files, persistence, snapshots, check).

200 power cuts pass; SIGKILL during writes, 15 of 15.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
MetroHash64's loop, which every block written goes through, without the
twelve calls a word it made; the pinned values (gefs_hash_test) hold
under the interpreter and the JIT.

fsbench adds parread1/parread4 (one reader, four at once) and
statidle/statbusy (a small file's stat and read, idle and during a 128
MB write, with the longest single operation): what one serving loop
costs, kfs and gefs alike. On the Pi 5's NVMe, gefs (p3) against kfs
(p2): seqwrite 11.6 / 21.7 MB/s, seqread 40.8 / 45.8, smallcreate 602 /
219 files/s, smallstat 2594 / 490, bigdir 944 / 107, lookup 1439 / 115
opens/s, parread4 47.3 / 43.2 MB/s, statbusy 140 / 224 ops/s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
The cuts go where blocks are written in place, the only ones a commit
can depend on, and tear those more; the workload makes logs long enough
to cross sectors. With the log tail fix taken out it now fails 12 cuts
in 100 (it was 1 in 200); with it, 300 of 300 pass. Default 24 cuts.

gefs_test and gefs_crash_test run in CI shard 2, and are in CLAUDE.md's
table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
Every write request hashed and wrote a new root as well as its data,
and the next request replaced it, so most were never pointed at by
anything on disk. A root made by fastupsert, most of them, is now only
cached (dirty, so never evicted) until something is to point at it: a
snapshot (tree2kv) or the superblock (packsb), both before a commit's
first flush. One replaced in the meantime costs neither hash nor write.
Still a fresh copy each time, never changed in place, since a scan
held between requests needs blocks as they were. In-memory only; the
disk is as before.

Hosted: seqwrite 7.6 -> 9.7 MB/s, a small file's stat and read during a
big write 85 -> 123 ops/s. 150 power cuts pass, gefs_test passes, and
150 s of writes across minute snapshots checks clean with each snapshot
holding what was there at its minute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
rootpath takes a third source, "gefs nvme0p3": osinit starts the
kernel's gefs on #S/nvme0p3, mounts it where 9front has them (the file
system on /n/gefs, its adm tree on /n/gefs.adm, every snapshot on
/n/dump) and unions its dis/ lib/ fonts/ icons/ after the recovery
root, as for the card. gefs.dis is in the recovery root, as dossrv is:
the root's server must match the disk and be there to repair it
whatever state the card is in (image 2.08 -> 2.21 MB). The first source
with a usr/ now has /usr, so "gefs nvme0p3" then "local" keeps the
disk's home with the card's trees behind it.

disk/gefsroot [-r] [-s src] part copies the card's trees onto it,
snapshot first (install.<seconds>), each tree whole so dropped files
go, usr/ only when the disk has none. Runs in the caller's namespace
(sh -n). gefsroot_test.sh: 14 checks, in CI.

Pi 5, nvme0p3: install 82.6 MB from the card in 139 s; booted with
rootpath gefs nvme0p3 + local: /dis is the recovery root, then
/n/gefs/dis, then /n/dos/dis; /usr is the disk's; a file written there
survived a reboot; snapshots on /n/dump; check clean. The card's
rootpath is back to local.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
A device that drops off and returns between two of the hub watcher's
looks (one a second) reads present both times, and the port it left has
disabled itself, as xHCI and USB 2.0 hubs do on any disconnect: every
transfer after is a Transaction Error and nothing reset the port. The
Pi 5 soak lost its USB Ethernet that way 80 minutes in, for the rest of
the boot (root port PORTSC 0x206e1: connected, disabled, connect change
unacknowledged). On xHCI (as #679's watcher changes are), a port with a
device set up on it that is present but not enabled is now that device
gone and back: detached and set up again, as an attach is.

baremetal_test.sh (virt): a keyboard in a root port pulled and put back
within milliseconds is set up again and typed on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
wifiaddr asked DHCP twice after the radio authenticated and, with no
lease, returned nil: nothing asked again, and the radio had no address
for the rest of the boot. A Pi 5 soak's radio did that after re-
associating mid-handshake; ip/dhcp run by hand minutes later got
192.168.1.101 at once. Authentication already keeps trying; the address
now does too, every 30 seconds, and the watcher starts when it comes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
The Pi 5 half of #1051 (keys from a hardware generator alone), which
integ/pi5-all took in its merge of it (462c72b) and the soaked build
ran: the board's own random.c goes, the RNG200's registers are named for
../bcm/rng200.c, and the README says so. Master has #1051 but not this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
- kernelpins_test: the lock window is closed either way, interrupts off
  across the test-and-set (this branch's) or PriLock before it
  (master's #640); a lock() doing neither still fails it (checked).
- wordmem_test removed, and -DWORDMEM from the Pi 5's build: master's
  eight-byte libkern copies (438d985) replace the WORDMEM ones, and
  libkern_copy_test covers them.
- gefs's mkfile sets MODULES, as check-mkfile-deps.py now requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
@pdfinn
pdfinn marked this pull request as ready for review November 7, 2026 18:11
@pdfinn

pdfinn commented Nov 7, 2026

Copy link
Copy Markdown
Member Author

Branch rebuilt as master plus the Pi 5 work, and marked ready for review. CI is green: 36 checks pass, 1 skipped.

What changed on feat/pi5-port (a0787c3)

  • Current master is merged in. Conflicts were resolved for master, and the merge commit says how:
  • The 22 Pi 5 commits from the integration line are added:
    • RP1 Ethernet transmitter stall recovery;
    • NVMe recovery in three steps and a bridge re-setup;
    • Wi-Fi re-join without dropping the address;
    • kfs check and fsbench;
    • the gefs port, with its crash-safety fixes and tests;
    • #S/sdctl flush;
    • root on gefs (rootpath gefs part), off by default;
    • from the soak: osinit sets up again a USB device that dropped off the bus and came back, and keeps asking for the Wi-Fi address after a failed lease.
  • The Pi 5 half of Bare metal: a key is made only from a hardware generator #1051 (../bcm/rng200.c) is added; the cherry-pick had missed it.
  • kernelpins_test now accepts either fix for the lock window. Removing the PriLock raise still fails it.
  • wordmem_test is removed: libkern_copy_test covers master's routines.
  • brick(1): styxbrick client — TLS pinning, sdg transport, brick d2 safety outputs #1015 (styxbrick), which the integration line also carried, is not included.

Checked against the soaked branch: every file that differs differs through master's own changes or styxbrick's.

Evidence

  • Local: the dis manifest builds (1,184 modules); device letters, mkfile dependencies, the bcm2712, bcm2711, bcm2837 and virt kernels, and the gefs, kfs, kernrandom, libkern, kernelpins and audiolevel tests all pass.
  • soak33 (12 h, Pi 5, on the integration line plus Bare metal: one ChaCha20 generator, keyed from the hardware, behind every reader #1059 and dossrv: a 1.1 MB sector cache, not a floppy's 90 KB #1062, so the same Pi 5 code without master's newer commits):
    • no reboots or crashes, memory flat;
    • Ethernet ~850/550 Mbit/s, Wi-Fi ~43/75, USB ~190/125;
    • every device loop failure-free (SD, NVMe, kfs, SPI, I2C, GPIO, Bluetooth, drawing, both VMs, memory, RNG, RSA);
    • 2 TLS failures early in the run, with output lost. They have not recurred: 457 runs since, 193 of them at ten times the rate under full load. The soak harness now keeps each failure's output.
  • soak34 (12 h, Pi 5, on this branch's build) started 00:36 and is clean at the start. Its result will be posted here.

Merge is yours. soak34 is the soak of exactly this build.

🤖 Generated with Claude Code

https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx

…ll three

Insecure and Verified still asked for 2xx/3xx, as TLS13 no longer does:
which status comes back is the server's policy. A Pi 5 soak counted
example.com's 403 to its GET, one time in about forty, as TLS failing on
a handshake that had succeeded (TLS 1.3, suite 0x1301, verified); the
soak's own log, which now keeps a failure's output, showed it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
pdfinn and others added 2 commits November 8, 2026 18:06
Conflicts:
- os/bcm/audiopwm.c: master's audiostat comments on played and
  underruns, and this branch's rounding carries and pulse range, kept
  together.
- man/3/i2c: one page for #J's two drivers, the Pi 5's header buses
  and the Pi 4's camera bus; every fact from each page kept, checked
  against both drivers.
- docs/DEVICE-LETTERS.md: #J's row names both boards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
Master's camera (#1085) is the Pi 4's own (os/bcm2711/devcam.c) and the
simulated sensor the Pi 5 served as #V is gone with os/port/devcam.c;
it dropped #V from bcm2837's table, and this does from bcm2712's (the
kernel did not link).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195u6z8JjsAy9yrE5TqKxzx
@pdfinn
pdfinn merged commit b8d8309 into master Nov 8, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants