1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
//! # mwdg-ffi — C FFI bindings for the mwdg micro-watchdog library
//!
//! This crate provides C-compatible bindings for the [`mwdg`] library,
//! enabling use from C/C++ code. Use the generated `include/mwdg.h` header
//! for the proper interface declarations.
//!
//! A user of the library must provide the following functions:
//!
//! ```c
//! extern uint32_t mwdg_get_time_milliseconds(void);
//! extern void mwdg_enter_critical(void);
//! extern void mwdg_exit_critical(void);
//! ```
use PanicInfo;
use UnsafeCell;
use Pin;
use ptr;
use ;
unsafe extern "C"
!
/// A single software watchdog node.
///
/// Each RTOS task owns one of these (typically as a static or stack variable
/// in a long-lived task). The struct is an intrusive linked-list node.
///
/// # C Usage
/// ```c
/// static struct mwdg_node my_wdg;
/// mwdg_add(&my_wdg, 200); // 200 ms timeout
/// ```
// `WatchdogNode` is `#[repr(C)]` with fields (u32, u32, u32, *mut Self,
// PhantomPinned). `PhantomPinned` is a ZST with alignment 1, so it does not
// affect the `repr(C)` layout. The first four fields are identical in type and
// order to `mwdg_node`, therefore the two types share the same size and
// alignment. Casting `*mut mwdg_node` ↔ `*mut WatchdogNode` is sound.
const _: = assert!;
const _: = assert!;
/// Cast a `*mut mwdg_node` to `*mut WatchdogNode`.
///
/// # Safety
/// The caller must ensure the pointer is either null or points to a valid
/// `mwdg_node`. The layout of `mwdg_node` and `WatchdogNode` is verified
/// at compile time to be identical.
unsafe
/// Create a `Pin<&mut WatchdogNode>` from a raw `*mut mwdg_node`.
///
/// Returns `None` if the pointer is null.
///
/// # Safety
/// The caller must ensure the pointer is valid, properly aligned, and
/// the pointed-to node will not be moved for the duration of the
/// returned reference's lifetime.
unsafe
/// Wrapper to allow `WatchdogRegistry` in a `static`.
///
/// # Safety
/// All access to the inner state is protected by the user-provided
/// critical section callbacks (enter/exit). `mwdg_init` must be called
/// once from a single context before any other function.
;
// SAFETY: All access is gated by user-provided critical section.
unsafe
static STATE: GlobalState = GlobalState;
/// Execute `f` inside the user-provided critical section.
/// Initialize the multi-watchdog subsystem.
///
/// Must be called exactly once before any other `mwdg_*` function,
/// from a single execution context (e.g., main or init task).
///
/// # Safety
/// - Must be called before any other `mwdg_*` function.
/// - Must not be called from multiple threads concurrently.
pub unsafe extern "C"
/// Register a software watchdog with the given timeout.
///
/// Initializes the watchdog fields and prepends it to the global list.
/// The watchdog's `last_touched_timestamp_ms` is set to the current time.
///
/// If the node is already in the list (detected by pointer comparison), the
/// call acts as a combined feed + timeout update — the node is **not** added
/// a second time.
///
/// # Parameters
/// - `wdg`: pointer to a caller-owned [`mwdg_node`]. Must remain valid
/// (not dropped/freed) for as long as it is registered.
/// - `timeout_ms`: the timeout interval in milliseconds.
///
/// # Safety
/// - `wdg` must be a valid, non-null pointer to a `mwdg_node`.
/// - `mwdg_init` must have been called.
pub unsafe extern "C"
/// Remove a previously registered watchdog from the global list.
///
/// If `wdg` is null or the node is not found in the list, the function
/// returns silently.
///
/// # Safety
/// - `wdg` must be either null or a valid pointer to an `mwdg_node`.
/// - `mwdg_init` must have been called.
pub unsafe extern "C"
/// Feed (touch) a watchdog, resetting its timestamp to the current time.
///
/// Must be called periodically by the owning task to signal liveness.
///
/// # Safety
/// - `wdg` must be a valid, non-null pointer to a registered `mwdg_node`.
/// - `mwdg_init` must have been called.
pub unsafe extern "C"
/// Assign a user-chosen identifier to a watchdog node.
///
/// The identifier is stored in the node and can be retrieved later via
/// [`mwdg_get_next_expired`] to determine which watchdog(s) have expired.
/// The library never modifies this field internally; it is purely for the
/// caller's use.
///
/// This function may be called at any time — before or after [`mwdg_add`].
///
/// # Parameters
/// - `wdg`: pointer to a caller-owned [`mwdg_node`].
/// - `id`: the identifier to assign.
///
/// # Safety
/// - `wdg` must be either null or a valid pointer to an `mwdg_node`.
/// - `mwdg_init` must have been called.
pub unsafe extern "C"
/// Check all registered watchdogs for expiration.
///
/// Iterates the linked list of registered watchdogs. For each one,
/// computes elapsed time using wrapping arithmetic (safe across `u32` overflow)
/// and compares against the timeout interval.
///
/// # Returns
/// - `0` if all watchdogs are healthy (fed within their timeout).
/// - `1` if any watchdog has expired.
///
/// # Safety
/// - `mwdg_init` must have been called.
/// - All registered `mwdg_node` pointers must still be valid.
pub unsafe extern "C"
/// Iterate over registered watchdogs and find the next expired one.
///
/// This function implements a cursor-based iterator over the linked list of
/// registered watchdogs. On each call it resumes from the position stored in
/// `*cursor` and scans forward for the next node whose elapsed time exceeds
/// its timeout interval.
///
/// # Precondition
/// [`mwdg_check`] must have been called **and returned `1`** before using
/// this function. Internally the iterator uses the timestamp snapshot
/// captured by `mwdg_check` (`expired_at_ms`) to evaluate each node, so
/// nodes are compared against the same point in time that triggered the
/// expiration — even if a frozen task calls [`mwdg_feed`] between
/// `mwdg_check` and this function. If `mwdg_check` has not yet detected
/// an expiration the function returns `0` immediately.
///
/// # Usage (C)
/// ```c
/// if (mwdg_check() != 0) {
/// struct mwdg_node *cursor = NULL;
/// uint32_t id;
/// while (mwdg_get_next_expired(&cursor, &id)) {
/// printf("expired watchdog id: %u\n", id);
/// }
/// }
/// ```
///
/// # Parameters
/// - `cursor`: pointer to a `*mut mwdg_node` that tracks iteration state.
/// The caller must initialise `*cursor` to `NULL` before the first call.
/// The function advances `*cursor` to the found node on success.
/// - `out_id`: pointer to a `u32` where the expired node's identifier
/// (set via [`mwdg_assign_id`]) will be written on success.
///
/// # Returns
/// - `1` if an expired node was found (`*out_id` is written, `*cursor` is
/// advanced).
/// - `0` when no more expired nodes remain (iteration complete), when
/// [`mwdg_check`] has not detected an expiration, or if `cursor` or
/// `out_id` is null.
///
/// # Note
/// Each call enters and exits the critical section independently. If the
/// list is modified between calls the iterator may skip or revisit nodes.
/// In typical RTOS usage the check loop runs from a single supervisory task,
/// so this is not a concern.
///
/// # Safety
/// - `cursor` must be either null or a valid pointer to a `*mut mwdg_node`.
/// - `out_id` must be either null or a valid pointer to a `u32`.
/// - `mwdg_init` must have been called.
/// - All registered `mwdg_node` pointers must still be valid.
pub unsafe extern "C"