☰
DAPLink 引导加载程序自动更新机制:原理、启用方式与安全实现深度解析
2026/10/5 2:20:54 网站建设 项目流程
  • 嵌入式
  • 固件
  • 驱动开发

【免费下载链接】DAPLink

项目地址:https://gitcode.com/gh_mirrors/da/DAPLink
点击查看免费下载

DAPLink 的接口固件(Interface Firmware)支持将引导加载程序(Bootloader)固件与自身捆绑在一起,并在接口固件首次启动时自动完成引导加载程序的更新。本文以 docs/BOOTLOADER_UPDATES.md 为核心脉络,结合仓库源码(bootloader_update.c、iap_flash_intf.c、main_interface.c 等)解析其工作流程、失电安全设计、版本校验与降级防护规则,并给出在具体板卡上启用该机制的完整配置方法。读完本文,你将掌握 DAPLink 引导加载程序随接口固件同包更新的原理、如何为板卡开启该特性,以及它在更新过程中的三大安全防线与三类使用限制。

一、机制总览:引导加载程序与接口固件同包更新

在传统固件升级流程中,引导加载程序通常需要借助调试器或独立的刷写工具单独更新,用户操作繁琐且容易出错。DAPLink 改变了这一模式:

DAPLink 能够把引导加载程序固件与接口固件捆绑在一起,并在接口固件首次启动时应用一次引导加载程序更新。这样一来,引导加载程序的更新就可以与接口固件的更新在同一时刻完成,用户无需任何特殊操作。

这一定位由 source/daplink/interface/bootloader_update.c 的注释明确阐述:

/** * @file bootloader_update.c * @brief Logic to perform a bootloader update when enabled */

从调用时序看,更新检查发生在接口固件启动流程的早期阶段。在 main_interface.c 中:

// Update versions and IDs info_init(); // Update bootloader if it is out of date bootloader_check_and_update(); // USB usbd_init();

即接口固件完成info_init()(读取并校验固件版本、CRC 等元信息)之后、USB 初始化之前,就调用bootloader_check_and_update()。这保证了引导加载程序更新在设备对外呈现为可用调试器/MSC 设备之前完成,用户看到的是一个“已经就绪”的 DAPLink。

二、启用方式:定义DAPLINK_BOOTLOADER_UPDATE

要为某块板卡或接口电路(HIC)启用引导加载程序更新,只需定义宏DAPLINK_BOOTLOADER_UPDATE:

#define DAPLINK_BOOTLOADER_UPDATE

定义该宏之后,即可走常规的发布流程(详见 developers guide)为所有目标构建固件。该宏在源码中扮演“开关”角色,见 bootloader_update.c:

#if !defined(DAPLINK_BOOTLOADER_UPDATE) #define DAPLINK_BOOTLOADER_UPDATE 0 #endif #if DAPLINK_BOOTLOADER_UPDATE // The bootloader must be built first or this header will not be found #include "bootloader_image.c" #else //DAPLINK_BOOTLOADER_UPDATE static const unsigned int image_start = 0; static const unsigned int image_size = 0; static const char image_data[1]; #endif //DAPLINK_BOOTLOADER_UPDATE

这里有一个非常关键的构建依赖:引导加载程序必须先构建,否则bootloader_image.c这个头文件不会存在。该文件由构建系统的后处理脚本自动生成,见 tools/post_build_script.py:

def post_build_script(input_file, output_file, board_id=None, ...): ... output_file_c = output_format_file + ".c" output_file_c_generic = join(dirname(output_file), "bootloader_image.c")

脚本在构建引导加载程序时,把其二进制镜像连同起始地址、大小一起转写为 C 数组(image_start、image_size、image_data),供接口固件构建时直接嵌入。这正是“捆绑”的实现方式——引导加载程序的镜像被静态包含进接口固件,运行时由接口固件负责将其写回引导区。

未定义该宏时,接口固件中嵌入的是空镜像(image_size = 0),bootloader_check_and_update()会因update_present为假而直接返回,不做任何更新动作。

