TL;DR

While porting the Ruckus H510 to OpenWrt, I wrote 4 bytes to what I thought was the U-Boot environment partition (mtd8). It was actually 0:CDT — the Qualcomm Configuration Data Table. The device hard-bricked before U-Boot even loaded. Recovery required an external SPI programmer and a SOIC-8 clip.

The setup

The H510 (like most embedded Qualcomm boards) has a 4 MiB SPI NOR that holds the boot chain: SBL1, MIBIB, QSEE, CDT, DDRPARAMS, U-Boot, and the ART partition. Each is exposed as a /dev/mtdN block device from the running kernel — and on the stock Ruckus firmware, every one of them is writable.

I was diagnosing why fw_setenv-style writes to mtd10 (0:APPSBLENV) weren’t persisting on unit B. As a test, I ran a “surgical” 4-byte dd write to mtd8 to see if other SPI NOR partitions were writable.

mtd8 happened to be 0:CDT.

The mistake

# The command that bricked the device
dd if=/tmp/4bytes of=/dev/mtd8 bs=1 count=4

A bare dd to a NOR mtd device performs the write, but the underlying flash AND-merges the new bytes into the existing bits. Flash can flip 1-bits to 0-bits, but not vice versa. Even a “harmless” 4-byte write therefore mangles the partition’s first 32 bits.

For 0:CDT, which SBL1 validates by magic number + length, those first 32 bits are enough.

The crash

On the next power cycle, the board didn’t reach U-Boot. The serial console showed:

B - boot_config_data_table_init, Start
B - Error: CDT is not programmed
B - Boot error occurred!. Error code: 302a

SBL1 (Stage 1 Boot Loader) runs from mask ROM. It validates CDT before loading anything else. Invalid CDT = no U-Boot, no recovery shell, no second chances.

Why this is more dangerous than it looks

No kernel-side write protection. The Ruckus kernel exposes every SPI NOR partition (SBL1, MIBIB, QSEE, CDT, DDRPARAMS, APPSBL) as a writable /dev/mtdN. The JEDEC-standard block-protection bits on the MX25L3233F could be set to write-protect these partitions, but Ruckus ships them unset.

MTD_POWERUP_LOCK doesn’t help. The stock SPI NOR driver reports MTD_POWERUP_LOCK in the flags, but it doesn’t implement the lock/unlock ioctl. flash_unlock /dev/mtd10 returns ENOTSUP (error 95). Despite the flag suggesting otherwise, flash_erase and dd write successfully without any unlock step.

MTD numbering is unstable. mtd8 was 0:CDT on the stock kernel. On a different kernel build, or on OpenWrt, the numbering shifts:

PartitionStock Ruckus mtdOpenWrt mtd
0:APPSBLENVmtd10mtd8
0:CDTmtd8mtd6
Board Datamtd3mtd11

The one stable anchor is 0:APPSBLENV = mtd10 on both stock and OpenWrt (by partition name, not number).

Recovery

With SBL1 refusing to boot, the only path is external: CH341A programmer + SOIC-8 clip on the SPI NOR chip.

0:CDT contains DK04.1-C1 reference-design configuration — no per-unit data — so cross-unit copy is safe. Restore the 64 KiB at offset 0xc0000 from a backup of another H510 (or the SPI NOR backup you definitely made before writing to any flash partition).

0:APPSBLENV can be left blank; U-Boot falls back to compiled-in defaults.

Flash lock investigation

After the recovery, I investigated whether flash_unlock is needed before writing to SPI NOR or NAND from the stock firmware.

NAND (mtd0, mtd1, mtd2): flags = 0x400 (MTD_WRITEABLE). No powerup lock. ubidetach + ubiformat work without any unlock step.

SPI NOR (mtd8–mtd12): flags = 0xc00 (MTD_WRITEABLE | MTD_POWERUP_LOCK). flash_unlock returns ENOTSUP — the stock kernel’s SPI NOR driver doesn’t implement the lock/unlock ioctl. Despite the flag, flash_erase and dd write successfully without any unlock step.

Conclusion: flash_unlock is neither required nor functional in the no-UART install path. The install sequence is simply flash_erasedd.

Rules going forward

  1. 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.
  2. Verify mtd numbers against /proc/mtd before every write. The numbering can shift between kernel builds.

  3. Capture a full SPI NOR backup before any NOR write. dd if=/dev/mtd0ro of=/tmp/full-nor.bin bs=4096 from the OEM kernel gives you a 4 MiB recovery image you can flash back externally if things go wrong.

What this teaches

“Read-only” and “write-protected” are different things. The Ruckus kernel exposes boot-critical partitions as writable block devices with no enforcement. A typo on the mtd number and the device is hard-bricked before any bootloader can help you.

The CDT brick is a reminder that “no UART” doesn’t mean “no risk” — the OEM kernel gives you enough flash access to brick the device beyond its own bootloader’s reach.