Search by Tags

USB Device Mode (Linux)

 

Article updated at 29 Oct 2021
Compare with Revision




The USB (Universal Serial Bus) standard specifies two roles USB host and USB peripheral (also known as USB function, USB peripheral device or sometimes just USB device). The bus can only have one USB host connected all other devices need to be in USB peripheral mode. Most PCs support USB host mode exclusively. However, embedded systems often have multiple USB controller supporting host or peripheral roles or supporting both roles on a single controller (dual-role controller). The dual-role controllers often also support the OTG (On-The-Go) specification which defines a host negotiation protocol allowing two devices to negotiate for the role of the host.

On Colibri modules the client port (USBC) is usually connected to a dual-role capable controller. By default the port is in peripheral mode. If a USB Micro-B OTG cable is used (which connects the ID pin to ground) the circuit on the carrier board automatically enables driving +5V onto VBUS in order to provide power to USB devices as required by a host. On the other hand VBUS is also connected to the Colibri USBC_DET signal (usually via a diode) allowing driver software to monitor and subsequently change the role. On Apalis modules a full OTG port (USBO1) is provided which makes use of the OTG ID pin using the USBO1_ID signal to determine its role and additionally the VBUS pin using the USBO1_VBUS signal to determine the actual connection status thereof allowing suspending the USB complex if not connected.

On Linux USB peripheral mode is supported through the Gadget API. This API abstracts the USB peripheral controller hardware as well as offers hardware neutral routines which allow to implement USB functions (e.g. USB CDC ACM or RNDIS). Traditionally the USB function had to be chosen at Kernel compile time (e.g. g_ether). The Linux USB gadget drivers received increasingly more attention and have evolved over time, especially since Android appeared. The Android variant of the Linux kernel added functionality allowing easy switching between USB functions using sysfs. Later in Linux 3.10 a similar functionality also landed in the upstream kernel which now allows configuring USB gadgets through configfs (the USB Gadget ConfigFS). Depending on the Linux kernel version provided by our BSP a different level of USB peripheral functionality and API is available.

USB peripheral devices need a vendor and product identification. Toradex has its own vendor ID (0x1b67) and assigns a product ID for each Colibri and Apalis product which you as a customer can use too. The USB Product ID is the sum of an offset of 0x4000 and the hexadecimal representation of the product identifier, e.g. 0x4000 + 12 = 0x400c (the product identifier is the first 4 digits of the Toradex Product Number, e.g. 0012 for Colibri VF61 256MB IT V1.1B).

Some platforms need device dependent and certified drivers (e.g. CDC ACM for Microsoft Windows) even for standard USB classes. While it is possible to use the driver delivered with Windows a custom inf file still needs to be provided. For better out-of-the-box experience the standard images typically use the default Linux USB product/vendor ID (e.g. 1d6b:0104 Linux Foundation Multifunction Composite Gadget).

Warning: this article has been written with our BSP Layers and Reference Images for Yocto Project 2.8 in mind. While it is still a good starting point, for newer BSP versions some commands may be obsolete or not working anymore.

U-Boot

U-Boot's CONFIG_USB_DEVICE config symbol allows for U-Boot to provide USB peripheral mode support. With recent versions we successfully tested ums (User Mode Storage) and dfu (Device Firmware Upgrade). Both USB functions conform to standardized USB classes.

Since BSP V2.4 all modules use the same U-Boot version 2015.04. This common U-Boot version configures the USB peripheral device identification using the Toradex vendor ID and product ID.

The following exports the eMMC on SoMs with an eMMC or the SD card on the SoMs with raw NAND. Note that with CTRL+C you can abort the operation and regain control over U-Boot.

> ums
ums - Use the UMS [USB Mass Storage]

Usage:
ums <USB_controller> [<devtype>] <dev[:part]>  e.g. ums 0 mmc 0
    devtype defaults to mmc

> ums 0 mmc 0
UMS: LUN 0, dev 0, hwpart 0, sector 0x0, count 0x1d5a000
<spinning />

