The traditional approach to gadgets composition was to create a kernel module for a given composition of a gadget. And even if you wanted only a slightly different set of USB functions in your gadget, you had to create another kernel module. Around late 2012 a new approach started appearing. The idea was to decouple information about gadget composition from code and only provide building blocks out of which the user composes their gadget at runtime. Very much in the spirit of "mechanism, not policy" philosophy. The interface chosen for userspace interaction was configfs (by default can be found in /sys/kernel/config). After about two years, all USB functions available in the Linux kernel had been converted to use the new interface.
The usage pattern is like this: the user creates a separate directory per each gadget they want to have, gives their gadget a personality by specifying vendor id, product id and USB strings (visible e.g. after running lsusb -v as root), then under that directory creates the configurations they want and instantiates USB functions they want (both by creating respective directories) and finally associates functions to configurations with symbolic links. At this point gadget's composition is already in memory, but is not bound to any UDC. To activate the gadget one must write UDC name to the UDC attribute in the gadget's configfs directory - the gadget then becomes bound to this particular UDC (and the UDC cannot be used by more than one gadget). Available UDC names are in /sys/class/udc. Only after a gadget is bound to a UDC can it be successfully enumerated by the USB host.
A working, minimal example of ECM (Ethernet) on an Odroid U3, which leaves some attributes at their default values:
# go to configfs directory for USB gadgets
CONFIGFS_ROOT=/sys/kernel/config # adapt to your machine
cd "${CONFIGFS_ROOT}"/usb_gadget
# create gadget directory and enter it
mkdir g1
cd g1
# USB ids
echo 0x1d6b > idVendor
echo 0x104 > idProduct
# USB strings, optional
mkdir strings/0x409 # US English, others rarely seen
echo "Collabora" > strings/0x409/manufacturer
echo "ECM" > strings/0x409/product
# create the (only) configuration
mkdir configs/c.1 # dot and number mandatory
# create the (only) function
mkdir functions/ecm.usb0 # .
# assign function to configuration
ln -s functions/ecm.usb0/ configs/c.1/
# bind!
echo 12480000.hsotg > UDC # ls /sys/class/udc to see available UDCs
Please note that your vendor id is assigned for a fee by USB Implementors Forum (USB IF) - this refers to products you want to put on the market. For your own tinkering you can choose whatever you like. However, these ids (vendor and product) can be used by the host to decide which host-side driver to use to talk to your device. 0x1d6b is for Linux Foundation and 0x0104 is for Ethernet Gagdet. If your USB host sees such ids it assumes it needs the cdc_ether host-side driver.
If your device is connected to a Linux host, then you shoud see output similar to the below in host's dmesg:
usb 3-1.2.1.4.4: New USB device found, idVendor=1d6b, idProduct=0104
usb 3-1.2.1.4.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 3-1.2.1.4.4: Product: ECM
usb 3-1.2.1.4.4: Manufacturer: Collabora
cdc_ether 3-1.2.1.4.4:1.0 usb0: register 'cdc_ether' at usb-0000:3c:00.0-1.2.1.4.4, CDC Ethernet Device, d2:c2:2d:b7:8e:6b
Note the Product and Manufacturer strings which are exactly what has been written to configfs.
A new usb interface should appear at the host side...
ifconfig -a
usb0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
ether d2:c2:2d:b7:8e:6b txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1 bytes 90 (90.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# why not configure it?
ifconfig usb0 192.168.1.2 up
...and at the device:
ifconfig -a
usb0: flags=4098<BROADCAST,MULTICAST> mtu 1500
ether f2:40:e6:d3:01:2c txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# why not configure this one as well?
ifconfig usb0 192.168.1.3 up
# and ping the host?
ping 192.168.1.2
PING 192.168.1.2 (192.168.1.2) 56(84) bytes of data.
64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=1.40 ms
Similarly, the device can be pinged from the host:
ping 192.168.1.3
PING 192.168.1.3 (192.168.1.3) 56(84) bytes of data.
64 bytes from 192.168.1.3: icmp_seq=1 ttl=64 time=1.06 ms