sys: onoff: redesign to meet changed needs

The previous architecture proved unable to support user expectations,
so the API has been rebuilt from first principles.  Backward
compatibility cannot be maintained for this change.

Key changes include:

* Formerly the service-provided transition functions were allowed to
  sleep, and the manager took care to not invoke them from ISR
  context, instead returning an error if unable to initiate a
  transition.  In the new architecture transition functions are
  required to work regardless of calling context: it is the service's
  responsibility to guarantee the transition will proceed even if it
  needs to be transferred to a thread.  This eliminates state machine
  complexities related to calling context.
* Constants identifying the visible state of the manager are exposed
  to clients through both notification callbacks and a new monitor API
  that allows clients to be notified of all state changes.
* Formerly the release operation was async, and would be delayed for the
  last release to ensure a client would exist to be notified of any
  failures.  It is now synchronous.
* Formerly the cancel operation would fail on the last client associated
  with a transition.  The cancel operation is now synchronous.
* A helper function is provided to safely synchronously release a
  request regardless of whether it has completed or is in progress,
  satisfying the use case underlying #22974.
* The user-data parameter to asynchronous notification callbacks has
  been removed as user data can be retrieved from the CONTAINER_OF
  the client data.

Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
This commit is contained in:
Peter Bigot 2020-03-17 08:24:05 -05:00 committed by Carles Cufí
commit 14e2ca4f16
4 changed files with 1620 additions and 1608 deletions

View file

@ -33,7 +33,9 @@ The manager has the following properties:
in the off state. The service may also be in a transition to a given in the off state. The service may also be in a transition to a given
state. state.
* The core operations are request (add a dependency) and release (remove * The core operations are request (add a dependency) and release (remove
a dependency). The service manages the state based on calls to a dependency). Supporting operations are reset (to clear an error
state) and cancel (to reclaim client data from an in-progress
transition). The service manages the state based on calls to
functions that initiate these operations. functions that initiate these operations.
* The service transitions from off to on when first client request is * The service transitions from off to on when first client request is
received. received.
@ -41,17 +43,11 @@ The manager has the following properties:
received. received.
* Each service configuration provides functions that implement the * Each service configuration provides functions that implement the
transition from off to on, from on to off, and optionally from an transition from off to on, from on to off, and optionally from an
error state to off. Transitions that may put a calling thread to error state to off. Transitions must be invokable from both thread
sleep must be flagged in the configuration to support detecting unsafe and interrupt context.
invocation from non-thread context. * The request and reset operations are asynchronous using
* All operations are asynchronous, and are initiated by a function call :ref:`async_notification`. Both operations may be cancelled, but
that references a specific service and is given client notification cancellation has no effect on the in-progress transition.
data. The function call will succeed or fail. On success, the
operation is guaranteed to be initiated, but whether the operation
itself succeeds or fails is indicated through client notification.
The initiation functions can be invoked from pre-kernel, thread, or
ISR context. In contexts and states where the operation cannot
be started the function will result in an error.
* Requests to turn on may be queued while a transition to off is in * Requests to turn on may be queued while a transition to off is in
progress: when the service has turned off successfully it will be progress: when the service has turned off successfully it will be
immediately turned on again (where context allows) and waiting clients immediately turned on again (where context allows) and waiting clients
@ -68,21 +64,15 @@ Failures in executing a transition are recorded and inhibit further
requests or releases until the manager is reset. Pending requests are requests or releases until the manager is reset. Pending requests are
notified (and cancelled) when errors are discovered. notified (and cancelled) when errors are discovered.
Transition operation completion notifications are provided through the Transition operation completion notifications are provided through
standard :ref:`async_notification`, supporting these methods: :ref:`async_notification`.
* Signal: A pointer to a :c:type:`struct k_poll_signal` is provided, and Clients and other components interested in tracking all service state
the signal is raised when the transition completes. The operation changes, including when a service begins turning off or enters an error
completion code is stored as the signal value. state, can be informed of state transitions by registering a monitor
* Callback: a function pointer is provided by the client along with an with onoff_monitor_register(). Notification of changes are provided
opaque pointer, and on completion of the operation the function is before issuing completion notifications associated with the new
invoked with the pointer and the operation completion code. state.
* Spin-wait: the client is required to check for operation completion
using the :cpp:func:`onoff_client_fetch_result()` function.
Synchronous transition may be implemented by a caller based on its
context, for example by using :cpp:func:`k_poll()` to wait until the
completion is signalled.
.. doxygengroup:: resource_mgmt_onoff_apis .. doxygengroup:: resource_mgmt_onoff_apis
:project: Zephyr :project: Zephyr

View file

