/* Copyright 2020 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "common.h" #include "console.h" #include "stdbool.h" #include "usb_pd.h" #include "usb_sm.h" #include "util.h" #ifdef CONFIG_COMMON_RUNTIME #define CPRINTF(format, args...) cprintf(CC_USB, format, ## args) #define CPRINTS(format, args...) cprints(CC_USB, format, ## args) #else /* CONFIG_COMMON_RUNTIME */ #define CPRINTF(format, args...) #define CPRINTS(format, args...) #endif /* Private structure (to this file) used to track state machine context */ struct internal_ctx { usb_state_ptr last_entered; uint32_t running : 1; uint32_t enter : 1; uint32_t exit : 1; }; BUILD_ASSERT(sizeof(struct internal_ctx) == member_size(struct sm_ctx, internal)); /* Gets the first shared parent state between a and b (inclusive) */ static usb_state_ptr shared_parent_state(usb_state_ptr a, usb_state_ptr b) { const usb_state_ptr orig_b = b; /* There are no common ancestors */ if (b == NULL) return NULL; /* This assumes that both A and B are NULL terminated without cycles */ while (a != NULL) { /* We found a match return */ if (a == b) return a; /* * Otherwise, increment b down the list for comparison until we * run out, then increment a and start over on b for comparison */ if (b->parent == NULL) { a = a->parent; b = orig_b; } else { b = b->parent; } } return NULL; } /* * Call all entry functions of parents before children. If set_state is called * during one of the entry functions, then do not call any remaining entry * functions. */ static void call_entry_functions(const int port, struct internal_ctx *const internal, const usb_state_ptr stop, const usb_state_ptr current) { if (current == stop) return; call_entry_functions(port, internal, stop, current->parent); /* * If the previous entry function called set_state, then don't enter * remaining states. */ if (!internal->enter) return; /* Track the latest state that was entered, so we can exit properly. */ internal->last_entered = current; if (current->entry) current->entry(port); } /* * Call all exit functions of children before parents. Note set_state is ignored * during an exit function. */ static void call_exit_functions(const int port, const usb_state_ptr stop, const usb_state_ptr current) { if (current == stop) return; if (current->exit) current->exit(port); call_exit_functions(port, stop, current->parent); } void set_state(const int port, struct sm_ctx *const ctx, const usb_state_ptr new_state) { struct internal_ctx * const internal = (void *) ctx->internal; usb_state_ptr last_state; usb_state_ptr shared_parent; /* * It does not make sense to call set_state in an exit phase of a state * since we are already in a transition; we would always ignore the * intended state to transition into. */ if (internal->exit) { CPRINTF("C%d: Ignoring set state to 0x%pP within 0x%pP", port, new_state, ctx->current); return; } /* * Determine the last state that was entered. Normally it is current, * but we could have called set_state within an entry phase, so we * shouldn't exit any states that weren't fully entered. */ last_state = internal->enter ? internal->last_entered : ctx->current; /* We don't exit and re-enter shared parent states */ shared_parent = shared_parent_state(last_state, new_state); /* * Exit all of the non-common states from the last state. */ internal->exit = true; call_exit_functions(port, shared_parent, last_state); internal->exit = false; ctx->previous = ctx->current; ctx->current = new_state; /* * Enter all new non-common states. last_entered will contain the last * state that successfully entered before another set_state was called. */ internal->last_entered = NULL; internal->enter = true; call_entry_functions(port, internal, shared_parent, ctx->current); /* * Setting enter to false ensures that all pending entry calls will be * skipped (in the case of a parent state calling set_state, which means * we should not enter any child states) */ internal->enter = false; /* * If we set_state while we are running a child state, then stop running * any remaining parent states. */ internal->running = false; } /* * Call all run functions of children before parents. If set_state is called * during one of the entry functions, then do not call any remaining entry * functions. */ static void call_run_functions(const int port, const struct internal_ctx *const internal, const usb_state_ptr current) { if (!current) return; /* If set_state is called during run, don't call remain functions. */ if (!internal->running) return; if (current->run) current->run(port); call_run_functions(port, internal, current->parent); } void run_state(const int port, struct sm_ctx *const ctx) { struct internal_ctx * const internal = (void *) ctx->internal; internal->running = true; call_run_functions(port, internal, ctx->current); internal->running = false; }