123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 |
- /*
- * Copyright (c) 2019 Peter Bigot Consulting, LLC
- * Copyright (c) 2020 Nordic Semiconductor ASA
- *
- * SPDX-License-Identifier: Apache-2.0
- */
- #include <kernel.h>
- #include <sys/onoff.h>
- #include <stdio.h>
- #define SERVICE_REFS_MAX UINT16_MAX
- /* Confirm consistency of public flags with private flags */
- BUILD_ASSERT((ONOFF_FLAG_ERROR | ONOFF_FLAG_ONOFF | ONOFF_FLAG_TRANSITION)
- < BIT(3));
- #define ONOFF_FLAG_PROCESSING BIT(3)
- #define ONOFF_FLAG_COMPLETE BIT(4)
- #define ONOFF_FLAG_RECHECK BIT(5)
- /* These symbols in the ONOFF_FLAGS namespace identify bits in
- * onoff_manager::flags that indicate the state of the machine. The
- * bits are manipulated by process_event() under lock, and actions
- * cued by bit values are executed outside of lock within
- * process_event().
- *
- * * ERROR indicates that the machine is in an error state. When
- * this bit is set ONOFF will be cleared.
- * * ONOFF indicates whether the target/current state is off (clear)
- * or on (set).
- * * TRANSITION indicates whether a service transition function is in
- * progress. It combines with ONOFF to identify start and stop
- * transitions, and with ERROR to identify a reset transition.
- * * PROCESSING indicates that the process_event() loop is active. It
- * is used to defer initiation of transitions and other complex
- * state changes while invoking notifications associated with a
- * state transition. This bounds the depth by limiting
- * active process_event() call stacks to two instances. State changes
- * initiated by a nested call will be executed when control returns
- * to the parent call.
- * * COMPLETE indicates that a transition completion notification has
- * been received. This flag is set in the notification, and cleared
- * by process_events() which is invoked from the notification. In
- * the case of nested process_events() the processing is deferred to
- * the top invocation.
- * * RECHECK indicates that a state transition has completed but
- * process_events() must re-check the overall state to confirm no
- * additional transitions are required. This is used to simplfy the
- * logic when, for example, a request is received during a
- * transition to off, which means that when the transition completes
- * a transition to on must be initiated if the request is still
- * present. Transition to ON with no remaining requests similarly
- * triggers a recheck.
- */
- /* Identify the events that can trigger state changes, as well as an
- * internal state used when processing deferred actions.
- */
- enum event_type {
- /* No-op event: used to process deferred changes.
- *
- * This event is local to the process loop.
- */
- EVT_NOP,
- /* Completion of a service transition.
- *
- * This event is triggered by the transition notify callback.
- * It can be received only when the machine is in a transition
- * state (TO-ON, TO-OFF, or RESETTING).
- */
- EVT_COMPLETE,
- /* Reassess whether a transition from a stable state is needed.
- *
- * This event causes:
- * * a start from OFF when there are clients;
- * * a stop from ON when there are no clients;
- * * a reset from ERROR when there are clients.
- *
- * The client list can change while the manager lock is
- * released (e.g. during client and monitor notifications and
- * transition initiations), so this event records the
- * potential for these state changes, and process_event() ...
- *
- */
- EVT_RECHECK,
- /* Transition to on.
- *
- * This is synthesized from EVT_RECHECK in a non-nested
- * process_event() when state OFF is confirmed with a
- * non-empty client (request) list.
- */
- EVT_START,
- /* Transition to off.
- *
- * This is synthesized from EVT_RECHECK in a non-nested
- * process_event() when state ON is confirmed with a
- * zero reference count.
- */
- EVT_STOP,
- /* Transition to resetting.
- *
- * This is synthesized from EVT_RECHECK in a non-nested
- * process_event() when state ERROR is confirmed with a
- * non-empty client (reset) list.
- */
- EVT_RESET,
- };
- static void set_state(struct onoff_manager *mgr,
- uint32_t state)
- {
- mgr->flags = (state & ONOFF_STATE_MASK)
- | (mgr->flags & ~ONOFF_STATE_MASK);
- }
- static int validate_args(const struct onoff_manager *mgr,
- struct onoff_client *cli)
- {
- if ((mgr == NULL) || (cli == NULL)) {
- return -EINVAL;
- }
- int rv = sys_notify_validate(&cli->notify);
- if ((rv == 0)
- && ((cli->notify.flags
- & ~BIT_MASK(ONOFF_CLIENT_EXTENSION_POS)) != 0)) {
- rv = -EINVAL;
- }
- return rv;
- }
- int onoff_manager_init(struct onoff_manager *mgr,
- const struct onoff_transitions *transitions)
- {
- if ((mgr == NULL)
- || (transitions == NULL)
- || (transitions->start == NULL)
- || (transitions->stop == NULL)) {
- return -EINVAL;
- }
- *mgr = (struct onoff_manager)ONOFF_MANAGER_INITIALIZER(transitions);
- return 0;
- }
- static void notify_monitors(struct onoff_manager *mgr,
- uint32_t state,
- int res)
- {
- sys_slist_t *mlist = &mgr->monitors;
- struct onoff_monitor *mon;
- struct onoff_monitor *tmp;
- SYS_SLIST_FOR_EACH_CONTAINER_SAFE(mlist, mon, tmp, node) {
- mon->callback(mgr, mon, state, res);
- }
- }
- static void notify_one(struct onoff_manager *mgr,
- struct onoff_client *cli,
- uint32_t state,
- int res)
- {
- onoff_client_callback cb =
- (onoff_client_callback)sys_notify_finalize(&cli->notify, res);
- if (cb) {
- cb(mgr, cli, state, res);
- }
- }
- static void notify_all(struct onoff_manager *mgr,
- sys_slist_t *list,
- uint32_t state,
- int res)
- {
- while (!sys_slist_is_empty(list)) {
- sys_snode_t *node = sys_slist_get_not_empty(list);
- struct onoff_client *cli =
- CONTAINER_OF(node,
- struct onoff_client,
- node);
- notify_one(mgr, cli, state, res);
- }
- }
- static void process_event(struct onoff_manager *mgr,
- int evt,
- k_spinlock_key_t key);
- static void transition_complete(struct onoff_manager *mgr,
- int res)
- {
- k_spinlock_key_t key = k_spin_lock(&mgr->lock);
- mgr->last_res = res;
- process_event(mgr, EVT_COMPLETE, key);
- }
- /* Detect whether static state requires a transition. */
- static int process_recheck(struct onoff_manager *mgr)
- {
- int evt = EVT_NOP;
- uint32_t state = mgr->flags & ONOFF_STATE_MASK;
- if ((state == ONOFF_STATE_OFF)
- && !sys_slist_is_empty(&mgr->clients)) {
- evt = EVT_START;
- } else if ((state == ONOFF_STATE_ON)
- && (mgr->refs == 0U)) {
- evt = EVT_STOP;
- } else if ((state == ONOFF_STATE_ERROR)
- && !sys_slist_is_empty(&mgr->clients)) {
- evt = EVT_RESET;
- } else {
- ;
- }
- return evt;
- }
- /* Process a transition completion.
- *
- * If the completion requires notifying clients, the clients are moved
- * from the manager to the output list for notification.
- */
- static void process_complete(struct onoff_manager *mgr,
- sys_slist_t *clients,
- int res)
- {
- uint32_t state = mgr->flags & ONOFF_STATE_MASK;
- if (res < 0) {
- /* Enter ERROR state and notify all clients. */
- *clients = mgr->clients;
- sys_slist_init(&mgr->clients);
- set_state(mgr, ONOFF_STATE_ERROR);
- } else if ((state == ONOFF_STATE_TO_ON)
- || (state == ONOFF_STATE_RESETTING)) {
- *clients = mgr->clients;
- sys_slist_init(&mgr->clients);
- if (state == ONOFF_STATE_TO_ON) {
- struct onoff_client *cp;
- /* Increment reference count for all remaining
- * clients and enter ON state.
- */
- SYS_SLIST_FOR_EACH_CONTAINER(clients, cp, node) {
- mgr->refs += 1U;
- }
- set_state(mgr, ONOFF_STATE_ON);
- } else {
- __ASSERT_NO_MSG(state == ONOFF_STATE_RESETTING);
- set_state(mgr, ONOFF_STATE_OFF);
- }
- if (process_recheck(mgr) != EVT_NOP) {
- mgr->flags |= ONOFF_FLAG_RECHECK;
- }
- } else if (state == ONOFF_STATE_TO_OFF) {
- /* Any active clients are requests waiting for this
- * transition to complete. Queue a RECHECK event to
- * ensure we don't miss them if we don't unlock to
- * tell anybody about the completion.
- */
- set_state(mgr, ONOFF_STATE_OFF);
- if (process_recheck(mgr) != EVT_NOP) {
- mgr->flags |= ONOFF_FLAG_RECHECK;
- }
- } else {
- __ASSERT_NO_MSG(false);
- }
- }
- /* There are two points in the state machine where the machine is
- * unlocked to perform some external action:
- * * Initiation of an transition due to some event;
- * * Invocation of the user-specified callback when a stable state is
- * reached or an error detected.
- *
- * Events received during these unlocked periods are recorded in the
- * state, but processing is deferred to the top-level invocation which
- * will loop to handle any events that occurred during the unlocked
- * regions.
- */
- static void process_event(struct onoff_manager *mgr,
- int evt,
- k_spinlock_key_t key)
- {
- sys_slist_t clients;
- uint32_t state = mgr->flags & ONOFF_STATE_MASK;
- int res = 0;
- bool processing = ((mgr->flags & ONOFF_FLAG_PROCESSING) != 0);
- __ASSERT_NO_MSG(evt != EVT_NOP);
- /* If this is a nested call record the event for processing in
- * the top invocation.
- */
- if (processing) {
- if (evt == EVT_COMPLETE) {
- mgr->flags |= ONOFF_FLAG_COMPLETE;
- } else {
- __ASSERT_NO_MSG(evt == EVT_RECHECK);
- mgr->flags |= ONOFF_FLAG_RECHECK;
- }
- goto out;
- }
- sys_slist_init(&clients);
- do {
- onoff_transition_fn transit = NULL;
- if (evt == EVT_RECHECK) {
- evt = process_recheck(mgr);
- }
- if (evt == EVT_NOP) {
- break;
- }
- res = 0;
- if (evt == EVT_COMPLETE) {
- res = mgr->last_res;
- process_complete(mgr, &clients, res);
- /* NB: This can trigger a RECHECK */
- } else if (evt == EVT_START) {
- __ASSERT_NO_MSG(state == ONOFF_STATE_OFF);
- __ASSERT_NO_MSG(!sys_slist_is_empty(&mgr->clients));
- transit = mgr->transitions->start;
- __ASSERT_NO_MSG(transit != NULL);
- set_state(mgr, ONOFF_STATE_TO_ON);
- } else if (evt == EVT_STOP) {
- __ASSERT_NO_MSG(state == ONOFF_STATE_ON);
- __ASSERT_NO_MSG(mgr->refs == 0);
- transit = mgr->transitions->stop;
- __ASSERT_NO_MSG(transit != NULL);
- set_state(mgr, ONOFF_STATE_TO_OFF);
- } else if (evt == EVT_RESET) {
- __ASSERT_NO_MSG(state == ONOFF_STATE_ERROR);
- __ASSERT_NO_MSG(!sys_slist_is_empty(&mgr->clients));
- transit = mgr->transitions->reset;
- __ASSERT_NO_MSG(transit != NULL);
- set_state(mgr, ONOFF_STATE_RESETTING);
- } else {
- __ASSERT_NO_MSG(false);
- }
- /* Have to unlock and do something if any of:
- * * We changed state and there are monitors;
- * * We completed a transition and there are clients to notify;
- * * We need to initiate a transition.
- */
- bool do_monitors = (state != (mgr->flags & ONOFF_STATE_MASK))
- && !sys_slist_is_empty(&mgr->monitors);
- evt = EVT_NOP;
- if (do_monitors
- || !sys_slist_is_empty(&clients)
- || (transit != NULL)) {
- uint32_t flags = mgr->flags | ONOFF_FLAG_PROCESSING;
- mgr->flags = flags;
- state = flags & ONOFF_STATE_MASK;
- k_spin_unlock(&mgr->lock, key);
- if (do_monitors) {
- notify_monitors(mgr, state, res);
- }
- if (!sys_slist_is_empty(&clients)) {
- notify_all(mgr, &clients, state, res);
- }
- if (transit != NULL) {
- transit(mgr, transition_complete);
- }
- key = k_spin_lock(&mgr->lock);
- mgr->flags &= ~ONOFF_FLAG_PROCESSING;
- state = mgr->flags & ONOFF_STATE_MASK;
- }
- /* Process deferred events. Completion takes priority
- * over recheck.
- */
- if ((mgr->flags & ONOFF_FLAG_COMPLETE) != 0) {
- mgr->flags &= ~ONOFF_FLAG_COMPLETE;
- evt = EVT_COMPLETE;
- } else if ((mgr->flags & ONOFF_FLAG_RECHECK) != 0) {
- mgr->flags &= ~ONOFF_FLAG_RECHECK;
- evt = EVT_RECHECK;
- } else {
- ;
- }
- state = mgr->flags & ONOFF_STATE_MASK;
- } while (evt != EVT_NOP);
- out:
- k_spin_unlock(&mgr->lock, key);
- }
- int onoff_request(struct onoff_manager *mgr,
- struct onoff_client *cli)
- {
- bool add_client = false; /* add client to pending list */
- bool start = false; /* trigger a start transition */
- bool notify = false; /* do client notification */
- int rv = validate_args(mgr, cli);
- if (rv < 0) {
- return rv;
- }
- k_spinlock_key_t key = k_spin_lock(&mgr->lock);
- uint32_t state = mgr->flags & ONOFF_STATE_MASK;
- /* Reject if this would overflow the reference count. */
- if (mgr->refs == SERVICE_REFS_MAX) {
- rv = -EAGAIN;
- goto out;
- }
- rv = state;
- if (state == ONOFF_STATE_ON) {
- /* Increment reference count, notify in exit */
- notify = true;
- mgr->refs += 1U;
- } else if ((state == ONOFF_STATE_OFF)
- || (state == ONOFF_STATE_TO_OFF)
- || (state == ONOFF_STATE_TO_ON)) {
- /* Start if OFF, queue client */
- start = (state == ONOFF_STATE_OFF);
- add_client = true;
- } else if (state == ONOFF_STATE_RESETTING) {
- rv = -ENOTSUP;
- } else {
- __ASSERT_NO_MSG(state == ONOFF_STATE_ERROR);
- rv = -EIO;
- }
- out:
- if (add_client) {
- sys_slist_append(&mgr->clients, &cli->node);
- }
- if (start) {
- process_event(mgr, EVT_RECHECK, key);
- } else {
- k_spin_unlock(&mgr->lock, key);
- if (notify) {
- notify_one(mgr, cli, state, 0);
- }
- }
- return rv;
- }
- int onoff_release(struct onoff_manager *mgr)
- {
- bool stop = false; /* trigger a stop transition */
- k_spinlock_key_t key = k_spin_lock(&mgr->lock);
- uint32_t state = mgr->flags & ONOFF_STATE_MASK;
- int rv = state;
- if (state != ONOFF_STATE_ON) {
- if (state == ONOFF_STATE_ERROR) {
- rv = -EIO;
- } else {
- rv = -ENOTSUP;
- }
- goto out;
- }
- __ASSERT_NO_MSG(mgr->refs > 0);
- mgr->refs -= 1U;
- stop = (mgr->refs == 0);
- out:
- if (stop) {
- process_event(mgr, EVT_RECHECK, key);
- } else {
- k_spin_unlock(&mgr->lock, key);
- }
- return rv;
- }
- int onoff_reset(struct onoff_manager *mgr,
- struct onoff_client *cli)
- {
- bool reset = false;
- int rv = validate_args(mgr, cli);
- if ((rv >= 0)
- && (mgr->transitions->reset == NULL)) {
- rv = -ENOTSUP;
- }
- if (rv < 0) {
- return rv;
- }
- k_spinlock_key_t key = k_spin_lock(&mgr->lock);
- uint32_t state = mgr->flags & ONOFF_STATE_MASK;
- rv = state;
- if ((state & ONOFF_FLAG_ERROR) == 0) {
- rv = -EALREADY;
- } else {
- reset = (state != ONOFF_STATE_RESETTING);
- sys_slist_append(&mgr->clients, &cli->node);
- }
- if (reset) {
- process_event(mgr, EVT_RECHECK, key);
- } else {
- k_spin_unlock(&mgr->lock, key);
- }
- return rv;
- }
- int onoff_cancel(struct onoff_manager *mgr,
- struct onoff_client *cli)
- {
- if ((mgr == NULL) || (cli == NULL)) {
- return -EINVAL;
- }
- int rv = -EALREADY;
- k_spinlock_key_t key = k_spin_lock(&mgr->lock);
- uint32_t state = mgr->flags & ONOFF_STATE_MASK;
- if (sys_slist_find_and_remove(&mgr->clients, &cli->node)) {
- __ASSERT_NO_MSG((state == ONOFF_STATE_TO_ON)
- || (state == ONOFF_STATE_TO_OFF)
- || (state == ONOFF_STATE_RESETTING));
- rv = state;
- }
- k_spin_unlock(&mgr->lock, key);
- return rv;
- }
- int onoff_monitor_register(struct onoff_manager *mgr,
- struct onoff_monitor *mon)
- {
- if ((mgr == NULL)
- || (mon == NULL)
- || (mon->callback == NULL)) {
- return -EINVAL;
- }
- k_spinlock_key_t key = k_spin_lock(&mgr->lock);
- sys_slist_append(&mgr->monitors, &mon->node);
- k_spin_unlock(&mgr->lock, key);
- return 0;
- }
- int onoff_monitor_unregister(struct onoff_manager *mgr,
- struct onoff_monitor *mon)
- {
- int rv = -EINVAL;
- if ((mgr == NULL)
- || (mon == NULL)) {
- return rv;
- }
- k_spinlock_key_t key = k_spin_lock(&mgr->lock);
- if (sys_slist_find_and_remove(&mgr->monitors, &mon->node)) {
- rv = 0;
- }
- k_spin_unlock(&mgr->lock, key);
- return rv;
- }
- int onoff_sync_lock(struct onoff_sync_service *srv,
- k_spinlock_key_t *keyp)
- {
- *keyp = k_spin_lock(&srv->lock);
- return srv->count;
- }
- int onoff_sync_finalize(struct onoff_sync_service *srv,
- k_spinlock_key_t key,
- struct onoff_client *cli,
- int res,
- bool on)
- {
- uint32_t state = ONOFF_STATE_ON;
- /* Clear errors visible when locked. If they are to be
- * preserved the caller must finalize with the previous
- * error code.
- */
- if (srv->count < 0) {
- srv->count = 0;
- }
- if (res < 0) {
- srv->count = res;
- state = ONOFF_STATE_ERROR;
- } else if (on) {
- srv->count += 1;
- } else {
- srv->count -= 1;
- /* state would be either off or on, but since
- * callbacks are used only when turning on don't
- * bother changing it.
- */
- }
- int rv = srv->count;
- k_spin_unlock(&srv->lock, key);
- if (cli) {
- /* Detect service mis-use: onoff does not callback on transition
- * to off, so no client should have been passed.
- */
- __ASSERT_NO_MSG(on);
- notify_one(NULL, cli, state, res);
- }
- return rv;
- }
|