Coverage Report

Created: 2025-08-29 06:57

/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 *)&notify, (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 *)&notify, 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