On the host connected to the SoM you will now see the following USB device. Assuming you have our regular BSP installed on an eMMC based SoM the following block devices will also appear and possibly mounted:

$ lsusb
...
Bus 001 Device 019: ID 1b67:401d
...

$ lsblk
NAME             MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
...
sdk                8:160  1   3.7G  0 disk 
├─sdk1             8:161  1    16M  0 part /mnt/BOOT
└─sdk2             8:162  1   3.7G  0 part /mnt/RFS

Note that ums currently can only export the user area of an eMMC but not its boot areas. This prevents you from updating the bootloader over this mechanism.

Linux

NXP/Freescale Vybrid/i.MX7/i.MX6ULL/i.MX6/i.MX8/i.MX8X based Modules

The NXP/Freescale Vybrid BSP (since V2.4 Beta 1) and the i.MX BSP (since V2.5 Beta 2) use the USB Gadget ConfigFS to configure the USB peripheral port.

The USB Gadget ConfigFS is a file system which allows to configure USB functions by using file system commands such as mkdir and creating/writing files. Typically the file system is mounted under /sys/kernel/config/. The official Linux kernel documentation has more information in Documentation/usb/gadget_configfs.txt on how to use the file system to enable USB functions. Matt Porter held a talk at ELC 2014 titled Kernel USB Gadget Configfs Interface cover the Gadget ConfigFS a bit more in depth.

The library libusbgx (or its predecessor libusbg) allows to use the USB Gadget ConfigFS through a C API. This can be useful if embedded software needs to dynamically control USB functions provided by a device. The library also provides two utilities called gadget-import and gadget-export. This utilities allow to export a hand-crafted USB Gadget to a schema file and reimport it.
The BSP provides a default schema specifying an RNDIS configurations (USB functions) under /etc/usbg/g1.schema. A systemd service called usbg.service uses gadget-import to import this schema on startup (see this commit libusbg: add updated version of USB gadget library which initially added the functionality, then providing an RNDIS and CDC ACM composite device).

To alter the schema, one can either create a completely new gadget schema, alter the existing gadget and reexport it, or alter the schema directly (for simple changes).

This example shows how to create a completely new configuration., to create a Gadget configuration for the a single USB CDC (Communications Device Class) Ethernet (ECM).

First a new gadget needs to be created

# Mount configfs
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget/

# Create gadget
mkdir g2
cd g2

# Use Toradex vendor and product id
echo 0x1b67 > idVendor
echo 0x400c > idProduct

# English language strings...
mkdir strings/0x409
cat /proc/device-tree/serial-number > strings/0x409/serialnumber
echo "Toradex" > strings/0x409/manufacturer
cat /proc/device-tree/model > strings/0x409/product

Attention: If /sys/kernel/config/usb_gadget/ does not exist you need to load the libcomposite module: modprobe libcomposite

Then, one or multiple configurations can be assigned. A Gadget supporting multiple configurations (USB functions) is also known as a composite gadget. Note that depending on the operating system used on the USB host device, it might be problematic to use such configurations (see also Documentation/usb/gadget_multi.txt)

Functions can be created by creating a directory like functions/<name>.<instance name>. The following table provides a (incomplete) list of available functions in the Linux kernel. Up to V2.5 Beta 1, only the Gadget drivers for RNDIS and CDC ACM (serial) functions have been built into the kernel. Newer BSP provide all Gadget functions in this list as kernel modules:

