mcuboot学习笔记

mcuboot简介

MCUboot 的目标是为引导加载程序,为微控制器系统上的系统闪存布局定义一个通用基础设施,并提供一个安全的引导加载程序,使软件升级变得容易。

–摘自mcuboot官网 mcuboot.

mcuboot一些基本概念

image header:

#define IMAGE_MAGIC                 0x96f3b83d
#define IMAGE_HEADER_SIZE           32
struct image_version {
    uint8_t iv_major;
    uint8_t iv_minor;
    uint16_t iv_revision;
    uint32_t iv_build_num;
};
/** Image 头部.  所有栏位使用小端模式. */
struct image_header {
    uint32_t ih_magic;
    uint32_t ih_load_addr;
    uint16_t ih_hdr_size;           /* image头部的大小 (bytes). */
    uint16_t ih_protect_tlv_size;   /* 受保护的TLV大小 (bytes). */
    uint32_t ih_img_size;           /* image大小,不包括头部. */
    uint32_t ih_flags;              /* IMAGE_F_[...]. */
    struct image_version ih_ver;
    uint32_t _pad1;
};

ih_load_addr的作用:在使用mcuboot image tool打包image package时,可以加入–load-addr选项,指定此image slot的加载地址。如果ih_load_addr与image slot的实际存储地址(RAM或者flash中)相同,那么当前代码运行模式为XIP模式。

以STM32F407 discovery开发板为例,它是XIP模式(代码直接可以从internal flash上运行): flash_base_addr = 0x08000000; image_header_size = 0x200; image_primary_slot_offset = 0x20000; ih_load_addr = 0x08020200 ih_load_addr与image slot在flash中存储的地址相同,则可以判定当前为XIP模式,可以直接从ih_load_addr处启动image,不需要做代码搬运动作。 如果ih_load_addr与image slot在flash中存储的地址不相同,则需要将image slot从flash中拷贝到ih_load_addr指定的地址上,再启动image。

image slot:

image slot是mcuboot管理image的最小单位,每一个slot都表示一个内容完整的,可以运行的image bin。flash的一部分可以划分为多个镜像区,每个镜像区包含两个image slot:一个primary slot和一个secondary slot。通常情况下,mcuboot bootloader只会运行primary slot中的image,因此必须配置image,以便它们可以从flash中的固定位置运行(例外情况是 direct-xipram-load 升级模式)。 如果mcuboot bootloader需要运行驻留在secondary slot中的image,则必须先将其内容复制到primary slot中,方法是交换两个镜像或覆盖主插槽的内容。 mcuboot bootloader支持基于交换或覆盖的镜像升级,但必须在构建时进行配置以选择这两种策略之一。 除了镜像区域的slot外,bootloader还需要一个scratch(暂存区)以允许可靠的镜像交换。

image trailer:

image trailer位于image的最后,在启动时,bootloader通过检查镜像尾部来确定引导交换类型,其结构如下:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~                                                               ~
~    Swap status (BOOT_MAX_IMG_SECTORS * min-write-size * 3)    ~
~                                                               ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 Encryption key 0 (16 octets) [*]              |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 Encryption key 1 (16 octets) [*]              |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      Swap size (4 octets)                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   Swap info   |           0xff padding (7 octets)             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   Copy done   |           0xff padding (7 octets)             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   Image OK    |           0xff padding (7 octets)             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       MAGIC (16 octets)                       |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  1. *:星标表示,仅当加密选项启用时才显示(MCUBOOT_ENC_IMAGES)。
  2. 加密密钥:密钥加密密钥 (KEY)。 镜像加密和解密需要这些密钥。
  3. 交换大小:当开始新的交换操作时,需要交换的总大小(基于具有最大镜像的插槽 + TLV)被写入此位置,以便在执行交换时重置的情况下更容易恢复。
  4. Swap info:占用一个字节,包含如下信息:
    1. 交换类型:存储在bit 0-3 中。 指示正在进行的交换操作的类型。 当 mcuboot 恢复中断的交换时,它使用此字段来确定要执行的操作类型。 该字段包含下表中的以下值之一。
    2. 镜像编号:存储在bit 4-7 中。 它在单镜像启动时始终为 0 值。 在多镜像引导的情况下,它指示中断发生时交换了哪个镜像(在所有镜像交换操作的情况下,使用相同的scratch区域。因此,如果在恢复交换操作时在scratch区发现启动状态,则该字段用于确定尾部属于哪个镜像)。
  5. 拷贝完成标志(copy done):一个字节的信息,用来指示这个插槽中的镜像是否拷贝完成(0x01:完成,0xff:没有完成)。
  6. 镜像有效标志(image ok):一个字节的信息,用来指示这个插槽中的镜像是否被用户确认完整有效(0x01:确认过,0xff:没有确认)。
  7. 魔数(magic):以下的16字节,以主机字节序列(host-byte-order HBO)写入:
