大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是 i.MXRT600 中的 Debug Mailbox 实现对 JLink 调试的影响。
事情缘起痞子衡的同事 - 喜欢打破砂锅问到底的 Kerry 小姐姐,她最近在研究 i.MXRT600 这款芯片,她发现在芯片 ROM 串行下载(ISP)模式下,连上芯片 USB 端口可以在设备管理器中正常看到枚举的 HID 设备(0x1fc9,0x0020),这个 HID 设备可配合上位机工具 blhost.exe 进行应用程序下载。但是当使用 JLink 正常连上芯片(选择的是 MIMXRT685,不是 CM33)后,之前的那个 HID 设备不见了,看起来芯片像是退出了 ROM 正常运行,这个体验跟 i.MXRT1050 上不太一样,这是为什么?这其实是 Debug Mailbox 在捣鬼,且听痞子衡细聊:
一、引出调试问题
按照我们之前在 i.MXRT1050 上的调试经验,将芯片设为串行下载模式后,使用 JLink 连接上芯片,并 halt 住内核,此时芯片 PC 是正常停在 ROM 区域的(0x200000 之后),让我们同样的过程在 i.MXRT600 上也操作一次:
我们发现 PC 指向了 0x1c04a,并且不管你如何 reset 再重新 halt,它一直停在这个地方,更奇怪的是这个地方不在 ROM 区域(0x03000000 或 0x13000000 之后)里,这是怎么回事?
二、什么是 Debug Mailbox?
与 i.MXRT1050 不同的是,i.MXRT600 中引入了 Debug Mailbox 机制,这个机制由 ROM 负责实现,因此连接上 JLink 后的行为是由 Debug Mailbox 机制决定的。
翻开 i.MXRT600 的 User Manual,在 Debug subsystem 这一章节可以找到 Debug Mailbox 相关信息,Debug Mailbox 其实最早是 NXP LPC 系列新推的一项功能,后来也用在了 i.MXRT600 上面。
下图是 i.MXRT600 的 SWD 调试系统内部连接图,其中蓝框标出的 DM AP 便是 Debug Mailbox。
我们知道 i.MXRT600 是基于 Cortex-M33 内核的 MCU,这款 ARM 内核主打特点是安全,因此 NXP 在设计芯片时也加入了很多安全方面的特性,Debug Mailbox 便是其一,Debug Mailbox 基于 NXP debug authentication protocol version 1.0,主要作用是实现外部调试器与芯片内 ROM 的通信,从而赋予调试器擦写 Flash、进入 ROM ISP、调试认证等功能。
三、ROM 中 Debug Mailbox 实现
那么 ROM 中的 Debug Mailbox 机制到底是什么?简单理解就是如下一段代码插入了 ROM 的初始化流程。这个机制其实很简单,就是确保 debug 特性是正常开启的,然后根据芯片复位类型来初始化 debug port 并决定要不要进入 Mailbox 命令处理。
// 确认 IFR 里 debug 特性正常开启
if (kStatus_DBG_Success != debug_auth_evaluate_dcfg_socu())
{
__set_FAULTMASK(1);
__WFI();
}
volatile uint32_t reset_status = RSTCTRL0->SYSRSTSTAT;
// 根据复位类型设置初始 debug port 状态
if (kStatus_DBG_Success != debug_auth_hal_set_initial_debug_port_state(reset_status))
{
__set_FAULTMASK(1);
__WFI();
}
if (reset_status & 0x20)
{
// 处理 mailbox 收到的来自 debugger 的命令
debug_mailbox_GetRequest();
}
RSTCTRL0->SYSRSTSTAT[5]位即 ARM_APD_RESET,表明 ARM 内核是否发生了软复位(warm reset),正常芯片上电,这个位不会被置 1,但是如果有调试器接入给内核发软复位,这个位就会被置位。一旦这个位被置起来,ROM 初始化过程中便会进入 Mailbox 命令处理函数 debug_mailbox_GetRequest(),不再往后执行正常的 ROM 串行下载 / 启动流程。
debug_mailbox_GetRequest()函数是 Debug Mailbox 机制的核心,它借助的是如下三个 Mailbox 寄存器来实现调试器与 ROM 的互动。
- CSW:命令状态寄存器,调试器操作这个寄存器指示 ROM 进入 mailbox 命令解析状态 REQUEST:请求寄存器,调试器将 mailbox 命令写入这个寄存器指示 ROM 去执行 RETURN:结果寄存器,调试器通过读这个寄存器获取 ROM 执行 mailbox 命令结果
对于使用者来说,一般借助调试器先向 CSW 寄存器写入 0x21 申请 re-sync 同时 reset device ,然后再按需写入如下具体的命令进 REQUEST 寄存器,便可实现 Debug Mailbox 相应功能。
#define ENTER_DEBUGGER_MAILBOX (0x0001) // Start Mailbox debug
#define GET_CRP_LEVEL (0x0002) // Deprecated and retuen 3
#define DM_ERASE_FLASH (0x0003) // Mass erase flash
#define EXIT_DEBUGGER_MAILBOX (0x0004) // Exit Mailbox debug
#define ENTER_ISP_MODE (0x0005) // Enter specified ISP mode
#define SET_FA_MODE (0x0006) // Set to "Fault Analysis" mode
#define START_DEBUG_SESSION (0x0007) // Start Debug session
#define DEBUG_AUTH_START (0x0010) // Start Debug Authentication Protocol
#define DEBUG_AUTH_RESP (0x0011) // Debug Authentication response
四、激活 Debug Mailbox 的 JLink Script
了解了 Debug Mailbox 机制原理,我们再来看 JLink 连接 i.MXRT600 时必须要加载执行的如下 Script 内容(开头痞子衡说了必须选择 MIMXRT685,而不是 CM33,因为在 JLink DLL / JLinkDevices.xml 里 MIMXRT685 才默认指定了配套 Script 脚本)。关于 JLink Script 知识,可以先看痞子衡之前文章 《JLink Script 文件基础及其在 IAR 下调用方法》。
这个脚本内容其实在 i.MXRT600 的 User Manual 中已经给出了相应伪代码,通过调用 JLINK_CORESIGHT_WriteAP()来写 Mailbox 寄存器(index 0 对应 CSW,index 1 对应 REQUEST),基本是按照前面介绍的 Debug Mailbox 使用流程来的,最后通过写入 START_DEBUG_SESSION 命令进 REQUEST 寄存器开启芯片调试模式。
void InitTarget(void) {
int v;
JLINK_CORESIGHT_Configure("IRPre=0;DRPre=0;IRPost=0;DRPost=0;IRLenDevice=4");
// Pre-select that we have a Cortex-M33 connected
CPU = CORTEX_M33;
// J-Link is allowed to use a TAP reset for JTAG-chain auto-detection
JTAG_AllowTAPReset = 0;
JTAG_SetDeviceId(0, 0x6BA02477);
// Read AP ID register to identify DM AP at index 2
JLINK_CORESIGHT_WriteDP(2, 0x020000f0);
v = JLINK_CORESIGHT_ReadAP(3);
JLINK_SYS_Report1("DAP-IDCODE:", v);
// Select DM AP index 2
JLINK_CORESIGHT_WriteDP(2, 0x02000000);
JLINK_CORESIGHT_ReadDP(0);
// Active DebugMailbox
JLINK_CORESIGHT_WriteAP(0, 0x21);
JLINK_CORESIGHT_ReadAP(0);
// Enter Debug Session
JLINK_CORESIGHT_WriteAP(1, 0x07);
JLINK_CORESIGHT_ReadAP(0);
}
五、芯片调试模式(REQUEST = 0x07)下的状态
前面讲了 JLink Script 会使芯片进入调试模式,那调试模式下芯片到底是什么状态?ROM 其实是通过如下函数加载执行了 0x1c040 - 0x1c04B 处的一小段代码,并最终停在了 0x1c04a 处的 while(1);,至此真相大白。
void go_debug_mode(void)
{
#define VECTOR_DUMMY_ROUTINE 0x0001c000u
#define APP_ENTRY (VECTOR_DUMMY_ROUTINE + 0x40 + 1)
uint32_t dummy_loop_routines[] = {
VECTOR_DUMMY_ROUTINE + 0x1000, // SP
APP_ENTRY, // Reset Handler
APP_ENTRY, // NMI Handler
APP_ENTRY, // HardFault_Handler
APP_ENTRY, // MemManage_Handler
APP_ENTRY, // BusFault_Handler
APP_ENTRY, // UsageFault_Handler
APP_ENTRY, // SecureFault_Handler
0, // Reserved
0, // Reserved
0, // Reserved
APP_ENTRY, // SVC_Handler
APP_ENTRY, // DebugMon_Handler
0, // Reserved
APP_ENTRY, // PendSV_Handler
APP_ENTRY, // SysTick_Handler
// Below are the binary codes for :
// register uint32_t dummy = SCB->CPUID;
// while(1);
0x5000f64eu,
0x0000f2ceu,
0xe7fe6801u,
};
{
uint32_t *dest = (uint32_t *)VECTOR_DUMMY_ROUTINE;
uint32_t *src = (uint32_t *)&dummy_loop_routines[0];
for (uint32_t i = 0u; i < ARRAY_SIZE(dummy_loop_routines); i++)
{
*dest++ = *src++;
}
jump_to_boot_image(VECTOR_DUMMY_ROUTINE);
}
}
六、Debug Mailbox 对 JLink 调试的影响
基于上面分析,最后痞子衡再总结一下 Debug Mailbox 对 JLink 调试的影响:
- 当芯片在 ROM 中执行(比如 ISP 模式,比如 Flash 中没有应用程序)时,JLink 要想正常连接,必须加载使能芯片调试模式的 Script 才行,否则会连不上芯片。通过加载执行 JLink Script 成功连接上芯片后,PC 总是停在 0x1c04a,这是 Debug Mailbox 机制决定的。只有当芯片正常启动 Flash 里的应用程序后(即离开了 ROM),用 JLink 连接芯片(选择 CM33,不加载 Script),halt 住内核,PC 指向的才是真实的应用程序位置。
至此,i.MXRT600 中的 Debug Mailbox 实现对 JLink 调试的影响痞子衡便介绍完毕了,掌声在哪里~~~