- 嵌入式
- 固件
- 驱动开发
【免费下载链接】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_BLDAPLINK_BUILD_KEY_BL(0x9B939D93)与接口固件的DAPLINK_BUILD_KEY_IF(0x9B939E8F)定义于 daplink.h,是固件身份校验的关键凭据,后文会再次涉及。
三、安全更新设计:向量表的“临时接管”
更新引导加载程序有一个固有的风险:接口固件在更新过程中需要擦除并重写向量表(Vector Table),而向量表是设备上电后第一条指令所在的位置。如果擦除后、写回前设备断电,芯片将没有任何有效的向量表可执行,直接进入不可引导(nonbootable)的砖机状态。
3.1 失电窗口最小化策略
文档给出的策略是:在正式更新前,接口固件先把引导加载程序的向量表替换为自己的向量表。这样,在批量加载引导加载程序主体期间,向量表始终有效,即使发生失电,设备也能正常启动(只是启动到接口固件而非引导加载程序)。
完整的更新流程如下(加粗步骤为设备绝对不能失电的临界区):
- 擦除引导向量表,并把接口固件的向量表编程到该位置;
- 逐扇区擦除并编程引导加载程序的新固件;
- 擦除引导向量表,并把新引导加载程序的向量表编程到该位置。
流程对应的核心实现在 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
相关推荐
如何快速开始Syncfusion Dashboard:React + Syncfusion环境搭建教程
如何快速开始Syncfusion Dashboard:React + Syncfusion环境搭建教程 想要快速构建功能强大的React管理仪表盘吗?Syncf
深入理解 inconshreveable/go-update 实现安全的Go程序自更新机制
深入理解 inconshreveable/go update 实现安全的Go程序自更新机制 概述 inconshreveable/go update 是一个专门
开发工具UEFI安全启动终极指南:shim引导加载器深度解析与实战应用
UEFI安全启动终极指南:shim引导加载器深度解析与实战应用 在现代计算机系统中, UEFI安全启动 已成为保护系统免受恶意软件侵害的关键技术。而 shim引
操作系统应用安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考