Skip to main content

nros_platform_cffi/
lib.rs

1//! Rust mirror of the canonical C ABI in `<nros/platform.h>`.
2//!
3//! Every nros binary links exactly one platform implementation; the
4//! free `extern "C"` symbols declared below are resolved at link time.
5//! There is no runtime registration step. To inject a platform from
6//! C, drop a translation unit defining the symbols (or link against
7//! a static library that does).
8//!
9//! Rust platform crates implement the [`nros_platform_api`] traits as
10//! before; a sibling `-cffi` shim crate re-exports the Rust impl as
11//! `#[unsafe(no_mangle)] extern "C"` symbols matching the names in the
12//! header. That separation lets the same Rust impl serve both
13//! trait-driven Rust callers and C-ABI consumers.
14//!
15//! # Usage
16//!
17//! - C implementor: implement the functions in `<nros/platform.h>` and
18//!   link against the nros binary.
19//! - Rust consumer: enable the `platform-cffi` feature on
20//!   `nros-platform`; [`CffiPlatform`] dispatches every trait call to
21//!   the linked C symbols.
22//!
23//! # Companion
24//!
25//! Platform sits one tier below RMW. The Phase 117 RMW vtable
26//! (`<nros/rmw_vtable.h>`) is a runtime-pluggable struct; the
27//! platform layer is link-time-bound free symbols. Different choice
28//! because RMW backends genuinely swap per session (zenoh vs cyclonedds
29//! vs xrce in the same binary at test time) while a platform is fixed
30//! for the life of a binary.
31
32#![no_std]
33#![allow(clippy::not_unsafe_ptr_arg_deref)]
34
35use core::ffi::c_void;
36
37// Anchor symbol so downstream crates can chain `#[used]` statics to
38// keep this rlib in the link graph. Without an explicit reference,
39// rustc elides the rlib (it's mostly extern decls + a trait impl that
40// gets inlined into callers), and the build.rs `cargo:rustc-link-lib=`
41// directive for `libnros_platform_posix.a` is dropped along with it,
42// leaving every `nros_platform_*` symbol unresolved at the binary
43// link step.
44#[cfg(feature = "posix-c-port")]
45#[doc(hidden)]
46#[inline(never)]
47pub extern "C" fn _nros_force_link_cffi() {}
48
49// ============================================================================
50// Canonical ABI declarations
51// ----------------------------------------------------------------------------
52// Hand-written mirror of `include/nros/platform.h`. Field order, names,
53// and types track the header byte-for-byte. Updates land in the header
54// first, then here.
55// ============================================================================
56
57// RFC-0054 (phase-299 W2): the extern declarations are GENERATED from the
58// platform headers (src/generated.rs, scripts/gen-abi-bindings.sh) — the
59// C headers are the SSoT. The nros_platform_export_*! macros below stay
60// hand-written: they EMIT the definitions (the port side).
61pub mod generated;
62pub use generated::*;
63
64// issue 0710 — the `LogSink` that speaks this ABI belongs with the ABI, so
65// "does this binary need `nros_platform_log_write`?" is a DEPENDENCY question
66// rather than a Cargo feature. See the module docs.
67pub mod log;
68
69/// Board-supplied writer fn type. ONLY meaningful on platforms whose
70/// `nros_platform_log_write` impl is itself a thin dispatcher to a
71/// board-registered fn (FreeRTOS, ThreadX, bare-metal). On platforms
72/// with a native logger (POSIX, Zephyr, ESP-IDF, NuttX), the symbol
73/// is absent and the board should not link against it.
74pub type NrosPlatformLogWriterFn = unsafe extern "C" fn(
75    severity: u8,
76    name_ptr: *const u8,
77    name_len: usize,
78    msg_ptr: *const u8,
79    msg_len: usize,
80);
81
82/// Board-supplied flush fn type. Pass `None` to
83/// [`nros_platform_register_log_writer`] when the writer is fully
84/// synchronous.
85pub type NrosPlatformLogFlushFn = unsafe extern "C" fn();
86
87// ============================================================================
88// Phase 121.6.rust-mirror — extended canonical ABI
89// ----------------------------------------------------------------------------
90// Mirrors `<nros/platform_timer.h>` + `<nros/platform_net.h>`. Declarations
91// only — definitions are supplied by whichever provider the binary links
92// (a per-RTOS C port via 121.6.<port>-c, or a future macro-expanded Rust
93// impl). Anyone NOT pulling these via `CffiPlatform`'s extended-surface
94// trait impls (those land in a follow-up commit) gets dead-code-stripped
95// extern refs at link time — no symbol resolution required.
96// ============================================================================
97
98// ============================================================================
99// Return codes (mirrors header)
100// ============================================================================
101
102// phase-364 W1 — RE-EXPORTED, not mirrored.
103//
104// This block used to be three hand-written constants and a hand-written `i32`
105// alias, none of which had a single user, and all of which could drift from the
106// header silently. They existed because the header wrote its values as
107// `((nros_platform_ret_t) 0)` and bindgen cannot evaluate a cast into a
108// constant, so the generated bindings carried the typedef and none of the
109// codes. The header now writes bare literals, so the generator emits all six
110// and there is one definition of each.
111//
112// `NrosPlatformRet` follows the header's own narrowing to `int8_t` (the width
113// every one of these functions already returned). The constants keep bindgen's
114// `i32` type — a code is compared against a widened return, not stored — so a
115// caller writes `ret as i32 == NROS_PLATFORM_RET_UNSUPPORTED`.
116pub use crate::generated::{
117    NROS_PLATFORM_RET_ERROR, NROS_PLATFORM_RET_INVALID, NROS_PLATFORM_RET_NOMEM,
118    NROS_PLATFORM_RET_OK, NROS_PLATFORM_RET_TIMEOUT, NROS_PLATFORM_RET_UNSUPPORTED,
119};
120
121/// The C `nros_platform_ret_t`, from the generated bindings.
122pub type NrosPlatformRet = crate::generated::nros_platform_ret_t;
123
124// ============================================================================
125// CffiPlatform — trait impls dispatching to the linked C symbols
126// ============================================================================
127
128/// Zero-sized type implementing the platform traits via the canonical
129/// `nros_platform_*` C symbols.
130///
131/// The crate that pulls `CffiPlatform` into a final binary is
132/// responsible for ensuring the symbols are supplied at link time
133/// (either by a C translation unit or a Rust `-cffi` shim crate).
134pub struct CffiPlatform;
135
136impl nros_platform_api::PlatformClock for CffiPlatform {
137    #[inline]
138    fn clock_ns() -> u64 {
139        unsafe { nros_platform_clock_ns() }
140    }
141
142    #[inline]
143    fn clock_resolution_ns() -> u64 {
144        unsafe { nros_platform_clock_resolution_ns() }
145    }
146
147    /// issue 0758 — forwards to the C symbol. NOT defaulted here: a C port
148    /// that supplies the symbol must be able to answer, and one that does
149    /// not returns `0` from its own definition. Taking the trait default
150    /// instead would silently ignore a C implementation that exists.
151    #[inline]
152    fn epoch_us() -> u64 {
153        unsafe { nros_platform_epoch_us() }
154    }
155}
156
157impl nros_platform_api::PlatformAlloc for CffiPlatform {
158    #[inline]
159    fn alloc(size: usize) -> *mut c_void {
160        unsafe { nros_platform_alloc(size) }
161    }
162
163    #[inline]
164    fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
165        unsafe { nros_platform_realloc(ptr, size) }
166    }
167
168    #[inline]
169    fn dealloc(ptr: *mut c_void) {
170        unsafe { nros_platform_dealloc(ptr) }
171    }
172
173    #[inline]
174    fn heap_used_bytes() -> usize {
175        unsafe { nros_platform_heap_used_bytes() }
176    }
177
178    #[inline]
179    fn heap_total_bytes() -> usize {
180        unsafe { nros_platform_heap_total_bytes() }
181    }
182}
183
184impl nros_platform_api::PlatformSleep for CffiPlatform {
185    #[inline]
186    fn sleep_us(us: usize) {
187        unsafe { nros_platform_sleep_us(us) }
188    }
189
190    #[inline]
191    fn sleep_ms(ms: usize) {
192        unsafe { nros_platform_sleep_ms(ms) }
193    }
194
195    #[inline]
196    fn sleep_s(s: usize) {
197        unsafe { nros_platform_sleep_s(s) }
198    }
199}
200
201impl nros_platform_api::PlatformYield for CffiPlatform {
202    #[inline]
203    fn yield_now() {
204        unsafe { nros_platform_yield_now() }
205    }
206}
207
208// Phase 110.D — `PlatformScheduler` is satisfied by the existing yield
209// symbol; per-thread scheduling controls land when a C consumer needs
210// hard-RT preemption.
211impl nros_platform_api::PlatformScheduler for CffiPlatform {
212    #[inline]
213    fn yield_now() {
214        unsafe { nros_platform_yield_now() }
215    }
216}
217
218// Phase 110.E.b — `PlatformTimer` dispatches to the
219// `nros_platform_timer_*` C ABI declared above. Backed by
220// `nros-platform-posix/src/timer.c` on POSIX (POSIX `timer_create` +
221// `SIGEV_THREAD` trampoline); each RTOS port supplies its own
222// `timer.c` mirroring the canonical signatures.
223//
224// `TimerHandle` is a `*mut c_void` newtype so the trait's
225// `Send + Sync` bound holds. Safety: the C layer owns the heap
226// record behind the pointer; Rust just shuttles the opaque handle
227// between `create_*` and `destroy` / `cancel`.
228#[derive(Debug)]
229pub struct CffiTimerHandle(*mut c_void);
230
231// SAFETY: the underlying `*mut c_void` is an opaque platform-owned
232// handle (POSIX `timer_t` wrapped in a heap record, FreeRTOS
233// `TimerHandle_t`, etc.). The Rust side never dereferences it; the
234// only operations are forwarding it back to `destroy` / `cancel`.
235// Send + Sync are required by the trait so the executor can stash
236// the handle across thread boundaries.
237unsafe impl Send for CffiTimerHandle {}
238unsafe impl Sync for CffiTimerHandle {}
239
240impl nros_platform_api::PlatformTimer for CffiPlatform {
241    type TimerHandle = CffiTimerHandle;
242
243    fn create_periodic(
244        period_us: u32,
245        callback: extern "C" fn(*mut c_void),
246        user_data: *mut c_void,
247    ) -> Result<Self::TimerHandle, nros_platform_api::TimerError> {
248        // `extern "C" fn` coerces structurally to `unsafe extern "C"
249        // fn` — both have the same ABI; Rust just demands the unsafe
250        // version at the C call site.
251        let cb: unsafe extern "C" fn(*mut c_void) = callback;
252        let raw = unsafe { nros_platform_timer_create_periodic(period_us, Some(cb), user_data) };
253        if raw.is_null() {
254            // The C layer returns NULL for both "unsupported on this
255            // platform" (default stub) and "syscall failed" (POSIX
256            // EINVAL / kernel error). The runtime treats both the
257            // same way (drop back to the polled-clock fallback), so
258            // surface `KernelError` to differentiate from the
259            // trait-default `Unsupported` that fires when the C
260            // symbol isn't linked at all.
261            return Err(nros_platform_api::TimerError::KernelError);
262        }
263        Ok(CffiTimerHandle(raw))
264    }
265
266    fn create_oneshot(
267        timeout_us: u32,
268        callback: extern "C" fn(*mut c_void),
269        user_data: *mut c_void,
270    ) -> Result<Self::TimerHandle, nros_platform_api::TimerError> {
271        let cb: unsafe extern "C" fn(*mut c_void) = callback;
272        let raw = unsafe { nros_platform_timer_create_oneshot(timeout_us, Some(cb), user_data) };
273        if raw.is_null() {
274            return Err(nros_platform_api::TimerError::KernelError);
275        }
276        Ok(CffiTimerHandle(raw))
277    }
278
279    fn destroy(handle: Self::TimerHandle) {
280        unsafe { nros_platform_timer_destroy(handle.0) }
281    }
282
283    fn cancel(handle: &mut Self::TimerHandle) -> bool {
284        let rc = unsafe { nros_platform_timer_cancel(handle.0) };
285        // `1` = cancellation prevented the callback from firing;
286        // `0` / `-1` = already fired (or error — treated as "not
287        // cancelled in time" by the caller).
288        rc == 1
289    }
290}
291
292impl nros_platform_api::PlatformRandom for CffiPlatform {
293    #[inline]
294    fn random_u8() -> u8 {
295        unsafe { nros_platform_random_u8() }
296    }
297
298    #[inline]
299    fn random_u16() -> u16 {
300        unsafe { nros_platform_random_u16() }
301    }
302
303    #[inline]
304    fn random_u32() -> u32 {
305        unsafe { nros_platform_random_u32() }
306    }
307
308    #[inline]
309    fn random_u64() -> u64 {
310        unsafe { nros_platform_random_u64() }
311    }
312
313    #[inline]
314    fn random_fill(buf: *mut c_void, len: usize) {
315        unsafe { nros_platform_random_fill(buf, len) }
316    }
317}
318
319impl nros_platform_api::PlatformTime for CffiPlatform {
320    #[inline]
321    fn time_now_ns() -> u64 {
322        unsafe { nros_platform_time_now_ns() }
323    }
324}
325
326impl nros_platform_api::PlatformThreading for CffiPlatform {
327    fn task_init(
328        task: *mut c_void,
329        attr: *mut c_void,
330        entry: Option<unsafe extern "C" fn(*mut c_void) -> *mut c_void>,
331        arg: *mut c_void,
332    ) -> i8 {
333        unsafe { nros_platform_task_init(task, attr, entry, arg) }
334    }
335    fn task_join(task: *mut c_void) -> i8 {
336        unsafe { nros_platform_task_join(task) }
337    }
338    fn task_detach(task: *mut c_void) -> i8 {
339        unsafe { nros_platform_task_detach(task) }
340    }
341    fn task_cancel(task: *mut c_void) -> i8 {
342        unsafe { nros_platform_task_cancel(task) }
343    }
344    fn task_exit() {
345        unsafe { nros_platform_task_exit() }
346    }
347    fn task_free(task: *mut *mut c_void) {
348        unsafe { nros_platform_task_free(task) }
349    }
350    fn mutex_init(m: *mut c_void) -> i8 {
351        unsafe { nros_platform_mutex_init(m) }
352    }
353    fn mutex_drop(m: *mut c_void) -> i8 {
354        unsafe { nros_platform_mutex_drop(m) }
355    }
356    fn mutex_lock(m: *mut c_void) -> i8 {
357        unsafe { nros_platform_mutex_lock(m) }
358    }
359    fn mutex_try_lock(m: *mut c_void) -> i8 {
360        unsafe { nros_platform_mutex_try_lock(m) }
361    }
362    fn mutex_unlock(m: *mut c_void) -> i8 {
363        unsafe { nros_platform_mutex_unlock(m) }
364    }
365    fn mutex_rec_init(m: *mut c_void) -> i8 {
366        unsafe { nros_platform_mutex_rec_init(m) }
367    }
368    fn mutex_rec_drop(m: *mut c_void) -> i8 {
369        unsafe { nros_platform_mutex_rec_drop(m) }
370    }
371    fn mutex_rec_lock(m: *mut c_void) -> i8 {
372        unsafe { nros_platform_mutex_rec_lock(m) }
373    }
374    fn mutex_rec_try_lock(m: *mut c_void) -> i8 {
375        unsafe { nros_platform_mutex_rec_try_lock(m) }
376    }
377    fn mutex_rec_unlock(m: *mut c_void) -> i8 {
378        unsafe { nros_platform_mutex_rec_unlock(m) }
379    }
380    fn condvar_init(cv: *mut c_void) -> i8 {
381        unsafe { nros_platform_condvar_init(cv) }
382    }
383    fn condvar_drop(cv: *mut c_void) -> i8 {
384        unsafe { nros_platform_condvar_drop(cv) }
385    }
386    fn condvar_signal(cv: *mut c_void) -> i8 {
387        unsafe { nros_platform_condvar_signal(cv) }
388    }
389    fn condvar_signal_all(cv: *mut c_void) -> i8 {
390        unsafe { nros_platform_condvar_signal_all(cv) }
391    }
392    fn condvar_signal_from_isr(cv: *mut c_void) -> i8 {
393        unsafe { nros_platform_condvar_signal_from_isr(cv) }
394    }
395    fn condvar_wait_until(cv: *mut c_void, m: *mut c_void, abstime: u64) -> i8 {
396        unsafe { nros_platform_condvar_wait_until(cv, m, abstime) }
397    }
398    fn wake_init(w: *mut c_void) -> i8 {
399        unsafe { nros_platform_wake_init(w) }
400    }
401    fn wake_drop(w: *mut c_void) -> i8 {
402        unsafe { nros_platform_wake_drop(w) }
403    }
404    fn wake_wait_ms(w: *mut c_void, timeout_ms: u32) -> i8 {
405        unsafe { nros_platform_wake_wait_ms(w, timeout_ms) }
406    }
407    fn wake_signal(w: *mut c_void) -> i8 {
408        unsafe { nros_platform_wake_signal(w) }
409    }
410    fn wake_signal_from_isr(w: *mut c_void) -> i8 {
411        unsafe { nros_platform_wake_signal_from_isr(w) }
412    }
413    // `wake_park_until_us` / `wake_park_granularity_us` are deliberately NOT
414    // forwarded. `platform.h` marks them OPTIONAL — Zephyr, ThreadX, FreeRTOS
415    // and POSIX (so NuttX) define them (phase-436 W7, C1, C2); the bare-metal
416    // ports do not (C3) — and this impl is linked into EVERY C port, so forwarding
417    // made an optional symbol a required one: `undefined reference to
418    // nros_platform_wake_park_until_us` on POSIX, FreeRTOS, NuttX and every
419    // other C port (`check-staticlib-symbols`, `check-c`). The trait defaults
420    // (-1 "cannot park", 0 "no granularity") are the honest answer for a shim
421    // that cannot know whether the port it is linked beside defines them. The
422    // ports that do are reached directly by their board entries
423    // (`nros::port_park::install_port_park`), never through this impl.
424    fn wake_storage_size() -> usize {
425        unsafe { nros_platform_wake_storage_size() }
426    }
427    fn wake_storage_align() -> usize {
428        unsafe { nros_platform_wake_storage_align() }
429    }
430    fn task_storage_size() -> usize {
431        unsafe { nros_platform_task_storage_size() }
432    }
433    fn task_storage_align() -> usize {
434        unsafe { nros_platform_task_storage_align() }
435    }
436}
437
438// ============================================================================
439// Phase 121.3.deprecate-rust-migrate — extended-surface trait impls
440// ----------------------------------------------------------------------------
441// CffiPlatform dispatches PlatformTcp / PlatformUdp / PlatformUdpMulticast /
442// PlatformSocketHelpers / PlatformNetworkPoll trait calls through the
443// `unsafe extern "C"` declarations above. Whichever provider supplies the
444// matching symbol set (a per-RTOS Rust crate with `cffi-export` on, or a
445// hand-written C port) backs the dispatch transparently.
446// ============================================================================
447
448impl nros_platform_api::PlatformTcp for CffiPlatform {
449    fn create_endpoint(ep: *mut c_void, address: *const u8, port: *const u8) -> i8 {
450        unsafe { nros_platform_tcp_create_endpoint(ep, address, port) }
451    }
452    fn free_endpoint(ep: *mut c_void) {
453        unsafe { nros_platform_tcp_free_endpoint(ep) }
454    }
455    fn open(sock: *mut c_void, endpoint: *const c_void, timeout_ms: u32) -> i8 {
456        unsafe { nros_platform_tcp_open(sock, endpoint, timeout_ms) }
457    }
458    fn listen(sock: *mut c_void, endpoint: *const c_void) -> i8 {
459        unsafe { nros_platform_tcp_listen(sock, endpoint) }
460    }
461    fn close(sock: *mut c_void) {
462        unsafe { nros_platform_tcp_close(sock) }
463    }
464    fn read(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
465        unsafe { nros_platform_tcp_read(sock, buf, len) }
466    }
467    fn read_exact(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
468        unsafe { nros_platform_tcp_read_exact(sock, buf, len) }
469    }
470    fn send(sock: *const c_void, buf: *const u8, len: usize) -> usize {
471        unsafe { nros_platform_tcp_send(sock, buf, len) }
472    }
473    fn set_rx_notify(
474        sock: *const c_void,
475        notify: Option<unsafe extern "C" fn(*mut c_void)>,
476        ctx: *mut c_void,
477    ) -> i8 {
478        unsafe { nros_platform_tcp_set_rx_notify(sock, notify, ctx) }
479    }
480}
481
482impl nros_platform_api::PlatformUdp for CffiPlatform {
483    fn create_endpoint(ep: *mut c_void, address: *const u8, port: *const u8) -> i8 {
484        unsafe { nros_platform_udp_create_endpoint(ep, address, port) }
485    }
486    fn free_endpoint(ep: *mut c_void) {
487        unsafe { nros_platform_udp_free_endpoint(ep) }
488    }
489    fn open(sock: *mut c_void, endpoint: *const c_void, timeout_ms: u32) -> i8 {
490        unsafe { nros_platform_udp_open(sock, endpoint, timeout_ms) }
491    }
492    fn listen(sock: *mut c_void, endpoint: *const c_void, timeout_ms: u32) -> i8 {
493        unsafe { nros_platform_udp_listen(sock, endpoint, timeout_ms) }
494    }
495    fn close(sock: *mut c_void) {
496        unsafe { nros_platform_udp_close(sock) }
497    }
498    fn read(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
499        unsafe { nros_platform_udp_read(sock, buf, len) }
500    }
501    fn read_exact(sock: *const c_void, buf: *mut u8, len: usize) -> usize {
502        unsafe { nros_platform_udp_read_exact(sock, buf, len) }
503    }
504    fn send(sock: *const c_void, buf: *const u8, len: usize, endpoint: *const c_void) -> usize {
505        unsafe { nros_platform_udp_send(sock, buf, len, endpoint) }
506    }
507    fn set_recv_timeout(sock: *const c_void, timeout_ms: u32) {
508        unsafe { nros_platform_udp_set_recv_timeout(sock, timeout_ms) }
509    }
510    fn readiness_handle(sock: *const c_void) -> i32 {
511        unsafe { nros_platform_udp_readiness_handle(sock) }
512    }
513    fn set_rx_notify(
514        sock: *const c_void,
515        notify: Option<unsafe extern "C" fn(*mut c_void)>,
516        ctx: *mut c_void,
517    ) -> i8 {
518        unsafe { nros_platform_udp_set_rx_notify(sock, notify, ctx) }
519    }
520}
521
522impl nros_platform_api::PlatformUdpMulticast for CffiPlatform {
523    fn mcast_open(
524        sock: *mut c_void,
525        endpoint: *const c_void,
526        lep: *mut c_void,
527        timeout_ms: u32,
528        iface: *const u8,
529    ) -> i8 {
530        unsafe { nros_platform_udp_mcast_open(sock, endpoint, lep, timeout_ms, iface) }
531    }
532    fn mcast_listen(
533        sock: *mut c_void,
534        endpoint: *const c_void,
535        timeout_ms: u32,
536        iface: *const u8,
537        join: *const u8,
538    ) -> i8 {
539        unsafe { nros_platform_udp_mcast_listen(sock, endpoint, timeout_ms, iface, join) }
540    }
541    fn mcast_close(
542        sockrecv: *mut c_void,
543        socksend: *mut c_void,
544        rep: *const c_void,
545        lep: *const c_void,
546    ) {
547        unsafe { nros_platform_udp_mcast_close(sockrecv, socksend, rep, lep) }
548    }
549    fn mcast_read(
550        sock: *const c_void,
551        buf: *mut u8,
552        len: usize,
553        lep: *const c_void,
554        addr: *mut c_void,
555    ) -> usize {
556        unsafe { nros_platform_udp_mcast_read(sock, buf, len, lep, addr) }
557    }
558    fn mcast_read_exact(
559        sock: *const c_void,
560        buf: *mut u8,
561        len: usize,
562        lep: *const c_void,
563        addr: *mut c_void,
564    ) -> usize {
565        unsafe { nros_platform_udp_mcast_read_exact(sock, buf, len, lep, addr) }
566    }
567    fn mcast_send(
568        sock: *const c_void,
569        buf: *const u8,
570        len: usize,
571        endpoint: *const c_void,
572    ) -> usize {
573        unsafe { nros_platform_udp_mcast_send(sock, buf, len, endpoint) }
574    }
575}
576
577impl nros_platform_api::PlatformSocketHelpers for CffiPlatform {
578    fn set_non_blocking(sock: *const c_void) -> i8 {
579        unsafe { nros_platform_socket_set_non_blocking(sock) }
580    }
581    fn accept(sock_in: *const c_void, sock_out: *mut c_void) -> i8 {
582        unsafe { nros_platform_socket_accept(sock_in, sock_out) }
583    }
584    fn close(sock: *mut c_void) {
585        unsafe { nros_platform_socket_close(sock) }
586    }
587    fn wait_event(peers: *mut c_void, mutex: *mut c_void) -> i8 {
588        unsafe { nros_platform_socket_wait_event(peers, mutex) }
589    }
590}
591
592impl nros_platform_api::PlatformNetworkPoll for CffiPlatform {
593    fn network_poll() {
594        unsafe { nros_platform_network_poll() }
595    }
596}
597
598impl nros_platform_api::PlatformCriticalSection for CffiPlatform {
599    fn acquire() -> u32 {
600        unsafe { nros_platform_critical_section_acquire() }
601    }
602    fn release(token: u32) {
603        unsafe { nros_platform_critical_section_release(token) }
604    }
605}
606
607impl nros_platform_api::PlatformLog for CffiPlatform {
608    fn write(severity: u8, name: &[u8], message: &[u8]) {
609        // SAFETY: extern decl matches the C ABI byte-for-byte; the
610        // pointer/length pairs come from `&[u8]` references that
611        // outlive the call.
612        unsafe {
613            nros_platform_log_write(
614                severity,
615                name.as_ptr(),
616                name.len(),
617                message.as_ptr(),
618                message.len(),
619            );
620        }
621    }
622
623    fn flush() {
624        // SAFETY: no args, no preconditions.
625        unsafe { nros_platform_log_flush() };
626    }
627}
628
629// ============================================================================
630// Phase 121.2 — export_*! macros
631// ----------------------------------------------------------------------------
632// Each macro emits the `#[unsafe(no_mangle)] extern "C"` definitions for one
633// capability group. The macro callee must implement the matching
634// `nros_platform_api::Platform*` trait; the trait bound is checked at the
635// macro-expansion site, so a missing impl produces a clear compile error in
636// the caller crate.
637//
638// Naming the symbols exactly matches `<nros/platform.h>`. Add a new ABI
639// symbol in three coordinated places, all inside this crate:
640//   1. declare it in `include/nros/platform.h`,
641//   2. declare it in the `unsafe extern "C" { … }` block above,
642//   3. emit it from the appropriate `export_*!` macro below.
643// ============================================================================
644
645/// Emit `nros_platform_clock_{ms,us}` delegating to
646/// `<$ty as PlatformClock>`.
647#[macro_export]
648macro_rules! nros_platform_export_clock {
649    ($ty:ty) => {
650        #[unsafe(no_mangle)]
651        pub extern "C" fn nros_platform_clock_ns() -> u64 {
652            <$ty as ::nros_platform_api::PlatformClock>::clock_ns()
653        }
654        #[unsafe(no_mangle)]
655        pub extern "C" fn nros_platform_clock_resolution_ns() -> u64 {
656            <$ty as ::nros_platform_api::PlatformClock>::clock_resolution_ns()
657        }
658        /// issue 0758 — a Rust port that does not override
659        /// `PlatformClock::epoch_us` emits this and answers `0`, which is
660        /// the honest reading for a platform with no wall clock.
661        #[unsafe(no_mangle)]
662        pub extern "C" fn nros_platform_epoch_us() -> u64 {
663            <$ty as ::nros_platform_api::PlatformClock>::epoch_us()
664        }
665    };
666}
667
668/// Emit `nros_platform_panic` delegating to `<$ty as PlatformPanic>`
669/// (phase-366 / RFC-0077).
670///
671/// NOT in `nros_platform_export!` deliberately. The fatal path is the IMAGE's,
672/// and a Rust port that exports it unconditionally would be the same defect
673/// this API exists to remove: a library claiming the image's ending. A port
674/// calls this only when it is the one supplying the behaviour.
675#[macro_export]
676macro_rules! nros_platform_export_panic {
677    ($ty:ty) => {
678        #[unsafe(no_mangle)]
679        pub extern "C" fn nros_platform_panic(msg: *const u8, len: usize) -> ! {
680            <$ty as ::nros_platform_api::PlatformPanic>::panic(msg, len)
681        }
682    };
683}
684
685/// Emit `nros_platform_{alloc,realloc,dealloc}` delegating to
686/// `<$ty as PlatformAlloc>`.
687#[macro_export]
688macro_rules! nros_platform_export_alloc {
689    ($ty:ty) => {
690        #[unsafe(no_mangle)]
691        pub extern "C" fn nros_platform_alloc(size: usize) -> *mut ::core::ffi::c_void {
692            <$ty as ::nros_platform_api::PlatformAlloc>::alloc(size)
693        }
694        #[unsafe(no_mangle)]
695        pub extern "C" fn nros_platform_realloc(
696            ptr: *mut ::core::ffi::c_void,
697            size: usize,
698        ) -> *mut ::core::ffi::c_void {
699            <$ty as ::nros_platform_api::PlatformAlloc>::realloc(ptr, size)
700        }
701        #[unsafe(no_mangle)]
702        pub extern "C" fn nros_platform_dealloc(ptr: *mut ::core::ffi::c_void) {
703            <$ty as ::nros_platform_api::PlatformAlloc>::dealloc(ptr)
704        }
705        #[unsafe(no_mangle)]
706        pub extern "C" fn nros_platform_heap_used_bytes() -> usize {
707            <$ty as ::nros_platform_api::PlatformAlloc>::heap_used_bytes()
708        }
709        #[unsafe(no_mangle)]
710        pub extern "C" fn nros_platform_heap_total_bytes() -> usize {
711            <$ty as ::nros_platform_api::PlatformAlloc>::heap_total_bytes()
712        }
713    };
714}
715
716/// Emit `nros_platform_sleep_{us,ms,s}` delegating to
717/// `<$ty as PlatformSleep>`.
718#[macro_export]
719macro_rules! nros_platform_export_sleep {
720    ($ty:ty) => {
721        #[unsafe(no_mangle)]
722        pub extern "C" fn nros_platform_sleep_us(us: usize) {
723            <$ty as ::nros_platform_api::PlatformSleep>::sleep_us(us)
724        }
725        #[unsafe(no_mangle)]
726        pub extern "C" fn nros_platform_sleep_ms(ms: usize) {
727            <$ty as ::nros_platform_api::PlatformSleep>::sleep_ms(ms)
728        }
729        #[unsafe(no_mangle)]
730        pub extern "C" fn nros_platform_sleep_s(s: usize) {
731            <$ty as ::nros_platform_api::PlatformSleep>::sleep_s(s)
732        }
733    };
734}
735
736/// Emit `nros_platform_yield_now` delegating to
737/// `<$ty as PlatformYield>`.
738#[macro_export]
739macro_rules! nros_platform_export_yield {
740    ($ty:ty) => {
741        #[unsafe(no_mangle)]
742        pub extern "C" fn nros_platform_yield_now() {
743            <$ty as ::nros_platform_api::PlatformYield>::yield_now()
744        }
745    };
746}
747
748/// Emit `nros_platform_random_*` delegating to `<$ty as PlatformRandom>`.
749#[macro_export]
750macro_rules! nros_platform_export_random {
751    ($ty:ty) => {
752        #[unsafe(no_mangle)]
753        pub extern "C" fn nros_platform_random_u8() -> u8 {
754            <$ty as ::nros_platform_api::PlatformRandom>::random_u8()
755        }
756        #[unsafe(no_mangle)]
757        pub extern "C" fn nros_platform_random_u16() -> u16 {
758            <$ty as ::nros_platform_api::PlatformRandom>::random_u16()
759        }
760        #[unsafe(no_mangle)]
761        pub extern "C" fn nros_platform_random_u32() -> u32 {
762            <$ty as ::nros_platform_api::PlatformRandom>::random_u32()
763        }
764        #[unsafe(no_mangle)]
765        pub extern "C" fn nros_platform_random_u64() -> u64 {
766            <$ty as ::nros_platform_api::PlatformRandom>::random_u64()
767        }
768        #[unsafe(no_mangle)]
769        pub extern "C" fn nros_platform_random_fill(buf: *mut ::core::ffi::c_void, len: usize) {
770            <$ty as ::nros_platform_api::PlatformRandom>::random_fill(buf, len)
771        }
772    };
773}
774
775/// Emit `nros_platform_time_*` delegating to `<$ty as PlatformTime>`.
776#[macro_export]
777macro_rules! nros_platform_export_time {
778    ($ty:ty) => {
779        #[unsafe(no_mangle)]
780        pub extern "C" fn nros_platform_time_now_ns() -> u64 {
781            <$ty as ::nros_platform_api::PlatformTime>::time_now_ns()
782        }
783    };
784}
785
786/// Emit `nros_platform_task_*`, `nros_platform_mutex_*`,
787/// `nros_platform_mutex_rec_*`, and `nros_platform_condvar_*` delegating
788/// to `<$ty as PlatformThreading>`. Skip this macro on platforms without
789/// kernel threads.
790#[macro_export]
791macro_rules! nros_platform_export_threading {
792    ($ty:ty) => {
793        #[unsafe(no_mangle)]
794        pub extern "C" fn nros_platform_task_init(
795            task: *mut ::core::ffi::c_void,
796            attr: *mut ::core::ffi::c_void,
797            entry: ::core::option::Option<
798                unsafe extern "C" fn(*mut ::core::ffi::c_void) -> *mut ::core::ffi::c_void,
799            >,
800            arg: *mut ::core::ffi::c_void,
801        ) -> i8 {
802            <$ty as ::nros_platform_api::PlatformThreading>::task_init(task, attr, entry, arg)
803        }
804        #[unsafe(no_mangle)]
805        pub extern "C" fn nros_platform_task_stack_unused_bytes() -> usize {
806            <$ty as ::nros_platform_api::PlatformThreading>::task_stack_unused_bytes()
807        }
808        #[unsafe(no_mangle)]
809        pub extern "C" fn nros_platform_task_stack_unused_bytes_upto(limit: usize) -> usize {
810            <$ty as ::nros_platform_api::PlatformThreading>::task_stack_unused_bytes_upto(limit)
811        }
812        #[unsafe(no_mangle)]
813        pub extern "C" fn nros_platform_task_join(task: *mut ::core::ffi::c_void) -> i8 {
814            <$ty as ::nros_platform_api::PlatformThreading>::task_join(task)
815        }
816        #[unsafe(no_mangle)]
817        pub extern "C" fn nros_platform_task_detach(task: *mut ::core::ffi::c_void) -> i8 {
818            <$ty as ::nros_platform_api::PlatformThreading>::task_detach(task)
819        }
820        #[unsafe(no_mangle)]
821        pub extern "C" fn nros_platform_task_cancel(task: *mut ::core::ffi::c_void) -> i8 {
822            <$ty as ::nros_platform_api::PlatformThreading>::task_cancel(task)
823        }
824        #[unsafe(no_mangle)]
825        pub extern "C" fn nros_platform_task_exit() {
826            <$ty as ::nros_platform_api::PlatformThreading>::task_exit()
827        }
828        #[unsafe(no_mangle)]
829        pub extern "C" fn nros_platform_task_free(task: *mut *mut ::core::ffi::c_void) {
830            <$ty as ::nros_platform_api::PlatformThreading>::task_free(task)
831        }
832        #[unsafe(no_mangle)]
833        pub extern "C" fn nros_platform_mutex_init(m: *mut ::core::ffi::c_void) -> i8 {
834            <$ty as ::nros_platform_api::PlatformThreading>::mutex_init(m)
835        }
836        #[unsafe(no_mangle)]
837        pub extern "C" fn nros_platform_mutex_drop(m: *mut ::core::ffi::c_void) -> i8 {
838            <$ty as ::nros_platform_api::PlatformThreading>::mutex_drop(m)
839        }
840        #[unsafe(no_mangle)]
841        pub extern "C" fn nros_platform_mutex_lock(m: *mut ::core::ffi::c_void) -> i8 {
842            <$ty as ::nros_platform_api::PlatformThreading>::mutex_lock(m)
843        }
844        #[unsafe(no_mangle)]
845        pub extern "C" fn nros_platform_mutex_try_lock(m: *mut ::core::ffi::c_void) -> i8 {
846            <$ty as ::nros_platform_api::PlatformThreading>::mutex_try_lock(m)
847        }
848        #[unsafe(no_mangle)]
849        pub extern "C" fn nros_platform_mutex_unlock(m: *mut ::core::ffi::c_void) -> i8 {
850            <$ty as ::nros_platform_api::PlatformThreading>::mutex_unlock(m)
851        }
852        #[unsafe(no_mangle)]
853        pub extern "C" fn nros_platform_mutex_rec_init(m: *mut ::core::ffi::c_void) -> i8 {
854            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_init(m)
855        }
856        #[unsafe(no_mangle)]
857        pub extern "C" fn nros_platform_mutex_rec_drop(m: *mut ::core::ffi::c_void) -> i8 {
858            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_drop(m)
859        }
860        #[unsafe(no_mangle)]
861        pub extern "C" fn nros_platform_mutex_rec_lock(m: *mut ::core::ffi::c_void) -> i8 {
862            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_lock(m)
863        }
864        #[unsafe(no_mangle)]
865        pub extern "C" fn nros_platform_mutex_rec_try_lock(m: *mut ::core::ffi::c_void) -> i8 {
866            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_try_lock(m)
867        }
868        #[unsafe(no_mangle)]
869        pub extern "C" fn nros_platform_mutex_rec_unlock(m: *mut ::core::ffi::c_void) -> i8 {
870            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_unlock(m)
871        }
872        #[unsafe(no_mangle)]
873        pub extern "C" fn nros_platform_condvar_init(cv: *mut ::core::ffi::c_void) -> i8 {
874            <$ty as ::nros_platform_api::PlatformThreading>::condvar_init(cv)
875        }
876        #[unsafe(no_mangle)]
877        pub extern "C" fn nros_platform_condvar_drop(cv: *mut ::core::ffi::c_void) -> i8 {
878            <$ty as ::nros_platform_api::PlatformThreading>::condvar_drop(cv)
879        }
880        #[unsafe(no_mangle)]
881        pub extern "C" fn nros_platform_condvar_signal(cv: *mut ::core::ffi::c_void) -> i8 {
882            <$ty as ::nros_platform_api::PlatformThreading>::condvar_signal(cv)
883        }
884        #[unsafe(no_mangle)]
885        pub extern "C" fn nros_platform_condvar_signal_all(cv: *mut ::core::ffi::c_void) -> i8 {
886            <$ty as ::nros_platform_api::PlatformThreading>::condvar_signal_all(cv)
887        }
888        #[unsafe(no_mangle)]
889        pub extern "C" fn nros_platform_condvar_signal_from_isr(
890            cv: *mut ::core::ffi::c_void,
891        ) -> i8 {
892            <$ty as ::nros_platform_api::PlatformThreading>::condvar_signal_from_isr(cv)
893        }
894        #[unsafe(no_mangle)]
895        pub extern "C" fn nros_platform_condvar_wait_until(
896            cv: *mut ::core::ffi::c_void,
897            m: *mut ::core::ffi::c_void,
898            abstime: u64,
899        ) -> i8 {
900            <$ty as ::nros_platform_api::PlatformThreading>::condvar_wait_until(cv, m, abstime)
901        }
902        // Phase 130 — wake primitive (binary semaphore shape).
903        #[unsafe(no_mangle)]
904        pub extern "C" fn nros_platform_wake_init(w: *mut ::core::ffi::c_void) -> i8 {
905            <$ty as ::nros_platform_api::PlatformThreading>::wake_init(w)
906        }
907        #[unsafe(no_mangle)]
908        pub extern "C" fn nros_platform_wake_drop(w: *mut ::core::ffi::c_void) -> i8 {
909            <$ty as ::nros_platform_api::PlatformThreading>::wake_drop(w)
910        }
911        #[unsafe(no_mangle)]
912        pub extern "C" fn nros_platform_wake_wait_ms(
913            w: *mut ::core::ffi::c_void,
914            timeout_ms: u32,
915        ) -> i8 {
916            <$ty as ::nros_platform_api::PlatformThreading>::wake_wait_ms(w, timeout_ms)
917        }
918        #[unsafe(no_mangle)]
919        pub extern "C" fn nros_platform_wake_signal(w: *mut ::core::ffi::c_void) -> i8 {
920            <$ty as ::nros_platform_api::PlatformThreading>::wake_signal(w)
921        }
922        #[unsafe(no_mangle)]
923        pub extern "C" fn nros_platform_wake_signal_from_isr(w: *mut ::core::ffi::c_void) -> i8 {
924            <$ty as ::nros_platform_api::PlatformThreading>::wake_signal_from_isr(w)
925        }
926        #[unsafe(no_mangle)]
927        pub extern "C" fn nros_platform_wake_park_until_us(
928            w: *mut ::core::ffi::c_void,
929            deadline_us: u64,
930        ) -> i8 {
931            <$ty as ::nros_platform_api::PlatformThreading>::wake_park_until_us(w, deadline_us)
932        }
933        #[unsafe(no_mangle)]
934        pub extern "C" fn nros_platform_wake_park_granularity_us() -> u64 {
935            <$ty as ::nros_platform_api::PlatformThreading>::wake_park_granularity_us()
936        }
937        #[unsafe(no_mangle)]
938        pub extern "C" fn nros_platform_wake_park_handles_us(
939            w: *mut ::core::ffi::c_void,
940            handles: *const i32,
941            n: usize,
942            deadline_us: u64,
943        ) -> i8 {
944            <$ty as ::nros_platform_api::PlatformThreading>::wake_park_handles_us(
945                w,
946                handles,
947                n,
948                deadline_us,
949            )
950        }
951        #[unsafe(no_mangle)]
952        pub extern "C" fn nros_platform_task_storage_size() -> usize {
953            <$ty as ::nros_platform_api::PlatformThreading>::task_storage_size()
954        }
955        #[unsafe(no_mangle)]
956        pub extern "C" fn nros_platform_task_storage_align() -> usize {
957            <$ty as ::nros_platform_api::PlatformThreading>::task_storage_align()
958        }
959        #[unsafe(no_mangle)]
960        pub extern "C" fn nros_platform_wake_storage_size() -> usize {
961            <$ty as ::nros_platform_api::PlatformThreading>::wake_storage_size()
962        }
963        #[unsafe(no_mangle)]
964        pub extern "C" fn nros_platform_wake_storage_align() -> usize {
965            <$ty as ::nros_platform_api::PlatformThreading>::wake_storage_align()
966        }
967        /// phase-364 W3 — the attribute defaults are pure data, so this is
968        /// emitted concretely rather than dispatched through the trait: every
969        /// port's answer is identical, and a trait method would only invite a
970        /// port to get it wrong.
971        #[unsafe(no_mangle)]
972        pub extern "C" fn nros_platform_task_attr_init(
973            attr: *mut $crate::generated::nros_platform_task_attr_t,
974        ) {
975            if attr.is_null() {
976                return;
977            }
978            // SAFETY: non-null, and the caller owns writable storage of this
979            // type by the ABI contract.
980            unsafe {
981                (*attr).name = ::core::ptr::null();
982                (*attr).stack_bytes = 0;
983                (*attr).stack_mem = ::core::ptr::null_mut();
984                (*attr).priority = i32::MIN;
985                (*attr).core = -1;
986                (*attr).flags = 0;
987            }
988        }
989        // NOTE: `nros_platform_task_storage_{size,align}` are emitted above,
990        // beside their `wake_storage_*` siblings — not here with the lock
991        // family. A second copy landed here and made every port that invokes
992        // this macro fail with E0428; both spans pointed at the same macro
993        // call, which is what a macro emitting a symbol twice looks like.
994        #[unsafe(no_mangle)]
995        pub extern "C" fn nros_platform_mutex_storage_size() -> usize {
996            <$ty as ::nros_platform_api::PlatformThreading>::mutex_storage_size()
997        }
998        #[unsafe(no_mangle)]
999        pub extern "C" fn nros_platform_mutex_storage_align() -> usize {
1000            <$ty as ::nros_platform_api::PlatformThreading>::mutex_storage_align()
1001        }
1002        #[unsafe(no_mangle)]
1003        pub extern "C" fn nros_platform_mutex_rec_storage_size() -> usize {
1004            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_storage_size()
1005        }
1006        #[unsafe(no_mangle)]
1007        pub extern "C" fn nros_platform_mutex_rec_storage_align() -> usize {
1008            <$ty as ::nros_platform_api::PlatformThreading>::mutex_rec_storage_align()
1009        }
1010        #[unsafe(no_mangle)]
1011        pub extern "C" fn nros_platform_condvar_storage_size() -> usize {
1012            <$ty as ::nros_platform_api::PlatformThreading>::condvar_storage_size()
1013        }
1014        #[unsafe(no_mangle)]
1015        pub extern "C" fn nros_platform_condvar_storage_align() -> usize {
1016            <$ty as ::nros_platform_api::PlatformThreading>::condvar_storage_align()
1017        }
1018    };
1019}
1020
1021/// Phase 121.9 — emit the two `nros_platform_critical_section_*`
1022/// symbols by delegating to the caller's `PlatformCriticalSection`
1023/// impl.
1024#[macro_export]
1025macro_rules! nros_platform_export_critical_section {
1026    ($ty:ty) => {
1027        #[unsafe(no_mangle)]
1028        pub extern "C" fn nros_platform_critical_section_acquire() -> u32 {
1029            <$ty as ::nros_platform_api::PlatformCriticalSection>::acquire()
1030        }
1031        #[unsafe(no_mangle)]
1032        pub extern "C" fn nros_platform_critical_section_release(token: u32) {
1033            <$ty as ::nros_platform_api::PlatformCriticalSection>::release(token)
1034        }
1035    };
1036}
1037
1038/// Phase 88.11 — emit `nros_platform_log_write` + `nros_platform_log_flush`
1039/// from a `PlatformLog`-implementing ZST. Use this on bare-metal /
1040/// custom platforms (mps2-an385, stm32f4, esp32-baremetal, …) that
1041/// don't ship a separate C implementation file. The implementor's
1042/// `write` receives the rendered body + logger name as `&[u8]` slices.
1043#[macro_export]
1044macro_rules! nros_platform_export_log {
1045    ($ty:ty) => {
1046        #[unsafe(no_mangle)]
1047        pub extern "C" fn nros_platform_log_write(
1048            severity: u8,
1049            name_ptr: *const u8,
1050            name_len: usize,
1051            msg_ptr: *const u8,
1052            msg_len: usize,
1053        ) {
1054            // SAFETY: caller passes valid `&[u8]` slices that outlive
1055            // the call; empty-name case (name_ptr=null, name_len=0)
1056            // collapses to an empty slice.
1057            let name: &[u8] = if name_ptr.is_null() || name_len == 0 {
1058                &[]
1059            } else {
1060                unsafe { ::core::slice::from_raw_parts(name_ptr, name_len) }
1061            };
1062            let msg: &[u8] = if msg_ptr.is_null() || msg_len == 0 {
1063                &[]
1064            } else {
1065                unsafe { ::core::slice::from_raw_parts(msg_ptr, msg_len) }
1066            };
1067            <$ty as ::nros_platform_api::PlatformLog>::write(severity, name, msg);
1068        }
1069        #[unsafe(no_mangle)]
1070        pub extern "C" fn nros_platform_log_flush() {
1071            <$ty as ::nros_platform_api::PlatformLog>::flush()
1072        }
1073        /// Phase 88.16.H — ABI-mirror parity. Direct-impl
1074        /// platforms (`mps2-an385`, `stm32f4`, …) route every
1075        /// record through `PlatformLog::write`, so the runtime
1076        /// swap slot is meaningless to them. The header mirror
1077        /// nonetheless declares `nros_platform_register_log_writer`,
1078        /// so the macro emits a no-op stub to satisfy the
1079        /// ABI-mirror check. Fn-ptr-slot platforms (FreeRTOS /
1080        /// ThreadX / NuttX) don't call this macro — their C body
1081        /// ships the real strong definition.
1082        #[unsafe(no_mangle)]
1083        pub extern "C" fn nros_platform_register_log_writer(
1084            _writer: ::core::option::Option<
1085                unsafe extern "C" fn(
1086                    severity: u8,
1087                    name_ptr: *const u8,
1088                    name_len: usize,
1089                    msg_ptr: *const u8,
1090                    msg_len: usize,
1091                ),
1092            >,
1093            _flusher: ::core::option::Option<unsafe extern "C" fn()>,
1094        ) {
1095        }
1096    };
1097}
1098
1099/// Convenience: emit every `nros_platform_*` symbol declared in
1100/// `<nros/platform.h>` by delegating to the corresponding
1101/// `nros_platform_api::Platform*` trait method on `$ty`. The caller must
1102/// implement every trait covered by the capability macros.
1103///
1104/// Logging (`nros_platform_export_log!`) is NOT part of this convenience
1105/// macro: bare-metal platforms typically need to supply a writer
1106/// (`hprintln!` / `defmt::info!`) that requires extra deps not all
1107/// platforms link against. Call `nros_platform_export_log!` separately
1108/// after the platform crate implements `PlatformLog`.
1109#[macro_export]
1110macro_rules! nros_platform_export {
1111    ($ty:ty) => {
1112        $crate::nros_platform_export_clock!($ty);
1113        $crate::nros_platform_export_alloc!($ty);
1114        $crate::nros_platform_export_sleep!($ty);
1115        $crate::nros_platform_export_yield!($ty);
1116        $crate::nros_platform_export_random!($ty);
1117        $crate::nros_platform_export_time!($ty);
1118        $crate::nros_platform_export_threading!($ty);
1119        $crate::nros_platform_export_critical_section!($ty);
1120    };
1121}
1122
1123// ============================================================================
1124// Phase 121.6.macros — extended-surface export macros
1125// ----------------------------------------------------------------------------
1126// `nros_platform_export_net!` mirrors `<nros/platform_net.h>` 1:1; trait
1127// signatures match the C ABI byte-for-byte. `nros_platform_export_timer!`
1128// adapts the Rust `PlatformTimer` trait's `Result<TimerHandle, _>` to the
1129// C ABI's `*mut c_void` (NULL on error). The caller's `TimerHandle`
1130// associated type must be `*mut c_void` — enforced at macro-expansion
1131// time via a `where` clause on the emitted dispatch functions.
1132// ============================================================================
1133
1134/// Emit every `nros_platform_timer_*` symbol declared in
1135/// `<nros/platform_timer.h>` by delegating to the corresponding
1136/// `PlatformTimer` trait method on `$ty`.
1137///
1138/// **Constraint:** the implementor's `TimerHandle` associated type
1139/// must be `*mut core::ffi::c_void` so the macro can pass the handle
1140/// through the C ABI unchanged. Implementations using kernel-specific
1141/// handle types should wrap them in a `*mut c_void` (typically by
1142/// `Box::into_raw` + a thin newtype) before exporting.
1143#[macro_export]
1144macro_rules! nros_platform_export_timer {
1145    ($ty:ty) => {
1146        // Compile-time guard: handle must be pointer-sized so the
1147        // transmute below is sound. PlatformTimer requires Send +
1148        // Sync + 'static, which `*mut c_void` itself fails — so
1149        // callers wrap their handle in a `#[repr(transparent)]`
1150        // newtype that implements those (PosixTimerHandle, etc.).
1151        // We round-trip through transmute at the C ABI boundary.
1152        const _: () = {
1153            if ::core::mem::size_of::<<$ty as ::nros_platform_api::PlatformTimer>::TimerHandle>()
1154                != ::core::mem::size_of::<*mut ::core::ffi::c_void>()
1155            {
1156                panic!(
1157                    "nros_platform_export_timer! requires \
1158                     PlatformTimer::TimerHandle to be pointer-sized"
1159                );
1160            }
1161        };
1162
1163        #[unsafe(no_mangle)]
1164        pub extern "C" fn nros_platform_timer_create_periodic(
1165            period_us: u32,
1166            callback: extern "C" fn(*mut ::core::ffi::c_void),
1167            user_data: *mut ::core::ffi::c_void,
1168        ) -> *mut ::core::ffi::c_void {
1169            match <$ty as ::nros_platform_api::PlatformTimer>::create_periodic(
1170                period_us, callback, user_data,
1171            ) {
1172                Ok(h) => unsafe {
1173                    ::core::mem::transmute_copy::<
1174                        <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1175                        *mut ::core::ffi::c_void,
1176                    >(&::core::mem::ManuallyDrop::new(h))
1177                },
1178                Err(_) => ::core::ptr::null_mut(),
1179            }
1180        }
1181        #[unsafe(no_mangle)]
1182        pub extern "C" fn nros_platform_timer_create_oneshot(
1183            timeout_us: u32,
1184            callback: extern "C" fn(*mut ::core::ffi::c_void),
1185            user_data: *mut ::core::ffi::c_void,
1186        ) -> *mut ::core::ffi::c_void {
1187            match <$ty as ::nros_platform_api::PlatformTimer>::create_oneshot(
1188                timeout_us, callback, user_data,
1189            ) {
1190                Ok(h) => unsafe {
1191                    ::core::mem::transmute_copy::<
1192                        <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1193                        *mut ::core::ffi::c_void,
1194                    >(&::core::mem::ManuallyDrop::new(h))
1195                },
1196                Err(_) => ::core::ptr::null_mut(),
1197            }
1198        }
1199        #[unsafe(no_mangle)]
1200        pub extern "C" fn nros_platform_timer_destroy(handle: *mut ::core::ffi::c_void) {
1201            let h: <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle = unsafe {
1202                ::core::mem::transmute_copy::<
1203                    *mut ::core::ffi::c_void,
1204                    <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1205                >(&handle)
1206            };
1207            <$ty as ::nros_platform_api::PlatformTimer>::destroy(h)
1208        }
1209        #[unsafe(no_mangle)]
1210        pub extern "C" fn nros_platform_timer_cancel(handle: *mut ::core::ffi::c_void) -> i8 {
1211            let mut h: <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle = unsafe {
1212                ::core::mem::transmute_copy::<
1213                    *mut ::core::ffi::c_void,
1214                    <$ty as ::nros_platform_api::PlatformTimer>::TimerHandle,
1215                >(&handle)
1216            };
1217            if <$ty as ::nros_platform_api::PlatformTimer>::cancel(&mut h) {
1218                1
1219            } else {
1220                0
1221            }
1222        }
1223    };
1224}
1225
1226/// Emit every `nros_platform_tcp_*` / `nros_platform_udp_*` /
1227/// `nros_platform_udp_mcast_*` / `nros_platform_socket_*` /
1228/// `nros_platform_network_poll` symbol declared in
1229/// `<nros/platform_net.h>` by delegating to the corresponding trait
1230/// method on `$ty`. The caller must implement `PlatformTcp`,
1231/// `PlatformUdp`, `PlatformUdpMulticast`, `PlatformSocketHelpers`, and
1232/// `PlatformNetworkPoll`.
1233#[macro_export]
1234macro_rules! nros_platform_export_net {
1235    ($ty:ty) => {
1236        // ---- TCP ----
1237        #[unsafe(no_mangle)]
1238        pub extern "C" fn nros_platform_tcp_create_endpoint(
1239            ep: *mut ::core::ffi::c_void,
1240            address: *const u8,
1241            port: *const u8,
1242        ) -> i8 {
1243            <$ty as ::nros_platform_api::PlatformTcp>::create_endpoint(ep, address, port)
1244        }
1245        #[unsafe(no_mangle)]
1246        pub extern "C" fn nros_platform_tcp_free_endpoint(ep: *mut ::core::ffi::c_void) {
1247            <$ty as ::nros_platform_api::PlatformTcp>::free_endpoint(ep)
1248        }
1249        #[unsafe(no_mangle)]
1250        pub extern "C" fn nros_platform_tcp_open(
1251            sock: *mut ::core::ffi::c_void,
1252            endpoint: *const ::core::ffi::c_void,
1253            timeout_ms: u32,
1254        ) -> i8 {
1255            <$ty as ::nros_platform_api::PlatformTcp>::open(sock, endpoint, timeout_ms)
1256        }
1257        #[unsafe(no_mangle)]
1258        pub extern "C" fn nros_platform_tcp_listen(
1259            sock: *mut ::core::ffi::c_void,
1260            endpoint: *const ::core::ffi::c_void,
1261        ) -> i8 {
1262            <$ty as ::nros_platform_api::PlatformTcp>::listen(sock, endpoint)
1263        }
1264        #[unsafe(no_mangle)]
1265        pub extern "C" fn nros_platform_tcp_close(sock: *mut ::core::ffi::c_void) {
1266            <$ty as ::nros_platform_api::PlatformTcp>::close(sock)
1267        }
1268        #[unsafe(no_mangle)]
1269        pub extern "C" fn nros_platform_tcp_read(
1270            sock: *const ::core::ffi::c_void,
1271            buf: *mut u8,
1272            len: usize,
1273        ) -> usize {
1274            <$ty as ::nros_platform_api::PlatformTcp>::read(sock, buf, len)
1275        }
1276        #[unsafe(no_mangle)]
1277        pub extern "C" fn nros_platform_tcp_read_exact(
1278            sock: *const ::core::ffi::c_void,
1279            buf: *mut u8,
1280            len: usize,
1281        ) -> usize {
1282            <$ty as ::nros_platform_api::PlatformTcp>::read_exact(sock, buf, len)
1283        }
1284        #[unsafe(no_mangle)]
1285        pub extern "C" fn nros_platform_tcp_send(
1286            sock: *const ::core::ffi::c_void,
1287            buf: *const u8,
1288            len: usize,
1289        ) -> usize {
1290            <$ty as ::nros_platform_api::PlatformTcp>::send(sock, buf, len)
1291        }
1292        #[unsafe(no_mangle)]
1293        pub extern "C" fn nros_platform_tcp_set_rx_notify(
1294            sock: *const ::core::ffi::c_void,
1295            notify: ::core::option::Option<unsafe extern "C" fn(*mut ::core::ffi::c_void)>,
1296            ctx: *mut ::core::ffi::c_void,
1297        ) -> i8 {
1298            <$ty as ::nros_platform_api::PlatformTcp>::set_rx_notify(sock, notify, ctx)
1299        }
1300
1301        // ---- UDP unicast ----
1302        #[unsafe(no_mangle)]
1303        pub extern "C" fn nros_platform_udp_create_endpoint(
1304            ep: *mut ::core::ffi::c_void,
1305            address: *const u8,
1306            port: *const u8,
1307        ) -> i8 {
1308            <$ty as ::nros_platform_api::PlatformUdp>::create_endpoint(ep, address, port)
1309        }
1310        #[unsafe(no_mangle)]
1311        pub extern "C" fn nros_platform_udp_free_endpoint(ep: *mut ::core::ffi::c_void) {
1312            <$ty as ::nros_platform_api::PlatformUdp>::free_endpoint(ep)
1313        }
1314        #[unsafe(no_mangle)]
1315        pub extern "C" fn nros_platform_udp_open(
1316            sock: *mut ::core::ffi::c_void,
1317            endpoint: *const ::core::ffi::c_void,
1318            timeout_ms: u32,
1319        ) -> i8 {
1320            <$ty as ::nros_platform_api::PlatformUdp>::open(sock, endpoint, timeout_ms)
1321        }
1322        #[unsafe(no_mangle)]
1323        pub extern "C" fn nros_platform_udp_listen(
1324            sock: *mut ::core::ffi::c_void,
1325            endpoint: *const ::core::ffi::c_void,
1326            timeout_ms: u32,
1327        ) -> i8 {
1328            <$ty as ::nros_platform_api::PlatformUdp>::listen(sock, endpoint, timeout_ms)
1329        }
1330        #[unsafe(no_mangle)]
1331        pub extern "C" fn nros_platform_udp_close(sock: *mut ::core::ffi::c_void) {
1332            <$ty as ::nros_platform_api::PlatformUdp>::close(sock)
1333        }
1334        #[unsafe(no_mangle)]
1335        pub extern "C" fn nros_platform_udp_read(
1336            sock: *const ::core::ffi::c_void,
1337            buf: *mut u8,
1338            len: usize,
1339        ) -> usize {
1340            <$ty as ::nros_platform_api::PlatformUdp>::read(sock, buf, len)
1341        }
1342        #[unsafe(no_mangle)]
1343        pub extern "C" fn nros_platform_udp_read_exact(
1344            sock: *const ::core::ffi::c_void,
1345            buf: *mut u8,
1346            len: usize,
1347        ) -> usize {
1348            <$ty as ::nros_platform_api::PlatformUdp>::read_exact(sock, buf, len)
1349        }
1350        #[unsafe(no_mangle)]
1351        pub extern "C" fn nros_platform_udp_send(
1352            sock: *const ::core::ffi::c_void,
1353            buf: *const u8,
1354            len: usize,
1355            endpoint: *const ::core::ffi::c_void,
1356        ) -> usize {
1357            <$ty as ::nros_platform_api::PlatformUdp>::send(sock, buf, len, endpoint)
1358        }
1359        #[unsafe(no_mangle)]
1360        pub extern "C" fn nros_platform_udp_set_recv_timeout(
1361            sock: *const ::core::ffi::c_void,
1362            timeout_ms: u32,
1363        ) {
1364            <$ty as ::nros_platform_api::PlatformUdp>::set_recv_timeout(sock, timeout_ms)
1365        }
1366        #[unsafe(no_mangle)]
1367        pub extern "C" fn nros_platform_udp_readiness_handle(
1368            sock: *const ::core::ffi::c_void,
1369        ) -> i32 {
1370            <$ty as ::nros_platform_api::PlatformUdp>::readiness_handle(sock)
1371        }
1372        #[unsafe(no_mangle)]
1373        pub extern "C" fn nros_platform_udp_set_rx_notify(
1374            sock: *const ::core::ffi::c_void,
1375            notify: ::core::option::Option<unsafe extern "C" fn(*mut ::core::ffi::c_void)>,
1376            ctx: *mut ::core::ffi::c_void,
1377        ) -> i8 {
1378            <$ty as ::nros_platform_api::PlatformUdp>::set_rx_notify(sock, notify, ctx)
1379        }
1380
1381        // ---- UDP multicast ----
1382        #[unsafe(no_mangle)]
1383        pub extern "C" fn nros_platform_udp_mcast_open(
1384            sock: *mut ::core::ffi::c_void,
1385            endpoint: *const ::core::ffi::c_void,
1386            lep: *mut ::core::ffi::c_void,
1387            timeout_ms: u32,
1388            iface: *const u8,
1389        ) -> i8 {
1390            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_open(
1391                sock, endpoint, lep, timeout_ms, iface,
1392            )
1393        }
1394        #[unsafe(no_mangle)]
1395        pub extern "C" fn nros_platform_udp_mcast_listen(
1396            sock: *mut ::core::ffi::c_void,
1397            endpoint: *const ::core::ffi::c_void,
1398            timeout_ms: u32,
1399            iface: *const u8,
1400            join: *const u8,
1401        ) -> i8 {
1402            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_listen(
1403                sock, endpoint, timeout_ms, iface, join,
1404            )
1405        }
1406        #[unsafe(no_mangle)]
1407        pub extern "C" fn nros_platform_udp_mcast_close(
1408            sockrecv: *mut ::core::ffi::c_void,
1409            socksend: *mut ::core::ffi::c_void,
1410            rep: *const ::core::ffi::c_void,
1411            lep: *const ::core::ffi::c_void,
1412        ) {
1413            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_close(
1414                sockrecv, socksend, rep, lep,
1415            )
1416        }
1417        #[unsafe(no_mangle)]
1418        pub extern "C" fn nros_platform_udp_mcast_read(
1419            sock: *const ::core::ffi::c_void,
1420            buf: *mut u8,
1421            len: usize,
1422            lep: *const ::core::ffi::c_void,
1423            addr: *mut ::core::ffi::c_void,
1424        ) -> usize {
1425            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_read(
1426                sock, buf, len, lep, addr,
1427            )
1428        }
1429        #[unsafe(no_mangle)]
1430        pub extern "C" fn nros_platform_udp_mcast_read_exact(
1431            sock: *const ::core::ffi::c_void,
1432            buf: *mut u8,
1433            len: usize,
1434            lep: *const ::core::ffi::c_void,
1435            addr: *mut ::core::ffi::c_void,
1436        ) -> usize {
1437            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_read_exact(
1438                sock, buf, len, lep, addr,
1439            )
1440        }
1441        #[unsafe(no_mangle)]
1442        pub extern "C" fn nros_platform_udp_mcast_send(
1443            sock: *const ::core::ffi::c_void,
1444            buf: *const u8,
1445            len: usize,
1446            endpoint: *const ::core::ffi::c_void,
1447        ) -> usize {
1448            <$ty as ::nros_platform_api::PlatformUdpMulticast>::mcast_send(sock, buf, len, endpoint)
1449        }
1450
1451        // ---- Socket helpers ----
1452        #[unsafe(no_mangle)]
1453        pub extern "C" fn nros_platform_socket_set_non_blocking(
1454            sock: *const ::core::ffi::c_void,
1455        ) -> i8 {
1456            <$ty as ::nros_platform_api::PlatformSocketHelpers>::set_non_blocking(sock)
1457        }
1458        #[unsafe(no_mangle)]
1459        pub extern "C" fn nros_platform_socket_accept(
1460            sock_in: *const ::core::ffi::c_void,
1461            sock_out: *mut ::core::ffi::c_void,
1462        ) -> i8 {
1463            <$ty as ::nros_platform_api::PlatformSocketHelpers>::accept(sock_in, sock_out)
1464        }
1465        #[unsafe(no_mangle)]
1466        pub extern "C" fn nros_platform_socket_close(sock: *mut ::core::ffi::c_void) {
1467            <$ty as ::nros_platform_api::PlatformSocketHelpers>::close(sock)
1468        }
1469        #[unsafe(no_mangle)]
1470        pub extern "C" fn nros_platform_socket_wait_event(
1471            peers: *mut ::core::ffi::c_void,
1472            mutex: *mut ::core::ffi::c_void,
1473        ) -> i8 {
1474            <$ty as ::nros_platform_api::PlatformSocketHelpers>::wait_event(peers, mutex)
1475        }
1476
1477        // ---- Network poll ----
1478        #[unsafe(no_mangle)]
1479        pub extern "C" fn nros_platform_network_poll() {
1480            <$ty as ::nros_platform_api::PlatformNetworkPoll>::network_poll()
1481        }
1482    };
1483}
1484
1485// ============================================================================
1486// Test-only self-export
1487// ----------------------------------------------------------------------------
1488// `cargo test -p nros-platform-cffi` builds a test binary that links the
1489// rlib. The `unsafe extern "C"` declarations above would fail to link
1490// without definitions; we satisfy them by invoking the macro on a dummy
1491// `TestPlatform` ZST defined here. This doubles as a smoke test that
1492// every macro arm expands and that the trait dispatch resolves.
1493//
1494// Real platform crates supply their own definitions via the same macro
1495// and never compile this module (it is gated on `cfg(test)`).
1496// ============================================================================
1497
1498#[cfg(all(test, not(feature = "c-stub-test"), not(feature = "posix-c-port")))]
1499mod test_self_export {
1500    use core::ffi::c_void;
1501    use nros_platform_api::{
1502        PlatformAlloc, PlatformClock, PlatformRandom, PlatformSleep, PlatformThreading,
1503        PlatformTime, PlatformYield,
1504    };
1505
1506    pub struct TestPlatform;
1507
1508    impl PlatformClock for TestPlatform {
1509        fn clock_ns() -> u64 {
1510            0
1511        }
1512        fn clock_resolution_ns() -> u64 {
1513            1
1514        }
1515    }
1516    impl PlatformAlloc for TestPlatform {
1517        fn alloc(_: usize) -> *mut c_void {
1518            core::ptr::null_mut()
1519        }
1520        fn realloc(_: *mut c_void, _: usize) -> *mut c_void {
1521            core::ptr::null_mut()
1522        }
1523        fn dealloc(_: *mut c_void) {}
1524    }
1525    impl PlatformSleep for TestPlatform {
1526        fn sleep_us(_: usize) {}
1527        fn sleep_ms(_: usize) {}
1528        fn sleep_s(_: usize) {}
1529    }
1530    impl PlatformYield for TestPlatform {
1531        fn yield_now() {}
1532    }
1533    impl PlatformRandom for TestPlatform {
1534        fn random_u8() -> u8 {
1535            0
1536        }
1537        fn random_u16() -> u16 {
1538            0
1539        }
1540        fn random_u32() -> u32 {
1541            0
1542        }
1543        fn random_u64() -> u64 {
1544            0
1545        }
1546        fn random_fill(_: *mut c_void, _: usize) {}
1547    }
1548    impl PlatformTime for TestPlatform {
1549        fn time_now_ns() -> u64 {
1550            0
1551        }
1552    }
1553    impl PlatformThreading for TestPlatform {
1554        fn task_init(
1555            _: *mut c_void,
1556            _: *mut c_void,
1557            _: Option<unsafe extern "C" fn(*mut c_void) -> *mut c_void>,
1558            _: *mut c_void,
1559        ) -> i8 {
1560            -1
1561        }
1562        fn task_join(_: *mut c_void) -> i8 {
1563            -1
1564        }
1565        fn task_detach(_: *mut c_void) -> i8 {
1566            -1
1567        }
1568        fn task_cancel(_: *mut c_void) -> i8 {
1569            -1
1570        }
1571        fn task_exit() {}
1572        fn task_free(_: *mut *mut c_void) {}
1573        fn mutex_init(_: *mut c_void) -> i8 {
1574            0
1575        }
1576        fn mutex_drop(_: *mut c_void) -> i8 {
1577            0
1578        }
1579        fn mutex_lock(_: *mut c_void) -> i8 {
1580            0
1581        }
1582        fn mutex_try_lock(_: *mut c_void) -> i8 {
1583            0
1584        }
1585        fn mutex_unlock(_: *mut c_void) -> i8 {
1586            0
1587        }
1588        fn mutex_rec_init(_: *mut c_void) -> i8 {
1589            0
1590        }
1591        fn mutex_rec_drop(_: *mut c_void) -> i8 {
1592            0
1593        }
1594        fn mutex_rec_lock(_: *mut c_void) -> i8 {
1595            0
1596        }
1597        fn mutex_rec_try_lock(_: *mut c_void) -> i8 {
1598            0
1599        }
1600        fn mutex_rec_unlock(_: *mut c_void) -> i8 {
1601            0
1602        }
1603        fn condvar_init(_: *mut c_void) -> i8 {
1604            0
1605        }
1606        fn condvar_drop(_: *mut c_void) -> i8 {
1607            0
1608        }
1609        fn condvar_signal(_: *mut c_void) -> i8 {
1610            0
1611        }
1612        fn condvar_signal_all(_: *mut c_void) -> i8 {
1613            0
1614        }
1615        fn condvar_wait_until(_: *mut c_void, _: *mut c_void, _: u64) -> i8 {
1616            0
1617        }
1618    }
1619    impl ::nros_platform_api::PlatformCriticalSection for TestPlatform {
1620        fn acquire() -> u32 {
1621            0
1622        }
1623        fn release(_: u32) {}
1624    }
1625
1626    /// Pointer-sized newtype wrapping `*mut c_void` so the
1627    /// PlatformTimer Send + Sync + 'static bound is satisfied.
1628    /// The transmute inside `nros_platform_export_timer!` rests on
1629    /// this being `#[repr(transparent)]` over a pointer.
1630    #[repr(transparent)]
1631    #[derive(Clone, Copy)]
1632    pub struct TestTimerHandle(pub *mut c_void);
1633    unsafe impl Send for TestTimerHandle {}
1634    unsafe impl Sync for TestTimerHandle {}
1635
1636    impl ::nros_platform_api::PlatformTimer for TestPlatform {
1637        type TimerHandle = TestTimerHandle;
1638        // create_periodic / create_oneshot / destroy / cancel inherit
1639        // the trait's default impls (return TimerError::Unsupported /
1640        // no-op destroy / false cancel) — fine for export-emission
1641        // verification.
1642    }
1643
1644    crate::nros_platform_export!(TestPlatform);
1645    crate::nros_platform_export_timer!(TestPlatform);
1646
1647    #[test]
1648    fn macro_expansion_dispatches() {
1649        // Touch every group through the FFI surface to confirm the
1650        // generated symbols are reachable and dispatch resolves.
1651        assert_eq!(super::CffiPlatform::clock_ns(), 0);
1652        assert_eq!(
1653            <super::CffiPlatform as ::nros_platform_api::PlatformAlloc>::alloc(0),
1654            core::ptr::null_mut(),
1655        );
1656        <super::CffiPlatform as ::nros_platform_api::PlatformYield>::yield_now();
1657    }
1658
1659    #[test]
1660    fn timer_macro_emits() {
1661        // Default impl returns Unsupported → null handle.
1662        let h = unsafe {
1663            super::nros_platform_timer_create_periodic(
1664                1000,
1665                Some(noop_callback),
1666                core::ptr::null_mut(),
1667            )
1668        };
1669        assert!(h.is_null(), "default Unsupported impl must surface as NULL");
1670    }
1671
1672    // bindgen wraps C function-pointer parameters in `Option`, and the
1673    // pointee is `unsafe extern "C"` — issue #545.
1674    unsafe extern "C" fn noop_callback(_: *mut c_void) {}
1675}