用 Bus Blaster v4 调试 MT7621 路由器

转载请注明原帖链接和作者!


一年多以前,曾经尝试用 Bus Blaster v4 配合 OpenOCD 来调试 MT7621,但是一直没有成功。OpenOCD 虽然能够识别 CPU,识别出 5 个 TAP(当时也不知道为什么会识别出 5 个来,MT7621 明明是双核四线程的,应该是 4 个才对),但是始终无法正常执行指令,因此也就无法继续进行后续的操作。这个问题一直困扰着我,直到最近才得以解决。

前几天在公司用 Trace32 调试 MT7621 的 U-Boot 时,发现了一个奇怪的现象:
首先介绍一下 MT7621 的一个缺陷:MT7621 的 RESET_L 引脚低电平使芯片复位时,会导致 EJTAG 模块一并被复位;OpenOCD 的 reset 指令会向 TAP 发出 EJTAGBOOT 指令。虽然 EJTAGBOOT 指令的作用是使 CPU 在执行第一条指令前中断,但是 OpenOCD 在发出 EJTAGBOOT 指令后,会拉低 RESET_L (nSRST) 引脚的电平,因此导致之前的设置无效。因此 OpenOCD 在对芯片进行复位时,无法只复位 CPU 而不复位 TAP,因此无法实现在 CPU 执行的第一条指令处中断。Trace32 中也存在同样的问题。
在使用 Trace32 进行调试时,Trace32 会等待一段时间,确保 TAP 正常后,才会对 CPU 进行中断,此时 U-Boot 的代码已经执行了一部分了,虽然可以正常进行单步调试等,但是进行读取 CPU 寄存器等操作会导致整个芯片失去响应 (Trace32 提示 Emulation debug port fail!)。
为了能够在 CPU 执行第一条指令时中断,我在 U-Boot 源码的入口代码处写了一条死循环,这样就可以在 Trace32 进行调试时,CPU 不会执行后续的的代码,那么我可以通过修改 PC 寄存器,使 CPU 按需执行后续的代码。
但是这样做了之后,我发现,虽然 Trace32 成功让 CPU 中断在了入口代码出,但是却无法进行单步调试了,Trace32 提示 Bus error generated by CPU。这个错误就很像之前用 Bus Blaster 调试时的问题了,我一直认为是 OpenOCD 对 MIPS 1004K 的支持不全造成的。

Trace32 的调试结果说明 U-Boot 中的有段代码执行后,才能使 CPU 能够正常使用单步等调试功能。因此我就花了点时间,修改死循环代码的位置,观察在哪一段代码执行后,单步执行可以正常使用。

最后,我找到了这段代码:
(文件位置 Uboot/cpu/ralink_soc/start_1004k.S)

	//set SPI clock to system bus /(5+2)
	li	t0, RALINK_SPI_BASE + 0x3c
	//sw	zero, 0(t0)
	li	t1, ~0x0FFF
	lw	t2, 0(t0)
	and	t2, t2, t1
	ori	t2, t2, 0x5
	sw	t2, 0(t0)

通过 MT7621_ProgrammingGuide_Preliminary_Platform.pdf,可以得到这个寄存器的定义:
MT7621 Register SPI_SPACE

可以看到那段汇编代码修改了 0x1e000b3c 寄存器的 fs_clk_sel 字段的值,从默认的 0x30 (十进制 48) 改为了 5.
这个寄存器字段的作用是设置 SPI Flash 数据映射空间 (0xbfc00000 - 0xbfffffff) 访问时,SPI 总线的时钟频率。
原始值是 48,也就是说总线频率是 hclk/50;修改后的值是 5,总线频率是 hclk/7。
虽然不知道 hclk 的数值是多少,但是也能看出来修改后 SPI 总线频率提升了不少。联想到 MIPS 的 CPU,一般就从 0xbfc00000 处取指执行 (MT7621 也是如此),那么也就是说默认 SPI 总线频率太低,导致 CPU 取指效率太低,以至于影响了 CPU 的运行?
突然又想到,在 Trace32 无法在 CPU 执行单步时,将 PC 修改到 SRAM 内却可以正常执行单步,说不定还真和这个有关。
不管怎么说,至少这个棘手的问题时解决了,以后用 Trace32 调试时,只需先修改 0xbe000b3c 这个寄存器的值为 5,之后就可以无忧无虑地进行调试追踪了。

。。。。。。

回家后,想到了 Bus Blaster,说不定以前用 Bus Blaster 不能调试也是这个因素造成的,于是就拿出了 Bus Blaster 进行测试。

以下才是正文


[连接设备]

由于 Bus Blaster 的接口是 ARM 20-pin 的,因此自己做了个转接板,将其转换为了 EJTAG 14-pin,最后的连接如下:
MT7621 With Bus Blaster v4

