A practical walkthrough: Linksys factory firmware → failed OpenWrt installation → UART and U-Boot → booting OpenWrt from RAM via TFTP → restoring the UBI partition → successful boot of OpenWrt 23.05.6.
Scope. This describes a specific case involving a Linksys EA4500 on Marvell Kirkwood, which has two firmware slots and NAND with a bad block at
0x01DE0000. The instructions are not universal for all EA4500 units, especially hardware revision v3, which uses a different platform. Before writing to NAND, always verify the output of/proc/mtdand the actual layout. The wrong partition can leave the device without even its factory fallback firmware.System status. Recovered and successfully booting OpenWrt 23.05.6. This OpenWrt series has already reached end of life and no longer receives security fixes. It is used here as a verified intermediate recovery point, not as a recommendation for long-term operation on an external WAN.
Starting Point
On hand was a Linksys EA4500 with the original Linksys Smart Wi-Fi firmware (2.1.41.162351). The goal was to replace the factory system with OpenWrt. This article is specifically about installing and recovering OpenWrt, not about configuring circumvention of blocks.
The device is equipped with a Marvell Kirkwood 88F6282 SoC (ARMv5, OpenWrt architecture arm_xscale), approximately 128 MiB of RAM, and 128 MiB of NAND with 128 KiB erase blocks. A UART serial console is available on the board. Power is 12 V; when working with UART, use 3.3 V logic levels, do not connect a 5 V TTL adapter to the board, and generally do not connect the USB–UART adapter’s VCC to the router. You need a common ground, TX↔RX, and RX↔TX. Terminal settings: 115200, 8N1.
An important feature of the EA4500 is dual-slot firmware: when updating through the standard interface, the new image goes into the inactive slot, after which the bootloader attempts to switch to it. This is useful during recovery, but it does not eliminate the need to protect the working slot.
Step 1. Attempting a Normal Installation from the Factory Interface
First, the official file was downloaded:
openwrt-23.05.6-kirkwood-generic-linksys_ea4500-squashfs-factory.bin
This is the correct file type for an initial installation through the Linksys web interface. sysupgrade.bin images are used for updating an already-running OpenWrt and are not interchangeable with factory.bin.
After flashing, the router did not boot: there were reboots and periods with no UART output. An attempt with OpenWrt 22.03 produced a similar result. This looked like a failed flash, but the cause turned out to be more specific.
At the same time, hardware quirks appeared: sometimes after a reboot the console was silent, and sometimes U-Boot started. The cause of this instability was not established; power or hardware problems are possible. This is a separate observation and must not be confused with the UBI mismatch diagnosed below. In cases of unstable boot, it is better to first check the power supply and the UART connection.
Step 2. Connecting UART and Gaining Access to U-Boot
After connecting UART and stopping autoboot, U-Boot 1.1.4 became available. Through it, the environment variables were checked:
text
printenv
Key values for our unit:
text
bootcmd=run altnandboot boot_part=2 bootdelay=0 auto_recovery=yes nandboot=nand read.e 0x2000000 0x200000 0x300000; setenv bootargs $(console) $(mtdparts) $(fs_bootargs_root) serial_number=$(sn) uuid=$(uuid) hw_version=$(hw) device_mac=$(mac) factory_date=$(date) wps_pin=$(wps); bootm 0x2000000; altnandboot=nand read.e 0x2000000 0x1c00000 0x300000; setenv bootargs $(console) $(mtdparts) $(alt_fs_bootargs_root) serial_number=$(sn) uuid=$(uuid) hw_version=$(hw) device_mac=$(mac) factory_date=$(date) wps_pin=$(wps); bootm 0x2000000;
In this case, nandboot loads the kernel of the first slot from 0x00200000, and altnandboot loads the second from 0x01C00000. Therefore, U-Boot was trying to boot the second slot, which was corrupted from a boot standpoint.
The first slot was checked manually:
text
run nandboot
Result: the factory Linksys firmware booted. This means U-Boot, the main hardware components, and the first firmware copy were alive. This became a safety net throughout the recovery.
A Quirk of the Factory Firmware’s Network Settings
After entering the stock Linux system, it turned out that the WAN obtains an address via DHCP (in our case 192.168.1.148), while the LAN bridge br0 had the address 10.199.36.77/24; the usual DHCP on LAN did not work. This made network access difficult but did not mean the router was hardware-faulty. For further operations, UART and a separate Ethernet path to the PC were used.
Step 3. Reading NAND and Finding the Real Cause
Through U-Boot and Linux, the partition sizes, bad blocks, and kernel boot output were checked.
The following bad NAND blocks were found on this device:
text
Bad eraseblock 239 at 0x000001de0000 Bad eraseblock 692 at 0x000005680000 Bad eraseblock 985 at 0x000007b20000
The first of these is especially interesting: 0x01DE0000 is located inside the kernel area of the second slot.
At the same time, U-Boot successfully read the kernel with the command:
text
nand read.e 0x2000000 0x1c00000 0x300000
The nand read.e command skips marked bad blocks. Because of this, the physical and logical placement of data in the second slot requires special attention.
In the OpenWrt 23.05.6 logs, the kernel successfully passed the checksum verification and started, but then could not find UBI:
text
UBI error: no valid UBI magic found inside mtd6 VFS: Cannot open root device Kernel panic
That is, the failure occurred not during kernel boot but when mounting the root filesystem.
Checking UBI Offsets
Next, in the factory Linux, the first bytes of consecutive blocks of the second slot were read (NAND erase block size is 0x20000, or 128 KiB). The following picture emerged:
| Physical address | Contents at the start of the block |
|---|---|
0x01EE0000 | 00 00 ... |
0x01F00000 | 00 00 ... |
0x01F20000 | 55 42 49 23 = UBI# |
0x01F40000 | UBI# |
0x01F60000 | UBI# |
But OpenWrt expected the start of the rootfs2 partition at address 0x01F00000, that is, exactly one eraseblock earlier than the first detected UBI# signature.
Working hypothesis: the stock firmware from the factory interface placed UBI with an additional offset of 128 KiB, probably because of the bad block in the kernel area of the second slot. This explains the mismatch with the OpenWrt layout. We did not investigate the implementation of the stock flasher down to the source code level, so the specific internal mechanism of the offset remains a hypothesis. The practical part is confirmed: after writing UBI directly to the correct MTD partition, the device worked.
Step 4. Why We Chose to Boot OpenWrt from RAM
Instead of manually modifying the device tree or rewriting U-Boot, we decided to use the official OpenWrt initramfs. It runs entirely in RAM, does not require a working root filesystem on NAND, and contains the ubiformat, ubiattach, and ubinfo utilities.
This made it possible to write the root filesystem precisely to the rootfs2 MTD partition without touching the first Linksys slot.
Two official OpenWrt 23.05.6 files are needed:
openwrt-23.05.6-kirkwood-generic-linksys_ea4500-initramfs-uImage— for loading into RAM via TFTP;openwrt-23.05.6-kirkwood-generic-linksys_ea4500-squashfs-factory.bin— for extracting the UBI part.
They are available in the OpenWrt 23.05.6 release archive for kirkwood/generic.
Verified official SHA256:
| File | SHA256 |
|---|---|
...linksys_ea4500-squashfs-factory.bin | fc368d0f70d4f23b5f84af0a54fab90253dd35f9c20b3fe20d28caa30920191a |
...linksys_ea4500-initramfs-uImage | 7ae7bc6b9f057c614b9011ccd82803fb10ae01deaa16e2ba89c486dcb539b0c2 |
The attached archive contains scripts/prepare_firmware.py, which downloads the files, verifies SHA256, and extracts rootfs.ubi. The binary images themselves are not included in the archive, since during its preparation the official server was not accessible from the environment where the files were created.
Step 5. Booting initramfs via TFTP
On a Windows computer, the Ethernet interface was configured with static IPv4 192.168.1.2/24 and a TFTP server was started. The specific TFTP program does not matter: it is enough for the server to serve initramfs-uImage from its directory. The Python HTTP server, which will be needed below, does not replace the TFTP server for the tftpboot command.
At the U-Boot prompt:
text
setenv ipaddr 192.168.1.1 setenv serverip 192.168.1.2 ping 192.168.1.2 tftpboot 0x2000000 openwrt-23.05.6-kirkwood-generic-linksys_ea4500-initramfs-uImage iminfo 0x2000000 setenv bootargs 'console=ttyS0,115200 root=/dev/ram rw' bootm 0x2000000
iminfo is needed to make sure the image loaded and its header/checksum are correct. setenv here changes only the values in the U-Boot runtime environment; we did not run the saveenv command.
Result: OpenWrt 23.05.6 booted successfully from RAM. On the console:
text
DISTRIB_ID='OpenWrt' DISTRIB_RELEASE='23.05.6' DISTRIB_TARGET='kirkwood/generic' DISTRIB_ARCH='arm_xscale'
Step 6. Verifying the Memory Map in RAM OpenWrt
This is the most important step before any NAND write. We ran:
sh
cat /proc/mtd dmesg | grep -E 'Creating|kernel2|ubi|0x000001f00000|Bad eraseblock' command -v ubiformat command -v ubiattach command -v ubinfo mount | grep ubi free
The actual memory map:
text
dev: size erasesize name mtd0: 00080000 00020000 "u-boot" mtd1: 00020000 00020000 "u_env" mtd2: 00020000 00020000 "s_env" mtd3: 01a00000 00020000 "kernel1" mtd4: 01700000 00020000 "rootfs1" mtd5: 01a00000 00020000 "kernel2" mtd6: 01700000 00020000 "rootfs2" mtd7: 04a00000 00020000 "syscfg" mtd8: 00140000 00020000 "unused"
The kernel additionally confirmed:
text
0x000001c00000-0x000003600000 : "kernel2" 0x000001f00000-0x000003600000 : "rootfs2"
Here kernel2 (/dev/mtd5) is a large overlapping area of the second slot, and rootfs2 (/dev/mtd6) is its filesystem, 23 MiB in size, starting at 0x01F00000. For recovery, you must write only to /dev/mtd6, not to /dev/mtd5, /dev/mtd3, /dev/mtd4, or U-Boot.
The tools ubiformat, ubiattach, ubinfo, and wget turned out to be already available in the initramfs. This made it possible to avoid installing additional packages.
Step 7. Extracting the Correct UBI Image
For this specific OpenWrt 23.05.6 factory image, the first 0x300000 bytes (3 MiB) are allocated to the kernel; the UBI image begins after that. This means it is enough to remove the first 3 MiB from the factory.bin file. Do not use this offset for OpenWrt 24.10 and newer without additional verification: the kernel area and boot parameters have been changed there.
The package includes a verification script:
powershell
py -3 scripts\prepare_firmware.py
It verifies the hashes of the two source files and requires that:
- the
UBI#signature be present at offset0x300000; - the extracted file have a size of 4,456,448 bytes (34 NAND blocks);
- the size be a multiple of
0x20000.
Output file: firmware/rootfs.ubi.
For reproducibility, the minimal version of the operation looks like this:
python
from pathlib import Path
factory = Path('openwrt-23.05.6-kirkwood-generic-linksys_ea4500-squashfs-factory.bin').read_bytes()
ubi = factory[0x300000:]
assert len(ubi) == 4456448
assert ubi[:4] == b'UBI#'
assert len(ubi) % 0x20000 == 0
Path('rootfs.ubi').write_bytes(ubi)
Unlike the minimal example, the attached script first verifies the official SHA256 of the image, which is necessary before flashing.
Step 8. Transferring rootfs.ubi to the Router
On Windows, from the folder containing the file, a simple HTTP server was started:
powershell
cd firmware py -3 -m http.server 8000 --bind 192.168.1.2
The package includes scripts/start_http_server.cmd for this purpose.
From OpenWrt booted in RAM:
sh
cd /tmp wget http://192.168.1.2:8000/rootfs.ubi ls -l /tmp/rootfs.ubi sha256sum /tmp/rootfs.ubi
In our case:
text
rootfs.ubi 4456448 bytes
The first four bytes were also checked. BusyBox did not have the od command, so a simpler method was used:
sh
dd if=/tmp/rootfs.ubi bs=4 count=1 2>/dev/null echo
Expected:
text
UBI#
The package includes scripts/check_ram_openwrt.sh and scripts/check_rootfs_ubi.sh — additional checks of the MTD map, tools, signature, and size. They can be sent to the router via the same HTTP server or copied manually.
Before writing, be sure to verify that the SHA256 of rootfs.ubi matches on the PC and the router.
Step 9. Restoring the rootfs2 Partition
Dangerous command. The following involves rewriting NAND. It is provided as a record of the operation actually performed, not as an unconditional recommendation for any device. Before running it, make sure you are working from initramfs, that
/dev/mtd6on your device is indeedrootfs2with size0x01700000, that therootfs.ubifile has been verified, and that power is stable. If the layout is different — stop.
We ran:
sh
ubiformat /dev/mtd6 -f /tmp/rootfs.ubi -y
Key lines of the actual output:
text
ubiformat: mtd6 (nand), size 24117248 bytes (23.0 MiB), 184 eraseblocks of 131072 bytes (128.0 KiB), min. I/O size 2048 bytes libscan: scanning eraseblock 183 -- 100 % complete ubiformat: 35 eraseblocks have valid erase counter, mean value is 0 ubiformat: 148 eraseblocks are supposedly empty ubiformat: warning!: 1 of 184 eraseblocks contain non-UBI data ubiformat: warning!: only 35 of 184 eraseblocks have valid erase counter ubiformat: erase counter 0 will be used for all eraseblocks ubiformat: flashing eraseblock 33 -- 100 % complete ubiformat: formatting eraseblock 183 -- 100 % complete
The warnings about old contents and erase counters did not lead to an error in this case. All of mtd6 was formatted, and UBI was written from the beginning of the required area.
Verification via ubiattach
After a successful ubiformat:
sh
ubiattach -m 6 ubinfo -a
Key lines from the result:
text
ubi0: attaching mtd6
UBI: EOF marker found, PEBs from 29 will be erased
ubi0: volume 1 ("rootfs_data") re-sized from 9 to 133 LEBs
ubi0: attached mtd6 (name "rootfs2", size 23 MiB)
ubi0: PEB size: 131072 bytes (128 KiB), LEB size: 129024 bytes
ubi0: min./max. I/O unit sizes: 2048/2048, sub-page size 512
ubi0: VID header offset: 512 (aligned 512), data offset: 2048
ubi0: good PEBs: 184, bad PEBs: 0, corrupted PEBs: 0
ubi0: user volume: 2, internal volumes: 1
And ubinfo -a:
| UBI volume | Size | State |
|---|---|---|
rootfs | 27 LEB / 3,483,648 bytes (~3.3 MiB) | OK |
rootfs_data | 133 LEB / 17,160,192 bytes (~16.3 MiB) | OK |
This confirmed the correct UBI structure. The automatic enlargement of rootfs_data is normal. Zero available LEBs was also expected: the free space is distributed between the volumes and the reserve for bad blocks.
Note:
ubinfoconfirms the correctness of the UBI metadata and the state of the volumes, but by itself it does not guarantee that all files insiderootfsare readable. The final check is provided by booting OpenWrt from NAND.
Step 10. Reboot — and a Successful Boot!
After checking the filesystem, UBI was detached from the RAM system (if ubidetach was available) and a normal boot of the second slot was checked:
sh
ubidetach -m 6
The router was then rebooted. U-Boot used the previously existing altnandboot; we did not touch the factory slot.
This time OpenWrt booted fully:
text
BusyBox v1.36.1 (2025-08-14 18:27:40 UTC) built-in shell (ash)
_______ ________ __
| |.-----.-----.-----.| | | |.----.| |_
| - || _ | -__| || | | || _|| _|
|_______|| __|_____|__|__||________||__| |____|
|__| W I R E L E S S F R E E D O M
-----------------------------------------------------
OpenWrt 23.05.6, r24232-539228933c
-----------------------------------------------------
=== WARNING! =====================================
There is no root password defined on this device!
Use the "passwd" command to set up a new password
in order to prevent unauthorized SSH logins.
--------------------------------------------------
root@OpenWrt:/#
This is no longer initramfs but an installed OpenWrt that has booted from NAND.
The first action after recovery should be to set a password:
sh
passwd
Then check the persistent overlay, filesystems, and network:
sh
cat /etc/openwrt_release mount | grep overlay df -h ip addr
Why It All Worked: A Brief Diagram
text
Factory Linksys (slot 1) ────────────────┐
│ remained working
Web flash of factory.bin │
│ │
▼ │
Slot 2: kernel2 + rootfs2 │
│ │
├─ kernel boots │
└─ UBI not found │
│ │
bad block 0x01DE0000 │
rootfs2 expected at 0x01F00000 │
UBI# found from 0x01F20000 │
│ │
▼ │
UART → U-Boot │
│ │
TFTP → initramfs (RAM) │
│ │
verify /proc/mtd │
│ │
extract rootfs.ubi │
│ │
ubiformat /dev/mtd6 -f ... │
│ │
ubiattach + ubinfo │
│ │
▼ │
OpenWrt 23.05.6 boots ◄────────┘
Key Takeaways
- Do not equate a kernel failing to boot with a damaged kernel. Here U-Boot loaded the kernel successfully, and the failure occurred at the UBI stage.
- Read the actual addresses and NAND state. A bad block inside the kernel area could have affected the layout when the firmware was written. The match between the assumed and actual addressing must be verified, not guessed.
- Use initramfs as a rescue tool. This made it possible to work with MTD directly without overwriting the working first slot.
- Do not confuse
mtd5andmtd6. In our layout,mtd5covers the second slot, whilemtd6is the area of its rootfs needed for UBI. - Verify SHA256 and the UBI signature. A wrong image during direct NAND writing is significantly more dangerous than a standard installation via the GUI.
- Do not change U-Boot variables unnecessarily. Recovering OpenWrt 23.05.6 did not require running
saveenvor replacing the bootloader. - Do not blindly transfer instructions between OpenWrt versions. For OpenWrt 24.10+ on the EA4500, the documentation describes increasing the kernel read size from
0x300000to0x400000; this separate step was not performed in our case.
Files and Scripts Attached to the Article
text
ea4500_openwrt_recovery/
├── EA4500_OpenWrt_Recovery.md ← this article
├── README.md ← package contents and quick start
├── firmware/
│ └── README_DOWNLOAD.md ← official links and SHA256
├── scripts/
│ ├── prepare_firmware.py ← download, verify SHA256, and extract UBI
│ ├── start_http_server.cmd ← HTTP server for transferring UBI
│ ├── check_ram_openwrt.sh ← safe MTD check from initramfs
│ └── check_rootfs_ubi.sh ← safe rootfs.ubi check
└── logs/
└── recovery_key_outputs.txt ← saved key diagnostic logs
The binary firmware files and the generated rootfs.ubi appear in firmware/ after running prepare_firmware.py. The article intentionally does not include a universal automatic ubiformat script: the dangerous operation must be performed manually only after verifying the MTD map.