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

#ifndef ZEPHYR_INCLUDE_SYS_ONOFF_H_
#define ZEPHYR_INCLUDE_SYS_ONOFF_H_

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

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @defgroup resource_mgmt_onoff_apis On-Off Service APIs
 * @ingroup kernel_apis
 * @{
 */

/**
 * @brief Flag indicating an error state.
 *
 * Error states are cleared using onoff_reset().
 */
#define ONOFF_FLAG_ERROR BIT(0)

/** @internal */
#define ONOFF_FLAG_ONOFF BIT(1)
/** @internal */
#define ONOFF_FLAG_TRANSITION BIT(2)

/**
 * @brief Mask used to isolate bits defining the service state.
 *
 * Mask a value with this then test for ONOFF_FLAG_ERROR to determine
 * whether the machine has an unfixed error, or compare against
 * ONOFF_STATE_ON, ONOFF_STATE_OFF, ONOFF_STATE_TO_ON,
 * ONOFF_STATE_TO_OFF, or ONOFF_STATE_RESETTING.
 */
#define ONOFF_STATE_MASK (ONOFF_FLAG_ERROR   \
			  | ONOFF_FLAG_ONOFF \
			  | ONOFF_FLAG_TRANSITION)

/**
 * @brief Value exposed by ONOFF_STATE_MASK when service is off.
 */
#define ONOFF_STATE_OFF 0U

/**
 * @brief Value exposed by ONOFF_STATE_MASK when service is on.
 */
#define ONOFF_STATE_ON ONOFF_FLAG_ONOFF

/**
 * @brief Value exposed by ONOFF_STATE_MASK when the service is in an
 * error state (and not in the process of resetting its state).
 */
#define ONOFF_STATE_ERROR ONOFF_FLAG_ERROR

/**
 * @brief Value exposed by ONOFF_STATE_MASK when service is
 * transitioning to on.
 */
#define ONOFF_STATE_TO_ON (ONOFF_FLAG_TRANSITION | ONOFF_STATE_ON)

/**
 * @brief Value exposed by ONOFF_STATE_MASK when service is
 * transitioning to off.
 */
#define ONOFF_STATE_TO_OFF (ONOFF_FLAG_TRANSITION | ONOFF_STATE_OFF)

/**
 * @brief Value exposed by ONOFF_STATE_MASK when service is in the
 * process of resetting.
 */
#define ONOFF_STATE_RESETTING (ONOFF_FLAG_TRANSITION | ONOFF_STATE_ERROR)

/* Forward declarations */
struct onoff_manager;
struct onoff_monitor;

/**
 * @brief Signature used to notify an on-off manager that a transition
 * has completed.
 *
 * Functions of this type are passed to service-specific transition
 * functions to be used to report the completion of the operation.
 * The functions may be invoked from any context.
 *
 * @param mgr the manager for which transition was requested.
 *
 * @param res the result of the transition.  This shall be
 * non-negative on success, or a negative error code.  If an error is
 * indicated the service shall enter an error state.
 */
typedef void (*onoff_notify_fn)(struct onoff_manager *mgr,
				int res);

/**
 * @brief Signature used by service implementations to effect a
 * transition.
 *
 * Service definitions use two required function pointers of this type
 * to be notified that a transition is required, and a third optional
 * one to reset the service when it is in an error state.
 *
 * The start function will be called only from the off state.
 *
 * The stop function will be called only from the on state.
 *
 * The reset function (where supported) will be called only when
 * onoff_has_error() returns true.
 *
 * @note All transitions functions must be isr-ok.
 *
 * @param mgr the manager for which transition was requested.
 *
 * @param notify the function to be invoked when the transition has
 * completed.  If the transition is synchronous, notify shall be
 * invoked by the implementation before the transition function
 * returns.  Otherwise the implementation shall capture this parameter
 * and invoke it when the transition completes.
 */
typedef void (*onoff_transition_fn)(struct onoff_manager *mgr,
				    onoff_notify_fn notify);