感谢星空de曲调的 MT7621 路由板子和 Bus Blaster v4 (笑

然后,就是编写一份用于 OpenOCD 的 cfg 文件了。

其实在之前调试时就已经写好了,但是因为后来了解到了 5 个 TAP 的来历,所以进行了少许修改:

#
# Written by Weijie Gao <hackpascal@gmail.com>
# DO NOT REMOVE THESE LINES
#

gdb_port 3333
telnet_port 4444

interface ftdi
ftdi_vid_pid 0x0403 0xcff8
ftdi_device_desc "Amontec JTAGkey"

ftdi_layout_init 0x0c08 0x0f1b
ftdi_layout_signal nTRST -data 0x0100 -noe 0x0400
ftdi_layout_signal nSRST -data 0x0200 -noe 0x0800

adapter_khz 15000

adapter_nsrst_delay 100
jtag_ntrst_delay 100

reset_config trst_and_srst srst_push_pull

set CHIPNAME mt7621

jtag newtap $CHIPNAME cm -irlen 5 -ircapture 0x1 -irmask 0x1f -expected-id 0x1762124f
jtag newtap $CHIPNAME.3 cpu -irlen 5 -ircapture 0x1 -irmask 0x1f -expected-id 0x1762124f
jtag newtap $CHIPNAME.2 cpu -irlen 5 -ircapture 0x1 -irmask 0x1f -expected-id 0x1762024f
jtag newtap $CHIPNAME.1 cpu -irlen 5 -ircapture 0x1 -irmask 0x1f -expected-id 0x1762124f
jtag newtap $CHIPNAME.0 cpu -irlen 5 -ircapture 0x1 -irmask 0x1f -expected-id 0x1762024f

target create $CHIPNAME.cm mips_m4k -endian little -chain-position $CHIPNAME.cm
target create $CHIPNAME.3.cpu mips_m4k -endian little -chain-position $CHIPNAME.3.cpu
target create $CHIPNAME.2.cpu mips_m4k -endian little -chain-position $CHIPNAME.2.cpu
target create $CHIPNAME.1.cpu mips_m4k -endian little -chain-position $CHIPNAME.1.cpu
target create $CHIPNAME.0.cpu mips_m4k -endian little -chain-position $CHIPNAME.0.cpu

MIPS 1004K/1074K 是多核 SMT 架构,每个核 (Core) 拥有两个线程 (VPE)。MIPS CPU 启动时,固定是 Core0/VPE0 最先启动,然后由软件启动剩余的核心和 VPE。
在 Trace32 的帮助文档 debugger_mips.pdf 中,有说到 MIPS 1004K 中的 TAP 连接方式:
MIPS 1004K TAP

可以看到,MIPS 1004K (PRID 修订号为 0x2f) 中的 Coherence Manager (CM,大概翻译为一致性管理器,用于多核之间的管理和同步),也提供了一个 TAP,这也就是 5 个 TAP 的来历。
根据说明,可以看到各个 TAP 间的连接方式:
从 Core0 开始一直连接到 CoreN,最后连接到 CM;一个 Core 间,首先连接 VPE0,再连接到 VPE1。
OpenOCD 在通过 JTAG 进行扫描时,会最先扫描出 CM,最后扫描到 Core0/VPE0。这也是我在实际测试后得出的,最后反映在 OpenOCD cfg 文件的描述中。

[启动 OpenOCD]

执行 openocd -f mt7621.cfg 后,可以看到如下输出:
MT7621 OpenOCD

执行 telnet 127.0.0.1 4444,连接到 OpenOCD,执行 halt 中断 CPU,然后可以执行 targets 命令,查看当前所有 TAP 的状态:
OpenOCD halt CPUs
在 targets 命令的输出中,可以看到 mt7621.0.cpu 的状态是 halted,并且 mt7621.0.cpu 带有星号,也就是当前正在操作的 Core0/VPE0。

然后立即执行
mww 0xbe000b3c 5
也就是上文所说的修改 SPI 总线频率,以确保 CPU 能够正常使用调试功能。

为了加快后续的操作,需要将 CPU 的频率调整到 500MHz:
mww 0xbe000410 0x0101

[制作内存初始化模块]

MT7621 的 U-Boot 没有提供内存控制器相关的源代码,因此需要用到其提供的 mt7621_stage_sram.bin 来进行内存初始化。

mt7621_stage_sram.bin 需要按需求进行修改后,才能使用:
MT7621 DRAM Init bin file Structure

如图所示,根据板子的内存类型 (DDR2/DDR3)、内存容量,CPU 晶振频率,需要修改至少 3 处数据。所有的数据均以自然顺序描述 (如 0xaabbccdd),但是使用小端序 (Little-Endian) 存储 (DDR 时序参数例外,见下文),因此写入文件时,需要先颠倒字节序,如:
描述时使用了 0xaabbccdd,那么写入文件时,需要转换为 dd cc bb aa。

所有数据描述如下:

1. CPU 频率参数
这是 0xbe005648 处寄存器的设定数值,其主要字段如下(因 Programming Guide 中关于 Memory Controller 的描述几乎全部错误,且有很多缺失):
从第 4 位到第 10 位,共 7 位,掩码 0x7f:CPU 频率倍频,用 FB 代替;
从第 12 位到第 13 位,共 2 位,掩码 0x3:晶振频率的分频,用 PREDIV 代替;

CPU 频率计算公式:CPU_FREQ = XTAL_FREQ / (PREDIV + 1) *  (FB + 1)
其中 CPU_FREQ 为最终得到的 CPU 频率;
XTAL_FREQ 为 CPU 使用的晶振频率。

为了兼容原厂 U-Boot,当 CPU 晶振频率为 40MHz 时,PREFIV 取 1,表示使用 20MHz 作为基准频率(外频);当晶振频率为 20MHz/25MHz 时,PREDIV 取 0。
该寄存器的其他字段不能改动。除去 FB 和 PREDIV 两个字段,其它字段的数值组合出的最终数值为 0xc0004802,最终写入 bin 的数值需要在此基础上加入 FB 和 PREDIV 的值。

举例:
CPU 频率设定为 880MHz,晶振频率为 40MHz,那么:
PREDIV = 1
FB = 880 * (PREDIV + 1) / XTAL_FREQ - 1 = 880 * 2 / 40 - 1 = 43 = 0x2b
最终寄存器的值为 REG = 0xc0004802 | (PREDIV << 12) | (FB << 4) = 0xc0004802 | (1 << 12) | (0x2b << 4) = 0xc0004802 | 0x1000 | 0x2b0 = 0xc0005ab2

CPU 频率设定为 875MHz,晶振频率为 25MHz,那么:
PREDIV = 0
FB = 875 * (PREDIV + 1) / XTAL_FREQ - 1 = 875 * 1 / 25 - 1 = 34 = 0x22
最终寄存器的值为 REG = 0xc0004802 | (PREDIV << 12) | (FB << 4) = 0xc0004802 | (0 << 12) | (0x22 << 4) = 0xc0004802 | 0x0000 | 0x220 = 0xc0004a22

2. 内存频率参数
这是一个位域字段,共有 3 个字段,指定三个内存控制相关的参数:
第 28 位到第 31 位,共 4 位,掩码 0xf:内存频率,1 代表 1200MHz,2 代表 1066MHz,3 代表 800MHz,4 代表 400MHz;
第 24 位,共 1 位:内存控制器 PLL 初始化:0 代表使用 1PLL 初始化,1 代表使用 3PLL 初始化。通常都使用 3PLL 模式;
第 20 位,共 1 位:内存时序参数的字节序,0 代表小端序,1 代表大端序;

举例:
内存频率 1200MHz,使用 3PLL,DDR 时序参数使用大端序:0x11000000;
内存频率 1066MHz,使用 3PLL,DDR 时序参数使用小端序:0x21100000。

3. DDR 时序参数
DDR2/DDR3 的时序参数是分开存放的,需要根据 DDR 的类型将时序参数写入不同的区域。
时序参数依照 DDR 的类型和容量而各有不同,如果选择的时序参数跟实际的 DDR 参数不同,那么可能导致 DDR 初始化失败
MTK U-Boot 提供了一些默认的参数,存放于 mt7621_ddr_param.txt 中。

时序参数写入 bin 文件的字节序受内存频率参数第 20 位的影响:
如果第 8 位为 0,那么 mt7621_ddr_param.txt 中的每项数据,都需要颠倒字节序后才能写入 bin 文件;
如果第 8 位为 1,那么 mt7621_ddr_param.txt 中的每项数据可以直接按顺序写入 bin 文件。

4. 串口波特率
mt7621_stage_sram.bin 在执行时会初始化串口,并输出相关信息,用于指明内存初始化是否完成,以及是否成功
波特率数值不能超过 115200,大多数时候,默认值是 57600。

举例:
波特率为 115200,十六进制数值为 0x0001c200;
波特率为 57600,十六进制数值为 0x0000e100。

[初始化内存]

在制作完成 mt7621_stage_sram.bin 后,就需要使用它初始化内存了。

首先初始化 SRAM:
在 OpenOCD 中依次执行:
mww 0xbe100004 1
sleep 1
mww 0xbe100004 6

这样就启用了 SRAM,其地址范围为 0xbe108000 - 0xbe10dfff。

然后加载 mt7621_stage_sram.bin 到 0xbe108800 处:
load_image mt7621_stage_sram.bin 0xbe108800

设置 mt7621_stage_sram.bin 执行参数为正常模式
mww 0xbe00001c 0

为了能够在 mt7621_stage_sram.bin 执行完毕后控制 CPU,防止跑飞,需要在 mt7621_stage_sram.bin 返回前中断 CPU。
mt7621_stage_sram.bin 在执行时会将返回地址存入 0xbe108000,在返回前从 0xbe108000 处读取返回地址。
因此可以在 0xbe108000 处设置一个 Word 类型 (4 字节) 的数据访问断点
wp 0xbe108000 4 r

执行 mt7621_stage_sram.bin 以初始化内存:
resume 0xbe108800

如果一切顺利,串口上将会有输出,并且会提示内存初始化是否完毕:
DRAM init Passed

如果初始化完毕,那么此时 OpenOCD 的 telnet 窗口上应该会有断点被触发的提示:
Data read breakpoint hit

然后删除断点:
rwp 0xbe108000

禁用 SRAM:
mww 0xbe100004 1
因为这里使用的是 Frame Engine 的 SRAM,只有在禁用 SRAM 后以太网模块才能正常工作。

设置 CM 的默认操作目标为内存:
mww 0xbfbf8008 0x1fbf8000

简要说明一下:
MIPS CM 能够管理的不仅仅是内存,还有 IOCU (I/O 一致性单元)。MT7621 复位后的 CM 默认操作目标是 IOCU1,这会导致通过总线 0x10000000 (0x80000000/0xa0000000) 无法访问到内存。
因此要修改 GCR_BASE 寄存器,使 CM 的默认操作目标改为内存。
因为缺少资料 (MD00597, MIPS® 1004K™ Coherent Processing System User's Manual),因此这里参考的是 MIPS 的培训资料:
MIPS CM GCR_BASE description

设置 OpenOCD 工作内存区域:
mt7621.0.cpu configure -work-area-phys 0xa0008000 -work-area-size 0x1000
如果执行此条命令后,后续的 load_image 执行出错,那么需要重新执行上述的各条命令,并且不再执行本命令

至此 MT7621 的初始化工作就完成了,之后就是加载 Bootloader 到内存并执行或者其它调试工作了。


[加载内存版 Bootloader]

在初始化内存后,就可以加载内存版 Bootloader 并执行了。当然这是在 Flash 数据损坏时才需要做的。

这里以专用的内存版 Breed 为例:

1. 加载 Breed 文件
load_image breed-mt7621-spi-mem-0xa0060000.bin 0xa0060000
这个专用版的 Breed 不会初始化其余的 Core 和 VPE,并且削减了一些在此状态下无法使用的功能。并且去掉了自动启动固件的功能,以方便直接进入 Web 恢复模式。
其加载和执行的地址均为 0xa0060000

2. 运行 Breed:
resume 0xa0060000

Load and run Breed

如果一切顺利,串口中将会看到 Breed 的输出:
Breed running in Memory

同时在浏览器中访问 192.168.1.1,也能打开 Breed 的页面:
Breed Web page

这个专用版 Breed 的一个特点是时钟频率的 DDR 部分会显示 Unknown,因为无法获取内存频率参数。


一点关于 OpenOCD 中多线程 (VPE) 调试的说明:
MIPS 手册中有明确指出,当一个 VPE 进入 Debug 模式时,其余的 VPE 会被冻结。
因此在 OpenOCD 中切换 TAP 时,需要注意,要保证当前没有 TAP 处于 Debug 模式,再进行切换 (targets 命令,例如 targets mt7621.1.cpu),否则后续的操作,例如 halt 等,都会失败。


关于 GDB 的使用,目前发现使用的 openocd-0.9.0 存在 BUG,因此暂时无法使用 GDB 进行调试。等以后有空了试试最新的版本。

用 Bus Blaster v4 调试 MT7621 路由器》有10个想法

  1. 你还在管理吗?我是从Github上过来的。
    有人说你的包可以让openwrt挂载上ntfs,我想试试
    请问你能不能教下我呢?

  2. Breed是您的作品是吧?我最近基于OpenWRT在做一些IoT产品。因为主要面向海外销售。固件可以切换到英语。但是Bootloader我也希望能够有英语版本。不知道Breed是开源的还是闭源的。在Github上找了一圈,发现有用Python客户端和JS脚本(也不知道如何在未联网情况下注入到Breed页面里,估计也是客户端)来间接实现的。不知道您有何建议?

  3. hiwifi4的Bootloader能否解锁或者替换成breed呢。我的半砖了,能否从他的Bootloader破解。如果可以,希望讨论。联系方式真实

发表评论

电子邮件地址不会被公开。 必填项已用*标注