WendyOS Docs

USB NCM Gadget

WendyOS configures the device as a composite USB gadget — a single USB device that exposes both a network interface (NCM) and a serial console (ACM) to the host. This is done at bo

USB NCM Gadget

WendyOS configures the device as a composite USB gadget — a single USB device that exposes both a network interface (NCM) and a serial console (ACM) to the host. This is done at boot time using the Linux USB gadget framework via configfs.

What Is USB NCM?

NCM (Network Control Model) is a USB class protocol that carries Ethernet frames over USB. It is part of the CDC (Communications Device Class) family. Compared to the older ECM protocol, NCM offers better throughput and is the preferred mode. WendyOS falls back to ECM only if the kernel does not have NCM support compiled in.

On the host, the gadget appears as a standard Ethernet adapter. Linux hosts load the cdc_ncm driver automatically; macOS supports NCM natively; Windows requires a driver for older OS versions.

Composite Gadget Layout

The gadget exposes two functions in a single configuration:

USB Configuration c.1 (CDC+ACM)
├── functions/ncm.usb0   → network interface (usb0 on device, enxXXXXXXXXXXXX on host)
└── functions/acm.usb0   → serial console   (/dev/ttyGS0 on device, /dev/ttyACM0 on host)

USB descriptor strings set on the gadget:

  • Manufacturer: Wendy Labs Inc
  • Product: WendyOS Device <device-name>
  • Serial number: device serial (from device tree, tegra fuse, /proc/cpuinfo, or eth0 MAC)

USB IDs:

  • VID: 0x1d6b (Linux Foundation)
  • PID: 0x0104 (Multifunction Composite Gadget)

Kernel Configuration

The gadget framework requires these kernel options. On RPi5 they are built-in; on Jetson they are loaded as modules.

RPi5 (meta-rpi-extensions/recipes-kernel/linux/linux-raspberrypi/usb-gadget.cfg):

CONFIG_CONFIGFS_FS=y
CONFIG_USB_GADGET=y
CONFIG_USB_LIBCOMPOSITE=y
CONFIG_USB_CONFIGFS=y
CONFIG_USB_CONFIGFS_ACM=y
CONFIG_USB_CONFIGFS_NCM=y
CONFIG_USB_CONFIGFS_ECM=y
CONFIG_USB_F_ACM=y
CONFIG_USB_F_NCM=y
CONFIG_USB_F_ECM=y

Jetson (meta-tegra-extensions/...usb-gadget.cfg): Same options but as modules (=m). The module load order matters — tegra_xudc must load before libcomposite so that the UDC is registered before gadget-setup.sh runs.

Initialization Flow

gadget-setup.service runs gadget-setup.sh once at boot, before NetworkManager starts.

Step 1 — Resolve device serial number

The script tries these sources in order, stopping at the first non-empty result:

  1. /proc/device-tree/serial-number (RPi device tree)
  2. /sys/devices/platform/tegra-fuse/uid (Jetson eFuse UID)
  3. awk '/Serial/ {print $2}' /proc/cpuinfo (legacy RPi CPU info)
  4. eth0 MAC address (stripped of colons)
  5. Unix timestamp (last-resort fallback)

The last 8 characters are extracted as SHORT_SERIAL and used for the human-readable device name when no /etc/wendyos/device-name file is present.

Step 2 — Detect USB controller

if lsmod | grep -q "^tegra_xudc"; then  USB_VERSION=0x0320  # USB 3.2
elif lsmod | grep -q "^dwc3";     then  USB_VERSION=0x0300  # USB 3.0
elif lsmod | grep -q "^dwc2";     then  USB_VERSION=0x0200  # USB 2.0
fi

The bcdUSB descriptor is set accordingly. The bus power attribute also changes: dwc2 devices set bmAttributes=0x80 (bus-powered, 250 mA), while other controllers use 0xC0 (self-powered).

Step 3 — Generate stable MAC addresses

Two MAC addresses are derived deterministically from the device serial. See MAC Address Generation for the algorithm.

Step 4 — Build the configfs tree

GADGET_DIR=/sys/kernel/config/usb_gadget/wendyos_device
mkdir -p $GADGET_DIR
echo 0x1d6b > idVendor
echo 0x0104 > idProduct
echo $USB_VERSION > bcdUSB
echo 0xEF > bDeviceClass    # Miscellaneous (required for IAD/composite)
echo 0x02 > bDeviceSubClass
echo 0x01 > bDeviceProtocol
# ... string descriptors ...
mkdir -p functions/ncm.usb0
echo $mac_host > functions/ncm.usb0/host_addr
echo $mac_self > functions/ncm.usb0/dev_addr
echo 10       > functions/ncm.usb0/qmult      # queue multiplier for throughput
ln -sf functions/ncm.usb0 configs/c.1/
mkdir -p functions/acm.usb0
ln -sf functions/acm.usb0 configs/c.1/

NCM falls back to ECM if functions/ncm.usb0 cannot be created (kernel without usb_f_ncm).

Step 5 — Activate the UDC

The script polls /sys/class/udc for up to 60 seconds until a UDC appears, then writes its name to $GADGET_DIR/UDC to bind the gadget to the hardware controller.

Step 6 — Tegra role-switch workaround

On Jetson, the USB-C port role is controlled by a CC chip (FUSB301). If the padctl role-switch notifier chain has not fired by the time the gadget is activated, the UDC stays in "default" mode and never enters device mode. The script forces the role explicitly:

echo "device" > /sys/class/usb_role/usb2-0-role-switch/role

It falls back to a glob (usb2-*-role-switch) on AGX-style topologies where the index differs.

Step 7 — Post-activation tuning

After the UDC binds:

  • usb0-force-up brings usb0 administratively up (retries for up to 10 seconds to avoid races).
  • ip link set usb0 txqueuelen 2000 increases the TX queue length.
  • The USB interrupt is pinned to CPUs 0–3 via /proc/irq/<irq>/smp_affinity.

systemd Services

ServicePurpose
gadget-setup.serviceRuns gadget-setup.sh + usb0-force-up; type oneshot, RemainAfterExit=yes
wendyos-usbgadget-unbind.serviceTriggered by udev udc remove event; writes "" to the UDC file to cleanly unbind

Service ordering: gadget-setup.service runs After=sysinit.target local-fs.target systemd-udevd.service and Before=NetworkManager.service network.target.

udev Rules

Rule fileEffect
90-usb0-up.rulesRuns usb0-force-up whenever usb0 is added or changes state
99-usb-gadget-udc.rulesTriggers wendyos-usbgadget-unbind.service on UDC removal

Teardown (Rebinding)

The script handles re-runs gracefully. If $GADGET_DIR already exists it performs a strict reverse-order teardown:

  1. Clear UDC binding
  2. Remove function symlinks from configs
  3. Remove function directories
  4. Remove config string and config directories
  5. Remove gadget string directories and the gadget root

This avoids the EBUSY errors that occur when configfs entries are removed out of order.

Known Issues

RPi5 / DWC2 DMA stall (kernel 6.6, BCM2712): BCM2712 reports DMA capability but DMA completion interrupts never fire for Out (host to device) transfers in peripheral mode. The symptom is that the device can send data but receives nothing from the host. The fix is g_dma=false in dwc2_set_bcm_params(). WendyOS applies this in a kernel patch under meta-rpi-extensions/. Do not write "" to the UDC sysfs file on an affected board without this patch — doing so triggers ep_stop_xfr timeouts that leave Global OUT NAK permanently asserted until reboot.

On this page