From bb0df2740e251ce1046afcfdb2cad2657c881d0c Mon Sep 17 00:00:00 2001
From: Jacob Potter <jacob@j4cbo.com>
Date: Sat, 31 Oct 2020 11:03:55 -0600
Subject: [PATCH 01/11] Add CDC NCM driver

---
 src/class/cdc/cdc.h            |   6 +-
 src/class/cdc/cdc_ncm_device.c | 493 +++++++++++++++++++++++++++++++++
 src/class/cdc/cdc_ncm_device.h |  88 ++++++
 src/device/usbd.c              |  13 +
 src/device/usbd.h              |  33 +++
 src/tusb.h                     |   4 +
 6 files changed, 635 insertions(+), 2 deletions(-)
 create mode 100644 src/class/cdc/cdc_ncm_device.c
 create mode 100644 src/class/cdc/cdc_ncm_device.h

diff --git a/src/class/cdc/cdc.h b/src/class/cdc/cdc.h
index 2c044ac7f..2a44f0fc8 100644
--- a/src/class/cdc/cdc.h
+++ b/src/class/cdc/cdc.h
@@ -69,7 +69,8 @@ typedef enum
   CDC_COMM_SUBCLASS_DEVICE_MANAGEMENT                 , ///< Device Management  [USBWMC1.1]
   CDC_COMM_SUBCLASS_MOBILE_DIRECT_LINE_MODEL          , ///< Mobile Direct Line Model  [USBWMC1.1]
   CDC_COMM_SUBCLASS_OBEX                              , ///< OBEX  [USBWMC1.1]
-  CDC_COMM_SUBCLASS_ETHERNET_EMULATION_MODEL            ///< Ethernet Emulation Model  [USBEEM1.0]
+  CDC_COMM_SUBCLASS_ETHERNET_EMULATION_MODEL          , ///< Ethernet Emulation Model  [USBEEM1.0]
+  CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL               ///< Network Control Model  [USBNCM1.0]
 } cdc_comm_sublcass_type_t;
 
 /// Communication Interface Protocol Codes
@@ -114,7 +115,8 @@ typedef enum
   CDC_FUNC_DESC_COMMAND_SET                                      = 0x16 , ///< Command Set Functional Descriptor
   CDC_FUNC_DESC_COMMAND_SET_DETAIL                               = 0x17 , ///< Command Set Detail Functional Descriptor
   CDC_FUNC_DESC_TELEPHONE_CONTROL_MODEL                          = 0x18 , ///< Telephone Control Model Functional Descriptor
-  CDC_FUNC_DESC_OBEX_SERVICE_IDENTIFIER                          = 0x19   ///< OBEX Service Identifier Functional Descriptor
+  CDC_FUNC_DESC_OBEX_SERVICE_IDENTIFIER                          = 0x19 , ///< OBEX Service Identifier Functional Descriptor
+  CDC_FUNC_DESC_NCM                                              = 0x1A , ///< NCM Functional Descriptor
 }cdc_func_desc_type_t;
 
 //--------------------------------------------------------------------+
diff --git a/src/class/cdc/cdc_ncm_device.c b/src/class/cdc/cdc_ncm_device.c
new file mode 100644
index 000000000..e6d65171d
--- /dev/null
+++ b/src/class/cdc/cdc_ncm_device.c
@@ -0,0 +1,493 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2020 Jacob Berg Potter
+ * Copyright (c) 2020 Peter Lawrence
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_NCM )
+
+#include "device/usbd_pvt.h"
+#include "cdc_device.h"
+#include "cdc_ncm_device.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+#define NTH16_SIGNATURE 0x484D434E
+#define NDP16_SIGNATURE_NCM0 0x304D434E
+#define NDP16_SIGNATURE_NCM1 0x314D434E
+
+typedef struct
+{
+  uint8_t itf_num;      // Index number of Management Interface, +1 for Data Interface
+  uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active
+
+  uint8_t ep_notif;
+  uint8_t ep_in;
+  uint8_t ep_out;
+
+  // Endpoint descriptor use to open/close when receving SetInterface
+  // TODO since configuration descriptor may not be long-lived memory, we should
+  // keep a copy of endpoint attribute instead
+  uint8_t const * ecm_desc_epdata;
+
+  enum {
+    REPORT_SPEED,
+    REPORT_CONNECTED,
+    REPORT_DONE
+  } report_state;
+  bool report_pending;
+
+  uint8_t current_ntb; // Index in transmit_ntb[] that is currently being filled with datagrams
+  uint8_t datagram_count; // Number of datagrams in transmit_ntb[current_ntb]
+  uint16_t next_datagram_offset; // Offset in transmit_ntb[current_ntb].data to place the next datagram
+  uint16_t ntb_in_size; // Maximum size of transmitted (IN to host) NTBs; initially CFG_TUD_NCM_IN_NTB_MAX_SIZE
+  uint8_t max_datagrams_per_ntb; // Maximum number of datagrams per NTB; initially CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB
+
+  uint16_t nth_sequence; // Sequence number counter for transmitted NTBs
+
+  bool transferring;
+
+} ncm_interface_t;
+
+typedef struct TU_ATTR_PACKED
+{
+  uint16_t wLength;
+  uint16_t bmNtbFormatsSupported;
+  uint32_t dwNtbInMaxSize;
+  uint16_t wNdbInDivisor;
+  uint16_t wNdbInPayloadRemainder;
+  uint16_t wNdbInAlignment;
+  uint16_t wReserved;
+  uint32_t dwNtbOutMaxSize;
+  uint16_t wNdbOutDivisor;
+  uint16_t wNdbOutPayloadRemainder;
+  uint16_t wNdbOutAlignment;
+  uint16_t wNtbOutMaxDatagrams;
+} ntb_parameters_t;
+
+typedef struct TU_ATTR_PACKED
+{
+  uint32_t dwSignature;
+  uint16_t wHeaderLength;
+  uint16_t wSequence;
+  uint16_t wBlockLength;
+  uint16_t wNdpIndex;
+} nth16_t;
+
+typedef struct TU_ATTR_PACKED
+{
+  uint16_t wDatagramIndex;
+  uint16_t wDatagramLength;
+} ndp16_datagram_t;
+
+typedef struct TU_ATTR_PACKED
+{
+  uint32_t dwSignature;
+  uint16_t wLength;
+  uint16_t wNextNdpIndex;
+  ndp16_datagram_t datagram[];
+} ndp16_t;
+
+typedef union TU_ATTR_PACKED {
+  struct {
+    nth16_t nth;
+    ndp16_t ndp;
+  };
+  uint8_t data[CFG_TUD_NCM_IN_NTB_MAX_SIZE];
+} transmit_ntb_t;
+
+struct ecm_notify_struct
+{
+  tusb_control_request_t header;
+  uint32_t downlink, uplink;
+};
+
+//--------------------------------------------------------------------+
+// INTERNAL OBJECT & FUNCTION DECLARATION
+//--------------------------------------------------------------------+
+
+CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static const ntb_parameters_t ntb_parameters = {
+    .wLength = sizeof(ntb_parameters_t),
+    .bmNtbFormatsSupported = 0x01,
+    .dwNtbInMaxSize = CFG_TUD_NCM_IN_NTB_MAX_SIZE,
+    .wNdbInDivisor = 4,
+    .wNdbInPayloadRemainder = 0,
+    .wNdbInAlignment = CFG_TUD_NCM_ALIGNMENT,
+    .wReserved = 0,
+    .dwNtbOutMaxSize = CFG_TUD_NCM_OUT_NTB_MAX_SIZE,
+    .wNdbOutDivisor = 4,
+    .wNdbOutPayloadRemainder = 0,
+    .wNdbOutAlignment = CFG_TUD_NCM_ALIGNMENT,
+    .wNtbOutMaxDatagrams = 0
+};
+
+CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static transmit_ntb_t transmit_ntb[2];
+
+CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t receive_ntb[CFG_TUD_NCM_OUT_NTB_MAX_SIZE];
+
+static ncm_interface_t ncm_interface;
+
+/*
+ * Set up the NTB state in ncm_interface to be ready to add datagrams.
+ */
+static void ncm_prepare_for_tx() {
+  ncm_interface.datagram_count = 0;
+  // datagrams start after all the headers
+  ncm_interface.next_datagram_offset = sizeof(nth16_t) + sizeof(ndp16_t)
+      + ((CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB + 1) * sizeof(ndp16_datagram_t));
+}
+
+/*
+ * If not already transmitting, start sending the current NTB to the host and swap buffers
+ * to start filling the other one with datagrams.
+ */
+static void ncm_start_tx() {
+  if (ncm_interface.transferring) {
+    return;
+  }
+
+  transmit_ntb_t *ntb = &transmit_ntb[ncm_interface.current_ntb];
+  size_t ntb_length = ncm_interface.next_datagram_offset;
+
+  // Fill in NTB header
+  ntb->nth.dwSignature = NTH16_SIGNATURE;
+  ntb->nth.wHeaderLength = sizeof(nth16_t);
+  ntb->nth.wSequence = ncm_interface.nth_sequence++;
+  ntb->nth.wBlockLength = ntb_length;
+  ntb->nth.wNdpIndex = sizeof(nth16_t);
+
+  // Fill in NDP16 header and terminator
+  ntb->ndp.dwSignature = NDP16_SIGNATURE_NCM0;
+  ntb->ndp.wLength = sizeof(ndp16_t) + (ncm_interface.datagram_count + 1) * sizeof(ndp16_datagram_t);
+  ntb->ndp.wNextNdpIndex = 0;
+  ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramIndex = 0;
+  ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramLength = 0;
+
+  // Kick off an endpoint transfer
+  usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_in, ntb->data, ntb_length);
+  ncm_interface.transferring = true;
+
+  // Swap to the other NTB and clear it out
+  ncm_interface.current_ntb = 1 - ncm_interface.current_ntb;
+  ncm_prepare_for_tx();
+}
+
+static struct ecm_notify_struct ncm_notify_connected =
+    {
+        .header = {
+            .bmRequestType = 0xA1,
+            .bRequest = 0 /* NETWORK_CONNECTION aka NetworkConnection */,
+            .wValue = 1 /* Connected */,
+            .wLength = 0,
+        },
+    };
+
+static struct ecm_notify_struct ncm_notify_speed_change =
+    {
+        .header = {
+            .bmRequestType = 0xA1,
+            .bRequest = 0x2A /* CONNECTION_SPEED_CHANGE aka ConnectionSpeedChange */,
+            .wLength = 8,
+        },
+        .downlink = 10000000,
+        .uplink = 10000000,
+    };
+
+void ncm_receive_renew(void)
+{
+  usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_out, receive_ntb, sizeof(receive_ntb));
+}
+
+//--------------------------------------------------------------------+
+// USBD Driver API
+//--------------------------------------------------------------------+
+
+void ncmd_init(void)
+{
+  tu_memclr(&ncm_interface, sizeof(ncm_interface));
+  ncm_interface.ntb_in_size = CFG_TUD_NCM_IN_NTB_MAX_SIZE;
+  ncm_interface.max_datagrams_per_ntb = CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB;
+  ncm_prepare_for_tx();
+}
+
+void ncmd_reset(uint8_t rhport)
+{
+  (void) rhport;
+
+  ncmd_init();
+}
+
+uint16_t ncmd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len)
+{
+  // confirm interface hasn't already been allocated
+  TU_ASSERT(0 == ncm_interface.ep_notif, 0);
+
+  //------------- Management Interface -------------//
+  ncm_interface.itf_num = itf_desc->bInterfaceNumber;
+
+  uint16_t drv_len = sizeof(tusb_desc_interface_t);
+  uint8_t const * p_desc = tu_desc_next( itf_desc );
+
+  // Communication Functional Descriptors
+  while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len )
+  {
+    drv_len += tu_desc_len(p_desc);
+    p_desc   = tu_desc_next(p_desc);
+  }
+
+  // notification endpoint (if any)
+  if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) )
+  {
+    TU_ASSERT( usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0 );
+
+    ncm_interface.ep_notif = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress;
+
+    drv_len += tu_desc_len(p_desc);
+    p_desc   = tu_desc_next(p_desc);
+  }
+
+  //------------- Data Interface -------------//
+  // - CDC-NCM data interface has 2 alternate settings
+  //   - 0 : zero endpoints for inactive (default)
+  //   - 1 : IN & OUT endpoints for transfer of NTBs
+  TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0);
+
+  do
+  {
+    tusb_desc_interface_t const * data_itf_desc = (tusb_desc_interface_t const *) p_desc;
+    TU_ASSERT(TUSB_CLASS_CDC_DATA == data_itf_desc->bInterfaceClass, 0);
+
+    drv_len += tu_desc_len(p_desc);
+    p_desc   = tu_desc_next(p_desc);
+  } while((TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && (drv_len <= max_len));
+
+  // Pair of endpoints
+  TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0);
+
+  // ECM by default is in-active, save the endpoint attribute
+  // to open later when receive_ntb setInterface
+  ncm_interface.ecm_desc_epdata = p_desc;
+
+  drv_len += 2*sizeof(tusb_desc_endpoint_t);
+
+  return drv_len;
+}
+
+// Invoked when class request DATA stage is finished.
+bool ncmd_control_complete(uint8_t rhport, tusb_control_request_t const * request)
+{
+  (void) rhport;
+  (void) request;
+  return true;
+}
+
+static void ncm_report()
+{
+  if (ncm_interface.report_state == REPORT_SPEED) {
+    ncm_notify_speed_change.header.wIndex = ncm_interface.itf_num;
+    usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_notif, (uint8_t *) &ncm_notify_speed_change, sizeof(ncm_notify_speed_change));
+    ncm_interface.report_state = REPORT_CONNECTED;
+    ncm_interface.report_pending = true;
+  } else if (ncm_interface.report_state == REPORT_CONNECTED) {
+    ncm_notify_connected.header.wIndex = ncm_interface.itf_num;
+    usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_notif, (uint8_t *) &ncm_notify_connected, sizeof(ncm_notify_connected));
+    ncm_interface.report_state = REPORT_DONE;
+    ncm_interface.report_pending = true;
+  }
+}
+
+// Handle class control request
+// return false to stall control endpoint (e.g unsupported request)
+bool ncmd_control_request(uint8_t rhport, tusb_control_request_t const * request)
+{
+  switch ( request->bmRequestType_bit.type )
+  {
+    case TUSB_REQ_TYPE_STANDARD:
+      switch ( request->bRequest )
+      {
+        case TUSB_REQ_GET_INTERFACE:
+        {
+          uint8_t const req_itfnum = (uint8_t) request->wIndex;
+          TU_VERIFY(ncm_interface.itf_num + 1 == req_itfnum);
+
+          tud_control_xfer(rhport, request, &ncm_interface.itf_data_alt, 1);
+        }
+          break;
+
+        case TUSB_REQ_SET_INTERFACE:
+        {
+          uint8_t const req_itfnum = (uint8_t) request->wIndex;
+          uint8_t const req_alt    = (uint8_t) request->wValue;
+
+          // Only valid for Data Interface with Alternate is either 0 or 1
+          TU_VERIFY(ncm_interface.itf_num + 1 == req_itfnum && req_alt < 2);
+
+          ncm_interface.itf_data_alt = req_alt;
+
+          if ( ncm_interface.itf_data_alt )
+          {
+            // TODO since we don't actually close endpoint
+            // hack here to not re-open it
+            if (ncm_interface.ep_in == 0 && ncm_interface.ep_out == 0)
+            {
+              TU_ASSERT(ncm_interface.ecm_desc_epdata);
+              TU_ASSERT( usbd_open_edpt_pair(rhport, ncm_interface.ecm_desc_epdata, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in) );
+
+              // TODO set flag indicating tx allowed?
+              ncm_receive_renew(); // prepare for incoming datagrams
+              tud_ncm_link_state_cb(true);
+            }
+          } else {
+            // TODO close the endpoint pair
+            // For now pretend that we did, this should have no harm since host won't try to
+            // communicate with the endpoints again
+            // ncm_interface.ep_in = ncm_interface.ep_out = 0
+            // ncm_interface.report_pending = 0;
+          }
+
+          tud_control_status(rhport, request);
+
+          if (!ncm_interface.report_pending) ncm_report();
+
+        }
+          break;
+
+          // unsupported request
+        default: return false;
+      }
+      break;
+
+    case TUSB_REQ_TYPE_CLASS:
+      TU_VERIFY (ncm_interface.itf_num == request->wIndex);
+
+      if (0x80 /* GET_NTB_PARAMETERS */ == request->bRequest)
+      {
+        tud_control_xfer(rhport, request, (void*)&ntb_parameters, sizeof(ntb_parameters));
+      }
+
+      break;
+
+      // unsupported request
+    default: return false;
+  }
+
+  return true;
+}
+
+static void handle_incoming_datagram(uint32_t len)
+{
+  uint32_t size = len;
+
+  if (len == 0) {
+    return;
+  }
+
+  TU_ASSERT(size >= sizeof(nth16_t), );
+
+  const nth16_t *hdr = (const nth16_t *)receive_ntb;
+  TU_ASSERT(hdr->dwSignature == NTH16_SIGNATURE, );
+  TU_ASSERT(hdr->wNdpIndex >= sizeof(nth16_t) && (hdr->wNdpIndex + sizeof(ndp16_t)) <= len, );
+
+  const ndp16_t *ndp = (const ndp16_t *)(receive_ntb + hdr->wNdpIndex);
+  TU_ASSERT(ndp->dwSignature == NDP16_SIGNATURE_NCM0 || ndp->dwSignature == NDP16_SIGNATURE_NCM1, );
+  TU_ASSERT(hdr->wNdpIndex + ndp->wLength <= len, );
+
+  int num_datagrams = (ndp->wLength - 12) / 4;
+  for (int i = 0; i < num_datagrams && ndp->datagram[i].wDatagramIndex && ndp->datagram[i].wDatagramLength; i++) {
+    tud_ncm_receive_cb(receive_ntb + ndp->datagram[i].wDatagramIndex, ndp->datagram[i].wDatagramLength);
+  }
+
+  ncm_receive_renew();
+}
+
+bool ncmd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
+{
+  (void) rhport;
+  (void) result;
+
+  /* new datagram receive_ntb */
+  if (ep_addr == ncm_interface.ep_out )
+  {
+    handle_incoming_datagram(xferred_bytes);
+  }
+
+  /* data transmission finished */
+  if (ep_addr == ncm_interface.ep_in )
+  {
+    if (ncm_interface.transferring) {
+      ncm_interface.transferring = false;
+    }
+
+    // If there are datagrams queued up that we tried to send while this NTB was being emitted, send them now
+    if (ncm_interface.datagram_count) {
+      ncm_start_tx();
+    }
+  }
+
+  if (ep_addr == ncm_interface.ep_notif )
+  {
+    ncm_interface.report_pending = false;
+    ncm_report();
+  }
+
+  return true;
+}
+
+bool tud_ncm_xmit(void *arg, uint16_t size, void (*flatten)(void *, uint8_t *, uint16_t)) {
+  transmit_ntb_t *ntb = &transmit_ntb[ncm_interface.current_ntb];
+
+  if (ncm_interface.datagram_count >= ncm_interface.max_datagrams_per_ntb) {
+    return false;
+  }
+
+  size_t next_datagram_offset = ncm_interface.next_datagram_offset;
+  if (next_datagram_offset + size > ncm_interface.ntb_in_size) {
+    return false;
+  }
+
+  flatten(arg, ntb->data + next_datagram_offset, size);
+
+  ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramIndex = ncm_interface.next_datagram_offset;
+  ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramLength = size;
+
+  ncm_interface.datagram_count++;
+  next_datagram_offset += size;
+
+  // round up so the next datagram is aligned correctly
+  next_datagram_offset += (CFG_TUD_NCM_ALIGNMENT - 1);
+  next_datagram_offset -= (next_datagram_offset % CFG_TUD_NCM_ALIGNMENT);
+
+  ncm_interface.next_datagram_offset = next_datagram_offset;
+
+  ncm_start_tx();
+
+  return true;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/class/cdc/cdc_ncm_device.h b/src/class/cdc/cdc_ncm_device.h
new file mode 100644
index 000000000..1c767cad2
--- /dev/null
+++ b/src/class/cdc/cdc_ncm_device.h
@@ -0,0 +1,88 @@
+/* 
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef _TUSB_CDC_NCM_DEVICE_H_
+#define _TUSB_CDC_NCM_DEVICE_H_
+
+#include "common/tusb_common.h"
+#include "device/usbd.h"
+#include "cdc.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#ifndef CFG_TUD_NCM_EP_BUFSIZE
+#define CFG_TUD_NCM_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#endif
+
+#ifndef CFG_TUD_NCM_IN_NTB_MAX_SIZE
+#define CFG_TUD_NCM_IN_NTB_MAX_SIZE 3200
+#endif
+
+#ifndef CFG_TUD_NCM_OUT_NTB_MAX_SIZE
+#define CFG_TUD_NCM_OUT_NTB_MAX_SIZE 3200
+#endif
+
+#ifndef CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB
+#define CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB 8
+#endif
+
+#ifndef CFG_TUD_NCM_ALIGNMENT
+#define CFG_TUD_NCM_ALIGNMENT 4
+#endif
+
+/**
+ * Transmit data. Returns true if successful, false if the packet was rejected (transmit buffer full,
+ * interface not up).
+ *
+ * This is structured with a callback to avoid the need for multiple copies; some network stacks,
+ * including LwIP, allow for non-contiguous buffers and provide a function to flatten them. In the
+ * LwIP case, a thin wrapper around pbuf_copy_partial() can be passed here.
+ */
+bool tud_ncm_xmit(void *arg, uint16_t size, void (*flatten)(void *arg, uint8_t *buffer, uint16_t size));
+
+//--------------------------------------------------------------------+
+// Application Callback API
+//--------------------------------------------------------------------+
+void tud_ncm_receive_cb(uint8_t *data, size_t length);
+void tud_ncm_link_state_cb(bool state);
+
+//--------------------------------------------------------------------+
+// INTERNAL USBD-CLASS DRIVER API
+//--------------------------------------------------------------------+
+void     ncmd_init             (void);
+void     ncmd_reset            (uint8_t rhport);
+uint16_t ncmd_open             (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len);
+bool     ncmd_control_request  (uint8_t rhport, tusb_control_request_t const * request);
+bool     ncmd_control_complete (uint8_t rhport, tusb_control_request_t const * request);
+bool     ncmd_xfer_cb          (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TUSB_CDC_NCM_DEVICE_H_ */
diff --git a/src/device/usbd.c b/src/device/usbd.c
index 90edc3dde..2edcf79f3 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -220,6 +220,19 @@ static usbd_class_driver_t const _usbd_driver[] =
       .sof              = NULL
   },
   #endif
+
+  #if CFG_TUD_NCM
+  {
+      DRIVER_NAME("NCM")
+      .init             = ncmd_init,
+      .reset            = ncmd_reset,
+      .open             = ncmd_open,
+      .control_request  = ncmd_control_request,
+      .control_complete = ncmd_control_complete,
+      .xfer_cb          = ncmd_xfer_cb,
+      .sof              = NULL
+  },
+  #endif
 };
 
 enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(_usbd_driver) };
