HaLow 802.11ah Driver Setup

NRC7292 HaLow Driver — Raspberry Pi 5 Setup Guide

Complete setup guide for the ALFA AHPI7292S HaLow HAT (Newracom NRC7292 chip, SPI interface) on Raspberry Pi 5 running Pi OS Bookworm 64-bit (Desktop or Lite). Covers the one-shot installer, every kernel 6.12 patch applied, all gotchas discovered during bring-up, and the correct boot procedure to avoid a hardware-specific timing trap that bricks the interface until reboot.

Chip NRC7292 ALFA AHPI7292S HAT, SPI
Target OS Pi OS Bookworm 64-bit, kernel 6.12.47
Driver source v1.5.2 newracom/nrc7292_sw_pkg
Interface wlan1 UP, scan working as of 2025-04-13

Quick Start

Three commands on a fresh Pi 5. The installer is idempotent — safe to re-run. After it finishes, reboot and follow the bring-up procedure below. Do not skip the board data wait — see Gotchas.

curl -o ~/nrc7292_setup.sh https://jaysoncraig.ca/sandbox/nrc7292_setup.sh
bash ~/nrc7292_setup.sh
sudo reboot
Bookworm Lite works fine. This guide and the installer are fully compatible with Pi OS Bookworm 64-bit Lite (headless/no GUI). Nothing requires a desktop environment.

The installer script is available at: nrc7292_setup.sh — review it before running if you prefer.

What the Installer Does

The script runs 11 steps in order. Each step is idempotent — it checks before modifying, so re-running after a partial failure is safe.

1

Enable Persistent Journal

By default, Pi OS uses Storage=auto for the systemd journal — it only writes to disk if /var/log/journal exists at boot. A kernel oops or hard hang can leave that directory unflushed, so crash logs are lost on reboot. The installer explicitly sets Storage=persistent and SyncIntervalSec=5s so kernel panics are captured to disk within 5 seconds.

Why this matters: The NRC7292 driver caused several kernel oopses during bring-up. Without persistent logging we were flying blind after every hard reboot.
2

Install Build Dependencies

Installs build-essential, raspberrypi-kernel-headers, and device-tree-compiler via apt. The kernel headers are essential — without them the module cannot be compiled against the running kernel.

3

Clone the NRC7292 Package

Clones newracom/nrc7292_sw_pkg v1.5.2 to ~/nrc7292_sw_pkg. If the directory already exists it is skipped. The source files are then reset to a clean git state with git checkout package/src/nrc/ before patching — this makes the script safe to re-run after a partial patch failure.

4

Apply Kernel 6.12 Patches

The driver was written for older kernels. Kernel 6.12 has breaking API changes. See the Kernel Patches section for full detail on every change. The most critical patch is the nrc_mac_config NULL guard — without it every attempt to bring up wlan1 causes a kernel oops, a segfault in ip, and a system hang.

5

Build the Kernel Module

Runs make clean && make in the driver source directory. The resulting nrc.ko is a kernel module for the running kernel version. Build takes 2–4 minutes on Pi 5.

Do not interrupt the build. If the previous session left a kernel oops with the RTNL lock held, make modules_install will hang. Always reboot to a clean state before building.
6

Install the Module

Runs make modules_install and depmod -a. The module is installed to /lib/modules/$(uname -r)/updates/nrc.ko.xz. The xz compression step can take 1–3 minutes — this is normal. A System.map warning from the Makefile's internal depmod is harmless; the subsequent depmod -a handles it correctly.

7

Device Tree Overlay + udev Rule

Generates and installs nrc-rpi.dtbo for the Pi 5 (BCM2712). Key details:

  • Uses compatible = "nrc80211" — must match the driver's NRC_DRIVER_NAME exactly. The original Newracom DTS uses "newracom,nrc7292" which generates modalias spi:nrc7292 and never auto-binds to the driver.
  • Disables spidev@0 on CS0 — dtparam=spi=on creates a conflicting spidev device on the same chip select. The installer removes that line from config.txt.
  • Sets the udev rule to driver_override=nrc80211 only — no RUN+=modprobe. See Gotcha #2 below.
