/* * 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 #include #include #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) /** @cond INTERNAL_HIDDEN */ #define ONOFF_FLAG_ONOFF BIT(1) #define ONOFF_FLAG_TRANSITION BIT(2) /** @endcond */ /** * @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), \ } /** @cond INTERNAL_HIDDEN */ #define ONOFF_MANAGER_INITIALIZER(_transitions) { \ .transitions = (_transitions), \ } /** @endcond */ /** * @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 { /** @cond INTERNAL_HIDDEN * * Links the client into the set of waiting service users. * Applications must ensure this field is zero-initialized * before use. */ sys_snode_t node; /** @endcond */ /** @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 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 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; /** 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_ */