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:
| Partition | Stock Ruckus mtd | OpenWrt mtd |
|---|---|---|
0:APPSBLENV | mtd10 | mtd8 |
0:CDT | mtd8 | mtd6 |
Board Data | mtd3 | mtd11 |
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
Never
dd,cat, orechoto any/dev/mtdNwhose partition name carries a0:prefix, except0:APPSBLENV. Usemtd_debug eraseandmtd_debug writeif you have to touch one, never the raw block device.Check the mtd numbers against
/proc/mtdbefore every write. The numbering moves between kernel builds, and it moved on me.Take a full SPI NOR image before any NOR write.
dd if=/dev/mtd0ro of=/tmp/full-nor.bin bs=4096from 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.