8

Install Firmware and Board Data

Copies nrc7292_cspi.bin (342 KB) and bd.dat (2 KB) from the cloned package to /lib/firmware/. The firmware is loaded into the chip by the driver on cold boot. The board data configures RF parameters and must be confirmed as loaded before bringing up the interface.

9

Configure modprobe + NetworkManager

Writes /etc/modprobe.d/nrc.conf with polling mode parameters:

options nrc fw_name=nrc7292_cspi.bin spi_gpio_irq=-1 spi_polling_interval=1

spi_gpio_irq=-1 disables hardware interrupt mode. On Pi 5, the GPIO IRQ line never fires with this HAT. Polling every 1 ms is the only reliable mode.

Also writes /etc/NetworkManager/conf.d/99-nrc7292.conf to prevent NetworkManager from managing wlan1. See Gotcha #3 below.

10

Fix start.py Paths

The Newracom start.py script hardcodes /home/pi/nrc_pkg and uses insmod with an absolute path to a non-installed binary. The installer creates a symlink from /home/pi/nrc_pkg to the actual package location and patches the insmod call to use modprobe nrc.

Note: start.py is not suitable for production use. It hardcodes wlan0 internally regardless of CLI arguments and has several other assumptions that don't hold. A custom bring-up script is the correct path for production use.
11

Install the systemd Service

Creates and enables /etc/systemd/system/nrc7292.service:

[Unit]
Description=NRC7292 HaLow driver
After=systemd-udev-settle.service

[Service]
Type=oneshot
ExecStart=/sbin/modprobe nrc
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

The After=systemd-udev-settle.service dependency is critical. See Gotcha #2 for the full explanation. This service is what automatically loads the driver on every boot, causing wlan1 to appear within seconds.

Kernel 6.12 Patches

The NRC7292 driver was written for kernels 5.x–6.1. Kernel 6.12 broke it in multiple ways. Every change below is applied by the installer automatically.

FileChangeWhy
nrc-build-config.h Disable CONFIG_WIRELESS_WDS WDS was removed from kernel 6.12. Build fails if this is defined.
nrc-build-config.h Fix CONFIG_USE_CHANNEL_CONTEXT guard Was gated behind a WDS || condition — disabling WDS accidentally disabled channel context too.
nrc-hif-cspi.c <asm/unaligned.h><linux/unaligned.h> Header moved in 6.12. Build error without this.
nrc-hif-cspi.c, nrc-mac80211.h, nrc-mac80211.c nrc_mac_stop(hw)nrc_mac_stop(hw, false) ieee80211_ops.stop gained a bool offchannel parameter in 6.12.
nrc-mac80211.c Add .tx = nrc_mac_tx to nrc_mac80211_ops ieee80211_alloc_hw_nm in 6.12.47 WARN_ONs and returns NULL if any of 7 mandatory ieee80211_ops fields are NULL. .tx at offset 0 was missing — wlan1 never appeared.
nrc-mac80211.c Add link_conf param to chanctx callbacks assign_vif_chanctx and unassign_vif_chanctx gained a struct ieee80211_bss_conf *link_conf parameter in 6.12.
nrc-mac80211.c Set g_bd_valid = true in board data success callback nrc_mac_start returns -EPERM if g_bd_valid is false. The variable is never set to true without this patch — the interface never comes up.
nrc-mac80211.c nrc_mac_config NULL guard for chandef.chan During ieee80211_do_open → ieee80211_hw_conf_init → drv_config, hw→conf.chandef.chan is NULL (no channel configured yet). The function unconditionally does memcpy(&ch, hw→conf.chandef.chan, …) — kernel oops, ip segfault, system hangs. Fixed by returning 0 early if chan is NULL.
nrc-hif.h Add correct 5-param nrc_hif_cspi_read_credit prototype Used by nrc-debug.c via extern. Wrong or missing prototype causes build error.
nrc-netlink.c genl_opsgenl_split_ops for pre/post_doit callbacks Kernel 6.1+ changed the callback signature in generic netlink ops.
Multiple files Add static (and __maybe_unused) to non-static functions 6.12 treats non-static functions with no external declaration as errors in out-of-tree modules.
wim.c ieee80211_csa_finish(vif)ieee80211_csa_finish(vif, 0) Gained a link_id parameter in 6.12.

