1 /*
2 * Copyright (c) 2022 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/usb/usbd.h>
8 #include <zephyr/toolchain.h>
9 #include <zephyr/drivers/usb/udc.h>
10 #include <zephyr/sys/slist.h>
11 #include <zephyr/sys/iterable_sections.h>
12
13 #include "usbd_device.h"
14 #include "usbd_class_api.h"
15 #include "usbd_config.h"
16 #include "usbd_endpoint.h"
17 #include "usbd_ch9.h"
18
19 #include <zephyr/logging/log.h>
20 #if defined(CONFIG_USBD_LOG_LEVEL)
21 #define USBD_CLASS_LOG_LEVEL CONFIG_USBD_LOG_LEVEL
22 #else
23 #define USBD_CLASS_LOG_LEVEL LOG_LEVEL_NONE
24 #endif
25 LOG_MODULE_REGISTER(usbd_class, CONFIG_USBD_LOG_LEVEL);
26
usbd_class_desc_len(struct usbd_class_data * const c_data,const enum usbd_speed speed)27 size_t usbd_class_desc_len(struct usbd_class_data *const c_data,
28 const enum usbd_speed speed)
29 {
30 struct usb_desc_header **dhp;
31 size_t len = 0;
32
33 dhp = usbd_class_get_desc(c_data, speed);
34 /*
35 * If the desired descriptor is available, count to the last element,
36 * which must be a pointer to a nil descriptor.
37 */
38 while (dhp != NULL && (*dhp != NULL) && (*dhp)->bLength != 0U) {
39 len += (*dhp)->bLength;
40 dhp++;
41 }
42
43 return len;
44 }
45
46 struct usbd_class_node *
usbd_class_get_by_config(struct usbd_context * const uds_ctx,const enum usbd_speed speed,const uint8_t cnum,const uint8_t inum)47 usbd_class_get_by_config(struct usbd_context *const uds_ctx,
48 const enum usbd_speed speed,
49 const uint8_t cnum,
50 const uint8_t inum)
51 {
52 struct usbd_class_node *c_nd;
53 struct usbd_config_node *cfg_nd;
54
55 cfg_nd = usbd_config_get(uds_ctx, speed, cnum);
56 if (cfg_nd == NULL) {
57 return NULL;
58 }
59
60 SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
61 if (c_nd->iface_bm & BIT(inum)) {
62 return c_nd;
63 }
64 }
65
66 return NULL;
67 }
68
69 struct usbd_class_node *
usbd_class_get_by_iface(struct usbd_context * const uds_ctx,const uint8_t inum)70 usbd_class_get_by_iface(struct usbd_context *const uds_ctx,
71 const uint8_t inum)
72 {
73 struct usbd_class_node *c_nd;
74 struct usbd_config_node *cfg_nd;
75
76 cfg_nd = usbd_config_get_current(uds_ctx);
77 if (cfg_nd == NULL) {
78 return NULL;
79 }
80
81 SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
82 if (c_nd->iface_bm & BIT(inum)) {
83 return c_nd;
84 }
85 }
86
87 return NULL;
88 }
89
xfer_owner_exist(struct usbd_context * const uds_ctx,struct usbd_config_node * const cfg_nd,struct net_buf * const buf)90 static bool xfer_owner_exist(struct usbd_context *const uds_ctx,
91 struct usbd_config_node *const cfg_nd,
92 struct net_buf *const buf)
93 {
94 struct udc_buf_info *bi = udc_get_buf_info(buf);
95 struct usbd_class_node *c_nd;
96
97 SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
98 if (bi->owner == c_nd->c_data) {
99 uint32_t ep_active = c_nd->ep_active;
100 uint32_t ep_assigned = c_nd->ep_assigned;
101
102 if (!usbd_ep_bm_is_set(&ep_active, bi->ep)) {
103 LOG_DBG("ep 0x%02x is not active", bi->ep);
104 }
105
106 if (!usbd_ep_bm_is_set(&ep_assigned, bi->ep)) {
107 LOG_DBG("ep 0x%02x is not assigned", bi->ep);
108 }
109
110 return true;
111 }
112 }
113
114 return false;
115 }
116
usbd_class_handle_xfer(struct usbd_context * const uds_ctx,struct net_buf * const buf,const int err)117 int usbd_class_handle_xfer(struct usbd_context *const uds_ctx,
118 struct net_buf *const buf,
119 const int err)
120 {
121 struct udc_buf_info *bi = udc_get_buf_info(buf);
122
123 if (unlikely(USBD_CLASS_LOG_LEVEL == LOG_LEVEL_DBG)) {
124 struct usbd_config_node *cfg_nd;
125
126 if (usbd_state_is_configured(uds_ctx)) {
127 cfg_nd = usbd_config_get_current(uds_ctx);
128 if (!xfer_owner_exist(uds_ctx, cfg_nd, buf)) {
129 LOG_DBG("Class request without owner");
130 }
131 } else {
132 LOG_DBG("Class request on not configured device");
133 }
134 }
135
136 return usbd_class_request(bi->owner, buf, err);
137 }
138
139 struct usbd_class_node *
usbd_class_get_by_ep(struct usbd_context * const uds_ctx,const uint8_t ep)140 usbd_class_get_by_ep(struct usbd_context *const uds_ctx,
141 const uint8_t ep)
142 {
143 struct usbd_class_node *c_nd;
144 struct usbd_config_node *cfg_nd;
145 enum usbd_speed speed;
146 uint8_t ep_idx = USB_EP_GET_IDX(ep);
147 uint8_t cfg;
148 uint32_t ep_bm;
149
150 if (USB_EP_DIR_IS_IN(ep)) {
151 ep_bm = BIT(ep_idx + 16);
152 } else {
153 ep_bm = BIT(ep_idx);
154 }
155
156 if (!usbd_state_is_configured(uds_ctx)) {
157 LOG_ERR("No configuration set (Address state)");
158 return NULL;
159 }
160
161 cfg = usbd_get_config_value(uds_ctx);
162 speed = usbd_bus_speed(uds_ctx);
163 cfg_nd = usbd_config_get(uds_ctx, speed, cfg);
164 if (cfg_nd == NULL) {
165 return NULL;
166 }
167
168 SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
169 if (c_nd->ep_assigned & ep_bm) {
170 return c_nd;
171 }
172 }
173
174 return NULL;
175 }
176
177 struct usbd_class_node *
usbd_class_get_by_req(struct usbd_context * const uds_ctx,const uint8_t request)178 usbd_class_get_by_req(struct usbd_context *const uds_ctx,
179 const uint8_t request)
180 {
181 struct usbd_config_node *cfg_nd;
182 struct usbd_class_node *c_nd;
183
184 cfg_nd = usbd_config_get_current(uds_ctx);
185 if (cfg_nd == NULL) {
186 return NULL;
187 }
188
189 SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
190 if (c_nd->c_data->v_reqs == NULL) {
191 continue;
192 }
193
194 for (int i = 0; i < c_nd->c_data->v_reqs->len; i++) {
195 /*
196 * First instance always wins.
197 * There is no other way to determine the recipient.
198 */
199 if (c_nd->c_data->v_reqs->reqs[i] == request) {
200 return c_nd;
201 }
202 }
203 }
204
205 return NULL;
206 }
207
208 static struct usbd_class_node *
usbd_class_node_get(const char * name,const enum usbd_speed speed)209 usbd_class_node_get(const char *name, const enum usbd_speed speed)
210 {
211 if (speed == USBD_SPEED_FS) {
212 STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_fs,
213 usbd_class_node, c_nd) {
214 if (strcmp(name, c_nd->c_data->name) == 0) {
215 return c_nd;
216 }
217 }
218 } else if (USBD_SUPPORTS_HIGH_SPEED && speed == USBD_SPEED_HS) {
219 STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_hs,
220 usbd_class_node, c_nd) {
221 if (strcmp(name, c_nd->c_data->name) == 0) {
222 return c_nd;
223 }
224 }
225 }
226
227 LOG_ERR("USB device class %s not found", name);
228
229 return NULL;
230 }
231
usbd_class_append(struct usbd_context * const uds_ctx,struct usbd_class_node * const c_nd,const enum usbd_speed speed,const uint8_t cfg)232 static int usbd_class_append(struct usbd_context *const uds_ctx,
233 struct usbd_class_node *const c_nd,
234 const enum usbd_speed speed,
235 const uint8_t cfg)
236 {
237 struct usbd_config_node *cfg_nd;
238
239 cfg_nd = usbd_config_get(uds_ctx, speed, cfg);
240 if (cfg_nd == NULL) {
241 return -ENODATA;
242 }
243
244 sys_slist_append(&cfg_nd->class_list, &c_nd->node);
245
246 return 0;
247 }
248
usbd_class_remove(struct usbd_context * const uds_ctx,struct usbd_class_node * const c_nd,const enum usbd_speed speed,const uint8_t cfg)249 static int usbd_class_remove(struct usbd_context *const uds_ctx,
250 struct usbd_class_node *const c_nd,
251 const enum usbd_speed speed,
252 const uint8_t cfg)
253 {
254 struct usbd_config_node *cfg_nd;
255
256 cfg_nd = usbd_config_get(uds_ctx, speed, cfg);
257 if (cfg_nd == NULL) {
258 return -ENODATA;
259 }
260
261 if (!sys_slist_find_and_remove(&cfg_nd->class_list, &c_nd->node)) {
262 return -ENODATA;
263 }
264
265 return 0;
266 }
267
usbd_class_remove_all(struct usbd_context * const uds_ctx,const enum usbd_speed speed,const uint8_t cfg)268 int usbd_class_remove_all(struct usbd_context *const uds_ctx,
269 const enum usbd_speed speed,
270 const uint8_t cfg)
271 {
272 struct usbd_config_node *cfg_nd;
273 struct usbd_class_node *c_nd;
274 sys_snode_t *node;
275
276 cfg_nd = usbd_config_get(uds_ctx, speed, cfg);
277 if (cfg_nd == NULL) {
278 return -ENODATA;
279 }
280
281 while ((node = sys_slist_get(&cfg_nd->class_list))) {
282 c_nd = CONTAINER_OF(node, struct usbd_class_node, node);
283 atomic_clear_bit(&c_nd->state, USBD_CCTX_REGISTERED);
284 usbd_class_shutdown(c_nd->c_data);
285 c_nd->c_data->uds_ctx = NULL;
286 LOG_DBG("Remove class node %p from configuration %u", c_nd, cfg);
287 }
288
289 return 0;
290 }
291
292 /*
293 * All the functions below are part of public USB device support API.
294 */
295
usbd_register_class(struct usbd_context * const uds_ctx,const char * name,const enum usbd_speed speed,const uint8_t cfg)296 int usbd_register_class(struct usbd_context *const uds_ctx,
297 const char *name,
298 const enum usbd_speed speed, const uint8_t cfg)
299 {
300 struct usbd_class_node *c_nd;
301 struct usbd_class_data *c_data;
302 int ret;
303
304 c_nd = usbd_class_node_get(name, speed);
305 if (c_nd == NULL) {
306 return -ENODEV;
307 }
308
309 usbd_device_lock(uds_ctx);
310
311 if (usbd_is_initialized(uds_ctx)) {
312 LOG_ERR("USB device support is initialized");
313 ret = -EBUSY;
314 goto register_class_error;
315 }
316
317 c_data = c_nd->c_data;
318
319 /* TODO: does it still need to be atomic ? */
320 if (atomic_test_bit(&c_nd->state, USBD_CCTX_REGISTERED)) {
321 LOG_WRN("Class instance already registered");
322 ret = -EBUSY;
323 goto register_class_error;
324 }
325
326 if ((c_data->uds_ctx != NULL) && (c_data->uds_ctx != uds_ctx)) {
327 LOG_ERR("Class registered to other context at different speed");
328 ret = -EBUSY;
329 goto register_class_error;
330 }
331
332 ret = usbd_class_append(uds_ctx, c_nd, speed, cfg);
333 if (ret == 0) {
334 /* Initialize pointer back to the device struct */
335 atomic_set_bit(&c_nd->state, USBD_CCTX_REGISTERED);
336 c_data->uds_ctx = uds_ctx;
337 }
338
339 register_class_error:
340 usbd_device_unlock(uds_ctx);
341 return ret;
342 }
343
is_blocklisted(const struct usbd_class_node * const c_nd,const char * const list[])344 static bool is_blocklisted(const struct usbd_class_node *const c_nd,
345 const char *const list[])
346 {
347 for (int i = 0; list[i] != NULL; i++) {
348 if (strcmp(c_nd->c_data->name, list[i]) == 0) {
349 return true;
350 }
351 }
352
353 return false;
354 }
355
usbd_register_all_classes(struct usbd_context * const uds_ctx,const enum usbd_speed speed,const uint8_t cfg,const char * const blocklist[])356 int usbd_register_all_classes(struct usbd_context *const uds_ctx,
357 const enum usbd_speed speed, const uint8_t cfg,
358 const char *const blocklist[])
359 {
360 int ret;
361
362 if (USBD_SUPPORTS_HIGH_SPEED && speed == USBD_SPEED_HS) {
363 STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_hs, usbd_class_node, c_nd) {
364 if (blocklist != NULL && is_blocklisted(c_nd, blocklist)) {
365 continue;
366 }
367
368 ret = usbd_register_class(uds_ctx, c_nd->c_data->name,
369 speed, cfg);
370 if (ret) {
371 LOG_ERR("Failed to register %s to HS configuration %u",
372 c_nd->c_data->name, cfg);
373 return ret;
374 }
375 }
376
377 return 0;
378 }
379
380 if (speed == USBD_SPEED_FS) {
381 STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_fs, usbd_class_node, c_nd) {
382 if (blocklist != NULL && is_blocklisted(c_nd, blocklist)) {
383 continue;
384 }
385
386 ret = usbd_register_class(uds_ctx, c_nd->c_data->name,
387 speed, cfg);
388 if (ret) {
389 LOG_ERR("Failed to register %s to FS configuration %u",
390 c_nd->c_data->name, cfg);
391 return ret;
392 }
393 }
394
395 return 0;
396 }
397
398 return -ENOTSUP;
399 }
400
usbd_unregister_class(struct usbd_context * const uds_ctx,const char * name,const enum usbd_speed speed,const uint8_t cfg)401 int usbd_unregister_class(struct usbd_context *const uds_ctx,
402 const char *name,
403 const enum usbd_speed speed, const uint8_t cfg)
404 {
405 struct usbd_class_node *c_nd;
406 struct usbd_class_data *c_data;
407 bool can_release_data = true;
408 int ret;
409
410 c_nd = usbd_class_node_get(name, speed);
411 if (c_nd == NULL) {
412 return -ENODEV;
413 }
414
415 usbd_device_lock(uds_ctx);
416
417 if (usbd_is_initialized(uds_ctx)) {
418 LOG_ERR("USB device support is initialized");
419 ret = -EBUSY;
420 goto unregister_class_error;
421 }
422
423 c_data = c_nd->c_data;
424 /* TODO: does it still need to be atomic ? */
425 if (!atomic_test_bit(&c_nd->state, USBD_CCTX_REGISTERED)) {
426 LOG_WRN("Class instance not registered");
427 ret = -EBUSY;
428 goto unregister_class_error;
429 }
430
431 /* TODO: The use of atomic here does not make this code thread safe.
432 * The atomic should be changed to something else.
433 */
434 if (USBD_SUPPORTS_HIGH_SPEED && speed == USBD_SPEED_HS) {
435 STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_hs,
436 usbd_class_node, i) {
437 if ((i->c_data == c_nd->c_data) &&
438 atomic_test_bit(&i->state, USBD_CCTX_REGISTERED)) {
439 can_release_data = false;
440 break;
441 }
442 }
443 } else {
444 STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_fs,
445 usbd_class_node, i) {
446 if ((i->c_data == c_nd->c_data) &&
447 atomic_test_bit(&i->state, USBD_CCTX_REGISTERED)) {
448 can_release_data = false;
449 break;
450 }
451 }
452 }
453
454 ret = usbd_class_remove(uds_ctx, c_nd, speed, cfg);
455 if (ret == 0) {
456 atomic_clear_bit(&c_nd->state, USBD_CCTX_REGISTERED);
457 usbd_class_shutdown(c_nd->c_data);
458
459 if (can_release_data) {
460 c_data->uds_ctx = NULL;
461 }
462 }
463
464 unregister_class_error:
465 usbd_device_unlock(uds_ctx);
466 return ret;
467 }
468
usbd_unregister_all_classes(struct usbd_context * const uds_ctx,const enum usbd_speed speed,const uint8_t cfg)469 int usbd_unregister_all_classes(struct usbd_context *const uds_ctx,
470 const enum usbd_speed speed, const uint8_t cfg)
471 {
472 int ret;
473
474 if (USBD_SUPPORTS_HIGH_SPEED && speed == USBD_SPEED_HS) {
475 STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_hs, usbd_class_node, c_nd) {
476 ret = usbd_unregister_class(uds_ctx, c_nd->c_data->name,
477 speed, cfg);
478 if (ret) {
479 LOG_ERR("Failed to unregister %s to HS configuration %u",
480 c_nd->c_data->name, cfg);
481 return ret;
482 }
483 }
484
485 return 0;
486 }
487
488 if (speed == USBD_SPEED_FS) {
489 STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_fs, usbd_class_node, c_nd) {
490 ret = usbd_unregister_class(uds_ctx, c_nd->c_data->name,
491 speed, cfg);
492 if (ret) {
493 LOG_ERR("Failed to unregister %s to FS configuration %u",
494 c_nd->c_data->name, cfg);
495 return ret;
496 }
497 }
498
499 return 0;
500 }
501
502 return -ENOTSUP;
503 }
504