/** * @file ota_boot_request.cpp * @brief DCU_APP 进入 Bootloader OTA 窗口控制实现。 */ #include "sys/ota_boot_request.h" #include "gd32e50x.h" #include "sys/config.h" #include "sys/systick.h" namespace { /** Bootloader OTA 配置页地址,与 BOOT 工程 sys/config.h 保持一致。 */ constexpr uint32_t kOtaConfigPageAddr = 0x0807E000U; constexpr uint32_t kAppLatestAvalAddr = kOtaConfigPageAddr + 0x00U; constexpr uint32_t kAppLatestEnAddr = kOtaConfigPageAddr + 0x08U; constexpr uint32_t kAppLatestCheckAddr = kOtaConfigPageAddr + 0x10U; constexpr uint32_t kOtaAval = 0x00000000U; constexpr uint32_t kOtaNotAval = 0xFFFFFFFFU; constexpr uint32_t kFmcPageSize = 0x00000800U; constexpr uint32_t kRebootDelayTicks = 2U; static bool writeWord(uint32_t addr, uint32_t value) { fmc_unlock(); fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); fmc_state_enum status = fmc_word_program(addr, value); fmc_lock(); return status == FMC_READY; } /** * @brief 按 Bootloader OTA_SET 语义更新配置页中单个 word。 * @note Flash 只能 1->0;需要写回 0xFFFFFFFF 时必须擦整页再恢复其他字段。 */ static bool otaConfigSet(uint32_t targetAddr, uint32_t value) { if(targetAddr < kOtaConfigPageAddr || targetAddr >= (kOtaConfigPageAddr + kFmcPageSize)) { return false; } const uint32_t current = *((volatile uint32_t *)targetAddr); if(current == value) { return true; } if(value != kOtaNotAval) { return writeWord(targetAddr, value); } uint32_t words[6]; for(uint32_t i = 0U; i < 6U; ++i) { words[i] = *((volatile uint32_t *)(kOtaConfigPageAddr + i * 4U)); } words[(targetAddr - kOtaConfigPageAddr) / 4U] = value; fmc_unlock(); fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR); fmc_state_enum status = fmc_page_erase(kOtaConfigPageAddr); if(status != FMC_READY) { fmc_lock(); return false; } bool ok = true; for(uint32_t i = 0U; i < 6U; ++i) { if(words[i] == kOtaNotAval) { continue; } status = fmc_word_program(kOtaConfigPageAddr + i * 4U, words[i]); if(status != FMC_READY) { ok = false; break; } } fmc_lock(); return ok; } static bool requestMagicValid(const uint8_t *data, uint8_t length) { return data != nullptr && length >= 4U && data[0] == CONFIG_CAN_OTA_BOOT_MAGIC0 && data[1] == CONFIG_CAN_OTA_BOOT_MAGIC1 && data[2] == CONFIG_CAN_OTA_BOOT_MAGIC2 && data[3] == CONFIG_CAN_OTA_BOOT_MAGIC3; } } // namespace /** @brief 已写入 OTA 标志并等待软复位的挂起标志。 */ volatile bool OtaBootRequest::s_rebootPending = false; /** @brief 复位前的调度延迟计数,给 SPI/CAN 应答留出发送时间。 */ uint32_t OtaBootRequest::s_rebootCountdown = 0U; /** * @brief 初始化进入 Bootloader 请求模块的运行态。 */ void OtaBootRequest::init() { s_rebootPending = false; s_rebootCountdown = 0U; } /** * @brief 写入 Bootloader OTA 等待标志,并安排稍后软复位。 * @return true 表示标志写入成功;false 表示 Flash 配置页写入失败。 */ bool OtaBootRequest::requestEnterBoot() { bool ok = true; ok = otaConfigSet(kAppLatestCheckAddr, kOtaAval) && ok; ok = otaConfigSet(kAppLatestAvalAddr, kOtaAval) && ok; if(ok) { ok = otaConfigSet(kAppLatestEnAddr, kOtaNotAval); } if(ok) { s_rebootCountdown = kRebootDelayTicks; s_rebootPending = true; } return ok; } /** * @brief 处理备用 CAN 入口的进入 Bootloader 请求。 * @note 当前主路径是 SPI 命令;CAN 入口只负责识别魔术字并触发同一套标志写入逻辑。 */ bool OtaBootRequest::handleCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t length) { if(id != CONFIG_CAN_OTA_BOOT_REQ_ID) { return false; } if(!requestMagicValid(data, length)) { return true; } (void)channel; const bool ok = requestEnterBoot(); (void)ok; return true; } /** * @brief 在主循环任务中执行延迟软复位。 * @note 不在 SPI/CAN 中断里直接复位,避免应答还没发出就重启。 */ void OtaBootRequest::process() { if(!s_rebootPending) { return; } if(s_rebootCountdown > 0U) { --s_rebootCountdown; return; } __disable_irq(); NVIC_SystemReset(); while(1) { } }