blob: 3e131a85c3d18a1542ca6f569add8f506c774bcf [file] [log] [blame]
Austin Schuh41baf202022-01-01 14:33:40 -08001/*
2 * The MIT License (MIT)
3 *
4 * Copyright (c) 2020 Jacob Berg Potter
5 * Copyright (c) 2020 Peter Lawrence
6 * Copyright (c) 2019 Ha Thach (tinyusb.org)
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 *
26 * This file is part of the TinyUSB stack.
27 */
28
29#include "tusb_option.h"
30
31#if ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_NCM )
32
33#include "device/usbd.h"
34#include "device/usbd_pvt.h"
35#include "net_device.h"
36
37//--------------------------------------------------------------------+
38// MACRO CONSTANT TYPEDEF
39//--------------------------------------------------------------------+
40
41#define NTH16_SIGNATURE 0x484D434E
42#define NDP16_SIGNATURE_NCM0 0x304D434E
43#define NDP16_SIGNATURE_NCM1 0x314D434E
44
45typedef struct TU_ATTR_PACKED
46{
47 uint16_t wLength;
48 uint16_t bmNtbFormatsSupported;
49 uint32_t dwNtbInMaxSize;
50 uint16_t wNdbInDivisor;
51 uint16_t wNdbInPayloadRemainder;
52 uint16_t wNdbInAlignment;
53 uint16_t wReserved;
54 uint32_t dwNtbOutMaxSize;
55 uint16_t wNdbOutDivisor;
56 uint16_t wNdbOutPayloadRemainder;
57 uint16_t wNdbOutAlignment;
58 uint16_t wNtbOutMaxDatagrams;
59} ntb_parameters_t;
60
61typedef struct TU_ATTR_PACKED
62{
63 uint32_t dwSignature;
64 uint16_t wHeaderLength;
65 uint16_t wSequence;
66 uint16_t wBlockLength;
67 uint16_t wNdpIndex;
68} nth16_t;
69
70typedef struct TU_ATTR_PACKED
71{
72 uint16_t wDatagramIndex;
73 uint16_t wDatagramLength;
74} ndp16_datagram_t;
75
76typedef struct TU_ATTR_PACKED
77{
78 uint32_t dwSignature;
79 uint16_t wLength;
80 uint16_t wNextNdpIndex;
81 ndp16_datagram_t datagram[];
82} ndp16_t;
83
84typedef union TU_ATTR_PACKED {
85 struct {
86 nth16_t nth;
87 ndp16_t ndp;
88 };
89 uint8_t data[CFG_TUD_NCM_IN_NTB_MAX_SIZE];
90} transmit_ntb_t;
91
92struct ecm_notify_struct
93{
94 tusb_control_request_t header;
95 uint32_t downlink, uplink;
96};
97
98typedef struct
99{
100 uint8_t itf_num; // Index number of Management Interface, +1 for Data Interface
101 uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active
102
103 uint8_t ep_notif;
104 uint8_t ep_in;
105 uint8_t ep_out;
106
107 const ndp16_t *ndp;
108 uint8_t num_datagrams, current_datagram_index;
109
110 enum {
111 REPORT_SPEED,
112 REPORT_CONNECTED,
113 REPORT_DONE
114 } report_state;
115 bool report_pending;
116
117 uint8_t current_ntb; // Index in transmit_ntb[] that is currently being filled with datagrams
118 uint8_t datagram_count; // Number of datagrams in transmit_ntb[current_ntb]
119 uint16_t next_datagram_offset; // Offset in transmit_ntb[current_ntb].data to place the next datagram
120 uint16_t ntb_in_size; // Maximum size of transmitted (IN to host) NTBs; initially CFG_TUD_NCM_IN_NTB_MAX_SIZE
121 uint8_t max_datagrams_per_ntb; // Maximum number of datagrams per NTB; initially CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB
122
123 uint16_t nth_sequence; // Sequence number counter for transmitted NTBs
124
125 bool transferring;
126
127} ncm_interface_t;
128
129//--------------------------------------------------------------------+
130// INTERNAL OBJECT & FUNCTION DECLARATION
131//--------------------------------------------------------------------+
132
133CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static const ntb_parameters_t ntb_parameters = {
134 .wLength = sizeof(ntb_parameters_t),
135 .bmNtbFormatsSupported = 0x01,
136 .dwNtbInMaxSize = CFG_TUD_NCM_IN_NTB_MAX_SIZE,
137 .wNdbInDivisor = 4,
138 .wNdbInPayloadRemainder = 0,
139 .wNdbInAlignment = CFG_TUD_NCM_ALIGNMENT,
140 .wReserved = 0,
141 .dwNtbOutMaxSize = CFG_TUD_NCM_OUT_NTB_MAX_SIZE,
142 .wNdbOutDivisor = 4,
143 .wNdbOutPayloadRemainder = 0,
144 .wNdbOutAlignment = CFG_TUD_NCM_ALIGNMENT,
145 .wNtbOutMaxDatagrams = 0
146};
147
148CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static transmit_ntb_t transmit_ntb[2];
149
150CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t receive_ntb[CFG_TUD_NCM_OUT_NTB_MAX_SIZE];
151
152static ncm_interface_t ncm_interface;
153
154/*
155 * Set up the NTB state in ncm_interface to be ready to add datagrams.
156 */
157static void ncm_prepare_for_tx(void) {
158 ncm_interface.datagram_count = 0;
159 // datagrams start after all the headers
160 ncm_interface.next_datagram_offset = sizeof(nth16_t) + sizeof(ndp16_t)
161 + ((CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB + 1) * sizeof(ndp16_datagram_t));
162}
163
164/*
165 * If not already transmitting, start sending the current NTB to the host and swap buffers
166 * to start filling the other one with datagrams.
167 */
168static void ncm_start_tx(void) {
169 if (ncm_interface.transferring) {
170 return;
171 }
172
173 transmit_ntb_t *ntb = &transmit_ntb[ncm_interface.current_ntb];
174 size_t ntb_length = ncm_interface.next_datagram_offset;
175
176 // Fill in NTB header
177 ntb->nth.dwSignature = NTH16_SIGNATURE;
178 ntb->nth.wHeaderLength = sizeof(nth16_t);
179 ntb->nth.wSequence = ncm_interface.nth_sequence++;
180 ntb->nth.wBlockLength = ntb_length;
181 ntb->nth.wNdpIndex = sizeof(nth16_t);
182
183 // Fill in NDP16 header and terminator
184 ntb->ndp.dwSignature = NDP16_SIGNATURE_NCM0;
185 ntb->ndp.wLength = sizeof(ndp16_t) + (ncm_interface.datagram_count + 1) * sizeof(ndp16_datagram_t);
186 ntb->ndp.wNextNdpIndex = 0;
187 ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramIndex = 0;
188 ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramLength = 0;
189
190 // Kick off an endpoint transfer
191 usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_in, ntb->data, ntb_length);
192 ncm_interface.transferring = true;
193
194 // Swap to the other NTB and clear it out
195 ncm_interface.current_ntb = 1 - ncm_interface.current_ntb;
196 ncm_prepare_for_tx();
197}
198
199static struct ecm_notify_struct ncm_notify_connected =
200{
201 .header = {
202 .bmRequestType_bit = {
203 .recipient = TUSB_REQ_RCPT_INTERFACE,
204 .type = TUSB_REQ_TYPE_CLASS,
205 .direction = TUSB_DIR_IN
206 },
207 .bRequest = CDC_NOTIF_NETWORK_CONNECTION,
208 .wValue = 1 /* Connected */,
209 .wLength = 0,
210 },
211};
212
213static struct ecm_notify_struct ncm_notify_speed_change =
214{
215 .header = {
216 .bmRequestType_bit = {
217 .recipient = TUSB_REQ_RCPT_INTERFACE,
218 .type = TUSB_REQ_TYPE_CLASS,
219 .direction = TUSB_DIR_IN
220 },
221 .bRequest = CDC_NOTIF_CONNECTION_SPEED_CHANGE,
222 .wLength = 8,
223 },
224 .downlink = 10000000,
225 .uplink = 10000000,
226};
227
228void tud_network_recv_renew(void)
229{
230 if (!ncm_interface.num_datagrams)
231 {
232 usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_out, receive_ntb, sizeof(receive_ntb));
233 return;
234 }
235
236 const ndp16_t *ndp = ncm_interface.ndp;
237 const int i = ncm_interface.current_datagram_index;
238 ncm_interface.current_datagram_index++;
239 ncm_interface.num_datagrams--;
240
241 tud_network_recv_cb(receive_ntb + ndp->datagram[i].wDatagramIndex, ndp->datagram[i].wDatagramLength);
242}
243
244//--------------------------------------------------------------------+
245// USBD Driver API
246//--------------------------------------------------------------------+
247
248void netd_init(void)
249{
250 tu_memclr(&ncm_interface, sizeof(ncm_interface));
251 ncm_interface.ntb_in_size = CFG_TUD_NCM_IN_NTB_MAX_SIZE;
252 ncm_interface.max_datagrams_per_ntb = CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB;
253 ncm_prepare_for_tx();
254}
255
256void netd_reset(uint8_t rhport)
257{
258 (void) rhport;
259
260 netd_init();
261}
262
263uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len)
264{
265 // confirm interface hasn't already been allocated
266 TU_ASSERT(0 == ncm_interface.ep_notif, 0);
267
268 //------------- Management Interface -------------//
269 ncm_interface.itf_num = itf_desc->bInterfaceNumber;
270
271 uint16_t drv_len = sizeof(tusb_desc_interface_t);
272 uint8_t const * p_desc = tu_desc_next( itf_desc );
273
274 // Communication Functional Descriptors
275 while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len )
276 {
277 drv_len += tu_desc_len(p_desc);
278 p_desc = tu_desc_next(p_desc);
279 }
280
281 // notification endpoint (if any)
282 if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) )
283 {
284 TU_ASSERT( usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0 );
285
286 ncm_interface.ep_notif = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress;
287
288 drv_len += tu_desc_len(p_desc);
289 p_desc = tu_desc_next(p_desc);
290 }
291
292 //------------- Data Interface -------------//
293 // - CDC-NCM data interface has 2 alternate settings
294 // - 0 : zero endpoints for inactive (default)
295 // - 1 : IN & OUT endpoints for transfer of NTBs
296 TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0);
297
298 do
299 {
300 tusb_desc_interface_t const * data_itf_desc = (tusb_desc_interface_t const *) p_desc;
301 TU_ASSERT(TUSB_CLASS_CDC_DATA == data_itf_desc->bInterfaceClass, 0);
302
303 drv_len += tu_desc_len(p_desc);
304 p_desc = tu_desc_next(p_desc);
305 } while((TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && (drv_len <= max_len));
306
307 // Pair of endpoints
308 TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0);
309
310 TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in) );
311
312 drv_len += 2*sizeof(tusb_desc_endpoint_t);
313
314 return drv_len;
315}
316
317static void ncm_report(void)
318{
319 if (ncm_interface.report_state == REPORT_SPEED) {
320 ncm_notify_speed_change.header.wIndex = ncm_interface.itf_num;
321 usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_notif, (uint8_t *) &ncm_notify_speed_change, sizeof(ncm_notify_speed_change));
322 ncm_interface.report_state = REPORT_CONNECTED;
323 ncm_interface.report_pending = true;
324 } else if (ncm_interface.report_state == REPORT_CONNECTED) {
325 ncm_notify_connected.header.wIndex = ncm_interface.itf_num;
326 usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_notif, (uint8_t *) &ncm_notify_connected, sizeof(ncm_notify_connected));
327 ncm_interface.report_state = REPORT_DONE;
328 ncm_interface.report_pending = true;
329 }
330}
331
332TU_ATTR_WEAK void tud_network_link_state_cb(bool state)
333{
334 (void)state;
335}
336
337// Handle class control request
338// return false to stall control endpoint (e.g unsupported request)
339bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
340{
341 if ( stage != CONTROL_STAGE_SETUP ) return true;
342
343 switch ( request->bmRequestType_bit.type )
344 {
345 case TUSB_REQ_TYPE_STANDARD:
346 switch ( request->bRequest )
347 {
348 case TUSB_REQ_GET_INTERFACE:
349 {
350 uint8_t const req_itfnum = (uint8_t) request->wIndex;
351 TU_VERIFY(ncm_interface.itf_num + 1 == req_itfnum);
352
353 tud_control_xfer(rhport, request, &ncm_interface.itf_data_alt, 1);
354 }
355 break;
356
357 case TUSB_REQ_SET_INTERFACE:
358 {
359 uint8_t const req_itfnum = (uint8_t) request->wIndex;
360 uint8_t const req_alt = (uint8_t) request->wValue;
361
362 // Only valid for Data Interface with Alternate is either 0 or 1
363 TU_VERIFY(ncm_interface.itf_num + 1 == req_itfnum && req_alt < 2);
364
365 if (req_alt != ncm_interface.itf_data_alt) {
366 ncm_interface.itf_data_alt = req_alt;
367
368 if (ncm_interface.itf_data_alt) {
369 if (!usbd_edpt_busy(rhport, ncm_interface.ep_out)) {
370 tud_network_recv_renew(); // prepare for incoming datagrams
371 }
372 if (!ncm_interface.report_pending) {
373 ncm_report();
374 }
375 }
376
377 tud_network_link_state_cb(ncm_interface.itf_data_alt);
378 }
379
380 tud_control_status(rhport, request);
381 }
382 break;
383
384 // unsupported request
385 default: return false;
386 }
387 break;
388
389 case TUSB_REQ_TYPE_CLASS:
390 TU_VERIFY (ncm_interface.itf_num == request->wIndex);
391
392 if (NCM_GET_NTB_PARAMETERS == request->bRequest)
393 {
394 tud_control_xfer(rhport, request, (void*)&ntb_parameters, sizeof(ntb_parameters));
395 }
396
397 break;
398
399 // unsupported request
400 default: return false;
401 }
402
403 return true;
404}
405
406static void handle_incoming_datagram(uint32_t len)
407{
408 uint32_t size = len;
409
410 if (len == 0) {
411 return;
412 }
413
414 TU_ASSERT(size >= sizeof(nth16_t), );
415
416 const nth16_t *hdr = (const nth16_t *)receive_ntb;
417 TU_ASSERT(hdr->dwSignature == NTH16_SIGNATURE, );
418 TU_ASSERT(hdr->wNdpIndex >= sizeof(nth16_t) && (hdr->wNdpIndex + sizeof(ndp16_t)) <= len, );
419
420 const ndp16_t *ndp = (const ndp16_t *)(receive_ntb + hdr->wNdpIndex);
421 TU_ASSERT(ndp->dwSignature == NDP16_SIGNATURE_NCM0 || ndp->dwSignature == NDP16_SIGNATURE_NCM1, );
422 TU_ASSERT(hdr->wNdpIndex + ndp->wLength <= len, );
423
424 int num_datagrams = (ndp->wLength - 12) / 4;
425 ncm_interface.current_datagram_index = 0;
426 ncm_interface.num_datagrams = 0;
427 ncm_interface.ndp = ndp;
428 for (int i = 0; i < num_datagrams && ndp->datagram[i].wDatagramIndex && ndp->datagram[i].wDatagramLength; i++)
429 {
430 ncm_interface.num_datagrams++;
431 }
432
433 tud_network_recv_renew();
434}
435
436bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
437{
438 (void) rhport;
439 (void) result;
440
441 /* new datagram receive_ntb */
442 if (ep_addr == ncm_interface.ep_out )
443 {
444 handle_incoming_datagram(xferred_bytes);
445 }
446
447 /* data transmission finished */
448 if (ep_addr == ncm_interface.ep_in )
449 {
450 if (ncm_interface.transferring) {
451 ncm_interface.transferring = false;
452 }
453
454 // If there are datagrams queued up that we tried to send while this NTB was being emitted, send them now
455 if (ncm_interface.datagram_count && ncm_interface.itf_data_alt == 1) {
456 ncm_start_tx();
457 }
458 }
459
460 if (ep_addr == ncm_interface.ep_notif )
461 {
462 ncm_interface.report_pending = false;
463 ncm_report();
464 }
465
466 return true;
467}
468
469// poll network driver for its ability to accept another packet to transmit
470bool tud_network_can_xmit(uint16_t size)
471{
472 TU_VERIFY(ncm_interface.itf_data_alt == 1);
473
474 if (ncm_interface.datagram_count >= ncm_interface.max_datagrams_per_ntb) {
475 TU_LOG2("NTB full [by count]\r\n");
476 return false;
477 }
478
479 size_t next_datagram_offset = ncm_interface.next_datagram_offset;
480 if (next_datagram_offset + size > ncm_interface.ntb_in_size) {
481 TU_LOG2("ntb full [by size]\r\n");
482 return false;
483 }
484
485 return true;
486}
487
488void tud_network_xmit(void *ref, uint16_t arg)
489{
490 transmit_ntb_t *ntb = &transmit_ntb[ncm_interface.current_ntb];
491 size_t next_datagram_offset = ncm_interface.next_datagram_offset;
492
493 uint16_t size = tud_network_xmit_cb(ntb->data + next_datagram_offset, ref, arg);
494
495 ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramIndex = ncm_interface.next_datagram_offset;
496 ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramLength = size;
497
498 ncm_interface.datagram_count++;
499 next_datagram_offset += size;
500
501 // round up so the next datagram is aligned correctly
502 next_datagram_offset += (CFG_TUD_NCM_ALIGNMENT - 1);
503 next_datagram_offset -= (next_datagram_offset % CFG_TUD_NCM_ALIGNMENT);
504
505 ncm_interface.next_datagram_offset = next_datagram_offset;
506
507 ncm_start_tx();
508}
509
510#endif