Name Driver Description Documentation
acm Abstract Control Model (CDC ACM) ACM serial link, works with Windows/Linux (use Documentation/usb/linux-cdc-acm.inf) Documentation/usb/gadget_serial.txt
gser Generic serial bulk in/out The function talks to the Linux-USB generic serial driver
obex Object Exchange Model (CDC OBEX) Needs an user space OBEX server
rndis RNDIS Microsoft "Remote NDIS" (RNDIS) Ethernet protocol (use Documentation/usb/linux.inf)
ecm Ethernet Control Model (CDC ECM) Ethernet Control Model
eem Ethernet Emulation Model (EEM) Newer USB Ethernet standard that is somewhat simpler than CDC ECM
ncm Network Control Model (CDC NCM) Advanced protocol for Ethernet
ffs Function filesystem (FunctionFS) Allows to implement USB functions from user-space (e.g. for MTP) Documentation/usb/functionfs.txt
mass_storage Mass storage Exports a regular file or block device as USB Mass Storage disk drive Documentation/usb/mass-storage.txt
hid HID function Generic emulation of USB Human Interface Devices (HID) Documentation/usb/gadget_hid.txt
# Create configuration
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo "USB CDC Ethernet config" > configs/c.1/strings/0x409/configuration

# Create CDC Ethernet (ECM) config
mkdir functions/ecm.usb0
ln -s functions/ecm.usb0 configs/c.1

Another example how to create a mass storage device:

# Create and format 8MB FAT image
dd if=/dev/zero of=/mass.img bs=4k count=2k
mkfs.vfat /mass.img

# Create USB mass storage config
mkdir functions/mass_storage.1
echo /mass.img > functions/mass_storage.1/lun.0/file
ln -s functions/mass_storage.1 configs/c.1

The Gadget testing documentation located at Documentation/usb/gadget-testing.txt of the Linux kernel also contains some valuable information how to use the USB Gadget functions.

To enable this configuration write the name of the USB controller to the UDC file (see /sys/class/udc/ for available USB Device Controllers). You need to disable the old configuration first before enabling the new one:

cd /sys/kernel/config/usb_gadget/
echo "" > g1/UDC
echo "ci_hdrc.0" > g2/UDC

Don't forget to store the configuration using gadget-export:

gadget-export g2 /etc/usbg/g2.schema

To make sure the g2 Gadget configuration gets loaded automatically, alter the systemd service located at /lib/systemd/system/usbg.service or name the schema g1.schema.

NVIDIA Tegra based Modules

Starting with our V2.x BSPs based on NVIDIA's L4T kernel we are using the Android multi gadget driver which allows various functionality to be exposed via a single USB device cable connection.

Mass Storage

USB Mass Storage allows exporting any block device from the target module to a PC and use it just in the same way as any regular USB memory stick.

Note: Whatever block device you export by using this method, it can not be in use on the target module side (e.g. by a file system driver). Otherwise the simultaneously accessing file system drivers would corrupt the file system on the block device!

The example below shows how a regular SD card inside one of the Apalis Evaluation board's slots can be shared:

Target

root@apalis-t30:~# echo 0 > /sys/class/android_usb/android0/enable
[  259.383810] android_usb: already disabled
root@apalis-t30:~# echo mass_storage > /sys/class/android_usb/android0/functions
root@apalis-t30:~# umount /dev/mmcblk1p1
root@apalis-t30:~# echo /dev/mmcblk1p1 > /sys/devices/virtual/android_usb/android0/f_mass_storage/lun/file
root@apalis-t30:~# echo 1 > /sys/class/android_usb/android0/enable
[ 1012.178981] android_work: sent uevent USB_STATE=DISCONNECTED
[ 1012.239487] android_work: sent uevent USB_STATE=CONNECTED
[ 1012.251323] android_work: sent uevent USB_STATE=DISCONNECTED
[ 1012.321347] android_work: sent uevent USB_STATE=CONNECTED
[ 1012.343418] android_usb gadget: high speed config #1: android
[ 1012.352025] android_work: sent uevent USB_STATE=CONFIGURED

Host

