Lehnte die Recovery ein Image ab, weil es bereits die laufende Version traegt, meldete die Firmware das als last_ota_error=trailer. Wer eine Lampe im Feld ausliest, haette dort einen Trailer-Fehler gesehen, wo in Wahrheit nichts passiert ist - und die Meldung in der Konsole lautete "update failed". Das bewusste Ueberspringen hat jetzt einen eigenen Zustand (nl_ota_skipped()), setzt keinen Fehlerwert und wird als "image already up to date" protokolliert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
173 lines
6.6 KiB
C
173 lines
6.6 KiB
C
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "wm_include.h"
|
|
#include "wm_http_fwup.h"
|
|
|
|
#include "config/nl_settings.h"
|
|
#include "sys/nl_app.h"
|
|
#include "sys/nl_fwup_hook.h"
|
|
#include "sys/nl_ota.h"
|
|
#include "util/nl_fwup_trailer.h"
|
|
|
|
#include "nl_version.h"
|
|
|
|
/* t_http_fwup() (sdk/Src/App/OTA/wm_http_fwup.c) has no declaration in any SDK header. */
|
|
extern int t_http_fwup(char *url);
|
|
|
|
#define NL_OTA_URL_MAX 256
|
|
#define NL_OTA_STACK 2048 /* words */
|
|
#define NL_OTA_PRIO 34
|
|
|
|
static char s_url[NL_OTA_URL_MAX];
|
|
static volatile bool s_busy;
|
|
static volatile bool s_reject_running_version; /* recovery path only, see nl_ota_start_recovery() */
|
|
static const char * volatile s_error;
|
|
static volatile bool s_skipped;
|
|
static u32 s_stack[NL_OTA_STACK];
|
|
static char s_tail_version[NL_TRAILER_VERSION_LEN]; /* used by the hook, kept off its 2 KB stack */
|
|
|
|
void nl_ota_init(void)
|
|
{
|
|
s_busy = false;
|
|
s_error = NULL;
|
|
}
|
|
|
|
bool nl_ota_busy(void) { return s_busy; }
|
|
bool nl_ota_skipped(void) { return s_skipped; }
|
|
const char *nl_ota_last_error(void) { return s_error; }
|
|
void nl_ota_set_error(const char *e) { s_error = e; }
|
|
|
|
/* Called by the SDK firmware-update path once the image has been written and hashed.
|
|
sha256 covers the whole payload (T_BOOTER header + compressed image), tail is the
|
|
512 byte end block that used to carry the QConnex RSA signature.
|
|
NB: this runs on the SDK's own fwup_scheduler task (tls_fwup_init(), stack
|
|
FWUP_TASK_STK_SIZE*4, ~2 KB) - NOT on the 8 KB nl_ota task stack. Keep this hook minimal:
|
|
one short printf, no extra locals (a version buffer used to sit here and was removed). */
|
|
bool nl_fwup_verify_tail(const uint8_t sha256[32], const uint8_t *tail, uint32_t tail_len)
|
|
{
|
|
nl_trailer_err_t e = nl_trailer_verify(tail, tail_len, NINLIGHT_DEVICE_ID, sha256);
|
|
|
|
if (e != NL_TRAILER_OK) {
|
|
s_error = nl_trailer_err_name(e);
|
|
printf("ota: trailer %s\r\n", s_error);
|
|
return false;
|
|
}
|
|
/* C1: recovery only - the rescue station serves the same image to every lamp that boots in
|
|
* front of it, so an image that already matches the running version must be refused here;
|
|
* otherwise the lamp flashes and reboots in an endless loop. No new last_ota_error value is
|
|
* invented for this (the vocabulary is fixed in spec 8.2); the log line below is what tells
|
|
* the two cases apart. */
|
|
if (s_reject_running_version) {
|
|
nl_trailer_version(tail, s_tail_version);
|
|
if (strcmp(s_tail_version, NINLIGHT_FW_VERSION) == 0) {
|
|
printf("ota: recovery image already carries the running version %s, not flashing\r\n",
|
|
s_tail_version);
|
|
/* Deliberately skipped, not failed: leave last_ota_error alone. Reporting "trailer"
|
|
* here used to make a perfectly healthy lamp look like it had rejected a malformed
|
|
* image, which is exactly the wrong thing to read off a lamp in the field. */
|
|
s_skipped = true;
|
|
return false;
|
|
}
|
|
printf("ota: recovery image version %s (running %s)\r\n", s_tail_version, NINLIGHT_FW_VERSION);
|
|
}
|
|
printf("ota: trailer ok\r\n");
|
|
return true;
|
|
}
|
|
|
|
/* Logs the URL without any "user:pass@" userinfo, so credentials embedded in the URL never
|
|
reach the console. s_url is validated by nl_ota_start() to start with "http://". */
|
|
static void log_url(const char *url)
|
|
{
|
|
const char *at = strchr(url + 7, '@');
|
|
|
|
if (at != NULL)
|
|
printf("ota: downloading http://%s\r\n", at + 1);
|
|
else
|
|
printf("ota: downloading %s\r\n", url);
|
|
}
|
|
|
|
static void ota_task(void *arg)
|
|
{
|
|
int rc;
|
|
|
|
(void)arg;
|
|
log_url(s_url);
|
|
rc = t_http_fwup(s_url);
|
|
/* Only reached on failure - the SDK resets the chip itself after a good image. */
|
|
if (s_error == NULL && !s_skipped)
|
|
s_error = "download";
|
|
printf("ota: failed (rc=%d, %s)\r\n", rc, s_error);
|
|
{
|
|
nl_settings_t *s;
|
|
nl_app_lock();
|
|
s = nl_app_settings();
|
|
s->ota_pending = 0;
|
|
nl_app_unlock();
|
|
(void)nl_app_save_settings();
|
|
}
|
|
s_busy = false;
|
|
tls_os_task_del(NL_OTA_PRIO, NULL);
|
|
}
|
|
|
|
static int ota_start(const char *url, bool reject_running_version)
|
|
{
|
|
size_t n;
|
|
|
|
if (url == NULL || strncmp(url, "http://", 7) != 0)
|
|
return -1;
|
|
n = strlen(url);
|
|
if (n <= 7 || n >= sizeof s_url) /* n == 7 is bare "http://" with no host */
|
|
return -1;
|
|
if (s_busy) {
|
|
/* Do NOT touch s_error here. last_ota_error describes the outcome of the *running or last*
|
|
* update; a request we reject because one is already in flight has no outcome of its own.
|
|
* Writing "busy" into it used to poison the running attempt's slot: ota_task() only falls
|
|
* back to "download" when s_error is still NULL, so a failed transfer was afterwards
|
|
* reported as `busy` instead of `download`. Found on the EVK, 2026-09-12. The REST layer
|
|
* reports the rejection to its own caller with 409. */
|
|
return -1;
|
|
}
|
|
s_busy = true;
|
|
s_error = NULL;
|
|
s_skipped = false;
|
|
s_reject_running_version = reject_running_version;
|
|
memcpy(s_url, url, n + 1);
|
|
|
|
/* Persist the open light state and mark the pending update before the flash gets busy. */
|
|
nl_app_flush();
|
|
{
|
|
nl_settings_t *s;
|
|
nl_app_lock();
|
|
s = nl_app_settings();
|
|
s->ota_pending = 1;
|
|
nl_app_unlock();
|
|
(void)nl_app_save_settings();
|
|
}
|
|
|
|
if (tls_os_task_create(NULL, "nl_ota", ota_task, NULL, (u8 *)s_stack, sizeof(s_stack),
|
|
NL_OTA_PRIO, 0) != TLS_OS_SUCCESS) {
|
|
/* The task never ran, so clear the ota_pending flag we just persisted above - otherwise
|
|
* the next boot wrongly logs "boot after ota attempt" for an update that never started. */
|
|
nl_settings_t *s;
|
|
nl_app_lock();
|
|
s = nl_app_settings();
|
|
s->ota_pending = 0;
|
|
nl_app_unlock();
|
|
(void)nl_app_save_settings();
|
|
s_busy = false;
|
|
/* Minor (final review): no value in the fixed vocabulary of spec 8.2 describes "could not
|
|
* create the download task" (out of RAM/TCBs), and "download" would claim a transfer that
|
|
* never happened. `busy` stays the closest fit - the chip really is too busy to start one -
|
|
* and this log line is what distinguishes it from "an update is already running". */
|
|
printf("ota: cannot create download task (out of resources)\r\n");
|
|
s_error = "busy";
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int nl_ota_start(const char *url) { return ota_start(url, false); }
|
|
|
|
int nl_ota_start_recovery(const char *url) { return ota_start(url, true); }
|