2.1 引导加载程序构建的配套宏

从 records/daplink/bootloader.yaml 可以看到引导加载程序构建的公共宏定义:

common: macros: - DAPLINK_BL - DAPLINK_BUILD_KEY=0x9B939D93 # DAPLINK_BUILD_KEY_BL

DAPLINK_BUILD_KEY_BL(0x9B939D93)与接口固件的DAPLINK_BUILD_KEY_IF(0x9B939E8F)定义于 daplink.h,是固件身份校验的关键凭据,后文会再次涉及。

三、安全更新设计:向量表的“临时接管”

更新引导加载程序有一个固有的风险:接口固件在更新过程中需要擦除并重写向量表(Vector Table),而向量表是设备上电后第一条指令所在的位置。如果擦除后、写回前设备断电,芯片将没有任何有效的向量表可执行,直接进入不可引导(nonbootable)的砖机状态。

3.1 失电窗口最小化策略

文档给出的策略是:在正式更新前,接口固件先把引导加载程序的向量表替换为自己的向量表。这样,在批量加载引导加载程序主体期间,向量表始终有效,即使发生失电,设备也能正常启动(只是启动到接口固件而非引导加载程序)。

完整的更新流程如下(加粗步骤为设备绝对不能失电的临界区):

  1. 擦除引导向量表,并把接口固件的向量表编程到该位置;
  2. 逐扇区擦除并编程引导加载程序的新固件;
  3. 擦除引导向量表,并把新引导加载程序的向量表编程到该位置。

流程对应的核心实现在 source/daplink/drag-n-drop/iap_flash_intf.c 中。该文件实现了flash_intf_iap_protected——一个“受保护的 IAP 闪存接口”,其声明与默认弱实现分别在 flash_intf.h 和 flash_intf.c:

__WEAK const flash_intf_t *const flash_intf_iap_protected = 0;

启用引导加载程序更新的平台则提供真实实现(iap_flash_intf.c):

const flash_intf_t *const flash_intf_iap_protected = &flash_intf;

3.2 临界擦写:critical_erase_and_program

临界区的擦写由critical_erase_and_program()完成(iap_flash_intf.c):