[28231.142003] usb 1-3.4.2.1: new high-speed USB device number 48 using xhci_hcd
[28231.244229] usb 1-3.4.2.1: New USB device found, idVendor=18d1, idProduct=0001
[28231.244231] usb 1-3.4.2.1: New USB device strings: Mfr=2, Product=3, SerialNumber=4
[28231.244233] usb 1-3.4.2.1: Product: Android
[28231.244234] usb 1-3.4.2.1: Manufacturer: Android
[28231.244235] usb 1-3.4.2.1: SerialNumber: 0123456789ABCDEF
[28231.263808] usb-storage 1-3.4.2.1:1.0: USB Mass Storage device detected
[28231.263898] scsi host6: usb-storage 1-3.4.2.1:1.0
[28232.266885] scsi 6:0:0:0: Direct-Access     Linux    File-CD Gadget   0000 PQ: 0 ANSI: 2
[28232.267872] sd 6:0:0:0: Attached scsi generic sg1 type 0
[28232.277777] sd 6:0:0:0: [sdb] 15556608 512-byte logical blocks: (7.96 GB/7.41 GiB)
[28232.277995] sd 6:0:0:0: [sdb] Write Protect is off
[28232.278003] sd 6:0:0:0: [sdb] Mode Sense: 0f 00 00 00
[28232.279925] sd 6:0:0:0: [sdb] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
[28232.293673]  sdb:
[28232.303721] sd 6:0:0:0: [sdb] Attached SCSI removable disk
[28232.674168] SELinux: initialized (dev sdb, type vfat), uses genfs_contexts

RNDIS

The Vibrante kernel in our V1.x BSPs contained the g_ether driver which acts as an Ethernet over USB controller (CDC Ethernet) by default. The later L4T kernel in our V2.x BSPs comes with the Android RNDIS Ethernet gadget driver. Most nowadays operating system (including Linux, Windows 7) include respective host drivers (plug and play). Our latest BSPs now run a DHCP server on the module to automatically assign your PC an IP address upon USB device connection.

[    3.979149] NVidia Tegra High-Speed USB SOC Device Controller driver (Apr 20, 2007)
[    3.992042] g_ether gadget: using random self ethernet address
[    3.997892] g_ether gadget: using random host ethernet address
[    4.003928] usb0: MAC 1a:aa:fc:27:db:07
[    4.007759] usb0: HOST MAC 66:f9:7a:09:53:15
[    4.012022] g_ether gadget: controller 'fsl-tegra-udc' not recognized; trying CDC Ethernet (ECM)
[    4.020935] g_ether gadget: Ethernet Gadget, version: Memorial Day 2008
[    4.027677] g_ether gadget: g_ether ready
[    4.031681] fsl-tegra-udc: bind to driver g_ether

USB Device Connection

[   88.986732] tegra-otg tegra-otg: SUSPEND --> PERIPHERAL
[   89.485848] g_ether gadget: high speed config #1: CDC Ethernet (ECM)
Target (USB Device)

Configure IP address on device usb0 and test performance using iperf.

root@colibri_t20:~# ifconfig usb0 192.168.100.2
root@colibri_t20:~# iperf -c 192.168.100.1
------------------------------------------------------------.
Client connecting to 192.168.100.1, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------.
[  3] local 192.168.100.2 port 40321 connected with 192.168.100.1 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec    130 MBytes    109 Mbits/sec
root@colibri_t20:~# iperf -c 192.168.100.1
------------------------------------------------------------.
Client connecting to 192.168.100.1, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------.
[  3] local 192.168.100.2 port 40322 connected with 192.168.100.1 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec    130 MBytes    109 Mbits/sec
root@colibri_t20:~# iperf -c 192.168.100.1
------------------------------------------------------------.
Client connecting to 192.168.100.1, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------.
[  3] local 192.168.100.2 port 40323 connected with 192.168.100.1 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec    130 MBytes    109 Mbits/sec
root@colibri_t20:~# iperf -c 192.168.100.1
------------------------------------------------------------.
Client connecting to 192.168.100.1, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------.
[  3] local 192.168.100.2 port 40324 connected with 192.168.100.1 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec    130 MBytes    109 Mbits/sec
root@colibri_t20:~# iperf -c 192.168.100.1
------------------------------------------------------------.
Client connecting to 192.168.100.1, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------.
[  3] local 192.168.100.2 port 40325 connected with 192.168.100.1 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec    130 MBytes    109 Mbits/sec
root@colibri_t20:~# iperf -s
------------------------------------------------------------.
Server listening on TCP port 5001
TCP window size: 85.3 KByte (default)
------------------------------------------------------------.
[  4] local 192.168.100.2 port 5001 connected with 192.168.100.1 port 35850
[ ID] Interval       Transfer     Bandwidth
[  4]  0.0-10.0 sec    112 MBytes  93.6 Mbits/sec
[  5] local 192.168.100.2 port 5001 connected with 192.168.100.1 port 35851
[  5]  0.0-10.0 sec    111 MBytes  92.7 Mbits/sec
[  4] local 192.168.100.2 port 5001 connected with 192.168.100.1 port 35852
[  4]  0.0-10.0 sec    122 MBytes    102 Mbits/sec
[  5] local 192.168.100.2 port 5001 connected with 192.168.100.1 port 35854
[  5]  0.0-10.0 sec    116 MBytes  96.6 Mbits/sec
[  4] local 192.168.100.2 port 5001 connected with 192.168.100.1 port 35855
[  4]  0.0-10.0 sec    112 MBytes  93.6 Mbits/sec

