Linux USB Gadget Configfs: Compose Functions, Bind UDCs, and Verify Enumeration
Build Linux USB gadgets through configfs with correct function composition, UDC lifecycle, descriptor ownership, host checks, and safe teardown.
Linux USB gadget mode lets a system with USB device-controller hardware present itself to a host as a peripheral. Configfs provides a userspace interface for assembling a gadget from descriptors, configurations, and function instances. It is not a way to turn any USB-A host port into a device port: the board, controller, cable, role-switch configuration, kernel, and device tree must all support peripheral mode. A gadget that exists in configfs but is not bound to a usable USB Device Controller (UDC) has not been presented to a host.
Treat setup as a lifecycle with explicit stages: verify hardware and kernel support, create a gadget identity, instantiate functions, link functions into one or more configurations, bind to a UDC, confirm host enumeration and class behavior, then unbind and remove objects in reverse dependency order. This makes failures easier to localize than copying a large init script onto an unverified board.
Confirm the device-side hardware first
Start by identifying whether the physical port is wired for USB device or dual-role operation. A connector’s shape does not determine its role. Inspect platform documentation, device-tree role configuration, kernel logs, and the exposed UDC class:
uname -r
ls -l /sys/class/udc/
journalctl -k -b --no-pager | grep -i -E 'udc|usb|role'
zgrep -E 'CONFIG_USB_(GADGET|LIBCOMPOSITE|CONFIGFS)' /proc/config.gz 2>/dev/null
The configuration file may not be available through /proc/config.gz; distribution kernel configuration can be stored elsewhere. An empty /sys/class/udc/ generally means no UDC has registered, which can result from unsupported hardware, disabled peripheral role, a missing controller driver, or a probe failure. It is not fixed by creating directories under configfs.
The kernel documentation identifies CONFIGFS_FS and the USB composite gadget support as prerequisites for the documented workflow. Modules such as libcomposite and a specific function driver may be built in, loaded automatically, or packaged separately. Module names and available functions vary by kernel build. Confirm them on the target system rather than assuming a function exists because an example uses it.
Configfs object model
Mount configfs if it is not already mounted, then create a directory below usb_gadget. That directory represents a gadget. Its attributes include USB device descriptors such as vendor ID, product ID, device revision, class fields, and strings. Configurations describe sets of functions the host can select. Function instances provide class-specific behavior such as serial, Ethernet, mass storage, or HID, depending on the modules present. Symbolic links from a configuration to function instances compose what the host sees.
This distinction matters. A function instance can exist without being exposed in a configuration. A configuration can link functions but remain inactive because no UDC is bound. A bound gadget can enumerate successfully while the host still fails to load the expected class driver or the userspace service behind that function.
Use vendor and product identifiers assigned to the organization that owns the product. The placeholder IDs in examples must not be copied into shipped hardware. Duplicate or unauthorized IDs can collide with other products and create misleading host-side driver matching. Product and serial strings should be stable enough for fleet inventory, but avoid embedding secrets or personally identifying information in descriptors. Keep the identity policy and function composition under version control outside the live configfs tree.
Language string directories use USB language IDs, and each configuration also has its own strings. Attributes and supported values are function-specific. Read the current kernel documentation and the relevant ABI description before writing function attributes; avoid assuming every kernel exposes the same knobs.
Compose a minimal test gadget
The commands below are a structural template, not a complete copy-paste deployment. Replace the mount point, gadget name, descriptor values, strings, and function with values appropriate to the hardware and assigned IDs. Do this on a disposable development board, not on a system whose USB device role is carrying production traffic.
mountpoint -q /sys/kernel/config || mount -t configfs none /sys/kernel/config
modprobe libcomposite
mkdir /sys/kernel/config/usb_gadget/lab0
cd /sys/kernel/config/usb_gadget/lab0
After creation, set vendor and product identifiers only when authorized, set the device revision and supported USB class descriptors according to the actual composite design, and create language string directories. The kernel documentation uses a separate configs/ directory for configurations and functions/ for instances. Instantiate only functions supported by the kernel and required for the test. Link each function into a configuration using the documented symlink relationship.
For a serial test, a function commonly has an instance name such as acm.usb0, but the exact function and attributes depend on configuration. For mass storage, use only an intentionally disposable backing image and understand whether the function is configured read-only or writable. A host may cache writes; removing the backing file or unbinding while it is mounted can corrupt data. Never expose a host root disk, private filesystem, or production block device as an experiment.
Set configuration attributes and strings only after reading their supported ranges and semantics. USB power descriptors are declarations to the host and must reflect actual design constraints. Do not copy a power value from an example without checking its unit interpretation, controller capability, and applicable USB specification for the negotiated speed and version.
Before binding, list /sys/class/udc/ and choose the correct UDC name for this board. Write that exact name to the gadget’s UDC attribute. Binding is the transition that asks the controller to expose the composed gadget; it can trigger immediate host enumeration. If there is no connected host, the UDC may still bind, but enumeration evidence will not exist until a cable/role path is present.
Verify both sides of enumeration
On the device, read back the UDC attribute and inspect kernel logs for bind, disconnect, reset, endpoint allocation, or descriptor errors. On the host, inspect USB topology and descriptors using its standard USB inventory tools. Confirm vendor/product identity, configuration, interfaces, endpoints, and class-specific driver binding. A host listing that only shows a device descriptor is not evidence that an application-level function works.
For each exposed function, test the actual protocol. A serial interface should open and exchange known bytes with a loopback or test peer. An Ethernet function should create the expected network interface and pass traffic under an isolated address plan. A mass-storage function should mount a disposable image, perform a checksum-verified transfer, flush and safely eject, then confirm the backing image contents. Each test should have an explicit cleanup sequence.
If enumeration fails, separate role, UDC, descriptor, and function errors. Check whether the port entered peripheral mode, whether the controller registered, whether the correct UDC is bound, and whether the host can electrically detect the device. Then check descriptor validity, configuration composition, endpoint constraints, and function-specific logs. Some UDCs have limits on endpoint type, count, direction, or packet size; function composition can fail even when each function works alone.
If the host sees the gadget but binds an unexpected driver, inspect class/subclass/protocol and interface descriptors. Composite gadgets expose multiple interfaces; host matching may occur per interface rather than once for the whole device. Avoid changing IDs as a guess. Descriptor changes should be deliberate, validated against the USB class specification, and tested on the operating systems the product supports.
Unbind, teardown, and persistence
Unbind before changing functions or removing the gadget. The documented operation is to write an empty value to the gadget’s UDC attribute; verify the controller no longer reports this gadget. Remove the function symlinks from each configuration, then remove configuration and function instances, string directories, and finally the gadget directory. Configfs enforces dependency ordering by refusing to remove busy objects, which is useful evidence that the composition has not been dismantled fully.
The kernel function modules may remain loaded after the configfs instances are removed. Module unloading is a separate action and can affect other gadgets or dependencies, so do not do it as routine cleanup unless the test owns the module lifecycle. If an automated service configures gadgets at boot, make it idempotent: detect an existing mount, avoid duplicate gadget creation, bind only after prerequisites are ready, and unbind cleanly on service stop. Record the live configfs state, since it is runtime configuration rather than a durable file-based manifest.
Production acceptance checklist
Before shipping or deploying a managed gadget, preserve:
- board revision, UDC driver, kernel release/configuration, role-switch state, and cable/port topology;
- assigned VID/PID ownership, descriptor strings, configuration order, and function attributes;
- host enumeration captures for each supported operating system and USB speed;
- function-level protocol tests, data integrity results, suspend/resume behavior, and disconnect recovery;
- an unbind and teardown test that leaves no stale configfs objects;
- a recovery plan if the gadget prevents access to the device’s normal management port.
A reliable gadget implementation is a device contract, not merely a set of configfs directories. Hardware role, descriptors, function composition, UDC constraints, host behavior, and teardown all need validation on the exact target. Keep a minimal known-good composition and add one function at a time so that a failure has a narrow search space.
Related:
- Linux USB URBs: Submission, Completion, Cancellation, and Teardown
- Building and Loading Your Own Linux Kernel Module
Sources: