/*
 * Copyright (c) 2019 Peter Bigot Consulting, LLC
 * Copyright (c) 2020 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef ZEPHYR_INCLUDE_SYS_NOTIFY_H_
#define ZEPHYR_INCLUDE_SYS_NOTIFY_H_

#include <kernel.h>
#include <zephyr/types.h>

#ifdef __cplusplus
extern "C" {
#endif

struct sys_notify;

/*
 * Flag value that overwrites the method field when the operation has
 * completed.
 */
#define SYS_NOTIFY_METHOD_COMPLETED 0

/*
 * Indicates that no notification will be provided.
 *
 * Callers must check for completions using
 * sys_notify_fetch_result().
 *
 * See sys_notify_init_spinwait().
 */
#define SYS_NOTIFY_METHOD_SPINWAIT 1

/*
 * Select notification through @ref k_poll signal
 *
 * See sys_notify_init_signal().
 */
#define SYS_NOTIFY_METHOD_SIGNAL 2

/*
 * Select notification through a user-provided callback.
 *
 * See sys_notify_init_callback().
 */
#define SYS_NOTIFY_METHOD_CALLBACK 3

#define SYS_NOTIFY_METHOD_MASK 0x03U
#define SYS_NOTIFY_METHOD_POS 0

/**
 * @brief Identify the region of sys_notify flags available for
 * containing services.
 *
 * Bits of the flags field of the sys_notify structure at and above
 * this position may be used by extensions to the sys_notify
 * structure.
 *
 * These bits are intended for use by containing service
 * implementations to record client-specific information.  The bits
 * are cleared by sys_notify_validate().  Use of these does not
 * imply that the flags field becomes public API.
 */
#define SYS_NOTIFY_EXTENSION_POS 2

/*
 * Mask isolating the bits of sys_notify::flags that are available
 * for extension.
 */
#define SYS_NOTIFY_EXTENSION_MASK (~BIT_MASK(SYS_NOTIFY_EXTENSION_POS))

/**
 * @defgroup sys_notify_apis Asynchronous Notification APIs
 * @ingroup kernel_apis
 * @{
 */

/**
 * @brief Generic signature used to notify of result completion by
 * callback.
 *
 * Functions with this role may be invoked from any context including
 * pre-kernel, ISR, or cooperative or pre-emptible threads.
 * Compatible functions must be isr-ok and not sleep.
 *
 * Parameters that should generally be passed to such functions include:
 *
 * * a pointer to a specific client request structure, i.e. the one
 *   that contains the sys_notify structure.
 * * the result of the operation, either as passed to
 *   sys_notify_finalize() or extracted afterwards using
 *   sys_notify_fetch_result().  Expected values are
 *   service-specific, but the value shall be non-negative if the
 *   operation succeeded, and negative if the operation failed.
 */
typedef void (*sys_notify_generic_callback)();

/**
 * @brief State associated with notification for an asynchronous
 * operation.
 *
 * Objects of this type are allocated by a client, which must use an
 * initialization function (e.g. sys_notify_init_signal()) to
 * configure them.  Generally the structure is a member of a
 * service-specific client structure, such as onoff_client.
 *
 * Control of the containing object transfers to the service provider
 * when a pointer to the object is passed to a service function that
 * is documented to take control of the object, such as
 * onoff_service_request().  While the service provider controls the
 * object the client must not change any object fields.  Control
 * reverts to the client:
 * * if the call to the service API returns an error;
 * * when operation completion is posted.  This may occur before the
 *   call to the service API returns.
 *
 * Operation completion is technically posted when the flags field is
 * updated so that sys_notify_fetch_result() returns success.  This
 * will happen before the signal is posted or callback is invoked.
 * Note that although the manager will no longer reference the
 * sys_notify object past this point, the containing object may have
 * state that will be referenced within the callback.  Where callbacks
 * are used control of the containing object does not revert to the
 * client until the callback has been invoked.  (Re-use within the
 * callback is explicitly permitted.)
 *
 * After control has reverted to the client the notify object must be
 * reinitialized for the next operation.
 *
 * The content of this structure is not public API to clients: all
 * configuration and inspection should be done with functions like
 * sys_notify_init_callback() and sys_notify_fetch_result().
 * However, services that use this structure may access certain
 * fields directly.
 */
struct sys_notify {
	union method {
		/* Pointer to signal used to notify client.
		 *
		 * The signal value corresponds to the res parameter
		 * of sys_notify_callback.
		 */
		struct k_poll_signal *signal;

		/* Generic callback function for callback notification. */
		sys_notify_generic_callback callback;
	} method;

	/*
	 * Flags recording information about the operation.
	 *
	 * Bits below SYS_NOTIFY_EXTENSION_POS are initialized by
	 * async notify API init functions like
	 * sys_notify_init_callback(), and must not by modified by
	 * extensions or client code.
	 *
	 * Bits at and above SYS_NOTIFY_EXTENSION_POS are available
	 * for use by service extensions while the containing object
	 * is managed by the service.  They are not for client use,
	 * are zeroed by the async notify API init functions, and will
	 * be zeroed by sys_notify_finalize().
	 */
	uint32_t volatile flags;

	/*
	 * The result of the operation.
	 *
	 * This is the value that was (or would be) passed to the
	 * async infrastructure.  This field is the sole record of
	 * success or failure for spin-wait synchronous operations.
	 */
	int volatile result;
};

