fix: Watchdog wirksam machen, Factory-Reset im App-Task, Resetzähler-Retry
- vApplicationIdleHook() füttert den Watchdog nicht mehr (sdk/Platform/Sys/wm_main.c, dokumentiert als Patch 4 in sdk/PATCHES.md). Bisher fütterte der Idle-Task den Watchdog bei jedem Leerlauf, auch wenn ein Task deadlockt (z.B. nl_app_lock() hält und nie freigibt) - der Watchdog konnte so nie greifen. - Neues Konsolenkommando `wdtest` (Parser in nl_console_cmd.c/.h, Ausführung in nl_console.c, Host-Test in test_console_cmd.c): hält nl_app_lock() für immer, damit light_task und app_task blockieren und nur der Watchdog-Reset den Chip retten kann. - nl_app_request_factory_reset() (nl_app.h) setzt nur ein volatile Flag; app_task prüft es in der Hauptschleife und ruft nl_app_factory_reset() selbst auf, da nur der App-Task die Flash-Stores besitzt. Das Konsolenkommando `factory` ruft jetzt diese Funktion statt nl_app_factory_reset() direkt aufzurufen, um die Race mit nl_state_log_append()/nl_resetcnt_clear() im App-Task zu vermeiden. Der Bootpfad (Bootzähler >= 5) ruft weiterhin direkt nl_app_factory_reset() auf. - nl_resetcnt_clear() wird nur noch als erledigt markiert, wenn es 0 zurückgibt, damit ein fehlgeschlagener Clear in der nächsten Schleifeniteration erneut versucht wird.
This commit is contained in:
@@ -14,3 +14,14 @@ Lib/GNU, Bin/secboot.img, Tools/GNU/link_w600_2m.ld (→ ld/) und wm_tool.c (→
|
|||||||
(`qconnex_sdk/src/qc_main.c`) war `pre_gpio_config()` bereits ein No-Op; `EarlyInit()` rief dort nur
|
(`qconnex_sdk/src/qc_main.c`) war `pre_gpio_config()` bereits ein No-Op; `EarlyInit()` rief dort nur
|
||||||
in die QConnex-Hardwareschicht (`QCXH_EarlyInit()`), die ninlight nicht verwendet. Board-spezifische
|
in die QConnex-Hardwareschicht (`QCXH_EarlyInit()`), die ninlight nicht verwendet. Board-spezifische
|
||||||
Frühinitialisierung (falls nötig) folgt in einem späteren Plan.
|
Frühinitialisierung (falls nötig) folgt in einem späteren Plan.
|
||||||
|
4. `Platform/Sys/wm_main.c`: `vApplicationIdleHook()` hat bisher jedes Mal `tls_watchdog_clr()`
|
||||||
|
aufgerufen, wenn der Idle-Task lief - also immer dann, wenn kein anderer Task rechenbereit war.
|
||||||
|
Ein Task-Deadlock (z.B. ein Task, der `nl_app_lock()` hält und nie freigibt) hätte den Watchdog
|
||||||
|
damit nie ausgelöst, weil der Idle-Task munter weiter gefüttert hätte. Der `tls_watchdog_clr()`-
|
||||||
|
Aufruf wurde entfernt; `wfi` bleibt. Der einzige verbleibende `tls_watchdog_clr()`-Aufruf im SDK
|
||||||
|
ist in `Platform/Common/fwup/wm_fwup.c` (Firmware-Update-Task) - das ist unkritisch, da er nur
|
||||||
|
während eines laufenden OTA-Updates feuert und den `app_task`-Heartbeat in `src/app_main.c`
|
||||||
|
nicht ersetzt (der Watchdog wird sonst ausschließlich dort über den `s_light_beat`-Heartbeat
|
||||||
|
des `light_task` gefüttert). Getestet mit dem Konsolenkommando `wdtest`
|
||||||
|
(src/sys/nl_console_cmd.c, src/sys/nl_console.c): es hält `nl_app_lock()` für immer, wodurch
|
||||||
|
`light_task` und `app_task` blockieren und der Watchdog den Chip zurücksetzen muss.
|
||||||
|
|||||||
@@ -136,8 +136,10 @@ void tls_os_timer_init(void)
|
|||||||
|
|
||||||
void vApplicationIdleHook( void )
|
void vApplicationIdleHook( void )
|
||||||
{
|
{
|
||||||
/* clear watch dog interrupt */
|
/* ninlight patch 4: do NOT feed the watchdog here. The idle task runs whenever
|
||||||
tls_watchdog_clr();
|
* no other task is ready, so clearing the watchdog from here would keep clearing
|
||||||
|
* it even while an app task is deadlocked (e.g. stuck holding nl_app_lock) and
|
||||||
|
* defeat the whole point of the watchdog. See sdk/PATCHES.md item 4. */
|
||||||
|
|
||||||
#if !defined(__CC_ARM)
|
#if !defined(__CC_ARM)
|
||||||
__asm volatile ("wfi");
|
__asm volatile ("wfi");
|
||||||
|
|||||||
+8
-4
@@ -22,6 +22,7 @@
|
|||||||
static tls_os_mutex_t *s_lock;
|
static tls_os_mutex_t *s_lock;
|
||||||
static nl_settings_t s_settings;
|
static nl_settings_t s_settings;
|
||||||
static volatile uint32_t s_light_beat;
|
static volatile uint32_t s_light_beat;
|
||||||
|
static volatile bool s_factory_reset_requested;
|
||||||
static u32 s_app_stack[1024];
|
static u32 s_app_stack[1024];
|
||||||
static u32 s_light_stack[512];
|
static u32 s_light_stack[512];
|
||||||
|
|
||||||
@@ -44,6 +45,8 @@ int nl_app_save_settings(void)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nl_app_request_factory_reset(void) { s_factory_reset_requested = true; }
|
||||||
|
|
||||||
void nl_app_factory_reset(void)
|
void nl_app_factory_reset(void)
|
||||||
{
|
{
|
||||||
printf("factory reset\r\n");
|
printf("factory reset\r\n");
|
||||||
@@ -130,10 +133,11 @@ static void app_task(void *arg)
|
|||||||
tls_os_time_delay(HZ / 10); /* 100 ms */
|
tls_os_time_delay(HZ / 10); /* 100 ms */
|
||||||
now = nl_app_now_ms();
|
now = nl_app_now_ms();
|
||||||
|
|
||||||
if (!counter_cleared && now - boot_ms >= NL_RESETCNT_CLEAR_MS) {
|
if (!counter_cleared && now - boot_ms >= NL_RESETCNT_CLEAR_MS)
|
||||||
nl_resetcnt_clear();
|
counter_cleared = nl_resetcnt_clear() == 0;
|
||||||
counter_cleared = true;
|
|
||||||
}
|
if (s_factory_reset_requested)
|
||||||
|
nl_app_factory_reset(); /* does not return */
|
||||||
|
|
||||||
nl_app_lock();
|
nl_app_lock();
|
||||||
due = nl_ctrl_persist_due(now, &pending);
|
due = nl_ctrl_persist_due(now, &pending);
|
||||||
|
|||||||
@@ -12,4 +12,11 @@ nl_settings_t *nl_app_settings(void);
|
|||||||
int nl_app_save_settings(void);
|
int nl_app_save_settings(void);
|
||||||
void nl_app_factory_reset(void);
|
void nl_app_factory_reset(void);
|
||||||
|
|
||||||
|
/* Only sets a volatile flag; the app task (which owns the flash stores) checks it
|
||||||
|
* once per loop iteration and calls nl_app_factory_reset() itself. This avoids the
|
||||||
|
* race with the app task's own nl_state_log_append()/nl_resetcnt_clear() calls.
|
||||||
|
* The console's `factory` command and, in a later plan, REST callers must use this
|
||||||
|
* function instead of calling nl_app_factory_reset() directly from another task. */
|
||||||
|
void nl_app_request_factory_reset(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -96,11 +96,16 @@ static void exec(const nl_cmd_t *c)
|
|||||||
nl_app_unlock();
|
nl_app_unlock();
|
||||||
return;
|
return;
|
||||||
case NL_CMD_FACTORY:
|
case NL_CMD_FACTORY:
|
||||||
nl_app_factory_reset();
|
nl_app_request_factory_reset();
|
||||||
return;
|
return;
|
||||||
case NL_CMD_REBOOT:
|
case NL_CMD_REBOOT:
|
||||||
tls_sys_reset();
|
tls_sys_reset();
|
||||||
return;
|
return;
|
||||||
|
case NL_CMD_WDTEST:
|
||||||
|
printf("wdtest: holding app lock forever\r\n");
|
||||||
|
nl_app_lock(); /* deliberately never released: light_task and app_task stall on it,
|
||||||
|
* so only the watchdog reset can recover the chip */
|
||||||
|
return;
|
||||||
case NL_CMD_HELP:
|
case NL_CMD_HELP:
|
||||||
printf("%s", nl_console_help);
|
printf("%s", nl_console_help);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
const char nl_console_help[] =
|
const char nl_console_help[] =
|
||||||
"on [ms] | off [ms] | bri <1-100> [ms] | rgb <r> <g> <b> [ms] | ct <K> [ms]\r\n"
|
"on [ms] | off [ms] | bri <1-100> [ms] | rgb <r> <g> <b> [ms] | ct <K> [ms]\r\n"
|
||||||
"pob last|on|off | fade <in> <out> | blink [n] | pulse on|off\r\n"
|
"pob last|on|off | fade <in> <out> | blink [n] | pulse on|off\r\n"
|
||||||
"state | factory | reboot | help\r\n";
|
"state | factory | reboot | wdtest | help\r\n";
|
||||||
|
|
||||||
static bool num(const char *s, int32_t lo, int32_t hi, int32_t *out)
|
static bool num(const char *s, int32_t lo, int32_t hi, int32_t *out)
|
||||||
{
|
{
|
||||||
@@ -104,7 +104,8 @@ nl_cmd_t nl_console_parse(const char *line)
|
|||||||
ok = n == 1 || (n == 2 && num(tok[1], 1, 10, &c.arg[0]));
|
ok = n == 1 || (n == 2 && num(tok[1], 1, 10, &c.arg[0]));
|
||||||
} else {
|
} else {
|
||||||
static const struct { const char *name; nl_cmd_type_t type; } SIMPLE[] = {
|
static const struct { const char *name; nl_cmd_type_t type; } SIMPLE[] = {
|
||||||
{"state", NL_CMD_STATE}, {"factory", NL_CMD_FACTORY}, {"reboot", NL_CMD_REBOOT}, {"help", NL_CMD_HELP}};
|
{"state", NL_CMD_STATE}, {"factory", NL_CMD_FACTORY}, {"reboot", NL_CMD_REBOOT},
|
||||||
|
{"help", NL_CMD_HELP}, {"wdtest", NL_CMD_WDTEST}};
|
||||||
ok = false;
|
ok = false;
|
||||||
for (size_t i = 0; i < sizeof SIMPLE / sizeof SIMPLE[0]; i++)
|
for (size_t i = 0; i < sizeof SIMPLE / sizeof SIMPLE[0]; i++)
|
||||||
if (!strcmp(tok[0], SIMPLE[i].name)) {
|
if (!strcmp(tok[0], SIMPLE[i].name)) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ typedef enum {
|
|||||||
NL_CMD_FACTORY,
|
NL_CMD_FACTORY,
|
||||||
NL_CMD_REBOOT,
|
NL_CMD_REBOOT,
|
||||||
NL_CMD_HELP,
|
NL_CMD_HELP,
|
||||||
|
NL_CMD_WDTEST,
|
||||||
NL_CMD_ERROR
|
NL_CMD_ERROR
|
||||||
} nl_cmd_type_t;
|
} nl_cmd_type_t;
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,9 @@ static void test_simple_commands(void)
|
|||||||
TEST_ASSERT_EQUAL(NL_CMD_FACTORY, nl_console_parse("factory").type);
|
TEST_ASSERT_EQUAL(NL_CMD_FACTORY, nl_console_parse("factory").type);
|
||||||
TEST_ASSERT_EQUAL(NL_CMD_REBOOT, nl_console_parse("reboot").type);
|
TEST_ASSERT_EQUAL(NL_CMD_REBOOT, nl_console_parse("reboot").type);
|
||||||
TEST_ASSERT_EQUAL(NL_CMD_HELP, nl_console_parse("help").type);
|
TEST_ASSERT_EQUAL(NL_CMD_HELP, nl_console_parse("help").type);
|
||||||
|
TEST_ASSERT_EQUAL(NL_CMD_WDTEST, nl_console_parse("wdtest").type);
|
||||||
TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("state now").type);
|
TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("state now").type);
|
||||||
|
TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("wdtest now").type);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
|
|||||||
Reference in New Issue
Block a user