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

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.x15.xcurrent (16.0)
Boots to multi-user on hardwareYesYesNo — see below
buildkernel KERNCONF=AM335XYesYesYes
Platform prerequisite appliesYesYesPartly — config only
Driver layers apply and compileYesYesYes
Device attach failures00untested
Reboot soakuntested6 clean cyclesuntested
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 8N1

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

LaneBundleSize
14.xfreebsd-bbb-14-patches.tgz72 KB
15.xfreebsd-bbb-15-patches.tgz90 KB
currentfreebsd-bbb-current-patches.tgz48 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-patches

Checksums, 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) = 22da4dafa2802f11e7224215193b737fd4ae77bd0457d5969e329f6b4d0bc7ef

Licence: 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-04

Those 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-specific

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

stable15-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    # optional

Three 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 with random(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=1

Bonus: 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=AM335X

buildworld 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 1

Nothing 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_ROOT

Write 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"
EOF

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

Build 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 $FATDIR

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

The 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 -T or -H to mkimg. CHS geometry makes it round the partition size up to a track boundary (102400 → 102438 sectors) and that breaks the rule.
  • sectors_per_cluster=8 is load-bearing. With 512-byte clusters, makefs caps 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_descriptor must be decimal 240; makefs refuses 0xf0 as “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 disk

Set 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=progress

Then 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 -echo

Insert 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 RNG

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

Name 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:

Repositorylatestquarterly
FreeBSD:14:armv72025-09-232025-09-03
FreeBSD:15:armv72025-09-26absent
FreeBSD:16:armv7absentabsent
FreeBSD:15:aarch642026-09-102026-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 further

Three 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 -f

Not 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 linked

So 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 && reboot

Use 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 -y in 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.

ChangeWhereStatus
Remove clock_commonD46712Committed 2026-08-28
AM335x clock namesD46713Committed 2026-08-28
syscon for PRCM/PRMD46714Accepted
AM335X kernel configD46703Needs review
ti,clksel driverD41888Needs review
simple-pm-busD46722Needs review
TI clock reworkD46723Needs review — blocks current
cpsw compatibleD46746Needs review
sys/intr.h includeD59118Needs review
softc member nameD59119Needs review
clksel register offsetD59120Needs review
EDMA3 split bindingD59124Needs review
SDHCI EDMA3 offloadD59125Needs review
geom_part access leakPR 297777In triage
Evidence for the AM335x workPR 297800Open