/** @internal */
static inline uint32_t sys_notify_get_method(const struct sys_notify *notify)
{
	uint32_t method = notify->flags >> SYS_NOTIFY_METHOD_POS;

	return method & SYS_NOTIFY_METHOD_MASK;
}

/**
 * @brief Validate and initialize the notify structure.
 *
 * This should be invoked at the start of any service-specific
 * configuration validation.  It ensures that the basic asynchronous
 * notification configuration is consistent, and clears the result.
 *
 * Note that this function does not validate extension bits (zeroed by
 * async notify API init functions like sys_notify_init_callback()).
 * It may fail to recognize that an uninitialized structure has been
 * passed because only method bits of flags are tested against method
 * settings.  To reduce the chance of accepting an uninititalized
 * operation service validation of structures that contain an
 * sys_notify instance should confirm that the extension bits are
 * set or cleared as expected.
 *
 * @retval 0 on successful validation and reinitialization
 * @retval -EINVAL if the configuration is not valid.
 */
int sys_notify_validate(struct sys_notify *notify);

/**
 * @brief Record and signal the operation completion.
 *
 * @param notify pointer to the notification state structure.
 *
 * @param res the result of the operation.  Expected values are
 * service-specific, but the value shall be non-negative if the
 * operation succeeded, and negative if the operation failed.
 *
 * @return If the notification is to be done by callback this returns
 * the generic version of the function to be invoked.  The caller must
 * immediately invoke that function with whatever arguments are
 * expected by the callback.  If notification is by spin-wait or
 * signal, the notification has been completed by the point this
 * function returns, and a null pointer is returned.
 */
sys_notify_generic_callback sys_notify_finalize(struct sys_notify *notify,
						    int res);

/**
 * @brief Check for and read the result of an asynchronous operation.
 *
 * @param notify pointer to the object used to specify asynchronous
 * function behavior and store completion information.
 *
 * @param result pointer to storage for the result of the operation.
 * The result is stored only if the operation has completed.
 *
 * @retval 0 if the operation has completed.
 * @retval -EAGAIN if the operation has not completed.
 */
static inline int sys_notify_fetch_result(const struct sys_notify *notify,
					    int *result)
{
	__ASSERT_NO_MSG(notify != NULL);
	__ASSERT_NO_MSG(result != NULL);
	int rv = -EAGAIN;

	if (sys_notify_get_method(notify) == SYS_NOTIFY_METHOD_COMPLETED) {
		rv = 0;
		*result = notify->result;
	}

	return rv;
}

/**
 * @brief Initialize a notify object for spin-wait notification.
 *
 * Clients that use this initialization receive no asynchronous
 * notification, and instead must periodically check for completion
 * using sys_notify_fetch_result().
 *
 * On completion of the operation the client object must be
 * reinitialized before it can be re-used.
 *
 * @param notify pointer to the notification configuration object.
 */
static inline void sys_notify_init_spinwait(struct sys_notify *notify)
{
	__ASSERT_NO_MSG(notify != NULL);

	*notify = (struct sys_notify){
		.flags = SYS_NOTIFY_METHOD_SPINWAIT,
	};
}

/**
 * @brief Initialize a notify object for (k_poll) signal notification.
 *
 * Clients that use this initialization will be notified of the
 * completion of operations through the provided signal.
 *
 * On completion of the operation the client object must be
 * reinitialized before it can be re-used.
 *
 * @note
 *   This capability is available only when @kconfig{CONFIG_POLL} is
 *   selected.
 *
 * @param notify pointer to the notification configuration object.
 *
 * @param sigp pointer to the signal to use for notification.  The
 * value must not be null.  The signal must be reset before the client
 * object is passed to the on-off service API.
 */
static inline void sys_notify_init_signal(struct sys_notify *notify,
					    struct k_poll_signal *sigp)
{
	__ASSERT_NO_MSG(notify != NULL);
	__ASSERT_NO_MSG(sigp != NULL);

	*notify = (struct sys_notify){
		.method = {
			.signal = sigp,
		},
		.flags = SYS_NOTIFY_METHOD_SIGNAL,
	};
}

/**
 * @brief Initialize a notify object for callback notification.
 *
 * Clients that use this initialization will be notified of the
 * completion of operations through the provided callback.  Note that
 * callbacks may be invoked from various contexts depending on the
 * specific service; see @ref sys_notify_generic_callback.
 *
 * On completion of the operation the client object must be
 * reinitialized before it can be re-used.
 *
 * @param notify pointer to the notification configuration object.
 *
 * @param handler a function pointer to use for notification.
 */
static inline void sys_notify_init_callback(struct sys_notify *notify,
					      sys_notify_generic_callback handler)
{
	__ASSERT_NO_MSG(notify != NULL);
	__ASSERT_NO_MSG(handler != NULL);

	*notify = (struct sys_notify){
		.method = {
			.callback = handler,
		},
		.flags = SYS_NOTIFY_METHOD_CALLBACK,
	};
}

/**
 * @brief Detect whether a particular notification uses a callback.
 *
 * The generic handler does not capture the signature expected by the
 * callback, and the translation to a service-specific callback must
 * be provided by the service.  This check allows abstracted services
 * to reject callback notification requests when the service doesn't
 * provide a translation function.
 *
 * @return true if and only if a callback is to be used for notification.
 */
static inline bool sys_notify_uses_callback(const struct sys_notify *notify)
{
	__ASSERT_NO_MSG(notify != NULL);

	return sys_notify_get_method(notify) == SYS_NOTIFY_METHOD_CALLBACK;
}

/** @} */

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_SYS_NOTIFY_H_ */
