1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright 2025 Google LLC.
4 */
5
6 #include <linux/types.h>
7 #include <linux/list_sort.h>
8 #include <linux/slab.h>
9 #include <linux/mutex.h>
10 #include <linux/workqueue.h>
11 #include <linux/usb/typec_altmode.h>
12
13 #include "class.h"
14
15 /**
16 * struct mode_state - State tracking for a specific Type-C alternate mode
17 * @svid: Standard or Vendor ID of the Alternate Mode
18 * @priority: Mode priority
19 * @error: Outcome of the last attempt to enter the mode
20 * @list: List head to link this mode state into a prioritized list
21 */
22 struct mode_state {
23 u16 svid;
24 u8 priority;
25 int error;
26 struct list_head list;
27 };
28
29 /**
30 * struct mode_selection - Manages the selection and state of Alternate Modes
31 * @mode_list: Prioritized list of available Alternate Modes
32 * @lock: Mutex to protect mode_list
33 * @work: Work structure
34 * @partner: Handle to the Type-C partner device
35 * @active_svid: svid of currently active mode
36 * @timeout: Timeout for a mode entry attempt, ms
37 * @delay: Delay between mode entry/exit attempts, ms
38 */
39 struct mode_selection {
40 struct list_head mode_list;
41 /* Protects the mode_list*/
42 struct mutex lock;
43 struct delayed_work work;
44 struct typec_partner *partner;
45 u16 active_svid;
46 unsigned int timeout;
47 unsigned int delay;
48 };
49
50 /**
51 * struct mode_order - Mode activation tracking
52 * @svid: Standard or Vendor ID of the Alternate Mode
53 * @enter: Flag indicating if the driver is currently attempting to enter or
54 * exit the mode
55 * @result: Outcome of the attempt to activate the mode
56 */
57 struct mode_order {
58 u16 svid;
59 int enter;
60 int result;
61 };
62
activate_altmode(struct device * dev,void * data)63 static int activate_altmode(struct device *dev, void *data)
64 {
65 if (is_typec_partner_altmode(dev)) {
66 struct typec_altmode *alt = to_typec_altmode(dev);
67 struct mode_order *order = (struct mode_order *)data;
68
69 if (order->svid == alt->svid) {
70 if (alt->ops && alt->ops->activate)
71 order->result = alt->ops->activate(alt, order->enter);
72 else
73 order->result = -EOPNOTSUPP;
74 return 1;
75 }
76 }
77 return 0;
78 }
79
mode_selection_activate(struct mode_selection * sel,const u16 svid,const int enter)80 static int mode_selection_activate(struct mode_selection *sel,
81 const u16 svid, const int enter)
82
83 __must_hold(&sel->lock)
84 {
85 struct mode_order order = {.svid = svid, .enter = enter, .result = -ENODEV};
86
87 /*
88 * The port driver may acquire its internal mutex during alternate mode
89 * activation. Since this is the same mutex that may be held during the
90 * execution of typec_altmode_state_update(), it is crucial to release
91 * sel->mutex before activation to avoid potential deadlock.
92 * Note that sel->mode_list must remain invariant throughout this unlocked
93 * interval.
94 */
95 mutex_unlock(&sel->lock);
96 device_for_each_child(&sel->partner->dev, &order, activate_altmode);
97 mutex_lock(&sel->lock);
98
99 return order.result;
100 }
101
mode_list_clean(struct mode_selection * sel)102 static void mode_list_clean(struct mode_selection *sel)
103 {
104 struct mode_state *ms, *tmp;
105
106 list_for_each_entry_safe(ms, tmp, &sel->mode_list, list) {
107 list_del(&ms->list);
108 kfree(ms);
109 }
110 }
111
112 /**
113 * mode_selection_work_fn() - Alternate mode activation task
114 * @work: work structure
115 *
116 * - If the Alternate Mode currently prioritized at the top of the list is already
117 * active, the entire selection process is considered finished.
118 * - If a different Alternate Mode is currently active, the system must exit that
119 * active mode first before attempting any new entry.
120 *
121 * The function then checks the result of the attempt to entre the current mode,
122 * stored in the `ms->error` field:
123 * - if the attempt FAILED, the mode is deactivated and removed from the list.
124 * - `ms->error` value of 0 signifies that the mode has not yet been activated.
125 *
126 * Once successfully activated, the task is scheduled for subsequent entry after
127 * a timeout period. The alternate mode driver is expected to call back with the
128 * actual mode entry result via `typec_altmode_state_update()`.
129 */
mode_selection_work_fn(struct work_struct * work)130 static void mode_selection_work_fn(struct work_struct *work)
131 {
132 struct mode_selection *sel = container_of(work,
133 struct mode_selection, work.work);
134 struct mode_state *ms;
135 unsigned int delay = sel->delay;
136 int result;
137
138 guard(mutex)(&sel->lock);
139
140 ms = list_first_entry_or_null(&sel->mode_list, struct mode_state, list);
141 if (!ms)
142 return;
143
144 if (sel->active_svid == ms->svid) {
145 dev_dbg(&sel->partner->dev, "%x altmode is active\n", ms->svid);
146 mode_list_clean(sel);
147 } else if (sel->active_svid != 0) {
148 result = mode_selection_activate(sel, sel->active_svid, 0);
149 if (result)
150 mode_list_clean(sel);
151 else
152 sel->active_svid = 0;
153 } else if (ms->error) {
154 dev_err(&sel->partner->dev, "%x: entry error %pe\n",
155 ms->svid, ERR_PTR(ms->error));
156 mode_selection_activate(sel, ms->svid, 0);
157 list_del(&ms->list);
158 kfree(ms);
159 } else {
160 result = mode_selection_activate(sel, ms->svid, 1);
161 if (result) {
162 dev_err(&sel->partner->dev, "%x: activation error %pe\n",
163 ms->svid, ERR_PTR(result));
164 list_del(&ms->list);
165 kfree(ms);
166 } else {
167 delay = sel->timeout;
168 ms->error = -ETIMEDOUT;
169 }
170 }
171
172 if (!list_empty(&sel->mode_list))
173 schedule_delayed_work(&sel->work, msecs_to_jiffies(delay));
174 }
175
typec_altmode_state_update(struct typec_partner * partner,const u16 svid,const int error)176 void typec_altmode_state_update(struct typec_partner *partner, const u16 svid,
177 const int error)
178 {
179 struct mode_selection *sel = partner->sel;
180 struct mode_state *ms;
181
182 if (sel) {
183 mutex_lock(&sel->lock);
184 ms = list_first_entry_or_null(&sel->mode_list, struct mode_state, list);
185 if (ms && ms->svid == svid) {
186 ms->error = error;
187 if (cancel_delayed_work(&sel->work))
188 schedule_delayed_work(&sel->work, 0);
189 }
190 if (!error)
191 sel->active_svid = svid;
192 else
193 sel->active_svid = 0;
194 mutex_unlock(&sel->lock);
195 }
196 }
197 EXPORT_SYMBOL_GPL(typec_altmode_state_update);
198
compare_priorities(void * priv,const struct list_head * a,const struct list_head * b)199 static int compare_priorities(void *priv,
200 const struct list_head *a, const struct list_head *b)
201 {
202 const struct mode_state *msa = container_of(a, struct mode_state, list);
203 const struct mode_state *msb = container_of(b, struct mode_state, list);
204
205 if (msa->priority < msb->priority)
206 return -1;
207 return 1;
208 }
209
altmode_add_to_list(struct device * dev,void * data)210 static int altmode_add_to_list(struct device *dev, void *data)
211 {
212 if (is_typec_partner_altmode(dev)) {
213 struct list_head *list = (struct list_head *)data;
214 struct typec_altmode *altmode = to_typec_altmode(dev);
215 const struct typec_altmode *pdev = typec_altmode_get_partner(altmode);
216 struct mode_state *ms;
217
218 if (pdev && altmode->ops && altmode->ops->activate) {
219 ms = kzalloc_obj(*ms);
220 if (!ms)
221 return -ENOMEM;
222 ms->svid = pdev->svid;
223 ms->priority = pdev->priority;
224 INIT_LIST_HEAD(&ms->list);
225 list_add_tail(&ms->list, list);
226 }
227 }
228 return 0;
229 }
230
typec_mode_selection_start(struct typec_partner * partner,const unsigned int delay,const unsigned int timeout)231 int typec_mode_selection_start(struct typec_partner *partner,
232 const unsigned int delay, const unsigned int timeout)
233 {
234 struct mode_selection *sel;
235 int ret;
236
237 if (partner->usb_mode == USB_MODE_USB4)
238 return -EBUSY;
239
240 if (partner->sel)
241 return -EALREADY;
242
243 sel = kzalloc_obj(*sel);
244 if (!sel)
245 return -ENOMEM;
246
247 INIT_LIST_HEAD(&sel->mode_list);
248
249 ret = device_for_each_child(&partner->dev, &sel->mode_list,
250 altmode_add_to_list);
251
252 if (ret || list_empty(&sel->mode_list)) {
253 mode_list_clean(sel);
254 kfree(sel);
255 return ret;
256 }
257
258 list_sort(NULL, &sel->mode_list, compare_priorities);
259 sel->partner = partner;
260 sel->delay = delay;
261 sel->timeout = timeout;
262 mutex_init(&sel->lock);
263 INIT_DELAYED_WORK(&sel->work, mode_selection_work_fn);
264 schedule_delayed_work(&sel->work, msecs_to_jiffies(delay));
265 partner->sel = sel;
266
267 return 0;
268 }
269 EXPORT_SYMBOL_GPL(typec_mode_selection_start);
270
typec_mode_selection_delete(struct typec_partner * partner)271 void typec_mode_selection_delete(struct typec_partner *partner)
272 {
273 struct mode_selection *sel = partner->sel;
274
275 if (sel) {
276 partner->sel = NULL;
277 cancel_delayed_work_sync(&sel->work);
278 mode_list_clean(sel);
279 mutex_destroy(&sel->lock);
280 kfree(sel);
281 }
282 }
283 EXPORT_SYMBOL_GPL(typec_mode_selection_delete);
284