diff --git a/src/device/usbd.h b/src/device/usbd.h
index 0a446fdc2..45caa7871 100644
--- a/src/device/usbd.h
+++ b/src/device/usbd.h
@@ -671,6 +671,39 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re
   TUD_BTH_PRI_ITF(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_interval, _ep_in, _ep_out, _ep_size) \
   TUD_BTH_ISO_ITFS(_itfnum + 1, _ep_in + 1, _ep_out + 1, __VA_ARGS__)
 
+
+//------------- CDC-NCM -------------//
+
+// Length of template descriptor
+#define TUD_CDC_NCM_DESC_LEN  (8+9+5+5+13+6+7+9+9+7+7)
+
+// CDC-ECM Descriptor Template
+// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size.
+#define TUD_CDC_NCM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize) \
+  /* Interface Association */\
+  8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, 0,\
+  /* CDC Control Interface */\
+  9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, _desc_stridx,\
+  /* CDC-NCM Header */\
+  5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0110),\
+  /* CDC-NCM Union */\
+  5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\
+  /* CDC-ECM Functional Descriptor */\
+  13, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ETHERNET_NETWORKING, _mac_stridx, 0, 0, 0, 0, U16_TO_U8S_LE(_maxsegmentsize), U16_TO_U8S_LE(0), 0, \
+  /* CDC-ECM Functional Descriptor */\
+  6, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_NCM, U16_TO_U8S_LE(0x0100), 0, \
+  /* Endpoint Notification */\
+  7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 1,\
+  /* CDC Data Interface (default inactive) */\
+  9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, 1, 0,\
+  /* CDC Data Interface (alternative active) */\
+  9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 1, 2, TUSB_CLASS_CDC_DATA, 0, 1, 0,\
+  /* Endpoint In */\
+  7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\
+  /* Endpoint Out */\
+  7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0
+
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/tusb.h b/src/tusb.h
index 8b0dd103c..d4a692d4a 100644
--- a/src/tusb.h
+++ b/src/tusb.h
@@ -103,6 +103,10 @@
   #if CFG_TUD_BTH
     #include "class/bth/bth_device.h"
   #endif
+
+  #if CFG_TUD_NCM
+    #include "class/cdc/cdc_ncm_device.h"
+  #endif
 #endif
 
 

From a3d6e8fc52db0d1272290a7d5cb7963909254ad5 Mon Sep 17 00:00:00 2001
From: Jacob Potter <jacob@j4cbo.com>
Date: Wed, 4 Nov 2020 21:18:44 -0700
Subject: [PATCH 02/11] Add default for CFG_TUD_NCM

---
 src/tusb_option.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/tusb_option.h b/src/tusb_option.h
index 393940282..b1ce62768 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -237,6 +237,10 @@
   #define CFG_TUD_BTH             0
 #endif
 
+#ifndef CFG_TUD_NCM
+  #define CFG_TUD_NCM             0
+#endif
+
 //--------------------------------------------------------------------
 // HOST OPTIONS
 //--------------------------------------------------------------------

From a3fdcbdf160a7780240e8b669826a917dce877f0 Mon Sep 17 00:00:00 2001
From: Jacob Potter <jacob@j4cbo.com>
Date: Wed, 4 Nov 2020 23:53:03 -0700
Subject: [PATCH 03/11] Just leave the endpoints open, instead of
 closing/reopening

---
 src/class/cdc/cdc_ncm_device.c | 46 +++++++++++-----------------------
 1 file changed, 15 insertions(+), 31 deletions(-)