const uint32_t boot_img_magic[4] = {
    0xf395c277,
    0x7fefd260,
    0x0f505235,
    0x8079b62c,
};

mcuboot flash map:

mcuboot使用结构体struct flash_area来管理flash分区,每一个flash area都被赋予一个唯一的ID,通过检索flash area id就可以查询到该flash area的offset和size。

/**
 * @brief Structure describing an area on a flash device.
 *
 * Multiple flash devices may be available in the system, each of
 * which may have its own areas. For this reason, flash areas track
 * which flash device they are part of.
 */
struct flash_area {
	uint8_t fa_id;       /**This flash area's ID; unique in the system.*/
	uint8_t fa_device_id;/*** ID of the flash device this area is a part of.*/
	uint16_t pad16;
	uint32_t fa_off;     /*** This area's offset, relative to the beginning of its flash device's storage.*/
	uint32_t fa_size;    /*** This area's size, in bytes.*/
};

#define BOOTLOADER_ID   0
#define PRIMARY_ID      1
#define SECONDARY_ID    2
#define SCRATCH_ID      3

/*MCUBOOT_IMAGE_NUMBER = 1的情况*/
#define FLASH_AREA_BOOTLOADER          BOOTLOADER_ID
#define FLASH_AREA_IMAGE_PRIMARY(x)    PRIMARY_ID
#define FLASH_AREA_IMAGE_SECONDARY(x)  SECONDARY_ID
#define FLASH_AREA_IMAGE_SCRATCH       SCRATCH_ID

mcuboot image layout:

A +---------------------+
  | Header              | <- struct image_header
  +---------------------+
  | Payload             |
  +---------------------+
  | TLV area            |
  | +-----------------+ |    struct image_tlv_info with
  | | TLV area header | | <- IMAGE_TLV_PROT_INFO_MAGIC (optional)
  | +-----------------+ |
  | | Protected TLVs  | | <- Protected TLVs (struct image_tlv)
B | +-----------------+ |
  | | TLV area header | | <- struct image_tlv_info with IMAGE_TLV_INFO_MAGIC
C | +-----------------+ |
  | | SHA256 hash     | | <- hash from A - B (struct image_tlv)
D | +-----------------+ |
  | | Keyhash         | | <- indicates which pub. key for sig (struct image_tlv)
  | +-----------------+ |
  | | Signature       | | <- signature from C - D (struct image_tlv), only hash
  | +-----------------+ |
  +---------------------+
  | Padding         	| <- padding behind tlvs area
  +---------------------+ 
  | Trailer         	| <- image trailer
  +---------------------+
  • SHA256 hash:A–B之间内容的SHA256信息摘要,如果TLV area包含protected tlvs,则SHA256信息摘要包含image header + image payload + protected tlvs,如果TLV area不包含protected tlvs,则SHA256信息摘要仅包含image header + image payload.
  • Keyhash:public key hash(使用3rd-party/mcuboot/scripts/imgtool.py getpub -k your-key.pem命令生成数组格式的public key)
  • Signature:签名(使用private key对SHA256 hash做加密)
  • Padding:填充
  • Trailer:image trailer

