diff --git a/CODEOWNERS b/CODEOWNERS index 7928a891855..49d404727da 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -284,6 +284,7 @@ /drivers/led/ @Mani-Sadhasivam /drivers/led_strip/ @mbolivar-nordic /drivers/lora/ @Mani-Sadhasivam +/drivers/mbox/ @carlocaione /drivers/memc/ @gmarull /drivers/misc/ @tejlmand /drivers/misc/ft8xx/ @hubertmis diff --git a/doc/reference/peripherals/index.rst b/doc/reference/peripherals/index.rst index e66f07929d0..194e3ac6bcd 100644 --- a/doc/reference/peripherals/index.rst +++ b/doc/reference/peripherals/index.rst @@ -23,6 +23,7 @@ Peripherals ipm.rst kscan.rst led.rst + mbox.rst pinmux.rst pwm.rst ps2.rst diff --git a/doc/reference/peripherals/mbox.rst b/doc/reference/peripherals/mbox.rst new file mode 100644 index 00000000000..b3b0c6f7e36 --- /dev/null +++ b/doc/reference/peripherals/mbox.rst @@ -0,0 +1,18 @@ +.. _mbox_api: + +MBOX +#### + +Overview +******** + +An MBOX device is a peripheral capable of passing signals (and data depending +on the peripheral) between CPUs and clusters in the system. Each MBOX instance +is providing one or more channels, each one targeting one other CPU cluster +(multiple channels can target the same cluster). + + +API Reference +************* + +.. doxygengroup:: mbox_interface diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 182060d7391..9837eb9f81d 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -62,3 +62,4 @@ add_subdirectory_ifdef(CONFIG_SYSCON syscon) add_subdirectory_ifdef(CONFIG_BBRAM bbram) add_subdirectory_ifdef(CONFIG_FPGA fpga) add_subdirectory_ifdef(CONFIG_PINCTRL pinctrl) +add_subdirectory_ifdef(CONFIG_MBOX mbox) diff --git a/drivers/Kconfig b/drivers/Kconfig index c21e9a29299..8390fcf0806 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -125,4 +125,6 @@ source "drivers/fpga/Kconfig" source "drivers/pinctrl/Kconfig" +source "drivers/mbox/Kconfig" + endmenu diff --git a/drivers/mbox/CMakeLists.txt b/drivers/mbox/CMakeLists.txt new file mode 100644 index 00000000000..b616f228f9a --- /dev/null +++ b/drivers/mbox/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_USERSPACE mbox_handlers.c) diff --git a/drivers/mbox/Kconfig b/drivers/mbox/Kconfig new file mode 100644 index 00000000000..d1c79945f58 --- /dev/null +++ b/drivers/mbox/Kconfig @@ -0,0 +1,8 @@ +# Copyright (c) 2021 Carlo Caione +# SPDX-License-Identifier: Apache-2.0 + +menuconfig MBOX + bool "MBOX drivers" + help + Include multi-channel interrupt-based inter-processor mailboxes + drivers in system configuration diff --git a/drivers/mbox/mbox_handlers.c b/drivers/mbox/mbox_handlers.c new file mode 100644 index 00000000000..89991037371 --- /dev/null +++ b/drivers/mbox/mbox_handlers.c @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Carlo Caione + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +static inline int z_vrfy_mbox_send(const struct mbox_channel *channel, + const struct mbox_msg *msg) +{ + Z_OOPS(Z_SYSCALL_MEMORY_READ(channel, sizeof(struct mbox_channel))); + Z_OOPS(Z_SYSCALL_DRIVER_MBOX(channel->dev, send)); + Z_OOPS(Z_SYSCALL_MEMORY_READ(msg, sizeof(struct mbox_msg))); + Z_OOPS(Z_SYSCALL_MEMORY_READ(msg->data, msg->size)); + + return z_impl_mbox_send(channel, msg); +} +#include + +static inline int z_vrfy_mbox_mtu_get(const struct device *dev) +{ + Z_OOPS(Z_SYSCALL_DRIVER_MBOX(dev, max_data_size_get)); + + return z_impl_mbox_mtu_get(dev); +} +#include + +static inline uint32_t z_vrfy_mbox_max_channels_get(const struct device *dev) +{ + Z_OOPS(Z_SYSCALL_DRIVER_MBOX(dev, max_channels_get)); + + return z_impl_mbox_max_channels_get(dev); +} +#include + +static inline int z_vrfy_mbox_set_enabled(const struct mbox_channel *channel, bool enable) +{ + Z_OOPS(Z_SYSCALL_MEMORY_READ(channel, sizeof(struct mbox_channel))); + Z_OOPS(Z_SYSCALL_DRIVER_MBOX(channel->dev, set_enabled)); + + return z_impl_mbox_set_enabled(channel, enable); +} +#include diff --git a/dts/bindings/test/vnd,mbox-zero-cell.yaml b/dts/bindings/test/vnd,mbox-zero-cell.yaml new file mode 100644 index 00000000000..de88f539267 --- /dev/null +++ b/dts/bindings/test/vnd,mbox-zero-cell.yaml @@ -0,0 +1,11 @@ +# Copyright (c) 2021, Carlo Caione +# SPDX-License-Identifier: Apache-2.0 + +description: VND MBOX controller (0 cell) + +compatible: "vnd,mbox-zero-cell" + +properties: + label: + type: string + required: true diff --git a/dts/bindings/test/vnd,mbox.yaml b/dts/bindings/test/vnd,mbox.yaml new file mode 100644 index 00000000000..bb731f008c7 --- /dev/null +++ b/dts/bindings/test/vnd,mbox.yaml @@ -0,0 +1,16 @@ +# Copyright (c) 2021, Carlo Caione +# SPDX-License-Identifier: Apache-2.0 + +description: VND MBOX controller + +compatible: "vnd,mbox" + +include: mailbox-controller.yaml + +properties: + label: + type: string + required: true + +mbox-cells: + - channel diff --git a/include/drivers/mbox.h b/include/drivers/mbox.h new file mode 100644 index 00000000000..b97d3987197 --- /dev/null +++ b/include/drivers/mbox.h @@ -0,0 +1,464 @@ +/** + * @file + * + * @brief Generic low-level multi-channel inter-processor mailbox communication API. + */ + +/* + * Copyright (c) 2021 Carlo Caione + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_MBOX_H_ +#define ZEPHYR_INCLUDE_DRIVERS_MBOX_H_ + +/** + * @brief MBOX Interface + * @defgroup mbox_interface MBOX Interface + * @ingroup io_interfaces + * @{ + * + * @code{.unparsed} + * + * CPU #1 | + * +----------+ | +----------+ + * | +---TX9----+ +--------+--RX8---+ | + * | dev A | | | | | CPU #2 | + * | <---RX8--+ | | +------+--TX9---> | + * +----------+ | | | | | +----------+ + * +--+-v---v-+--+ | + * | | | + * | MBOX dev | | + * | | | + * +--+-^---^--+-+ | + * +----------+ | | | | | +----------+ + * | <---RX2--+ | | +-----+--TX3---> | + * | dev B | | | | | CPU #3 | + * | +---TX3----+ +--------+--RX2---+ | + * +----------+ | +----------+ + * | + * + * @endcode + * + * An MBOX device is a peripheral capable of passing signals (and data depending + * on the peripheral) between CPUs and clusters in the system. Each MBOX + * instance is providing one or more channels, each one targeting one other CPU + * cluster (multiple channels can target the same cluster). + * + * For example in the plot the device 'dev A' is using the TX channel 9 to + * signal (or send data to) the CPU #2 and it's expecting data or signals on + * the RX channel 8. Thus it can send the message through the channel 9, and it + * can register a callback on the channel 8 of the MBOX device. + * + * This API supports two modes: signalling mode and data transfer mode. + * + * In signalling mode: + * - mbox_mtu_get() must return 0 + * - mbox_send() must have (msg == NULL) + * - the callback must be called with (data == NULL) + * + * In data transfer mode: + * - mbox_mtu_get() must return a (value != 0) + * - mbox_send() must have (msg != NULL) + * - the callback must be called with (data != NULL) + * - The msg content must be the same between sender and receiver + * + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Message struct (to hold data and its size). + */ +struct mbox_msg { + /** Pointer to the data sent in the message. */ + const void *data; + + /** Size of the data. */ + size_t size; +}; + +/** + * @brief Provides a type to hold an MBOX channel + * + * Struct type to hold an MBOX device pointer and the channel ID. + */ +struct mbox_channel { + /** MBOX device pointer. */ + const struct device *dev; + + /** Channel ID. */ + uint32_t id; +}; + +/** + * @brief Structure initializer for mbox_channel from devicetree + * + * This helper macro expands to a static initializer for a @p mbox_channel by + * reading the relevant device controller and channel number from the + * devicetree. + * + * Example devicetree fragment: + * + * mbox1: mbox-controller@... { ... }; + * + * n: node { + * mboxes = <&mbox1 8>, + * <&mbox1 9>; + * mbox-names = "tx", "rx"; + * }; + * + * Example usage: + * + * const struct mbox_channel channel = MBOX_DT_CHANNEL_GET(DT_NODELABEL(n), tx); + * + * @param node_id Devicetree node identifier for the MBOX device + * @param name lowercase-and-underscores name of the mboxes element + */ +#define MBOX_DT_CHANNEL_GET(node_id, name) \ + { \ + .dev = DEVICE_DT_GET(MBOX_DT_CTLR_BY_NAME(node_id, name)), \ + .id = MBOX_DT_CHANNEL_ID_BY_NAME(node_id, name), \ + } + +/** + * @brief Get the node identifier for the MBOX controller from a mboxes + * property by name + * + * Example devicetree fragment: + * + * mbox1: mbox-controller@... { ... }; + * + * n: node { + * mboxes = <&mbox1 8>, + * <&mbox1 9>; + * mbox-names = "tx", "rx"; + * }; + * + * Example usage: + * + * MBOX_DT_CTLR_BY_NAME(DT_NODELABEL(n), tx) // DT_NODELABEL(mbox1) + * MBOX_DT_CTLR_BY_NAME(DT_NODELABEL(n), rx) // DT_NODELABEL(mbox1) + * + * @param node_id node identifier for a node with a mboxes property + * @param name lowercase-and-underscores name of a mboxes element + * as defined by the node's mbox-names property + * + * @return the node identifier for the MBOX controller in the named element + * + * @see DT_PHANDLE_BY_NAME() + */ +#define MBOX_DT_CTLR_BY_NAME(node_id, name) \ + DT_PHANDLE_BY_NAME(node_id, mboxes, name) + +/** + * @brief Get a MBOX channel value by name + * + * Example devicetree fragment: + * + * mbox1: mbox@... { + * #mbox-cells = <1>; + * }; + * + * n: node { + * mboxes = <&mbox1 1>, + * <&mbox1 6>; + * mbox-names = "tx", "rx"; + * }; + * + * Bindings fragment for the mbox compatible: + * + * mbox-cells: + * - channel + * + * Example usage: + * + * MBOX_DT_CHANNEL_ID_BY_NAME(DT_NODELABEL(n), tx) // 1 + * MBOX_DT_CHANNEL_ID_BY_NAME(DT_NODELABEL(n), rx) // 6 + * + * @param node_id node identifier for a node with a mboxes property + * @param name lowercase-and-underscores name of a mboxes element + * as defined by the node's mbox-names property + * + * @return the channel value in the specifier at the named element or 0 if no + * channels are supported + * + * @see DT_PHA_BY_NAME_OR() + */ +#define MBOX_DT_CHANNEL_ID_BY_NAME(node_id, name) \ + DT_PHA_BY_NAME_OR(node_id, mboxes, name, channel, 0) + +/** + * @typedef mbox_callback_t + * + * @brief Callback API for incoming MBOX messages + * + * These callbacks execute in interrupt context. Therefore, use only + * interrupt-safe APIS. Registration of callbacks is done via @a + * mbox_register_callback() + * + * The data parameter must be NULL in signalling mode. + * + * @param dev Driver instance + * @param channel Channel ID + * @param user_data Pointer to some private data provided at registration + * time + * @param data Message struct + */ +typedef void (*mbox_callback_t)(const struct device *dev, uint32_t channel, + void *user_data, struct mbox_msg *data); + +/** + * @typedef mbox_send_t + * + * @brief Callback API to send MBOX messages + * + * See @a mbox_send() for function description + * + * @param dev Driver instance + * @param channel Channel ID + * @param msg Message struct + * + * @return See return values for @a mbox_send() + */ +typedef int (*mbox_send_t)(const struct device *dev, uint32_t channel, + const struct mbox_msg *msg); + +/** + * @typedef mbox_mtu_get_t + * + * @brief Callback API to get maximum data size + * + * See @a mbox_mtu_get() for argument definitions. + */ +typedef int (*mbox_mtu_get_t)(const struct device *dev); + +/** + * @typedef mbox_register_callback_t + * + * @brief Callback API upon registration + * + * See @a mbox_register_callback() for function description + * + * @param dev Driver instance + * @param channel Channel ID + * @param cb Callback function to execute on incoming message interrupts. + * @param user_data Application-specific data pointer which will be passed + * to the callback function when executed. + * + * @return See return values for @a mbox_register_callback() + */ +typedef int (*mbox_register_callback_t)(const struct device *dev, + uint32_t channel, + mbox_callback_t cb, + void *user_data); + +/** + * @typedef mbox_set_enabled_t + * + * @brief Callback API upon enablement of interrupts + * + * See @a mbox_set_enabled() for function description + * + * @param dev Driver instance + * @param channel Channel ID + * @param enable Set to 0 to disable and to nonzero to enable. + * + * @return See return values for @a mbox_set_enabled() + */ +typedef int (*mbox_set_enabled_t)(const struct device *dev, uint32_t channel, bool enable); + +/** + * @typedef mbox_max_channels_get_t + * + * @brief Callback API to get maximum number of channels + * + * See @a mbox_max_channels_get() for argument definitions. + */ +typedef uint32_t (*mbox_max_channels_get_t)(const struct device *dev); + +__subsystem struct mbox_driver_api { + mbox_send_t send; + mbox_register_callback_t register_callback; + mbox_mtu_get_t mtu_get; + mbox_max_channels_get_t max_channels_get; + mbox_set_enabled_t set_enabled; +}; + +/** + * @brief Initialize a channel struct + * + * Initialize an @p mbox_channel passed by the user with a provided MBOX device + * and channel ID. This function is needed when the information about the + * device and the channel ID is not in the DT. In the DT case + * MBOX_DT_CHANNEL_GET() must be used instead. + * + * @param channel Pointer to the channel struct + * @param dev Driver instance + * @param ch_id Channel ID + */ +static inline void mbox_init_channel(struct mbox_channel *channel, const struct device *dev, + uint32_t ch_id) +{ + channel->dev = dev; + channel->id = ch_id; +} + +/** + * @brief Try to send a message over the MBOX device. + * + * Send a message over an @p mbox_channel. The msg parameter must be NULL when + * the driver is used for signalling. + * + * If the msg parameter is not NULL, this data is expected to be delivered on + * the receiving side using the data parameter of the receiving callback. + * + * @param channel Channel instance pointer + * @param msg Pointer to the message struct + * + * @retval -EBUSY If the remote hasn't yet read the last data sent. + * @retval -EMSGSIZE If the supplied data size is unsupported by the driver. + * @retval -EINVAL If there was a bad parameter, such as: too-large channel + * descriptor or the device isn't an outbound MBOX channel. + * + * @retval 0 On success, negative value on error. + */ +__syscall int mbox_send(const struct mbox_channel *channel, const struct mbox_msg *msg); + +static inline int z_impl_mbox_send(const struct mbox_channel *channel, const struct mbox_msg *msg) +{ + const struct mbox_driver_api *api = + (const struct mbox_driver_api *)channel->dev->api; + + if (api->send == NULL) { + return -ENOSYS; + } + + return api->send(channel->dev, channel->id, msg); +} + +/** + * @brief Register a callback function on a channel for incoming messages. + * + * This function doesn't assume anything concerning the status of the + * interrupts. Use @a mbox_set_enabled() to enable or to disable the interrupts + * if needed. + * + * @param channel Channel instance pointer. + * @param cb Callback function to execute on incoming message interrupts. + * @param user_data Application-specific data pointer which will be passed + * to the callback function when executed. + * + * @retval 0 On success, negative value on error. + */ +static inline int mbox_register_callback(const struct mbox_channel *channel, + mbox_callback_t cb, + void *user_data) +{ + const struct mbox_driver_api *api = + (const struct mbox_driver_api *)channel->dev->api; + + if (api->register_callback == NULL) { + return -ENOSYS; + } + + return api->register_callback(channel->dev, channel->id, cb, user_data); +} + +/** + * @brief Return the maximum number of bytes possible in an outbound message. + * + * Returns the actual number of bytes that it is possible to send through an + * outgoing channel. + * + * This number can be 0 when the driver only supports signalling or when on the + * receiving side the content and size of the message must be retrieved in an + * indirect way (i.e. probing some other peripheral, reading memory regions, + * etc...). + * + * If this function returns 0, the msg parameter in @a mbox_send() is expected + * to be NULL. + * + * @param dev Driver instance pointer. + * + * @return Maximum possible size of a message in bytes, 0 for signalling, + * negative value on error. + */ +__syscall int mbox_mtu_get(const struct device *dev); + +static inline int z_impl_mbox_mtu_get(const struct device *dev) +{ + const struct mbox_driver_api *api = + (const struct mbox_driver_api *)dev->api; + + if (api->mtu_get == NULL) { + return -ENOSYS; + } + + return api->mtu_get(dev); +} + +/** + * @brief Enable interrupts and callbacks for inbound channels. + * + * @param channel Channel instance pointer. + * @param enable Set to 0 to disable and to nonzero to enable. + * + * @retval 0 On success. + * @retval -EINVAL If it isn't an inbound channel. + */ +__syscall int mbox_set_enabled(const struct mbox_channel *channel, bool enable); + +static inline int z_impl_mbox_set_enabled(const struct mbox_channel *channel, bool enable) +{ + const struct mbox_driver_api *api = + (const struct mbox_driver_api *)channel->dev->api; + + if (api->set_enabled == NULL) { + return -ENOSYS; + } + + return api->set_enabled(channel->dev, channel->id, enable); +} + +/** + * @brief Return the maximum number of channels. + * + * Return the maximum number of channels supported by the hardware. + * + * @param dev Driver instance pointer. + * + * @return Maximum possible number of supported channels on success, negative + * value on error. + */ +__syscall uint32_t mbox_max_channels_get(const struct device *dev); + +static inline uint32_t z_impl_mbox_max_channels_get(const struct device *dev) +{ + const struct mbox_driver_api *api = + (const struct mbox_driver_api *)dev->api; + + if (api->max_channels_get == NULL) { + return -ENOSYS; + } + + return api->max_channels_get(dev); +} + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#include + +#endif /* ZEPHYR_INCLUDE_DRIVERS_MBOX_H_ */ diff --git a/tests/lib/devicetree/api/app.overlay b/tests/lib/devicetree/api/app.overlay index b4b76509c6c..20ca627edb9 100644 --- a/tests/lib/devicetree/api/app.overlay +++ b/tests/lib/devicetree/api/app.overlay @@ -221,6 +221,20 @@ }; }; + test_mbox: mbox { + label = "TEST_MBOX"; + compatible = "vnd,mbox"; + #mbox-cells = <1>; + status = "okay"; + }; + + test_mbox_zero_cell: mbox_zero_cell { + label = "TEST_MBOX_ZERO_CELL"; + compatible = "vnd,mbox-zero-cell"; + #mbox-cells = <0>; + status = "okay"; + }; + test_spi: spi@33334444 { #address-cells = < 1 >; #size-cells = < 0 >; @@ -324,6 +338,8 @@ pinctrl-1 = <&test_pincfg_c &test_pincfg_d>; pinctrl-2 = <&test_pincfg_d>; pinctrl-names = "default", "sleep", "f.o.o2"; + mboxes = <&test_mbox 1>, <&test_mbox 2>, <&test_mbox_zero_cell>; + mbox-names = "tx", "rx", "zero"; }; /* there should only be one of these */ diff --git a/tests/lib/devicetree/api/src/main.c b/tests/lib/devicetree/api/src/main.c index ccc55d0721e..6f5897c8010 100644 --- a/tests/lib/devicetree/api/src/main.c +++ b/tests/lib/devicetree/api/src/main.c @@ -37,6 +37,7 @@ #include #include #include +#include #define TEST_CHILDREN DT_PATH(test, test_children) #define TEST_DEADBEEF DT_PATH(test, gpio_deadbeef) @@ -2078,6 +2079,38 @@ static void test_pinctrl(void) zassert_equal(DT_INST_PINCTRL_HAS_NAME(0, f_o_o2), 0, ""); } +static void test_mbox(void) +{ +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT vnd_adc_temp_sensor + + const struct mbox_channel channel_tx = MBOX_DT_CHANNEL_GET(TEST_TEMP, tx); + const struct mbox_channel channel_rx = MBOX_DT_CHANNEL_GET(TEST_TEMP, rx); + + zassert_equal(channel_tx.id, 1, ""); + zassert_equal(channel_rx.id, 2, ""); + + zassert_equal(MBOX_DT_CHANNEL_ID_BY_NAME(TEST_TEMP, tx), 1, ""); + zassert_equal(MBOX_DT_CHANNEL_ID_BY_NAME(TEST_TEMP, rx), 2, ""); + + zassert_true(DT_SAME_NODE(MBOX_DT_CTLR_BY_NAME(TEST_TEMP, tx), + DT_NODELABEL(test_mbox)), ""); + zassert_true(DT_SAME_NODE(MBOX_DT_CTLR_BY_NAME(TEST_TEMP, rx), + DT_NODELABEL(test_mbox)), ""); + + zassert_equal(MBOX_DT_CHANNEL_ID_BY_NAME(TEST_TEMP, tx), 1, ""); + zassert_equal(MBOX_DT_CHANNEL_ID_BY_NAME(TEST_TEMP, rx), 2, ""); + + const struct mbox_channel channel_zero = MBOX_DT_CHANNEL_GET(TEST_TEMP, zero); + + zassert_equal(channel_zero.id, 0, ""); + + zassert_equal(MBOX_DT_CHANNEL_ID_BY_NAME(TEST_TEMP, zero), 0, ""); + + zassert_true(DT_SAME_NODE(MBOX_DT_CTLR_BY_NAME(TEST_TEMP, zero), + DT_NODELABEL(test_mbox_zero_cell)), ""); +} + void test_main(void) { ztest_test_suite(devicetree_api, @@ -2120,7 +2153,8 @@ void test_main(void) ztest_unit_test(test_path), ztest_unit_test(test_node_name), ztest_unit_test(test_same_node), - ztest_unit_test(test_pinctrl) + ztest_unit_test(test_pinctrl), + ztest_unit_test(test_mbox) ); ztest_run_test_suite(devicetree_api); }