/*
 * Copyright (c) 2021 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef ZEPHYR_INCLUDE_IPC_IPC_SERVICE_H_
#define ZEPHYR_INCLUDE_IPC_IPC_SERVICE_H_

#include <stdio.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief IPC
 * @defgroup ipc IPC
 * @ingroup os_services
 * @{
 * @}
 */

/**
 * @brief IPC Service API
 * @defgroup ipc_service_api IPC service APIs
 * @ingroup ipc
 * @{
 */

/**
 * @cond INTERNAL_HIDDEN
 *
 * These are for internal use only, so skip these in
 * public documentation.
 */

/**
 * Some terminology:
 *
 * - INSTANCE: an instance is the external representation of a physical
 *             communication channel between two domains / CPUs.
 *
 *             The actual implementation and internal representation of the
 *             instance is peculiar to each backend. For example for
 *             OpenAMP-based backends, an instance is usually represented by a
 *             shared memory region and a couple of IPM devices for RX/TX
 *             signalling.
 *
 *             It's important to note that an instance per se is not used to
 *             send data between domains / CPUs. To send and receive data the
 *             user have to create (register) an endpoint in the instance
 *             connecting the two domains of interest.
 *
 *             It's possible to have zero or multiple endpoints in one single
 *             instance, each one used to exchange data, possibly with different
 *             priorities.
 *
 *             The creation of the instances is left to the backend (usually at
 *             init time), while the registration of the endpoints is left to
 *             the user (usually at run time).
 *
 * - ENDPOINT: an endpoint is the entity the user must use to send / receive
 *             data between two domains (connected by the instance). An
 *             endpoint is always associated to an instance.
 *
 * - BACKEND: the backend must take care of at least two different things:
 *
 *            1) creating the instances at init time
 *            2) creating / registering the endpoints onto an instance at run
 *               time when requested by the user
 *
 *            The API doesn't mandate a way for the backend to create the
 *            instances but itis strongly recommended to use the DT to retrieve
 *            the configuration parameters for the instance.
 *
 * Common API usage from the application prospective:
 *
 *   HOST                                         REMOTE
 *   -----------------------------------------------------------------------------
 *   # Open the (same) instance on host and remote
 *   ipc_service_open()                           ipc_service_open()
 *
 *   # Register the endpoints
 *   ipc_service_register_endpoint()              ipc_service_register_endpoint()
 *   .bound()                                     .bound()
 *
 *   # After the .bound() callbacks are received the communication channel
 *   # is ready to be used
 *
 *   # Start sending and receiving data
 *   ipc_service_send()
 *                                                .receive()
 *                                                ipc_service_send()
 *   .receive()
 *
 *
 * Common API usage from the application prospective when using NOCOPY feature:
 *
 *   HOST                                         REMOTE
 *   -----------------------------------------------------------------------------
 *   ipc_service_open()                           ipc_service_open()
 *
 *   ipc_service_register_endpoint()              ipc_service_register_endpoint()
 *   .bound()                                     .bound()
 *
 *   # Get a pointer to an available TX buffer
 *   ipc_service_get_tx_buffer()
 *
 *   # Fill the buffer with data
 *
 *   # Send out the buffer
 *   ipc_service_send_nocopy()
 *                                                .receive()
 *
 *                                                # Get hold of the received RX buffer
 *                                                # in the .receive callback
 *                                                ipc_service_hold_rx_buffer()
 *
 *                                                # Copy the data out of the buffer at
 *                                                # user convenience
 *
 *                                                # Release the buffer when done
 *                                                ipc_service_release_rx_buffer()
 *
 *    # Get another TX buffer
 *    ipc_service_get_tx_buffer()
 *
 *    # We can also drop it if needed
 *    ipc_service_drop_tx_buffer()
 *
 */

/**
 * @endcond
 */

/** @brief Event callback structure.
 *
 *  It is registered during endpoint registration.
 *  This structure is part of the endpoint configuration.
 */
struct ipc_service_cb {
	/** @brief Bind was successful.
	 *
	 *  This callback is called when the endpoint binding is successful.
	 *
	 *  @param[in] priv Private user data.
	 */
	void (*bound)(void *priv);