Boot Procedure

After a successful install and reboot, the driver loads automatically via the systemd service. wlan1 appears within a few seconds. However, the interface cannot be brought up until board data has loaded — a process that takes between 2 and 13 minutes and is non-deterministic.

Critical: Running sudo ip link set wlan1 up before board data loads causes nrc_mac_start to return EPERM. mac80211 records this failure. All subsequent ip link set wlan1 up attempts will also return EPERM — even after board data loads — without calling the driver at all. You must reload the module to recover.

Recommended bring-up command

Use this one-liner instead of manual timing. It blocks until board data appears, then brings up the interface:

echo "Waiting for board data..."; \
until dmesg | grep -q "succeed in loading board data on target"; do sleep 5; done; \
echo "Board data loaded!"; \
sudo iw reg set US && sudo ip link set wlan1 up && iw dev wlan1 info

If you already hit EPERM early

Reload the module to clear the stuck mac80211 state, then re-run the bring-up command:

sudo modprobe -r nrc && sudo modprobe nrc

Verifying board data in dmesg

watch -n5 'dmesg | grep -i "board data"'

Wait for: nrc80211 spi0.0: succeed in loading board data on target

What a healthy boot looks like in dmesg

[  2.8s]  nrc: loading out-of-tree module taints kernel.
[  2.8s]  nrc80211 spi0.0: SPI probing. chip_id:7292 modem_id:00000001 status:1
[  3.6s]  nrc80211 spi0.0: spi_poll_thread: gpio=-1 interval=1
[  3.6s]  nrc80211 spi0.0: [ERR] Invalid country code(CA). Set default value(1)
[  3.6s]  Succeed to register spi driver(nrc80211).
[ ~163s]  nrc80211 spi0.0: succeed in loading board data on target  ← WAIT FOR THIS
Invalid country code in dmesg is normal and harmless. The [ERR] Invalid country code(CA) line appears because the driver reads the system locale at probe time — before you run iw reg set. The chip falls back to its internal default channel plan (value 1). This does not prevent the interface from coming up. Set the regulatory domain with sudo iw reg set US (or your country code) as part of the bring-up sequence after board data loads — that is when it takes effect. Only the two-letter ISO codes the driver recognises are valid; anything else produces this same message at probe and is silently ignored.

Gotchas & Workarounds

Gotcha 1 — Board data takes 2–13 minutes (non-deterministic)

After the SPI poll thread starts, the chip firmware exchanges WIM messages with the driver to load board data from /lib/firmware/bd.dat. This exchange is asynchronous and the timing is highly variable — observed as fast as 163 seconds and as slow as 775 seconds across multiple boots with no obvious cause. Never assume a fixed wait time. Always poll dmesg for the confirmation message.

Gotcha 2 — udev RUN+=modprobe causes a silent firmware deadlock

An earlier version of this setup used a udev rule with RUN+="/sbin/modprobe nrc" to auto-load the driver when the SPI device appeared. This caused a deadlock:

  1. udev fires RUN+=modprobe nrc on SPI device add
  2. Inside nrc probe(), the driver calls request_firmware() to download nrc7292_cspi.bin
  3. request_firmware() needs udev to deliver the firmware file
  4. udev is blocked running the modprobe RUN directive — deadlock
  5. Firmware never downloads silently. Board data never loads. ip link set wlan1 up returns EPERM forever.

Fix: the systemd service with After=systemd-udev-settle.service runs modprobe after udev is completely idle. The udev rule only sets driver_override=nrc80211 and does nothing else.

Gotcha 3 — NetworkManager brings up wlan1 before board data loads