/** @brief On-off service transition functions. */
struct onoff_transitions {
	/* Function to invoke to transition the service to on. */
	onoff_transition_fn start;

	/* Function to invoke to transition the service to off. */
	onoff_transition_fn stop;

	/* Function to force the service state to reset, where
	 * supported.
	 */
	onoff_transition_fn reset;
};

/**
 * @brief State associated with an on-off manager.
 *
 * No fields in this structure are intended for use by service
 * providers or clients.  The state is to be initialized once, using
 * onoff_manager_init(), when the service provider is initialized.  In
 * case of error it may be reset through the onoff_reset() API.
 */
struct onoff_manager {
	/* List of clients waiting for request or reset completion
	 * notifications.
	 */
	sys_slist_t clients;

	/* List of monitors to be notified of state changes including
	 * errors and transition completion.
	 */
	sys_slist_t monitors;

	/* Transition functions. */
	const struct onoff_transitions *transitions;

	/* Mutex protection for other fields. */
	struct k_spinlock lock;

	/* The result of the last transition. */
	int last_res;

	/* Flags identifying the service state. */
	uint16_t flags;

	/* Number of active clients for the service. */
	uint16_t refs;
};

/** @brief Initializer for a onoff_transitions object.
 *
 * @param _start a function used to transition from off to on state.
 *
 * @param _stop a function used to transition from on to off state.
 *
 * @param _reset a function used to clear errors and force the service
 * to an off state. Can be null.
 */
#define ONOFF_TRANSITIONS_INITIALIZER(_start, _stop, _reset) { \
		.start = _start,			       \
		.stop = _stop,				       \
		.reset = _reset,			       \
}

/** @internal */
#define ONOFF_MANAGER_INITIALIZER(_transitions) { \
		.transitions = _transitions,	  \
}

/**
 * @brief Initialize an on-off service to off state.
 *
 * This function must be invoked exactly once per service instance, by
 * the infrastructure that provides the service, and before any other
 * on-off service API is invoked on the service.
 *
 * This function should never be invoked by clients of an on-off
 * service.
 *
 * @param mgr the manager definition object to be initialized.
 *
 * @param transitions pointer to a structure providing transition
 * functions.  The referenced object must persist as long as the
 * manager can be referenced.
 *
 * @retval 0 on success
 * @retval -EINVAL if start, stop, or flags are invalid
 */
int onoff_manager_init(struct onoff_manager *mgr,
		       const struct onoff_transitions *transitions);

/* Forward declaration */
struct onoff_client;

/**
 * @brief Signature used to notify an on-off service client of the
 * completion of an operation.
 *
 * These functions 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.
 *
 * @param mgr the manager for which the operation was initiated.  This may be
 * null if the on-off service uses synchronous transitions.
 *
 * @param cli the client structure passed to the function that
 * initiated the operation.
 *
 * @param state the state of the machine at the time of completion,
 * restricted by ONOFF_STATE_MASK.  ONOFF_FLAG_ERROR must be checked
 * independently of whether res is negative as a machine error may
 * indicate that all future operations except onoff_reset() will fail.
 *
 * @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.  If res
 * is negative ONOFF_FLAG_ERROR will be set in state, but if res is
 * non-negative ONOFF_FLAG_ERROR may still be set in state.
 */
typedef void (*onoff_client_callback)(struct onoff_manager *mgr,
				      struct onoff_client *cli,
				      uint32_t state,
				      int res);

/**
 * @brief State associated with a client of an on-off service.
 *
 * Objects of this type are allocated by a client, which is
 * responsible for zero-initializing the node field and invoking the
 * approprite sys_notify init function to configure notification.
 *
 * Control of the object content transfers to the service provider
 * when a pointer to the object is passed to any on-off manager
 * function.  While the service provider controls the object the
 * client must not change any object fields.  Control reverts to the
 * client concurrent with release of the owned sys_notify structure,
 * or when indicated by an onoff_cancel() return value.
 *
 * After control has reverted to the client the notify field must be
 * reinitialized for the next operation.
 */
