MT7921/WiFi 6E USB adapter working on BalenaOS with PREEMPT_RT kernel (5.10.x)

The Problem

I needed WiFi 6E connectivity on a Revolution Pi Connect S running BalenaOS 3.0.8+rev2 (kernel 5.10.152-rt75-v8) using an EDUP AX3000M USB adapter (MediaTek MT7961, USB ID 0e8d:7961).

Two problems stood in the way:

  1. The mt7921u driver was only merged into Linux 5.12 — it simply doesn’t exist in the 5.10 kernel that BalenaOS ships.
  2. The mt76 driver has a PREEMPT_RT bug — even on newer kernels that include the driver, it crashes with BUG: scheduling while atomic on any RT kernel. This is a known issue in the mt76 worker thread (__mt76_worker_fn in util.c).

Since BalenaOS uses a CONFIG_PREEMPT_RT kernel, both problems hit at once.

The Solution

I created a reproducible Docker-based build system that:

  • Cross-compiles the mt76/mt7921u driver from the OpenWrt mt76 project for aarch64
  • Applies 11 compatibility patches to backport the driver from kernel 5.18 APIs down to 5.10
  • Includes a critical PREEMPT_RT fix that replaces the unsafe set_current_state() + schedule() pattern with RT-safe usleep_range()
  • Handles CONFIG_MODVERSIONS CRC injection via direct ELF binary patching (needed because BalenaOS enables CONFIG_MODVERSIONS)

The result: 5 kernel modules that load cleanly on the stock BalenaOS kernel with zero RT scheduling bugs.

What’s working

  • WiFi 6E on both 2.4 GHz and 5 GHz bands
  • Automatic module loading on boot via systemd service
  • Firmware loading from the persistent data partition (/mnt/data/)
  • NetworkManager integration (connect via nmcli)
  • Zero scheduling while atomic errors

BalenaOS-specific challenges & solutions

BalenaOS has a read-only root filesystem, which makes persistent kernel module loading tricky. Here’s what I learned:

What didn’t work:

  • /mnt/state/root-overlay/etc/modprobe.d/ — BalenaOS doesn’t apply root-overlay for modprobe.d
  • /etc/udev/rules.d/ on mmcblk0p5 — BalenaOS clears custom udev rules on every boot

What works:

  • mount -o remount,rw / + systemd service — survives reboots, but not BalenaOS OTA updates
  • Kernel cmdline firmware_class.path= in /mnt/boot/cmdline.txt — persistent, survives everything

The final setup uses:

  • Modules + firmware on /mnt/data/mt76-modules/ (persistent across updates)
  • firmware_class.path in /mnt/boot/cmdline.txt (persistent on boot partition)
  • Systemd service installed via mount -o remount,rw / (needs reinstall after OTA)

Get it

Everything is open source:

GitHub: Spunky84/mt76-rt-backport

The repo includes:

  • Reproducible Dockerfile build
  • All 11 patches with detailed descriptions
  • Step-by-step device setup guide
  • The PREEMPT_RT fix as a standalone patch (useful for any mt76 driver on any RT kernel)

Who this helps

  • Anyone running BalenaOS on older kernels (5.10.x, 5.11.x) who needs MT7921/MT7961 WiFi
  • Anyone running any mt76-based driver on a PREEMPT_RT kernel (the RT fix applies to mt7915, mt7603, etc. too)
  • Anyone struggling with CONFIG_MODVERSIONS CRC mismatches when loading custom kernel modules on BalenaOS

Hardware tested

  • Device: RevPi Connect S (BCM2711)
  • OS: BalenaOS 3.0.8+rev2
  • Kernel: 5.10.152-rt75-v8
  • Adapter: EDUP AX3000M (MT7961)
  • Status: Working

Happy to answer questions or help adapt this for other kernel versions.