Safe API Framework 0.1.0 build 384e6ae
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
rte_watchdog.c
Go to the documentation of this file.
1
28
30#include "rte/oal/log/rte_log.h"
33
34#include <stdbool.h>
35#include <string.h>
36
37#ifndef RTE_WATCHDOG_MAX_COUNT
41#define RTE_WATCHDOG_MAX_COUNT 8U
42#endif
43
67
71static uint8_t g_manager_initialized = 0U;
72
73/* ============================================================================
74 * Internal helpers
75 * ========================================================================== */
76
88static bool is_valid_handle(rte_watchdog_t watchdog)
89{
90 const rte_watchdog_s *slot = (const rte_watchdog_s *)watchdog;
91
92 return (slot != NULL) && (slot >= &g_watchdog_pool[0]) && (slot < &g_watchdog_pool[RTE_WATCHDOG_MAX_COUNT])
93 && (slot->in_use != 0U);
94}
95
102{
103 bool valid;
104
105 switch (action)
106 {
112 valid = true;
113 break;
114 default:
115 valid = false;
116 break;
117 }
118 return valid;
119}
120
128static bool is_supervising(const rte_watchdog_s *slot)
129{
130 return (slot->in_use != 0U) && (slot->active != 0U) && (slot->fired == 0U);
131}
132
144static uint32_t first_supervising_index(void)
145{
146 uint32_t found = (uint32_t)RTE_WATCHDOG_MAX_COUNT;
147 uint32_t i;
148
149 for (i = 0U; (i < (uint32_t)RTE_WATCHDOG_MAX_COUNT) && (found == (uint32_t)RTE_WATCHDOG_MAX_COUNT); i++)
150 {
152 {
153 found = i;
154 }
155 }
156 return found;
157}
158
159/* ============================================================================
160 * API: Watchdog Manager
161 * ========================================================================== */
162
164{
165 if (g_manager_initialized == 0U)
166 {
167 (void)memset(g_watchdog_pool, 0, sizeof(g_watchdog_pool));
169 rte_log_write(RTE_LOG_LEVEL_INFO, "watchdog", "manager initialized");
170 }
171 return RTE_STATUS_OK;
172}
173
175{
176 size_t i;
177
178 for (i = 0U; i < (size_t)RTE_WATCHDOG_MAX_COUNT; i++)
179 {
180 g_watchdog_pool[i].active = 0U;
181 }
183 rte_log_write(RTE_LOG_LEVEL_INFO, "watchdog", "manager shut down");
184 return RTE_STATUS_OK;
185}
186
187/* ============================================================================
188 * API: Core Watchdog Operations
189 * ========================================================================== */
190
191rte_status_t rte_watchdog_create(rte_watchdog_t *handle_out, const rte_watchdog_config_t *config)
192{
193 size_t i;
194
195 if ((handle_out == NULL) || (config == NULL) || (config->timeout_ms == 0U) || !is_valid_action(config->action))
196 {
198 }
200 && (config->custom_action == NULL))
201 {
202 /* FAILOVER requires a real handler just like CUSTOM does (see
203 * rte_watchdog_timeout_handler()'s own doc) - a FAILOVER watchdog
204 * with no handler wired up can only ever log, which is what
205 * ACTION_LOG is already for; requiring custom_action here catches
206 * that misconfiguration at create() time instead of silently
207 * degrading to a log line the first time it actually fires. */
209 }
210 if (g_manager_initialized == 0U)
211 {
213 }
214 {
215 /* REQ-LIFECYCLE-001 (ADR-026): a watchdog is a setup-only resource - refuse once the
216 * application's setup phase has been locked. */
218
219 if (lifecycle_status != RTE_STATUS_OK)
220 {
221 return lifecycle_status;
222 }
223 }
224
225 for (i = 0U; i < (size_t)RTE_WATCHDOG_MAX_COUNT; i++)
226 {
227 if (g_watchdog_pool[i].in_use == 0U)
228 {
229 (void)memset(&g_watchdog_pool[i], 0, sizeof(g_watchdog_pool[i]));
230 g_watchdog_pool[i].in_use = 1U;
231 g_watchdog_pool[i].config = *config;
232 *handle_out = (rte_watchdog_t)&g_watchdog_pool[i];
233 rte_log_write(RTE_LOG_LEVEL_INFO, config->name, "watchdog created");
234 return RTE_STATUS_OK;
235 }
236 }
238}
239
240rte_status_t rte_watchdog_start(rte_watchdog_t watchdog)
241{
242 rte_watchdog_s *slot = (rte_watchdog_s *)watchdog;
243 rte_timestamp_ms_t now_ms = 0U;
244 rte_status_t status;
245
246 if (!is_valid_handle(watchdog))
247 {
249 }
250 else
251 {
252 /* REQ-WATCHDOG-003: a failed clock read is returned as-is and the
253 * watchdog is not armed - arming against a deadline computed from a
254 * made-up "now" (0) would create a watchdog that either fires at
255 * once or never, neither of which the caller asked for. */
256 status = rte_timer_now(&now_ms);
257 if (status == RTE_STATUS_OK)
258 {
259 slot->active = 1U;
260 slot->fired = 0U;
261 slot->deadline_ms = now_ms + slot->config.timeout_ms;
262 slot->last_kick_ms = now_ms;
263 }
264 }
265 return status;
266}
267
268rte_status_t rte_watchdog_stop(rte_watchdog_t watchdog)
269{
270 rte_watchdog_s *slot = (rte_watchdog_s *)watchdog;
271
272 if (!is_valid_handle(watchdog))
273 {
275 }
276 slot->active = 0U;
277 return RTE_STATUS_OK;
278}
279
280rte_status_t rte_watchdog_kick(rte_watchdog_t watchdog)
281{
282 rte_watchdog_s *slot = (rte_watchdog_s *)watchdog;
283 rte_timestamp_ms_t now_ms = 0U;
284 rte_status_t status;
285
286 if (!is_valid_handle(watchdog))
287 {
289 }
290 else if ((slot->active == 0U) || (slot->fired != 0U))
291 {
292 /* Not running, or already fired and awaiting an explicit
293 * start() before it can be kicked again - this codebase's real
294 * status enum has no generic RTE_STATUS_ERROR (see
295 * rte_status.h); RTE_STATUS_INTERNAL_ERROR is the closest
296 * "kick while not in a kickable state" signal available. */
298 }
299 else
300 {
301 /* REQ-WATCHDOG-003: a failed clock read is returned as-is; the
302 * deadline stays where the last good read put it and the kick is
303 * not counted, so a clock fault cannot be mistaken for liveness. */
304 status = rte_timer_now(&now_ms);
305 if (status == RTE_STATUS_OK)
306 {
307 slot->deadline_ms = now_ms + slot->config.timeout_ms;
308 slot->last_kick_ms = now_ms;
309 slot->kicks++;
310 }
311 }
312 return status;
313}
314
315rte_status_t rte_watchdog_get_status(rte_watchdog_t watchdog, rte_watchdog_status_t *status_out)
316{
317 const rte_watchdog_s *slot = (const rte_watchdog_s *)watchdog;
318 rte_timestamp_ms_t now_ms = 0U;
319 rte_status_t status;
320
321 if (!is_valid_handle(watchdog) || (status_out == NULL))
322 {
324 }
325 else
326 {
327 /* REQ-WATCHDOG-003: no partial read-out - the two time fields need
328 * the clock, so a failed read returns its status and leaves
329 * *status_out exactly as the caller passed it. */
330 status = rte_timer_now(&now_ms);
331 if (status == RTE_STATUS_OK)
332 {
333 status_out->active = slot->active;
334 status_out->kicks = slot->kicks;
335 status_out->fires = slot->fires;
336 status_out->recoveries = slot->recoveries;
337 status_out->time_since_last_kick =
338 (rte_duration_ms_t)((now_ms >= slot->last_kick_ms) ? (now_ms - slot->last_kick_ms) : 0U);
339 status_out->time_until_fire =
340 (rte_duration_ms_t)((slot->deadline_ms > now_ms) ? (slot->deadline_ms - now_ms) : 0U);
341 }
342 }
343 return status;
344}
345
346rte_status_t rte_watchdog_destroy(rte_watchdog_t watchdog)
347{
348 rte_watchdog_s *slot = (rte_watchdog_s *)watchdog;
349
350 if (!is_valid_handle(watchdog))
351 {
353 }
354 slot->active = 0U;
355 slot->in_use = 0U;
356 return RTE_STATUS_OK;
357}
358
359/* ============================================================================
360 * Internal: Timeout Dispatch
361 * ========================================================================== */
362
363void rte_watchdog_timeout_handler(uint32_t watchdog_id)
364{
365 rte_watchdog_s *slot;
366
367 if (watchdog_id >= (uint32_t)RTE_WATCHDOG_MAX_COUNT)
368 {
369 return;
370 }
371 slot = &g_watchdog_pool[watchdog_id];
372 if ((slot->in_use == 0U) || (slot->active == 0U) || (slot->fired != 0U))
373 {
374 return;
375 }
376
377 slot->fired = 1U;
378 slot->fires++;
379
380 switch (slot->config.action)
381 {
383 rte_log_write(RTE_LOG_LEVEL_ERROR, slot->config.name, "watchdog timeout");
384 break;
386 slot->recoveries++;
387 rte_log_write(RTE_LOG_LEVEL_ERROR, slot->config.name, "watchdog timeout - entering SAFE state");
389 (int32_t)__LINE__, slot->config.name);
390 break; /* Not statically unreachable: rte_safestate_enter() has no
391 * [[noreturn]]/_Noreturn attribute, so the compiler cannot
392 * prove this dead - kept for switch-statement completeness. */
394 slot->recoveries++;
395 rte_log_write(RTE_LOG_LEVEL_ERROR, slot->config.name, "watchdog timeout - requesting reboot");
397 (int32_t)__LINE__, slot->config.name);
398 break; /* Same non-return note as SAFESTATE above. */
400 slot->recoveries++;
401 rte_log_write(RTE_LOG_LEVEL_ERROR, slot->config.name, "watchdog timeout - failover requested");
402 /* rte_watchdog_create() requires config.custom_action != NULL
403 * for this action (see its own doc) - dispatched exactly like
404 * ACTION_CUSTOM, just under a name that documents *why* the
405 * integrator registered this watchdog (loss of a redundant
406 * peer/channel) rather than *how* it reacts, which is
407 * identical to CUSTOM's own mechanism. This used to be a dead
408 * stub ("no generic failover primitive in this framework;
409 * caller must poll rte_watchdog_get_status()") - replaced
410 * once a real integrator (safeAPIRBC2oo2's dual-channel A/B
411 * link) needed exactly this reaction and found nothing to
412 * call. */
413 if (slot->config.custom_action != NULL)
414 {
415 slot->config.custom_action(slot->config.context);
416 }
417 break;
419 slot->recoveries++;
420 if (slot->config.custom_action != NULL)
421 {
422 slot->config.custom_action(slot->config.context);
423 }
424 break;
425 default: /* GCOVR_EXCL_LINE - see rationale below */
426 /* Defense-in-depth, not a reachable API path:
427 * rte_watchdog_create() rejects any config.action that fails
428 * is_valid_action() before a slot is ever populated, so a live
429 * slot's action is always one of the 5 named cases above.
430 * There is no public way to mutate an already-created slot's
431 * action, so this default cannot be reached by any caller -
432 * kept for switch-statement completeness against a future
433 * rte_watchdog_action_t value, not because it is exercised
434 * today. See docs/COVERAGE_REPORT.md for the accepted-
435 * exception rationale (this is one of the project's two
436 * documented gaps against the 100% branch coverage target). */
437 rte_log_write(RTE_LOG_LEVEL_ERROR, slot->config.name, "watchdog timeout - unrecognized action"); /* GCOVR_EXCL_LINE */
438 break; /* GCOVR_EXCL_LINE */
439 }
440}
441
442/* ============================================================================
443 * Timer Integration
444 * ========================================================================== */
445
447{
448 /* Polling design, not an ISR/hardware-timer callback: this framework's
449 * only portable time source is rte_timer_now() (a plain "read the
450 * clock" query, not a way to register a recurring OS-level interrupt
451 * across every target this framework claims to support - POSIX,
452 * QNX, bare-metal SysTick, etc.). Giving this module its own
453 * platform-specific interrupt setup would duplicate what
454 * rte::timer already exists to abstract, and would break the
455 * "OSAdapters are integrator-supplied" philosophy (ADR-005) for a
456 * module whose own header was never given an OSAdapter vtable. Instead:
457 * whatever already runs periodically in the integrating application
458 * (its own rte_timer periodic callback, or just its own main loop)
459 * is expected to call this function regularly - each call is an O(N)
460 * scan (N = RTE_WATCHDOG_MAX_COUNT, a small fixed pool) comparing
461 * each active watchdog's deadline against the current time. */
462 rte_timestamp_ms_t now_ms = 0U;
463
464 if (g_manager_initialized != 0U)
465 {
466 if (rte_timer_now(&now_ms) != RTE_STATUS_OK)
467 {
468 /* REQ-WATCHDOG-004: the clock this whole module measures with
469 * has failed. If any watchdog is supervising right now, its
470 * deadline can no longer be checked - and a watchdog that can
471 * never expire is exactly the silent failure watchdogs exist to
472 * prevent - so the failed time base is itself treated as a
473 * fired SAFE-level watchdog (logged under the first supervising
474 * slot's name). With nothing supervising there is nothing being
475 * silently defeated, and the tick stays a no-op. */
476 const uint32_t supervising = first_supervising_index();
477
478 if (supervising < (uint32_t)RTE_WATCHDOG_MAX_COUNT)
479 {
480 /* A NULL name (never validated by create()) only changes
481 * the log text; the decision above does not depend on it. */
482 const char *name = g_watchdog_pool[supervising].config.name;
483
484 if (name == NULL)
485 {
486 name = "(unnamed)";
487 }
488 rte_log_write(RTE_LOG_LEVEL_ERROR, name, "watchdog clock failed - entering SAFE state");
490 (int32_t)__LINE__, name);
491 }
492 }
493 else
494 {
495 uint32_t i;
496
497 for (i = 0U; i < (uint32_t)RTE_WATCHDOG_MAX_COUNT; i++)
498 {
499 if (is_supervising(&g_watchdog_pool[i]) && (now_ms >= g_watchdog_pool[i].deadline_ms))
500 {
502 }
503 }
504 }
505 }
506}
rte_status_t rte_lifecycle_check_setup_allowed(void)
Convenience check for a setup-only constructor: call this as one of the first checks in any function ...
void rte_log_write(rte_log_level_t level, const char *tag, const char *message)
Emits one log message. Non-blocking; never fails the caller's control flow even if the message is dro...
Definition rte_log.c:108
@ RTE_LOG_LEVEL_INFO
Definition rte_log.h:36
@ RTE_LOG_LEVEL_ERROR
Definition rte_log.h:38
#define RTE_SAFESTATE_REASON_CLOCK_FAILED
void rte_safestate_enter(rte_safestate_level_t level, rte_safestate_reason_t reason, const char *file, int32_t line, const char *message)
Enters a safe-state level: invokes the registered handler (if any), then, for RTE_SAFESTATE_LEVEL_SAF...
#define RTE_SAFESTATE_REASON_UNSPECIFIED
@ RTE_SAFESTATE_LEVEL_REBOOT
@ RTE_SAFESTATE_LEVEL_SAFE
rte_status_t
Common result/status codes.
Definition rte_status.h:27
@ RTE_STATUS_INTERNAL_ERROR
Definition rte_status.h:38
@ RTE_STATUS_OK
Definition rte_status.h:28
@ RTE_STATUS_INVALID_PARAM
Definition rte_status.h:29
@ RTE_STATUS_NOT_INITIALIZED
Definition rte_status.h:30
@ RTE_STATUS_RESOURCE_EXHAUSTED
Definition rte_status.h:33
rte_status_t rte_timer_now(rte_timestamp_ms_t *out_now_ms)
Returns the current monotonic time base used by all timers.
Definition rte_timer.c:139
uint32_t rte_duration_ms_t
Definition rte_types.h:27
uint64_t rte_timestamp_ms_t
Definition rte_types.h:30
rte_status_t rte_watchdog_start(rte_watchdog_t watchdog)
Start watchdog timer.
rte_status_t rte_watchdog_manager_shutdown(void)
Shutdown watchdog manager (call once at shutdown).
void rte_watchdog_timeout_handler(uint32_t watchdog_id)
Watchdog timeout handler (INTERNAL - called by framework).
rte_status_t rte_watchdog_kick(rte_watchdog_t watchdog)
Kick (pet) watchdog - prove liveness.
rte_status_t rte_watchdog_destroy(rte_watchdog_t watchdog)
Destroy watchdog.
rte_status_t rte_watchdog_manager_initialize(void)
Initialize watchdog manager (call once at startup).
rte_watchdog_action_t
Recovery action when watchdog fires.
rte_status_t rte_watchdog_get_status(rte_watchdog_t watchdog, rte_watchdog_status_t *status_out)
Get watchdog status.
rte_status_t rte_watchdog_stop(rte_watchdog_t watchdog)
Stop watchdog timer.
void rte_watchdog_timer_tick(void)
Poll all active watchdogs for expiry (call periodically).
rte_status_t rte_watchdog_create(rte_watchdog_t *handle_out, const rte_watchdog_config_t *config)
Create a watchdog.
@ RTE_WATCHDOG_ACTION_REBOOT
@ RTE_WATCHDOG_ACTION_SAFESTATE
@ RTE_WATCHDOG_ACTION_LOG
@ RTE_WATCHDOG_ACTION_FAILOVER
@ RTE_WATCHDOG_ACTION_CUSTOM
Process-wide application setup-phase lock (ADR-026).
OS Abstraction Layer - Logging/diagnostics service.
Safe-state transitions and checked assertions (ADR-004).
OS Abstraction Layer - Timer service.
static bool is_supervising(const rte_watchdog_s *slot)
Checks whether slot is currently supervising: allocated, started (not stopped) and not yet fired - i....
static bool is_valid_action(rte_watchdog_action_t action)
Checks whether action is one of the defined rte_watchdog_action_t enumerators.
#define RTE_WATCHDOG_MAX_COUNT
static uint32_t first_supervising_index(void)
Finds the first supervising slot (see is_supervising()).
static uint8_t g_manager_initialized
1 once rte_watchdog_manager_initialize() has been called.
static bool is_valid_handle(rte_watchdog_t watchdog)
Defensive check that handle actually points at one of this module's own live pool slots,...
static rte_watchdog_s g_watchdog_pool[RTE_WATCHDOG_MAX_COUNT]
Fixed-size static pool backing every rte_watchdog_t handle.
Watchdog mechanism for detecting system/task hang conditions.
Watchdog configuration.
rte_duration_ms_t timeout_ms
rte_watchdog_action_t action
void(*) custom_action(void *ctx)
Watchdog handle (opaque).
rte_timestamp_ms_t deadline_ms
rte_watchdog_config_t config
rte_timestamp_ms_t last_kick_ms
uint32_t recoveries
Watchdog health/status information.
rte_duration_ms_t time_since_last_kick
rte_duration_ms_t time_until_fire