事发
在一次在海鲜市场淘了一个红米 AX6 路由器回来刷机玩的时候,脑子里净想着小米 AX3600 的 CPU 和大部分配置都和红米 AX6 的是一样的,脑子一抽将小米 AX3600 的分区表和 uboot 刷到了红米 AX6 上。但小米 AX3600 的 ROM 是 256MB 的,而红米 AX6 的 ROM 却只有 128MB 大小,显然无法开机。具体表现是红米 AX6 的 Internet 蓝灯常亮,但只能进 uboot,刷什么固件都没法进系统,设置静态 IP 肯定也无法连上 SSH 了。当时还纳闷,为什么是 Internet 的蓝灯常亮,而不是 SYSTEM 灯常亮?后来发现,这俩的 SYSTEM 灯和 Internet 灯也是反过来的……显然,既然能进 uboot 就说明也不是完全没得救。要救砖,就得尝试用 USB to TTL 线试一下看看能不能进 uboot 命令行刷回正确的分区表了。
USB to TTL
关于这一点,网上的教程还是比较多的,这里可以特别补充一部分要点。
- 网上不少分享里,USB 模块的芯片都是用的 FT232,实测成本更低的 CH340 也是完全能用的。
- 路由器主板上 TTL 接口是用锡堵死的,我的做法是直接使用公头杜邦线焊上去。当然,用手压住也可以,只是确实没那么灵活。总之只要能保持连接就可以。
- 散热片接触面朝下,两个灯指向左边时,四个 TTL 孔定义从分别是 TX, GND, RX, VCC。
- 路由器的 TX 是连接 USB-to-TTL 模块的 RX,路由器的 RX 是连接 USB-to-TTL 模块的 TX,且 GND 之间必须相连。
- USB-to-TTL 模块如果有 3.3V 输出可以自行短接模块的 3.3V 输出和 VCC 口,即路由器的 VCC 和模块的 VCC 之间不是必须对接的。
- 波特率使用 115200。
- 使用 putty 等工具连上路由器后,如果黑屏或者跳乱码,需要多点几次回车键,看到
IPQ807x#的提示就说明成功连上了。如果没成功,试试先启动路由器后,再将 TTL 线连上。
Uboot 环境
首先,使用 help 命令查看所有可用的指令。这里贴出版本和所有指令,仅供参考。
IPQ807x# version
U-Boot 2016.01 (Dec 26 2021 - 16:39:11 +0800)
arm-openwrt-linux-muslgnueabi-gcc (OpenWrt GCC 5.2.0 6404588+r49254) 5.2.0
GNU ld (GNU Binutils) 2.24.0
IPQ807x# help
? - alias for 'help'
aes_256 - AES 256 CBC/ECB encryption/decryption
aq_load_fw- LOAD aq-fw-binary
aq_phy_restart- Restart Aquantia phy
base - print or set address offset
bdinfo - print Board Info structure
bootipq - bootipq from flash device
bootm - boot application image from memory
bootp - boot image via network using BOOTP/TFTP protocol
bootz - boot Linux zImage image from memory
canary - test stack canary
chpart - change active partition
cmp - memory compare
cp - memory copy
crc32 - checksum calculation
dhcp - boot image via network using DHCP/TFTP protocol
dhcpd - start dhcpd server
dm - Driver model low level access
echo - echo args to console
env - environment handling commands
erase - erase FLASH memory
exectzt - execute TZT
exit - exit script
false - do nothing, unsuccessfully
fdt - flattened device tree utility commands
flash - flash part_name
flash part_name load_addr file_size
flasherase- flerase part_name
flinfo - print FLASH memory information
fuseipq - fuse QFPROM registers from memory
go - start application at address 'addr'
gpio - print gpio direction and value
help - print command description/usage
httpd - start www server for firmware recovery with [localAddress]
i2c - I2C sub-system
imxtract- extract a part of a multi-image
ipq_mdio- IPQ mdio utility commands
is_sec_boot_enabled- check secure boot fuse is enabled or not
itest - return true/false on integer compare
loop - infinite loop on address range
md - memory display
mii - MII utility commands
mm - memory modify (auto-incrementing address)
mtdparts- define flash/nand partitions
mtest - simple RAM read/write test
mw - memory write (fill)
nand - NAND sub-system
nboot - boot from NAND device
nm - memory modify (constant address)
pci - list and access PCI Configuration Space
ping - send ICMP ECHO_REQUEST to network host
printenv- print environment variables
protect - enable or disable FLASH write protection
reset - Perform RESET of the CPU
run - run commands in an environment variable
runmulticore- Enable and schedule secondary cores
saveenv - save environment variables to persistent storage
secure_authenticate- authenticate the signed image
setenv - set environment variables
sf - SPI flash sub-system
showvar - print local hushshell variables
sleep - delay execution for some time
smeminfo- print SMEM FLASH information
source - run script from memory
test - minimal test like /bin/sh
tftpboot- boot image via network using TFTP protocol
tftpput - TFTP put command, for uploading files to a server
true - do nothing, successfully
uart - UART sub-system
ubi - ubi commands
usb - USB sub-system
usbboot - boot from USB device
version - print monitor, compiler and linker version
首先,使用 smeminfo 看看现在刷入小米 AX3600 的分区表之后,变成什么样子了。
IPQ807x# smeminfo
flash_type: 0x2
flash_index: 0x0
flash_chip_select: 0x0
flash_block_size: 0x20000
flash_density: 0x100000
partition table offset 0x0
No.: Name Attributes Start Size
0: 0:SBL1 0x0000ffff 0x0 0x100000
1: 0:MIBIB 0x0000ffff 0x100000 0x100000
2: 0:QSEE 0x0000ffff 0x200000 0x300000
3: 0:DEVCFG 0x0000ffff 0x500000 0x80000
4: 0:RPM 0x0000ffff 0x580000 0x80000
5: 0:CDT 0x0000ffff 0x600000 0x80000
6: 0:APPSBLENV 0x0000ffff 0x680000 0x80000
7: 0:APPSBL 0x0000ffff 0x700000 0x100000
8: 0:ART 0x0000ffff 0x800000 0x80000
9: bdata 0x0000ffff 0x880000 0x80000
10: crash 0x0000ffff 0x900000 0x80000
11: crash_syslog 0x0000ffff 0x980000 0x80000
12: rootfs 0x0000ffff 0xa00000 0xf000000
13: rsvd0 0x0000ffff 0xfa00000 0x80000
这里还是比较明显的,第 12 分区 rootfs 的分区大小变成了 0xf000000,这是大约 250MB 的空间。刷入这个分区表之后,红米 AX6 在尝试引导系统的时候,需要给这个分区预留 250MB 的大小,然后发现 ROM 的实际可用空间根本没有那么大,然后进不去了。而我们要做的事情是重新刷一个正确的分区表将其恢复。
那么要重新刷分区表,则需要将正确的分区表放在路由器的某处,并写入正确的内容到第 1 分区,即 MIBIB 分区。这里需要先将 MIBIB 分区在闪存芯片的位置记录下来,从 0x100000 处开始,一共占用了 0x100000 字节,即大小为 1MB 。同时,还需要将环境信息打印一下,以备不时之需。
IPQ807x# printenv
baudrate=115200
bootargs=console=ttyMSM0,115200n8
bootcmd=bootipq
bootdelay=2
eth1addr=64:64:4a:48:26:3c
eth2addr=64:64:4a:3d:f7:f2
eth3addr=64:64:4a:3d:f7:f2
eth4addr=64:64:4a:3d:f7:f2
ethact=eth0
ethaddr=64:64:4a:3d:f7:f2
fdt_high=0x4A400000
fdtcontroladdr=4a979830
flash_type=2
ipaddr=192.168.1.1
machid=8010010
netmask=255.255.255.0
serverip=192.168.1.10
soc_hw_version=200d0200
soc_version_major=2
soc_version_minor=0
stderr=serial@78B3000
stdin=serial@78B3000
stdout=serial@78B3000
Environment size: 535/262140 bytes
在这个模式下,TTL 只是用来输入命令的,并非直接用于传输文件的。如果需要将文件传输到路由器上,则需要使用 TFTP 进行传输。而 TFTP 工作需要将 PC 和路由器使用网线连在一起,并在 PC 中运行 tftpd 软件。并且,在路由器的 uboot 里,需要设置两个环境变量:
serverip,表示发送方的 IP 地址ipaddr,表示自己的地址
在上面的环境信息中,可以看见 ipaddr 当前是 192.168.1.1,掩码 255.255.255.0,为了便于传输,执行了下面两行命令,将 PC 的地址设置为静态的 192.168.31.100,路由器设置成 192.168.31.1。设置好后,可以在路由器上 ping 一下 PC 的地址,如果返回的信息最后显示 host [IP] is alive,说明能通;如果最后不返回这句,并且等了很久,则说明不通。其实只要最后能 ping 通,基本上就没什么问题。这里展示一下设置环境变量和 ping 测的结果。
IPQ807x# setenv serverip 192.168.31.100
IPQ807x# setenv ipaddr 192.168.31.1
IPQ807x# ping 192.168.31.100
ipq807x_eth_halt: done
eth0 PHY0 Down Speed :10 Half duplex
eth0 PHY1 Down Speed :10 Half duplex
eth0 PHY2 Down Speed :10 Half duplex
eth0 PHY3 up Speed :1000 Full duplex
eth0 PHY4 Down Speed :10 Half duplex
eth0 PHY5 Down Speed :10 Half duplex
ipq807x_eth_init: done
Using eth0 device
ipq807x_eth_halt: done
host 192.168.31.100 is alive
写入正确的分区表
下一步就是实际传输文件的过程了。这里先用 tftpboot mibib.bin 将正确的分区表传上去,回显如下。
IPQ807x# tftpboot mibib.bin
ipq807x_eth_halt: done
eth0 PHY0 Down Speed :10 Half duplex
eth0 PHY1 Down Speed :10 Half duplex
eth0 PHY2 Down Speed :10 Half duplex
eth0 PHY3 up Speed :1000 Full duplex
eth0 PHY4 Down Speed :10 Half duplex
eth0 PHY5 Down Speed :10 Half duplex
ipq807x_eth_init: done
Using eth0 device
TFTP from server 192.168.31.100; our IP address is 192.168.31.1
Filename 'mibib.bin'.
Load address: 0x44000000
Loading: *
Got TFTP_OACK: TFTP remote port: changes from 69 to 52820
#################################################################
#######
4.6 MiB/s
done
Bytes transferred = 1048576 (100000 hex)
ipq807x_eth_halt: done
因为在 uboot 中,其实没有文件系统的概念,也就没有文件名的概念。要定位文件,只能通过地址来进行定位。上面从结果中,需要记录几个信息,后面会用到:
- 文件被放在了内存空间里,即
0x44000000地址处,即 Load address 后面显示的地址,这个其他机型可能是不一样的。 - 一共传输了
0x100000个字节,这个参数在 Bytes transferred 后面的括号内,上面的 1048576 是十进制的,即 1MB 大小,但这里要看的是十六进制的大小,在后面的括号里用十六进制表示了文件的大小。建议和实际的文件大小进行比对,确认文件传输完整。
接下来就是把这个 mibib.bin 文件刷入到分区表里了。上面我们知道,我们的目标,是将我们刚刚的文件写进 MIBIB 分区里,我们可以检查一下,这个文件正好是 1MB 大,从上文我们也知道 MIBIB 的分区也是正好一样的大小。因此,至少这个文件是可以完整地被写入进 MIBIB 分区里的。要进行写入,需要先擦除 MIBIB 分区的内容,然后再把文件当前存在的地址空间里的内容,复制到 MIBIB 分区的地址空间上。实际上,我怀疑直接对 MIBIB 分区的地址写入也是可以的,只要完整写满 1MB 的大小,没有原有的数据遗留,应该就可以,但出于稳妥考虑,我没有验证这个想法。下面是使用 nand 命令进行擦除和写入。提一下相关命令的用法:
nand erase <起始地址> <擦除大小>nand write <被复制内容的起始地址> <要写入的起始地址> <写入大小>
其中,
- 起始地址:MIBIB 分区的起始地址,这里是
0x100000 - 擦除大小:需要擦除整个 MIBIB 分区,所以是整个 MIBIB 分区的大小,这里是
0x100000 - 被复制内容的起始地址:就是我们要写入的文件现在的起始地址,即
0x44000000 - 要写入的起始地址:MIBIB 分区的起始地址,这里是
0x100000 - 写入大小:即待写入文件的大小,这里是
0x100000
我们希望只有在擦除成功后,才进行写入,因此使用 && 将两条命令连在一起,得到如下命令。
IPQ807x# nand erase 0x100000 0x100000 && nand write 0x44000000 0x100000 0x100000
NAND erase: device 0 offset 0x100000, size 0x100000
Erasing at 0x1e0000 -- 100% complete.
OK
NAND write: device 0 offset 0x100000, size 0x100000
1048576 bytes written: OK
写入正确的 uboot
既然来都来了,干脆也把 uboot 也更正过来吧。于是如法炮制,先用 tftpboot 把文件写入到内存中,然后使用 nand 命令刷入正确的地址内。Uboot 存在于第 7 分区中,即 APPSBL 分区,其起始地址是 0x700000,大小同样是 0x100000,即 1MB。操作如下:
IPQ807x# tftpboot uboot.bin
ipq807x_eth_halt: done
eth0 PHY0 Down Speed :10 Half duplex
eth0 PHY1 Down Speed :10 Half duplex
eth0 PHY2 Down Speed :10 Half duplex
eth0 PHY3 up Speed :1000 Full duplex
eth0 PHY4 Down Speed :10 Half duplex
eth0 PHY5 Down Speed :10 Half duplex
ipq807x_eth_init: done
Using eth0 device
TFTP from server 192.168.31.100; our IP address is 192.168.31.1
Filename 'uboot.bin'.
Load address: 0x44000000
Loading: *
Got TFTP_OACK: TFTP remote port: changes from 69 to 61502
############################################
4.3 MiB/s
done
Bytes transferred = 642838 (9cf16 hex)
ipq807x_eth_halt: done
上传完毕后,进行擦除和写入
IPQ807x# nand erase 0x700000 0x100000 && nand write 0x44000000 0x700000 0x9cf16
NAND erase: device 0 offset 0x700000, size 0x100000
Erasing at 0x7e0000 -- 100% complete.
OK
NAND write: device 0 offset 0x700000, size 0x9cf16
NAND write to offset 700000 failed -22
0 bytes written: ERROR
注意,这里出现了报错。这个时候可千万不能断电重启,因为这个时候已经成功执行了 nand erase,理论上 APPSBL 分区已经是空的了,而新的内容尚未写入进去,这个时候重启很可能会变成硬砖,需要用编程器才能救了。简单分析了一下,怀疑是没有做好地址对齐导致的,这个 0x9cf16 虽然比分区表的空间小,分区表能容纳这个大小的内容,但这个数字看起来还是太零碎了,于是干脆写入整个分区的大小,把 0x9cf16 扩大成 0x100000 试试:
IPQ807x# nand erase 0x700000 0x100000 && nand write 0x44000000 0x700000 0x100000
NAND erase: device 0 offset 0x700000, size 0x100000
Erasing at 0x7e0000 -- 100% complete.
OK
NAND write: device 0 offset 0x700000, size 0x100000
1048576 bytes written: OK
这次成功了。至此,正确的分区表和 uboot 都已经成功写入红米 AX6 中。此时,因为修改了分区表,直接断电重启,尝试进 uboot 界面并直接在 uboot 里面刷入需要的系统固件。至此,成功救活。最后,看看红米 AX6 正确的大容量分区表内容是怎么样的:
# cat /proc/partitions
major minor #blocks name
31 0 1024 mtdblock0
31 1 1024 mtdblock1
31 2 3072 mtdblock2
31 3 512 mtdblock3
31 4 512 mtdblock4
31 5 512 mtdblock5
31 6 512 mtdblock6
31 7 1024 mtdblock7
31 8 512 mtdblock8
31 9 512 mtdblock9
31 10 512 mtdblock10
31 11 512 mtdblock11
31 12 104704 mtdblock12
31 13 512 mtdblock13
254 0 24056 ubiblock0_1
253 0 137216 zram0
# cat /proc/mtd
dev: size erasesize name
mtd0: 00100000 00020000 "0:sbl1"
mtd1: 00100000 00020000 "0:mibib"
mtd2: 00300000 00020000 "0:qsee"
mtd3: 00080000 00020000 "0:devcfg"
mtd4: 00080000 00020000 "0:rpm"
mtd5: 00080000 00020000 "0:cdt"
mtd6: 00080000 00020000 "0:appsblenv"
mtd7: 00100000 00020000 "0:appsbl"
mtd8: 00080000 00020000 "0:art"
mtd9: 00080000 00020000 "bdata"
mtd10: 00080000 00020000 "crash"
mtd11: 00080000 00020000 "crash_syslog"
mtd12: 06640000 00020000 "rootfs"
mtd13: 00080000 00020000 "rsvd0"
本文由 Webster Zhang 创作,采用 知识共享署名4.0 国际许可协议进行许可