Skip to content

Repository files navigation

Qualcommax NSS Builder

OpenWrt image builder for IPQ807x — NSS hardware offload on the upstream EDMA drivers

Build Lint License Last Commit

A GitHub Actions pipeline that builds an OpenWrt image for every IPQ807x device in the target — all 39 of them: Qualcomm NSS hardware offload running on OpenWrt main's upstream qca_edma / qca_ppe ethernet drivers (PR #22381) — not the vendor qca-nss-dp / qca-ssdk stack every other NSS build uses. Built from openwrt-nss-edma and nss-packages.

New to NSS offload? The wiki page NSS Offload Explained covers the concept from the ground up. Architecture, runtime model and measured results are in the full wiki.


Use it

Every release carries one sysupgrade image per device, named after its OpenWrt profile id. Grab ...-<your device>-squashfs-sysupgrade.bin from the newest edma-nss-* release and flash it:

sysupgrade -n /tmp/openwrt-qualcommax-ipq807x-xiaomi_ax3600-squashfs-sysupgrade.bin

Or via LuCI: System → Backup / Flash Firmware, upload, uncheck "Keep settings" for a first-time flash.

Each build also publishes an edma-nss-mesh-* release with the same images built for 802.11s mesh offload. Mesh interfaces are only accepted by NSS firmware 11.4.0.5, the last line that supports them, so that release carries 11.4 in place of the 12.5 firmware the default images ship. Take it if you run 802.11s; take edma-nss-* otherwise.

