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