blob: 8163adbd730853ab1478497f252788ec287dda49 [file] [log] [blame]
/*
* 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 <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/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
* appropriate 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_ */