Files
GD32E50X_BOOT/GD32E50X_BOOT/sys/OTA.cpp
T
cbb f9ae1be120 feat(ota): 完善OTA会话管理机制并优化AES实现
完善OTA会话状态管理,增加clearOtaSessionQueues函数统一处理会话清理,
确保CANCEL/超时/错误情况下完全复位状态、缓冲区和信用额度。

修复bootloader在stay_in_ota模式下的死循环问题,添加活动状态监控,
当OTA会话结束后自动跳转回APP,避免设备永久停留在bootloader。

统一AES模块的数据类型定义,从u8改为标准uint8_t,提升代码兼容性。
更新AES实现中的变量声明,使用一致的uint8_t类型避免潜在类型转换问题。
2026-07-30 11:57:51 +08:00

1370 lines
46 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "OTA.h"
#include "Can.h"
#include "SpiOta.h"
#include "aes128.h"
#include "config.h"
#include "core_cm33.h"
#include "gd32e50x.h"
#include "gd32e50x_can.h"
#include "gd32e50x_crc.h"
#include "gd32e50x_fmc.h"
#include "gd32e50x_rcu.h"
#include "initial.h"
#include "systick.h"
#include <stdio.h>
#include <string.h>
extern CAN_transmiter CAN0_transmiter;
OTAHandler ota;
namespace {
__attribute__((naked, noreturn)) void branchToApplication(uint32_t msp_value, uint32_t reset_handler) {
__asm volatile(
"movs r2, #0 \n"
"msr control, r2 \n"
"msr primask, r2 \n"
"msr basepri, r2 \n"
"msr faultmask, r2 \n"
"msr psp, r2 \n"
"msr msp, r0 \n"
"dsb \n"
"isb \n"
"bx r1 \n");
}
} // namespace
/**
* OTA.cpp 模块说明
* ----------------
* 这里实现 Bootloader 的核心 OTA 逻辑:
* 1. 握手与会话密钥派生
* 2. 固件大小确认
* 3. 擦除应用区
* 4. 固件包校验、解密、写 Flash
* 5. 超时处理、错误上报、应用跳转
*
* 设计上,OTA 状态机与底层传输层解耦:
* - CAN 与 SPI 只负责收发原始消息
* - 进入本文件后统一走 handleLinkMessage()
*/
/**
* @brief CAN 链路回包发送适配器。
*
* @param type OTA 回包类型。
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
static void ota_can_sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
/**
* 将 OTA 统一回包类型映射到具体 CAN ID。
* 当前只定义了 ACK 与 WAIT 两类返回帧。
*/
u32 can_id = 0U;
if (type == OTA_LINK_TX_ACK) {
can_id = CANFD_OTA_ACK_ID;
} else if (type == OTA_LINK_TX_WAIT) {
can_id = CANFD_OTA_WAIT_ID;
} else {
return;
}
CAN0_transmiter.CANFD_AsyncSend(can_id, len, const_cast<u8 *>(data));
}
/**
* @brief SPI 链路回包发送适配器。
*
* @param type OTA 回包类型。
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
static void ota_spi_sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
/**
* 关键修复:这里不能是空函数!
*
* 之前是占位实现,因为 ISR 内的回包由 SpiOta::isrProcessFrame() 直接调用
* SpiOta::sender() → submitResponse() → DMA 推送。
*
* 但 process() 主循环中也会发送 ACK(如擦除完成后的 ACK(seq=0)),
* 这些调用走的是 transport_send = ota_spi_sender。
* 如果这里是空的,ACK 就被吞掉了,ESP32 永远等不到擦除完成的准入信号。
*
* 所以必须委托给 SpiOta::sender(),它会构建帧并推 DMA。
*/
SpiOta::sender(type, len, data);
}
/**
* @brief 向指定 Flash 地址写入一个 32 位字。
*
* @param address 目标地址。
* @param data 目标数据。
* @return FMC 编程状态码。
*/
static inline fmc_state_enum write_ADDRESS(u32 address, u32 data) {
/**
* 对单个配置字进行安全写入。
* 主要用于 OTA 状态页 / 睡眠配置页中的标志位更新。
*/
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_word_program(address, data);
#if DEBUG_PRINT
printf("[FW] W addr=0x%08lX data=0x%08lX\r\n",
(unsigned long)address, (unsigned long)data);
#endif
fmc_lock();
return status;
}
/**
* @brief 更新 OTA 配置页中的指定状态字。
*
* @param addr 目标配置字地址。
* @param data 目标写入值。
* @return FLASH_EEPROM_OK 表示成功,FLASH_EEPROM_ERROR 表示失败。
*/
u8 OTA_SET(u32 addr, u32 data) {
/**
* OTA 配置页更新策略:
* - 如果目标值本来就是 data,直接返回成功
* - 如果只是把某个字从擦除态写成 OTA_AVAL(0),可以直接写
* - 其它情况(尤其涉及从 0 改回 1)则必须整页擦除后重写全部 6 个配置字
*/
fmc_state_enum fmc_status;
u32 temp_val[OTA_CONFIG_NUM];
volatile u32 *flash_ptr = (volatile u32 *)addr;
temp_val[0] = *flash_ptr;
if (temp_val[0] == data) {
return FLASH_EEPROM_OK;
}
/**
* Flash 只能把 bit 从 1 写成 0,所以写 OTA_AVAL(0x00000000) 时可以尝试直接编程。
*/
if (data == OTA_AVAL) {
fmc_status = write_ADDRESS(addr, data);
if (fmc_status == FMC_READY) {
return FLASH_EEPROM_OK;
}
}
/** 读取当前配置页中的全部状态(含 BOOT 版本 word 6/7),供擦除后重写。 */
flash_ptr = (volatile u32 *)n_APP_LATEST_AVAL_ADDR;
temp_val[0] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_AVAL_ADDR;
temp_val[1] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_LATEST_EN_ADDR;
temp_val[2] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_EN_ADDR;
temp_val[3] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_LATEST_CHECK_ADDR;
temp_val[4] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_CHECK_ADDR;
temp_val[5] = *flash_ptr;
flash_ptr = (volatile u32 *)n_BOOT_VERSION_ADDR0;
temp_val[6] = *flash_ptr;
flash_ptr = (volatile u32 *)n_BOOT_VERSION_ADDR1;
temp_val[7] = *flash_ptr;
/** 整页擦除后重建配置页。 */
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_status = fmc_page_erase(OTA_CONFIG_PAGE_ADDR);
#if DEBUG_PRINT
printf("[FW] E page=0x%08lX\r\n", (unsigned long)OTA_CONFIG_PAGE_ADDR);
#endif
if (fmc_status != FMC_READY) {
fmc_lock();
return FLASH_EEPROM_ERROR;
}
for (int i = 0; i < OTA_CONFIG_NUM; ++i) {
u32 current_addr = n_APP_LATEST_AVAL_ADDR + 4U * (u32)i;
if (current_addr != addr && temp_val[i] != OTA_NOT_AVAL) {
fmc_status = fmc_word_program(current_addr, temp_val[i]);
#if DEBUG_PRINT
printf("[FW] W addr=0x%08lX data=0x%08lX\r\n",
(unsigned long)current_addr, (unsigned long)temp_val[i]);
#endif
if (fmc_status != FMC_READY) {
fmc_lock();
return FLASH_EEPROM_ERROR;
}
}
}
if (data != OTA_NOT_AVAL) {
fmc_status = fmc_word_program(addr, data);
#if DEBUG_PRINT
printf("[FW] W addr=0x%08lX data=0x%08lX\r\n",
(unsigned long)addr, (unsigned long)data);
#endif
if (fmc_status != FMC_READY) {
fmc_lock();
return FLASH_EEPROM_ERROR;
}
}
fmc_lock();
return FLASH_EEPROM_OK;
}
/**
* @brief 从 4 字节小端数组中提取 32 位整数。
*
* @param array 指向至少 4 字节数组的指针。
* @return 提取出的 32 位无符号整数。
*/
static inline uint32_t extract_u32_from_4bytes(const uint8_t *array) {
/**
* 协议里数值字段按小端排列存放:
* byte0 -> 最低字节,byte3 -> 最高字节
*/
return ((uint32_t)array[3] << 24) | ((uint32_t)array[2] << 16) |
((uint32_t)array[1] << 8) | (uint32_t)array[0];
}
/**
* @brief 将 32 位整数按小端写入 4 字节数组。
*/
static inline void write_u32_to_4bytes(uint8_t *array, uint32_t value) {
array[0] = (uint8_t)(value & 0xFFU);
array[1] = (uint8_t)((value >> 8) & 0xFFU);
array[2] = (uint8_t)((value >> 16) & 0xFFU);
array[3] = (uint8_t)((value >> 24) & 0xFFU);
}
/**
* @brief 发送带 0xBA 0xC0 头的结构化 ACK。
*/
static inline void send_structured_ack(uint32_t seq_or_flag) {
u8 send_data[6] = {0xBA, 0xC0, 0x00, 0x00, 0x00, 0x00};
#if DEBUG_PRINT
if (seq_or_flag <= 20U || seq_or_flag == 0xF0F0F0F0U) {
printf("[OTA] send_structured_ack seq=%u\r\n", (unsigned)seq_or_flag);
}
#endif
write_u32_to_4bytes(&send_data[2], seq_or_flag);
send_ack_frame(6, send_data);
}
/**
* @brief 初始化 OTA 状态机。
*
* @return 无。
*/
/**
* @brief 完整复位 OTA 会话到 IDLE(密钥/缓冲/CREDIT/SPI 队列一并清理)。
*/
void OTAHandler::resetToIdle() {
ota_state = OTA_STATE_IDLE;
ota_time = 0;
ota_error = OTA_ERROR_NONE;
received_size = 0;
expected_size = 0;
memset(current_K_session, 0, sizeof(current_K_session));
memset(nonce_server, 0, sizeof(nonce_server));
memset(&aes_ctx, 0, sizeof(aes_ctx));
pending_flash_erase = false;
pending_app_jump = false;
pending_finish_ack = false;
pending_finish_ota = false;
flash_buf_wi = 0U;
flash_buf_ri = 0U;
flash_buf_cnt = 0U;
next_expected_packet = 0U;
/* SPI 侧:清 ISR DATA 队列 + 恢复 g_localCredit,避免失败后半会话残留 */
SpiOta::clearOtaSessionQueues();
}
void OTAHandler::init() {
/** 默认回包出口先绑定到 CAN。 */
transport_send = ota_can_sender;
/** 显式重置状态机,防止 APP 软跳转后残留旧状态。 */
resetToIdle();
}
/**
* @brief 切换 OTA 回包出口到 CAN。
*
* @return 无。
*/
void ota_bind_can_transport(void) {
/* SPI OTA 会话进行中禁止 CAN 抢回包出口(否则 ACK 发到错误通道)。 */
if (ota.isActive() && ota.isSpiTransport()) {
return;
}
ota.setTransportSender(ota_can_sender);
}
/**
* @brief 切换 OTA 回包出口到 SPI。
*
* @return 无。
*/
void ota_bind_spi_transport(void) {
ota.setTransportSender(ota_spi_sender);
}
bool OTAHandler::isSpiTransport() const {
return transport_send == ota_spi_sender;
}
/**
* @brief 推进 OTA 状态机后台逻辑。
*
* @return 无。
*/
void OTAHandler::process() {
/**
* 把真正耗时的 Flash 擦除动作推迟到主循环做,
* 避免在接收消息的处理路径里执行过重操作。
*
* 协议关键点:SIZE 阶段不能在“刚收到大小”时立刻 ACK(seq=0),
* 否则 ESP32 会马上开始发数据包,而此时 BOOT 可能仍在整片擦 Flash。
* 擦除期间本函数会关中断,SPI 无法及时收包/回 ACK,导致包0超时。
*
* 因此改为:
* 1. ISR 收到 SIZE 后只回 WAIT,并置 pending_flash_erase=true
* 2. 这里真正擦除完成后,再切到 RECEIVING_FIRMWARE 并发送 ACK(seq=0)
*/
if (pending_flash_erase) {
pending_flash_erase = false;
eraseApplicationArea();
ota_state = OTA_STATE_RECEIVING_FIRMWARE;
/* 关键修复:SIZE 请求来自 SPI 时,擦除完成后的“准入 ACK(seq=0)”
* 必须强制走 SPI 回包。
*
* 否则如果主循环里恰好有 CAN communication_check() 把 transport_send
* 切回了 CAN,process() 这里发出的 ACK 会跑到 CAN 通道,ESP32 SPI 侧就
* 只能一直读到上一次 WAIT 帧残留(例如 0x27 CRC 尾字节)。 */
ota_bind_spi_transport();
#if DEBUG_PRINT
printf("[OTA] erase done, send SPI ACK seq0\r\n");
#endif
send_structured_ack(0U);
SpiOta::servicePendingTx();
}
if (pending_finish_ack || pending_finish_ota) {
drainFlashBuffer();
} else {
drainFlashBufferOne();
}
if (pending_finish_ack && flash_buf_cnt == 0U) {
pending_finish_ack = false;
}
/** FINISH_OTA 已在 ISR 里回 ACK,等 Flash 缓冲 drain 完再做校验/CRC/写标志/跳转,
* 避免在 CS 释放 ISR 里长时间阻塞 SPIready。 */
if (pending_finish_ota && flash_buf_cnt == 0U) {
pending_finish_ota = false;
if (!validateApplication()) {
resetToIdle();
ota_error = OTA_ERROR_FLASH_ERROR;
send_error_frame(0xFFFFFFFFU, OTA_ERROR_FLASH_ERROR);
} else {
/** 计算并写入 A区固件 CRC32,供后续启动时完整性校验。 */
u32 latest_crc = calculateAppCrc(APPLATEST_START_ADDR, APP_FIRMWARE_SIZE / 4U);
writeAppCrc(APPLATEST_START_ADDR, latest_crc);
OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_AVAL);
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
#if DEBUG_PRINT
/* OTA 完成后立即 dump Reset_Handler 地址 */
{
uint32_t rh_addr = *(__IO uint32_t *)(APPLATEST_START_ADDR + 4U) & ~1U;
printf("[OTA-DONE2] RH@0x%08lX: ", (unsigned long)rh_addr);
const uint8_t *p = (const uint8_t *)rh_addr;
for (int i = 0; i < 16; i++) printf("%02X ", p[i]);
printf("\r\n");
}
#endif
pending_app_jump = true;
}
}
if (pending_app_jump) {
pending_app_jump = false;
/* 给 ESP32 FINISH 后安静窗口留时间:本侧再稍等再 jump,
* 避免 jump 瞬间与主机 HELLO 时钟重叠打坏 APP SPI 初始化。 */
delay_1ms(80);
jumpToApplication();
return;
}
if (ota_state == OTA_STATE_IDLE) {
ota_time = 0;
/* 不再主动发 WAIT 帧——ISR 处理完请求后会发 ACK/WAIT/ERROR。
* 主动发 WAIT 会覆盖 ISR 写入的响应帧,导致 ESP32 读到 WAIT 而非 ACK。 */
delay_1ms(10);
return;
} else {
/**
* 非空闲态时,按固定节奏推进超时计数。
* 这里用 100us 粗略步进,不追求绝对精确,只做会话保活超时保护。
*/
delay_1us(20);
ota_time++;
}
if (ota_time > OTA_TIMEOUT) {
/**
* OTA 会话超时:
* - 完整复位状态机到空闲态(含 CREDIT/缓冲)
* - 上报超时错误
* - 如果当前主应用仍有效,则尝试回到主应用运行
*/
resetToIdle();
ota_error = OTA_ERROR_TIMEOUT;
volatile u32 *flash_ptr = (volatile u32 *)n_APP_LATEST_CHECK_ADDR;
u32 if_checked = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_LATEST_AVAL_ADDR;
u32 if_aval = *flash_ptr;
send_error_frame(0xFFFFFFFFU, OTA_ERROR_TIMEOUT);
if (if_checked == OTA_AVAL && if_aval == OTA_AVAL) {
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
jumpToApplication();
}
}
}
/**
* @brief 通过当前链路发送 ACK。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void OTAHandler::sendAck(u8 len, const u8 *data) {
if (transport_send != nullptr) {
transport_send(OTA_LINK_TX_ACK, len, data);
}
}
/**
* @brief 通过当前链路发送 WAIT。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void OTAHandler::sendWait(u8 len, const u8 *data) {
if (transport_send != nullptr) {
transport_send(OTA_LINK_TX_WAIT, len, data);
}
}
/**
* @brief 校验主应用向量表是否合法。
*
* @retval true 主应用向量表合法。
* @retval false 主应用向量表非法。
*/
bool validateApplication() {
/**
* 通过应用向量表做一个最基本的合法性检查:
* 1. 初始 MSP 必须落在 SRAM 范围内
* 2. Reset_Handler 必须落在主应用区范围内
*/
u32 *app_vector = (u32 *)APPLATEST_START_ADDR;
u32 stack_ptr = app_vector[0];
u32 reset_handler = app_vector[1];
bool stack_valid = (stack_ptr >= 0x20000000U && stack_ptr < 0x20020000U);
bool reset_valid = (reset_handler >= APPLATEST_START_ADDR &&
reset_handler < (APPLATEST_START_ADDR + APP_MAX_SIZE));
#if DEBUG_PRINT
#endif
return stack_valid && reset_valid;
}
/**
* @brief 跳转执行主应用。
*
* @return 无。
*/
void OTAHandler::jumpToApplication() {
if (!validateApplication()) {
return;
}
/** CRC32 完整性校验:若固件被破坏则不跳转。 */
if (!verifyAppCrc(APPLATEST_START_ADDR)) {
#if DEBUG_PRINT
printf("[BOOT] CRC verify FAIL, abort jump\r\n");
#endif
return;
}
#if DEBUG_PRINT
printf("[J1]\r\n");
#endif
/*
* [关键修复] 先关中断再做 Flash 操作。
*/
__disable_irq();
for (int i = 0; i < 8; ++i) {
NVIC->ICER[i] = 0xFFFFFFFFU;
NVIC->ICPR[i] = 0xFFFFFFFFU;
}
__DSB();
__ISB();
#if DEBUG_PRINT
printf("[J2]\r\n");
#endif
/* 把当前主应用重新标记为"有效且允许启动"。 */
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_AVAL);
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_AVAL);
#if DEBUG_PRINT
printf("[J3]\r\n");
#endif
/* 关闭 Bootloader 运行过程中启用过的外设。先关 DMA 再关外设。 */
DMA_CHCTL(DMA0, DMA_CH1) &= ~DMA_CHXCTL_CHEN;
DMA_CHCTL(DMA0, DMA_CH2) &= ~DMA_CHXCTL_CHEN;
can_deinit(CAN0);
spi_i2s_deinit(SPI0);
exti_interrupt_disable(EXTI_15);
#if DEBUG_PRINT
printf("[J4]\r\n");
#endif
SysTick->CTRL = 0U;
SysTick->LOAD = 0U;
SysTick->VAL = 0U;
/* 把中断向量表切到主应用起始地址。 */
SCB->VTOR = APPLATEST_START_ADDR;
/* 读取主应用向量表中的 MSP 和 Reset_Handler。 */
uint32_t msp_value = *(__IO uint32_t *)APPLATEST_START_ADDR;
uint32_t reset_handler = *(__IO uint32_t *)(APPLATEST_START_ADDR + 4U);
#if DEBUG_PRINT
printf("[J5] msp=0x%08lX rst=0x%08lX\r\n",
(unsigned long)msp_value, (unsigned long)reset_handler);
/* 打印 Reset_Handler 地址处的原始字节,验证是否有有效代码 */
printf("[J5-HEX] @0x%08lX: ", (unsigned long)(reset_handler & ~1U));
{
const uint8_t *p = (const uint8_t *)(reset_handler & ~1U);
for (int i = 0; i < 16; i++) {
printf("%02X ", p[i]);
}
printf("\r\n");
}
/* 打印向量表前 8 字节(MSP + Reset_Handler) */
printf("[J5-VEC] @0x%08lX: ", (unsigned long)APPLATEST_START_ADDR);
{
const uint8_t *p = (const uint8_t *)APPLATEST_START_ADDR;
for (int i = 0; i < 8; i++) {
printf("%02X ", p[i]);
}
printf("\r\n");
}
#endif
/*
* 直接用内联汇编跳转,消除 naked 函数调用的所有不确定性。
* 使用显式寄存器变量保证 msp_value 在 r0,reset_handler 在 r1。
*/
register uint32_t r0_val __asm("r0") = msp_value;
register uint32_t r1_val __asm("r1") = reset_handler;
__asm volatile (
"movs r2, #0 \n"
"msr control, r2 \n"
"msr primask, r2 \n"
"msr basepri, r2 \n"
"msr faultmask, r2 \n"
"msr psp, r2 \n"
"msr msp, %0 \n"
"dsb \n"
"isb \n"
"bx %1 \n"
:
: "r"(r0_val), "r"(r1_val)
: "r2"
);
__builtin_unreachable();
}
/**
* @brief 擦除当前主应用区。
*
* @return 无。
*/
void OTAHandler::eraseApplicationArea() {
/**
* 开始真正接收新固件前,先把当前主应用状态全部标记为无效,
* 防止中途掉电后误跳到一个被擦了一半的应用。
*/
OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_NOT_AVAL);
OTA_SET(n_APP_LATEST_EN_ADDR, OTA_NOT_AVAL);
OTA_SET(n_APP_LATEST_AVAL_ADDR, OTA_NOT_AVAL);
/* 只擦「本轮固件需要」的页,而不是整片 APP_MAX_SIZE(~108 页)。
* 旧实现整片擦 + 全程关中断可达数秒:SPI 无响应,ESP32 卡在 SEND_SIZE WAIT。
* 53KB 固件约 27 页,体感从数秒降到约 1 秒内。 */
u32 erase_bytes = expected_size;
if (erase_bytes == 0U || erase_bytes > APP_MAX_SIZE) {
erase_bytes = APP_MAX_SIZE;
}
/* 向上对齐页;额外 +1 页余量,避免边界少擦 */
u32 erase_pages = (erase_bytes + FMC_PAGE_SIZE - 1U) / FMC_PAGE_SIZE;
if (erase_pages < (APP_MAX_SIZE / FMC_PAGE_SIZE)) {
erase_pages += 1U;
}
u32 erase_end = APPLATEST_START_ADDR + erase_pages * FMC_PAGE_SIZE;
if (erase_end > (APPLATEST_START_ADDR + APP_MAX_SIZE)) {
erase_end = APPLATEST_START_ADDR + APP_MAX_SIZE;
}
fmc_unlock();
#if DEBUG_PRINT
printf("[FW] E app 0x%08lX ~ 0x%08lX (%lu pages, fw=%lu)\r\n",
(unsigned long)APPLATEST_START_ADDR,
(unsigned long)erase_end,
(unsigned long)((erase_end - APPLATEST_START_ADDR) / FMC_PAGE_SIZE),
(unsigned long)expected_size);
#endif
u32 page_i = 0U;
for (u32 addr = APPLATEST_START_ADDR; addr < erase_end; addr += FMC_PAGE_SIZE) {
/* 仅在单页擦除窗口关中断,避免整片擦时 SPI/SysTick 假死 */
__disable_irq();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_page_erase(addr);
__enable_irq();
if (status != FMC_READY) {
ota_error = OTA_ERROR_FLASH_ERROR;
ota_bind_spi_transport();
send_error_frame(0xFFFFFFFFU, OTA_ERROR_FLASH_ERROR);
break;
}
++page_i;
/* 每 8 页刷一次 WAIT,让 ESP32 SEND_SIZE 轮询看到“仍在忙”而非全 0xFF */
if ((page_i & 7U) == 0U) {
ota_bind_spi_transport();
u8 wait_hint[1] = {0x02};
send_wait_frame(1, wait_hint);
/* process() 内嵌擦除时 poll 尚未 re-arm,这里立刻装 DMA */
SpiOta::servicePendingTx();
}
}
/* 关键修复:CRC 地址 (n_APP_LATEST_CRC_ADDR = APP_FIRMWARE_SIZE 末尾) 所在的 page
* 必须擦除,否则 writeAppCrc 调用 fmc_word_program 时会因目标 word 非擦除态
* (上次 OTA 残留的旧 CRC) 触发 FMC_PGERR (status=2),导致 CRC 写入失败 →
* verifyAppCrc FAIL → abort jump → GD32 卡在 BOOT,ESP32 探测 APP 超时。
*
* 当固件较小时(如 53KB),固件 pages 不覆盖 CRC page(0x0807D800~0x0807DFFF),
* 需要额外擦除这一页。固件较大时(>0x35800)固件 pages 已覆盖 CRC page,跳过。
* 仅多擦 1 page (~30ms),不破坏 eraseApplicationArea 的速度优化。 */
const u32 crc_page_start = n_APP_LATEST_CRC_ADDR & ~(FMC_PAGE_SIZE - 1U);
if (erase_end <= crc_page_start) {
__disable_irq();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum crc_status = fmc_page_erase(crc_page_start);
__enable_irq();
if (crc_status != FMC_READY) {
ota_error = OTA_ERROR_FLASH_ERROR;
ota_bind_spi_transport();
send_error_frame(0xFFFFFFFFU, OTA_ERROR_FLASH_ERROR);
}
#if DEBUG_PRINT
printf("[FW] E crc_page=0x%08lX status=%u\r\n",
(unsigned long)crc_page_start, (unsigned)crc_status);
#endif
}
fmc_lock();
}
/**
* @brief 向主应用区写入一段固件数据。
*
* @param offset 相对于主应用起始地址的偏移量。
* @param data 指向待写入数据的指针。
* @param size 数据长度。
* @return 无。
*/
bool OTAHandler::drainFlashBufferOne() {
if (flash_buf_cnt == 0U) { return false; }
FlashSlot slot = {};
__disable_irq();
if (flash_buf_cnt > 0U) {
u8 ri = flash_buf_ri;
slot = flash_buf[ri];
flash_buf_ri = (ri + 1U) % kFlashBufSlots;
flash_buf_cnt--;
} else {
__enable_irq();
return false;
}
__enable_irq();
writeApplicationData(slot.offset, slot.data, OTA_PACKET_SIZE);
{ u8 pending = SpiOta::pendingDataCount();
g_localCredit = (flash_buf_cnt + pending >= kFlashBufSlots) ? 0U
: (u8)(kFlashBufSlots - flash_buf_cnt - pending); }
SpiOta::notifyCreditRestore();
#if DEBUG_PRINT
static u32 s_drain_since_log = 0U;
s_drain_since_log++;
if (s_drain_since_log >= 10U) {
printf("[FW] drain %u pkts\r\n", (unsigned)s_drain_since_log);
s_drain_since_log = 0U;
}
#endif
return true;
}
void OTAHandler::drainFlashBuffer() {
while (drainFlashBufferOne()) {}
}
void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) {
/**
* 将一个分包写入主应用区:
* - 先按 4 字节对齐批量写
* - 剩余不足 4 字节时,用 0xFF 补齐一个 word 再写入
*/
u32 write_addr = APPLATEST_START_ADDR + offset;
fmc_unlock();
u32 i = 0;
for (; i + 4U <= size; i += 4U) {
fmc_state_enum st = fmc_word_program(write_addr + i, *(u32 *)&data[i]);
if (st != FMC_READY) {
#if DEBUG_PRINT
printf("[FW] write err 0x%08lX st=%u\r\n", (unsigned long)(write_addr + i), (unsigned)st);
#endif
fmc_lock();
return;
}
}
if (i < size) {
u32 word = 0xFFFFFFFFU;
memcpy(&word, &data[i], size - i);
fmc_state_enum st = fmc_word_program(write_addr + i, word);
if (st != FMC_READY) {
#if DEBUG_PRINT
printf("[FW] write tail err 0x%08lX st=%u\r\n", (unsigned long)(write_addr + i), (unsigned)st);
#endif
fmc_lock();
return;
}
}
fmc_lock();
}
/**
* @brief 处理统一抽象后的 OTA 链路消息。
*
* @param type 链路消息类型。
* @param data 指向消息数据缓冲区的指针。
* @param len 消息数据长度。
* @return 无。
*/
void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) {
#if DEBUG_PRINT
/* 精简: 只打印关键帧(非DATA包或状态变化时), DATA包省略 */
if (type != OTA_LINK_MSG_DATA || ota_state != OTA_STATE_RECEIVING_FIRMWARE) {
printf("[OTA] hl: t=%u l=%u s=%u\r\n",
(unsigned)type, (unsigned)len, (unsigned)ota_state);
}
#endif
/**
* CANCEL:无条件终止当前 OTA 会话,完整回到空闲态。
* 走 resetToIdle():清密钥/缓冲/CREDIT/SPI DATA 队列,避免半会话残留。
* 仅置 IDLE 不跳 APP:跳转由 main.cpp stay_in_ota 循环检测 ota.isActive()
* 变 false 后统一执行,避免在此处关中断/校验与 ISR 上下文冲突。
* 回 ACK 便于 ESP32 确认远端已清理(无 payload 时也 ACK)。
*/
if (type == OTA_LINK_MSG_CANCEL) {
resetToIdle();
#if DEBUG_PRINT
printf("[OTA] CANCEL -> resetToIdle\r\n");
#endif
{
u8 cancel_ack[1] = {0x03U};
send_ack_frame(1, cancel_ack);
}
return;
}
/**
* REQ: 握手/开始请求。
*
* 只有 2 字节 payload (0x00 0xAC) 才是真正的握手请求。
* 16 字节 payload 是协议流程中的 nonce / confirm 数据,
* 需交由后续分支正常处理。
*
* 这解决了 CAN 总线帧在 SPI OTA 等待窗口内抢先改变
* ota_state 导致 SPI HANDSHAKE_REQ 收到 WAIT 的问题。
*/
else if (type == OTA_LINK_MSG_REQ && len == 2U && data[0] == 0x00U && data[1] == 0xACU) {
ota_time = 0;
if (ota_state <= OTA_STATE_CHECKING_FIRMWARE) {
/* 可重启态:IDLE / WAITING_RANDOM / WAITING_CONFIRM / CHECKING_FIRMWARE */
u8 ack_echo[2] = {0x00, 0xAC};
send_ack_frame(2, ack_echo);
ota_state = OTA_STATE_WAITING_RANDOM;
} else {
/* 已接收固件数据,拒绝新 REQ */
u8 wait_hint[1] = {0x01};
send_wait_frame(1, wait_hint);
}
return;
}
/**
* DATA:根据当前状态解释为"固件分包"或"固件大小信息"等。
*/
else if (type == OTA_LINK_MSG_DATA) {
ota_time = 0;
/**
* 正常固件数据包:
* - 固定 64 字节
* - 含会话号、包号、48 字节密文、结束标志、CRC32
*/
if (len == 64U && ota_state == OTA_STATE_RECEIVING_FIRMWARE) {
u32 receive_session_num = extract_u32_from_4bytes(data);
u32 expected_session_num = extract_u32_from_4bytes(nonce_server);
if (receive_session_num != expected_session_num) {
ota_error = OTA_ERROR_INVALID_PACKET;
send_error_frame(extract_u32_from_4bytes(data + 4), OTA_ERROR_INVALID_PACKET);
return;
}
u32 receive_packet_num = extract_u32_from_4bytes(data + 4);
u32 receive_IFEND = extract_u32_from_4bytes(data + 56);
#if DEBUG_PRINT
if (receive_packet_num <= 20U) {
printf("[OTA] DATA in: seq=%u next=%u flash_cnt=%u\r\n",
(unsigned)receive_packet_num, (unsigned)next_expected_packet, (unsigned)flash_buf_cnt);
}
#endif
const u8 *receive_encrypted_data = data + 8;
const u8 *receive_dataCRC32 = data + 60;
/** 去重:跳过已经确认接收过的包号(ESP32 超时重传)。 */
if (receive_packet_num < next_expected_packet) {
u32 request_packet = receive_packet_num + 1U;
#if DEBUG_PRINT
if (receive_packet_num <= 20U) {
printf("[OTA] DATA dup seq=%u (next=%u) -> ack %u\r\n",
(unsigned)receive_packet_num, (unsigned)next_expected_packet, (unsigned)request_packet);
}
#endif
send_structured_ack(request_packet);
return;
}
u8 decrypted_data[OTA_PACKET_SIZE];
u8 calculated_dataCRC32[4];
/** 使用当前会话密钥解密 48 字节数据区。 */
aes128_set_key(&aes_ctx, current_K_session);
aes128_ecb_decrypt_48bytes(&aes_ctx, receive_encrypted_data, decrypted_data);
/** 校验分包 CRC32(密钥参与 CRC,防止简单重放/篡改)。 */
calculate_crc32_with_key(current_K_session, decrypted_data, calculated_dataCRC32);
if (memcmp(receive_dataCRC32, calculated_dataCRC32, SIZE_U32) != 0) {
#if DEBUG_PRINT
printf("[OTA] DATA crc err seq=%u\r\n", (unsigned)receive_packet_num);
#endif
ota_error = OTA_ERROR_INVALID_CRC;
send_error_frame(receive_packet_num, OTA_ERROR_INVALID_CRC);
return;
} else {
u32 offset = receive_packet_num * OTA_PACKET_SIZE;
if (offset >= expected_size) {
resetToIdle();
ota_error = OTA_ERROR_INVALID_SIZE;
send_error_frame(receive_packet_num, OTA_ERROR_INVALID_SIZE);
return;
}
if (flash_buf_cnt >= kFlashBufSlots) {
/* 缓冲满:同步排空已写入的槽位,腾出空间后再入队。
* 旧实现在此直接 return 丢弃当前包,但 ISR 已推进
* s_expectedRxSeq,导致 ESP32 重发被判为 DUP-SEQ,
* 永远只能读到缓存的 WAIT,包永久丢失。 */
/* 只排空一个包腾槽,避免一次 drain 8 包阻塞主循环。
* 剩余包由 process() 的增量 drain 逐步消费。 */
drainFlashBufferOne();
if (flash_buf_cnt >= kFlashBufSlots) {
g_localCredit = 0U;
send_wait_frame(1, (const u8 *)"\x03");
return;
}
}
u8 wi = flash_buf_wi;
flash_buf[wi].offset = offset;
memcpy(flash_buf[wi].data, decrypted_data, OTA_PACKET_SIZE);
flash_buf_wi = (wi + 1U) % kFlashBufSlots;
__disable_irq();
flash_buf_cnt++;
__enable_irq();
/* 更新 CREDIT:剩余可接收槽位。缓冲将满时置 0,
* ESP32 收到 CREDIT=0 后会改为 POLL 等待而非盲目重发。 */
/* CREDIT 需同时减去 ISR DATA 队列待处理数,
* 否则 ESP32 看到 credit>0 但 flash_buf 即将满。 */
{ u8 pending = SpiOta::pendingDataCount();
u8 avail = (flash_buf_cnt + pending >= kFlashBufSlots) ? 0U
: (u8)(kFlashBufSlots - flash_buf_cnt - pending);
g_localCredit = avail; }
next_expected_packet = receive_packet_num + 1U;
#if DEBUG_PRINT
if (receive_packet_num <= 20U) {
printf("[OTA] DATA ok seq=%u -> ack %u\r\n",
(unsigned)receive_packet_num, (unsigned)next_expected_packet);
}
#endif
}
/**
* IFEND == 0xFFFFFFFF 表示最后一包:
* 先反馈"全部写入完成,等待 FINISH_OTA",
* 真正跳转放到收到 0x0A AC 后再执行。
*/
if (receive_IFEND == 0xFFFFFFFFU) {
ota_state = OTA_STATE_FINISHED;
pending_finish_ack = true;
send_structured_ack(0xF0F0F0F0U);
return;
}
/** 正常情况下 ACK 下一个希望收到的包号。 */
u32 request_packet = receive_packet_num + 1U;
send_structured_ack(request_packet);
}
/**
* 固件大小阶段:
* 在握手成功后,由上位机先发 4 字节固件大小,
* Bootloader 校验是否超过 APP_MAX_SIZE,并准备擦除 Flash。
*/
else if (len == 4U && ota_state == OTA_STATE_CHECKING_FIRMWARE) {
u32 new_size = *(u32 *)data;
if (new_size <= APP_MAX_SIZE) {
expected_size = new_size;
received_size = 0;
next_expected_packet = 0U;
} else {
resetToIdle();
ota_error = OTA_ERROR_INVALID_SIZE;
send_error_frame(0U, OTA_ERROR_INVALID_SIZE);
return;
}
/**
* 不要立刻 ACK(seq=0)。
* 先告诉 ESP32“我在忙(WAIT)”,等 process() 真正擦除完 Flash 后,
* 再发送 ACK(seq=0) 作为“现在可以开始发包”的准入信号。
*/
pending_flash_erase = true;
u8 wait_hint[1] = {0x02};
send_wait_frame(1, wait_hint);
}
/** 结束态下要求显式收到 0x0A AC,确认可以切换到 APP。 */
else if (len == 2U && ota_state == OTA_STATE_FINISHED) {
if (data[0] == 0x0AU && data[1] == 0xACU) {
u8 finish_ack[2] = {0x0A, 0xAC};
send_ack_frame(2, finish_ack);
/** 重活放到主循环 process() 里做,ISR 只回 ACK 并置标志,
* 避免 SPIready 被拉低太久导致 ESP32 超时。 */
if (!pending_finish_ota) {
pending_finish_ota = true;
}
return;
}
resetToIdle();
ota_error = OTA_ERROR_INVALID_PACKET;
send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_PACKET);
return;
}
/**
* FINISHED 状态下收到重复数据包(ESP32 重传):
* 不应视为无效包而重置状态机,而是回 WAIT 让 ESP32 稍后重发 FINISH_OTA。
*/
else if (ota_state == OTA_STATE_FINISHED) {
u8 wait_hint[1] = {0x04};
send_wait_frame(1, wait_hint);
}
else {
#if DEBUG_PRINT
printf("[OTA] DATA ignored/INV_PKT: type=%u len=%u state=%u\r\n",
(unsigned)type, (unsigned)len, (unsigned)ota_state);
#endif
resetToIdle();
ota_error = OTA_ERROR_INVALID_PACKET;
send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_PACKET);
}
}
/**
* REQ:用于握手阶段。
*/
else if (type == OTA_LINK_MSG_REQ) {
ota_time = 0;
/**
* 第一步:上位机发固定魔术字 0x00 0xAC,申请开始握手。
*/
if (data[0] == 0x00U && data[1] == 0xACU && len == 2U && ota_state == OTA_STATE_IDLE) {
u8 send_data[2] = {0x00, 0xAC};
send_ack_frame(2, send_data);
ota_state = OTA_STATE_WAITING_RANDOM;
}
/**
* 第二步:上位机发送 16 字节随机数 nonce_server。
* Bootloader:
* - 读取设备 UID
* - 用主密钥加密 nonce 派生会话密钥 K_session
* - 回传 UID + nonce 片段 + 加密后的 K_session
*/
else if (len == 16U && ota_state == OTA_STATE_WAITING_RANDOM) {
for (u8 i = 0; i < 16U; ++i) {
nonce_server[i] = data[i];
}
getDeviceSerialNumber(device_uid);
aes128_set_key(&aes_ctx, K_master);
u8 K_session[16];
aes128_encrypt_block(&aes_ctx, nonce_server, K_session);
u8 encrypted_Ksession[16];
u8 temp_block[AES_BLOCK_SIZE];
aes128_encrypt_block(&aes_ctx, K_session, temp_block);
memcpy(encrypted_Ksession, temp_block, 16);
u8 handshake_response[36];
memcpy(handshake_response, device_uid, 12);
memcpy(handshake_response + 12, nonce_server + 8, 8);
memcpy(handshake_response + 20, encrypted_Ksession, 16);
send_ack_frame(36, handshake_response);
memcpy(current_K_session, K_session, 16);
ota_state = OTA_STATE_WAITING_CONFIRM;
}
/**
* 第三步:上位机使用会话密钥加密 (nonce_server XOR 0x55),回传 16 字节确认块。
* Bootloader 解密后比对,成功则握手完成。
*/
else if (len == 16U && ota_state == OTA_STATE_WAITING_CONFIRM) {
u8 plaintext[16];
u8 expected[16];
u8 encrypted_confirm[16];
memcpy(encrypted_confirm, data, 16);
aes128_set_key(&aes_ctx, current_K_session);
aes128_decrypt_block(&aes_ctx, encrypted_confirm, plaintext);
for (int i = 0; i < 16; ++i) {
expected[i] = nonce_server[i] ^ 0x55U;
}
if (memcmp(plaintext, expected, 16) != 0) {
resetToIdle();
ota_error = OTA_ERROR_INVALID_KEY;
send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_KEY);
return;
}
u8 send_data[2] = {0xBA, 0xC0};
send_ack_frame(2, send_data);
ota_state = OTA_STATE_CHECKING_FIRMWARE;
}
/** 其它情况一律判定为握手非法。 */
else {
resetToIdle();
ota_error = OTA_ERROR_INVALID_HANDSHAKE;
send_error_frame(0xFFFFFFFFU, OTA_ERROR_INVALID_HANDSHAKE);
return;
}
}
}
/**
* @brief 发送 OTA ACK 帧。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void send_ack_frame(u8 len, const u8 *data) {
ota.sendAck(len, data);
}
/**
* @brief 发送 OTA WAIT 帧。
*
* @param len 有效载荷长度。
* @param data 指向有效载荷缓冲区的指针。
* @return 无。
*/
void send_wait_frame(u8 len, const u8 *data) {
ota.sendWait(len, data);
}
/**
* @brief 发送 OTA 错误帧。
*
* @param error 错误码。
* @return 无。
*/
void send_error_frame(u32 packet_num, u8 error) {
/**
* 结构化错误帧:
* [0..1] = 0xBA 0xC0
* [2..5] = 相关包号(小端)
* [6] = 错误码
*/
u8 error_frame[7] = {0xBA, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, error};
write_u32_to_4bytes(&error_frame[2], packet_num);
send_ack_frame(7, error_frame);
}
/**
* @brief 读取芯片唯一序列号。
*
* @param buffer 指向至少 12 字节输出缓冲区的指针。
* @return 无。
*/
void getDeviceSerialNumber(u8 *buffer) {
/** 从 GD32 固定 UID 地址读取 96bit 设备序列号。 */
volatile uint32_t *lsb = (volatile uint32_t *)DEVICE_SERIAL_ADDR_LSB;
volatile uint32_t *mid = (volatile uint32_t *)DEVICE_SERIAL_ADDR_MID;
volatile uint32_t *msb = (volatile uint32_t *)DEVICE_SERIAL_ADDR_MSB;
uint32_t serial_lsb = *lsb;
uint32_t serial_mid = *mid;
uint32_t serial_msb = *msb;
for (int i = 0; i < 4; ++i) {
buffer[i] = (u8)(serial_lsb >> (i * 8));
buffer[i + 4] = (u8)(serial_mid >> (i * 8));
buffer[i + 8] = (u8)(serial_msb >> (i * 8));
}
}
/**
* @brief 将备份应用区整份固件搬运到当前主应用区。
*
* @return FMC 状态码;CRC 校验失败时返回 0xFF。
*/
fmc_state_enum move_app_data() {
/**
* 把备份应用区整体复制到当前主应用区:
* 1. 先擦当前主应用区
* 2. 计算备份区 CRC
* 3. 逐 word 拷贝非 0xFFFFFFFF 的数据
* 4. 再计算目标区 CRC 比较,确认搬运成功
*/
/** 恢复前校验 B区固件 CRC 完整性,B区也坏了则不恢复。 */
if (!verifyAppCrc(APPBACK_START_ADDR)) {
#if DEBUG_PRINT
printf("[CRC] backup CRC verify FAIL, skip restore\r\n");
#endif
return (fmc_state_enum)0xFFU;
}
fmc_state_enum fmc_status = FMC_READY;
__disable_irq();
fmc_unlock();
uint32_t num_pages = (APP_MAX_SIZE + FMC_PAGE_SIZE - 1U) / FMC_PAGE_SIZE;
#if DEBUG_PRINT
printf("[FW] E restore dst=0x%08lX pages=%lu\r\n",
(unsigned long)APPLATEST_START_ADDR, (unsigned long)num_pages);
#endif
for (uint32_t i = 0; i < num_pages; ++i) {
uint32_t page_addr = APPLATEST_START_ADDR + i * FMC_PAGE_SIZE;
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_status = fmc_page_erase(page_addr);
if (fmc_status != FMC_READY) {
fmc_lock();
__enable_irq();
return fmc_status;
}
}
crc_deinit();
crc_data_register_reset();
uint32_t src_crc = crc_block_data_calculate((void *)APPBACK_START_ADDR, APP_MAX_SIZE / 4U, INPUT_FORMAT_WORD);
#if DEBUG_PRINT
u32 dbg_word_cnt = 0;
#endif
for (uint32_t offset = 0; offset < APP_MAX_SIZE; offset += 4U) {
uint32_t src_data = *(uint32_t *)(APPBACK_START_ADDR + offset);
if (src_data != 0xFFFFFFFFU) {
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_status = fmc_word_program(APPLATEST_START_ADDR + offset, src_data);
#if DEBUG_PRINT
dbg_word_cnt++;
#endif
if (fmc_status != FMC_READY) {
fmc_lock();
__enable_irq();
return fmc_status;
}
}
}
#if DEBUG_PRINT
printf("[FW] W restore src=0x%08lX dst=0x%08lX words=%lu\r\n",
(unsigned long)APPBACK_START_ADDR,
(unsigned long)APPLATEST_START_ADDR,
(unsigned long)dbg_word_cnt);
#endif
fmc_lock();
__enable_irq();
crc_data_register_reset();
uint32_t dst_crc = crc_block_data_calculate((void *)APPLATEST_START_ADDR, APP_MAX_SIZE / 4U, INPUT_FORMAT_WORD);
if (src_crc != dst_crc) {
return (fmc_state_enum)0xFFU;
}
/** 将 B区的固件 CRC 也写入 A区末尾,保持校验数据一致。 */
volatile u32 *backup_crc_ptr = (volatile u32 *)n_APP_BACKUP_CRC_ADDR;
u32 firmware_crc = *backup_crc_ptr;
writeAppCrc(APPLATEST_START_ADDR, firmware_crc);
return fmc_status;
}
/**
* @brief 对会话密钥和数据区执行 CRC32 计算。
*
* @param key 指向 16 字节会话密钥的指针。
* @param data 指向 48 字节数据区的指针。
* @param output 指向 4 字节输出缓冲区的指针。
* @return 无。
*/
void calculate_crc32_with_key(const u8 *key, const u8 *data, u8 *output) {
/**
* 计算方式:
* 1. 先把 16 字节会话密钥喂入 CRC32
* 2. 再把 48 字节有效数据喂入 CRC32
* 3. 最终按小端输出 4 字节结果
*/
uint32_t crc = 0xFFFFFFFFU;
for (int i = 0; i < 16; ++i) {
crc ^= key[i];
for (int j = 0; j < 8; ++j) {
crc = (crc >> 1) ^ (0xEDB88320U & (0U - (crc & 1U)));
}
}
for (u32 i = 0; i < OTA_PACKET_SIZE; ++i) {
crc ^= data[i];
for (int j = 0; j < 8; ++j) {
crc = (crc >> 1) ^ (0xEDB88320U & (0U - (crc & 1U)));
}
}
crc ^= 0xFFFFFFFFU;
output[0] = (u8)(crc & 0xFFU);
output[1] = (u8)((crc >> 8) & 0xFFU);
output[2] = (u8)((crc >> 16) & 0xFFU);
output[3] = (u8)((crc >> 24) & 0xFFU);
}
/**
* @brief 使用硬件 CRC 单元计算指定区域的 CRC32。
*
* @param start_addr 区域起始地址。
* @param word_count 区域长度(按 32 位字计数)。
* @return 计算得到的 CRC32 值。
*/
u32 calculateAppCrc(u32 start_addr, u32 word_count) {
crc_deinit();
crc_data_register_reset();
return crc_block_data_calculate((void *)start_addr, word_count, INPUT_FORMAT_WORD);
}
/**
* @brief 校验应用区固件 CRC 完整性。
*
* 计算 start_addr ~ start_addr + APP_FIRMWARE_SIZE 的硬件 CRC32,
* 并与应用区末尾 4 字节存储的 CRC 值比对。
*
* @param start_addr 应用区起始地址(A区或B区)。
* @retval true CRC 校验通过,固件完整。
* @retval false CRC 不匹配或未写入 CRC,固件不可信。
*/
bool verifyAppCrc(u32 start_addr) {
u32 crc_addr = start_addr + APP_FIRMWARE_SIZE;
volatile u32 *stored_crc_ptr = (volatile u32 *)crc_addr;
u32 stored_crc = *stored_crc_ptr;
/** Flash 擦除态 = 0xFFFFFFFF,如果 CRC 地址未写过说明无校验数据。
* APP 通过调试器直接烧录时 CRC 未写入,此时不应阻止跳转,
* 视为校验通过(未启用 CRC 保护),仅 OTA 升级后才真正校验。 */
if (stored_crc == FLASH_EMPTY) {
return true;
}
crc_deinit();
crc_data_register_reset();
u32 calculated = crc_block_data_calculate(
(void *)start_addr, APP_FIRMWARE_SIZE / 4U, INPUT_FORMAT_WORD);
#if DEBUG_PRINT
printf("[CRC] verify addr=0x%08lX stored=0x%08lX calc=0x%08lX %s\r\n",
(unsigned long)start_addr,
(unsigned long)stored_crc,
(unsigned long)calculated,
(calculated == stored_crc) ? "OK" : "FAIL");
#endif
return (calculated == stored_crc);
}
/**
* @brief 将 CRC 值写入应用区末尾。
*
* @param start_addr 应用区起始地址,用于计算 CRC 存储位置。
* @param crc_value 待写入的 CRC32 值。
* @return 无。
*/
void writeAppCrc(u32 start_addr, u32 crc_value) {
u32 crc_addr = start_addr + APP_FIRMWARE_SIZE;
#if DEBUG_PRINT
printf("[CRC] write addr=0x%08lX crc=0x%08lX\r\n",
(unsigned long)crc_addr,
(unsigned long)crc_value);
#endif
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
fmc_state_enum status = fmc_word_program(crc_addr, crc_value);
fmc_lock();
if (status != FMC_READY) {
#if DEBUG_PRINT
printf("[CRC] write FAILED status=%u\r\n", (unsigned)status);
#endif
}
}