diff --git a/src/class/cdc/cdc_ncm_device.c b/src/class/cdc/cdc_ncm_device.c
index e6d65171d..e1759ff2f 100644
--- a/src/class/cdc/cdc_ncm_device.c
+++ b/src/class/cdc/cdc_ncm_device.c
@@ -51,11 +51,6 @@ typedef struct
   uint8_t ep_in;
   uint8_t ep_out;
 
-  // Endpoint descriptor use to open/close when receving SetInterface
-  // TODO since configuration descriptor may not be long-lived memory, we should
-  // keep a copy of endpoint attribute instead
-  uint8_t const * ecm_desc_epdata;
-
   enum {
     REPORT_SPEED,
     REPORT_CONNECTED,
@@ -290,9 +285,7 @@ uint16_t ncmd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint1
   // Pair of endpoints
   TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0);
 
-  // ECM by default is in-active, save the endpoint attribute
-  // to open later when receive_ntb setInterface
-  ncm_interface.ecm_desc_epdata = p_desc;
+  TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in) );
 
   drv_len += 2*sizeof(tusb_desc_endpoint_t);
 
@@ -348,33 +341,22 @@ bool ncmd_control_request(uint8_t rhport, tusb_control_request_t const * request
           // Only valid for Data Interface with Alternate is either 0 or 1
           TU_VERIFY(ncm_interface.itf_num + 1 == req_itfnum && req_alt < 2);
 
-          ncm_interface.itf_data_alt = req_alt;
+          if (req_alt != ncm_interface.itf_data_alt) {
+            ncm_interface.itf_data_alt = req_alt;
 
-          if ( ncm_interface.itf_data_alt )
-          {
-            // TODO since we don't actually close endpoint
-            // hack here to not re-open it
-            if (ncm_interface.ep_in == 0 && ncm_interface.ep_out == 0)
-            {
-              TU_ASSERT(ncm_interface.ecm_desc_epdata);
-              TU_ASSERT( usbd_open_edpt_pair(rhport, ncm_interface.ecm_desc_epdata, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in) );
-
-              // TODO set flag indicating tx allowed?
-              ncm_receive_renew(); // prepare for incoming datagrams
-              tud_ncm_link_state_cb(true);
+            if (ncm_interface.itf_data_alt) {
+              if (!usbd_edpt_busy(rhport, ncm_interface.ep_out)) {
+                ncm_receive_renew(); // prepare for incoming datagrams
+              }
+              if (!ncm_interface.report_pending) {
+                ncm_report();
+              }
             }
-          } else {
-            // TODO close the endpoint pair
-            // For now pretend that we did, this should have no harm since host won't try to
-            // communicate with the endpoints again
-            // ncm_interface.ep_in = ncm_interface.ep_out = 0
-            // ncm_interface.report_pending = 0;
+
+            tud_ncm_link_state_cb(ncm_interface.itf_data_alt);
           }
 
           tud_control_status(rhport, request);
-
-          if (!ncm_interface.report_pending) ncm_report();
-
         }
           break;
 
@@ -445,7 +427,7 @@ bool ncmd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
     }
 
     // If there are datagrams queued up that we tried to send while this NTB was being emitted, send them now
-    if (ncm_interface.datagram_count) {
+    if (ncm_interface.datagram_count && ncm_interface.itf_data_alt == 1) {
       ncm_start_tx();
     }
   }
@@ -462,6 +444,8 @@ bool ncmd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
 bool tud_ncm_xmit(void *arg, uint16_t size, void (*flatten)(void *, uint8_t *, uint16_t)) {
   transmit_ntb_t *ntb = &transmit_ntb[ncm_interface.current_ntb];
 
+  TU_VERIFY(ncm_interface.itf_data_alt == 1);
+
   if (ncm_interface.datagram_count >= ncm_interface.max_datagrams_per_ntb) {
     return false;
   }

From 377f8ce76f98ab199d808a263023432649266372 Mon Sep 17 00:00:00 2001
From: Jacob Potter <jacob@j4cbo.com>
Date: Thu, 5 Nov 2020 10:18:29 -0700
Subject: [PATCH 04/11] Log on full NTBs

---
 src/class/cdc/cdc_ncm_device.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/class/cdc/cdc_ncm_device.c b/src/class/cdc/cdc_ncm_device.c
index e1759ff2f..4698fa9c2 100644
--- a/src/class/cdc/cdc_ncm_device.c
+++ b/src/class/cdc/cdc_ncm_device.c
@@ -447,11 +447,13 @@ bool tud_ncm_xmit(void *arg, uint16_t size, void (*flatten)(void *, uint8_t *, u
   TU_VERIFY(ncm_interface.itf_data_alt == 1);
 
   if (ncm_interface.datagram_count >= ncm_interface.max_datagrams_per_ntb) {
+    TU_LOG2("NTB full [by count]\r\n");
     return false;
   }
 
   size_t next_datagram_offset = ncm_interface.next_datagram_offset;
   if (next_datagram_offset + size > ncm_interface.ntb_in_size) {
+    TU_LOG2("ntb full [by size]\r\n");
     return false;
   }
 

From 226efdcec0d880feb4af3995129b85fae9b8ddb8 Mon Sep 17 00:00:00 2001
From: Jacob Potter <jacob@j4cbo.com>
Date: Thu, 5 Nov 2020 18:14:56 -0700
Subject: [PATCH 05/11] Reduce excessive poll rate on interrupt endpoint

---
 src/device/usbd.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/device/usbd.h b/src/device/usbd.h
index 45caa7871..c547ceeee 100644
--- a/src/device/usbd.h
+++ b/src/device/usbd.h
@@ -693,7 +693,7 @@ TU_ATTR_WEAK bool tud_vendor_control_complete_cb(uint8_t rhport, tusb_control_re
   /* CDC-ECM Functional Descriptor */\
   6, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_NCM, U16_TO_U8S_LE(0x0100), 0, \
   /* Endpoint Notification */\
-  7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 1,\
+  7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 50,\
   /* CDC Data Interface (default inactive) */\
   9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, 1, 0,\
   /* CDC Data Interface (alternative active) */\

From 3158b323d8d701f70b80dca986f8b0e8e60eba97 Mon Sep 17 00:00:00 2001
From: Jacob Potter <jacob@j4cbo.com>
Date: Sun, 3 Jan 2021 15:16:54 -0700
Subject: [PATCH 06/11] Adapt to new control xfer callback

---
 src/class/cdc/cdc_ncm_device.c | 12 +++---------
 src/class/cdc/cdc_ncm_device.h |  3 +--
 src/device/usbd.c              |  3 +--
 3 files changed, 5 insertions(+), 13 deletions(-)

diff --git a/src/class/cdc/cdc_ncm_device.c b/src/class/cdc/cdc_ncm_device.c
index 4698fa9c2..a8983dd91 100644
--- a/src/class/cdc/cdc_ncm_device.c
+++ b/src/class/cdc/cdc_ncm_device.c
@@ -292,14 +292,6 @@ uint16_t ncmd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint1
   return drv_len;
 }
 
-// Invoked when class request DATA stage is finished.
-bool ncmd_control_complete(uint8_t rhport, tusb_control_request_t const * request)
-{
-  (void) rhport;
-  (void) request;
-  return true;
-}
-
 static void ncm_report()
 {
   if (ncm_interface.report_state == REPORT_SPEED) {
@@ -317,8 +309,10 @@ static void ncm_report()
 
 // Handle class control request
 // return false to stall control endpoint (e.g unsupported request)
-bool ncmd_control_request(uint8_t rhport, tusb_control_request_t const * request)
+bool ncmd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
 {
+  if ( stage != CONTROL_STAGE_SETUP ) return true;
+
   switch ( request->bmRequestType_bit.type )
   {
     case TUSB_REQ_TYPE_STANDARD:
diff --git a/src/class/cdc/cdc_ncm_device.h b/src/class/cdc/cdc_ncm_device.h
index 1c767cad2..e6c7237b1 100644
--- a/src/class/cdc/cdc_ncm_device.h
+++ b/src/class/cdc/cdc_ncm_device.h
@@ -77,8 +77,7 @@ void tud_ncm_link_state_cb(bool state);
 void     ncmd_init             (void);
 void     ncmd_reset            (uint8_t rhport);
 uint16_t ncmd_open             (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len);
-bool     ncmd_control_request  (uint8_t rhport, tusb_control_request_t const * request);
-bool     ncmd_control_complete (uint8_t rhport, tusb_control_request_t const * request);
+bool     ncmd_control_xfer_cb  (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request);
 bool     ncmd_xfer_cb          (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
 
 #ifdef __cplusplus
diff --git a/src/device/usbd.c b/src/device/usbd.c
index e2110b7a8..c6124263f 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -217,8 +217,7 @@ static usbd_class_driver_t const _usbd_driver[] =
       .init             = ncmd_init,
       .reset            = ncmd_reset,
       .open             = ncmd_open,
-      .control_request  = ncmd_control_request,
-      .control_complete = ncmd_control_complete,
+      .control_xfer_cb  = ncmd_control_xfer_cb,
       .xfer_cb          = ncmd_xfer_cb,
       .sof              = NULL
   },

From 03d7988df3f491515389751a8cf4baecafb155fc Mon Sep 17 00:00:00 2001
From: Peter Lawrence <12226419+majbthrd@users.noreply.github.com>
Date: Tue, 3 Aug 2021 11:28:44 -0500
Subject: [PATCH 07/11] add NCM driver in a compatible manner :
 hathach/tinyusb#550

---
 .../cdc_msc_freertos/src/CMakeLists.txt       |   3 +-
 .../hid_composite_freertos/src/CMakeLists.txt |   3 +-
 examples/device/net_lwip_webserver/src/main.c |   2 +-
 .../net_lwip_webserver/src/tusb_config.h      |   1 +
 .../net_lwip_webserver/src/usb_descriptors.c  |  20 +
 examples/rules.mk                             |   3 +-
 hw/bsp/rp2040/family.cmake                    |   3 +-
 src/class/cdc/cdc.h                           |   6 +-
 .../net/{net_device.c => ecmrndis_device.c}   |   8 +-
 src/class/net/ncm_device.c                    | 500 ++++++++++++++++++
 src/class/net/net_device.h                    |  21 +-
 src/device/usbd.h                             |  31 ++
 src/tusb_option.h                             |   4 +
 13 files changed, 595 insertions(+), 10 deletions(-)
 rename src/class/net/{net_device.c => ecmrndis_device.c} (98%)
 create mode 100644 src/class/net/ncm_device.c

diff --git a/examples/device/cdc_msc_freertos/src/CMakeLists.txt b/examples/device/cdc_msc_freertos/src/CMakeLists.txt
index c0a665fa0..f1c1e8c5d 100644
--- a/examples/device/cdc_msc_freertos/src/CMakeLists.txt
+++ b/examples/device/cdc_msc_freertos/src/CMakeLists.txt
@@ -23,7 +23,8 @@ target_sources(${COMPONENT_TARGET} PUBLIC
   "${TOP}/src/class/hid/hid_device.c"
   "${TOP}/src/class/midi/midi_device.c"
   "${TOP}/src/class/msc/msc_device.c"
-  "${TOP}/src/class/net/net_device.c"
+  "${TOP}/src/class/net/ecmrndis_device.c"
+  "${TOP}/src/class/net/ncm_device.c"
   "${TOP}/src/class/usbtmc/usbtmc_device.c"
   "${TOP}/src/class/vendor/vendor_device.c"
   "${TOP}/src/portable/espressif/esp32sx/dcd_esp32sx.c"
diff --git a/examples/device/hid_composite_freertos/src/CMakeLists.txt b/examples/device/hid_composite_freertos/src/CMakeLists.txt
index a2a4fc9b1..70df447dd 100644
--- a/examples/device/hid_composite_freertos/src/CMakeLists.txt
+++ b/examples/device/hid_composite_freertos/src/CMakeLists.txt
@@ -23,7 +23,8 @@ target_sources(${COMPONENT_TARGET} PUBLIC
   "${TOP}/src/class/hid/hid_device.c"
   "${TOP}/src/class/midi/midi_device.c"
   "${TOP}/src/class/msc/msc_device.c"
-  "${TOP}/src/class/net/net_device.c"
+  "${TOP}/src/class/net/ecmrndis_device.c"
+  "${TOP}/src/class/net/ncm_device.c"
   "${TOP}/src/class/usbtmc/usbtmc_device.c"
   "${TOP}/src/class/vendor/vendor_device.c"
   "${TOP}/src/portable/espressif/esp32sx/dcd_esp32sx.c"
diff --git a/examples/device/net_lwip_webserver/src/main.c b/examples/device/net_lwip_webserver/src/main.c
index 80831921a..a5bd2153a 100644
--- a/examples/device/net_lwip_webserver/src/main.c
+++ b/examples/device/net_lwip_webserver/src/main.c
@@ -97,7 +97,7 @@ static err_t linkoutput_fn(struct netif *netif, struct pbuf *p)
       return ERR_USE;
 
     /* if the network driver can accept another packet, we make it happen */
-    if (tud_network_can_xmit())
+    if (tud_network_can_xmit(p->tot_len))
     {
       tud_network_xmit(p, 0 /* unused for this example */);
       return ERR_OK;
diff --git a/examples/device/net_lwip_webserver/src/tusb_config.h b/examples/device/net_lwip_webserver/src/tusb_config.h
index 262d4ebce..457918cac 100644
--- a/examples/device/net_lwip_webserver/src/tusb_config.h
+++ b/examples/device/net_lwip_webserver/src/tusb_config.h
@@ -101,6 +101,7 @@
 #define CFG_TUD_MIDI              0
 #define CFG_TUD_VENDOR            0
 #define CFG_TUD_NET               1
+#define CFG_TUD_NCM               0
 
 #ifdef __cplusplus
  }
diff --git a/examples/device/net_lwip_webserver/src/usb_descriptors.c b/examples/device/net_lwip_webserver/src/usb_descriptors.c
index 71e6c4582..d9618a3dc 100644
--- a/examples/device/net_lwip_webserver/src/usb_descriptors.c
+++ b/examples/device/net_lwip_webserver/src/usb_descriptors.c
@@ -55,8 +55,12 @@ enum
 
 enum
 {
+#if !CFG_TUD_NCM
   CONFIG_ID_RNDIS = 0,
   CONFIG_ID_ECM   = 1,
+#else
+  CONFIG_ID_NCM   = 0,
+#endif
   CONFIG_ID_COUNT
 };
 
@@ -99,6 +103,7 @@ uint8_t const * tud_descriptor_device_cb(void)
 //--------------------------------------------------------------------+
 #define MAIN_CONFIG_TOTAL_LEN    (TUD_CONFIG_DESC_LEN + TUD_RNDIS_DESC_LEN)
 #define ALT_CONFIG_TOTAL_LEN     (TUD_CONFIG_DESC_LEN + TUD_CDC_ECM_DESC_LEN)
+#define NCM_CONFIG_TOTAL_LEN     (TUD_CONFIG_DESC_LEN + TUD_CDC_NCM_DESC_LEN)
 
 #if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX
   // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number
@@ -120,6 +125,7 @@ uint8_t const * tud_descriptor_device_cb(void)
   #define EPNUM_NET_IN      0x82
 #endif
 
+#if !CFG_TUD_NCM
 static uint8_t const rndis_configuration[] =
 {
   // Config number (index+1), interface count, string index, total length, attribute, power in mA
@@ -137,6 +143,16 @@ static uint8_t const ecm_configuration[] =
   // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size.
   TUD_CDC_ECM_DESCRIPTOR(ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN, CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU),
 };
+#else
+static uint8_t const ncm_configuration[] =
+{
+  // Config number (index+1), interface count, string index, total length, attribute, power in mA
+  TUD_CONFIG_DESCRIPTOR(CONFIG_ID_NCM+1, ITF_NUM_TOTAL, 0, NCM_CONFIG_TOTAL_LEN, 0, 100),
+
+  // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size.
+  TUD_CDC_NCM_DESCRIPTOR(ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN, CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU),
+};
+#endif
 
 // Configuration array: RNDIS and CDC-ECM
 // - Windows only works with RNDIS
@@ -144,8 +160,12 @@ static uint8_t const ecm_configuration[] =
 // - Linux will work on both
 static uint8_t const * const configuration_arr[2] =
 {
+#if !CFG_TUD_NCM
   [CONFIG_ID_RNDIS] = rndis_configuration,
   [CONFIG_ID_ECM  ] = ecm_configuration
+#else
+  [CONFIG_ID_NCM  ] = ncm_configuration
+#endif
 };
 
 // Invoked when received GET CONFIGURATION DESCRIPTOR
diff --git a/examples/rules.mk b/examples/rules.mk
index 861973284..a9dfa1363 100644
--- a/examples/rules.mk
+++ b/examples/rules.mk
@@ -33,7 +33,8 @@ SRC_C += \
 	src/class/hid/hid_device.c \
 	src/class/midi/midi_device.c \
 	src/class/msc/msc_device.c \
-	src/class/net/net_device.c \
+	src/class/net/ecmrndis_device.c \
+	src/class/net/ncm_device.c \
 	src/class/usbtmc/usbtmc_device.c \
 	src/class/vendor/vendor_device.c
 
diff --git a/hw/bsp/rp2040/family.cmake b/hw/bsp/rp2040/family.cmake
index 41960b6cd..a0f41212c 100644
--- a/hw/bsp/rp2040/family.cmake
+++ b/hw/bsp/rp2040/family.cmake
@@ -70,7 +70,8 @@ if (NOT TARGET _rp2040_family_inclusion_marker)
 			${TOP}/src/class/hid/hid_device.c
 			${TOP}/src/class/midi/midi_device.c
 			${TOP}/src/class/msc/msc_device.c
-			${TOP}/src/class/net/net_device.c
+			${TOP}/src/class/net/ecmrndis_device.c
+			${TOP}/src/class/net/ncm_device.c
 			${TOP}/src/class/usbtmc/usbtmc_device.c
 			${TOP}/src/class/vendor/vendor_device.c
 			)
diff --git a/src/class/cdc/cdc.h b/src/class/cdc/cdc.h
index 5df47f70b..52de859ca 100644
--- a/src/class/cdc/cdc.h
+++ b/src/class/cdc/cdc.h
@@ -69,7 +69,8 @@ typedef enum
   CDC_COMM_SUBCLASS_DEVICE_MANAGEMENT                 , ///< Device Management  [USBWMC1.1]
   CDC_COMM_SUBCLASS_MOBILE_DIRECT_LINE_MODEL          , ///< Mobile Direct Line Model  [USBWMC1.1]
   CDC_COMM_SUBCLASS_OBEX                              , ///< OBEX  [USBWMC1.1]
-  CDC_COMM_SUBCLASS_ETHERNET_EMULATION_MODEL            ///< Ethernet Emulation Model  [USBEEM1.0]
+  CDC_COMM_SUBCLASS_ETHERNET_EMULATION_MODEL          , ///< Ethernet Emulation Model  [USBEEM1.0]
+  CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL               ///< Network Control Model  [USBNCM1.0]
 } cdc_comm_sublcass_type_t;
 
 /// Communication Interface Protocol Codes
@@ -114,7 +115,8 @@ typedef enum
   CDC_FUNC_DESC_COMMAND_SET                                      = 0x16 , ///< Command Set Functional Descriptor
   CDC_FUNC_DESC_COMMAND_SET_DETAIL                               = 0x17 , ///< Command Set Detail Functional Descriptor
   CDC_FUNC_DESC_TELEPHONE_CONTROL_MODEL                          = 0x18 , ///< Telephone Control Model Functional Descriptor
-  CDC_FUNC_DESC_OBEX_SERVICE_IDENTIFIER                          = 0x19   ///< OBEX Service Identifier Functional Descriptor
+  CDC_FUNC_DESC_OBEX_SERVICE_IDENTIFIER                          = 0x19 , ///< OBEX Service Identifier Functional Descriptor
+  CDC_FUNC_DESC_NCM                                              = 0x1A , ///< NCM Functional Descriptor
 }cdc_func_desc_type_t;
 
 //--------------------------------------------------------------------+
diff --git a/src/class/net/net_device.c b/src/class/net/ecmrndis_device.c
similarity index 98%
rename from src/class/net/net_device.c
rename to src/class/net/ecmrndis_device.c
index 9d9719ce4..288ecb7e2 100644
--- a/src/class/net/net_device.c
+++ b/src/class/net/ecmrndis_device.c
@@ -27,7 +27,7 @@
 
 #include "tusb_option.h"
 
-#if ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_NET )
+#if ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_NET && !CFG_TUD_NCM )
 
 #include "device/usbd.h"
 #include "device/usbd_pvt.h"
@@ -119,6 +119,8 @@ static void do_in_xfer(uint8_t *buf, uint16_t len)
 
 void netd_report(uint8_t *buf, uint16_t len)
 {
+  // skip if previous report not yet acknowledged by host
+  if ( usbd_edpt_busy(TUD_OPT_RHPORT, _netd_itf.ep_notif) ) return;
   usbd_edpt_xfer(TUD_OPT_RHPORT, _netd_itf.ep_notif, buf, len);
 }
 
@@ -407,8 +409,10 @@ bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_
   return true;
 }
 
-bool tud_network_can_xmit(void)
+bool tud_network_can_xmit(uint16_t size)
 {
+  (void)size;
+
   return can_xmit;
 }
 
diff --git a/src/class/net/ncm_device.c b/src/class/net/ncm_device.c
new file mode 100644
index 000000000..1f592401e
--- /dev/null
+++ b/src/class/net/ncm_device.c
@@ -0,0 +1,500 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2020 Jacob Berg Potter
+ * Copyright (c) 2020 Peter Lawrence
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_NET && CFG_TUD_NCM )
+
+#include "device/usbd.h"
+#include "device/usbd_pvt.h"
+#include "net_device.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+#define NTH16_SIGNATURE 0x484D434E
+#define NDP16_SIGNATURE_NCM0 0x304D434E
+#define NDP16_SIGNATURE_NCM1 0x314D434E
+
+typedef struct TU_ATTR_PACKED
+{
+  uint16_t wLength;
+  uint16_t bmNtbFormatsSupported;
+  uint32_t dwNtbInMaxSize;
+  uint16_t wNdbInDivisor;
+  uint16_t wNdbInPayloadRemainder;
+  uint16_t wNdbInAlignment;
+  uint16_t wReserved;
+  uint32_t dwNtbOutMaxSize;
+  uint16_t wNdbOutDivisor;
+  uint16_t wNdbOutPayloadRemainder;
+  uint16_t wNdbOutAlignment;
+  uint16_t wNtbOutMaxDatagrams;
+} ntb_parameters_t;
+
+typedef struct TU_ATTR_PACKED
+{
+  uint32_t dwSignature;
+  uint16_t wHeaderLength;
+  uint16_t wSequence;
+  uint16_t wBlockLength;
+  uint16_t wNdpIndex;
+} nth16_t;
+
+typedef struct TU_ATTR_PACKED
+{
+  uint16_t wDatagramIndex;
+  uint16_t wDatagramLength;
+} ndp16_datagram_t;
+
+typedef struct TU_ATTR_PACKED
+{
+  uint32_t dwSignature;
+  uint16_t wLength;
+  uint16_t wNextNdpIndex;
+  ndp16_datagram_t datagram[];
+} ndp16_t;
+
+typedef union TU_ATTR_PACKED {
+  struct {
+    nth16_t nth;
+    ndp16_t ndp;
+  };
+  uint8_t data[CFG_TUD_NCM_IN_NTB_MAX_SIZE];
+} transmit_ntb_t;
+
+struct ecm_notify_struct
+{
+  tusb_control_request_t header;
+  uint32_t downlink, uplink;
+};
+
+typedef struct
+{
+  uint8_t itf_num;      // Index number of Management Interface, +1 for Data Interface
+  uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active
+
+  uint8_t ep_notif;
+  uint8_t ep_in;
+  uint8_t ep_out;
+
+  const ndp16_t *ndp;
+  uint8_t num_datagrams, current_datagram_index;
+
+  enum {
+    REPORT_SPEED,
+    REPORT_CONNECTED,
+    REPORT_DONE
+  } report_state;
+  bool report_pending;
+
+  uint8_t current_ntb; // Index in transmit_ntb[] that is currently being filled with datagrams
+  uint8_t datagram_count; // Number of datagrams in transmit_ntb[current_ntb]
+  uint16_t next_datagram_offset; // Offset in transmit_ntb[current_ntb].data to place the next datagram
+  uint16_t ntb_in_size; // Maximum size of transmitted (IN to host) NTBs; initially CFG_TUD_NCM_IN_NTB_MAX_SIZE
+  uint8_t max_datagrams_per_ntb; // Maximum number of datagrams per NTB; initially CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB
+
+  uint16_t nth_sequence; // Sequence number counter for transmitted NTBs
+
+  bool transferring;
+
+} ncm_interface_t;
+
+//--------------------------------------------------------------------+
+// INTERNAL OBJECT & FUNCTION DECLARATION
+//--------------------------------------------------------------------+
+
+CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static const ntb_parameters_t ntb_parameters = {
+    .wLength = sizeof(ntb_parameters_t),
+    .bmNtbFormatsSupported = 0x01,
+    .dwNtbInMaxSize = CFG_TUD_NCM_IN_NTB_MAX_SIZE,
+    .wNdbInDivisor = 4,
+    .wNdbInPayloadRemainder = 0,
+    .wNdbInAlignment = CFG_TUD_NCM_ALIGNMENT,
+    .wReserved = 0,
+    .dwNtbOutMaxSize = CFG_TUD_NCM_OUT_NTB_MAX_SIZE,
+    .wNdbOutDivisor = 4,
+    .wNdbOutPayloadRemainder = 0,
+    .wNdbOutAlignment = CFG_TUD_NCM_ALIGNMENT,
+    .wNtbOutMaxDatagrams = 0
+};
+
+CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static transmit_ntb_t transmit_ntb[2];
+
+CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t receive_ntb[CFG_TUD_NCM_OUT_NTB_MAX_SIZE];
+
+static ncm_interface_t ncm_interface;
+
+/*
+ * Set up the NTB state in ncm_interface to be ready to add datagrams.
+ */
+static void ncm_prepare_for_tx(void) {
+  ncm_interface.datagram_count = 0;
+  // datagrams start after all the headers
+  ncm_interface.next_datagram_offset = sizeof(nth16_t) + sizeof(ndp16_t)
+      + ((CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB + 1) * sizeof(ndp16_datagram_t));
+}
+
+/*
+ * If not already transmitting, start sending the current NTB to the host and swap buffers
+ * to start filling the other one with datagrams.
+ */
+static void ncm_start_tx(void) {
+  if (ncm_interface.transferring) {
+    return;
+  }
+
+  transmit_ntb_t *ntb = &transmit_ntb[ncm_interface.current_ntb];
+  size_t ntb_length = ncm_interface.next_datagram_offset;
+
+  // Fill in NTB header
+  ntb->nth.dwSignature = NTH16_SIGNATURE;
+  ntb->nth.wHeaderLength = sizeof(nth16_t);
+  ntb->nth.wSequence = ncm_interface.nth_sequence++;
+  ntb->nth.wBlockLength = ntb_length;
+  ntb->nth.wNdpIndex = sizeof(nth16_t);
+
+  // Fill in NDP16 header and terminator
+  ntb->ndp.dwSignature = NDP16_SIGNATURE_NCM0;
+  ntb->ndp.wLength = sizeof(ndp16_t) + (ncm_interface.datagram_count + 1) * sizeof(ndp16_datagram_t);
+  ntb->ndp.wNextNdpIndex = 0;
+  ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramIndex = 0;
+  ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramLength = 0;
+
+  // Kick off an endpoint transfer
+  usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_in, ntb->data, ntb_length);
+  ncm_interface.transferring = true;
+
+  // Swap to the other NTB and clear it out
+  ncm_interface.current_ntb = 1 - ncm_interface.current_ntb;
+  ncm_prepare_for_tx();
+}
+
+static struct ecm_notify_struct ncm_notify_connected =
+    {
+        .header = {
+            .bmRequestType = 0xA1,
+            .bRequest = 0 /* NETWORK_CONNECTION aka NetworkConnection */,
+            .wValue = 1 /* Connected */,
+            .wLength = 0,
+        },
+    };
+
+static struct ecm_notify_struct ncm_notify_speed_change =
+    {
+        .header = {
+            .bmRequestType = 0xA1,
+            .bRequest = 0x2A /* CONNECTION_SPEED_CHANGE aka ConnectionSpeedChange */,
+            .wLength = 8,
+        },
+        .downlink = 10000000,
+        .uplink = 10000000,
+    };
+
+void tud_network_recv_renew(void)
+{
+  if (!ncm_interface.num_datagrams)
+  {
+    usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_out, receive_ntb, sizeof(receive_ntb));
+    return;
+  }
+
+  const ndp16_t *ndp = ncm_interface.ndp;
+  const int i = ncm_interface.current_datagram_index;
+  ncm_interface.current_datagram_index++;
+  ncm_interface.num_datagrams--;
+
+  tud_network_recv_cb(receive_ntb + ndp->datagram[i].wDatagramIndex, ndp->datagram[i].wDatagramLength);
+}
+
+//--------------------------------------------------------------------+
+// USBD Driver API
+//--------------------------------------------------------------------+
+
+void netd_init(void)
+{
+  tu_memclr(&ncm_interface, sizeof(ncm_interface));
+  ncm_interface.ntb_in_size = CFG_TUD_NCM_IN_NTB_MAX_SIZE;
+  ncm_interface.max_datagrams_per_ntb = CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB;
+  ncm_prepare_for_tx();
+}
+
+void netd_reset(uint8_t rhport)
+{
+  (void) rhport;
+
+  netd_init();
+}
+
+uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len)
+{
+  // confirm interface hasn't already been allocated
+  TU_ASSERT(0 == ncm_interface.ep_notif, 0);
+
+  //------------- Management Interface -------------//
+  ncm_interface.itf_num = itf_desc->bInterfaceNumber;
+
+  uint16_t drv_len = sizeof(tusb_desc_interface_t);
+  uint8_t const * p_desc = tu_desc_next( itf_desc );
+
+  // Communication Functional Descriptors
+  while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len )
+  {
+    drv_len += tu_desc_len(p_desc);
+    p_desc   = tu_desc_next(p_desc);
+  }
+
+  // notification endpoint (if any)
+  if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) )
+  {
+    TU_ASSERT( usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0 );
+
+    ncm_interface.ep_notif = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress;
+
+    drv_len += tu_desc_len(p_desc);
+    p_desc   = tu_desc_next(p_desc);
+  }
+
+  //------------- Data Interface -------------//
+  // - CDC-NCM data interface has 2 alternate settings
+  //   - 0 : zero endpoints for inactive (default)
+  //   - 1 : IN & OUT endpoints for transfer of NTBs
+  TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0);
+
+  do
+  {
+    tusb_desc_interface_t const * data_itf_desc = (tusb_desc_interface_t const *) p_desc;
+    TU_ASSERT(TUSB_CLASS_CDC_DATA == data_itf_desc->bInterfaceClass, 0);
+
+    drv_len += tu_desc_len(p_desc);
+    p_desc   = tu_desc_next(p_desc);
+  } while((TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && (drv_len <= max_len));
+
+  // Pair of endpoints
+  TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0);
+
+  TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in) );
+
+  drv_len += 2*sizeof(tusb_desc_endpoint_t);
+
+  return drv_len;
+}
+
+static void ncm_report(void)
+{
+  if (ncm_interface.report_state == REPORT_SPEED) {
+    ncm_notify_speed_change.header.wIndex = ncm_interface.itf_num;
+    usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_notif, (uint8_t *) &ncm_notify_speed_change, sizeof(ncm_notify_speed_change));
+    ncm_interface.report_state = REPORT_CONNECTED;
+    ncm_interface.report_pending = true;
+  } else if (ncm_interface.report_state == REPORT_CONNECTED) {
+    ncm_notify_connected.header.wIndex = ncm_interface.itf_num;
+    usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_notif, (uint8_t *) &ncm_notify_connected, sizeof(ncm_notify_connected));
+    ncm_interface.report_state = REPORT_DONE;
+    ncm_interface.report_pending = true;
+  }
+}
+
+TU_ATTR_WEAK void tud_network_link_state_cb(bool state)
+{
+  (void)state;
+}
+
+// Handle class control request
+// return false to stall control endpoint (e.g unsupported request)
+bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request)
+{
+  if ( stage != CONTROL_STAGE_SETUP ) return true;
+
+  switch ( request->bmRequestType_bit.type )
+  {
+    case TUSB_REQ_TYPE_STANDARD:
+      switch ( request->bRequest )
+      {
+        case TUSB_REQ_GET_INTERFACE:
+        {
+          uint8_t const req_itfnum = (uint8_t) request->wIndex;
+          TU_VERIFY(ncm_interface.itf_num + 1 == req_itfnum);
+
+          tud_control_xfer(rhport, request, &ncm_interface.itf_data_alt, 1);
+        }
+          break;
+
+        case TUSB_REQ_SET_INTERFACE:
+        {
+          uint8_t const req_itfnum = (uint8_t) request->wIndex;
+          uint8_t const req_alt    = (uint8_t) request->wValue;
+
+          // Only valid for Data Interface with Alternate is either 0 or 1
+          TU_VERIFY(ncm_interface.itf_num + 1 == req_itfnum && req_alt < 2);
+
+          if (req_alt != ncm_interface.itf_data_alt) {
+            ncm_interface.itf_data_alt = req_alt;
+
+            if (ncm_interface.itf_data_alt) {
+              if (!usbd_edpt_busy(rhport, ncm_interface.ep_out)) {
+                tud_network_recv_renew(); // prepare for incoming datagrams
+              }
+              if (!ncm_interface.report_pending) {
+                ncm_report();
+              }
+            }
+
+            tud_network_link_state_cb(ncm_interface.itf_data_alt);
+          }
+
+          tud_control_status(rhport, request);
+        }
+          break;
+
+          // unsupported request
+        default: return false;
+      }
+      break;
+
+    case TUSB_REQ_TYPE_CLASS:
+      TU_VERIFY (ncm_interface.itf_num == request->wIndex);
+
+      if (0x80 /* GET_NTB_PARAMETERS */ == request->bRequest)
+      {
+        tud_control_xfer(rhport, request, (void*)&ntb_parameters, sizeof(ntb_parameters));
+      }
+
+      break;
+
+      // unsupported request
+    default: return false;
+  }
+
+  return true;
+}
+
+static void handle_incoming_datagram(uint32_t len)
+{
+  uint32_t size = len;
+
+  if (len == 0) {
+    return;
+  }
+
+  TU_ASSERT(size >= sizeof(nth16_t), );
+
+  const nth16_t *hdr = (const nth16_t *)receive_ntb;
+  TU_ASSERT(hdr->dwSignature == NTH16_SIGNATURE, );
+  TU_ASSERT(hdr->wNdpIndex >= sizeof(nth16_t) && (hdr->wNdpIndex + sizeof(ndp16_t)) <= len, );
+
+  const ndp16_t *ndp = (const ndp16_t *)(receive_ntb + hdr->wNdpIndex);
+  TU_ASSERT(ndp->dwSignature == NDP16_SIGNATURE_NCM0 || ndp->dwSignature == NDP16_SIGNATURE_NCM1, );
+  TU_ASSERT(hdr->wNdpIndex + ndp->wLength <= len, );
+
+  int num_datagrams = (ndp->wLength - 12) / 4;
+  ncm_interface.current_datagram_index = 0;
+  ncm_interface.num_datagrams = 0;
+  ncm_interface.ndp = ndp;
+  for (int i = 0; i < num_datagrams && ndp->datagram[i].wDatagramIndex && ndp->datagram[i].wDatagramLength; i++)
+    ncm_interface.num_datagrams++;
+  
+  tud_network_recv_renew();
+}
+
+bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
+{
+  (void) rhport;
+  (void) result;
+
+  /* new datagram receive_ntb */
+  if (ep_addr == ncm_interface.ep_out )
+  {
+    handle_incoming_datagram(xferred_bytes);
+  }
+
+  /* data transmission finished */
+  if (ep_addr == ncm_interface.ep_in )
+  {
+    if (ncm_interface.transferring) {
+      ncm_interface.transferring = false;
+    }
+
+    // If there are datagrams queued up that we tried to send while this NTB was being emitted, send them now
+    if (ncm_interface.datagram_count && ncm_interface.itf_data_alt == 1) {
+      ncm_start_tx();
+    }
+  }
+
+  if (ep_addr == ncm_interface.ep_notif )
+  {
+    ncm_interface.report_pending = false;
+    ncm_report();
+  }
+
+  return true;
+}
+
+// poll network driver for its ability to accept another packet to transmit
+bool tud_network_can_xmit(uint16_t size)
+{
+  TU_VERIFY(ncm_interface.itf_data_alt == 1);
+
+  if (ncm_interface.datagram_count >= ncm_interface.max_datagrams_per_ntb) {
+    TU_LOG2("NTB full [by count]\r\n");
+    return false;
+  }
+
+  size_t next_datagram_offset = ncm_interface.next_datagram_offset;
+  if (next_datagram_offset + size > ncm_interface.ntb_in_size) {
+    TU_LOG2("ntb full [by size]\r\n");
+    return false;
+  }
+
+  return true;
+}
+
+void tud_network_xmit(void *ref, uint16_t arg)
+{
+  transmit_ntb_t *ntb = &transmit_ntb[ncm_interface.current_ntb];
+  size_t next_datagram_offset = ncm_interface.next_datagram_offset;
+
+  uint16_t size = tud_network_xmit_cb(ntb->data + next_datagram_offset, ref, arg);
+
+  ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramIndex = ncm_interface.next_datagram_offset;
+  ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramLength = size;
+
+  ncm_interface.datagram_count++;
+  next_datagram_offset += size;
+
+  // round up so the next datagram is aligned correctly
+  next_datagram_offset += (CFG_TUD_NCM_ALIGNMENT - 1);
+  next_datagram_offset -= (next_datagram_offset % CFG_TUD_NCM_ALIGNMENT);
+
+  ncm_interface.next_datagram_offset = next_datagram_offset;
+
+  ncm_start_tx();
+}
+
+#endif
diff --git a/src/class/net/net_device.h b/src/class/net/net_device.h
index f030f3077..e6cdc8b8b 100644
--- a/src/class/net/net_device.h
+++ b/src/class/net/net_device.h
@@ -38,6 +38,22 @@
 #define CFG_TUD_NET_MTU           1514
 #endif
 
