1 /*
2  * USB loopback function
3  *
4  * Copyright (c) 2018 Phytec Messtechnik GmbH
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  */
8 
9 #include <zephyr/init.h>
10 
11 #include <zephyr/sys/byteorder.h>
12 #include <zephyr/usb/usb_device.h>
13 #include <usb_descriptor.h>
14 
15 #include <zephyr/logging/log.h>
16 LOG_MODULE_REGISTER(usb_loopback, CONFIG_USB_DEVICE_LOG_LEVEL);
17 
18 #define LOOPBACK_OUT_EP_ADDR		0x01
19 #define LOOPBACK_IN_EP_ADDR		0x81
20 
21 #define LOOPBACK_OUT_EP_IDX		0
22 #define LOOPBACK_IN_EP_IDX		1
23 
24 static uint8_t loopback_buf[1024];
25 BUILD_ASSERT(sizeof(loopback_buf) == CONFIG_USB_REQUEST_BUFFER_SIZE);
26 
27 /* usb.rst config structure start */
28 struct usb_loopback_config {
29 	struct usb_if_descriptor if0;
30 	struct usb_ep_descriptor if0_out_ep;
31 	struct usb_ep_descriptor if0_in_ep;
32 } __packed;
33 
34 USBD_CLASS_DESCR_DEFINE(primary, 0) struct usb_loopback_config loopback_cfg = {
35 	/* Interface descriptor 0 */
36 	.if0 = {
37 		.bLength = sizeof(struct usb_if_descriptor),
38 		.bDescriptorType = USB_DESC_INTERFACE,
39 		.bInterfaceNumber = 0,
40 		.bAlternateSetting = 0,
41 		.bNumEndpoints = 2,
42 		.bInterfaceClass = USB_BCC_VENDOR,
43 		.bInterfaceSubClass = 0,
44 		.bInterfaceProtocol = 0,
45 		.iInterface = 0,
46 	},
47 
48 	/* Data Endpoint OUT */
49 	.if0_out_ep = {
50 		.bLength = sizeof(struct usb_ep_descriptor),
51 		.bDescriptorType = USB_DESC_ENDPOINT,
52 		.bEndpointAddress = LOOPBACK_OUT_EP_ADDR,
53 		.bmAttributes = USB_DC_EP_BULK,
54 		.wMaxPacketSize = sys_cpu_to_le16(CONFIG_LOOPBACK_BULK_EP_MPS),
55 		.bInterval = 0x00,
56 	},
57 
58 	/* Data Endpoint IN */
59 	.if0_in_ep = {
60 		.bLength = sizeof(struct usb_ep_descriptor),
61 		.bDescriptorType = USB_DESC_ENDPOINT,
62 		.bEndpointAddress = LOOPBACK_IN_EP_ADDR,
63 		.bmAttributes = USB_DC_EP_BULK,
64 		.wMaxPacketSize = sys_cpu_to_le16(CONFIG_LOOPBACK_BULK_EP_MPS),
65 		.bInterval = 0x00,
66 	},
67 };
68 /* usb.rst config structure end */
69 
loopback_out_cb(uint8_t ep,enum usb_dc_ep_cb_status_code ep_status)70 static void loopback_out_cb(uint8_t ep, enum usb_dc_ep_cb_status_code ep_status)
71 {
72 	uint32_t bytes_to_read;
73 
74 	usb_read(ep, NULL, 0, &bytes_to_read);
75 	LOG_DBG("ep 0x%x, bytes to read %d ", ep, bytes_to_read);
76 	usb_read(ep, loopback_buf, bytes_to_read, NULL);
77 }
78 
loopback_in_cb(uint8_t ep,enum usb_dc_ep_cb_status_code ep_status)79 static void loopback_in_cb(uint8_t ep, enum usb_dc_ep_cb_status_code ep_status)
80 {
81 	if (usb_write(ep, loopback_buf, CONFIG_LOOPBACK_BULK_EP_MPS, NULL)) {
82 		LOG_DBG("ep 0x%x", ep);
83 	}
84 }
85 
86 /* usb.rst endpoint configuration start */
87 static struct usb_ep_cfg_data ep_cfg[] = {
88 	{
89 		.ep_cb = loopback_out_cb,
90 		.ep_addr = LOOPBACK_OUT_EP_ADDR,
91 	},
92 	{
93 		.ep_cb = loopback_in_cb,
94 		.ep_addr = LOOPBACK_IN_EP_ADDR,
95 	},
96 };
97 /* usb.rst endpoint configuration end */
98 
loopback_status_cb(struct usb_cfg_data * cfg,enum usb_dc_status_code status,const uint8_t * param)99 static void loopback_status_cb(struct usb_cfg_data *cfg,
100 			       enum usb_dc_status_code status,
101 			       const uint8_t *param)
102 {
103 	ARG_UNUSED(cfg);
104 
105 	switch (status) {
106 	case USB_DC_INTERFACE:
107 		loopback_in_cb(ep_cfg[LOOPBACK_IN_EP_IDX].ep_addr, 0);
108 		LOG_DBG("USB interface configured");
109 		break;
110 	case USB_DC_SET_HALT:
111 		LOG_DBG("Set Feature ENDPOINT_HALT");
112 		break;
113 	case USB_DC_CLEAR_HALT:
114 		LOG_DBG("Clear Feature ENDPOINT_HALT");
115 		if (*param == ep_cfg[LOOPBACK_IN_EP_IDX].ep_addr) {
116 			loopback_in_cb(ep_cfg[LOOPBACK_IN_EP_IDX].ep_addr, 0);
117 		}
118 		break;
119 	default:
120 		break;
121 	}
122 }
123 
124 /* usb.rst vendor handler start */
loopback_vendor_handler(struct usb_setup_packet * setup,int32_t * len,uint8_t ** data)125 static int loopback_vendor_handler(struct usb_setup_packet *setup,
126 				   int32_t *len, uint8_t **data)
127 {
128 	LOG_DBG("Class request: bRequest 0x%x bmRequestType 0x%x len %d",
129 		setup->bRequest, setup->bmRequestType, *len);
130 
131 	if (setup->RequestType.recipient != USB_REQTYPE_RECIPIENT_DEVICE) {
132 		return -ENOTSUP;
133 	}
134 
135 	if (usb_reqtype_is_to_device(setup) &&
136 	    setup->bRequest == 0x5b) {
137 		LOG_DBG("Host-to-Device, data %p", *data);
138 		/*
139 		 * Copy request data in loopback_buf buffer and reuse
140 		 * it later in control device-to-host transfer.
141 		 */
142 		memcpy(loopback_buf, *data,
143 		       MIN(sizeof(loopback_buf), setup->wLength));
144 		return 0;
145 	}
146 
147 	if ((usb_reqtype_is_to_host(setup)) &&
148 	    (setup->bRequest == 0x5c)) {
149 		LOG_DBG("Device-to-Host, wLength %d, data %p",
150 			setup->wLength, *data);
151 		*data = loopback_buf;
152 		*len = MIN(sizeof(loopback_buf), setup->wLength);
153 		return 0;
154 	}
155 
156 	return -ENOTSUP;
157 }
158 /* usb.rst vendor handler end */
159 
loopback_interface_config(struct usb_desc_header * head,uint8_t bInterfaceNumber)160 static void loopback_interface_config(struct usb_desc_header *head,
161 				      uint8_t bInterfaceNumber)
162 {
163 	ARG_UNUSED(head);
164 
165 	loopback_cfg.if0.bInterfaceNumber = bInterfaceNumber;
166 }
167 
168 /* usb.rst device config data start */
169 USBD_DEFINE_CFG_DATA(loopback_config) = {
170 	.usb_device_description = NULL,
171 	.interface_config = loopback_interface_config,
172 	.interface_descriptor = &loopback_cfg.if0,
173 	.cb_usb_status = loopback_status_cb,
174 	.interface = {
175 		.class_handler = NULL,
176 		.custom_handler = NULL,
177 		.vendor_handler = loopback_vendor_handler,
178 	},
179 	.num_endpoints = ARRAY_SIZE(ep_cfg),
180 	.endpoint = ep_cfg,
181 };
182 /* usb.rst device config data end */
183