image upgrade mode:

固件升级包含以下三种模式:

  • direct-xip mode: eXecute In Place(芯片内执行),是指CPU直接从存储器中读取程序代码执行,而不用再读到内存中。应用程序可以直接在flash闪存内运行,不必再把代码读到系统RAM中。在此模式下,bootloader可以直接从primary slot或secondary slot运行镜像(没有必须将其移动/复制到primary slot中)。在启动时,bootloader首先在插槽中查找镜像,然后检查镜像标头中的版本号。 它选择最新的镜像(具有最高版本号)和然后检查其有效性(完整性检查、签名验证等)

  • ram loading mode:primary slot & secondary slot的地位是相同的,与 direct-xip 模式一样,此模式也通过读取镜像头中的镜像版本号来选择最新的镜像。 但是不是就地执行它,而是将最新的镜像复制到用于执行的 RAM。

  • normal mode:mcuboot bootloader只会运行primary slot,如果需要运行secondary slot,则需要将其复制到primary slot中,根据primary slot & secondary slot中image的交换/移动策略,有下面三种方式:

    • overwrite :secondary slot直接覆盖掉primary slot

    • swap

      • use scratch : 增加一个额外的临时区(scratch),用来作为交换primary slot & secondary slot的过渡区域
      • use move :不使用临时区,而是在构建primary slot的存储区域时,比secondary slot多使用一个sector,然后完成image的交换

mcuboot image升级策略

  • fih_int boot_go(struct boot_rsp *rsp)

    • fih_int context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp)

      • 三次遍历所有image的primary slot & secondary slot

        • 第一次遍历:完成之前所有没中断的swap operation(如果有的话),确定此次启动的swap type。比较重要的函数:void boot_prepare_image_for_update(struct boot_loader_state *state, struct boot_status *bs)。

        • 第二次遍历:根据上一次遍历返回的swap type,如果需要swap的话,执行int boot_perform_update(struct boot_loader_state *state, struct boot_status *bs),完成primary slot & secondary slot的交换。在primary slot image trailer中设置image ok & copy done标志。

          #ifndef MCUBOOT_OVERWRITE_ONLY
              /* The following state needs image_ok be explicitly set after the
               * swap was finished to avoid a new revert.
               */
              swap_type = BOOT_SWAP_TYPE(state);
              if (swap_type == BOOT_SWAP_TYPE_REVERT ||
                      swap_type == BOOT_SWAP_TYPE_PERM) {
                  rc = swap_set_image_ok(BOOT_CURR_IMG(state));
                  if (rc != 0) {
                      BOOT_SWAP_TYPE(state) = swap_type = BOOT_SWAP_TYPE_PANIC;
                  }
              }
              if (BOOT_IS_UPGRADE(swap_type)) {
                  rc = swap_set_copy_done(BOOT_CURR_IMG(state));
                  if (rc != 0) {
                      BOOT_SWAP_TYPE(state) = BOOT_SWAP_TYPE_PANIC;
                  }
              }
          #endif /* !MCUBOOT_OVERWRITE_ONLY */
          
        • 第三次遍历:此时所有的image swap都已经完成,在启动primary slot之前,需要先验证下primary slot是否有效 (fih_int boot_validate_slot(struct boot_loader_state *state, int slot, struct boot_status *bs))。验证的大致步骤是:

          • 1)计算image header + image payload ( + protected tlv)内容的sha256 hash,将sha256 hash计算结果同tlv area中的sha256 hash做比较,验证image的完整性。
          • 2)从tlv area中读取出IMAGE_TLV_KEYHASH(public key hash),然后在用户的public key(bootutil_keys[])中搜索是否有相同的public key,这一步是验证public key的有效性。
          • 3)从tlv area中读取出EXPECTED_SIG_TLV(签名,即对sha256 hash摘要使用private key加密),然后使用步骤2)中的public key解密EXPECTED_SIG_TLV,将解密结果同步骤1)中计算出的sha256 hash作比较,相同的话表示验签成功,否则验签失败。
      • 最后返回primary slot的位置和image header信息,交由用户的代码启动primary slot

