Replicating a USB connection over ethernet including target board 12v power and USB 5v power control.
When automating software tests deployed to embedded target devices it's generally necessary to be able to control the target power/reset and also any other external interfaces (e.g. USB) that affect the testing. Doing this with commodity scalable hardware is a well-known problem for building test farms, but even with one or two boards on your desk it can get fiddly quite quickly. It's also quite cool for being tidy and possibly scable if you can send more functions down the same wire. This was a proof-of-concept for the LAVA On Your Desk boF session at Linaro Connect HKG18.
CE Edition 96Boards (e.g. Hikey, DB410C) have USB OTG ports which are used for loading images and firmware. However, when powered, the OTG port disables the USB host ports, which it the primary route for connecting ethernet via USB-ethernet adaptors. You can't run deploy automated Linux-based tests that assume network functionality to these boards without USB OTG power switching. To work around this behaviour in an automated test setup, the sequence of operations needs to look something like this:
- Power on the OTG port
- Power up the board
- Load the image over OTG
- Power off the board
- Power off the OTG port
- Power on the board
- Boot the test
- Test connects to the network
- A Linux PC client running some kind of test framework
- An embedded server controlling power and USB
- A target device-under-test (DUT)
- PC running Debian and a local instance of LAVA
- RPi with Motor Hat switching power and USB OTG on the target (BBB should work also)
- Hikey6220 DUT
There's a data connection and a control connection:
- The data connection is USB-over-IP - replicating a USB connection (in this case fastboot) on a remote device
- The control connection is XML-RPC to control power and port switching
The RPi runs an XML-RPC server which executes local functions to control:
- Power switching via 12V tolerant motor H-bridge board (Adafruit)
- USB on-off for OTG via local RPi root hub control
- RPi USB (for OTG) shared over IP using usbip
- The USB binding needs to be actively managed because switching operations break it
- A client on the PC invokes the XML-RPC calls on the RPi
On boot, the RPi server connected to the target finds the USB-connected fastboot (Google) device and shares it over usbip. For more info, look at the docs for usbip. You need to load a kernel module on both the server and client IIRC. See the for more info on usbip.
At start up on the RPi server:
- load the
usbip-host
module - start the
usbipd -D
daemon - start the xmlrpc server
Each time you re-power the OTG port via the xmlrpc call (this is called the ‘pre-power’ command in LAVA-speak)
On the RPi server:
- power on the port
- Wait for the Hikey to initialise
- list the locally connected usb devices
- find the Google one
- bind the Google one to usbip
- return
On the PC client:
- call the USB port power-on RPC command & assume we wait for it to return
- list the remotely connected usb devices (dispatcher context, fortunately) and find the Google one/only shared one attach it locally
status() - Return status info (String/Binary)
get_otg() - Query OTG power state (String/Binary)
set_otg(state) - Set OTG power state
get_pwr() - Query 12V power state (String/Binary)
set_pwr(state) - Set 12V power state
pi: $ sudo apt-get install usbip
pi: $ sudo usbip list -l # gives a list of local usb devices
pi: $ sudo modprobe usbip-host
pi: $ sudo usbipd -D
See hub-ctrl.c
host: $ sudo modprobe vhci-hcd
To switch 12v I use the Adafruit MotorHAT. It's a PWM motor control board. Most of the work was to figure out how to NOT do PWM but just use the bridge transistors to switch DC 12v on/off. Adafruit_MotorHAT - Adafruit_PWM_Servo_Driver
commands:
connect: telnet localhost 7002
hard_reset: python /usr/bin/rpcusb_client.py 12v reset
soft_reset: fastboot reboot -s 547E6F8B000B090B
power_off: python /usr/bin/rpcusb_client.py 12v 0
power_on: python /usr/bin/rpcusb_client.py 12v 1
pre_power_command: python /usr/bin/rpcusb_client.py otg 1
pre_os_command: python /usr/bin/rpcusb_client.py otg 0
IPs are static at the moment