Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Zenoh-pico Symbol Reference

This page documents the ~55 FFI symbols that zenoh-pico requires at link time. These symbols are provided by zpico-sys’s C alias translation unit (c/zpico/platform_aliases.c, built by the default-on platform-aliases feature), which forwards each z_* / _z_* call to the canonical nros_platform_* ABI. When porting to a new platform, you implement an nros-platform-<name> crate (see Custom Platform) that supplies the nros_platform_* symbols — you do not provide the z_* symbols directly.

Historical note: this mapping used to live in a separate zpico-platform-shim crate; Phase 129 deleted it and folded the forwarders into the zpico-sys alias TU.

This page serves as a reference for understanding what the alias TU maps and what capabilities your nros-platform-<name> crate must provide.

Platform crate structure

packages/core/nros-platform-<name>/
├── Cargo.toml
└── src/
    ├── lib.rs
    ├── clock.rs
    ├── memory.rs
    ├── sleep.rs
    ├── random.rs
    ├── time.rs
    ├── threading.rs          # no-op stubs or real RTOS impl
    ├── socket_stubs.rs       # if using smoltcp
    └── network.rs            # if using smoltcp

Cargo.toml must have zero nros-* dependencies. It may depend on:

  • Hardware HAL crate (e.g., stm32f4xx-hal, esp-hal)
  • zpico-smoltcp (if using smoltcp networking)
  • embedded-alloc (for heap on bare-metal)
  • RTOS bindings crate

Required FFI symbols

Clock (critical)

The clock is the most important primitive. zenoh-pico uses it for session keep-alive, query timeouts, and transport timeouts. It must be backed by a hardware timer or OS tick — never by a software counter that only advances when polled.

// Monotonic clock — returns an opaque timestamp (lower 32 bits of ms)
usize   z_clock_now(void);

// Elapsed time since a previous z_clock_now() value
c_ulong z_clock_elapsed_us(usize *time);
c_ulong z_clock_elapsed_ms(usize *time);
c_ulong z_clock_elapsed_s(usize *time);

// Advance a timestamp value by a duration
void    z_clock_advance_us(usize *clock, c_ulong duration);
void    z_clock_advance_ms(usize *clock, c_ulong duration);
void    z_clock_advance_s(usize *clock, c_ulong duration);

If using smoltcp (bare-metal networking), also provide:

// Called by zpico-smoltcp for TCP/IP timestamping
u64 smoltcp_clock_now_ms(void);

Implementation checklist:

  1. Identify a hardware timer (SysTick, GPT, DWT) or use the OS tick API (xTaskGetTickCount, k_uptime_get, clock_gettime)
  2. Handle 32-bit timer wraps — track a wrap count in an atomic or use a 64-bit counter
  3. Never advance the clock inside smoltcp_network_poll() — read the hardware timer directly
  4. Verify with QEMU: use -icount shift=auto to synchronize virtual time with wall-clock time

Reference implementations:

PlatformClock sourceFile
MPS2-AN385CMSDK APB Timer0 (25 MHz)nros-platform-mps2-an385/src/clock.rs
STM32F4ARM DWT cycle counternros-platform-stm32f4/src/clock.rs
ESP32-C3 (QEMU)esp_hal::time::Instantnros-platform-esp32-qemu/src/clock.rs
FreeRTOSxTaskGetTickCount()Use OS tick directly
NuttXclock_gettime(CLOCK_MONOTONIC)POSIX API

Memory

zenoh-pico allocates heap during session open and entity creation. Typical minimum: 64 KB heap.

void *z_malloc(usize size);
void *z_realloc(void *ptr, usize size);
void  z_free(void *ptr);

Options:

  • embedded-alloc FreeListHeap (bare-metal)
  • RTOS heap (pvPortMalloc / tx_byte_allocate)
  • System malloc (POSIX, NuttX)

Sleep

i8 z_sleep_us(usize time);
i8 z_sleep_ms(usize time);
i8 z_sleep_s(usize time);

All return 0 (_Z_RES_OK). On bare-metal, busy-wait using the clock. On RTOS, delegate to vTaskDelay / tx_thread_sleep / k_sleep.

If using smoltcp, poll the network stack during busy-wait sleep to avoid missing packets.

Random

zenoh-pico needs randomness for session IDs, SN initialization, and scouting nonces.

u8   z_random_u8(void);
u16  z_random_u16(void);
u32  z_random_u32(void);
u64  z_random_u64(void);
void z_random_fill(void *buf, usize len);

A simple xorshift32 PRNG is sufficient. Seed it with hardware entropy (RNG peripheral, ADC noise, semihosting wall-clock time) during init.

Time

System time (wall clock). Used for logging and z_time_now_as_str().

u64         z_time_now(void);
const char *z_time_now_as_str(char *buf, c_ulong buflen);
c_ulong     z_time_elapsed_us(u64 *time);
c_ulong     z_time_elapsed_ms(u64 *time);
c_ulong     z_time_elapsed_s(u64 *time);
i8          _z_get_time_since_epoch(ZTimeSinceEpoch *t);

Where ZTimeSinceEpoch is:

#[repr(C)]
struct ZTimeSinceEpoch {
    u32 secs,
    u32 nanos,
}

On bare-metal without an RTC, return monotonic time or zeros.

Threading

For single-threaded platforms (bare-metal, RTIC), provide no-op stubs. For RTOS platforms, implement real task/mutex/condvar operations.