+#ifndef CFG_TUD_NCM_IN_NTB_MAX_SIZE
+#define CFG_TUD_NCM_IN_NTB_MAX_SIZE 3200
+#endif
+
+#ifndef CFG_TUD_NCM_OUT_NTB_MAX_SIZE
+#define CFG_TUD_NCM_OUT_NTB_MAX_SIZE 3200
+#endif
+
+#ifndef CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB
+#define CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB 8
+#endif
+
+#ifndef CFG_TUD_NCM_ALIGNMENT
+#define CFG_TUD_NCM_ALIGNMENT 4
+#endif
+
 #ifdef __cplusplus
  extern "C" {
 #endif
@@ -63,11 +79,14 @@ extern const uint8_t tud_network_mac_address[6];
 void tud_network_recv_renew(void);
 
 // poll network driver for its ability to accept another packet to transmit
-bool tud_network_can_xmit(void);
+bool tud_network_can_xmit(uint16_t size);
 
 // if network_can_xmit() returns true, network_xmit() can be called once
 void tud_network_xmit(void *ref, uint16_t arg);
 
+// callback to client providing optional indication of internal state of network driver
+void tud_network_link_state_cb(bool state);
+
 //--------------------------------------------------------------------+
 // INTERNAL USBD-CLASS DRIVER API
 //--------------------------------------------------------------------+
diff --git a/src/device/usbd.h b/src/device/usbd.h
index 638d93094..200e1cca1 100644
--- a/src/device/usbd.h
+++ b/src/device/usbd.h
@@ -781,6 +781,37 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb
   TUD_BTH_PRI_ITF(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_interval, _ep_in, _ep_out, _ep_size) \
   TUD_BTH_ISO_ITFS(_itfnum + 1, _ep_in + 1, _ep_out + 1, __VA_ARGS__)
 
+//------------- CDC-NCM -------------//
+
+// Length of template descriptor
+#define TUD_CDC_NCM_DESC_LEN  (8+9+5+5+13+6+7+9+9+7+7)
+
+// CDC-ECM Descriptor Template
+// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size.
+#define TUD_CDC_NCM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize) \
+  /* Interface Association */\
+  8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, 0,\
+  /* CDC Control Interface */\
+  9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, _desc_stridx,\
+  /* CDC-NCM Header */\
+  5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0110),\
+  /* CDC-NCM Union */\
+  5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\
+  /* CDC-ECM Functional Descriptor */\
+  13, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ETHERNET_NETWORKING, _mac_stridx, 0, 0, 0, 0, U16_TO_U8S_LE(_maxsegmentsize), U16_TO_U8S_LE(0), 0, \
+  /* CDC-ECM Functional Descriptor */\
+  6, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_NCM, U16_TO_U8S_LE(0x0100), 0, \
+  /* Endpoint Notification */\
+  7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 50,\
+  /* CDC Data Interface (default inactive) */\
+  9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, 1, 0,\
+  /* CDC Data Interface (alternative active) */\
+  9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 1, 2, TUSB_CLASS_CDC_DATA, 0, 1, 0,\
+  /* Endpoint In */\
+  7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\
+  /* Endpoint Out */\
+  7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/tusb_option.h b/src/tusb_option.h
index ad2a26a66..12aab82fc 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -269,6 +269,10 @@
   #define CFG_TUD_BTH             0
 #endif
 
