| /* |
| * 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 |
| * 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_ */ |