static error_t critical_erase_and_program(uint32_t addr, const uint8_t *data, uint32_t size) { uint32_t iap_status; if (size < DAPLINK_MIN_WRITE_SIZE) { util_assert(0); return ERROR_INTERNAL; } // CRITICAL SECTION BELOW HERE! // If something goes wrong with either // the erase or write then the device // will no longer be bootable. // Erase the first sector iap_status = flash_erase_sector(addr); ... // Program the interface's vector table iap_status = flash_program_page(addr, size, (uint8_t *)data); ... }

源码注释用大写明确警告:该区域内一旦擦除或写入出错,设备将不再可引导。函数先擦除首扇区,再立刻编程“接口固件的向量表”,正是文档所述三步流程中的第 1 步与第 3 步的落地点。

3.3 扇区擦除与页写入的拦截逻辑

为了让普通拖放(drag-n-drop)刷写流程能够安全地更新引导区,iap_flash_intf.c对写入与擦除做了“拦截(intercept)”处理:

  • 页写入拦截intercept_page_write()(iap_flash_intf.c):只允许写入更新区[DAPLINK_ROM_UPDATE_START, DAPLINK_ROM_UPDATE_START + DAPLINK_ROM_UPDATE_SIZE);落在更新区首扇区内的数据先被暂存到sector_buf,并同步累计 CRC32;当收到更新区的最后一个页时,校验镜像末尾内嵌的 CRC(ERROR_BL_UPDT_BAD_CRC表示 CRC 不符),随后编程该页,并调用critical_erase_and_program(DAPLINK_ROM_UPDATE_START, sector_buf, DAPLINK_SECTOR_SIZE)把暂存的首扇区(含接口向量表)写回更新区起始处。

  • 扇区擦除拦截intercept_sector_erase()(iap_flash_intf.c):当擦除目标恰好是DAPLINK_ROM_UPDATE_START时,改为执行critical_erase_and_program(addr, (uint8_t *)DAPLINK_ROM_IF_START, DAPLINK_MIN_WRITE_SIZE)——擦除后立即把接口固件开头的向量表编程进该位置,保证擦除瞬间过后设备依然具备有效向量表。

需要说明的是,只有daplink_is_interface()为真(即运行的是接口固件而非引导加载程序)时,拦截逻辑才会生效(iap_flash_intf.c)。

3.4 内存布局约束

向量表“先换后写”的策略之所以成立,依赖于 DAPLink 固定且连续的内存分区布局。daplink.h 中的编译期断言强制这些约束:

// ROM check COMPILER_ASSERT(DAPLINK_ROM_BL_START == DAPLINK_ROM_START); COMPILER_ASSERT(DAPLINK_ROM_IF_START + DAPLINK_ROM_IF_SIZE == DAPLINK_ROM_CONFIG_USER_START); COMPILER_ASSERT(DAPLINK_ROM_CONFIG_USER_START + DAPLINK_ROM_CONFIG_USER_SIZE == DAPLINK_ROM_START + DAPLINK_ROM_SIZE);

即 ROM 从引导加载程序区开始,紧接接口固件区,再是用户配置区,三区连续、无空隙、恰好占满全部 ROM。具体的偏移量由各板卡的_bl.c文件以编译期断言固化,例如:

  • k20dx_bl.c:COMPILER_ASSERT(DAPLINK_ROM_IF_START == KB(32));——接口固件从 0x8000(32 KB)处加载;
  • kl27z_microbit_bl.c:DAPLINK_ROM_IF_START == KB(32)且DAPLINK_ROM_IF_SIZE == (KB(128) - KB(32) - KB(1))。

注释“Warning - changing the interface start will break backwards compatibility”直白地提醒:改动接口固件加载偏移会破坏向后兼容。

四、更新前的三项安全检查

接口固件在执行引导加载程序更新前,会依次通过三道检查(bootloader_update.c 中的bootloader_check_and_update()):

void bootloader_check_and_update(void) { int same; error_t ret; bool update_present = image_size > 0; if (!update_present) { return; } if (info_get_bootloader_present() && (info_get_bootloader_version() > DAPLINK_VERSION)) { // Bootloader is more recent than the one we have so // don't change it return; } if (!interface_image_valid()) { // The interface is corrupt so don't attempt // to apply the update util_assert(0); return; } same = memcmp((void*)image_start, image_data, image_size) == 0; if (!same) { ret = flash_manager_init(flash_intf_iap_protected); ... ret = flash_manager_data(image_start, (const uint8_t*)image_data, image_size); ... ret = flash_manager_uninit(); ... } }

4.1 检查一:不降级(版本比较)

if (info_get_bootloader_present() && (info_get_bootloader_version() > DAPLINK_VERSION)) { // Bootloader is more recent than the one we have so // don't change it return; }

如果当前引导加载程序具有有效签名,且其版本号大于接口固件内嵌副本的版本(DAPLINK_VERSION),则接口固件不会替换它。这是文档明确承诺的行为:

接口固件不会降级引导加载程序。若当前引导加载程序拥有有效签名且版本高于接口固件中的副本,接口固件将不会替换它。

“有效签名”的判断来自info_get_bootloader_present()(info.c),它依次校验:引导区大小非零(DAPLINK_ROM_BL_SIZE != 0)、引导区可读、build_key等于DAPLINK_BUILD_KEY_BL、hic_id与当前 HIC 一致——任一不满足都视为“不存在有效引导加载程序”。

4.2 检查二:接口固件自检 CRC

static bool interface_image_valid() { uint32_t stored_crc = 0; uint32_t computed_crc; if (flash_is_readable(DAPLINK_ROM_IF_START + DAPLINK_ROM_IF_SIZE - 4, 4)) { stored_crc = *(uint32_t *)(DAPLINK_ROM_IF_START + DAPLINK_ROM_IF_SIZE - 4); } computed_crc = info_get_crc_interface(); return computed_crc == stored_crc; }

接口固件会对自身镜像做一次 CRC32 校验:把镜像末尾 4 字节中存储的 CRC 与info_get_crc_interface()现算出的 CRC 比对。若发现自身已损坏,则放弃引导加载程序更新,同时util_assert(0)会在大容量存储(MSC)设备上呈现一条断言信息。CRC 的计算发生在info_crc_compute()(info.c),它对引导区、接口区、用户配置区分别用crc32计算(各自剔除末尾 4 字节的 CRC 槽位)。

4.3 检查三:镜像比对(跳过重复更新)

same = memcmp((void*)image_start, image_data, image_size) == 0; if (!same) { ... }

接口固件把内嵌副本与当前引导加载程序逐字节比对,若二者相同则不做任何写操作。这避免了无意义的擦写,也降低了每次启动时的损耗与风险窗口。

三道检查全部通过后,才经由flash_manager_init(flash_intf_iap_protected)→flash_manager_data(...)→flash_manager_uninit()完成整个更新,使用的正是上文所述的“受保护 IAP 接口”。

五、注意事项与使用限制

5.1 固定加载偏移(0x8000)

DAPLink 引导加载程序对接口固件的加载位置有固定要求——典型情况下接口固件从 ROM 起始偏移 0x8000 处加载(对应 32 KB,见各板卡_bl.c中的COMPILER_ASSERT(DAPLINK_ROM_IF_START == KB(32)))。如果更新到一个偏移不同的引导加载程序,则现有的接口固件将无法继续使用,必须改用为新偏移构建的接口固件。

5.2 不支持降级与第三方引导加载程序

该机制不支持降级,也不支持加载第三方引导加载程序。若确有此类需求,只能通过调试器刷写,或构建 DAPLink 的自定义版本。

5.3 LPC11U35 无引导加载程序

LPC11U35 接口本身没有引导加载程序(仓库中不存在 lpc11u35 的_bl板卡文件,其板卡源码为 swdap-lpc11u35.c),因此该接口上无法使用引导加载程序更新机制。

5.4 更新状态的查看方式

更新是否发生、当前引导加载程序版本与 CRC 等信息,都可以通过接口固件挂载出的DETAILS.TXT查看。相关字段在 vfs_user.c 中生成,例如:

Daplink Mode: Interface Interface Version: @V Bootloader Version: xxxx Git SHA: ... USB Interfaces: MSD, CDC, HID Bootloader CRC: ... Interface CRC: ...

其中 “Bootloader Version” 仅在DAPLINK_ROM_BL_SIZE != 0且引导加载程序存在时输出(vfs_user.c),是快速确认更新是否成功落盘的直观手段。

六、小结

DAPLink 的引导加载程序自动更新机制,本质上是把“引导加载程序镜像内嵌进接口固件 + 首启动时自动刷写 + 向量表临时接管”三者组合起来:

  • 启用:为板卡定义DAPLINK_BOOTLOADER_UPDATE,并保证先构建引导加载程序(生成bootloader_image.c)再构建接口固件;
  • 安全:通过三步流程(换入接口向量表 → 逐扇区写入 → 换回新引导向量表)把不可引导的失电窗口压缩到最小,并辅以 IAP 拦截、CRC 校验等手段;
  • 防线:版本不降级、接口自检 CRC 不过则放弃、镜像相同则跳过;
  • 边界:固定加载偏移、不支持降级/第三方引导加载程序、LPC11U35 无此能力。

理解了这些约束与实现细节,无论是为自有板卡接入该机制,还是排查引导加载程序更新失败的问题,都能做到有据可依。

  • 嵌入式
  • 固件
  • 驱动开发

【免费下载链接】DAPLink

项目地址:https://gitcode.com/gh_mirrors/da/DAPLink
点击查看免费下载
上一篇:PyPTO Tile Framework 测试工程架构与设计指南:UT/ST 目录规划、CMake 编排与执行加速体系
下一篇:XiaoMusic:小爱音箱播放全网音乐的 Docker 部署教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询