Introduction to USBIP

Sometimes it can be very useful to obtain access to a USB device over very large distances. There are various hardware adapters that can be used to run USB over ethernet cables but in the post I will deal with using USBIP on Linux to remote a USB device over internet protocols.

USBIP has been around for a while but seems to be in a confused state at the moment. The linux driver has been moved from the independent project into the Linux kernel and for some time has been placed in the drivers/staging/usbip directory up to kernel version 3.18 where it has moved into the main tree. A further problem is that while a Windows driver exists the linux driver has implemented some protocol changes in version 3.16 which now means the Windows version is no longer compatible. Something else to fix.

However, with a bit of work I have got my Linux Mint machine working with a USB device hosted on an OpenWRT "Chaos Calmer" router.

OpenWRT installation

The chaos calmer version of openwrt includes the kernel modules for usbip in the kmod-usbip,kmod-usbip-server and kmod-usbip-client packages. However, the userland tools are not available in any package. These therefore had to be built manually. This is done by building openwrt to get the toolchain and kernel setup on the cross-compilation machine and then building usbip independently.

# required packages
opkg update
opkg install kmod-usb-ohci kmod-usbip kmod-usbip-server kmod-usbip-client
opkg install udev usbutils

To setup the build environment I followed the OpenWRT instructions, in my case selecting the TL-WR1043ND device specifically so that I get a MIPS toolchain and the kernel gets configured for my device. We need to support udev so that the libudev library and headers and included in the target tree.

To cross-compile the usbip tools, we need to find the sources and configure for cross-compiling. In kernel 3.18 (as used in chaos calmer) the usbip package has migrated to the main kernel tree so this is now found in build_dir/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2/linux/tools/usb/usbip/. The actual path will vary according to the device selection as this is toolchain dependent. Moving into this directory we must run autogen.sh to setup the configuration utility then run it with a number of additional options to find everything. The following script runs configure with the right options for my device.

#!/bin/bash

BASE=/opt/src/openwrt-chaos
STAGING_DIR=${BASE}/staging_dir
TOOLCHAIN=${STAGING_DIR}/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2
TARGET=${STAGING_DIR}/target-mips_34kc_uClibc-0.9.33.2

PATH=${TOOLCHAIN}/bin:$PATH

env \
  CFLAGS=-I$TARGET/usr/include \
  CPPFLAGS=-I$TARGET/usr/include \
  LDFLAGS=-L$TARGET/lib \
./configure \
  --build=x86_64-unknown-linux-gnu \
  --host=mips-openwrt-linux-uclibc \
  $*

In particular this sets the --build and --host options to enable cross-compiling and adds the linux include folder for the target device.

Running make then creates the tools in the src/.libs dirctory. We need usbip,usbipd and libusbip.so. These need to be copied to the openwrt device and installed. The library needs placing in /lib with some softlinks creating. The utility files can go in /usr/bin. I also found I had to unpack /usr/share/usb.ids.gz into usr/share/hwdata/usb.ids. It might be nice to arrange the tools to read the compressed files directly in the future.

root@OpenWrt:/tmp# ls -l /lib/*usbip* /usr/bin/*usbip*
lrwxrwxrwx    1 root     root            17 Feb  4 21:39 /lib/libusbip.so -> libusbip.so.0.0.1
lrwxrwxrwx    1 root     root            17 Feb  4 21:39 /lib/libusbip.so.0 -> libusbip.so.0.0.1
-rwxr-xr-x    1 root     root         64034 Feb  4 21:39 /lib/libusbip.so.0.0.1
-rwxr-xr-x    1 root     root         45715 Feb  6 15:53 /usr/bin/usbip
-rwxr-xr-x    1 root     root         38300 Feb  6 15:53 /usr/bin/usbipd

I shall have to look into how to setup this as a package, but for now, manual installation will suffice.

Exporting a USB device from OpenWRT

