1 /*
2  * Copyright (c) 2017 Linaro Ltd
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/logging/log.h>
8 LOG_MODULE_REGISTER(usb_eem, CONFIG_USB_DEVICE_NETWORK_LOG_LEVEL);
9 
10 #include <zephyr/net/net_pkt.h>
11 #include <zephyr/net/ethernet.h>
12 #include <net_private.h>
13 
14 #include <zephyr/usb/usb_device.h>
15 #include <zephyr/usb/class/usb_cdc.h>
16 
17 #include "netusb.h"
18 
19 static uint8_t sentinel[] = { 0xde, 0xad, 0xbe, 0xef };
20 
21 #define EEM_FRAME_SIZE (NET_ETH_MAX_FRAME_SIZE + sizeof(sentinel) + \
22 			sizeof(uint16_t)) /* EEM header */
23 
24 static uint8_t tx_buf[EEM_FRAME_SIZE], rx_buf[EEM_FRAME_SIZE];
25 
26 struct usb_cdc_eem_config {
27 	struct usb_if_descriptor if0;
28 	struct usb_ep_descriptor if0_in_ep;
29 	struct usb_ep_descriptor if0_out_ep;
30 } __packed;
31 
32 USBD_CLASS_DESCR_DEFINE(primary, 0) struct usb_cdc_eem_config cdc_eem_cfg = {
33 	/* Interface descriptor 0 */
34 	/* CDC Communication interface */
35 	.if0 = {
36 		.bLength = sizeof(struct usb_if_descriptor),
37 		.bDescriptorType = USB_DESC_INTERFACE,
38 		.bInterfaceNumber = 0,
39 		.bAlternateSetting = 0,
40 		.bNumEndpoints = 2,
41 		.bInterfaceClass = USB_BCC_CDC_CONTROL,
42 		.bInterfaceSubClass = EEM_SUBCLASS,
43 		.bInterfaceProtocol = EEM_PROTOCOL,
44 		.iInterface = 0,
45 	},
46 
47 	/* Data Endpoint IN */
48 	.if0_in_ep = {
49 		.bLength = sizeof(struct usb_ep_descriptor),
50 		.bDescriptorType = USB_DESC_ENDPOINT,
51 		.bEndpointAddress = CDC_EEM_IN_EP_ADDR,
52 		.bmAttributes = USB_DC_EP_BULK,
53 		.wMaxPacketSize =
54 			sys_cpu_to_le16(CONFIG_CDC_EEM_BULK_EP_MPS),
55 		.bInterval = 0x00,
56 	},
57 
58 	/* Data Endpoint OUT */
59 	.if0_out_ep = {
60 		.bLength = sizeof(struct usb_ep_descriptor),
61 		.bDescriptorType = USB_DESC_ENDPOINT,
62 		.bEndpointAddress = CDC_EEM_OUT_EP_ADDR,
63 		.bmAttributes = USB_DC_EP_BULK,
64 		.wMaxPacketSize =
65 			sys_cpu_to_le16(CONFIG_CDC_EEM_BULK_EP_MPS),
66 		.bInterval = 0x00,
67 	},
68 };
69 
eem_get_first_iface_number(void)70 static uint8_t eem_get_first_iface_number(void)
71 {
72 	return cdc_eem_cfg.if0.bInterfaceNumber;
73 }
74 
75 #define EEM_OUT_EP_IDX		0
76 #define EEM_IN_EP_IDX		1
77 
78 static struct usb_ep_cfg_data eem_ep_data[] = {
79 	{
80 		/* Use transfer API */
81 		.ep_cb = usb_transfer_ep_callback,
82 		.ep_addr = CDC_EEM_OUT_EP_ADDR
83 	},
84 	{
85 		/* Use transfer API */
86 		.ep_cb = usb_transfer_ep_callback,
87 		.ep_addr = CDC_EEM_IN_EP_ADDR
88 	},
89 };
90 
eem_pkt_size(uint16_t hdr)91 static inline uint16_t eem_pkt_size(uint16_t hdr)
92 {
93 	if (hdr & BIT(15)) {
94 		return hdr & 0x07ff;
95 	} else {
96 		return hdr & 0x3fff;
97 	}
98 }
99 
eem_send(struct net_pkt * pkt)100 static int eem_send(struct net_pkt *pkt)
101 {
102 	uint16_t *hdr = (uint16_t *)&tx_buf[0];
103 	int ret, len, b_idx = 0;
104 
105 	/* With EEM, it's possible to send multiple ethernet packets in one
106 	 * transfer, we don't do that for now.
107 	 */
108 	len = net_pkt_get_len(pkt) + sizeof(sentinel);
109 
110 	if (len + sizeof(uint16_t) > sizeof(tx_buf)) {
111 		LOG_WRN("Trying to send too large packet, drop");
112 		return -ENOMEM;
113 	}
114 
115 	/* Add EEM header */
116 	*hdr = sys_cpu_to_le16(0x3FFF & len);
117 	b_idx += sizeof(uint16_t);
118 
119 	if (net_pkt_read(pkt, &tx_buf[b_idx], net_pkt_get_len(pkt))) {
120 		return -ENOBUFS;
121 	}
122 
123 	b_idx += len - sizeof(sentinel);
124 
125 	/* Add crc-sentinel */
126 	memcpy(&tx_buf[b_idx], sentinel, sizeof(sentinel));
127 	b_idx += sizeof(sentinel);
128 
129 	/* transfer data to host */
130 	ret = usb_transfer_sync(eem_ep_data[EEM_IN_EP_IDX].ep_addr,
131 				tx_buf, b_idx,
132 				USB_TRANS_WRITE);
133 	if (ret != b_idx) {
134 		LOG_ERR("Transfer failure");
135 		return -EIO;
136 	}
137 
138 	return 0;
139 }
140 
eem_read_cb(uint8_t ep,int size,void * priv)141 static void eem_read_cb(uint8_t ep, int size, void *priv)
142 {
143 	uint8_t *ptr = rx_buf;
144 
145 	do {
146 		uint16_t eem_hdr, eem_size;
147 		struct net_pkt *pkt;
148 
149 		if (size < sizeof(uint16_t)) {
150 			break;
151 		}
152 
153 		eem_hdr = sys_get_le16(ptr);
154 		eem_size = eem_pkt_size(eem_hdr);
155 
156 		if (eem_size + sizeof(uint16_t) > size) {
157 			/* eem pkt greater than transferred data */
158 			LOG_ERR("pkt size error");
159 			break;
160 		}
161 
162 		size -= sizeof(uint16_t);
163 		ptr += sizeof(uint16_t);
164 
165 		if (eem_hdr & BIT(15)) {
166 			/* EEM Command, do nothing for now */
167 			goto done;
168 		}
169 
170 		LOG_DBG("hdr 0x%x, eem_size %d, size %d",
171 			eem_hdr, eem_size, size);
172 
173 		if (!size || !eem_size) {
174 			LOG_DBG("no payload");
175 			break;
176 		}
177 
178 		pkt = net_pkt_rx_alloc_with_buffer(netusb_net_iface(),
179 						   eem_size - sizeof(sentinel),
180 						   AF_UNSPEC, 0, K_FOREVER);
181 		if (!pkt) {
182 			LOG_ERR("Unable to alloc pkt");
183 			break;
184 		}
185 
186 		/* copy payload and discard 32-bit sentinel */
187 		if (net_pkt_write(pkt, ptr, eem_size - sizeof(sentinel))) {
188 			LOG_ERR("Unable to write into pkt");
189 			net_pkt_unref(pkt);
190 			break;
191 		}
192 
193 		netusb_recv(pkt);
194 
195 done:
196 		size -= eem_size;
197 		ptr += eem_size;
198 	} while (size);
199 
200 	usb_transfer(eem_ep_data[EEM_OUT_EP_IDX].ep_addr, rx_buf,
201 		     sizeof(rx_buf), USB_TRANS_READ, eem_read_cb, NULL);
202 }
203 
eem_connect(bool connected)204 static int eem_connect(bool connected)
205 {
206 	if (connected) {
207 		eem_read_cb(eem_ep_data[EEM_OUT_EP_IDX].ep_addr, 0, NULL);
208 	} else {
209 		/* Cancel any transfer */
210 		usb_cancel_transfer(eem_ep_data[EEM_OUT_EP_IDX].ep_addr);
211 		usb_cancel_transfer(eem_ep_data[EEM_IN_EP_IDX].ep_addr);
212 	}
213 
214 	return 0;
215 }
216 
217 static struct netusb_function eem_function = {
218 	.connect_media = eem_connect,
219 	.send_pkt = eem_send,
220 };
221 
eem_status_interface(const uint8_t * desc)222 static inline void eem_status_interface(const uint8_t *desc)
223 {
224 	const struct usb_if_descriptor *if_desc = (void *)desc;
225 	uint8_t iface_num = if_desc->bInterfaceNumber;
226 
227 	LOG_DBG("");
228 
229 	if (iface_num != eem_get_first_iface_number()) {
230 		return;
231 	}
232 
233 	netusb_enable(&eem_function);
234 }
235 
eem_status_cb(struct usb_cfg_data * cfg,enum usb_dc_status_code status,const uint8_t * param)236 static void eem_status_cb(struct usb_cfg_data *cfg,
237 			  enum usb_dc_status_code status,
238 			  const uint8_t *param)
239 {
240 	ARG_UNUSED(cfg);
241 
242 	/* Check the USB status and do needed action if required */
243 	switch (status) {
244 	case USB_DC_DISCONNECTED:
245 		LOG_DBG("USB device disconnected");
246 		netusb_disable();
247 		break;
248 
249 	case USB_DC_INTERFACE:
250 		LOG_DBG("USB interface selected");
251 		eem_status_interface(param);
252 		break;
253 
254 	case USB_DC_ERROR:
255 	case USB_DC_RESET:
256 	case USB_DC_CONNECTED:
257 	case USB_DC_CONFIGURED:
258 	case USB_DC_SUSPEND:
259 	case USB_DC_RESUME:
260 		LOG_DBG("USB unhandled state: %d", status);
261 		break;
262 
263 	case USB_DC_SOF:
264 		break;
265 
266 	case USB_DC_UNKNOWN:
267 	default:
268 		LOG_DBG("USB unknown state: %d", status);
269 		break;
270 	}
271 }
272 
eem_interface_config(struct usb_desc_header * head,uint8_t bInterfaceNumber)273 static void eem_interface_config(struct usb_desc_header *head,
274 				 uint8_t bInterfaceNumber)
275 {
276 	ARG_UNUSED(head);
277 
278 	cdc_eem_cfg.if0.bInterfaceNumber = bInterfaceNumber;
279 }
280 
281 USBD_DEFINE_CFG_DATA(cdc_eem_config) = {
282 	.usb_device_description = NULL,
283 	.interface_config = eem_interface_config,
284 	.interface_descriptor = &cdc_eem_cfg.if0,
285 	.cb_usb_status = eem_status_cb,
286 	.interface = {
287 		.class_handler = NULL,
288 		.custom_handler = NULL,
289 		.vendor_handler = NULL,
290 	},
291 	.num_endpoints = ARRAY_SIZE(eem_ep_data),
292 	.endpoint = eem_ep_data,
293 };
294