diff --git a/doc/services/index.rst b/doc/services/index.rst
index e9db60376aadb..61c1695ac47c6 100644
--- a/doc/services/index.rst
+++ b/doc/services/index.rst
@@ -29,4 +29,5 @@ OS Services
usb/index.rst
virtualization/index.rst
rtio/index.rst
+ zbus/index.rst
misc.rst
diff --git a/doc/services/zbus/images/zbus_anatomy.svg b/doc/services/zbus/images/zbus_anatomy.svg
new file mode 100644
index 0000000000000..e9bc6c79cefc9
--- /dev/null
+++ b/doc/services/zbus/images/zbus_anatomy.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/doc/services/zbus/images/zbus_operations.svg b/doc/services/zbus/images/zbus_operations.svg
new file mode 100644
index 0000000000000..46733fdd9dc24
--- /dev/null
+++ b/doc/services/zbus/images/zbus_operations.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/doc/services/zbus/images/zbus_overview.svg b/doc/services/zbus/images/zbus_overview.svg
new file mode 100644
index 0000000000000..24861bbbe3375
--- /dev/null
+++ b/doc/services/zbus/images/zbus_overview.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/doc/services/zbus/index.rst b/doc/services/zbus/index.rst
new file mode 100644
index 0000000000000..1ae18ee3018a2
--- /dev/null
+++ b/doc/services/zbus/index.rst
@@ -0,0 +1,387 @@
+.. _zbus:
+
+Zephyr message bus (zbus)
+#########################
+
+The :dfn:`Zephyr message bus - Zbus` is a lightweight and flexible message bus enabling a simple way for threads to talk to one another.
+
+.. contents::
+ :local:
+ :depth: 2
+
+Concepts
+********
+
+Threads can broadcast messages to all interested observers using zbus. Many-to-many communication is possible. The bus implements message-passing and publish/subscribe communication paradigms that enable threads to communicate synchronously or asynchronously through shared memory. The communication through zbus is channel-based, where threads publish and read to and from using messages. Additionally, threads can observe channels and receive notifications from the bus when the channels are modified. :numref:`zbus common` shows an example of a typical application using zbus in which the application logic (hardware independent) talks to other threads via message bus. Note that the threads are decoupled from each other because they only use zbus' channels and do not need to know each other to talk.
+
+
+.. _zbus common:
+.. figure:: images/zbus_overview.svg
+ :alt: zbus usage overview
+ :width: 75%
+
+ A typical zbus application architecture.
+
+:numref:`zbus anatomy` illustrates zbus' anatomy. The bus comprises:
+
+* Set of channels that consists of a unique identifier, its control metadata information, and the message itself;
+* :dfn:`Virtual distributed event dispatcher` (VDED), the bus logic responsible for sending notifications to the observers. The VDED logic runs inside the publishing action in the same thread context, giving the bus an idea of a distributed execution. When a thread publishes to a channel, it also propagates the notifications to the observers;
+* Threads (subscribers) and callbacks (listeners) publishing, reading, and receiving notifications from the bus.
+
+.. _zbus anatomy:
+.. figure:: images/zbus_anatomy.svg
+ :alt: Zbus anatomy
+ :width: 70%
+
+ Zbus internals details.
+
+The bus makes the publish, read, and subscribe actions available over channels. Publishing and reading are available in all RTOS contexts except inside an Interrupt Service Routine (ISR). The publish and read operations were designed to be simple and fast; the procedure is a mutex locking followed by a memory copy to and from a shared memory region. Zbus observers' registration can be:
+
+* Static, defined in compile time. It is not possible to remove at runtime, but it is possible to suppress it by calling the :c:func:`zbus_obs_set_enable`;
+* Dynamic, it can be added and removed to and from a channel at runtime.
+
+
+For illustration purposes, suppose a usual sensor-based solution in :numref:`zbus operations`. When the timer is triggered, it pushes an action to a workqueue that publishes to the ``Start trigger`` channel. As the sensor thread subscribed to the ``Start trigger`` channel, it starts to fetch the sensor data. Notice the event dispatcher executes the blink callback because it also listens to the ``Start trigger`` channel. When the sensor data is ready, the sensor thread publishes it to the ``Sensor data`` channel. The core thread as a ``Sensor data`` channel subscriber process the sensor data and stores it in a internal sample buffer. It repeats until the sample buffer is full; when it happens, the core thread aggregates the sample buffer information, prepares a package, and publishes that to the ``Payload`` channel. The Lora thread receives that because it is a ``Payload`` channel subscriber and sends the payload to the cloud. When the transmission is completed, the Lora thread publishes to the ``Transmission done`` channel. The blink callback will be executed again since it listens to the ``Transmission done`` channel.
+
+
+
+.. _zbus operations:
+.. figure:: images/zbus_operations.svg
+ :alt: Zbus sensor-based application
+ :width: 80%
+
+ Zbus sensor-based application.
+
+This way of implementing the solution gives us certain flexibility enabling us to change things independently. For example, suppose we would like to change the trigger from a timer to a button press. We can do that, and the change does not affect other parts of the system. Suppose, again, we would like to change the communication interface from LoRa to Bluetooth; for that, we only need to change the LoRa thread. No other change is needed to make that work. Thus, the developer would do that for every block of the image. Based on that, there is a sign zbus promotes decoupling in the system architecture.
+
+Another important aspect of using zbus is the reuse of system modules. If a module, code portion with a set of well-defined behaviors, only uses zbus channels and not hardware interfaces, it can easily be reused in other solutions. For that, the new solution must implement the interfaces (set of channels) the module needs to work. That indicates zbus could improve the module reuse.
+
+The last important note is the zbus solution reach. We can count on many different ways of using zbus to enable the developer to be as free as possible to create what they need with it. Messages can be dynamic or static allocated, notifications can be synchronous or asynchronous, the developer can control the channel in so many different ways claiming the channel, developers can add their metadata information to a channel by using the user-data field, the discretionary use of a validator enables the systems to be accurate over message format, and so on. Those characteristics increase the solutions that can be done with zbus and make it a good fit as an open-source community tool.
+
+Limitations
+===========
+
+Based on the fact that developers can use zbus to solve many different problems, some challenges arise. Zbus will not solve every problem, so it is necessary to analyze the situation to be sure zbus is applicable. For instance, based on the zbus benchmark, it would not be well suited to a high-speed stream of bytes between threads. The `Pipe` kernel object solves this kind of need.
+
+Delivery guarantees
+-------------------
+
+Zbus always delivers the messages to the listeners. However, there are no message delivery guarantees for subscribers because zbus only sends the notification, but the message reading depends on the subscriber's implementation. It is possible to increase the delivery rate by following design tips:
+
+* Keep the listeners quick-as-possible (deal with them as ISRs). If some processing is needed, consider submitting a work to a work-queue;
+* Try to give producers a high priority to avoid losses;
+* Leave spare CPU for observers to consume data produced;
+* Consider using message queues or pipes for intensive byte transfers.
+
+
+Message delivery sequence
+-------------------------
+
+The listeners (synchronous observers) will follow the channel definition sequence as the notification and message consumption sequence. However, the subscribers, as they have an asynchronous nature, all will receive the notification as the channel definition sequence but only will consume the data when they execute again, so the delivery respects the order, but the priority assigned to the subscribers will define the reaction sequence. All the listeners (static o dynamic) will receive the message before subscribers receive the notification. The sequence of delivery is: (i) static listeners; (ii) runtime listeners; (iii) static subscribers; at last (iv) runtime subscribers.
+
+Implementation
+**************
+
+Zbus operation depends on channels and observers. Therefore, it is necessary to determine its message and observers list during the channel definition. A message is a regular C struct; the observer can be a subscriber (asynchronous) or a listener (synchronous). Channels can have a ``validator function`` that enables a channel to accept only valid messages.
+
+The following code defines and initializes a regular channel and its dependencies. This channel exchanges accelerometer data, for example.
+
+.. code-block:: c
+
+ struct acc_msg {
+ int x;
+ int y;
+ int z;
+ };
+
+ ZBUS_CHAN_DEFINE(acc_chan, /* Name */
+ struct acc_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User Data */
+ ZBUS_OBSERVERS(my_listener, my_subscriber), /* observers */
+ ZBUS_MSG_INIT(.x = 0, .y = 0, .z = 0) /* Initial value {0} */
+ );
+
+ void listener_callback_example(const struct zbus_channel *chan)
+ {
+ const struct acc_msg *acc;
+ if (&acc_chan == chan) {
+ acc = zbus_chan_const_msg(chan); // Direct message access
+ LOG_DBG("From listener -> Acc x=%d, y=%d, z=%d", acc->x, acc->y, acc->z);
+ }
+ }
+
+ ZBUS_LISTENER_DEFINE(my_listener, listener_callback_example);
+
+ ZBUS_SUBSCRIBER_DEFINE(my_subscriber, 4);
+ void subscriber_task(void)
+ {
+ const struct zbus_channel *chan;
+
+ while (!zbus_sub_wait(&my_subscriber, &chan, K_FOREVER)) {
+ struct acc_msg acc = {0};
+
+ if (&acc_chan == chan) {
+ // Indirect message access
+ zbus_chan_read(&acc_chan, &acc, K_NO_WAIT);
+ LOG_DBG("From subscriber -> Acc x=%d, y=%d, z=%d", acc.x, acc.y, acc.z);
+ }
+ }
+ }
+ K_THREAD_DEFINE(subscriber_task_id, 512, subscriber_task, NULL, NULL, NULL, 3, 0, 0);
+
+
+.. note::
+ It is unnecessary to claim/lock a channel before accessing the message inside the listener since the event dispatcher calls listeners with the notifying channel already locked. Subscribers, however, must claim/lock that or use regular read operations to access the message after being notified.
+
+The following code defines and initializes a :dfn:`hard channel` and its dependencies. Only valid messages can be published to a :dfn:`hard channel`. It is possible because a ``Validator function`` passed to the channel's definition. In this example, only messages with ``move`` equal to 0, -1, and 1 are valid. Publish function will discard all other values to ``move``.
+
+.. code-block:: c
+
+ struct control_msg {
+ int move;
+ };
+
+ bool control_validator(const void* msg, size_t msg_size) {
+ const struct control_msg* cm = msg;
+ bool is_valid = (cm->move == -1) || (cm->move == 0) || (cm->move == 1);
+ return is_valid;
+ }
+
+ static int message_count = 0;
+
+ ZBUS_CHAN_DEFINE(control_chan, /* Name */
+ struct control_msg, /* Message type */
+
+ control_validator, /* Validator */
+ &message_count, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(.move = 0) /* Initial value {.move=0} */
+ );
+
+The following sections describe in detail how to use zbus features.
+
+
+Publishing to a channel
+=======================
+
+Messages are published to a channel in zbus by calling :c:func:`zbus_chan_pub`. For example, the following code builds on the examples above and publishes to channel ``acc_chan``. The code is trying to publish the message ``acc1`` to channel ``acc_chan``, and it will wait up to one second for the message to be published. Otherwise, the operation fails.
+
+.. code-block:: c
+
+ struct acc_msg acc1 = {.x = 1, .y = 1, .z = 1};
+ zbus_chan_pub(&acc_chan, &acc1, K_SECONDS(1));
+
+.. warning::
+ Do not use this function inside an ISR.
+
+Reading from a channel
+======================
+
+Messages are read from a channel in zbus by calling :c:func:`zbus_chan_read`. So, for example, the following code tries to read the channel ``acc_chan``, which will wait up to 500 milliseconds to read the message. Otherwise, the operation fails.
+
+.. code-block:: c
+
+ struct acc_msg acc = {0};
+ zbus_chan_read(&acc_chan, &acc, K_MSEC(500));
+
+.. warning::
+ Do not use this function inside an ISR.
+
+Forcing channel notification
+============================
+
+It is possible to force zbus to notify a channel's observers by calling :c:func:`zbus_chan_notify`. For example, the following code builds on the examples above and forces a notification for the channel ``acc_chan``. Note this can send events with no message, which does not require any data exchange.
+
+.. code-block:: c
+
+ zbus_chan_notify(&acc_chan, K_NO_WAIT);
+
+.. warning::
+ Do not use this function inside an ISR.
+
+Declaring channels and observers
+================================
+
+For accessing channels or observers from files other than its defining files, it is necessary to declare them by calling :c:macro:`ZBUS_CHAN_DECLARE` and :c:macro:`ZBUS_OBS_DECLARE`. It is possible to declare more than one channel or observer at the same call. The following code builds on the examples above and displays the defined channels and observers.
+
+.. code-block:: c
+
+ ZBUS_OBS_DECLARE(my_listener, my_subscriber);
+ ZBUS_CHAN_DECLARE(acc_chan, version_chan);
+
+
+Iterating over channels and observers
+=====================================
+
+There is an iterator mechanism in zbus that enables the developer to execute some procedure per channel and observer. The sequence executed is sorted by channel or observer name.
+
+.. code-block:: c
+
+ int count;
+
+ bool print_channel_data_iterator(const struct zbus_channel *chan)
+ {
+ LOG_DBG("%d - Channel %s:", count, zbus_chan_name(chan));
+ LOG_DBG(" Message size: %d", zbus_chan_msg_size(chan));
+ ++count;
+ LOG_DBG(" Observers:");
+ for (struct zbus_observer **obs = chan->observers; *obs != NULL; ++obs) {
+ LOG_DBG(" - %s", zbus_obs_name(*obs));
+ }
+ return true;
+ }
+
+ bool print_observer_data_iterator(const struct zbus_observer *obs)
+ {
+ LOG_DBG("%d - %s %s", count, ((obs->queue != NULL) ? "Subscriber" : "Listener"), zbus_obs_name(obs));
+ ++count;
+ return true;
+ }
+ void main(void)
+ {
+ LOG_DBG("Channel list:");
+ count = 0;
+ zbus_iterate_over_channels(print_channel_data_iterator);
+
+ LOG_DBG("Observers list:");
+ count = 0;
+ zbus_iterate_over_observers(print_observer_data_iterator);
+ }
+
+The code will log the following output:
+
+.. code-block:: console
+
+ D: Channel list:
+ D: 0 - Channel acc_chan:
+ D: Message size: 12
+ D: Observers:
+ D: - my_listener
+ D: - my_subscriber
+ D: 1 - Channel version_chan:
+ D: Message size: 4
+ D: Observers:
+ D: Observers list:
+ D: 0 - Listener my_listener
+ D: 1 - Subscriber my_subscriber
+
+
+Advanced channel control
+========================
+
+Zbus was designed to be as flexible and extensible as possible. Thus there are some features designed to provide some control and extensibility to the bus.
+
+Listeners message access
+------------------------
+
+For performance purposes, listeners can access the receiving channel message directly since they already have the mutex lock for it. To access the channel's message, the listener should use the ``zbus_chan_const_msg`` because the channel passed as an argument to the listener function is a constant pointer to the channel. The const pointer ensures that the message will be kept unchanged during the notification process.
+
+.. code-block:: c
+
+ void listener_callback_example(const struct zbus_channel *chan)
+ {
+ const struct acc_msg *acc;
+ if (&acc_chan == chan) {
+ acc = zbus_chan_const_msg(chan); // Use this
+ // instead of zbus_chan_read(chan, &acc, K_MSEC(200))
+ // or zbus_chan_msg(chan)
+
+ LOG_DBG("From listener -> Acc x=%d, y=%d, z=%d", acc->x, acc->y, acc->z);
+ }
+ }
+
+User Data
+---------
+
+There is a possibility of injecting data into the channel's metadata by passing the ``user_data`` pointer to the channel's definition macro. The ``user_data`` field enables others to access the data. Note that it needs to be set individually for each channel.
+
+Claim and finish a channel
+--------------------------
+
+To take more control over channels, two function were added :c:func:`zbus_chan_claim` and :c:func:`zbus_chan_finish`. With these functions, it is possible to access the channel's metadata safely. When a channel is claimed, no actions are available to that channel. After finishing the channel, all the actions are available again.
+
+.. warning::
+ Never change the fields of the channel struct directly. It may cause zbus behavior inconsistencies and concurrency issues.
+
+The following code builds on the examples above and claims the ``acc_chan`` to set the ``user_data`` to the channel. Suppose we would like to count how many times the channels exchange messages. We defined the ``user_data`` to have the 32 bits integer. This code could be added to the listener code described above.
+
+.. code-block:: c
+
+ if (!zbus_chan_claim(&acc_chan, K_MSEC(200))) {
+ int *message_counting = (int *) zbus_chan_user_data(acc_chan);
+ *message_counting += 1;
+ zbus_chan_finish(&acc_chan);
+ }
+
+.. warning::
+ Do not use these functions inside an ISR.
+
+
+Runtime observer registration
+-----------------------------
+
+It is possible to add observers to channels in runtime. This feature uses the object pool pattern technique in which the dynamic nodes are pre-allocated and can be used and recycled. Therefore, it is necessary to set the pool size by changing the feature :kconfig:option:`CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE` to enable this feature. Furthermore, it uses memory slabs. When necessary, turn on the :kconfig:option:`CONFIG_MEM_SLAB_TRACE_MAX_UTILIZATION` configuration to track the maximum usage of the pool. The following example illustrates the runtime registration usage.
+
+.. code-block:: c
+
+ ZBUS_LISTENER_DEFINE(my_listener, callback);
+ // ...
+ void thread_entry(void) {
+ // ...
+ /* Adding the observer to channel chan1 */
+ zbus_chan_add_obs(&chan1, &my_listener);
+ /* Removing the observer from channel chan1 */
+ zbus_chan_rm_obs(&chan1, &my_listener);
+
+
+Zbus can only use a limited number of dynamic observers. The configuration option :kconfig:option:`CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE` represents the size of the runtime observers pool (memory slab). Change that to fit the solution needs. Use the :c:func:`k_mem_slab_num_used_get` to verify how many runtime observers slots are available. The function :c:func:`k_mem_slab_max_used_get` will provide information regarding the maximum number of used slots count reached during the execution. Use that to set the appropriate pool size avoiding waste. The following code illustrates how to use that.
+
+.. code-block:: c
+
+ extern struct k_mem_slab _zbus_runtime_obs_pool;
+ uint32_t slots_available = k_mem_slab_num_free_get(&_zbus_runtime_obs_pool);
+ uint32_t max_usage = k_mem_slab_max_used_get(&_zbus_runtime_obs_pool);
+
+
+.. warning::
+ Do not use ``_zbus_runtime_obs_pool`` memory slab directly. It may lead to inconsistencies.
+
+Samples
+*******
+
+For a complete overview of zbus usage, take a look at the samples. There are the following samples available:
+
+* :ref:`zbus-hello-world-sample` illustrates the code used above in action;
+* :ref:`zbus-work-queue-sample` shows how to define and use different kinds of observers. Note there is an example of using a work queue instead of executing the listener as an execution option;
+* :ref:`zbus-dyn-channel-sample` demonstrates how to use dynamically allocated exchanging data in zbus;
+* :ref:`zbus-uart-bridge-sample` shows an example of sending the operation of the channel to a host via serial;
+* :ref:`zbus-remote-mock-sample` illustrates how to implement an external mock (on the host) to send and receive messages to and from the bus.
+* :ref:`zbus-runtime-obs-registration-sample` illustrates a way of using the runtime observer registration feature;
+* :ref:`zbus-benchmark-sample` implements a benchmark with different combinations of inputs.
+
+Suggested Uses
+**************
+
+Use zbus to transfer data (messages) between threads in one-to-one, one-to-many, and many-to-many synchronously or asynchronously.
+
+.. note::
+ Zbus can be used to transfer streams from the producer to the consumer. However, this can increase zbus' communication latency. So maybe consider a Pipe a good alternative for this communication topology.
+
+Configuration Options
+*********************
+
+For enabling zbus, it is necessary to enable the :kconfig:option:`CONFIG_ZBUS` option.
+
+Related configuration options:
+
+* :kconfig:option:`CONFIG_ZBUS_CHANNEL_NAME`
+* :kconfig:option:`CONFIG_ZBUS_OBSERVER_NAME`
+* :kconfig:option:`CONFIG_ZBUS_STRUCTS_ITERABLE_ACCESS`
+* :kconfig:option:`CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE`
+
+API Reference
+*************
+
+.. doxygengroup:: zbus_apis
diff --git a/include/zephyr/zbus/zbus.h b/include/zephyr/zbus/zbus.h
new file mode 100644
index 0000000000000..75f66e5bd97e4
--- /dev/null
+++ b/include/zephyr/zbus/zbus.h
@@ -0,0 +1,615 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef ZEPHYR_INCLUDE_ZBUS_H_
+#define ZEPHYR_INCLUDE_ZBUS_H_
+
+#include
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Zbus API
+ * @defgroup zbus_apis Zbus APIs
+ * @{
+ */
+
+/**
+ * @brief Type used to represent a channel.
+ *
+ * Every channel has a zbus_channel structure associated used to control the channel
+ * access and usage.
+ */
+struct zbus_channel {
+#if defined(CONFIG_ZBUS_CHANNEL_NAME) || defined(__DOXYGEN__)
+ /** Channel name. */
+ const char *const name;
+#endif
+ /** Message size. Represents the channel's message size. */
+ const uint16_t message_size;
+
+ /** User data available to extend zbus features. The channel must be claimed before
+ * using this field.
+ */
+ void *const user_data;
+
+ /** Message reference. Represents the message's reference that points to the actual
+ * shared memory region.
+ */
+ void *const message;
+
+ /** Message validator. Stores the reference to the function to check the message
+ * validity before actually performing the publishing. No invalid messages can be
+ * published. Every message is valid when this field is empty.
+ */
+ bool (*const validator)(const void *msg, size_t msg_size);
+
+ /** Access control mutex. Points to the mutex used to avoid race conditions
+ * for accessing the channel.
+ */
+ struct k_mutex *mutex;
+#if (CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE > 0) || defined(__DOXYGEN__)
+ /** Dynamic channel observer list. Represents the channel's observers list, it can be empty
+ * or have listeners and subscribers mixed in any sequence. It can be changed in runtime.
+ */
+ sys_slist_t *runtime_observers;
+#endif /* CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE */
+
+ /** Channel observer list. Represents the channel's observers list, it can be empty or
+ * have listeners and subscribers mixed in any sequence.
+ */
+ const struct zbus_observer *const *observers;
+};
+
+/**
+ * @brief Type used to represent an observer.
+ *
+ * Every observer has an representation structure containing the relevant information.
+ * An observer is a code portion interested in some channel. The observer can be notified
+ * synchronously or asynchronously and it is called listener and subscriber respectively.
+ * The observer can be enabled or disabled during runtime by change the enabled boolean
+ * field of the structure. The listeners have a callback function that is executed by the
+ * bus with the index of the changed channel as argument when the notification is sent.
+ * The subscribers have a message queue where the bus enqueues the index of the changed
+ * channel when a notification is sent.
+ *
+ * @see zbus_obs_set_enable function to properly change the observer's enabled field.
+ *
+ */
+struct zbus_observer {
+#if defined(CONFIG_ZBUS_OBSERVER_NAME) || defined(__DOXYGEN__)
+ /** Observer name. */
+ const char *const name;
+#endif
+ /** Enabled flag. Indicates if observer is receiving notification. */
+ bool enabled;
+ /** Observer message queue. It turns the observer into a subscriber. */
+ struct k_msgq *const queue;
+
+ /** Observer callback function. It turns the observer into a listener. */
+ void (*const callback)(const struct zbus_channel *chan);
+};
+
+/** @cond INTERNAL_HIDDEN */
+
+#if defined(CONFIG_ZBUS_ASSERT_MOCK)
+#define _ZBUS_ASSERT(_cond, _fmt, ...) \
+ do { \
+ if (!(_cond)) { \
+ printk("ZBUS ASSERT: "); \
+ printk(_fmt, ##__VA_ARGS__); \
+ printk("\n"); \
+ return -EFAULT; \
+ } \
+ } while (0)
+#else
+#define _ZBUS_ASSERT(_cond, _fmt, ...) __ASSERT(_cond, _fmt, ##__VA_ARGS__)
+#endif
+
+#if defined(CONFIG_ZBUS_CHANNEL_NAME)
+#define ZBUS_CHANNEL_NAME_INIT(_name) .name = #_name,
+#else
+#define ZBUS_CHANNEL_NAME_INIT(_name)
+#endif
+
+#if defined(CONFIG_ZBUS_OBSERVER_NAME)
+#define ZBUS_OBSERVER_NAME_INIT(_name) .name = #_name,
+#define _ZBUS_OBS_NAME(_obs) (_obs)->name
+#else
+#define ZBUS_OBSERVER_NAME_INIT(_name)
+#define _ZBUS_OBS_NAME(_obs) ""
+#endif
+
+#if CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE > 0
+#define ZBUS_RUNTIME_OBSERVERS_LIST_DECL(_slist_name) static sys_slist_t _slist_name
+#define ZBUS_RUNTIME_OBSERVERS_LIST_INIT(_slist_name) .runtime_observers = &_slist_name,
+#else
+#define ZBUS_RUNTIME_OBSERVERS_LIST_DECL(_slist_name)
+#define ZBUS_RUNTIME_OBSERVERS_LIST_INIT(_slist_name) /* No runtime observers */
+#endif
+
+#if defined(CONFIG_ZBUS_STRUCTS_ITERABLE_ACCESS)
+#define _ZBUS_STRUCT_DECLARE(_type, _name) STRUCT_SECTION_ITERABLE(_type, _name)
+#else
+#define _ZBUS_STRUCT_DECLARE(_type, _name) struct _type _name
+#endif /* CONFIG_ZBUS_STRUCTS_ITERABLE_ACCESS */
+
+#define _ZBUS_OBS_EXTERN(_name) extern struct zbus_observer _name
+
+#define _ZBUS_CHAN_EXTERN(_name) extern const struct zbus_channel _name
+
+#define ZBUS_REF(_value) &(_value)
+
+k_timeout_t _zbus_timeout_remainder(uint64_t end_ticks);
+/** @endcond */
+
+/**
+ * @def ZBUS_OBS_DECLARE
+ * This macro list the observers to be used in a file. Internally, it declares the observers with
+ * the extern statement. Note it is only necessary when the observers are declared outside the file.
+ */
+#define ZBUS_OBS_DECLARE(...) FOR_EACH(_ZBUS_OBS_EXTERN, (;), __VA_ARGS__)
+
+/**
+ * @def ZBUS_CHAN_DECLARE
+ * This macro list the channels to be used in a file. Internally, it declares the channels with the
+ * extern statement. Note it is only necessary when the channels are declared outside the file.
+ */
+#define ZBUS_CHAN_DECLARE(...) FOR_EACH(_ZBUS_CHAN_EXTERN, (;), __VA_ARGS__)
+
+/**
+ * @def ZBUS_OBSERVERS_EMPTY
+ * This macro indicates the channel has no observers.
+ */
+#define ZBUS_OBSERVERS_EMPTY
+
+/**
+ * @def ZBUS_OBSERVERS
+ * This macro indicates the channel has listed observers. Note the sequence of observer notification
+ * will follow the same as listed.
+ */
+#define ZBUS_OBSERVERS(...) __VA_ARGS__
+
+/**
+ * @brief Zbus channel definition.
+ *
+ * This macro defines a channel.
+ *
+ * @param _name The channel's name.
+ * @param _type The Message type. It must be a struct or union.
+ * @param _validator The validator function.
+ * @param _user_data A pointer to the user data.
+ *
+ * @see struct zbus_channel
+ * @param _observers The observers list. The sequence indicates the priority of the observer. The
+ * first the highest priority.
+ * @param _init_val The message initialization.
+ */
+#define ZBUS_CHAN_DEFINE(_name, _type, _validator, _user_data, _observers, _init_val) \
+ static _type _CONCAT(_zbus_message_, _name) = _init_val; \
+ static K_MUTEX_DEFINE(_CONCAT(_zbus_mutex_, _name)); \
+ ZBUS_RUNTIME_OBSERVERS_LIST_DECL(_CONCAT(_runtime_observers_, _name)); \
+ FOR_EACH_NONEMPTY_TERM(_ZBUS_OBS_EXTERN, (;), _observers) \
+ static const struct zbus_observer *const _CONCAT(_zbus_observers_, _name)[] = { \
+ FOR_EACH_NONEMPTY_TERM(ZBUS_REF, (,), _observers) NULL}; \
+ const _ZBUS_STRUCT_DECLARE(zbus_channel, _name) = { \
+ ZBUS_CHANNEL_NAME_INIT(_name) /* Name */ \
+ .message_size = sizeof(_type), /* Message size */ \
+ .user_data = _user_data, /* User data */ \
+ .message = &_CONCAT(_zbus_message_, _name), /* Reference to the message */\
+ .validator = (_validator), /* Validator function */ \
+ .mutex = &_CONCAT(_zbus_mutex_, _name), /* Channel's Mutex */ \
+ ZBUS_RUNTIME_OBSERVERS_LIST_INIT( \
+ _CONCAT(_runtime_observers_, _name)) /* Runtime observer list */ \
+ .observers = _CONCAT(_zbus_observers_, _name)} /* Static observer list */
+
+/**
+ * @brief Initialize a message.
+ *
+ * This macro initializes a message by passing the values to initialize the message struct
+ * or union.
+ *
+ * @param[in] _val Variadic with the initial values. ``ZBUS_INIT(0)`` means ``{0}``, as
+ * ZBUS_INIT(.a=10, .b=30) means ``{.a=10, .b=30}``.
+ */
+#define ZBUS_MSG_INIT(_val, ...) \
+ { \
+ _val, ##__VA_ARGS__ \
+ }
+
+/**
+ * @brief Define and initialize a subscriber.
+ *
+ * This macro defines an observer of subscriber type. It defines a message queue where the
+ * subscriber will receive the notification asynchronously, and initialize the ``struct
+ * zbus_observer`` defining the subscriber.
+ *
+ * @param[in] _name The subscriber's name.
+ * @param[in] _queue_size The notification queue's size.
+ */
+#define ZBUS_SUBSCRIBER_DEFINE(_name, _queue_size) \
+ K_MSGQ_DEFINE(_zbus_observer_queue_##_name, sizeof(const struct zbus_channel *), \
+ _queue_size, sizeof(const struct zbus_channel *)); \
+ _ZBUS_STRUCT_DECLARE(zbus_observer, \
+ _name) = {ZBUS_OBSERVER_NAME_INIT(_name) /* Name field */ \
+ .enabled = true, \
+ .queue = &_zbus_observer_queue_##_name, .callback = NULL}
+
+/**
+ * @brief Define and initialize a listener.
+ *
+ * This macro defines an observer of listener type. This macro establishes the callback where the
+ * listener will be notified synchronously, and initialize the ``struct zbus_observer`` defining the
+ * listener.
+ *
+ * @param[in] _name The listener's name.
+ * @param[in] _cb The callback function.
+ */
+#define ZBUS_LISTENER_DEFINE(_name, _cb) \
+ _ZBUS_STRUCT_DECLARE(zbus_observer, \
+ _name) = {ZBUS_OBSERVER_NAME_INIT(_name) /* Name field */ \
+ .enabled = true, \
+ .queue = NULL, .callback = (_cb)}
+
+/**
+ *
+ * @brief Publish to a channel
+ *
+ * This routine publishes a message to a channel.
+ *
+ * @param chan The channel's reference.
+ * @param msg Reference to the message where the publish function copies the channel's
+ * message data from.
+ * @param timeout Waiting period to publish the channel,
+ * or one of the special values K_NO_WAIT and K_FOREVER.
+ *
+ * @retval 0 Channel published.
+ * @retval -ENOMSG The message is invalid based on the validator function or some of the
+ * observers could not receive the notification.
+ * @retval -EBUSY The channel is busy.
+ * @retval -EAGAIN Waiting period timed out.
+ * @retval -EFAULT A parameter is incorrect, the notification could not be sent to one or more
+ * observer, or the function context is invalid (inside an ISR). The function only returns this
+ * value when the CONFIG_ZBUS_ASSERT_MOCK is enabled.
+ */
+int zbus_chan_pub(const struct zbus_channel *chan, const void *msg, k_timeout_t timeout);
+
+/**
+ * @brief Read a channel
+ *
+ * This routine reads a message from a channel.
+ *
+ * @param[in] chan The channel's reference.
+ * @param[out] msg Reference to the message where the read function copies the channel's
+ * message data to.
+ * @param[in] timeout Waiting period to read the channel,
+ * or one of the special values K_NO_WAIT and K_FOREVER.
+ *
+ * @retval 0 Channel read.
+ * @retval -EBUSY The channel is busy.
+ * @retval -EAGAIN Waiting period timed out.
+ * @retval -EFAULT A parameter is incorrect, or the function context is invalid (inside an ISR). The
+ * function only returns this value when the CONFIG_ZBUS_ASSERT_MOCK is enabled.
+ */
+int zbus_chan_read(const struct zbus_channel *chan, void *msg, k_timeout_t timeout);
+
+/**
+ * @brief Claim a channel
+ *
+ * This routine claims a channel. During the claiming period the channel is blocked for publishing,
+ * reading, notifying or claiming again. Finishing is the only available action.
+ *
+ * @warning After calling this routine, the channel cannot be used by other
+ * thread until the zbus_chan_finish routine is performed.
+ *
+ * @warning This routine should only be called once before a zbus_chan_finish.
+ *
+ * @param[in] chan The channel's reference.
+ * @param[in] timeout Waiting period to claim the channel,
+ * or one of the special values K_NO_WAIT and K_FOREVER.
+ *
+ * @retval 0 Channel claimed.
+ * @retval -EBUSY The channel is busy.
+ * @retval -EAGAIN Waiting period timed out.
+ * @retval -EFAULT A parameter is incorrect, or the function context is invalid (inside an ISR). The
+ * function only returns this value when the CONFIG_ZBUS_ASSERT_MOCK is enabled.
+ */
+int zbus_chan_claim(const struct zbus_channel *chan, k_timeout_t timeout);
+
+/**
+ * @brief Finish a channel claim.
+ *
+ * This routine finishes a channel claim. After calling this routine with success, the channel will
+ * be able to be used by other thread.
+ *
+ * @warning This routine must only be used after a zbus_chan_claim.
+ *
+ * @param chan The channel's reference.
+ *
+ * @retval 0 Channel finished.
+ * @retval -EPERM The channel was claimed by other thread.
+ * @retval -EINVAL The channel's mutex is not locked.
+ * @retval -EFAULT A parameter is incorrect, or the function context is invalid (inside an ISR). The
+ * function only returns this value when the CONFIG_ZBUS_ASSERT_MOCK is enabled.
+ */
+int zbus_chan_finish(const struct zbus_channel *chan);
+
+/**
+ * @brief Force a channel notification.
+ *
+ * This routine forces the event dispatcher to notify the channel's observers even if the message
+ * has no changes. Note this function could be useful after claiming/finishing actions.
+ *
+ * @param chan The channel's reference.
+ * @param timeout Waiting period to notify the channel,
+ * or one of the special values K_NO_WAIT and K_FOREVER.
+ *
+ * @retval 0 Channel notified.
+ * @retval -EPERM The current thread does not own the channel.
+ * @retval -EBUSY The channel's mutex returned without waiting.
+ * @retval -EAGAIN Timeout to acquiring the channel's mutex.
+ * @retval -EFAULT A parameter is incorrect, the notification could not be sent to one or more
+ * observer, or the function context is invalid (inside an ISR). The function only returns this
+ * value when the CONFIG_ZBUS_ASSERT_MOCK is enabled.
+ */
+int zbus_chan_notify(const struct zbus_channel *chan, k_timeout_t timeout);
+
+#if defined(CONFIG_ZBUS_CHANNEL_NAME) || defined(__DOXYGEN__)
+
+/**
+ * @brief Get the channel's name.
+ *
+ * This routine returns the channel's name reference.
+ *
+ * @param chan The channel's reference.
+ *
+ * @return Channel's name reference.
+ */
+static inline const char *zbus_chan_name(const struct zbus_channel *chan)
+{
+ __ASSERT(chan != NULL, "chan is required");
+
+ return chan->name;
+}
+
+#endif
+
+/**
+ * @brief Get the reference for a channel message directly.
+ *
+ * This routine returns the reference of a channel message.
+ *
+ * @warning This function must only be used directly for acquired (locked by mutex) channels. This
+ * can be done inside a listener for the receiving channel or after claim a channel.
+ *
+ * @param chan The channel's reference.
+ *
+ * @return Channel's message reference.
+ */
+static inline void *zbus_chan_msg(const struct zbus_channel *chan)
+{
+ __ASSERT(chan != NULL, "chan is required");
+
+ return chan->message;
+}
+
+/**
+ * @brief Get a constant reference for a channel message directly.
+ *
+ * This routine returns a constant reference of a channel message. This should be used
+ * inside listeners to access the message directly. In this way zbus prevents the listener of
+ * changing the notifying channel's message during the notification process.
+ *
+ * @warning This function must only be used directly for acquired (locked by mutex) channels. This
+ * can be done inside a listener for the receiving channel or after claim a channel.
+ *
+ * @param chan The channel's constant reference.
+ *
+ * @return A constant channel's message reference.
+ */
+static inline const void *zbus_chan_const_msg(const struct zbus_channel *chan)
+{
+ __ASSERT(chan != NULL, "chan is required");
+
+ return chan->message;
+}
+
+/**
+ * @brief Get the channel's message size.
+ *
+ * This routine returns the channel's message size.
+ *
+ * @param chan The channel's reference.
+ *
+ * @return Channel's message size.
+ */
+static inline uint16_t zbus_chan_msg_size(const struct zbus_channel *chan)
+{
+ __ASSERT(chan != NULL, "chan is required");
+
+ return chan->message_size;
+}
+
+/**
+ * @brief Get the channel's user data.
+ *
+ * This routine returns the channel's user data.
+ *
+ * @param chan The channel's reference.
+ *
+ * @return Channel's user data.
+ */
+static inline void *zbus_chan_user_data(const struct zbus_channel *chan)
+{
+ __ASSERT(chan != NULL, "chan is required");
+
+ return chan->user_data;
+}
+
+#if (CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE > 0) || defined(__DOXYGEN__)
+
+/**
+ * @brief Add an observer to a channel.
+ *
+ * This routine adds an observer to the channel.
+ *
+ * @param chan The channel's reference.
+ * @param obs The observer's reference to be added.
+ * @param timeout Waiting period to add an observer,
+ * or one of the special values K_NO_WAIT and K_FOREVER.
+ *
+ * @retval 0 Observer added to the channel.
+ * @retval -EALREADY The observer is already present in the channel's runtime observers list.
+ * @retval -ENOMEM Returned without waiting.
+ * @retval -EAGAIN Waiting period timed out.
+ * @retval -EINVAL Some parameter is invalid.
+ */
+int zbus_chan_add_obs(const struct zbus_channel *chan, const struct zbus_observer *obs,
+ k_timeout_t timeout);
+
+/**
+ * @brief Remove an observer from a channel.
+ *
+ * This routine removes an observer to the channel.
+ *
+ * @param chan The channel's reference.
+ * @param obs The observer's reference to be removed.
+ * @param timeout Waiting period to remove an observer,
+ * or one of the special values K_NO_WAIT and K_FOREVER.
+ *
+ * @retval 0 Observer removed to the channel.
+ * @retval -EINVAL Invalid data supplied.
+ * @retval -EBUSY Returned without waiting.
+ * @retval -EAGAIN Waiting period timed out.
+ * @retval -ENODATA no observer found in channel's runtime observer list.
+ * @retval -ENOMEM Returned without waiting.
+ */
+int zbus_chan_rm_obs(const struct zbus_channel *chan, const struct zbus_observer *obs,
+ k_timeout_t timeout);
+
+/**
+ * @brief Get zbus runtime observers pool.
+ *
+ * This routine returns a reference of the runtime observers pool.
+ *
+ * @return Reference of runtime observers pool.
+ */
+struct k_mem_slab *zbus_runtime_obs_pool(void);
+
+/** @cond INTERNAL_HIDDEN */
+
+struct zbus_observer_node {
+ sys_snode_t node;
+ const struct zbus_observer *obs;
+};
+
+/** @endcond */
+
+#endif /* CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE */
+
+/**
+ * @brief Change the observer state.
+ *
+ * This routine changes the observer state. A channel when disabled will not receive
+ * notifications from the event dispatcher.
+ *
+ * @param[in] obs The observer's reference.
+ * @param[in] enabled State to be. When false the observer stops to receive notifications.
+ *
+ * @retval 0 Observer set enable.
+ * @retval -EFAULT A parameter is incorrect, or the function context is invalid (inside an ISR). The
+ * function only returns this value when the CONFIG_ZBUS_ASSERT_MOCK is enabled.
+ */
+static inline int zbus_obs_set_enable(struct zbus_observer *obs, bool enabled)
+{
+ _ZBUS_ASSERT(obs != NULL, "obs is required");
+
+ obs->enabled = enabled;
+
+ return 0;
+}
+
+#if defined(CONFIG_ZBUS_OBSERVER_NAME) || defined(__DOXYGEN__)
+
+/**
+ * @brief Get the observer's name.
+ *
+ * This routine returns the observer's name reference.
+ *
+ * @param obs The observer's reference.
+ *
+ * @return The observer's name reference.
+ */
+static inline const char *zbus_obs_name(const struct zbus_observer *obs)
+{
+ __ASSERT(obs != NULL, "obs is required");
+
+ return obs->name;
+}
+
+#endif
+
+/**
+ * @brief Wait for a channel notification.
+ *
+ * This routine makes the subscriber to wait a notification. The notification comes as a channel
+ * reference.
+ *
+ * @param[in] sub The subscriber's reference.
+ * @param[out] chan The notification channel's reference.
+ * @param[in] timeout Waiting period for a notification arrival,
+ * or one of the special values K_NO_WAIT and K_FOREVER.
+ *
+ * @retval 0 Notification received.
+ * @retval -ENOMSG Returned without waiting.
+ * @retval -EAGAIN Waiting period timed out.
+ * @retval -EINVAL The observer is not a subscriber.
+ * @retval -EFAULT A parameter is incorrect, or the function context is invalid (inside an ISR). The
+ * function only returns this value when the CONFIG_ZBUS_ASSERT_MOCK is enabled.
+ */
+int zbus_sub_wait(const struct zbus_observer *sub, const struct zbus_channel **chan,
+ k_timeout_t timeout);
+
+#if defined(CONFIG_ZBUS_STRUCTS_ITERABLE_ACCESS) || defined(__DOXYGEN__)
+/**
+ *
+ * @brief Iterate over channels.
+ *
+ * Enables the developer to iterate over the channels giving to this function an
+ * iterator_func which is called for each channel. If the iterator_func returns false all
+ * the iteration stops.
+ *
+ * @retval true Iterator executed for all channels.
+ * @retval false Iterator could not be executed. Some iterate returned false.
+ */
+bool zbus_iterate_over_channels(bool (*iterator_func)(const struct zbus_channel *chan));
+
+/**
+ *
+ * @brief Iterate over observers.
+ *
+ * Enables the developer to iterate over the observers giving to this function an
+ * iterator_func which is called for each observer. If the iterator_func returns false all
+ * the iteration stops.
+ *
+ * @retval true Iterator executed for all channels.
+ * @retval false Iterator could not be executed. Some iterate returned false.
+ */
+bool zbus_iterate_over_observers(bool (*iterator_func)(const struct zbus_observer *obs));
+
+#endif /* CONFIG_ZBUS_STRUCTS_ITERABLE_ACCESS */
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZEPHYR_INCLUDE_ZBUS_H_ */
diff --git a/samples/subsys/zbus/benchmark/CMakeLists.txt b/samples/subsys/zbus/benchmark/CMakeLists.txt
new file mode 100644
index 0000000000000..e66cd1e4ace6b
--- /dev/null
+++ b/samples/subsys/zbus/benchmark/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(benchmark)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/samples/subsys/zbus/benchmark/Kconfig b/samples/subsys/zbus/benchmark/Kconfig
new file mode 100644
index 0000000000000..2fdeb3ff76b0b
--- /dev/null
+++ b/samples/subsys/zbus/benchmark/Kconfig
@@ -0,0 +1,16 @@
+# Copyright (c) 2022 Rodrigo Peixoto
+# SPDX-License-Identifier: Apache-2.0
+
+config BM_MESSAGE_SIZE
+ int "Message size"
+ default 256
+
+config BM_ONE_TO
+ int "Number of consumers"
+ default 8
+
+config BM_ASYNC
+ bool "Consuming in asynchronous mode"
+ default false
+
+source "Kconfig.zephyr"
diff --git a/samples/subsys/zbus/benchmark/README.rst b/samples/subsys/zbus/benchmark/README.rst
new file mode 100644
index 0000000000000..3994420049133
--- /dev/null
+++ b/samples/subsys/zbus/benchmark/README.rst
@@ -0,0 +1,132 @@
+.. _zbus-benchmark-sample:
+
+Benchmark sample
+################
+
+This sample implements an application to measure the time for sending 256KB from the producer to the consumers.
+
+Building and Running
+********************
+
+.. zephyr-app-commands::
+ :zephyr-app: samples/subsys/zbus/dyn_channel
+ :host-os: unix
+ :board: qemu_cortex_m3
+ :gen-args: -DCONFIG_BM_MESSAGE_SIZE=1 -DCONFIG_BM_ONE_TO=1 -DCONFIG_BM_ASYNC=0
+ :goals: build run
+
+Notice we have the following parameters:
+
+* **CONFIG_BM_MESSAGE_SIZE** the size of the message to be transferred;
+* **CONFIG_BM_ONE_TO** number of consumers to send;
+* **CONFIG_BM_ASYNC** if the execution must be asynchronous or synchronous. Use y to async and n to sync;
+
+Sample Output
+=============
+The result would be something like:
+
+.. code-block:: console
+
+ *** Booting Zephyr OS build zephyr-v3.2.0 ***
+ I: Benchmark 1 to 8: Dynamic memory, ASYNC transmission and message size 256
+ I: Bytes sent = 262144, received = 262144
+ I: Average data rate: 1872457.14B/s
+ I: Duration: 140ms
+
+ @140
+
+
+Running the benchmark automatically
+===================================
+
+There is a Robot script called ``benchmark_256KB.robot`` which runs all the input combinations as the complete benchmark.
+The resulting file, ''zbus_dyn_benchmark_256KB.csv`` is generated in the project root folder. It takes a long time to execute. In the CSV file, we have the following columns:
+
++------------+---------------------+--------------------------+---------------+-------------+-------------+
+| Style | Number of consumers | Message size (bytes) | Duration (ms) | RAM (bytes) | ROM (bytes) |
++============+=====================+==========================+===============+=============+=============+
+| SYNC/ASYNC | 1,2,4,8 | 1,2,4,8,16,32,64,128,256 | float | int | int |
++------------+---------------------+--------------------------+---------------+-------------+-------------+
+
+The complete benchmark command using Robot framework is:
+
+.. code-block:: console
+
+ robot --variable serial_port:/dev/ttyACM0 -d /tmp/benchmark_out benchmark_256KB.robot
+
+An example of execution using the ``hifive1_revb`` board would generate a file like this:
+
+.. code-block::
+
+ SYNC,1,1,8534.0,6856,17434
+ SYNC,1,2,4469.0,6856,17440
+ SYNC,1,4,2362.3333333333335,6856,17438
+ SYNC,1,8,1307.6666666666667,6864,17448
+ SYNC,1,16,768.6666666666666,6872,17478
+ SYNC,1,32,492.0,6888,17506
+ SYNC,1,64,391.0,6920,17540
+ SYNC,1,128,321.0,6984,17600
+ SYNC,1,256,258.0,7112,17758
+ SYNC,2,1,4856.666666666667,6880,17490
+ SYNC,2,2,2464.0,6880,17494
+ SYNC,2,4,1367.0,6880,17494
+ SYNC,2,8,778.6666666666666,6888,17504
+ SYNC,2,16,477.0,6896,17534
+ SYNC,2,32,321.0,6912,17562
+ SYNC,2,64,266.0,6944,17592
+ SYNC,2,128,203.0,7008,17662
+ SYNC,2,256,188.0,7136,17814
+ SYNC,4,1,3021.3333333333335,6920,17536
+ SYNC,4,2,1505.3333333333333,6920,17542
+ SYNC,4,4,860.0,6920,17542
+ SYNC,4,8,521.3333333333334,6928,17552
+ SYNC,4,16,328.0,6936,17582
+ SYNC,4,32,227.0,6952,17606
+ SYNC,4,64,180.0,6984,17646
+ SYNC,4,128,164.0,7048,17710
+ SYNC,4,256,149.0,7176,17854
+ SYNC,8,1,2044.3333333333333,7000,17632
+ SYNC,8,2,1002.6666666666666,7000,17638
+ SYNC,8,4,586.0,7000,17638
+ SYNC,8,8,383.0,7008,17648
+ SYNC,8,16,250.0,7016,17674
+ SYNC,8,32,203.0,7032,17708
+ SYNC,8,64,156.0,7064,17742
+ SYNC,8,128,141.0,7128,17806
+ SYNC,8,256,133.0,7256,17958
+ ASYNC,1,1,22187.666666666668,7312,17776
+ ASYNC,1,2,11424.666666666666,7312,17782
+ ASYNC,1,4,5823.0,7312,17778
+ ASYNC,1,8,3071.0,7312,17790
+ ASYNC,1,16,1625.0,7328,17832
+ ASYNC,1,32,966.3333333333334,7344,17862
+ ASYNC,1,64,578.0,7376,17896
+ ASYNC,1,128,403.6666666666667,7440,17956
+ ASYNC,1,256,352.0,7568,18126
+ ASYNC,2,1,18547.333333333332,7792,18030
+ ASYNC,2,2,9563.0,7792,18034
+ ASYNC,2,4,4831.0,7792,18030
+ ASYNC,2,8,2497.3333333333335,7792,18044
+ ASYNC,2,16,1377.6666666666667,7824,18098
+ ASYNC,2,32,747.3333333333334,7856,18132
+ ASYNC,2,64,492.0,7920,18162
+ ASYNC,2,128,321.0,8048,18232
+ ASYNC,2,256,239.33333333333334,8304,18408
+ ASYNC,4,1,16823.0,8744,18474
+ ASYNC,4,2,8604.333333333334,8744,18480
+ ASYNC,4,4,4325.666666666667,8744,18472
+ ASYNC,4,8,2258.0,8744,18490
+ ASYNC,4,16,1198.3333333333333,8808,18572
+ ASYNC,4,32,696.0,8872,18610
+ ASYNC,4,64,430.0,9000,18650
+ ASYNC,4,128,289.0,9256,18714
+ ASYNC,4,256,227.0,9768,18906
+ ASYNC,8,1,15976.666666666666,10648,19366
+ ASYNC,8,2,7929.666666666667,10648,19372
+ ASYNC,8,4,4070.6666666666665,10648,19356
+ ASYNC,8,8,2158.6666666666665,10648,19382
+ ASYNC,8,16,1119.6666666666667,10776,19506
+ ASYNC,8,32,619.6666666666666,10904,19566
+ ASYNC,8,64,391.0,11160,19600
+ ASYNC,8,128,273.0,11672,19686
+ ASYNC,8,256,211.0,12696,19934
diff --git a/samples/subsys/zbus/benchmark/benchmark_256KB.robot b/samples/subsys/zbus/benchmark/benchmark_256KB.robot
new file mode 100644
index 0000000000000..ef637f358f139
--- /dev/null
+++ b/samples/subsys/zbus/benchmark/benchmark_256KB.robot
@@ -0,0 +1,83 @@
+*** Settings ***
+Library Process
+Library String
+Library SerialLibrary
+Library CSVLibrary
+
+Suite Teardown Terminate All Processes kill=True
+
+
+*** Variables ***
+${csv_file} zbus_dyn_benchmark_256kb.csv
+${board} hifive1_revb
+${serial_port} /dev/ttyACM0
+
+*** Tasks ***
+Clear Old CSV File
+ Empty Csv File ${csv_file}
+
+Zbus Benchmark
+ FOR ${async} IN "n" "y"
+ FOR ${consumers} IN 1 2 4 8
+ FOR ${msg_size} IN 1 2 4 8 16 32 64 128 256
+ Benchmark Report For message_size=${msg_size} one_to=${consumers} asynchronous=${async}
+ END
+ END
+ END
+
+
+*** Keywords ***
+Run Memory Report
+ [Arguments] ${type}
+ ${result} Run Process west build -t ${type}_report shell=True
+ Should Be Equal As Integers ${result.rc} 0
+ ${mem} Get Substring ${result.stdout} -20
+ ${mem} Strip String ${mem}
+ ${mem} Convert To Integer ${mem}
+ RETURN ${mem}
+
+Measure Results
+ ${total} Set Variable 0
+ Add Port ${serial_port} timeout=120 baudrate=115200
+ Set Encoding ascii
+ FOR ${count} IN RANGE 3
+ ${result} Run Process west flash shell=True
+ Should Be Equal As Integers ${result.rc} 0
+ ${val} Read Until expected=@ encoding=ascii
+ ${val} Read Until encoding=ascii
+ ${val} Strip String ${val}
+ ${val} Convert To Integer ${val}
+ ${total} Evaluate ${total}+${val}
+ END
+ ${duration} Evaluate ${total}/3.0
+ RETURN ${duration}
+ [Teardown] Delete All Ports
+
+Benchmark
+ [Arguments] ${message_size}=256 ${one_to}=1 ${asynchronous}=n
+ ${result} Run Process
+ ... west build -b ${board} -p always -- -DCONFIG_BM_MESSAGE_SIZE\=${message_size} -DCONFIG_BM_ONE_TO\=${one_to} -DCONFIG_BM_ASYNC\=${asynchronous}
+ ... shell=True
+ Should Be Equal As Integers ${result.rc} 0
+ ${duration} Measure Results
+ RETURN ${duration}
+
+Benchmark Report For
+ [Arguments] ${message_size}=256 ${one_to}=1 ${asynchronous}=n
+ ${duration} Benchmark message_size=${message_size} one_to=${one_to} asynchronous=${asynchronous}
+ ${ram_amount} Run Memory Report ram
+ ${rom_amount} Run Memory Report rom
+ IF ${asynchronous} == "y"
+ ${async_str} Set Variable ASYNC
+ ELSE
+ ${async_str} Set Variable SYNC
+ END
+ @{results} Create List
+ ... ${async_str}
+ ... ${one_to}
+ ... ${message_size}
+ ... ${duration}
+ ... ${ram_amount}
+ ... ${rom_amount}
+ Log To Console \n${results}
+ Append To Csv File ${csv_file} ${results}
diff --git a/samples/subsys/zbus/benchmark/prj.conf b/samples/subsys/zbus/benchmark/prj.conf
new file mode 100644
index 0000000000000..cb828e646a1c6
--- /dev/null
+++ b/samples/subsys/zbus/benchmark/prj.conf
@@ -0,0 +1,6 @@
+CONFIG_LOG=y
+CONFIG_LOG_MODE_MINIMAL=y
+CONFIG_HEAP_MEM_POOL_SIZE=1024
+CONFIG_ASSERT=n
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_INF=y
diff --git a/samples/subsys/zbus/benchmark/sample.yaml b/samples/subsys/zbus/benchmark/sample.yaml
new file mode 100644
index 0000000000000..495baa4f79dd5
--- /dev/null
+++ b/samples/subsys/zbus/benchmark/sample.yaml
@@ -0,0 +1,41 @@
+sample:
+ name: Benchmark
+tests:
+ sample.zbus.benchmark_async:
+ tags: zbus
+ min_ram: 16
+ filter: CONFIG_SYS_CLOCK_EXISTS
+ harness: console
+ harness_config:
+ type: multi_line
+ ordered: true
+ regex:
+ - "I: Benchmark 1 to 8: Dynamic memory, ASYNC transmission and message size 256"
+ - "I: Bytes sent = 262144, received = 262144"
+ - "I: Average data rate: (.*)B/s"
+ - "I: Duration: (.*)ms"
+ - "@(.*)"
+ extra_configs:
+ - CONFIG_BM_ONE_TO=8
+ - CONFIG_BM_MESSAGE_SIZE=256
+ - CONFIG_BM_ASYNC=y
+ platform_exclude: qemu_x86 qemu_x86_64 qemu_riscv32_smp native_posix native_posix_64 qemu_riscv32_smp qemu_cortex_a53_smp qemu_riscv64_smp qemu_leon3 qemu_xtensa qemu_cortex_a53 qemu_riscv32 qemu_malta qemu_malta_be qemu_arc_hs6x qemu_riscv64 m2gl025_miv hifive_unleashed
+ sample.zbus.benchmark_sync:
+ tags: zbus
+ min_ram: 16
+ filter: CONFIG_SYS_CLOCK_EXISTS
+ harness: console
+ harness_config:
+ type: multi_line
+ ordered: true
+ regex:
+ - "I: Benchmark 1 to 8: Dynamic memory, SYNC transmission and message size 256"
+ - "I: Bytes sent = 262144, received = 262144"
+ - "I: Average data rate: (.*)B/s"
+ - "I: Duration: (.*)ms"
+ - "@(.*)"
+ extra_configs:
+ - CONFIG_BM_ONE_TO=8
+ - CONFIG_BM_MESSAGE_SIZE=256
+ - CONFIG_BM_ASYNC=n
+ platform_exclude: qemu_x86 qemu_x86_64 qemu_riscv32_smp native_posix native_posix_64 qemu_riscv32_smp qemu_cortex_a53_smp qemu_riscv64_smp qemu_leon3 qemu_xtensa qemu_cortex_a53 qemu_riscv32 m2gl025_miv m2gl025_miv
diff --git a/samples/subsys/zbus/benchmark/src/benchmark.c b/samples/subsys/zbus/benchmark/src/benchmark.c
new file mode 100644
index 0000000000000..2b9498018d567
--- /dev/null
+++ b/samples/subsys/zbus/benchmark/src/benchmark.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_CHAN_DEFINE(bm_channel, /* Name */
+ struct external_data_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(s1
+
+#if (CONFIG_BM_ONE_TO >= 2LLU)
+ ,
+ s2
+#if (CONFIG_BM_ONE_TO > 2LLU)
+ ,
+ s3, s4
+#if (CONFIG_BM_ONE_TO > 4LLU)
+ ,
+ s5, s6, s7, s8
+#if (CONFIG_BM_ONE_TO > 8LLU)
+ ,
+ s9, s10, s11, s12, s13, s14, s15, s16
+#endif
+#endif
+#endif
+#endif
+ ), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value {0} */
+);
+
+#define BYTES_TO_BE_SENT (256LLU * 1024LLU)
+static uint64_t count;
+
+#if (CONFIG_BM_ASYNC == 1)
+ZBUS_SUBSCRIBER_DEFINE(s1, 4);
+#if (CONFIG_BM_ONE_TO >= 2LLU)
+ZBUS_SUBSCRIBER_DEFINE(s2, 4);
+#if (CONFIG_BM_ONE_TO > 2LLU)
+ZBUS_SUBSCRIBER_DEFINE(s3, 4);
+ZBUS_SUBSCRIBER_DEFINE(s4, 4);
+#if (CONFIG_BM_ONE_TO > 4LLU)
+ZBUS_SUBSCRIBER_DEFINE(s5, 4);
+ZBUS_SUBSCRIBER_DEFINE(s6, 4);
+ZBUS_SUBSCRIBER_DEFINE(s7, 4);
+ZBUS_SUBSCRIBER_DEFINE(s8, 4);
+#if (CONFIG_BM_ONE_TO > 8LLU)
+ZBUS_SUBSCRIBER_DEFINE(s9, 4);
+ZBUS_SUBSCRIBER_DEFINE(s10, 4);
+ZBUS_SUBSCRIBER_DEFINE(s11, 4);
+ZBUS_SUBSCRIBER_DEFINE(s12, 4);
+ZBUS_SUBSCRIBER_DEFINE(s13, 4);
+ZBUS_SUBSCRIBER_DEFINE(s14, 4);
+ZBUS_SUBSCRIBER_DEFINE(s15, 4);
+ZBUS_SUBSCRIBER_DEFINE(s16, 4);
+#endif
+#endif
+#endif
+#endif
+
+#define S_TASK(name) \
+ void name##_task(void) \
+ { \
+ struct external_data_msg *actual_message_data; \
+ const struct zbus_channel *chan; \
+ struct bm_msg msg_received; \
+ \
+ while (!zbus_sub_wait(&name, &chan, K_FOREVER)) { \
+ zbus_chan_claim(chan, K_NO_WAIT); \
+ \
+ actual_message_data = zbus_chan_msg(chan); \
+ __ASSERT_NO_MSG(actual_message_data->reference != NULL); \
+ \
+ memcpy(&msg_received, actual_message_data->reference, \
+ sizeof(struct bm_msg)); \
+ \
+ zbus_chan_finish(chan); \
+ \
+ count += CONFIG_BM_MESSAGE_SIZE; \
+ } \
+ } \
+ \
+ K_THREAD_DEFINE(name##_id, CONFIG_BM_MESSAGE_SIZE + 196, name##_task, NULL, NULL, NULL, 3, \
+ 0, 0);
+
+S_TASK(s1)
+#if (CONFIG_BM_ONE_TO >= 2LLU)
+S_TASK(s2)
+#if (CONFIG_BM_ONE_TO > 2LLU)
+S_TASK(s3)
+S_TASK(s4)
+#if (CONFIG_BM_ONE_TO > 4LLU)
+S_TASK(s5)
+S_TASK(s6)
+S_TASK(s7)
+S_TASK(s8)
+#if (CONFIG_BM_ONE_TO > 8LLU)
+S_TASK(s9)
+S_TASK(s10)
+S_TASK(s11)
+S_TASK(s12)
+S_TASK(s13)
+S_TASK(s14)
+S_TASK(s15)
+S_TASK(s16)
+#endif
+#endif
+#endif
+#endif
+
+#else /* SYNC */
+
+static void s_cb(const struct zbus_channel *chan);
+
+ZBUS_LISTENER_DEFINE(s1, s_cb);
+
+#if (CONFIG_BM_ONE_TO >= 2LLU)
+ZBUS_LISTENER_DEFINE(s2, s_cb);
+#if (CONFIG_BM_ONE_TO > 2LLU)
+ZBUS_LISTENER_DEFINE(s3, s_cb);
+ZBUS_LISTENER_DEFINE(s4, s_cb);
+#if (CONFIG_BM_ONE_TO > 4LLU)
+ZBUS_LISTENER_DEFINE(s5, s_cb);
+ZBUS_LISTENER_DEFINE(s6, s_cb);
+ZBUS_LISTENER_DEFINE(s7, s_cb);
+ZBUS_LISTENER_DEFINE(s8, s_cb);
+#if (CONFIG_BM_ONE_TO > 8LLU)
+ZBUS_LISTENER_DEFINE(s9, s_cb);
+ZBUS_LISTENER_DEFINE(s10, s_cb);
+ZBUS_LISTENER_DEFINE(s11, s_cb);
+ZBUS_LISTENER_DEFINE(s12, s_cb);
+ZBUS_LISTENER_DEFINE(s13, s_cb);
+ZBUS_LISTENER_DEFINE(s14, s_cb);
+ZBUS_LISTENER_DEFINE(s15, s_cb);
+ZBUS_LISTENER_DEFINE(s16, s_cb);
+#endif
+#endif
+#endif
+#endif
+
+static void s_cb(const struct zbus_channel *chan)
+{
+ struct bm_msg msg_received;
+ const struct external_data_msg *actual_message_data = zbus_chan_const_msg(chan);
+
+ memcpy(&msg_received, actual_message_data->reference, sizeof(struct bm_msg));
+
+ count += CONFIG_BM_MESSAGE_SIZE;
+}
+
+#endif /* CONFIG_BM_ASYNC */
+
+static void producer_thread(void)
+{
+ LOG_INF("Benchmark 1 to %d: Dynamic memory, %sSYNC transmission and message size %u",
+ CONFIG_BM_ONE_TO, IS_ENABLED(CONFIG_BM_ASYNC) ? "A" : "", CONFIG_BM_MESSAGE_SIZE);
+
+ struct bm_msg msg;
+ struct external_data_msg *actual_message_data;
+
+ for (uint64_t i = (CONFIG_BM_MESSAGE_SIZE - 1); i > 0; --i) {
+ msg.bytes[i] = i;
+ }
+
+ zbus_chan_claim(&bm_channel, K_NO_WAIT);
+
+ actual_message_data = zbus_chan_msg(&bm_channel);
+ actual_message_data->reference = k_malloc(sizeof(struct bm_msg));
+ __ASSERT_NO_MSG(actual_message_data->reference != NULL);
+ actual_message_data->size = sizeof(struct bm_msg);
+ __ASSERT_NO_MSG(actual_message_data->size > 0);
+
+ zbus_chan_finish(&bm_channel);
+
+ uint32_t start = k_uptime_get_32();
+
+ for (uint64_t internal_count = BYTES_TO_BE_SENT / CONFIG_BM_ONE_TO; internal_count > 0;
+ internal_count -= CONFIG_BM_MESSAGE_SIZE) {
+ zbus_chan_claim(&bm_channel, K_NO_WAIT);
+
+ actual_message_data = zbus_chan_msg(&bm_channel);
+
+ memcpy(actual_message_data->reference, &msg, CONFIG_BM_MESSAGE_SIZE);
+
+ zbus_chan_finish(&bm_channel);
+
+ zbus_chan_notify(&bm_channel, K_MSEC(200));
+ }
+ uint32_t duration = (k_uptime_get_32() - start);
+
+ if (duration == 0) {
+ LOG_ERR("Something wrong. Duration is zero!\n");
+ k_oops();
+ }
+ uint64_t i = (BYTES_TO_BE_SENT * 1000) / duration;
+ uint64_t f = ((BYTES_TO_BE_SENT * 100000) / duration) % 100;
+
+ LOG_INF("Bytes sent = %lld, received = %llu", BYTES_TO_BE_SENT, count);
+ LOG_INF("Average data rate: %llu.%lluB/s", i, f);
+ LOG_INF("Duration: %ums", duration);
+
+ printk("\n@%u\n", duration);
+}
+
+K_THREAD_DEFINE(producer_thread_id, 1024, producer_thread, NULL, NULL, NULL, 5, 0, 0);
diff --git a/samples/subsys/zbus/benchmark/src/messages.h b/samples/subsys/zbus/benchmark/src/messages.h
new file mode 100644
index 0000000000000..e25654d63efd3
--- /dev/null
+++ b/samples/subsys/zbus/benchmark/src/messages.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef _MESSAGES_H_
+#define _MESSAGES_H_
+#include
+#include
+
+struct external_data_msg {
+ void *reference;
+ size_t size;
+};
+
+struct bm_msg {
+ uint8_t bytes[CONFIG_BM_MESSAGE_SIZE];
+};
+
+#endif /* _MESSAGES_H_ */
diff --git a/samples/subsys/zbus/dyn_channel/CMakeLists.txt b/samples/subsys/zbus/dyn_channel/CMakeLists.txt
new file mode 100644
index 0000000000000..b2c71cd7e0d35
--- /dev/null
+++ b/samples/subsys/zbus/dyn_channel/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(dyn_channel)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/samples/subsys/zbus/dyn_channel/README.rst b/samples/subsys/zbus/dyn_channel/README.rst
new file mode 100644
index 0000000000000..190c5422cd791
--- /dev/null
+++ b/samples/subsys/zbus/dyn_channel/README.rst
@@ -0,0 +1,84 @@
+.. _zbus-dyn-channel-sample:
+
+Dynamic channel sample
+######################
+
+Overview
+********
+This sample implements an application using zbus to illustrate the way zbus supports dynamically allocated channels.
+
+Building and Running
+********************
+
+This project outputs to the console. It can be built and executed
+on QEMU as follows:
+
+.. zephyr-app-commands::
+ :zephyr-app: samples/subsys/zbus/dyn_channel
+ :host-os: unix
+ :board: qemu_x86
+ :goals: run
+
+Sample Output
+=============
+
+.. code-block:: console
+
+ W: size=01
+ W: Content
+ W: 00 |.
+ W: size=02
+ W: Content
+ W: 01 01 |..
+ W: size=03
+ W: Content
+ W: 00 00 00 |...
+ W: size=04
+ W: Content
+ W: 03 03 03 03 |....
+ W: size=05
+ W: Content
+ W: 00 00 00 00 00 |.....
+ W: size=06
+ W: Content
+ W: 05 05 05 05 05 05 |......
+ W: size=07
+ W: Content
+ W: 00 00 00 00 00 00 00 |.......
+ W: size=08
+ W: Content
+ W: 07 07 07 07 07 07 07 07 |........
+ W: size=09
+ W: Content
+ W: 00 00 00 00 00 00 00 00 |........
+ W: 00 |.
+ W: size=10
+ W: Content
+ W: 09 09 09 09 09 09 09 09 |........
+ W: 09 09 |..
+ W: size=11
+ W: Content
+ W: 00 00 00 00 00 00 00 00 |........
+ W: 00 00 00 |...
+ W: size=12
+ W: Content
+ W: 0b 0b 0b 0b 0b 0b 0b 0b |........
+ W: 0b 0b 0b 0b |....
+ W: size=13
+ W: Content
+ W: 00 00 00 00 00 00 00 00 |........
+ W: 00 00 00 00 00 |.....
+ W: size=14
+ W: Content
+ W: 0d 0d 0d 0d 0d 0d 0d 0d |........
+ W: 0d 0d 0d 0d 0d 0d |......
+ W: size=15
+ W: Content
+ W: 00 00 00 00 00 00 00 00 |........
+ W: 00 00 00 00 00 00 00 |.......
+ W: size=16
+ W: Content
+ W: 0f 0f 0f 0f 0f 0f 0f 0f |........
+ W: 0f 0f 0f 0f 0f 0f 0f 0f |........
+
+Exit QEMU by pressing :kbd:`CTRL+A` :kbd:`x`.
diff --git a/samples/subsys/zbus/dyn_channel/prj.conf b/samples/subsys/zbus/dyn_channel/prj.conf
new file mode 100644
index 0000000000000..011bc66dcf0e2
--- /dev/null
+++ b/samples/subsys/zbus/dyn_channel/prj.conf
@@ -0,0 +1,9 @@
+CONFIG_LOG=y
+CONFIG_LOG_MODE_MINIMAL=y
+CONFIG_ASSERT=y
+CONFIG_HEAP_MEM_POOL_SIZE=1024
+
+CONFIG_ZBUS=y
+CONFIG_ZBUS_CHANNEL_NAME=y
+CONFIG_ZBUS_LOG_LEVEL_WRN=y
+CONFIG_BOOT_BANNER=n
diff --git a/samples/subsys/zbus/dyn_channel/sample.yaml b/samples/subsys/zbus/dyn_channel/sample.yaml
new file mode 100644
index 0000000000000..f299ac7c58eed
--- /dev/null
+++ b/samples/subsys/zbus/dyn_channel/sample.yaml
@@ -0,0 +1,68 @@
+sample:
+ name: Dynamic channel
+common:
+ tags: zbus
+ harness: console
+ harness_config:
+ type: multi_line
+ ordered: true
+ regex:
+ - "W: size=01"
+ - "W: Content"
+ - "W: 00 |."
+ - "W: size=02"
+ - "W: Content"
+ - "W: 01 01 |.."
+ - "W: size=03"
+ - "W: Content"
+ - "W: 00 00 00 |..."
+ - "W: size=04"
+ - "W: Content"
+ - "W: 03 03 03 03 |...."
+ - "W: size=05"
+ - "W: Content"
+ - "W: 00 00 00 00 00 |....."
+ - "W: size=06"
+ - "W: Content"
+ - "W: 05 05 05 05 05 05 |......"
+ - "W: size=07"
+ - "W: Content"
+ - "W: 00 00 00 00 00 00 00 |......."
+ - "W: size=08"
+ - "W: Content"
+ - "W: 07 07 07 07 07 07 07 07 |........"
+ - "W: size=09"
+ - "W: Content"
+ - "W: 00 00 00 00 00 00 00 00 |........"
+ - "W: 00 |."
+ - "W: size=10"
+ - "W: Content"
+ - "W: 09 09 09 09 09 09 09 09 |........"
+ - "W: 09 09 |.."
+ - "W: size=11"
+ - "W: Content"
+ - "W: 00 00 00 00 00 00 00 00 |........"
+ - "W: 00 00 00 |..."
+ - "W: size=12"
+ - "W: Content"
+ - "W: 0b 0b 0b 0b 0b 0b 0b 0b |........"
+ - "W: 0b 0b 0b 0b |...."
+ - "W: size=13"
+ - "W: Content"
+ - "W: 00 00 00 00 00 00 00 00 |........"
+ - "W: 00 00 00 00 00 |....."
+ - "W: size=14"
+ - "W: Content"
+ - "W: 0d 0d 0d 0d 0d 0d 0d 0d |........"
+ - "W: 0d 0d 0d 0d 0d 0d |......"
+ - "W: size=15"
+ - "W: Content"
+ - "W: 00 00 00 00 00 00 00 00 |........"
+ - "W: 00 00 00 00 00 00 00 |......."
+ - "W: size=16"
+ - "W: Content"
+ - "W: 0f 0f 0f 0f 0f 0f 0f 0f |........"
+ - "W: 0f 0f 0f 0f 0f 0f 0f 0f |........"
+tests:
+ sample.zbus.dyn_channel:
+ tags: zbus
diff --git a/samples/subsys/zbus/dyn_channel/src/consumer.c b/samples/subsys/zbus/dyn_channel/src/consumer.c
new file mode 100644
index 0000000000000..4fe1b8d6cafc0
--- /dev/null
+++ b/samples/subsys/zbus/dyn_channel/src/consumer.c
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_CHAN_DECLARE(pkt_chan, data_ready_chan, ack);
+
+ZBUS_SUBSCRIBER_DEFINE(consumer_sub, 4);
+
+static void consumer_thread(void)
+{
+ struct external_data_msg *dyn_alloc_msg;
+ const struct zbus_channel *chan;
+
+ while (!zbus_sub_wait(&consumer_sub, &chan, K_FOREVER)) {
+ LOG_INF("Channel %s\n", zbus_chan_name(chan));
+
+ __ASSERT_NO_MSG(chan == &data_ready_chan);
+
+ if (!zbus_chan_claim(&pkt_chan, K_MSEC(100))) {
+ dyn_alloc_msg = zbus_chan_msg(&pkt_chan);
+
+ LOG_WRN("size=%02d", (int)dyn_alloc_msg->size);
+ LOG_HEXDUMP_WRN(dyn_alloc_msg->reference, dyn_alloc_msg->size, "Content");
+
+ /* Cleanup the dynamic memory after using that */
+ k_free(dyn_alloc_msg->reference);
+ dyn_alloc_msg->reference = NULL;
+ dyn_alloc_msg->size = 0;
+
+ zbus_chan_finish(&pkt_chan);
+ }
+ struct ack_msg a = {1};
+
+ zbus_chan_pub(&ack, &a, K_MSEC(250));
+ }
+}
+
+K_THREAD_DEFINE(consumer_thread_id, 1024, consumer_thread, NULL, NULL, NULL, 4, 0, 1000);
diff --git a/samples/subsys/zbus/dyn_channel/src/main.c b/samples/subsys/zbus/dyn_channel/src/main.c
new file mode 100644
index 0000000000000..433dcd7bfb8af
--- /dev/null
+++ b/samples/subsys/zbus/dyn_channel/src/main.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+
+#include
+#include
+#include
+
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_CHAN_DEFINE(pkt_chan, /* Name */
+ struct external_data_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(filter_lis), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value {0} */
+);
+
+ZBUS_CHAN_DEFINE(version_chan, /* Name */
+ struct version_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(.major = 0, .minor = 1, .build = 1) /* Initial value {0} */
+);
+
+ZBUS_CHAN_DEFINE(data_ready_chan, /* Name */
+ struct ack_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(consumer_sub), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value {0} */
+);
+
+ZBUS_CHAN_DEFINE(ack, /* Name */
+ struct ack_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(producer_sub), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value {0} */
+);
+
+static void filter_cb(const struct zbus_channel *chan)
+{
+ /* Note that the listener does not change the actual message.
+ * It changes the external dynamic memory area but not the reference and size of the
+ * channel's message struct. Use that with care.
+ */
+
+ const struct external_data_msg *chan_message;
+
+ __ASSERT_NO_MSG(chan == &pkt_chan);
+
+ chan_message = zbus_chan_const_msg(chan);
+
+ if (chan_message->size % 2) {
+ memset(chan_message->reference, 0, chan_message->size);
+ }
+
+ struct ack_msg dr = {1};
+
+ zbus_chan_pub(&data_ready_chan, &dr, K_NO_WAIT);
+}
+
+ZBUS_LISTENER_DEFINE(filter_lis, filter_cb);
+
+void main(void)
+{
+ const struct version_msg *v = zbus_chan_const_msg(&version_chan);
+
+ LOG_DBG(" -> Dynamic channel sample version %u.%u-%u\n\n", v->major, v->minor, v->build);
+}
diff --git a/samples/subsys/zbus/dyn_channel/src/messages.h b/samples/subsys/zbus/dyn_channel/src/messages.h
new file mode 100644
index 0000000000000..e1b0df49fad35
--- /dev/null
+++ b/samples/subsys/zbus/dyn_channel/src/messages.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef _MESSAGES_H_
+#define _MESSAGES_H_
+#include
+#include
+
+struct version_msg {
+ uint8_t major;
+ uint8_t minor;
+ uint16_t build;
+};
+
+struct external_data_msg {
+ void *reference;
+ size_t size;
+};
+
+struct ack_msg {
+ uint8_t value;
+};
+
+#endif /* _MESSAGES_H_ */
diff --git a/samples/subsys/zbus/dyn_channel/src/producer.c b/samples/subsys/zbus/dyn_channel/src/producer.c
new file mode 100644
index 0000000000000..8c91d4ddaeda8
--- /dev/null
+++ b/samples/subsys/zbus/dyn_channel/src/producer.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_CHAN_DECLARE(pkt_chan);
+
+ZBUS_SUBSCRIBER_DEFINE(producer_sub, 4);
+
+static void producer_thread(void)
+{
+ const struct zbus_channel *chan;
+ void *msg;
+
+ for (uint8_t i = 0; (i < 16); ++i) {
+ msg = k_malloc(i + 1);
+
+ __ASSERT_NO_MSG(msg != NULL);
+
+ struct external_data_msg dyn_alloc_msg = {.reference = msg, .size = i + 1};
+
+ for (int j = 0; j < dyn_alloc_msg.size; ++j) {
+ ((uint8_t *)dyn_alloc_msg.reference)[j] = i;
+ }
+
+ zbus_chan_pub(&pkt_chan, &dyn_alloc_msg, K_MSEC(250));
+
+ /* Wait for an consumer's ACK */
+ if (zbus_sub_wait(&producer_sub, &chan, K_FOREVER)) {
+ LOG_ERR("Ack not received!");
+ }
+ }
+}
+
+K_THREAD_DEFINE(producer_thread_id, 1024, producer_thread, NULL, NULL, NULL, 5, 0, 1000);
diff --git a/samples/subsys/zbus/hello_world/CMakeLists.txt b/samples/subsys/zbus/hello_world/CMakeLists.txt
new file mode 100644
index 0000000000000..2a50e1b51349f
--- /dev/null
+++ b/samples/subsys/zbus/hello_world/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(hello_world)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/samples/subsys/zbus/hello_world/README.rst b/samples/subsys/zbus/hello_world/README.rst
new file mode 100644
index 0000000000000..09c897e3241e6
--- /dev/null
+++ b/samples/subsys/zbus/hello_world/README.rst
@@ -0,0 +1,49 @@
+.. _zbus-hello-world-sample:
+
+Hello world sample
+##################
+
+Overview
+********
+This sample implements a simple hello world application using zbus to make the threads talk to each other.
+
+Building and Running
+********************
+
+This project outputs to the console. It can be built and executed
+on QEMU as follows:
+
+.. zephyr-app-commands::
+ :zephyr-app: samples/subsys/zbus/hello_world
+ :host-os: unix
+ :board: qemu_x86
+ :goals: run
+
+Sample Output
+=============
+
+.. code-block:: console
+
+ D: Sensor sample started raw reading, version 0.1-2!
+ D: Channel list:
+ D: 0 - Channel acc_data:
+ D: Message size: 12
+ D: Observers:
+ D: - my_listener
+ D: - my_subscriber
+ D: 1 - Channel version:
+ D: Message size: 4
+ D: Observers:
+ D: Observers list:
+ D: 0 - Listener my_listener
+ D: 1 - Subscriber my_subscriber
+ D: START processing channel acc_data change
+ D: From listener -> Acc x=1, y=1, z=1
+ D: FINISH processing channel acc_data change
+ D: From subscriber -> Acc x=1, y=1, z=1
+ D: START processing channel acc_data change
+ D: From listener -> Acc x=2, y=2, z=2
+ D: FINISH processing channel acc_data change
+ D: From subscriber -> Acc x=2, y=2, z=2
+
+Exit QEMU by pressing :kbd:`CTRL+A` :kbd:`x`.
diff --git a/samples/subsys/zbus/hello_world/prj.conf b/samples/subsys/zbus/hello_world/prj.conf
new file mode 100644
index 0000000000000..c36fc40826fd0
--- /dev/null
+++ b/samples/subsys/zbus/hello_world/prj.conf
@@ -0,0 +1,8 @@
+CONFIG_LOG=y
+CONFIG_LOG_MODE_MINIMAL=y
+CONFIG_BOOT_BANNER=n
+CONFIG_MAIN_THREAD_PRIORITY=5
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_INF=y
+CONFIG_ZBUS_CHANNEL_NAME=y
+CONFIG_ZBUS_OBSERVER_NAME=y
diff --git a/samples/subsys/zbus/hello_world/sample.yaml b/samples/subsys/zbus/hello_world/sample.yaml
new file mode 100644
index 0000000000000..90348385485ff
--- /dev/null
+++ b/samples/subsys/zbus/hello_world/sample.yaml
@@ -0,0 +1,46 @@
+sample:
+ name: Hello World
+tests:
+ sample.zbus.hello_world:
+ harness: console
+ harness_config:
+ type: multi_line
+ ordered: false
+ regex:
+ - "I: Sensor sample started raw reading, version 0.1-2!"
+ - "I: Channel list:"
+ - "I: 0 - Channel acc_data_chan:"
+ - "I: Message size: 12"
+ - "I: Observers:"
+ - "I: - foo_lis"
+ - "I: - bar_sub"
+ - "I: 1 - Channel simple_chan:"
+ - "I: 2 - Channel version_chan:"
+ - "I: Message size: 4"
+ - "I: Observers list:"
+ - "I: 0 - Subscriber bar_sub"
+ - "I: 1 - Listener foo_lis"
+ - "I: From subscriber -> Acc x=1, y=1, z=1"
+ - "I: From listener -> Acc x=1, y=1, z=1"
+ - "I: From subscriber -> Acc x=2, y=2, z=2"
+ - "I: From listener -> Acc x=2, y=2, z=2"
+ - "I: Pub a valid value to a channel with validator successfully."
+ - "I: Pub an invalid value to a channel with validator successfully."
+ arch_exclude: posix xtensa
+ platform_exclude: qemu_leon3
+ tags: zbus
+ sample.zbus.hello_world_no_iterable_sections:
+ harness: console
+ harness_config:
+ type: multi_line
+ ordered: false
+ regex:
+ - "I: Sensor sample started raw reading, version 0.1-2!"
+ - "I: From subscriber -> Acc x=1, y=1, z=1"
+ - "I: From listener -> Acc x=1, y=1, z=1"
+ - "I: From subscriber -> Acc x=2, y=2, z=2"
+ - "I: From listener -> Acc x=2, y=2, z=2"
+ - "I: Pub a valid value to a channel with validator successfully."
+ - "I: Pub an invalid value to a channel with validator successfully."
+ arch_allow: posix xtensa
+ tags: zbus
diff --git a/samples/subsys/zbus/hello_world/src/main.c b/samples/subsys/zbus/hello_world/src/main.c
new file mode 100644
index 0000000000000..64e33e878a4f1
--- /dev/null
+++ b/samples/subsys/zbus/hello_world/src/main.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include
+
+#include
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+struct version_msg {
+ uint8_t major;
+ uint8_t minor;
+ uint16_t build;
+};
+
+struct acc_msg {
+ int x;
+ int y;
+ int z;
+};
+
+ZBUS_CHAN_DEFINE(version_chan, /* Name */
+ struct version_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(.major = 0, .minor = 1,
+ .build = 2) /* Initial value major 0, minor 1, build 2 */
+);
+
+ZBUS_CHAN_DEFINE(acc_data_chan, /* Name */
+ struct acc_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(foo_lis, bar_sub), /* observers */
+ ZBUS_MSG_INIT(.x = 0, .y = 0, .z = 0) /* Initial value */
+);
+
+static bool simple_chan_validator(const void *msg, size_t msg_size)
+{
+ ARG_UNUSED(msg_size);
+
+ const int *simple = msg;
+
+ if ((*simple >= 0) && (*simple < 10)) {
+ return true;
+ }
+
+ return false;
+}
+
+ZBUS_CHAN_DEFINE(simple_chan, /* Name */
+ int, /* Message type */
+
+ simple_chan_validator, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ 0 /* Initial value is 0 */
+);
+
+static void listener_callback_example(const struct zbus_channel *chan)
+{
+ const struct acc_msg *acc = zbus_chan_const_msg(chan);
+
+ LOG_INF("From listener -> Acc x=%d, y=%d, z=%d", acc->x, acc->y, acc->z);
+}
+
+ZBUS_LISTENER_DEFINE(foo_lis, listener_callback_example);
+
+ZBUS_SUBSCRIBER_DEFINE(bar_sub, 4);
+
+static void subscriber_task(void)
+{
+ const struct zbus_channel *chan;
+
+ while (!zbus_sub_wait(&bar_sub, &chan, K_FOREVER)) {
+ struct acc_msg acc;
+
+ if (&acc_data_chan == chan) {
+ zbus_chan_read(&acc_data_chan, &acc, K_MSEC(500));
+
+ LOG_INF("From subscriber -> Acc x=%d, y=%d, z=%d", acc.x, acc.y, acc.z);
+ }
+ }
+}
+
+K_THREAD_DEFINE(subscriber_task_id, 512, subscriber_task, NULL, NULL, NULL, 3, 0, 0);
+
+#if defined(CONFIG_ZBUS_STRUCTS_ITERABLE_ACCESS)
+static int count;
+
+static bool print_channel_data_iterator(const struct zbus_channel *chan)
+{
+ LOG_INF("%d - Channel %s:", count, zbus_chan_name(chan));
+ LOG_INF(" Message size: %d", zbus_chan_msg_size(chan));
+ ++count;
+ LOG_INF(" Observers:");
+ for (const struct zbus_observer *const *obs = chan->observers; *obs != NULL; ++obs) {
+ LOG_INF(" - %s", (*obs)->name);
+ }
+
+ return true;
+}
+
+static bool print_observer_data_iterator(const struct zbus_observer *obs)
+{
+ LOG_INF("%d - %s %s", count, obs->queue ? "Subscriber" : "Listener", zbus_obs_name(obs));
+
+ ++count;
+
+ return true;
+}
+#endif /* CONFIG_ZBUS_STRUCTS_ITERABLE_ACCESS */
+
+void main(void)
+{
+ int err, value;
+ struct acc_msg acc1 = {.x = 1, .y = 1, .z = 1};
+ const struct version_msg *v = zbus_chan_const_msg(&version_chan);
+
+ LOG_INF("Sensor sample started raw reading, version %u.%u-%u!", v->major, v->minor,
+ v->build);
+
+#if defined(CONFIG_ZBUS_STRUCTS_ITERABLE_ACCESS)
+ count = 0;
+
+ LOG_INF("Channel list:");
+ zbus_iterate_over_channels(print_channel_data_iterator);
+
+ count = 0;
+
+ LOG_INF("Observers list:");
+ zbus_iterate_over_observers(print_observer_data_iterator);
+#endif
+ zbus_chan_pub(&acc_data_chan, &acc1, K_SECONDS(1));
+
+ k_msleep(1000);
+
+ acc1.x = 2;
+ acc1.y = 2;
+ acc1.z = 2;
+ zbus_chan_pub(&acc_data_chan, &(acc1), K_SECONDS(1));
+
+ value = 5;
+ err = zbus_chan_pub(&simple_chan, &value, K_MSEC(200));
+
+ if (err == 0) {
+ LOG_INF("Pub a valid value to a channel with validator successfully.");
+ }
+
+ value = 15;
+ err = zbus_chan_pub(&simple_chan, &value, K_MSEC(200));
+
+ if (err == -ENOMSG) {
+ LOG_INF("Pub an invalid value to a channel with validator successfully.");
+ }
+}
diff --git a/samples/subsys/zbus/remote_mock/CMakeLists.txt b/samples/subsys/zbus/remote_mock/CMakeLists.txt
new file mode 100644
index 0000000000000..8f4ba216efef2
--- /dev/null
+++ b/samples/subsys/zbus/remote_mock/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(remote_mock)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/samples/subsys/zbus/remote_mock/README.rst b/samples/subsys/zbus/remote_mock/README.rst
new file mode 100644
index 0000000000000..1afba8879876c
--- /dev/null
+++ b/samples/subsys/zbus/remote_mock/README.rst
@@ -0,0 +1,94 @@
+.. _zbus-remote-mock-sample:
+
+Remote mock sample
+##################
+
+Overview
+********
+
+This application demonstrates how a host script can publish to the zbus in an embedded device using the UART as a bridge.
+The device sends information to the script running on a computer host. Then, the script sends back information when it would simulate a publish to some channel. Finally, the script decodes the data exchanged and prints it to the stdout.
+
+With this approach, developers can implement tests using any language on a computer to talk directly via channels with threads running on a device. Furthermore, it gives the tests more controllability and observability since we can control and access what is sent and received by the script.
+
+Building and Running
+********************
+
+This project outputs to the console. It can be built and executed
+on native_posix as follows:
+
+.. zephyr-app-commands::
+ :zephyr-app: samples/subsys/zbus/remote_mock
+ :host-os: unix
+ :board: native_posix
+ :goals: run
+
+Sample Output
+=============
+
+.. code-block:: console
+
+ -- west build: running target run
+ [0/1] cd /.../zephyr/build/zephyr/zephyr.exe
+ uart_1 connected to pseudotty: /dev/pts/2
+ uart connected to pseudotty: /dev/pts/3
+ *** Booting Zephyr OS build zephyr-v3.1.0 ***
+ D: [Mock Proxy] Started.
+ D: [Mock Proxy RX] Started.
+ D: [Mock Proxy RX] Found sentence
+ D: Channel start_measurement claimed
+ D: Channel start_measurement finished
+ D: Publishing channel start_measurement
+ D: START processing channel start_measurement change
+ D: Channel start_measurement claimed
+ D: discard loopback event (channel start_measurement)
+ D: Channel start_measurement finished
+ D: FINISH processing channel start_measurement change
+ D: START processing channel sensor_data change
+ D: Channel sensor_data claimed
+ D: sending message to host (channel sensor_data)
+ D: Channel sensor_data finished
+ D: FINISH processing channel sensor_data change
+ D: sending sensor data err = 0
+
+
+
+Exit execution by pressing :kbd:`CTRL+C`.
+
+The :file:`remote_mock.py` script can be executed using the following command:
+
+.. code-block:: bash
+
+ python3.8 samples/subsys/zbus/remote_mock/remote_mock.py /dev/pts/2
+
+
+Note the run command above prints the value of pts port because it is running in ``native_posix``. Look at the line indicating ``uart_1 connected to pseudotty: /dev/pts/2``. It can be different in your case. If you are using a board, read the documentation to get the correct port destination (in Linux is something like ``/dev/tty...`` or in Windows ``COM...``).
+
+From the remote mock (Python script), you would see something like this:
+
+.. code-block:: shell
+
+ Proxy PUB [start_measurement] -> start measurement
+ Proxy NOTIFY: [sensor_data] -> sensor value 1
+ Proxy PUB [start_measurement] -> start measurement
+ Proxy NOTIFY: [sensor_data] -> sensor value 2
+ Proxy PUB [start_measurement] -> start measurement
+ Proxy NOTIFY: [sensor_data] -> sensor value 3
+ Proxy PUB [start_measurement] -> start measurement
+ Proxy NOTIFY: [sensor_data] -> sensor value 4
+ Proxy PUB [start_measurement] -> start measurement
+ Proxy NOTIFY: [sensor_data] -> sensor value 5
+ Proxy PUB [start_measurement] -> start measurement
+ Proxy NOTIFY: [sensor_data] -> sensor value 6
+ Proxy PUB [start_measurement] -> start measurement
+ Proxy NOTIFY: [sensor_data] -> sensor value 7
+ Proxy PUB [start_measurement] -> start measurement
+ Proxy NOTIFY: [sensor_data] -> sensor value 8
+ Proxy PUB [start_measurement] -> start measurement
+ Proxy NOTIFY: [sensor_data] -> sensor value 9
+ Proxy PUB [start_measurement] -> start measurement
+ Proxy NOTIFY: [sensor_data] -> sensor value 10
+
+
+
+Exit the remote mock script by pressing :kbd:`CTRL+C`.
diff --git a/samples/subsys/zbus/remote_mock/boards/hifive1_revb.overlay b/samples/subsys/zbus/remote_mock/boards/hifive1_revb.overlay
new file mode 100644
index 0000000000000..acaaa9109b7e1
--- /dev/null
+++ b/samples/subsys/zbus/remote_mock/boards/hifive1_revb.overlay
@@ -0,0 +1,9 @@
+/*
+ * RS232 usart
+ */
+
+&uart1 {
+ status = "okay";
+ current-speed = <115200>;
+ clock-frequency = <16000000>;
+};
diff --git a/samples/subsys/zbus/remote_mock/prj.conf b/samples/subsys/zbus/remote_mock/prj.conf
new file mode 100644
index 0000000000000..3c92c036d3494
--- /dev/null
+++ b/samples/subsys/zbus/remote_mock/prj.conf
@@ -0,0 +1,15 @@
+CONFIG_LOG=y
+CONFIG_LOG_MODE_MINIMAL=n
+CONFIG_THREAD_NAME=y
+CONFIG_ASSERT=y
+
+CONFIG_SERIAL=y
+CONFIG_UART_LINE_CTRL=n
+
+CONFIG_NET_BUF=y
+CONFIG_NET_BUF_LOG=y
+CONFIG_NET_BUF_SIMPLE_LOG=y
+
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
+CONFIG_ZBUS_CHANNEL_NAME=y
diff --git a/samples/subsys/zbus/remote_mock/prj_hifive1_revb.conf b/samples/subsys/zbus/remote_mock/prj_hifive1_revb.conf
new file mode 100644
index 0000000000000..ec97403630f62
--- /dev/null
+++ b/samples/subsys/zbus/remote_mock/prj_hifive1_revb.conf
@@ -0,0 +1,18 @@
+CONFIG_LOG=y
+CONFIG_LOG_MODE_MINIMAL=y
+CONFIG_THREAD_NAME=y
+CONFIG_ASSERT=y
+
+CONFIG_SERIAL=y
+CONFIG_UART_LINE_CTRL=n
+CONFIG_UART_SIFIVE_PORT_1=y
+CONFIG_UART_SIFIVE_PORT_1_TXCNT_IRQ=1
+CONFIG_UART_SIFIVE_PORT_1_RXCNT_IRQ=1
+
+CONFIG_NET_BUF=y
+CONFIG_NET_BUF_LOG=y
+CONFIG_NET_BUF_SIMPLE_LOG=y
+
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
+CONFIG_ZBUS_CHANNEL_NAME=y
diff --git a/samples/subsys/zbus/remote_mock/prj_native_posix.conf b/samples/subsys/zbus/remote_mock/prj_native_posix.conf
new file mode 100644
index 0000000000000..0daf4fb4af716
--- /dev/null
+++ b/samples/subsys/zbus/remote_mock/prj_native_posix.conf
@@ -0,0 +1,17 @@
+CONFIG_LOG=y
+CONFIG_LOG_MODE_MINIMAL=y
+CONFIG_THREAD_NAME=y
+CONFIG_ASSERT=y
+
+CONFIG_SERIAL=y
+CONFIG_UART_LINE_CTRL=n
+CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE=y
+CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME=y
+
+CONFIG_NET_BUF=y
+CONFIG_NET_BUF_LOG=y
+CONFIG_NET_BUF_SIMPLE_LOG=y
+
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
+CONFIG_ZBUS_CHANNEL_NAME=y
diff --git a/samples/subsys/zbus/remote_mock/remote_mock.py b/samples/subsys/zbus/remote_mock/remote_mock.py
new file mode 100755
index 0000000000000..589ef4faf1325
--- /dev/null
+++ b/samples/subsys/zbus/remote_mock/remote_mock.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 Rodrigo Peixoto
+# SPDX-License-Identifier: Apache-2.0
+import serial
+import json
+from time import sleep
+import argparse
+
+j = """
+[
+{"name":"version","on_changed": false, "read_only": true, "message_size": 4},
+{"name":"sensor_data","on_changed": true, "read_only": false, "message_size": 4},
+{"name":"start_measurement","on_changed": false, "read_only": false, "message_size": 1}
+]"""
+
+channels = json.loads(j)
+parser = argparse.ArgumentParser(description='Read zbus events via serial.')
+parser.add_argument("port", type=str, help='The tty or COM port to be used')
+
+args = parser.parse_args()
+
+
+def fetch_sentence(ser):
+ channel_id = int.from_bytes(ser.read(), "little")
+ channel_name = channels[channel_id]['name']
+ msg_size = channels[channel_id]['message_size']
+ msg = ser.read(msg_size)
+ ser.read(1) # skip '*'
+ return (channel_name, msg_size, msg)
+
+
+def pub_start_measurement(ser, action: bool):
+ print(
+ f"Proxy PUB [{channels[2]['name']}] -> start measurement")
+ ser.write(b'$')
+ ser.write(b'\x02') # idx
+ ser.write(b'\x01')
+ ser.write(b'*')
+ ser.flush()
+
+
+ser = serial.Serial(args.port)
+pub_start_measurement(ser, True)
+while True:
+ d = ser.read()
+ if d == b'$':
+ channel_name, msg_size, msg = fetch_sentence(ser)
+ if channel_name == "sensor_data":
+ print(
+ f"Proxy NOTIFY: [{channel_name}] -> sensor value {int.from_bytes(msg, 'little')}")
+ sleep(1)
+ pub_start_measurement(ser, True)
diff --git a/samples/subsys/zbus/remote_mock/sample.yaml b/samples/subsys/zbus/remote_mock/sample.yaml
new file mode 100644
index 0000000000000..e498bb4b61d5a
--- /dev/null
+++ b/samples/subsys/zbus/remote_mock/sample.yaml
@@ -0,0 +1,7 @@
+sample:
+ name: Remote mock
+tests:
+ sample.zbus.remote_mock:
+ build_only: true
+ tags: zbus
+ platform_allow: native_posix hifive1_revb
diff --git a/samples/subsys/zbus/remote_mock/src/messages.h b/samples/subsys/zbus/remote_mock/src/messages.h
new file mode 100644
index 0000000000000..31da5e6a71c27
--- /dev/null
+++ b/samples/subsys/zbus/remote_mock/src/messages.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef _MESSAGES_H_
+#define _MESSAGES_H_
+#include
+#include
+
+struct version_msg {
+ uint8_t major;
+ uint8_t minor;
+ uint16_t build;
+};
+
+struct sensor_data_msg {
+ int value;
+};
+
+struct action_msg {
+ bool status;
+};
+
+#endif /* _MESSAGES_H_ */
diff --git a/samples/subsys/zbus/remote_mock/src/mock_proxy.c b/samples/subsys/zbus/remote_mock/src/mock_proxy.c
new file mode 100644
index 0000000000000..2760d654b7325
--- /dev/null
+++ b/samples/subsys/zbus/remote_mock/src/mock_proxy.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+const static struct device *uart_dev = DEVICE_DT_GET(DT_NODELABEL(uart1));
+
+ZBUS_CHAN_DECLARE(sensor_data_chan);
+
+ZBUS_CHAN_DEFINE(version_chan, /* Name */
+ struct version_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(.major = 0, .minor = 1,
+ .build = 1023) /* Initial value major 0, minor 1, build 1023 */
+);
+
+static bool start_measurement_from_bridge;
+ZBUS_CHAN_DEFINE(start_measurement_chan, /* Name */
+ struct action_msg, /* Message type */
+
+ NULL, /* Validator */
+ &start_measurement_from_bridge, /* User data */
+ ZBUS_OBSERVERS(proxy_lis, peripheral_sub), /* observers */
+ ZBUS_MSG_INIT(false) /* Initial value */
+);
+
+static uint8_t encoder(const struct zbus_channel *chan)
+{
+ if (chan == &sensor_data_chan) {
+ return 1;
+ } else if (chan == &start_measurement_chan) {
+ return 2;
+ }
+ return 0;
+}
+
+static const struct zbus_channel *decoder(uint8_t chan_idx)
+{
+ if (chan_idx == 1) {
+ return &sensor_data_chan;
+ } else if (chan_idx == 2) {
+ return &start_measurement_chan;
+ }
+ return NULL;
+}
+
+static void proxy_callback(const struct zbus_channel *chan)
+{
+ bool *generated_by_the_bridge = zbus_chan_user_data(chan);
+
+ if (*generated_by_the_bridge) {
+ LOG_DBG("discard loopback event (channel %s)", zbus_chan_name(chan));
+
+ *generated_by_the_bridge = false;
+ } else {
+ uart_poll_out(uart_dev, '$');
+
+ uart_poll_out(uart_dev, encoder(chan));
+
+ for (int i = 0; i < zbus_chan_msg_size(chan); ++i) {
+ uart_poll_out(uart_dev, ((unsigned char *)zbus_chan_const_msg(chan))[i]);
+ }
+
+ uart_poll_out(uart_dev, '*');
+
+ LOG_DBG("sending message to host (channel %s)", zbus_chan_name(chan));
+ }
+}
+
+ZBUS_LISTENER_DEFINE(proxy_lis, proxy_callback);
+
+void main(void)
+{
+ LOG_DBG("[Mock Proxy] Started.");
+}
+
+static void decode_sentence(struct net_buf_simple *rx_buf)
+{
+ if (rx_buf->len <= 1) {
+ LOG_DBG("[Mock Proxy RX] Discard invalid sequence");
+ /* '*' indicates the end of a sentence. Sometimes it is
+ * necessary to flush more than on ensure sending it from
+ * the python script. The code must discard when there is no
+ * other data at the buffer.
+ */
+ } else {
+ if ('$' == net_buf_simple_pull_u8(rx_buf)) {
+ LOG_DBG("[Mock Proxy RX] Found sentence");
+
+ const struct zbus_channel *chan = decoder(net_buf_simple_pull_u8(rx_buf));
+
+ __ASSERT_NO_MSG(chan != NULL);
+
+ if (!zbus_chan_claim(chan, K_MSEC(250))) {
+ memcpy(zbus_chan_msg(chan),
+ net_buf_simple_pull_mem(rx_buf, zbus_chan_msg_size(chan)),
+ zbus_chan_msg_size(chan));
+
+ bool *generated_by_the_bridge = zbus_chan_user_data(chan);
+ *generated_by_the_bridge = true;
+
+ zbus_chan_finish(chan);
+
+ LOG_DBG("Publishing channel %s", zbus_chan_name(chan));
+
+ zbus_chan_notify(chan, K_MSEC(500));
+ }
+ }
+ net_buf_simple_init(rx_buf, 0);
+ }
+}
+
+static void mock_proxy_rx_thread(void)
+{
+ LOG_DBG("[Mock Proxy RX] Started.");
+
+ uint8_t byte;
+ struct net_buf_simple *rx_buf = NET_BUF_SIMPLE(64);
+
+ net_buf_simple_init(rx_buf, 0);
+
+ while (1) {
+ while (uart_poll_in(uart_dev, &byte) < 0) {
+ /* Allow other thread/workqueue to work. */
+ k_msleep(50);
+ }
+ if (byte == '*') {
+ decode_sentence(rx_buf);
+ } else {
+ net_buf_simple_add_u8(rx_buf, byte);
+ }
+ }
+}
+
+K_THREAD_DEFINE(mock_proxy_rx_thread_tid, 2048, mock_proxy_rx_thread, NULL, NULL, NULL, 5, 0, 1500);
diff --git a/samples/subsys/zbus/remote_mock/src/peripheral.c b/samples/subsys/zbus/remote_mock/src/peripheral.c
new file mode 100644
index 0000000000000..404fa04e3e946
--- /dev/null
+++ b/samples/subsys/zbus/remote_mock/src/peripheral.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+static bool sensor_from_bridge;
+ZBUS_CHAN_DEFINE(sensor_data_chan, /* Name */
+ struct sensor_data_msg, /* Message type */
+
+ NULL, /* Validator */
+ &sensor_from_bridge, /* Validator */
+ ZBUS_OBSERVERS(proxy_lis), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value {0} */
+);
+
+ZBUS_SUBSCRIBER_DEFINE(peripheral_sub, 8);
+
+static void peripheral_thread(void)
+{
+ const struct zbus_channel *chan;
+ struct sensor_data_msg sd = {0};
+
+ while (!zbus_sub_wait(&peripheral_sub, &chan, K_FOREVER)) {
+ sd.value += 1;
+
+ LOG_DBG("sending sensor data err = %d",
+ zbus_chan_pub(&sensor_data_chan, &sd, K_MSEC(250)));
+ }
+}
+
+K_THREAD_DEFINE(peripheral_thread_id, 1024, peripheral_thread, NULL, NULL, NULL, 5, 0, 0);
diff --git a/samples/subsys/zbus/runtime_obs_registration/CMakeLists.txt b/samples/subsys/zbus/runtime_obs_registration/CMakeLists.txt
new file mode 100644
index 0000000000000..d51d7311f340c
--- /dev/null
+++ b/samples/subsys/zbus/runtime_obs_registration/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(runtime_obs_registration)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/samples/subsys/zbus/runtime_obs_registration/README.rst b/samples/subsys/zbus/runtime_obs_registration/README.rst
new file mode 100644
index 0000000000000..9e59d11dad67d
--- /dev/null
+++ b/samples/subsys/zbus/runtime_obs_registration/README.rst
@@ -0,0 +1,67 @@
+.. _zbus-runtime-obs-registration-sample:
+
+Runtime observer registration sample
+####################################
+
+Overview
+********
+The sample illustrates a way of using the runtime observer registration feature. The developer can understand how to use static and runtime observer registration with this sample.
+
+In this sample, we have the threads producer and consumer. In the middle of the communication, we have the filter responsible for filtering the data generated by the producer. In a loop, the code activates the filter. After 5 seconds, the filter is disabled, and the filter bypass is enabled. At last, 5 seconds later, the filter bypass is disabled, and the loop repeats everything.
+
+Building and Running
+********************
+
+This project outputs to the console. It can be built and executed
+on QEMU as follows:
+
+.. zephyr-app-commands::
+ :zephyr-app: samples/subsys/zbus/runtime_obs_registration
+ :host-os: unix
+ :board: qemu_x86
+ :goals: run
+
+Sample Output
+=============
+
+.. code-block:: console
+
+ I: System started
+ I: Activating filter
+ I: >-- Raw data fetched
+ I: -|- Filtering data
+ I: --> Consuming data: Acc x=0, y=0, z=0
+ I: >-- Raw data fetched
+ I: -|- Filtering data
+ I: --> Consuming data: Acc x=2, y=2, z=2
+ I: >-- Raw data fetched
+ I: -|- Filtering data
+ I: --> Consuming data: Acc x=0, y=0, z=0
+ I: >-- Raw data fetched
+ I: -|- Filtering data
+ I: --> Consuming data: Acc x=4, y=4, z=4
+ I: >-- Raw data fetched
+ I: -|- Filtering data
+ I: --> Consuming data: Acc x=0, y=0, z=0
+ I: Deactivating filter
+ I: Bypass filter
+ I: >-- Raw data fetched
+ I: --> Consuming data: Acc x=6, y=6, z=6
+ I: >-- Raw data fetched
+ I: --> Consuming data: Acc x=7, y=7, z=7
+ I: >-- Raw data fetched
+ I: --> Consuming data: Acc x=8, y=8, z=8
+ I: >-- Raw data fetched
+ I: --> Consuming data: Acc x=9, y=9, z=9
+ I: >-- Raw data fetched
+ I: --> Consuming data: Acc x=10, y=10, z=10
+ I: Disable bypass filter
+ I: >-- Raw data fetched
+ I: >-- Raw data fetched
+ I: >-- Raw data fetched
+ I: >-- Raw data fetched
+ I: >-- Raw data fetched
+
+
+
+Exit QEMU by pressing :kbd:`CTRL+A` :kbd:`x`.
diff --git a/samples/subsys/zbus/runtime_obs_registration/prj.conf b/samples/subsys/zbus/runtime_obs_registration/prj.conf
new file mode 100644
index 0000000000000..af6571cd87241
--- /dev/null
+++ b/samples/subsys/zbus/runtime_obs_registration/prj.conf
@@ -0,0 +1,7 @@
+CONFIG_LOG=y
+CONFIG_ASSERT=y
+CONFIG_LOG_MODE_MINIMAL=y
+CONFIG_BOOT_BANNER=n
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_INF=y
+CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE=2
diff --git a/samples/subsys/zbus/runtime_obs_registration/sample.yaml b/samples/subsys/zbus/runtime_obs_registration/sample.yaml
new file mode 100644
index 0000000000000..80f38737e53d6
--- /dev/null
+++ b/samples/subsys/zbus/runtime_obs_registration/sample.yaml
@@ -0,0 +1,27 @@
+sample:
+ name: Runtime observer registration
+tests:
+ sample.zbus.runtime_os_registration:
+ min_ram: 16
+ platform_exclude: hifive_unleashed qemu_xtensa qemu_cortex_a53_smp qemu_cortex_a53 qemu_riscv32_smp qemu_riscv64_smp qemu_riscv64 qemu_leon3 qemu_x86_64
+ harness: console
+ harness_config:
+ type: multi_line
+ ordered: false
+ regex:
+ - "I: System started"
+ - "I: Activating filter"
+ - "I: Deactivating filter"
+ - "I: Bypass filter"
+ - "I: Disable bypass filter"
+ - "I: >-- Raw data fetched"
+ - "I: -|- Filtering data"
+ - "I: --> Consuming data: Acc x=0, y=0, z=0"
+ - "I: --> Consuming data: Acc x=2, y=2, z=2"
+ - "I: --> Consuming data: Acc x=4, y=4, z=4"
+ - "I: --> Consuming data: Acc x=6, y=6, z=6"
+ - "I: --> Consuming data: Acc x=7, y=7, z=7"
+ - "I: --> Consuming data: Acc x=8, y=8, z=8"
+ - "I: --> Consuming data: Acc x=9, y=9, z=9"
+ - "I: --> Consuming data: Acc x=10, y=10, z=10"
+ tags: zbus
diff --git a/samples/subsys/zbus/runtime_obs_registration/src/consumer.c b/samples/subsys/zbus/runtime_obs_registration/src/consumer.c
new file mode 100644
index 0000000000000..b3b71bb05e319
--- /dev/null
+++ b/samples/subsys/zbus/runtime_obs_registration/src/consumer.c
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_SUBSCRIBER_DEFINE(consumer_sub, 4);
+
+static void consumer_subscriber_thread(void)
+{
+ const struct zbus_channel *chan;
+
+ while (!zbus_sub_wait(&consumer_sub, &chan, K_FOREVER)) {
+ struct sensor_msg acc;
+
+ zbus_chan_read(chan, &acc, K_MSEC(500));
+ LOG_INF(" --> Consuming data: Acc x=%d, y=%d, z=%d", acc.x, acc.y, acc.z);
+ }
+}
+
+K_THREAD_DEFINE(consumer_thread_id, 512, consumer_subscriber_thread, NULL, NULL, NULL, 3, 0, 0);
diff --git a/samples/subsys/zbus/runtime_obs_registration/src/main.c b/samples/subsys/zbus/runtime_obs_registration/src/main.c
new file mode 100644
index 0000000000000..6b2ca2166af38
--- /dev/null
+++ b/samples/subsys/zbus/runtime_obs_registration/src/main.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "messages.h"
+
+#include
+
+#include
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_CHAN_DEFINE(processed_data_chan, struct sensor_msg, NULL, NULL, ZBUS_OBSERVERS(consumer_sub),
+ ZBUS_MSG_INIT(.x = 0, .y = 0, .z = 0));
+
+ZBUS_CHAN_DECLARE(raw_data_chan, state_chan);
+
+static void filter_callback(const struct zbus_channel *chan)
+{
+ const struct sensor_msg *msg = zbus_chan_const_msg(chan);
+ struct sensor_msg proc_msg = {0};
+
+ if (0 == (msg->x % 2)) {
+ proc_msg.x = msg->x;
+ }
+ if (0 == (msg->y % 2)) {
+ proc_msg.y = msg->y;
+ }
+ if (0 == (msg->z % 2)) {
+ proc_msg.z = msg->z;
+ }
+ LOG_INF(" -|- Filtering data");
+ zbus_chan_pub(&processed_data_chan, &proc_msg, K_MSEC(200));
+}
+
+ZBUS_LISTENER_DEFINE(filter_lis, filter_callback);
+
+ZBUS_SUBSCRIBER_DEFINE(state_change_sub, 5);
+
+void main(void)
+{
+ LOG_INF("System started");
+
+ const struct zbus_channel *chan;
+
+ while (1) {
+ LOG_INF("Activating filter");
+ zbus_chan_add_obs(&raw_data_chan, &filter_lis, K_MSEC(200));
+
+ zbus_sub_wait(&state_change_sub, &chan, K_FOREVER);
+
+ LOG_INF("Deactivating filter");
+ zbus_chan_rm_obs(&raw_data_chan, &filter_lis, K_MSEC(200));
+
+ LOG_INF("Bypass filter");
+ zbus_chan_add_obs(&raw_data_chan, &consumer_sub, K_MSEC(200));
+
+ zbus_sub_wait(&state_change_sub, &chan, K_FOREVER);
+
+ LOG_INF("Disable bypass filter");
+ zbus_chan_rm_obs(&raw_data_chan, &consumer_sub, K_MSEC(200));
+
+ zbus_sub_wait(&state_change_sub, &chan, K_FOREVER);
+ }
+}
diff --git a/samples/subsys/zbus/runtime_obs_registration/src/messages.h b/samples/subsys/zbus/runtime_obs_registration/src/messages.h
new file mode 100644
index 0000000000000..5b388fbeb291f
--- /dev/null
+++ b/samples/subsys/zbus/runtime_obs_registration/src/messages.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef _MESSAGES_H_
+#define _MESSAGES_H_
+
+#include
+
+struct sensor_msg {
+ int x;
+ int y;
+ int z;
+};
+
+#endif /* _MESSAGES_H_ */
diff --git a/samples/subsys/zbus/runtime_obs_registration/src/producer.c b/samples/subsys/zbus/runtime_obs_registration/src/producer.c
new file mode 100644
index 0000000000000..c73e35d6fe371
--- /dev/null
+++ b/samples/subsys/zbus/runtime_obs_registration/src/producer.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_CHAN_DEFINE(raw_data_chan, struct sensor_msg, NULL, NULL, ZBUS_OBSERVERS_EMPTY,
+ ZBUS_MSG_INIT(.x = 0, .y = 0, .z = 0));
+
+ZBUS_CHAN_DEFINE(state_chan, bool, NULL, NULL, ZBUS_OBSERVERS(state_change_sub), false);
+
+static void producer_thread(void)
+{
+ struct sensor_msg acc = {0};
+ uint32_t count = 0;
+
+ while (1) {
+ if (count == 5) {
+ bool change_state = true;
+
+ zbus_chan_pub(&state_chan, &change_state, K_MSEC(200));
+ count = 0;
+ }
+
+ ++acc.x;
+ ++acc.y;
+ ++acc.z;
+
+ LOG_INF(" >-- Raw data fetched");
+
+ zbus_chan_pub(&raw_data_chan, &acc, K_MSEC(200));
+
+ k_msleep(500);
+
+ ++count;
+ }
+}
+
+K_THREAD_DEFINE(produce_thread_id, 512, producer_thread, NULL, NULL, NULL, 3, 0, 0);
diff --git a/samples/subsys/zbus/uart_bridge/CMakeLists.txt b/samples/subsys/zbus/uart_bridge/CMakeLists.txt
new file mode 100644
index 0000000000000..d51d7311f340c
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(runtime_obs_registration)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/samples/subsys/zbus/uart_bridge/README.rst b/samples/subsys/zbus/uart_bridge/README.rst
new file mode 100644
index 0000000000000..6b8ba15bd8adb
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/README.rst
@@ -0,0 +1,99 @@
+.. _zbus-uart-bridge-sample:
+
+UART bridge sample
+##################
+
+Overview
+********
+
+This simple application demonstrates a UART redirection of all channel events to the host.
+The device sends information to the script running on a computer host. The script decodes it and prints it to the stdout.
+
+Building and Running
+********************
+
+This project outputs to the console. It can be built and executed
+on native_posix as follows:
+
+.. zephyr-app-commands::
+ :zephyr-app: samples/subsys/zbus/uart_bridge
+ :host-os: unix
+ :board: native_posix
+ :goals: run
+
+Sample Output
+=============
+
+.. code-block:: console
+
+ [0/1] cd .../zephyr/build/zephyr/zephyr.exe
+ uart_1 connected to pseudotty: /dev/pts/2
+ uart connected to pseudotty: /dev/pts/3
+ *** Booting Zephyr OS build zephyr-v3.1.0 ***
+ D: Core sending start measurement with status 0
+ D: START processing channel start_measurement change
+ D: FINISH processing channel start_measurement change
+ D: Peripheral sending sensor data
+ D: START processing channel sensor_data change
+ D: FINISH processing channel sensor_data change
+ D: Bridge Started
+ D: Channel start_measurement claimed
+ D: Channel start_measurement finished
+ D: Bridge send start_measurement
+ D: Channel sensor_data claimed
+ D: Channel sensor_data finished
+ D: Bridge send sensor_data
+ D: Core sending start measurement with status 1
+ D: START processing channel start_measurement change
+ D: FINISH processing channel start_measurement change
+ D: Channel start_measurement claimed
+ D: Channel start_measurement finished
+ D: Bridge send start_measurement
+ D: Peripheral sending sensor data
+ D: START processing channel sensor_data change
+ D: FINISH processing channel sensor_data change
+ D: Channel sensor_data claimed
+ D: Channel sensor_data finished
+ D: Bridge send sensor_data
+
+
+
+Exit execution by pressing :kbd:`CTRL+C`.
+
+The :file:`decoder.py` script can be executed using the following command:
+
+.. code-block:: bash
+
+ python3.8 samples/subsys/zbus/uart_bridge/decoder.py /dev/pts/2
+
+
+Note the run command above prints the value of pts port because it is running in ``native_posix``. Look at the line indicating ``uart_1 connected to pseudotty: /dev/pts/2``. It can be different in your case. If you are using a board, read the documentation to get the correct port destination (in Linux is something like ``/dev/tty...`` or in Windows ``COM...``).
+
+From the serial decoder (Python script), you would see something like this:
+
+.. code-block:: shell
+
+ PUB [sensor_data] -> b'\xc5\x01\x00\x00\xb2\x11\x00\x00'
+ PUB [start_measurement] -> b'\x00'
+ PUB [sensor_data] -> b'\xc6\x01\x00\x00\xbc\x11\x00\x00'
+ PUB [start_measurement] -> b'\x01'
+ PUB [sensor_data] -> b'\xc7\x01\x00\x00\xc6\x11\x00\x00'
+ PUB [start_measurement] -> b'\x00'
+ PUB [sensor_data] -> b'\xc8\x01\x00\x00\xd0\x11\x00\x00'
+ PUB [start_measurement] -> b'\x01'
+ PUB [sensor_data] -> b'\xc9\x01\x00\x00\xda\x11\x00\x00'
+ PUB [start_measurement] -> b'\x00'
+ PUB [sensor_data] -> b'\xca\x01\x00\x00\xe4\x11\x00\x00'
+ PUB [start_measurement] -> b'\x01'
+ PUB [sensor_data] -> b'\xcb\x01\x00\x00\xee\x11\x00\x00'
+ PUB [start_measurement] -> b'\x00'
+ PUB [sensor_data] -> b'\xcc\x01\x00\x00\xf8\x11\x00\x00'
+ PUB [start_measurement] -> b'\x01'
+ PUB [sensor_data] -> b'\xcd\x01\x00\x00\x02\x12\x00\x00'
+ PUB [start_measurement] -> b'\x00'
+ PUB [sensor_data] -> b'\xce\x01\x00\x00\x0c\x12\x00\x00'
+ PUB [start_measurement] -> b'\x01'
+ PUB [sensor_data] -> b'\xcf\x01\x00\x00\x16\x12\x00\x00'
+ PUB [start_measurement] -> b'\x00'
+
+Exit the decoder script by pressing :kbd:`CTRL+C`.
diff --git a/samples/subsys/zbus/uart_bridge/boards/hifive1_revb.overlay b/samples/subsys/zbus/uart_bridge/boards/hifive1_revb.overlay
new file mode 100644
index 0000000000000..cd24c3a29b5d0
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/boards/hifive1_revb.overlay
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+&uart1 {
+ status = "okay";
+ current-speed = <115200>;
+};
diff --git a/samples/subsys/zbus/uart_bridge/boards/nrf52840dk_nrf52840.overlay b/samples/subsys/zbus/uart_bridge/boards/nrf52840dk_nrf52840.overlay
new file mode 100644
index 0000000000000..cd24c3a29b5d0
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/boards/nrf52840dk_nrf52840.overlay
@@ -0,0 +1,8 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+&uart1 {
+ status = "okay";
+ current-speed = <115200>;
+};
diff --git a/samples/subsys/zbus/uart_bridge/decoder.py b/samples/subsys/zbus/uart_bridge/decoder.py
new file mode 100755
index 0000000000000..20c0a6c1d6b5d
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/decoder.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022 Rodrigo Peixoto
+# SPDX-License-Identifier: Apache-2.0
+import serial
+import json
+import argparse
+
+j = """
+[
+ {"name":"version","on_changed": false, "read_only": true, "message_size": 4},
+ {"name":"sensor_data","on_changed": true, "read_only": false, "message_size":8},
+ {"name":"start_measurement","on_changed": false, "read_only": false, "message_size": 1},
+ {"name":"finish","on_changed": false, "read_only": false, "message_size": 1}
+]
+"""
+
+parser = argparse.ArgumentParser(description='Read zbus events via serial.')
+parser.add_argument("port", type=str, help='The tty or COM port to be used')
+
+args = parser.parse_args()
+
+channels = json.loads(j)
+
+
+def fetch_sentence():
+ name = b""
+ while True:
+ b = ser.read(size=1)
+ if b == b",":
+ break
+ name += b
+ print(name)
+ found_msg_size = int.from_bytes(ser.read(size=1), byteorder="little")
+ found_msg = ser.read(found_msg_size)
+ return name, found_msg_size, found_msg
+
+
+try:
+ ser = serial.Serial(args.port)
+ while True:
+ d = ser.read()
+ if d == b'$':
+ channel_name, msg_size, msg = fetch_sentence()
+ print(f"PUB [{channel_name}] -> {msg}")
+except serial.serialutil.SerialException as e:
+ print(e)
diff --git a/samples/subsys/zbus/uart_bridge/prj.conf b/samples/subsys/zbus/uart_bridge/prj.conf
new file mode 100644
index 0000000000000..a7e9366229932
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/prj.conf
@@ -0,0 +1,10 @@
+CONFIG_LOG=y
+CONFIG_LOG_MODE_MINIMAL=y
+CONFIG_THREAD_NAME=y
+
+CONFIG_SERIAL=y
+CONFIG_UART_LINE_CTRL=n
+
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
+CONFIG_ZBUS_CHANNEL_NAME=y
diff --git a/samples/subsys/zbus/uart_bridge/prj_hifive1_revb.conf b/samples/subsys/zbus/uart_bridge/prj_hifive1_revb.conf
new file mode 100644
index 0000000000000..663c0d3ddfc3c
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/prj_hifive1_revb.conf
@@ -0,0 +1,14 @@
+CONFIG_LOG=y
+CONFIG_LOG_MODE_MINIMAL=y
+CONFIG_THREAD_NAME=y
+
+CONFIG_SERIAL=y
+CONFIG_UART_LINE_CTRL=n
+CONFIG_UART_INTERRUPT_DRIVEN=y
+CONFIG_UART_SIFIVE_PORT_1=y
+CONFIG_UART_SIFIVE_PORT_1_TXCNT_IRQ=1
+CONFIG_UART_SIFIVE_PORT_1_RXCNT_IRQ=1
+
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
+CONFIG_ZBUS_CHANNEL_NAME=y
diff --git a/samples/subsys/zbus/uart_bridge/prj_native_posix.conf b/samples/subsys/zbus/uart_bridge/prj_native_posix.conf
new file mode 100644
index 0000000000000..0f973b533c0e9
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/prj_native_posix.conf
@@ -0,0 +1,10 @@
+CONFIG_LOG=y
+CONFIG_LOG_MODE_MINIMAL=y
+CONFIG_THREAD_NAME=y
+CONFIG_SERIAL=y
+CONFIG_UART_LINE_CTRL=n
+CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE=y
+
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
+CONFIG_ZBUS_CHANNEL_NAME=y
diff --git a/samples/subsys/zbus/uart_bridge/prj_native_posix_64.conf b/samples/subsys/zbus/uart_bridge/prj_native_posix_64.conf
new file mode 100644
index 0000000000000..59061f86ae5eb
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/prj_native_posix_64.conf
@@ -0,0 +1,11 @@
+CONFIG_LOG=y
+CONFIG_LOG_MODE_MINIMAL=y
+CONFIG_THREAD_NAME=y
+
+CONFIG_SERIAL=y
+CONFIG_UART_LINE_CTRL=n
+CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE=y
+
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
+CONFIG_ZBUS_CHANNEL_NAME=y
diff --git a/samples/subsys/zbus/uart_bridge/sample.yaml b/samples/subsys/zbus/uart_bridge/sample.yaml
new file mode 100644
index 0000000000000..7a95cb363ee45
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/sample.yaml
@@ -0,0 +1,20 @@
+sample:
+ name: UART bridge
+common:
+ tags: zbus
+ harness: console
+ harness_config:
+ type: multi_line
+ ordered: false
+ regex:
+ - "D: Core sending start measurement with status 0"
+ - "D: Peripheral sending sensor data"
+ - "D: Bridge Started"
+ - "D: Bridge send start_measurement"
+ - "D: Bridge send sensor_data"
+ - "D: Core sending start measurement with status 1"
+
+tests:
+ sample.zbus.uart_bridge_build:
+ tags: zbus
+ filter: dt_nodelabel_enabled("uart1")
diff --git a/samples/subsys/zbus/uart_bridge/src/bridge.c b/samples/subsys/zbus/uart_bridge/src/bridge.c
new file mode 100644
index 0000000000000..65d68e4e183b8
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/src/bridge.c
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_SUBSCRIBER_DEFINE(bridge_sub, 4);
+
+ZBUS_CHAN_DEFINE(finish_chan, /* Name */
+ struct action_msg, /* Message type */
+
+ NULL, /* Validator */
+NULL, /* User data */
+ ZBUS_OBSERVERS(bridge_sub), /* observers */
+ ZBUS_MSG_INIT(false) /* Initial value */
+);
+
+const static struct device *bridge_uart = DEVICE_DT_GET(DT_NODELABEL(uart1));
+
+static void bridge_tx_thread(void)
+{
+ LOG_DBG("Bridge Started");
+
+ const struct zbus_channel *chan;
+
+ while (1) {
+ if (!zbus_sub_wait(&bridge_sub, &chan, K_FOREVER)) {
+ if (!zbus_chan_claim(chan, K_MSEC(500))) {
+ bool *user_data = (bool *)zbus_chan_user_data(chan);
+ bool generated_by_the_bridge = *user_data;
+ *user_data = false;
+
+ zbus_chan_finish(chan);
+
+ /* true here means the package was published by the bridge and must
+ * be discarded
+ */
+ if (!generated_by_the_bridge) {
+ LOG_DBG("Bridge send %s", zbus_chan_name(chan));
+
+ uart_poll_out(bridge_uart, '$');
+
+ for (int i = 0; i < strlen(zbus_chan_name(chan)); ++i) {
+ uart_poll_out(bridge_uart, zbus_chan_name(chan)[i]);
+ }
+
+ uart_poll_out(bridge_uart, ',');
+
+ uart_poll_out(bridge_uart, zbus_chan_msg_size(chan));
+
+ for (int i = 0; i < zbus_chan_msg_size(chan); ++i) {
+ uart_poll_out(bridge_uart,
+ ((uint8_t *)zbus_chan_msg(chan))[i]);
+ }
+
+ uart_poll_out(bridge_uart, '\r');
+
+ uart_poll_out(bridge_uart, '\n');
+ }
+ }
+ }
+ }
+}
+
+K_THREAD_DEFINE(bridge_thread_tid, 2048, bridge_tx_thread, NULL, NULL, NULL, 1, 0, 500);
diff --git a/samples/subsys/zbus/uart_bridge/src/core.c b/samples/subsys/zbus/uart_bridge/src/core.c
new file mode 100644
index 0000000000000..807ddd078ff12
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/src/core.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_CHAN_DECLARE(start_measurement_chan);
+
+ZBUS_CHAN_DEFINE(version_chan, /* Name */
+ struct version_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(.major = 0, .minor = 1,
+ .build = 1023) /* Initial value major 0, minor 1, build 1023 */
+);
+
+static void core_thread(void)
+{
+ struct action_msg start = {false};
+
+ while (1) {
+ LOG_DBG("Core sending start measurement with status %d", start.status);
+
+ start.status = !start.status;
+ zbus_chan_pub(&start_measurement_chan, &start, K_MSEC(500));
+
+ k_msleep(1000);
+ }
+}
+
+K_THREAD_DEFINE(core_thread_id, 1024, core_thread, NULL, NULL, NULL, 3, 0, 0);
diff --git a/samples/subsys/zbus/uart_bridge/src/messages.h b/samples/subsys/zbus/uart_bridge/src/messages.h
new file mode 100644
index 0000000000000..564b98d808a12
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/src/messages.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef _MESSAGES_H_
+#define _MESSAGES_H_
+#include
+#include
+
+struct version_msg {
+ uint8_t major;
+ uint8_t minor;
+ uint16_t build;
+};
+
+struct sensor_data_msg {
+ int a;
+ int b;
+};
+
+struct net_pkt_msg {
+ char x;
+ bool y;
+};
+
+struct action_msg {
+ bool status;
+};
+
+#endif /* _MESSAGES_H_ */
diff --git a/samples/subsys/zbus/uart_bridge/src/peripheral.c b/samples/subsys/zbus/uart_bridge/src/peripheral.c
new file mode 100644
index 0000000000000..10c5e1857a54e
--- /dev/null
+++ b/samples/subsys/zbus/uart_bridge/src/peripheral.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_SUBSCRIBER_DEFINE(peripheral_sub, 8);
+
+ZBUS_CHAN_DECLARE(sensor_data_chan);
+
+static bool sensor_data_from_bridge;
+ZBUS_CHAN_DEFINE(sensor_data_chan, /* Name */
+ struct sensor_data_msg, /* Message type */
+
+ NULL, /* Validator */
+ &sensor_data_from_bridge, /* User data */
+ ZBUS_OBSERVERS(bridge_sub), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value {0} */
+);
+
+static bool start_measurement_from_bridge;
+ZBUS_CHAN_DEFINE(start_measurement_chan, /* Name */
+ struct action_msg, /* Message type */
+
+ NULL, /* Validator */
+ &start_measurement_from_bridge, /* User data */
+ ZBUS_OBSERVERS(bridge_sub, peripheral_sub), /* observers */
+ ZBUS_MSG_INIT(false) /* Initial value */
+);
+
+static void peripheral_thread(void)
+{
+ const struct zbus_channel *chan;
+ struct sensor_data_msg sd = {0, 0};
+
+ while (!zbus_sub_wait(&peripheral_sub, &chan, K_FOREVER)) {
+ LOG_DBG("Peripheral sending sensor data");
+
+ sd.a += 1;
+ sd.b += 10;
+
+ zbus_chan_pub(&sensor_data_chan, &sd, K_MSEC(250));
+ }
+}
+
+K_THREAD_DEFINE(peripheral_thread_id, 1024, peripheral_thread, NULL, NULL, NULL, 3, 0, 0);
diff --git a/samples/subsys/zbus/work_queue/CMakeLists.txt b/samples/subsys/zbus/work_queue/CMakeLists.txt
new file mode 100644
index 0000000000000..d51d7311f340c
--- /dev/null
+++ b/samples/subsys/zbus/work_queue/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(runtime_obs_registration)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/samples/subsys/zbus/work_queue/README.rst b/samples/subsys/zbus/work_queue/README.rst
new file mode 100644
index 0000000000000..55a94a45999c1
--- /dev/null
+++ b/samples/subsys/zbus/work_queue/README.rst
@@ -0,0 +1,56 @@
+.. _zbus-work-queue-sample:
+
+Workqueue sample
+################
+
+Overview
+********
+This sample implements an application using zbus to illustrate three different reaction options. First, the observer can react "instantaneously" by using a listener callback. It can schedule a job, pushing that to the system work queue. Last, it can wait and execute that in a subscriber thread.
+
+Building and Running
+********************
+
+This project outputs to the console. It can be built and executed
+on QEMU as follows:
+
+.. zephyr-app-commands::
+ :zephyr-app: samples/subsys/zbus/work_queue
+ :host-os: unix
+ :board: qemu_x86
+ :goals: run
+
+Sample Output
+=============
+
+.. code-block:: console
+
+ I: Sensor msg processed by CALLBACK fh1: temp = 10, press = 1, humidity = 100
+ I: Sensor msg processed by CALLBACK fh2: temp = 10, press = 1, humidity = 100
+ I: Sensor msg processed by CALLBACK fh3: temp = 10, press = 1, humidity = 100
+ I: Sensor msg processed by WORK QUEUE handler dh1: temp = 10, press = 1, humidity = 100
+ I: Sensor msg processed by WORK QUEUE handler dh2: temp = 10, press = 1, humidity = 100
+ I: Sensor msg processed by WORK QUEUE handler dh3: temp = 10, press = 1, humidity = 100
+ I: Sensor msg processed by THREAD handler 1: temp = 10, press = 1, humidity = 100
+ I: Sensor msg processed by THREAD handler 2: temp = 10, press = 1, humidity = 100
+ I: Sensor msg processed by THREAD handler 3: temp = 10, press = 1, humidity = 100
+ I: Sensor msg processed by CALLBACK fh1: temp = 20, press = 2, humidity = 200
+ I: Sensor msg processed by CALLBACK fh2: temp = 20, press = 2, humidity = 200
+ I: Sensor msg processed by CALLBACK fh3: temp = 20, press = 2, humidity = 200
+ I: Sensor msg processed by WORK QUEUE handler dh1: temp = 20, press = 2, humidity = 200
+ I: Sensor msg processed by WORK QUEUE handler dh2: temp = 20, press = 2, humidity = 200
+ I: Sensor msg processed by WORK QUEUE handler dh3: temp = 20, press = 2, humidity = 200
+ I: Sensor msg processed by THREAD handler 1: temp = 20, press = 2, humidity = 200
+ I: Sensor msg processed by THREAD handler 2: temp = 20, press = 2, humidity = 200
+ I: Sensor msg processed by THREAD handler 3: temp = 20, press = 2, humidity = 200
+ I: Sensor msg processed by CALLBACK fh1: temp = 30, press = 3, humidity = 300
+ I: Sensor msg processed by CALLBACK fh2: temp = 30, press = 3, humidity = 300
+ I: Sensor msg processed by CALLBACK fh3: temp = 30, press = 3, humidity = 300
+ I: Sensor msg processed by WORK QUEUE handler dh1: temp = 30, press = 3, humidity = 300
+ I: Sensor msg processed by WORK QUEUE handler dh2: temp = 30, press = 3, humidity = 300
+ I: Sensor msg processed by WORK QUEUE handler dh3: temp = 30, press = 3, humidity = 300
+ I: Sensor msg processed by THREAD handler 1: temp = 30, press = 3, humidity = 300
+ I: Sensor msg processed by THREAD handler 2: temp = 30, press = 3, humidity = 300
+ I: Sensor msg processed by THREAD handler 3: temp = 30, press = 3, humidity = 300
+
+
+Exit QEMU by pressing :kbd:`CTRL+A` :kbd:`x`.
diff --git a/samples/subsys/zbus/work_queue/prj.conf b/samples/subsys/zbus/work_queue/prj.conf
new file mode 100644
index 0000000000000..53015f7781d80
--- /dev/null
+++ b/samples/subsys/zbus/work_queue/prj.conf
@@ -0,0 +1,6 @@
+CONFIG_LOG=y
+CONFIG_THREAD_NAME=y
+CONFIG_ASSERT=y
+CONFIG_LOG_MODE_MINIMAL=y
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_INF=y
diff --git a/samples/subsys/zbus/work_queue/sample.yaml b/samples/subsys/zbus/work_queue/sample.yaml
new file mode 100644
index 0000000000000..a9267f9f35517
--- /dev/null
+++ b/samples/subsys/zbus/work_queue/sample.yaml
@@ -0,0 +1,31 @@
+sample:
+ name: Work queue
+common:
+ tags: zbus
+ harness: console
+ harness_config:
+ type: multi_line
+ ordered: false
+ regex:
+ - "I: Sensor msg processed by CALLBACK fh1: temp = 10, press = 1, humidity = 100"
+ - "I: Sensor msg processed by CALLBACK fh2: temp = 10, press = 1, humidity = 100"
+ - "I: Sensor msg processed by CALLBACK fh3: temp = 10, press = 1, humidity = 100"
+ - "I: Sensor msg processed by WORK QUEUE handler dh1: temp = 10, press = 1, humidity = 100"
+ - "I: Sensor msg processed by WORK QUEUE handler dh2: temp = 10, press = 1, humidity = 100"
+ - "I: Sensor msg processed by WORK QUEUE handler dh3: temp = 10, press = 1, humidity = 100"
+ - "I: Sensor msg processed by THREAD handler 1: temp = 10, press = 1, humidity = 100"
+ - "I: Sensor msg processed by THREAD handler 2: temp = 10, press = 1, humidity = 100"
+ - "I: Sensor msg processed by THREAD handler 3: temp = 10, press = 1, humidity = 100"
+ - "I: Sensor msg processed by CALLBACK fh1: temp = 20, press = 2, humidity = 200"
+ - "I: Sensor msg processed by CALLBACK fh2: temp = 20, press = 2, humidity = 200"
+ - "I: Sensor msg processed by CALLBACK fh3: temp = 20, press = 2, humidity = 200"
+ - "I: Sensor msg processed by WORK QUEUE handler dh1: temp = 20, press = 2, humidity = 200"
+ - "I: Sensor msg processed by WORK QUEUE handler dh2: temp = 20, press = 2, humidity = 200"
+ - "I: Sensor msg processed by WORK QUEUE handler dh3: temp = 20, press = 2, humidity = 200"
+ - "I: Sensor msg processed by THREAD handler 1: temp = 20, press = 2, humidity = 200"
+ - "I: Sensor msg processed by THREAD handler 2: temp = 20, press = 2, humidity = 200"
+ - "I: Sensor msg processed by THREAD handler 3: temp = 20, press = 2, humidity = 200"
+
+tests:
+ sample.zbus.work_queue:
+ tags: zbus
diff --git a/samples/subsys/zbus/work_queue/src/main.c b/samples/subsys/zbus/work_queue/src/main.c
new file mode 100644
index 0000000000000..1c4ccda987ffd
--- /dev/null
+++ b/samples/subsys/zbus/work_queue/src/main.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "messages.h"
+
+#include
+
+#include
+#include
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_CHAN_DEFINE(version_chan, /* Name */
+ struct version_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(.major = 0, .minor = 1,
+ .build = 1023) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZBUS_CHAN_DEFINE(sensor_data_chan, /* Name */
+ struct sensor_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(fast_handler1_lis, fast_handler2_lis, fast_handler3_lis,
+ delay_handler1_lis, delay_handler2_lis, delay_handler3_lis,
+ thread_handler1_sub, thread_handler2_sub,
+ thread_handler3_sub), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value {0} */
+);
+
+static void fh1_cb(const struct zbus_channel *chan)
+{
+ const struct sensor_msg *msg = zbus_chan_const_msg(chan);
+
+ LOG_INF("Sensor msg processed by CALLBACK fh1: temp = %u, press = %u, humidity = %u",
+ msg->temp, msg->press, msg->humidity);
+}
+
+ZBUS_LISTENER_DEFINE(fast_handler1_lis, fh1_cb);
+
+static void fh2_cb(const struct zbus_channel *chan)
+{
+ const struct sensor_msg *msg = zbus_chan_const_msg(chan);
+
+ LOG_INF("Sensor msg processed by CALLBACK fh2: temp = %u, press = %u, humidity = %u",
+ msg->temp, msg->press, msg->humidity);
+}
+
+ZBUS_LISTENER_DEFINE(fast_handler2_lis, fh2_cb);
+
+static void fh3_cb(const struct zbus_channel *chan)
+{
+ const struct sensor_msg *msg = zbus_chan_const_msg(chan);
+
+ LOG_INF("Sensor msg processed by CALLBACK fh3: temp = %u, press = %u, humidity = %u",
+ msg->temp, msg->press, msg->humidity);
+}
+
+ZBUS_LISTENER_DEFINE(fast_handler3_lis, fh3_cb);
+
+struct sensor_wq_info {
+ struct k_work work;
+ const struct zbus_channel *chan;
+ uint8_t handle;
+};
+
+static struct sensor_wq_info wq_handler1 = {.handle = 1};
+static struct sensor_wq_info wq_handler2 = {.handle = 2};
+static struct sensor_wq_info wq_handler3 = {.handle = 3};
+
+static void wq_dh_cb(struct k_work *item)
+{
+ struct sensor_msg msg;
+ struct sensor_wq_info *sens = CONTAINER_OF(item, struct sensor_wq_info, work);
+
+ zbus_chan_read(sens->chan, &msg, K_MSEC(200));
+
+ LOG_INF("Sensor msg processed by WORK QUEUE handler dh%u: temp = %u, press = %u, "
+ "humidity = %u",
+ sens->handle, msg.temp, msg.press, msg.humidity);
+}
+
+static void dh1_cb(const struct zbus_channel *chan)
+{
+ wq_handler1.chan = chan;
+
+ k_work_submit(&wq_handler1.work);
+}
+
+ZBUS_LISTENER_DEFINE(delay_handler1_lis, dh1_cb);
+
+static void dh2_cb(const struct zbus_channel *chan)
+{
+ wq_handler2.chan = chan;
+
+ k_work_submit(&wq_handler2.work);
+}
+
+ZBUS_LISTENER_DEFINE(delay_handler2_lis, dh2_cb);
+
+static void dh3_cb(const struct zbus_channel *chan)
+{
+ wq_handler3.chan = chan;
+
+ k_work_submit(&wq_handler3.work);
+}
+
+ZBUS_LISTENER_DEFINE(delay_handler3_lis, dh3_cb);
+
+void main(void)
+{
+ k_work_init(&wq_handler1.work, wq_dh_cb);
+ k_work_init(&wq_handler2.work, wq_dh_cb);
+ k_work_init(&wq_handler3.work, wq_dh_cb);
+
+ struct version_msg *v = zbus_chan_msg(&version_chan);
+
+ LOG_DBG("Sensor sample started, version %u.%u-%u!", v->major, v->minor, v->build);
+}
+
+ZBUS_SUBSCRIBER_DEFINE(thread_handler1_sub, 4);
+
+static void thread_handler1_task(void)
+{
+ const struct zbus_channel *chan;
+
+ while (!zbus_sub_wait(&thread_handler1_sub, &chan, K_FOREVER)) {
+ struct sensor_msg msg;
+
+ zbus_chan_read(chan, &msg, K_MSEC(200));
+
+ LOG_INF("Sensor msg processed by THREAD handler 1: temp = %u, press = %u, "
+ "humidity = %u",
+ msg.temp, msg.press, msg.humidity);
+ }
+}
+
+K_THREAD_DEFINE(thread_handler1_id, 1024, thread_handler1_task, NULL, NULL, NULL, 3, 0, 0);
+
+ZBUS_SUBSCRIBER_DEFINE(thread_handler2_sub, 4);
+
+static void thread_handler2_task(void)
+{
+ const struct zbus_channel *chan;
+
+ while (!zbus_sub_wait(&thread_handler2_sub, &chan, K_FOREVER)) {
+ struct sensor_msg msg;
+
+ zbus_chan_read(chan, &msg, K_MSEC(200));
+
+ LOG_INF("Sensor msg processed by THREAD handler 2: temp = %u, press = %u, "
+ "humidity = %u",
+ msg.temp, msg.press, msg.humidity);
+ }
+}
+
+K_THREAD_DEFINE(thread_handler2_id, 1024, thread_handler2_task, NULL, NULL, NULL, 3, 0, 0);
+
+ZBUS_SUBSCRIBER_DEFINE(thread_handler3_sub, 4);
+
+static void thread_handler3_task(void)
+{
+ const struct zbus_channel *chan;
+
+ while (!zbus_sub_wait(&thread_handler3_sub, &chan, K_FOREVER)) {
+ struct sensor_msg msg;
+
+ zbus_chan_read(chan, &msg, K_MSEC(200));
+
+ LOG_INF("Sensor msg processed by THREAD handler 3: temp = %u, press = %u, "
+ "humidity = %u",
+ msg.temp, msg.press, msg.humidity);
+ }
+}
+
+K_THREAD_DEFINE(thread_handler3_id, 1024, thread_handler3_task, NULL, NULL, NULL, 3, 0, 0);
diff --git a/samples/subsys/zbus/work_queue/src/messages.h b/samples/subsys/zbus/work_queue/src/messages.h
new file mode 100644
index 0000000000000..888d63379ddbe
--- /dev/null
+++ b/samples/subsys/zbus/work_queue/src/messages.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef _MESSAGES_H_
+#define _MESSAGES_H_
+#include
+
+struct version_msg {
+ uint8_t major;
+ uint8_t minor;
+ uint16_t build;
+};
+
+struct sensor_msg {
+ uint32_t temp;
+ uint32_t press;
+ uint32_t humidity;
+};
+
+#endif /* _MESSAGES_H_ */
diff --git a/samples/subsys/zbus/work_queue/src/sensors.c b/samples/subsys/zbus/work_queue/src/sensors.c
new file mode 100644
index 0000000000000..3f61e0e374fb3
--- /dev/null
+++ b/samples/subsys/zbus/work_queue/src/sensors.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_CHAN_DECLARE(sensor_data_chan);
+
+void peripheral_thread(void)
+{
+ struct sensor_msg sm = {0};
+
+ while (1) {
+ LOG_DBG("Sending sensor data...");
+
+ sm.press += 1;
+ sm.temp += 10;
+ sm.humidity += 100;
+
+ zbus_chan_pub(&sensor_data_chan, &sm, K_MSEC(250));
+
+ k_msleep(500);
+ }
+}
+
+K_THREAD_DEFINE(peripheral_thread_id, 1024, peripheral_thread, NULL, NULL, NULL, 5, 0, 0);
diff --git a/samples/subsys/zbus/zbus.rst b/samples/subsys/zbus/zbus.rst
new file mode 100644
index 0000000000000..569802c632f0d
--- /dev/null
+++ b/samples/subsys/zbus/zbus.rst
@@ -0,0 +1,10 @@
+.. _zbus_samples:
+
+Zbus Samples
+############
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ **/*
diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt
index a2f049f323eaa..a7f385cc545f7 100644
--- a/subsys/CMakeLists.txt
+++ b/subsys/CMakeLists.txt
@@ -32,3 +32,4 @@ add_subdirectory_ifdef(CONFIG_DEMAND_PAGING demand_paging)
add_subdirectory(modbus)
add_subdirectory(sd)
add_subdirectory(rtio)
+add_subdirectory_ifdef(CONFIG_ZBUS zbus)
diff --git a/subsys/Kconfig b/subsys/Kconfig
index 423a5cf42a33a..a37bd623d58bb 100644
--- a/subsys/Kconfig
+++ b/subsys/Kconfig
@@ -72,4 +72,6 @@ source "subsys/demand_paging/Kconfig"
source "subsys/rtio/Kconfig"
+source "subsys/zbus/Kconfig"
+
endmenu
diff --git a/subsys/zbus/CMakeLists.txt b/subsys/zbus/CMakeLists.txt
new file mode 100644
index 0000000000000..d6511501a203b
--- /dev/null
+++ b/subsys/zbus/CMakeLists.txt
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: Apache-2.0
+
+zephyr_library()
+
+zephyr_library_sources(zbus.c)
+
+if(CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE GREATER 0)
+ zephyr_library_sources(zbus_runtime_observers.c)
+endif()
+
+if(CONFIG_ZBUS_STRUCTS_ITERABLE_ACCESS)
+ zephyr_library_sources(zbus_iterable_sections.c)
+ zephyr_linker_sources(DATA_SECTIONS zbus.ld)
+endif()
diff --git a/subsys/zbus/Kconfig b/subsys/zbus/Kconfig
new file mode 100644
index 0000000000000..186b33f32d5a4
--- /dev/null
+++ b/subsys/zbus/Kconfig
@@ -0,0 +1,43 @@
+# Copyright (c) 2022 Rodrigo Peixoto
+# SPDX-License-Identifier: Apache-2.0
+
+menuconfig ZBUS
+ bool "Zbus support"
+ help
+ Enables support for Zephyr message bus.
+
+if ZBUS
+
+config ZBUS_STRUCTS_ITERABLE_ACCESS
+ bool "Zbus iterable sections support."
+ depends on !XTENSA && !ARCH_POSIX
+ default y
+
+config ZBUS_CHANNEL_NAME
+ bool "Channel name field"
+
+config ZBUS_OBSERVER_NAME
+ bool "Observer name field"
+
+config ZBUS_RUNTIME_OBSERVERS_POOL_SIZE
+ int "The size of the runtime observers pool."
+ default 0
+ help
+ When the size is bigger than zero this feature will be enabled. It applies the Object Pool Pattern,
+ where the objects in the pool are pre-allocated and can be used and recycled after use. The
+ technique avoids dynamic allocation and allows the code to increase the number of observers by
+ only changing a configuration.
+
+config ZBUS_ASSERT_MOCK
+ bool "Zbus assert mock for test purposes."
+ help
+ This configuration enables the developer to change the _ZBUS_ASSERT behavior. When this configuration is
+ enabled, _ZBUS_ASSERT returns -EFAULT instead of assert. It makes it more straightforward to test invalid
+ parameters.
+
+
+module = ZBUS
+module-str = zbus
+source "subsys/logging/Kconfig.template.log_config"
+
+endif # ZBUS
diff --git a/subsys/zbus/zbus.c b/subsys/zbus/zbus.c
new file mode 100644
index 0000000000000..5357e3439a00a
--- /dev/null
+++ b/subsys/zbus/zbus.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include
+#include
+#include
+#include
+LOG_MODULE_REGISTER(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+k_timeout_t _zbus_timeout_remainder(uint64_t end_ticks)
+{
+ int64_t now_ticks = sys_clock_tick_get();
+
+ return K_TICKS((k_ticks_t)MAX(end_ticks - now_ticks, 0));
+}
+
+#if (CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE > 0)
+static inline void _zbus_notify_runtime_listeners(const struct zbus_channel *chan)
+{
+ __ASSERT(chan != NULL, "chan is required");
+
+ struct zbus_observer_node *obs_nd, *tmp;
+
+ SYS_SLIST_FOR_EACH_CONTAINER_SAFE(chan->runtime_observers, obs_nd, tmp, node) {
+
+ __ASSERT(obs_nd != NULL, "observer node is NULL");
+
+ if (obs_nd->obs->enabled && (obs_nd->obs->callback != NULL)) {
+ obs_nd->obs->callback(chan);
+ }
+ }
+}
+
+static inline int _zbus_notify_runtime_subscribers(const struct zbus_channel *chan,
+ uint64_t end_ticks)
+{
+ __ASSERT(chan != NULL, "chan is required");
+
+ int last_error = 0, err;
+ struct zbus_observer_node *obs_nd, *tmp;
+
+ SYS_SLIST_FOR_EACH_CONTAINER_SAFE(chan->runtime_observers, obs_nd, tmp, node) {
+
+ __ASSERT(obs_nd != NULL, "observer node is NULL");
+
+ if (obs_nd->obs->enabled && (obs_nd->obs->queue != NULL)) {
+ err = k_msgq_put(obs_nd->obs->queue, &chan,
+ _zbus_timeout_remainder(end_ticks));
+
+ _ZBUS_ASSERT(err == 0,
+ "could not deliver notification to observer %s. Error code %d",
+ _ZBUS_OBS_NAME(obs_nd->obs), err);
+
+ if (err) {
+ last_error = err;
+ }
+ }
+ }
+
+ return last_error;
+}
+#endif /* CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE */
+
+static int _zbus_notify_observers(const struct zbus_channel *chan, uint64_t end_ticks)
+{
+ int last_error = 0, err;
+ /* Notify static listeners */
+ for (const struct zbus_observer *const *obs = chan->observers; *obs != NULL; ++obs) {
+ if ((*obs)->enabled && ((*obs)->callback != NULL)) {
+ (*obs)->callback(chan);
+ }
+ }
+
+#if CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE > 0
+ _zbus_notify_runtime_listeners(chan);
+#endif /* CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE */
+
+ /* Notify static subscribers */
+ for (const struct zbus_observer *const *obs = chan->observers; *obs != NULL; ++obs) {
+ if ((*obs)->enabled && ((*obs)->queue != NULL)) {
+ err = k_msgq_put((*obs)->queue, &chan, _zbus_timeout_remainder(end_ticks));
+ _ZBUS_ASSERT(err == 0, "could not deliver notification to observer %s.",
+ _ZBUS_OBS_NAME(*obs));
+ if (err) {
+ LOG_ERR("Observer %s at %p could not be notified. Error code %d",
+ _ZBUS_OBS_NAME(*obs), *obs, err);
+ last_error = err;
+ }
+ }
+ }
+
+#if CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE > 0
+ err = _zbus_notify_runtime_subscribers(chan, end_ticks);
+ if (err) {
+ last_error = err;
+ }
+#endif /* CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE */
+ return last_error;
+}
+
+int zbus_chan_pub(const struct zbus_channel *chan, const void *msg, k_timeout_t timeout)
+{
+ int err;
+ uint64_t end_ticks = sys_clock_timeout_end_calc(timeout);
+
+ _ZBUS_ASSERT(!k_is_in_isr(), "zbus cannot be used inside ISRs");
+ _ZBUS_ASSERT(chan != NULL, "chan is required");
+ _ZBUS_ASSERT(msg != NULL, "msg is required");
+
+ if (chan->validator != NULL && !chan->validator(msg, chan->message_size)) {
+ return -ENOMSG;
+ }
+
+ err = k_mutex_lock(chan->mutex, timeout);
+ if (err) {
+ return err;
+ }
+
+ memcpy(chan->message, msg, chan->message_size);
+
+ err = _zbus_notify_observers(chan, end_ticks);
+
+ k_mutex_unlock(chan->mutex);
+
+ return err;
+}
+
+int zbus_chan_read(const struct zbus_channel *chan, void *msg, k_timeout_t timeout)
+{
+ int err;
+
+ _ZBUS_ASSERT(!k_is_in_isr(), "zbus cannot be used inside ISRs");
+ _ZBUS_ASSERT(chan != NULL, "chan is required");
+ _ZBUS_ASSERT(msg != NULL, "msg is required");
+
+ err = k_mutex_lock(chan->mutex, timeout);
+ if (err) {
+ return err;
+ }
+
+ memcpy(msg, chan->message, chan->message_size);
+
+ return k_mutex_unlock(chan->mutex);
+}
+
+int zbus_chan_notify(const struct zbus_channel *chan, k_timeout_t timeout)
+{
+ int err;
+ uint64_t end_ticks = sys_clock_timeout_end_calc(timeout);
+
+ _ZBUS_ASSERT(!k_is_in_isr(), "zbus cannot be used inside ISRs");
+ _ZBUS_ASSERT(chan != NULL, "chan is required");
+
+ err = k_mutex_lock(chan->mutex, timeout);
+ if (err) {
+ return err;
+ }
+
+ err = _zbus_notify_observers(chan, end_ticks);
+
+ k_mutex_unlock(chan->mutex);
+
+ return err;
+}
+
+int zbus_chan_claim(const struct zbus_channel *chan, k_timeout_t timeout)
+{
+ _ZBUS_ASSERT(!k_is_in_isr(), "zbus cannot be used inside ISRs");
+ _ZBUS_ASSERT(chan != NULL, "chan is required");
+
+ int err = k_mutex_lock(chan->mutex, timeout);
+
+ if (err) {
+ return err;
+ }
+
+ return 0;
+}
+
+int zbus_chan_finish(const struct zbus_channel *chan)
+{
+ _ZBUS_ASSERT(!k_is_in_isr(), "zbus cannot be used inside ISRs");
+ _ZBUS_ASSERT(chan != NULL, "chan is required");
+
+ int err = k_mutex_unlock(chan->mutex);
+
+ return err;
+}
+
+int zbus_sub_wait(const struct zbus_observer *sub, const struct zbus_channel **chan,
+ k_timeout_t timeout)
+{
+ _ZBUS_ASSERT(!k_is_in_isr(), "zbus cannot be used inside ISRs");
+ _ZBUS_ASSERT(sub != NULL, "sub is required");
+ _ZBUS_ASSERT(chan != NULL, "chan is required");
+
+ if (sub->queue == NULL) {
+ return -EINVAL;
+ }
+
+ return k_msgq_get(sub->queue, chan, timeout);
+}
diff --git a/subsys/zbus/zbus.ld b/subsys/zbus/zbus.ld
new file mode 100644
index 0000000000000..734d7b3eed5d9
--- /dev/null
+++ b/subsys/zbus/zbus.ld
@@ -0,0 +1,2 @@
+ITERABLE_SECTION_RAM(zbus_channel, 4)
+ITERABLE_SECTION_RAM(zbus_observer, 4)
diff --git a/subsys/zbus/zbus_iterable_sections.c b/subsys/zbus/zbus_iterable_sections.c
new file mode 100644
index 0000000000000..1b0a7b93f19a5
--- /dev/null
+++ b/subsys/zbus/zbus_iterable_sections.c
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+bool zbus_iterate_over_channels(bool (*iterator_func)(const struct zbus_channel *chan))
+{
+ STRUCT_SECTION_FOREACH(zbus_channel, chan) {
+ if (!(*iterator_func)(chan)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool zbus_iterate_over_observers(bool (*iterator_func)(const struct zbus_observer *obs))
+{
+ STRUCT_SECTION_FOREACH(zbus_observer, obs) {
+ if (!(*iterator_func)(obs)) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/subsys/zbus/zbus_runtime_observers.c b/subsys/zbus/zbus_runtime_observers.c
new file mode 100644
index 0000000000000..8e966e1942331
--- /dev/null
+++ b/subsys/zbus/zbus_runtime_observers.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include
+#include
+#include
+
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+K_MEM_SLAB_DEFINE_STATIC(_zbus_runtime_obs_pool, sizeof(struct zbus_observer_node),
+ CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE, 4);
+
+struct k_mem_slab *zbus_runtime_obs_pool(void)
+{
+ return &_zbus_runtime_obs_pool;
+}
+
+int zbus_chan_add_obs(const struct zbus_channel *chan, const struct zbus_observer *obs,
+ k_timeout_t timeout)
+{
+ int err;
+ struct zbus_observer_node *obs_nd, *tmp;
+ uint64_t end_ticks = sys_clock_timeout_end_calc(timeout);
+
+ _ZBUS_ASSERT(!k_is_in_isr(), "ISR blocked");
+ _ZBUS_ASSERT(chan != NULL, "chan is required");
+ _ZBUS_ASSERT(obs != NULL, "obs is required");
+
+ /* Check if the observer is already a static observer */
+ for (const struct zbus_observer *const *static_obs = chan->observers; *static_obs != NULL;
+ ++static_obs) {
+ if (*static_obs == obs) {
+ return -EEXIST;
+ }
+ }
+
+ err = k_mutex_lock(chan->mutex, timeout);
+ if (err) {
+ return err;
+ }
+
+ /* Check if the observer is already a runtime observer */
+ SYS_SLIST_FOR_EACH_CONTAINER_SAFE(chan->runtime_observers, obs_nd, tmp, node) {
+ if (obs_nd->obs == obs) {
+ k_mutex_unlock(chan->mutex);
+
+ return -EALREADY;
+ }
+ }
+
+ err = k_mem_slab_alloc(&_zbus_runtime_obs_pool, (void **)&obs_nd,
+ _zbus_timeout_remainder(end_ticks));
+
+ if (err) {
+ LOG_ERR("Could not allocate memory on runtime observers pool\n");
+
+ k_mutex_unlock(chan->mutex);
+
+ return err;
+ }
+
+ obs_nd->obs = obs;
+
+ sys_slist_append(chan->runtime_observers, &obs_nd->node);
+
+ k_mutex_unlock(chan->mutex);
+
+ return 0;
+}
+
+int zbus_chan_rm_obs(const struct zbus_channel *chan, const struct zbus_observer *obs,
+ k_timeout_t timeout)
+{
+ int err;
+ struct zbus_observer_node *obs_nd, *tmp;
+ struct zbus_observer_node *prev_obs_nd = NULL;
+
+ _ZBUS_ASSERT(!k_is_in_isr(), "ISR blocked");
+ _ZBUS_ASSERT(chan != NULL, "chan is required");
+ _ZBUS_ASSERT(obs != NULL, "obs is required");
+
+ err = k_mutex_lock(chan->mutex, timeout);
+ if (err) {
+ return err;
+ }
+
+ SYS_SLIST_FOR_EACH_CONTAINER_SAFE(chan->runtime_observers, obs_nd, tmp, node) {
+ if (obs_nd->obs == obs) {
+ sys_slist_remove(chan->runtime_observers, &prev_obs_nd->node,
+ &obs_nd->node);
+
+ k_mem_slab_free(&_zbus_runtime_obs_pool, (void **)&obs_nd);
+
+ k_mutex_unlock(chan->mutex);
+
+ return 0;
+ }
+
+ prev_obs_nd = obs_nd;
+ }
+
+ k_mutex_unlock(chan->mutex);
+
+ return -ENODATA;
+}
diff --git a/tests/subsys/zbus/dyn_channel/CMakeLists.txt b/tests/subsys/zbus/dyn_channel/CMakeLists.txt
new file mode 100644
index 0000000000000..05fedaa2b5da2
--- /dev/null
+++ b/tests/subsys/zbus/dyn_channel/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(test_dyn_channel)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/tests/subsys/zbus/dyn_channel/prj.conf b/tests/subsys/zbus/dyn_channel/prj.conf
new file mode 100644
index 0000000000000..fcb4160678fc9
--- /dev/null
+++ b/tests/subsys/zbus/dyn_channel/prj.conf
@@ -0,0 +1,8 @@
+CONFIG_ZTEST=y
+CONFIG_ZTEST_NEW_API=y
+CONFIG_ASSERT=y
+CONFIG_LOG=y
+CONFIG_HEAP_MEM_POOL_SIZE=256
+CONFIG_ZBUS=y
+CONFIG_ZBUS_CHANNEL_NAME=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
diff --git a/tests/subsys/zbus/dyn_channel/src/main.c b/tests/subsys/zbus/dyn_channel/src/main.c
new file mode 100644
index 0000000000000..6854c15aede19
--- /dev/null
+++ b/tests/subsys/zbus/dyn_channel/src/main.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+struct version_msg {
+ uint8_t major;
+ uint8_t minor;
+ uint16_t build;
+};
+
+struct external_data_msg {
+ void *reference;
+ size_t size;
+};
+
+ZBUS_CHAN_DEFINE(dyn_chan_no_subs, /* Name */
+ struct external_data_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value {0} */
+);
+
+ZBUS_CHAN_DEFINE(dyn_chan, /* Name */
+ struct external_data_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(s1), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value {0} */
+);
+
+static struct {
+ uint8_t a;
+ uint64_t b;
+} my_random_data_expected;
+
+static void s1_cb(const struct zbus_channel *chan)
+{
+ LOG_DBG("Callback called");
+
+ const struct external_data_msg *chan_message;
+
+ chan_message = zbus_chan_const_msg(chan);
+ memcpy(&my_random_data_expected, chan_message->reference, sizeof(my_random_data_expected));
+
+ zbus_chan_finish(chan);
+}
+ZBUS_LISTENER_DEFINE(s1, s1_cb);
+
+/**
+ * @brief Test Asserts
+ *
+ * This test verifies various assert macros provided by ztest.
+ *
+ */
+ZTEST(dynamic, test_static)
+{
+ uint8_t static_memory[256] = {0};
+ struct external_data_msg static_external_data = {.reference = static_memory, .size = 256};
+
+ struct {
+ uint8_t a;
+ uint64_t b;
+ } my_random_data = {.a = 10, .b = 200000};
+
+ memcpy(static_memory, &my_random_data, sizeof(my_random_data));
+
+ int err = zbus_chan_pub(&dyn_chan, &static_external_data, K_MSEC(500));
+
+ zassert_equal(err, 0, "Allocation could not be performed");
+
+ k_msleep(1000);
+
+ zassert_equal(my_random_data.a, my_random_data_expected.a,
+ "It must be 10, random data is %d", my_random_data_expected.a);
+ zassert_equal(my_random_data.b, my_random_data_expected.b, "It must be 200000");
+
+ struct external_data_msg edm = {0};
+
+ zbus_chan_read(&dyn_chan, &edm, K_NO_WAIT);
+ zassert_equal(edm.reference, static_memory, "The pointer must be equal here");
+}
+
+ZTEST(dynamic, test_malloc)
+{
+ uint8_t *dynamic_memory = k_malloc(128);
+
+ zassert_not_equal(dynamic_memory, NULL, "Memory could not be allocated");
+
+ struct external_data_msg static_external_data = {.reference = dynamic_memory, .size = 128};
+
+ struct {
+ uint8_t a;
+ uint64_t b;
+ } my_random_data = {.a = 20, .b = 300000};
+
+ memcpy(dynamic_memory, &my_random_data, sizeof(my_random_data));
+
+ int err = zbus_chan_pub(&dyn_chan, &static_external_data, K_NO_WAIT);
+
+ zassert_equal(err, 0, "Allocation could not be performed");
+
+ k_msleep(1000);
+
+ zassert_equal(my_random_data.a, my_random_data_expected.a, "It must be 20");
+ zassert_equal(my_random_data.b, my_random_data_expected.b, "It must be 300000");
+
+ struct external_data_msg *actual_message_data = NULL;
+
+ err = zbus_chan_claim(&dyn_chan, K_NO_WAIT);
+ zassert_equal(err, 0, "Could not claim the channel");
+
+ actual_message_data = (struct external_data_msg *)dyn_chan.message;
+ zassert_equal(actual_message_data->reference, dynamic_memory,
+ "The pointer must be equal here");
+
+ k_free(actual_message_data->reference);
+ actual_message_data->reference = NULL;
+ actual_message_data->size = 0;
+
+ zbus_chan_finish(&dyn_chan);
+
+ struct external_data_msg expected_to_be_clean = {0};
+
+ zbus_chan_read(&dyn_chan, &expected_to_be_clean, K_NO_WAIT);
+ zassert_equal(expected_to_be_clean.reference, NULL,
+ "The current message reference should be NULL");
+ zassert_equal(expected_to_be_clean.size, 0, "The current message size should be zero");
+}
+
+ZTEST_SUITE(dynamic, NULL, NULL, NULL, NULL, NULL);
diff --git a/tests/subsys/zbus/dyn_channel/testcase.yaml b/tests/subsys/zbus/dyn_channel/testcase.yaml
new file mode 100644
index 0000000000000..8b1253fe1791b
--- /dev/null
+++ b/tests/subsys/zbus/dyn_channel/testcase.yaml
@@ -0,0 +1,5 @@
+tests:
+ dyn_channel.static_and_dynamic_channels:
+ build_only: false
+ platform_exclude: fvp_base_revc_2xaemv8a_smp_ns
+ tags: zbus
diff --git a/tests/subsys/zbus/integration/CMakeLists.txt b/tests/subsys/zbus/integration/CMakeLists.txt
new file mode 100644
index 0000000000000..a07ef9599ef3e
--- /dev/null
+++ b/tests/subsys/zbus/integration/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(test_integration)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/tests/subsys/zbus/integration/prj.conf b/tests/subsys/zbus/integration/prj.conf
new file mode 100644
index 0000000000000..4d2bd32315d65
--- /dev/null
+++ b/tests/subsys/zbus/integration/prj.conf
@@ -0,0 +1,7 @@
+CONFIG_ZTEST=y
+CONFIG_ZTEST_NEW_API=y
+CONFIG_ASSERT=n
+CONFIG_LOG=y
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
+CONFIG_ZBUS_CHANNEL_NAME=y
diff --git a/tests/subsys/zbus/integration/src/channels.c b/tests/subsys/zbus/integration/src/channels.c
new file mode 100644
index 0000000000000..82fe1e00bbb2c
--- /dev/null
+++ b/tests/subsys/zbus/integration/src/channels.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+
+ZBUS_CHAN_DEFINE(version_chan, /* Name */
+ struct version_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(.major = 0, .minor = 1,
+ .build = 1023) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZBUS_CHAN_DEFINE(sensor_data_chan, /* Name */
+ struct sensor_data_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(core_sub), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value {0} */
+);
+
+ZBUS_CHAN_DEFINE(net_pkt_chan, /* Name */
+ struct net_pkt_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(net_sub), /* observers */
+ ZBUS_MSG_INIT(.total = 0) /* Initial value */
+);
+
+ZBUS_CHAN_DEFINE(start_measurement_chan, /* Name */
+ struct action_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(peripheral_sub, critical_lis), /* observers */
+ ZBUS_MSG_INIT(.status = false) /* Initial value */
+);
+
+ZBUS_CHAN_DEFINE(busy_chan, /* Name */
+ struct action_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(.status = false) /* Initial value */
+);
diff --git a/tests/subsys/zbus/integration/src/main.c b/tests/subsys/zbus/integration/src/main.c
new file mode 100644
index 0000000000000..147c4e264ab84
--- /dev/null
+++ b/tests/subsys/zbus/integration/src/main.c
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "messages.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_CHAN_DECLARE(version_chan, sensor_data_chan, net_pkt_chan, start_measurement_chan, busy_chan);
+
+static int count_callback;
+
+static void urgent_callback(const struct zbus_channel *chan)
+{
+ LOG_INF(" *** LISTENER activated for channel %s ***\n", zbus_chan_name(chan));
+
+ ++count_callback;
+}
+
+ZBUS_LISTENER_DEFINE(critical_lis, urgent_callback);
+
+static int count_core;
+
+ZBUS_SUBSCRIBER_DEFINE(core_sub, 1);
+
+static void core_thread(void)
+{
+ const struct zbus_channel *chan = NULL;
+
+ while (1) {
+ if (!zbus_sub_wait(&core_sub, &chan, K_FOREVER)) {
+ count_core++;
+
+ struct sensor_data_msg data;
+
+ zbus_chan_read(&sensor_data_chan, &data, K_NO_WAIT);
+
+ struct net_pkt_msg pkt = {.total = data.a + data.b};
+
+ LOG_DBG("Sensor {a = %d, b = %d}. Sending pkt {total=%d}", data.a, data.b,
+ pkt.total);
+
+ zbus_chan_pub(&net_pkt_chan, &pkt, K_MSEC(200));
+ }
+ }
+}
+
+K_THREAD_DEFINE(core_thread_id, 1024, core_thread, NULL, NULL, NULL, 3, 0, 0);
+
+static int count_net;
+static struct net_pkt_msg pkt = {0};
+
+ZBUS_SUBSCRIBER_DEFINE(net_sub, 4);
+
+static void net_thread(void)
+{
+ const struct zbus_channel *chan;
+
+ while (1) {
+ if (!zbus_sub_wait(&net_sub, &chan, K_FOREVER)) {
+ count_net++;
+
+ zbus_chan_read(&net_pkt_chan, &pkt, K_NO_WAIT);
+
+ LOG_DBG("[Net] Total %d", pkt.total);
+ }
+ }
+}
+
+K_THREAD_DEFINE(net_thread_id, 1024, net_thread, NULL, NULL, NULL, 3, 0, 0);
+
+static int a;
+static int b;
+static int count_peripheral;
+
+ZBUS_SUBSCRIBER_DEFINE(peripheral_sub, 1);
+
+static void peripheral_thread(void)
+{
+ struct sensor_data_msg sd = {0, 0};
+
+ const struct zbus_channel *chan;
+
+ while (!zbus_sub_wait(&peripheral_sub, &chan, K_FOREVER)) {
+ LOG_DBG("[Peripheral] starting measurement");
+
+ ++count_peripheral;
+ ++a;
+ ++b;
+
+ sd.a = a;
+ sd.b = b;
+
+ LOG_DBG("[Peripheral] sending sensor data");
+
+ zbus_chan_pub(&sensor_data_chan, &sd, K_MSEC(250));
+
+ k_msleep(150);
+ }
+}
+
+K_THREAD_DEFINE(peripheral_thread_id, 1024, peripheral_thread, NULL, NULL, NULL, 3, 0, 0);
+
+static void context_reset(void *f)
+{
+ k_busy_wait(1000000);
+
+ a = 0;
+ b = 0;
+ count_callback = 0;
+ count_core = 0;
+ count_net = 0;
+ count_peripheral = 0;
+ pkt.total = 0;
+ struct net_pkt_msg *p;
+
+ zbus_chan_claim(&net_pkt_chan, K_NO_WAIT);
+ p = zbus_chan_msg(&net_pkt_chan);
+ p->total = 0;
+ zbus_chan_finish(&net_pkt_chan);
+ struct sensor_data_msg *sd;
+
+ zbus_chan_claim(&sensor_data_chan, K_NO_WAIT);
+ sd = (struct sensor_data_msg *)sensor_data_chan.message;
+ sd->a = 0;
+ sd->b = 1;
+ zbus_chan_finish(&sensor_data_chan);
+ zbus_obs_set_enable(&critical_lis, true);
+ zbus_obs_set_enable(&peripheral_sub, true);
+ zbus_chan_claim(&start_measurement_chan, K_NO_WAIT);
+ struct action_msg *act = (struct action_msg *)start_measurement_chan.message;
+
+ act->status = false;
+ zbus_chan_finish(&start_measurement_chan);
+}
+
+ZTEST(integration, test_basic)
+{
+ struct action_msg start = {true};
+
+ zassert_equal(0, zbus_chan_pub(&start_measurement_chan, &start, K_MSEC(200)), NULL);
+
+ k_msleep(200);
+
+ zassert_equal(count_callback, 1, NULL);
+ zassert_equal(count_core, 1, NULL);
+ zassert_equal(count_net, 1, NULL);
+ zassert_equal(count_peripheral, 1, NULL);
+
+ zassert_equal(0, zbus_chan_pub(&start_measurement_chan, &start, K_MSEC(200)), NULL);
+
+ k_msleep(200);
+
+ zassert_equal(count_callback, 2, NULL);
+ zassert_equal(count_core, 2, NULL);
+ zassert_equal(count_net, 2, NULL);
+ zassert_equal(count_peripheral, 2, NULL);
+
+ zassert_equal(0, zbus_chan_pub(&start_measurement_chan, &start, K_MSEC(200)), NULL);
+
+ k_msleep(200);
+
+ zassert_equal(count_callback, 3, NULL);
+ zassert_equal(count_core, 3, NULL);
+ zassert_equal(count_net, 3, NULL);
+ zassert_equal(count_peripheral, 3, NULL);
+
+ zassert_equal(pkt.total, 6, "result was %d", pkt.total);
+}
+
+ZTEST(integration, test_channel_set_enable)
+{
+ struct action_msg start = {true};
+
+ zassert_equal(0, zbus_obs_set_enable(&critical_lis, false), NULL);
+ zassert_equal(0, zbus_obs_set_enable(&peripheral_sub, false), NULL);
+ zassert_equal(0, zbus_chan_pub(&start_measurement_chan, &start, K_MSEC(200)), NULL);
+
+ k_msleep(200);
+
+ zassert_equal(count_callback, 0, NULL);
+ zassert_equal(count_core, 0, NULL);
+ zassert_equal(count_net, 0, NULL);
+ zassert_equal(count_peripheral, 0, NULL);
+
+ zassert_equal(0, zbus_obs_set_enable(&critical_lis, false), NULL);
+ zassert_equal(0, zbus_obs_set_enable(&peripheral_sub, true), NULL);
+ zassert_equal(0, zbus_chan_pub(&start_measurement_chan, &start, K_MSEC(200)), NULL);
+
+ k_msleep(200);
+
+ zassert_equal(count_callback, 0, NULL);
+ zassert_equal(count_core, 1, NULL);
+ zassert_equal(count_net, 1, NULL);
+ zassert_equal(count_peripheral, 1, NULL);
+
+ zassert_equal(0, zbus_obs_set_enable(&critical_lis, true), NULL);
+ zassert_equal(0, zbus_obs_set_enable(&peripheral_sub, false), NULL);
+ zassert_equal(0, zbus_chan_pub(&start_measurement_chan, &start, K_MSEC(200)), NULL);
+
+ k_msleep(200);
+
+ zassert_equal(count_callback, 1, NULL);
+ zassert_equal(count_core, 1, NULL);
+ zassert_equal(count_net, 1, NULL);
+ zassert_equal(count_peripheral, 1, NULL);
+
+ zassert_equal(0, zbus_obs_set_enable(&critical_lis, true), NULL);
+ zassert_equal(0, zbus_obs_set_enable(&peripheral_sub, true), NULL);
+ zassert_equal(0, zbus_chan_pub(&start_measurement_chan, &start, K_MSEC(200)), NULL);
+
+ k_msleep(200);
+
+ zassert_equal(count_callback, 2, NULL);
+ zassert_equal(count_core, 2, NULL);
+ zassert_equal(count_net, 2, NULL);
+ zassert_equal(count_peripheral, 2, NULL);
+
+ zassert_equal(pkt.total, 4, "result was %d", pkt.total);
+}
+
+static void greedy_thread_entry(void *p1, void *p2, void *p3)
+{
+ int err = zbus_chan_claim(&busy_chan, K_MSEC(500));
+
+ zassert_equal(err, 0, "Could not claim the channel");
+ k_msleep(2000);
+ zassert_equal(0, zbus_chan_finish(&busy_chan), NULL);
+}
+
+K_THREAD_STACK_DEFINE(greedy_thread_stack_area, 1024);
+
+static struct k_thread greedy_thread_data;
+
+ZTEST(integration, test_event_dispatcher_mutex_timeout)
+{
+ struct action_msg read;
+ struct action_msg sent = {.status = true};
+
+ int err = zbus_chan_read(&busy_chan, &read, K_NO_WAIT);
+
+ zassert_equal(err, 0, "Could not read the channel");
+
+ zassert_equal(read.status, 0, "Read status must be false");
+
+ k_thread_create(&greedy_thread_data, greedy_thread_stack_area,
+ K_THREAD_STACK_SIZEOF(greedy_thread_stack_area), greedy_thread_entry, NULL,
+ NULL, NULL, CONFIG_ZTEST_THREAD_PRIORITY, K_USER | K_INHERIT_PERMS,
+ K_NO_WAIT);
+
+ k_msleep(500);
+
+ err = zbus_chan_pub(&busy_chan, &sent, K_MSEC(200));
+ zassert_equal(err, -EAGAIN, "Channel must be busy and could no be published %d", err);
+ err = zbus_chan_read(&busy_chan, &read, K_MSEC(200));
+ zassert_equal(err, -EAGAIN, "Channel must be busy and could no be published %d", err);
+ err = zbus_chan_claim(&busy_chan, K_MSEC(200));
+ zassert_equal(err, -EAGAIN, "Channel must be busy and could no be claimed %d", err);
+ err = zbus_chan_pub(&busy_chan, &sent, K_MSEC(200));
+ zassert_equal(err, -EAGAIN, "Channel must be busy and could no be published %d", err);
+ /* Wait for the greedy thread to finish and pub and read successfully */
+ err = zbus_chan_pub(&busy_chan, &sent, K_MSEC(2000));
+ zassert_equal(err, 0, "Channel must be busy and could no be published %d", err);
+ err = zbus_chan_read(&busy_chan, &read, K_MSEC(2000));
+ zassert_equal(err, 0, "Could not read the channel");
+
+ zassert_equal(read.status, true, "Read status must be false");
+}
+
+ZTEST(integration, test_event_dispatcher_queue_timeout)
+{
+ struct action_msg sent = {.status = true};
+ struct action_msg read = {.status = true};
+
+ zassert_equal(0, zbus_obs_set_enable(&core_sub, false), NULL);
+ zassert_equal(0, zbus_obs_set_enable(&net_sub, false), NULL);
+ int err = zbus_chan_pub(&start_measurement_chan, &sent, K_MSEC(100));
+
+ zassert_equal(err, 0, "Could not pub the channel");
+ k_msleep(10);
+ sent.status = false;
+ err = zbus_chan_pub(&start_measurement_chan, &sent, K_MSEC(100));
+ zassert_equal(err, 0, "Could not pub the channel");
+ k_msleep(10);
+ err = zbus_chan_pub(&start_measurement_chan, &sent, K_MSEC(100));
+ zassert_equal(err, -EAGAIN, "Pub to the channel %d must not occur here", err);
+ err = zbus_chan_read(&start_measurement_chan, &read, K_NO_WAIT);
+ zassert_equal(err, 0, "Could not read the channel");
+ zassert_equal(read.status, false,
+ "Read status must be false. The notification was not sent, but "
+ "the channel actually changed");
+ k_msleep(500);
+ zassert_equal(count_callback, 3, NULL);
+ zassert_equal(count_peripheral, 2, NULL);
+}
+
+ZTEST_SUITE(integration, NULL, NULL, context_reset, NULL, NULL);
diff --git a/tests/subsys/zbus/integration/src/messages.h b/tests/subsys/zbus/integration/src/messages.h
new file mode 100644
index 0000000000000..aa80c1d7176ea
--- /dev/null
+++ b/tests/subsys/zbus/integration/src/messages.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef _ZBUS_MESSAGES_H_
+#define _ZBUS_MESSAGES_H_
+#include
+#include
+
+struct version_msg {
+ uint8_t major;
+ uint8_t minor;
+ uint16_t build;
+};
+
+struct sensor_data_msg {
+ int a;
+ int b;
+};
+
+struct net_pkt_msg {
+ int total;
+};
+
+struct action_msg {
+ bool status;
+};
+
+#endif /* _ZBUS_MESSAGES_H_ */
diff --git a/tests/subsys/zbus/integration/testcase.yaml b/tests/subsys/zbus/integration/testcase.yaml
new file mode 100644
index 0000000000000..03d4feff47fdc
--- /dev/null
+++ b/tests/subsys/zbus/integration/testcase.yaml
@@ -0,0 +1,5 @@
+tests:
+ integration.module_interaction_no_error:
+ build_only: false
+ platform_exclude: qemu_cortex_a9 hifive_unleashed fvp_base_revc_2xaemv8a_smp_ns
+ tags: zbus
diff --git a/tests/subsys/zbus/runtime_observers_registration/CMakeLists.txt b/tests/subsys/zbus/runtime_observers_registration/CMakeLists.txt
new file mode 100644
index 0000000000000..2c3ab9232c546
--- /dev/null
+++ b/tests/subsys/zbus/runtime_observers_registration/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(test_runtime_observers_registration)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/tests/subsys/zbus/runtime_observers_registration/prj.conf b/tests/subsys/zbus/runtime_observers_registration/prj.conf
new file mode 100644
index 0000000000000..917ca1fbd8f96
--- /dev/null
+++ b/tests/subsys/zbus/runtime_observers_registration/prj.conf
@@ -0,0 +1,10 @@
+CONFIG_ZTEST=y
+CONFIG_ZTEST_NEW_API=y
+# CONFIG_ASSERT=y
+CONFIG_LOG=y
+CONFIG_ZBUS=y
+CONFIG_ZBUS_ASSERT_MOCK=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
+CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE=6
+CONFIG_ZBUS_OBSERVER_NAME=y
+CONFIG_MEM_SLAB_TRACE_MAX_UTILIZATION=y
diff --git a/tests/subsys/zbus/runtime_observers_registration/src/main.c b/tests/subsys/zbus/runtime_observers_registration/src/main.c
new file mode 100644
index 0000000000000..87ac46eed1795
--- /dev/null
+++ b/tests/subsys/zbus/runtime_observers_registration/src/main.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include
+#include
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+struct sensor_data_msg {
+ int a;
+ int b;
+};
+
+ZBUS_CHAN_DEFINE(chan1, /* Name */
+ struct sensor_data_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZBUS_CHAN_DEFINE(chan2, /* Name */
+ struct sensor_data_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(lis2), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZBUS_CHAN_DEFINE(chan3, /* Name */
+ struct sensor_data_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZBUS_SUBSCRIBER_DEFINE(sub1, 1);
+ZBUS_SUBSCRIBER_DEFINE(sub2, 1);
+
+static int count_callback1;
+static void callback1(const struct zbus_channel *chan)
+{
+ ++count_callback1;
+}
+
+ZBUS_LISTENER_DEFINE(lis1, callback1);
+
+static int count_callback2;
+static void callback2(const struct zbus_channel *chan)
+{
+ ++count_callback2;
+}
+
+ZBUS_LISTENER_DEFINE(lis2, callback2);
+ZBUS_LISTENER_DEFINE(lis3, callback2);
+ZBUS_LISTENER_DEFINE(lis4, callback2);
+ZBUS_LISTENER_DEFINE(lis5, callback2);
+ZBUS_LISTENER_DEFINE(lis6, callback2);
+ZBUS_LISTENER_DEFINE(lis7, callback2);
+
+ZTEST(basic, test_specification_based__zbus_obs_add_rm_obs)
+{
+ count_callback1 = 0;
+ struct sensor_data_msg sd = {.a = 10, .b = 100};
+
+ zassert_equal(CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE,
+ k_mem_slab_num_free_get(zbus_runtime_obs_pool()), NULL);
+ /* Tyring to add same static observer as one dynamic */
+ zassert_equal(-EEXIST, zbus_chan_add_obs(&chan2, &lis2, K_MSEC(200)), NULL);
+
+ zassert_equal(0, zbus_chan_pub(&chan1, &sd, K_MSEC(500)), NULL);
+ zassert_equal(count_callback1, 0, "The counter could not be more than zero, no obs");
+
+ zassert_equal(0, zbus_chan_add_obs(&chan1, &lis1, K_MSEC(200)), NULL);
+ zassert_equal(CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE - 1,
+ k_mem_slab_num_free_get(zbus_runtime_obs_pool()), NULL);
+ zassert_equal(-EALREADY, zbus_chan_add_obs(&chan1, &lis1, K_MSEC(200)),
+ "It cannot be added twice");
+
+ zassert_equal(1, k_mem_slab_max_used_get(zbus_runtime_obs_pool()), NULL);
+ zassert_equal(CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE - 1,
+ k_mem_slab_num_free_get(zbus_runtime_obs_pool()), NULL);
+
+ zassert_equal(0, zbus_chan_pub(&chan1, &sd, K_MSEC(500)), NULL);
+ zassert_equal(count_callback1, 1, "The counter could not be more than zero, no obs, %d",
+ count_callback1);
+
+ zassert_equal(0, zbus_chan_rm_obs(&chan1, &lis1, K_MSEC(200)), "It must remove the obs");
+ zassert_equal(CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE,
+ k_mem_slab_num_free_get(zbus_runtime_obs_pool()), NULL);
+
+ zassert_equal(1, k_mem_slab_max_used_get(zbus_runtime_obs_pool()), NULL);
+ zassert_equal(-ENODATA, zbus_chan_rm_obs(&chan1, &lis1, K_MSEC(200)),
+ "It cannot be removed twice");
+
+ zassert_equal(CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE,
+ k_mem_slab_num_free_get(zbus_runtime_obs_pool()), NULL);
+ zassert_equal(0, zbus_chan_pub(&chan1, &sd, K_MSEC(500)), NULL);
+ zassert_equal(count_callback1, 1, "The counter could not be more than zero, no obs, %d",
+ count_callback1);
+
+ count_callback2 = 0;
+
+ zassert_equal(0, zbus_chan_pub(&chan2, &sd, K_MSEC(500)), NULL);
+ zassert_equal(count_callback2, 1, "The counter could not be more than zero, no obs");
+
+ zassert_equal(0, zbus_chan_add_obs(&chan2, &lis3, K_MSEC(200)), NULL);
+
+ zassert_equal(-EALREADY, zbus_chan_add_obs(&chan2, &lis3, K_MSEC(200)),
+ "It cannot be added twice");
+
+ zassert_equal(0, zbus_chan_pub(&chan2, &sd, K_MSEC(500)), NULL);
+ zassert_equal(count_callback2, 3, "The counter could not be more than zero, no obs, %d",
+ count_callback2);
+ count_callback2 = 0;
+ zassert_equal(0, zbus_chan_add_obs(&chan2, &sub1, K_MSEC(200)), NULL);
+ zassert_equal(0, zbus_chan_add_obs(&chan2, &sub2, K_MSEC(200)), NULL);
+ zassert_equal(0, zbus_chan_add_obs(&chan2, &lis4, K_MSEC(200)), "It must add the obs");
+ zassert_equal(0, zbus_chan_add_obs(&chan2, &lis5, K_MSEC(200)), "It must add the obs");
+ zassert_equal(0, zbus_chan_add_obs(&chan2, &lis6, K_MSEC(200)), "It must add the obs");
+ zassert_equal(-EAGAIN, zbus_chan_add_obs(&chan2, &lis7, K_MSEC(200)),
+ "It must add the obs");
+ zassert_equal(0, zbus_chan_pub(&chan2, &sd, K_MSEC(500)), NULL);
+ zassert_equal(count_callback2, 5, NULL);
+ /* To cause an error to sub1 and sub2. They have the queue full in this point */
+ /* An error message must be printed saying the */
+ zassert_equal(-EFAULT, zbus_chan_pub(&chan2, &sd, K_MSEC(500)), NULL);
+ zassert_equal(count_callback2, 10, NULL);
+
+ zassert_equal(CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE,
+ k_mem_slab_max_used_get(zbus_runtime_obs_pool()), NULL);
+
+ zassert_equal(0, k_mem_slab_num_free_get(zbus_runtime_obs_pool()), NULL);
+ zassert_equal(0, zbus_chan_rm_obs(&chan2, &sub1, K_MSEC(200)), NULL);
+ zassert_equal(0, zbus_chan_rm_obs(&chan2, &sub2, K_MSEC(200)), NULL);
+ zassert_equal(2, k_mem_slab_num_free_get(zbus_runtime_obs_pool()), NULL);
+
+ zassert_equal(CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE,
+ k_mem_slab_max_used_get(zbus_runtime_obs_pool()), NULL);
+}
+
+struct aux2_wq_data {
+ struct k_work work;
+};
+
+static struct aux2_wq_data wq_handler;
+
+static void wq_dh_cb(struct k_work *item)
+{
+ zassert_equal(-EAGAIN, zbus_chan_add_obs(&chan2, &sub1, K_MSEC(200)), NULL);
+ zassert_equal(-EAGAIN, zbus_chan_rm_obs(&chan2, &sub2, K_MSEC(200)), NULL);
+}
+
+ZTEST(basic, test_specification_based__zbus_obs_add_rm_obs_busy)
+{
+ zassert_equal(0, zbus_chan_claim(&chan2, K_NO_WAIT), NULL);
+
+ k_work_init(&wq_handler.work, wq_dh_cb);
+ k_work_submit(&wq_handler.work);
+ k_msleep(1000);
+
+ zassert_equal(2, k_mem_slab_num_free_get(zbus_runtime_obs_pool()), NULL);
+ zassert_equal(0, zbus_chan_finish(&chan2), NULL);
+}
+
+ZTEST_SUITE(basic, NULL, NULL, NULL, NULL, NULL);
diff --git a/tests/subsys/zbus/runtime_observers_registration/testcase.yaml b/tests/subsys/zbus/runtime_observers_registration/testcase.yaml
new file mode 100644
index 0000000000000..10472f5507f14
--- /dev/null
+++ b/tests/subsys/zbus/runtime_observers_registration/testcase.yaml
@@ -0,0 +1,4 @@
+tests:
+ runtime_obs_reg.add_and_remove_observers:
+ build_only: false
+ tags: zbus
diff --git a/tests/subsys/zbus/unittests/CMakeLists.txt b/tests/subsys/zbus/unittests/CMakeLists.txt
new file mode 100644
index 0000000000000..586400f77ec41
--- /dev/null
+++ b/tests/subsys/zbus/unittests/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(test_unit)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/tests/subsys/zbus/unittests/prj.conf b/tests/subsys/zbus/unittests/prj.conf
new file mode 100644
index 0000000000000..395c033a7b552
--- /dev/null
+++ b/tests/subsys/zbus/unittests/prj.conf
@@ -0,0 +1,11 @@
+CONFIG_ZTEST=y
+CONFIG_ZTEST_NEW_API=y
+CONFIG_ASSERT=y
+CONFIG_LOG=y
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
+CONFIG_ZBUS_ASSERT_MOCK=y
+CONFIG_IRQ_OFFLOAD=y
+CONFIG_ZBUS_RUNTIME_OBSERVERS_POOL_SIZE=4
+CONFIG_ZBUS_CHANNEL_NAME=y
+CONFIG_ZBUS_OBSERVER_NAME=y
diff --git a/tests/subsys/zbus/unittests/src/main.c b/tests/subsys/zbus/unittests/src/main.c
new file mode 100644
index 0000000000000..b9e00d247f3c9
--- /dev/null
+++ b/tests/subsys/zbus/unittests/src/main.c
@@ -0,0 +1,565 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "messages.h"
+
+#include
+#include
+#include
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+static bool hard_msg_validator(const void *msg, size_t msg_size)
+{
+ ARG_UNUSED(msg_size);
+
+ const struct hard_msg *ref = (struct hard_msg *)msg;
+
+ return (ref->range >= 0) && (ref->range <= 1023) && (ref->binary <= 1) &&
+ (ref->pointer != NULL);
+}
+
+ZBUS_CHAN_DEFINE(version_chan, /* Name */
+ struct version_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(.major = 0, .minor = 1,
+ .build = 1023) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZBUS_CHAN_DEFINE(aux1_chan, /* Name */
+ struct s1_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(fast_lis), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZBUS_CHAN_DEFINE(aux2_chan, /* Name */
+ struct action_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(fast_lis), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZBUS_CHAN_DEFINE(aux3_on_change_chan, /* Name */
+ struct action_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(fast_lis), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZBUS_CHAN_DEFINE(go_busy_chan, /* Name */
+ struct action_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS(busy_lis), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZBUS_CHAN_DEFINE(hard_chan, /* Name */
+ struct hard_msg, /* Message type */
+
+ hard_msg_validator, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZBUS_CHAN_DEFINE(stuck_chan, /* Name */
+ struct hard_msg, /* Message type */
+
+ hard_msg_validator, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value major 0, minor 1, build 1023 */
+);
+
+static int count_fast;
+
+static void callback(const struct zbus_channel *chan)
+{
+ ++count_fast;
+}
+
+ZBUS_LISTENER_DEFINE(fast_lis, callback);
+
+ZBUS_LISTENER_DEFINE(rt_fast_lis, callback);
+
+static int isr_return;
+
+enum operation {
+ PUB_ISR_INVAL,
+ READ_ISR_INVAL,
+ NOTIFY_ISR_INVAL,
+ CLAIM_ISR_INVAL,
+ FINISH_ISR_INVAL,
+ ADD_OBS_ISR_INVAL,
+ RM_OBS_ISR_INVAL,
+ PUB_ISR,
+ READ_ISR,
+ NOTIFY_ISR,
+ CLAIM_ISR,
+ FINISH_ISR,
+ ADD_OBS_ISR,
+ RM_OBS_ISR,
+ NONE
+};
+
+static enum operation current_isr_operation = NONE;
+
+static struct action_msg ga;
+
+static void isr_handler(const void *operation)
+{
+ enum operation *op = (enum operation *)operation;
+
+ switch (*op) {
+ case PUB_ISR_INVAL:
+ isr_return = zbus_chan_pub(&aux2_chan, &ga, K_MSEC(500));
+ break;
+ case READ_ISR_INVAL:
+ isr_return = zbus_chan_read(&aux2_chan, &ga, K_MSEC(500));
+ break;
+ case NOTIFY_ISR_INVAL:
+ isr_return = zbus_chan_notify(&aux2_chan, K_MSEC(100));
+ break;
+ case CLAIM_ISR_INVAL:
+ isr_return = zbus_chan_claim(&aux2_chan, K_MSEC(200));
+ break;
+ case FINISH_ISR_INVAL:
+ isr_return = zbus_chan_finish(NULL);
+ break;
+ case ADD_OBS_ISR_INVAL:
+ isr_return = zbus_chan_add_obs(&aux2_chan, &fast_lis, K_MSEC(200));
+ break;
+ case RM_OBS_ISR_INVAL:
+ isr_return = zbus_chan_rm_obs(&aux2_chan, &fast_lis, K_MSEC(200));
+ break;
+ case PUB_ISR:
+ isr_return = zbus_chan_pub(&aux2_chan, &ga, K_NO_WAIT);
+ break;
+ case READ_ISR:
+ isr_return = zbus_chan_read(&aux2_chan, &ga, K_NO_WAIT);
+ break;
+ case NOTIFY_ISR:
+ isr_return = zbus_chan_notify(&aux2_chan, K_NO_WAIT);
+ break;
+ case CLAIM_ISR:
+ isr_return = zbus_chan_claim(&aux2_chan, K_NO_WAIT);
+ break;
+ case FINISH_ISR:
+ isr_return = zbus_chan_finish(&aux2_chan);
+ break;
+ case ADD_OBS_ISR:
+ isr_return = zbus_chan_add_obs(&aux2_chan, NULL, K_MSEC(200));
+ break;
+ case RM_OBS_ISR:
+ isr_return = zbus_chan_rm_obs(&aux2_chan, NULL, K_MSEC(200));
+ break;
+ case NONE:
+ break;
+ }
+}
+
+static void busy_callback(const struct zbus_channel *chan)
+{
+ zbus_chan_claim(&go_busy_chan, K_NO_WAIT);
+}
+
+ZBUS_LISTENER_DEFINE(busy_lis, busy_callback);
+
+#define ISR_OP(_op, _exp) \
+ isr_return = 0; \
+ current_isr_operation = _op; \
+ irq_offload(isr_handler, ¤t_isr_operation); \
+ zassert_equal(_exp, isr_return, "isr return wrong, it is %d", isr_return); \
+ k_msleep(100);
+
+struct aux2_wq_data {
+ struct k_work work;
+};
+
+static struct aux2_wq_data wq_handler;
+
+static void wq_dh_cb(struct k_work *item)
+{
+ struct action_msg a = {0};
+
+ zassert_equal(-EBUSY, zbus_chan_pub(&aux2_chan, &a, K_NO_WAIT), "It must not be valid");
+
+ zassert_equal(-EBUSY, zbus_chan_read(&aux2_chan, &a, K_NO_WAIT), "It must not be valid");
+
+ zassert_equal(-EBUSY, zbus_chan_notify(&aux2_chan, K_NO_WAIT), "It must not be invalid");
+
+ zassert_equal(-EFAULT, zbus_chan_finish(NULL), "It must be invalid");
+}
+
+ZBUS_SUBSCRIBER_DEFINE(sub1, 1);
+
+ZTEST(basic, test_specification_based__zbus_chan)
+{
+ struct action_msg a = {0};
+
+ /* Trying invalid parameters */
+ zassert_equal(-EFAULT, zbus_chan_pub(NULL, &a, K_NO_WAIT), "It must be -EFAULT");
+
+ k_msleep(100);
+
+ zassert_equal(-EFAULT, zbus_chan_pub(&aux2_chan, NULL, K_NO_WAIT), "It must be -EFAULT");
+
+ k_msleep(100);
+
+ zassert_equal(-EFAULT, zbus_chan_read(NULL, &a, K_NO_WAIT), "It must be -EFAULT");
+
+ k_msleep(100);
+
+ zassert_equal(-EFAULT, zbus_chan_read(&aux2_chan, NULL, K_NO_WAIT), "It must be -EFAULT");
+
+ k_msleep(100);
+
+ zassert_equal(-EFAULT, zbus_chan_notify(NULL, K_NO_WAIT), "It must be -EFAULT");
+
+ zassert_equal(-EFAULT, zbus_chan_claim(NULL, K_NO_WAIT), "It must be -EFAULT");
+
+ zassert_equal(-EFAULT, zbus_chan_finish(NULL), "It must be -EFAULT");
+
+ zassert_equal(-EFAULT, zbus_chan_add_obs(NULL, &sub1, K_MSEC(200)), NULL);
+
+ zassert_equal(-EFAULT, zbus_chan_add_obs(&aux2_chan, NULL, K_MSEC(200)), NULL);
+
+ zassert_equal(-EFAULT, zbus_chan_rm_obs(NULL, &sub1, K_MSEC(200)), NULL);
+
+ zassert_equal(-EFAULT, zbus_chan_rm_obs(&aux2_chan, NULL, K_MSEC(200)), NULL);
+ /* Trying valid parameters */
+ zassert_equal(0, zbus_chan_pub(&aux2_chan, &a, K_NO_WAIT), "It must be valid");
+
+ k_msleep(100);
+
+ zassert_equal(0, zbus_chan_read(&aux2_chan, &a, K_NO_WAIT), "It must be valid");
+
+ k_msleep(100);
+
+ zassert_equal(0, zbus_chan_notify(&aux2_chan, K_NO_WAIT), "It must be valid");
+
+ zassert_equal(0, zbus_chan_claim(&aux2_chan, K_NO_WAIT), "It must be valid");
+
+ k_work_init(&wq_handler.work, wq_dh_cb);
+ k_work_submit(&wq_handler.work);
+
+ k_msleep(100);
+
+ zassert_equal(0, zbus_chan_pub(&aux2_chan, &a, K_NO_WAIT), "It must not be valid");
+
+ zassert_equal(0, zbus_chan_read(&aux2_chan, &a, K_NO_WAIT), "It must not be valid");
+
+ zassert_equal(0, zbus_chan_notify(&aux2_chan, K_NO_WAIT), "It must not be invalid");
+
+ zassert_equal(0, zbus_chan_finish(&aux2_chan), "It must finish correctly");
+
+ struct action_msg repeated = {.status = false};
+
+ zbus_chan_pub(&aux3_on_change_chan, &repeated, K_NO_WAIT);
+
+ k_msleep(100);
+
+ zbus_chan_pub(&aux3_on_change_chan, &repeated, K_NO_WAIT);
+
+ k_msleep(100);
+
+ zassert_equal(0, zbus_chan_pub(&go_busy_chan, &repeated, K_NO_WAIT),
+ "It must be ok, but it causes an error inside the event dispatcher telling "
+ "the channel is busy.");
+
+ k_msleep(100);
+
+ zassert_equal(0, zbus_chan_add_obs(&stuck_chan, &sub1, K_MSEC(200)), NULL);
+
+ zassert_equal(0, zbus_chan_notify(&stuck_chan, K_MSEC(200)), "It must finish correctly");
+
+ zassert_equal(-EFAULT, zbus_chan_notify(&stuck_chan, K_MSEC(200)),
+ "It must finish correctly");
+
+ /* Trying to call the zbus functions in a ISR context. None must work */
+ ISR_OP(PUB_ISR, -EFAULT);
+ ISR_OP(PUB_ISR_INVAL, -EFAULT);
+ ISR_OP(READ_ISR, -EFAULT);
+ ISR_OP(READ_ISR_INVAL, -EFAULT);
+ ISR_OP(NOTIFY_ISR, -EFAULT);
+ ISR_OP(NOTIFY_ISR_INVAL, -EFAULT);
+ ISR_OP(CLAIM_ISR, -EFAULT);
+ ISR_OP(CLAIM_ISR_INVAL, -EFAULT);
+ ISR_OP(FINISH_ISR, -EFAULT);
+ ISR_OP(FINISH_ISR_INVAL, -EFAULT);
+ ISR_OP(ADD_OBS_ISR, -EFAULT);
+ ISR_OP(ADD_OBS_ISR_INVAL, -EFAULT);
+ ISR_OP(RM_OBS_ISR, -EFAULT);
+ ISR_OP(RM_OBS_ISR_INVAL, -EFAULT);
+}
+
+#if defined(CONFIG_ZBUS_STRUCTS_ITERABLE_ACCESS)
+static bool always_true_chan_iterator(const struct zbus_channel *chan)
+{
+ return true;
+}
+
+static bool always_true_obs_iterator(const struct zbus_observer *obs)
+{
+ return true;
+}
+
+static bool always_false_chan_iterator(const struct zbus_channel *chan)
+{
+ return false;
+}
+
+static bool always_false_obs_iterator(const struct zbus_observer *obs)
+{
+ return false;
+}
+
+static bool check_chan_iterator(const struct zbus_channel *chan)
+{
+ static int chan_idx;
+
+ LOG_DBG("Idx %d - Channel %s", chan_idx, chan->name);
+
+ switch (chan_idx) {
+ case 0:
+ zassert_mem_equal__(zbus_chan_name(chan), "aux1_chan", 9, "Must be equal");
+ break;
+ case 1:
+ zassert_mem_equal__(zbus_chan_name(chan), "aux2_chan", 9, "Must be equal");
+ break;
+ case 2:
+ zassert_mem_equal__(zbus_chan_name(chan), "aux3_on_change_chan", 19,
+ "Must be equal");
+ break;
+ case 3:
+ zassert_mem_equal__(zbus_chan_name(chan), "go_busy_chan", 12, "Must be equal");
+ break;
+ case 4:
+ zassert_mem_equal__(zbus_chan_name(chan), "hard_chan", 9, "Must be equal");
+ break;
+ case 5:
+ zassert_mem_equal__(zbus_chan_name(chan), "stuck_chan", 10, "Must be equal");
+ break;
+ case 6:
+ zassert_mem_equal__(zbus_chan_name(chan), "version_chan", 12, "Must be equal");
+ break;
+ default:
+ zassert_unreachable(NULL);
+ }
+ ++chan_idx;
+
+ return true;
+}
+
+static bool check_obs_iterator(const struct zbus_observer *obs)
+{
+ static int obs_idx;
+
+ LOG_DBG("Idx %d - Observer %s", obs_idx, obs->name);
+
+ switch (obs_idx) {
+ case 0:
+ zassert_mem_equal__(zbus_obs_name(obs), "busy_lis", 8, "Must be equal");
+ break;
+ case 1:
+ zassert_mem_equal__(zbus_obs_name(obs), "fast_lis", 8, "Must be equal");
+ break;
+ case 2:
+ zassert_mem_equal__(zbus_obs_name(obs), "foo_sub", 7, "Must be equal");
+ break;
+ case 3:
+ zassert_mem_equal__(zbus_obs_name(obs), "rt_fast_lis", 11, "Must be equal");
+ break;
+ case 4:
+ zassert_mem_equal__(zbus_obs_name(obs), "sub1", 4, "Must be equal");
+ break;
+ default:
+ zassert_unreachable(NULL);
+ }
+ ++obs_idx;
+
+ return true;
+}
+
+static int oracle;
+static int idx = -1;
+
+static bool count_false_chan_iterator(const struct zbus_channel *chan)
+{
+ ++idx;
+
+ LOG_DBG("chan_idx %d, oracle %d", idx, oracle);
+
+ return (bool)(idx != oracle);
+}
+
+static bool count_false_obs_iterator(const struct zbus_observer *obs)
+{
+ ++idx;
+
+ LOG_DBG("obs_idx %d, oracle %d", idx, oracle);
+
+ return (bool)(idx != oracle);
+}
+
+ZTEST(basic, test_iterators)
+{
+ zassert_equal(true, zbus_iterate_over_channels(always_true_chan_iterator), "Must be true");
+
+ zassert_equal(true, zbus_iterate_over_observers(always_true_obs_iterator), "Must be true");
+
+ zassert_equal(false, zbus_iterate_over_channels(always_false_chan_iterator),
+ "Must be false");
+
+ zassert_equal(false, zbus_iterate_over_observers(always_false_obs_iterator),
+ "Must be false");
+
+ zassert_equal(true, zbus_iterate_over_channels(check_chan_iterator), "Must be true");
+
+ zassert_equal(true, zbus_iterate_over_observers(check_obs_iterator), "Must be true");
+
+ int chan_count = 0;
+
+ STRUCT_SECTION_COUNT(zbus_channel, &chan_count);
+
+ chan_count -= 1;
+
+ for (int i = 0; i < chan_count; ++i) {
+ oracle = i;
+
+ zassert_equal(false, zbus_iterate_over_channels(count_false_chan_iterator),
+ "Must be false");
+
+ k_msleep(100);
+
+ idx = -1;
+ }
+ int obs_count = 0;
+
+ STRUCT_SECTION_COUNT(zbus_observer, &obs_count);
+
+ obs_count -= 1;
+
+ LOG_DBG("Counts obs %d, chan %d", obs_count, chan_count);
+
+ for (int i = 0; i < obs_count; ++i) {
+ oracle = i;
+
+ zassert_equal(false, zbus_iterate_over_observers(count_false_obs_iterator),
+ "Must be false");
+
+ idx = -1;
+ }
+}
+#else
+ZTEST(basic, test_iterators)
+{
+ ztest_test_skip();
+}
+#endif
+
+ZTEST(basic, test_hard_channel)
+{
+ struct hard_msg valid = {.range = 10, .binary = 1, .pointer = &valid.range};
+
+ zbus_chan_pub(&hard_chan, &valid, K_NO_WAIT);
+
+ struct hard_msg current;
+
+ zbus_chan_read(&hard_chan, ¤t, K_NO_WAIT);
+
+ zassert_equal(valid.range, current.range, "Range must be equal");
+ zassert_equal(valid.binary, current.binary, "Binary must be equal");
+ zassert_equal(valid.pointer, current.pointer, "Pointer must be equal");
+
+ struct hard_msg invalid = {.range = 10000, .binary = 1, .pointer = &valid.range};
+ int err = zbus_chan_pub(&hard_chan, &invalid, K_NO_WAIT);
+
+ zassert_equal(err, -ENOMSG, "Err must be -ENOMSG, the channel message is invalid");
+
+ invalid = (struct hard_msg){.range = 1, .binary = 3, .pointer = &invalid.range};
+ err = zbus_chan_pub(&hard_chan, &invalid, K_NO_WAIT);
+ zassert_equal(err, -ENOMSG, "Err must be -ENOMSG, the channel message is invalid");
+
+ invalid = (struct hard_msg){.range = 1, .binary = 0, .pointer = NULL};
+ err = zbus_chan_pub(&hard_chan, &invalid, K_NO_WAIT);
+ zassert_equal(err, -ENOMSG, "Err must be -ENOMSG, the channel message is invalid");
+}
+
+ZTEST(basic, test_specification_based__zbus_obs_set_enable)
+{
+ count_fast = 0;
+
+ zassert_equal(-EFAULT, zbus_obs_set_enable(NULL, false), NULL);
+
+ zassert_equal(0, zbus_obs_set_enable(&rt_fast_lis, false),
+ "Must be zero. The observer must be disabled");
+
+ zassert_equal(0, zbus_chan_add_obs(&aux1_chan, &rt_fast_lis, K_MSEC(200)), NULL);
+
+ zassert_equal(0, zbus_obs_set_enable(&fast_lis, false),
+ "Must be zero. The observer must be disabled");
+
+ zbus_chan_notify(&aux1_chan, K_NO_WAIT);
+
+ k_msleep(300);
+
+ zassert_equal(count_fast, 0, "Must be zero. No callback called");
+
+ zassert_equal(0, zbus_obs_set_enable(&fast_lis, true),
+ "Must be zero. The observer must be enabled");
+
+ zassert_equal(0, zbus_obs_set_enable(&rt_fast_lis, true),
+ "Must be zero. The observer must be enabled");
+
+ zbus_chan_notify(&aux1_chan, K_NO_WAIT);
+
+ k_msleep(300);
+
+ zassert_equal(count_fast, 2, "Must be 2. Callback must be called once");
+
+ zassert_equal(0, zbus_chan_rm_obs(&aux1_chan, &rt_fast_lis, K_MSEC(200)), NULL);
+}
+
+ZBUS_SUBSCRIBER_DEFINE(foo_sub, 1);
+
+static void isr_sub_wait(const void *operation)
+{
+ const struct zbus_channel *chan;
+
+ /* All the calls must not work. Zbus cannot work in IRSs */
+ zassert_equal(-EFAULT, zbus_sub_wait(NULL, NULL, K_NO_WAIT), NULL);
+ zassert_equal(-EFAULT, zbus_sub_wait(&foo_sub, NULL, K_NO_WAIT), NULL);
+ zassert_equal(-EFAULT, zbus_sub_wait(&foo_sub, &chan, K_NO_WAIT), NULL);
+}
+
+ZTEST(basic, test_specification_based__zbus_sub_wait)
+{
+ count_fast = 0;
+ const struct zbus_channel *chan;
+
+ zassert_equal(-EFAULT, zbus_sub_wait(NULL, NULL, K_NO_WAIT), NULL);
+ zassert_equal(-EFAULT, zbus_sub_wait(&foo_sub, NULL, K_NO_WAIT), NULL);
+
+ /* It must run but return a -ENOMSG because of the K_NO_WAIT */
+ zassert_equal(-ENOMSG, zbus_sub_wait(&foo_sub, &chan, K_NO_WAIT), NULL);
+
+ irq_offload(isr_sub_wait, NULL);
+}
+
+ZTEST_SUITE(basic, NULL, NULL, NULL, NULL, NULL);
diff --git a/tests/subsys/zbus/unittests/src/messages.h b/tests/subsys/zbus/unittests/src/messages.h
new file mode 100644
index 0000000000000..16834652527ed
--- /dev/null
+++ b/tests/subsys/zbus/unittests/src/messages.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef _ZBUS_MESSAGES_H_
+#define _ZBUS_MESSAGES_H_
+#include
+#include
+#include
+
+struct version_msg {
+ uint8_t major;
+ uint8_t minor;
+ uint16_t build;
+};
+
+struct sensor_data_msg {
+ int a;
+ int b;
+};
+
+struct net_pkt_msg {
+ char x;
+ bool y;
+};
+
+struct action_msg {
+ bool status;
+};
+
+struct s1_msg {
+ int m;
+ struct {
+ int a, b, c;
+ } field;
+};
+
+struct hard_msg {
+ int16_t range; /* Range 0 to 1023 */
+ uint8_t binary; /* Range 0 to 1 */
+ int16_t *pointer; /* Not null */
+};
+
+#endif /* _ZBUS_MESSAGES_H_ */
diff --git a/tests/subsys/zbus/unittests/testcase.yaml b/tests/subsys/zbus/unittests/testcase.yaml
new file mode 100644
index 0000000000000..e9bdb3db6989f
--- /dev/null
+++ b/tests/subsys/zbus/unittests/testcase.yaml
@@ -0,0 +1,5 @@
+tests:
+ unittests.hard_and_readonly_channels:
+ build_only: false
+ platform_exclude: fvp_base_revc_2xaemv8a_smp_ns
+ tags: zbus
diff --git a/tests/subsys/zbus/user_data/CMakeLists.txt b/tests/subsys/zbus/user_data/CMakeLists.txt
new file mode 100644
index 0000000000000..cdfe01e1715eb
--- /dev/null
+++ b/tests/subsys/zbus/user_data/CMakeLists.txt
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: Apache-2.0
+cmake_minimum_required(VERSION 3.20.0)
+
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(test_user_data)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
diff --git a/tests/subsys/zbus/user_data/prj.conf b/tests/subsys/zbus/user_data/prj.conf
new file mode 100644
index 0000000000000..5e78e1b452e9c
--- /dev/null
+++ b/tests/subsys/zbus/user_data/prj.conf
@@ -0,0 +1,6 @@
+CONFIG_ZTEST=y
+CONFIG_ZTEST_NEW_API=y
+CONFIG_ASSERT=y
+CONFIG_LOG=y
+CONFIG_ZBUS=y
+CONFIG_ZBUS_LOG_LEVEL_DBG=y
diff --git a/tests/subsys/zbus/user_data/src/main.c b/tests/subsys/zbus/user_data/src/main.c
new file mode 100644
index 0000000000000..e74b49120f665
--- /dev/null
+++ b/tests/subsys/zbus/user_data/src/main.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "messages.h"
+
+#include
+#include
+#include
+#include
+LOG_MODULE_DECLARE(zbus, CONFIG_ZBUS_LOG_LEVEL);
+
+ZBUS_CHAN_DEFINE(version_chan, /* Name */
+ struct version_msg, /* Message type */
+
+ NULL, /* Validator */
+ NULL, /* User data */
+ ZBUS_OBSERVERS_EMPTY, /* observers */
+ ZBUS_MSG_INIT(.major = 0, .minor = 1,
+ .build = 2) /* Initial value major 0, minor 1, build 1023 */
+);
+
+static int my_user_data;
+
+ZBUS_CHAN_DEFINE(regular_chan, /* Name */
+ struct foo_msg, /* Message type */
+
+ NULL, /* Validator */
+ &my_user_data, /* User data */
+ ZBUS_OBSERVERS(foo_listener, foo_subscriber), /* observers */
+ ZBUS_MSG_INIT(0) /* Initial value major 0, minor 1, build 1023 */
+);
+
+ZTEST(user_data, test_channel_user_data)
+{
+ zassert_true(sizeof(my_user_data) > 0, NULL);
+
+ zassert_equal_ptr(version_chan.user_data, NULL, NULL);
+
+ zassert_equal_ptr(regular_chan.user_data, &my_user_data, NULL);
+
+ int *counter = regular_chan.user_data;
+ *counter = -2;
+
+ zassert_equal(zbus_chan_user_data(®ular_chan), counter, NULL);
+ zassert_equal(*(int *)zbus_chan_user_data(®ular_chan), -2, NULL);
+
+ memset(regular_chan.user_data, 0, sizeof(my_user_data));
+}
+
+static void urgent_callback(const struct zbus_channel *chan)
+{
+ if (chan == &(regular_chan)) {
+ int *count = zbus_chan_user_data(®ular_chan);
+
+ *count += 1;
+ }
+}
+
+ZBUS_LISTENER_DEFINE(foo_listener, urgent_callback);
+
+ZBUS_SUBSCRIBER_DEFINE(foo_subscriber, 1);
+
+static void foo_subscriber_thread(void)
+{
+ struct zbus_channel *chan = NULL;
+
+ while (1) {
+ if (!k_msgq_get(foo_subscriber.queue, &chan, K_FOREVER)) {
+ if (chan == &(regular_chan)) {
+ if (!zbus_chan_claim(®ular_chan, K_FOREVER)) {
+ int *count = zbus_chan_user_data(®ular_chan);
+
+ *count += 1;
+
+ zbus_chan_finish(®ular_chan);
+ }
+ }
+ }
+ }
+}
+
+K_THREAD_DEFINE(foo_subscriber_thread_id, 1024, foo_subscriber_thread, NULL, NULL, NULL, 3, 0, 0);
+
+ZTEST(user_data, test_user_data_regression)
+{
+ /* To ensure the pub/sub behavior is kept */
+ struct foo_msg sent = {.a = 10, .b = 1000};
+
+ zbus_chan_pub(®ular_chan, &sent, K_MSEC(100));
+
+ struct foo_msg received;
+
+ zbus_chan_read(®ular_chan, &received, K_MSEC(100));
+
+ zassert_equal(sent.a, received.a, NULL);
+
+ zassert_equal(sent.b, received.b, NULL);
+
+ k_msleep(1000);
+
+ if (!zbus_chan_claim(®ular_chan, K_FOREVER)) {
+ int *count = zbus_chan_user_data(®ular_chan);
+
+ zassert_equal(*count, 2, NULL);
+
+ zbus_chan_finish(®ular_chan);
+ }
+}
+
+ZTEST_SUITE(user_data, NULL, NULL, NULL, NULL, NULL);
diff --git a/tests/subsys/zbus/user_data/src/messages.h b/tests/subsys/zbus/user_data/src/messages.h
new file mode 100644
index 0000000000000..9e15469e5ede0
--- /dev/null
+++ b/tests/subsys/zbus/user_data/src/messages.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2022 Rodrigo Peixoto
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef _ZBUS_MESSAGES_H_
+#define _ZBUS_MESSAGES_H_
+#include
+
+struct version_msg {
+ uint8_t major;
+ uint8_t minor;
+ uint16_t build;
+};
+
+struct foo_msg {
+ int a;
+ int b;
+};
+
+#endif /* _ZBUS_MESSAGES_H_ */
diff --git a/tests/subsys/zbus/user_data/testcase.yaml b/tests/subsys/zbus/user_data/testcase.yaml
new file mode 100644
index 0000000000000..4058bf7d92fa2
--- /dev/null
+++ b/tests/subsys/zbus/user_data/testcase.yaml
@@ -0,0 +1,5 @@
+tests:
+ user_data.channel_user_data:
+ build_only: false
+ platform_exclude: fvp_base_revc_2xaemv8a_smp_ns
+ tags: zbus