From 2a1b23802aaabbebb0e2e273c0d34ba8c61ee829 Mon Sep 17 00:00:00 2001 From: Jorge Guzman Date: Wed, 26 Aug 2026 10:15:37 -0300 Subject: [PATCH] zbus: Add linker support and documentation for the zbus port NuttX-side support for the zbus message bus port (apps/system/zbus in nuttx-apps), built on the link-time iterable sections infrastructure added in a companion PR: - include/nuttx/linker/common-rom.ld and common-insert.ld: register the zbus channel, observer and channel observation iterable sections (ITERABLE_SECTION blocks guarded by CONFIG_ZBUS, no-op otherwise) for the include and the zero-touch INSERT modes respectively; common-ram.ld: note that zbus needs no RAM sections. - Documentation/applications/system/zbus: Sphinx documentation for the zbus application, with the upstream Zephyr diagrams (Apache-2.0). - .codespellrc: skip the reused zbus SVG diagrams (embedded base64 raster data trips the spell checker). Assisted-by: Claude Code Signed-off-by: Jorge Guzman --- .codespellrc | 1 + .../system/zbus/images/zbus_anatomy.svg | 3 + .../zbus/images/zbus_observation_mask.svg | 152 +++++++++++++ .../system/zbus/images/zbus_operations.svg | 49 ++++ .../system/zbus/images/zbus_overview.svg | 3 + .../zbus/images/zbus_type_of_observers.svg | 18 ++ .../applications/system/zbus/index.rst | 210 ++++++++++++++++++ include/nuttx/linker/common-insert.ld | 5 + include/nuttx/linker/common-ram.ld | 4 + include/nuttx/linker/common-rom.ld | 6 + 10 files changed, 451 insertions(+) create mode 100644 Documentation/applications/system/zbus/images/zbus_anatomy.svg create mode 100644 Documentation/applications/system/zbus/images/zbus_observation_mask.svg create mode 100644 Documentation/applications/system/zbus/images/zbus_operations.svg create mode 100644 Documentation/applications/system/zbus/images/zbus_overview.svg create mode 100644 Documentation/applications/system/zbus/images/zbus_type_of_observers.svg create mode 100644 Documentation/applications/system/zbus/index.rst diff --git a/.codespellrc b/.codespellrc index 14770824d32..4ed2e6b695c 100644 --- a/.codespellrc +++ b/.codespellrc @@ -9,6 +9,7 @@ exclude-file = .codespell-ignore-lines skip = LICENSE, */CODEOWNERS, + */system/zbus/images/*, # Ignore seemingly misspelled words. # lowercase: case insensitive diff --git a/Documentation/applications/system/zbus/images/zbus_anatomy.svg b/Documentation/applications/system/zbus/images/zbus_anatomy.svg new file mode 100644 index 00000000000..e9bc6c79cef --- /dev/null +++ b/Documentation/applications/system/zbus/images/zbus_anatomy.svg @@ -0,0 +1,3 @@ + + + diff --git a/Documentation/applications/system/zbus/images/zbus_observation_mask.svg b/Documentation/applications/system/zbus/images/zbus_observation_mask.svg new file mode 100644 index 00000000000..4405a8f3e4a --- /dev/null +++ b/Documentation/applications/system/zbus/images/zbus_observation_mask.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Documentation/applications/system/zbus/images/zbus_operations.svg b/Documentation/applications/system/zbus/images/zbus_operations.svg new file mode 100644 index 00000000000..419cc6c3d5d --- /dev/null +++ b/Documentation/applications/system/zbus/images/zbus_operations.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Documentation/applications/system/zbus/images/zbus_overview.svg b/Documentation/applications/system/zbus/images/zbus_overview.svg new file mode 100644 index 00000000000..24861bbbe33 --- /dev/null +++ b/Documentation/applications/system/zbus/images/zbus_overview.svg @@ -0,0 +1,3 @@ + + + diff --git a/Documentation/applications/system/zbus/images/zbus_type_of_observers.svg b/Documentation/applications/system/zbus/images/zbus_type_of_observers.svg new file mode 100644 index 00000000000..1bc29e3dadf --- /dev/null +++ b/Documentation/applications/system/zbus/images/zbus_type_of_observers.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/Documentation/applications/system/zbus/index.rst b/Documentation/applications/system/zbus/index.rst new file mode 100644 index 00000000000..ab7eac19cb6 --- /dev/null +++ b/Documentation/applications/system/zbus/index.rst @@ -0,0 +1,210 @@ +========================== +``zbus`` ZBus message bus +========================== + +Port of the `zbus `_ +message bus to NuttX, built entirely on native NuttX primitives. ZBus +implements many-to-many communication through **channels** (typed shared +messages) observed by **observers**, keeping publishers and consumers +fully decoupled. + +.. figure:: images/zbus_overview.svg + :alt: zbus usage overview + :width: 75% + + A typical zbus application architecture. + +Channels and observers are defined statically, in any source file, with +declarative macros. The definitions are collected at link time through +iterable sections (see the Iterable Sections component documentation) -- +there is no runtime registration and no central list to maintain. + +.. figure:: images/zbus_anatomy.svg + :alt: zbus anatomy + :width: 70% + + ZBus anatomy: channels, observers and observations. + +Observer types +============== + +.. figure:: images/zbus_type_of_observers.svg + :alt: zbus observer types + :width: 70% + + The four observer types. + +======================== =================================================== +Type Behavior +======================== =================================================== +Listener Callback executed synchronously in the publisher + context (``ZBUS_LISTENER_DEFINE``). +Subscriber Receives channel references through a message + queue; waits with ``zbus_sub_wait()`` + (``ZBUS_SUBSCRIBER_DEFINE``). +Message subscriber Receives a *copy* of every published message, in + order; waits with ``zbus_sub_wait_msg()`` + (``ZBUS_MSG_SUBSCRIBER_DEFINE``, + ``CONFIG_ZBUS_MSG_SUBSCRIBER``). +Async listener Callback executed on a dedicated task with a + copy of the message + (``ZBUS_ASYNC_LISTENER_DEFINE``, + ``CONFIG_ZBUS_ASYNC_LISTENER``). +======================== =================================================== + +Runtime observers (``zbus_chan_add_obs()``/``zbus_chan_rm_obs()``, +``CONFIG_ZBUS_RUNTIME_OBSERVERS``), per-observation notification masks, +observer enable/disable, message validators and channel user data are +also supported. + +.. figure:: images/zbus_observation_mask.svg + :alt: zbus observation mask + :width: 75% + + Observer enable/disable and per-observation masks: disabling the + observer (b) silences every channel; masking observations (c, d) + silences individual channels. + +Example +======= + +The figure below shows the kind of decoupled architecture zbus enables: +every block only talks to channels, so each one can be replaced without +touching the others. + +.. figure:: images/zbus_operations.svg + :alt: zbus sensor-based application + :width: 85% + + A sensor-based application built on zbus. + +.. code-block:: c + + #include + + struct acc_msg + { + int x; + int y; + int z; + }; + + static void listener_cb(const struct zbus_channel *chan) + { + const struct acc_msg *msg = zbus_chan_const_msg(chan); + printf("x=%d y=%d z=%d\n", msg->x, msg->y, msg->z); + } + + ZBUS_LISTENER_DEFINE(acc_listener, listener_cb); + ZBUS_SUBSCRIBER_DEFINE(acc_subscriber, 4); + + ZBUS_CHAN_DEFINE(acc_chan, /* Name */ + struct acc_msg, /* Message type */ + NULL, /* Validator */ + NULL, /* User data */ + ZBUS_OBSERVERS(acc_listener, /* Observers, in */ + acc_subscriber),/* priority order */ + ZBUS_MSG_INIT(.x = 0, .y = 0, .z = 0)); + + /* Publisher: */ + + struct acc_msg msg = { 1, 10, 100 }; + zbus_chan_pub(&acc_chan, &msg, 1000); + + /* Subscriber thread: */ + + const struct zbus_channel *chan; + if (zbus_sub_wait(&acc_subscriber, &chan, ZBUS_FOREVER) == 0) + { + zbus_chan_read(chan, &msg, 500); + } + +A complete runnable example is available in ``apps/examples/zbus`` +(``CONFIG_EXAMPLES_ZBUS``), and a cmocka test suite covering the whole +API in ``apps/testing/zbus`` (``CONFIG_TESTING_ZBUS``). + +Not ported +========== + +The following Zephyr zbus features are **not available** in this port: + +* **Multi-domain proxy agent** (``CONFIG_ZBUS_PROXY_AGENT``): bridges + channels between domains/cores over IPC. Experimental upstream and + tied to the Zephyr IPC service; a NuttX equivalent would be built on + rpmsg and is left as future work. +* **Publishing from interrupt handlers**: the Zephyr original allows + ``zbus_chan_pub()`` from ISRs with ``K_NO_WAIT``. This port is a + userspace library and its primitives (semaphores, message queues, lazy + initialization) are not ISR-safe: interrupt handling belongs to the + driver, which should hand the data to a thread (the usual NuttX + pattern) that then publishes it. ``test_timer_driven_publisher`` in + ``apps/testing/zbus`` shows the pattern with a kernel timer interrupt + delivering a signal to a sampling thread. +* **Priority boost (Highest Locker Protocol)** + (``CONFIG_ZBUS_PRIORITY_BOOST``): the Zephyr hand-rolled protection + against priority inversion during the notification process. Not + needed: enable the native ``CONFIG_PRIORITY_INHERITANCE`` so the + channel semaphores get equivalent protection from the kernel. +* **net_buf pools and pool isolation** + (``CONFIG_ZBUS_MSG_SUBSCRIBER_BUF_*``): obsolete by design in this + port -- message queues copy the payload on ``mq_send``, so no shared + reference-counted buffers exist at all. +* **Static/user-provided runtime observer nodes** + (``CONFIG_ZBUS_RUNTIME_OBSERVERS_NODE_ALLOC_STATIC/NONE``): runtime + observer nodes are always heap-allocated in this port. + +Differences from the Zephyr original +==================================== + +* Timeouts are plain milliseconds (``int32_t``): ``ZBUS_NO_WAIT`` (0) and + ``ZBUS_FOREVER`` (-1) replace ``K_NO_WAIT``/``K_FOREVER``. +* Subscriber queues are POSIX message queues opened lazily on first API + use through the kernel ``file_mq_*`` interface, making them usable from + any task (a ``mqd_t`` descriptor would die with the opening task). +* Initialization is lazy (``pthread_once`` on the first API call) + replacing the Zephyr ``SYS_INIT`` hook; no explicit init call is + needed. +* Async listeners run on a dedicated task per listener (spawned on + first use; priority and stack size are configurable) instead of the + Zephyr system work queue. + +Configuration +============= + +Requirements: + +* The board linker script must provide the zbus iterable sections, either + by including ```` inside ``.text`` (see the + ``linum-stm32h753bi`` board) or through + ``CONFIG_ITERABLE_SECTIONS_LINKER_INSERT`` + (zero-touch mode; see its help text for the MEMORY-layout constraint). +* ``CONFIG_MQ_MAXMSGSIZE`` must be at least + ``CONFIG_ZBUS_MSG_SUBSCRIBER_MAX_MSG_SIZE`` plus the size of a pointer + when message subscribers or async listeners are used, otherwise their + queues fail to open with ``-EINVAL``. +* FLAT build (the library uses the kernel ``file_mq_*`` interface + directly). + +Main options: + +* ``CONFIG_ZBUS`` -- enable the library. +* ``CONFIG_ZBUS_CHANNEL_NAME`` / ``CONFIG_ZBUS_OBSERVER_NAME`` -- name + fields and lookup by name. +* ``CONFIG_ZBUS_CHANNEL_ID`` -- numeric channel identifiers + (``ZBUS_CHAN_DEFINE_WITH_ID``, ``zbus_chan_from_id()``). +* ``CONFIG_ZBUS_MSG_SUBSCRIBER`` -- message subscribers + (+ ``_MAX_MSG_SIZE``, ``_QUEUE_SIZE``). +* ``CONFIG_ZBUS_ASYNC_LISTENER`` -- async listeners (+ ``_PRIORITY``, + ``_STACKSIZE`` of their tasks). +* ``CONFIG_ZBUS_RUNTIME_OBSERVERS`` -- runtime observers. +* ``CONFIG_ZBUS_CHANNEL_PUBLISH_STATS`` -- publish timestamp/count. +* ``CONFIG_ZBUS_ASSERT_MOCK`` -- invalid parameters return ``-EFAULT`` + instead of asserting (for tests). + +Credits +======= + +The zbus design and the diagrams in this page come from the upstream +`Zephyr zbus documentation +`_ +by Rodrigo Peixoto and contributors (Apache License 2.0). diff --git a/include/nuttx/linker/common-insert.ld b/include/nuttx/linker/common-insert.ld index 44a11e06c2a..77d4faa6b99 100644 --- a/include/nuttx/linker/common-insert.ld +++ b/include/nuttx/linker/common-insert.ld @@ -54,6 +54,11 @@ SECTIONS { .iterable_sections : SUBALIGN(4) { +#ifdef CONFIG_ZBUS + ITERABLE_SECTION(zbus_channel) + ITERABLE_SECTION(zbus_observer) + ITERABLE_SECTION(zbus_channel_observation) +#endif } } INSERT AFTER .text; diff --git a/include/nuttx/linker/common-ram.ld b/include/nuttx/linker/common-ram.ld index 30b56edae83..a7920fee55f 100644 --- a/include/nuttx/linker/common-ram.ld +++ b/include/nuttx/linker/common-ram.ld @@ -40,4 +40,8 @@ * sections; blocks are added below, each guarded by its Kconfig option. */ +/* zbus needs no RAM iterable sections: notification masks live in .bss + * and are initialized at runtime from ROM-preserved values. + */ + #endif /* !CONFIG_ITERABLE_SECTIONS_LINKER_INSERT */ diff --git a/include/nuttx/linker/common-rom.ld b/include/nuttx/linker/common-rom.ld index 7c0467c25e8..32621d396c5 100644 --- a/include/nuttx/linker/common-rom.ld +++ b/include/nuttx/linker/common-rom.ld @@ -44,4 +44,10 @@ * #endif */ +#ifdef CONFIG_ZBUS +ITERABLE_SECTION(zbus_channel) +ITERABLE_SECTION(zbus_observer) +ITERABLE_SECTION(zbus_channel_observation) +#endif + #endif /* !CONFIG_ITERABLE_SECTIONS_LINKER_INSERT */