1、MCU上电后的启动流程
- 芯片上电
- 硬件依次以0x0、0x40000和0x80000为基地址并偏移到对应的0x20位置检查字符标记(“TLNK”,如果0x0位置没找到,则往后两个地址寻找,如果找到,则以该基地址为ram的启动基址)
- 直接从flash的启动基地址位置(步骤2中确定的)读取代码(flash - 1MB,0x20000000 ~ 0x20100000;以基址 + 偏移地址形式进行访问)
- 初始化 I / D-Cache(8KB大小;当 I / D-Cache初始化完成时,后续的代码运行则会转移至 ram 下进行;Note:大部分数据和代码不是直接从flash读到ram执行的,一般会先读到 I / D-Cache中,然后再根据实际读入ram中使用)
- 将所需各段信息(如retention_restet,aes_data,retention_data,ram_code)搬到 ram 中,以及做一些清理、初始化操作(如设置.bss、.sbss段初始值为0等)
- 将main函数指针压入t0寄存器
- 程序执行
放一张ram和flash的对应图相信各位就懂了(b91芯片的flash是1MB大小;ram为256KB大小,分为 I-RAM 和 D-RAM 两块,各占128KB,地址分别为0x00000000 ~ 0x00020000 和 0x00080000 ~ 0x000a0000,其中,I-RAM能够存放指令和数据,D-RAM只存放数据);从flash中搬移到ram的段有retention_reset、retention_data、ram_code:
2、cstartup.s与boot.link的作用
在芯片的启动中,主要涉及cstartup.s与boot.link两个文件,简单的去看的话,就是,boot.link文件主要是为cstartup.s文件提供包括如各个段的起始地址、标识符等信息的(两个文件的具体交互过程我就不懂了,涉及到编译器的链接规则啥的,且每家编译器的链接规则都有区别),而cstartup.s则是程序启动前要执行的代码。另外,由于telink的b91芯片是基于risc-v架构搭建的,因此,启动代码中的一些指令与ram的还是有些区别的。
3、执行代码
3.1 boot.link
我们首先来看看boot.link里的内容长啥样:
图中,ENTRY(_RESET_ENTRY)用于指定程序的入口点,即程序开始执行的位置。另外,从图中可以明显看到,.link文件的整个操作都是在SECTIONS下操作的。
对于.link文件,我觉得主要弄清楚以下三点就没问题了:
- LMA和VMA是什么?
- 几个关键指令的作用,如图中的 ‘ . ’, ’ .retention_reset : AT( ALIGN(LOADADDR (.vectors) + SIZEOF (.vectors),8)) ’ 和 ’ (NOLOAD) ’
- .link中分配好的LMA和VMA具体在 .elk中是怎么体现的,如何计算?
第一点:LMA和VMA是什么?
LMA:Load Memory Address,加载地址,是指一个指令在内存中的加载地址,即指令的内容是从哪个地址加载进内存的。
VMA:Virtual Memory Address,虚拟地址,是实际代码执行时所对应的地址位置。
一般来说,LMA与VMA相同,但是存在一些情况是不同的,比如说LMA所处地址是只读属性,即代码无法在该地址下直接执行,因此,需要将LMA位置的代码 / 数据读到其他位置(VMA)使用。
第二点:几个主要指令如何理解?
对于图中红框的几个指令:
- ’ . ‘ 按我理解的话就是当前LMA与VMA的所处位置(地址值),如图中所示:. = 0x20000000; 即将LMA与VMA同时设置为 0x20000000;
- 第二个红框的两句指令需要合起来看,即先同时设置当前LMA与VMA为0x00000000,之后,针对.retention_reset段,将其LMA设置为.vectors段的LMA + .vectors段的长度(LOADADDR (.vectors) + SIZEOF (.vectors)),即对于.retention_reset段的内容,是从对应LMA下加载过来的。最后再对LMA的地址值以8字节进行对齐(ALIGN(xxx, 8));
- 第三个红框也比较重要,通常,对于.bss等段的内容是直接清0操作的,也就是说该段一般是不占信息量的。但是,当你在编写.link文件时,如果不加上NOLOAD声明,则实际编译出的对应段还是有长度的,也即占了位置,有信息量表示了。为此,通常会加上NOLOAD声明,告诉编译器该段不占信息量。
第三点:如何计算LMA和VMA?
首先我们来看看 .elk中分配的各个段的LMA和VMA是怎样的:
其中,size表示为对应段的大小,即len;
结合上下两张图可以看到,在加上NOLOAD声明后,对应的.aes_data段的LMA是没有增长的,也即说明了该指令的作用。
就举一个例子就好了,具体其他的计算就自己结合上下图进行计算了。另外,多注意字节对齐的问题,且不是全部位置都设置了字节对齐。
. = 0x00000000;
.retention_reset : AT( ALIGN(LOADADDR (.vectors) + SIZEOF (.vectors),8))
{ KEEP(*(.retention_reset )) }
首先,设置VMA和LMA的地址为0x00000000,然后,设置.retention_reset段的LMA地址为(0x20000000 + 0x18a = 2000 018A),又因为是8字节对齐,A大于8了,进位对齐,即LMA = 2000 0190。
3.2 cstartup.s
cstartup.s文件的话主要是一些启动指令代码,和其他的差不太多,这里只列出几个关键地方:
1、由于在进行bootloader阶段的时候,软件的初始化还没完成,即各种io口其实还不能使用,但是有时候为了调试 / 测试deep_retention / suspend等模式下的启动时间、启动顺序等,则需要一个高低电平指示,表示程序运行到相应位置,因此,这时候就引入了下图中的硬件io口来生成电平变化。
图中代码中 ‘ li t2, 0x10 ’ 是设置端口输出高电平,设为0x00时则是输出低电平。
2、另外,有时候栈空间是有富裕的,为了更加充分使用,可以通过下图的指令代码来计算栈的剩余空间。
具体操作即为:对于没有使用到的栈空间位置使用0x55555去填充,有数据的位置则不操作,以此不断循环填充,直达栈空间填满,然后通过手动计算即可知栈的剩余空间大小。