1、概述
在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行的一段代码。
他的作用就是为操作系统内核准备好运行环境,比如初始化必要的设备硬件,建立内存映射图等。
bootloader不一定只有一个,有些操作系统有两级的bootloader,第一级bootloader和第二级bootloader分别完成不同的功能。
二级bootloader功能如下:
1)提供OTA升级运行环境
OTA的差分及压缩升级功能,需要一个与OS及APP隔离的运行环境,用于差分恢复运行区版本。该运行环境可以在一级bootloader中实现,但是一级bootloader通常是芯片或者模组厂商提供,而且很多芯片及模组厂商未提供bootloader的源码,无法保证可以在所有的一级bootlaoder中实现。
2)充分利用内存空间
在二级bootloader运行时,OS还未启动,二级bootloader可以使用全部的内存空间,用于差分解压算法等比较耗内存的操作。OS启动后,将整个系统内存重新初始化,也可以使用全部内存空间,二级bootloader使用了多少内存对OS无影响。这样可以达到内存的充分利用。
3)版本自动回滚
可以提供版本回滚功能,版本升级后,如果启动失败,可以通过二级bootloader自动回滚到升级前可正常运行的版本。
4)设备本地升级。
可以支持设备在不直接联网的场景下,通过本地升级功能,作为子设备升级。
2、二级bootloader总体框架
2.1、物理部署
如上图所示,二级bootloader介于一级bootloader与OS之间,与一级bootloader及OS共用FLASH空间,运行时独享系统RAM空间。
2.2、二级bootloader对外功能
1)UART驱动、FLASH驱动级Watchdog驱动,需要独立运行,不能对一级bootloader及OS产生符号依赖。可以对一级bootloader有功能依赖,不能对OS有功能依赖。
2)FLASH分区表中二级boot相关的配置需要与OS的FLASH分区表二级boot相关的配置完全一致。
3、二级bootloader详细设计方案
3.1、二级bootloader启动流程
3.1.1、二级bootloader加载启动
对于一级bootloader来说,二级bootloader与OS一样,是一个被引导启动的application,因此二级bootloader编译及启动方式与OS基本相同。
由于每个MCU,bootloader启动OS的方式不一样,因此二级bootloader被一级bootloader引导启动的方式也不尽相同。目前AliOS Things支持的MCU,通常有下面几种启动引导方式:
1)一级bootloader从固定地址跳转到二级bootloader的入口执行。目前,mk3060及developerkit等采用的这种方式。
2)一级bootloader从固定地址读取二级bootloader的入口函数地址,再跳转到二级bootloader入口执行。目前,mk3080采用的是这种启动方式。
3)有多个固定的FLASH启动地址,一级bootloader根据FLASH中的固定参数,决定从哪个FLASH地址启动。developerkit等STM32系列的MCU均是该方式启动。
注:mk3080的一级bootloader实际也有选择启动地址的过程,选择完后,再按照步骤2启动。
下面详述一下一级bootloader加载启动二级bootloader的流程。
- 二级bootloader加载启动流程
- 二级bootloader加载启动流程2
FLASH排列规则如下:
注:部分MCU是一级bootloader实现,一级bootloader会将二级bootloader的data段数据拷贝到RAM中。目前mk3060,是二级bootloader自己拷贝的data段数据;mk3080是一级bootloader加载二级bootloader前完成。
该流程,由一级bootloader完成,二级bootloader本身不需要操作,只需要按照MCU要求OS的编译链接方式生成bin文件即可。
3.1.2、二级bootloader内存初始化
- 将data段数据拷贝到RAM中对应地址
该操作,需要在FLASH中找到data段的起始地址和结束地址,以及在RAM中的起始地址和结束地址,然后将FLASH中的data数据拷贝到RAM中。
对于大部分CPU,data段的起始和结束地址,定义在链接脚本中,代码通过extern变量方式获取。少量MCU,如mk3080,需要按照特殊方式获取(3080的特殊方式,可以参加3080二级bootloader加载启动流程章节)。
- bss段数据清0
该操作需要在RAM中找到bss段的起始地址和结束地址,然后将RAM中对应地址的数据清0。
所有的MCU,bss段的起始和结束地址,都可以直接定义在链接脚本中,代码通过extern变量方式获取。
- 动态内存管理
二级bootloader在运行OTA差分功能时,需要使用动态内存申请及释放功能。因此要求二级bootloader中支持动态内存管理。
二级bootloader中,使用的动态内存范围在二级bootloader的链接脚本中定义,从二级bootloader的bss段结束,到recovery栈空间开始的范围。内存管理使用与OS相同的动态内存管理算法。
3.2、二级bootloader引导启动OS流程
3.2.1、OS启动流程
二级bootloader引导启动OS的流程,基本与一级bootloader引导启动二级bootloader的流程相同。基本也分2种方式:固定地址跳转和从固定地址读取入口函数地址跳转,详见二级bootloader启动流程种的描述。
下面以mk3060和mk3080为列,详述一下二级bootloader加载启动OS的流程。
- 二级bootloader加载启动OS流程
注意:FLASH烧写,需要4K对齐,因此需要从0x1C000开始烧写。而OS的起始FLASH地址需要从0x1C01C开始,因此需要在OS的bin文件头部补充0x1C byte的填充数据。
- 二级bootloader加载启动OS流程
FLASH排列规则如下:
3.2.2、中断向量表
二级bootloader本身不处理中断,全程禁止中断运行(串口中断除外),但是在OS启动后,需要将中断送给OS。目前有几种实现方式,如下:
- 采用逐级中断向量表跳转方式,硬件产生中断后,先进入一级bootloader,一级bootloader转给二级bootloader,二级bootloader转给OS。
OS启动后,直接将中断注册给MCU SDK提供的中断机制。
硬件触发中断后,直接调用中断处理回调函数。
直接修改中断向量表入口地址
OS启动后,设置MCU硬件寄存器,将中断向量表地址设置为OS内部中断向量表地址。硬件产生中断后,直接进入OS的中断向量表。developerkit目前采用的这种方式。
3.3、二级bootloader运行流程
3.3.1、运行流程
说明:1)在recovery中,也会初始化串口。
2)一旦进入recovery流程,不能再返回启动OS的流程,需要重启后再进入。
3)一旦进入命令行模式,不能再返回启动OS的流程,需要重启后进入。
4)部分MCU,OS的启动入口地址是写死的,不需要动态获取。
3.4、命令行功能
命令行功能,运行开发者通过串口输入命令方式,查询版本信息、进行本地升级等操作。
3.4.1、进入流程
在判断是否需要进入命令行模式时,会持续读串口100ms,如果读到字符'w',就会进入命令行模式。
进入后,会打印二级bootloader版本号,以及命令提示信息。然后等待用户进一步输入串口命令。
3.4.2、支持命令
1)打印运行区版本号及备份区版本号。
2)Xmodem读写flash
3)Ymodem读写flash
4)USB升级
5)Canbus升级
6)版本回退到备区
7)reboot
说明,上面命令,支持配置项配置,通过配置项决定是否支持对应功能。
3.5、异常处理
二级bootloader中发生异常时,直接reset。
4、现有系统影响分析
4.1、FLASH空间
4.1.1、FLASH空间消耗增多
增加二级bootloader功能后,相对于没有实现二级bootloader以及差分升级的设备,会增加36k的FLASH消耗。对于已经实现差分升级的设备,会增加16k的FLASH消耗。
增加的FLASH消耗主要是由下面几方面导致:
1)增加了二级bootloader的处理逻辑,代码需要消耗FLASH空间。
2)增加了命令行处理功能,相关代码需要消耗FLASH空间。
3)增加双备份及回滚功能,相关代码需要消耗FLASH空间。
4)增加了任意时刻断点续传功能,增加了8K的备份数据区。
4.1.2、FLASH空间规划发生变化
增加二级boot及差分功能后,会导致FLASH空间规划发生变化。原因如下:
1)二级bootloader在FLASH中的位置,需要位于一级bootloader之后,OS之前。
2)增加了一个8K备份区,用于备份二级bootloader及差分参数,一级备份断点续传数据。
4.2、版本烧写方式
4.2.1、当前烧写方式
当前烧写方式归纳一下,存在下面2种烧写方式:
- OS的bin文件单独烧写。
- OS与bootloader的bin作为一个文件一起烧写。
4.2.2、增加二级bootloader后烧写方式
增加二级bootloader后,有下面3种方式:
烧写方式 |
优点 |
缺点 |
二级bootloader单独烧写 |
1)可以在二级bootloader中实现整包升级及回滚。 2)OTA文件不带二级bootloader,可以节省流量 |
1)第一次烧写时,需要多烧写一个文件。 2)对于OS的bin单独烧写的设备,烧写地址与现在有差异。 3)对于目前将bootloader与OS合并成一个bin一起烧写的设备,需要拆分开单独烧写。 |
二级bootlaoder与一级bootloader合并成一个bin烧写,OS单独烧写 |
1)对于OS与bootloader单独烧写的设备,不需要增加bin文件。 2)可以在二级bootloader中实现整包升级及回滚。 |
1)第一次烧写时,需要将一级bootloader重新烧写。 2)对于OS的bin单独烧写的设备,烧写地址与现在有差异。 |
二级bootloader与OS合并成一个bin,单独烧写 |
1) 对于OS与bootloader单独烧写的设备,不需要增加bin文件。 2)对于OS与bootloader单独烧写的设备,OS bin的烧写地址不需要变化,与当前方式兼容。 |
1)二级bootloader中无法实现整包升级及回滚。 2)对于OS与一级bootloader合并一起烧写的设备,需要拆分开单独烧写。 3)OTA文件中带二级bootloader,增加流量消耗。 |
一级bootloader、二级bootloader及OS合并成一个bin,一起烧写 |
1)对于OS与bootloader合并成一个bin单独烧写的设备,可以完全兼容 |
1)二级bootloader中无法实现整包升级及回滚。 |
可行方案:
1)对于一级bootloader可以烧写的设备
将二级bootloader与一级bootloader合并成一个bin文件,单独烧写。OS的bin文件单独烧写。
2)对于一级bootloader不可以烧写(或者不需要烧写)的设备
将二级bootloader单独烧写。OS的bin文件单独烧写。
4.3、启动时间
由于需要支持命令行模式,在每次启动时,会有100ms的等待按键输入的时间。导致每次启动会慢100ms。
5、移植说明
5.1、概述
二级bootloader实现,依赖UART驱动、FLASH驱动、WatchDog驱动,需要能够被一级bootloader启动,需要能够加载启动OS的bin。
5.2、启动引导
启动引导,需要实现下列功能:
1)能够被一级bootloader引导启动
2)能够找到OS的data段在flash中的位置,以及在RAM中的位置,启动OS前,将data段拷贝到RAM中。
2)能够找到OS的入口地址,启动OS。
3)能够将中断和异常转给OS。
5.3、UART驱动移植
UART驱动,需要提供下列功能:
接口 |
功能 |
备注 |
void rec_uart_init(void) |
UART功能初始化 |
有些设备,运行二级bootloader时,UART已经初始化了,不需要再初始化,该函数实现为空函数即可 |
void rec_uart_send_string(char *buff) |
将buff内容打印到串口 |
|
unsigned char rec_uart_recv_byte(unsigned char *c) |
从串口接收一个字符,字符内容放在出参c中,返回值为1标识收到,返回值为0表示没有收到。该接口为非阻塞接口。 |
|
5.4、FLASH驱动移植
FLASH驱动,需要提供下列功能:
接口 |
功能 |
备注 |
void rec_flash_init(void) |
FLASH驱动初始化 |
|
void rec_flash_erase(unsigned long offset) |
从地址offset开始,擦除1个sector |
非必须,如果某个设备上,写FLASH不需要擦除,则该接口直接实现为空 |
void rec_flash_write_data(unsigned char *buffer, unsigned long offset, unsigned long len) |
将内存地址buffer开始的len个byte的内容,写入flash起始地址offset处。 |
|
void rec_flash_read_data(unsigned char *buffer, unsigned long offset, unsigned long len) |
将flash地址offset开始的len个byte的内容,拷贝到内存地址buffer处。 |
|
hal_logic_partition_t *rec_flash_get_info(hal_partition_t pno) |
分解分区号pno获取分区信息 |
5.5、WatchDog驱动移植
WatchDog驱动,需要提供下列内容:
接口 |
功能 |
备注 |
void rec_wdt_init(unsigned int timeout_ms) |
初始化watch dog,设置timeout_ms ms时间后,超时复位 |
如果系统默认已经启动了看门狗的,该接口可以实现为空 |
void rec_wdt_start() |
启动watch dog |
如果系统默认已经启动了看门狗的,该接口可以实现为空。 如果硬件上init后,自动启动的,该接口也可以实现为空。 |
void rec_wdt_stop() |
停止watch dog |
如果系统无法停止看门狗的,该接口可以实现为空。 |
void rec_wdt_feed() |
设置watch dog重新计数(feed dog) |
5.6、内存管理
内存管理,主要需要提供动态管理内存的起始地址和结束地址,方式如下:
接口 |
功能 |
备注 |
_rec_heap_start |
_rec_heap_start的地址为动态管理内存的起始地址,即&_rec_heap_start为起始地址 |
|
_rec_heap_end |
_rec_heap_end的地址为动态内存管理的结束地址,即&_rec_heap_end为结束地址 |
5.7、复位及延时
需提供如下接口:
接口 |
功能 |
备注 |
void rec_reboot(void) |
重启设备 |
|
void rec_switch_reboot() |
切换分区,重启设备 |
适合AB双分区启动的平台,其它平台,改函数实现为空函数。 |
void rec_delayms(volatile int times_ms) |
延时times_ms ms的时间 |
硬延时,不释放cpu。 |
开发者技术支持
如需更多技术支持,可加入钉钉开发者群,或者关注微信公众号
更多技术与解决方案介绍,请访问阿里云AIoT首页https://iot.aliyun.com/
转载:https://blog.csdn.net/HaaSTech/article/details/114946049