struct onoff_client {
	/** @internal
	 *
	 * Links the client into the set of waiting service users.
	 * Applications must ensure this field is zero-initialized
	 * before use.
	 */
	sys_snode_t node;

	/** @brief Notification configuration. */
	struct sys_notify notify;
};

/**
 * @brief Identify region of sys_notify flags available for
 * containing services.
 *
 * Bits of the flags field of the sys_notify structure contained
 * within the queued_operation structure at and above this position
 * may be used by extensions to the onoff_client structure.
 *
 * These bits are intended for use by containing service
 * implementations to record client-specific information and are
 * subject to other conditions of use specified on the sys_notify API.
 */
#define ONOFF_CLIENT_EXTENSION_POS SYS_NOTIFY_EXTENSION_POS

/**
 * @brief Test whether an on-off service has recorded an error.
 *
 * This function can be used to determine whether the service has
 * recorded an error.  Errors may be cleared by invoking
 * onoff_reset().
 *
 * This is an unlocked convenience function suitable for use only when
 * it is known that no other process might invoke an operation that
 * transitions the service between an error and non-error state.
 *
 * @return true if and only if the service has an uncleared error.
 */
static inline bool onoff_has_error(const struct onoff_manager *mgr)
{
	return (mgr->flags & ONOFF_FLAG_ERROR) != 0;
}

/**
 * @brief Request a reservation to use an on-off service.
 *
 * The return value indicates the success or failure of an attempt to
 * initiate an operation to request the resource be made available.
 * If initiation of the operation succeeds the result of the request
 * operation is provided through the configured client notification
 * method, possibly before this call returns.
 *
 * Note that the call to this function may succeed in a case where the
 * actual request fails.  Always check the operation completion
 * result.
 *
 * @param mgr the manager that will be used.
 *
 * @param cli a non-null pointer to client state providing
 * instructions on synchronous expectations and how to notify the
 * client when the request completes.  Behavior is undefined if client
 * passes a pointer object associated with an incomplete service
 * operation.
 *
 * @retval non-negative the observed state of the machine at the time
 * the request was processed, if successful.
 * @retval -EIO if service has recorded an an error.
 * @retval -EINVAL if the parameters are invalid.
 * @retval -EAGAIN if the reference count would overflow.
 */
int onoff_request(struct onoff_manager *mgr,
		  struct onoff_client *cli);

/**
 * @brief Release a reserved use of an on-off service.
 *
 * This synchronously releases the caller's previous request.  If the
 * last request is released the manager will initiate a transition to
 * off, which can be observed by registering an onoff_monitor.
 *
 * @note Behavior is undefined if this is not paired with a preceding
 * onoff_request() call that completed successfully.
 *
 * @param mgr the manager for which a request was successful.
 *
 * @retval non-negative the observed state (ONOFF_STATE_ON) of the
 * machine at the time of the release, if the release succeeds.
 * @retval -EIO if service has recorded an an error.
 * @retval -ENOTSUP if the machine is not in a state that permits
 * release.
 */
int onoff_release(struct onoff_manager *mgr);

/**
 * @brief Attempt to cancel an in-progress client operation.
 *
 * It may be that a client has initiated an operation but needs to
 * shut down before the operation has completed.  For example, when a
 * request was made and the need is no longer present.
 *
 * Cancelling is supported only for onoff_request() and onoff_reset()
 * operations, and is a synchronous operation.  Be aware that any
 * transition that was initiated on behalf of the client will continue
 * to progress to completion: it is only notification of transition
 * completion that may be eliminated.  If there are no active requests
 * when a transition to on completes the manager will initiate a
 * transition to off.
 *
 * Client notification does not occur for cancelled operations.
 *
 * @param mgr the manager for which an operation is to be cancelled.
 *
 * @param cli a pointer to the same client state that was provided
 * when the operation to be cancelled was issued.
 *
 * @retval non-negative the observed state of the machine at the time
 * of the cancellation, if the cancellation succeeds.  On successful
 * cancellation ownership of @c *cli reverts to the client.
 * @retval -EINVAL if the parameters are invalid.
 * @retval -EALREADY if cli was not a record of an uncompleted
 * notification at the time the cancellation was processed.  This
 * likely indicates that the operation and client notification had
 * already completed.
 */
