xref: /linux/drivers/usb/typec/mode_selection.c (revision bf4afc53b77aeaa48b5409da5c8da6bb4eff7f43)
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