新的升级

MCUBOOT_OVERWRITE_ONLY

这种模式是直接用secondary slot覆盖primary slot,因为没有备份区域,所以覆盖之后无法恢复primary slot。

MCUBOOT_SWAP_USING_SCRATCH

增加一个额外的临时区(scratch),用来作为交换primary slot & secondary slot的过渡区域,交换之后如果secondary slot无法运行,还可以执行revert操作,恢复primary slot到交换之前的状态。此方式的优点是可以对原版本做保护,缺点也显而易见,flash的使用会增大,同时scratch区域会被频繁擦写,增加了flash损耗。

关于scratch,还有些补充说明,不论primary slot & secondary slot占用几个sector,假设scratch的大小设置为只占用一个sector的大小,此时primary slot & secondary slot交换的最小单位是一个flash sector。所以当primary slot & secondary slot较大时(占用多个sector size),那么scratch将会被频繁的擦写,这时scratch size的大小就需要重新慎重考虑,因为flash的擦写是有次数限制的。

MCUBOOT_SWAP_USING_MOVE

不使用临时区,而是在构建primary slot的存储区域时,比secondary slot多使用一个sector,然后完成image的交换:

1)将主插槽的所有扇区向上移动一个扇区。 从 N=0 开始:
2)将第 N 个扇区从次插槽复制到主插槽的第 N 个扇区。
3)将第 (N+1) 个扇区从主槽复制到辅助槽的第 N 个扇区。
4)重复第 2 步和第 3 步,直到交换所有插槽的扇区。

假设primary slot & secondary slot image内容都占用5个sector大小,假如使用not use scratch swap这种交换方式:
primary slot
+-----------+-----------+-----------+-----------+-----------+-----------+
| sector0   | sector1	|  sector2  | sector3	| sector4	|			|
+-----------+-----------+-----------+-----------+-----------+-----------+
整体向右移动一个sector
+-----------+-----------+-----------+-----------+-----------+-----------+
|     	    | sector0   | sector1	|  sector2  | sector3	| sector4	|
+-----------+-----------+-----------+-----------+-----------+-----------+
secondary slot
+-----------+-----------+-----------+-----------+-----------+
| sector0   | sector1	|  sector2  | sector3	| sector4	|
+-----------+-----------+-----------+-----------+-----------+
然后以插空的方式完成primary slot & secondary slot的交换
#define BOOTLOADER_SIZE                     (64 * 1024 )
#define APPLICATION_SIZE                    (128 * 1024)
#define BOOTLOADER_START_ADDRESS            (ADDR_FLASH_SECTOR_0)
#define APPLICATION_PRIMARY_START_ADDRESS   (ADDR_FLASH_SECTOR_5)
#define APPLICATION_SECONDARY_START_ADDRESS (ADDR_FLASH_SECTOR_7)
#define BOOTLOADER_SECTOR_SIZE              (16 * 1024)
#define APPLICATION_SECTOR_SIZE             (128 * 1024)
static const struct flash_area bootloader = {
    .fa_id = FLASH_AREA_BOOTLOADER,
    .fa_device_id = FLASH_DEVICE_INTERNAL_FLASH,
    .fa_off = BOOTLOADER_START_ADDRESS,
    .fa_size = BOOTLOADER_SIZE,
};
static const struct flash_area primary_img0 = {
    .fa_id = FLASH_AREA_IMAGE_PRIMARY(0),
    .fa_device_id = FLASH_DEVICE_INTERNAL_FLASH,
    .fa_off = APPLICATION_PRIMARY_START_ADDRESS,
    .fa_size = APPLICATION_SIZE * 2,
};
static const struct flash_area secondary_img0 = {
    .fa_id = FLASH_AREA_IMAGE_SECONDARY(0),
    .fa_device_id = FLASH_DEVICE_INTERNAL_FLASH,
    .fa_off = APPLICATION_SECONDARY_START_ADDRESS,
    .fa_size = APPLICATION_SIZE,
};
void swap_run(struct boot_loader_state *state, struct boot_status *bs, uint32_t copy_size)
{
    uint32_t sz;
    uint32_t sector_sz;
    uint32_t idx;
    uint32_t trailer_sz;
    uint32_t first_trailer_idx;
    uint8_t image_index;
    const struct flash_area *fap_pri;
    const struct flash_area *fap_sec;
    int rc;
    sz = 0;
    g_last_idx = 0;
    sector_sz = boot_img_sector_size(state, BOOT_PRIMARY_SLOT, 0);
    while (1) {
        sz += sector_sz;
        /* Skip to next sector because all sectors will be moved up. */
        g_last_idx++;
        if (sz >= copy_size) {
            break;
        }
    }
    /*
     * When starting a new swap upgrade, check that there is enough space.
     */
    if (boot_status_is_reset(bs)) {
        sz = 0;
        trailer_sz = boot_trailer_sz(BOOT_WRITE_SZ(state));
        first_trailer_idx = boot_img_num_sectors(state, BOOT_PRIMARY_SLOT) - 1;
        while (1) {
            sz += sector_sz;
            if  (sz >= trailer_sz) {
                break;
            }
            first_trailer_idx--;
        }
        if (g_last_idx >= first_trailer_idx) {
            BOOT_LOG_WRN("Not enough free space to run swap upgrade");
            bs->swap_type = BOOT_SWAP_TYPE_NONE;
            return;
        }
    }
	...省略部分代码
}
在mcuboot_config.h中打开MCUBOOT_SWAP_USING_MOVE,primary slot size配置为secondary slot size的两倍,且两个slot sector size相同,swap_run()会遇到"Not enough free space to run swap upgrade"错误,这里的逻辑无法理解。
swap use move的所有条件都已满足,且image payload size + image trailer size没有超过一个sector size.