@ -23,49 +23,66 @@ extern "C" {
*/ */
/** /**
* @brief Flag fields used to specify on-off service behavior. * @brief Flag indicating an error state.
*
* Error states are cleared using onoff_reset().
*/ */
enum onoff_manager_flags { #define ONOFF_FLAG_ERROR BIT(0)
/**
* @brief Flag used in struct onoff_manager_transitions.
*
* When provided this indicates the start transition function
* may cause the calling thread to wait. This blocks attempts
* to initiate a transition from a non-thread context.
*/
ONOFF_START_SLEEPS = BIT(0),
/** /** @internal */
* @brief Flag used in struct onoff_manager_transitions. #define ONOFF_FLAG_ONOFF BIT(1)
* /** @internal */
* As with @ref ONOFF_START_SLEEPS but describing the stop #define ONOFF_FLAG_TRANSITION BIT(2)
* transition function.
*/
ONOFF_STOP_SLEEPS = BIT(1),
/** /**
* @brief Flag used in struct onoff_manager_transitions. * @brief Mask used to isolate bits defining the service state.
* *
* As with @ref ONOFF_START_SLEEPS but describing the reset * Mask a value with this then test for ONOFF_FLAG_ERROR to determine
* transition function. * whether the machine has an unfixed error, or compare against
*/ * ONOFF_STATE_ON, ONOFF_STATE_OFF, ONOFF_STATE_TO_ON,
ONOFF_RESET_SLEEPS = BIT(2), * ONOFF_STATE_TO_OFF, or ONOFF_STATE_RESETTING.
*/
#define ONOFF_STATE_MASK (ONOFF_FLAG_ERROR \
| ONOFF_FLAG_ONOFF \
| ONOFF_FLAG_TRANSITION)
/* Internal use. */ /**
ONOFF_HAS_ERROR = BIT(3), * @brief Value exposed by ONOFF_STATE_MASK when service is off.
*/
#define ONOFF_STATE_OFF 0
/* This and higher bits reserved for internal use. */ /**
ONOFF_INTERNAL_BASE = BIT(4), * @brief Value exposed by ONOFF_STATE_MASK when service is on.
}; */
#define ONOFF_STATE_ON ONOFF_FLAG_ONOFF
#define ONOFF_SERVICE_START_SLEEPS __DEPRECATED_MACRO ONOFF_START_SLEEPS /**
#define ONOFF_SERVICE_STOP_SLEEPS __DEPRECATED_MACRO ONOFF_STOP_SLEEPS * @brief Value exposed by ONOFF_STATE_MASK when the service is in an
#define ONOFF_SERVICE_RESET_SLEEPS __DEPRECATED_MACRO ONOFF_RESET_SLEEPS * error state (and not in the process of resetting its state).
#define ONOFF_SERVICE_HAS_ERROR __DEPRECATED_MACRO ONOFF_HAS_ERROR */
#define ONOFF_SERVICE_INTERNAL_BASE __DEPRECATED_MACRO ONOFF_INTERNAL_BASE #define ONOFF_STATE_ERROR ONOFF_FLAG_ERROR
/* Forward declaration */ /**
* @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_manager;
struct onoff_monitor;
/** /**
* @brief Signature used to notify an on-off manager that a transition * @brief Signature used to notify an on-off manager that a transition
@ -88,24 +105,26 @@ typedef void (*onoff_notify_fn)(struct onoff_manager *mgr,
* @brief Signature used by service implementations to effect a * @brief Signature used by service implementations to effect a
* transition. * transition.
* *
* Service definitions use two function pointers of this type to be * Service definitions use two required function pointers of this type
* notified that a transition is required, and a third optional one to * to be notified that a transition is required, and a third optional
* reset service state. * one to reset the service when it is in an error state.
* *
* The start function will be called only from the off state. * The start function will be called only from the off state.
* *
* The stop function will be called only from the on state. * The stop function will be called only from the on state.
* *
* The reset function may be called only when onoff_has_error() * The reset function (where supported) will be called only when
* returns true. * onoff_has_error() returns true.
*
* @note All transitions functions must be isr-ok.
* *
* @param mgr the manager for which transition was requested. * @param mgr the manager for which transition was requested.
* *
* @param notify the function to be invoked when the transition has * @param notify the function to be invoked when the transition has
* completed. The callee shall capture this parameter to notify on * completed. If the transition is synchronous, notify shall be
* completion of asynchronous transitions. If the transition is not * invoked by the implementation before the transition function
* asynchronous, notify shall be invoked before the transition * returns. Otherwise the implementation shall capture this parameter
* function returns. * and invoke it when the transition completes.
*/ */
typedef void (*onoff_transition_fn)(struct onoff_manager *mgr, typedef void (*onoff_transition_fn)(struct onoff_manager *mgr,
onoff_notify_fn notify); onoff_notify_fn notify);
@ -118,11 +137,10 @@ struct onoff_transitions {
/* Function to invoke to transition the service to off. */ /* Function to invoke to transition the service to off. */
onoff_transition_fn stop; onoff_transition_fn stop;
/* Function to force the service state to reset, where supported. */ /* Function to force the service state to reset, where
* supported.
*/
onoff_transition_fn reset; onoff_transition_fn reset;
/* Flags identifying transition function capabilities. */
u8_t flags;
}; };
/** /**
@ -134,19 +152,24 @@ struct onoff_transitions {
* case of error it may be reset through the onoff_reset() API. * case of error it may be reset through the onoff_reset() API.
*/ */
struct onoff_manager { struct onoff_manager {
/* List of clients waiting for completion of reset or /* List of clients waiting for request or reset completion
* transition to on. * notifications.
*/ */
sys_slist_t clients; sys_slist_t clients;
/* List of monitors to be notified of state changes including
* errors and transition completion.
*/
sys_slist_t monitors;
/* Transition functions. */ /* Transition functions. */
const struct onoff_transitions *transitions; const struct onoff_transitions *transitions;
/* Mutex protection for flags, clients, releaser, and refs. */ /* Mutex protection for other fields. */
struct k_spinlock lock; struct k_spinlock lock;
/* Client to be informed when transition to off completes. */ /* The result of the last transition. */
struct onoff_client *releaser; int last_res;
/* Flags identifying the service state. */ /* Flags identifying the service state. */
u16_t flags; u16_t flags;
@ -161,33 +184,20 @@ struct onoff_manager {
* *
* @param _stop a function used to transition from on to off 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 * @param _reset a function used to clear errors and force the service
* state. Can be null. * to an off state. Can be null.
*
* @param _flags any or all of the flags from enum onoff_manager_flags.
*/ */
#define ONOFF_TRANSITIONS_INITIALIZER(_start, _stop, _reset, _flags) { \ #define ONOFF_TRANSITIONS_INITIALIZER(_start, _stop, _reset) { \
.start = _start, \ .start = _start, \
.stop = _stop, \ .stop = _stop, \
.reset = _reset, \ .reset = _reset, \
.flags = _flags, \
} }
#define ONOFF_SERVICE_TRANSITIONS_INITIALIZER(_start, _stop, _reset, _flags) \
__DEPRECATED_MACRO \
ONOFF_TRANSISTIONS_INITIALIZER(_start, _stop, _reset, _flags)
/** @internal */ /** @internal */
#define ONOFF_MANAGER_INITIALIZER(_transitions) { \ #define ONOFF_MANAGER_INITIALIZER(_transitions) { \
.transitions = _transitions, \ .transitions = _transitions, \
.flags = (_transitions)->flags, \
} }
#define ONOFF_SERVICE_INITIALIZER(_transitions) \
__DEPRECATED_MACRO \
ONOFF_MANAGER_INITIALIZER(_transitions)
/** /**
* @brief Initialize an on-off service to off state. * @brief Initialize an on-off service to off state.
* *
@ -195,12 +205,14 @@ struct onoff_manager {
* the infrastructure that provides the service, and before any other * the infrastructure that provides the service, and before any other
* on-off service API is invoked on the service. * on-off service API is invoked on the service.
* *
* This function should never be invoked by clients of an on-off service. * This function should never be invoked by clients of an on-off
* service.
* *
* @param mgr the manager definition object to be initialized. * @param mgr the manager definition object to be initialized.
* *
* @param transitions A structure with transition functions. Structure must be * @param transitions pointer to a structure providing transition
* persistent as it is used by the service. * functions. The referenced object must persist as long as the
* manager can be referenced.
* *
* @retval 0 on success * @retval 0 on success
* @retval -EINVAL if start, stop, or flags are invalid * @retval -EINVAL if start, stop, or flags are invalid
@ -224,226 +236,65 @@ struct onoff_client;
* @param cli the client structure passed to the function that * @param cli the client structure passed to the function that
* initiated the operation. * initiated the operation.
* *
* @param user_data user data provided when the client structure was * @param state the state of the machine at the time of completion,
* initialized with onoff_client_init_callback(). * 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 * @param res the result of the operation. Expected values are
* service-specific, but the value shall be non-negative if the * service-specific, but the value shall be non-negative if the
* operation succeeded, and negative if the operation failed. * 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, typedef void (*onoff_client_callback)(struct onoff_manager *mgr,
struct onoff_client *cli, struct onoff_client *cli,
void *user_data, u32_t state,
int res); int res);
/** /**
* @brief State associated with a client of an on-off service. * @brief State associated with a client of an on-off service.
* *
* Objects of this type are allocated by a client, which must use an * Objects of this type are allocated by a client, which is
* initialization function (e.g. onoff_client_init_signal()) to * responsible for zero-initializing the node field and invoking the
* configure them. * approprite sys_notify init function to configure notification.
* *
* Control of the object content transfers to the service provider * Control of the object content transfers to the service provider
* when a pointer to the object is passed to any on-off service * when a pointer to the object is passed to any on-off manager
* function. While the service provider controls the object the * function. While the service provider controls the object the
* client must not change any object fields. Control reverts to the * client must not change any object fields. Control reverts to the
* client concurrent with release of the owned sys_notify structure. * 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 state object must be * After control has reverted to the client the notify field must be
* reinitialized for the next operation. * reinitialized for the next operation.
*
* The content of this structure is not public API: all configuration
* and inspection should be done with functions like
* onoff_client_init_callback() and onoff_client_fetch_result().
*/ */
struct onoff_client { struct onoff_client {
/* Links the client into the set of waiting service users. */ /** @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; sys_snode_t node;
/* Notification configuration. */ /** @brief Notification configuration. */
struct sys_notify notify; struct sys_notify notify;
/* User data for callback-based notification. */
void *user_data;
}; };
/** /**
* @brief Check for and read the result of an asynchronous operation. * @brief Identify region of sys_notify flags available for
* containing services.
* *
* @param op pointer to the object used to specify asynchronous * Bits of the flags field of the sys_notify structure contained
* function behavior and store completion information. * within the queued_operation structure at and above this position
* may be used by extensions to the onoff_client structure.
* *
* @param result pointer to storage for the result of the operation. * These bits are intended for use by containing service
* The result is stored only if the operation has completed. * implementations to record client-specific information and are
* * subject to other conditions of use specified on the sys_notify API.
* @retval 0 if the operation has completed.
* @retval -EAGAIN if the operation has not completed.
*/ */
static inline int onoff_client_fetch_result(const struct onoff_client *op, #define ONOFF_CLIENT_EXTENSION_POS SYS_NOTIFY_EXTENSION_POS
int *result)
{
__ASSERT_NO_MSG(op != NULL);
return sys_notify_fetch_result(&op->notify, result);
}
/**
* @brief Initialize an on-off client to be used for a spin-wait
* operation notification.
*
* Clients that use this initialization receive no asynchronous
* notification, and instead must periodically check for completion
* using onoff_client_fetch_result().
*
* On completion of the operation the client object must be
* reinitialized before it can be re-used.
*
* @param cli pointer to the client state object.
*/
static inline void onoff_client_init_spinwait(struct onoff_client *cli)
{
__ASSERT_NO_MSG(cli != NULL);
*cli = (struct onoff_client){};
sys_notify_init_spinwait(&cli->notify);
}
/**
* @brief Initialize an on-off client to be used for a signal
* operation notification.
*
* Clients that use this initialization will be notified of the
* completion of operations submitted through onoff_request() and
* onoff_release() through the provided signal.
*
* On completion of the operation the client object must be
* reinitialized before it can be re-used.
*
* @note
* @rst
* This capability is available only when :option:`CONFIG_POLL` is
* selected.
* @endrst
*
* @param cli pointer to the client state 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 onoff_client_init_signal(struct onoff_client *cli,
struct k_poll_signal *sigp)
{
__ASSERT_NO_MSG(cli != NULL);
*cli = (struct onoff_client){};
sys_notify_init_signal(&cli->notify, sigp);
}
/**
* @brief Initialize an on-off client to be used for a callback
* operation notification.
*
* Clients that use this initialization will be notified of the
* completion of operations submitted through on-off service API
* through the provided callback. Note that callbacks may be invoked
* from various contexts depending on the specific service; see
* @ref onoff_client_callback.
*
* On completion of the operation the client object must be
* reinitialized before it can be re-used.
*
* @param cli pointer to the client state object.
*
* @param handler a function pointer to use for notification.
*
* @param user_data an opaque pointer passed to the handler to provide
* additional context.
*/
static inline void onoff_client_init_callback(struct onoff_client *cli,
onoff_client_callback handler,
void *user_data)
{
__ASSERT_NO_MSG(cli != NULL);
__ASSERT_NO_MSG(handler != NULL);
*cli = (struct onoff_client){
.user_data = user_data,
};
sys_notify_init_callback(&cli->notify, handler);
}
/**
* @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.
*
* As a specific example: A call to this function may succeed at a
* point while the service is still transitioning to off due to a
* previous call to onoff_release(). When the transition completes
* the service would normally start a transition to on. However, if
* the transition to off completed in a non-thread context, and the
* transition to on can sleep, the transition cannot be started and
* the request will fail with `-EWOULDBLOCK`.
*
* @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 on successful (initiation of) request
* @retval -EIO if service has recorded an an error
* @retval -EINVAL if the parameters are invalid
* @retval -EAGAIN if the reference count would overflow
* @retval -EWOULDBLOCK if the function was invoked from non-thread
* context and successful initiation could result in an attempt to
* make the calling thread sleep.
*/
int onoff_request(struct onoff_manager *mgr,
struct onoff_client *cli);
/**
* @brief Release a reserved use of an on-off service.
*
* The return value indicates the success or failure of an attempt to
* initiate an operation to release the resource. If initiation of
* the operation succeeds the result of the release operation itself
* 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 release 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 how to notify the client when release completes.
* Behavior is undefined if cli references an object associated with
* an incomplete service operation.
*
* @retval Non-negative on successful (initiation of) release
* @retval -EINVAL if the parameters are invalid
* @retval -EIO if service has recorded an an error
* @retval -EWOULDBLOCK if a non-blocking request was made and
* could not be satisfied without potentially blocking.
* @retval -EALREADY if the service is already off or transitioning
* to off
* @retval -EBUSY if the service is transitioning to on
*/
int onoff_release(struct onoff_manager *mgr,
struct onoff_client *cli);
/** /**
* @brief Test whether an on-off service has recorded an error. * @brief Test whether an on-off service has recorded an error.
@ -460,7 +311,133 @@ int onoff_release(struct onoff_manager *mgr,
*/ */
static inline bool onoff_has_error(const struct onoff_manager *mgr) static inline bool onoff_has_error(const struct onoff_manager *mgr)
{ {
return (mgr->flags & ONOFF_HAS_ERROR) != 0; 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;
} }
/** /**
@ -492,7 +469,8 @@ static inline bool onoff_has_error(const struct onoff_manager *mgr)
* if cli references an object associated with an incomplete service * if cli references an object associated with an incomplete service
* operation. * operation.
* *
* @retval 0 on success * @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 -ENOTSUP if reset is not supported by the service.
* @retval -EINVAL if the parameters are invalid. * @retval -EINVAL if the parameters are invalid.
* @retval -EALREADY if the service does not have a recorded error. * @retval -EALREADY if the service does not have a recorded error.
@ -501,49 +479,86 @@ int onoff_reset(struct onoff_manager *mgr,
struct onoff_client *cli); struct onoff_client *cli);
/** /**
* @brief Attempt to cancel an in-progress client operation. * @brief Signature used to notify a monitor of an onoff service of
* errors or completion of a state transition.
* *
* It may be that a client has initiated an operation but needs to * This is similar to onoff_client_callback but provides information
* shut down before the operation has completed. For example, when a * about all transitions, not just ones associated with a specific
* request was made and the need is no longer present. * client. Monitor callbacks are invoked before any completion
* notifications associated with the state change are made.
* *
* There is limited support for cancelling an in-progress operation: * These functions may be invoked from any context including
* * If a start or reset is in progress, all but one clients * pre-kernel, ISR, or cooperative or pre-emptible threads.
* requesting the start can cancel their request. * Compatible functions must be isr-callable and non-suspendable.
* * If a stop is in progress, all clients requesting a restart can
* cancel their request;
* * A client requesting a release cannot cancel the release.
* *
* Be aware that any transition that was initiated on behalf of the * The callback is permitted to unregister itself from the manager,
* client will continue to progress to completion. The restricted * but must not register or unregister any other monitors.
* support for cancellation ensures that for any in-progress
* transition there will always be at least one client that will be
* notified when the operation completes.
* *
* If the cancellation fails the service retains control of the client * @param mgr the manager for which a transition has completed.
* object, and the client must wait for operation completion.
* *
* @param mgr the manager for which an operation is to be cancelled. * @param mon the monitor instance through which this notification
* arrived.
* *
* @param cli a pointer to the same client state that was provided * @param state the state of the machine at the time of completion,
* when the operation to be cancelled was issued. If the cancellation * restricted by ONOFF_STATE_MASK. All valid states may be observed.
* is successful the client will be notified of operation completion
* with a result of `-ECANCELED`.
* *
* @retval 0 if the cancellation was completed before the client could * @param res the result of the operation. Expected values are
* be notified. The client will be notified through cli with an * service- and state-specific, but the value shall be non-negative if
* operation completion of `-ECANCELED`. * the operation succeeded, and negative if the operation failed.
* @retval -EINVAL if the parameters are invalid.
* @retval -EWOULDBLOCK if cancellation was rejected because the
* client is the only waiter for an in-progress transition. The
* service retains control of the client structure.
* @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, typedef void (*onoff_monitor_callback)(struct onoff_manager *mgr,
struct onoff_client *cli); struct onoff_monitor *mon,
u32_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);
/** @} */ /** @} */

