blob: 3f8ffc6594760befc4aa36096a9ab8bc8d7ffecf [file] [log] [blame]
/*
* Copyright (c) 2021 Carlo Caione <ccaione@baylibre.com>
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_MBOX_H_
#define ZEPHYR_INCLUDE_DRIVERS_MBOX_H_
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief MBOX Interface
* @defgroup mbox_interface MBOX Interface
* @since 1.0
* @version 0.1.0
* @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
*
*/
/** @brief Type for MBOX channel identifiers */
typedef uint32_t mbox_channel_id_t;
/** @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 MBOX specification from DT */
struct mbox_dt_spec {
/** MBOX device pointer. */
const struct device *dev;
/** Channel ID. */
mbox_channel_id_t channel_id;
};
/**
* @brief Structure initializer for struct mbox_dt_spec from devicetree
*
* This helper macro expands to a static initializer for a struct mbox_dt_spec
* by reading the relevant device controller and channel number from the
* devicetree.
*
* Example devicetree fragment:
*
* @code{.devicetree}
* n: node {
* mboxes = <&mbox1 8>,
* <&mbox1 9>;
* mbox-names = "tx", "rx";
* };
* @endcode
*
* Example usage:
*
* @code{.c}
* const struct mbox_dt_spec spec = MBOX_DT_SPEC_GET(DT_NODELABEL(n), tx);
* @endcode
*
* @param node_id Devicetree node identifier for the MBOX device
* @param name lowercase-and-underscores name of the mboxes element
*
* @return static initializer for a struct mbox_dt_spec
*/
#define MBOX_DT_SPEC_GET(node_id, name) \
{ \
.dev = DEVICE_DT_GET(DT_MBOX_CTLR_BY_NAME(node_id, name)), \
.channel_id = DT_MBOX_CHANNEL_BY_NAME(node_id, name), \
}
/**
* @brief Instance version of MBOX_DT_CHANNEL_GET()
*
* @param inst DT_DRV_COMPAT instance number
* @param name lowercase-and-underscores name of the mboxes element
*
* @return static initializer for a struct mbox_dt_spec
*/
#define MBOX_DT_SPEC_INST_GET(inst, name) \
MBOX_DT_SPEC_GET(DT_DRV_INST(inst), name)
/** @cond INTERNAL_HIDDEN */
/**
* @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
* mbox_register_callback()
*
* The data parameter must be NULL in signalling mode.
*
* @param dev MBOX device instance
* @param channel_id 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,
mbox_channel_id_t channel_id, void *user_data,
struct mbox_msg *data);
/**
* @brief Callback API to send MBOX messages
*
* @param dev MBOX device instance
* @param channel_id Channel ID
* @param msg Message struct
*
* @return See the return values for mbox_send()
* @see mbox_send()
*/
typedef int (*mbox_send_t)(const struct device *dev,
mbox_channel_id_t channel_id,
const struct mbox_msg *msg);
/**
* @brief Callback API to get maximum data size
*
* @param dev MBOX device instance
*
* @return See the return values for mbox_mtu_get()
* @see mbox_mtu_get()
*/
typedef int (*mbox_mtu_get_t)(const struct device *dev);
/**
* @brief Callback API upon registration
*
* @param dev MBOX device instance
* @param channel_id 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 mbox_register_callback()
* @see mbox_register_callback()
*/
typedef int (*mbox_register_callback_t)(const struct device *dev,
mbox_channel_id_t channel_id,
mbox_callback_t cb, void *user_data);
/**
* @brief Callback API upon enablement of interrupts
*
* @param dev MBOX device instance
* @param channel_id Channel ID
* @param enables Set to 0 to disable and to nonzero to enable.
*
* @return See return values for mbox_set_enabled()
* @see mbox_set_enabled()
*/
typedef int (*mbox_set_enabled_t)(const struct device *dev,
mbox_channel_id_t channel_id, bool enabled);
/**
* @brief Callback API to get maximum number of channels
*
* @param dev MBOX device instance
*
* @return See return values for mbox_max_channels_get()
* @see mbox_max_channels_get()
*/
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;
};
/** @endcond */
/**
* @brief Validate if MBOX device instance from a struct mbox_dt_spec is ready.
*
* @param spec MBOX specification from devicetree
*
* @return See return values for mbox_send()
*/
static inline bool mbox_is_ready_dt(const struct mbox_dt_spec *spec)
{
return device_is_ready(spec->dev);
}
/**
* @brief Try to send a message over the MBOX device.
*
* Send a message over an struct 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 dev MBOX device instance
* @param channel_id MBOX channel identifier
* @param msg Message
*
* @retval 0 On success.
* @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.
*/
__syscall int mbox_send(const struct device *dev, mbox_channel_id_t channel_id,
const struct mbox_msg *msg);
static inline int z_impl_mbox_send(const struct device *dev,
mbox_channel_id_t channel_id,
const struct mbox_msg *msg)
{
const struct mbox_driver_api *api =
(const struct mbox_driver_api *)dev->api;
if (api->send == NULL) {
return -ENOSYS;
}
return api->send(dev, channel_id, msg);
}
/**
* @brief Try to send a message over the MBOX device from a struct mbox_dt_spec.
*
* @param spec MBOX specification from devicetree
* @param msg Message
*
* @return See return values for mbox_send()
*/
static inline int mbox_send_dt(const struct mbox_dt_spec *spec,
const struct mbox_msg *msg)
{
return mbox_send(spec->dev, spec->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 mbox_set_enabled() to enable or to disable the interrupts
* if needed.
*
* @param dev MBOX device instance
* @param channel_id MBOX channel identifier
* @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.
* @retval -errno Negative errno on error.
*/
static inline int mbox_register_callback(const struct device *dev,
mbox_channel_id_t channel_id,
mbox_callback_t cb,
void *user_data)
{
const struct mbox_driver_api *api =
(const struct mbox_driver_api *)dev->api;
if (api->register_callback == NULL) {
return -ENOSYS;
}
return api->register_callback(dev, channel_id, cb, user_data);
}
/**
* @brief Register a callback function on a channel for incoming messages from a
* struct mbox_dt_spec.
*
* @param spec MBOX specification from devicetree
* @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 mbox_register_callback()
*/
static inline int mbox_register_callback_dt(const struct mbox_dt_spec *spec,
mbox_callback_t cb, void *user_data)
{
return mbox_register_callback(spec->dev, spec->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 mbox_send() is expected
* to be NULL.
*
* @param dev MBOX device instance.
*
* @retval >0 Maximum possible size of a message in bytes
* @retval 0 Indicates signalling
* @retval -errno Negative errno 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 Return the maximum number of bytes possible in an outbound message
* from struct mbox_dt_spec.
*
* @param spec MBOX specification from devicetree
*
* @return See return values for mbox_register_callback()
*/
static inline int mbox_mtu_get_dt(const struct mbox_dt_spec *spec)
{
return mbox_mtu_get(spec->dev);
}
/**
* @brief Enable (disable) interrupts and callbacks for inbound channels.
*
* Enable interrupt for the channel when the parameter 'enable' is set to true.
* Disable it otherwise.
*
* Immediately after calling this function with 'enable' set to true, the
* channel is considered enabled and ready to receive signal and messages (even
* already pending), so the user must take care of installing a proper callback
* (if needed) using mbox_register_callback() on the channel before enabling
* it. For this reason it is recommended that all the channels are disabled at
* probe time.
*
* Enabling a channel for which there is no installed callback is considered
* undefined behavior (in general the driver must take care of gracefully
* handling spurious interrupts with no installed callback).
*
* @param dev MBOX device instance
* @param channel_id MBOX channel identifier
* @param enabled Enable (true) or disable (false) the channel.
*
* @retval 0 On success.
* @retval -EINVAL If it isn't an inbound channel.
* @retval -EALREADY If channel is already @p enabled.
*/
__syscall int mbox_set_enabled(const struct device *dev,
mbox_channel_id_t channel_id, bool enabled);
static inline int z_impl_mbox_set_enabled(const struct device *dev,
mbox_channel_id_t channel_id,
bool enabled)
{
const struct mbox_driver_api *api =
(const struct mbox_driver_api *)dev->api;
if (api->set_enabled == NULL) {
return -ENOSYS;
}
return api->set_enabled(dev, channel_id, enabled);
}
/**
* @brief Enable (disable) interrupts and callbacks for inbound channels from a
* struct mbox_dt_spec.
*
* @param spec MBOX specification from devicetree
* @param enabled Enable (true) or disable (false) the channel.
*
* @return See return values for mbox_set_enabled()
*/
static inline int mbox_set_enabled_dt(const struct mbox_dt_spec *spec,
bool enabled)
{
return mbox_set_enabled(spec->dev, spec->channel_id, enabled);
}
/**
* @brief Return the maximum number of channels.
*
* Return the maximum number of channels supported by the hardware.
*
* @param dev MBOX device instance.
*
* @return >0 Maximum possible number of supported channels on success
* @return -errno Negative errno 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);
}
/**
* @brief Return the maximum number of channels from a struct mbox_dt_spec.
*
* @param spec MBOX specification from devicetree
*
* @return See return values for mbox_max_channels_get()
*/
static inline int mbox_max_channels_get_dt(const struct mbox_dt_spec *spec)
{
return mbox_max_channels_get(spec->dev);
}
/** @} */
#ifdef __cplusplus
}
#endif
#include <zephyr/syscalls/mbox.h>
#endif /* ZEPHYR_INCLUDE_DRIVERS_MBOX_H_ */