Task operations:

i8   _z_task_init(ZTask *task, ZTaskAttr *attr, void*(*fun)(void*), void *arg);
i8   _z_task_join(ZTask *task);
i8   _z_task_detach(ZTask *task);
i8   _z_task_cancel(ZTask *task);
void _z_task_exit(void);
void _z_task_free(ZTask **task);

Mutex operations:

i8 _z_mutex_init(ZMutex *m);
i8 _z_mutex_drop(ZMutex *m);
i8 _z_mutex_lock(ZMutex *m);
i8 _z_mutex_try_lock(ZMutex *m);
i8 _z_mutex_unlock(ZMutex *m);

Recursive mutex operations:

i8 _z_mutex_rec_init(ZMutexRec *m);
i8 _z_mutex_rec_drop(ZMutexRec *m);
i8 _z_mutex_rec_lock(ZMutexRec *m);
i8 _z_mutex_rec_try_lock(ZMutexRec *m);
i8 _z_mutex_rec_unlock(ZMutexRec *m);

Condition variable operations:

i8 _z_condvar_init(ZCondvar *cv);
i8 _z_condvar_drop(ZCondvar *cv);
i8 _z_condvar_signal(ZCondvar *cv);
i8 _z_condvar_signal_all(ZCondvar *cv);
i8 _z_condvar_wait(ZCondvar *cv, ZMutex *m);
i8 _z_condvar_wait_until(ZCondvar *cv, ZMutex *m, u64 *abstime);

Single-threaded stubs: Return 0 for all mutex/condvar operations. Return -1 for _z_task_init (task creation is not supported).

RTOS implementations: Map to xTaskCreate/xSemaphoreCreateMutex (FreeRTOS), tx_thread_create/tx_mutex_create (ThreadX), or pthread_create/pthread_mutex_init (NuttX, POSIX). zenoh-pico requires recursive mutexes (configUSE_RECURSIVE_MUTEXES=1 on FreeRTOS).

Sockets

If using smoltcp (bare-metal), socket operations are handled by zpico-smoltcp. Your platform crate provides thin shims:

#[repr(C)]
struct ZSysNetSocket { i8 _handle, bool _connected }

i8   _z_socket_set_non_blocking(const ZSysNetSocket *sock);  // Return 0
i8   _z_socket_accept(const ZSysNetSocket *in, ZSysNetSocket *out);  // Return -1
void _z_socket_close(ZSysNetSocket *sock);
i8   _z_socket_wait_event(void *peers, ZMutexRecRef *mutex);  // Return 0

If using OS sockets (POSIX, NuttX, Zephyr), zenoh-pico’s built-in socket layer handles everything — no socket stubs needed.

libc stubs (bare-metal only)

Bare-metal targets without a C runtime need standard C library functions:

usize  strlen(const char *s);
int    strcmp(const char *s1, const char *s2);
int    strncmp(const char *s1, const char *s2, usize n);
char  *strchr(const char *s, int c);
char  *strncpy(char *dest, const char *src, usize n);
void  *memcpy(void *dest, const void *src, usize n);
void  *memmove(void *dest, const void *src, usize n);
void  *memset(void *dest, int c, usize n);
int    memcmp(const void *s1, const void *s2, usize n);
void  *memchr(const void *s, int c, usize n);
c_ulong strtoul(const char *nptr, char **endptr, int base);
int   *__errno(void);

RTOS platforms (FreeRTOS, NuttX, ThreadX, Zephyr) ship their own libc — you do not need these stubs.

Network poll callback (smoltcp only)

If using smoltcp for networking, provide a poll callback that zpico-smoltcp calls to process network events:

void smoltcp_network_poll(void);

And a Rust API for the board crate to register the network state:

#![allow(unused)]
fn main() {
pub unsafe fn set_network_state(
    iface: *mut Interface,
    sockets: *mut SocketSet<'static>,
    device: *mut (),
);

pub unsafe fn clear_network_state();
}

Step-by-step procedure

  1. Create the platform crate (nros-platform-<name>) – see Custom Platform for the full guide
  2. Implement and verify the clock — this is the #1 cause of porting failures. Print clock_ms() in a loop and verify monotonic advance
  3. Implement remaining primitives — memory, random, sleep, time, threading, sockets. Each module is independent
  4. Wire into nros-platform — add a feature and ConcretePlatform alias
  5. Create the board crate — see Custom Board Package
  6. Add the platform feature to nros with mutual exclusivity checks
  7. Write an example — see Creating Examples
  8. Add test infrastructurejust test-<name> recipe + nextest group

Platform capability summary

CapabilityBare-metalRTOS (FreeRTOS/ThreadX)POSIX-like (NuttX/Zephyr)
ClockHardware timerOS tick APIclock_gettime
Memoryembedded-allocRTOS heapSystem malloc
SleepBusy-wait + pollvTaskDelaynanosleep
ThreadingNo-op stubsReal tasks + mutexespthreads
Socketssmoltcp shimslwIP or NetX socketsBSD sockets
RandomSeeded xorshiftRTOS RNG or xorshift/dev/urandom
libcHand-written stubsRTOS libcSystem libc
Network pollsmoltcp_network_pollStack-specific pollNot needed

Common pitfalls

See Platform Porting Pitfalls for detailed failure modes including poll-driven clocks, DMA buffer placement, QEMU I/O starvation, recursive mutexes, stack sizing, and heap sizing.