一次红米 AX6 的救砖经历

日期 2026-01-10
Miscellaneous/杂类
作者 Webster Zhang

事发

在一次在海鲜市场淘了一个红米 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

关于这一点,网上的教程还是比较多的,这里可以特别补充一部分要点。

  1. 网上不少分享里,USB 模块的芯片都是用的 FT232,实测成本更低的 CH340 也是完全能用的。
  2. 路由器主板上 TTL 接口是用锡堵死的,我的做法是直接使用公头杜邦线焊上去。当然,用手压住也可以,只是确实没那么灵活。总之只要能保持连接就可以。
  3. 散热片接触面朝下,两个灯指向左边时,四个 TTL 孔定义从分别是 TX, GND, RX, VCC。
  4. 路由器的 TX 是连接 USB-to-TTL 模块的 RX,路由器的 RX 是连接 USB-to-TTL 模块的 TX,且 GND 之间必须相连。
  5. USB-to-TTL 模块如果有 3.3V 输出可以自行短接模块的 3.3V 输出和 VCC 口,即路由器的 VCC 和模块的 VCC 之间不是必须对接的。
  6. 波特率使用 115200。
  7. 使用 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 里,需要设置两个环境变量:

  1. serverip,表示发送方的 IP 地址
  2. 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 中,其实没有文件系统的概念,也就没有文件名的概念。要定位文件,只能通过地址来进行定位。上面从结果中,需要记录几个信息,后面会用到:

  1. 文件被放在了内存空间里,即 0x44000000 地址处,即 Load address 后面显示的地址,这个其他机型可能是不一样的。
  2. 一共传输了 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"