/src/tinyusb/src/class/net/ecm_rndis_device.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * The MIT License (MIT) |
3 | | * |
4 | | * Copyright (c) 2020 Peter Lawrence |
5 | | * Copyright (c) 2019 Ha Thach (tinyusb.org) |
6 | | * |
7 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
8 | | * of this software and associated documentation files (the "Software"), to deal |
9 | | * in the Software without restriction, including without limitation the rights |
10 | | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 | | * copies of the Software, and to permit persons to whom the Software is |
12 | | * furnished to do so, subject to the following conditions: |
13 | | * |
14 | | * The above copyright notice and this permission notice shall be included in |
15 | | * all copies or substantial portions of the Software. |
16 | | * |
17 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
20 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
22 | | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
23 | | * THE SOFTWARE. |
24 | | * |
25 | | * This file is part of the TinyUSB stack. |
26 | | */ |
27 | | |
28 | | #include "tusb_option.h" |
29 | | |
30 | | #if ( CFG_TUD_ENABLED && CFG_TUD_ECM_RNDIS ) |
31 | | |
32 | | #include "device/usbd.h" |
33 | | #include "device/usbd_pvt.h" |
34 | | |
35 | | #include "net_device.h" |
36 | | #include "rndis_protocol.h" |
37 | | |
38 | | extern void rndis_class_set_handler(uint8_t *data, int size); /* found in ./misc/networking/rndis_reports.c */ |
39 | | |
40 | 0 | #define CFG_TUD_NET_PACKET_PREFIX_LEN sizeof(rndis_data_packet_t) |
41 | | #define CFG_TUD_NET_PACKET_SUFFIX_LEN 0 |
42 | | |
43 | 0 | #define NETD_PACKET_SIZE (CFG_TUD_NET_PACKET_PREFIX_LEN + CFG_TUD_NET_MTU + CFG_TUD_NET_PACKET_PREFIX_LEN) |
44 | 0 | #define NETD_CONTROL_SIZE 120 |
45 | | |
46 | | //--------------------------------------------------------------------+ |
47 | | // MACRO CONSTANT TYPEDEF |
48 | | //--------------------------------------------------------------------+ |
49 | | typedef struct { |
50 | | uint8_t itf_num; // Index number of Management Interface, +1 for Data Interface |
51 | | uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active |
52 | | |
53 | | uint8_t ep_notif; |
54 | | uint8_t ep_in; |
55 | | uint8_t ep_out; |
56 | | |
57 | | bool ecm_mode; |
58 | | |
59 | | // Endpoint descriptor use to open/close when receiving SetInterface |
60 | | // TODO since configuration descriptor may not be long-lived memory, we should |
61 | | // keep a copy of endpoint attribute instead |
62 | | uint8_t const * ecm_desc_epdata; |
63 | | } netd_interface_t; |
64 | | |
65 | | typedef struct ecm_notify_struct { |
66 | | tusb_control_request_t header; |
67 | | uint32_t downlink, uplink; |
68 | | } ecm_notify_t; |
69 | | |
70 | | typedef struct { |
71 | | TUD_EPBUF_DEF(rx, NETD_PACKET_SIZE); |
72 | | TUD_EPBUF_DEF(tx, NETD_PACKET_SIZE); |
73 | | |
74 | | TUD_EPBUF_DEF(notify, sizeof(ecm_notify_t)); |
75 | | TUD_EPBUF_DEF(ctrl, NETD_CONTROL_SIZE); |
76 | | } netd_epbuf_t; |
77 | | |
78 | | //--------------------------------------------------------------------+ |
79 | | // INTERNAL OBJECT & FUNCTION DECLARATION |
80 | | //--------------------------------------------------------------------+ |
81 | | static netd_interface_t _netd_itf; |
82 | | CFG_TUD_MEM_SECTION static netd_epbuf_t _netd_epbuf; |
83 | | static bool can_xmit; |
84 | | static bool ecm_link_is_up = true; // Store link state for ECM mode |
85 | | |
86 | 0 | void tud_network_recv_renew(void) { |
87 | 0 | usbd_edpt_xfer(0, _netd_itf.ep_out, _netd_epbuf.rx, NETD_PACKET_SIZE); |
88 | 0 | } |
89 | | |
90 | 0 | static void do_in_xfer(uint8_t *buf, uint16_t len) { |
91 | 0 | can_xmit = false; |
92 | 0 | usbd_edpt_xfer(0, _netd_itf.ep_in, buf, len); |
93 | 0 | } |
94 | | |
95 | 0 | void netd_report(uint8_t *buf, uint16_t len) { |
96 | 0 | const uint8_t rhport = 0; |
97 | 0 | len = tu_min16(len, sizeof(ecm_notify_t)); |
98 | |
|
99 | 0 | if (!usbd_edpt_claim(rhport, _netd_itf.ep_notif)) { |
100 | 0 | TU_LOG1("ECM: Failed to claim notification endpoint\n"); |
101 | 0 | return; |
102 | 0 | } |
103 | | |
104 | 0 | memcpy(_netd_epbuf.notify, buf, len); |
105 | 0 | usbd_edpt_xfer(rhport, _netd_itf.ep_notif, _netd_epbuf.notify, len); |
106 | 0 | } |
107 | | |
108 | | //--------------------------------------------------------------------+ |
109 | | // USBD Driver API |
110 | | //--------------------------------------------------------------------+ |
111 | 0 | void netd_init(void) { |
112 | 0 | tu_memclr(&_netd_itf, sizeof(_netd_itf)); |
113 | 0 | } |
114 | | |
115 | 0 | bool netd_deinit(void) { |
116 | 0 | return true; |
117 | 0 | } |
118 | | |
119 | 0 | void netd_reset(uint8_t rhport) { |
120 | 0 | (void) rhport; |
121 | 0 | netd_init(); |
122 | 0 | } |
123 | | |
124 | 0 | uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) { |
125 | 0 | bool const is_rndis = (TUD_RNDIS_ITF_CLASS == itf_desc->bInterfaceClass && |
126 | 0 | TUD_RNDIS_ITF_SUBCLASS == itf_desc->bInterfaceSubClass && |
127 | 0 | TUD_RNDIS_ITF_PROTOCOL == itf_desc->bInterfaceProtocol); |
128 | |
|
129 | 0 | bool const is_ecm = (TUSB_CLASS_CDC == itf_desc->bInterfaceClass && |
130 | 0 | CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL == itf_desc->bInterfaceSubClass && |
131 | 0 | 0x00 == itf_desc->bInterfaceProtocol); |
132 | |
|
133 | 0 | TU_VERIFY(is_rndis || is_ecm, 0); |
134 | | |
135 | | // confirm interface hasn't already been allocated |
136 | 0 | TU_ASSERT(0 == _netd_itf.ep_notif, 0); |
137 | | |
138 | | // sanity check the descriptor |
139 | 0 | _netd_itf.ecm_mode = is_ecm; |
140 | | |
141 | | //------------- Management Interface -------------// |
142 | 0 | _netd_itf.itf_num = itf_desc->bInterfaceNumber; |
143 | |
|
144 | 0 | uint16_t drv_len = sizeof(tusb_desc_interface_t); |
145 | 0 | uint8_t const * p_desc = tu_desc_next( itf_desc ); |
146 | | |
147 | | // Communication Functional Descriptors |
148 | 0 | while (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len) { |
149 | 0 | drv_len += tu_desc_len(p_desc); |
150 | 0 | p_desc = tu_desc_next(p_desc); |
151 | 0 | } |
152 | | |
153 | | // notification endpoint (if any) |
154 | 0 | if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) { |
155 | 0 | TU_ASSERT(usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0); |
156 | | |
157 | 0 | _netd_itf.ep_notif = ((tusb_desc_endpoint_t const*)p_desc)->bEndpointAddress; |
158 | |
|
159 | 0 | drv_len += tu_desc_len(p_desc); |
160 | 0 | p_desc = tu_desc_next(p_desc); |
161 | 0 | } |
162 | | |
163 | | //------------- Data Interface -------------// |
164 | | // - RNDIS Data followed immediately by a pair of endpoints |
165 | | // - CDC-ECM data interface has 2 alternate settings |
166 | | // - 0 : zero endpoints for inactive (default) |
167 | | // - 1 : IN & OUT endpoints for active networking |
168 | 0 | TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); |
169 | | |
170 | 0 | do { |
171 | 0 | tusb_desc_interface_t const * data_itf_desc = (tusb_desc_interface_t const *) p_desc; |
172 | 0 | TU_ASSERT(TUSB_CLASS_CDC_DATA == data_itf_desc->bInterfaceClass, 0); |
173 | | |
174 | 0 | drv_len += tu_desc_len(p_desc); |
175 | 0 | p_desc = tu_desc_next(p_desc); |
176 | 0 | } while (_netd_itf.ecm_mode && (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && (drv_len <= max_len)); |
177 | | |
178 | | // Pair of endpoints |
179 | 0 | TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); |
180 | | |
181 | 0 | if (_netd_itf.ecm_mode) { |
182 | | // ECM by default is in-active, save the endpoint attribute |
183 | | // to open later when received setInterface |
184 | 0 | _netd_itf.ecm_desc_epdata = p_desc; |
185 | 0 | } else { |
186 | | // Open endpoint pair for RNDIS |
187 | 0 | TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &_netd_itf.ep_out, &_netd_itf.ep_in), 0); |
188 | | |
189 | | // we are ready to transmit a packet |
190 | 0 | can_xmit = true; |
191 | | |
192 | | // prepare for incoming packets |
193 | 0 | tud_network_recv_renew(); |
194 | 0 | } |
195 | | |
196 | 0 | drv_len += 2*sizeof(tusb_desc_endpoint_t); |
197 | |
|
198 | 0 | return drv_len; |
199 | 0 | } |
200 | | |
201 | 0 | static void ecm_report(bool nc) { |
202 | 0 | ecm_notify_t ecm_notify_nc = { |
203 | 0 | .header = { |
204 | 0 | .bmRequestType = 0xA1, |
205 | 0 | .bRequest = 0, /* NETWORK_CONNECTION aka NetworkConnection */ |
206 | 0 | .wValue = ecm_link_is_up ? 1 : 0, /* Use current link state */ |
207 | 0 | .wLength = 0, |
208 | 0 | }, |
209 | 0 | }; |
210 | |
|
211 | 0 | const ecm_notify_t ecm_notify_csc = { |
212 | 0 | .header = { |
213 | 0 | .bmRequestType = 0xA1, |
214 | 0 | .bRequest = 0x2A, /* CONNECTION_SPEED_CHANGE aka ConnectionSpeedChange */ |
215 | 0 | .wLength = 8, |
216 | 0 | }, |
217 | 0 | .downlink = 9728000, |
218 | 0 | .uplink = 9728000, |
219 | 0 | }; |
220 | |
|
221 | 0 | ecm_notify_t notify = (nc) ? ecm_notify_nc : ecm_notify_csc; |
222 | 0 | notify.header.wIndex = _netd_itf.itf_num; |
223 | 0 | netd_report((uint8_t *)¬ify, (nc) ? sizeof(notify.header) : sizeof(notify)); |
224 | 0 | } |
225 | | |
226 | | // Invoked when a control transfer occurred on an interface of this class |
227 | | // Driver response accordingly to the request and the transfer stage (setup/data/ack) |
228 | | // return false to stall control endpoint (e.g unsupported request) |
229 | 0 | bool netd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { |
230 | 0 | if (stage == CONTROL_STAGE_SETUP) { |
231 | 0 | switch (request->bmRequestType_bit.type) { |
232 | 0 | case TUSB_REQ_TYPE_STANDARD: |
233 | 0 | switch (request->bRequest) { |
234 | 0 | case TUSB_REQ_GET_INTERFACE: { |
235 | 0 | uint8_t const req_itfnum = (uint8_t)request->wIndex; |
236 | 0 | TU_VERIFY(_netd_itf.itf_num+1 == req_itfnum); |
237 | | |
238 | 0 | tud_control_xfer(rhport, request, &_netd_itf.itf_data_alt, 1); |
239 | 0 | } |
240 | 0 | break; |
241 | | |
242 | 0 | case TUSB_REQ_SET_INTERFACE: { |
243 | 0 | uint8_t const req_itfnum = (uint8_t)request->wIndex; |
244 | 0 | uint8_t const req_alt = (uint8_t)request->wValue; |
245 | | |
246 | | // Only valid for Data Interface with Alternate is either 0 or 1 |
247 | 0 | TU_VERIFY(_netd_itf.itf_num+1 == req_itfnum && req_alt < 2); |
248 | | |
249 | | // ACM-ECM only: qequest to enable/disable network activities |
250 | 0 | TU_VERIFY(_netd_itf.ecm_mode); |
251 | | |
252 | 0 | _netd_itf.itf_data_alt = req_alt; |
253 | |
|
254 | 0 | if (_netd_itf.itf_data_alt) { |
255 | | // TODO since we don't actually close endpoint |
256 | | // hack here to not re-open it |
257 | 0 | if (_netd_itf.ep_in == 0 && _netd_itf.ep_out == 0) { |
258 | 0 | TU_ASSERT(_netd_itf.ecm_desc_epdata); |
259 | 0 | TU_ASSERT( |
260 | 0 | usbd_open_edpt_pair(rhport, _netd_itf.ecm_desc_epdata, 2, TUSB_XFER_BULK, &_netd_itf.ep_out, & |
261 | 0 | _netd_itf.ep_in)); |
262 | | |
263 | | // TODO should be merge with RNDIS's after endpoint opened |
264 | | // Also should have opposite callback for application to disable network !! |
265 | 0 | can_xmit = true; // we are ready to transmit a packet |
266 | 0 | tud_network_recv_renew(); // prepare for incoming packets |
267 | 0 | } |
268 | 0 | } else { |
269 | | // TODO close the endpoint pair |
270 | | // For now pretend that we did, this should have no harm since host won't try to |
271 | | // communicate with the endpoints again |
272 | | // _netd_itf.ep_in = _netd_itf.ep_out = 0 |
273 | 0 | } |
274 | | |
275 | 0 | tud_control_status(rhport, request); |
276 | 0 | } |
277 | 0 | break; |
278 | | |
279 | | // unsupported request |
280 | 0 | default: return false; |
281 | 0 | } |
282 | 0 | break; |
283 | | |
284 | 0 | case TUSB_REQ_TYPE_CLASS: |
285 | 0 | TU_VERIFY(_netd_itf.itf_num == request->wIndex); |
286 | | |
287 | 0 | if (_netd_itf.ecm_mode) { |
288 | | /* the only required CDC-ECM Management Element Request is SetEthernetPacketFilter */ |
289 | 0 | if (0x43 /* SET_ETHERNET_PACKET_FILTER */ == request->bRequest) { |
290 | 0 | tud_control_xfer(rhport, request, NULL, 0); |
291 | | // Only send connection notification if link is up |
292 | 0 | if (ecm_link_is_up) { |
293 | 0 | ecm_report(true); |
294 | 0 | } |
295 | 0 | } |
296 | 0 | } else { |
297 | 0 | if (request->bmRequestType_bit.direction == TUSB_DIR_IN) { |
298 | 0 | rndis_generic_msg_t* rndis_msg = (rndis_generic_msg_t*)((void*)_netd_epbuf.ctrl); |
299 | 0 | uint32_t msglen = tu_le32toh(rndis_msg->MessageLength); |
300 | 0 | TU_ASSERT(msglen <= NETD_CONTROL_SIZE); |
301 | 0 | tud_control_xfer(rhport, request, _netd_epbuf.ctrl, (uint16_t)msglen); |
302 | 0 | } else { |
303 | 0 | tud_control_xfer(rhport, request, _netd_epbuf.ctrl, NETD_CONTROL_SIZE); |
304 | 0 | } |
305 | 0 | } |
306 | 0 | break; |
307 | | |
308 | | // unsupported request |
309 | 0 | default: return false; |
310 | 0 | } |
311 | 0 | } else if (stage == CONTROL_STAGE_DATA) { |
312 | | // Handle RNDIS class control OUT only |
313 | 0 | if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && |
314 | 0 | request->bmRequestType_bit.direction == TUSB_DIR_OUT && |
315 | 0 | _netd_itf.itf_num == request->wIndex) { |
316 | 0 | if (!_netd_itf.ecm_mode) { |
317 | 0 | rndis_class_set_handler(_netd_epbuf.ctrl, request->wLength); |
318 | 0 | } |
319 | 0 | } |
320 | 0 | } |
321 | | |
322 | 0 | return true; |
323 | 0 | } |
324 | | |
325 | 0 | static void handle_incoming_packet(uint32_t len) { |
326 | 0 | uint8_t* pnt = _netd_epbuf.rx; |
327 | 0 | uint32_t size = 0; |
328 | |
|
329 | 0 | if (_netd_itf.ecm_mode) { |
330 | 0 | size = len; |
331 | 0 | } else { |
332 | 0 | rndis_data_packet_t* r = (rndis_data_packet_t*)((void*)pnt); |
333 | 0 | if (len >= sizeof(rndis_data_packet_t)) { |
334 | 0 | if ((r->MessageType == REMOTE_NDIS_PACKET_MSG) && (r->MessageLength <= len)) { |
335 | 0 | if ((r->DataOffset + offsetof(rndis_data_packet_t, DataOffset) + r->DataLength) <= len) { |
336 | 0 | pnt = &_netd_epbuf.rx[r->DataOffset + offsetof(rndis_data_packet_t, DataOffset)]; |
337 | 0 | size = r->DataLength; |
338 | 0 | } |
339 | 0 | } |
340 | 0 | } |
341 | 0 | } |
342 | |
|
343 | 0 | if (!tud_network_recv_cb(pnt, (uint16_t)size)) { |
344 | | /* if a buffer was never handled by user code, we must renew on the user's behalf */ |
345 | 0 | tud_network_recv_renew(); |
346 | 0 | } |
347 | 0 | } |
348 | | |
349 | 0 | bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { |
350 | 0 | (void)rhport; |
351 | 0 | (void)result; |
352 | | |
353 | | /* new packet received */ |
354 | 0 | if (ep_addr == _netd_itf.ep_out) { |
355 | 0 | handle_incoming_packet(xferred_bytes); |
356 | 0 | } |
357 | | |
358 | | /* data transmission finished */ |
359 | 0 | if (ep_addr == _netd_itf.ep_in) { |
360 | | /* TinyUSB requires the class driver to implement ZLP (since ZLP usage is class-specific) */ |
361 | |
|
362 | 0 | if (xferred_bytes && (0 == (xferred_bytes % CFG_TUD_NET_ENDPOINT_SIZE))) { |
363 | 0 | do_in_xfer(NULL, 0); /* a ZLP is needed */ |
364 | 0 | } else { |
365 | | /* we're finally finished */ |
366 | 0 | can_xmit = true; |
367 | 0 | } |
368 | 0 | } |
369 | |
|
370 | 0 | if (_netd_itf.ecm_mode && (ep_addr == _netd_itf.ep_notif)) { |
371 | | // Notification transfer complete - endpoint is now free |
372 | | // Don't automatically send speed change notification after link state changes |
373 | 0 | } |
374 | |
|
375 | 0 | return true; |
376 | 0 | } |
377 | | |
378 | 0 | bool tud_network_can_xmit(uint16_t size) { |
379 | 0 | (void)size; |
380 | 0 | return can_xmit; |
381 | 0 | } |
382 | | |
383 | 0 | void tud_network_xmit(void *ref, uint16_t arg) { |
384 | 0 | if (!can_xmit) { |
385 | 0 | return; |
386 | 0 | } |
387 | | |
388 | 0 | uint16_t len = (_netd_itf.ecm_mode) ? 0 : CFG_TUD_NET_PACKET_PREFIX_LEN; |
389 | 0 | uint8_t* data = _netd_epbuf.tx + len; |
390 | |
|
391 | 0 | len += tud_network_xmit_cb(data, ref, arg); |
392 | |
|
393 | 0 | if (!_netd_itf.ecm_mode) { |
394 | 0 | rndis_data_packet_t *hdr = (rndis_data_packet_t *) ((void*) _netd_epbuf.tx); |
395 | 0 | memset(hdr, 0, sizeof(rndis_data_packet_t)); |
396 | 0 | hdr->MessageType = REMOTE_NDIS_PACKET_MSG; |
397 | 0 | hdr->MessageLength = len; |
398 | 0 | hdr->DataOffset = sizeof(rndis_data_packet_t) - offsetof(rndis_data_packet_t, DataOffset); |
399 | 0 | hdr->DataLength = len - sizeof(rndis_data_packet_t); |
400 | 0 | } |
401 | |
|
402 | 0 | do_in_xfer(_netd_epbuf.tx, len); |
403 | 0 | } |
404 | | |
405 | | // Set the network link state (up/down) and notify the host |
406 | 0 | void tud_network_link_state(uint8_t rhport, bool is_up) { |
407 | 0 | (void)rhport; |
408 | |
|
409 | 0 | if (_netd_itf.ecm_mode) { |
410 | 0 | ecm_link_is_up = is_up; |
411 | | |
412 | | // For ECM mode, send network connection notification only |
413 | | // Don't trigger speed change notification for link state changes |
414 | 0 | ecm_notify_t notify = { |
415 | 0 | .header = { |
416 | 0 | .bmRequestType = 0xA1, |
417 | 0 | .bRequest = 0, /* NETWORK_CONNECTION */ |
418 | 0 | .wValue = is_up ? 1 : 0, /* 0 = disconnected, 1 = connected */ |
419 | 0 | .wLength = 0, |
420 | 0 | }, |
421 | 0 | }; |
422 | 0 | notify.header.wIndex = _netd_itf.itf_num; |
423 | 0 | netd_report((uint8_t *)¬ify, sizeof(notify.header)); |
424 | 0 | } else { |
425 | | // For RNDIS mode, we would need to implement RNDIS status indication |
426 | | // This is more complex and requires RNDIS_INDICATE_STATUS_MSG |
427 | | // For now, RNDIS doesn't support dynamic link state changes |
428 | 0 | (void)is_up; |
429 | 0 | } |
430 | 0 | } |
431 | | |
432 | | #endif |