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 && !USBD_SUPPORTS_HIGH_SPEED) {
270 LOG_ERR("Stack was compiled without High-Speed support");
271 ret = -ENOTSUP;
272 goto add_configuration_exit;
273 }
274
275 if (speed == USBD_SPEED_HS &&
276 usbd_caps_speed(uds_ctx) == USBD_SPEED_FS) {
277 LOG_ERR("Controller doesn't support HS");
278 ret = -ENOTSUP;
279 goto add_configuration_exit;
280 }
281
282 if (desc->bmAttributes & USB_SCD_REMOTE_WAKEUP) {
283 struct udc_device_caps caps = udc_caps(uds_ctx->dev);
284
285 if (!caps.rwup) {
286 LOG_ERR("Feature not supported by controller");
287 ret = -ENOTSUP;
288 goto add_configuration_exit;
289 }
290 }
291
292 configs = usbd_configs(uds_ctx, speed);
293 switch (speed) {
294 case USBD_SPEED_HS:
295 SYS_SLIST_FOR_EACH_NODE(&uds_ctx->fs_configs, node) {
296 if (node == &cfg_nd->node) {
297 LOG_ERR("HS config already on FS list");
298 ret = -EINVAL;
299 goto add_configuration_exit;
300 }
301 }
302 break;
303 case USBD_SPEED_FS:
304 SYS_SLIST_FOR_EACH_NODE(&uds_ctx->hs_configs, node) {
305 if (node == &cfg_nd->node) {
306 LOG_ERR("FS config already on HS list");
307 ret = -EINVAL;
308 goto add_configuration_exit;
309 }
310 }
311 break;
312 default:
313 LOG_ERR("Unsupported configuration speed");
314 ret = -ENOTSUP;
315 goto add_configuration_exit;
316 }
317
318 if (sys_slist_find_and_remove(configs, &cfg_nd->node)) {
319 LOG_WRN("Configuration %u re-inserted",
320 usbd_config_get_value(cfg_nd));
321 } else {
322 uint8_t num = usbd_get_num_configs(uds_ctx, speed) + 1;
323
324 usbd_config_set_value(cfg_nd, num);
325 usbd_set_num_configs(uds_ctx, speed, num);
326 }
327
328 if (cfg_nd->str_desc_nd != NULL) {
329 ret = usbd_add_descriptor(uds_ctx, cfg_nd->str_desc_nd);
330 if (ret != 0) {
331 LOG_ERR("Failed to add configuration string descriptor");
332 goto add_configuration_exit;
333 }
334
335 desc->iConfiguration = usbd_str_desc_get_idx(cfg_nd->str_desc_nd);
336 }
337
338 sys_slist_append(configs, &cfg_nd->node);
339
340 add_configuration_exit:
341 usbd_device_unlock(uds_ctx);
342 return ret;
343 }
344