完善OTA会话状态管理,增加clearOtaSessionQueues函数统一处理会话清理, 确保CANCEL/超时/错误情况下完全复位状态、缓冲区和信用额度。 修复bootloader在stay_in_ota模式下的死循环问题,添加活动状态监控, 当OTA会话结束后自动跳转回APP,避免设备永久停留在bootloader。 统一AES模块的数据类型定义,从u8改为标准uint8_t,提升代码兼容性。 更新AES实现中的变量声明,使用一致的uint8_t类型避免潜在类型转换问题。
1370 lines
46 KiB
C++
1370 lines
46 KiB
C++
#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
|
||
}
|
||
}
|