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 != NULL) {
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 != NULL) {
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