AliOS Things 二级bootloader方案介绍

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中实现整包升级及回滚。
3)OTA文件不带二级bootloader,可以节省流量。

1)第一次烧写时,需要将一级bootloader重新烧写。

2)对于OS的bin单独烧写的设备,烧写地址与现在有差异。
3)对于目前将bootloader与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中无法实现整包升级及回滚。
2)对于OS与一级bootloader合并一起烧写的设备,需要拆分开单独烧写。
3)OTA文件中带二级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/

猜你喜欢

转载自blog.csdn.net/HaaSTech/article/details/114946049