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 #include <zephyr/kernel.h>
9 #include <zephyr/sys/onoff.h>
10 #include <stdio.h>
11 
12 #define SERVICE_REFS_MAX UINT16_MAX
13 
14 /* Confirm consistency of public flags with private flags */
15 BUILD_ASSERT((ONOFF_FLAG_ERROR | ONOFF_FLAG_ONOFF | ONOFF_FLAG_TRANSITION)
16 	     < BIT(3));
17 
18 #define ONOFF_FLAG_PROCESSING BIT(3)
19 #define ONOFF_FLAG_COMPLETE BIT(4)
20 #define ONOFF_FLAG_RECHECK BIT(5)
21 
22 /* These symbols in the ONOFF_FLAGS namespace identify bits in
23  * onoff_manager::flags that indicate the state of the machine.  The
24  * bits are manipulated by process_event() under lock, and actions
25  * cued by bit values are executed outside of lock within
26  * process_event().
27  *
28  * * ERROR indicates that the machine is in an error state.  When
29  *   this bit is set ONOFF will be cleared.
30  * * ONOFF indicates whether the target/current state is off (clear)
31  *   or on (set).
32  * * TRANSITION indicates whether a service transition function is in
33  *   progress.  It combines with ONOFF to identify start and stop
34  *   transitions, and with ERROR to identify a reset transition.
35  * * PROCESSING indicates that the process_event() loop is active.  It
36  *   is used to defer initiation of transitions and other complex
37  *   state changes while invoking notifications associated with a
38  *   state transition.  This bounds the  depth by limiting
39  *   active process_event() call stacks to two instances.  State changes
40  *   initiated by a nested call will be executed when control returns
41  *   to the parent call.
42  * * COMPLETE indicates that a transition completion notification has
43  *   been received.  This flag is set in the notification, and cleared
44  *   by process_events() which is invoked from the notification.  In
45  *   the case of nested process_events() the processing is deferred to
46  *   the top invocation.
47  * * RECHECK indicates that a state transition has completed but
48  *   process_events() must re-check the overall state to confirm no
49  *   additional transitions are required.  This is used to simplify the
50  *   logic when, for example, a request is received during a
51  *   transition to off, which means that when the transition completes
52  *   a transition to on must be initiated if the request is still
53  *   present.  Transition to ON with no remaining requests similarly
54  *   triggers a recheck.
55  */
56 
57 /* Identify the events that can trigger state changes, as well as an
58  * internal state used when processing deferred actions.
59  */
60 enum event_type {
61 	/* No-op event: used to process deferred changes.
62 	 *
63 	 * This event is local to the process loop.
64 	 */
65 	EVT_NOP,
66 
67 	/* Completion of a service transition.
68 	 *
69 	 * This event is triggered by the transition notify callback.
70 	 * It can be received only when the machine is in a transition
71 	 * state (TO-ON, TO-OFF, or RESETTING).
72 	 */
73 	EVT_COMPLETE,
74 
75 	/* Reassess whether a transition from a stable state is needed.
76 	 *
77 	 * This event causes:
78 	 * * a start from OFF when there are clients;
79 	 * * a stop from ON when there are no clients;
80 	 * * a reset from ERROR when there are clients.
81 	 *
82 	 * The client list can change while the manager lock is
83 	 * released (e.g. during client and monitor notifications and
84 	 * transition initiations), so this event records the
85 	 * potential for these state changes, and process_event() ...
86 	 *
87 	 */
88 	EVT_RECHECK,
89 
90 	/* Transition to on.
91 	 *
92 	 * This is synthesized from EVT_RECHECK in a non-nested
93 	 * process_event() when state OFF is confirmed with a
94 	 * non-empty client (request) list.
95 	 */
96 	EVT_START,
97 
98 	/* Transition to off.
99 	 *
100 	 * This is synthesized from EVT_RECHECK in a non-nested
101 	 * process_event() when state ON is confirmed with a
102 	 * zero reference count.
103 	 */
104 	EVT_STOP,
105 
106 	/* Transition to resetting.
107 	 *
108 	 * This is synthesized from EVT_RECHECK in a non-nested
109 	 * process_event() when state ERROR is confirmed with a
110 	 * non-empty client (reset) list.
111 	 */
112 	EVT_RESET,
113 };
114 
set_state(struct onoff_manager * mgr,uint32_t state)115 static void set_state(struct onoff_manager *mgr,
116 		      uint32_t state)
117 {
118 	mgr->flags = (state & ONOFF_STATE_MASK)
119 		     | (mgr->flags & ~ONOFF_STATE_MASK);
120 }
121 
validate_args(const struct onoff_manager * mgr,struct onoff_client * cli)122 static int validate_args(const struct onoff_manager *mgr,
123 			 struct onoff_client *cli)
124 {
125 	if ((mgr == NULL) || (cli == NULL)) {
126 		return -EINVAL;
127 	}
128 
129 	int rv = sys_notify_validate(&cli->notify);
130 
131 	if ((rv == 0)
132 	    && ((cli->notify.flags
133 		 & ~BIT_MASK(ONOFF_CLIENT_EXTENSION_POS)) != 0)) {
134 		rv = -EINVAL;
135 	}
136 
137 	return rv;
138 }
139 
onoff_manager_init(struct onoff_manager * mgr,const struct onoff_transitions * transitions)140 int onoff_manager_init(struct onoff_manager *mgr,
141 		       const struct onoff_transitions *transitions)
142 {
143 	if ((mgr == NULL)
144 	    || (transitions == NULL)
145 	    || (transitions->start == NULL)
146 	    || (transitions->stop == NULL)) {
147 		return -EINVAL;
148 	}
149 
150 	*mgr = (struct onoff_manager)ONOFF_MANAGER_INITIALIZER(transitions);
151 
152 	return 0;
153 }
154 
notify_monitors(struct onoff_manager * mgr,uint32_t state,int res)155 static void notify_monitors(struct onoff_manager *mgr,
156 			    uint32_t state,
157 			    int res)
158 {
159 	sys_slist_t *mlist = &mgr->monitors;
160 	struct onoff_monitor *mon;
161 	struct onoff_monitor *tmp;
162 
163 	SYS_SLIST_FOR_EACH_CONTAINER_SAFE(mlist, mon, tmp, node) {
164 		mon->callback(mgr, mon, state, res);
165 	}
166 }
167 
notify_one(struct onoff_manager * mgr,struct onoff_client * cli,uint32_t state,int res)168 static void notify_one(struct onoff_manager *mgr,
169 		       struct onoff_client *cli,
170 		       uint32_t state,
171 		       int res)
172 {
173 	onoff_client_callback cb =
174 		(onoff_client_callback)sys_notify_finalize(&cli->notify, res);
175 
176 	if (cb) {
177 		cb(mgr, cli, state, res);
178 	}
179 }
180 
notify_all(struct onoff_manager * mgr,sys_slist_t * list,uint32_t state,int res)181 static void notify_all(struct onoff_manager *mgr,
182 		       sys_slist_t *list,
183 		       uint32_t state,
184 		       int res)
185 {
186 	while (!sys_slist_is_empty(list)) {
187 		sys_snode_t *node = sys_slist_get_not_empty(list);
188 		struct onoff_client *cli =
189 			CONTAINER_OF(node,
190 				     struct onoff_client,
191 				     node);
192 
193 		notify_one(mgr, cli, state, res);
194 	}
195 }
196 
197 static void process_event(struct onoff_manager *mgr,
198 			  int evt,
199 			  k_spinlock_key_t key);
200 
transition_complete(struct onoff_manager * mgr,int res)201 static void transition_complete(struct onoff_manager *mgr,
202 				int res)
203 {
204 	k_spinlock_key_t key = k_spin_lock(&mgr->lock);
205 
206 	mgr->last_res = res;
207 	process_event(mgr, EVT_COMPLETE, key);
208 }
209 
210 /* Detect whether static state requires a transition. */
process_recheck(struct onoff_manager * mgr)211 static int process_recheck(struct onoff_manager *mgr)
212 {
213 	int evt = EVT_NOP;
214 	uint32_t state = mgr->flags & ONOFF_STATE_MASK;
215 
216 	if ((state == ONOFF_STATE_OFF)
217 	    && !sys_slist_is_empty(&mgr->clients)) {
218 		evt = EVT_START;
219 	} else if ((state == ONOFF_STATE_ON)
220 		   && (mgr->refs == 0U)) {
221 		evt = EVT_STOP;
222 	} else if ((state == ONOFF_STATE_ERROR)
223 		   && !sys_slist_is_empty(&mgr->clients)) {
224 		evt = EVT_RESET;
225 	} else {
226 		;
227 	}
228 
229 	return evt;
230 }
231 
232 /* Process a transition completion.
233  *
234  * If the completion requires notifying clients, the clients are moved
235  * from the manager to the output list for notification.
236  */
process_complete(struct onoff_manager * mgr,sys_slist_t * clients,int res)237 static void process_complete(struct onoff_manager *mgr,
238 			     sys_slist_t *clients,
239 			     int res)
240 {
241 	uint32_t state = mgr->flags & ONOFF_STATE_MASK;
242 
243 	if (res < 0) {
244 		/* Enter ERROR state and notify all clients. */
245 		*clients = mgr->clients;
246 		sys_slist_init(&mgr->clients);
247 		set_state(mgr, ONOFF_STATE_ERROR);
248 	} else if ((state == ONOFF_STATE_TO_ON)
249 		   || (state == ONOFF_STATE_RESETTING)) {
250 		*clients = mgr->clients;
251 		sys_slist_init(&mgr->clients);
252 
253 		if (state == ONOFF_STATE_TO_ON) {
254 			struct onoff_client *cp;
255 
256 			/* Increment reference count for all remaining
257 			 * clients and enter ON state.
258 			 */
259 			SYS_SLIST_FOR_EACH_CONTAINER(clients, cp, node) {
260 				mgr->refs += 1U;
261 			}
262 
263 			set_state(mgr, ONOFF_STATE_ON);
264 		} else {
265 			__ASSERT_NO_MSG(state == ONOFF_STATE_RESETTING);
266 
267 			set_state(mgr, ONOFF_STATE_OFF);
268 		}
269 		if (process_recheck(mgr) != EVT_NOP) {
270 			mgr->flags |= ONOFF_FLAG_RECHECK;
271 		}
272 	} else if (state == ONOFF_STATE_TO_OFF) {
273 		/* Any active clients are requests waiting for this
274 		 * transition to complete.  Queue a RECHECK event to
275 		 * ensure we don't miss them if we don't unlock to
276 		 * tell anybody about the completion.
277 		 */
278 		set_state(mgr, ONOFF_STATE_OFF);
279 		if (process_recheck(mgr) != EVT_NOP) {
280 			mgr->flags |= ONOFF_FLAG_RECHECK;
281 		}
282 	} else {
283 		__ASSERT_NO_MSG(false);
284 	}
285 }
286 
287 /* There are two points in the state machine where the machine is
288  * unlocked to perform some external action:
289  * * Initiation of an transition due to some event;
290  * * Invocation of the user-specified callback when a stable state is
291  *   reached or an error detected.
292  *
293  * Events received during these unlocked periods are recorded in the
294  * state, but processing is deferred to the top-level invocation which
295  * will loop to handle any events that occurred during the unlocked
296  * regions.
297  */
process_event(struct onoff_manager * mgr,int evt,k_spinlock_key_t key)298 static void process_event(struct onoff_manager *mgr,
299 			  int evt,
300 			  k_spinlock_key_t key)
301 {
302 	sys_slist_t clients;
303 	uint32_t state = mgr->flags & ONOFF_STATE_MASK;
304 	int res = 0;
305 	bool processing = ((mgr->flags & ONOFF_FLAG_PROCESSING) != 0);
306 
307 	__ASSERT_NO_MSG(evt != EVT_NOP);
308 
309 	/* If this is a nested call record the event for processing in
310 	 * the top invocation.
311 	 */
312 	if (processing) {
313 		if (evt == EVT_COMPLETE) {
314 			mgr->flags |= ONOFF_FLAG_COMPLETE;
315 		} else {
316 			__ASSERT_NO_MSG(evt == EVT_RECHECK);
317 
318 			mgr->flags |= ONOFF_FLAG_RECHECK;
319 		}
320 
321 		goto out;
322 	}
323 
324 	sys_slist_init(&clients);
325 	do {
326 		onoff_transition_fn transit = NULL;
327 
328 		if (evt == EVT_RECHECK) {
329 			evt = process_recheck(mgr);
330 		}
331 
332 		if (evt == EVT_NOP) {
333 			break;
334 		}
335 
336 		res = 0;
337 		if (evt == EVT_COMPLETE) {
338 			res = mgr->last_res;
339 			process_complete(mgr, &clients, res);
340 			/* NB: This can trigger a RECHECK */
341 		} else if (evt == EVT_START) {
342 			__ASSERT_NO_MSG(state == ONOFF_STATE_OFF);
343 			__ASSERT_NO_MSG(!sys_slist_is_empty(&mgr->clients));
344 
345 			transit = mgr->transitions->start;
346 			__ASSERT_NO_MSG(transit != NULL);
347 			set_state(mgr, ONOFF_STATE_TO_ON);
348 		} else if (evt == EVT_STOP) {
349 			__ASSERT_NO_MSG(state == ONOFF_STATE_ON);
350 			__ASSERT_NO_MSG(mgr->refs == 0);
351 
352 			transit = mgr->transitions->stop;
353 			__ASSERT_NO_MSG(transit != NULL);
354 			set_state(mgr, ONOFF_STATE_TO_OFF);
355 		} else if (evt == EVT_RESET) {
356 			__ASSERT_NO_MSG(state == ONOFF_STATE_ERROR);
357 			__ASSERT_NO_MSG(!sys_slist_is_empty(&mgr->clients));
358 
359 			transit = mgr->transitions->reset;
360 			__ASSERT_NO_MSG(transit != NULL);
361 			set_state(mgr, ONOFF_STATE_RESETTING);
362 		} else {
363 			__ASSERT_NO_MSG(false);
364 		}
365 
366 		/* Have to unlock and do something if any of:
367 		 * * We changed state and there are monitors;
368 		 * * We completed a transition and there are clients to notify;
369 		 * * We need to initiate a transition.
370 		 */
371 		bool do_monitors = (state != (mgr->flags & ONOFF_STATE_MASK))
372 				   && !sys_slist_is_empty(&mgr->monitors);
373 
374 		evt = EVT_NOP;
375 		if (do_monitors
376 		    || !sys_slist_is_empty(&clients)
377 		    || (transit != NULL)) {
378 			uint32_t flags = mgr->flags | ONOFF_FLAG_PROCESSING;
379 
380 			mgr->flags = flags;
381 			state = flags & ONOFF_STATE_MASK;
382 
383 			k_spin_unlock(&mgr->lock, key);
384 
385 			if (do_monitors) {
386 				notify_monitors(mgr, state, res);
387 			}
388 
389 			if (!sys_slist_is_empty(&clients)) {
390 				notify_all(mgr, &clients, state, res);
391 			}
392 
393 			if (transit != NULL) {
394 				transit(mgr, transition_complete);
395 			}
396 
397 			key = k_spin_lock(&mgr->lock);
398 			mgr->flags &= ~ONOFF_FLAG_PROCESSING;
399 		}
400 
401 		/* Process deferred events.  Completion takes priority
402 		 * over recheck.
403 		 */
404 		if ((mgr->flags & ONOFF_FLAG_COMPLETE) != 0) {
405 			mgr->flags &= ~ONOFF_FLAG_COMPLETE;
406 			evt = EVT_COMPLETE;
407 		} else if ((mgr->flags & ONOFF_FLAG_RECHECK) != 0) {
408 			mgr->flags &= ~ONOFF_FLAG_RECHECK;
409 			evt = EVT_RECHECK;
410 		} else {
411 			;
412 		}
413 
414 		state = mgr->flags & ONOFF_STATE_MASK;
415 	} while (evt != EVT_NOP);
416 
417 out:
418 	k_spin_unlock(&mgr->lock, key);
419 }
420 
onoff_request(struct onoff_manager * mgr,struct onoff_client * cli)421 int onoff_request(struct onoff_manager *mgr,
422 		  struct onoff_client *cli)
423 {
424 	bool add_client = false;        /* add client to pending list */
425 	bool start = false;             /* trigger a start transition */
426 	bool notify = false;            /* do client notification */
427 	int rv = validate_args(mgr, cli);
428 
429 	if (rv < 0) {
430 		return rv;
431 	}
432 
433 	k_spinlock_key_t key = k_spin_lock(&mgr->lock);
434 	uint32_t state = mgr->flags & ONOFF_STATE_MASK;
435 
436 	/* Reject if this would overflow the reference count. */
437 	if (mgr->refs == SERVICE_REFS_MAX) {
438 		rv = -EAGAIN;
439 		goto out;
440 	}
441 
442 	rv = state;
443 	if (state == ONOFF_STATE_ON) {
444 		/* Increment reference count, notify in exit */
445 		notify = true;
446 		mgr->refs += 1U;
447 	} else if ((state == ONOFF_STATE_OFF)
448 		   || (state == ONOFF_STATE_TO_OFF)
449 		   || (state == ONOFF_STATE_TO_ON)) {
450 		/* Start if OFF, queue client */
451 		start = (state == ONOFF_STATE_OFF);
452 		add_client = true;
453 	} else if (state == ONOFF_STATE_RESETTING) {
454 		rv = -ENOTSUP;
455 	} else {
456 		__ASSERT_NO_MSG(state == ONOFF_STATE_ERROR);
457 		rv = -EIO;
458 	}
459 
460 out:
461 	if (add_client) {
462 		sys_slist_append(&mgr->clients, &cli->node);
463 	}
464 
465 	if (start) {
466 		process_event(mgr, EVT_RECHECK, key);
467 	} else {
468 		k_spin_unlock(&mgr->lock, key);
469 
470 		if (notify) {
471 			notify_one(mgr, cli, state, 0);
472 		}
473 	}
474 
475 	return rv;
476 }
477 
onoff_release(struct onoff_manager * mgr)478 int onoff_release(struct onoff_manager *mgr)
479 {
480 	bool stop = false;      /* trigger a stop transition */
481 
482 	k_spinlock_key_t key = k_spin_lock(&mgr->lock);
483 	uint32_t state = mgr->flags & ONOFF_STATE_MASK;
484 	int rv = state;
485 
486 	if (state != ONOFF_STATE_ON) {
487 		if (state == ONOFF_STATE_ERROR) {
488 			rv = -EIO;
489 		} else {
490 			rv = -ENOTSUP;
491 		}
492 		goto out;
493 	}
494 
495 	__ASSERT_NO_MSG(mgr->refs > 0);
496 	mgr->refs -= 1U;
497 	stop = (mgr->refs == 0);
498 
499 out:
500 	if (stop) {
501 		process_event(mgr, EVT_RECHECK, key);
502 	} else {
503 		k_spin_unlock(&mgr->lock, key);
504 	}
505 
506 	return rv;
507 }
508 
onoff_reset(struct onoff_manager * mgr,struct onoff_client * cli)509 int onoff_reset(struct onoff_manager *mgr,
510 		struct onoff_client *cli)
511 {
512 	bool reset = false;
513 	int rv = validate_args(mgr, cli);
514 
515 	if ((rv >= 0)
516 	    && (mgr->transitions->reset == NULL)) {
517 		rv = -ENOTSUP;
518 	}
519 
520 	if (rv < 0) {
521 		return rv;
522 	}
523 
524 	k_spinlock_key_t key = k_spin_lock(&mgr->lock);
525 	uint32_t state = mgr->flags & ONOFF_STATE_MASK;
526 
527 	rv = state;
528 
529 	if ((state & ONOFF_FLAG_ERROR) == 0) {
530 		rv = -EALREADY;
531 	} else {
532 		reset = (state != ONOFF_STATE_RESETTING);
533 		sys_slist_append(&mgr->clients, &cli->node);
534 	}
535 
536 	if (reset) {
537 		process_event(mgr, EVT_RECHECK, key);
538 	} else {
539 		k_spin_unlock(&mgr->lock, key);
540 	}
541 
542 	return rv;
543 }
544 
onoff_cancel(struct onoff_manager * mgr,struct onoff_client * cli)545 int onoff_cancel(struct onoff_manager *mgr,
546 		 struct onoff_client *cli)
547 {
548 	if ((mgr == NULL) || (cli == NULL)) {
549 		return -EINVAL;
550 	}
551 
552 	int rv = -EALREADY;
553 	k_spinlock_key_t key = k_spin_lock(&mgr->lock);
554 	uint32_t state = mgr->flags & ONOFF_STATE_MASK;
555 
556 	if (sys_slist_find_and_remove(&mgr->clients, &cli->node)) {
557 		__ASSERT_NO_MSG((state == ONOFF_STATE_TO_ON)
558 				|| (state == ONOFF_STATE_TO_OFF)
559 				|| (state == ONOFF_STATE_RESETTING));
560 		rv = state;
561 	}
562 
563 	k_spin_unlock(&mgr->lock, key);
564 
565 	return rv;
566 }
567 
onoff_monitor_register(struct onoff_manager * mgr,struct onoff_monitor * mon)568 int onoff_monitor_register(struct onoff_manager *mgr,
569 			   struct onoff_monitor *mon)
570 {
571 	if ((mgr == NULL)
572 	    || (mon == NULL)
573 	    || (mon->callback == NULL)) {
574 		return -EINVAL;
575 	}
576 
577 	k_spinlock_key_t key = k_spin_lock(&mgr->lock);
578 
579 	sys_slist_append(&mgr->monitors, &mon->node);
580 
581 	k_spin_unlock(&mgr->lock, key);
582 
583 	return 0;
584 }
585 
onoff_monitor_unregister(struct onoff_manager * mgr,struct onoff_monitor * mon)586 int onoff_monitor_unregister(struct onoff_manager *mgr,
587 			     struct onoff_monitor *mon)
588 {
589 	int rv = -EINVAL;
590 
591 	if ((mgr == NULL)
592 	    || (mon == NULL)) {
593 		return rv;
594 	}
595 
596 	k_spinlock_key_t key = k_spin_lock(&mgr->lock);
597 
598 	if (sys_slist_find_and_remove(&mgr->monitors, &mon->node)) {
599 		rv = 0;
600 	}
601 
602 	k_spin_unlock(&mgr->lock, key);
603 
604 	return rv;
605 }
606 
onoff_sync_lock(struct onoff_sync_service * srv,k_spinlock_key_t * keyp)607 int onoff_sync_lock(struct onoff_sync_service *srv,
608 		    k_spinlock_key_t *keyp)
609 {
610 	*keyp = k_spin_lock(&srv->lock);
611 	return srv->count;
612 }
613 
onoff_sync_finalize(struct onoff_sync_service * srv,k_spinlock_key_t key,struct onoff_client * cli,int res,bool on)614 int onoff_sync_finalize(struct onoff_sync_service *srv,
615 			k_spinlock_key_t key,
616 			struct onoff_client *cli,
617 			int res,
618 			bool on)
619 {
620 	uint32_t state = ONOFF_STATE_ON;
621 
622 	/* Clear errors visible when locked.  If they are to be
623 	 * preserved the caller must finalize with the previous
624 	 * error code.
625 	 */
626 	if (srv->count < 0) {
627 		srv->count = 0;
628 	}
629 	if (res < 0) {
630 		srv->count = res;
631 		state = ONOFF_STATE_ERROR;
632 	} else if (on) {
633 		srv->count += 1;
634 	} else {
635 		srv->count -= 1;
636 		/* state would be either off or on, but since
637 		 * callbacks are used only when turning on don't
638 		 * bother changing it.
639 		 */
640 	}
641 
642 	int rv = srv->count;
643 
644 	k_spin_unlock(&srv->lock, key);
645 
646 	if (cli) {
647 		/* Detect service mis-use: onoff does not callback on transition
648 		 * to off, so no client should have been passed.
649 		 */
650 		__ASSERT_NO_MSG(on);
651 		notify_one(NULL, cli, state, res);
652 	}
653 
654 	return rv;
655 }
656