[PATCH 6/8] usb: eth: Add CDC NCM driver
Jonas Karlman
jonas at kwiboo.se
Sat Jul 11 18:08:04 CEST 2026
Add support for CDC NCM (Communications Device Class, Network Control
Model) devices.
References:
- [USBNCM11] USB Subclass Specification for Network Control Model 1.1.
- [USBECM12] USB Subclass Specification for Ethernet Control Model 1.2.
- [USBCDC12] USB Class Definitions for Communications Devices 1.2.
- [USB20] USB Specification 2.0.
Signed-off-by: Jonas Karlman <jonas at kwiboo.se>
---
Please note that this driver currently does not fully implement
alignment using wNdpOutDivisor and wNdpOutPayloadRemainder parameters.
Something that did not seem to be needed for the CDC NCM devices that
I have access to, so not something I could verify being correctly
implemented.
---
doc/README.usb | 1 +
drivers/usb/eth/Kconfig | 9 +
drivers/usb/eth/Makefile | 1 +
drivers/usb/eth/cdc_common.c | 112 ++++++++
drivers/usb/eth/cdc_common.h | 9 +
drivers/usb/eth/cdc_ncm.c | 480 +++++++++++++++++++++++++++++++++++
6 files changed, 612 insertions(+)
create mode 100644 drivers/usb/eth/cdc_ncm.c
diff --git a/doc/README.usb b/doc/README.usb
index 65bafe7030f7..0949dc9a7303 100644
--- a/doc/README.usb
+++ b/doc/README.usb
@@ -134,6 +134,7 @@ and one or more of the following for individual adapter hardware:
CONFIG_USB_ETHER_ASIX=y
CONFIG_USB_ETHER_ASIX88179=y
CONFIG_USB_ETHER_CDC_ECM=y
+CONFIG_USB_ETHER_CDC_NCM=y
CONFIG_USB_ETHER_LAN75XX=y
CONFIG_USB_ETHER_LAN78XX=y
CONFIG_USB_ETHER_MCS7830=y
diff --git a/drivers/usb/eth/Kconfig b/drivers/usb/eth/Kconfig
index 0b1aae69ccac..83835b00b02d 100644
--- a/drivers/usb/eth/Kconfig
+++ b/drivers/usb/eth/Kconfig
@@ -29,6 +29,15 @@ config USB_ETHER_CDC_ECM
Subclass Specifications for Ethernet Control Model Devices is
available from <http://www.usb.org/>.
+config USB_ETHER_CDC_NCM
+ bool "CDC NCM support"
+ depends on USB_HOST_ETHER
+ help
+ Say Y here if you would like to support CDC NCM (Communications Device
+ Class, Network Control Model) devices. The USB Communications Class
+ Subclass Specifications for Network Control Model Devices is available
+ from <http://www.usb.org/>.
+
config USB_ETHER_LAN75XX
bool "Microchip LAN75XX support"
depends on USB_HOST_ETHER
diff --git a/drivers/usb/eth/Makefile b/drivers/usb/eth/Makefile
index 3ddace19578a..752e9ea42ad9 100644
--- a/drivers/usb/eth/Makefile
+++ b/drivers/usb/eth/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_USB_HOST_ETHER) += usb_ether.o
obj-$(CONFIG_USB_ETHER_ASIX) += asix.o
obj-$(CONFIG_USB_ETHER_ASIX88179) += asix88179.o
obj-$(CONFIG_USB_ETHER_CDC_ECM) += cdc_common.o cdc_ecm.o
+obj-$(CONFIG_USB_ETHER_CDC_NCM) += cdc_common.o cdc_ncm.o
obj-$(CONFIG_USB_ETHER_MCS7830) += mcs7830.o
obj-$(CONFIG_USB_ETHER_SMSC95XX) += smsc95xx.o
obj-$(CONFIG_USB_ETHER_LAN75XX) += lan7x.o lan75xx.o
diff --git a/drivers/usb/eth/cdc_common.c b/drivers/usb/eth/cdc_common.c
index 2342a56310f4..f8c2cc7ebbbf 100644
--- a/drivers/usb/eth/cdc_common.c
+++ b/drivers/usb/eth/cdc_common.c
@@ -31,6 +31,35 @@ int cdc_bulk_xfer(struct usb_device *udev, u32 pipe, void *data, int length,
return ret;
}
+static int cdc_ctrl_get(struct usb_device *udev, u8 request, u8 requesttype,
+ u16 value, u16 index, void *data, u16 size)
+{
+ int ret;
+ ALLOC_CACHE_ALIGN_BUFFER(u8, tmpbuf, size);
+
+ ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ request, requesttype, value, index, tmpbuf, size,
+ USB_CTRL_GET_TIMEOUT);
+ if (ret > 0 && ret <= size)
+ memcpy(data, tmpbuf, ret);
+
+ return ret;
+}
+
+static int cdc_ctrl_set(struct usb_device *udev, u8 request, u8 requesttype,
+ u16 value, u16 index, const void *data, u16 size)
+{
+ int ret;
+ ALLOC_CACHE_ALIGN_BUFFER(u8, tmpbuf, size);
+
+ memcpy(tmpbuf, data, size);
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ request, requesttype, value, index, tmpbuf, size,
+ USB_CTRL_SET_TIMEOUT);
+
+ return ret;
+}
+
int cdc_get_mac_address(struct usb_device *udev, int index, u8 *data)
{
u8 tmpbuf[13];
@@ -45,6 +74,60 @@ int cdc_get_mac_address(struct usb_device *udev, int index, u8 *data)
return -EINVAL;
}
+int cdc_get_net_address(struct usb_device *udev, int index, u8 *data)
+{
+ int ret;
+
+ /* 7.2.2 GetNetAddress [USBNCM11] */
+ ret = cdc_ctrl_get(udev, USB_CDC_GET_NET_ADDRESS,
+ USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, index, data, ETH_ALEN);
+ log_debug("GetNetAddress(): ret=%d, status=%lx\n", ret, udev->status);
+ if (ret == ETH_ALEN)
+ return 0;
+
+ memset(data, 0, ETH_ALEN);
+ return -EINVAL;
+}
+
+int cdc_get_ntb_parameters(struct usb_device *udev, u16 index,
+ struct usb_cdc_ncm_ntb_parameters *params)
+{
+ int ret;
+
+ /* 7.2.1 GetNtbParameters [USBNCM11] */
+ ret = cdc_ctrl_get(udev, USB_CDC_GET_NTB_PARAMETERS,
+ USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, index, params, sizeof(*params));
+ log_debug("GetNtbParameters(): ret=%d, status=%lx\n", ret, udev->status);
+ if (ret != sizeof(*params))
+ return ret < 0 ? ret : -EINVAL;
+
+ le16_to_cpus(¶ms->wLength);
+ le16_to_cpus(¶ms->bmNtbFormatsSupported);
+ le32_to_cpus(¶ms->dwNtbInMaxSize);
+ le16_to_cpus(¶ms->wNdpInDivisor);
+ le16_to_cpus(¶ms->wNdpInPayloadRemainder);
+ le16_to_cpus(¶ms->wNdpInAlignment);
+ le32_to_cpus(¶ms->dwNtbOutMaxSize);
+ le16_to_cpus(¶ms->wNdpOutDivisor);
+ le16_to_cpus(¶ms->wNdpOutPayloadRemainder);
+ le16_to_cpus(¶ms->wNdpOutAlignment);
+ le16_to_cpus(¶ms->wNtbOutMaxDatagrams);
+
+ if (params->wLength != USB_CDC_NCM_NTB_MAX_LENGTH ||
+ !(params->bmNtbFormatsSupported & USB_CDC_NCM_NTB16_SUPPORTED) ||
+ params->wNdpInAlignment < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE ||
+ params->wNdpOutAlignment < USB_CDC_NCM_NDP_ALIGN_MIN_SIZE) {
+ log_warning("Invalid NTB parameters: [%u,%x,%u,%u]\n",
+ params->wLength, params->bmNtbFormatsSupported,
+ params->wNdpInAlignment, params->wNdpOutAlignment);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
int cdc_set_ethernet_packet_filter(struct usb_device *udev, u16 index, u16 value)
{
int ret;
@@ -74,6 +157,35 @@ int cdc_set_interface(struct usb_device *udev, u16 index, u16 value)
return ret;
}
+int cdc_set_net_address(struct usb_device *udev, u16 index, const u8 *data)
+{
+ int ret;
+
+ /* 7.2.3 SetNetAddress [USBNCM11] */
+ ret = cdc_ctrl_set(udev, USB_CDC_SET_NET_ADDRESS,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, index, data, ETH_ALEN);
+ log_debug("SetNetAddress(%pM): ret=%d, status=%lx\n",
+ data, ret, udev->status);
+
+ return ret == ETH_ALEN ? 0 : ret;
+}
+
+int cdc_set_ntb_input_size(struct usb_device *udev, u16 index, u32 value)
+{
+ u32 dwNtbInMaxSize = cpu_to_le32(value);
+ int ret;
+
+ /* 7.2.6 SetNtbInputSize [USBNCM11] */
+ ret = cdc_ctrl_set(udev, USB_CDC_SET_NTB_INPUT_SIZE,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, index, &dwNtbInMaxSize, sizeof(dwNtbInMaxSize));
+ log_debug("SetNtbInputSize(%u): ret=%d, status=%lx\n",
+ value, ret, udev->status);
+
+ return ret == sizeof(dwNtbInMaxSize) ? 0 : ret;
+}
+
int cdc_wait_on_connection(struct usb_device *udev, u32 pipe, int interval)
{
struct usb_cdc_notification *notification;
diff --git a/drivers/usb/eth/cdc_common.h b/drivers/usb/eth/cdc_common.h
index 200860f63ffd..64caa7024e67 100644
--- a/drivers/usb/eth/cdc_common.h
+++ b/drivers/usb/eth/cdc_common.h
@@ -14,10 +14,19 @@ int cdc_bulk_xfer(struct usb_device *udev, u32 pipe, void *data, int length,
int cdc_get_mac_address(struct usb_device *udev, int index, u8 *data);
+int cdc_get_net_address(struct usb_device *udev, int index, u8 *data);
+
+int cdc_get_ntb_parameters(struct usb_device *udev, u16 index,
+ struct usb_cdc_ncm_ntb_parameters *params);
+
int cdc_set_ethernet_packet_filter(struct usb_device *udev, u16 index, u16 value);
int cdc_set_interface(struct usb_device *udev, u16 index, u16 value);
+int cdc_set_net_address(struct usb_device *udev, u16 index, const u8 *data);
+
+int cdc_set_ntb_input_size(struct usb_device *udev, u16 index, u32 value);
+
int cdc_wait_on_connection(struct usb_device *udev, u32 pipe, int interval);
struct usb_config_descriptor *cdc_get_config_descriptor(struct usb_device *udev,
diff --git a/drivers/usb/eth/cdc_ncm.c b/drivers/usb/eth/cdc_ncm.c
new file mode 100644
index 000000000000..2ce3ac37b2f0
--- /dev/null
+++ b/drivers/usb/eth/cdc_ncm.c
@@ -0,0 +1,480 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Copyright Contributors to the U-Boot project.
+
+#define LOG_CATEGORY UCLASS_ETH
+
+#include <dm.h>
+#include <memalign.h>
+#include <net-common.h>
+#include <usb.h>
+#include <linux/usb/cdc.h>
+#include "cdc_common.h"
+
+struct cdc_ncm_priv {
+ struct usb_cdc_ether_desc ether_desc;
+ struct usb_cdc_ncm_desc ncm_desc;
+ struct usb_cdc_ncm_ntb_parameters ncm_ntb_parameters;
+ bool normal_operation;
+ u16 control;
+ u16 data;
+ u32 intpipe;
+ int intinterval;
+ u32 bulkinpipe;
+ u32 bulkoutpipe;
+ u32 rx_size;
+ u8 *rx_buffer;
+ struct usb_cdc_ncm_dpe16 *rx_dpe;
+ u16 tx_ndp_alignment;
+ u16 tx_sequence;
+};
+
+static void cdc_ncm_stop(struct udevice *dev)
+{
+ struct usb_device *udev = dev_get_parent_priv(dev);
+ struct cdc_ncm_priv *priv = dev_get_priv(dev);
+
+ if (priv->ncm_desc.bmNetworkCapabilities & USB_CDC_NCM_NCAP_ETH_FILTER) {
+ /* 6.2.4 SetEthernetPacketFilter [USBECM12] */
+ cdc_set_ethernet_packet_filter(udev, priv->control, 0);
+ }
+}
+
+static int cdc_ncm_start(struct udevice *dev)
+{
+ struct usb_device *udev = dev_get_parent_priv(dev);
+ struct cdc_ncm_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (priv->ncm_desc.bmNetworkCapabilities & USB_CDC_NCM_NCAP_ETH_FILTER) {
+ /* 6.2.4 SetEthernetPacketFilter [USBECM12] */
+ cdc_set_ethernet_packet_filter(udev, priv->control,
+ USB_CDC_PACKET_TYPE_DIRECTED |
+ USB_CDC_PACKET_TYPE_BROADCAST |
+ USB_CDC_PACKET_TYPE_MULTICAST);
+ }
+
+ if (!priv->normal_operation) {
+ /* Reset transmit sequence */
+ priv->tx_sequence = 0;
+
+ /* 7.2.6 SetNtbInputSize [USBNCM11] */
+ cdc_set_ntb_input_size(udev, priv->control, priv->rx_size);
+
+ /* Set non-initial state (AltSet=1) to enable normal operation */
+ ret = cdc_set_interface(udev, priv->data, 1);
+ if (ret < 0)
+ return ret;
+
+ priv->normal_operation = true;
+ }
+
+ ret = cdc_wait_on_connection(udev, priv->intpipe, priv->intinterval);
+ if (ret < 0)
+ cdc_ncm_stop(dev);
+
+ return ret;
+}
+
+static int cdc_ncm_send(struct udevice *dev, void *packet, int length)
+{
+ struct usb_device *udev = dev_get_parent_priv(dev);
+ struct cdc_ncm_priv *priv = dev_get_priv(dev);
+ struct usb_cdc_ncm_nth16 *nth;
+ struct usb_cdc_ncm_ndp16 *ndp;
+ struct usb_cdc_ncm_dpe16 *dpe;
+ int actual_length, offset;
+ ALLOC_CACHE_ALIGN_BUFFER(u8, tmpbuf, SZ_2K);
+
+ if (length < ETH_HLEN || length > SZ_2K)
+ return -EINVAL;
+
+ /* 3.2.1 NTH for 16-bit NTB (NTH16) [USBNCM11] */
+ nth = (struct usb_cdc_ncm_nth16 *)tmpbuf;
+ nth->dwSignature = cpu_to_le32(USB_CDC_NCM_NTH16_SIGN);
+ nth->wHeaderLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_nth16));
+ nth->wSequence = cpu_to_le16(priv->tx_sequence++);
+ offset = ALIGN(le16_to_cpu(nth->wHeaderLength), priv->tx_ndp_alignment);
+ nth->wNdpIndex = cpu_to_le16(offset);
+
+ /* 3.3.1 NDP for 16-bit NTBs (NDP16) [USBNCM11] */
+ ndp = (struct usb_cdc_ncm_ndp16 *)(tmpbuf + offset);
+ ndp->dwSignature = cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN);
+ ndp->wLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_ndp16) +
+ sizeof(struct usb_cdc_ncm_dpe16) * 2);
+ ndp->wNextNdpIndex = cpu_to_le16(0);
+
+ dpe = (struct usb_cdc_ncm_dpe16 *)ndp->dpe16;
+ // FIXME: align using wNdpOutDivisor and wNdpOutPayloadRemainder ?
+ offset += ALIGN(le16_to_cpu(ndp->wLength), 16);
+ dpe[0].wDatagramIndex = cpu_to_le16(offset);
+ dpe[0].wDatagramLength = cpu_to_le16(length);
+
+ /* 3.7 Null NCM Datagram Pointer Entries [USBNCM11] */
+ dpe[1].wDatagramIndex = cpu_to_le16(0);
+ dpe[1].wDatagramLength = cpu_to_le16(0);
+
+ nth->wBlockLength = cpu_to_le16(offset + length);
+ if (le16_to_cpu(nth->wBlockLength) > SZ_2K)
+ return -EINVAL;
+
+ memcpy(tmpbuf + offset, packet, length);
+
+ return cdc_bulk_xfer(udev, priv->bulkoutpipe,
+ tmpbuf, le16_to_cpu(nth->wBlockLength),
+ &actual_length, USB_BULK_SEND_TIMEOUT);
+}
+
+static struct usb_cdc_ncm_dpe16 *cdc_ncm_validate_nth(void *buffer, int size)
+{
+ struct usb_cdc_ncm_nth16 *nth = buffer;
+ struct usb_cdc_ncm_ndp16 *ndp;
+ struct usb_cdc_ncm_dpe16 *dpe;
+
+ /* 3.2.1 NTH for 16-bit NTB (NTH16) [USBNCM11] */
+ if (le32_to_cpu(nth->dwSignature) != USB_CDC_NCM_NTH16_SIGN ||
+ le16_to_cpu(nth->wHeaderLength) != sizeof(struct usb_cdc_ncm_nth16) ||
+ le16_to_cpu(nth->wBlockLength) > size ||
+ le16_to_cpu(nth->wNdpIndex) % 4 ||
+ le16_to_cpu(nth->wNdpIndex) < sizeof(struct usb_cdc_ncm_nth16) ||
+ le16_to_cpu(nth->wBlockLength) < (le16_to_cpu(nth->wNdpIndex) +
+ sizeof(struct usb_cdc_ncm_ndp16))) {
+ log_warning("Invalid NTH16: [%08x,%u,%u,%u,%u]\n",
+ le32_to_cpu(nth->dwSignature),
+ le16_to_cpu(nth->wHeaderLength),
+ le16_to_cpu(nth->wSequence),
+ le16_to_cpu(nth->wBlockLength),
+ le16_to_cpu(nth->wNdpIndex));
+ return NULL;
+ }
+
+ /* 3.3.1 NDP for 16-bit NTBs (NDP16) [USBNCM11] */
+ buffer += le16_to_cpu(nth->wNdpIndex);
+ ndp = (struct usb_cdc_ncm_ndp16 *)buffer;
+ if (le32_to_cpu(ndp->dwSignature) != USB_CDC_NCM_NDP16_NOCRC_SIGN ||
+ le16_to_cpu(ndp->wLength) % 4 ||
+ le16_to_cpu(ndp->wLength) < (sizeof(struct usb_cdc_ncm_ndp16) +
+ sizeof(struct usb_cdc_ncm_dpe16) * 2) ||
+ le16_to_cpu(ndp->wNextNdpIndex) != 0 ||
+ le16_to_cpu(nth->wBlockLength) < (le16_to_cpu(nth->wNdpIndex) +
+ le16_to_cpu(ndp->wLength))) {
+ log_warning("Invalid NDP16: [%08x,%u,%u]\n",
+ le32_to_cpu(ndp->dwSignature),
+ le16_to_cpu(ndp->wLength),
+ le16_to_cpu(ndp->wNextNdpIndex));
+ return NULL;
+ }
+
+ /* Last DPE is always zero */
+ dpe = (struct usb_cdc_ncm_dpe16 *)(buffer + le16_to_cpu(ndp->wLength) -
+ sizeof(struct usb_cdc_ncm_dpe16));
+ if (le16_to_cpu(dpe->wDatagramIndex) ||
+ le16_to_cpu(dpe->wDatagramLength))
+ return NULL;
+
+ /* First DPE should not be a Null entry */
+ dpe = ndp->dpe16;
+ if (!le16_to_cpu(dpe->wDatagramIndex) ||
+ !le16_to_cpu(dpe->wDatagramLength))
+ return NULL;
+
+ do {
+ /* 3.7 Null NCM Datagram Pointer Entries [USBNCM11] */
+ if (!le16_to_cpu(dpe->wDatagramIndex) ||
+ !le16_to_cpu(dpe->wDatagramLength))
+ break;
+
+ if (le16_to_cpu(dpe->wDatagramIndex) < sizeof(struct usb_cdc_ncm_nth16) ||
+ le16_to_cpu(dpe->wDatagramLength) < ETH_HLEN ||
+ le16_to_cpu(dpe->wDatagramIndex) + le16_to_cpu(dpe->wDatagramLength) >
+ le16_to_cpu(nth->wBlockLength)) {
+ log_warning("Invalid DPE16: [%u,%u]\n",
+ le16_to_cpu(dpe->wDatagramIndex),
+ le16_to_cpu(dpe->wDatagramLength));
+ return NULL;
+ }
+ } while (++dpe);
+
+ return ndp->dpe16;
+}
+
+static int cdc_ncm_recv(struct udevice *dev, int flags, uchar **packetp)
+{
+ struct usb_device *udev = dev_get_parent_priv(dev);
+ struct cdc_ncm_priv *priv = dev_get_priv(dev);
+ int actual_length, ret;
+
+ if (!priv->rx_dpe && (flags & ETH_RECV_CHECK_DEVICE)) {
+ ret = cdc_bulk_xfer(udev, priv->bulkinpipe,
+ priv->rx_buffer, priv->rx_size,
+ &actual_length, USB_BULK_RECV_TIMEOUT);
+ if (!ret && actual_length)
+ priv->rx_dpe = cdc_ncm_validate_nth(priv->rx_buffer,
+ actual_length);
+ }
+
+ if (priv->rx_dpe) {
+ *packetp = priv->rx_buffer + le16_to_cpu(priv->rx_dpe->wDatagramIndex);
+ return le16_to_cpu(priv->rx_dpe->wDatagramLength);
+ }
+
+ *packetp = NULL;
+ return -EAGAIN;
+}
+
+static int cdc_ncm_free_pkt(struct udevice *dev, uchar *packet, int length)
+{
+ struct cdc_ncm_priv *priv = dev_get_priv(dev);
+
+ if (priv->rx_dpe) {
+ /* Advance to the next DPE */
+ priv->rx_dpe++;
+
+ /* Stop at first Null entry */
+ if (!le16_to_cpu(priv->rx_dpe->wDatagramIndex) ||
+ !le16_to_cpu(priv->rx_dpe->wDatagramLength))
+ priv->rx_dpe = NULL;
+ }
+
+ return 0;
+}
+
+static int cdc_ncm_write_hwaddr(struct udevice *dev)
+{
+ struct usb_device *udev = dev_get_parent_priv(dev);
+ struct cdc_ncm_priv *priv = dev_get_priv(dev);
+ struct eth_pdata *pdata = dev_get_plat(dev);
+
+ if (!(priv->ncm_desc.bmNetworkCapabilities & USB_CDC_NCM_NCAP_NET_ADDRESS))
+ return -ENOSYS;
+
+ /* 7.2.3 SetNetAddress [USBNCM11] */
+ return cdc_set_net_address(udev, priv->control, pdata->enetaddr);
+}
+
+static int cdc_ncm_read_rom_hwaddr(struct udevice *dev)
+{
+ struct usb_device *udev = dev_get_parent_priv(dev);
+ struct cdc_ncm_priv *priv = dev_get_priv(dev);
+ struct eth_pdata *pdata = dev_get_plat(dev);
+
+ if (!(priv->ncm_desc.bmNetworkCapabilities & USB_CDC_NCM_NCAP_NET_ADDRESS))
+ return cdc_get_mac_address(udev, priv->ether_desc.iMACAddress,
+ pdata->enetaddr);
+
+ /* 7.2.2 GetNetAddress [USBNCM11] */
+ return cdc_get_net_address(udev, priv->control, pdata->enetaddr);
+}
+
+static int cdc_ncm_parse_config_descriptor(struct udevice *dev,
+ struct usb_config_descriptor *config)
+{
+ struct usb_device *udev = dev_get_parent_priv(dev);
+ struct cdc_ncm_priv *priv = dev_get_priv(dev);
+ struct usb_interface_descriptor *iface_desc;
+ struct usb_endpoint_descriptor *ep_desc;
+ struct usb_descriptor_header *head;
+ bool comm_if_found = false;
+ bool data_if_found = false;
+ u8 *buffer = (u8 *)config;
+ int ep, index;
+
+ priv->control = U16_MAX;
+ priv->data = U16_MAX;
+ priv->intpipe = 0;
+ priv->intinterval = 0;
+ priv->bulkinpipe = 0;
+ priv->bulkoutpipe = 0;
+ memset(&priv->ether_desc, 0, sizeof(priv->ether_desc));
+ memset(&priv->ncm_desc, 0, sizeof(priv->ncm_desc));
+
+ index = config->bLength;
+ head = (struct usb_descriptor_header *)&buffer[index];
+ while (index + 1 < config->wTotalLength && head->bLength) {
+ if (index + head->bLength > config->wTotalLength)
+ return -EINVAL;
+ switch (head->bDescriptorType) {
+ /* 9.6.5 Interface [USB20] */
+ case USB_DT_INTERFACE:
+ if (head->bLength != USB_DT_INTERFACE_SIZE)
+ return -EINVAL;
+ iface_desc = (struct usb_interface_descriptor *)head;
+ comm_if_found = (iface_desc->bInterfaceClass == USB_CLASS_COMM &&
+ iface_desc->bInterfaceSubClass == USB_CDC_SUBCLASS_NCM &&
+ iface_desc->bInterfaceProtocol == USB_CDC_PROTO_NONE);
+ data_if_found = (iface_desc->bInterfaceClass == USB_CLASS_CDC_DATA &&
+ iface_desc->bInterfaceSubClass == 0 &&
+ iface_desc->bInterfaceProtocol == USB_CDC_NCM_PROTO_NTB &&
+ iface_desc->bAlternateSetting == 1 &&
+ iface_desc->bNumEndpoints == 2);
+ if (comm_if_found)
+ priv->control = iface_desc->bInterfaceNumber;
+ else if (data_if_found)
+ priv->data = iface_desc->bInterfaceNumber;
+ break;
+ /* 9.6.6 Endpoint [USB20] */
+ case USB_DT_ENDPOINT:
+ if (head->bLength != USB_DT_ENDPOINT_SIZE &&
+ head->bLength != USB_DT_ENDPOINT_AUDIO_SIZE)
+ return -EINVAL;
+ ep_desc = (struct usb_endpoint_descriptor *)head;
+ if (comm_if_found && usb_endpoint_is_int_in(ep_desc)) {
+ ep = usb_endpoint_num(ep_desc);
+ priv->intpipe = usb_rcvintpipe(udev, ep);
+ priv->intinterval = ep_desc->bInterval;
+ } else if (data_if_found && usb_endpoint_is_bulk_in(ep_desc)) {
+ ep = usb_endpoint_num(ep_desc);
+ priv->bulkinpipe = usb_rcvbulkpipe(udev, ep);
+ } else if (data_if_found && usb_endpoint_is_bulk_out(ep_desc)) {
+ ep = usb_endpoint_num(ep_desc);
+ priv->bulkoutpipe = usb_sndbulkpipe(udev, ep);
+ }
+ break;
+ /* 5.2.3 Functional Descriptors [USBCDC12] */
+ case USB_DT_CS_INTERFACE:
+ if (head->bLength < 4)
+ return -EINVAL;
+ if (!comm_if_found)
+ break;
+ switch (buffer[index + 2]) { /* bDescriptorSubType */
+ /* 5.4 Ethernet Networking Functional Descriptor [USBECM12] */
+ case USB_CDC_ETHERNET_TYPE:
+ memcpy(&priv->ether_desc, head, sizeof(priv->ether_desc));
+ le32_to_cpus(&priv->ether_desc.bmEthernetStatistics);
+ le16_to_cpus(&priv->ether_desc.wMaxSegmentSize);
+ le16_to_cpus(&priv->ether_desc.wNumberMCFilters);
+ break;
+ /* 6.2.1 NCM Functional Descriptor [USBNCM11] */
+ case USB_CDC_NCM_TYPE:
+ memcpy(&priv->ncm_desc, head, sizeof(priv->ncm_desc));
+ le16_to_cpus(&priv->ncm_desc.bcdNcmVersion);
+ break;
+ }
+ break;
+ }
+ index += head->bLength;
+ head = (struct usb_descriptor_header *)&buffer[index];
+ }
+
+ if (priv->control == U16_MAX || priv->data == U16_MAX ||
+ !priv->intpipe || !priv->bulkinpipe || !priv->bulkoutpipe)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int cdc_ncm_probe(struct udevice *dev)
+{
+ struct usb_device *udev = dev_get_parent_priv(dev);
+ struct cdc_ncm_priv *priv = dev_get_priv(dev);
+ bool configuration_found = false;
+ int cfgno, ret;
+
+ /* Find the CDC NCM configuration */
+ for (cfgno = 0; cfgno < udev->descriptor.bNumConfigurations; cfgno++) {
+ struct usb_config_descriptor *config;
+
+ /* 9.4.3 Get Descriptor [USB20] */
+ config = cdc_get_config_descriptor(udev, cfgno);
+ if (!config)
+ return -ENODEV;
+
+ ret = cdc_ncm_parse_config_descriptor(dev, config);
+ if (!ret) {
+ configuration_found = true;
+ free(config);
+ break;
+ }
+
+ free(config);
+ }
+ if (!configuration_found)
+ return -ENODEV;
+
+ /* 9.4.7 Set Configuration [USB20] */
+ ret = usb_select_configuration_no(udev, cfgno);
+ if (ret < 0)
+ return -ENODEV;
+
+ /* 7.2.1 GetNtbParameters [USBNCM11] */
+ ret = cdc_get_ntb_parameters(udev, priv->control,
+ &priv->ncm_ntb_parameters);
+ if (ret < 0)
+ return -ENODEV;
+
+ priv->tx_ndp_alignment = max_t(u32, USB_CDC_NCM_NDP_ALIGN_MIN_SIZE,
+ priv->ncm_ntb_parameters.wNdpOutAlignment);
+ if (priv->tx_ndp_alignment > SZ_512)
+ return -EINVAL;
+
+ /*
+ * The NTB-16 format used by this driver can support NTB sizes up to 64
+ * KiB and the host must support NTBs of at least 2 KiB. Support up to
+ * 16 KiB NTBs as a reasonable default and limit it to the maximum
+ * supported by the device and the host controller.
+ */
+
+ priv->rx_size = SZ_16K;
+ if (priv->ncm_ntb_parameters.dwNtbInMaxSize)
+ priv->rx_size = min(priv->rx_size,
+ priv->ncm_ntb_parameters.dwNtbInMaxSize);
+
+ if (CONFIG_IS_ENABLED(DM_USB)) {
+ size_t size = 0;
+
+ ret = usb_get_max_xfer_size(udev, &size);
+ if (ret >= 0 && size < priv->rx_size)
+ priv->rx_size = size;
+ }
+
+ if (priv->rx_size < USB_CDC_NCM_NTB_MIN_IN_SIZE)
+ priv->rx_size = USB_CDC_NCM_NTB_MIN_IN_SIZE;
+
+ priv->rx_buffer = malloc_cache_aligned(priv->rx_size);
+ if (!priv->rx_buffer)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int cdc_ncm_remove(struct udevice *dev)
+{
+ struct usb_device *udev = dev_get_parent_priv(dev);
+ struct cdc_ncm_priv *priv = dev_get_priv(dev);
+
+ if (priv->normal_operation) {
+ /* Set initial state (AltSet=0) to disable networking traffic */
+ cdc_set_interface(udev, priv->data, 0);
+ }
+
+ free(priv->rx_buffer);
+
+ return 0;
+}
+
+static const struct eth_ops cdc_ncm_eth_ops = {
+ .start = cdc_ncm_start,
+ .send = cdc_ncm_send,
+ .recv = cdc_ncm_recv,
+ .free_pkt = cdc_ncm_free_pkt,
+ .stop = cdc_ncm_stop,
+ .write_hwaddr = cdc_ncm_write_hwaddr,
+ .read_rom_hwaddr = cdc_ncm_read_rom_hwaddr,
+};
+
+U_BOOT_DRIVER(usb_cdc_1_ncm) = {
+ .name = "cdc_ncm",
+ .id = UCLASS_ETH,
+ .probe = cdc_ncm_probe,
+ .remove = cdc_ncm_remove,
+ .ops = &cdc_ncm_eth_ops,
+ .priv_auto = sizeof(struct cdc_ncm_priv),
+ .plat_auto = sizeof(struct eth_pdata),
+};
+
+static const struct usb_device_id cdc_ncm_id_table[] = {
+ { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM,
+ USB_CDC_PROTO_NONE), },
+ { /* sentinel */ }
+};
+
+U_BOOT_USB_DEVICE(usb_cdc_1_ncm, cdc_ncm_id_table);
--
2.54.0
More information about the U-Boot
mailing list