	/** @brief New packet arrived.
	 *
	 *  This callback is called when new data is received.
	 *
	 *  @note When @ref ipc_service_hold_rx_buffer is not used, the data
	 *	  buffer is to be considered released and available again only
	 *	  when this callback returns.
	 *
	 *  @param[in] data Pointer to data buffer.
	 *  @param[in] len Length of @a data.
	 *  @param[in] priv Private user data.
	 */
	void (*received)(const void *data, size_t len, void *priv);

	/** @brief An error occurred.
	 *
	 *  @param[in] message Error message.
	 *  @param[in] priv Private user data.
	 */
	void (*error)(const char *message, void *priv);
};

/** @brief Endpoint instance.
 *
 *  Token is not important for user of the API. It is implemented in a
 *  specific backend.
 */
struct ipc_ept {

	/** Instance this endpoint belongs to. */
	const struct device *instance;

	/** Backend-specific token used to identify an endpoint in an instance. */
	void *token;
};

/** @brief Endpoint configuration structure. */
struct ipc_ept_cfg {

	/** Name of the endpoint. */
	const char *name;

	/** Endpoint priority. If the backend supports priorities. */
	int prio;

	/** Event callback structure. */
	struct ipc_service_cb cb;

	/** Private user data. */
	void *priv;
};

/** @brief Open an instance
 *
 *  Function to be used to open an instance before being able to register a new
 *  endpoint on it.
 *
 *  @param[in] instance Instance to open.
 *
 *  @retval -EINVAL when instance configuration is invalid.
 *  @retval -EIO when no backend is registered.
 *  @retval -EALREADY when the instance is already opened (or being opened).
 *
 *  @retval 0 on success or when not implemented on the backend (not needed).
 *  @retval other errno codes depending on the implementation of the backend.
 */
int ipc_service_open_instance(const struct device *instance);

/** @brief Close an instance
 *
 *  Function to be used to close an instance. All bounded endpoints must be
 *  deregistered using ipc_service_deregister_endpoint before this
 *  is called.
 *
 *  @param[in] instance Instance to close.
 *
 *  @retval -EINVAL when instance configuration is invalid.
 *  @retval -EIO when no backend is registered.
 *  @retval -EALREADY when the instance is not already opened.
 *  @retval -EBUSY when an endpoint exists that hasn't been
 *           deregistered
 *
 *  @retval 0 on success or when not implemented on the backend (not needed).
 *  @retval other errno codes depending on the implementation of the backend.
 */
int ipc_service_close_instance(const struct device *instance);

/** @brief Register IPC endpoint onto an instance.
 *
 *  Registers IPC endpoint onto an instance to enable communication with a
 *  remote device.
 *
 *  The same function registers endpoints for both host and remote devices.
 *
 *  @param[in] instance Instance to register the endpoint onto.
 *  @param[in] ept Endpoint object.
 *  @param[in] cfg Endpoint configuration.
 *
 *  @note Keep the variable pointed by @p cfg alive when endpoint is in use.
 *
 *  @retval -EIO when no backend is registered.
 *  @retval -EINVAL when instance, endpoint or configuration is invalid.
 *  @retval -EBUSY when the instance is busy.
 *
 *  @retval 0 on success.
 *  @retval other errno codes depending on the implementation of the backend.
 */
int ipc_service_register_endpoint(const struct device *instance,
				  struct ipc_ept *ept,
				  const struct ipc_ept_cfg *cfg);

/** @brief Deregister an IPC endpoint from its instance.
 *
 *  Deregisters an IPC endpoint from its instance.
 *
 *  The same function deregisters endpoints for both host and remote devices.
 *
 *  @param[in] ept Endpoint object.
 *
 *  @retval -EIO when no backend is registered.
 *  @retval -EINVAL when instance, endpoint or configuration is invalid.
 *  @retval -ENOENT when the endpoint is not registered with the instance.
 *  @retval -EBUSY when the instance is busy.
 *
 *  @retval 0 on success.
 *  @retval other errno codes depending on the implementation of the backend.
 */
int ipc_service_deregister_endpoint(struct ipc_ept *ept);