NetworkManager auto-probes new wireless interfaces. When wlan1 appears (within seconds of boot), NM tries to bring it up — triggering nrc_mac_start before board data loads. This sets the mac80211 failure state described above. The installer adds wlan1 to NM's unmanaged list:

# /etc/NetworkManager/conf.d/99-nrc7292.conf
[keyfile]
unmanaged-devices=interface-name:wlan1

Gotcha 4 — ip link set wlan1 up causes kernel oops (nrc_mac_config NULL deref)

Even after nrc_mac_start passes the g_bd_valid check and returns 0, mac80211 immediately calls drv_confignrc_mac_config via ieee80211_hw_conf_init. At this point hw→conf.chandef.chan is NULL because no channel has been configured. The driver function unconditionally does memcpy(&ch, hw→conf.chandef.chan, …) — this dereferences NULL, causes a kernel oops, kills the ip process with SIGSEGV, and leaves the RTNL lock held by the dead process. Every subsequent iw dev or ip call hangs the system.

Fix: add a NULL guard at the top of nrc_mac_config, before the memcpy:

/* chandef.chan is NULL during ieee80211_hw_conf_init on first ip link set up.
 * The memcpy below dereferences it unconditionally -- return 0 until valid. */
if (!hw->conf.chandef.chan)
    return 0;

This is applied by the installer as part of Step 4. The patch script is also available standalone at patch_nrc_config.py.

Gotcha 5 — dtparam=spi=on conflicts with the HAT

If dtparam=spi=on is present in /boot/firmware/config.txt, the kernel creates a spidev device on SPI0 CS0 — the same chip select used by the HAT. The two devices fight over CS0 and the HAT never probes. The installer removes dtparam=spi=on automatically.

Gotcha 6 — DT compatible string mismatch (spi:nrc7292 vs nrc80211)

The original Newracom device tree uses compatible = "newracom,nrc7292", which generates the modalias spi:nrc7292. The driver's spi_device_id table registers as "nrc80211" (the value of NRC_DRIVER_NAME). These don't match, so the SPI device never binds to the driver automatically. The installer's overlay uses compatible = "nrc80211" and the udev rule sets driver_override=nrc80211 as a belt-and-suspenders fallback.

Gotcha 7 — Persistent journal not surviving hard reboots

Creating /var/log/journal and restarting journald works in the current boot, but a kernel hang + hard power cycle can leave that directory unflushed — it may not exist on the next boot, reverting to volatile storage. The installer explicitly sets Storage=persistent in /etc/systemd/journald.conf so logging is persistent unconditionally, regardless of directory state.

Gotcha 8 — .tx missing from ieee80211_ops (wlan1 never appears)

In kernel 6.12.47, ieee80211_alloc_hw_nm checks 7 mandatory ieee80211_ops fields via cbz instructions and WARN_ONs / returns NULL if any are absent. The .tx field at offset 0 was missing from nrc_mac80211_ops in the v1.5.2 source (a prior development version had removed it). Without this, ieee80211_register_hw fails silently and wlan1 never appears. The fix is to add .tx = nrc_mac_tx as the first entry in the ops struct.

Verifying It Works

1. Module loaded and chip detected

dmesg | grep nrc | head -10
# Expect: "SPI probing. chip_id:7292 modem_id:00000001 status:1"

2. wlan1 interface present

iw dev
# Expect: Interface wlan1 under phy#nrc80211

3. Board data loaded

dmesg | grep -i "board data"
# Expect: "succeed in loading board data on target"
# Note: this takes 2-13 minutes after boot

4. Interface UP

ip link show wlan1
# Expect: <NO-CARRIER,BROADCAST,MULTICAST,UP>
# NO-CARRIER is normal — no AP associated yet

5. Scan works

sudo iw dev wlan1 scan
echo $?
# Expect: exit code 0
# No output = no HaLow APs in range (normal with one device)

6. Phy capabilities