+#ifndef CFG_TUD_NCM
+  #define CFG_TUD_NCM             0
+#endif
+
 //--------------------------------------------------------------------
 // HOST OPTIONS
 //--------------------------------------------------------------------

From a867d87072e6f25e47f209e9615e00b97a9eb36f Mon Sep 17 00:00:00 2001
From: hathach <thach@tinyusb.org>
Date: Tue, 5 Oct 2021 12:32:07 +0700
Subject: [PATCH 08/11] rename CFG_TUD_NET to CFG_TUD_ECM_RNDIS

---
 .../cdc_msc_freertos/src/CMakeLists.txt       |  2 +-
 .../hid_composite_freertos/src/CMakeLists.txt |  2 +-
 .../net_lwip_webserver/src/tusb_config.h      | 12 +++---
 .../net_lwip_webserver/src/usb_descriptors.c  | 12 ++++--
 examples/rules.mk                             |  2 +-
 hw/bsp/rp2040/family.cmake                    |  2 +-
 .../{ecmrndis_device.c => ecm_rndis_device.c} |  2 +-
 src/class/net/ncm_device.c                    | 24 +++++------
 src/class/net/net_device.h                    | 40 ++++++++++++-------
 src/device/usbd.c                             |  2 +-
 src/tusb.h                                    |  2 +-
 src/tusb_option.h                             | 10 ++---
 12 files changed, 63 insertions(+), 49 deletions(-)
 rename src/class/net/{ecmrndis_device.c => ecm_rndis_device.c} (99%)

