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
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.
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.
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.
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.
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.
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.
make modules_install will hang. Always reboot to
a clean state before building.
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.
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'sNRC_DRIVER_NAMEexactly. The original Newracom DTS uses"newracom,nrc7292"which generates modaliasspi:nrc7292and never auto-binds to the driver. - Disables
spidev@0on CS0 —dtparam=spi=oncreates a conflicting spidev device on the same chip select. The installer removes that line fromconfig.txt. - Sets the udev rule to
driver_override=nrc80211only — noRUN+=modprobe. See Gotcha #2 below.
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.
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.
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.
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.
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.
| File | Change | Why |
|---|---|---|
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_ops → genl_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.
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 [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:
- udev fires
RUN+=modprobe nrcon SPI device add - Inside
nrc probe(), the driver callsrequest_firmware()to downloadnrc7292_cspi.bin request_firmware()needs udev to deliver the firmware file- udev is blocked running the
modprobeRUN directive — deadlock - Firmware never downloads silently. Board data never loads.
ip link set wlan1 upreturns 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_config → nrc_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.
| Layer | Status | Notes |
|---|---|---|
| SPI device | WORKING | spi0.0 visible, modalias spi:nrc7292 |
| DT overlay | WORKING | dtoverlay=nrc-rpi in config.txt |
| Module build | WORKING | Compiles clean on kernel 6.12.47 |
| SPI probe / chip detect | WORKING | chip_id:7292 confirmed |
| Firmware load | WORKING | Chip boots from own SPI flash |
| Board data | WORKING | Loads in 2–13 min (variable) |
| wlan1 creation | WORKING | Appears within seconds of boot |
| ip link set wlan1 up | WORKING | Works after board data loads |
| iw dev wlan1 scan | WORKING | Exit 0, no crash |
| HaLow link test | PENDING | Needs 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 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
wlan1 is usable is unacceptable for a
tactical device. Root cause is in the WIM exchange loop in the driver. Needs investigation.
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.