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 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 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