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 /** @internal */
33 #define ONOFF_FLAG_ONOFF BIT(1)
34 /** @internal */
35 #define ONOFF_FLAG_TRANSITION BIT(2)
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 /** @internal */
197 #define ONOFF_MANAGER_INITIALIZER(_transitions) { \
198 .transitions = _transitions, \
199 }
200
201 /**
202 * @brief Initialize an on-off service to off state.
203 *
204 * This function must be invoked exactly once per service instance, by
205 * the infrastructure that provides the service, and before any other
206 * on-off service API is invoked on the service.
207 *
208 * This function should never be invoked by clients of an on-off
209 * service.
210 *
211 * @param mgr the manager definition object to be initialized.
212 *
213 * @param transitions pointer to a structure providing transition
214 * functions. The referenced object must persist as long as the
215 * manager can be referenced.
216 *
217 * @retval 0 on success
218 * @retval -EINVAL if start, stop, or flags are invalid
219 */
220 int onoff_manager_init(struct onoff_manager *mgr,
221 const struct onoff_transitions *transitions);
222
223 /* Forward declaration */
224 struct onoff_client;
225
226 /**
227 * @brief Signature used to notify an on-off service client of the
228 * completion of an operation.
229 *
230 * These functions may be invoked from any context including
231 * pre-kernel, ISR, or cooperative or pre-emptible threads.
232 * Compatible functions must be isr-ok and not sleep.
233 *
234 * @param mgr the manager for which the operation was initiated. This may be
235 * null if the on-off service uses synchronous transitions.
236 *
237 * @param cli the client structure passed to the function that
238 * initiated the operation.
239 *
240 * @param state the state of the machine at the time of completion,
241 * restricted by ONOFF_STATE_MASK. ONOFF_FLAG_ERROR must be checked
242 * independently of whether res is negative as a machine error may
243 * indicate that all future operations except onoff_reset() will fail.
244 *
245 * @param res the result of the operation. Expected values are
246 * service-specific, but the value shall be non-negative if the
247 * operation succeeded, and negative if the operation failed. If res
248 * is negative ONOFF_FLAG_ERROR will be set in state, but if res is
249 * non-negative ONOFF_FLAG_ERROR may still be set in state.
250 */
251 typedef void (*onoff_client_callback)(struct onoff_manager *mgr,
252 struct onoff_client *cli,
253 uint32_t state,
254 int res);
255
256 /**
257 * @brief State associated with a client of an on-off service.
258 *
259 * Objects of this type are allocated by a client, which is
260 * responsible for zero-initializing the node field and invoking the
261 * appropriate sys_notify init function to configure notification.
262 *
263 * Control of the object content transfers to the service provider
264 * when a pointer to the object is passed to any on-off manager
265 * function. While the service provider controls the object the
266 * client must not change any object fields. Control reverts to the
267 * client concurrent with release of the owned sys_notify structure,
268 * or when indicated by an onoff_cancel() return value.
269 *
270 * After control has reverted to the client the notify field must be
271 * reinitialized for the next operation.
272 */
273 struct onoff_client {
274 /** @internal
275 *
276 * Links the client into the set of waiting service users.
277 * Applications must ensure this field is zero-initialized
278 * before use.
279 */
280 sys_snode_t node;
281
282 /** @brief Notification configuration. */
283 struct sys_notify notify;
284 };
285
286 /**
287 * @brief Identify region of sys_notify flags available for
288 * containing services.
289 *
290 * Bits of the flags field of the sys_notify structure contained
291 * within the queued_operation structure at and above this position
292 * may be used by extensions to the onoff_client structure.
293 *
294 * These bits are intended for use by containing service
295 * implementations to record client-specific information and are
296 * subject to other conditions of use specified on the sys_notify API.
297 */
298 #define ONOFF_CLIENT_EXTENSION_POS SYS_NOTIFY_EXTENSION_POS
299
300 /**
301 * @brief Test whether an on-off service has recorded an error.
302 *
303 * This function can be used to determine whether the service has
304 * recorded an error. Errors may be cleared by invoking
305 * onoff_reset().
306 *
307 * This is an unlocked convenience function suitable for use only when
308 * it is known that no other process might invoke an operation that
309 * transitions the service between an error and non-error state.
310 *
311 * @return true if and only if the service has an uncleared error.
312 */
onoff_has_error(const struct onoff_manager * mgr)313 static inline bool onoff_has_error(const struct onoff_manager *mgr)
314 {
315 return (mgr->flags & ONOFF_FLAG_ERROR) != 0;
316 }
317
318 /**
319 * @brief Request a reservation to use an on-off service.
320 *
321 * The return value indicates the success or failure of an attempt to
322 * initiate an operation to request the resource be made available.
323 * If initiation of the operation succeeds the result of the request
324 * operation is provided through the configured client notification
325 * method, possibly before this call returns.
326 *
327 * Note that the call to this function may succeed in a case where the
328 * actual request fails. Always check the operation completion
329 * result.
330 *
331 * @param mgr the manager that will be used.
332 *
333 * @param cli a non-null pointer to client state providing
334 * instructions on synchronous expectations and how to notify the
335 * client when the request completes. Behavior is undefined if client
336 * passes a pointer object associated with an incomplete service
337 * operation.
338 *
339 * @retval non-negative the observed state of the machine at the time
340 * the request was processed, if successful.
341 * @retval -EIO if service has recorded an an error.
342 * @retval -EINVAL if the parameters are invalid.
343 * @retval -EAGAIN if the reference count would overflow.
344 */
345 int onoff_request(struct onoff_manager *mgr,
346 struct onoff_client *cli);
347
348 /**
349 * @brief Release a reserved use of an on-off service.
350 *
351 * This synchronously releases the caller's previous request. If the
352 * last request is released the manager will initiate a transition to
353 * off, which can be observed by registering an onoff_monitor.
354 *
355 * @note Behavior is undefined if this is not paired with a preceding
356 * onoff_request() call that completed successfully.
357 *
358 * @param mgr the manager for which a request was successful.
359 *
360 * @retval non-negative the observed state (ONOFF_STATE_ON) of the
361 * machine at the time of the release, if the release succeeds.
362 * @retval -EIO if service has recorded an an error.
363 * @retval -ENOTSUP if the machine is not in a state that permits
364 * release.
365 */
366 int onoff_release(struct onoff_manager *mgr);
367
368 /**
369 * @brief Attempt to cancel an in-progress client operation.
370 *
371 * It may be that a client has initiated an operation but needs to
372 * shut down before the operation has completed. For example, when a
373 * request was made and the need is no longer present.
374 *
375 * Cancelling is supported only for onoff_request() and onoff_reset()
376 * operations, and is a synchronous operation. Be aware that any
377 * transition that was initiated on behalf of the client will continue
378 * to progress to completion: it is only notification of transition
379 * completion that may be eliminated. If there are no active requests
380 * when a transition to on completes the manager will initiate a
381 * transition to off.
382 *
383 * Client notification does not occur for cancelled operations.
384 *
385 * @param mgr the manager for which an operation is to be cancelled.
386 *
387 * @param cli a pointer to the same client state that was provided
388 * when the operation to be cancelled was issued.
389 *
390 * @retval non-negative the observed state of the machine at the time
391 * of the cancellation, if the cancellation succeeds. On successful
392 * cancellation ownership of @c *cli reverts to the client.
393 * @retval -EINVAL if the parameters are invalid.
394 * @retval -EALREADY if cli was not a record of an uncompleted
395 * notification at the time the cancellation was processed. This
396 * likely indicates that the operation and client notification had
397 * already completed.
398 */
399 int onoff_cancel(struct onoff_manager *mgr,
400 struct onoff_client *cli);
401
402 /**
403 * @brief Helper function to safely cancel a request.
404 *
405 * Some applications may want to issue requests on an asynchronous
406 * event (such as connection to a USB bus) and to release on a paired
407 * event (such as loss of connection to a USB bus). Applications
408 * cannot precisely determine that an in-progress request is still
409 * pending without using onoff_monitor and carefully avoiding race
410 * conditions.
411 *
412 * This function is a helper that attempts to cancel the operation and
413 * issues a release if cancellation fails because the request was
414 * completed. This synchronously ensures that ownership of the client
415 * data reverts to the client so is available for a future request.
416 *
417 * @param mgr the manager for which an operation is to be cancelled.
418 *
419 * @param cli a pointer to the same client state that was provided
420 * when onoff_request() was invoked. Behavior is undefined if this is
421 * a pointer to client data associated with an onoff_reset() request.
422 *
423 * @retval ONOFF_STATE_TO_ON if the cancellation occurred before the
424 * transition completed.
425 *
426 * @retval ONOFF_STATE_ON if the cancellation occurred after the
427 * transition completed.
428 *
429 * @retval -EINVAL if the parameters are invalid.
430 *
431 * @retval negative other errors produced by onoff_release().
432 */
onoff_cancel_or_release(struct onoff_manager * mgr,struct onoff_client * cli)433 static inline int onoff_cancel_or_release(struct onoff_manager *mgr,
434 struct onoff_client *cli)
435 {
436 int rv = onoff_cancel(mgr, cli);
437
438 if (rv == -EALREADY) {
439 rv = onoff_release(mgr);
440 }
441 return rv;
442 }
443
444 /**
445 * @brief Clear errors on an on-off service and reset it to its off
446 * state.
447 *
448 * A service can only be reset when it is in an error state as
449 * indicated by onoff_has_error().
450 *
451 * The return value indicates the success or failure of an attempt to
452 * initiate an operation to reset the resource. If initiation of the
453 * operation succeeds the result of the reset operation itself is
454 * provided through the configured client notification method,
455 * possibly before this call returns. Multiple clients may request a
456 * reset; all are notified when it is complete.
457 *
458 * Note that the call to this function may succeed in a case where the
459 * actual reset fails. Always check the operation completion result.
460 *
461 * @note Due to the conditions on state transition all incomplete
462 * asynchronous operations will have been informed of the error when
463 * it occurred. There need be no concern about dangling requests left
464 * after a reset completes.
465 *
466 * @param mgr the manager to be reset.
467 *
468 * @param cli pointer to client state, including instructions on how
469 * to notify the client when reset completes. Behavior is undefined
470 * if cli references an object associated with an incomplete service
471 * operation.
472 *
473 * @retval non-negative the observed state of the machine at the time
474 * of the reset, if the reset succeeds.
475 * @retval -ENOTSUP if reset is not supported by the service.
476 * @retval -EINVAL if the parameters are invalid.
477 * @retval -EALREADY if the service does not have a recorded error.
478 */
479 int onoff_reset(struct onoff_manager *mgr,
480 struct onoff_client *cli);
481
482 /**
483 * @brief Signature used to notify a monitor of an onoff service of
484 * errors or completion of a state transition.
485 *
486 * This is similar to onoff_client_callback but provides information
487 * about all transitions, not just ones associated with a specific
488 * client. Monitor callbacks are invoked before any completion
489 * notifications associated with the state change are made.
490 *
491 * These functions may be invoked from any context including
492 * pre-kernel, ISR, or cooperative or pre-emptible threads.
493 * Compatible functions must be isr-ok and not sleep.
494 *
495 * The callback is permitted to unregister itself from the manager,
496 * but must not register or unregister any other monitors.
497 *
498 * @param mgr the manager for which a transition has completed.
499 *
500 * @param mon the monitor instance through which this notification
501 * arrived.
502 *
503 * @param state the state of the machine at the time of completion,
504 * restricted by ONOFF_STATE_MASK. All valid states may be observed.
505 *
506 * @param res the result of the operation. Expected values are
507 * service- and state-specific, but the value shall be non-negative if
508 * the operation succeeded, and negative if the operation failed.
509 */
510 typedef void (*onoff_monitor_callback)(struct onoff_manager *mgr,
511 struct onoff_monitor *mon,
512 uint32_t state,
513 int res);
514
515 /**
516 * @brief Registration state for notifications of onoff service
517 * transitions.
518 *
519 * Any given onoff_monitor structure can be associated with at most
520 * one onoff_manager instance.
521 */
522 struct onoff_monitor {
523 /* Links the client into the set of waiting service users.
524 *
525 * This must be zero-initialized.
526 */
527 sys_snode_t node;
528
529 /** @brief Callback to be invoked on state change.
530 *
531 * This must not be null.
532 */
533 onoff_monitor_callback callback;
534 };
535
536 /**
537 * @brief Add a monitor of state changes for a manager.
538 *
539 * @param mgr the manager for which a state changes are to be monitored.
540 *
541 * @param mon a linkable node providing a non-null callback to be
542 * invoked on state changes.
543 *
544 * @return non-negative on successful addition, or a negative error
545 * code.
546 */
547 int onoff_monitor_register(struct onoff_manager *mgr,
548 struct onoff_monitor *mon);
549
550 /**
551 * @brief Remove a monitor of state changes from a manager.
552 *
553 * @param mgr the manager for which a state changes are to be monitored.
554 *
555 * @param mon a linkable node providing the callback to be invoked on
556 * state changes.
557 *
558 * @return non-negative on successful removal, or a negative error
559 * code.
560 */
561 int onoff_monitor_unregister(struct onoff_manager *mgr,
562 struct onoff_monitor *mon);
563
564 /**
565 * @brief State used when a driver uses the on-off service API for synchronous
566 * operations.
567 *
568 * This is useful when a subsystem API uses the on-off API to support
569 * asynchronous operations but the transitions required by a
570 * particular driver are isr-ok and not sleep. It serves as a
571 * substitute for #onoff_manager, with locking and persisted state
572 * updates supported by onoff_sync_lock() and onoff_sync_finalize().
573 */
574 struct onoff_sync_service {
575 /* Mutex protection for other fields. */
576 struct k_spinlock lock;
577
578 /* Negative is error, non-negative is reference count. */
579 int32_t count;
580 };
581
582 /**
583 * @brief Lock a synchronous onoff service and provide its state.
584 *
585 * @note If an error state is returned it is the caller's responsibility to
586 * decide whether to preserve it (finalize with the same error state) or clear
587 * the error (finalize with a non-error result).
588 *
589 * @param srv pointer to the synchronous service state.
590 *
591 * @param keyp pointer to where the lock key should be stored
592 *
593 * @return negative if the service is in an error state, otherwise the
594 * number of active requests at the time the lock was taken. The lock
595 * is held on return regardless of whether a negative state is
596 * returned.
597 */
598 int onoff_sync_lock(struct onoff_sync_service *srv,
599 k_spinlock_key_t *keyp);
600
601 /**
602 * @brief Process the completion of a transition in a synchronous
603 * service and release lock.
604 *
605 * This function updates the service state on the @p res and @p on parameters
606 * then releases the lock. If @p cli is not null it finalizes the client
607 * notification using @p res.
608 *
609 * If the service was in an error state when locked, and @p res is non-negative
610 * when finalized, the count is reset to zero before completing finalization.
611 *
612 * @param srv pointer to the synchronous service state
613 *
614 * @param key the key returned by the preceding invocation of onoff_sync_lock().
615 *
616 * @param cli pointer to the onoff client through which completion
617 * information is returned. If a null pointer is passed only the
618 * state of the service is updated. For compatibility with the
619 * behavior of callbacks used with the manager API @p cli must be null
620 * when @p on is false (the manager does not support callbacks when
621 * turning off devices).
622 *
623 * @param res the result of the transition. A negative value places the service
624 * into an error state. A non-negative value increments or decrements the
625 * reference count as specified by @p on.
626 *
627 * @param on Only when @p res is non-negative, the service reference count will
628 * be incremented if@p on is @c true, and decremented if @p on is @c false.
629 *
630 * @return negative if the service is left or put into an error state, otherwise
631 * the number of active requests at the time the lock was released.
632 */
633 int onoff_sync_finalize(struct onoff_sync_service *srv,
634 k_spinlock_key_t key,
635 struct onoff_client *cli,
636 int res,
637 bool on);
638
639 /** @} */
640
641 #ifdef __cplusplus
642 }
643 #endif
644
645 #endif /* ZEPHYR_INCLUDE_SYS_ONOFF_H_ */
646