embassy book

引导加载程序

embassy-boot 是一个轻量级的引导加载程序,支持以电源故障安全的方式进行固件应用程序升级,具有试用启动和回滚功能。

引导加载程序既可以作为库使用,也可以在您对默认配置和功能感到满意时直接刷写。

根据设计,引导加载程序不提供任何网络功能。用于获取新固件的网络功能可以由用户应用程序提供,使用引导加载程序作为库来更新固件,或者使用引导加载程序作为库并自行添加此功能。

引导加载程序通过依赖 embedded-storage 特性来支持内部和外部闪存。引导加载程序可选地支持验证已进行数字签名的固件(推荐)。

硬件支持

引导加载程序支持

  • nRF52 有和没有软设备
  • STM32 L4、WB、WL、L1、L0、F3、F7 和 H7
  • 树莓派:RP2040

一般来说,引导加载程序可以在任何为其内部闪存实现 embedded-storage 特性的平台上工作,但可能需要自定义初始化代码才能工作。

STM32L0x1 设备需要启用 flash-erase-zero 功能。

设计

引导加载程序闪存布局

引导加载程序将存储划分为 4 个主要分区,这些分区在创建引导加载程序实例时或通过链接器脚本配置:

  • BOOTLOADER - 引导加载程序放置的位置。引导加载程序本身消耗大约 8kB 的闪存,但如果您需要调试它并且有可用空间,则将其增加到 24kB 将允许您使用 probe-rs 运行引导加载程序。
  • ACTIVE - 主要应用程序放置的位置。引导加载程序将尝试在此分区的开头加载应用程序。此分区仅由引导加载程序写入。此分区所需的大小取决于您的应用程序的大小。
  • DFU - 要交换的应用程序放置的位置。此分区由应用程序写入。此分区必须比 ACTIVE 分区至少大 1 页,因为交换算法使用额外的空间来确保数据电源安全复制:

Partition Sizedfu= Partition Sizeactive+ Page Sizeactive + 所有值均以字节为单位指定。

  • BOOTLOADER STATE - 引导加载程序存储当前状态的位置,描述是否需要交换活动分区和 dfu 分区。当新固件已写入 DFU 分区时,会写入一个魔术字段,指示引导加载程序应交换分区。此分区必须能够存储魔术字段以及分区交换进度。分区大小由下式给出:

Partition Sizestate = Write Sizestate + (2 × Partition Sizeactive / Page Sizeactive) + 所有值均以字节为单位指定。

ACTIVE (+BOOTLOADER)、DFU 和 BOOTLOADER_STATE 的分区可以放置在单独的闪存中。引导加载程序使用的页面大小由 ACTIVE 和 DFU 页面大小的最小公倍数确定。 BOOTLOADER_STATE 分区必须足够大,以存储 ACTIVE 和 DFU 分区中每页一个字。

引导加载程序有一个平台无关的部分,它实现了电源故障安全交换算法,给定分区设置的边界。平台特定的部分是一个最小的垫片,提供额外的功能,例如看门狗或支持 nRF52 软设备。

注意:应用程序和引导加载程序的链接器脚本看起来相似,但引导加载程序的 FLASH 区域必须指向 BOOTLOADER 分区,应用程序的 FLASH 区域必须指向 ACTIVE 分区。

FirmwareUpdater

FirmwareUpdater 是一个对象,用于方便地将固件刷写到 DFU 分区,然后在下次重置时将其标记为准备好与活动分区交换。它的主要方法是 write_firmware,它在闪存"写入块"大小(通常为 4KiB)时调用一次,以及 mark_updated,这是最终调用。

验证

引导加载程序支持验证已刷写到 DFU 分区的固件。验证要求固件已使用 link:https://ed25519.cr.yp.to/[`ed25519`] 签名进行数字签名。启用验证后,将调用 FirmwareUpdater::verify_and_mark_updated 方法来代替 mark_updated。需要公钥和签名,以及已刷写固件的实际长度。如果验证失败,则固件将不会被标记为已更新,因此将被拒绝。

签名通常与要更新的固件一起传递,而不是写入闪存。如何提供签名是固件的责任。

要启用验证,请在依赖 embassy-boot crate 时使用 ed25519-daleked25519-salty 功能。我们目前推荐 ed25519-salty,因为它体积小。

关于密钥和使用 ed25519 签名的提示

Ed25519 是一个公钥签名系统,您有责任保护私钥的安全。我们建议将公钥嵌入到您的程序中,以便可以轻松地将其传递给 verify_and_mark_updated。以下是在您的固件中声明公钥的示例:

static PUBLIC_SIGNING_KEY: &[u8] = include_bytes!("key.pub");

签名通常通过附加的方式与固件一起传递。

Ed25519 密钥可以通过多种工具生成。我们推荐 link:https://man.openbsd.org/signify[`signify`],因为它被广泛用于签名和验证 OpenBSD 发行版,并且使用起来很简单。

以下 Bash 命令集可用于在 Unix 平台上生成公钥和私钥,并生成本地 key.pub 文件,其中删除了 signify 文件头。在安全位置声明 SECRETS_DIR 环境变量。

signify -G -n -p $SECRETS_DIR/key.pub -s $SECRETS_DIR/key.sec
tail -n1 $SECRETS_DIR/key.pub | base64 -d -i - | dd ibs=10 skip=1 > key.pub
chmod 700 $SECRETS_DIR/key.sec
export SECRET_SIGNING_KEY=$(tail -n1 $SECRETS_DIR/key.sec)

然后,要签署您的固件,给定 FIRMWARE_DIR 的声明和 myfirmware 的固件文件名:

shasum -a 512 -b $FIRMWARE_DIR/myfirmware | head -c128 | xxd -p -r > $SECRETS_DIR/message.txt
signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig
cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed
tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed

请记住,保护 $SECRETS_DIR/key.sec 密钥,因为泄露它意味着另一方可以签署您的固件。