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 <kernel.h>
12 #include <zephyr/types.h>
13 #include <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  * approprite 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