ems_boot_handle.c
get_ems_boot_ver()(:196-206)4.2 Boot 补丁主流程 ems_boot_handle()(:86-187)4.3 片内 Flash 逐字节写 on_chip_flash_write()(:49-77)4.4 升级准入判定 ems_upgrade_boot_check()(:215-239)4.5 定时/调度与 uCOS-II 配合4.6 关键全局变量读写5. 对外接口本文件导出(声明于 net/ems_boot_handle.h:14-15)本文件内部静态符号本文件调用的外部函数 / 变量6. 依赖关系依赖(本文件 include)被依赖(会用到本文件)7. 备注与风险点源文件路径:
net/ems_boot_handle.c所属模块:net(网络应用层 — EMS 固件升级) 代码行数:240
1. 文件职责
本文件负责 GD32F407 片内 Flash 起始处 EMS Bootloader(0x08000000 起 11552 字节底层程序)的版本识别、旧版自动打补丁升级,以及"是否允许进入 BOOT 升级"的准入判定。
它处在 EMS 整机升级数据流的最末端:云端/Modbus 下发固件 → 写入外挂 SPI Flash → calculate_ems_flash_md5() 校验通过后,由 net/ems_download.c:172 调用本文件的 ems_upgrade_boot_check() 决定能否落盘重启。
get_ems_boot_ver() 从片内 Flash 绝对地址 0x08000000 + 0x22B4 读取 8 字节版本串,被云端上报(net/ems.c:2084、net/ems.c:2458)和 Modbus 寄存器读取(midware/interface/modbus/modbus.c:4254、:4261、:4268)共用,是 EMS 对外暴露"底层版本"的唯一来源。
当识别到 Boot 为 V1.00.03 时,本文件把整片 Boot 镜像复制到 SDRAM,按固定偏移打补丁(版本号字符 + 两处跳转字节),再用 MD5 双向校验(旧版校验 + 新版校验)后回写片内 Flash,实现 V1.00.03 → V1.00.04 的静默升级。
整个回写过程在 OSSchedLock() 保护下执行,完成后由上层复位系统从新 Boot 启动。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
MCU_START_ADDRESS | 宏 | 0x08000000,片内 Flash / Bootloader 起始地址(ems_boot_handle.c:24) |
MCU_BOOT_FLASH_LEN | 宏 | 11552,参与 MD5 计算与回写的 Boot 字节数(:25) |
BOOT_VER_ADDRESS_OFFSET | 宏 | 0x22B4,Boot 版本字符串在 Boot 镜像内的偏移(:26) |
s_u8_bootloader_ver | 全局静态数组 char[9] | get_ems_boot_ver() 的返回缓冲,8 字符版本 + '\0'(:31) |
s_u8_boot_buf | 全局静态数组 u8[SDRAM_16K],__attribute__((at(SDRAM_BOOT_FILE_ADDR))) | Boot 镜像暂存缓冲,定位在 SDRAM(:32) |
s_u8_old_boot_md5 | 全局静态常量数组 const u8[16] | Boot V1.00.03 全片 MD5 期望值(:33) |
s_u8_new_boot_md5 | 全局静态常量数组 const u8[16] | 打补丁后 Boot V1.00.04 全片 MD5 期望值(:34) |
SDRAM_16K | 宏(外部 hardware/sdram/sdram.h:30) | 16 * 1024,SDRAM 分区单位 |
SDRAM_BOOT_FILE_ADDR | 宏(外部 hardware/sdram/sdram.h:43) | SDRAM_FAULT_LOG_START_ADDR + SDRAM_20K,本文件专用 16K 区 |
FMC_READY / FMC_FLAG_* | 枚举/宏(外部 gd32f4xx_fmc.h) | 片内 Flash 操作返回码与标志位 |
CTL_SECTOR_NUMBER_0 / CTL_SECTOR_NUMBER_1 | 宏(外部 gd32f4xx_fmc.h:257-258) | 片内 Flash 扇区 0 / 1(各 16KB,合计 32KB = APP 起始偏移 0x08000) |
本文件未定义结构体、枚举、联合体和位域。
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
on_chip_flash_write(u32 WriteAddr, u8 *pBuffer, u32 NumToWrite) | static | 逐字节调用 fmc_byte_program 写片内 Flash,单次序列失败则从起点整段重试,最多 3 次(:49) |
ems_boot_handle(void) | static | Boot 补丁主流程:读片内 Boot → 打补丁 → MD5 校验 → 擦扇区回写 → 再校验(:86) |
get_ems_boot_ver(void) | 全局 | 清空静态缓冲后从 0x080022B4 读 8 字节,返回 Boot 版本字符串指针(:196) |
ems_upgrade_boot_check(u32 file_len) | 全局 | 依据当前 Boot 版本与待升级文件长度,判断是否允许进入 BOOT 升级(:215) |
另外在文件头有两条前置声明与定义重复:static u8 ems_boot_handle(void);(:39)和 static u8 on_chip_flash_write(u32, u8 *, u32);(:40)。
4. 核心逻辑解析
4.1 Boot 版本读取 get_ems_boot_ver()(:196-206)
memset(s_u8_bootloader_ver, 0x00, 9);
for i in 0..7: s_u8_bootloader_ver[i] = *(volatile uint8_t*)(0x08000000 + 0x22B4 + i);
return s_u8_bootloader_ver;
- 直接从片内 Flash 地址空间按字节做
volatile读取,不做任何合法性校验,返回的完全取决于烧录进 Boot 的内容。 - 返回静态缓冲的指针,非可重入;调用方不负责释放也不应长期持有。
- 该地址与 Boot 镜像内偏移
0x22B4对应,正好是ems_boot_handle()补丁中s_u8_boot_buf[0x22BB]修改的最后一个字符所在位置("V1.00.03"的'3'→'4')。
4.2 Boot 补丁主流程 ems_boot_handle()(:86-187)
流程分为"校验-打补丁-再校验"与"擦写回写-再校验"两段,各自带一个 5 次重试循环。
第一段:读取与打补丁(:92-136)
for (k = 0; k < 5; k++)
1) memset(s_u8_boot_buf, 0xFF, SDRAM_16K) // 全片填 0xFF
2) 逐字节读 0x08000000 .. +11551 到 s_u8_boot_buf[0..11551] // 11552 次 volatile 读
3) MD5Init / MD5Update(11552) / MD5Final
4) if (memcmp(md5.digest, s_u8_old_boot_md5, 16) != 0) continue; // 必须等于 V1.00.03 的 MD5
5) 按偏移打补丁(见下表)
6) 重新算 MD5,若 != s_u8_new_boot_md5 则 continue
7) l_u8_res = TRUE; break;
IWDG_Feed();
if (!l_u8_res) return FALSE;
补丁偏移表(:111-123):
| 偏移 | 写入值 | 说明 |
|---|---|---|
0x1FF2 | 0xF0 | Boot 内部字节,无注释 |
0x20E2 | 0xF0 | Boot 内部字节,无注释 |
0x22BB | '4' | 版本串 "V1.00.03" → "V1.00.04" |
0x22CA | '2' | 汇编/常量区字节,无注释 |
0x22CB | '2' | 同上 |
0x22CD | '1' | 同上 |
0x22CE | '1' | 同上 |
0x22D0 | '0' | 同上 |
0x22D1 | '4' | 同上 |
注意 0x22CA..0x22D1 只写了 6 个位置中的部分下标(跳过了 0x22CC、0x22CF),说明这些写入的是字符数组中的某一串数字的局部改值,具体语义在源码中没有注释说明。
第二段:擦写回写(:145-186)
l_u8_res = FALSE;
OSSchedLock(); // :147 锁调度器
fmc_unlock(); // :148
for (k = 0; k < 5; k++) {
fmc_flag_clear(FMC_FLAG_RDDERR|PGSERR|PGMERR|WPERR|OPERR|END); // :152
if (fmc_sector_erase(CTL_SECTOR_NUMBER_0) != FMC_READY) continue; // :154 擦 16KB
if (fmc_sector_erase(CTL_SECTOR_NUMBER_1) != FMC_READY) continue; // :159 擦 16KB
if (on_chip_flash_write(MCU_START_ADDRESS, s_u8_boot_buf, 11552) == FALSE) continue; // :164
MD5Init/Update((char *)MCU_START_ADDRESS, 11552)/Final // :169-171
if (memcmp(md5.digest, s_u8_new_boot_md5, 16) != 0) continue; // :173
l_u8_res = TRUE; break;
}
fmc_lock(); // :182
OSSchedUnlock(); // :183
IWDG_Feed(); // :184
return l_u8_res; // :186
- 第二段 MD5 直接对
(char *)MCU_START_ADDRESS即片内 Flash 地址做运算,等于"读回校验",只有回读结果与s_u8_new_boot_md5完全一致才算成功。 on_chip_flash_write()内部已含 3 次重试,第二段外层又套 5 次,因此最坏情况下单次调用会执行5 × (2 次扇区擦除 + 3 × 11552 次字节编程)。
4.3 片内 Flash 逐字节写 on_chip_flash_write()(:49-77)
for (j = 0; j < 3; j++) {
l_u8_res = TRUE;
for (i = 0; i < NumToWrite; i++)
if (fmc_byte_program(WriteAddr + i, pBuffer[i]) != FMC_READY) { l_u8_res = FALSE; break; }
if (l_u8_res == FALSE) continue; else break;
}
return l_u8_res;
- 使用 GD32 标准外设库的
fmc_byte_program()逐字节编程(非字/半字编程),11552 字节需要 11552 次函数调用,耗时明显。 - 重试策略是"整段重来"而非"从失败处续写",若中途某字节写成功、后面的失败,重试时会再次对已写位置调用编程(Flash 只能 1→0,重复写同一值通常可容忍,但属于不规范用法)。
- 无地址对齐检查、无目标区写入范围检查。
4.4 升级准入判定 ems_upgrade_boot_check()(:215-239)
if (memcmp(get_ems_boot_ver(), "V1.00.03", 8) == 0) {
ems_boot_handle(); // 先尝试把旧 Boot 补成 V1.00.04
if (memcmp(get_ems_boot_ver(), "V1.00.03", 8) == 0 && get_length_file_updating(UPDT_EMS) >= 352*1024)
return FALSE; // 补丁失败且待升级文件 ≥ 352KB → 拒绝升级
else
return TRUE;
}
else if (memcmp(get_ems_boot_ver(), "V1.00.04", 8) == 0) return TRUE;
else return FALSE; // 其它任何版本一律拒绝
这是本文件唯一的对外状态机判定,三分支语义如下:
| 当前 Boot 版本 | 补丁结果 | 待升级文件长度 | 返回 |
|---|---|---|---|
| V1.00.03 | 成功升到 V1.00.04 | 任意 | TRUE |
| V1.00.03 | 仍为 V1.00.03 | < 352KB | TRUE(旧 Boot 只能写 352K,小于此值可继续) |
| V1.00.03 | 仍为 V1.00.03 | >= 352KB | FALSE |
| V1.00.04 | — | 任意 | TRUE |
| 其它 | — | 任意 | FALSE |
- 判定结果的使用方是
net/ems_download.c:172;返回 FALSE 时上层会置SYSTEM_RESTART上报标志、清空stUpt[UPDT_EMS]并置boot_error位,不再重启升级。 - 形参
file_len在函数体内从未被使用,长度判定实际走get_length_file_updating(UPDT_EMS)(:222)。 352*1024是硬编码的魔法数字,对应旧版 Boot 可寻址的固件上限。
4.5 定时/调度与 uCOS-II 配合
- 本文件不创建任务、不使用信号量或消息邮箱,纯同步调用式流程。
- 调用链:
net/server.c:3785或midware/interface/modbus/modbus.c:9336→calculate_ems_flash_md5()→ems_boot_handle_check()→ 本文件的ems_boot_handle()。执行体处于 net 任务(TCP/Modbus 升级流程)上下文。 - 唯一的调度器交互是
OSSchedLock()/OSSchedUnlock()(:147、:183),用于保证 32KB 片内 Flash 擦写期间不被其它任务抢占。 - 看门狗喂狗点只有两处:
:138(补丁校验后、擦写前)和:184(擦写后)。擦写循环内部完全没有喂狗。 - 无超时/重试计数上报,失败信息只体现在返回值上。
4.6 关键全局变量读写
| 变量 | 读/写 | 触发条件 |
|---|---|---|
s_u8_boot_buf | 写(源自片内 Flash 读取)、读(回写 Flash) | 每次 ems_boot_handle() 调用 |
s_u8_bootloader_ver | 写 | 每次 get_ems_boot_ver() 调用,会先 memset 清零 |
get_length_file_updating(UPDT_EMS)(外部 g_stEMSInfo.stUpt[UPDT_EMS].u32LenFile) | 只读 | ems_upgrade_boot_check() 的长度判定 |
5. 对外接口
本文件导出(声明于 net/ems_boot_handle.h:14-15)
| 符号 | 使用者 |
|---|---|
char *get_ems_boot_ver(void) | net/ems.c:2084(拼 "EMSBootVer" 上报字段)、net/ems.c:2458(System restart 消息里带 bootver)、midware/interface/modbus/modbus.c:4254/4261/4268(REG_EMS_BOOT_VERSION_HIGH/MIDDLE/LOW_E 三个寄存器,用 sscanf 拆 V%d.%d.%d) |
u8 ems_upgrade_boot_check(u32 file_len) | net/ems_download.c:172 |
本文件内部静态符号
on_chip_flash_write(:49)、ems_boot_handle(:86)、s_u8_boot_buf、s_u8_bootloader_ver、s_u8_old_boot_md5、s_u8_new_boot_md5,均不导出。
本文件调用的外部函数 / 变量
| 外部符号 | 来源模块 |
|---|---|
fmc_unlock / fmc_lock / fmc_sector_erase / fmc_byte_program / fmc_flag_clear、FMC_READY、FMC_FLAG_*、CTL_SECTOR_NUMBER_0/1 | HALLIB/GD32F4xx_standard_peripheral/Include/gd32f4xx_fmc.h |
MD5Init / MD5Update / MD5Final / MD5_CTX | common/md5.h |
IWDG_Feed | hardware/iwdg/iwdg.h:6 |
OSSchedLock / OSSchedUnlock | uCOS-II |
get_length_file_updating | net/upd_download.c:327 |
SDRAM_16K / SDRAM_BOOT_FILE_ADDR | hardware/sdram/sdram.h |
memset / memcmp / strlen / size_t | 标准库(经 includes.h) |
6. 依赖关系
依赖(本文件 include)
ems_boot_handle.h、upd_download.h、ems_download.h、sdram.h、iwdg.h、board_init.h、ems.h、server.h、md5.h、gd32f4xx_fmc.h(:13-22)。
被依赖(会用到本文件)
net/ems_download.c— 调用ems_upgrade_boot_check()(:172)net/ems.c— 调用get_ems_boot_ver()(:2084、:2458)midware/interface/modbus/modbus.c— 调用get_ems_boot_ver()(:4254、:4261、:4268)net/ems_boot_handle.h被上述文件 include
7. 备注与风险点
- 看门狗风险(较高):
ems_boot_handle()第二段在OSSchedLock()保护下执行最多 5 轮2 次扇区擦除 + 3 × 11552 次字节编程,循环内完全没有IWDG_Feed()。按hardware/iwdg/iwdg.c:11的fwdgt_config(4000, 4)(约 0.4~0.5 s 超时),单轮就可能踩到看门狗上限,存在被复位打断导致 Boot 半写入的风险(擦除已做、写入未完成 → 变砖)。 - 无效重试:第一段
for (k = 0; k < 5; k++)(:92)的重试没有意义——输入是同一片 Flash,MD5 不一致必然每次都不同,白白多做 4 次 11552 字节 Flash 读取 + 4 次 MD5。第二段的 5 次重试才是有意义的。 - 魔法数字与二进制强耦合:
0x1FF2、0x20E2、0x22CA、0x22CB、0x22CD、0x22CE、0x22D0、0x22D1全部无注释,且跳过了0x22CC/0x22CF;这些偏移与特定 Boot 编译产物绑定,Boot 一旦重新编译即失效,且 MD5 白名单机制(s_u8_old_boot_md5)会先一步拦住,属于"隐式契约"。 - 版本白名单过窄:
ems_upgrade_boot_check()(:215)只认"V1.00.03"与"V1.00.04",任何其它 Boot 版本(如更高版本、带后缀、或 Flash 全0xFF未烧录)一律返回 FALSE,会直接把 EMS 升级通道堵死并置boot_error,后续版本维护时容易遗漏。 - 形参未使用:
ems_upgrade_boot_check(u32 file_len)的file_len完全未使用(:215-239),长度来源被写死为get_length_file_updating(UPDT_EMS)。 - 静态返回缓冲非可重入:
s_u8_bootloader_ver(:31)被 net 任务与 Modbus 任务同时使用;get_ems_boot_ver()每次进入先memset再填,若两个任务并发调用,一方可能在另一方memset之后读到全 0。 const数据放在非只读段:两个 MD5 常量数组为static const u8[16],在 Keil 默认配置下通常放.constdata(Flash),风险较低但依赖链接脚本。- 重复 include 冗余:
ems_boot_handle.c同时 includeems_boot_handle.h与ems_download.h,而后者也定义了START_ADDR_SPI_FLASH_EMS_FW等宏;本文件未使用这些宏。 - 文件头注释与实际内容不符:
@file :ems_boot_handle.c正确,但@brief为空,且文件内没有任何关于补丁偏移含义的说明。