ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_wdt_supervisor.c
Go to the documentation of this file.
1
16
17#include "ra8_wdt_supervisor.h"
18
19#include <stdint.h>
20#include <string.h>
21
22#include "ra8_attributes.h"
23#include "ra8_err.h"
24#include "ra8_wdt.h"
25#ifdef RA8_OFF_TARGET
27#else
28#include "tx_api.h"
29#endif
30
44
49typedef struct {
50 uint8_t state;
52 uint32_t deadline_ms;
53 uint32_t last_checkin_ms;
55
70
78
99{
100 return (uint32_t)tx_time_get() * (uint32_t)k_ra8_wdt_sup_default_tick_ms;
101}
102
122
142{
143 if (cfg == nullptr) {
144 return k_ra8_err_null_ptr;
145 }
146 if (cfg->stack == nullptr) {
147 return k_ra8_err_null_ptr;
148 }
149 if (cfg->stack_size_bytes < (uint32_t)k_ra8_wdt_sup_min_stack) {
151 }
152 if (cfg->refresh_period_ms == 0U) {
154 }
155 if (cfg->priority > (uint32_t)k_ra8_wdt_sup_max_priority) {
157 }
158 return k_ra8_ok;
159}
160
184RA8_INTERNAL static bool internal_is_overdue(uint32_t now, uint32_t last_checkin, uint32_t deadline)
185{
186 const uint32_t gap = now - last_checkin;
187 return gap > deadline;
188}
189
190/* GCOVR_EXCL_START -- host shim tx_thread_create does not invoke the entry callback */
210{
211 (void)arg;
212 /* NASA Rule 2: outer loop is the canonical "main control loop"
213 * exception. Body is finite. */
214 while (true) {
215 bool refreshed = false;
216 (void)ra8_wdt_supervisor_tick(&refreshed);
217 /* tx_thread_sleep takes ticks. Convert ms->ticks at the default
218 * 1 kHz rate; non-default rates can override the now hook. */
219 (void)tx_thread_sleep((ULONG)s_state.cfg.refresh_period_ms);
220 }
221}
222/* GCOVR_EXCL_STOP */
223
224/* =============================================================================
225 * Public API
226 * =============================================================================
227 */
228
230{
231 const ra8_err_t cfg_err = internal_validate_cfg(cfg);
232 if (cfg_err != k_ra8_ok) {
233 return cfg_err;
234 }
235 if (s_state.initialized) {
236 return k_ra8_err_busy;
237 }
238
239 (void)memset(&s_state.slots[0], 0, sizeof s_state.slots);
240 s_state.cfg = *cfg;
243 s_state.started = false;
244
245 const UINT mx = tx_mutex_create(&s_state.mutex, (CHAR*)(uintptr_t)"ra8_wdt_sup", TX_NO_INHERIT);
246 if (mx != TX_SUCCESS) {
247 return k_ra8_err_rtos_error; /* GCOVR_EXCL_LINE -- shim always returns TX_SUCCESS */
248 }
249
250 s_state.initialized = true;
251 return k_ra8_ok;
252}
253
255{
256 if (s_state.initialized) {
257 if (s_state.started) {
258 (void)tx_thread_terminate(&s_state.thread);
259 (void)tx_thread_delete(&s_state.thread);
260 }
261 (void)tx_mutex_delete(&s_state.mutex);
262 }
263 (void)memset(&s_state, 0, sizeof s_state);
264 return k_ra8_ok;
265}
266
288RA8_INTERNAL static void internal_fill_slot(uint8_t idx, const char* name, uint32_t deadline_ms)
289{
290 s_state.slots[idx].state = (uint8_t)k_ra8_wdt_sup_slot_used;
291 s_state.slots[idx].deadline_ms = deadline_ms;
292 s_state.slots[idx].last_checkin_ms = s_state.now();
293 /* Bounded copy with explicit NUL termination. */
294 (void)memset(s_state.slots[idx].name, 0, sizeof s_state.slots[idx].name);
295 const size_t cap = (size_t)k_ra8_wdt_sup_name_max - 1U;
296 for (size_t k = 0U; k < cap; ++k) {
297 const char ch = name[k];
298 if (ch == '\0') {
299 break;
300 }
301 s_state.slots[idx].name[k] = ch;
302 }
303}
304
306ra8_wdt_supervisor_register_thread(const char* name, uint32_t deadline_ms, uint8_t* out_handle)
307{
308 if (out_handle == nullptr) {
309 return k_ra8_err_null_ptr;
310 }
311 *out_handle = (uint8_t)k_ra8_wdt_sup_handle_invalid;
312
313 if (name == nullptr) {
314 return k_ra8_err_null_ptr;
315 }
316 if (deadline_ms == 0U) {
318 }
319 if (!s_state.initialized) {
321 }
322
323 const UINT mx = tx_mutex_get(&s_state.mutex, TX_WAIT_FOREVER);
324 if (mx != TX_SUCCESS) {
325 return k_ra8_err_rtos_error; /* GCOVR_EXCL_LINE -- shim always returns TX_SUCCESS */
326 }
327
329 for (uint8_t i = 0U; i < (uint8_t)k_ra8_wdt_sup_max_threads; ++i) {
330 if (s_state.slots[i].state == (uint8_t)k_ra8_wdt_sup_slot_free) {
331 internal_fill_slot(i, name, deadline_ms);
332 *out_handle = i;
333 result = k_ra8_ok;
334 break;
335 }
336 }
337
338 (void)tx_mutex_put(&s_state.mutex);
339 return result;
340}
341
343{
344 if (handle >= (uint8_t)k_ra8_wdt_sup_max_threads) {
346 }
347 if (!s_state.initialized) {
349 }
350
351 const UINT mx = tx_mutex_get(&s_state.mutex, TX_WAIT_FOREVER);
352 if (mx != TX_SUCCESS) {
353 return k_ra8_err_rtos_error; /* GCOVR_EXCL_LINE -- shim always returns TX_SUCCESS */
354 }
355
357 if (s_state.slots[handle].state == (uint8_t)k_ra8_wdt_sup_slot_used) {
358 s_state.slots[handle].last_checkin_ms = s_state.now();
359 result = k_ra8_ok;
360 }
361
362 (void)tx_mutex_put(&s_state.mutex);
363 return result;
364}
365
367{
368 if (!s_state.initialized) {
370 }
371 if (s_state.started) {
372 return k_ra8_err_busy;
373 }
374
375 const UINT tx = tx_thread_create(&s_state.thread,
376 (CHAR*)(uintptr_t)"ra8_wdt_sup",
378 0UL,
379 s_state.cfg.stack,
380 (ULONG)s_state.cfg.stack_size_bytes,
381 (UINT)s_state.cfg.priority,
382 (UINT)s_state.cfg.priority,
385 if (tx != TX_SUCCESS) {
386 return k_ra8_err_rtos_error; /* GCOVR_EXCL_LINE -- shim always returns TX_SUCCESS */
387 }
388
389 s_state.started = true;
390 return k_ra8_ok;
391}
392
393ra8_err_t ra8_wdt_supervisor_tick(bool* out_did_refresh)
394{
395 if (!s_state.initialized) {
396 if (out_did_refresh != nullptr) {
397 *out_did_refresh = false;
398 }
400 }
401
402 const UINT mx = tx_mutex_get(&s_state.mutex, TX_WAIT_FOREVER);
403 if (mx != TX_SUCCESS) {
404 /* GCOVR_EXCL_START -- shim always returns TX_SUCCESS */
405 if (out_did_refresh != nullptr) {
406 *out_did_refresh = false;
407 }
409 /* GCOVR_EXCL_STOP */
410 }
411
412 bool all_alive = true;
413 bool any_present = false;
414 const uint32_t now_ms = s_state.now();
415 for (uint8_t i = 0U; i < (uint8_t)k_ra8_wdt_sup_max_threads; ++i) {
416 if (s_state.slots[i].state != (uint8_t)k_ra8_wdt_sup_slot_used) {
417 continue;
418 }
419 any_present = true;
420 if (internal_is_overdue(now_ms,
421 s_state.slots[i].last_checkin_ms,
422 s_state.slots[i].deadline_ms)) {
423 all_alive = false;
424 break;
425 }
426 }
427
428 (void)tx_mutex_put(&s_state.mutex);
429
430 /* Only refresh if at least one thread is registered AND every
431 * registered thread is alive. With zero workers we deliberately do
432 * NOT kick the dog -- the supervisor would mask a degenerate config. */
433 const bool will_refresh = any_present && all_alive;
434 // mcdc-deactivated: ra8_wdt_supervisor_tick refresh dispatch gate; s_state.refresh is registered at init and remains non-NULL for the lifetime of the supervisor (it is the WDT kick callback, mandatory per the public-API contract). will_refresh varies independently and is fully covered, but the second condition is invariant on every reachable post-init path.
435 if (will_refresh && (s_state.refresh != nullptr)) {
436 s_state.refresh();
437 }
438 if (out_did_refresh != nullptr) {
439 *out_did_refresh = will_refresh;
440 }
441 return k_ra8_ok;
442}
443
445{
446 s_state.now = (now != nullptr) ? now : internal_default_now;
447 return k_ra8_ok;
448}
449
451{
452 s_state.refresh = (refresh != nullptr) ? refresh : internal_default_refresh;
453 return k_ra8_ok;
454}
455
457{
458 uint8_t count = 0U;
459 for (uint8_t i = 0U; i < (uint8_t)k_ra8_wdt_sup_max_threads; ++i) {
460 if (s_state.slots[i].state == (uint8_t)k_ra8_wdt_sup_slot_used) {
461 ++count;
462 }
463 }
464 return count;
465}
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Error Code Definitions for ra8-firmware.
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
Definition ra8_err.h:142
@ k_ra8_err_busy
Resource busy – blocking operation cannot proceed.
Definition ra8_err.h:195
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_err_not_initialized
Module not initialized – _init() not yet called successfully.
Definition ra8_err.h:235
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_null_ptr
Pointer was NULL where a valid pointer was required.
Definition ra8_err.h:478
@ k_ra8_err_rtos_error
Generic RTOS error (reserved for future use).
Definition ra8_err.h:357
@ k_ra8_err_not_found
Requested item not found (lookup / search missed).
Definition ra8_err.h:173
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
void * memset(void *dst, int value, size_t n)
Fill memory with a constant byte value.
static uint32_t s_state
Software Watchdog Timer (WDT) driver header.
void ra8_wdt_refresh_deferred(void)
Refresh the software WDT counter (WDT0).
Definition ra8_wdt.c:350
ThreadX shim for ra8_wdt_supervisor (host unit-test build only).
#define TX_NO_INHERIT
unsigned int UINT
ThreadX-compatible unsigned int (host stub).
#define TX_SUCCESS
#define tx_thread_delete
#define tx_thread_create
#define tx_thread_terminate
#define tx_time_get
#define tx_mutex_delete
#define tx_thread_sleep
#define TX_NO_TIME_SLICE
#define tx_mutex_create
#define tx_mutex_put
char CHAR
ThreadX-compatible CHAR (host stub).
#define TX_AUTO_START
#define TX_WAIT_FOREVER
unsigned long ULONG
ThreadX-compatible unsigned long (host stub).
#define tx_mutex_get
static void internal_fill_slot(uint8_t idx, const char *name, uint32_t deadline_ms)
Initialise an unused slot with the caller's parameters.
ra8_err_t ra8_wdt_supervisor_deinit(void)
Reset the supervisor to its uninitialized state (test helper).
static void internal_default_refresh(void)
Default WDT-refresh hook.
ra8_err_t ra8_wdt_supervisor_tick(bool *out_did_refresh)
Run one supervisor tick synchronously (test / introspection hook).
static ra8_err_t internal_validate_cfg(const ra8_wdt_sup_cfg_t *cfg)
Validate the public configuration block.
ra8_err_t ra8_wdt_supervisor_set_now_hook(ra8_wdt_sup_now_fn_t now)
Override the monotonic-time hook (test injection point).
ra8_err_t ra8_wdt_supervisor_register_thread(const char *name, uint32_t deadline_ms, uint8_t *out_handle)
Register a worker thread with the supervisor.
ra8_wdt_sup_internal_t
Internal numeric constants used by the implementation.
@ k_ra8_wdt_sup_mutex_id
'WDSS' marker for the mutex.
@ k_ra8_wdt_sup_thread_id
'WDST' marker for the thread.
@ k_ra8_wdt_sup_max_priority
Highest legal ThreadX priority.
@ k_ra8_wdt_sup_slot_used
Slot tag: registered.
@ k_ra8_wdt_sup_min_stack
Minimum acceptable stack size.
@ k_ra8_wdt_sup_slot_free
Slot tag: empty.
@ k_ra8_wdt_sup_default_tick_ms
Default tick: 1 kHz kernel.
static bool internal_is_overdue(uint32_t now, uint32_t last_checkin, uint32_t deadline)
Compute whether now - last_checkin exceeds deadline.
ra8_err_t ra8_wdt_supervisor_checkin(uint8_t handle)
Record a thread check-in, resetting its deadline window.
ra8_err_t ra8_wdt_supervisor_init(const ra8_wdt_sup_cfg_t *cfg)
Initialise the supervisor registry and runtime hooks.
uint8_t ra8_wdt_supervisor_thread_count(void)
Read the number of currently-registered worker threads.
static uint32_t internal_default_now(void)
Default monotonic-time hook – scales tx_time_get to ms.
ra8_err_t ra8_wdt_supervisor_set_refresh_hook(ra8_wdt_sup_refresh_fn_t refresh)
Override the WDT-refresh hook (test injection point).
static void internal_thread_entry(ULONG arg)
The supervisor thread's entry point.
ra8_err_t ra8_wdt_supervisor_start(void)
Spawn the supervisor thread.
ThreadX-aware watchdog supervisor (per-thread check-in registry).
uint32_t(* ra8_wdt_sup_now_fn_t)(void)
Hook used by tests to inject monotonic-time readings.
@ k_ra8_wdt_sup_handle_invalid
Invalid / unregistered handle.
void(* ra8_wdt_sup_refresh_fn_t)(void)
Hook used to call into ra8_wdt_refresh_deferred.
@ k_ra8_wdt_sup_max_threads
Max simultaneously registered threads.
@ k_ra8_wdt_sup_name_max
Max bytes (incl.
Opaque mutex stand-in for the host build.
Opaque thread stand-in for the host build.
One-shot supervisor configuration block.
uint32_t stack_size_bytes
Size of stack in bytes (>= 512).
uint32_t refresh_period_ms
Tick period of the supervisor's loop.
uint32_t priority
ThreadX priority for the supervisor thread.
void * stack
Caller-owned byte region for the supervisor's stack.
One row of the supervisor registry.
char name[k_ra8_wdt_sup_name_max]
Diagnostic name (NUL-terminated).
uint32_t deadline_ms
Max gap between check-ins.
uint32_t last_checkin_ms
Monotonic time of last check-in.
uint8_t state
slot_free or slot_used.
Module state – entirely static.
TX_THREAD thread
Supervisor thread control block.
bool started
True once the thread is spawned.
ra8_wdt_sup_slot_t slots[k_ra8_wdt_sup_max_threads]
Registry.
ra8_wdt_sup_now_fn_t now
Monotonic-time hook.
ra8_wdt_sup_refresh_fn_t refresh
WDT-refresh hook.
TX_MUTEX mutex
Guards slots.
ra8_wdt_sup_cfg_t cfg
Cached configuration.
bool initialized
True after successful init.