View file

@ -7,27 +7,116 @@
#include <kernel.h> #include <kernel.h>
#include <sys/onoff.h> #include <sys/onoff.h>
#include <stdio.h>
#define SERVICE_CONFIG_FLAGS \
(ONOFF_START_SLEEPS \
| ONOFF_STOP_SLEEPS \
| ONOFF_RESET_SLEEPS)
#define SERVICE_REFS_MAX UINT16_MAX #define SERVICE_REFS_MAX UINT16_MAX
#define ST_OFF 0 /* Confirm consistency of public flags with private flags */
#define ST_ON ONOFF_INTERNAL_BASE BUILD_ASSERT((ONOFF_FLAG_ERROR | ONOFF_FLAG_ONOFF | ONOFF_FLAG_TRANSITION)
#define ST_TRANSITION (ONOFF_INTERNAL_BASE << 1) < BIT(3));
#define ST_TO_ON (ST_TRANSITION | ST_ON)
#define ST_TO_OFF (ST_TRANSITION | ST_OFF)
#define ST_MASK (ST_ON | ST_TRANSITION) #define ONOFF_FLAG_PROCESSING BIT(3)
#define ONOFF_FLAG_COMPLETE BIT(4)
#define ONOFF_FLAG_RECHECK BIT(5)
static void set_service_state(struct onoff_manager *mgr, /* These symbols in the ONOFF_FLAGS namespace identify bits in
u32_t state) * onoff_manager::flags that indicate the state of the machine. The
* bits are manipulated by process_event() under lock, and actions
* cued by bit values are executed outside of lock within
* process_event().
*
* * ERROR indicates that the machine is in an error state. When
* this bit is set ONOFF will be cleared.
* * ONOFF indicates whether the target/current state is off (clear)
* or on (set).
* * TRANSITION indicates whether a service transition function is in
* progress. It combines with ONOFF to identify start and stop
* transitions, and with ERROR to identify a reset transition.
* * PROCESSING indicates that the process_event() loop is active. It
* is used to defer initiation of transitions and other complex
* state changes while invoking notifications associated with a
* state transition. This bounds the depth by limiting
* active process_event() call stacks to two instances. State changes
* initiated by a nested call will be executed when control returns
* to the parent call.
* * COMPLETE indicates that a transition completion notification has
* been received. This flag is set in the notification, and cleared
* by process_events() which is invoked from the notification. In
* the case of nested process_events() the processing is deferred to
* the top invocation.
* * RECHECK indicates that a state transition has completed but
* process_events() must re-check the overall state to confirm no
* additional transitions are required. This is used to simplfy the
* logic when, for example, a request is received during a
* transition to off, which means that when the transition completes
* a transition to on must be initiated if the request is still
* present. Transition to ON with no remaining requests similarly
* triggers a recheck.
*/
/* Identify the events that can trigger state changes, as well as an
* internal state used when processing deferred actions.
*/
enum event_type {
/* No-op event: used to process deferred changes.
*
* This event is local to the process loop.
*/
EVT_NOP,
/* Completion of a service transition.
*
* This event is triggered by the transition notify callback.
* It can be received only when the machine is in a transition
* state (TO-ON, TO-OFF, or RESETTING).
*/
EVT_COMPLETE,
/* Reassess whether a transition from a stable state is needed.
*
* This event causes:
* * a start from OFF when there are clients;
* * a stop from ON when there are no clients;
* * a reset from ERROR when there are clients.
*
* The client list can change while the manager lock is
* released (e.g. during client and monitor notifications and
* transition initiations), so this event records the
* potential for these state changes, and process_event() ...
*
*/
EVT_RECHECK,
/* Transition to on.
*
* This is synthesized from EVT_RECHECK in a non-nested
* process_event() when state OFF is confirmed with a
* non-empty client (request) list.
*/
EVT_START,
/* Transition to off.
*
* This is synthesized from EVT_RECHECK in a non-nested
* process_event() when state ON is confirmed with a
* zero reference count.
*/
EVT_STOP,
/* Transition to resetting.
*
* This is synthesized from EVT_RECHECK in a non-nested
* process_event() when state ERROR is confirmed with a
* non-empty client (reset) list.
*/
EVT_RESET,
};
static void set_state(struct onoff_manager *mgr,
u32_t state)
{ {
mgr->flags &= ~ST_MASK; mgr->flags = (state & ONOFF_STATE_MASK)
mgr->flags |= (state & ST_MASK); | (mgr->flags & ~ONOFF_STATE_MASK);
} }
static int validate_args(const struct onoff_manager *mgr, static int validate_args(const struct onoff_manager *mgr,
@ -40,7 +129,8 @@ static int validate_args(const struct onoff_manager *mgr,
int rv = sys_notify_validate(&cli->notify); int rv = sys_notify_validate(&cli->notify);
if ((rv == 0) if ((rv == 0)
&& ((cli->notify.flags & SYS_NOTIFY_EXTENSION_MASK) != 0)) { && ((cli->notify.flags
& ~BIT_MASK(ONOFF_CLIENT_EXTENSION_POS)) != 0)) {
rv = -EINVAL; rv = -EINVAL;
} }
@ -50,11 +140,10 @@ static int validate_args(const struct onoff_manager *mgr,
int onoff_manager_init(struct onoff_manager *mgr, int onoff_manager_init(struct onoff_manager *mgr,
const struct onoff_transitions *transitions) const struct onoff_transitions *transitions)
{ {
if (transitions->flags & ~SERVICE_CONFIG_FLAGS) { if ((mgr == NULL)
return -EINVAL; || (transitions == NULL)
} || (transitions->start == NULL)
|| (transitions->stop == NULL)) {
if ((transitions->start == NULL) || (transitions->stop == NULL)) {
return -EINVAL; return -EINVAL;
} }
@ -63,21 +152,35 @@ int onoff_manager_init(struct onoff_manager *mgr,
return 0; return 0;
} }
static void notify_monitors(struct onoff_manager *mgr,
u32_t state,
int res)
{
sys_slist_t *mlist = &mgr->monitors;
struct onoff_monitor *mon;
struct onoff_monitor *tmp;
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(mlist, mon, tmp, node) {
mon->callback(mgr, mon, state, res);
}
}
static void notify_one(struct onoff_manager *mgr, static void notify_one(struct onoff_manager *mgr,
struct onoff_client *cli, struct onoff_client *cli,
u32_t state,
int res) int res)
{ {
void *ud = cli->user_data;
onoff_client_callback cb = onoff_client_callback cb =
(onoff_client_callback)sys_notify_finalize(&cli->notify, res); (onoff_client_callback)sys_notify_finalize(&cli->notify, res);
if (cb) { if (cb) {
cb(mgr, cli, ud, res); cb(mgr, cli, state, res);
} }
} }
static void notify_all(struct onoff_manager *mgr, static void notify_all(struct onoff_manager *mgr,
sys_slist_t *list, sys_slist_t *list,
u32_t state,
int res) int res)
{ {
while (!sys_slist_is_empty(list)) { while (!sys_slist_is_empty(list)) {
@ -87,65 +190,236 @@ static void notify_all(struct onoff_manager *mgr,
struct onoff_client, struct onoff_client,
node); node);
notify_one(mgr, cli, res); notify_one(mgr, cli, state, res);
} }
} }
static void onoff_start_notify(struct onoff_manager *mgr, static void process_event(struct onoff_manager *mgr,
int res) int evt,
k_spinlock_key_t key);
static void transition_complete(struct onoff_manager *mgr,
int res)
{ {
k_spinlock_key_t key = k_spin_lock(&mgr->lock); k_spinlock_key_t key = k_spin_lock(&mgr->lock);
sys_slist_t clients = mgr->clients;
/* Can't have a queued releaser during start */ mgr->last_res = res;
__ASSERT_NO_MSG(mgr->releaser == NULL); process_event(mgr, EVT_COMPLETE, key);
}
/* If the start failed log an error and leave the rest of the /* Detect whether static state requires a transition. */
* state in place for diagnostics. static int process_recheck(struct onoff_manager *mgr)
* {
* If the start succeeded record a reference for all clients int evt = EVT_NOP;
* and set the state to ON. There must be at least one client u32_t state = mgr->flags & ONOFF_STATE_MASK;
* left to receive the result.
*
* In either case reset the client queue and notify all
* clients of operation completion.
*/
if (res < 0) {
mgr->flags &= ~ST_TRANSITION;
mgr->flags |= ONOFF_HAS_ERROR;
} else {
sys_snode_t *node;
unsigned int refs = 0U;
set_service_state(mgr, ST_ON); if ((state == ONOFF_STATE_OFF)
&& !sys_slist_is_empty(&mgr->clients)) {
SYS_SLIST_FOR_EACH_NODE(&clients, node) { evt = EVT_START;
refs += 1U; } else if ((state == ONOFF_STATE_ON)
} && (mgr->refs == 0)) {
evt = EVT_STOP;
/* Update the reference count, or fail if the count } else if ((state == ONOFF_STATE_ERROR)
* would overflow. && !sys_slist_is_empty(&mgr->clients)) {
*/ evt = EVT_RESET;
if (mgr->refs > (SERVICE_REFS_MAX - refs)) {
mgr->flags |= ONOFF_HAS_ERROR;
} else {
mgr->refs += refs;
}
__ASSERT_NO_MSG(mgr->refs > 0U);
} }
sys_slist_init(&mgr->clients); return evt;
}
/* Process a transition completion.
*
* If the completion requires notifying clients, the clients are moved
* from the manager to the output list for notification.
*/
static void process_complete(struct onoff_manager *mgr,
sys_slist_t *clients,
int res)
{
u32_t state = mgr->flags & ONOFF_STATE_MASK;
if (res < 0) {
/* Enter ERROR state and notify all clients. */
*clients = mgr->clients;
sys_slist_init(&mgr->clients);
set_state(mgr, ONOFF_STATE_ERROR);
} else if ((state == ONOFF_STATE_TO_ON)
|| (state == ONOFF_STATE_RESETTING)) {
*clients = mgr->clients;
sys_slist_init(&mgr->clients);
if (state == ONOFF_STATE_TO_ON) {
struct onoff_client *cp;
/* Increment reference count for all remaining
* clients and enter ON state.
*/
SYS_SLIST_FOR_EACH_CONTAINER(clients, cp, node) {
mgr->refs += 1U;
}
set_state(mgr, ONOFF_STATE_ON);
} else {
__ASSERT_NO_MSG(state == ONOFF_STATE_RESETTING);
set_state(mgr, ONOFF_STATE_OFF);
}
if (process_recheck(mgr) != EVT_NOP) {
mgr->flags |= ONOFF_FLAG_RECHECK;
}
} else if (state == ONOFF_STATE_TO_OFF) {
/* Any active clients are requests waiting for this
* transition to complete. Queue a RECHECK event to
* ensure we don't miss them if we don't unlock to
* tell anybody about the completion.
*/
set_state(mgr, ONOFF_STATE_OFF);
if (process_recheck(mgr) != EVT_NOP) {
mgr->flags |= ONOFF_FLAG_RECHECK;
}
} else {
__ASSERT_NO_MSG(false);
}
}
/* There are two points in the state machine where the machine is
* unlocked to perform some external action:
* * Initiation of an transition due to some event;
* * Invocation of the user-specified callback when a stable state is
* reached or an error detected.
*
* Events received during these unlocked periods are recorded in the
* state, but processing is deferred to the top-level invocation which
* will loop to handle any events that occurred during the unlocked
* regions.
*/
static void process_event(struct onoff_manager *mgr,
int evt,
k_spinlock_key_t key)
{
sys_slist_t clients;
u32_t state = mgr->flags & ONOFF_STATE_MASK;
int res = 0;
bool processing = ((mgr->flags & ONOFF_FLAG_PROCESSING) != 0);
__ASSERT_NO_MSG(evt != EVT_NOP);
/* If this is a nested call record the event for processing in
* the top invocation.
*/
if (processing) {
if (evt == EVT_COMPLETE) {
mgr->flags |= ONOFF_FLAG_COMPLETE;
} else {
__ASSERT_NO_MSG(evt == EVT_RECHECK);
mgr->flags |= ONOFF_FLAG_RECHECK;
}
goto out;
}
sys_slist_init(&clients);
do {
onoff_transition_fn transit = NULL;
if (evt == EVT_RECHECK) {
evt = process_recheck(mgr);
}
if (evt == EVT_NOP) {
break;
}
res = 0;
if (evt == EVT_COMPLETE) {
res = mgr->last_res;
process_complete(mgr, &clients, res);
/* NB: This can trigger a RECHECK */
} else if (evt == EVT_START) {
__ASSERT_NO_MSG(state == ONOFF_STATE_OFF);
__ASSERT_NO_MSG(!sys_slist_is_empty(&mgr->clients));
transit = mgr->transitions->start;
__ASSERT_NO_MSG(transit != NULL);
set_state(mgr, ONOFF_STATE_TO_ON);
} else if (evt == EVT_STOP) {
__ASSERT_NO_MSG(state == ONOFF_STATE_ON);
__ASSERT_NO_MSG(mgr->refs == 0);
transit = mgr->transitions->stop;
__ASSERT_NO_MSG(transit != NULL);
set_state(mgr, ONOFF_STATE_TO_OFF);
} else if (evt == EVT_RESET) {
__ASSERT_NO_MSG(state == ONOFF_STATE_ERROR);
__ASSERT_NO_MSG(!sys_slist_is_empty(&mgr->clients));
transit = mgr->transitions->reset;
__ASSERT_NO_MSG(transit != NULL);
set_state(mgr, ONOFF_STATE_RESETTING);
} else {
__ASSERT_NO_MSG(false);
}
/* Have to unlock and do something if any of:
* * We changed state and there are monitors;
* * We completed a transition and there are clients to notify;
* * We need to initiate a transition.
*/
bool do_monitors = (state != (mgr->flags & ONOFF_STATE_MASK))
&& !sys_slist_is_empty(&mgr->monitors);
evt = EVT_NOP;
if (do_monitors
|| !sys_slist_is_empty(&clients)
|| (transit != NULL)) {
u32_t flags = mgr->flags | ONOFF_FLAG_PROCESSING;
mgr->flags = flags;
state = flags & ONOFF_STATE_MASK;
k_spin_unlock(&mgr->lock, key);
if (do_monitors) {
notify_monitors(mgr, state, res);
}
if (!sys_slist_is_empty(&clients)) {
notify_all(mgr, &clients, state, res);
}
if (transit != NULL) {
transit(mgr, transition_complete);
}
key = k_spin_lock(&mgr->lock);
mgr->flags &= ~ONOFF_FLAG_PROCESSING;
state = mgr->flags & ONOFF_STATE_MASK;
}
/* Process deferred events. Completion takes priority
* over recheck.
*/
if ((mgr->flags & ONOFF_FLAG_COMPLETE) != 0) {
mgr->flags &= ~ONOFF_FLAG_COMPLETE;
evt = EVT_COMPLETE;
} else if ((mgr->flags & ONOFF_FLAG_RECHECK) != 0) {
mgr->flags &= ~ONOFF_FLAG_RECHECK;
evt = EVT_RECHECK;
}
state = mgr->flags & ONOFF_STATE_MASK;
} while (evt != EVT_NOP);
out:
k_spin_unlock(&mgr->lock, key); k_spin_unlock(&mgr->lock, key);
notify_all(mgr, &clients, res);
} }
int onoff_request(struct onoff_manager *mgr, int onoff_request(struct onoff_manager *mgr,
struct onoff_client *cli) struct onoff_client *cli)
{ {
bool add_client = false; /* add client to pending list */ bool add_client = false; /* add client to pending list */
bool start = false; /* invoke start transition */ bool start = false; /* trigger a start transition */
bool notify = false; /* do client notification */ bool notify = false; /* do client notification */
int rv = validate_args(mgr, cli); int rv = validate_args(mgr, cli);
@ -154,11 +428,7 @@ int onoff_request(struct onoff_manager *mgr,
} }
k_spinlock_key_t key = k_spin_lock(&mgr->lock); k_spinlock_key_t key = k_spin_lock(&mgr->lock);
u32_t state = mgr->flags & ONOFF_STATE_MASK;
if ((mgr->flags & ONOFF_HAS_ERROR) != 0) {
rv = -EIO;
goto out;
}
/* Reject if this would overflow the reference count. */ /* Reject if this would overflow the reference count. */
if (mgr->refs == SERVICE_REFS_MAX) { if (mgr->refs == SERVICE_REFS_MAX) {
@ -166,268 +436,104 @@ int onoff_request(struct onoff_manager *mgr,
goto out; goto out;
} }
u32_t state = mgr->flags & ST_MASK; rv = state;
if (state == ONOFF_STATE_ON) {
switch (state) { /* Increment reference count, notify in exit */
case ST_TO_OFF:
/* Queue to start after release */
__ASSERT_NO_MSG(mgr->releaser != NULL);
add_client = true;
rv = 3;
break;
case ST_OFF:
/* Reject if in a non-thread context and start could
* wait.
*/
if ((k_is_in_isr() || k_is_pre_kernel())
&& ((mgr->flags & ONOFF_START_SLEEPS) != 0U)) {
rv = -EWOULDBLOCK;
break;
}
/* Start with first request while off */
__ASSERT_NO_MSG(mgr->refs == 0);
set_service_state(mgr, ST_TO_ON);
start = true;
add_client = true;
rv = 2;
break;
case ST_TO_ON:
/* Already starting, just queue it */
add_client = true;
rv = 1;
break;
case ST_ON:
/* Just increment the reference count */
notify = true; notify = true;
break; mgr->refs += 1U;
default: } else if ((state == ONOFF_STATE_OFF)
rv = -EINVAL; || (state == ONOFF_STATE_TO_OFF)
break; || (state == ONOFF_STATE_TO_ON)) {
/* Start if OFF, queue client */
start = (state == ONOFF_STATE_OFF);
add_client = true;
} else if (state == ONOFF_STATE_RESETTING) {
rv = -ENOTSUP;
} else {
__ASSERT_NO_MSG(state == ONOFF_STATE_ERROR);
rv = -EIO;
} }
out: out:
if (add_client) { if (add_client) {
sys_slist_append(&mgr->clients, &cli->node); sys_slist_append(&mgr->clients, &cli->node);
} else if (notify) {
mgr->refs += 1;
} }
k_spin_unlock(&mgr->lock, key);
if (start) { if (start) {
__ASSERT_NO_MSG(mgr->transitions->start != NULL); process_event(mgr, EVT_RECHECK, key);
mgr->transitions->start(mgr, onoff_start_notify); } else {
} else if (notify) { k_spin_unlock(&mgr->lock, key);
notify_one(mgr, cli, 0);
if (notify) {
notify_one(mgr, cli, state, 0);
}
} }
return rv; return rv;
} }
static void onoff_stop_notify(struct onoff_manager *mgr, int onoff_release(struct onoff_manager *mgr)
int res)
{ {
bool notify_clients = false; bool stop = false; /* trigger a stop transition */
int client_res = res;
bool start = false;
k_spinlock_key_t key = k_spin_lock(&mgr->lock);
sys_slist_t clients = mgr->clients;
struct onoff_client *releaser = mgr->releaser;
/* If the stop operation failed log an error and leave the
* rest of the state in place.
*
* If it succeeded remove the last reference and transition to
* off.
*
* In either case remove the last reference, and notify all
* waiting clients of operation completion.
*/
if (res < 0) {
mgr->flags &= ~ST_TRANSITION;
mgr->flags |= ONOFF_HAS_ERROR;
notify_clients = true;
} else if (sys_slist_is_empty(&clients)) {
set_service_state(mgr, ST_OFF);
} else if ((k_is_in_isr() || k_is_pre_kernel())
&& ((mgr->flags & ONOFF_START_SLEEPS) != 0U)) {
set_service_state(mgr, ST_OFF);
notify_clients = true;
client_res = -EWOULDBLOCK;
} else {
set_service_state(mgr, ST_TO_ON);
start = true;
}
__ASSERT_NO_MSG(releaser);
mgr->refs -= 1U;
mgr->releaser = NULL;
__ASSERT_NO_MSG(mgr->refs == 0);
/* Remove the clients if there was an error or a delayed start
* couldn't be initiated, because we're resolving their
* operation with an error.
*/
if (notify_clients) {
sys_slist_init(&mgr->clients);
}
k_spin_unlock(&mgr->lock, key);
/* Notify the releaser. If there was an error, notify any
* pending requests; otherwise if there are pending requests
* start the transition to ON.
*/
notify_one(mgr, releaser, res);
if (notify_clients) {
notify_all(mgr, &clients, client_res);
} else if (start) {
mgr->transitions->start(mgr, onoff_start_notify);
}
}
int onoff_release(struct onoff_manager *mgr,
struct onoff_client *cli)
{
bool stop = false; /* invoke stop transition */
bool notify = false; /* do client notification */
int rv = validate_args(mgr, cli);
if (rv < 0) {
return rv;
}
k_spinlock_key_t key = k_spin_lock(&mgr->lock); k_spinlock_key_t key = k_spin_lock(&mgr->lock);
u32_t state = mgr->flags & ONOFF_STATE_MASK;
int rv = state;
if ((mgr->flags & ONOFF_HAS_ERROR) != 0) { if (state != ONOFF_STATE_ON) {
rv = -EIO; if (state == ONOFF_STATE_ERROR) {
rv = -EIO;
} else {
rv = -ENOTSUP;
}
goto out; goto out;
} }
u32_t state = mgr->flags & ST_MASK; __ASSERT_NO_MSG(mgr->refs > 0);
mgr->refs -= 1U;
switch (state) { stop = (mgr->refs == 0);
case ST_ON:
/* Stay on if release leaves a client. */
if (mgr->refs > 1U) {
notify = true;
rv = 1;
break;
}
/* Reject if in non-thread context but stop could
* wait
*/
if ((k_is_in_isr() || k_is_pre_kernel())
&& ((mgr->flags & ONOFF_STOP_SLEEPS) != 0)) {
rv = -EWOULDBLOCK;
break;
}
stop = true;
set_service_state(mgr, ST_TO_OFF);
mgr->releaser = cli;
rv = 2;
break;
case ST_TO_ON:
rv = -EBUSY;
break;
case ST_OFF:
case ST_TO_OFF:
rv = -EALREADY;
break;
default:
rv = -EINVAL;
}
out: out:
if (notify) {
mgr->refs -= 1U;
}
k_spin_unlock(&mgr->lock, key);
if (stop) { if (stop) {
__ASSERT_NO_MSG(mgr->transitions->stop != NULL); process_event(mgr, EVT_RECHECK, key);
mgr->transitions->stop(mgr, onoff_stop_notify); } else {
} else if (notify) { k_spin_unlock(&mgr->lock, key);
notify_one(mgr, cli, 0);
} }
return rv; return rv;
} }
static void onoff_reset_notify(struct onoff_manager *mgr,
int res)
{
k_spinlock_key_t key = k_spin_lock(&mgr->lock);
sys_slist_t clients = mgr->clients;
/* If the reset failed clear the transition flag but otherwise
* leave the state unchanged.
*
* If it was successful clear the reference count and all
* flags except capability flags (sets to ST_OFF).
*/
if (res < 0) {
mgr->flags &= ~ST_TRANSITION;
} else {
__ASSERT_NO_MSG(mgr->refs == 0U);
mgr->refs = 0U;
mgr->flags &= SERVICE_CONFIG_FLAGS;
}
sys_slist_init(&mgr->clients);
k_spin_unlock(&mgr->lock, key);
notify_all(mgr, &clients, res);
}
int onoff_reset(struct onoff_manager *mgr, int onoff_reset(struct onoff_manager *mgr,
struct onoff_client *cli) struct onoff_client *cli)
{ {
if (mgr->transitions->reset == NULL) {
return -ENOTSUP;
}
bool reset = false; bool reset = false;
int rv = validate_args(mgr, cli); int rv = validate_args(mgr, cli);
if ((rv >= 0)
&& (mgr->transitions->reset == NULL)) {
rv = -ENOTSUP;
}
if (rv < 0) { if (rv < 0) {
return rv; return rv;
} }
/* Reject if in a non-thread context and reset could wait. */
if ((k_is_in_isr() || k_is_pre_kernel())
&& ((mgr->flags & ONOFF_RESET_SLEEPS) != 0U)) {
return -EWOULDBLOCK;
}
k_spinlock_key_t key = k_spin_lock(&mgr->lock); k_spinlock_key_t key = k_spin_lock(&mgr->lock);
u32_t state = mgr->flags & ONOFF_STATE_MASK;
if ((mgr->flags & ONOFF_HAS_ERROR) == 0) { rv = state;
if ((state & ONOFF_FLAG_ERROR) == 0) {
rv = -EALREADY; rv = -EALREADY;
goto out; } else {
} reset = (state != ONOFF_STATE_RESETTING);
if ((mgr->flags & ST_TRANSITION) == 0) {
reset = true;
mgr->flags |= ST_TRANSITION;
}
out:
if (rv >= 0) {
sys_slist_append(&mgr->clients, &cli->node); sys_slist_append(&mgr->clients, &cli->node);
} }
k_spin_unlock(&mgr->lock, key);
if (reset) { if (reset) {
mgr->transitions->reset(mgr, onoff_reset_notify); process_event(mgr, EVT_RECHECK, key);
} else {
k_spin_unlock(&mgr->lock, key);
} }
return rv; return rv;
@ -436,38 +542,61 @@ out:
int onoff_cancel(struct onoff_manager *mgr, int onoff_cancel(struct onoff_manager *mgr,
struct onoff_client *cli) struct onoff_client *cli)
{ {
int rv = validate_args(mgr, cli); if ((mgr == NULL) || (cli == NULL)) {
return -EINVAL;
if (rv < 0) {
return rv;
} }
rv = -EALREADY; int rv = -EALREADY;
k_spinlock_key_t key = k_spin_lock(&mgr->lock); k_spinlock_key_t key = k_spin_lock(&mgr->lock);
u32_t state = mgr->flags & ST_MASK; u32_t state = mgr->flags & ONOFF_STATE_MASK;
/* Can't remove the last client waiting for the in-progress
* transition, as there would be nobody to receive the
* completion notification, which might indicate a service
* error.
*/
if (sys_slist_find_and_remove(&mgr->clients, &cli->node)) { if (sys_slist_find_and_remove(&mgr->clients, &cli->node)) {
rv = 0; __ASSERT_NO_MSG((state == ONOFF_STATE_TO_ON)
if (sys_slist_is_empty(&mgr->clients) || (state == ONOFF_STATE_TO_OFF)
&& (state != ST_TO_OFF)) { || (state == ONOFF_STATE_RESETTING));
rv = -EWOULDBLOCK; rv = state;
sys_slist_append(&mgr->clients, &cli->node);
}
} else if (mgr->releaser == cli) {
/* must be waiting for TO_OFF to complete */
rv = -EWOULDBLOCK;
} }
k_spin_unlock(&mgr->lock, key); k_spin_unlock(&mgr->lock, key);
if (rv == 0) { return rv;
notify_one(mgr, cli, -ECANCELED); }
int onoff_monitor_register(struct onoff_manager *mgr,
struct onoff_monitor *mon)
{
if ((mgr == NULL)
|| (mon == NULL)
|| (mon->callback == NULL)) {
return -EINVAL;
} }
k_spinlock_key_t key = k_spin_lock(&mgr->lock);
sys_slist_append(&mgr->monitors, &mon->node);
k_spin_unlock(&mgr->lock, key);
return 0;
}
int onoff_monitor_unregister(struct onoff_manager *mgr,
struct onoff_monitor *mon)
{
int rv = -EINVAL;
if ((mgr == NULL)
|| (mon == NULL)) {
return rv;
}
k_spinlock_key_t key = k_spin_lock(&mgr->lock);
if (sys_slist_find_and_remove(&mgr->monitors, &mon->node)) {
rv = 0;
}
k_spin_unlock(&mgr->lock, key);
return rv; return rv;
} }

File diff suppressed because it is too large Load diff