diff --git a/examples/device/cdc_msc_freertos/src/CMakeLists.txt b/examples/device/cdc_msc_freertos/src/CMakeLists.txt
index f1c1e8c5d..07fc4df89 100644
--- a/examples/device/cdc_msc_freertos/src/CMakeLists.txt
+++ b/examples/device/cdc_msc_freertos/src/CMakeLists.txt
@@ -23,7 +23,7 @@ target_sources(${COMPONENT_TARGET} PUBLIC
   "${TOP}/src/class/hid/hid_device.c"
   "${TOP}/src/class/midi/midi_device.c"
   "${TOP}/src/class/msc/msc_device.c"
-  "${TOP}/src/class/net/ecmrndis_device.c"
+  "${TOP}/src/class/net/ecm_rndis_device.c"
   "${TOP}/src/class/net/ncm_device.c"
   "${TOP}/src/class/usbtmc/usbtmc_device.c"
   "${TOP}/src/class/vendor/vendor_device.c"
diff --git a/examples/device/hid_composite_freertos/src/CMakeLists.txt b/examples/device/hid_composite_freertos/src/CMakeLists.txt
index 70df447dd..6f156379f 100644
--- a/examples/device/hid_composite_freertos/src/CMakeLists.txt
+++ b/examples/device/hid_composite_freertos/src/CMakeLists.txt
@@ -23,7 +23,7 @@ target_sources(${COMPONENT_TARGET} PUBLIC
   "${TOP}/src/class/hid/hid_device.c"
   "${TOP}/src/class/midi/midi_device.c"
   "${TOP}/src/class/msc/msc_device.c"
-  "${TOP}/src/class/net/ecmrndis_device.c"
+  "${TOP}/src/class/net/ecm_rndis_device.c"
   "${TOP}/src/class/net/ncm_device.c"
   "${TOP}/src/class/usbtmc/usbtmc_device.c"
   "${TOP}/src/class/vendor/vendor_device.c"
diff --git a/examples/device/net_lwip_webserver/src/tusb_config.h b/examples/device/net_lwip_webserver/src/tusb_config.h
index 457918cac..114961cb7 100644
--- a/examples/device/net_lwip_webserver/src/tusb_config.h
+++ b/examples/device/net_lwip_webserver/src/tusb_config.h
@@ -95,13 +95,11 @@
 #endif
 
 //------------- CLASS -------------//
-#define CFG_TUD_CDC               0
-#define CFG_TUD_MSC               0
-#define CFG_TUD_HID               0
-#define CFG_TUD_MIDI              0
-#define CFG_TUD_VENDOR            0
-#define CFG_TUD_NET               1
-#define CFG_TUD_NCM               0
+
+// Network class has 2 drivers: ECM/RNDIS and NCM.
+// Only one of the drivers can be enabled
+#define CFG_TUD_ECM_RNDIS     1
+#define CFG_TUD_NCM           (1-CFG_TUD_ECM_RNDIS)
 
 #ifdef __cplusplus
  }
diff --git a/examples/device/net_lwip_webserver/src/usb_descriptors.c b/examples/device/net_lwip_webserver/src/usb_descriptors.c
index d9618a3dc..2b4b2a0c3 100644
--- a/examples/device/net_lwip_webserver/src/usb_descriptors.c
+++ b/examples/device/net_lwip_webserver/src/usb_descriptors.c
@@ -33,7 +33,7 @@
  */
 #define _PID_MAP(itf, n)  ( (CFG_TUD_##itf) << (n) )
 #define USB_PID           (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
-                           _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) | _PID_MAP(NET, 5) )
+                           _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) | _PID_MAP(ECM_RNDIS, 5) | _PID_MAP(NCM, 5) )
 
 // String Descriptor Index
 enum
@@ -55,7 +55,7 @@ enum
 
 enum
 {
-#if !CFG_TUD_NCM
+#if CFG_TUD_ECM_RNDIS
   CONFIG_ID_RNDIS = 0,
   CONFIG_ID_ECM   = 1,
 #else
@@ -125,7 +125,8 @@ uint8_t const * tud_descriptor_device_cb(void)
   #define EPNUM_NET_IN      0x82
 #endif
 
-#if !CFG_TUD_NCM
+#if CFG_TUD_ECM_RNDIS
+
 static uint8_t const rndis_configuration[] =
 {
   // Config number (index+1), interface count, string index, total length, attribute, power in mA
@@ -143,7 +144,9 @@ static uint8_t const ecm_configuration[] =
   // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size.
   TUD_CDC_ECM_DESCRIPTOR(ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN, CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU),
 };
+
 #else
+
 static uint8_t const ncm_configuration[] =
 {
   // Config number (index+1), interface count, string index, total length, attribute, power in mA
@@ -152,6 +155,7 @@ static uint8_t const ncm_configuration[] =
   // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size.
   TUD_CDC_NCM_DESCRIPTOR(ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN, CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU),
 };
+
 #endif
 
 // Configuration array: RNDIS and CDC-ECM
@@ -160,7 +164,7 @@ static uint8_t const ncm_configuration[] =
 // - Linux will work on both
 static uint8_t const * const configuration_arr[2] =
 {
-#if !CFG_TUD_NCM
+#if CFG_TUD_ECM_RNDIS
   [CONFIG_ID_RNDIS] = rndis_configuration,
   [CONFIG_ID_ECM  ] = ecm_configuration
 #else
diff --git a/examples/rules.mk b/examples/rules.mk
index a9dfa1363..5f9dd9d19 100644
--- a/examples/rules.mk
+++ b/examples/rules.mk
@@ -33,7 +33,7 @@ SRC_C += \
 	src/class/hid/hid_device.c \
 	src/class/midi/midi_device.c \
 	src/class/msc/msc_device.c \
-	src/class/net/ecmrndis_device.c \
+	src/class/net/ecm_rndis_device.c \
 	src/class/net/ncm_device.c \
 	src/class/usbtmc/usbtmc_device.c \
 	src/class/vendor/vendor_device.c
diff --git a/hw/bsp/rp2040/family.cmake b/hw/bsp/rp2040/family.cmake
index a0f41212c..4ba24b3aa 100644
--- a/hw/bsp/rp2040/family.cmake
+++ b/hw/bsp/rp2040/family.cmake
@@ -70,7 +70,7 @@ if (NOT TARGET _rp2040_family_inclusion_marker)
 			${TOP}/src/class/hid/hid_device.c
 			${TOP}/src/class/midi/midi_device.c
 			${TOP}/src/class/msc/msc_device.c
-			${TOP}/src/class/net/ecmrndis_device.c
+			${TOP}/src/class/net/ecm_rndis_device.c
 			${TOP}/src/class/net/ncm_device.c
 			${TOP}/src/class/usbtmc/usbtmc_device.c
 			${TOP}/src/class/vendor/vendor_device.c
diff --git a/src/class/net/ecmrndis_device.c b/src/class/net/ecm_rndis_device.c
similarity index 99%
rename from src/class/net/ecmrndis_device.c
rename to src/class/net/ecm_rndis_device.c
index 288ecb7e2..c6cd388e3 100644
--- a/src/class/net/ecmrndis_device.c
+++ b/src/class/net/ecm_rndis_device.c
@@ -27,7 +27,7 @@
 
 #include "tusb_option.h"
 
-#if ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_NET && !CFG_TUD_NCM )
+#if ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_ECM_RNDIS )
 
 #include "device/usbd.h"
 #include "device/usbd_pvt.h"
diff --git a/src/class/net/ncm_device.c b/src/class/net/ncm_device.c
index 1f592401e..2f7d58f99 100644
--- a/src/class/net/ncm_device.c
+++ b/src/class/net/ncm_device.c
@@ -28,7 +28,7 @@
 
 #include "tusb_option.h"
 
-#if ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_NET && CFG_TUD_NCM )
+#if ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_NCM )
 
 #include "device/usbd.h"
 #include "device/usbd_pvt.h"
@@ -131,18 +131,18 @@ typedef struct
 //--------------------------------------------------------------------+
 
 CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static const ntb_parameters_t ntb_parameters = {
-    .wLength = sizeof(ntb_parameters_t),
-    .bmNtbFormatsSupported = 0x01,
-    .dwNtbInMaxSize = CFG_TUD_NCM_IN_NTB_MAX_SIZE,
-    .wNdbInDivisor = 4,
-    .wNdbInPayloadRemainder = 0,
-    .wNdbInAlignment = CFG_TUD_NCM_ALIGNMENT,
-    .wReserved = 0,
-    .dwNtbOutMaxSize = CFG_TUD_NCM_OUT_NTB_MAX_SIZE,
-    .wNdbOutDivisor = 4,
+    .wLength                 = sizeof(ntb_parameters_t),
+    .bmNtbFormatsSupported   = 0x01,
+    .dwNtbInMaxSize          = CFG_TUD_NCM_IN_NTB_MAX_SIZE,
+    .wNdbInDivisor           = 4,
+    .wNdbInPayloadRemainder  = 0,
+    .wNdbInAlignment         = CFG_TUD_NCM_ALIGNMENT,
+    .wReserved               = 0,
+    .dwNtbOutMaxSize         = CFG_TUD_NCM_OUT_NTB_MAX_SIZE,
+    .wNdbOutDivisor          = 4,
     .wNdbOutPayloadRemainder = 0,
-    .wNdbOutAlignment = CFG_TUD_NCM_ALIGNMENT,
-    .wNtbOutMaxDatagrams = 0
+    .wNdbOutAlignment        = CFG_TUD_NCM_ALIGNMENT,
+    .wNtbOutMaxDatagrams     = 0
 };
 
 CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static transmit_ntb_t transmit_ntb[2];
diff --git a/src/class/net/net_device.h b/src/class/net/net_device.h
index e6cdc8b8b..ad369b680 100644
--- a/src/class/net/net_device.h
+++ b/src/class/net/net_device.h
@@ -30,10 +30,14 @@
 
 #include "class/cdc/cdc.h"
 
+#if CFG_TUD_ECM_RNDIS && CFG_TUD_NCM
+#error "Cannot enable both ECM_RNDIS and NCM network drivers"
+#endif
+
 /* declared here, NOT in usb_descriptors.c, so that the driver can intelligently ZLP as needed */
 #define CFG_TUD_NET_ENDPOINT_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
 
-/* Maximum Tranmission Unit (in bytes) of the network, including Ethernet header */
+/* Maximum Transmission Unit (in bytes) of the network, including Ethernet header */
 #ifndef CFG_TUD_NET_MTU
 #define CFG_TUD_NET_MTU           1514
 #endif
@@ -62,19 +66,6 @@
 // Application API
 //--------------------------------------------------------------------+
 
-// client must provide this: initialize any network state back to the beginning
-void tud_network_init_cb(void);
-
-// client must provide this: return false if the packet buffer was not accepted
-bool tud_network_recv_cb(const uint8_t *src, uint16_t size);
-
-// client must provide this: copy from network stack packet pointer to dst
-uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg);
-
-// client must provide this: 48-bit MAC address
-// TODO removed later since it is not part of tinyusb stack
-extern const uint8_t tud_network_mac_address[6];
-
 // indicate to network driver that client has finished with the packet provided to network_recv_cb()
 void tud_network_recv_renew(void);
 
@@ -84,6 +75,27 @@ bool tud_network_can_xmit(uint16_t size);
 // if network_can_xmit() returns true, network_xmit() can be called once
 void tud_network_xmit(void *ref, uint16_t arg);
 
+//--------------------------------------------------------------------+
+// Application Callbacks (WEAK is optional)
+//--------------------------------------------------------------------+
+
+// client must provide this: return false if the packet buffer was not accepted
+bool tud_network_recv_cb(const uint8_t *src, uint16_t size);
+
+// client must provide this: copy from network stack packet pointer to dst
+uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg);
+
+//------------- ECM/RNDIS -------------//
+
+// client must provide this: initialize any network state back to the beginning
+void tud_network_init_cb(void);
+
+// client must provide this: 48-bit MAC address
+// TODO removed later since it is not part of tinyusb stack
+extern const uint8_t tud_network_mac_address[6];
+
+//------------- NCM -------------//
+
 // callback to client providing optional indication of internal state of network driver
 void tud_network_link_state_cb(bool state);
 