恢复被中断的升级

MCUBOOT_OVERWRITE_ONLY

MCUBOOT_SWAP_USING_SCRATCH

MCUBOOT_SWAP_USING_MOVE

to do…

如何选择合适的升级方法呢?

升级方式优点缺点
MCUBOOT_OVERWRITE_ONLY不会使用额外的flash区域,算法简单,维护简单不能保护源版本,升级失败后设备可能变砖
MCUBOOT_SWAP_USING_SCRATCH可以保护源版本,升级失败后可以恢复到原来的版本,容错率高flash的使用量增加,scratch区域flash擦写次数成倍增加,损坏风险大大增加,算法复杂,维护困难
MCUBOOT_SWAP_USING_MOVE可以保护源版本,升级失败后可以恢复到原来的版本,容错率高,相对MCUBOOT_SWAP_USING_SCRATCH方式,flash擦写更加均匀此算法仅限于拥有相同布局的flash, 所有插槽的扇区应具有相同的大小

根据flash容量、flash擦写损耗、升级算法的复杂程度几方面来选择最适合你的方案。

mcuboot安全

mcuboot没有实现自己的加密算法库,可以通过开启Mbed-TLS or Tinycrypt来启用第三方加密算法库:

/* Uncomment to use ARM's mbedTLS cryptographic primitives */
#define MCUBOOT_USE_MBED_TLS

/* Uncomment to use Tinycrypt's. */
#define MCUBOOT_USE_TINYCRYPT

mcuboot image tool脚本的使用

python3 ./3rd-party/mcuboot/scripts/imgtool.py -h

Usage: imgtool.py [OPTIONS] COMMAND [ARGS]...

Options:
  -h, --help  Show this message and exit.

Commands:
  create   Create a signed or unsigned image INFILE and OUTFILE are parsed...
  getpriv  Dump private key from keypair
  getpub   Dump public key from keypair
  keygen   Generate pub/private keypair
  sign     Create a signed or unsigned image INFILE and OUTFILE are parsed...
  verify   Check that signed image can be verified by given key
  version  Print imgtool version information

mcuboot移植

mcuboot在stm32f407上的移植例程

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值