Running modern FreeBSD on the BeagleBone Black
Running modern FreeBSD on the BeagleBone Black
FreeBSD 13.3 was the last release that booted on a BeagleBone Black. AM335x was removed from the armv7 GENERIC kernel config, the drivers left behind bit-rotted, and 13.x has since been purged from the download mirrors — it survives only on archive.freebsd.org/old-releases.
This is a working port for 14.x and 15.x, validated on real hardware, plus the patches and a full walkthrough. It boots to multi-user with serial console, microSD root, eMMC, Ethernet, USB, I2C, GPIO, and — through device tree overlays — ADC, SPI and PWM.
Most of this is on its way upstream. Some of it has already landed. Upstreaming is slow, and there is no reason for the patches to sit in a queue in the meantime.
Contents
- Credit, first
- What actually works, and where
- What you need
- Wiring the serial console
- Downloads
- 1. Get the source
- 2. Apply the patches
- 3. Build the kernel and world
- 4. Build a bootable image, without root
- 5. Write the card
- 6. Boot it
- 7. Installing packages
- Iterating without touching the card
- What is not done
- The current (16.0) lane
- Upstream status
Credit, first
The part that makes the board boot at all is not mine. The ti_sysc / clock / simple-pm-bus rework for AM335x is Oskar Holmlund’s (oh@FreeBSD.org), posted to FreeBSD Phabricator as D46703, D46712, D46713, D46714, D41888, D46722, D46723 and D46746. Benny Goemans (github.com/malavon) rebased that stack onto stable/14 and carried a musb clock-name hunk that the narrower Phabricator diffs had lost.
Two of those — D46712 and D46713 — landed in main on 2026-08-28. D46714 is accepted and waiting. If you want to help, the most useful thing you can do is test the remaining revisions and say so on Phabricator.
Layers 1 through 3 below are mine: bring-up fixes, two new drivers, and an experimental USB DMA offload.
What actually works, and where
| 14.x | 15.x | current (16.0) | |
|---|---|---|---|
| Boots to multi-user on hardware | Yes | Yes | No — see below |
buildkernel KERNCONF=AM335X | Yes | Yes | Yes |
| Platform prerequisite applies | Yes | Yes | Partly — config only |
| Driver layers apply and compile | Yes | Yes | Yes |
| Device attach failures | 0 | 0 | untested |
| Reboot soak | untested | 6 clean cycles | untested |
| Independently reproduced | — | Yes | — |
The 15.x lane has also been built and booted by another user following this guide, on their own board and a clean 15.1 build host.
If you were going to start with current, read the note on it below first — it builds, but it will not boot a board yet, and the reason is not something you can work around.
What you need
- A BeagleBone Black. Mine is a rev B3, AM3358 ES2.0.
- A FreeBSD build host. Cross-building is the only sane option — do not try to build on the board. Mine is amd64, 8 cores, 32 GB.
- A 3.3V USB serial adapter. There is no video console worth using and you will need the console before you need the network.
- A microSD card, 4 GB or larger.
Everything below runs unprivileged. The image pipeline uses make -DNO_ROOT, makefs -F METALOG and mkimg rather than release.sh, which needs mdconfig and root. The only step that wants privilege is writing the finished image to the card.
Wiring the serial console
The console is on the J1 header, not P9. Six pins near the P9 side of the board, next to the “BEAGLEBONE” silkscreen.
J1-1 GND -> adapter GND
J1-4 board RX -> adapter TX
J1-5 board TX -> adapter RX
115200 8N1Do not connect the adapter’s VCC pin. The board powers itself; feeding 3.3V or 5V back into J1 is a good way to damage it.
Downloads
One bundle per branch. Each unpacks into its own directory and contains the patches for that lane, a README, and tools/verify-image.sh.
| Lane | Bundle | Size |
|---|---|---|
| 14.x | freebsd-bbb-14-patches.tgz | 72 KB |
| 15.x | freebsd-bbb-15-patches.tgz | 90 KB |
| current | freebsd-bbb-current-patches.tgz | 48 KB |
Each unpacks into its own directory and carries the patches for that lane, a README, and tools/verify-image.sh — which comes out executable, because tar preserves the mode:
fetch https://www.isysop.com/wp-content/uploads/2026/09/freebsd-bbb-15-patches.tgz
fetch https://www.isysop.com/wp-content/uploads/2026/09/SHA256.txt
sha256 -c SHA256.txt freebsd-bbb-15-patches.tgz
tar xzf freebsd-bbb-15-patches.tgz
cd freebsd-bbb-15-patchesChecksums, also in SHA256.txt. The .tar and .zip rows in that file are the same bundles in other formats; ask if you want them posted:
SHA256 (freebsd-bbb-14-patches.tgz) = 71b4e28d1843fe91189289d0050c00deac6a337b6d96e66133fc65bd109f1101
SHA256 (freebsd-bbb-15-patches.tgz) = ae55ff4768ae0bbad1126b6f7c15ce550d096b89cc2f4bb30294e99172f1ee47
SHA256 (freebsd-bbb-current-patches.tgz) = 2b27bc40be52cb56e1bd1e2298051a61eff18450e617d9cc468e311198adeb92
SHA256 (freebsd-bbb-14-patches.zip) = bbce9f8ab84631e19865e7a8106f7f35d824e0a8737faa496798f98c6533574f
SHA256 (freebsd-bbb-15-patches.zip) = 6ca3884fdc5e1b1111ae01a4d078d642a1f81003449760cd34ca68ef36ca4665
SHA256 (freebsd-bbb-current-patches.zip) = 22da4dafa2802f11e7224215193b737fd4ae77bd0457d5969e329f6b4d0bc7efLicence: two-clause BSD, same as FreeBSD.
1. Get the source
The patches are cut against specific commits. Pin to them — stable/14 and stable/15 move, and a patch set that “applies to stable/15” without saying which stable/15 is a patch set that will not apply next month.
git clone https://git.freebsd.org/src.git /usr/src-bbb
cd /usr/src-bbb
# 14.x lane
git checkout b88d20eb0241 # stable/14, committed 2026-09-04
# 15.x lane
git checkout c41e308ef27b # stable/15, committed 2026-09-05
# current lane
git checkout 6e1436d74827 # main, committed 2026-09-04Those are commit dates. The author dates on the same commits read 2026-08-21 and 2026-08-23, because stable branches land batches of MFCs that keep the original author timestamps. Check out by hash rather than by date.
They will apply to nearby commits too. These are the ones I tested.
2. Apply the patches
The set is layered so you can stop where you like. Apply them in order; each assumes the one before it. The three lanes do not carry the same file list, so use the block for the branch you checked out.
14.x
cd /usr/src-bbb
git apply /path/to/14/0-platform-prereq.diff
git apply /path/to/14/1-bringup-fixes.diff
git apply /path/to/14/2-drivers.diff
git apply /path/to/14/3-usb-dma-experimental.diff # optional, default-off
git apply /path/to/14/geom_part-access-leak.diff # optional, not BBB-specific15.x
Same four layers, plus one patch that has nothing to do with the BeagleBone:
cd /usr/src-bbb
git apply /path/to/15/0-platform-prereq.diff
git apply /path/to/15/1-bringup-fixes.diff
git apply /path/to/15/2-drivers.diff
git apply /path/to/15/3-usb-dma-experimental.diff # optional, default-off
git apply /path/to/15/geom_part-access-leak.diff # optional, not BBB-specific
git apply /path/to/15/stable15-procdesc-include.diff # see belowstable15-procdesc-include.diff fixes an upstream build break in stable/15 that stops buildworld dead. You need either this patch or WITHOUT_TESTS in /etc/src.conf — details in the next section. Neither is needed on 14.x or current.
current
Different list. There is no combined platform layer, because most of that stack does not apply to main — what is here is enough to build, not to boot:
cd /usr/src-bbb
git apply /path/to/current/0-kernel-config.diff
git apply /path/to/current/0b-restore-clock-common.diff
git apply /path/to/current/1-bringup-fixes.diff
git apply /path/to/current/2a-ti_edma3.diff
git apply /path/to/current/2b-ti_sdhci.diff
git apply /path/to/current/2c-ti_rng.diff
git apply /path/to/current/3-usb-dma-experimental.diff # optional, default-off
git apply /path/to/current/geom_part-access-leak.diff # optionalThree differences from the stable lanes, all explained in the “try current first” section above: 0-kernel-config.diff carries only the AM335X kernel config rather than the whole prerequisite stack; 0b-restore-clock-common.diff un-breaks sys/arm/ti, which does not compile on main today; and layer 2 is split into 2a/2b/2c because main refactored random(4) and the RNG driver needed a port to match.
Layer 0 — platform prerequisite
Holmlund’s stack plus the AM335X kernel config. Without this the board does not boot. Credit as above. If you are only interested in getting a board running, this is the layer that matters.
Layer 1 — bring-up fixes
Small things that were wrong or bit-rotted. The one worth naming: three TI clock drivers took a register offset from the wrong node, which faults at boot with FAR=ffd00001 the moment a ti,clksel parent is involved. Upstream as D59120, with D59118 and D59119 alongside.
Layer 2 — drivers
Two additions, both upstream and awaiting review:
- SD/MMC data phase through EDMA3 (D59124, D59125). The SDHCI controller on this part cannot do its own DMA, so the driver was doing PIO for every sector. Handing the data phase to EDMA3 gives 98% of PIO throughput at 1/15 the CPU and 1/40 the interrupt time. That is the single biggest quality-of-life change here — the board stops spending itself on the SD card.
- Hardware random number generator (
ti_rng). AM335x has a SafeXcel EIP-75 TRNG that FreeBSD never used. It registers withrandom(4)as a fast entropy source. Measured over 2 MB of raw output: entropy 7.999908 bits/byte, chi-square p 30%, serial correlation −0.000718.
Layer 3 — experimental USB DMA
Every knob in this layer is off by default. It offloads USB bulk transfers to the AM335x CPPI 4.1 DMA engine. On a fast device: 19.6 MB/s at 24.1 CPU-ms/MB, against 12.2 MB/s at 39.3 for PIO — 61% more throughput for 39% less CPU per byte.
It is marked experimental for specific reasons. It fixes a real wedge — sustained bulk writes would hang a mass storage device within tens to hundreds of megabytes — and after the fix it survived 7 consecutive 512 MB runs and 3.5 GB total with zero failures. But the frame-interrupt handling is not refcounted across endpoints, which is correct for one active DMA endpoint and latent for several. There are known cosmetic issues still to clean up. Enable it if you want to test it, not if you want a quiet machine:
sysctl hw.musb_dma=1 hw.musb_tx_dma=1Bonus: a GEOM fix that is not BBB-specific
geom_part-access-leak.diff fixes a 13-year-old access leak in g_part_spoiled() that wedges every subsequent USB disk attach after a partition table is resized. It is machine-independent, applies to all three branches, and is unrelated to the BeagleBone. Filed as PR 297777.
3. Build the kernel and world
First, put this in /etc/src.conf. It is not optional advice — see below:
WITHOUT_TESTS="YES"
WITHOUT_TESTS_SUPPORT="YES"
WITHOUT_EXAMPLES="YES"
WITHOUT_GAMES="YES"
WITHOUT_SENDMAIL="YES"
WITHOUT_SHAREDOCS="YES"cd /usr/src-bbb
export MAKEOBJDIRPREFIX=/usr/obj-bbb
make -j8 buildworld TARGET=arm TARGET_ARCH=armv7
make -j8 buildkernel TARGET=arm TARGET_ARCH=armv7 KERNCONF=AM335Xbuildworld fails on stable/15 without this
If you build world on stable/15 with tests enabled, you will get this after a long wait:
--- all_subdir_tests/sys/kern ---
tests/sys/kern/procdesc.c:374:2: error: call to undeclared function
'ATF_REQUIRE_FEATURE'; ISO C99 and later do not support implicit function
declarations [-Werror,-Wimplicit-function-declaration]
*** [procdesc.o] Error code 1Nothing in this patch set touches tests/. What happened upstream: commit 2fbb853ec71e on stable/15 (a cherry-pick of ddf62c83fc0a) added an ATF_REQUIRE_FEATURE call to that file. The macro lives in tests/freebsd_test_suite/macros.h, and the commit that adds that include on main — 3c5c55beee5e — was never merged back. main has the include and builds; stable/15 has the call without the include. Still true on stable/15 tip as of 2026-09-11. stable/14 is unaffected, and so is current.
Two ways out. WITHOUT_TESTS above is the quick one, and you probably want it anyway — it takes a substantial bite out of a build you are only doing to get a BeagleBone running. If you would rather keep tests, the 15.x bundle includes stable15-procdesc-include.diff, which adds the one missing #include and is the actual fix.
This was found by another user building from this guide on a clean 15.1 host. A build host with an older world already in place will not hit it.
AM335X is the kernel config added by layer 0. It does not exist in an unpatched tree — this is the config from D46703, and it is why AM335x support cannot simply be switched on in GENERIC any more.
World takes a while. The kernel alone is a few minutes and is all you need if you are iterating on drivers.
4. Build a bootable image, without root
You need MLO and u-boot.img from the port — do not scavenge them out of an old 13.x image:
pkg install u-boot-beaglebone
# installs into /usr/local/share/u-boot/u-boot-beaglebone/Stage the root filesystem. -DNO_ROOT records ownership and permissions into a METALOG file instead of applying them, so none of this needs privilege:
ROOTFS=/usr/bbb/rootfs
make installworld distribution installkernel \
TARGET=arm TARGET_ARCH=armv7 KERNCONF=AM335X \
DESTDIR=$ROOTFS -DNO_ROOTWrite the config files. Serial console is not the default and you will see nothing without it:
cat > $ROOTFS/boot/loader.conf <<'EOF'
console="comconsole"
comconsole_speed="115200"
boot_verbose="YES"
EOF
cat > $ROOTFS/etc/fstab <<'EOF'
/dev/ufs/rootfs / ufs rw 1 1
EOF
cat > $ROOTFS/etc/rc.conf <<'EOF'
hostname="bbb"
ifconfig_cpsw0="DHCP"
sshd_enable="YES"
EOFAny file you create by hand must be declared in METALOG or makefs silently leaves it out of the image. This is the easiest way to lose an afternoon on the unprivileged path:
for f in boot/loader.conf etc/fstab etc/rc.conf; do
echo "./$f type=file uname=root gname=wheel mode=0644" >> $ROOTFS/METALOG
done
# -DNO_ROOT never emits some debug directories, and makefs needs parents
# before children. Dropping usr/lib/debug fixes both, and saves 457 MB.
rm -rf $ROOTFS/usr/lib/debug
grep -v '^\./usr/lib/debug' $ROOTFS/METALOG | sort -u > $ROOTFS/METALOG.sortedBuild the FAT boot partition:
FATDIR=/usr/bbb/fat
mkdir -p $FATDIR/EFI/BOOT
cp /usr/local/share/u-boot/u-boot-beaglebone/MLO $FATDIR/
cp /usr/local/share/u-boot/u-boot-beaglebone/u-boot.img $FATDIR/
cp $MAKEOBJDIRPREFIX/usr/src-bbb/arm.armv7/stand/efi/loader_lua/loader_lua.efi \
$FATDIR/EFI/BOOT/bootarm.efi
cp -R $ROOTFS/boot/dtb $FATDIR/
makefs -t msdos -o fat_type=16 -o sectors_per_cluster=8 \
-o media_descriptor=240 -o reserved_sectors=1 \
-s 50m /usr/bbb/boot.fat $FATDIRThen the UFS root, a BSD label around it, and the MBR:
makefs -t ffs -F $ROOTFS/METALOG.sorted -o version=2,label=rootfs \
-s 3g /usr/bbb/root.ufs $ROOTFS
mkimg -s bsd -p freebsd-ufs:=/usr/bbb/root.ufs -o /usr/bbb/root.bsd
mkimg -s mbr \
-p fat32lba:=/usr/bbb/boot.fat \
-p freebsd:=/usr/bbb/root.bsd \
-a 1 -o /usr/bbb/bbb.imgThe boot ROM rule that silently rejects cards
The AM335x boot ROM requires that the MBR partition entry’s sector count equal the FAT filesystem’s sector count. If they differ it rejects the card silently — no console output at all, not even a character. A dead-looking board and a perfectly good kernel.
Two consequences, both counter-intuitive:
- Do not pass
-Tor-Htomkimg. CHS geometry makes it round the partition size up to a track boundary (102400 → 102438 sectors) and that breaks the rule. sectors_per_cluster=8is load-bearing. With 512-byte clusters,makefscaps a 50 MiB FAT16 volume at 66069 sectors inside a 102400-sector partition, because of FAT16’s 65525-cluster limit. 4 KiB clusters let the filesystem span the whole partition. Also:media_descriptormust be decimal240;makefsrefuses0xf0as “illegal number”.
And two pieces of widely repeated folklore that are false. The known-good 13.5 image violates both and boots fine:
- “MLO must be the first file in the root directory.” It need not be.
- “HiddenSectors must equal the partition LBA.” It need not.
-a 1 marks partition 1 active, which the ROM does require.
5. Write the card
Identify the card before you type anything. dd will overwrite whatever you point it at, without asking, and /dev/da0 is a perfectly plausible name for a disk you care about. Insert the card, then:
dmesg | tail # the card announces itself as it attaches
geom disk list # confirm the SIZE matches your card, not your system diskSet it once, in a variable, so there is exactly one place to get it right and the rest of the commands follow:
CARD=/dev/daX # <-- REPLACE daX with the device you just identified
dd if=/usr/bbb/bbb.img of=$CARD bs=1m conv=sync status=progressThen verify the card, not the file. Two traps: dd can exit 0 having written nothing at all if the media is absent or the device node is stale, and dd bs=1 fails on raw devices on FreeBSD because reads must be sector-aligned.
verify-image.sh is in the bundle and does this for you. Point it at the card, not the image — checking the file you just built tells you nothing about what reached the media:
sh verify-image.sh $CARD
ok MBR signature @0x1FE 55aa
ok partition 1 active flag 80
ok partition 1 type (FAT) 0x0c
info partition 1 start LBA 2048
ok BPB BytesPerSector 512
info BPB SectorsPerCluster 0x08
info FAT filesystem sectors 102400
ok MBR p1 size == FAT sector count 102400
All documented boot ROM requirements satisfied.The last check is the one that matters. If it says WILL NOT BOOT, the card will produce a completely silent board — no console output at all — and you will spend the evening suspecting your kernel.
6. Boot it
Open the serial port before setting the speed. Opening /dev/cuaU0 applies the termios from /dev/cuaU0.init, which is 9600 and needs root to change. If you stty first and open second, your settings are silently discarded and you get total silence from a board that is booting perfectly.
cat /dev/cuaU0 > console.log &
stty -f /dev/cuaU0 115200 cs8 -parenb -cstopb clocal raw -echoInsert the card, apply power, and you should see the ROM hand off to MLO, then u-boot, then the FreeBSD loader.
FreeBSD 15.1-STABLE (AM335X) arm
CPU: Cortex-A8 r3p2 (ECO: 0x00000000)
cpsw0: <Three-Port Switch Ethernet Subsystem> ...
ti_rng0: <TI EIP-75 random number generator> ...
random: registering fast source TI EIP-75 RNG7. Installing packages
On 15.x pkg does not work out of the box. The stock repository in /etc/pkg/FreeBSD.conf points at the quarterly branch:
FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/quarterly",
mirror_type: "srv",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg",
enabled: yes
}There is no quarterly tree for FreeBSD:15:armv7. meta.conf, packagesite.pkg, data.pkg and Latest/pkg.pkg all return 404, so pkg update fails and pkg install finds nothing. Point the repository at latest, which is complete and signed:
mkdir -p /usr/local/etc/pkg/repos
cat > /usr/local/etc/pkg/repos/FreeBSD.conf <<'EOF'
FreeBSD-ports: {
url: "pkg+https://pkg.FreeBSD.org/${ABI}/latest"
}
EOF
pkg update -fName the block FreeBSD-ports. That is what the stock repository is called on 15.x; the older name FreeBSD parses without error and does nothing at all. Quote the heredoc delimiter as 'EOF' too, or your shell expands ${ABI} to an empty string before pkg ever sees the file.
14.x needs none of this: FreeBSD:14:armv7/quarterly exists and the stock configuration works.
pkg install then behaves normally. The latest tree carries 32,321 signed packages, git, python311, curl, bash and tmux among them.
armv7 packages stopped being built in September 2025
Whichever branch you point at, the packages are a year old. Checked 2026-09-12, from the packagesite.pkg timestamp on each repository:
| Repository | latest | quarterly |
|---|---|---|
FreeBSD:14:armv7 | 2025-09-23 | 2025-09-03 |
FreeBSD:15:armv7 | 2025-09-26 | absent |
FreeBSD:16:armv7 | absent | absent |
FreeBSD:15:aarch64 | 2026-09-10 | 2026-09-12 |
aarch64 and amd64 are current to the week. armv7 is frozen, and there is no FreeBSD:16:armv7 repository at all, which matches the 16.0 deprecation. Expect no further package updates, security fixes included.
Building your own packages
Worth the trouble only if you need something newer than September 2025, or a port outside the frozen set. Cross-build on an amd64 host: a 1 GHz single-core Cortex-A8 with 512 MB of RAM will not build anything large in reasonable time.
Install and configure it first. A fresh poudriere has no usable configuration, and the sample it ships will not run as installed:
pkg install poudriere qemu-user-static
cp /usr/local/etc/poudriere.conf.sample /usr/local/etc/poudriere.conf
zpool list # pick a pool with room to spare
cat >> /usr/local/etc/poudriere.conf <<'EOF'
ZPOOL=zroot # on UFS use NO_ZFS=yes instead
FREEBSD_HOST=https://download.FreeBSD.org
EOF
mkdir -p /usr/ports/distfiles
service qemu_user_static onestart # registers the armv7 binmisc handler
binmiscctl lookup armv7 # confirm it took before going furtherThree settings in that sample will stop you. ZPOOL is commented out, so poudriere exits with Error: ZPOOL variable is not set — name a real pool from zpool list, or set NO_ZFS=yes if the host is UFS. FREEBSD_HOST ships as the literal placeholder _PROTO_://_CHANGE_THIS_. And DISTFILES_CACHE points at /usr/ports/distfiles, which has to exist. Appending to the file is enough: poudriere sources it as shell, so the last assignment wins.
Then build:
poudriere jail -c -j 15armv7 -a arm.armv7 -m git+https -v stable/15
poudriere ports -c
poudriere bulk -j 15armv7 -p default www/nginx shells/bash-j names the jail created on the line above — the armv7 stable/15 userland the packages are compiled against. -p names the ports tree that poudriere ports -c checked out. The trailing arguments are port origins: list only what you actually want and poudriere builds the dependency graph for you. Use -f portlist once there are more than a handful.
The result is a complete repository at /usr/local/poudriere/data/packages/15armv7-default, named for the jail and the ports tree. Serve that directory over HTTP and point the board at it:
mkdir -p /usr/local/etc/pkg/repos
cat > /usr/local/etc/pkg/repos/local.conf <<'EOF'
local: {
url: "http://BUILDHOST/packages/15armv7-default",
enabled: yes
}
FreeBSD-ports: { enabled: no }
EOF
pkg update -fNot everything runs emulated. Creating a cross jail also builds a native toolchain beside it, in nxb-bin under the jail directory, and poudriere compiles with that:
file /usr/local/poudriere/jails/15armv7/nxb-bin/usr/bin/cc
ELF 64-bit LSB executable, x86-64 ... statically linked
file /usr/local/poudriere/jails/15armv7/usr/bin/cc
ELF 32-bit LSB executable, ARM, EABI5 ... dynamically linkedSo the compiler and linker run at full host speed, and only target binaries — configure probes, build tools a port generates and then runs — go through qemu. Budget the time for poudriere jail -c regardless: it is a full buildworld for armv7 plus that second toolchain.
poudriere ports -c is not optional. Without a ports tree, bulk has nothing to build; check with poudriere ports -l before starting a long run.
Verified this far: the jail builds and reports ready to be used. The bulk run beyond it has not been exercised here, so treat the last command as a starting point.
Iterating without touching the card
Pulling the card for every kernel build gets old immediately. The board can fetch over HTTP:
# build host
cp $MAKEOBJDIRPREFIX/usr/src-bbb/arm.armv7/sys/AM335X/kernel /srv/deploy/kernel
python3 -m http.server 8080 --directory /srv/deploy &
# on the board, over the console
cp -a /boot/kernel /boot/kernel.working
fetch -o /tmp/k http://BUILDHOST:8080/kernel
cp /tmp/k /boot/kernel/kernel && sync && rebootUse nextboot -k kernel.working to arm a one-shot fallback so a bad kernel recovers itself on the next reboot instead of stranding you.
What is not done
Known gaps and untested areas:
- current does not boot, for the reason above.
- No reboot soak on 14.x. The six clean cycles were on 15.x.
- The pinned bases have not been booted. The patches were validated on 2026-08-10 trees, then rebased onto commits 238, 603 and 247 commits newer. The files the patches touch are the tested ones; the rest of the tree is not.
- One unexplained panic on a first boot, reported once by another user, with the LAN cable plugged in while the board was booting. It has not been reproduced. The second boot needed
fsck -yin single user and was clean afterwards. If you hit it, capture the console log from power-on. - PRUSS — needs a PRU toolchain, not attempted.
- eQEP — no FreeBSD driver exists at all.
- RTC — no battery fitted, so untested.
- SPI loopback and electrical verification at the header pins — needs a jumper and instruments.
- The USB DMA layer has the open items listed above. It is off by default for a reason.
The current (16.0) lane
current builds but will not boot a board. If you were planning to start there, read this section first.
current builds. Verified in a clean room: pristine main at 6e1436d74827, apply the eight current/ patches in order, and buildkernel KERNCONF=AM335X produces a 10.4 MB kernel with ti_rng.o, ti_edma3.o, ti_sdhci.o, musb_otg.o, am3359_cppi41.o and am335x_musb.o all compiled in. If you want to check that something has not rotted against current, that is enough to work with.
Three things are needed beyond the driver patches themselves.
main’s sys/arm/ti does not currently compile
Not because of anything in this bundle. D46712 “Remove clock_common.*” was committed on 2026-08-28 as 541aa93794e6. Its replacement, D46723, has not been committed. Five files in main still call the helpers that removal took away:
sys/arm/ti/ti_sysc.c (2 uses)
sys/arm/ti/clk/ti_divider_clock.c (13 uses)
sys/arm/ti/clk/ti_dpll_clock.c (13 uses)
sys/arm/ti/clk/ti_gate_clock.c (13 uses)
sys/arm/ti/clk/ti_mux_clock.c (13 uses)All five are standard in files.ti, so no kernel configuration can leave them out. Nobody upstream trips over it because AM335x was removed from armv7 GENERIC in 3416e102c4e9, so no in-tree kernel config compiles that directory at all. The breakage is invisible precisely because the hardware was dropped.
0b-restore-clock-common.diff bridges the window: it restores the two files verbatim from 541aa93794e6^ and puts the .c back in files.ti. It is a verbatim revert, nothing more. Do not send it upstream. When D46723 lands it rewrites every one of those call sites and this patch has to be dropped.
The other two: 0-kernel-config.diff is Holmlund’s AM335X config (D46703) plus one line of mine, device ti_rng — files.ti marks that driver optional ti_rng, so without a device line it is silently not compiled at all. And 1-bringup-fixes.diff is D59118 and D59119, without which ti_gpio.c fails to compile on an incomplete struct intr_irqsrc.
current does not boot a board. Booting also needs the ti_sysc / clock / simple-pm-bus stack, and the clock rework in it (D46723) no longer applies to main. D46712 and D46713 were committed on 2026-08-28, and in landing they moved the clock code past what D46723 was written against. The stack is not stalled; it is arriving out of order.
Refreshing D46723 is its author’s call. Publishing a forked rebase of someone’s in-review work is not a favour to them, so there is no rebase of it in this bundle. If you want a running board today, use the 14.x or 15.x lane.
main is 16.0-CURRENT, and 32-bit ARM is on notice in exactly that release:
sys/kern/init_main.c:397
"WARNING: 32-bit kernels are deprecated and may be removed in FreeBSD 16.0."armv7 is Tier 2. Anything not landed before the 16.0 branch goes with the architecture.
Upstream status
If you would rather wait for this to reach the tree than patch it yourself, these are the revisions to watch. Testing one and reporting the result on the review is the most useful thing you can add.
| Change | Where | Status |
|---|---|---|
| Remove clock_common | D46712 | Committed 2026-08-28 |
| AM335x clock names | D46713 | Committed 2026-08-28 |
| syscon for PRCM/PRM | D46714 | Accepted |
| AM335X kernel config | D46703 | Needs review |
| ti,clksel driver | D41888 | Needs review |
| simple-pm-bus | D46722 | Needs review |
| TI clock rework | D46723 | Needs review — blocks current |
| cpsw compatible | D46746 | Needs review |
| sys/intr.h include | D59118 | Needs review |
| softc member name | D59119 | Needs review |
| clksel register offset | D59120 | Needs review |
| EDMA3 split binding | D59124 | Needs review |
| SDHCI EDMA3 offload | D59125 | Needs review |
| geom_part access leak | PR 297777 | In triage |
| Evidence for the AM335x work | PR 297800 | Open |
Leave a Reply