Introduction
Pricinple & Machenism
HA01的内存地址空间
BOOT ROM简介
BOOT ROM中的Bootloader执行过程
BVT的数据结构
Practice
源码中关于BVT的设计
在集成开发环境中调试
Conclusion
Reference
Introduction
HA系列微控制器基于Arm Cortex-M7处理器内核,集成了Security Boot的功能。Security Boot的关键代码集成在BOOT ROM中的bootloader程序中,用户不可见,以确保信息安全的需要。然而,开发者在自行编译固件时,需要配合BOOT ROM中的bootloader,才能正常地引导到用户应用程序,完成启动过程。
本文以HA系列的YTM32B1HA01微控制器(下文简称HA01)为例,将详细介绍BOOT ROM中bootloader的运行机制,以及用户程序与之对接的设计要点。
Pricinple & Machenism
HA01的内存地址空间
从RM手册中可以找到HA01的内存地址空间映射表,如图x所示。从表中可以看到,0x0000_0000开始的地址区是一块ITCM存储器(RAM),而存放用户程序的pflash则被安排在0x0200_0000开始的地址区间。在0x0100_0000地址开始的内存空间中,安排了一块ROM。按照芯片内部的硬件设计,芯片上电复位对硬件电路做好初始化后,先进入ROM执行预烧录的bootloader程序,进一步引导至用户应用程序。
图x HA01的内存地址空间
在Arm Cortex-M7架构中,ITCM是专门用来存放程序指令(Instruction),DTCM是专门用来存放数据(Data),至于OCRAM,就对应于普通的SRAM。如果按照以往Arm Cortex-M架构中只区分flash和SRAM的用法,DTCM和OCRAM都可以当做是SRAM使用,所以用户在常规用例的linker文件中,也可以把DTCM和OCRAM连续的地址空间都当做SRAM使用。但实际上,ITCM和DTCM都是TCM(Tightly Coupled Memories)存储器,分别通过I-TCM和D-TCM总线访问,是等同于Cache存储器的角色,直接同Arm Cortex-M7内核相连,而不同于OCRAM(或者早先Arm Cortex-M架构中的RAM)是通过总线(AXI总线)间接同Arm Cortex-M7内核连通。如此,一般情况下,若没有Cache加持,Cortex-M7内核访问TCM的效率要高于访问OCRAM。
对这些不同同种类的内存,可以根据需要灵活配置,以获得最优的访问效率。一种典型的用法:
在ITCM中存放中断向量表。这也是为什么把ITCM安排到0x0000_0000地址的原因。但需要在执行到pflash中的用户程序之后,由用户程序将存放在pflash中的中断向量表自行搬运到ITCM,然后修改Arm核心的VTOR寄存器重映射到0x0000_0000作为中断向量表的基地址。如果ITCM的空间大于预留的中断向量表占用的空间,剩下的空间用来存放ram code也是不错的。
在DTCM中存放栈。方便处理器快速地申请和释放。
在OCRAM中存放堆,以及用户程序中定义的全局变量、缓冲区等。
OCRAM可能跟Cache搭配使用,以提高访问效率,但如果处理器内核和DMA进行数据同步,Cache机制可能会引入一些麻烦,此时如果不想繁琐地调整Cache的配置,也可以将需要同步的缓冲区安排到DTCM中。
BOOT ROM简介
HA01上集成的BOOT ROM使用了64KB的存储器。芯片复位后,Cortex-M7内核从ROM里的bootloader开始执行引导程序。其中,将解析存放在pflash开始位置的BVT(Boot Vector Table)。这里的bootloader主要用于实现四个功能:
安全启动Security Boot
在信息安全的体系中,Security Boot作为整个安全系统的置信根(Root of Trust),可以使用CMAC授权算法,对用户指定的程序(例如2nd bootloader或OTA)进行认证,这个过程中,利用硬件的HCU外设模块进行计算,其配置信息存放在BVT中。进一步,HCU外设模块使用的密钥,存放在HCU_NVR中,由OEM主机厂或Tier-1预先烧录,用户全程不可见。
如果CMAC验签失败,处理器内核可以继续运行bootloader并引导至用户程序,但HCU将不会导入存放与HCU_NVR中的密钥,一切加密相关的操作将得不到芯片硬件的支持。
快速从Powerdown模式下唤醒
当检测到从Powerdown模式下唤醒而产生的复位,可以跳过Security Boot和内核自检的过程,从而加速启动过程。
可选地启用配置独立的IVT(Interrupt Vector Table)。这需要配合REGFILE中最开始的两个存储字存放配置信息。REGFILE在Powerdown模式下仍能保存数据。
对内核进行例行自检(Structural Core Self-Test,SCST)
这是为了配合实现功能安全等级ASIL-D标准,而进行的运行时测试。
配置系统时钟和看门狗
根据BVT中的配置,在bootlaoder过程中启用或者停用看门狗,以及对应的超时周期(HA01的WDG在默认情况下,使用来自于IPC的SIRC/4作为计数时钟源)
BOOT ROM中的Bootloader执行过程
在HA01的RM手册中,可以找到bootloader执行过程的示意图。如图x所示。
图x BOOT ROM中的bootloader运行流程图
从图x中可以看到,芯片从上电启动到用户的main()函数,大体要经历三个阶段:
MCU Reset Phase
Boot Code Function
MCU App run Phase
芯片上电后(POR),根据REGFILE区域中的REG0的配置值,分别进入快速启动流程(Fast Wakeup Boot Mode)或常规启动流程(Normal Boot Mode)。
在快速启动流程中:
解析BVT中预设的参数,以初始化系统时钟。
从REGFILE->REG1寄存器中获取快速启动流程的应用程序入口地址(同常规启动流程最终引导的应用程序入口地址可能不同)
查看IVT的地址并确保指定IVT的地址空间位于SRAM,后续将在用户自行编写的应用程序初始化阶段存放中断向量表。
跳转到快速启动流程的程序入口。
中间过程如果遇到任何异常,都会进入static mode(原地死循环)。
在常规启动流程中:
解析BVT中预设的参数。
若BVT中配置启用了Security Boot,则执行验签过程。
若BVT中配置启用了SCST Test,则执行Cortex-M7处理器内核的自检。
根据BVT中的参数,配置看门狗WDG外设模块。
跳转到常规启动流程的程序入口。
中间过程如果遇到任何异常,都会进入static mode(原地死循环)。
快速启动流程中不执行Security Boot和SCST Test过程,因此可以缩短启动时间。但注意,快速启动流程的应用程序入口同常规启动的应用程序入口可能不是同一个。
BVT的数据结构
BVT保存了启动过程中的很多关键参数,它本身位于pflash存储区,0x0200_0000开始的一段内存空间,可由用户在编写应用程序时,创建一个结构体,然后在链接过程中将其安排至0x0200_0000地址。BVT的参数占用108个字节,但实际上却需要预留2KB的空间,这是由于pflash的一个sector大小为2KB。用户实际开发过程中,也可以调整linker file,避开0x0200_0000开始的一个sector,如此,可保持最近一次烧写的可用的BVT配置不变。
手册中列写了BVT的字段定义,如图x所示。
图x 描述BVT的字段信息
从这里可以窥到一个有趣的设计,竟然提到了有CM7_0、CM7_1和CM7_2三个Cortex-M7处理器核心,并且每个核心有main和secondary两个核心。从描述上看,这描述的是一对锁步核(lockstep),并且每个核心都可以有独立的应用起始地址(application start address),将来用于全并行地分别执行不同的应用程序。实际上,目前的HA01使用的是一个不可拆开的单锁步核,故只有CM7_0_main可用。可以想见,在后续的产品规划中,可能会出现最多3对锁步核,如果进一步将锁步核拆开,可以设计最多6个独立的Cortex-M7核心。
BVT中0x4偏移地址上的Boot Configuration Word中进一步定义了启动相关的配置项,如图x中的表格所示。
图x Boot Configuration Word的字段描述
这里提供了一个信息,运行bootloader程序使用了FIRC(96MHz)作为时钟源,但可以通过CPDIVS设置1分频或者2分频。
这里还设计了很多功能的开关,例如看门狗、6个核心的独立使能开关,SCST自检功能。
这里还提到了serial security boot mode和normal boot mode,猜测前者可能就是验签的计算过程。
Practice
源码中关于BVT的设计
用户从SDK中导出的样例工程,就有关于对BVT的定义,以及在链接脚本中专门分配的代码段。
以hello_world工程为例,其中的secure_boot_YTM32B1HA0.h文件中有关于BVT类型的定义:
... #define BVT_HEADER_SEG __attribute__((used)) __attribute__((section (".bvt_header"))) ... /*! * @brief the BVT structure type definition * * Implements : bvt_header_config_t_Class */ typedef struct{ uint32_t bvt_marker; /* BVT marker */ uint32_t boot_config_word; /* Boot configuration word */ uint32_t sbt_config_group_addr; /* secure boot start address */ uint32_t lc_config; /* lifecycle configuration */ uint32_t cm7_0_main_app_addr; /* CM7_0 main core start address */ uint32_t cm7_0_secondary_app_addr; /* Reserved for CM7_0_SECONDARY_APP_ADDR */ uint32_t cm7_1_main_app_addr; /* Reserved for CM7_1_MAIN_APP_ADDR */ uint32_t cm7_1_secondary_app_addr; /* Reserved for CM7_1_SECONDARY_APP_ADDR */ uint32_t cm7_2_main_app_addr; /* Reserved for CM7_2_MAIN_APP_ADDR */ uint32_t cm7_2_secondary_app_addr; /* Reserved for CM7_2_SECONDARY_APP_ADDR */ uint32_t app_wdg_timeout; /* timeout set of the WDG watchdog of the application core */ } bvt_header_config_t;
而在secure_boot_YTM32B1HA0.c文件中,有定义具体的结构体实例:
#define BOOT_CONFIG_WORD (BVT_BCW_CPDIVS_SET(1) | CM7_0_M_EN) /* define the timeout of ADG watchdog of the application main core */ #define APP_WDG_TIMEOUT (120000) /* 10ms respect to 12MHz SIRC as reference clock*/ /* Flash erased status */ #define RESERVED ( 0xFFFFFFFF ) /* BVT Header configuration */ const bvt_header_config_t bvt_header BVT_HEADER_SEG = { BVT_VALID_MARK, /* BVT marker */ BOOT_CONFIG_WORD, /* Boot configuration word */ (uint32_t)&secure_boot_group, /* secure boot start address */ RESERVED, /* lifecycle configuration */ DEFAULT_START_ADDRESS, /* CM7 main core start address */ RESERVED, RESERVED, RESERVED, RESERVED, RESERVED, APP_WDG_TIMEOUT, /* timeout set of the WDG watchdog of the application core */ };
此处注意,定义的bvt_header结构体将位于BVT_HEADER_SEG数据段内。其中DEFAULT_START_ADDRESS定义了同应用软件约定的启动地址,具体位于0x0200_0800。位于secure_boot_YTM32B1HA0.h文件中。
/* BVT valid marker */ #define BVT_VALID_MARK ( 0xA55AA55A ) /* Default start address */ #define DEFAULT_START_ADDRESS ( 0x02000800 )
同时,在YTM32B1HA01_flash.sct文件中,也相应指定了BVT所占用的存储空间(预留在0x0200_0000至0x0200_07FF,和应用程序的起始地址0x0200_0800:
#define m_bvt_start 0x02000000 #define m_bvt_end 0x020007FF #define m_bvt_size m_bvt_end - m_bvt_start + 1 #define m_interrupts_start 0x02000800 #define m_interrupts_size 0x00000400 。。。 LR_m_text m_bvt_start { ; load region size_region ER_m_text_2 m_bvt_start FIXED m_bvt_size { .ANY (.bvt_header) .ANY (.sb_config_group) .ANY (.sb_config_section) .ANY (.sb_cmac) } // vectors. VECTOR_ROM m_interrupts_start FIXED m_interrupts_size { *(RESET +First) } // code. ER_m_text m_text_start FIXED m_text_size { *(InRoot$$Sections) .ANY (+RO) } 。。。
当下载程序后,可以在memory的视图中可以看到,0x0200_0000开始的位置,有存放关于BVT的配置信息。如图x所示。
图x 下载到HA01芯片中的BVT配置数据
在集成开发环境中调试
BOOT ROM + BVT相互配合,在HA01芯片内部实现了一个bootloader,但程序的起始地址也不再是常规默认的0x0000_0000,如此,当在集成开发环境中调试程序时,可能会需要指定一个程序入口点。实际上,bootloader的存在并不影响在载入调试环境时自动将程序断点到main()函数:
在调试环境中,下载程序到芯片后复位,如果不显式地执行入口程序地址,芯片会自动从bootloader开始执行,然后从BVT中取得用户应用程序入口地址后,跳转到用户应用程序。确切地说,是进入到用户程序的Reset_Handler函数,然后进入到main()函数。
大多数集成调试环境,会“智能”地在main()函数开始的位置打一个断点,作为调试程序的开始,等待开发者发起进一步的调试动作。
这类似于以往开发者在有用户自定义的bootloader的环境下开发application。
无论是Keil还是IAR,在集成开发环境内部启动调试时,都可以自动执行上述流程至main()函数的断点。但是,当使用Segger Ozone调试可执行文件时,由于默认会显式指定一个入口地址,如果没有合适的配置,默认从0x0000_0000开始,就会出现错误。如图x。
图x 在ozone中配置程序入口点
图x 默认配置下载入调试出错
从图x中可以看到,Ozone默认从固件程序的开始,0x0200_0000,读到了初始的PC和SP值,但这里的数据其实是BVT的内容,不是中断向量表的项目。故会报错。
此时,在配置初始载入点时,有几种可行的配置,都可以实现正常调试:
Initial PC -> Read from Location -> 0x0100_0004, Initial Stack Pointer -> Read from Location -> 0x0100_0000。这种设置是从BOOT ROM中的bootloader开始启动,然后引导到用户应用程序。
Initial PC -> Read from Location -> 0x0200_0804, Initial Stack Pointer -> Read from Location -> 0x0200_0800。这种设置是跳过BOOT ROM中的bootloader启动,直接从用户应用程序启动。
Initial PC -> Do not set, Initial Stack Pointer -> Do not set。这种设置是软件工具不要多做,让芯片的硬件机制起作用。此时芯片从BOOT ROM中的bootloader开始启动,然后引导到用户应用程序。
建议使用第三种方式,充分利用硬件机制,确保对芯片有最完整的初始化过程,也是最接近在芯片正常上电启动的运行情况。
Conclusion
YTM32B1HA系列微控制器基于Arm Cortex-M7处理器内核,集成了BOOT ROM,并重新划分了地址空间中的内存分配,这使得芯片上电之后的引导过程和用户应用程序的存放地址发生了一些变化,对应地,使用调试软件工具也需要做相应的适配操作,以避免出现异常的情况。
无论如何,BOOT ROM中的bootloader和BVT的配置,为应用带来了一些新的功能,例如基于硬件计算引擎进行加速和加密的安全启动过程、对多核心运行的管理等等。这为支持复杂的片上系统的奠定了一定的基础,也是设计多核心SoC的启动过程的一种可行的尝试。
全部0条评论
快来发表一下你的评论吧 !