大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是 i.MXRTxxx 系列 ROM API 设计细节。
痞子衡之前写过两篇文章 《利用 i.MXRT1xxx 系列 ROM 提供的 FlexSPI driver API 可轻松 IAP》、《其实 i.MXRT1050,1020,1015 系列 ROM 也提供了 FlexSPI driver API》 基本把 i.MXRT1xxx 全系列的 ROM API 及其 FlexSPI NOR 驱动设计都讲清楚了,其实 i.MXRTxxx 系列的 ROM API 设计跟 i.MXRT1xxx 系列的设计思路差不多(其实本就是同一个恩智浦研发小组负责的),仅有一些微小区别,本文痞子衡主要就是点出那些区别。
一、ROM 基址差异
ROM API 代码首先是在 BootROM 里,BootROM 代码是出厂前固化在 ROM 区域的。因为架构设计的关系,i.MXRTxxx 系列和 i.MXRT1xxx 系列的 ROM 区域在系统内存里的映射地址不同。
下表是 i.MXRTxxx 系列代表型号 i.MXRT500 的部分系统内存映射,可以看到 ROM 区域起始地址是 0x03000000(非安全域)。目前 i.MXRTxxx 都是 Cortex-M33 内核,支持 TrustZone 特性,所以 0x13000000 也是 ROM 起始地址(安全域),为了通用性,我们认 0x03000000 就可以了,这个地址在安全状态和非安全状态下都能被访问。
下表是 i.MXRT1xxx 系列代表型号 i.MXRT1060 的部分系统内存映射,可以看到 ROM 区域起始地址是 0x00200000。i.MXRT1xxx 系列都是 Cortex-M7 内核,没有 TrustZone 特性,不存在 i.MXRTxxx 上那样的两种状态域下的地址。
二、API 基址备份位置差异
在 i.MXRT1xxx 系列 ROM API 介绍的文章里,痞子衡介绍过 g_bootloaderTree 地址值被复制了一份放在了 BootROM 中断向量表第 8 个向量的位置处(该向量为 ARMv7-M 架构下未定义的系统向量),因此读取 0x0020001c 处开始的 4bytes 便能找到 i.MXRT1xxx 系列的 g_bootloaderTree。
但是由于 i.MXRTxxx 是 Cortex-M33 内核,属于 ARMv8-M 架构,从下图中可以看出 ARMv8-M 架构下中断向量表第 8 个向量是 SecureFault,已经被定义了,因此 BootROM 把 g_bootloaderTree 地址值放到了第 9 个向量的位置处(该向量为 ARMv8-M 架构下未定义的系统向量),故读取 0x03000020 处开始的 4bytes 才能找到 i.MXRTxxx 系列的 g_bootloaderTree(这种方式在实际 API 调用中并不可取,至于原因嘛,先卖个关子)。
下面是 i.MXRT500 BootROM 工程的 startup 文件(IAR 版),g_bootloaderTree 确实在第 9 个向量处:
MODULE ?cstartup
;; Forward declaration of sections.
SECTION CSTACK:DATA:NOROOT(3)
SECTION .intvec:CODE:NOROOT(2)
EXTERN __iar_program_start
EXTERN g_bootloaderTree
PUBLIC __vector_table
PUBLIC __vector_table_0x1c
DATA
__vector_table
DCD sfe(CSTACK)
DCD Reset_Handler
DCD DefaultISR
DCD HardFault_Handler
DCD DefaultISR
DCD DefaultISR
DCD UsageFault_Handler
__vector_table_0x1c
DCD SecureFault_Handler
DCD g_bootloaderTree
DCD 0
DCD 0
DCD SVC_Handler
DCD DefaultISR
DCD 0
DCD DefaultISR
DCD SysTick_Handler
;; ...
三、API 原型定义差异
下面是 i.MXRTxxx 系列 ROM API 原型定义及其实例(适用 i.MXRT500/600),基本形式跟 i.MXRT1xxx 差不多,但是 API 功能更丰富,除了 FlexSPI NOR 驱动,还有 iap api、USB low-level driver、otp driver 等(我们知道,i.MXRTxxx 与 LPC 系列同根同源,LPC 系列 ROM 里一般都会集成很多经典 SDK 驱动,比如内部 flash、low power 驱动,有了这些稳定的驱动 API,LPC 系列的用户手册里甚至都会省去这些 IP 的寄存器介绍,直接就是 API 的介绍)。
typedef struct
{
void (*runBootloader)(void *arg);
uint32_t version;
const char *copyright;
const bootloader_context_t *runtimeContext;
const kb_interface_t *kbApi;
const usb_driver_interface_t *usbDriver;
const USBD_API_T *lpcUsbDriver;
const flexspi_nor_flash_driver_t *flexspiNorDriver;
const ocotp_driver_t *otpDriver;
const skboot_authenticate_interface_t *skbootAuthenticate;
} bootloader_api_entry_t;
//! @brief Static API tree.
__root const bootloader_api_entry_t g_bootloaderTree @".rom_api_tree_section" = {
.runBootloader = bootloader_user_entry,
.version = MAKE_VERSION('K', 3, 0, 0),
.copyright = "Copyright 2019 NXP.",
.runtimeContext = &g_bootloaderContext,
.kbApi = &g_romApiInterface,
.usbDriver = &g_usbDriverInterface,
.flexspiNorDriver = &g_flexspiNorFlashDriverInterface,
.otpDriver = &g_otpDriverInterface,
.skbootAuthenticate = &g_skbootAuthenticateInterface,
};
四、API 实例链接差异
i.MXRT1xxx 系列 ROM API 实例 g_bootloaderTree 都是让链接器自由链接的,因此每个具体型号的实际 ROM API 链接地址没有一致的规律可循(这也是为什么要在中断向量表里固定位置统一保存一份),而这点在 i.MXRTxxx 上有了改进,i.MXRTxxx 里将 g_bootloaderTree 放到了 .rom_api_tree_section 段里,在链接文件里将该段固定链接在 ROM 区域最后 4KB 处(BootROM 代码没有把全部 ROM 空间用尽)。
下面是 i.MXRTxxx BootROM 源文件中 g_bootloaderTree 的定义,加了段修饰。此外还有额外的 k_romcrc,标示 API 实例区域的结束。
__root const bootloader_api_entry_t g_bootloaderTree @".rom_api_tree_section" = {
.runBootloader = bootloader_user_entry,
.version = MAKE_VERSION('K', 3, 0, 0),
.copyright = "Copyright 2019 NXP.",
.runtimeContext = &g_bootloaderContext,
// ...
};
__root const uint32_t k_romcrc @".romcrc" = 0xdeadbeef;
下面是 i.MXRTxxx 链接文件(IAR 工程)中 .rom_api_tree_section 段的处理(i.MXRT500 型号示例,ROM 空间是 192KB)。你可能好奇为啥 ROM_API_TREE_xx 等值是放在 0x13000000 开始的安全域 ROM 空间映射,BootROM 属于上电启动第一级,负责芯片系统的安全和启动,当然是工作在安全状态下,可以访问安全域地址空间。
define symbol __ICFEDIT_region_ROM_API_TREE_start__ = 0x1302f000;
define symbol __ICFEDIT_region_ROM_API_TREE_end__ = 0x1302f0ff;
define symbol __ICFEDIT_region_ROM_CRC_CHECKSUM_start__ = 0x1302fffc;
define symbol __ICFEDIT_region_ROM_CRC_CHECKSUM_end__ = 0x1302ffff;
define region ROM_API_TREE_region = mem:[from __ICFEDIT_region_ROM_API_TREE_start__ to __ICFEDIT_region_ROM_API_TREE_end__];
define region ROM_CRC_CHECKSUM = mem:[from __ICFEDIT_region_ROM_CRC_CHECKSUM_start__ to __ICFEDIT_region_ROM_CRC_CHECKSUM_end__];
place in ROM_API_TREE_region { section .rom_api_tree_section };
place in ROM_CRC_CHECKSUM { section .romcrc };
基于上面的设计,你才会在 i.MXRT500 参考手册里 Non-Secure Boot ROM 章节看到如下 ROM API 地址及结构信息图(图中仅标了常用的 API 功能函数),实际 ROM API 调用时,App 的执行其实都是经过 ROM 引导和认证的,App 中既可以访问安全域地址(0x1302f000)来调用 API,也可以访问非安全域地址(0x0302f000)来调用 API。
最后再来回答前面卖的关子,为什么 i.MXRTxxx 系列通过 BootROM 中断向量表第 9 个向量值来访问 ROM API 这种方式并不可取?其实从 BootROM 煞费苦心地将 g_bootloaderTree 固定链接在 ROM 区域最后 4KB 处,你就能看出其用意。如果你挂上调试器直接访问 i.MXRTxxx 的 ROM 区域前 20KB 的空间,你会发现无法访问,在 App 里 AHB 方式读这个区域,也会直接产生 HardFault,因为 BootROM 里做了特殊设计故意隐藏了前 20KB 空间,这个空间里存放了 BootROM 想要保护的数据和代码,至于内容是啥,纯属机密,恕不奉告,哈哈。
至此,i.MXRTxxx 系列 ROM API 设计细节痞子衡便介绍完毕了,掌声在哪里~~~