iw phy nrc80211 info | head -30
# Shows supported modes: managed, AP, monitor, mesh point
# Note: shows 2.4GHz/5GHz bands — this is normal. CONFIG_S1G_CHANNEL
# is disabled by default. The chip always operates at sub-1GHz regardless.
LayerStatusNotes
SPI deviceWORKINGspi0.0 visible, modalias spi:nrc7292
DT overlayWORKINGdtoverlay=nrc-rpi in config.txt
Module buildWORKINGCompiles clean on kernel 6.12.47
SPI probe / chip detectWORKINGchip_id:7292 confirmed
Firmware loadWORKINGChip boots from own SPI flash
Board dataWORKINGLoads in 2–13 min (variable)
wlan1 creationWORKINGAppears within seconds of boot
ip link set wlan1 upWORKINGWorks after board data loads
iw dev wlan1 scanWORKINGExit 0, no crash
HaLow link testPENDINGNeeds second NRC7292 device

Troubleshooting

wlan1 never appears after boot

Check if the systemd service ran:

sudo systemctl status nrc7292.service
journalctl -u nrc7292.service

Check if the SPI device is present:

ls /sys/bus/spi/devices/
# Expect: spi0.0

Check dmesg for probe messages:

dmesg | grep nrc

ip link set wlan1 up returns EPERM

Two possible causes:

  • Board data not loaded yet — check dmesg | grep -i "board data" and wait.
  • Previous early attempt poisoned mac80211 state — reload the module: sudo modprobe -r nrc && sudo modprobe nrc, then wait for board data again.

ip link set wlan1 up causes a segfault and system hangs

This means the nrc_mac_config NULL guard patch is not in the installed module. The installer applies it in Step 4, but if you rebuilt the source after a git checkout reset, the patch needs to be re-applied. Use the standalone patch script:

cd ~/nrc7292_sw_pkg/package/src/nrc
curl -O https://jaysoncraig.ca/sandbox/patch_nrc_config.py
python3 patch_nrc_config.py
make clean && make && sudo make modules_install && sudo depmod -a
After a kernel oops hang: always reboot to a clean state before running make modules_install. The oops leaves the RTNL lock held — any subsequent kernel operation that needs it will hang the system for minutes or indefinitely.

No kernel crash logs after a hang

The persistent journal configuration may have been lost. Re-apply it:

sudo mkdir -p /var/log/journal
sudo chown root:systemd-journal /var/log/journal
sudo chmod 2755 /var/log/journal
sudo sed -i 's/#Storage=auto/Storage=persistent/' /etc/systemd/journald.conf
sudo systemctl restart systemd-journald

After the next crash, read the previous boot log:

journalctl -b -1 -k | grep -iE 'oops|BUG|null|nrc|call trace' | head -80

Build hangs at modules_install / depmod

The system is in a bad kernel state from a previous oops. Hard reboot, then retry. Do not use Ctrl-C — if it resumes, let it finish. XZ compression of the module can take up to 4 minutes on Pi 5.

Board data never loads

Check that the firmware files exist:

ls -la /lib/firmware/bd.dat /lib/firmware/nrc7292_cspi.bin

Check for any errors after the poll thread starts:

dmesg | grep -i nrc | tail -30

If no errors and files are present, just wait longer. Board data has been observed loading as late as 775 seconds (nearly 13 minutes) after modprobe with no apparent cause.

Next Steps

Board data delay The 2–13 minute wait before wlan1 is usable is unacceptable for a tactical device. Root cause is in the WIM exchange loop in the driver. Needs investigation.
Second NRC7292 device Actual HaLow link test (AP + STA), PTT audio path, and throughput measurement require two devices. One HAT is not enough to validate the radio layer.
Custom bring-up script Replace start.py (hardcodes wlan0, broken insmod path) with a custom service that waits for board data, sets the regulatory domain, and brings up wlan1 in the correct mode.
CONFIG_S1G_CHANNEL Currently disabled. May need enabling for correct sub-1GHz channel selection when operating as AP or STA. The chip always operates at HaLow frequencies regardless — this flag only affects how channels are reported to mac80211.