Why this thing
The Ruckus H510 is an in-wall 802.11ac AP that shipped into MDU and
hospitality installs. It’s built around Qualcomm’s IPQ40xx SoC, has
512 MiB of RAM, 128 MiB of NAND, and a 4 MiB SPI NOR for the
boot chain. It’s also end-of-life: Commscope isn’t shipping firmware
updates anymore, and the stock Unleashed firmware is stuck on an
old-ish kernel with a proprietary userspace (v54_bsp) that most
operators have no reason to trust or maintain.
No Ruckus device exists upstream in target/linux/ipq40xx/ as of
2026-04-24, so this was a from-scratch port. Here’s how it went.
Phase 1: Serial recon
First step: get a console on the thing. The H510 has a populated
4-pin header on the board (TX=pin 1, GND=pin 4, RX=pin 5). FT232
at 115200 8N1. The SoC is an IPQ40xx family (Dakota) — the boot
log confirms AP-DK04.1-C1 reference design, quad-core Cortex-A7
[410fc075], 512 MiB RAM.
The U-Boot requires Ctrl-C (not any key) to interrupt the
4-second autoboot countdown. Timing a single keypress is a coin
flip. The technique that worked: spam Ctrl-C bytes (0x03) in a
tight loop for the whole PBL → SBL1 → U-Boot window (~15–25 seconds):
for i in $(seq 1 500); do
printf '\x03' > /dev/ttyUSB0 2>/dev/null
sleep 0.05
done
With a background cat /dev/ttyUSB0 > capture.log & reading serial,
this is fully automatable. The excess Ctrl-Cs just print
<INTERRUPT> harmlessly after the prompt appears.
A single boot log gives you almost everything: SoC family, RAM size, flash layout, PHY IDs, reserved memory regions, kernel version, and the board identifier strings. For locked-bootloader devices this is the cheapest, safest source of truth.
Phase 2: U-Boot recon (reads only)
Never nand write, never sf erase, never saveenv until you’ve
dumped everything read-only. Key finds from the U-Boot session:
The stock DTB is the single most valuable artifact. On any
OpenWrt-kernel-based OEM, the DTB is embedded in a FIT image inside
a UBI volume. Ruckus wraps the FIT with a 160-byte rcks_wlan
header, but U-Boot can crack it:
setenv mtdids nand0=nand0
setenv mtdparts mtdparts=nand0:48m(main),48m(bkup),32m(data)
ubi part bkup
ubi read 0x84100000 kernel
imxtract 0x841000a0 fdt@1 0x84500000
fdt addr 0x84500000
fdt print /soc
This gave us 8 LED GPIOs, the reset button, the TPM/I2C reset lines, and the switch port ↔ MDIO mapping in a single session.
Full SPI NOR backup via TFTP put — nuclear recovery fallback:
sf read 0x86000000 0 0x400000
tftpput 0x86000000 0x400000 h510-spinor-backup.bin
This covers SBL1, MIBIB, QSEE, CDT, DDRPARAMS, APPSBL (U-Boot), ART, Board Data — everything short of NAND contents.
Phase 3: DTS drafting
Every non-trivial claim in the DTS carries an inline comment citing the capture file and line where it was observed:
memory {
device_type = "memory";
/* 512 MiB confirmed: attempt-2-uboot.log "DRAM: 512 MiB" */
reg = <0x80000000 0x20000000>;
};
The nearest upstream reference for the IPQ4019 Dakota: Aruba Instant
On AP-303H (qcom-ipq4029-ap-303h.dts). It’s the closest
architectural match — enterprise AP, dual integrated radios, TPM,
NAND + SPI NOR. Meraki MR33 as a cross-check for locked-bootloader
workarounds on this SoC.
Dead ends
“The R500 is the template” — wrong. R500 is ath79 / QCA9557
(MIPS, 64 MiB SPI NOR, 256 MiB RAM). Completely different
architecture. The mistake was caught in the first 30 seconds of the
boot log — Format: Log Type - Time(microsec)... is a Qualcomm SBL1
signature, not Atheros.
“The R510 is upstream in ipq40xx” — also wrong. Verified via
git ls-files in the OpenWrt tree: zero *ruckus* DTS files
anywhere under target/linux/ipq40xx/dts/ as of 2026-04-24.
Lesson: grep the tree before claiming a device is upstream. Even for devices that “feel like they should be” supported.
Phase 4: Build + TFTP-boot loop
All build tooling runs inside a Debian bookworm Docker container matched to the host UID/GID. The WSL2 host stays clean, the build is reproducible, and subsequent DTS-only rebuilds after the first toolchain bootstrap are 2–5 minutes.
The initramfs FIT image (h510-initramfs-fit-uImage.itb) boots via:
tftpboot 0x84000000 h510-initramfs.itb
bootm 0x84000000
First boot revealed:
GPIO polarity was wrong. The stock Ruckus DTB declares all 8 LED GPIOs as
GPIO_ACTIVE_LOW. The H510’s actual wiring is active-high — physical HIGH lights the LED. Ruckus’s proprietaryv54_bspdriver silently inverts; the upstreamgpio-ledsdriver doesn’t. Symptom: every LED was lit because “off” (deasserted active-low = physical HIGH) was actually the on state. Diagnosed by toggling and observing, then flipping every flag toGPIO_ACTIVE_HIGH.MAC offset off-by-one. First DTS draft put the WAN MAC nvmem cell at offset
0x807f. Correct offset is0x807e. The failure mode was safe — reading 6 bytes from0x807fgave a multicast bit-set address, which the ethernet driver correctly rejected and fell back to random MAC. Caught because the interfaces came up with82:54:76:...instead of the sticker30:87:d9:1c:cd:40.DEVICE_DTS default derivation surprise. The
ipq40xximage Makefile derivesDEVICE_DTSfrom the device name via the last underscore-part. Forruckus_h510that producedh510, but our file was namedqcom-ipq4019-ruckus-h510.dts. Fix: explicitly setDEVICE_DTS := qcom-ipq4019-ruckus-h510in the device stanza.
Phase 5: NAND install + persistence
With the initramfs boot confirmed stable, the next step was flashing OpenWrt into NAND and making it autoboot from cold power-up.
The H510 uses an A/B boot scheme: rcks_wlan.main (slot A,
0x0–0x3000000) and rcks_wlan.bkup (slot B, 0x3000000–0x6000000)
with a UBIFS datafs in the remaining 32 MiB. We flash OpenWrt
into slot B and pin bootcmd to always boot it.
From U-Boot:
tftpboot 0x84000000 h510-factory.ubi
nand erase 0x3000000 0x3000000
nand write 0x84000000 0x3000000 <size>
The bootcmd environment needs two paths: one for slot A (stock
Ruckus, using bootlcl rcks_wlan.main — the Ruckus U-Boot’s
purpose-built command that handles TZ handoff) and one for slot B
(OpenWrt, using bootm). The naive bootm 0x840000a0 for slot A
crashes with external abort on non-linefetch because it skips the
TZ handoff that the stock kernel requires.
From the running OpenWrt system, fw_setenv sets the U-Boot
environment to always boot slot B. The AP now autoboots OpenWrt on
power-up. Slot A is untouched as a one-command fallback.
A notable gotcha: the 0:CDT (Configuration Data Table) partition
is exposed as a writable /dev/mtdN from the OEM kernel with no
write protections. A typo on the mtd number and the device is
hard-bricked before U-Boot even runs — SBL1 validates CDT by magic
- length, and a corrupted CDT means no bootloader handoff. Recovered on one unit via CH341A + SOIC-8 clip with the SPI NOR backup.
Rule going forward: never dd/cat/echo to any /dev/mtdN whose
partition has a 0: prefix except 0:APPSBLENV. Use
mtd_debug erase + mtd_debug write if you must, never the raw
block device.
Phase 6: WiFi
Both ath10k radios (2.4 GHz and 5 GHz, integrated into the
IPQ4019) come up, but the stock Board Data in the SPI NOR 0:ART
partition returns zeros via BMI (bmi-board-id). Ruckus’s stock
v54_bsp driver writes calibration via a side channel that the
upstream ath10k driver doesn’t replicate.
The workaround: custom board file generated via ath10k-bdencoder,
using the upstream bmi-board-id=16/17 reference-design entries from
the IPQ40xx DK04.1 reference board as payload. Each radio gets a
per-radio variant string (Ruckus-H510-2g / Ruckus-H510-5g)
to distinguish them in the board file lookup.
This is now packaged properly as ipq-wifi-ruckus_h510:
package/firmware/ipq-wifi/files/board-ruckus_h510.QCA4019 holds
the board file, and the Makefile calls
generate-ipq-wifi-package for it. No more files/ overlay
hack — it’s a proper OpenWrt package ready for upstreaming.
What works
- ✅ Boot from NAND (slot B), autoboot on power-up
- ✅ Ethernet: 4-port switch (IPQ40xx ESS + QCA8075 PHY), WAN port
- ✅ Both ath10k radios (2.4 + 5 GHz) with custom board-2.bin
- ✅ LEDs (power red/green, wifi, director) with correct polarity
- ✅ Reset button
- ✅ TPM (Infineon SLB9645 I2C,
tpm_tisdriver) - ✅ Slot A fallback: stock Ruckus firmware untouched in
rcks_wlan.main - ✅ MAC addresses from proprietary
Board DataSPI partition via nvmem
What’s next
The immediate work is upstreaming: DTS cleanup and the 02_network /
uboot-envtools additions. The ipq-wifi-ruckus_h510 package
is already in place. The H510 is EOL but the same librkscli.so
pattern and IPQ40xx reference design show up across the Ruckus
Unleashed 200.x line — the R510, R610, and likely others are
candidates for the same porting treatment.
The DTS, image recipe, and the ipq-wifi-ruckus_h510 board-file
package live on the
ruckus-h510 branch
of my OpenWrt fork
(target/linux/ipq40xx/dts/qcom-ipq4019-ruckus-h510.dts and the
matching image stanza).
Lessons learned
Ctrl-C flood is portable. The Ruckus U-Boot needs Ctrl-C specifically, not any-key. The spam technique should work on any Ruckus AP with a similar boot chain.
Template from architecture, not model number. Ruckus re-uses the “R/M/H/T + 3-digit” naming across totally different SoC families. The R500 is MIPS, the H510 is ARM — model-number similarity means nothing.
Vendor DTBs lie about polarity. They’re authoritative for pin numbers but not necessarily for polarity flags or labels. Always toggle and observe on real hardware.
bootlcl vs bootm matters on OEM U-Boots with TZ handoff.
The naive bootm approach skips the TZ setup that the stock kernel
expects. Use the OEM’s purpose-built boot command for slot A.
MTD_POWERUP_LOCK doesn’t mean locked. The stock SPI NOR driver
doesn’t implement the lock/unlock ioctl despite reporting the flag.
flash_erase and dd work directly — convenient for install,
dangerous for accidental writes.