TL;DR

While porting the Ruckus H510 to OpenWrt I wrote four bytes to mtd8, which I was fairly sure was the U-Boot environment partition. It was 0:CDT, the Qualcomm Configuration Data Table, which SBL1 validates before it loads anything else, so the board stopped booting before U-Boot ever ran. Getting it back took a CH341A 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 one 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 trying to work out why fw_setenv-style writes to mtd10 (0:APPSBLENV) were not persisting on unit B, and the next thing I wanted to know was whether any other SPI NOR partition would accept a write at all. Four bytes seemed like the smallest question I could ask the hardware, and the smallest question that could possibly go wrong.

mtd8 was 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 does perform the write, but with no erase first the flash can only clear bits and never set them, so what actually lands is the AND of the new bytes with whatever was already sitting there. Four bytes is therefore enough to mangle the first 32 bits of the partition, and since SBL1 validates 0:CDT by magic number and length, the first 32 bits are exactly the ones that have to survive.

The crash

On the next power cycle the board never reached U-Boot. The serial console had this and nothing after it:

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

SBL1 runs out of mask ROM and validates the CDT before it loads any later stage, so there was no U-Boot prompt to interrupt, no recovery shell to drop into, and no amount of holding the reset pinhole that was going to change that. The failure happens upstream of everything that would normally save you.

Why this is more dangerous than it looks

There is 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, but Ruckus ships them unset.

MTD_POWERUP_LOCK is reported but not implemented. The stock SPI NOR driver puts MTD_POWERUP_LOCK in the flags and then never implements the lock/unlock ioctl, so flash_unlock /dev/mtd10 returns ENOTSUP (error 95) while flash_erase and dd go through without any unlock step at all. The flag describes a capability the driver does not have, which is worse than not advertising it, because it reads like a safety interlock while behaving like a comment.

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

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

0:APPSBLENV happens to be mtd10 on both stock and OpenWrt, which is the one anchor worth remembering, and it is an anchor by partition name rather than by number.

Recovery

With SBL1 refusing to hand off, the only way back in is external: a CH341A programmer and a SOIC-8 clip on the NOR chip itself.

0:CDT holds DK04.1-C1 reference-design configuration with nothing per-unit in it, so a cross-unit copy is safe here. Restore the 64 KiB at offset 0xc0000 from a backup of another H510, or from the full SPI NOR backup you definitely took before writing to any flash partition.

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

Flash lock investigation

Once the unit was back, I went and checked whether flash_unlock is ever needed before writing to SPI NOR or NAND from the stock firmware, since that was the question that started all of this.

NAND (mtd0, mtd1, mtd2): flags are 0x400 (MTD_WRITEABLE), no powerup lock, and ubidetach plus ubiformat work with no unlock step.

SPI NOR (mtd8–mtd12): flags are 0xc00 (MTD_WRITEABLE | MTD_POWERUP_LOCK), flash_unlock returns ENOTSUP, and flash_erase and dd write successfully anyway.

So flash_unlock is neither required nor functional in the no-UART install path, and the install sequence is just flash_erase → dd. The answer cost me one device.

Rules going forward

  1. Never dd, cat, or echo to any /dev/mtdN whose partition name carries a 0: prefix, except 0:APPSBLENV. Use mtd_debug erase and mtd_debug write if you have to touch one, never the raw block device.

  2. Check the mtd numbers against /proc/mtd before every write. The numbering moves between kernel builds, and it moved on me.

  3. Take a full SPI NOR image 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, and it costs one command.

None of this is specific to the H510. Any board that hands you its whole boot chain as writable mtd devices will let you do the same thing, and the numbering that makes it easy to get wrong is a property of whichever kernel you happen to be running rather than of the hardware. Take the image first. It is the difference between a reflash and a clip.