summaryrefslogtreecommitdiff
path: root/common/usbc/usb_sm.c
blob: 04b7193c0f05cc66f8f018ae472d03d81122a7f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/* Copyright 2019 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 "task.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;

	/*
	 * Since we are changing states, we want to ensure that we process the
	 * next state's run method as soon as we can to ensure that we don't
	 * delay important processing until the next task interval.
	 */
	if (IS_ENABLED(HAS_TASK_PD_C0))
		task_wake(PD_PORT_TO_TASK_ID(port));
}

/*
 * 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;
}