Rigo的胡言乱语录

Rigo's Stupid Blogs Archives

View on GitHub

龙芯的官方 EJTAG 调试探头

硬件

龙芯 EJTAG 调试探头(下称 “调试器” )采用了一片 Cypress CY7C68013A 单片机作为 USB HS PHY,并配置成 FIFO 模式,将 FIFO 的读写接口拉出供 FPGA 使用;FPGA 采用了一片 Altera Cyclone-II EP2C5T144,上面实现了对被调试芯片 TAP 的操纵逻辑。

FPGA 上面载有一个 MIPS789 软核,并配有 8KiB 的固件,以此实现 USB 通讯、指令包解析、TAP 控制逻辑等任务。

实现细节

JTAG 时钟配置寄存器 0x81000070

FPGA 上的 MIPS 软核的总线上可以访问 FPGA 上的一些逻辑电路的寄存器,其中 JTAG 时钟配置就是通过使 MIPS 软核写一个寄存器实现的。这个寄存器在 MIPS 软核地址空间中的 0x81000070 地址处,向此地址写入 4 字节宽的整数以调节 JTAG 时钟分频和采样 TDO 时机。

(文档中和程序帮助中写的是 TDI 采样时机,但调试器需要采样的应当只有 TDO,我认为应当是笔误)

寄存器位域 读/写 含义
[31:16] 写,读出为0 此字段为 0x1 时,低半段表示分频系数;
此字段为 0x2 时,低半段表示 TDO 采样时机。
其他值将被硬件忽略。
[15:0] 写,读出为0 表示分频系数 CLKDIV 时,原则上只允许此字段中有 1 个被置位的位(测试中发现如果有多个位被置位,则除最高的那个被置 1 的位以外的位均被视为 0)。此时 JTAG 端口的时钟频率为 $ f_{JTAG} = {15 \over CLKDIV}\text{MHz} $。如果 CLKDIV 被设为 0,JTAG 端口的时钟将停止。

表示 TDO 采样时机 SAMPLESEL 时,仅最低 2 位([1:0])有效,因此有效的取值为 0、1、2、3 ;当更大的值被写入时,高位将被硬件所忽略。依照 JTAG 时序,第 n 位输入在 CLK 上升沿被从 TDI 读入时,第 n - 1 位才被从 TDO 移出,因此标准的 TDO 采样时机总是位于滞后于输入数据采样时机 1 个周期的时间点;然而由于信号完整性问题或者芯片设计本身导致等原因,可能需要微调采样时机来获得正确的 TDO 读出值。经初步实验验证,当 SAMPLESEL 为 0 时采样时机提前,为 3 时采样时机滞后,且都最大不超过 TCK 一周期的时间。结合配置文件中基本都会将采样时机配置为 1,可以认为 SAMPLESEL 设为 1 时代表使用标准的采样时机。

TAP 参数变量指针

在此版本的调试器固件中,不再写死所使用的 IR 指令、IR 长度等信息(因为它们在不同的硬件、不同的架构如 LoongArch 和 MIPS 中都不相同,甚至不排除龙芯以后继续更改的可能性),而是使用全局变量保存;同时,地址空间的首部放置了指向这些全局变量的指针,这样可以通过上位机软件根据选定的芯片型号修改这些参数。即使全局变量的地址改变,也可以通过这些固定位置(入口点程序由汇编编写的话,很方便就能做到不影响这些指针所处位置)的指针确定需要修改的变量位于哪里。

所有指针指向的数据均为 uint32_t ,修改时直接使用下文 0x01 USB 指令即可。

指针所处地址 含义
0x40 指向目标芯片 IR 寄存器长度变量
0x44 指向应使用的 EJTAG SKIP 指令码
0x48 指向应使用的 EJTAG CONTROL 指令码
0x4c 指向应使用的 EJTAG DATA 指令码
0x50 指向应使用的 EJTAG ADDRESS 指令码
0x54 指向应使用的 EJTAG FASTDATA 指令码

例如,上位机命令如 usblooptest ${usblooptest 0x44} 0x0 就等于告诉调试器,之后如果要选中目标芯片 TAP 上的 Skip 寄存器,那么应当向 IR 中写入 0x0。(示例来自上位机软件中附带的 scripts/gdb-la.cmd 文件)

USB 通信

描述符

调试器连接上电脑后是一个 Vendor Class 自定义设备,属于 USB 2.0 HS 设备,VID=0x2961(占了 Miselu 公司的位子),PID=0x6688 。

使用两个 USB 端点(Endpoint)进行 Bulk 传输:端点 2 为 OUT,端点 6 为 IN。

通信协议

上位机(la_dbg_tool_usb等程序,下同)与调试器遵守基本数据格式约定如下:

每次向调试器发送的字节流中含有一个或多个指令包。上位机会期待有的指令包被执行后,调试器向上位机回复一些回读的数据(否则上位机将无穷地等待下去,且协议中不存在超时重传、错误处理的部分)。一次发送了多个指令包时,调试器将顺序执行、按执行顺序返回数据。

包的一般结构如下:

偏移量 长度 含义
[9:0] 10 位 每种指令特定的配置(下称 “配置”
[15:10] 6 位 指令码
[…:16] 任意 负荷数据(可选)

下面列出的指令码的定义来源于对 FPGA 固件、上位机程序的逆向工程与早年间旧版本 EJTAG 上位机中附带的部分头文件中发掘的信息。

理论上可以依据这些信息制作与官方的调试上位机相兼容的调试探头,但是需要注意的是,官方上位机存在使用 usblooptest 指令覆盖远古版本(准确地说是 2015 年的某个版本)固件中的代码的行为,这使得仅仅依据某个版本固件的逆向工程成果制作的兼容探头或许无法在将来使用。

注:下列定义来源于对一个固件版本为 0x20210129 的调试器的固件的逆向工程。使用了 Ghidra。部分内容同时参考了上位机程序的逆向工程。

快速读写数据格式

假设有以下场景:用户使用快速数据读/写指令欲读/写 32 个 u64 的内存数据,被调试的处理器字长为 64 位,拥有 4 个 CPU 核心,配置文件指定使用 FASTDATA,且用户指定使用 CPU2 进行读/写。那么在 USB 上传输的数据流,结构将如下所示:

                                     ┌───Padding for aligning to 32-bit words                                   
                                     │                                                                          
              ┌──────────────────────┴─────────────────────┐                          CPU0 SKIP───────┐         
              │                                            │  ┌───CPU2 SPrAcc         CPU1 SKIP────┐  │         
              │                                            │  ▼                                    ▼  ▼         
             ┌──┬──┬──┬──┬──┬──┬───────┬──┬──┬──┬──┬──┬──┬──┬──┬─────────────────────────────────┬──┬──┐        
       For   │  │  │  │  │  │  │       │  │  │  │  │  │  │  │  │  64-bit machine word from CPU2  │  │  │        
     Machine ├──┴──┴──┴──┴──┴──┴───────┴──┴──┼──┴──┴──┴──┴──┴──┴─────┬───────────────────────────┴──┴──┼───────┐
      Word   │5F 5E 5D 5C 5B 5A ... ... 49 48│47 46 45 44 43 42 41 40│3F 3E           05 04 03 02 01 00│ Bits  │
        0    ├─────────────────────┬─────────┼───────────────────────┼───────────┬─────────────────────┼───────┤
             │          0B      ...│...   09 │         08            │  07   ... │ ...      00         │ Bytes │
             └─────────────────────┴─────────┴───────────────────────┴───────────┴─────────────────────┴───────┘
                                                                                                                
             ┌──┬──┬──┬──┬──┬──┬───────┬──┬──┬──┬──┬──┬──┬──┬──┬─────────────────────────────────┬──┬──┐        
       For   │  │  │  │  │  │  │       │  │  │  │  │  │  │  │  │  64-bit machine word from CPU2  │  │  │        
     Machine ├──┴──┴──┴──┴──┴──┴───────┴──┴──┼──┴──┴──┴──┴──┴──┴─────┬───────────────────────────┴──┴──┼───────┐
      Word   │5F 5E 5D 5C 5B 5A ... ... 49 48│47 46 45 44 43 42 41 40│3F 3E           05 04 03 02 01 00│ Bits  │
        1    ├─────────────────────┬─────────┼───────────────────────┼───────────┬─────────────────────┼───────┤
             │          17      ...│...   15 │         14            │  13   ... │ ...      0C         │ Bytes │
             └─────────────────────┴─────────┴───────────────────────┴───────────┴─────────────────────┴───────┘
                                                                                                                
                                                                      ......                                    
                                                                                                                
             ┌──┬──┬──┬──┬──┬──┬───────┬──┬──┬──┬──┬──┬──┬──┬──┬─────────────────────────────────┬──┬──┐        
       For   │  │  │  │  │  │  │       │  │  │  │  │  │  │  │  │  64-bit machine word from CPU2  │  │  │        
     Machine ├──┴──┴──┴──┴──┴──┴───────┴──┴──┼──┴──┴──┴──┴──┴──┴─────┬───────────────────────────┴──┴──┼───────┐
      Word   │5F 5E 5D 5C 5B 5A ... ... 49 48│47 46 45 44 43 42 41 40│3F 3E           05 04 03 02 01 00│ Bits  │
       31    ├─────────────────────┬─────────┼───────────────────────┼───────────┬─────────────────────┼───────┤
             │         17F      ...│...  17D │        17C            │  17B  ... │ ...     174         │ Bytes │
             └─────────────────────┴─────────┴───────────────────────┴───────────┴─────────────────────┴───────┘

上位机与探头间传输的其实并不是原始数据本身,而是 DR 串的数值:在使用 FASTDATA 时,是 IR 选择 FASTDATA 而其他 CPU 选择为 SKIP 时的整个 DR 串;在不使用 FASTDATA 时,是目标 CPU IR 选择为 DATA 而其他 CPU 选择为 SKIP 时的整个 DR 串。DR 串本身的长度一般是 (处理器字长 + CPU个数 - 1 + (使用 FASTWRITE ? 1 : 0)) 个比特(多核 CPU 要考虑其他 CPU 调到 SKIP 时的那一个空闲位,使用 FASTWRITE 时要考虑 SPrAcc 的长度)。每一个内存读/写操作对应一个 DR 串。每个 DR 串占据的空间会向上对齐到 32 位(比如 65 位长的 DR 串会占据 12 个字节)。