1 /*
2 * Copyright (c) 2019 Peter Bigot Consulting, LLC
3 * Copyright (c) 2020 Nordic Semiconductor ASA
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #ifndef ZEPHYR_INCLUDE_SYS_ONOFF_H_
9 #define ZEPHYR_INCLUDE_SYS_ONOFF_H_
10
11 #include <zephyr/kernel.h>
12 #include <zephyr/types.h>
13 #include <zephyr/sys/notify.h>
14
15 #ifdef __cplusplus
16 extern "C" {
17 #endif
18
19 /**
20 * @defgroup resource_mgmt_onoff_apis On-Off Service APIs
21 * @ingroup kernel_apis
22 * @{
23 */
24
25 /**
26 * @brief Flag indicating an error state.
27 *
28 * Error states are cleared using onoff_reset().
29 */
30 #define ONOFF_FLAG_ERROR BIT(0)
31
32 /** @cond INTERNAL_HIDDEN */
33 #define ONOFF_FLAG_ONOFF BIT(1)
34 #define ONOFF_FLAG_TRANSITION BIT(2)
35 /** @endcond */
36
37 /**
38 * @brief Mask used to isolate bits defining the service state.
39 *
40 * Mask a value with this then test for ONOFF_FLAG_ERROR to determine
41 * whether the machine has an unfixed error, or compare against
42 * ONOFF_STATE_ON, ONOFF_STATE_OFF, ONOFF_STATE_TO_ON,
43 * ONOFF_STATE_TO_OFF, or ONOFF_STATE_RESETTING.
44 */
45 #define ONOFF_STATE_MASK (ONOFF_FLAG_ERROR \
46 | ONOFF_FLAG_ONOFF \
47 | ONOFF_FLAG_TRANSITION)
48
49 /**
50 * @brief Value exposed by ONOFF_STATE_MASK when service is off.
51 */
52 #define ONOFF_STATE_OFF 0U
53
54 /**
55 * @brief Value exposed by ONOFF_STATE_MASK when service is on.
56 */
57 #define ONOFF_STATE_ON ONOFF_FLAG_ONOFF
58
59 /**
60 * @brief Value exposed by ONOFF_STATE_MASK when the service is in an
61 * error state (and not in the process of resetting its state).
62 */
63 #define ONOFF_STATE_ERROR ONOFF_FLAG_ERROR
64
65 /**
66 * @brief Value exposed by ONOFF_STATE_MASK when service is
67 * transitioning to on.
68 */
69 #define ONOFF_STATE_TO_ON (ONOFF_FLAG_TRANSITION | ONOFF_STATE_ON)
70
71 /**
72 * @brief Value exposed by ONOFF_STATE_MASK when service is
73 * transitioning to off.
74 */
75 #define ONOFF_STATE_TO_OFF (ONOFF_FLAG_TRANSITION | ONOFF_STATE_OFF)
76
77 /**
78 * @brief Value exposed by ONOFF_STATE_MASK when service is in the
79 * process of resetting.
80 */
81 #define ONOFF_STATE_RESETTING (ONOFF_FLAG_TRANSITION | ONOFF_STATE_ERROR)
82
83 /* Forward declarations */
84 struct onoff_manager;
85 struct onoff_monitor;
86
87 /**
88 * @brief Signature used to notify an on-off manager that a transition
89 * has completed.
90 *
91 * Functions of this type are passed to service-specific transition
92 * functions to be used to report the completion of the operation.
93 * The functions may be invoked from any context.
94 *
95 * @param mgr the manager for which transition was requested.
96 *
97 * @param res the result of the transition. This shall be
98 * non-negative on success, or a negative error code. If an error is
99 * indicated the service shall enter an error state.
100 */
101 typedef void (*onoff_notify_fn)(struct onoff_manager *mgr,
102 int res);
103
104 /**
105 * @brief Signature used by service implementations to effect a
106 * transition.
107 *
108 * Service definitions use two required function pointers of this type
109 * to be notified that a transition is required, and a third optional
110 * one to reset the service when it is in an error state.
111 *
112 * The start function will be called only from the off state.
113 *
114 * The stop function will be called only from the on state.
115 *
116 * The reset function (where supported) will be called only when
117 * onoff_has_error() returns true.
118 *
119 * @note All transitions functions must be isr-ok.
120 *
121 * @param mgr the manager for which transition was requested.
122 *
123 * @param notify the function to be invoked when the transition has
124 * completed. If the transition is synchronous, notify shall be
125 * invoked by the implementation before the transition function
126 * returns. Otherwise the implementation shall capture this parameter
127 * and invoke it when the transition completes.
128 */
129 typedef void (*onoff_transition_fn)(struct onoff_manager *mgr,
130 onoff_notify_fn notify);
131
132 /** @brief On-off service transition functions. */
133 struct onoff_transitions {
134 /** Function to invoke to transition the service to on. */
135 onoff_transition_fn start;
136
137 /** Function to invoke to transition the service to off. */
138 onoff_transition_fn stop;
139
140 /** Function to force the service state to reset, where
141 * supported.
142 */
143 onoff_transition_fn reset;
144 };
145
146 /**
147 * @brief State associated with an on-off manager.
148 *
149 * No fields in this structure are intended for use by service
150 * providers or clients. The state is to be initialized once, using
151 * onoff_manager_init(), when the service provider is initialized. In
152 * case of error it may be reset through the onoff_reset() API.
153 */
154 struct onoff_manager {
155 /** List of clients waiting for request or reset completion
156 * notifications.
157 */
158 sys_slist_t clients;
159
160 /** List of monitors to be notified of state changes including
161 * errors and transition completion.
162 */
163 sys_slist_t monitors;
164
165 /** Transition functions. */
166 const struct onoff_transitions *transitions;
167
168 /** Mutex protection for other fields. */
169 struct k_spinlock lock;
170
171 /** The result of the last transition. */
172 int last_res;
173
174 /** Flags identifying the service state. */
175 uint16_t flags;
176
177 /** Number of active clients for the service. */
178 uint16_t refs;
179 };
180
181 /** @brief Initializer for a onoff_transitions object.
182 *
183 * @param _start a function used to transition from off to on state.
184 *
185 * @param _stop a function used to transition from on to off state.
186 *
187 * @param _reset a function used to clear errors and force the service
188 * to an off state. Can be null.
189 */
190 #define ONOFF_TRANSITIONS_INITIALIZER(_start, _stop, _reset) { \
191 .start = (_start), \
192 .stop = (_stop), \
193 .reset = (_reset), \
194 }
195
196 /** @cond INTERNAL_HIDDEN */
197 #define ONOFF_MANAGER_INITIALIZER(_transitions) { \
198 .transitions = (_transitions), \
199 }
200 /** @endcond */
201
202 /**
203 * @brief Initialize an on-off service to off state.
204 *
205 * This function must be invoked exactly once per service instance, by
206 * the infrastructure that provides the service, and before any other
207 * on-off service API is invoked on the service.
208 *
209 * This function should never be invoked by clients of an on-off
210 * service.
211 *
212 * @param mgr the manager definition object to be initialized.
213 *
214 * @param transitions pointer to a structure providing transition
215 * functions. The referenced object must persist as long as the
216 * manager can be referenced.
217 *
218 * @retval 0 on success
219 * @retval -EINVAL if start, stop, or flags are invalid
220 */
221 int onoff_manager_init(struct onoff_manager *mgr,
222 const struct onoff_transitions *transitions);
223
224 /* Forward declaration */
225 struct onoff_client;
226
227 /**
228 * @brief Signature used to notify an on-off service client of the
229 * completion of an operation.
230 *
231 * These functions may be invoked from any context including
232 * pre-kernel, ISR, or cooperative or pre-emptible threads.
233 * Compatible functions must be isr-ok and not sleep.
234 *
235 * @param mgr the manager for which the operation was initiated. This may be
236 * null if the on-off service uses synchronous transitions.
237 *
238 * @param cli the client structure passed to the function that
239 * initiated the operation.
240 *
241 * @param state the state of the machine at the time of completion,
242 * restricted by ONOFF_STATE_MASK. ONOFF_FLAG_ERROR must be checked
243 * independently of whether res is negative as a machine error may
244 * indicate that all future operations except onoff_reset() will fail.
245 *
246 * @param res the result of the operation. Expected values are
247 * service-specific, but the value shall be non-negative if the
248 * operation succeeded, and negative if the operation failed. If res
249 * is negative ONOFF_FLAG_ERROR will be set in state, but if res is
250 * non-negative ONOFF_FLAG_ERROR may still be set in state.
251 */
252 typedef void (*onoff_client_callback)(struct onoff_manager *mgr,
253 struct onoff_client *cli,
254 uint32_t state,
255 int res);
256
257 /**
258 * @brief State associated with a client of an on-off service.
259 *
260 * Objects of this type are allocated by a client, which is
261 * responsible for zero-initializing the node field and invoking the
262 * appropriate sys_notify init function to configure notification.
263 *
264 * Control of the object content transfers to the service provider
265 * when a pointer to the object is passed to any on-off manager
266 * function. While the service provider controls the object the
267 * client must not change any object fields. Control reverts to the
268 * client concurrent with release of the owned sys_notify structure,
269 * or when indicated by an onoff_cancel() return value.
270 *
271 * After control has reverted to the client the notify field must be
272 * reinitialized for the next operation.
273 */
274 struct onoff_client {
275 /** @cond INTERNAL_HIDDEN
276 *
277 * Links the client into the set of waiting service users.
278 * Applications must ensure this field is zero-initialized
279 * before use.
280 */
281 sys_snode_t node;
282 /** @endcond */
283
284 /** @brief Notification configuration. */
285 struct sys_notify notify;
286 };
287
288 /**
289 * @brief Identify region of sys_notify flags available for
290 * containing services.
291 *
292 * Bits of the flags field of the sys_notify structure contained
293 * within the queued_operation structure at and above this position
294 * may be used by extensions to the onoff_client structure.
295 *
296 * These bits are intended for use by containing service
297 * implementations to record client-specific information and are
298 * subject to other conditions of use specified on the sys_notify API.
299 */
300 #define ONOFF_CLIENT_EXTENSION_POS SYS_NOTIFY_EXTENSION_POS
301
302 /**
303 * @brief Test whether an on-off service has recorded an error.
304 *
305 * This function can be used to determine whether the service has
306 * recorded an error. Errors may be cleared by invoking
307 * onoff_reset().
308 *
309 * This is an unlocked convenience function suitable for use only when
310 * it is known that no other process might invoke an operation that
311 * transitions the service between an error and non-error state.
312 *
313 * @return true if and only if the service has an uncleared error.
314 */
onoff_has_error(const struct onoff_manager * mgr)315 static inline bool onoff_has_error(const struct onoff_manager *mgr)
316 {
317 return (mgr->flags & ONOFF_FLAG_ERROR) != 0;
318 }
319
320 /**
321 * @brief Request a reservation to use an on-off service.
322 *
323 * The return value indicates the success or failure of an attempt to
324 * initiate an operation to request the resource be made available.
325 * If initiation of the operation succeeds the result of the request
326 * operation is provided through the configured client notification
327 * method, possibly before this call returns.
328 *
329 * Note that the call to this function may succeed in a case where the
330 * actual request fails. Always check the operation completion
331 * result.
332 *
333 * @param mgr the manager that will be used.
334 *
335 * @param cli a non-null pointer to client state providing
336 * instructions on synchronous expectations and how to notify the
337 * client when the request completes. Behavior is undefined if client
338 * passes a pointer object associated with an incomplete service
339 * operation.
340 *
341 * @retval non-negative the observed state of the machine at the time
342 * the request was processed, if successful.
343 * @retval -EIO if service has recorded an error.
344 * @retval -EINVAL if the parameters are invalid.
345 * @retval -EAGAIN if the reference count would overflow.
346 */
347 int onoff_request(struct onoff_manager *mgr,
348 struct onoff_client *cli);
349
350 /**
351 * @brief Release a reserved use of an on-off service.
352 *
353 * This synchronously releases the caller's previous request. If the
354 * last request is released the manager will initiate a transition to
355 * off, which can be observed by registering an onoff_monitor.
356 *
357 * @note Behavior is undefined if this is not paired with a preceding
358 * onoff_request() call that completed successfully.
359 *
360 * @param mgr the manager for which a request was successful.
361 *
362 * @retval non-negative the observed state (ONOFF_STATE_ON) of the
363 * machine at the time of the release, if the release succeeds.
364 * @retval -EIO if service has recorded an error.
365 * @retval -ENOTSUP if the machine is not in a state that permits
366 * release.
367 */
368 int onoff_release(struct onoff_manager *mgr);
369
370 /**
371 * @brief Attempt to cancel an in-progress client operation.
372 *
373 * It may be that a client has initiated an operation but needs to
374 * shut down before the operation has completed. For example, when a
375 * request was made and the need is no longer present.
376 *
377 * Cancelling is supported only for onoff_request() and onoff_reset()
378 * operations, and is a synchronous operation. Be aware that any
379 * transition that was initiated on behalf of the client will continue
380 * to progress to completion: it is only notification of transition
381 * completion that may be eliminated. If there are no active requests
382 * when a transition to on completes the manager will initiate a
383 * transition to off.
384 *
385 * Client notification does not occur for cancelled operations.
386 *
387 * @param mgr the manager for which an operation is to be cancelled.
388 *
389 * @param cli a pointer to the same client state that was provided
390 * when the operation to be cancelled was issued.
391 *
392 * @retval non-negative the observed state of the machine at the time
393 * of the cancellation, if the cancellation succeeds. On successful
394 * cancellation ownership of @c *cli reverts to the client.
395 * @retval -EINVAL if the parameters are invalid.
396 * @retval -EALREADY if cli was not a record of an uncompleted
397 * notification at the time the cancellation was processed. This
398 * likely indicates that the operation and client notification had
399 * already completed.
400 */
401 int onoff_cancel(struct onoff_manager *mgr,
402 struct onoff_client *cli);
403
404 /**
405 * @brief Helper function to safely cancel a request.
406 *
407 * Some applications may want to issue requests on an asynchronous
408 * event (such as connection to a USB bus) and to release on a paired
409 * event (such as loss of connection to a USB bus). Applications
410 * cannot precisely determine that an in-progress request is still
411 * pending without using onoff_monitor and carefully avoiding race
412 * conditions.
413 *
414 * This function is a helper that attempts to cancel the operation and
415 * issues a release if cancellation fails because the request was
416 * completed. This synchronously ensures that ownership of the client
417 * data reverts to the client so is available for a future request.
418 *
419 * @param mgr the manager for which an operation is to be cancelled.
420 *
421 * @param cli a pointer to the same client state that was provided
422 * when onoff_request() was invoked. Behavior is undefined if this is
423 * a pointer to client data associated with an onoff_reset() request.
424 *
425 * @retval ONOFF_STATE_TO_ON if the cancellation occurred before the
426 * transition completed.
427 *
428 * @retval ONOFF_STATE_ON if the cancellation occurred after the
429 * transition completed.
430 *
431 * @retval -EINVAL if the parameters are invalid.
432 *
433 * @retval negative other errors produced by onoff_release().
434 */
onoff_cancel_or_release(struct onoff_manager * mgr,struct onoff_client * cli)435 static inline int onoff_cancel_or_release(struct onoff_manager *mgr,
436 struct onoff_client *cli)
437 {
438 int rv = onoff_cancel(mgr, cli);
439
440 if (rv == -EALREADY) {
441 rv = onoff_release(mgr);
442 }
443 return rv;
444 }
445
446 /**
447 * @brief Clear errors on an on-off service and reset it to its off
448 * state.
449 *
450 * A service can only be reset when it is in an error state as
451 * indicated by onoff_has_error().
452 *
453 * The return value indicates the success or failure of an attempt to
454 * initiate an operation to reset the resource. If initiation of the
455 * operation succeeds the result of the reset operation itself is
456 * provided through the configured client notification method,
457 * possibly before this call returns. Multiple clients may request a
458 * reset; all are notified when it is complete.
459 *
460 * Note that the call to this function may succeed in a case where the
461 * actual reset fails. Always check the operation completion result.
462 *
463 * @note Due to the conditions on state transition all incomplete
464 * asynchronous operations will have been informed of the error when
465 * it occurred. There need be no concern about dangling requests left
466 * after a reset completes.
467 *
468 * @param mgr the manager to be reset.
469 *
470 * @param cli pointer to client state, including instructions on how
471 * to notify the client when reset completes. Behavior is undefined
472 * if cli references an object associated with an incomplete service
473 * operation.
474 *
475 * @retval non-negative the observed state of the machine at the time
476 * of the reset, if the reset succeeds.
477 * @retval -ENOTSUP if reset is not supported by the service.
478 * @retval -EINVAL if the parameters are invalid.
479 * @retval -EALREADY if the service does not have a recorded error.
480 */
481 int onoff_reset(struct onoff_manager *mgr,
482 struct onoff_client *cli);
483
484 /**
485 * @brief Signature used to notify a monitor of an onoff service of
486 * errors or completion of a state transition.
487 *
488 * This is similar to onoff_client_callback but provides information
489 * about all transitions, not just ones associated with a specific
490 * client. Monitor callbacks are invoked before any completion
491 * notifications associated with the state change are made.
492 *
493 * These functions may be invoked from any context including
494 * pre-kernel, ISR, or cooperative or pre-emptible threads.
495 * Compatible functions must be isr-ok and not sleep.
496 *
497 * The callback is permitted to unregister itself from the manager,
498 * but must not register or unregister any other monitors.
499 *
500 * @param mgr the manager for which a transition has completed.
501 *
502 * @param mon the monitor instance through which this notification
503 * arrived.
504 *
505 * @param state the state of the machine at the time of completion,
506 * restricted by ONOFF_STATE_MASK. All valid states may be observed.
507 *
508 * @param res the result of the operation. Expected values are
509 * service- and state-specific, but the value shall be non-negative if
510 * the operation succeeded, and negative if the operation failed.
511 */
512 typedef void (*onoff_monitor_callback)(struct onoff_manager *mgr,
513 struct onoff_monitor *mon,
514 uint32_t state,
515 int res);
516
517 /**
518 * @brief Registration state for notifications of onoff service
519 * transitions.
520 *
521 * Any given onoff_monitor structure can be associated with at most
522 * one onoff_manager instance.
523 */
524 struct onoff_monitor {
525 /** Links the client into the set of waiting service users.
526 *
527 * This must be zero-initialized.
528 */
529 sys_snode_t node;
530
531 /** Callback to be invoked on state change.
532 *
533 * This must not be null.
534 */
535 onoff_monitor_callback callback;
536 };
537
538 /**
539 * @brief Add a monitor of state changes for a manager.
540 *
541 * @param mgr the manager for which a state changes are to be monitored.
542 *
543 * @param mon a linkable node providing a non-null callback to be
544 * invoked on state changes.
545 *
546 * @return non-negative on successful addition, or a negative error
547 * code.
548 */
549 int onoff_monitor_register(struct onoff_manager *mgr,
550 struct onoff_monitor *mon);
551
552 /**
553 * @brief Remove a monitor of state changes from a manager.
554 *
555 * @param mgr the manager for which a state changes are to be monitored.
556 *
557 * @param mon a linkable node providing the callback to be invoked on
558 * state changes.
559 *
560 * @return non-negative on successful removal, or a negative error
561 * code.
562 */
563 int onoff_monitor_unregister(struct onoff_manager *mgr,
564 struct onoff_monitor *mon);
565
566 /**
567 * @brief State used when a driver uses the on-off service API for synchronous
568 * operations.
569 *
570 * This is useful when a subsystem API uses the on-off API to support
571 * asynchronous operations but the transitions required by a
572 * particular driver are isr-ok and not sleep. It serves as a
573 * substitute for #onoff_manager, with locking and persisted state
574 * updates supported by onoff_sync_lock() and onoff_sync_finalize().
575 */
576 struct onoff_sync_service {
577 /** Mutex protection for other fields. */
578 struct k_spinlock lock;
579
580 /** Negative is error, non-negative is reference count. */
581 int32_t count;
582 };
583
584 /**
585 * @brief Lock a synchronous onoff service and provide its state.
586 *
587 * @note If an error state is returned it is the caller's responsibility to
588 * decide whether to preserve it (finalize with the same error state) or clear
589 * the error (finalize with a non-error result).
590 *
591 * @param srv pointer to the synchronous service state.
592 *
593 * @param keyp pointer to where the lock key should be stored
594 *
595 * @return negative if the service is in an error state, otherwise the
596 * number of active requests at the time the lock was taken. The lock
597 * is held on return regardless of whether a negative state is
598 * returned.
599 */
600 int onoff_sync_lock(struct onoff_sync_service *srv,
601 k_spinlock_key_t *keyp);
602
603 /**
604 * @brief Process the completion of a transition in a synchronous
605 * service and release lock.
606 *
607 * This function updates the service state on the @p res and @p on parameters
608 * then releases the lock. If @p cli is not null it finalizes the client
609 * notification using @p res.
610 *
611 * If the service was in an error state when locked, and @p res is non-negative
612 * when finalized, the count is reset to zero before completing finalization.
613 *
614 * @param srv pointer to the synchronous service state
615 *
616 * @param key the key returned by the preceding invocation of onoff_sync_lock().
617 *
618 * @param cli pointer to the onoff client through which completion
619 * information is returned. If a null pointer is passed only the
620 * state of the service is updated. For compatibility with the
621 * behavior of callbacks used with the manager API @p cli must be null
622 * when @p on is false (the manager does not support callbacks when
623 * turning off devices).
624 *
625 * @param res the result of the transition. A negative value places the service
626 * into an error state. A non-negative value increments or decrements the
627 * reference count as specified by @p on.
628 *
629 * @param on Only when @p res is non-negative, the service reference count will
630 * be incremented if@p on is @c true, and decremented if @p on is @c false.
631 *
632 * @return negative if the service is left or put into an error state, otherwise
633 * the number of active requests at the time the lock was released.
634 */
635 int onoff_sync_finalize(struct onoff_sync_service *srv,
636 k_spinlock_key_t key,
637 struct onoff_client *cli,
638 int res,
639 bool on);
640
641 /** @} */
642
643 #ifdef __cplusplus
644 }
645 #endif
646
647 #endif /* ZEPHYR_INCLUDE_SYS_ONOFF_H_ */
648