/** @brief Send data using given IPC endpoint.
 *
 *  @param[in] ept Registered endpoint by @ref ipc_service_register_endpoint.
 *  @param[in] data Pointer to the buffer to send.
 *  @param[in] len Number of bytes to send.
 *
 *  @retval -EIO when no backend is registered or send hook is missing from
 *               backend.
 *  @retval -EINVAL when instance or endpoint is invalid.
 *  @retval -ENOENT when the endpoint is not registered with the instance.
 *  @retval -EBADMSG when the data is invalid (i.e. invalid data format,
 *		     invalid length, ...)
 *  @retval -EBUSY when the instance is busy.
 *  @retval -ENOMEM when no memory / buffers are available.
 *
 *  @retval bytes number of bytes sent.
 *  @retval other errno codes depending on the implementation of the backend.
 */
int ipc_service_send(struct ipc_ept *ept, const void *data, size_t len);

/** @brief Get the TX buffer size
 *
 *  Get the maximal size of a buffer which can be obtained by @ref
 *  ipc_service_get_tx_buffer
 *
 *  @param[in] ept Registered endpoint by @ref ipc_service_register_endpoint.
 *
 *  @retval -EIO when no backend is registered or send hook is missing from
 *		 backend.
 *  @retval -EINVAL when instance or endpoint is invalid.
 *  @retval -ENOENT when the endpoint is not registered with the instance.
 *  @retval -ENOTSUP when the operation is not supported by backend.
 *
 *  @retval size TX buffer size on success.
 *  @retval other errno codes depending on the implementation of the backend.
 */
int ipc_service_get_tx_buffer_size(struct ipc_ept *ept);

/** @brief Get an empty TX buffer to be sent using @ref ipc_service_send_nocopy
 *
 *  This function can be called to get an empty TX buffer so that the
 *  application can directly put its data into the sending buffer without copy
 *  from an application buffer.
 *
 *  It is the application responsibility to correctly fill the allocated TX
 *  buffer with data and passing correct parameters to @ref
 *  ipc_service_send_nocopy function to perform data no-copy-send mechanism.
 *
 *  The size parameter can be used to request a buffer with a certain size:
 *  - if the size can be accommodated the function returns no errors and the
 *    buffer is allocated
 *  - if the requested size is too big, the function returns -ENOMEM and the
 *    the buffer is not allocated.
 *  - if the requested size is '0' the buffer is allocated with the maximum
 *    allowed size.
 *
 *  In all the cases on return the size parameter contains the maximum size for
 *  the returned buffer.
 *
 *  When the function returns no errors, the buffer is intended as allocated
 *  and it is released under two conditions: (1) when sending the buffer using
 *  @ref ipc_service_send_nocopy (and in this case the buffer is automatically
 *  released by the backend), (2) when using @ref ipc_service_drop_tx_buffer on
 *  a buffer not sent.
 *
 *  @param[in] ept Registered endpoint by @ref ipc_service_register_endpoint.
 *  @param[out] data Pointer to the empty TX buffer.
 *  @param[in,out] size Pointer to store the requested TX buffer size. If the
 *			function returns -ENOMEM, this parameter returns the
 *			maximum allowed size.
 *  @param[in] wait Timeout waiting for an available TX buffer.
 *
 *  @retval -EIO when no backend is registered or send hook is missing from
 *		 backend.
 *  @retval -EINVAL when instance or endpoint is invalid.
 *  @retval -ENOENT when the endpoint is not registered with the instance.
 *  @retval -ENOTSUP when the operation or the timeout is not supported by backend.
 *  @retval -ENOBUFS when there are no TX buffers available.
 *  @retval -EALREADY when a buffer was already claimed and not yet released.
 *  @retval -ENOMEM when the requested size is too big (and the size parameter
 *		    contains the maximum allowed size).
 *
 *  @retval 0 on success.
 *  @retval other errno codes depending on the implementation of the backend.
 */
int ipc_service_get_tx_buffer(struct ipc_ept *ept, void **data, uint32_t *size, k_timeout_t wait);

/** @brief Drop and release a TX buffer
 *
 *  Drop and release a TX buffer. It is possible to drop only TX buffers
 *  obtained by using @ref ipc_service_get_tx_buffer.
 *
 *  @param[in] ept Registered endpoint by @ref ipc_service_register_endpoint.
 *  @param[in] data Pointer to the TX buffer.
 *
 *  @retval -EIO when no backend is registered or send hook is missing from
 *		 backend.
 *  @retval -EINVAL when instance or endpoint is invalid.
 *  @retval -ENOENT when the endpoint is not registered with the instance.
 *  @retval -ENOTSUP when this is not supported by backend.
 *  @retval -EALREADY when the buffer was already dropped.
 *  @retval -ENXIO when the buffer was not obtained using @ref
 *		   ipc_service_get_tx_buffer
 *
 *  @retval 0 on success.
 *  @retval other errno codes depending on the implementation of the backend.
 */