diff --git a/src/device/usbd.c b/src/device/usbd.c
index a33d778db..ab1837895 100644
--- a/src/device/usbd.c
+++ b/src/device/usbd.c
@@ -203,7 +203,7 @@ static usbd_class_driver_t const _usbd_driver[] =
   },
   #endif
 
-  #if CFG_TUD_NET
+  #if CFG_TUD_ECM_RNDIS || CFG_TUD_NCM
   {
     DRIVER_NAME("NET")
     .init             = netd_init,
diff --git a/src/tusb.h b/src/tusb.h
index b52f8839a..2e1b7a43d 100644
--- a/src/tusb.h
+++ b/src/tusb.h
@@ -100,7 +100,7 @@
     #include "class/dfu/dfu_device.h"
   #endif
 
-  #if CFG_TUD_NET
+  #if CFG_TUD_ECM_RNDIS || CFG_TUD_NCM
     #include "class/net/net_device.h"
   #endif
 
diff --git a/src/tusb_option.h b/src/tusb_option.h
index 3cb672d80..369c35e2d 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -261,16 +261,16 @@
   #define CFG_TUD_DFU             0
 #endif
 
-#ifndef CFG_TUD_NET
-  #define CFG_TUD_NET             0
-#endif
-
 #ifndef CFG_TUD_BTH
   #define CFG_TUD_BTH             0
 #endif
 
+#ifndef CFG_TUD_ECM_RNDIS
+  #define CFG_TUD_ECM_RNDIS   0
+#endif
+
 #ifndef CFG_TUD_NCM
-  #define CFG_TUD_NCM             0
+  #define CFG_TUD_NCM         0
 #endif
 
 //--------------------------------------------------------------------

From 6fd62c8902915b987d600cd17e70383cbef78802 Mon Sep 17 00:00:00 2001
From: hathach <thach@tinyusb.org>
Date: Tue, 5 Oct 2021 16:06:54 +0700
Subject: [PATCH 09/11] update docs

---
 CONTRIBUTORS.rst | 23 +++++++++++++----------
 README.rst       |  2 +-
 2 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst
index 040f40166..cd1c19373 100644
--- a/CONTRIBUTORS.rst
+++ b/CONTRIBUTORS.rst
@@ -31,6 +31,12 @@ Notable contributors
 -  Improve ESP32s2 DCD
 
 
+`Jacob Berg Potter <https://github.com/j4cbo>`__
+------------------------------------------------
+
+-  Add new class driver for network CDC-NCM
+
+
 `Jan Dümpelmann <https://github.com/duempel>`__
 -----------------------------------------------
 
@@ -93,9 +99,9 @@ Notable contributors
 ------------------------------------------------
 
 -  Add new DCD port for Nuvoton NUC 120, 121, 125, 126, 505
--  Add new class driver for USBNET RNDIS, CDC-ECM
--  Add *net\_lwip\_webserver* example for demonstration of usbnet with
-   lwip
+-  Add new class driver for network RNDIS, CDC-ECM
+-  Enhance NCM network driver to compatible with RNDIS/ECM
+-  Add *net\_lwip\_webserver* example for demonstration of usbnet with lwip
 -  Board support for NuTiny NUC120, NUC121s, NUC125s, NUC126V, NUC505
 -  Improve multiple cdc interfaces API & add cdc\_dual\_ports example
 
@@ -118,10 +124,8 @@ Notable contributors
 ------------------------------------------------
 
 -  Add new class driver for USB Audio Class 2.0 (UAC2)
--  Rework tu\_fifo with unmasked pointer, add DMA support, and constant
-   address support
--  Add new DCD/USBD edpt\_xfer\_fifo() API for optimizing endpoint
-   transfer
+-  Rework tu\_fifo with unmasked pointer, add DMA support, and constant address support
+-  Add new DCD/USBD edpt\_xfer\_fifo() API for optimizing endpoint transfer
 -  Add and greatly improve Isochronous transfer
 -  Add new audio examples: audio\_test and audio\_4\_channel\_mic
 
@@ -130,8 +134,7 @@ Notable contributors
 ------------------------------------------------
 
 -  Add new DCD port for SAMD21 and SAMD51
--  Add new class driver for Musical Instrument Digital Interface
-   (MIDI)
+-  Add new class driver for Musical Instrument Digital Interface (MIDI)
 -  Improve USBD control transfer, MSC, CDC class driver
 -  Board support for Metro M0 & M4 express
 -  Write the excellent porting.md documentation
@@ -178,7 +181,7 @@ Notable contributors
 
 
 `Zixun Li <https://github.com/HiFiPhile>`__
------------------------------------------------
+-------------------------------------------
 
 -  Add new DCD port for Microchip SAMx7x
 -  Add IAR compiler support
diff --git a/README.rst b/README.rst
index 52738da32..5eecd63ac 100644
--- a/README.rst
+++ b/README.rst
@@ -65,7 +65,7 @@ Supports multiple device configurations by dynamically changing USB descriptors,
 -  Human Interface Device (HID): Generic (In & Out), Keyboard, Mouse, Gamepad etc ...
 -  Mass Storage Class (MSC): with multiple LUNs
 -  Musical Instrument Digital Interface (MIDI)
--  Network with RNDIS, CDC-ECM (work in progress)
+-  Network with RNDIS, Ethernet Control Model (ECM), Network Control Model (NCM)
 -  USB Test and Measurement Class (USBTMC)
 -  Vendor-specific class support with generic In & Out endpoints. Can be used with MS OS 2.0 compatible descriptor to load winUSB driver without INF file.
 -  `WebUSB <https://github.com/WICG/webusb>`__ with vendor-specific class

From a8eea9f6a540ef5c6314c400754364e0456002cd Mon Sep 17 00:00:00 2001
From: hathach <thach@tinyusb.org>
Date: Sat, 9 Oct 2021 11:28:55 +0700
Subject: [PATCH 10/11] add backward compatible with warnings for CFG_TUD_NET

---
 src/tusb_option.h | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/tusb_option.h b/src/tusb_option.h
index 057bc61c2..cda8c45ce 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -266,7 +266,12 @@
 #endif
 
 #ifndef CFG_TUD_ECM_RNDIS
-  #define CFG_TUD_ECM_RNDIS   0
+  #ifdef CFG_TUD_NET
+    #warning "CFG_TUD_NET is renamed to CFG_TUD_ECM_RNDIS"
+    #define CFG_TUD_ECM_RNDIS   CFG_TUD_NET
+  #else
+    #define CFG_TUD_ECM_RNDIS   0
+  #endif
 #endif
 
 #ifndef CFG_TUD_NCM

From a6723f556d7b302137ca42c7f811d3484f25414a Mon Sep 17 00:00:00 2001
From: hathach <thach@tinyusb.org>
Date: Mon, 11 Oct 2021 12:36:03 +0700
Subject: [PATCH 11/11] add ncm.h for magic number

mostly clean up magic number
---
 src/class/cdc/cdc.h        | 69 ++++++++++++++++++--------------------
 src/class/net/ncm.h        | 69 ++++++++++++++++++++++++++++++++++++++
 src/class/net/ncm_device.c | 56 ++++++++++++++++++-------------
 src/class/net/net_device.h |  2 ++
 src/device/usbd.h          |  8 ++---
 5 files changed, 141 insertions(+), 63 deletions(-)
 create mode 100644 src/class/net/ncm.h

diff --git a/src/class/cdc/cdc.h b/src/class/cdc/cdc.h
index 52de859ca..e345139ea 100644
--- a/src/class/cdc/cdc.h
+++ b/src/class/cdc/cdc.h
@@ -58,32 +58,32 @@ typedef enum
 /// Communication Interface Subclass Codes
 typedef enum
 {
-  CDC_COMM_SUBCLASS_DIRECT_LINE_CONTROL_MODEL = 0x01  , ///< Direct Line Control Model  [USBPSTN1.2]
-  CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL            , ///< Abstract Control Model  [USBPSTN1.2]
-  CDC_COMM_SUBCLASS_TELEPHONE_CONTROL_MODEL           , ///< Telephone Control Model  [USBPSTN1.2]
-  CDC_COMM_SUBCLASS_MULTICHANNEL_CONTROL_MODEL        , ///< Multi-Channel Control Model  [USBISDN1.2]
-  CDC_COMM_SUBCLASS_CAPI_CONTROL_MODEL                , ///< CAPI Control Model  [USBISDN1.2]
-  CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL            , ///< Ethernet Networking Control Model  [USBECM1.2]
-  CDC_COMM_SUBCLASS_ATM_NETWORKING_CONTROL_MODEL      , ///< ATM Networking Control Model  [USBATM1.2]
-  CDC_COMM_SUBCLASS_WIRELESS_HANDSET_CONTROL_MODEL    , ///< Wireless Handset Control Model  [USBWMC1.1]
-  CDC_COMM_SUBCLASS_DEVICE_MANAGEMENT                 , ///< Device Management  [USBWMC1.1]
-  CDC_COMM_SUBCLASS_MOBILE_DIRECT_LINE_MODEL          , ///< Mobile Direct Line Model  [USBWMC1.1]
-  CDC_COMM_SUBCLASS_OBEX                              , ///< OBEX  [USBWMC1.1]
-  CDC_COMM_SUBCLASS_ETHERNET_EMULATION_MODEL          , ///< Ethernet Emulation Model  [USBEEM1.0]
-  CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL               ///< Network Control Model  [USBNCM1.0]
+  CDC_COMM_SUBCLASS_DIRECT_LINE_CONTROL_MODEL      = 0x01 , ///< Direct Line Control Model         [USBPSTN1.2]
+  CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL         = 0x02 , ///< Abstract Control Model            [USBPSTN1.2]
+  CDC_COMM_SUBCLASS_TELEPHONE_CONTROL_MODEL        = 0x03 , ///< Telephone Control Model           [USBPSTN1.2]
+  CDC_COMM_SUBCLASS_MULTICHANNEL_CONTROL_MODEL     = 0x04 , ///< Multi-Channel Control Model       [USBISDN1.2]
+  CDC_COMM_SUBCLASS_CAPI_CONTROL_MODEL             = 0x05 , ///< CAPI Control Model                [USBISDN1.2]
+  CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL         = 0x06 , ///< Ethernet Networking Control Model [USBECM1.2]
+  CDC_COMM_SUBCLASS_ATM_NETWORKING_CONTROL_MODEL   = 0x07 , ///< ATM Networking Control Model      [USBATM1.2]
+  CDC_COMM_SUBCLASS_WIRELESS_HANDSET_CONTROL_MODEL = 0x08 , ///< Wireless Handset Control Model    [USBWMC1.1]
+  CDC_COMM_SUBCLASS_DEVICE_MANAGEMENT              = 0x09 , ///< Device Management                 [USBWMC1.1]
+  CDC_COMM_SUBCLASS_MOBILE_DIRECT_LINE_MODEL       = 0x0A , ///< Mobile Direct Line Model          [USBWMC1.1]
+  CDC_COMM_SUBCLASS_OBEX                           = 0x0B , ///< OBEX                              [USBWMC1.1]
+  CDC_COMM_SUBCLASS_ETHERNET_EMULATION_MODEL       = 0x0C , ///< Ethernet Emulation Model          [USBEEM1.0]
+  CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL          = 0x0D   ///< Network Control Model             [USBNCM1.0]
 } cdc_comm_sublcass_type_t;
 
 /// Communication Interface Protocol Codes
 typedef enum
 {
-  CDC_COMM_PROTOCOL_NONE                   = 0x00 , ///< No specific protocol
-  CDC_COMM_PROTOCOL_ATCOMMAND                     , ///< AT Commands: V.250 etc
-  CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101            , ///< AT Commands defined by PCCA-101
-  CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101_AND_ANNEXO , ///< AT Commands defined by PCCA-101 & Annex O
-  CDC_COMM_PROTOCOL_ATCOMMAND_GSM_707             , ///< AT Commands defined by GSM 07.07
-  CDC_COMM_PROTOCOL_ATCOMMAND_3GPP_27007          , ///< AT Commands defined by 3GPP 27.007
-  CDC_COMM_PROTOCOL_ATCOMMAND_CDMA                , ///< AT Commands defined by TIA for CDMA
-  CDC_COMM_PROTOCOL_ETHERNET_EMULATION_MODEL        ///< Ethernet Emulation Model
+  CDC_COMM_PROTOCOL_NONE                          = 0x00 , ///< No specific protocol
+  CDC_COMM_PROTOCOL_ATCOMMAND                     = 0x01 , ///< AT Commands: V.250 etc
+  CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101            = 0x02 , ///< AT Commands defined by PCCA-101
+  CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101_AND_ANNEXO = 0x03 , ///< AT Commands defined by PCCA-101 & Annex O
+  CDC_COMM_PROTOCOL_ATCOMMAND_GSM_707             = 0x04 , ///< AT Commands defined by GSM 07.07
+  CDC_COMM_PROTOCOL_ATCOMMAND_3GPP_27007          = 0x05 , ///< AT Commands defined by 3GPP 27.007
+  CDC_COMM_PROTOCOL_ATCOMMAND_CDMA                = 0x06 , ///< AT Commands defined by TIA for CDMA
+  CDC_COMM_PROTOCOL_ETHERNET_EMULATION_MODEL      = 0x07   ///< Ethernet Emulation Model
 } cdc_comm_protocol_type_t;
 
 //------------- SubType Descriptor in COMM Functional Descriptor -------------//
@@ -124,7 +124,8 @@ typedef enum
 //--------------------------------------------------------------------+
 
 // SUBCLASS code of Data Interface is not used and should/must be zero
-/// Data Interface Protocol Codes
+
+// Data Interface Protocol Codes
 typedef enum{
   CDC_DATA_PROTOCOL_ISDN_BRI                               = 0x30, ///< Physical interface protocol for ISDN BRI
   CDC_DATA_PROTOCOL_HDLC                                   = 0x31, ///< HDLC
@@ -149,7 +150,6 @@ typedef enum
 {
   CDC_REQUEST_SEND_ENCAPSULATED_COMMAND                    = 0x00, ///< is used to issue a command in the format of the supported control protocol of the Communications Class interface
   CDC_REQUEST_GET_ENCAPSULATED_RESPONSE                    = 0x01, ///< is used to request a response in the format of the supported control protocol of the Communications Class interface.
-
   CDC_REQUEST_SET_COMM_FEATURE                             = 0x02,
   CDC_REQUEST_GET_COMM_FEATURE                             = 0x03,
   CDC_REQUEST_CLEAR_COMM_FEATURE                           = 0x04,
@@ -196,21 +196,18 @@ typedef enum
 // Management Elemenent Notification (Notification Endpoint)
 //--------------------------------------------------------------------+
 
-/// Communication Interface Management Element Notification Codes
+/// 6.3 Notification Codes
 typedef enum
 {
-  NETWORK_CONNECTION               = 0x00, ///< This notification allows the device to notify the host about network connection status.
-  RESPONSE_AVAILABLE               = 0x01, ///< This notification allows the device to notify the hostthat a response is available. This response can be retrieved with a subsequent \ref CDC_REQUEST_GET_ENCAPSULATED_RESPONSE request.
-
-  AUX_JACK_HOOK_STATE              = 0x08,
-  RING_DETECT                      = 0x09,
-
-  SERIAL_STATE                     = 0x20,
-
-  CALL_STATE_CHANGE                = 0x28,
-  LINE_STATE_CHANGE                = 0x29,
-  CONNECTION_SPEED_CHANGE          = 0x2A, ///< This notification allows the device to inform the host-networking driver that a change in either the upstream or the downstream bit rate of the connection has occurred
-  MDLM_SEMANTIC_MODEL_NOTIFICATION = 0x40,
+  CDC_NOTIF_NETWORK_CONNECTION               = 0x00, ///< This notification allows the device to notify the host about network connection status.
+  CDC_NOTIF_RESPONSE_AVAILABLE               = 0x01, ///< This notification allows the device to notify the hostthat a response is available. This response can be retrieved with a subsequent \ref CDC_REQUEST_GET_ENCAPSULATED_RESPONSE request.
+  CDC_NOTIF_AUX_JACK_HOOK_STATE              = 0x08,
+  CDC_NOTIF_RING_DETECT                      = 0x09,
+  CDC_NOTIF_SERIAL_STATE                     = 0x20,
+  CDC_NOTIF_CALL_STATE_CHANGE                = 0x28,
+  CDC_NOTIF_LINE_STATE_CHANGE                = 0x29,
+  CDC_NOTIF_CONNECTION_SPEED_CHANGE          = 0x2A, ///< This notification allows the device to inform the host-networking driver that a change in either the upstream or the downstream bit rate of the connection has occurred
+  CDC_NOTIF_MDLM_SEMANTIC_MODEL_NOTIFICATION = 0x40,
 }cdc_notification_request_t;
 
 //--------------------------------------------------------------------+
diff --git a/src/class/net/ncm.h b/src/class/net/ncm.h
new file mode 100644
index 000000000..96ba11fbc
--- /dev/null
+++ b/src/class/net/ncm.h
@@ -0,0 +1,69 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021, Ha Thach (tinyusb.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+
+#ifndef _TUSB_NCM_H_
+#define _TUSB_NCM_H_
+
+#include "common/tusb_common.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+// Table 4.3 Data Class Interface Protocol Codes
+typedef enum
+{
+  NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK = 0x01
+} ncm_data_interface_protocol_code_t;
+
+
+// Table 6.2 Class-Specific Request Codes for Network Control Model subclass
+typedef enum
+{
+  NCM_SET_ETHERNET_MULTICAST_FILTERS               = 0x40,
+  NCM_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41,
+  NCM_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42,
+  NCM_SET_ETHERNET_PACKET_FILTER                   = 0x43,
+  NCM_GET_ETHERNET_STATISTIC                       = 0x44,
+  NCM_GET_NTB_PARAMETERS                           = 0x80,
+  NCM_GET_NET_ADDRESS                              = 0x81,
+  NCM_SET_NET_ADDRESS                              = 0x82,
+  NCM_GET_NTB_FORMAT                               = 0x83,
+  NCM_SET_NTB_FORMAT                               = 0x84,
+  NCM_GET_NTB_INPUT_SIZE                           = 0x85,
+  NCM_SET_NTB_INPUT_SIZE                           = 0x86,
+  NCM_GET_MAX_DATAGRAM_SIZE                        = 0x87,
+  NCM_SET_MAX_DATAGRAM_SIZE                        = 0x88,
+  NCM_GET_CRC_MODE                                 = 0x89,
+  NCM_SET_CRC_MODE                                 = 0x8A,
+} ncm_request_code_t;
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
diff --git a/src/class/net/ncm_device.c b/src/class/net/ncm_device.c
index 2f7d58f99..3e131a85c 100644
--- a/src/class/net/ncm_device.c
+++ b/src/class/net/ncm_device.c
@@ -38,7 +38,7 @@
 // MACRO CONSTANT TYPEDEF
 //--------------------------------------------------------------------+
 
-#define NTH16_SIGNATURE 0x484D434E
+#define NTH16_SIGNATURE      0x484D434E
 #define NDP16_SIGNATURE_NCM0 0x304D434E
 #define NDP16_SIGNATURE_NCM1 0x314D434E
 
@@ -114,13 +114,13 @@ typedef struct
   } report_state;
   bool report_pending;
 
-  uint8_t current_ntb; // Index in transmit_ntb[] that is currently being filled with datagrams
-  uint8_t datagram_count; // Number of datagrams in transmit_ntb[current_ntb]
-  uint16_t next_datagram_offset; // Offset in transmit_ntb[current_ntb].data to place the next datagram
-  uint16_t ntb_in_size; // Maximum size of transmitted (IN to host) NTBs; initially CFG_TUD_NCM_IN_NTB_MAX_SIZE
-  uint8_t max_datagrams_per_ntb; // Maximum number of datagrams per NTB; initially CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB
+  uint8_t  current_ntb;           // Index in transmit_ntb[] that is currently being filled with datagrams
+  uint8_t  datagram_count;        // Number of datagrams in transmit_ntb[current_ntb]
+  uint16_t next_datagram_offset;  // Offset in transmit_ntb[current_ntb].data to place the next datagram
+  uint16_t ntb_in_size;           // Maximum size of transmitted (IN to host) NTBs; initially CFG_TUD_NCM_IN_NTB_MAX_SIZE
+  uint8_t  max_datagrams_per_ntb; // Maximum number of datagrams per NTB; initially CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB
 
-  uint16_t nth_sequence; // Sequence number counter for transmitted NTBs
+  uint16_t nth_sequence;          // Sequence number counter for transmitted NTBs
 
   bool transferring;
 
@@ -197,25 +197,33 @@ static void ncm_start_tx(void) {
 }
 
 static struct ecm_notify_struct ncm_notify_connected =
-    {
-        .header = {
-            .bmRequestType = 0xA1,
-            .bRequest = 0 /* NETWORK_CONNECTION aka NetworkConnection */,
-            .wValue = 1 /* Connected */,
-            .wLength = 0,
+{
+    .header = {
+        .bmRequestType_bit = {
+          .recipient = TUSB_REQ_RCPT_INTERFACE,
+          .type      = TUSB_REQ_TYPE_CLASS,
+          .direction = TUSB_DIR_IN
         },
-    };
+        .bRequest      = CDC_NOTIF_NETWORK_CONNECTION,
+        .wValue        = 1 /* Connected */,
+        .wLength       = 0,
+    },
+};
 
 static struct ecm_notify_struct ncm_notify_speed_change =
-    {
-        .header = {
-            .bmRequestType = 0xA1,
-            .bRequest = 0x2A /* CONNECTION_SPEED_CHANGE aka ConnectionSpeedChange */,
-            .wLength = 8,
+{
+    .header = {
+        .bmRequestType_bit = {
+          .recipient = TUSB_REQ_RCPT_INTERFACE,
+          .type      = TUSB_REQ_TYPE_CLASS,
+          .direction = TUSB_DIR_IN
         },
-        .downlink = 10000000,
-        .uplink = 10000000,
-    };
+        .bRequest = CDC_NOTIF_CONNECTION_SPEED_CHANGE,
+        .wLength = 8,
+    },
+    .downlink = 10000000,
+    .uplink = 10000000,
+};
 
 void tud_network_recv_renew(void)
 {
@@ -381,7 +389,7 @@ bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t
     case TUSB_REQ_TYPE_CLASS:
       TU_VERIFY (ncm_interface.itf_num == request->wIndex);
 
-      if (0x80 /* GET_NTB_PARAMETERS */ == request->bRequest)
+      if (NCM_GET_NTB_PARAMETERS == request->bRequest)
       {
         tud_control_xfer(rhport, request, (void*)&ntb_parameters, sizeof(ntb_parameters));
       }
@@ -418,7 +426,9 @@ static void handle_incoming_datagram(uint32_t len)
   ncm_interface.num_datagrams = 0;
   ncm_interface.ndp = ndp;
   for (int i = 0; i < num_datagrams && ndp->datagram[i].wDatagramIndex && ndp->datagram[i].wDatagramLength; i++)
+  {
     ncm_interface.num_datagrams++;
+  }
   
   tud_network_recv_renew();
 }
diff --git a/src/class/net/net_device.h b/src/class/net/net_device.h
index ad369b680..6e294465b 100644
--- a/src/class/net/net_device.h
+++ b/src/class/net/net_device.h
@@ -34,6 +34,8 @@
 #error "Cannot enable both ECM_RNDIS and NCM network drivers"
 #endif
 
+#include "ncm.h"
+
 /* declared here, NOT in usb_descriptors.c, so that the driver can intelligently ZLP as needed */
 #define CFG_TUD_NET_ENDPOINT_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
 
diff --git a/src/device/usbd.h b/src/device/usbd.h
index 7575ef42c..8d02de1ff 100644
--- a/src/device/usbd.h
+++ b/src/device/usbd.h
@@ -827,16 +827,16 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb
   5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0110),\
   /* CDC-NCM Union */\
   5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\
-  /* CDC-ECM Functional Descriptor */\
+  /* CDC-NCM Functional Descriptor */\
   13, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ETHERNET_NETWORKING, _mac_stridx, 0, 0, 0, 0, U16_TO_U8S_LE(_maxsegmentsize), U16_TO_U8S_LE(0), 0, \
-  /* CDC-ECM Functional Descriptor */\
+  /* CDC-NCM Functional Descriptor */\
   6, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_NCM, U16_TO_U8S_LE(0x0100), 0, \
   /* Endpoint Notification */\
   7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 50,\
   /* CDC Data Interface (default inactive) */\
-  9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, 1, 0,\
+  9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK, 0,\
   /* CDC Data Interface (alternative active) */\
-  9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 1, 2, TUSB_CLASS_CDC_DATA, 0, 1, 0,\
+  9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 1, 2, TUSB_CLASS_CDC_DATA, 0, NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK, 0,\
   /* Endpoint In */\
   7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\
   /* Endpoint Out */\