1 /*
2  * Copyright (c) 2022 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/drivers/usb/udc.h>
8 #include <zephyr/usb/usbd.h>
9 
10 #include "usbd_device.h"
11 #include "usbd_config.h"
12 #include "usbd_interface.h"
13 #include "usbd_ch9.h"
14 #include "usbd_class_api.h"
15 
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_REGISTER(usbd_cfg, CONFIG_USBD_LOG_LEVEL);
18 
usbd_configs(struct usbd_context * uds_ctx,const enum usbd_speed speed)19 static sys_slist_t *usbd_configs(struct usbd_context *uds_ctx,
20 				 const enum usbd_speed speed)
21 {
22 	switch (speed) {
23 	case USBD_SPEED_FS:
24 		return &uds_ctx->fs_configs;
25 	case USBD_SPEED_HS:
26 		return &uds_ctx->hs_configs;
27 	default:
28 		return NULL;
29 	}
30 }
31 
usbd_config_get(struct usbd_context * const uds_ctx,const enum usbd_speed speed,const uint8_t cfg)32 struct usbd_config_node *usbd_config_get(struct usbd_context *const uds_ctx,
33 					 const enum usbd_speed speed,
34 					 const uint8_t cfg)
35 {
36 	struct usbd_config_node *cfg_nd;
37 
38 	SYS_SLIST_FOR_EACH_CONTAINER(usbd_configs(uds_ctx, speed), cfg_nd, node) {
39 		if (usbd_config_get_value(cfg_nd) == cfg) {
40 			return cfg_nd;
41 		}
42 	}
43 
44 	return NULL;
45 }
46 
47 struct usbd_config_node *
usbd_config_get_current(struct usbd_context * const uds_ctx)48 usbd_config_get_current(struct usbd_context *const uds_ctx)
49 {
50 	if (!usbd_state_is_configured(uds_ctx)) {
51 		LOG_INF("No configuration set (Address state?)");
52 		return NULL;
53 	}
54 
55 	return usbd_config_get(uds_ctx, usbd_bus_speed(uds_ctx),
56 			       usbd_get_config_value(uds_ctx));
57 }
58 
usbd_config_classes_enable(struct usbd_config_node * const cfg_nd,const bool enable)59 static void usbd_config_classes_enable(struct usbd_config_node *const cfg_nd,
60 				       const bool enable)
61 {
62 	struct usbd_class_node *c_nd;
63 
64 	SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
65 		if (enable) {
66 			usbd_class_enable(c_nd->c_data);
67 		} else {
68 			usbd_class_disable(c_nd->c_data);
69 		}
70 	}
71 }
72 
73 /* Reset configuration to addressed state, shutdown all endpoints */
usbd_config_reset(struct usbd_context * const uds_ctx)74 static int usbd_config_reset(struct usbd_context *const uds_ctx)
75 {
76 	struct usbd_config_node *cfg_nd;
77 	int ret = 0;
78 
79 	cfg_nd = usbd_config_get_current(uds_ctx);
80 	if (cfg_nd == NULL) {
81 		return -ENODATA;
82 	}
83 
84 	ret = usbd_interface_shutdown(uds_ctx, cfg_nd);
85 
86 	memset(&uds_ctx->ch9_data.alternate, 0,
87 	       USBD_NUMOF_INTERFACES_MAX);
88 
89 	usbd_set_config_value(uds_ctx, 0);
90 	usbd_config_classes_enable(cfg_nd, false);
91 
92 	return ret;
93 }
94 
usbd_config_exist(struct usbd_context * const uds_ctx,const enum usbd_speed speed,const uint8_t cfg)95 bool usbd_config_exist(struct usbd_context *const uds_ctx,
96 		       const enum usbd_speed speed,
97 		       const uint8_t cfg)
98 {
99 	struct usbd_config_node *config;
100 
101 	config = usbd_config_get(uds_ctx, speed, cfg);
102 
103 	return (config != NULL) ? true : false;
104 }
105 
usbd_config_set(struct usbd_context * const uds_ctx,const uint8_t new_cfg)106 int usbd_config_set(struct usbd_context *const uds_ctx,
107 		    const uint8_t new_cfg)
108 {
109 	struct usbd_config_node *cfg_nd;
110 	const enum usbd_speed speed = usbd_bus_speed(uds_ctx);
111 	int ret;
112 
113 	if (usbd_get_config_value(uds_ctx) != 0) {
114 		ret = usbd_config_reset(uds_ctx);
115 		if (ret) {
116 			LOG_ERR("Failed to reset configuration");
117 			return ret;
118 		}
119 	}
120 
121 	if (new_cfg == 0) {
122 		usbd_set_config_value(uds_ctx, new_cfg);
123 		return 0;
124 	}
125 
126 	cfg_nd = usbd_config_get(uds_ctx, speed, new_cfg);
127 	if (cfg_nd == NULL) {
128 		return -ENODATA;
129 	}
130 
131 	ret = usbd_interface_default(uds_ctx, speed, cfg_nd);
132 	if (ret) {
133 		return ret;
134 	}
135 
136 	usbd_set_config_value(uds_ctx, new_cfg);
137 	usbd_config_classes_enable(cfg_nd, true);
138 
139 	return 0;
140 }
141 
142 /*
143  * All the functions below are part of public USB device support API.
144  */
145 
usbd_config_attrib_rwup(struct usbd_context * const uds_ctx,const enum usbd_speed speed,const uint8_t cfg,const bool enable)146 int usbd_config_attrib_rwup(struct usbd_context *const uds_ctx,
147 			    const enum usbd_speed speed,
148 			    const uint8_t cfg, const bool enable)
149 {
150 	struct usbd_config_node *cfg_nd;
151 	struct usb_cfg_descriptor *desc;
152 	struct udc_device_caps caps;
153 	int ret = 0;
154 
155 	usbd_device_lock(uds_ctx);
156 
157 	if (usbd_is_enabled(uds_ctx)) {
158 		ret = -EALREADY;
159 		goto attrib_rwup_exit;
160 	}
161 
162 	caps = udc_caps(uds_ctx->dev);
163 	if (!caps.rwup) {
164 		LOG_ERR("Feature not supported by controller");
165 		ret = -ENOTSUP;
166 		goto attrib_rwup_exit;
167 	}
168 
169 	cfg_nd = usbd_config_get(uds_ctx, speed, cfg);
170 	if (cfg_nd == NULL) {
171 		LOG_INF("Configuration %u not found", cfg);
172 		ret = -ENODATA;
173 		goto attrib_rwup_exit;
174 	}
175 
176 	desc = cfg_nd->desc;
177 	if (enable) {
178 		desc->bmAttributes |= USB_SCD_REMOTE_WAKEUP;
179 	} else {
180 		desc->bmAttributes &= ~USB_SCD_REMOTE_WAKEUP;
181 	}
182 
183 attrib_rwup_exit:
184 	usbd_device_unlock(uds_ctx);
185 	return ret;
186 }
187 
usbd_config_attrib_self(struct usbd_context * const uds_ctx,const enum usbd_speed speed,const uint8_t cfg,const bool enable)188 int usbd_config_attrib_self(struct usbd_context *const uds_ctx,
189 			    const enum usbd_speed speed,
190 			    const uint8_t cfg, const bool enable)
191 {
192 	struct usbd_config_node *cfg_nd;
193 	struct usb_cfg_descriptor *desc;
194 	int ret = 0;
195 
196 	usbd_device_lock(uds_ctx);
197 
198 	if (usbd_is_enabled(uds_ctx)) {
199 		ret = -EALREADY;
200 		goto attrib_self_exit;
201 	}
202 
203 	cfg_nd = usbd_config_get(uds_ctx, speed, cfg);
204 	if (cfg_nd == NULL) {
205 		LOG_INF("Configuration %u not found", cfg);
206 		ret = -ENODATA;
207 		goto attrib_self_exit;
208 	}
209 
210 	desc = cfg_nd->desc;
211 	if (enable) {
212 		desc->bmAttributes |= USB_SCD_SELF_POWERED;
213 	} else {
214 		desc->bmAttributes &= ~USB_SCD_SELF_POWERED;
215 	}
216 
217 attrib_self_exit:
218 	usbd_device_unlock(uds_ctx);
219 	return ret;
220 }
221 
usbd_config_maxpower(struct usbd_context * const uds_ctx,const enum usbd_speed speed,const uint8_t cfg,const uint8_t power)222 int usbd_config_maxpower(struct usbd_context *const uds_ctx,
223 			 const enum usbd_speed speed,
224 			 const uint8_t cfg, const uint8_t power)
225 {
226 	struct usbd_config_node *cfg_nd;
227 	struct usb_cfg_descriptor *desc;
228 	int ret = 0;
229 
230 	usbd_device_lock(uds_ctx);
231 
232 	if (usbd_is_enabled(uds_ctx)) {
233 		ret = -EALREADY;
234 		goto maxpower_exit;
235 	}
236 
237 	cfg_nd = usbd_config_get(uds_ctx, speed, cfg);
238 	if (cfg_nd == NULL) {
239 		LOG_INF("Configuration %u not found", cfg);
240 		ret = -ENODATA;
241 		goto maxpower_exit;
242 	}
243 
244 	desc = cfg_nd->desc;
245 	desc->bMaxPower = power;
246 
247 maxpower_exit:
248 	usbd_device_unlock(uds_ctx);
249 	return ret;
250 }
251 
usbd_add_configuration(struct usbd_context * const uds_ctx,const enum usbd_speed speed,struct usbd_config_node * const cfg_nd)252 int usbd_add_configuration(struct usbd_context *const uds_ctx,
253 			   const enum usbd_speed speed,
254 			   struct usbd_config_node *const cfg_nd)
255 {
256 	struct usb_cfg_descriptor *desc = cfg_nd->desc;
257 	sys_slist_t *configs;
258 	sys_snode_t *node;
259 	int ret = 0;
260 
261 	usbd_device_lock(uds_ctx);
262 
263 	if (usbd_is_initialized(uds_ctx)) {
264 		LOG_ERR("USB device support is initialized");
265 		ret = -EBUSY;
266 		goto add_configuration_exit;
267 	}
268 
269 	if (speed == USBD_SPEED_HS &&
270 	    usbd_caps_speed(uds_ctx) == USBD_SPEED_FS) {
271 		LOG_ERR("Controller doesn't support HS");
272 		ret = -ENOTSUP;
273 		goto add_configuration_exit;
274 	}
275 
276 	if (desc->bmAttributes & USB_SCD_REMOTE_WAKEUP) {
277 		struct udc_device_caps caps = udc_caps(uds_ctx->dev);
278 
279 		if (!caps.rwup) {
280 			LOG_ERR("Feature not supported by controller");
281 			ret = -ENOTSUP;
282 			goto add_configuration_exit;
283 		}
284 	}
285 
286 	configs = usbd_configs(uds_ctx, speed);
287 	switch (speed) {
288 	case USBD_SPEED_HS:
289 		SYS_SLIST_FOR_EACH_NODE(&uds_ctx->fs_configs, node) {
290 			if (node == &cfg_nd->node) {
291 				LOG_ERR("HS config already on FS list");
292 				ret = -EINVAL;
293 				goto add_configuration_exit;
294 			}
295 		}
296 		break;
297 	case USBD_SPEED_FS:
298 		SYS_SLIST_FOR_EACH_NODE(&uds_ctx->hs_configs, node) {
299 			if (node == &cfg_nd->node) {
300 				LOG_ERR("FS config already on HS list");
301 				ret = -EINVAL;
302 				goto add_configuration_exit;
303 			}
304 		}
305 		break;
306 	default:
307 		LOG_ERR("Unsupported configuration speed");
308 		ret = -ENOTSUP;
309 		goto add_configuration_exit;
310 	}
311 
312 	if (sys_slist_find_and_remove(configs, &cfg_nd->node)) {
313 		LOG_WRN("Configuration %u re-inserted",
314 			usbd_config_get_value(cfg_nd));
315 	} else {
316 		uint8_t num = usbd_get_num_configs(uds_ctx, speed) + 1;
317 
318 		usbd_config_set_value(cfg_nd, num);
319 		usbd_set_num_configs(uds_ctx, speed, num);
320 	}
321 
322 	if (cfg_nd->str_desc_nd != NULL) {
323 		ret = usbd_add_descriptor(uds_ctx, cfg_nd->str_desc_nd);
324 		if (ret != 0) {
325 			LOG_ERR("Failed to add configuration string descriptor");
326 			goto add_configuration_exit;
327 		}
328 
329 		desc->iConfiguration = usbd_str_desc_get_idx(cfg_nd->str_desc_nd);
330 	}
331 
332 	sys_slist_append(configs, &cfg_nd->node);
333 
334 add_configuration_exit:
335 	usbd_device_unlock(uds_ctx);
336 	return ret;
337 }
338