These are sysupgrade images only — there is no factory image. You must already be running OpenWrt on the device; if you are on stock vendor firmware, install stock OpenWrt first (your device's OpenWrt device page covers that), then flash this over it.

Devices built — all 39 in qualcommax/ipq807x

The split is the ath11k memory profile, which is compile-time and image-wide, so each group of boards that shares a profile shares a build.

Group RAM Devices
xiaomi_ax3600 512 MB Xiaomi AX3600 (own build: adds the board's wireless defaults and SQM template)
ipq807x-1g 1 GB+ Aliyun AP8220, Arcadyan AW1000, Asus RT-AX89X, Buffalo WXR-5950AX12, Dynalink DL-WRX36, Edgecore EAP102, Linksys HomeWRK, Linksys MX4200 v2, Linksys MX4300, Linksys MX5300, Linksys MX8500, Netgear RAX120v2, Netgear RBR750, Netgear RBS750, Netgear SXR80, Netgear SXS80, Netgear WAX620, Netgear WAX630, prpl Haze, QNAP 301w, Spectrum SAX1V1K, TCL LINKHUB HH500V, TP-Link Deco X80-5G, TP-Link EAP620 HD v1, TP-Link EAP660 HD v1, Xiaomi AX9000, Yuncore AX880, Zbtlink ZBT-Z800AX, Zyxel NBG7815, Zyxel NWA110AX, Zyxel NWA210AX
ipq807x-512m 512 MB CMCC RM2-6, Compex WPQ873, Edimax CAX1800, Linksys MX4200 v1, Redmi AX6, ZTE MF269
ipq807x-256m 256 MB Netgear WAX218

The AX3600 is the board every change is validated on; the rest carry the same data path and the same NSS device-tree nodes, and are built so a bug report starts from a known image instead of a hand-rolled config.

Runtime model: the nss service (/etc/init.d/nss) makes the data-path decision once, early in boot: it arms the NSS data plane, boots the firmware, and loads Wi-Fi with offload already selected — the radios come up directly on the wifili path, with no later rebind. Its /usr/sbin/nss-up instance then layers ECM (and SQM, once you have configured it) on top as the network comes up; output lands in the system log (logread -e nss). If arming or the Wi-Fi offload registration fails, the boot falls back to host-mode Wi-Fi and the host stack on its own. The universal recovery path is the uci flag: uci set nss.general.enabled='0'; uci commit nss (survives sysupgrade) — with it set, every boot is a stock host-only system.

Check plane health any time with nss-status over ssh, or in LuCI under Status → NSS Offload.

Wi-Fi ships disabled, like any OpenWrt image with no factory credentials — no image can carry a working password without publishing it here. Connect a cable, set an SSID and key in LuCI → Network → Wireless, and enable the radios. On the AX3600 the radio paths, band, channel and 802.11k/v options come preconfigured, so there is no first-boot detection race; the other images use OpenWrt's own first-boot radio detection.


What ships by default

The edma-nss image enables the full offload stack plus a hardened, batteries- included desktop-router config:

Area What's on
NSS data plane kmod-qca-nss-drv + the kmod-qca-ppe-nss glue
Connection offload ECM (kmod-qca-nss-ecm), PPPoE manager (kmod-qca-nss-drv-pppoe) — IPv4 NAT, IPv6 routing, PPPoE-over-VLAN
Bridge offload kmod-qca-nss-drv-bridge-mgr — wired LAN bridging in hardware
Multicast kmod-qca-mcs — same-subnet multicast hardware-bridged to snooped members
SQM NSS qdiscs (-qdisc/-igs) + sqm-scripts-nss (nss-edma.qos, DSCP fast lane both directions) + luci-app-sqm. Ships as a disabled template: set download/upload to ~90-95 % of your measured line rate and enable it (LuCI Network → SQM or uci) — there is no safe universal default rate
QoS marking nssqos + luci-app-nssqos — DSCP marking & fast-lane prioritization rules (CLI /etc/config/nssqos, LuCI Network → QoS Marking (NSS)), effective on accelerated flows; the applied class shows per flow in the DSCP column of Status → Realtime → Connections
Wi-Fi ath11k NSS offload (wifili) on both radios (CONFIG_ATH11K_NSS_SUPPORT); radio paths, band, channel and 802.11k/v preconfigured, interfaces ship disabled with no key — set SSID/key over the LAN port and enable them
Diagnostics nss-status CLI health report (now incl. fast-lane counters) + LuCI Status → NSS Offload page + per-station firmware Wi-Fi counters (/sys/kernel/debug/ieee80211/phy*/netdev:*/stations/<mac>/nss_stats: A-MSDU aggregation, MPDU retries)
Firmware/profile NSS.FW.12.5-210-HK.R; NSS memory profile matched to the board's RAM (HIGH / MEDIUM / LOW)
Security OpenSSH only (post-quantum KEX, AEAD/ETM, RSA ≥ 3072), PKG_* hardening (ASLR/PIE, stack protector, FORTIFY_3, RELRO, seccomp), WAN DROP + BCP38, HTTPS redirect, OQS provider in OpenSSL
Toolchain GCC 15 + Graphite, Binutils 2.46, Mold linker, LTO, -mcpu=cortex-a53+crc+crypto; ccache off
Userland LuCI (SSL), htop, iperf3, curl, BBR

Toolchain and package pins live in devices/common/config, shared by every image; devices/<group>/config adds only the device list and the memory profiles.

Enable the rest in your fork

These are build-verified and wired in code, but off by default because the reference network does not use them. Add the package to devices/common/config and rebuild:

Feature Add to config Notes
Routed L3 multicast (IPTV WAN→LAN) CONFIG_PACKAGE_igmpproxy=y (or smcroute) ECM offloads each kernel MFC entry to the PPE; needs a real WAN multicast source and a two-VIF topology. See docs/CUSTOMIZE.md.
MAP-T / 464XLAT CONFIG_PACKAGE_kmod-nat46=y nat46 headers + QCA MAP-T exports are staged in the tree.
VXLAN CONFIG_PACKAGE_kmod-vxlan=y fdb/age-update offload via kernel patch 0972.
MACVLAN CONFIG_PACKAGE_kmod-macvlan=y ECM support via kernel patch 0962.
GRE CONFIG_PACKAGE_kmod-gre=y ECM GRE support builds.

Not available on this platform/firmware: IPsec (ESP) offload, TLS/DTLS, and CoDel ECN marking. (Wi-Fi mesh offload works on an 11.4-firmware build — NSS_FIRMWARE_VERSION_11_4 + ATH11K_NSS_MESH_SUPPORT; only the default 12.5 firmware blocks it.) See the Limitations page.


Measured results

AX3600 (IPQ8071A, 512 MB), NSS.FW.12.5-210, kernel 6.18 — details in the wiki:

Metric Host path NSS offload
311 Mbit/s PPPoE NAT ~42 % of one core (softirq) ~99.7 % CPU idle
SQM at 285 Mbit ingress CPU-bound 258 Mbit goodput, ~99 % idle
RTT under shaped load bufferbloat 16 ms avg vs 20 idle — flat
Wi-Fi data path mac80211/ath11k on the CPU wifili on the NSS cores

Build it yourself

Everything is parameterized in the env: block of .github/workflows/build.yml — fork the repo, edit, and the pipeline builds on push. Or build locally:

git clone --branch nss-edma-rework https://github.com/JuliusBairaktaris/openwrt-nss-edma openwrt
cd openwrt
cp feeds.conf.default feeds.conf
echo "src-git nss https://github.com/JuliusBairaktaris/nss-packages.git;edma-nss" >> feeds.conf
./scripts/feeds update -a && ./scripts/feeds install -a
B=../Qualcommax_NSS_Builder/devices
cat "$B/common/config" "$B/xiaomi_ax3600/config" > .config   # or any devices/<group>
make defconfig && make -j"$(nproc)"

Important

Updating an existing checkout: both branches (nss-edma-rework and the edma-nss feed) are periodically rebased — fixes are folded into the commits that own them, so history gets rewritten. A plain git pull will fail or produce a broken merge, and ./scripts/feeds update (which runs git pull --ff-only inside feeds/nss) fails quietly and leaves the feed stale — a common source of build errors that don't reproduce upstream. Update like this instead:

git fetch origin && git reset --hard origin/nss-edma-rework
rm -rf feeds/nss package/feeds/nss
./scripts/feeds update -a && ./scripts/feeds install -a
make defconfig

Before reporting a build error, verify you are current: git log --oneline -1 in the tree and in feeds/nss must match the tips of nss-edma-rework and edma-nss.

The NSS runtime tools (nss-up, nss-status, the nss boot service, the QoS marking CLI/UI) ship as regular packages from the openwrt fork (nss-tools, nssqos, luci-app-nss, luci-app-nssqos) — plain fork checkouts get them by selecting the packages, no builder needed. The few remaining overlay files (SSH config and QoL defaults in devices/common/files*/, the AX3600's wireless defaults and SQM template in devices/xiaomi_ax3600/files*/) are copied into the image with a files/ directory or the builder pipeline. See docs/CUSTOMIZE.md for the full customization guide and docs/ARCHITECTURE.md for how the pipeline works.

Repo layout

devices/common/          # shared by every image
  config                 # the .config bulk (toolchain, hardening, NSS packages)
  files/                 # base rootfs overlay (sshd_config, QoL uci-defaults)
  files.edma-nss/        # edma-nss overlay (rc.local)
devices/xiaomi_ax3600/   # one directory per build: config + optional overlays
devices/ipq807x-{1g,512m,256m}/
scripts/                 # check-updates, prepare-build, prune-releases (tested, linted)
docs/                    # CUSTOMIZE.md, ARCHITECTURE.md
.github/workflows/       # build.yml (check → release → build → publish → prune), lint.yml

Each devices/<name>/config is concatenated onto devices/common/config and resolved with make defconfig, which is verified to have kept every requested symbol — a silently reduced image fails the build instead of shipping.

The pipeline runs check → release → build → publish → prune: check resolves the upstream/NSS ref to a SHA and skips a scheduled build when nothing changed; release opens a draft release; build runs once per device group in parallel, applying the config + overlays, compiling, and uploading its images into that draft; publish makes it public once every group succeeded, and discards it otherwise, so a release is never missing devices; prune keeps the newest KEEP releases. Builds are uncached (fresh runner, reproducible SOURCE_DATE_EPOCH) and the pipeline is linted (actionlint, shellcheck, yamllint) on every PR.


Contributing

Issues and PRs welcome — see CONTRIBUTING.md.

Acknowledgements

Support the project

This is an unpaid, single-maintainer effort. If this work is useful to you, consider chipping in — it goes toward IPQ807x development and hardware to start looking into IPQ50xx and IPQ60xx next.

Thank you!

License

GPL-2.0, consistent with OpenWrt.

Releases

Packages

Used by

Contributors

Languages