int ipc_service_drop_tx_buffer(struct ipc_ept *ept, const void *data);

/** @brief Send data in a TX buffer reserved by @ref ipc_service_get_tx_buffer
 *         using the given IPC endpoint.
 *
 *  This is equivalent to @ref ipc_service_send but in this case the TX buffer
 *  has been obtained by using @ref ipc_service_get_tx_buffer.
 *
 *  The application has to take the responsibility for getting the TX buffer
 *  using @ref ipc_service_get_tx_buffer and filling the TX buffer with the data.
 *
 *  After the @ref ipc_service_send_nocopy function is issued the TX buffer is
 *  no more owned by the sending task and must not be touched anymore unless
 *  the function fails and returns an error.
 *
 *  If this function returns an error, @ref ipc_service_drop_tx_buffer can be
 *  used to drop the TX buffer.
 *
 *  @param[in] ept Registered endpoint by @ref ipc_service_register_endpoint.
 *  @param[in] data Pointer to the buffer to send obtained by @ref
 *		    ipc_service_get_tx_buffer.
 *  @param[in] len Number of bytes to send.
 *
 *  @retval -EIO when no backend is registered or send hook is missing from
 *		 backend.
 *  @retval -EINVAL when instance or endpoint is invalid.
 *  @retval -ENOENT when the endpoint is not registered with the instance.
 *  @retval -EBADMSG when the data is invalid (i.e. invalid data format,
 *		     invalid length, ...)
 *  @retval -EBUSY when the instance is busy.
 *
 *  @retval bytes number of bytes sent.
 *  @retval other errno codes depending on the implementation of the backend.
 */
int ipc_service_send_nocopy(struct ipc_ept *ept, const void *data, size_t len);

/** @brief Holds the RX buffer for usage outside the receive callback.
 *
 *  Calling this function prevents the receive buffer from being released
 *  back to the pool of shmem buffers. This function can be called in the
 *  receive callback when the user does not want to copy the message out in
 *  the callback itself.
 *
 *  After the message is processed, the application must release the buffer
 *  using the @ref ipc_service_release_rx_buffer function.
 *
 *  @param[in] ept Registered endpoint by @ref ipc_service_register_endpoint.
 *  @param[in] data Pointer to the RX buffer to hold.
 *
 *  @retval -EIO when no backend is registered or release hook is missing from
 *		 backend.
 *  @retval -EINVAL when instance or endpoint is invalid.
 *  @retval -ENOENT when the endpoint is not registered with the instance.
 *  @retval -EALREADY when the buffer data has been hold already.
 *  @retval -ENOTSUP when this is not supported by backend.
 *
 *  @retval 0 on success.
 *  @retval other errno codes depending on the implementation of the backend.
 */
int ipc_service_hold_rx_buffer(struct ipc_ept *ept, void *data);

/** @brief Release the RX buffer for future reuse.
 *
 *  When supported by the backend, this function can be called after the
 *  received message has been processed and the buffer can be marked as
 *  reusable again.
 *
 *  It is possible to release only RX buffers on which @ref
 *  ipc_service_hold_rx_buffer was previously used.
 *
 *  @param[in] ept Registered endpoint by @ref ipc_service_register_endpoint.
 *  @param[in] data Pointer to the RX buffer to release.
 *
 *  @retval -EIO when no backend is registered or release hook is missing from
 *		 backend.
 *  @retval -EINVAL when instance or endpoint is invalid.
 *  @retval -ENOENT when the endpoint is not registered with the instance.
 *  @retval -EALREADY when the buffer data has been already released.
 *  @retval -ENOTSUP when this is not supported by backend.
 *  @retval -ENXIO when the buffer was not hold before using @ref
 *		   ipc_service_hold_rx_buffer
 *
 *  @retval 0 on success.
 *  @retval other errno codes depending on the implementation of the backend.
 */
int ipc_service_release_rx_buffer(struct ipc_ept *ept, void *data);

/**
 * @}
 */

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_IPC_IPC_SERVICE_H_ */
