以下以nrf52840的DFU为例,这里不采用NRF5340为例的原因是Nordic在mcuboot的源码中对5340的DFU做了一些适配。
简介
Zephyr的DFU子系统提供了在运行时升级基于Zephyr的应用程序映像所需的框架。它目前由两个不同的模块组成:
subsys/dfu/boot/:引导加载程序的接口代码subsys/dfu/img_util/: 映像管理代码
应用程序分区
首先需要确保应用程序的设备树中适配mcuboot的硬件层分区。
boot_partition:对于 MCUboot 本身image_0_primary_partition:图像 0 的主插槽image_0_secondary_partition:映像 0 的辅助插槽scratch_partition:暂存槽&flash0 {partitions {compatible = "fixed-partitions";#address-cells = <1>;#size-cells = <1>;boot_partition: partition@0 {label = "mcuboot";reg = <0x000000000 0x0000C000>;};slot0_partition: partition@c000 {label = "image-0";reg = <0x0000C000 0x00067000>;};slot1_partition: partition@73000 {label = "image-1";reg = <0x00073000 0x00067000>;};scratch_partition: partition@da000 {label = "image-scratch";reg = <0x000da000 0x0001e000>;};/** The flash starting at 0x000f8000 and ending at* 0x000fffff is reserved for use by the application.*//** Storage partition will be used by FCB/LittleFS/NVS* if enabled.*/storage_partition: partition@f8000 {label = "storage";reg = <0x000f8000 0x00008000>;};};};
boot_partition节点: 存放mcuboot的程序slot0_partition节点: 存放运行的应用程序slot1_partition节点: 存放要升级的应用程序scratch_partition节点: 主要是为了拷贝slot1_partition节点的程序到slot0_partition节点中。
默认运行的应用程序插槽配置
上面FLASH分区分配好了之后,需要在chosen节点下设置默认运行的应用程序到底是哪个分区:
zephyr,code-partition = &slot0_partition;
应用程序的配置
在应用程序的Kconfig中配置启用mcuboot
CONFIG_BOOTLOADER_MCUBOOT=y
imgtool介绍
imgtool工具是mucboot提供的生成签名文件和对应用程序进行签名的工具。其中工具所在路径为bootloader/mcuboot/scripts/imgtool.py
生成签名文件
这里我们采用mcuboot提供的imgtool工具生成一个签名文件:
./bootloader/mcuboot/scripts/imgtool.py keygen -k filename.pem -t rsa-2048
-t代表的是生成签名文件的加密方式,目前支持如下方式:
- rsa-2048
- rsa-3072
- ecdsa-p256
- ed25519
签名
imgtool在签名时提供的常用参数
| 参数 | 描述 | 必须 |
|---|---|---|
-k, --key filename |
签名文件路径 | 必须 |
--align [1|2|4|8] |
对其字节数 | 必须 |
-v, --version TEXT |
版本号 | 必须 |
-s, --security-counter TEXT |
计数器号 | 非必须 |
--pad-sig |
在1.5版本之前,需要在ECDSA签名中添加0-2字节的填充 | 非必须 |
--header-size |
中断矢量表的偏移大小,这个大小必须和Kconfig中的CONFIG_ROM_START_OFFSET一致 |
必须 |
--slot-size |
运行插槽的大小 | 必须 |
指令格式:
imgtool.py <参数> <需要签名的文件名> <签名后的文件名>
应用程序的签名
这里可以直接使用mcuboot提供的imgtool工具,还可以使用zephyr提供的west sign工具,其实west工具是对imgtool工具做了一个外层的封装,底层还是在使用imgtool。
这里我们直接采用imgtool工具来进行应用程序的签名:
imgtool.py sign --key ~/root-rsa-2048.pem --header-size 0 --align 8 --version 1.0 --slot-size 0x67000 ./zephyr/zephyr.bin zephyr.signed.bin
合并mcuboot和应用程序
我们在上一章介绍了如何集成mcuboot,这里不做过多的讲解,最终的目的其实就是将mcuboot和应用程序合并为一个程序而已。
其实nordic提供了多应用构建的操作,所以我们只需要配置就可以将mcuboot和应用程序集成好,对于多应用配置,我们之后会专门讲解。目前多应用构建是nordic特有的功能。
DFU升级
- 下载升级文件
- 存储到
image_0_secondary_partition中 - 设置升级标志
mcuboot升级原理
首先这里我们需要说明一下,需要升级文件永远存储在image_0_secondary_partition这个区域,当将文件下载到此区域之后,将此区域的交换标志置位,之后重启后mcuboot会检测image_0_secondary_partition的交换标志是否设置,如果设置那就将image_0_secondary_partition和image_0_primary_partition交换,交换完成后,最终还是在image_0_primary_partition运行程序。
下载升级文件
下载文件的过程可以通过wifi、BLE或有线连接的方式,这里不做太多介绍。
存储文件
我们需要将下载的文件存储到image_0_secondary_partition分区。
其实在zephyr\subsys\dfu\img_util\flash_img.c里提供的函数就是为了操作升级区域的。
| API | 含义 |
|---|---|
| flash_img_init_id | 根据提供的区域id初始化flash的上下文 |
| flash_img_init | 直接采用默认的区域id(secondary_partition)初始化flash上下文 |
| flash_img_bytes_written | 获取写入FLASH的字节数 |
| flash_img_buffered_write | 通过缓冲器的方式写入flash |
| flash_img_check | 验证完整性 |
还可以直接通过区域操作的API来操作升级区域,区域操作API的路径zephyr/include/storage/flash_map.h:
| API | 含义 |
|---|---|
| flash_area_open | 根据区域id打开要操作的区域 |
| flash_area_close | 关于指定的区域 |
| flash_area_read | 从区域内读取数据 |
| flash_area_write | 从区域内写入数据 |
| flash_area_erase | 擦除区域 |
| flash_area_check_int_sha256 | 验证区域完整性 |
示例:
void test_collecting(void){const struct flash_area *fa;struct flash_img_context ctx;uint32_t i, j;uint8_t data[5], temp, k;int ret;// 初始化flash img的变量,这里其实就在再找image_0_secondary_partition区域ret = flash_img_init(&ctx);//擦除该区域#ifdef CONFIG_IMG_ERASE_PROGRESSIVELYuint8_t erase_buf[8];(void)memset(erase_buf, 0xff, sizeof(erase_buf));// 这种方式是是另外一种获取升级区域的方式ret = flash_area_open(FLASH_AREA_ID(image_1), &fa);if (ret) {printf("Flash driver was not found!\n");return;}/* ensure image payload area dirt */for (i = 0U; i < 300 * sizeof(data) / sizeof(erase_buf); i++) {ret = flash_area_write(fa, i * sizeof(erase_buf), erase_buf,sizeof(erase_buf));zassert_true(ret == 0, "Flash write failure (%d)", ret);}/* ensure that the last page dirt */ret = flash_area_write(fa, fa->fa_size - sizeof(erase_buf), erase_buf,sizeof(erase_buf));zassert_true(ret == 0, "Flash write failure (%d)", ret);#else// 直接擦除整个区域ret = flash_area_erase(ctx.flash_area, 0, ctx.flash_area->fa_size);zassert_true(ret == 0, "Flash erase failure (%d)", ret);#endif//直接在该区域写入一个字节zassert(flash_img_bytes_written(&ctx) == 0, "pass", "fail");k = 0U;for (i = 0U; i < 300; i++) {for (j = 0U; j < ARRAY_SIZE(data); j++) {data[j] = k++;}// 在改区域通过缓冲区的方式写入数据ret = flash_img_buffered_write(&ctx, data, sizeof(data), false);zassert_true(ret == 0, "image colletion fail: %d\n", ret);}zassert(flash_img_buffered_write(&ctx, data, 0, true) == 0, "pass","fail");// 通过指定区域id的方式获取区域ret = flash_area_open(FLASH_AREA_ID(image_1), &fa);if (ret) {printf("Flash driver was not found!\n");return;}k = 0U;for (i = 0U; i < 300 * sizeof(data); i++) {// 从该区域获取数据zassert(flash_area_read(fa, i, &temp, 1) == 0, "pass", "fail");zassert(temp == k, "pass", "fail");k++;}#ifdef CONFIG_IMG_ERASE_PROGRESSIVELYuint8_t buf[sizeof(erase_buf)];// 从该区域获取数据ret = flash_area_read(fa, fa->fa_size - sizeof(buf), buf, sizeof(buf));zassert_true(ret == 0, "Flash read failure (%d)", ret);zassert_true(memcmp(erase_buf, buf, sizeof(buf)) == 0,"Image trailer was not cleared");#endif}
其实这里还可以直接操作SPI FLASH的方式将数据存放在改区域,只是已经给我门提供了更方便的调用,完全没必要在去操作SPI FLASH的API了。
设置升级标志
其实和设置升级标志相关的函数都存在与zephyr\subsys\dfu\boot\mcuboot.c里。
| API | 含义 |
|---|---|
| boot_read_bank_header | 从指定区域里读取头信息 |
| boot_is_img_confirmed | 检查image_0_primary_partition区域的确认标志是否设置 |
| boot_write_img_confirmed | 设置image_0_primary_partition区域的确认标志 |
| mcuboot_swap_type | 获取MCUboot的交换类型 |
| boot_request_upgrade | 设置交换标志,意味着下次重启的时候,就会将image_0_secondary_partition的内容和image_0_primary_partition的内容做交换,然后依然运行image_0_primary_partition的内容,这样就完成了升级 |
| boot_erase_img_bank | 擦除指定区域 |
boot_request_upgrade的介绍
函数原型:
int boot_request_upgrade(int permanent);
这个函数的permanent参数为false时,下次重启时虽然会运行我们需要升级的程序,但如果我们在升级程序内没有调用boot_write_img_confirmed这个函数话,那第二次重启的时候系统就会回滚到未升级之前的程序。
如果permanent参数为true时,那无论是否在升级程序中调用boot_write_img_confirmed这个函数,升级程序都不会回滚。
关于boot_request_upgrade的实现如下:
int boot_request_upgrade(int permanent){#ifdef FLASH_AREA_IMAGE_SECONDARYint rc;rc = boot_set_pending(permanent);if (rc) {return -EFAULT;}#endif /* FLASH_AREA_IMAGE_SECONDARY */return 0;}intboot_set_pending(int permanent){return boot_set_pending_multi(0, permanent);}intboot_set_pending_multi(int image_index, int permanent){const struct flash_area *fap;struct boot_swap_state state_secondary_slot;uint8_t swap_type;int rc;rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_SECONDARY(image_index),&state_secondary_slot);if (rc != 0) {return rc;}switch (state_secondary_slot.magic) {case BOOT_MAGIC_GOOD:/* Swap already scheduled. */return 0;case BOOT_MAGIC_UNSET:rc = flash_area_open(FLASH_AREA_IMAGE_SECONDARY(image_index), &fap);if (rc != 0) {rc = BOOT_EFLASH;} else {rc = boot_write_magic(fap);}if (rc == 0 && permanent) {rc = boot_write_image_ok(fap);}if (rc == 0) {if (permanent) {swap_type = BOOT_SWAP_TYPE_PERM;} else {swap_type = BOOT_SWAP_TYPE_TEST;}rc = boot_write_swap_info(fap, swap_type, 0);}flash_area_close(fap);return rc;case BOOT_MAGIC_BAD:/* The image slot is corrupt. There is no way to recover, so erase the* slot to allow future upgrades.*/rc = flash_area_open(FLASH_AREA_IMAGE_SECONDARY(image_index), &fap);if (rc != 0) {return BOOT_EFLASH;}flash_area_erase(fap, 0, fap->fa_size);flash_area_close(fap);return BOOT_EBADIMAGE;default:assert(0);return BOOT_EBADIMAGE;}}intboot_set_pending_multi(int image_index, int permanent){const struct flash_area *fap;struct boot_swap_state state_secondary_slot;uint8_t swap_type;int rc;rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_SECONDARY(image_index),&state_secondary_slot);if (rc != 0) {return rc;}switch (state_secondary_slot.magic) {case BOOT_MAGIC_GOOD:/* Swap already scheduled. */return 0;case BOOT_MAGIC_UNSET:rc = flash_area_open(FLASH_AREA_IMAGE_SECONDARY(image_index), &fap);if (rc != 0) {rc = BOOT_EFLASH;} else {rc = boot_write_magic(fap);}if (rc == 0 && permanent) {rc = boot_write_image_ok(fap);}if (rc == 0) {if (permanent) {swap_type = BOOT_SWAP_TYPE_PERM;} else {swap_type = BOOT_SWAP_TYPE_TEST;}rc = boot_write_swap_info(fap, swap_type, 0);}flash_area_close(fap);return rc;case BOOT_MAGIC_BAD:/* The image slot is corrupt. There is no way to recover, so erase the* slot to allow future upgrades.*/rc = flash_area_open(FLASH_AREA_IMAGE_SECONDARY(image_index), &fap);if (rc != 0) {return BOOT_EFLASH;}flash_area_erase(fap, 0, fap->fa_size);flash_area_close(fap);return BOOT_EBADIMAGE;default:assert(0);return BOOT_EBADIMAGE;}}
这里我之所以贴出这个函数的实现是因为公版就采用的操作区域的方式来设置了三个标志:
- 交换标志
- 魔幻数
- 确认标志
这样其实时很不友好的。
