1 /*
2  * Copyright (c) 2022, 2025 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/usb/usbd.h>
8 #include <zephyr/shell/shell.h>
9 #include <zephyr/drivers/usb/udc.h>
10 
11 #include <zephyr/logging/log.h>
12 LOG_MODULE_REGISTER(usb_loopback, CONFIG_USBD_LOOPBACK_LOG_LEVEL);
13 
14 /*
15  * This function does not define its own pool and requires a large enough UDC
16  * pool. To use it with the Linux kernel tool testusb, we need about 4096 bytes
17  * in the current configuration.
18  */
19 
20 /*
21  * NOTE: this class is experimental and is in development.
22  * Primary purpose currently is testing of the class initialization and
23  * interface and endpoint configuration.
24  */
25 
26 /* Internal buffer for intermediate test data */
27 static uint8_t lb_buf[1024];
28 
29 #define LB_VENDOR_REQ_OUT		0x5b
30 #define LB_VENDOR_REQ_IN		0x5c
31 
32 #define LB_ISO_EP_MPS			256
33 #define LB_ISO_EP_INTERVAL		1
34 
35 #define LB_FUNCTION_ENABLED		0
36 #define LB_FUNCTION_BULK_MANUAL		1
37 #define LB_FUNCTION_IN_ENGAGED		2
38 #define LB_FUNCTION_OUT_ENGAGED		3
39 
40 /* Make supported vendor request visible for the device stack */
41 static const struct usbd_cctx_vendor_req lb_vregs =
42 	USBD_VENDOR_REQ(LB_VENDOR_REQ_OUT, LB_VENDOR_REQ_IN);
43 
44 struct loopback_desc {
45 	struct usb_association_descriptor iad;
46 	struct usb_if_descriptor if0;
47 	struct usb_ep_descriptor if0_out_ep;
48 	struct usb_ep_descriptor if0_in_ep;
49 	struct usb_ep_descriptor if0_hs_out_ep;
50 	struct usb_ep_descriptor if0_hs_in_ep;
51 	struct usb_if_descriptor if1;
52 	struct usb_ep_descriptor if1_int_out_ep;
53 	struct usb_ep_descriptor if1_int_in_ep;
54 	struct usb_if_descriptor if2_0;
55 	struct usb_ep_descriptor if2_0_iso_in_ep;
56 	struct usb_ep_descriptor if2_0_iso_out_ep;
57 	struct usb_if_descriptor if2_1;
58 	struct usb_ep_descriptor if2_1_iso_in_ep;
59 	struct usb_ep_descriptor if2_1_iso_out_ep;
60 	struct usb_desc_header nil_desc;
61 };
62 
63 struct lb_data {
64 	struct loopback_desc *const desc;
65 	const struct usb_desc_header **const fs_desc;
66 	const struct usb_desc_header **const hs_desc;
67 	atomic_t state;
68 };
69 
lb_get_bulk_out(struct usbd_class_data * const c_data)70 static uint8_t lb_get_bulk_out(struct usbd_class_data *const c_data)
71 {
72 	struct lb_data *data = usbd_class_get_private(c_data);
73 	struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data);
74 	struct loopback_desc *desc = data->desc;
75 
76 	if (usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) {
77 		return desc->if0_hs_out_ep.bEndpointAddress;
78 	}
79 
80 	return desc->if0_out_ep.bEndpointAddress;
81 }
82 
lb_get_bulk_in(struct usbd_class_data * const c_data)83 static uint8_t lb_get_bulk_in(struct usbd_class_data *const c_data)
84 {
85 	struct lb_data *data = usbd_class_get_private(c_data);
86 	struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data);
87 	struct loopback_desc *desc = data->desc;
88 
89 	if (usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) {
90 		return desc->if0_hs_in_ep.bEndpointAddress;
91 	}
92 
93 	return desc->if0_in_ep.bEndpointAddress;
94 }
95 
lb_submit_bulk_out(struct usbd_class_data * const c_data)96 static int lb_submit_bulk_out(struct usbd_class_data *const c_data)
97 {
98 	struct lb_data *data = usbd_class_get_private(c_data);
99 	struct net_buf *buf;
100 	int err;
101 
102 	if (!atomic_test_bit(&data->state, LB_FUNCTION_ENABLED)) {
103 		return -EPERM;
104 	}
105 
106 	if (atomic_test_and_set_bit(&data->state, LB_FUNCTION_OUT_ENGAGED)) {
107 		return -EBUSY;
108 	}
109 
110 	buf = usbd_ep_buf_alloc(c_data, lb_get_bulk_out(c_data), sizeof(lb_buf));
111 	if (buf == NULL) {
112 		LOG_ERR("Failed to allocate buffer");
113 		return -ENOMEM;
114 	}
115 
116 	err = usbd_ep_enqueue(c_data, buf);
117 	if (err) {
118 		LOG_ERR("Failed to enqueue buffer");
119 		net_buf_unref(buf);
120 	}
121 
122 	return err;
123 }
124 
lb_submit_bulk_in(struct usbd_class_data * const c_data)125 static int lb_submit_bulk_in(struct usbd_class_data *const c_data)
126 {
127 	struct lb_data *data = usbd_class_get_private(c_data);
128 	struct net_buf *buf;
129 	int err;
130 
131 	if (!atomic_test_bit(&data->state, LB_FUNCTION_ENABLED)) {
132 		return -EPERM;
133 	}
134 
135 	if (atomic_test_and_set_bit(&data->state, LB_FUNCTION_IN_ENGAGED)) {
136 		return -EBUSY;
137 	}
138 
139 	buf = usbd_ep_buf_alloc(c_data, lb_get_bulk_in(c_data), sizeof(lb_buf));
140 	if (buf == NULL) {
141 		LOG_ERR("Failed to allocate buffer");
142 		return -ENOMEM;
143 	}
144 
145 	net_buf_add_mem(buf, lb_buf, MIN(sizeof(lb_buf), net_buf_tailroom(buf)));
146 	err = usbd_ep_enqueue(c_data, buf);
147 	if (err) {
148 		LOG_ERR("Failed to enqueue buffer");
149 		net_buf_unref(buf);
150 	}
151 
152 	return err;
153 }
154 
lb_request_handler(struct usbd_class_data * const c_data,struct net_buf * const buf,const int err)155 static int lb_request_handler(struct usbd_class_data *const c_data,
156 			      struct net_buf *const buf, const int err)
157 {
158 	struct udc_buf_info *bi = (struct udc_buf_info *)net_buf_user_data(buf);
159 	struct lb_data *data = usbd_class_get_private(c_data);
160 
161 	LOG_DBG("Transfer finished %s -> ep 0x%02x, len %u, err %d",
162 		c_data->name, bi->ep, buf->len, err);
163 
164 	if (bi->ep == lb_get_bulk_out(c_data)) {
165 		atomic_clear_bit(&data->state, LB_FUNCTION_OUT_ENGAGED);
166 	}
167 
168 	if (bi->ep == lb_get_bulk_in(c_data)) {
169 		atomic_clear_bit(&data->state, LB_FUNCTION_IN_ENGAGED);
170 	}
171 
172 	if (err) {
173 		if (err == -ECONNABORTED) {
174 			LOG_INF("request ep 0x%02x, len %u cancelled",
175 				bi->ep, buf->len);
176 		} else {
177 			LOG_ERR("request ep 0x%02x, len %u failed",
178 				bi->ep, buf->len);
179 		}
180 
181 		net_buf_unref(buf);
182 
183 		return err;
184 	}
185 
186 	if (bi->ep == lb_get_bulk_out(c_data)) {
187 		memcpy(lb_buf, buf->data, MIN(sizeof(lb_buf), buf->len));
188 		net_buf_unref(buf);
189 		if (!atomic_test_bit(&data->state, LB_FUNCTION_BULK_MANUAL)) {
190 			lb_submit_bulk_out(c_data);
191 		}
192 	}
193 
194 	if (bi->ep == lb_get_bulk_in(c_data)) {
195 		bi->ep = lb_get_bulk_out(c_data);
196 		net_buf_unref(buf);
197 		if (!atomic_test_bit(&data->state, LB_FUNCTION_BULK_MANUAL)) {
198 			lb_submit_bulk_in(c_data);
199 		}
200 	}
201 
202 	return 0;
203 }
204 
lb_update(struct usbd_class_data * c_data,uint8_t iface,uint8_t alternate)205 static void lb_update(struct usbd_class_data *c_data,
206 		      uint8_t iface, uint8_t alternate)
207 {
208 	LOG_DBG("Instance %p, interface %u alternate %u changed",
209 		c_data, iface, alternate);
210 }
211 
lb_control_to_host(struct usbd_class_data * c_data,const struct usb_setup_packet * const setup,struct net_buf * const buf)212 static int lb_control_to_host(struct usbd_class_data *c_data,
213 			      const struct usb_setup_packet *const setup,
214 			      struct net_buf *const buf)
215 {
216 	if (setup->RequestType.recipient != USB_REQTYPE_RECIPIENT_DEVICE) {
217 		errno = -ENOTSUP;
218 		return 0;
219 	}
220 
221 	if (setup->bRequest == LB_VENDOR_REQ_IN) {
222 		net_buf_add_mem(buf, lb_buf,
223 				MIN(sizeof(lb_buf), setup->wLength));
224 
225 		LOG_WRN("Device-to-Host, wLength %u | %zu", setup->wLength,
226 			MIN(sizeof(lb_buf), setup->wLength));
227 
228 		return 0;
229 	}
230 
231 	LOG_ERR("Class request 0x%x not supported", setup->bRequest);
232 	errno = -ENOTSUP;
233 
234 	return 0;
235 }
236 
lb_control_to_dev(struct usbd_class_data * c_data,const struct usb_setup_packet * const setup,const struct net_buf * const buf)237 static int lb_control_to_dev(struct usbd_class_data *c_data,
238 			     const struct usb_setup_packet *const setup,
239 			     const struct net_buf *const buf)
240 {
241 	if (setup->RequestType.recipient != USB_REQTYPE_RECIPIENT_DEVICE) {
242 		errno = -ENOTSUP;
243 		return 0;
244 	}
245 
246 	if (setup->bRequest == LB_VENDOR_REQ_OUT) {
247 		LOG_WRN("Host-to-Device, wLength %u | %zu", setup->wLength,
248 			MIN(sizeof(lb_buf), buf->len));
249 		memcpy(lb_buf, buf->data, MIN(sizeof(lb_buf), buf->len));
250 		return 0;
251 	}
252 
253 	LOG_ERR("Class request 0x%x not supported", setup->bRequest);
254 	errno = -ENOTSUP;
255 
256 	return 0;
257 }
258 
lb_get_desc(struct usbd_class_data * const c_data,const enum usbd_speed speed)259 static void *lb_get_desc(struct usbd_class_data *const c_data,
260 			 const enum usbd_speed speed)
261 {
262 	struct lb_data *data = usbd_class_get_private(c_data);
263 
264 	if (speed == USBD_SPEED_HS) {
265 		return data->hs_desc;
266 	}
267 
268 	return data->fs_desc;
269 }
270 
lb_enable(struct usbd_class_data * const c_data)271 static void lb_enable(struct usbd_class_data *const c_data)
272 {
273 	struct lb_data *data = usbd_class_get_private(c_data);
274 
275 	LOG_INF("Enable %s", c_data->name);
276 	if (!atomic_test_and_set_bit(&data->state, LB_FUNCTION_ENABLED) &&
277 	    !atomic_test_bit(&data->state, LB_FUNCTION_BULK_MANUAL)) {
278 		lb_submit_bulk_out(c_data);
279 		lb_submit_bulk_in(c_data);
280 	}
281 }
282 
lb_disable(struct usbd_class_data * const c_data)283 static void lb_disable(struct usbd_class_data *const c_data)
284 {
285 	struct lb_data *data = usbd_class_get_private(c_data);
286 
287 	atomic_clear_bit(&data->state, LB_FUNCTION_ENABLED);
288 	LOG_INF("Disable %s", c_data->name);
289 }
290 
lb_init(struct usbd_class_data * c_data)291 static int lb_init(struct usbd_class_data *c_data)
292 {
293 	LOG_DBG("Init class instance %p", c_data);
294 
295 	return 0;
296 }
297 
298 struct usbd_class_api lb_api = {
299 	.update = lb_update,
300 	.control_to_host = lb_control_to_host,
301 	.control_to_dev = lb_control_to_dev,
302 	.request = lb_request_handler,
303 	.get_desc = lb_get_desc,
304 	.enable = lb_enable,
305 	.disable = lb_disable,
306 	.init = lb_init,
307 };
308 
309 #define DEFINE_LOOPBACK_DESCRIPTOR(x, _)					\
310 static struct loopback_desc lb_desc_##x = {					\
311 	.iad = {								\
312 		.bLength = sizeof(struct usb_association_descriptor),		\
313 		.bDescriptorType = USB_DESC_INTERFACE_ASSOC,			\
314 		.bFirstInterface = 0,						\
315 		.bInterfaceCount = 3,						\
316 		.bFunctionClass = USB_BCC_VENDOR,				\
317 		.bFunctionSubClass = 0,						\
318 		.bFunctionProtocol = 0,						\
319 		.iFunction = 0,							\
320 	},									\
321 										\
322 	/* Interface descriptor 0 */						\
323 	.if0 = {								\
324 		.bLength = sizeof(struct usb_if_descriptor),			\
325 		.bDescriptorType = USB_DESC_INTERFACE,				\
326 		.bInterfaceNumber = 0,						\
327 		.bAlternateSetting = 0,						\
328 		.bNumEndpoints = 2,						\
329 		.bInterfaceClass = USB_BCC_VENDOR,				\
330 		.bInterfaceSubClass = 0,					\
331 		.bInterfaceProtocol = 0,					\
332 		.iInterface = 0,						\
333 	},									\
334 										\
335 	/* Data Endpoint OUT */							\
336 	.if0_out_ep = {								\
337 		.bLength = sizeof(struct usb_ep_descriptor),			\
338 		.bDescriptorType = USB_DESC_ENDPOINT,				\
339 		.bEndpointAddress = 0x01,					\
340 		.bmAttributes = USB_EP_TYPE_BULK,				\
341 		.wMaxPacketSize = sys_cpu_to_le16(64U),				\
342 		.bInterval = 0x00,						\
343 	},									\
344 										\
345 	/* Data Endpoint IN */							\
346 	.if0_in_ep = {								\
347 		.bLength = sizeof(struct usb_ep_descriptor),			\
348 		.bDescriptorType = USB_DESC_ENDPOINT,				\
349 		.bEndpointAddress = 0x81,					\
350 		.bmAttributes = USB_EP_TYPE_BULK,				\
351 		.wMaxPacketSize = sys_cpu_to_le16(64U),				\
352 		.bInterval = 0x00,						\
353 	},									\
354 										\
355 	/* Data Endpoint OUT */							\
356 	.if0_hs_out_ep = {							\
357 		.bLength = sizeof(struct usb_ep_descriptor),			\
358 		.bDescriptorType = USB_DESC_ENDPOINT,				\
359 		.bEndpointAddress = 0x01,					\
360 		.bmAttributes = USB_EP_TYPE_BULK,				\
361 		.wMaxPacketSize = sys_cpu_to_le16(512),				\
362 		.bInterval = 0x00,						\
363 	},									\
364 										\
365 	/* Data Endpoint IN */							\
366 	.if0_hs_in_ep = {							\
367 		.bLength = sizeof(struct usb_ep_descriptor),			\
368 		.bDescriptorType = USB_DESC_ENDPOINT,				\
369 		.bEndpointAddress = 0x81,					\
370 		.bmAttributes = USB_EP_TYPE_BULK,				\
371 		.wMaxPacketSize = sys_cpu_to_le16(512),				\
372 		.bInterval = 0x00,						\
373 	},									\
374 										\
375 	/* Interface descriptor 1 */						\
376 	.if1 = {								\
377 		.bLength = sizeof(struct usb_if_descriptor),			\
378 		.bDescriptorType = USB_DESC_INTERFACE,				\
379 		.bInterfaceNumber = 1,						\
380 		.bAlternateSetting = 0,						\
381 		.bNumEndpoints = 2,						\
382 		.bInterfaceClass = USB_BCC_VENDOR,				\
383 		.bInterfaceSubClass = 0,					\
384 		.bInterfaceProtocol = 0,					\
385 		.iInterface = 0,						\
386 	},									\
387 										\
388 	/* Interface Interrupt Endpoint OUT */					\
389 	.if1_int_out_ep = {							\
390 		.bLength = sizeof(struct usb_ep_descriptor),			\
391 		.bDescriptorType = USB_DESC_ENDPOINT,				\
392 		.bEndpointAddress = 0x02,					\
393 		.bmAttributes = USB_EP_TYPE_INTERRUPT,				\
394 		.wMaxPacketSize = sys_cpu_to_le16(64),				\
395 		.bInterval = 0x01,						\
396 	},									\
397 										\
398 	/* Interrupt Interrupt Endpoint IN */					\
399 	.if1_int_in_ep = {							\
400 		.bLength = sizeof(struct usb_ep_descriptor),			\
401 		.bDescriptorType = USB_DESC_ENDPOINT,				\
402 		.bEndpointAddress = 0x82,					\
403 		.bmAttributes = USB_EP_TYPE_INTERRUPT,				\
404 		.wMaxPacketSize = sys_cpu_to_le16(64),				\
405 		.bInterval = 0x01,						\
406 	},									\
407 										\
408 	.if2_0 = {								\
409 		.bLength = sizeof(struct usb_if_descriptor),			\
410 		.bDescriptorType = USB_DESC_INTERFACE,				\
411 		.bInterfaceNumber = 2,						\
412 		.bAlternateSetting = 0,						\
413 		.bNumEndpoints = 2,						\
414 		.bInterfaceClass = USB_BCC_VENDOR,				\
415 		.bInterfaceSubClass = 0,					\
416 		.bInterfaceProtocol = 0,					\
417 		.iInterface = 0,						\
418 	},									\
419 										\
420 	.if2_0_iso_in_ep = {							\
421 		.bLength = sizeof(struct usb_ep_descriptor),			\
422 		.bDescriptorType = USB_DESC_ENDPOINT,				\
423 		.bEndpointAddress = 0x83,					\
424 		.bmAttributes = USB_EP_TYPE_ISO,				\
425 		.wMaxPacketSize = sys_cpu_to_le16(0),				\
426 		.bInterval = LB_ISO_EP_INTERVAL,				\
427 	},									\
428 										\
429 	.if2_0_iso_out_ep = {							\
430 		.bLength = sizeof(struct usb_ep_descriptor),			\
431 		.bDescriptorType = USB_DESC_ENDPOINT,				\
432 		.bEndpointAddress = 0x03,					\
433 		.bmAttributes = USB_EP_TYPE_ISO,				\
434 		.wMaxPacketSize = sys_cpu_to_le16(0),				\
435 		.bInterval = LB_ISO_EP_INTERVAL,				\
436 	},									\
437 										\
438 	.if2_1 = {								\
439 		.bLength = sizeof(struct usb_if_descriptor),			\
440 		.bDescriptorType = USB_DESC_INTERFACE,				\
441 		.bInterfaceNumber = 2,						\
442 		.bAlternateSetting = 1,						\
443 		.bNumEndpoints = 2,						\
444 		.bInterfaceClass = USB_BCC_VENDOR,				\
445 		.bInterfaceSubClass = 0,					\
446 		.bInterfaceProtocol = 0,					\
447 		.iInterface = 0,						\
448 	},									\
449 										\
450 	.if2_1_iso_in_ep = {							\
451 		.bLength = sizeof(struct usb_ep_descriptor),			\
452 		.bDescriptorType = USB_DESC_ENDPOINT,				\
453 		.bEndpointAddress = 0x83,					\
454 		.bmAttributes = USB_EP_TYPE_ISO,				\
455 		.wMaxPacketSize = sys_cpu_to_le16(LB_ISO_EP_MPS),		\
456 		.bInterval = LB_ISO_EP_INTERVAL,				\
457 	},									\
458 										\
459 	.if2_1_iso_out_ep = {							\
460 		.bLength = sizeof(struct usb_ep_descriptor),			\
461 		.bDescriptorType = USB_DESC_ENDPOINT,				\
462 		.bEndpointAddress = 0x03,					\
463 		.bmAttributes = USB_EP_TYPE_ISO,				\
464 		.wMaxPacketSize = sys_cpu_to_le16(LB_ISO_EP_MPS),		\
465 		.bInterval = LB_ISO_EP_INTERVAL,				\
466 	},									\
467 										\
468 	/* Termination descriptor */						\
469 	.nil_desc = {								\
470 		.bLength = 0,							\
471 		.bDescriptorType = 0,						\
472 	},									\
473 };										\
474 										\
475 const static struct usb_desc_header *lb_fs_desc_##x[] = {			\
476 	(struct usb_desc_header *) &lb_desc_##x.iad,				\
477 	(struct usb_desc_header *) &lb_desc_##x.if0,				\
478 	(struct usb_desc_header *) &lb_desc_##x.if0_in_ep,			\
479 	(struct usb_desc_header *) &lb_desc_##x.if0_out_ep,			\
480 	(struct usb_desc_header *) &lb_desc_##x.if1,				\
481 	(struct usb_desc_header *) &lb_desc_##x.if1_int_in_ep,			\
482 	(struct usb_desc_header *) &lb_desc_##x.if1_int_out_ep,			\
483 	(struct usb_desc_header *) &lb_desc_##x.if2_0,				\
484 	(struct usb_desc_header *) &lb_desc_##x.if2_0_iso_in_ep,		\
485 	(struct usb_desc_header *) &lb_desc_##x.if2_0_iso_out_ep,		\
486 	(struct usb_desc_header *) &lb_desc_##x.if2_1,				\
487 	(struct usb_desc_header *) &lb_desc_##x.if2_1_iso_in_ep,		\
488 	(struct usb_desc_header *) &lb_desc_##x.if2_1_iso_out_ep,		\
489 	(struct usb_desc_header *) &lb_desc_##x.nil_desc,			\
490 };										\
491 										\
492 const static struct usb_desc_header *lb_hs_desc_##x[] = {			\
493 	(struct usb_desc_header *) &lb_desc_##x.iad,				\
494 	(struct usb_desc_header *) &lb_desc_##x.if0,				\
495 	(struct usb_desc_header *) &lb_desc_##x.if0_hs_in_ep,			\
496 	(struct usb_desc_header *) &lb_desc_##x.if0_hs_out_ep,			\
497 	(struct usb_desc_header *) &lb_desc_##x.if1,				\
498 	(struct usb_desc_header *) &lb_desc_##x.if1_int_in_ep,			\
499 	(struct usb_desc_header *) &lb_desc_##x.if1_int_out_ep,			\
500 	(struct usb_desc_header *) &lb_desc_##x.if2_0,				\
501 	(struct usb_desc_header *) &lb_desc_##x.if2_0_iso_in_ep,		\
502 	(struct usb_desc_header *) &lb_desc_##x.if2_0_iso_out_ep,		\
503 	(struct usb_desc_header *) &lb_desc_##x.if2_1,				\
504 	(struct usb_desc_header *) &lb_desc_##x.if2_1_iso_in_ep,		\
505 	(struct usb_desc_header *) &lb_desc_##x.if2_1_iso_out_ep,		\
506 	(struct usb_desc_header *) &lb_desc_##x.nil_desc,			\
507 };
508 
509 
510 #define DEFINE_LOOPBACK_CLASS_DATA(x, _)					\
511 	static struct lb_data lb_data_##x = {					\
512 		.desc = &lb_desc_##x,						\
513 		.fs_desc = lb_fs_desc_##x,					\
514 		.hs_desc = lb_hs_desc_##x,					\
515 	};									\
516 										\
517 	USBD_DEFINE_CLASS(loopback_##x, &lb_api, &lb_data_##x, &lb_vregs);
518 
519 LISTIFY(CONFIG_USBD_LOOPBACK_INSTANCES_COUNT, DEFINE_LOOPBACK_DESCRIPTOR, ())
520 LISTIFY(CONFIG_USBD_LOOPBACK_INSTANCES_COUNT, DEFINE_LOOPBACK_CLASS_DATA, ())
521 
522 #if CONFIG_USBD_SHELL
523 
524 /*
525  * Device and Host Troubleshooting Shell Commands
526  *
527  * When set to manual mode, the function does not automatically submit new
528  * transfers. The user can manually enqueue or not enqueue new transfers, so
529  * the NAK behavior can also be tested.
530  *
531  * Only bulk endpoints are supported at this time.
532  */
533 
set_manual(struct usbd_class_data * const c_data,const bool on)534 static void set_manual(struct usbd_class_data *const c_data, const bool on)
535 {
536 	struct lb_data *data = usbd_class_get_private(c_data);
537 
538 	if (on) {
539 		atomic_set_bit(&data->state, LB_FUNCTION_BULK_MANUAL);
540 	} else {
541 		atomic_clear_bit(&data->state, LB_FUNCTION_BULK_MANUAL);
542 	}
543 }
544 
lb_get_node(const struct shell * const sh,const char * const name)545 static struct usbd_class_node *lb_get_node(const struct shell *const sh,
546 					   const char *const name)
547 {
548 	STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_fs, usbd_class_node, c_nd) {
549 		if (strcmp(c_nd->c_data->name, name) == 0) {
550 			return c_nd;
551 		}
552 	}
553 
554 	shell_error(sh, "Function %s could not be found", name);
555 
556 	return NULL;
557 }
558 
cmd_manual_on(const struct shell * sh,size_t argc,char ** argv)559 static int cmd_manual_on(const struct shell *sh, size_t argc, char **argv)
560 {
561 	struct usbd_class_node *c_nd;
562 
563 	c_nd = lb_get_node(sh, argv[1]);
564 	if (c_nd == NULL) {
565 		return ENODEV;
566 	}
567 
568 	shell_print(sh, "%s bulk transfers can be submitted from the shell", argv[1]);
569 	set_manual(c_nd->c_data, true);
570 
571 	return 0;
572 }
573 
cmd_manual_off(const struct shell * sh,size_t argc,char ** argv)574 static int cmd_manual_off(const struct shell *sh, size_t argc, char **argv)
575 {
576 	struct usbd_class_node *c_nd;
577 
578 	c_nd = lb_get_node(sh, argv[1]);
579 	if (c_nd == NULL) {
580 		return ENODEV;
581 	}
582 
583 	shell_print(sh, "%s bulk endpoints are automatically engaged", argv[1]);
584 	set_manual(c_nd->c_data, false);
585 
586 	return 0;
587 }
588 
cmd_enqueue_out(const struct shell * sh,size_t argc,char ** argv)589 static int cmd_enqueue_out(const struct shell *sh, size_t argc, char **argv)
590 {
591 	struct usbd_class_node *c_nd;
592 	int err;
593 
594 	c_nd = lb_get_node(sh, argv[1]);
595 	if (c_nd == NULL) {
596 		return ENODEV;
597 	}
598 
599 	err = lb_submit_bulk_out(c_nd->c_data);
600 	if (err == -EPERM) {
601 		shell_error(sh, "%s is not enabled", argv[1]);
602 	} else if (err == -EBUSY) {
603 		shell_error(sh, "%s bulk OUT endpoint is busy", argv[1]);
604 	} else if (err == -ENOMEM) {
605 		shell_error(sh, "%s failed to allocate transfer", argv[1]);
606 	} else if (err) {
607 		shell_error(sh, "%s failed to enqueue transfer", argv[1]);
608 	} else {
609 		shell_print(sh, "%s, new transfer enqueued", argv[1]);
610 	}
611 
612 	return err;
613 }
614 
cmd_enqueue_in(const struct shell * sh,size_t argc,char ** argv)615 static int cmd_enqueue_in(const struct shell *sh, size_t argc, char **argv)
616 {
617 	struct usbd_class_node *c_nd;
618 	int err;
619 
620 	c_nd = lb_get_node(sh, argv[1]);
621 	if (c_nd == NULL) {
622 		return ENODEV;
623 	}
624 
625 	err = lb_submit_bulk_in(c_nd->c_data);
626 	if (err == -EPERM) {
627 		shell_error(sh, "%s is not enabled", argv[1]);
628 	} else if (err == -EBUSY) {
629 		shell_error(sh, "%s bulk IN endpoint is busy", argv[1]);
630 	} else if (err == -ENOMEM) {
631 		shell_error(sh, "%s failed to allocate transfer", argv[1]);
632 	} else if (err) {
633 		shell_error(sh, "%s failed to enqueue transfer", argv[1]);
634 	} else {
635 		shell_print(sh, "%s, new transfer enqueued", argv[1]);
636 	}
637 
638 	return err;
639 }
640 
lb_node_name_lookup(size_t idx,struct shell_static_entry * entry)641 static void lb_node_name_lookup(size_t idx, struct shell_static_entry *entry)
642 {
643 	size_t match_idx = 0;
644 
645 	entry->syntax = NULL;
646 	entry->handler = NULL;
647 	entry->help = NULL;
648 	entry->subcmd = NULL;
649 
650 	STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_fs, usbd_class_node, c_nd) {
651 		if (c_nd->c_data->name != NULL && strlen(c_nd->c_data->name) != 0) {
652 			if (match_idx == idx) {
653 				entry->syntax = c_nd->c_data->name;
654 				break;
655 			}
656 
657 			++match_idx;
658 		}
659 	}
660 }
661 
662 SHELL_DYNAMIC_CMD_CREATE(dsub_node_name, lb_node_name_lookup);
663 
664 SHELL_STATIC_SUBCMD_SET_CREATE(sub_cmd_manual,
665 	SHELL_CMD_ARG(off, &dsub_node_name, "<function name>", cmd_manual_off, 2, 0),
666 	SHELL_CMD_ARG(on, &dsub_node_name, "<function name>", cmd_manual_on, 2, 0),
667 	SHELL_SUBCMD_SET_END
668 );
669 
670 SHELL_STATIC_SUBCMD_SET_CREATE(sub_cmd_enqueue,
671 	SHELL_CMD_ARG(out, &dsub_node_name, "<function name>", cmd_enqueue_out, 2, 0),
672 	SHELL_CMD_ARG(in, &dsub_node_name, "<function name>", cmd_enqueue_in, 2, 0),
673 	SHELL_SUBCMD_SET_END
674 );
675 
676 SHELL_STATIC_SUBCMD_SET_CREATE(lb_bulk_cmds,
677 	SHELL_CMD_ARG(manual, &sub_cmd_manual, "off  on", NULL, 2, 0),
678 	SHELL_CMD_ARG(enqueue, &sub_cmd_enqueue, "out  in", NULL, 2, 0),
679 	SHELL_SUBCMD_SET_END
680 );
681 
682 SHELL_STATIC_SUBCMD_SET_CREATE(sub_lb_cmds,
683 	SHELL_CMD_ARG(bulk, &lb_bulk_cmds, "bulk endpoint commands", NULL, 2, 0),
684 	SHELL_SUBCMD_SET_END
685 );
686 
687 SHELL_CMD_REGISTER(lb, &sub_lb_cmds, "USB device loopback function commands", NULL);
688 
689 #endif
690