int onoff_cancel(struct onoff_manager *mgr,
		 struct onoff_client *cli);

/**
 * @brief Helper function to safely cancel a request.
 *
 * Some applications may want to issue requests on an asynchronous
 * event (such as connection to a USB bus) and to release on a paired
 * event (such as loss of connection to a USB bus).  Applications
 * cannot precisely determine that an in-progress request is still
 * pending without using onoff_monitor and carefully avoiding race
 * conditions.
 *
 * This function is a helper that attempts to cancel the operation and
 * issues a release if cancellation fails because the request was
 * completed.  This synchronously ensures that ownership of the client
 * data reverts to the client so is available for a future request.
 *
 * @param mgr the manager for which an operation is to be cancelled.
 *
 * @param cli a pointer to the same client state that was provided
 * when onoff_request() was invoked.  Behavior is undefined if this is
 * a pointer to client data associated with an onoff_reset() request.
 *
 * @retval ONOFF_STATE_TO_ON if the cancellation occurred before the
 * transition completed.
 *
 * @retval ONOFF_STATE_ON if the cancellation occurred after the
 * transition completed.
 *
 * @retval -EINVAL if the parameters are invalid.
 *
 * @retval negative other errors produced by onoff_release().
 */
static inline int onoff_cancel_or_release(struct onoff_manager *mgr,
					  struct onoff_client *cli)
{
	int rv = onoff_cancel(mgr, cli);

	if (rv == -EALREADY) {
		rv = onoff_release(mgr);
	}
	return rv;
}

/**
 * @brief Clear errors on an on-off service and reset it to its off
 * state.
 *
 * A service can only be reset when it is in an error state as
 * indicated by onoff_has_error().
 *
 * The return value indicates the success or failure of an attempt to
 * initiate an operation to reset the resource.  If initiation of the
 * operation succeeds the result of the reset operation itself is
 * provided through the configured client notification method,
 * possibly before this call returns.  Multiple clients may request a
 * reset; all are notified when it is complete.
 *
 * Note that the call to this function may succeed in a case where the
 * actual reset fails.  Always check the operation completion result.
 *
 * @note Due to the conditions on state transition all incomplete
 * asynchronous operations will have been informed of the error when
 * it occurred.  There need be no concern about dangling requests left
 * after a reset completes.
 *
 * @param mgr the manager to be reset.
 *
 * @param cli pointer to client state, including instructions on how
 * to notify the client when reset completes.  Behavior is undefined
 * if cli references an object associated with an incomplete service
 * operation.
 *
 * @retval non-negative the observed state of the machine at the time
 * of the reset, if the reset succeeds.
 * @retval -ENOTSUP if reset is not supported by the service.
 * @retval -EINVAL if the parameters are invalid.
 * @retval -EALREADY if the service does not have a recorded error.
 */
int onoff_reset(struct onoff_manager *mgr,
		struct onoff_client *cli);

/**
 * @brief Signature used to notify a monitor of an onoff service of
 * errors or completion of a state transition.
 *
 * This is similar to onoff_client_callback but provides information
 * about all transitions, not just ones associated with a specific
 * client.  Monitor callbacks are invoked before any completion
 * notifications associated with the state change are made.
 *
 * These functions 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.
 *
 * The callback is permitted to unregister itself from the manager,
 * but must not register or unregister any other monitors.
 *
 * @param mgr the manager for which a transition has completed.
 *
 * @param mon the monitor instance through which this notification
 * arrived.
 *
 * @param state the state of the machine at the time of completion,
 * restricted by ONOFF_STATE_MASK.  All valid states may be observed.
 *
 * @param res the result of the operation.  Expected values are
 * service- and state-specific, but the value shall be non-negative if
 * the operation succeeded, and negative if the operation failed.
 */
