diff --git a/CODEOWNERS b/CODEOWNERS index 50125184737bd..636683bc2e87f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -118,7 +118,7 @@ /doc/scripts/ @carlescufi /doc/guides/bluetooth/ @joerchan @jhedberg @Vudentz /doc/reference/bluetooth/ @joerchan @jhedberg @Vudentz -/doc/reference/kernel/other/resource_mgmt.rst @pabigot +/doc/reference/resource_management/ @pabigot /doc/reference/networking/can* @alexanderwachter /drivers/debug/ @nashif /drivers/*/*cc13xx_cc26xx* @bwitherspoon diff --git a/doc/reference/index.rst b/doc/reference/index.rst index f48adf85d5155..5163b204fd965 100644 --- a/doc/reference/index.rst +++ b/doc/reference/index.rst @@ -6,6 +6,7 @@ API Reference .. toctree:: :maxdepth: 1 + misc/async_notify.rst bluetooth/index.rst kconfig/index.rst drivers/index.rst @@ -18,6 +19,7 @@ API Reference peripherals/index.rst power_management/index.rst random/index.rst + resource_management/index.rst shell/index.rst storage/index.rst usb/index.rst diff --git a/doc/reference/kernel/index.rst b/doc/reference/kernel/index.rst index 21504fcc065da..b4f0ef27b432a 100644 --- a/doc/reference/kernel/index.rst +++ b/doc/reference/kernel/index.rst @@ -116,7 +116,6 @@ These pages cover other kernel services. other/atomic.rst other/float.rst other/ring_buffers.rst - other/resource_mgmt.rst other/cxx_support.rst other/version.rst other/fatal.rst diff --git a/doc/reference/kernel/other/resource_mgmt.rst b/doc/reference/kernel/other/resource_mgmt.rst deleted file mode 100644 index 3cf284e0be5fa..0000000000000 --- a/doc/reference/kernel/other/resource_mgmt.rst +++ /dev/null @@ -1,83 +0,0 @@ -.. _resource_mgmt: - -Resource Management -################### - -There are various situations where it's necessary to coordinate resource -use at runtime among multiple clients. These include power rails, -clocks, other peripherals, and binary device power management. The -complexity of properly managing multiple consumers of a device in a -multithreaded system, especially when transitions may be asynchronous, -suggests that a shared implementation is desirable. - -.. contents:: - :local: - :depth: 2 - - -On-Off Services -*************** - -An on-off service supports an arbitrary number of clients of a service -which has a binary state. Example applications are power rails, clocks, -and binary device power management. - -The service has the following properties: - -* The stable states are off, on, and error. The service always begins - in the off state. The service may also be in a transition to a given - state. -* The core operations are request (add a dependency) and release (remove - a dependency). The service manages the state based on calls to - functions that initiate these operations. -* The service transitions from off to on when first client request is - received. -* The service transitions from on to off when last client release is - received. -* Each service configuration provides functions that implement the - 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 - sleep must be flagged in the configuration to support safe invocation - from non-thread context. -* All operations are asynchronous, and are initiated by a function call - that references a specific service and is given client notification - 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 - progress: when the service has turned off successfully it will be - immediately turned on again (where context allows) and waiting clients - notified when the start completes. - -Requests are reference counted, but not tracked. That means clients are -responsible for recording whether their requests were accepted, and for -initiating a release only if they have previously successfully completed -a request. Improper use of the API can cause an active client to be -shut out, and the service does not maintain a record of specific clients -that have been granted a request. - -Failures in executing a transition are recorded and inhibit further -requests or releases until the service is reset. Pending requests are -notified (and cancelled) when errors are discovered. - -Transition operation completion notifications are provided through any -of the following mechanisms: - -* Signal: A pointer to a :c:type:`struct k_poll_signal` is provided, and - the signal is raised when the transition completes. The operation - completion code is stored as the signal value. -* Callback: a function pointer is provided by the client along with an - opaque pointer, and on completion of the operation the function is - invoked with the pointer and the operation completion code. -* 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_apis - :project: Zephyr diff --git a/doc/reference/misc/async_notify.rst b/doc/reference/misc/async_notify.rst new file mode 100644 index 0000000000000..2c7af47df513d --- /dev/null +++ b/doc/reference/misc/async_notify.rst @@ -0,0 +1,25 @@ +.. _async_notification: + +Asynchronous Notification APIs +############################## + +Zephyr APIs often include :ref:`api_term_async` functions where an +operation is initiated and the application needs to be informed when it +completes, and whether it succeeded. Using :cpp:func:`k_poll()` is +often a good method, but some application architectures may be more +suited to a callback notification, and operations like enabling clocks +and power rails may need to be invoked before kernel functions are +available so a busy-wait for completion may be needed. + +This API is intended to be embedded within specific subsystems such as +:ref:`resource_mgmt_onoff` and other APIs that support async +transactions. The subsystem wrappers are responsible for extracting +operation-specific data from requests that include a notification +element, and for invoking callbacks with the parameters required by the +API. + +API Reference +************* + +.. doxygengroup:: async_notify_apis + :project: Zephyr diff --git a/doc/reference/resource_management/index.rst b/doc/reference/resource_management/index.rst new file mode 100644 index 0000000000000..6e25d6cbf8079 --- /dev/null +++ b/doc/reference/resource_management/index.rst @@ -0,0 +1,146 @@ +.. _resource_mgmt: + +Resource Management +################### + +There are various situations where it's necessary to coordinate resource +use at runtime among multiple clients. These include power rails, +clocks, other peripherals, and binary device power management. The +complexity of properly managing multiple consumers of a device in a +multithreaded system, especially when transitions may be asynchronous, +suggests that a shared implementation is desirable. + +Zephyr provides managers for several coordination policies. These +managers are embedded into services that use them for specific +functions. + +.. contents:: + :local: + :depth: 2 + +.. _resource_mgmt_onoff: + +On-Off Manager +************** + +An on-off manager supports an arbitrary number of clients of a service +which has a binary state. Example applications are power rails, clocks, +and binary device power management. + +The manager has the following properties: + +* The stable states are off, on, and error. The service always begins + in the off state. The service may also be in a transition to a given + state. +* The core operations are request (add a dependency) and release (remove + a dependency). The service manages the state based on calls to + functions that initiate these operations. +* The service transitions from off to on when first client request is + received. +* The service transitions from on to off when last client release is + received. +* Each service configuration provides functions that implement the + 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 + sleep must be flagged in the configuration to support safe invocation + from non-thread context. +* All operations are asynchronous, and are initiated by a function call + that references a specific service and is given client notification + 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 + progress: when the service has turned off successfully it will be + immediately turned on again (where context allows) and waiting clients + notified when the start completes. + +Requests are reference counted, but not tracked. That means clients are +responsible for recording whether their requests were accepted, and for +initiating a release only if they have previously successfully completed +a request. Improper use of the API can cause an active client to be +shut out, and the manager does not maintain a record of specific clients +that have been granted a request. + +Failures in executing a transition are recorded and inhibit further +requests or releases until the manager is reset. Pending requests are +notified (and cancelled) when errors are discovered. + +Transition operation completion notifications are provided through the +standard :ref:`async_notification`, supporting these methods: + +* Signal: A pointer to a :c:type:`struct k_poll_signal` is provided, and + the signal is raised when the transition completes. The operation + completion code is stored as the signal value. +* Callback: a function pointer is provided by the client along with an + opaque pointer, and on completion of the operation the function is + invoked with the pointer and the operation completion code. +* 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 + :project: Zephyr + +.. _resource_mgmt_queued_operation: + +Queued Operation Manager +************************ + +While :ref:`resource_mgmt_onoff` supports a shared resource that must be +available as long as any user still depends on it, the queued operation +manager provides serialized exclusive access to a resource that executes +operations asynchronously. This can be used to support (for example) +ADC sampling for different sensors, or groups of bus transactions. +Clients submit a operation request that is processed when the device +becomes available, with clients being notified of the completion of the +operation though the standard :ref:`async_notification`. + +As with the on-off manager, the queued resource manager is a generic +infrastructure tool that should be used by a extending service, such as +an I2C bus controller or an ADC. The manager has the following +characteristics: + +* The stable states are idle and processing. The manager always begins + in the idle state. +* The core client operations are submit (add an operation) and cancel + (remove an operation before it starts). +* Ownership of the operation object transitions from the client to the + manager when a queue request is accepted, and is returned to the + client when the manager notifies the client of operation completion. +* The core client event is completion. Manager state changes only as a + side effect from submitting or completing an operation. +* The service transitions from idle to processing when an operation is + submitted. +* The service transitions from processing to idle when notification of + the last operation has completed and there are no queued operations. +* The manager selects the next operation to process when notification of + completion has itself completed. In particular, changes to the set of + pending operations that are made during a completion callback affect + the next operation to execute. +* Each submitted operation includes a priority that orders execution by + first-come-first-served within priority. +* Operations are asynchronous, with completion notification through the + :ref:`async_notification`. The operations and notifications are run + in a context that is service-specific. This may be one or more + dedicated threads, or work queues. Notifications may come from + interrupt handlers. Note that for some services certain operations + may complete before the submit request has returned to its caller. + +The generic infrastructure holds the active operation and a queue of +pending operations. A service extension shall provide functions that: + +* check that a request is well-formed, i.e. can be added to the queue; +* receive notification that a new operation is to be processed, or that + no operations are available (allowing the service to enter a + power-down mode); +* translate a generic completion callback into a service-specific + callback. + +.. doxygengroup:: resource_mgmt_queued_operation_apis + :project: Zephyr diff --git a/include/sys/async_notify.h b/include/sys/async_notify.h new file mode 100644 index 0000000000000..b99b677bb42ce --- /dev/null +++ b/include/sys/async_notify.h @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_SYS_ASYNC_NOTIFY_H_ +#define ZEPHYR_INCLUDE_SYS_ASYNC_NOTIFY_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup async_notify_apis Asynchronous Notification APIs + * @ingroup kernel_apis + * @{ + */ + +/* Forward declaration */ +struct async_notify; + +/* + * Flag value that overwrites the method field when the operation has + * completed. + */ +#define ASYNC_NOTIFY_METHOD_COMPLETED 0 + +/* + * Indicates that no notification will be provided. + * + * Callers must check for completions using + * async_notify_fetch_result(). + * + * See async_notify_init_spinwait(). + */ +#define ASYNC_NOTIFY_METHOD_SPINWAIT 1 + +/* + * Select notification through @ref k_poll signal + * + * See async_notify_init_signal(). + */ +#define ASYNC_NOTIFY_METHOD_SIGNAL 2 + +/* + * Select notification through a user-provided callback. + * + * See async_notify_init_callback(). + */ +#define ASYNC_NOTIFY_METHOD_CALLBACK 3 + +#define ASYNC_NOTIFY_METHOD_MASK 0x03 +#define ASYNC_NOTIFY_METHOD_POS 0 + +/** + * @brief Identify the region of async_notify flags available for + * containing services. + * + * Bits of the flags field of the async_notify structure at and above + * this position may be used by extensions to the async_notify + * structure. + * + * These bits are intended for use by containing service + * implementations to record client-specific information. The bits + * are cleared by async_notify_validate(). Use of these does not + * imply that the flags field becomes public API. + */ +#define ASYNC_NOTIFY_EXTENSION_POS 2 + +/* + * Mask isolating the bits of async_notify::flags that are available + * for extension. + */ +#define ASYNC_NOTIFY_EXTENSION_MASK (~BIT_MASK(ASYNC_NOTIFY_EXTENSION_POS)) + +/** + * @brief Generic signature used to notify of result completion by + * callback. + * + * Functions with this role 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. + * + * Parameters that should generally be passed to such functions include: + * + * * a pointer to a specific client request structure, i.e. the one + * that contains the async_notify structure. + * * the result of the operation, either as passed to + * async_notify_finalize() or extracted afterwards using + * async_notify_fetch_result(). Expected values are + * service-specific, but the value shall be non-negative if the + * operation succeeded, and negative if the operation failed. + */ +typedef void (*async_notify_generic_callback)(); + +/** + * @brief State associated with notification for an asynchronous + * operation. + * + * Objects of this type are allocated by a client, which must use an + * initialization function (e.g. async_notify_init_signal()) to + * configure them. Generally the structure is a member of a + * service-specific client structure, such as onoff_client. + * + * Control of the containing object transfers to the service provider + * when a pointer to the object is passed to a service function that + * is documented to take control of the object, such as + * onoff_service_request(). While the service provider controls the + * object the client must not change any object fields. Control + * reverts to the client: + * * if the call to the service API returns an error; + * * if the call to the service API succeeds for a no-wait operation; + * * when operation completion is posted (signalled or callback + * invoked). + * + * After control has reverted to the client the notify object must be + * reinitialized for the next operation. + * + * The content of this structure is not public API to clients: all + * configuration and inspection should be done with functions like + * async_notify_init_callback() and async_notify_fetch_result(). + * However, services that use this structure may access certain + * fields directly. + */ +struct async_notify { + union method { + /* Pointer to signal used to notify client. + * + * The signal value corresponds to the res parameter + * of async_notify_callback. + */ + struct k_poll_signal *signal; + + /* Generic callback function for callback notification. */ + async_notify_generic_callback callback; + } method; + + /* + * Flags recording information about the operation. + * + * Bits below ASYNC_NOTIFY_EXTENSION_POS are initialized by + * async notify API init functions like + * async_notify_init_callback(), and must not by modified by + * extensions or client code. + * + * Bits at and above ASYNC_NOTIFY_EXTENSION_POS are available + * for use by service extensions while the containing object + * is managed by the service. They are not for client use, + * are zeroed by the async notify API init functions, and will + * be zeroed by async_notify_finalize(). + */ + u32_t volatile flags; + + /* + * The result of the operation. + * + * This is the value that was (or would be) passed to the + * async infrastructure. This field is the sole record of + * success or failure for no-wait synchronous operations. + */ + int volatile result; +}; + +/** @internal */ +static inline u32_t async_notify_get_method(const struct async_notify *notify) +{ + u32_t method = notify->flags >> ASYNC_NOTIFY_METHOD_POS; + + return method & ASYNC_NOTIFY_METHOD_MASK; +} + +/** + * @brief Validate and initialize the notify structure. + * + * This should be invoked at the start of any service-specific + * configuration validation. It ensures that the basic asynchronous + * notification configuration is consistent, and clears the result. + * + * Note that this function does not validate extension bits (zeroed by + * async notify API init functions like async_notify_init_callback()). + * It may fail to recognize that an uninitialized structure has been + * passed because only method bits of flags are tested against method + * settings. To reduce the chance of accepting an uninititalized + * operation service validation of structures that contain an + * async_notify instance should confirm that the extension bits are + * set or cleared as expected. + * + * @retval 0 on successful validation and reinitialization + * @retval -EINVAL if the configuration is not valid. + */ +int async_notify_validate(struct async_notify *notify); + +/** + * @brief Record and signal the operation completion. + * + * @param notify pointer to the notification state structure. + * + * @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. + * + * @return If the notification is to be done by callback this returns + * the generic version of the function to be invoked. The caller must + * immediately invoke that function with whatever arguments are + * expected by the callback. If notification is by spin-wait or + * signal, the notification has been completed by the point this + * function returns. + */ +async_notify_generic_callback async_notify_finalize(struct async_notify *notify, + int res); + +/** + * @brief Check for and read the result of an asynchronous operation. + * + * @param notify pointer to the object used to specify asynchronous + * function behavior and store completion information. + * + * @param result pointer to storage for the result of the operation. + * The result is stored only if the operation has completed. + * + * @retval 0 if the operation has completed. + * @retval -EAGAIN if the operation has not completed. + */ +static inline int async_notify_fetch_result(const struct async_notify *notify, + int *result) +{ + __ASSERT_NO_MSG(notify != NULL); + __ASSERT_NO_MSG(result != NULL); + int rv = -EAGAIN; + + if (async_notify_get_method(notify) == ASYNC_NOTIFY_METHOD_COMPLETED) { + rv = 0; + *result = notify->result; + } + return rv; +} + +/** + * @brief Initialize a notify object for spin-wait notification. + * + * Clients that use this initialization receive no asynchronous + * notification, and instead must periodically check for completion + * using async_notify_fetch_result(). + * + * On completion of the operation the client object must be + * reinitialized before it can be re-used. + * + * @param notify pointer to the notification configuration object. + */ +static inline void async_notify_init_spinwait(struct async_notify *notify) +{ + __ASSERT_NO_MSG(notify != NULL); + + *notify = (struct async_notify){ + .flags = ASYNC_NOTIFY_METHOD_SPINWAIT, + }; +} + +/** + * @brief Initialize a notify object for (k_poll) signal notification. + * + * Clients that use this initialization will be notified of the + * completion of operations 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 notify pointer to the notification configuration 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 async_notify_init_signal(struct async_notify *notify, + struct k_poll_signal *sigp) +{ + __ASSERT_NO_MSG(notify != NULL); + __ASSERT_NO_MSG(sigp != NULL); + + *notify = (struct async_notify){ + .method = { + .signal = sigp, + }, + .flags = ASYNC_NOTIFY_METHOD_SIGNAL, + }; +} + +/** + * @brief Initialize a notify object for callback notification. + * + * Clients that use this initialization will be notified of the + * completion of operations through the provided callback. Note that + * callbacks may be invoked from various contexts depending on the + * specific service; see @ref async_notify_generic_callback. + * + * On completion of the operation the client object must be + * reinitialized before it can be re-used. + * + * @param notify pointer to the notification configuration object. + * + * @param handler a function pointer to use for notification. + */ +static inline void async_notify_init_callback(struct async_notify *notify, + async_notify_generic_callback handler) +{ + __ASSERT_NO_MSG(notify != NULL); + __ASSERT_NO_MSG(handler != NULL); + + *notify = (struct async_notify){ + .method = { + .callback = handler, + }, + .flags = ASYNC_NOTIFY_METHOD_CALLBACK, + }; +} + +/** + * @brief Detect whether a particular notification uses a callback. + * + * The generic handler does not capture the signature expected by the + * callback, and the translation to a service-specific callback must + * be provided by the service. This check allows abstracted services + * to reject callback notification requests when the service doesn't + * provide a translation function. + * + * @return true if and only if a callback is to be used for notification. + */ +static inline bool async_notify_uses_callback(const struct async_notify *notify) +{ + __ASSERT_NO_MSG(notify != NULL); + + return async_notify_get_method(notify) == ASYNC_NOTIFY_METHOD_CALLBACK; +} + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_SYS_ASYNC_NOTIFY_H_ */ diff --git a/include/sys/onoff.h b/include/sys/onoff.h index d8629d6a549aa..e51d25475fd14 100644 --- a/include/sys/onoff.h +++ b/include/sys/onoff.h @@ -9,13 +9,14 @@ #include #include +#include #ifdef __cplusplus extern "C" { #endif /** - * @defgroup resource_mgmt_apis Resource Management APIs + * @defgroup resource_mgmt_onoff_apis On-Off Service APIs * @ingroup kernel_apis * @{ */ @@ -25,7 +26,7 @@ extern "C" { */ enum onoff_service_flags { /** - * @brief Flag passed to onoff_service_init(). + * @brief Flag used in struct onoff_service_transitions. * * When provided this indicates the start transition function * may cause the calling thread to wait. This blocks attempts @@ -34,7 +35,7 @@ enum onoff_service_flags { ONOFF_SERVICE_START_SLEEPS = BIT(0), /** - * @brief Flag passed to onoff_service_init(). + * @brief Flag used in struct onoff_service_transitions. * * As with @ref ONOFF_SERVICE_START_SLEEPS but describing the * stop transition function. @@ -42,7 +43,7 @@ enum onoff_service_flags { ONOFF_SERVICE_STOP_SLEEPS = BIT(1), /** - * @brief Flag passed to onoff_service_init(). + * @brief Flag used in struct onoff_service_transitions. * * As with @ref ONOFF_SERVICE_START_SLEEPS but describing the * reset transition function. @@ -102,6 +103,21 @@ typedef void (*onoff_service_notify_fn)(struct onoff_service *srv, typedef void (*onoff_service_transition_fn)(struct onoff_service *srv, onoff_service_notify_fn notify); +/** @brief On-off service transition functions. */ +struct onoff_service_transitions { + /* Function to invoke to transition the service to on. */ + onoff_service_transition_fn start; + + /* Function to invoke to transition the service to off. */ + onoff_service_transition_fn stop; + + /* Function to force the service state to reset, where supported. */ + onoff_service_transition_fn reset; + + /* Flags identifying transition function capabilities. */ + u8_t flags; +}; + /** * @brief State associated with an on-off service. * @@ -117,16 +133,8 @@ struct onoff_service { */ sys_slist_t clients; - /* Function to invoke to transition the service to on. */ - onoff_service_transition_fn start; - - /* Function to invoke to transition the service to off. */ - onoff_service_transition_fn stop; - - /* Function to force the service state to reset, where - * supported. - */ - onoff_service_transition_fn reset; + /* Transition functions. */ + const struct onoff_service_transitions *transitions; /* Mutex protection for flags, clients, releaser, and refs. */ struct k_spinlock lock; @@ -141,14 +149,56 @@ struct onoff_service { u16_t refs; }; +/** @brief Initializer of transitions structure. + * + * @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. + * + * @param _flags any or all of the flags from enum onoff_service_flags. + */ +#define ONOFF_SERVICE_TRANSITIONS_INITIALIZER(_start, _stop, _reset, _flags) { \ + .start = _start, \ + .stop = _stop, \ + .reset = _reset, \ + .flags = _flags, \ +} + /** @internal */ -#define ONOFF_SERVICE_INITIALIZER(_start, _stop, _reset, _flags) { \ - .start = _start, \ - .stop = _stop, \ - .reset = _reset, \ - .flags = _flags, \ +#define ONOFF_SERVICE_INITIALIZER(_transitions) { \ + .transitions = _transitions, \ + .flags = (_transitions)->flags, \ } +/** @brief Statically define and initialize an on-off service. + * + * @param _name service name. + * + * @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. + * + * @param _flags any or all of the flags from enum onoff_service_flags. + */ +#define ONOFF_SERVICE_DEFINE(_name, _start, _stop, _reset, _flags) \ + BUILD_ASSERT_MSG(_start != NULL, "start function is required"); \ + BUILD_ASSERT_MSG(_stop != NULL, "stop function is required"); \ + BUILD_ASSERT_MSG((_flags & ~(ONOFF_SERVICE_START_SLEEPS | \ + ONOFF_SERVICE_STOP_SLEEPS | \ + ONOFF_SERVICE_RESET_SLEEPS)) == 0, \ + "Flags misused"); \ + static const struct onoff_service_transitions _name##_transitions =\ + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(_start, _stop, \ + _reset, _flags); \ + static struct onoff_service _name = \ + ONOFF_SERVICE_INITIALIZER(&_name##_transitions) + /** * @brief Initialize an on-off service to off state. * @@ -160,70 +210,14 @@ struct onoff_service { * * @param srv the service definition object to be initialized. * - * @param start the function used to (initiate a) transition from off - * to on. This must not be null. Include @ref ONOFF_SERVICE_START_SLEEPS as - * appropriate in flags. - * - * @param stop the function used to (initiate a) transition from on to - * off. This must not be null. Include @ref ONOFF_SERVICE_STOP_SLEEPS - * as appropriate in flags. - * - * @param reset the function used to clear errors and force the - * service to an off state. Pass null if the service cannot or need - * not be reset. (Services where a transition operation can complete - * with an error notification should support the reset operation.) - * Include @ref ONOFF_SERVICE_RESET_SLEEPS as appropriate in flags. - * - * @param flags any or all of the flags mentioned above, - * e.g. @ref ONOFF_SERVICE_START_SLEEPS. Use of other flags produces an - * error. + * @param transitions A structure with transition functions. Structure must be + * persistent as it is used by the service. * * @retval 0 on success * @retval -EINVAL if start, stop, or flags are invalid */ int onoff_service_init(struct onoff_service *srv, - onoff_service_transition_fn start, - onoff_service_transition_fn stop, - onoff_service_transition_fn reset, - u32_t flags); - -/** @internal - * - * Flag fields used to specify on-off client behavior. - * - * These flags control whether calls to onoff_service_request() and - * onoff_service_release() are synchronous or asynchronous, and for - * asynchronous operations how the operation result is communicated to - * the client. - */ -enum onoff_client_flags { - /* Known-invalid field, used in validation */ - ONOFF_CLIENT_NOTIFY_INVALID = 0, - - /* - * Indicates that no notification will be provided. - * - * Callers must check for completions using - * onoff_client_fetch_result(). - * - * See onoff_client_init_spinwait(). - */ - ONOFF_CLIENT_NOTIFY_SPINWAIT = 1, - - /* - * Select notification through @ref k_poll signal - * - * See onoff_client_init_signal(). - */ - ONOFF_CLIENT_NOTIFY_SIGNAL = 2, - - /** - * Select notification through a user-provided callback. - * - * See onoff_client_init_callback(). - */ - ONOFF_CLIENT_NOTIFY_CALLBACK = 3, -}; + const struct onoff_service_transitions *transitions); /* Forward declaration */ struct onoff_client; @@ -281,32 +275,11 @@ struct onoff_client { /* Links the client into the set of waiting service users. */ sys_snode_t node; - union async { - /* Pointer to signal used to notify client. - * - * The signal value corresponds to the res parameter - * of onoff_client_callback. - */ - struct k_poll_signal *signal; - - /* Handler and argument for callback notification. */ - struct callback { - onoff_client_callback handler; - void *user_data; - } callback; - } async; - - /* - * The result of the operation. - * - * This is the value that was (or would be) passed to the - * async infrastructure. This field is the sole record of - * success or failure for no-wait synchronous operations. - */ - int volatile result; + /* Notification configuration. */ + struct async_notify notify; - /* Flags recording client state. */ - u32_t volatile flags; + /* User data for callback-based notification. */ + void *user_data; }; /** @@ -325,15 +298,8 @@ static inline int onoff_client_fetch_result(const struct onoff_client *op, int *result) { __ASSERT_NO_MSG(op != NULL); - __ASSERT_NO_MSG(result != NULL); - int rv = -EAGAIN; - - if (op->flags == 0U) { - rv = 0; - *result = op->result; - } - return rv; + return async_notify_fetch_result(&op->notify, result); } /** @@ -353,9 +319,8 @@ static inline void onoff_client_init_spinwait(struct onoff_client *cli) { __ASSERT_NO_MSG(cli != NULL); - *cli = (struct onoff_client){ - .flags = ONOFF_CLIENT_NOTIFY_SPINWAIT, - }; + *cli = (struct onoff_client){}; + async_notify_init_spinwait(&cli->notify); } /** @@ -385,16 +350,9 @@ static inline void onoff_client_init_signal(struct onoff_client *cli, struct k_poll_signal *sigp) { __ASSERT_NO_MSG(cli != NULL); - __ASSERT_NO_MSG(sigp != NULL); - *cli = (struct onoff_client){ -#ifdef CONFIG_POLL - .async = { - .signal = sigp, - }, -#endif /* CONFIG_POLL */ - .flags = ONOFF_CLIENT_NOTIFY_SIGNAL, - }; + *cli = (struct onoff_client){}; + async_notify_init_signal(&cli->notify, sigp); } /** @@ -425,14 +383,9 @@ static inline void onoff_client_init_callback(struct onoff_client *cli, __ASSERT_NO_MSG(handler != NULL); *cli = (struct onoff_client){ - .async = { - .callback = { - .handler = handler, - .user_data = user_data, - }, - }, - .flags = ONOFF_CLIENT_NOTIFY_CALLBACK, + .user_data = user_data, }; + async_notify_init_callback(&cli->notify, handler); } /** diff --git a/include/sys/queued_operation.h b/include/sys/queued_operation.h new file mode 100644 index 0000000000000..f29bf5430478b --- /dev/null +++ b/include/sys/queued_operation.h @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_SYS_QUEUED_OPERATION_H_ +#define ZEPHYR_INCLUDE_SYS_QUEUED_OPERATION_H_ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declaration */ +struct queued_operation; +struct queued_operation_manager; + +/** + * @defgroup resource_mgmt_queued_operation_apis Queued Operation APIs + * @ingroup kernel_apis + * @{ + */ + +/** @internal */ +#define QUEUED_OPERATION_PRIORITY_POS ASYNC_NOTIFY_EXTENSION_POS +/** @internal */ +#define QUEUED_OPERATION_PRIORITY_BITS 8U +/** @internal */ +#define QUEUED_OPERATION_PRIORITY_MASK BIT_MASK(QUEUED_OPERATION_PRIORITY_BITS) + +/** + * @brief Special priority value to indicate operation should be + * placed last in current queue. + * + * This is like providing the lowest priority but uses a constant-time + * insertion and is FIFO. + */ +#define QUEUED_OPERATION_PRIORITY_APPEND ((int)QUEUED_OPERATION_PRIORITY_MASK + 1) + +/** + * @brief Special priority value to indicate operation should be + * placed first in the current queue. + * + * This is like providing the highest priority but uses a + * constant-time insertion and is LIFO. + */ +#define QUEUED_OPERATION_PRIORITY_PREPEND ((int)QUEUED_OPERATION_PRIORITY_MASK + 2) + +/** + * @brief Identify the region of async_notify flags available for + * containing services. + * + * Bits of the flags field of the async_notify structure contained + * within the queued_operation structure at and above this position + * may be used by extensions to the async_notify structure. + * + * These bits are intended for use by containing service + * implementations to record client-specific information. The bits + * are cleared by async_notify_validate(). Use of these does not + * imply that the flags field becomes public API. + */ +#define QUEUED_OPERATION_EXTENSION_POS (QUEUED_OPERATION_PRIORITY_POS + QUEUED_OPERATION_PRIORITY_BITS) + +/** + * @brief Base object providing state for an operation. + * + * Instances of this should be members of a service-specific structure + * that provides the operation parameters. + */ +struct queued_operation { + /** @internal + * + * Links the operation into the operation queue. + */ + sys_snode_t node; + + /** + * @brief Notification configuration. + * + * This must be initialized using async_notify_init_callback() + * or its sibling functions before an operation can be passed + * to queued_operation_submit(). + * + * The queued operation manager provides specific error codes + * for failures identified at the manager level: + * * -ENODEV indicates a failure in an onoff service. + */ + struct async_notify notify; +}; + +/** + * @ brief Table of functions used by a queued operation manager. + */ +struct queued_operation_functions { + /** + * @brief Function used to verify an operation is well-defined. + * + * When provided this function is invoked by + * queued_operation_submit() to verify that the operation + * definition meets the expectations of the service. The + * operation is acceptable only if a non-negative value is + * returned. + * + * If not provided queued_operation_submit() will assume + * service-specific expectations are trivially satisfied, and + * will reject the operation only if async_notify_validate() + * fails. Because that validation is limited services should + * at a minimum verify that the extension bits have the + * expected value (zero, when none are being used). + * + * @param mgr the service that supports queued operations. + * + * @param op the operation being considered for suitability. + * + * @return the value to be returned from queued_operation_submit(). + */ + int (*validate)(struct queued_operation_manager *mgr, + struct queued_operation *op); + + /** + * @brief Function to transform a generic notification + * callback to its service-specific form. + * + * The implementation should cast cb to the proper signature + * for the service, and invoke the cast pointer with the + * appropriate arguments. + * + * @param mgr the service that supports queued operations. + * + * @param op the operation that has been completed. + * + * @param cb the generic callback to invoke. + */ + void (*callback)(struct queued_operation_manager *mgr, + struct queued_operation *op, + async_notify_generic_callback cb); + + /** + * @brief Function used to inform the manager of a new operation. + * + * This function can be called as a side effect of + * queued_operation_submit() or queued_operation_finalize() to + * tell the service that a new operation needs to be + * processed, or that there are no operations left to process. + * + * Be aware that if processing is entirely + * synchronous--meaning queued_operation_finalize() can be + * invoked during process()--then the process() function will + * be invoked recursively, possibly with another operation. + * This can cause unbounded stack growth, and requires that + * process() be re-entrant. Generally the process() function + * should itself be async, with finalization done after + * process() returns. + * + * @param mgr the service that supports queued operations. + * + * @param op the operation that should be initiated. A null + * pointer is passed if there are no pending operations. + */ + void (*process)(struct queued_operation_manager *mgr, + struct queued_operation *op); +}; + +/** + * @brief State associated with a manager instance. + */ +struct queued_operation_manager { + /* Links the operation into the operation queue. */ + sys_slist_t operations; + + /* Pointer to the functions that support the manager. */ + const struct queued_operation_functions *vtable; + + /* Pointer to an on-off service supporting this service. NULL + * if service is always-on. + */ + struct onoff_service *onoff; + + /* The state of on-off service requests. */ + struct onoff_client onoff_client; + + /* Lock controlling access to other fields. */ + struct k_spinlock lock; + + /* The operation that is being processed. */ + struct queued_operation *current; + + /* Information about the internal state of the manager. */ + u32_t volatile state; +}; + +#define QUEUED_OPERATION_MANAGER_INITIALIZER(_vtable, _onoff) { \ + .vtable = _vtable, \ + .onoff = _onoff, \ +} + +/** + * @brief Submit an operation to be processed when the service is + * available. + * + * The service process function will be invoked during this call if + * the service is available. + * + * @param mgr a generic pointer to the service instance + * + * @param op a generic pointer to an operation to be performed. The + * notify field in the provided operation must have been initialized + * before being submitted, even if the operation description is being + * re-used. This may be done directly with async_notify API or by + * wrapping it in a service-specific operation init function. + * + * @param priority the priority of the operation relative to other + * operations. Numerically lower values are higher priority. Values + * outside the range of a signed 8-bit integer will be rejected, + * except for named priorities like QUEUED_OPERATION_PRIORITY_APPEND. + * + * @retval -ENOTSUP if callback notification is requested and the + * service does not provide a callback translation. This may also be + * returned due to service-specific validation. + * + * @retval -EINVAL if the passed priority is out of the range of + * supported priorities. This may also be returned due to + * service-specific validation. + * + * @return A negative value if the operation was rejected by service + * validation or due to other configuration errors. A non-negative + * value indicates the operation has been accepted for processing and + * completion notification will be provided. + */ +int queued_operation_submit(struct queued_operation_manager *mgr, + struct queued_operation *op, + int priority); + +/** + * @brief Helper to extract the result from a queued operation. + * + * This forwards to async_notify_fetch_result(). + */ +static inline int queued_operation_fetch_result(const struct queued_operation *op, + int *result) +{ + return async_notify_fetch_result(&op->notify, result); +} + +/** + * @brief Attempt to cancel a queued operation. + * + * Successful cancellation issues a completion notification with + * result -ECANCELED for the submitted operation before this function + * returns. + * + * @retval 0 if successfully cancelled. + * @retval -EINPROGRESS if op is currently being executed, so cannot + * be cancelled. + * @retval -EINVAL if op is neither being executed nor in the queue of + * pending operations + */ +int queued_operation_cancel(struct queued_operation_manager *mgr, + struct queued_operation *op); + +/** + * @brief Send the completion notification for a queued operation. + * + * This function must be invoked by services that support queued + * operations when the operation provided to them through the process + * function have been completed. It is not intended to be invoked by + * users of a service. + * + * During the call to this function the service process function will + * be invoked at least once, either providing another operation or + * indicating that no operations are pending. + * + * @param mgr a generic pointer to the service instance + * @param res the result of the operation, as with + * async_notify_finalize(). + */ +void queued_operation_finalize(struct queued_operation_manager *mgr, + int res); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_SYS_ASYNCNOTIFY_H_ */ diff --git a/lib/os/CMakeLists.txt b/lib/os/CMakeLists.txt index 7918f78dc6d72..2e9e3a125bec3 100644 --- a/lib/os/CMakeLists.txt +++ b/lib/os/CMakeLists.txt @@ -3,6 +3,7 @@ zephyr_sources_if_kconfig(base64.c) zephyr_sources( + async_notify.c crc32_sw.c crc16_sw.c crc8_sw.c @@ -13,6 +14,7 @@ zephyr_sources( mempool.c printk.c onoff.c + queued_operation.c rb.c sem.c thread_entry.c diff --git a/lib/os/async_notify.c b/lib/os/async_notify.c new file mode 100644 index 0000000000000..ba6974c35bdc7 --- /dev/null +++ b/lib/os/async_notify.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +int async_notify_validate(struct async_notify *notify) +{ + int rv = 0; + + if (notify == NULL) { + return -EINVAL; + } + + /* Validate configuration based on mode */ + switch (async_notify_get_method(notify)) { + case ASYNC_NOTIFY_METHOD_SPINWAIT: + break; + case ASYNC_NOTIFY_METHOD_CALLBACK: + if (notify->method.callback == NULL) { + rv = -EINVAL; + } + break; +#ifdef CONFIG_POLL + case ASYNC_NOTIFY_METHOD_SIGNAL: + if (notify->method.signal == NULL) { + rv = -EINVAL; + } + break; +#endif /* CONFIG_POLL */ + default: + rv = -EINVAL; + break; + } + + /* Clear the result here instead of in all callers. */ + if (rv == 0) { + notify->result = 0; + } + + return rv; +} + +async_notify_generic_callback async_notify_finalize(struct async_notify *notify, + int res) +{ + struct k_poll_signal *sig = NULL; + async_notify_generic_callback rv = 0; + u32_t method = async_notify_get_method(notify); + + /* Store the result and capture secondary notification + * information. + */ + notify->result = res; + switch (method) { + case ASYNC_NOTIFY_METHOD_SPINWAIT: + break; + case ASYNC_NOTIFY_METHOD_CALLBACK: + rv = notify->method.callback; + break; + case ASYNC_NOTIFY_METHOD_SIGNAL: + sig = notify->method.signal; + break; + default: + __ASSERT_NO_MSG(false); + } + + /* Mark completion by clearing the flags field to the + * completed state, releasing any spin-waiters, then complete + * secondary notification. + */ + compiler_barrier(); + notify->flags = ASYNC_NOTIFY_METHOD_COMPLETED; + + if (IS_ENABLED(CONFIG_POLL) && (sig != NULL)) { + k_poll_signal_raise(sig, res); + } + + return rv; +} diff --git a/lib/os/onoff.c b/lib/os/onoff.c index 2a999b6ef8b90..df033fac35f40 100644 --- a/lib/os/onoff.c +++ b/lib/os/onoff.c @@ -6,15 +6,12 @@ #include #include -#include -#define CLIENT_NOTIFY_METHOD_MASK 0x03 -#define CLIENT_VALID_FLAGS_MASK 0x07 -#define SERVICE_CONFIG_FLAGS \ - (ONOFF_SERVICE_START_SLEEPS \ - | ONOFF_SERVICE_STOP_SLEEPS \ - | ONOFF_SERVICE_RESET_SLEEPS) +#define SERVICE_CONFIG_FLAGS \ + (ONOFF_SERVICE_START_SLEEPS \ + | ONOFF_SERVICE_STOP_SLEEPS \ + | ONOFF_SERVICE_RESET_SLEEPS) #define SERVICE_REFS_MAX UINT16_MAX @@ -40,57 +37,28 @@ static int validate_args(const struct onoff_service *srv, return -EINVAL; } - int rv = 0; - u32_t mode = cli->flags; + int rv = async_notify_validate(&cli->notify); - /* Reject unexpected flags. */ - if (mode != (cli->flags & CLIENT_VALID_FLAGS_MASK)) { - return -EINVAL; - } - - /* Validate configuration based on mode */ - switch (mode & CLIENT_NOTIFY_METHOD_MASK) { - case ONOFF_CLIENT_NOTIFY_SPINWAIT: - break; - case ONOFF_CLIENT_NOTIFY_CALLBACK: - if (cli->async.callback.handler == NULL) { - rv = -EINVAL; - } - break; - case ONOFF_CLIENT_NOTIFY_SIGNAL: - if (cli->async.signal == NULL) { - rv = -EINVAL; - } - break; - default: + if ((rv == 0) + && ((cli->notify.flags & ASYNC_NOTIFY_EXTENSION_MASK) != 0)) { rv = -EINVAL; - break; - } - - /* Clear the result here instead of in all callers. */ - if (rv == 0) { - cli->result = 0; } return rv; } int onoff_service_init(struct onoff_service *srv, - onoff_service_transition_fn start, - onoff_service_transition_fn stop, - onoff_service_transition_fn reset, - u32_t flags) + const struct onoff_service_transitions *transitions) { - if ((flags & SERVICE_CONFIG_FLAGS) != flags) { + if (transitions->flags & ~SERVICE_CONFIG_FLAGS) { return -EINVAL; } - if ((start == NULL) || (stop == NULL)) { + if ((transitions->start == NULL) || (transitions->stop == NULL)) { return -EINVAL; } - *srv = (struct onoff_service)ONOFF_SERVICE_INITIALIZER(start, stop, - reset, flags); + *srv = (struct onoff_service)ONOFF_SERVICE_INITIALIZER(transitions); return 0; } @@ -99,25 +67,11 @@ static void notify_one(struct onoff_service *srv, struct onoff_client *cli, int res) { - unsigned int flags = cli->flags; + onoff_client_callback cb = + (onoff_client_callback)async_notify_finalize(&cli->notify, res); - /* Store the result, and notify if requested. */ - cli->result = res; - cli->flags = 0; - switch (flags & CLIENT_NOTIFY_METHOD_MASK) { - case ONOFF_CLIENT_NOTIFY_SPINWAIT: - break; - case ONOFF_CLIENT_NOTIFY_CALLBACK: - cli->async.callback.handler(srv, cli, - cli->async.callback.user_data, res); - break; -#ifdef CONFIG_POLL - case ONOFF_CLIENT_NOTIFY_SIGNAL: - k_poll_signal_raise(cli->async.signal, res); - break; -#endif /* CONFIG_POLL */ - default: - __ASSERT_NO_MSG(false); + if (cb) { + cb(srv, cli, cli->user_data, res); } } @@ -261,8 +215,8 @@ int onoff_request(struct onoff_service *srv, k_spin_unlock(&srv->lock, key); if (start) { - __ASSERT_NO_MSG(srv->start != NULL); - srv->start(srv, onoff_start_notify); + __ASSERT_NO_MSG(srv->transitions->start != NULL); + srv->transitions->start(srv, onoff_start_notify); } else if (notify) { notify_one(srv, cli, 0); } @@ -328,7 +282,7 @@ static void onoff_stop_notify(struct onoff_service *srv, if (notify_clients) { notify_all(srv, &clients, client_res); } else if (start) { - srv->start(srv, onoff_start_notify); + srv->transitions->start(srv, onoff_start_notify); } } @@ -396,8 +350,8 @@ int onoff_release(struct onoff_service *srv, k_spin_unlock(&srv->lock, key); if (stop) { - __ASSERT_NO_MSG(srv->stop != NULL); - srv->stop(srv, onoff_stop_notify); + __ASSERT_NO_MSG(srv->transitions->stop != NULL); + srv->transitions->stop(srv, onoff_stop_notify); } else if (notify) { notify_one(srv, cli, 0); } @@ -435,7 +389,7 @@ static void onoff_reset_notify(struct onoff_service *srv, int onoff_service_reset(struct onoff_service *srv, struct onoff_client *cli) { - if (srv->reset == NULL) { + if (srv->transitions->reset == NULL) { return -ENOTSUP; } @@ -472,7 +426,7 @@ int onoff_service_reset(struct onoff_service *srv, k_spin_unlock(&srv->lock, key); if (reset) { - srv->reset(srv, onoff_reset_notify); + srv->transitions->reset(srv, onoff_reset_notify); } return rv; diff --git a/lib/os/queued_operation.c b/lib/os/queued_operation.c new file mode 100644 index 0000000000000..49285eabb6666 --- /dev/null +++ b/lib/os/queued_operation.c @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +/* States used in the manager state field. */ +enum state { + /* Service is not active. + * + * Transitions to STARTING on queued_operation_submit(). + */ + ST_OFF = 0, + + /* Service is being started. + * + * This is a transient state while an associated on-off + * service request is incomplete. Transitions to IDLE and + * reschedules on successful start, and ERROR on failure to + * start. + */ + ST_STARTING, + + /* Service is active with no known operations. + * + * This is a transient substate of an implicit ON state. The + * machine will transition to NOTIFYING or STOPPING within + * the current mutex region + */ + ST_IDLE, + + /* The manager is invoking process() to notify the service of + * a new operation or a transition to idle. + * + * Transitions to PROCESSING if an operation was passed and + * queued_operation_finalize() has not been invoked before + * process() returns to the manager. + * + * Transitions to IDLE if an operation was not passed and the + * manager queue remains empty when process() returns to the + * manager. + * + * Re-runs select in all other cases. + */ + ST_NOTIFYING, + + /* A new operation has been identified and the service + * process() function will be/is/has-been invoked on it. + * + * Transitions to FINALIZING when queued_operation_finalize() + * is invoked. + */ + ST_PROCESSING, + + /* An operation that was processing is being finalized. + * + * Re-selects after finalization and any containing notifying + * completes. + */ + ST_FINALIZING, + + /* Service is being started. + * + * This is a transient state while an associated on-off + * service request is pending. + */ + ST_STOPPING, + + /* Service is in an error state. */ + ST_ERROR, +}; + +/* Forward declaration */ +static void select_next_and_unlock(struct queued_operation_manager *mgr, + k_spinlock_key_t key); +static void start_and_unlock(struct queued_operation_manager *mgr, + k_spinlock_key_t key); + +static inline void trivial_start_and_unlock(struct queued_operation_manager *mgr, + k_spinlock_key_t key) +{ + mgr->state = ST_IDLE; + select_next_and_unlock(mgr, key); +} + +static inline int op_get_priority(const struct queued_operation *op) +{ + return (s8_t)(op->notify.flags >> QUEUED_OPERATION_PRIORITY_POS); +} + +static inline int op_set_priority(struct queued_operation *op, + int priority) +{ + s8_t prio = (s8_t)priority; + u32_t mask = (QUEUED_OPERATION_PRIORITY_MASK << QUEUED_OPERATION_PRIORITY_POS); + + if (priority == QUEUED_OPERATION_PRIORITY_PREPEND) { + prio = INT8_MIN; + } else if (priority == QUEUED_OPERATION_PRIORITY_APPEND) { + prio = INT8_MAX; + } else if (prio != priority) { + return -EINVAL; + } + + op->notify.flags = (op->notify.flags & ~mask) + | (mask & (prio << QUEUED_OPERATION_PRIORITY_POS)); + + return 0; +} + +static inline void finalize_and_notify(struct queued_operation_manager *mgr, + struct queued_operation *op, + int res) +{ + async_notify_generic_callback cb = async_notify_finalize(&op->notify, res); + + if (cb != NULL) { + mgr->vtable->callback(mgr, op, cb); + } +} + +/* React to the completion of an onoff transition, either from a + * manager or directly. + * + * @param mgr the operation manager + * @param from either ST_STARTING or ST_STOPPING depending on transition direction + * @param res the transition completion value, negative for error. + */ +static void settle_onoff(struct queued_operation_manager *mgr, + enum state from, + int res) +{ + k_spinlock_key_t key = k_spin_lock(&mgr->lock); + + __ASSERT_NO_MSG(mgr->state == from); + + if (res >= 0) { + if (from == ST_STARTING) { + trivial_start_and_unlock(mgr, key); + return; + } + + /* Came from STOPPING so set to OFF, but automatically + * initiate a new start if operations have been added. + */ + mgr->state = ST_OFF; + if (!sys_slist_is_empty(&mgr->operations)) { + start_and_unlock(mgr, key); + return; + } + + k_spin_unlock(&mgr->lock, key); + return; + } + + /* On transition failure mark service failed. All unstarted + * operations are unlinked and completed as a service failure. + */ + sys_slist_t ops = mgr->operations; + sys_slist_init(&mgr->operations); + mgr->state = ST_ERROR; + + k_spin_unlock(&mgr->lock, key); + + struct queued_operation *op; + + SYS_SLIST_FOR_EACH_CONTAINER(&ops, op, node) { + finalize_and_notify(mgr, op, -ENODEV); + } +} + +static void start_callback(struct onoff_service *srv, + struct onoff_client *cli, + void *user_data, + int res) +{ + settle_onoff(user_data, ST_STARTING, res); +} + +static void stop_callback(struct onoff_service *srv, + + struct onoff_client *cli, + void *user_data, + int res) +{ + settle_onoff(user_data, ST_STOPPING, res); +} + +static void stop_and_unlock(struct queued_operation_manager *mgr, + k_spinlock_key_t key) +{ + __ASSERT_NO_MSG(mgr->state == ST_IDLE); + + if (mgr->onoff == NULL) { + mgr->state = ST_OFF; + k_spin_unlock(&mgr->lock, key); + return; + } + + mgr->state = ST_STOPPING; + + struct onoff_service *onoff = mgr->onoff; + + k_spin_unlock(&mgr->lock, key); + + struct onoff_client *cli = &mgr->onoff_client; + int rc = 0; + + onoff_client_init_callback(cli, stop_callback, mgr); + rc = onoff_release(onoff, cli); + + if (rc < 0) { + settle_onoff(mgr, ST_STOPPING, rc); + } +} + +static void select_next_and_unlock(struct queued_operation_manager *mgr, + k_spinlock_key_t key) +{ + bool loop = false; + + do { + struct queued_operation *op = NULL; + sys_snode_t *node = sys_slist_get(&mgr->operations); + u32_t state = mgr->state; + + __ASSERT_NO_MSG((state == ST_IDLE) + || (state == ST_FINALIZING)); + loop = false; + if (node) { + op = CONTAINER_OF(node, struct queued_operation, node); + mgr->state = ST_NOTIFYING; + mgr->current = op; + + k_spin_unlock(&mgr->lock, key); + + /* Notify the service, then check everything again + * because the operation might have completed or the + * queue might have changed while we were unlocked. + */ + mgr->vtable->process(mgr, op); + + /* Update the state to one of IDLE, PROCESSING, or + * leave it FINALIZING; loop if something needs to be + * done. + */ + key = k_spin_lock(&mgr->lock); + + state = mgr->state; + + /* If an operation finalized during notification we need to + * reselect because finalization couldn't do that, otherwise + * it's still running. + */ + loop = (state == ST_FINALIZING); + if (!loop) { + __ASSERT_NO_MSG(state == ST_NOTIFYING); + mgr->state = ST_PROCESSING; + } + } else { + __ASSERT_NO_MSG(state == ST_FINALIZING); + mgr->state = ST_IDLE; + mgr->current = op; + } + + if (!loop) { + /* All done, release lock and exit */ + if (mgr->state == ST_IDLE) { + stop_and_unlock(mgr, key); + } else { + k_spin_unlock(&mgr->lock, key); + } + } + } while (loop); +} + +static void start_and_unlock(struct queued_operation_manager *mgr, + k_spinlock_key_t key) +{ + struct onoff_service *onoff = mgr->onoff; + struct onoff_client *cli = &mgr->onoff_client; + int rv = 0; + + if (onoff == NULL) { + trivial_start_and_unlock(mgr, key); + return; + } + + mgr->state = ST_STARTING; + k_spin_unlock(&mgr->lock, key); + + onoff_client_init_callback(cli, start_callback, mgr); + rv = onoff_request(onoff, cli); + + if (rv >= 0) { + /* Success. Replace the request result value with a + * fixed success value, and lock so we can keep + * going at the call site. + */ + rv = 0; + } else { + /* Failure, record the error state */ + settle_onoff(mgr, ST_STARTING, rv); + } +} + +int queued_operation_submit(struct queued_operation_manager *mgr, + struct queued_operation *op, + int priority) +{ + int validate_rv = -ENOTSUP; + int rv = 0; + + __ASSERT_NO_MSG(mgr != NULL); + __ASSERT_NO_MSG(mgr->vtable != NULL); + __ASSERT_NO_MSG(mgr->vtable->process != NULL); + __ASSERT_NO_MSG(op != NULL); + + /* Validation is optional; if present, use it. */ + if (mgr->vtable->validate) { + validate_rv = mgr->vtable->validate(mgr, op); + rv = validate_rv; + } + + /* Set the priority, checking whether it's in range. */ + if (rv >= 0) { + rv = op_set_priority(op, priority); + } + + /* Reject callback notifications without translation + * function. + */ + if ((rv >= 0) + && async_notify_uses_callback(&op->notify) + && (mgr->vtable->callback == NULL)) { + rv = -ENOTSUP; + } + + if (rv < 0) { + goto out; + } + + k_spinlock_key_t key = k_spin_lock(&mgr->lock); + sys_slist_t *list = &mgr->operations; + u32_t state = mgr->state; + + if (state == ST_ERROR) { + rv = -ENODEV; + goto out; + } + + if (priority == QUEUED_OPERATION_PRIORITY_PREPEND) { + sys_slist_prepend(list, &op->node); + } else if (priority == QUEUED_OPERATION_PRIORITY_APPEND) { + sys_slist_append(list, &op->node); + } else { + struct queued_operation *prev = NULL; + struct queued_operation *tmp; + + SYS_SLIST_FOR_EACH_CONTAINER(list, tmp, node) { + if (priority < op_get_priority(tmp)) { + break; + } + prev = tmp; + } + + if (prev == NULL) { + sys_slist_prepend(list, &op->node); + } else { + sys_slist_insert(list, &prev->node, &op->node); + } + } + + if (state == ST_OFF) { + start_and_unlock(mgr, key); + } else { + __ASSERT_NO_MSG(state != ST_IDLE); + k_spin_unlock(&mgr->lock, key); + } + +out: + /* Preserve a service-specific success code on success */ + if ((rv >= 0) && (validate_rv >= 0)) { + rv = validate_rv; + } + + return rv; +} + +void queued_operation_finalize(struct queued_operation_manager *mgr, + int res) +{ + __ASSERT_NO_MSG(mgr != NULL); + + k_spinlock_key_t key = k_spin_lock(&mgr->lock); + struct queued_operation *op = mgr->current; + u32_t state = mgr->state; + bool processing = (state == ST_PROCESSING); + + __ASSERT_NO_MSG(op != NULL); + __ASSERT_NO_MSG((state == ST_NOTIFYING) + || (state == ST_PROCESSING)); + + mgr->state = ST_FINALIZING; + + k_spin_unlock(&mgr->lock, key); + + finalize_and_notify(mgr, op, res); + + /* If we were processing we need to reselect; if we were + * notifying we'll reselect when the notification completes. + */ + if (processing) { + select_next_and_unlock(mgr, k_spin_lock(&mgr->lock)); + } +} + +int queued_operation_cancel(struct queued_operation_manager *mgr, + struct queued_operation *op) +{ + __ASSERT_NO_MSG(mgr != NULL); + __ASSERT_NO_MSG(op != NULL); + + int rv = 0; + k_spinlock_key_t key = k_spin_lock(&mgr->lock); + + if (op == mgr->current) { + rv = -EINPROGRESS; + } else if (!sys_slist_find_and_remove(&mgr->operations, &op->node)) { + rv = -EINVAL; + } + + k_spin_unlock(&mgr->lock, key); + + if (rv == 0) { + finalize_and_notify(mgr, op, -ECANCELED); + } + + return rv; +} diff --git a/tests/lib/async_notify/CMakeLists.txt b/tests/lib/async_notify/CMakeLists.txt new file mode 100644 index 0000000000000..4e594898d81ee --- /dev/null +++ b/tests/lib/async_notify/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(async_notify) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/lib/async_notify/prj.conf b/tests/lib/async_notify/prj.conf new file mode 100644 index 0000000000000..1948aaa649a4e --- /dev/null +++ b/tests/lib/async_notify/prj.conf @@ -0,0 +1,2 @@ +CONFIG_POLL=y +CONFIG_ZTEST=y diff --git a/tests/lib/async_notify/src/main.c b/tests/lib/async_notify/src/main.c new file mode 100644 index 0000000000000..e5d521050c5d9 --- /dev/null +++ b/tests/lib/async_notify/src/main.c @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +static u32_t get_extflags(const struct async_notify *anp) +{ + u32_t flags = anp->flags & ASYNC_NOTIFY_EXTENSION_MASK; + + return flags >> ASYNC_NOTIFY_EXTENSION_POS; +} + +static void set_extflags(struct async_notify *anp, + u32_t flags) +{ + anp->flags = (anp->flags & ~ASYNC_NOTIFY_EXTENSION_MASK) + | (flags << ASYNC_NOTIFY_EXTENSION_POS); +} + +static void callback(struct async_notify *anp, + int *resp) +{ + zassert_equal(async_notify_fetch_result(anp, resp), 0, + "failed callback fetch"); +} + +static void test_validate(void) +{ + struct async_notify notify = { + .flags = 0, + }; + + zassert_equal(async_notify_validate(NULL), -EINVAL, + "accepted null pointer"); + zassert_equal(async_notify_validate(¬ify), -EINVAL, + "accepted bad method"); +} + + +static void test_spinwait(void) +{ + int rc; + int set_res = 423; + int res; + async_notify_generic_callback cb; + struct async_notify notify; + u32_t xflags = 0x1234; + + memset(¬ify, 0xac, sizeof(notify)); + rc = async_notify_validate(¬ify); + zassert_equal(rc, -EINVAL, + "invalid not diagnosed"); + + async_notify_init_spinwait(¬ify); + rc = async_notify_validate(¬ify); + zassert_equal(rc, 0, + "init_spinwait invalid"); + + zassert_false(async_notify_uses_callback(¬ify), + "uses callback"); + + zassert_equal(notify.flags, ASYNC_NOTIFY_METHOD_SPINWAIT, + "flags mismatch"); + + set_extflags(¬ify, xflags); + zassert_equal(async_notify_get_method(¬ify), + ASYNC_NOTIFY_METHOD_SPINWAIT, + "method corrupted"); + zassert_equal(get_extflags(¬ify), xflags, + "xflags extract failed"); + + rc = async_notify_fetch_result(¬ify, &res); + zassert_equal(rc, -EAGAIN, + "spinwait ready too soon"); + + zassert_not_equal(notify.flags, 0, + "flags cleared"); + + cb = async_notify_finalize(¬ify, set_res); + zassert_equal(cb, (async_notify_generic_callback)NULL, + "callback not null"); + zassert_equal(notify.flags, 0, + "flags not cleared"); + + rc = async_notify_fetch_result(¬ify, &res); + zassert_equal(rc, 0, + "spinwait not ready"); + zassert_equal(res, set_res, + "result not set"); +} + +static void test_signal(void) +{ +#ifdef CONFIG_POLL + int rc; + int set_res = 423; + int res; + struct k_poll_signal sig; + async_notify_generic_callback cb; + struct async_notify notify; + u32_t xflags = 0x1234; + + memset(¬ify, 0xac, sizeof(notify)); + rc = async_notify_validate(¬ify); + zassert_equal(rc, -EINVAL, + "invalid not diagnosed"); + + k_poll_signal_init(&sig); + k_poll_signal_check(&sig, &rc, &res); + zassert_equal(rc, 0, + "signal set"); + + async_notify_init_signal(¬ify, &sig); + notify.method.signal = NULL; + rc = async_notify_validate(¬ify); + zassert_equal(rc, -EINVAL, + "null signal not invalid"); + + memset(¬ify, 0xac, sizeof(notify)); + async_notify_init_signal(¬ify, &sig); + rc = async_notify_validate(¬ify); + zassert_equal(rc, 0, + "init_spinwait invalid"); + + zassert_false(async_notify_uses_callback(¬ify), + "uses callback"); + + zassert_equal(notify.flags, ASYNC_NOTIFY_METHOD_SIGNAL, + "flags mismatch"); + zassert_equal(notify.method.signal, &sig, + "signal pointer mismatch"); + + set_extflags(¬ify, xflags); + zassert_equal(async_notify_get_method(¬ify), + ASYNC_NOTIFY_METHOD_SIGNAL, + "method corrupted"); + zassert_equal(get_extflags(¬ify), xflags, + "xflags extract failed"); + + rc = async_notify_fetch_result(¬ify, &res); + zassert_equal(rc, -EAGAIN, + "spinwait ready too soon"); + + zassert_not_equal(notify.flags, 0, + "flags cleared"); + + cb = async_notify_finalize(¬ify, set_res); + zassert_equal(cb, (async_notify_generic_callback)NULL, + "callback not null"); + zassert_equal(notify.flags, 0, + "flags not cleared"); + k_poll_signal_check(&sig, &rc, &res); + zassert_equal(rc, 1, + "signal not set"); + zassert_equal(res, set_res, + "signal result wrong"); + + rc = async_notify_fetch_result(¬ify, &res); + zassert_equal(rc, 0, + "signal not ready"); + zassert_equal(res, set_res, + "result not set"); +#endif /* CONFIG_POLL */ +} + +static void test_callback(void) +{ + int rc; + int set_res = 423; + int res; + async_notify_generic_callback cb; + struct async_notify notify; + u32_t xflags = 0x8765432; + + memset(¬ify, 0xac, sizeof(notify)); + rc = async_notify_validate(¬ify); + zassert_equal(rc, -EINVAL, + "invalid not diagnosed"); + + async_notify_init_callback(¬ify, callback); + notify.method.callback = NULL; + rc = async_notify_validate(¬ify); + zassert_equal(rc, -EINVAL, + "null callback not invalid"); + + memset(¬ify, 0xac, sizeof(notify)); + async_notify_init_callback(¬ify, callback); + rc = async_notify_validate(¬ify); + zassert_equal(rc, 0, + "init_spinwait invalid"); + + zassert_true(async_notify_uses_callback(¬ify), + "not using callback"); + + zassert_equal(notify.flags, ASYNC_NOTIFY_METHOD_CALLBACK, + "flags mismatch"); + zassert_equal(notify.method.callback, + (async_notify_generic_callback)callback, + "callback mismatch"); + + set_extflags(¬ify, xflags); + zassert_equal(async_notify_get_method(¬ify), + ASYNC_NOTIFY_METHOD_CALLBACK, + "method corrupted"); + zassert_equal(get_extflags(¬ify), xflags, + "xflags extract failed"); + + rc = async_notify_fetch_result(¬ify, &res); + zassert_equal(rc, -EAGAIN, + "callback ready too soon"); + + zassert_not_equal(notify.flags, 0, + "flags cleared"); + + cb = async_notify_finalize(¬ify, set_res); + zassert_equal(cb, (async_notify_generic_callback)callback, + "callback wrong"); + zassert_equal(notify.flags, 0, + "flags not cleared"); + + res = ~set_res; + ((async_notify_generic_callback)cb)(¬ify, &res); + zassert_equal(res, set_res, + "result not set"); +} + +void test_main(void) +{ + ztest_test_suite(async_notify_api, + ztest_unit_test(test_validate), + ztest_unit_test(test_spinwait), + ztest_unit_test(test_signal), + ztest_unit_test(test_callback)); + ztest_run_test_suite(async_notify_api); +} diff --git a/tests/lib/async_notify/testcase.yaml b/tests/lib/async_notify/testcase.yaml new file mode 100644 index 0000000000000..830f00ea2160a --- /dev/null +++ b/tests/lib/async_notify/testcase.yaml @@ -0,0 +1,3 @@ +tests: + libraries.async_notify: + tags: async_notify diff --git a/tests/lib/onoff/src/main.c b/tests/lib/onoff/src/main.c index 0cf2de278dfb9..03088118b5fde 100644 --- a/tests/lib/onoff/src/main.c +++ b/tests/lib/onoff/src/main.c @@ -167,44 +167,53 @@ static void test_service_init_validation(void) { int rc; struct onoff_service srv; + const struct onoff_service_transitions null_transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(NULL, NULL, NULL, 0); + const struct onoff_service_transitions start_transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, NULL, NULL, 0); + const struct onoff_service_transitions stop_transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(NULL, stop, NULL, 0); + struct onoff_service_transitions start_stop_transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, NULL, 0); + const struct onoff_service_transitions all_transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, reset, + ONOFF_SERVICE_START_SLEEPS); clear_transit(); - rc = onoff_service_init(NULL, NULL, NULL, NULL, 0); + rc = onoff_service_init(NULL, &null_transitions); zassert_equal(rc, -EINVAL, "init null srv %d", rc); - rc = onoff_service_init(&srv, NULL, NULL, NULL, 0); + rc = onoff_service_init(&srv, &null_transitions); zassert_equal(rc, -EINVAL, "init null transit %d", rc); - rc = onoff_service_init(&srv, start, NULL, NULL, 0); + rc = onoff_service_init(&srv, &start_transitions); zassert_equal(rc, -EINVAL, "init null stop %d", rc); - rc = onoff_service_init(&srv, NULL, stop, NULL, 0); + rc = onoff_service_init(&srv, &stop_transitions); zassert_equal(rc, -EINVAL, "init null start %d", rc); - rc = onoff_service_init(&srv, start, stop, NULL, - ONOFF_SERVICE_INTERNAL_BASE); + start_stop_transitions.flags |= ONOFF_SERVICE_INTERNAL_BASE; + rc = onoff_service_init(&srv, &start_stop_transitions); zassert_equal(rc, -EINVAL, "init bad flags %d", rc); - u32_t flags = ONOFF_SERVICE_START_SLEEPS; - memset(&srv, 0xA5, sizeof(srv)); zassert_false(sys_slist_is_empty(&srv.clients), "slist empty"); - rc = onoff_service_init(&srv, start, stop, reset, flags); + rc = onoff_service_init(&srv, &all_transitions); zassert_equal(rc, 0, "init good %d", rc); - zassert_equal(srv.start, start, + zassert_equal(srv.transitions->start, start, "init start mismatch"); - zassert_equal(srv.stop, stop, + zassert_equal(srv.transitions->stop, stop, "init stop mismatch"); - zassert_equal(srv.reset, reset, + zassert_equal(srv.transitions->reset, reset, "init reset mismatch"); zassert_equal(srv.flags, ONOFF_SERVICE_START_SLEEPS, "init flags mismatch"); @@ -224,7 +233,7 @@ static void test_client_init_validation(void) onoff_client_init_spinwait(&cli); zassert_equal(z_snode_next_peek(&cli.node), NULL, "cli node mismatch"); - zassert_equal(cli.flags, ONOFF_CLIENT_NOTIFY_SPINWAIT, + zassert_equal(cli.notify.flags, ASYNC_NOTIFY_METHOD_SPINWAIT, "cli spinwait flags"); struct k_poll_signal sig; @@ -233,20 +242,20 @@ static void test_client_init_validation(void) onoff_client_init_signal(&cli, &sig); zassert_equal(z_snode_next_peek(&cli.node), NULL, "cli signal node"); - zassert_equal(cli.flags, ONOFF_CLIENT_NOTIFY_SIGNAL, + zassert_equal(cli.notify.flags, ASYNC_NOTIFY_METHOD_SIGNAL, "cli signal flags"); - zassert_equal(cli.async.signal, &sig, + zassert_equal(cli.notify.method.signal, &sig, "cli signal async"); memset(&cli, 0xA5, sizeof(cli)); onoff_client_init_callback(&cli, callback, &sig); zassert_equal(z_snode_next_peek(&cli.node), NULL, "cli callback node"); - zassert_equal(cli.flags, ONOFF_CLIENT_NOTIFY_CALLBACK, + zassert_equal(cli.notify.flags, ASYNC_NOTIFY_METHOD_CALLBACK, "cli callback flags"); - zassert_equal(cli.async.callback.handler, callback, + zassert_equal(cli.notify.method.callback, callback, "cli callback handler"); - zassert_equal(cli.async.callback.user_data, &sig, + zassert_equal(cli.user_data, &sig, "cli callback user_data"); } @@ -256,6 +265,8 @@ static void test_validate_args(void) struct onoff_service srv; struct k_poll_signal sig; struct onoff_client cli; + const struct onoff_service_transitions transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, NULL, 0); clear_transit(); @@ -263,7 +274,7 @@ static void test_validate_args(void) * release, and reset; test it through the request API. */ - rc = onoff_service_init(&srv, start, stop, NULL, 0); + rc = onoff_service_init(&srv, &transitions); zassert_equal(rc, 0, "service init"); @@ -294,7 +305,7 @@ static void test_validate_args(void) "validate req cli flags"); init_spinwait(&cli); - cli.flags = ONOFF_CLIENT_NOTIFY_INVALID; + cli.notify.flags = ASYNC_NOTIFY_METHOD_COMPLETED; rc = onoff_request(&srv, &cli); zassert_equal(rc, -EINVAL, "validate req cli mode"); @@ -304,7 +315,7 @@ static void test_validate_args(void) zassert_equal(rc, 0, "validate req cli signal: %d", rc); init_notify_sig(&cli, &sig); - cli.async.signal = NULL; + cli.notify.method.signal = NULL; rc = onoff_request(&srv, &cli); zassert_equal(rc, -EINVAL, "validate req cli signal null"); @@ -315,7 +326,7 @@ static void test_validate_args(void) "validate req cli callback"); init_notify_cb(&cli); - cli.async.callback.handler = NULL; + cli.notify.method.callback = NULL; rc = onoff_request(&srv, &cli); zassert_equal(rc, -EINVAL, "validate req cli callback null"); @@ -334,17 +345,21 @@ static void test_reset(void) struct onoff_client cli; unsigned int signalled = 0; int result = 0; + const struct onoff_service_transitions transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, NULL, 0); + struct onoff_service_transitions transitions_with_reset = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, reset, 0); clear_transit(); - rc = onoff_service_init(&srv, start, stop, NULL, 0); + rc = onoff_service_init(&srv, &transitions); zassert_equal(rc, 0, "service init"); rc = onoff_service_reset(&srv, &cli); zassert_equal(rc, -ENOTSUP, "reset: %d", rc); - rc = onoff_service_init(&srv, start, stop, reset, 0); + rc = onoff_service_init(&srv, &transitions_with_reset); zassert_equal(rc, 0, "service init"); @@ -423,8 +438,8 @@ static void test_reset(void) zassert_false(onoff_service_has_error(&srv), "has error"); - rc = onoff_service_init(&srv, start, stop, reset, - ONOFF_SERVICE_RESET_SLEEPS); + transitions_with_reset.flags |= ONOFF_SERVICE_RESET_SLEEPS; + rc = onoff_service_init(&srv, &transitions_with_reset); zassert_equal(rc, 0, "service init"); start_state.retval = -23; @@ -460,10 +475,12 @@ static void test_request(void) { int rc; struct onoff_service srv; + struct onoff_service_transitions transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, reset, 0); clear_transit(); - rc = onoff_service_init(&srv, start, stop, reset, 0); + rc = onoff_service_init(&srv, &transitions); zassert_equal(rc, 0, "service init"); @@ -546,8 +563,8 @@ static void test_request(void) "has error"); /* Diagnose a no-wait delayed start */ - rc = onoff_service_init(&srv, start, stop, reset, - ONOFF_SERVICE_START_SLEEPS); + transitions.flags |= ONOFF_SERVICE_START_SLEEPS; + rc = onoff_service_init(&srv, &transitions); zassert_equal(rc, 0, "service init"); start_state.async = true; @@ -578,10 +595,12 @@ static void test_sync(void) { int rc; struct onoff_service srv; + const struct onoff_service_transitions transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, reset, 0); clear_transit(); - rc = onoff_service_init(&srv, start, stop, reset, 0); + rc = onoff_service_init(&srv, &transitions); zassert_equal(rc, 0, "service init"); @@ -628,6 +647,9 @@ static void test_async(void) struct onoff_client cli[2]; unsigned int signalled = 0; int result = 0; + const struct onoff_service_transitions transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, reset, + ONOFF_SERVICE_START_SLEEPS | ONOFF_SERVICE_STOP_SLEEPS); clear_transit(); start_state.async = true; @@ -635,9 +657,7 @@ static void test_async(void) stop_state.async = true; stop_state.retval = 17; - rc = onoff_service_init(&srv, start, stop, reset, - ONOFF_SERVICE_START_SLEEPS - | ONOFF_SERVICE_STOP_SLEEPS); + rc = onoff_service_init(&srv, &transitions); zassert_equal(rc, 0, "service init"); @@ -802,7 +822,7 @@ static void test_async(void) "rel to-off: %d", rc); /* Finalize queued start, gets us to on */ - cli[0].result = 1 + start_state.retval; + cli[0].notify.result = 1 + start_state.retval; zassert_equal(cli_result(&cli[0]), -EAGAIN, "fetch failed"); zassert_false(start_state.notify == NULL, @@ -820,14 +840,16 @@ static void test_half_sync(void) struct onoff_service srv; struct k_poll_signal sig; struct onoff_client cli; + const struct onoff_service_transitions transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, NULL, + ONOFF_SERVICE_STOP_SLEEPS); clear_transit(); start_state.retval = 23; stop_state.async = true; stop_state.retval = 17; - rc = onoff_service_init(&srv, start, stop, NULL, - ONOFF_SERVICE_STOP_SLEEPS); + rc = onoff_service_init(&srv, &transitions); zassert_equal(rc, 0, "service init"); @@ -876,6 +898,9 @@ static void test_cancel_request_waits(void) struct onoff_service srv; struct k_poll_signal sig; struct onoff_client cli; + const struct onoff_service_transitions transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, NULL, + ONOFF_SERVICE_START_SLEEPS | ONOFF_SERVICE_STOP_SLEEPS); clear_transit(); start_state.async = true; @@ -883,9 +908,7 @@ static void test_cancel_request_waits(void) stop_state.async = true; stop_state.retval = 31; - rc = onoff_service_init(&srv, start, stop, NULL, - ONOFF_SERVICE_START_SLEEPS - | ONOFF_SERVICE_STOP_SLEEPS); + rc = onoff_service_init(&srv, &transitions); zassert_equal(rc, 0, "service init"); @@ -972,14 +995,16 @@ static void test_cancel_request_ok(void) struct onoff_service srv; struct k_poll_signal sig; struct onoff_client cli; + const struct onoff_service_transitions transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, NULL, + ONOFF_SERVICE_START_SLEEPS); clear_transit(); start_state.async = true; start_state.retval = 14; stop_state.retval = 31; - rc = onoff_service_init(&srv, start, stop, NULL, - ONOFF_SERVICE_START_SLEEPS); + rc = onoff_service_init(&srv, &transitions); zassert_equal(rc, 0, "service init"); @@ -1027,6 +1052,9 @@ static void test_blocked_restart(void) int result; struct k_poll_signal sig[2]; struct onoff_client cli[2]; + const struct onoff_service_transitions transitions = + ONOFF_SERVICE_TRANSITIONS_INITIALIZER(start, stop, NULL, + ONOFF_SERVICE_START_SLEEPS | ONOFF_SERVICE_STOP_SLEEPS); clear_transit(); start_state.async = true; @@ -1034,9 +1062,7 @@ static void test_blocked_restart(void) stop_state.async = true; stop_state.retval = 31; - rc = onoff_service_init(&srv, start, stop, NULL, - ONOFF_SERVICE_START_SLEEPS - | ONOFF_SERVICE_STOP_SLEEPS); + rc = onoff_service_init(&srv, &transitions); zassert_equal(rc, 0, "service init"); @@ -1101,19 +1127,14 @@ static void test_blocked_restart(void) static void test_cancel_release(void) { + ONOFF_SERVICE_DEFINE(srv, start, stop, NULL, ONOFF_SERVICE_STOP_SLEEPS); int rc; - struct onoff_service srv; clear_transit(); start_state.retval = 16; stop_state.async = true; stop_state.retval = 94; - rc = onoff_service_init(&srv, start, stop, NULL, - ONOFF_SERVICE_STOP_SLEEPS); - zassert_equal(rc, 0, - "service init"); - init_spinwait(&spinwait_cli); rc = onoff_request(&srv, &spinwait_cli); zassert_true(rc > 0, diff --git a/tests/lib/queued_operation/CMakeLists.txt b/tests/lib/queued_operation/CMakeLists.txt new file mode 100644 index 0000000000000..90b0fb17ea108 --- /dev/null +++ b/tests/lib/queued_operation/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(queued_operation) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/lib/queued_operation/prj.conf b/tests/lib/queued_operation/prj.conf new file mode 100644 index 0000000000000..b008d47339881 --- /dev/null +++ b/tests/lib/queued_operation/prj.conf @@ -0,0 +1,3 @@ +CONFIG_POLL=y +CONFIG_ZTEST=y +CONFIG_ZTEST_STACKSIZE=2048 diff --git a/tests/lib/queued_operation/src/main.c b/tests/lib/queued_operation/src/main.c new file mode 100644 index 0000000000000..b5ca0ae992adc --- /dev/null +++ b/tests/lib/queued_operation/src/main.c @@ -0,0 +1,960 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +struct service; + +struct operation { + struct queued_operation operation; + void (*callback)(struct service *sp, + struct operation *op, + void *ud); + void *user_data; +}; + +struct service { + /* State of the manager */ + struct queued_operation_manager manager; + + /* State for an on-off service optionally used by the manager. */ + struct onoff_service onoff; + + /* Value to return from basic_request handler. */ + int onoff_request_rv; + + /* Value to return from basic_release handler. */ + int onoff_release_rv; + + /* Notifier to use when async_onoff is set. */ + onoff_service_notify_fn onoff_notify; + /* The current operation cast for this service type. Null if + * service is idle. + */ + struct operation *current; + + /* Value to return from service_impl_validate() */ + int validate_rv; + + /* Value to return from service_impl_validate() + * + * This is incremented before each synchronous finalization by + * service_impl_callback. + */ + int process_rv; + + /* Parameters passed to test_callback */ + struct operation *callback_op; + int callback_res; + + /* Count of process submissions since reset. */ + size_t process_cnt; + + /* Test-specific data associated with the service. */ + void *data; + + /* If set defer notification of onoff operation. + * + * The callback to invoke will be stored in onoff_notify. + */ + bool async_onoff; + + /* If set inhibit synchronous completion. */ + bool async; + + /* Set to indicate that the lass process() call provided an + * operation. + */ + bool active; +}; + +static void basic_start(struct onoff_service *srv, + onoff_service_notify_fn notify) +{ + struct service *sp = CONTAINER_OF(srv, struct service, onoff); + + if (sp->async_onoff) { + sp->onoff_notify = notify; + } else { + sp->active = sp->onoff_request_rv >= 0; + notify(srv, sp->onoff_request_rv); + } +} + +static void basic_stop(struct onoff_service *srv, + onoff_service_notify_fn notify) +{ + struct service *sp = CONTAINER_OF(srv, struct service, onoff); + + if (sp->async_onoff) { + sp->onoff_notify = notify; + } else { + sp->active = false; + notify(srv, sp->onoff_release_rv); + } +} + +static struct onoff_service_transitions const basic_onoff_transitions = { + .start = basic_start, + .stop = basic_stop, + .flags = 0, +}; + +typedef void (*service_callback)(struct service *sp, + struct operation *op, + int res); + +static void test_callback(struct service *sp, + struct operation *op, + int res) +{ + sp->callback_op = op; + sp->callback_res = res; + if (op->callback) { + op->callback(sp, op, op->user_data); + } +} + +static inline void operation_init_spinwait(struct operation *op) +{ + *op = (struct operation){}; + async_notify_init_spinwait(&op->operation.notify); +} + +static inline void operation_init_signal(struct operation *op, + struct k_poll_signal *sigp) +{ + *op = (struct operation){}; + async_notify_init_signal(&op->operation.notify, sigp); +} + +static inline void operation_init_callback(struct operation *op, + service_callback handler) +{ + *op = (struct operation){}; + async_notify_init_callback(&op->operation.notify, + (async_notify_generic_callback)handler); +} + +static int service_submit(struct service *sp, + struct operation *op, + int priority) +{ + return queued_operation_submit(&sp->manager, &op->operation, priority); +} + +static int service_cancel(struct service *sp, + struct operation *op) +{ + return queued_operation_cancel(&sp->manager, &op->operation); +} + +static int service_impl_validate(struct queued_operation_manager *mgr, + struct queued_operation *op) +{ + struct service *sp = CONTAINER_OF(mgr, struct service, manager); + + return sp->validate_rv; +} + +static void service_impl_callback(struct queued_operation_manager *mgr, + struct queued_operation *op, + async_notify_generic_callback cb) +{ + service_callback handler = (service_callback)cb; + struct service *sp = CONTAINER_OF(mgr, struct service, manager); + struct operation *sop = CONTAINER_OF(op, struct operation, operation); + int res = -EINPROGRESS; + + zassert_equal(queued_operation_fetch_result(op, &res), 0, + "callback before finalized"); + handler(sp, sop, res); +} + +/* Split out finalization to support async testing. */ +static void service_finalize(struct service *sp, + int res) +{ + struct queued_operation *op = &sp->current->operation; + + sp->current = NULL; + (void)op; + queued_operation_finalize(&sp->manager, res); +} + +static void service_impl_process(struct queued_operation_manager *mgr, + struct queued_operation *op) +{ + struct service *sp = CONTAINER_OF(mgr, struct service, manager); + + zassert_equal(sp->current, NULL, + "process collision"); + + sp->process_cnt++; + sp->active = (op != NULL); + if (sp->active) { + sp->current = CONTAINER_OF(op, struct operation, operation); + if (!sp->async) { + service_finalize(sp, ++sp->process_rv); + } + } +} + +static struct queued_operation_functions const service_vtable = { + .validate = service_impl_validate, + .callback = service_impl_callback, + .process = service_impl_process, +}; +/* Live copy, mutated for testing. */ +static struct queued_operation_functions vtable; + +static struct service service = { + .manager = QUEUED_OPERATION_MANAGER_INITIALIZER(&vtable, &service.onoff), + .onoff = { + .transitions = &basic_onoff_transitions, + }, +}; + +static void reset_service(bool onoff) +{ + vtable = service_vtable; + service = (struct service){ + .manager = QUEUED_OPERATION_MANAGER_INITIALIZER(&vtable, &service.onoff), + .onoff = { + .transitions = &basic_onoff_transitions, + }, + }; + + if (!onoff) { + service.manager.onoff = NULL; + } +} + +static void replace_service_onoff(struct onoff_service_transitions *transitions) +{ + service.onoff.transitions = transitions; +} + +static void test_notification_spinwait(void) +{ + struct operation operation; + struct operation *op = &operation; + struct async_notify *np = &op->operation.notify; + int res = 0; + int rc = 0; + + reset_service(true); + + operation_init_spinwait(&operation); + zassert_equal(async_notify_fetch_result(np, &res), -EAGAIN, + "failed spinwait unfinalized"); + + rc = service_submit(&service, op, 0); + zassert_equal(rc, service.validate_rv, + "submit spinwait failed: %d != %d", rc, + service.validate_rv); + zassert_equal(async_notify_fetch_result(np, &res), 0, + "failed spinwait fetch"); + zassert_equal(res, service.process_rv, + "failed spinwait result"); + + zassert_false(service.active, "service not idled"); +} + +static void test_notification_signal(void) +{ + struct operation operation; + struct operation *op = &operation; + struct async_notify *np = &op->operation.notify; + struct k_poll_signal sig; + unsigned int signaled; + int res = 0; + int rc = 0; + + reset_service(false); + + k_poll_signal_init(&sig); + operation_init_signal(op, &sig); + zassert_equal(async_notify_fetch_result(np, &res), -EAGAIN, + "failed signal unfinalized"); + k_poll_signal_check(&sig, &signaled, &res); + zassert_equal(signaled, 0, + "failed signal unsignaled"); + + service.process_rv = 23; + rc = service_submit(&service, op, 0); + zassert_equal(rc, 0, + "submit signal failed: %d", rc); + zassert_equal(async_notify_fetch_result(np, &res), 0, + "failed signal fetch"); + zassert_equal(res, service.process_rv, + "failed signal result"); + k_poll_signal_check(&sig, &signaled, &res); + zassert_equal(signaled, 1, + "failed signal signaled"); + zassert_equal(res, service.process_rv, + "failed signal signal result"); +} + +static void test_notification_callback(void) +{ + struct operation operation; + struct operation *op = &operation; + struct service *sp = &service; + struct async_notify *np = &op->operation.notify; + struct k_poll_signal sig; + int res = 0; + int rc = 0; + + reset_service(false); + + k_poll_signal_init(&sig); + operation_init_callback(op, test_callback); + zassert_equal(async_notify_fetch_result(np, &res), -EAGAIN, + "failed callback unfinalized"); + zassert_equal(sp->callback_op, NULL, + "failed callback pre-check"); + + service.process_rv = 142; + rc = service_submit(&service, op, 0); + zassert_equal(rc, 0, + "submit callback failed: %d", rc); + zassert_equal(async_notify_fetch_result(np, &res), 0, + "failed callback fetch"); + zassert_equal(res, service.process_rv, + "failed callback result"); + zassert_equal(sp->callback_op, op, + "failed callback captured op"); + zassert_equal(sp->callback_res, service.process_rv, + "failed callback captured res"); +} + +struct pri_order { + int priority; + size_t ordinal; +}; + +static void test_sync_priority(void) +{ + struct pri_order const pri_order[] = { + { 0, 0 }, /* first because it gets grabbed when submitted */ + /* rest in FIFO within priority */ + { -1, 2 }, + { 1, 4 }, + { -2, 1 }, + { 2, 6 }, + { 1, 5 }, + { 0, 3 }, + }; + struct operation operation[ARRAY_SIZE(pri_order)]; + struct async_notify *np[ARRAY_SIZE(operation)]; + int res = -EINPROGRESS; + int rc; + + /* Reset the service, and tell it to not finalize operations + * synchronously (so we can build up a queue). + */ + reset_service(false); + service.async = true; + + for (u32_t i = 0; i < ARRAY_SIZE(operation); ++i) { + operation_init_spinwait(&operation[i]); + np[i] = &operation[i].operation.notify; + rc = service_submit(&service, &operation[i], pri_order[i].priority); + zassert_equal(rc, 0, + "submit op%u failed: %d", i, rc); + zassert_equal(async_notify_fetch_result(np[i], &res), -EAGAIN, + "op%u finalized!", i); + } + + zassert_equal(service.current, &operation[0], + "submit op0 didn't process"); + + /* Enable synchronous finalization and kick off the first + * entry. All the others will execute immediately. + */ + service.async = false; + service_finalize(&service, service.process_rv); + + for (u32_t i = 0; i < ARRAY_SIZE(operation); ++i) { + size_t ordinal = pri_order[i].ordinal; + + zassert_equal(async_notify_fetch_result(np[i], &res), 0, + "op%u unfinalized", i); + zassert_equal(res, ordinal, + "op%u wrong order: %d != %u", i, res, ordinal); + } +} + +static void test_special_priority(void) +{ + struct pri_order const pri_order[] = { + { 0, 0 }, /* first because it gets grabbed when submitted */ + /* rest gets tricky */ + { QUEUED_OPERATION_PRIORITY_APPEND, 3}, + { INT8_MAX, 4 }, + { INT8_MIN, 2 }, + { QUEUED_OPERATION_PRIORITY_PREPEND, 1}, + { QUEUED_OPERATION_PRIORITY_APPEND, 5}, + }; + struct operation operation[ARRAY_SIZE(pri_order)]; + struct async_notify *np[ARRAY_SIZE(operation)]; + int res = -EINPROGRESS; + int rc; + + /* Reset the service, and tell it to not finalize operations + * synchronously (so we can build up a queue). + */ + reset_service(false); + service.async = true; + + for (u32_t i = 0; i < ARRAY_SIZE(operation); ++i) { + operation_init_spinwait(&operation[i]); + np[i] = &operation[i].operation.notify; + rc = service_submit(&service, &operation[i], pri_order[i].priority); + zassert_equal(rc, 0, + "submit op%u failed: %d", i, rc); + zassert_equal(async_notify_fetch_result(np[i], &res), -EAGAIN, + "op%u finalized!", i); + } + + zassert_equal(service.current, &operation[0], + "submit op0 didn't process"); + + /* Enable synchronous finalization and kick off the first + * entry. All the others will execute immediately. + */ + service.async = false; + service_finalize(&service, service.process_rv); + + for (u32_t i = 0; i < ARRAY_SIZE(operation); ++i) { + size_t ordinal = pri_order[i].ordinal; + + zassert_equal(async_notify_fetch_result(np[i], &res), 0, + "op%u unfinalized", i); + zassert_equal(res, ordinal, + "op%u wrong order: %d != %u", i, res, ordinal); + } +} + +struct delayed_submit { + struct operation *op; + int priority; +}; + +static void test_delayed_submit(struct service *sp, + struct operation *op, + void *ud) +{ + struct delayed_submit *dsp = ud; + int rc = service_submit(sp, dsp->op, dsp->priority); + + zassert_equal(rc, 0, + "delayed submit failed: %d", rc); +} + +static void test_resubmit_priority(void) +{ + struct pri_order const pri_order[] = { + { 0, 0 }, /* first because it gets grabbed when submitted */ + { 0, 2 }, /* delayed by submit of higher priority during callback */ + { -1, 1 }, /* submitted during completion of op0 */ + }; + size_t di = ARRAY_SIZE(pri_order) - 1; + struct operation operation[ARRAY_SIZE(pri_order)]; + struct async_notify *np[ARRAY_SIZE(operation)]; + int res = -EINPROGRESS; + int rc; + + /* Queue two operations, but in the callback for the first + * schedule a third operation that has higher priority. + */ + reset_service(false); + service.async = true; + + for (u32_t i = 0; i <= di; ++i) { + operation_init_callback(&operation[i], test_callback); + np[i] = &operation[i].operation.notify; + if (i < di) { + rc = service_submit(&service, &operation[i], 0); + zassert_equal(rc, 0, + "submit op%u failed: %d", i, rc); + zassert_equal(async_notify_fetch_result(np[i], &res), -EAGAIN, + "op%u finalized!", i); + } + } + + struct delayed_submit ds = { + .op = &operation[di], + .priority = pri_order[di].priority, + }; + operation[0].callback = test_delayed_submit; + operation[0].user_data = &ds; + + /* Enable synchronous finalization and kick off the first + * entry. All the others will execute immediately. + */ + service.async = false; + service_finalize(&service, service.process_rv); + + zassert_equal(service.process_cnt, ARRAY_SIZE(operation), + "not all processed once: %d != %d", + ARRAY_SIZE(operation), service.process_cnt); + + for (u32_t i = 0; i < ARRAY_SIZE(operation); ++i) { + size_t ordinal = pri_order[i].ordinal; + + zassert_equal(async_notify_fetch_result(np[i], &res), 0, + "op%u unfinalized", i); + zassert_equal(res, ordinal, + "op%u wrong order: %d != %u", i, res, ordinal); + } +} + +static void test_missing_validation(void) +{ + struct operation operation; + struct operation *op = &operation; + struct async_notify *np = &op->operation.notify; + int res = 0; + int rc = 0; + + reset_service(false); + vtable.validate = NULL; + + operation_init_spinwait(&operation); + zassert_equal(async_notify_fetch_result(np, &res), -EAGAIN, + "failed spinwait unfinalized"); + + rc = service_submit(&service, op, 0); + zassert_equal(rc, 0, + "submit spinwait failed: %d", rc); + zassert_equal(async_notify_fetch_result(np, &res), 0, + "failed spinwait fetch"); + zassert_equal(res, service.process_rv, + "failed spinwait result"); +} + +static void test_success_validation(void) +{ + struct operation operation; + struct operation *op = &operation; + struct async_notify *np = &op->operation.notify; + int res = 0; + int rc = 0; + + reset_service(false); + service.validate_rv = 57; + + operation_init_spinwait(&operation); + zassert_equal(async_notify_fetch_result(np, &res), -EAGAIN, + "failed spinwait unfinalized"); + + rc = service_submit(&service, op, 0); + zassert_equal(rc, service.validate_rv, + "submit validation did not succeed as expected: %d", rc); +} + +static void test_failed_validation(void) +{ + struct operation operation; + struct operation *op = &operation; + struct async_notify *np = &op->operation.notify; + int res = 0; + int rc = 0; + + reset_service(false); + service.validate_rv = -EINVAL; + + operation_init_spinwait(&operation); + zassert_equal(async_notify_fetch_result(np, &res), -EAGAIN, + "failed spinwait unfinalized"); + + rc = service_submit(&service, op, 0); + zassert_equal(rc, service.validate_rv, + "submit validation did not fail as expected: %d", rc); +} + +static void test_callback_validation(void) +{ + struct operation operation; + struct operation *op = &operation; + int expect = -ENOTSUP; + int rc = 0; + + reset_service(false); + vtable.callback = NULL; + + operation_init_callback(&operation, test_callback); + rc = service_submit(&service, op, 0); + zassert_equal(rc, expect, + "unsupported callback check failed: %d != %d", rc, expect); +} + +static void test_priority_validation(void) +{ + struct operation operation; + struct operation *op = &operation; + int expect = -EINVAL; + int rc = 0; + + reset_service(false); + + operation_init_callback(&operation, test_callback); + rc = service_submit(&service, op, 128); + zassert_equal(rc, expect, + "unsupported priority check failed: %d != %d", rc, expect); +} + +static void test_cancel_active(void) +{ + struct operation operation; + struct operation *op = &operation; + int expect = -EINPROGRESS; + int rc = 0; + + reset_service(false); + service.async = true; + service.validate_rv = 152; + + operation_init_spinwait(&operation); + rc = service_submit(&service, op, 0); + zassert_equal(rc, service.validate_rv, + "submit failed: %d != %d", rc, service.validate_rv); + + rc = service_cancel(&service, op); + zassert_equal(rc, expect, + "cancel failed: %d != %d", rc, expect); +} + +static void test_cancel_inactive(void) +{ + struct operation operation[2]; + struct async_notify *np[ARRAY_SIZE(operation)]; + struct operation *op1 = &operation[1]; + int res; + int rc = 0; + + reset_service(false); + service.async = true; + + /* Set up two operations, but only submit the first. */ + for (u32_t i = 0; i < ARRAY_SIZE(operation); ++i) { + operation_init_spinwait(&operation[i]); + np[i] = &operation[i].operation.notify; + if (i == 0) { + rc = service_submit(&service, &operation[i], 0); + zassert_equal(rc, service.validate_rv, + "submit failed: %d != %d", rc, service.validate_rv); + } + } + + zassert_equal(service.current, &operation[0], + "current not op0"); + + zassert_equal(async_notify_fetch_result(np[1], &res), -EAGAIN, + "op1 finalized!"); + + /* Verify attempt to cancel unsubmitted operation. */ + rc = service_cancel(&service, op1); + zassert_equal(rc, -EINVAL, + "cancel failed: %d != %d", rc, -EINVAL); + + /* Submit, then verify cancel succeeds. */ + rc = service_submit(&service, op1, 0); + zassert_equal(rc, service.validate_rv, + "submit failed: %d != %d", rc, service.validate_rv); + + zassert_equal(async_notify_fetch_result(np[1], &res), -EAGAIN, + "op1 finalized!"); + + rc = service_cancel(&service, op1); + zassert_equal(rc, 0, + "cancel failed: %d", rc); + + zassert_equal(async_notify_fetch_result(np[1], &res), 0, + "op1 NOT finalized"); + zassert_equal(res, -ECANCELED, + "op1 cancel result unexpected: %d", res); + + service.async = false; + service_finalize(&service, service.process_rv); + zassert_equal(service.process_cnt, 1, + "too many processed"); +} + +static void test_async_idle(void) +{ + struct operation operation; + + reset_service(true); + service.async = true; + service.process_rv = 142; + + operation_init_spinwait(&operation); + service_submit(&service, &operation, 0); + service_finalize(&service, service.process_rv); + zassert_false(service.active, "service not idled"); +} + +static void test_onoff_success(void) +{ + struct operation operation; + struct operation *op = &operation; + struct async_notify *np = &op->operation.notify; + int res = 0; + int rc = 0; + + reset_service(true); + service.process_rv = 23; + service.async_onoff = true; + + operation_init_spinwait(&operation); + rc = service_submit(&service, op, 0); + zassert_equal(rc, service.validate_rv, + "submit spinwait failed: %d != %d", rc, + service.validate_rv); + zassert_equal(service.process_cnt, 0, + "unexpected process"); + zassert_equal(async_notify_fetch_result(np, &res), -EAGAIN, + "unexpected fetch succeeded"); + zassert_not_equal(service.onoff_notify, NULL, + "unexpected notifier"); + + service.active = true; + service.async_onoff = false; + service.onoff_notify(&service.onoff, 0); + + zassert_equal(service.process_cnt, 1, + "unexpected process"); + + zassert_equal(async_notify_fetch_result(np, &res), 0, + "failed spinwait fetch"); + zassert_equal(res, service.process_rv, + "failed spinwait result"); + + zassert_false(service.active, "service not idled"); +} + +static void test_onoff_start_failure(void) +{ + struct operation operation[2]; + struct async_notify *np[ARRAY_SIZE(operation)]; + int onoff_res = -13; + int res = 0; + int rc = 0; + + reset_service(true); + service.async_onoff = true; + + /* Queue two operations that will block on onoff start */ + for (u32_t idx = 0; idx < ARRAY_SIZE(operation); ++idx) { + np[idx] = &operation[idx].operation.notify; + operation_init_spinwait(&operation[idx]); + + rc = service_submit(&service, &operation[idx], 0); + zassert_equal(rc, service.validate_rv, + "submit spinwait %u failed: %d != %d", idx, + rc, service.validate_rv); + } + + zassert_equal(service.process_cnt, 0, + "unexpected process"); + for (u32_t idx = 0; idx < ARRAY_SIZE(operation); ++idx) { + zassert_equal(async_notify_fetch_result(np[idx], &res), -EAGAIN, + "unexpected fetch %u succeeded", idx); + } + zassert_not_equal(service.onoff_notify, NULL, + "unexpected notifier"); + + /* Fail the start */ + service.async_onoff = false; + service.onoff_notify(&service.onoff, onoff_res); + + zassert_equal(service.process_cnt, 0, + "unexpected process"); + + for (u32_t idx = 0; idx < ARRAY_SIZE(operation); ++idx) { + zassert_equal(async_notify_fetch_result(np[idx], &res), 0, + "fetch %u failed", idx); + /* TBD: provide access to onoff result code? */ + zassert_equal(res, -ENODEV, + "fetch %u value failed", idx); + } +} + +/* Data used to submit an operation during an onoff transition. */ +struct onoff_restart_data { + struct operation *op; + int res; + bool invoked; +}; + +/* Mutate the operation list during a stop to force a restart. */ +static void onoff_restart_stop(struct onoff_service *srv, + onoff_service_notify_fn notify) +{ + struct service *sp = CONTAINER_OF(srv, struct service, onoff); + struct onoff_restart_data *dp = sp->data; + + if (dp) { + int rc = service_submit(sp, dp->op, 0); + + zassert_equal(rc, sp->validate_rv, + "submit spinwait failed: %d != %d", + rc, sp->validate_rv); + sp->data = NULL; + dp->invoked = true; + } + + basic_stop(srv, notify); +} + +static void test_onoff_restart(void) +{ + struct operation operation[2]; + struct async_notify *np[ARRAY_SIZE(operation)]; + int res = 0; + int rc = 0; + + reset_service(true); + + struct onoff_service_transitions onoff_transitions = *service.onoff.transitions; + struct onoff_restart_data stop_data = { + .op = &operation[1], + }; + + service.data = &stop_data; + onoff_transitions.stop = onoff_restart_stop; + replace_service_onoff(&onoff_transitions); + + /* Initialize two operations. The first is submitted, onoff + * starts, invokes the first, then stops. During the stop the + * second is queued, which causes a restart when the stop + * completes. + */ + for (u32_t idx = 0; idx < ARRAY_SIZE(operation); ++idx) { + np[idx] = &operation[idx].operation.notify; + operation_init_spinwait(&operation[idx]); + + } + + rc = service_submit(&service, &operation[0], 0); + zassert_equal(rc, service.validate_rv, + "submit spinwait 0 failed: %d != %d", + rc, service.validate_rv); + + zassert_equal(service.process_cnt, 2, + "unexpected process"); + + zassert_equal(stop_data.invoked, true, + "stop mock not invoked"); + + for (u32_t idx = 0; idx < ARRAY_SIZE(operation); ++idx) { + zassert_equal(async_notify_fetch_result(np[idx], &res), 0, + "failed spinwait fetch"); + zassert_equal(res, 1 + idx, + "failed spinwait result"); + } +} + +/* Mutate the operation list during a stop to force a restart. */ +static void onoff_stop_failure_stop(struct onoff_service *srv, + onoff_service_notify_fn notify) +{ + struct service *sp = CONTAINER_OF(srv, struct service, onoff); + struct onoff_restart_data *dp = sp->data; + int rc = service_submit(sp, dp->op, 0); + + zassert_equal(rc, sp->validate_rv, + "submit spinwait failed: %d != %d", + rc, sp->validate_rv); + dp->invoked = true; + sp->onoff_release_rv = dp->res; + + basic_stop(srv, notify); +} + +static void test_onoff_stop_failure(void) +{ + struct operation operation[2]; + struct async_notify *np[ARRAY_SIZE(operation)]; + int res = 0; + int rc = 0; + + reset_service(true); + + struct onoff_service_transitions onoff_transitions = *service.onoff.transitions; + struct onoff_restart_data stop_data = { + .op = &operation[1], + .res = -14, + }; + + service.data = &stop_data; + onoff_transitions.stop = onoff_stop_failure_stop; + replace_service_onoff(&onoff_transitions); + + /* Initialize two operations. The first is submitted, onoff + * starts, invokes the first, then stops. During the stop the + * second is queued, but the stop operation forces an error. + */ + for (u32_t idx = 0; idx < ARRAY_SIZE(operation); ++idx) { + np[idx] = &operation[idx].operation.notify; + operation_init_spinwait(&operation[idx]); + } + + rc = service_submit(&service, &operation[0], 0); + zassert_equal(rc, service.validate_rv, + "submit spinwait 0 failed: %d != %d", + rc, service.validate_rv); + + zassert_equal(service.process_cnt, 1, + "unexpected process"); + zassert_equal(stop_data.invoked, true, + "stop mock not invoked"); + + zassert_equal(async_notify_fetch_result(np[0], &res), 0, + "failed spinwait 0 fetch"); + zassert_equal(res, service.process_rv, + "failed spinwait 0 result"); + zassert_equal(async_notify_fetch_result(np[1], &res), 0, + "failed spinwait 1 fetch"); + zassert_equal(res, -ENODEV, + "failed spinwait 1 result"); + + /* Verify that resubmits also return failure */ + + operation_init_spinwait(&operation[0]); + rc = service_submit(&service, &operation[0], 0); + zassert_equal(rc, -ENODEV, + "failed error submit"); +} + +void test_main(void) +{ + ztest_test_suite(queued_operation_api, + ztest_unit_test(test_notification_spinwait), + ztest_unit_test(test_notification_signal), + ztest_unit_test(test_notification_callback), + ztest_unit_test(test_sync_priority), + ztest_unit_test(test_special_priority), + ztest_unit_test(test_resubmit_priority), + ztest_unit_test(test_missing_validation), + ztest_unit_test(test_success_validation), + ztest_unit_test(test_failed_validation), + ztest_unit_test(test_callback_validation), + ztest_unit_test(test_priority_validation), + ztest_unit_test(test_async_idle), + ztest_unit_test(test_cancel_active), + ztest_unit_test(test_cancel_inactive), + ztest_unit_test(test_onoff_success), + ztest_unit_test(test_onoff_start_failure), + ztest_unit_test(test_onoff_restart), + ztest_unit_test(test_onoff_stop_failure) + ); + ztest_run_test_suite(queued_operation_api); +} diff --git a/tests/lib/queued_operation/testcase.yaml b/tests/lib/queued_operation/testcase.yaml new file mode 100644 index 0000000000000..8558dedb9892b --- /dev/null +++ b/tests/lib/queued_operation/testcase.yaml @@ -0,0 +1,3 @@ +tests: + libraries.queued_operation: + tags: queued_operation timer