fix(ota): 修复SPI OTA重传机制和跳转应用逻辑

- 主循环发送ACK时同步更新重发缓存,解决ISR先写WAIT后主循环发ACK不更新
  缓存的问题,避免重发时返回过期WAIT导致ESP32永远接收WAIT而中止传输

- 在stay_in_ota状态结束跳转应用前增加CHECK/AVAL标志门控,防止在
  OTA中途取消时verifyAppCrc的FLASH_EMPTY兜底逻辑误判通过,跳入半成品
  固件导致GD32永久失联
This commit is contained in:
cbb
2026-08-02 17:36:26 +08:00
parent f9ae1be120
commit 1ec8aa3b5b
3 changed files with 33 additions and 4 deletions
+12
View File
@@ -187,6 +187,18 @@ void SpiOta::sender(ota_link_tx_type_t type, u8 len, const u8 *data) {
u16 built_len = 0U;
if (buildFrame(rsp_type, data, len, frame_buf, &built_len)) {
submitResponse(frame_buf, built_len);
/* ★ 主循环发 ACK 时同步更新重发缓存(仅 ACK,不缓存 WAIT)。
* 旧 bug:ISR 先写 WAIT 入缓存,主循环后续发 ACK 却不更新缓存,
* 一旦该 ACK 帧因时序丢失,ESP32 重发触发 DUP-SEQ 时只能回放过期
* WAIT → ESP32 永远只见 WAIT → stall 中止(特别在 8MHz 下必现)。
* 现在重发会回放最终 ACK,实现“重发快且不漏”。 */
if (type == OTA_LINK_TX_ACK && built_len > 0U && built_len <= sizeof(s_lastRespBuf)) {
uint32_t primask = enterCritical();
memcpy(s_lastRespBuf, frame_buf, built_len);
s_lastRespLen = built_len;
s_hasCachedResp = true;
exitCritical(primask);
}
}
}
+20 -3
View File
@@ -265,11 +265,28 @@ void BootloaderSystem::run() {
was_active = true;
idle_since = 0U;
} else if (was_active) {
/* 曾进入 OTA,现在回到 IDLE(CANCEL/超时/完成)→ 跳回 APP */
/* 曾进入 OTA,现在回到 IDLE(CANCEL/超时/完成)→ 跳回 APP。
*
* ★ 必须与 OTA.cpp 超时路径一样用应用区标志门控:
* eraseApplicationArea() 在 SIZE 阶段已把 CHECK/AVAL 置为
* NOT_AVAL(0xFFFFFFFF),应用区处于“半擦/半写”状态;此时
* verifyAppCrc() 的 FLASH_EMPTY 兜底(CRC 页已擦=0xFFFFFFFF
* → 判“无 CRC 保护=有效”)会误判通过,跳入半成品固件:
* 向量表已被前几包写入 → validateApplication 通过 → 直接
* BX 到残缺代码 → HardFault → GD32 永久失联(SPI 全超时,
* 日志特征:CANCEL 后 queryFeedback 全部 ESP_ERR_TIMEOUT)。
* 只有标志仍为 AVAL(OTA 从未擦过应用区,旧固件完好)
* 才允许跳回;中途中止则留在 BOOT 等重试/重启。 */
flash_ptr = (volatile uint32_t *)n_APP_LATEST_CHECK_ADDR;
const u32 ended_check = *flash_ptr;
flash_ptr = (volatile uint32_t *)n_APP_LATEST_AVAL_ADDR;
const u32 ended_aval = *flash_ptr;
#if DEBUG_PRINT
printf("[BOOT] stay_in_ota -> ota ended, jump app\r\n");
printf("[BOOT] stay_in_ota -> ota ended, check=0x%08lX aval=0x%08lX\r\n",
(unsigned long)ended_check, (unsigned long)ended_aval);
#endif
if (verifyAppCrc(APPLATEST_START_ADDR)) {
if (ended_check == OTA_AVAL && ended_aval == OTA_AVAL &&
verifyAppCrc(APPLATEST_START_ADDR)) {
ota.jumpToApplication();
}
/* jumpToApplication 失败(CRC/向量不合法)则留在 BOOT */
+1 -1
View File
@@ -16,7 +16,7 @@ typedef uint32_t u32;
#define BOOT_FW_VERSION_MAJOR 1U
#define BOOT_FW_VERSION_MINOR 1U
#define BOOT_FW_VERSION_PATCH 0U
#define BOOT_FW_VERSION_BUILD 1U
#define BOOT_FW_VERSION_BUILD 3U /* v1.1.0.3: stay_in_ota 跳回 APP 前加 CHECK/AVAL 标志门控,修复中断 OTA 后 verifyAppCrc FLASH_EMPTY 误判跳入半成品固件导致 GD32 永久失联 */
/**
* Flash 布局说明(GD32E508VE,512KB)