With all the above completed we can use the userland utility to query the available devices and expose them over TCP/IP.

usbip list will list either local or remote devices. We need to use this to identfy the usb bus id for the device to expose.

root@OpenWrt:/tmp# usbip list -l
 - busid 1-1 (1a86:7523)
   QinHeng Electronics : HL-340 USB-Serial adapter (1a86:7523)

usbip bind --busid=1-1 is then used to actually expose the device over TCP/IP. usbipd -D must be run at some point to actually deal with the network communications as well.

Linux Mint installation

On the desktop side we need the kernel drivers and the userland tools. The kernel driver modules are included for Mint 17.2 which is using kernel version 3.16. However the usbip utility needs to be built. To do this we need to get the kernel source cloned. For this kernel version the tools are in drivers/staging/usbip. We need to go into the userspace subdirectory and run autogen.sh to generate the configuration script and then run that and make the utilities. I also had to install the libudev-dev package to satisfy the configure script.

sudo apt-get install libudev-dev
cd /opt/src
git clone https://github.com/torvalds/linux
cd linux
git checkout v3.16
cd drivers/staging/usbip/userspace
./autogen.sh
./configure
make
sudo make install
sudo ldconfig

With the above commands completed the usbip utility is installed in /usr/local/bin and can now be used to query and connect to remote usb devices. I have an arduino clone on the openwrt device that shows up as a usb-serial adapter.

$ usbip list -r 192.168.1.47
Exportable USB devices
======================
 - 192.168.1.47
        1-1: QinHeng Electronics : HL-340 USB-Serial adapter (1a86:7523)
           : /sys/devices/platform/ehci-platform/usb1/1-1
           : Vendor Specific Class / unknown subclass / unknown protocol (ff/00/00)
           :  0 - Vendor Specific Class / unknown subclass / unknown protocol (ff/01/02)

Success! This is the correct USB details for a CH340G chip. The usbip tool can also attach to the remote device and connect it into the local system using usbip attach -r ipaddr -b 1-1. This does not produce any output but looking in the dmesg logs we can see that we have a new virtual controller and the remote device has been registered as /dev/ttyUSB1.

[72041.213180] vhci_hcd: USB/IP 'Virtual' Host Controller (VHCI) Driver v1.0.0
[72052.409413] vhci_hcd vhci_hcd: rhport(0) sockfd(3) devid(65538) speed(2) speed_str(full-speed)
[72052.648913] usb 5-1: new full-speed USB device number 2 using vhci_hcd
[72052.760982] usb 5-1: SetAddress Request (2) to port 0
[72052.782662] usb 5-1: New USB device found, idVendor=1a86, idProduct=7523
[72052.782670] usb 5-1: New USB device strings: Mfr=0, Product=2, SerialNumber=0
[72052.782673] usb 5-1: Product: USB2.0-Serial
[72052.821611] usbcore: registered new interface driver ch341
[72052.821625] usbserial: USB Serial support registered for ch341-uart
[72052.821636] ch341 5-1:1.0: ch341-uart converter detected
[72052.834595] usb 5-1: ch341-uart converter now attached to ttyUSB1

Finally this device can now be used on the desktop system as if it was actually plugged into a local USB port. screen /dev/ttyUSB1 57600 works to view the serial output of this device and it can even be programmed using the Arduino environment. Below is a screenshot of the Arduino serial plotter reading remote data from the OpenWRT USB device.

Once we are finished, sudo usbip detach can disconnect the device. The sudo usbip port command shows details about the currently attached devices, including the vendor id, product id, remote ip and port number.

Conclusion

While this shows some promise, I have found that between these two devices only the serial adapter has worked reliably. A usb stick and a webcam both failed to operate over the usbip link. Hopefully some work has been done on this in more recent kernels. In Raspian for the Raspberry Pi the usbip packages is already setup with the current version of these tools so possibly systems using Linux kernel version 4.x might interoperate more reliably.