typedef void (*onoff_monitor_callback)(struct onoff_manager *mgr,
				       struct onoff_monitor *mon,
				       uint32_t state,
				       int res);

/**
 * @brief Registration state for notifications of onoff service
 * transitions.
 *
 * Any given onoff_monitor structure can be associated with at most
 * one onoff_manager instance.
 */
struct onoff_monitor {
	/* Links the client into the set of waiting service users.
	 *
	 * This must be zero-initialized.
	 */
	sys_snode_t node;

	/** @brief Callback to be invoked on state change.
	 *
	 * This must not be null.
	 */
	onoff_monitor_callback callback;
};

/**
 * @brief Add a monitor of state changes for a manager.
 *
 * @param mgr the manager for which a state changes are to be monitored.
 *
 * @param mon a linkable node providing a non-null callback to be
 * invoked on state changes.
 *
 * @return non-negative on successful addition, or a negative error
 * code.
 */
int onoff_monitor_register(struct onoff_manager *mgr,
			   struct onoff_monitor *mon);

/**
 * @brief Remove a monitor of state changes from a manager.
 *
 * @param mgr the manager for which a state changes are to be monitored.
 *
 * @param mon a linkable node providing the callback to be invoked on
 * state changes.
 *
 * @return non-negative on successful removal, or a negative error
 * code.
 */
int onoff_monitor_unregister(struct onoff_manager *mgr,
			     struct onoff_monitor *mon);

/**
 * @brief State used when a driver uses the on-off service API for synchronous
 * operations.
 *
 * This is useful when a subsystem API uses the on-off API to support
 * asynchronous operations but the transitions required by a
 * particular driver are isr-ok and not sleep.  It serves as a
 * substitute for #onoff_manager, with locking and persisted state
 * updates supported by onoff_sync_lock() and onoff_sync_finalize().
 */
struct onoff_sync_service {
	/* Mutex protection for other fields. */
	struct k_spinlock lock;

	/* Negative is error, non-negative is reference count. */
	int32_t count;
};

/**
 * @brief Lock a synchronous onoff service and provide its state.
 *
 * @note If an error state is returned it is the caller's responsibility to
 * decide whether to preserve it (finalize with the same error state) or clear
 * the error (finalize with a non-error result).
 *
 * @param srv pointer to the synchronous service state.
 *
 * @param keyp pointer to where the lock key should be stored
 *
 * @return negative if the service is in an error state, otherwise the
 * number of active requests at the time the lock was taken.  The lock
 * is held on return regardless of whether a negative state is
 * returned.
 */
int onoff_sync_lock(struct onoff_sync_service *srv,
		    k_spinlock_key_t *keyp);

/**
 * @brief Process the completion of a transition in a synchronous
 * service and release lock.
 *
 * This function updates the service state on the @p res and @p on parameters
 * then releases the lock.  If @p cli is not null it finalizes the client
 * notification using @p res.
 *
 * If the service was in an error state when locked, and @p res is non-negative
 * when finalized, the count is reset to zero before completing finalization.
 *
 * @param srv pointer to the synchronous service state
 *
 * @param key the key returned by the preceding invocation of onoff_sync_lock().
 *
 * @param cli pointer to the onoff client through which completion
 * information is returned.  If a null pointer is passed only the
 * state of the service is updated.  For compatibility with the
 * behavior of callbacks used with the manager API @p cli must be null
 * when @p on is false (the manager does not support callbacks when
 * turning off devices).
 *
 * @param res the result of the transition.  A negative value places the service
 * into an error state.  A non-negative value increments or decrements the
 * reference count as specified by @p on.
 *
 * @param on Only when @p res is non-negative, the service reference count will
 * be incremented if@p on is @c true, and decremented if @p on is @c false.
 *
 * @return negative if the service is left or put into an error state, otherwise
 * the number of active requests at the time the lock was released.
 */
int onoff_sync_finalize(struct onoff_sync_service *srv,
			 k_spinlock_key_t key,
			 struct onoff_client *cli,
			 int res,
			 bool on);

/** @} */

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_SYS_ONOFF_H_ */