Host (USB Device)

ubuntu@ubuntu~$ sudo ifconfig usb0 192.168.100.1
ubuntu@ubuntu~$ iperf -s
------------------------------------------------------------.
Server listening on TCP port 5001
TCP window size: 85.3 KByte (default)
------------------------------------------------------------.
[  4] local 192.168.100.1 port 5001 connected with 192.168.100.2 port 40321
[ ID] Interval       Transfer     Bandwidth
[  4]  0.0-10.0 sec    130 MBytes    109 Mbits/sec
[  5] local 192.168.100.1 port 5001 connected with 192.168.100.2 port 40322
[  5]  0.0-10.0 sec    130 MBytes    109 Mbits/sec
[  4] local 192.168.100.1 port 5001 connected with 192.168.100.2 port 40323
[  4]  0.0-10.0 sec    130 MBytes    109 Mbits/sec
[  5] local 192.168.100.1 port 5001 connected with 192.168.100.2 port 40324
[  5]  0.0-10.0 sec    130 MBytes    109 Mbits/sec
[  4] local 192.168.100.1 port 5001 connected with 192.168.100.2 port 40325
[  4]  0.0-10.0 sec    130 MBytes    109 Mbits/sec
^Cubuntu@ubuntu~$ iperf -c 192.168.100.2
------------------------------------------------------------.
Client connecting to 192.168.100.2, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------.
[  3] local 192.168.100.1 port 35850 connected with 192.168.100.2 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec    112 MBytes  93.8 Mbits/sec
ubuntu@ubuntu~$ iperf -c 192.168.100.2
------------------------------------------------------------.
Client connecting to 192.168.100.2, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------.
[  3] local 192.168.100.1 port 35851 connected with 192.168.100.2 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec    111 MBytes  92.9 Mbits/sec
ubuntu@ubuntu~$ iperf -c 192.168.100.2
------------------------------------------------------------.
Client connecting to 192.168.100.2, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------.
[  3] local 192.168.100.1 port 35852 connected with 192.168.100.2 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec    122 MBytes    103 Mbits/sec
ubuntu@ubuntu~$ iperf -c 192.168.100.2
------------------------------------------------------------.
Client connecting to 192.168.100.2, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------.
[  3] local 192.168.100.1 port 35854 connected with 192.168.100.2 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec    116 MBytes  96.9 Mbits/sec
ubuntu@ubuntu~$ iperf -c 192.168.100.2
------------------------------------------------------------.
Client connecting to 192.168.100.2, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------.
[  3] local 192.168.100.1 port 35855 connected with 192.168.100.2 port 5001
[ ID] Interval       Transfer     Bandwidth
[  3]  0.0-10.0 sec    112 MBytes  93.8 Mbits/sec

USB Device Disconnect

[  253.282060] tegra-otg tegra-otg: PERIPHERAL --> SUSPEND