GitHub

Docs / API reference / syn_model_ota.h

syn_model_ota.hv0.1.0

<synaptic/syn_model_ota.h> defines the over-the-air model update API: a chunked transfer session (beginwrite_chunkfinish) followed by explicit activation of the staged model, with rollback to the previous one. Progress through the update is exposed as a state machine via syn_ota_get_state(). The design intent — validate before staging, stage before activating, always keep a rollback target — is described in Model lifecycle & OTA updates.

Status · Implemented in Phase 4 (v0.4.0), API unchanged since v0.1.0

The frozen header is implemented over the flash-backed model store: chunks stream through a page buffer into the A/B staging slot, finish() re-reads the staged image from flash and validates magic, size, name, and payload CRC32 before committing a staged record, and activation/rollback are each one power-loss-safe ping-pong registry commit plus a hot-swap under the live scheduler. On dual-core boards, begin() parks CPU1 in reset (its XIP bank hosts the staging slots) and every terminal state releases it through the normal blank-check-guarded boot path — resume measured at 1,514 µs boot + 1,519 µs handshake on the FRDM-MCXN947. Board-verified: a slot-maximum 442,432-byte update staged, CRC-verified, and activated; power loss mid-transfer leaves the previous model serving; a staged update survives reboot and activates from IDLE. See Model lifecycle & OTA updates for the architecture. The header is included by syn_api.h only when CONFIG_SYNAPTIC_OTA is enabled.

Types & constants

NameKindDescription
syn_ota_state_tenumOTA session state machine: SYN_OTA_STATE_IDLE — no update in progress; SYN_OTA_STATE_DOWNLOADING — session open, chunks being written; SYN_OTA_STATE_VALIDATING — transfer complete, integrity being checked; SYN_OTA_STATE_STAGING — validated image being placed in the staging slot; SYN_OTA_STATE_READY — staged and awaiting activation; SYN_OTA_STATE_ERROR — the update failed.

Functions

SignatureDescription
int syn_ota_begin(const char *model_name, size_t total_size)Opens an OTA session for the named model, declaring the total image size up front (a .synm image: 64-byte header + payload). Rejects sizes beyond the staging slot's capacity (-EFBIG). Since v0.5.0, first drains the in-flight cross-core inference (1000 ms budget) — refusing with -EBUSY and resuming serving on timeout — closing the Phase 4 gap where a live remote exchange could be killed mid-flight (board-demonstrated under a ~20 req/s offload with zero serve errors). Erases the staging slot sector-by-sector, and on dual-core boards parks CPU1 first (offload pauses; local inference continues). Returns 0 on success, negative errno on failure.
int syn_ota_write_chunk(const uint8_t *data, size_t len)Appends len bytes of the incoming model image to the open session, buffered to the 128-byte flash program page. Any chunk size works — the engine is transport-agnostic. Call repeatedly until total_size bytes have been written. Returns 0 on success, negative errno on failure.
int syn_ota_finish(void)Closes the transfer and validates the staged image by re-reading it from flash: header magic, version, declared size, model name, and payload CRC32 must all match. On success the staged record is committed to the registry and the session reaches SYN_OTA_STATE_READY — a staged update survives reboot. Returns 0 on success, negative errno on failure (state ERROR, recoverable; the active model is untouched).
int syn_ota_activate(void)Commits the staged model as active (one ping-pong registry write) and hot-swaps inference to it; the previous model becomes the rollback candidate. Works from READY or — for a staged update found after reboot — from IDLE. Releases CPU1 on dual-core boards. Returns 0 on success, negative errno on failure.
int syn_ota_rollback(void)Reverts to the previously active model with the same commit + hot-swap path. Also recovers service when an activated update turns out to be unloadable. Returns 0 on success, negative errno on failure.
syn_ota_state_t syn_ota_get_state(void)Returns the current OTA state machine value.

Usage

The update flow — receive a .synm image in chunks from any transport, then activate it. This is exactly what the syn ota shell transport and the samples/ota_update demo do:

c
/* Chunked transfer, then explicit activation */
int ret = syn_ota_begin("test_classify", image_size);
if (ret != 0) {
    return ret;
}

while (bytes_remaining > 0) {
    size_t len = receive_next_chunk(chunk_buf, sizeof(chunk_buf));

    ret = syn_ota_write_chunk(chunk_buf, len);
    if (ret != 0) {
        return ret;
    }
    bytes_remaining -= len;
}

ret = syn_ota_finish();          /* validate + stage */
if (ret != 0 || syn_ota_get_state() != SYN_OTA_STATE_READY) {
    return ret;
}

ret = syn_ota_activate();        /* switch inference to the new model */
if (ret != 0) {
    syn_ota_rollback();          /* revert to the previous model */
}

Notes

  • Power-loss safety: the previous model stays authoritative until the single registry commit in activate(); a reset anywhere mid-transfer or mid-commit boots the old model — exercised by fault injection in the QEMU suite and by physical reset on the board.
  • CPU1 quiescence: the staging slots share flash bank 1 with CPU1's XIP image, so an OTA session parks CPU1; cross-core offload callers time out during the session while local CPU0 inference keeps serving (demonstrated under load).
  • Build gating: the module is compiled and included only with CONFIG_SYNAPTIC_OTA — see the Kconfig reference.
  • Single session: the API models one global update session (no session handle); one OTA update runs at a time.
  • Transport-agnostic: the API only consumes byte chunks — how they arrive (UART, network, USB) is up to the application. The built-in syn ota shell transport offers two modes over 115200-baud UART: ack-paced hex lines (~5.4 KB/s) and, since v0.5.0, the rawdata binary bypass — 11.1 KB/s measured on the board (2.06× hex, 98.7% of the line rate), with a power-loss injection at 81% of a raw 432 KB transfer leaving the store intact. The engine itself is unchanged since Phase 4.
  • Related: an activated model becomes visible through the syn_model.h registry, and syn_model_swap() provides the registry-side hot-swap primitive.