大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是i.MXRT500/600上串行NOR Flash双程序可交替启动设计。
在上一篇文章 《i.MXRT1170上串行NOR Flash双程序可交替启动设计》 里,痞子衡详细介绍了 i.MXRT11xx 系列上的双程序启动设计,本质上其就是在双备份程序启动基础上增加了 image 版本控制,所以两份 image 可以按版本优先级来灵活选择启动,而不是死板地靠物理地址高低来定启动顺序。
i.MXRTxxx 系列上(RT500/600)也有双程序可交替启动特性,其主体设计逻辑基本上跟 i.MXRT1170 是差不多的,只是一些细节处略有差异(比如可启动 image 结构不同、otp 配置地址不同、签名实现不同、非易失性寄存器暂存状态设计不同、image 版本判断逻辑略有不同等),除此之外 i.MXRTxxx 上在验证 image 完整性方面除了签名外,还有一种相对平民化的 CRC32 校验可供选择,这也是今天本文要介绍的重点:
一、与 i.MXRT11xx 系列双程序启动细节差异
本文不打算从头开始完整介绍 i.MXRTxxx 上双程序可交替启动特性,这里只讲和 i.MXRT11xx 上的差异点,其余流程直接参考 《i.MXRT1170上串行NOR Flash双程序可交替启动设计》 一文。
1.1 恢复启动的接口外设不同
第一点不同其实与本文要讨论的 FlexSPI 双程序启动特性无关,因为我们要聊的还是在一片挂载在 FlexSPI 上的串行 NOR Flash 里做双程序设计,就是下图中的 image 0 和 image 1,不涉及 Flexcomm SPI 接口 Flash B 里的 image 2(在 i.MXRT1170 上这个外设是 LPSPI)。
在介绍 i.MXRT1170 双程序启动一文里我们用了 image L/H 来表示 image 0/1,这里还是恢复使用 image 0/1 来表示,因为后面我们要看的 i.MXRT500/600 参考手册启动流程图里就是用 image 0/1 来表达的,避免表达混乱。
1.2 可启动 image 结构不同
i.MXRT1170 上最简易可启动 image 结构比较复杂(包含 FDCB、img_ver、IVT、BD、App),而 i.MXRTxxx 上就比较简单了(仅需 FDCB、img_ver、App),但是好在两者关于 image version 头结构定义以及偏移位置是完全一致的(0x600)。
typedef struct
{
uint16_t version; // 版本值
uint16_t inversion; // version值的取反(~version)
} img_ver_t;
此外 i.MXRT1170 上第一个 FlexSPI 的 AHB 映射地址是 0x3000_0000,而 i.MXRTxxx 上第一个 FlexSPI 的 AHB 映射地址是 0x0800_0000,这是系统设计差异,需要注意。
- Note:下图中示意地址均是 Flash 偏移地址,没有包含 AHB 映射地址,另外这里假设第二份 image 偏移地址在 0x400000(具体是由 otp 配置值来决定的)。
1.3 使能双程序启动的otp配置地址不同
i.MXRTxxx 上关于使能双程序启动的 otp 配置定义与 i.MXRT1170 上是一致的,只是因为两者 otp 空间设计不同,所以具体配置地址不同。i.MXRTxxx 上具体配置在 otp BOOT_CFG2/3 上面:
Remap功能的ADDR_START寄存器固定设为 Flash 起始映射地址。
otp 0x188[31:28] - FlexSPI remap size, App的最大长度,标识了第一份App的结束地址,该值加上ADDR_START后被填入Remap功能的ADDR_END寄存器。
otp 0x18C[31:22] - Second image offset,标识了第二份App的起始地址(在Flash中偏移位置),即填入Remap功能的ADDR_OFFSET寄存器的值。
这次我们要在 MIMXRT595-EVK 板卡上实测,这个板子 FlexSPI0 上挂了两片 Flash,默认连接的 64MB OctalFlash,还有一片 8MB QuadSPI Flash(需要做板子改动才能使能)。为了跟之前测试保持一致,还是借助 MCUBootUtility 工具将 Second image offset 烧录为 0x10,FlexSPI remap size 保持默认 0,即第二份 image 偏移地址在 Flash 0x400000(4MB)处,最大 image 长度也是 4MB。
1.4 暂存状态的非易失寄存器有差异
i.MXRT1170 是用非易失寄存器 SRC_GPR10 其中 2bit 来记录当前启动状态的,而 i.MXRTxxx 上则复杂得多,它采用了 SYSCTL0 外设里的一个非易失寄存器的全部 32bit 来暂存启动状态。
// Load redundant boot options stored in specific register
#define LOAD_REDUNDANT_BOOT_OPTIONS() (*(volatile uint32_t *)(SYSCTL0_BASE + 0x384))
// Store redundant boot options in specific register before system reset
#define SET_REDUNDANT_BOOT_OPTIONS(val) ((*(volatile uint32_t *)(SYSCTL0_BASE + 0x384)) = val)
这个 32bit 寄存器功能原型如下,它不单纯是用做双程序启动的状态记录了,还糅合了 ROM API 功能。具体用法可以在芯片参考手册 ROM API 小节找到,这里不具体展开了,不是本文重点。
typedef struct _user_app_boot_invoke_option
{
union
{
struct
{
uint32_t reserved : 8;
uint32_t boot_image_index : 4;
uint32_t instance : 4;
uint32_t boot_interface : 4;
uint32_t mode : 4;
uint32_t tag : 8;
} B;
uint32_t U;
} option;
} user_app_boot_invoke_option_t;
1.5 image 版本判断逻辑不同
在 i.MXRT1170 上 image version 头有效的条件一定是其高低16bit符合取反关系,而 i.MXRTxxx 上除了这个条件外,其认定 0xFFFFFFFF 也是一个有效版本(被定为最低版本)。
芯片参考手册里有比较详细的 version 判断逻辑如下,这个逻辑跟 i.MXRT1170 上差异还是比较大的,i.MXRTxxx 上 BootROM 只会启动包含有效版本号的 image,版本有效性是 image 能被启动的一个必要条件,不像 i.MXRT1170 上版本信息只是单纯用来判断启动顺序,不作为 image 是否有效的标准。
痞子衡在 MIMXRT595-EVK 开发板上对 image 版本设置情况也做了比较全面的实测,测试结果如下:
image 0 版本值 | image 1 版本值 | 启动结果 |
---|---|---|
0xFFFFAA55 (或其他无效版本值) |
0xFFFFAA55 (或其他无效版本值) |
不启动任何 image |
0xFFFFAA55 (或其他无效版本值) |
0xFFFE0001 (或其他任意有效版本值包含全0xFFs) |
仅启动 image 1 |
0xFFFFAA55 (或其他无效版本值) |
0xFFFF0000 | 会误先启动 image 0 失败再启动 image 1 |
0xFFFFFFFF | 0xFFFFFFFF | 先启动 image 0 失败再启动 image 1 |
0xFFFFFFFF | 0xFFFF0000 (或其他高于0x0的有效版本值) |
先启动 image 1 失败再启动 image 0 |
0xFFFE0001 | 0xFFFE0001或0xFFFF0000或者全0xFFs | 先启动 image 0 失败再启动 image 1 |
二、测试CRC32校验双程序启动
现在来到本文的重头戏了,如何使能 image 的 CRC32 检验启动?这个设计其实最早可追溯到 Kinetis 系列,痞子衡有一篇旧文 《Kinetis BOOT特性(完整性检测)》,文章很详细地介绍了 Kinetis 系列 BootROM 里是如何支持 CRC32 校验的。
2.1 启动头CRC32参数存储位置
i.MXRTxxx BootROM 关于 CRC32 校验的设计与 Kinetis 非常类似,最大的区别就在于存储 CRC32 三大参数(起始地址,校验长度,校验值)的位置。i.MXRTxxx 上也是放在了 App 默认中断向量表里的保留空间里(offset 0x20, 0x28, 0x34),共 12 个字节。
offset 0x34 - imageLoadAddress: App加载后中断向量表首地址,也决定CRC校验起始地址
- 对于 XIP image,一般固定为 0x08001000(App无需加载)
- 对于 Non-XIP image,App加载前存储起始地址是 0x08001000,加载后到指定链接的 RAM 地址,CRC计算和校验是发生在App加载后。
offset 0x20 - imageLength: 决定CRC校验总长度,一般是 App 的长度(从中断向量表首地址开始到代码体结束)
offset 0x28 - crcChecksum: CRC校验值,[imageLoadAddress : imageLoadAddress + imageLength] 范围内数据的正确 CRC32 结果
2.2 使能CRC32校验的条件
当 App 默认中断向量表里 offset 0x24 处的 imageType[7:0] 类型为 0x02 或者 0x05,且 offset 0x20 处的 imageLength 不为 0 时,CRC32 校验的功能就会被使能。BootROM 在做 CRC32 计算时主要有如下两个注意事项:
- Note 1: 指定的CRC计算范围如果包含crcChecksum这4bytes的话,在计算CRC时会自动跳过这4bytes。Note 2: 指定的CRC计算长度如果不是4字节对齐,CRC数据计算到最后会自动补0对齐。
2.3 具体CRC32算法选项
关于 CRC32 算法的具体实现有很多分支,BootROM 中使用的比较主流的 MPEG2 分支,其在计算 image 具体 CRC 时主要借助了芯片内部的 CRC 模块(这个模块也常见于恩智浦 LPC 系列芯片上),这个 CRC 模块支持三种固定的 CRC 算法多项式(多项式系数不是可自由配置的),BootROM 用得就是最后一个模式选项 CRC-32:
BootROM 中对 CRC 模块的配置代码如下:
#include "fsl_crc.h"
void crc32_init(void)
{
crc_config_t crcUserConfigPtr;
CRC_GetDefaultConfig(&crcUserConfigPtr);
crcUserConfigPtr.seed = 0xffffffffU;
crcUserConfigPtr.polynomial = kCRC_Polynomial_CRC_32;
crcUserConfigPtr.reverseIn = false;
crcUserConfigPtr.reverseOut = false;
crcUserConfigPtr.complementIn = false;
crcUserConfigPtr.complementOut = false;
}
2.4 利用工具自动添加CRC校验参数
对 CRC32 校验启动的原理了解差不多了,我们现在在 MIMXRT595-EVK 开发板上实测一下,跟前面测试一样,先使用 SDK_2.10.1_EVK-MIMXRT595boardsevkmimxrt595driver_examplesgpioled_outputiarflash_debug 例程生成两个闪灯间隔时间不同的程序镜像文件:image 0 - gpio_led_output_delay200ms.bin 和 image 1 - gpio_led_output_delay2s.bin。
然后借助 MCUBootUtility 工具(需要 v3.5.0 版本及以上),在 Secure Boot Type 里选择 Plain CRC Image Boot,点击 All-In-One 下载按钮(两个文件分别做两次同样的下载流程),工具会自动在 image 相应地方填充进所需的 CRC32 参数并下载进 Flash。
这时候在工具通用编程器模式(Boot Device Memory)里我们再读回 image 保存就可以得到两个含 CRC32 校验的程序镜像文件 image 0 - gpio_led_output_delay200ms_crc.bin 和 image 1 - gpio_led_output_delay2s_crc.bin。
以 image 0 为例,根据 0x08001020 处的 imageLength 信息显示,image 0 App 本身长度为 0x36e8 字节,而 App 起始偏移是 0x1000,所以我们直接是从偏移 0 地址处开始读回 0x46e8 字节作为 gpio_led_output_delay200ms_crc.bin 文件数据。此外 image 0 的 CRC32 校验值已经填好了,是 0x4d8957d8。
2.5 手动验证CRC32校验值的方法
在使用 image 0 - gpio_led_output_delay200ms_crc.bin 和 image 1 - gpio_led_output_delay2s_crc.bin 做双程序启动前,我们可以先手动地验证下其中的 CRC32 校验值是否正确,痞子衡找到一个在线计算 CRC 的网站:
- CRC在线校验网站:http://www.sunshine2k.de/coding/javascript/crc/crc_js.html
在这个网站里把模式选好,然后从 gpio_led_output_delay200ms_crc.bin 文件里仅拷贝出 App 部分的数据放到网站 CRC Input Data 框(注意要手动删除 crcChecksum 四个字节,另外还要检查总数据字节长度是否按 4 对齐,如果不对齐,要在数据末尾按格式补上相应的 00),最后点击网站上的 Calculate CRC! 按钮可以得到结果,这里我们看到两个结果是一致的:
2.6 含CRC32校验的双程序启动测试
现在可以利用 image 0 - gpio_led_output_delay200ms_crc.bin 和 image 1 - gpio_led_output_delay2s_crc.bin 测试双程序启动了,继续借助 MCUBootUtility 工具的通用编程器模式将其分别下载进 0x0 和 0x400000 地址处,必要时还可以手动调整两个 image 里的版本号,测试过程中也可以稍微修改一下 image 数据再下载或者下载后再擦除一些 image 区域(故意让CRC32校验失败),最终测试结果如下:
image 0 | image 1 | 启动结果 | ||
---|---|---|---|---|
CRC情况 | 版本值 | CRC情况 | 版本值 | |
校验通过 | 0xFFFFFFFF | 校验通过/失败 | 0xFFFFFFFF | 直接启动 image 0 |
校验失败 | 0xFFFFFFFF | 校验通过 | 0xFFFFFFFF | 启动 image 0 失败后启动了 image 1 |
校验通过 | 0xFFFE0001 | 校验通过/失败 | 0xFFFF0000 | 直接启动 image 0 |
校验失败 | 0xFFFE0001 | 校验通过 | 0xFFFF0000 | 启动 image 0 失败后启动了 image 1 |
校验通过/失败 | 0xFFFF0000 | 校验通过 | 0xFFFE0001 | 直接启动 image 1 |
校验通过 | 0xFFFF0000 | 校验失败 | 0xFFFE0001 | 启动 image 1 失败后启动了 image 0 |
三、一些关于 image 的注意事项
- Note1: 虽然文中所有的测试均是针对 XIP image,但这个双程序可交替启动特性对于 Non-XIP image 也同样适用。
- Note2: 如果是 XIP image,其链接地址要求固定在 Flash 偏移 0x1000 处(如果 Flash 挂在第一个 FlexSPI 上,其 AHB 地址就是 0x08001000)。
- Note3: 如果是 Non-XIP image,在 SDK 包里无法直接生成含启动头的 Non-XIP image binary,这时候可以先使用 MCUBootUtility 主界面的 All-In-One 操作下载一次 image,再通过通用编程器界面 Read 操作读回来便是含启动头的 Non-XIP image binary。
- Note4: 使能 CRC32 校验的双程序可交替启动,也是同时支持 XIP image 和 Non-XIP image 的。
至此,i.MXRT500/600上串行NOR Flash双程序可交替启动设计痞子衡便介绍完毕了,掌声在哪里~~~