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

#ifndef ZEPHYR_INCLUDE_IPC_ICMSG_H_
#define ZEPHYR_INCLUDE_IPC_ICMSG_H_

#include <stddef.h>
#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/mbox.h>
#include <zephyr/ipc/ipc_service.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/spsc_pbuf.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Icmsg IPC library API
 * @defgroup ipc_icmsg_api Icmsg IPC library API
 * @ingroup ipc
 * @{
 */

enum icmsg_state {
	ICMSG_STATE_OFF,
	ICMSG_STATE_BUSY,
	ICMSG_STATE_READY,
};

struct icmsg_config_t {
	uintptr_t tx_shm_addr;
	uintptr_t rx_shm_addr;
	size_t tx_shm_size;
	size_t rx_shm_size;
	struct mbox_channel mbox_tx;
	struct mbox_channel mbox_rx;
};

struct icmsg_data_t {
	/* Tx/Rx buffers. */
	struct spsc_pbuf *tx_ib;
	struct spsc_pbuf *rx_ib;
	atomic_t tx_buffer_state;
#ifdef CONFIG_IPC_SERVICE_ICMSG_SHMEM_ACCESS_SYNC
	struct k_mutex tx_lock;
#endif

	/* Callbacks for an endpoint. */
	const struct ipc_service_cb *cb;
	void *ctx;

	/* General */
	const struct icmsg_config_t *cfg;
	struct k_work_delayable notify_work;
	struct k_work mbox_work;
	atomic_t state;
	/* No-copy */
#ifdef CONFIG_IPC_SERVICE_ICMSG_NOCOPY_RX
	atomic_t rx_buffer_state;
	const void *rx_buffer;
	uint16_t rx_len;
#endif
};

/** @brief Open an icmsg instance
 *
 *  Open an icmsg instance to be able to send and receive messages to a remote
 *  instance.
 *  This function is blocking until the handshake with the remote instance is
 *  completed.
 *  This function is intended to be called late in the initialization process,
 *  possibly from a thread which can be safely blocked while handshake with the
 *  remote instance is being pefromed.
 *
 *  @param[in] conf Structure containing configuration parameters for the icmsg
 *                  instance.
 *  @param[inout] dev_data Structure containing run-time data used by the icmsg
 *                         instance.
 *  @param[in] cb Structure containing callback functions to be called on
 *                events generated by this icmsg instance. The pointed memory
 *                must be preserved while the icmsg instance is active.
 *  @param[in] ctx Pointer to context passed as an argument to callbacks.
 *
 *
 *  @retval 0 on success.
 *  @retval -EALREADY when the instance is already opened.
 *  @retval other errno codes from dependent modules.
 */
int icmsg_open(const struct icmsg_config_t *conf,
	       struct icmsg_data_t *dev_data,
	       const struct ipc_service_cb *cb, void *ctx);

/** @brief Close an icmsg instance
 *
 *  Closing an icmsg instance results in releasing all resources used by given
 *  instance including the shared memory regions and mbox devices.
 *
 *  @param[in] conf Structure containing configuration parameters for the icmsg
 *                  instance being closed. Its content must be the same as used
 *                  for creating this instance with @ref icmsg_open.
 *  @param[inout] dev_data Structure containing run-time data used by the icmsg
 *                         instance.
 *
 *  @retval 0 on success.
 *  @retval other errno codes from dependent modules.
 */
int icmsg_close(const struct icmsg_config_t *conf,
		struct icmsg_data_t *dev_data);

/** @brief Send a message to the remote icmsg instance.
 *
 *  @param[in] conf Structure containing configuration parameters for the icmsg
 *                  instance.
 *  @param[inout] dev_data Structure containing run-time data used by the icmsg
 *                         instance.
 *  @param[in] msg Pointer to a buffer containing data to send.
 *  @param[in] len Size of data in the @p msg buffer.
 *
 *
 *  @retval 0 on success.
 *  @retval -EBUSY when the instance has not finished handshake with the remote
 *                 instance.
 *  @retval -ENODATA when the requested data to send is empty.
 *  @retval -EBADMSG when the requested data to send is too big.
 *  @retval -ENOBUFS when there are no TX buffers available.
 *  @retval other errno codes from dependent modules.
 */
int icmsg_send(const struct icmsg_config_t *conf,
	       struct icmsg_data_t *dev_data,
	       const void *msg, size_t len);

/** @brief Get an empty TX buffer to be sent using @ref icmsg_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 avoiding copy
 *  performed by the icmsg library.
 *
 *  It is the application responsibility to correctly fill the allocated TX
 *  buffer with data and passing correct parameters to @ref
 *  icmsg_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 one of two conditions: (1) when sending the buffer
 *  using @ref icmsg_send_nocopy (and in this case the buffer is automatically
 *  released by the backend), (2) when using @ref icmsg_drop_tx_buffer on a
 *  buffer not sent.
 *
 *  @param[in] conf Structure containing configuration parameters for the icmsg
 *                  instance.
 *  @param[inout] dev_data Structure containing run-time data used by the icmsg
 *                         instance.
 *  @param[out] data Pointer to the empty TX buffer.
 *  @param[inout] size Pointer to store the requested TX buffer size. If the
 *		       function returns -ENOMEM, this parameter returns the
 *		       maximum allowed size.
 *
 *  @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.
 */
int icmsg_get_tx_buffer(const struct icmsg_config_t *conf,
			struct icmsg_data_t *dev_data,
			void **data, size_t *size);

/** @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 icmsg_get_tx_buffer.
 *
 *  @param[in] conf Structure containing configuration parameters for the icmsg
 *                  instance.
 *  @param[inout] dev_data Structure containing run-time data used by the icmsg
 *                         instance.
 *  @param[in] data Pointer to the TX buffer.
 *
 *  @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.
 */
int icmsg_drop_tx_buffer(const struct icmsg_config_t *conf,
			 struct icmsg_data_t *dev_data,
			 const void *data);

/** @brief Send a message from a buffer obtained by @ref icmsg_get_tx_buffer
 *         to the remote icmsg instance.
 *
 *  This is equivalent to @ref icmsg_send but in this case the TX buffer must
 *  have been obtained by using @ref icmsg_get_tx_buffer.
 *
 *  The API user has to take the responsibility for getting the TX buffer using
 *  @ref icmsg_get_tx_buffer and filling the TX buffer with the data.
 *
 *  After the @ref icmsg_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 icmsg_drop_tx_buffer can be used
 *  to drop the TX buffer.
 *
 *  @param[in] conf Structure containing configuration parameters for the icmsg
 *                  instance.
 *  @param[inout] dev_data Structure containing run-time data used by the icmsg
 *                         instance.
 *  @param[in] msg Pointer to a buffer containing data to send.
 *  @param[in] len Size of data in the @p msg buffer.
 *
 *
 *  @return Size of sent data on success.
 *  @retval -EBUSY when the instance has not finished handshake with the remote
 *                 instance.
 *  @retval -ENODATA when the requested data to send is empty.
 *  @retval -EBADMSG when the requested data to send is too big.
 *  @retval -ENXIO when the buffer was not obtained using @ref
 *		   ipc_service_get_tx_buffer
 *  @retval other errno codes from dependent modules.
 */
int icmsg_send_nocopy(const struct icmsg_config_t *conf,
		      struct icmsg_data_t *dev_data,
		      const void *msg, size_t len);

#ifdef CONFIG_IPC_SERVICE_ICMSG_NOCOPY_RX
/** @brief Hold RX buffer to be used outside of the received callback.
 *
 *  @param[in] conf Structure containing configuration parameters for the icmsg
 *                  instance.
 *  @param[inout] dev_data Structure containing run-time data used by the icmsg
 *                         instance.
 *  @param[in] data Pointer to the buffer to be held.
 *
 *  @retval 0 on success.
 *  @retval -EBUSY when the instance has not finished handshake with the remote
 *                 instance.
 *  @retval -EINVAL when the @p data argument does not point to a valid RX
 *                  buffer.
 *  @retval -EALREADY when the buffer is already held.
 */
int icmsg_hold_rx_buffer(const struct icmsg_config_t *conf,
			 struct icmsg_data_t *dev_data,
			 const void *data);

/** @brief Release RX buffer for future use.
 *
 *  @param[in] conf Structure containing configuration parameters for the icmsg
 *                  instance.
 *  @param[inout] dev_data Structure containing run-time data used by the icmsg
 *                         instance.
 *  @param[in] data Pointer to the buffer to be released.
 *
 *  @retval 0 on success.
 *  @retval -EBUSY when the instance has not finished handshake with the remote
 *                 instance.
 *  @retval -EINVAL when the @p data argument does not point to a valid RX
 *                  buffer.
 *  @retval -EALREADY when the buffer is not held.
 */
int icmsg_release_rx_buffer(const struct icmsg_config_t *conf,
			    struct icmsg_data_t *dev_data,
			    const void *data);
#endif

/**
 * @}
 */

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_IPC_ICMSG_H_ */
