Give BWM OTA begin enough time to erase, and stop retuning UART for the transfer

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Will
2026-09-04 08:24:16 -04:00
co-authored by Cursor
parent a019756bfa
commit 22969ec79a
3 changed files with 72 additions and 23 deletions
+36 -12
View File
@@ -24,9 +24,30 @@ static uint16_t wifi_crc16(const uint8_t *d, size_t n, uint16_t crc) {
// Runs only during setup, so it fully owns the RX stream here.
typedef enum { W_H1, W_H2, W_CL, W_CH, W_LL, W_LH, W_PL, W_KL, W_KH } wstate_t;
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
// Drop whatever is already sitting in the UART RX ring so a late ACK from a
// timed-out command cannot be consumed as the next command's response (same
// cmd code, e.g. a slow OTA_BEGIN ack arriving after we already gave up).
static void bwm_cmd_drain_rx(void) {
uint8_t buf[64];
uint32_t t0 = GetTickCount();
while (bwm_uart_rx_available()) {
uint16_t avail = bwm_uart_rx_available();
(void)bwm_uart_read(buf, (uint32_t)MIN(avail, (uint16_t)sizeof(buf)));
if (GetTickCountDelta(t0) > 50) {
break;
}
}
}
int bwm_cmd(uint16_t cmd, const uint8_t *req, uint16_t req_len,
uint8_t *resp, uint16_t *resp_len, uint32_t timeout_ms) {
bwm_cmd_drain_rx();
// ---- build + send HOST_CMD frame ----
uint8_t frame[8 + 256];
if (req_len > sizeof(frame) - 8) {
@@ -63,6 +84,7 @@ int bwm_cmd(uint16_t cmd, const uint8_t *req, uint16_t req_len,
uint8_t buf[64];
uint16_t avail = bwm_uart_rx_available();
if (avail == 0) {
SpinDelay(1);
continue;
}
uint32_t n = bwm_uart_read(buf, (uint32_t)MIN(avail, (uint16_t)sizeof(buf)));
@@ -304,16 +326,20 @@ int bwm_esp_ota_begin(uint32_t total_size) {
uint8_t off = 0;
(void)bwm_cmd(BWM_CMD_LOG_FORWARD_ENABLE, &off, 1, NULL, NULL, 500);
// Drop the AT32<->ESP link to a slow, forgiving baud for the transfer. An OTA
// is one-shot so speed is irrelevant, and 921600 is marginal against the
// flash-write / BLE contention that drops the odd frame -> random timeouts.
(void)bwm_fwd_negotiate_baud(BWM_OTA_BAUD);
// Stop BLE so NimBLE is not hitting flash (NVS / auto-suspend) while we
// erase and program the OTA slot. WiFi stays as-is: tearing it down here
// is slow and the USB OTA path does not need it off.
(void)bwm_cmd(BWM_CMD_STOP_BLE_SPP, NULL, 0, NULL, NULL, 2000);
// Do NOT retune the UART here. Version already proved the current baud
// works; dropping 921600 -> 460800/460000 can leave the ESP on one rate
// and the AT32 on the other, after which every OTA_BEGIN times out.
uint8_t p[4] = {
(uint8_t)(total_size & 0xFF), (uint8_t)((total_size >> 8) & 0xFF),
(uint8_t)((total_size >> 16) & 0xFF), (uint8_t)((total_size >> 24) & 0xFF)
};
return bwm_cmd(BWM_CMD_OTA_BEGIN, p, sizeof(p), NULL, NULL, 15000);
return bwm_cmd(BWM_CMD_OTA_BEGIN, p, sizeof(p), NULL, NULL, BWM_OTA_BEGIN_TIMEOUT_MS);
}
int bwm_esp_ota_write(const uint8_t *data, uint16_t len) {
@@ -322,25 +348,23 @@ int bwm_esp_ota_write(const uint8_t *data, uint16_t len) {
// fail the OTA_END size check) - recovery is to restart the whole OTA, which
// the client does. Keep a generous timeout so a slow flash write is not
// mistaken for a drop.
return bwm_cmd(BWM_CMD_OTA_WRITE, data, len, NULL, NULL, 15000);
return bwm_cmd(BWM_CMD_OTA_WRITE, data, len, NULL, NULL, BWM_OTA_WRITE_TIMEOUT_MS);
}
int bwm_esp_ota_end(void) {
// OTA_END goes out at the slow OTA baud (both ends still there); only after it
// do we restore the fast link and log forwarding (both set in _begin).
int r = bwm_cmd(BWM_CMD_OTA_END, NULL, 0, NULL, NULL, 20000);
(void)bwm_fwd_negotiate_baud(BWM_UART_BAUD_TARGET);
uint8_t on = 1;
(void)bwm_cmd(BWM_CMD_LOG_FORWARD_ENABLE, &on, 1, NULL, NULL, 500);
(void)bwm_cmd(BWM_CMD_START_BLE_SPP, NULL, 0, NULL, NULL, 2000);
return r;
}
int bwm_esp_ota_abort(void) {
// OTA gave up mid-transfer: restore the fast link + logs so the module is
// usable again. The ESP's incomplete OTA state is discarded by the next BEGIN.
(void)bwm_fwd_negotiate_baud(BWM_UART_BAUD_TARGET);
// Restore logs + BLE. Leave the UART baud alone (see begin). The ESP's
// incomplete OTA state is discarded by the next BEGIN.
uint8_t on = 1;
(void)bwm_cmd(BWM_CMD_LOG_FORWARD_ENABLE, &on, 1, NULL, NULL, 500);
(void)bwm_cmd(BWM_CMD_START_BLE_SPP, NULL, 0, NULL, NULL, 2000);
return PM3_SUCCESS;
}
+9 -3
View File
@@ -75,11 +75,17 @@ int bwm_wifi_forward_status(uint8_t *state, uint32_t *ip_out);
// After a working OTA_END, the ESP has marked the new partition bootable but
// does not reboot on its own - REBOOT must be sent explicitly (DEV.md 12.8).
#define BWM_CMD_REBOOT 1803
// Run the OTA at a slow, reliable baud (restored to the fast rate at end/abort).
#ifndef BWM_OTA_BAUD
#define BWM_OTA_BAUD 460000
// BEGIN erases (or finishes aborting) an OTA slot. A 1.8 MB erase on ESP32-C2
// commonly takes 20-40 s, so this must be well above the old 15 s race.
#ifndef BWM_OTA_BEGIN_TIMEOUT_MS
#define BWM_OTA_BEGIN_TIMEOUT_MS 60000
#endif
#ifndef BWM_OTA_WRITE_TIMEOUT_MS
#define BWM_OTA_WRITE_TIMEOUT_MS 20000
#endif
#define BWM_CMD_GET_VERSION_INFO 1000 // resp: running firmware version string
#define BWM_CMD_STOP_BLE_SPP 4022 // no payload: stop BLE during OTA (flash contention)
#define BWM_CMD_START_BLE_SPP 4021 // no payload: restore BLE after OTA
int bwm_esp_get_version(uint8_t *buf, uint16_t *buflen);
int bwm_esp_ota_begin(uint32_t total_size);
int bwm_esp_ota_write(const uint8_t *data, uint16_t len);
+27 -8
View File
@@ -2310,15 +2310,23 @@ static void progressbar(long sent, long total, int style) {
static int bwm_ota_once(const uint8_t *fw, size_t fwlen, uint32_t write_delay_ms) {
PacketResponseNG resp;
// BEGIN: tell the BWM how many bytes are coming (it erases the target partition)
// BEGIN: tell the BWM how many bytes are coming. The ESP erases the idle
// OTA slot here; that can take 20-40 s on a 4 MB ESP32-C2, so wait longer
// than the device-side 60 s timeout plus USB round-trip.
uint8_t beg[5] = { BWM_OTA_ACTION_BEGIN,
(uint8_t)(fwlen & 0xFF), (uint8_t)((fwlen >> 8) & 0xFF),
(uint8_t)((fwlen >> 16) & 0xFF), (uint8_t)((fwlen >> 24) & 0xFF) };
clearCommandBuffer();
SendCommandNG(CMD_PM5_BWM_ESP_OTA, beg, sizeof(beg));
if ((WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &resp, 20000) == false) || (resp.status != PM3_SUCCESS)) {
PrintAndLogEx(FAILED, "OTA begin failed (is a responsive BWM fitted?)");
return PM3_EFAILED;
if (WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &resp, 75000) == false) {
PrintAndLogEx(FAILED, "OTA begin timed out (ESP is likely still erasing the OTA slot)");
PrintAndLogEx(HINT, "Wait a few seconds and retry; do not power-cycle mid-erase.");
return PM3_ETIMEOUT;
}
if (resp.status != PM3_SUCCESS) {
PrintAndLogEx(FAILED, "OTA begin failed (status %d)%s", resp.status,
(resp.status == PM3_ETIMEOUT) ? " - UART timeout waiting for ESP" : "");
return resp.status;
}
PrintAndLogEx(INFO, "Uploading " _YELLOW_("%zu") " bytes of ESP firmware over the BWM link...", fwlen);
@@ -2336,11 +2344,13 @@ static int bwm_ota_once(const uint8_t *fw, size_t fwlen, uint32_t write_delay_ms
memcpy(buf + 1, fw + sent, n);
clearCommandBuffer();
SendCommandNG(CMD_PM5_BWM_ESP_OTA, buf, (uint16_t)(n + 1));
bool got = WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &resp, 15000);
bool got = WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &resp, 25000);
if (!got || resp.status != PM3_SUCCESS) {
PrintAndLogEx(NORMAL, "");
if (!got) {
PrintAndLogEx(WARNING, "OTA write stalled at offset %zu (no response)", sent);
} else if (resp.status == PM3_ETIMEOUT) {
PrintAndLogEx(WARNING, "OTA write timed out at offset %zu (ESP did not ACK that chunk)", sent);
} else {
PrintAndLogEx(WARNING, "OTA write rejected at offset %zu (status %d)", sent, resp.status);
}
@@ -2376,7 +2386,7 @@ static int bwm_ota_once(const uint8_t *fw, size_t fwlen, uint32_t write_delay_ms
// That is a genuine failure (boot partition NOT switched) - restart, and
// hint at pacing, which is the usual cure.
PrintAndLogEx(WARNING, "OTA finalize rejected (status %d) - data was lost in transit", resp.status);
PrintAndLogEx(HINT, "Try a per-write delay: " _YELLOW_("hw bwm upgrade -f <fw> --delay 10"));
PrintAndLogEx(HINT, "Try a per-write delay: " _YELLOW_("hw bwm upgrade -f <fw> --delay 20"));
return PM3_EFAILED;
}
// No answer at all. Over BLE the END auto-reboot drops the link before the ack
@@ -2410,14 +2420,14 @@ static int CmdBWMUpgrade(const char *Cmd) {
void *argtable[] = {
arg_param_begin,
arg_str1("f", "file", "<fn>", "ESP32 firmware image (.bin)"),
arg_int0(NULL, "delay", "<ms>", "per-chunk delay to pace the slow AT32<->ESP UART (default 10)"),
arg_int0(NULL, "delay", "<ms>", "per-chunk delay to pace the slow AT32<->ESP UART (default 20)"),
arg_param_end,
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int fnlen = 0;
char fn[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)fn, sizeof(fn), &fnlen);
uint32_t write_delay_ms = (uint32_t)arg_get_int_def(ctx, 2, 10);
uint32_t write_delay_ms = (uint32_t)arg_get_int_def(ctx, 2, 20);
CLIParserFree(ctx);
if (fnlen == 0) {
@@ -2468,7 +2478,16 @@ static int CmdBWMUpgrade(const char *Cmd) {
// retry (offset-idempotent ESP write) lands.
for (int attempt = 1; attempt <= max_attempts; attempt++) {
if (attempt > 1) {
// Abort the in-flight ESP OTA (if any) and give a slow erase a chance
// to finish before we BEGIN again. Otherwise attempt N's BEGIN races
// attempt N-1's still-running erase and times out.
PrintAndLogEx(INFO, "restarting OTA from the beginning (attempt " _YELLOW_("%d") "/%d)", attempt, max_attempts);
uint8_t ab[1] = { BWM_OTA_ACTION_ABORT };
clearCommandBuffer();
SendCommandNG(CMD_PM5_BWM_ESP_OTA, ab, sizeof(ab));
PacketResponseNG abortr;
(void)WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &abortr, 8000);
msleep(3000);
}
int res = bwm_ota_once(fw, fwlen, write_delay_ms);