diff --git a/GD32E50X_BOOT/device/SpiOta.cpp b/GD32E50X_BOOT/device/SpiOta.cpp index bca63b2..e9c5922 100644 --- a/GD32E50X_BOOT/device/SpiOta.cpp +++ b/GD32E50X_BOOT/device/SpiOta.cpp @@ -1,4 +1,4 @@ -#include "SpiOta.h" +#include "SpiOta.h" #include "core_cm33.h" #include "gd32e50x_gpio.h" #include "gd32e50x_exti.h" @@ -188,6 +188,7 @@ void SpiOta::isrProcessFrame() { * 4) onCsRelease() 看到 s_isrResponded 仍为 false,于是再次调用 isrProcessFrame() * 5) 同一帧被重复处理一次,状态机被错误推进(如 challenge 又被当 confirm) * + isrLogFrameEx("[RX-RAW]", s_rxStream, (rx_len < 16U) ? rx_len : 16U); * 所以必须在函数入口就先置位,防止 REQ/DATA 被双重分发。 */ s_isrResponded = true; @@ -248,22 +249,9 @@ void SpiOta::isrProcessFrame() { s_otaSessionActive = true; s_otaHandler->setTransportSender(sender); ota_link_msg_type_t msg_type = mapMsgType(frame_type); - /* 精简诊断: 只记录帧类型+状态, 减少ISR日志开销 */ - { - u8 diag[3] = {(u8)frame_type, (u8)(s_otaHandler ? s_otaHandler->getState() : 0xFFU), - (u8)p_len}; - isrLogFrameEx("[OT]", diag, 3); - } s_otaHandler->handleLinkMessage(msg_type, const_cast(payload), (u8)p_len); } - /* 精简: 只在非DATA包时记录响应帧内容, DATA包只记录ACK的seq */ - if (frame_type != SPI_OTA_CMD_DATA) { - isrLogFrameEx("[RD]", s_txBuf, (s_txLen < 12U) ? s_txLen : 12U); - } else { - /* DATA包响应: 只记录payload前6字节(=BAC0+seq) */ - isrLogFrameEx("[RD]", s_txBuf + 4, (s_txLen > 10U) ? 6U : (s_txLen > 4U ? s_txLen - 4U : 0U)); - } s_isrResponded = true; } @@ -516,14 +504,6 @@ void SpiOta::onCsRelease() { } } - /* 关键调试日志:记录本次 CS 周期结束时到底收到了多少字节。 - * 这样能直接判断: - * 1) 是不是完整收到了 ESP32 发来的整帧 - * 2) 还是只收到了首字节 A5 / 少数字节,导致永远无法解析回包 */ - /* 精简: CS-RX只记录前4字节, 减少ISR日志开销 */ - if (s_rxStreamLen > 0U) { - isrLogFrame("[CR]", s_rxStream, (s_rxStreamLen < 4U) ? s_rxStreamLen : 4U); - } if (s_rxStreamLen > 0U && !s_isrResponded) { isrProcessFrame(); diff --git a/GD32E50X_BOOT/main.cpp b/GD32E50X_BOOT/main.cpp index 88d8f60..e3d1924 100644 --- a/GD32E50X_BOOT/main.cpp +++ b/GD32E50X_BOOT/main.cpp @@ -1,4 +1,4 @@ -#include "main.h" +#include "main.h" #include "device/Can.h" #include "device/SpiOta.h" #include "sys/config.h" @@ -43,6 +43,7 @@ void BootloaderSystem::initHardware() { (unsigned long)APP_MAX_SIZE); printf("[BOOT] SystemCoreClock=%lu Hz\r\n", (unsigned long)SystemCoreClock); #endif + rcu_periph_clock_enable(RCU_CRC); /* OTA CRC 校验需要 CRC 外设时钟 */ CAN0_transmiter.init(); ota.init(); SpiOta::init(); @@ -125,16 +126,6 @@ void BootloaderSystem::run() { OTA_SET(n_APP_LATEST_EN_ADDR, OTA_NOT_AVAL); } - /** 检测看门狗复位:如果上次复位是由 APP 的 FWDGT 超时触发, - * 则将 app_enable 标记为 NOT_AVAL,阻止 BOOT 再次跳入卡死的 APP, - * 让 BOOT 停留在 OTA 稡式等待远程修复。 */ - if (rcu_flag_get(RCU_FLAG_FWDGTRST) != RESET) { -#if DEBUG_PRINT - printf("[BOOT] FWDGT reset detected, blocking app jump\r\n"); -#endif - OTA_SET(n_APP_LATEST_EN_ADDR, OTA_NOT_AVAL); - } - rcu_all_reset_flag_clear(); flash_ptr = (volatile uint32_t *)n_APP_LATEST_EN_ADDR; @@ -150,19 +141,30 @@ void BootloaderSystem::run() { /** * 情况 1:应用已校验完成,且允许启动。 - * 直接跳转到主应用,不在 Bootloader 多停留。 + * 增加 CRC32 完整性校验;若校验失败则标记 A区无效,fall through 到恢复逻辑。 */ if (app_check == OTA_AVAL && app_enable == OTA_AVAL) { #if DEBUG_PRINT printf("[BOOT] path=jump_latest_now\r\n"); #endif - ota.jumpToApplication(); + if (verifyAppCrc(APPLATEST_START_ADDR)) { + ota.jumpToApplication(); + /** jumpToApplication() 正常不返回;若返回则继续 fall through。 */ + } else { + /** CRC 校验失败:标记 A区无效,继续往下走恢复逻辑。 */ +#if DEBUG_PRINT + printf("[BOOT] CRC fail, mark latest invalid\r\n"); +#endif + OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_NOT_AVAL); + } } + /** * 情况 2:应用是有效的,但当前被标记为"暂不启动"。 * 这通常表示系统希望给 OTA 留一个短暂窗口,若窗口内没有升级请求,再启动应用。 + * 窗口超时后也做 CRC 校验,失败则 fall through。 */ - else if (app_check == OTA_AVAL && app_enable == OTA_NOT_AVAL) { + if (app_check == OTA_AVAL && app_enable == OTA_NOT_AVAL) { uint32_t delay_count = 0; uint32_t heartbeat_counter = 0U; int ota_requested = 0; @@ -188,32 +190,37 @@ void BootloaderSystem::run() { } if (ota_requested) { - /** - * 已经进入 OTA 会话: - * 后续不再尝试跳应用,持续停留在 Bootloader 内处理升级。 - */ #if DEBUG_PRINT printf("[BOOT] path=stay_in_ota\r\n"); #endif uint32_t hb_ota = 0U; while (1) { pollAllLinks(); - heartbeatPrint(hb_ota, 10U, "stay_in_ota"); - delay_1ms(10); + heartbeatPrint(hb_ota, 1U, "stay_in_ota"); + delay_1ms(1); } } else { - /** 等待窗口内没有任何 OTA 请求,正常启动当前应用。 */ #if DEBUG_PRINT printf("[BOOT] ota_window_timeout_then_jump\r\n"); #endif - ota.jumpToApplication(); + if (verifyAppCrc(APPLATEST_START_ADDR)) { + ota.jumpToApplication(); + /** jumpToApplication() 正常不返回;若返回则继续 fall through。 */ + } else { + /** CRC 校验失败:标记 A区无效,继续往下走恢复逻辑。 */ +#if DEBUG_PRINT + printf("[BOOT] CRC fail after OTA window, mark latest invalid\r\n"); +#endif + OTA_SET(n_APP_LATEST_CHECK_ADDR, OTA_NOT_AVAL); + } } } + /** - * 情况 3:当前主应用区不可用。 + * 情况 3:当前主应用区不可用(标志无效或 CRC 校验失败 fall through)。 * 尝试检查备份区是否可恢复;如果可恢复,则复制到主应用区后启动。 */ - else { + { uint32_t back_aval, back_check; flash_ptr = (volatile uint32_t *)n_APP_BACKUP_AVAL_ADDR; back_aval = *flash_ptr; @@ -227,41 +234,45 @@ void BootloaderSystem::run() { #endif if (back_check == OTA_AVAL && back_aval == OTA_AVAL) { - /** - * 备份区可用: - * 1. 先发送一帧提示,表示即将走备份恢复路径; - * 2. 把备份应用搬运到主应用区; - * 3. 跳转运行。 - */ #if DEBUG_PRINT printf("[BOOT] path=restore_backup_then_jump\r\n"); #endif CAN0_transmiter.sendBackup(); - move_app_data(); - ota.jumpToApplication(); + fmc_state_enum restore_result = move_app_data(); + if (restore_result == FMC_READY) { + if (verifyAppCrc(APPLATEST_START_ADDR)) { + ota.jumpToApplication(); + } +#if DEBUG_PRINT + printf("[BOOT] CRC fail after restore, stay in BOOT\r\n"); +#endif + } else { +#if DEBUG_PRINT + printf("[BOOT] restore failed status=%u\r\n", (unsigned)restore_result); +#endif + } } else { - /** - * 当前区和备份区都不可用: - * 只能长期停留在 Bootloader,等待外部重新下发固件。 - */ #if DEBUG_PRINT printf("[BOOT] path=stay_boot_wait_image\r\n"); #endif - uint32_t hb_wait = 0U; - while (1) { - pollAllLinks(); - heartbeatPrint(hb_wait, 10U, "stay_boot_wait_image"); - delay_1ms(10); - } + } + + /** + * 当前区和备份区都不可用,或恢复后校验失败: + * 只能长期停留在 Bootloader,等待外部重新下发固件。 + */ +#if DEBUG_PRINT + printf("[BOOT] path=stay_boot_wait_ota\r\n"); +#endif + uint32_t hb_wait = 0U; + while (1) { + pollAllLinks(); + heartbeatPrint(hb_wait, 10U, "stay_boot_wait_ota"); + delay_1ms(10); } } } -/** - * @brief Bootloader 程序入口。 - * - * @return 理论上不会返回;若异常返回则维持空转。 - */ int main(void) { sys.initHardware(); sys.run(); diff --git a/GD32E50X_BOOT/sys/OTA.cpp b/GD32E50X_BOOT/sys/OTA.cpp index 5136c7b..080ade4 100644 --- a/GD32E50X_BOOT/sys/OTA.cpp +++ b/GD32E50X_BOOT/sys/OTA.cpp @@ -1,4 +1,4 @@ -#include "OTA.h" +#include "OTA.h" #include "Can.h" #include "SpiOta.h" #include "aes128.h" @@ -294,6 +294,14 @@ void OTAHandler::process() { #endif send_structured_ack(0U); } + drainFlashBuffer(); + if (pending_finish_ack && flash_buf_cnt == 0U) { + pending_finish_ack = false; +#if DEBUG_PRINT + printf("[OTA] flash drain done, send FINISH ACK\r\n"); +#endif + send_structured_ack(0xFFFFFFFFU); + } if (pending_app_jump) { pending_app_jump = false; @@ -313,7 +321,7 @@ void OTAHandler::process() { * 非空闲态时,按固定节奏推进超时计数。 * 这里用 100us 粗略步进,不追求绝对精确,只做会话保活超时保护。 */ - delay_1us(100); + delay_1us(20); ota_time++; } @@ -389,14 +397,6 @@ bool validateApplication() { reset_handler < (APPLATEST_START_ADDR + APP_MAX_SIZE)); #if DEBUG_PRINT - printf("[BOOT] validateApp: MSP=0x%08lX (%s) Reset=0x%08lX (%s) @0x%08lX\r\n", - (unsigned long)stack_ptr, stack_valid ? "OK" : "INVALID", - (unsigned long)reset_handler, reset_valid ? "OK" : "INVALID", - (unsigned long)APPLATEST_START_ADDR); - /* 额外打印前 4 个 word 帮助诊断固件内容是否正确 */ - printf("[BOOT] vector[0..3]=%08lX %08lX %08lX %08lX\r\n", - (unsigned long)app_vector[0], (unsigned long)app_vector[1], - (unsigned long)app_vector[2], (unsigned long)app_vector[3]); #endif return stack_valid && reset_valid; @@ -409,15 +409,17 @@ bool validateApplication() { */ void OTAHandler::jumpToApplication() { if (!validateApplication()) { +return; + } + + /** CRC32 完整性校验:若固件被破坏则不跳转。 */ + if (!verifyAppCrc(APPLATEST_START_ADDR)) { #if DEBUG_PRINT - printf("[BOOT] jump rejected: invalid app vector @0x%08lX\r\n", (unsigned long)APPLATEST_START_ADDR); + printf("[BOOT] CRC verify FAIL, abort jump\r\n"); #endif return; } -#if DEBUG_PRINT - printf("[BOOT] jumping to app @0x%08lX\r\n", (unsigned long)APPLATEST_START_ADDR); -#endif /* * [关键修复] 先关中断再做 Flash 操作。 @@ -433,43 +435,19 @@ void OTAHandler::jumpToApplication() { __DSB(); __ISB(); -#if DEBUG_PRINT - printf("[DBG] NVIC disabled\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("[DBG] OTA_SET done\r\n"); -#endif /* 关闭 Bootloader 运行过程中启用过的外设。先关 DMA 再关外设。 */ DMA_CHCTL(DMA0, DMA_CH1) &= ~DMA_CHXCTL_CHEN; -#if DEBUG_PRINT - printf("[DBG] DMA_CH1 disabled\r\n"); -#endif DMA_CHCTL(DMA0, DMA_CH2) &= ~DMA_CHXCTL_CHEN; -#if DEBUG_PRINT - printf("[DBG] DMA_CH2 disabled\r\n"); -#endif can_deinit(CAN0); -#if DEBUG_PRINT - printf("[DBG] CAN0 deinit\r\n"); -#endif spi_i2s_deinit(SPI0); -#if DEBUG_PRINT - printf("[DBG] SPI0 deinit\r\n"); -#endif exti_interrupt_disable(EXTI_15); -#if DEBUG_PRINT - printf("[DBG] EXTI15 disabled\r\n"); -#endif -#if DEBUG_PRINT - printf("[DBG] peripherals deinit done\r\n"); -#endif /* * SysTick 不属于 NVIC 外设中断,跳转前单独关闭即可。 @@ -480,9 +458,6 @@ void OTAHandler::jumpToApplication() { SysTick->LOAD = 0U; SysTick->VAL = 0U; -#if DEBUG_PRINT - printf("[DBG] SysTick disabled\r\n"); -#endif /* 把中断向量表切到主应用起始地址。 */ SCB->VTOR = APPLATEST_START_ADDR; @@ -491,12 +466,6 @@ void OTAHandler::jumpToApplication() { 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("[DBG] app MSP=0x%08lX Reset=0x%08lX\r\n", - (unsigned long)msp_value, - (unsigned long)reset_handler); - printf("[DBG] calling app entry\r\n"); -#endif /* 仍在 Boot 栈上做最后一次清理,真正切栈和跳转交给裸汇编 trampoline。 */ rcu_all_reset_flag_clear(); @@ -541,6 +510,18 @@ void OTAHandler::eraseApplicationArea() { * @param size 数据长度。 * @return 无。 */ + + +void OTAHandler::drainFlashBuffer() { + while (flash_buf_cnt > 0U) { + u8 ri = flash_buf_ri; + writeApplicationData(flash_buf[ri].offset, flash_buf[ri].data, OTA_PACKET_SIZE); + flash_buf_ri = (ri + 1U) % kFlashBufSlots; + __disable_irq(); + flash_buf_cnt--; + __enable_irq(); + } +} void OTAHandler::writeApplicationData(u32 offset, u8 *data, u32 size) { /** * 将一个分包写入主应用区: @@ -644,13 +625,23 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) { return; } else { u32 offset = receive_packet_num * OTA_PACKET_SIZE; - if (offset + OTA_PACKET_SIZE <= APP_MAX_SIZE) { - writeApplicationData(offset, decrypted_data, OTA_PACKET_SIZE); - } else { + if (offset + OTA_PACKET_SIZE > APP_MAX_SIZE) { ota_error = OTA_ERROR_INVALID_SIZE; ota_state = OTA_STATE_IDLE; send_error_frame(receive_packet_num, OTA_ERROR_INVALID_SIZE); + return; } + if (flash_buf_cnt >= kFlashBufSlots) { + 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(); } /** @@ -660,7 +651,9 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) { */ if (receive_IFEND == 0xFFFFFFFFU) { ota_state = OTA_STATE_FINISHED; - send_structured_ack(0xFFFFFFFFU); + pending_finish_ack = true; + u8 wait_hint[1] = {0x04}; + send_wait_frame(1, wait_hint); return; } @@ -704,6 +697,10 @@ void OTAHandler::handleLinkMessage(ota_link_msg_type_t type, u8 *data, u8 len) { return; } + /** 计算并写入 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); @@ -906,6 +903,14 @@ fmc_state_enum move_app_data() { * 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(); @@ -947,6 +952,12 @@ fmc_state_enum move_app_data() { 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; } @@ -987,3 +998,80 @@ void calculate_crc32_with_key(const u8 *key, const u8 *data, u8 *output) { 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 地址未写过说明无校验数据。 */ + if (stored_crc == FLASH_EMPTY) { + return false; + } + + 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 + } +} diff --git a/GD32E50X_BOOT/sys/OTA.h b/GD32E50X_BOOT/sys/OTA.h index fa4c644..d0a9395 100644 --- a/GD32E50X_BOOT/sys/OTA.h +++ b/GD32E50X_BOOT/sys/OTA.h @@ -98,9 +98,10 @@ class OTAHandler { memset(&aes_ctx, 0, sizeof(aes_ctx)); pending_flash_erase = false; pending_app_jump = false; - pending_flash_offset = 0; - memset(pending_flash_data, 0, sizeof(pending_flash_data)); - pending_flash_size = 0; + pending_finish_ack = false; + flash_buf_wi = 0U; + flash_buf_ri = 0U; + flash_buf_cnt = 0U; } /** @@ -178,11 +179,22 @@ class OTAHandler { bool pending_flash_erase = false; /**< 是否存在待处理的延迟擦除请求。 */ bool pending_app_jump = false; /**< 是否存在待处理的应用跳转请求。 */ - u32 pending_flash_offset = 0; /**< 预留:批量写入方案使用的偏移字段。 */ - u8 pending_flash_data[64]; /**< 预留:批量写入方案使用的数据缓冲。 */ - u8 pending_flash_size = 0; /**< 预留:批量写入方案使用的数据长度。 */ ota_send_fn_t transport_send = nullptr; /**< 当前底层回包发送函数。 */ + /* Flash 写入环形缓冲(ISR 写入,主循环 drain 写 Flash) */ + static constexpr u8 kFlashBufSlots = 8U; + struct FlashSlot { + u32 offset; + u8 data[OTA_PACKET_SIZE]; + }; + FlashSlot flash_buf[kFlashBufSlots]; + volatile u8 flash_buf_wi = 0U; /**< ISR 写指针 */ + volatile u8 flash_buf_ri = 0U; /**< 主循环读指针 */ + volatile u8 flash_buf_cnt = 0U; /**< 缓冲中待写入条目数 */ + volatile bool pending_finish_ack = false; /**< FINISH 包已缓冲,等待 drain 后回 ACK */ + + void drainFlashBuffer(); + /** * @brief OTA 流程状态定义。 */ @@ -300,4 +312,34 @@ fmc_state_enum move_app_data(); */ u8 OTA_SET(u32 addr, u32 data); +/** + * @brief 使用硬件 CRC 单元计算指定区域的 CRC32。 + * + * @param start_addr 区域起始地址。 + * @param word_count 区域长度(按 32 位字计数)。 + * @return 计算得到的 CRC32 值。 + */ +u32 calculateAppCrc(u32 start_addr, u32 word_count); + +/** + * @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); + +/** + * @brief 将 CRC 值写入应用区末尾。 + * + * @param start_addr 应用区起始地址,用于计算 CRC 存储位置。 + * @param crc_value 待写入的 CRC32 值。 + * @return 无。 + */ +void writeAppCrc(u32 start_addr, u32 crc_value); + #endif diff --git a/GD32E50X_BOOT/sys/config.h b/GD32E50X_BOOT/sys/config.h index 4e5f1d6..bb7f4e0 100644 --- a/GD32E50X_BOOT/sys/config.h +++ b/GD32E50X_BOOT/sys/config.h @@ -1,4 +1,4 @@ -#ifndef __CONFIG_H__ +#ifndef __CONFIG_H__ #define __CONFIG_H__ #include @@ -24,9 +24,13 @@ inline constexpr u32 BOOTLOADER_START_ADDR = 0x08000000U; inline constexpr u32 BOOTLOADER_SIZE = 0x00012000U; /**< 72KB */ inline constexpr u32 APPBACK_START_ADDR = 0x08012000U; /**< 备份应用起始地址 */ inline constexpr u32 APPLATEST_START_ADDR = 0x08048000U; /**< 当前有效应用起始地址 */ -inline constexpr u32 APP_MAX_SIZE = 0x00036000U; /**< 216KB */ +inline constexpr u32 APP_MAX_SIZE = 0x00035FFCU; /**< 216KB - 4B,末尾预留 CRC32 */ inline constexpr u32 FMC_PAGE_SIZE = 0x00000800U; /**< 2KB,GD32E50x 单页大小 */ inline constexpr u32 OTA_CONFIG_PAGE_ADDR = 0x0807E000U; /**< OTA 元数据页 */ +inline constexpr u32 APP_CRC_SIZE = 4U; /**< 固件区末尾 CRC32 占用字节数 */ +inline constexpr u32 APP_FIRMWARE_SIZE = APP_MAX_SIZE - APP_CRC_SIZE; /**< 实际固件大小(不含 CRC) */ +inline constexpr u32 n_APP_LATEST_CRC_ADDR = APPLATEST_START_ADDR + APP_FIRMWARE_SIZE; /**< A区 CRC 存储地址 */ +inline constexpr u32 n_APP_BACKUP_CRC_ADDR = APPBACK_START_ADDR + APP_FIRMWARE_SIZE; /**< B区 CRC 存储地址 */ inline constexpr u32 SLEEP_CONFIG_START = 0x0807E800U; /**< 睡眠配置页起始 */ inline constexpr u32 SLEEP_CONFIG_END = 0x0807F000U; /**< 睡眠配置页结束(开区间) */ @@ -97,7 +101,7 @@ inline constexpr uint8_t K_master[16] = { * - OTA_TIMEOUT:OTA 会话超时时间计数上限 * - APP_DELAY_BOOT_TIME:发现应用存在但被禁用时,Bootloader 等待 OTA 请求的窗口 */ -inline constexpr u32 OTA_TIMEOUT = 10000U; +inline constexpr u32 OTA_TIMEOUT = 50000U; inline constexpr u32 APP_DELAY_BOOT_TIME = 10000U; /** 常用尺寸常量。 */