3. Two things at once

Published on 17 June 2016

This is a post about pi-zero, usb and explorations.

A series of posts exploring the possibilities of using the Pi Zero as a USB MIDI device. The previous post was "talk to me".

The last post used a convoluted setup of the USB cable to act as the USB gadget and a USB-TTL serial cable to give us an SSH connection into the Pi Zero using a PPP connection.

This post does away with that and provides a way to enable both ethernet and MIDI over the same cable. It's a miracle!

Never rush in

I spent many hours last week grappling with PPP on Linux and OS X and grappling with Mac OS X packet filtering and forwarding.

Then I saw this great writeup by Tobias Girstmair. He proposes a way to configure multiple USB gadget functions through a single USB cable using libcomposite. This allows configuration of the gadget functionality using configfs a way of configuring the kernel by modifying things that look like files. Confusing? Yes!

Get ethernet access

I started by following Andrew Mulholland's super-quick instructions to ethernet access to the Pi Zero. I also enabled internet access to the Pi.

Andrew's instructions mean we have dtoverlay=dwc2 on a single line on the Pi Zero's /boot/cmdline.txt.

Configuring a gadget

This document describes how to use configfs to configure Linux USB Gadgets.

A "gadget" can have multiple "functions". A function is something like ethernet or MIDI. And it seems that you can have multiple instances of each function.

To create a gadget you create a folder in /sys/kernel/config/usb_gadget/<some-gadget-name>/. And then each function is created in the functions subdirectory with the name <function>.<instance>.

<function> is the thing that determines which bit of code is loaded and <instance> is just a unique name for that instance of the function.

So a full path for an ethernet function might be: /sys/kernel/config/usb_gadget/some-gadgets/functions/ecm.usb0 where some-gadgets and usb0 are arbitrary names we've picked.

The documentation lists all the available functions and the configuration properties. The USB gadgets are the various configfs-usb-gadget* files.

When you create an instance, you magically get files created in the directory that gives you read and write access to the gadgets properties.

Configure a gadget and ethernet function

To get started, I adapted the script from iSticktoit's blog post (see below).

This script creates a gadget, sets it's vendor ID and product ID and gives it a human-readable name. The Linux USB website has a full list of Vendor IDs and Device IDs.

I found that putting modprobe libcomposite into the script meant that nothing needed to be put into /etc/modules.

The script also creates an ethernet function so that we can access the Pi Zero via USB, just like we have now.

I created a file in /boot/create-usb-gadgets so I could edit it by inserting the SD card into my Mac.

/boot/create-usb-gadgets has the following contents:

#!/bin/sh

# Load libcomposite
modprobe libcomposite

# Create a gadget called usb-gadgets
cd /sys/kernel/config/usb_gadget/
mkdir -p usb-gadgets
cd usb-gadgets

# Configure our gadget details
echo 0x1d6b > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2
mkdir -p strings/0x409
echo "fedcba9876543210" > strings/0x409/serialnumber
echo "Pi Zero USB Gadget" > strings/0x409/manufacturer
echo "Pi Zero USB Gadget" > strings/0x409/product
mkdir -p configs/c.1/strings/0x409
echo "Config 1: ECM network" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower

# Ethernet gadget
mkdir -p functions/ecm.usb0
# first byte of address must be even
HOST="48:6f:73:74:50:43" # "HostPC"
SELF="42:61:64:55:53:42" # "BadUSB"
echo $HOST > functions/ecm.usb0/host_addr
echo $SELF > functions/ecm.usb0/dev_addr
ln -s functions/ecm.usb0 configs/c.1/

# End functions
ls /sys/class/udc > UDC

We need to make this script executable so that we can run it:

$ chmod +x /boot/create-usb-gadgets

Run on boot

The configuration is not permanent. Restarting the Pi Zero will remove it so we need to make sure that script runs on boot.

We can create a systemd service that will do this. I created /etc/systemd/system/create-usb-gadgets.service:

[Unit]
Description=Create USB gadgets

[Service]
Type=oneshot
ExecStart=/boot/create-usb-gadgets
StandardInput=tty
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Then to enable it:

$ sudo systemctl daemon-reload
$ sudo systemctl enable create-usb-gadgets

Remove the existing g_ether config

We don't need the g_ether config we used at the beginning because our new script will do that for us.

Remove modules-load=dwc2,g_ether from cmdline.txt.

Reboot the Pi. After a short delay, you should be able to ssh pi@raspberrpi.local to access the Pi Zero.

The Mac will think the ethernet adapter is different because we've changed it's name and ID so we can re-bridge the Mac's internet connection via the "Sharing" → "Internet Sharing" preferences pane. The Ethernet gadget will appear as whatever you've configured the name to be in the script above.

MIDI

Now that we're back on the ethernet, we need to figure out how to configure other Linux USB Gadget functions.

I'm interested in the MIDI gadget interface and the configuration document is quite sparse:

What:        /config/usb-gadget/gadget/functions/midi.name
Date:        Nov 2014
KernelVersion:    3.19
Description:
        The attributes:

        index       - index value for the USB MIDI adapter
        id      - ID string for the USB MIDI adapter
        buflen      - MIDI buffer length
        qlen        - USB read request queue length
        in_ports    - number of MIDI input ports
        out_ports   - number of MIDI output ports

Since the script above configured the ethernet interface, it's useful to compare the two:

What:        /config/usb-gadget/gadget/functions/ecm.name
Date:        Jun 2013
KernelVersion:    3.11
Description:
        The attributes:

        ifname      - network device interface name associated with
                this function instance
        qmult       - queue length multiplier for high and
                super speed
        host_addr   - MAC address of host's end of this
                Ethernet over USB link
        dev_addr    - MAC address of device's end of this
                Ethernet over USB link

Here's where using configfs becomes useful. You can create "files" on what appears to be a "filesystem", which are then magically turned into USB driver calls. This allows us to iteratively try out the config on the Pi Zero live to see what happens.

So, I booted the Pi Zero with the Ethernet configuration and ran variations of the commands below until I worked out what did the trick.

For example, I tried created a gadget function midi with the instance name usb0:

    $ cd /sys/kernel/config/usb_gadget/usb-gadgets
    $ sudo mkdir -p functions/midi.usb0
    $ ls functions/midi.usb0/
    buflen  id  index  in_ports  out_ports  qlen

Creating this directory automatically exposes some "files" that map to the items in the MIDI documentation above.

$ cat functions/midi.usb0/in_ports
    1

This suggests that the function is initialised with some defaults.

However cat functions/midi.usb0/id returns nothing so maybe we should set the id item:

    $ echo "pi" > functions/midi.usb0/id

Then, you need to link the function so it's available in the same configuration as the ethernet:

$ ln -s functions/midi.usb0 configs/c.1/

At this point nothing happened, because I needed to disable and re-enable the gadget by writing to /sys/class/UDC.

$ sudo su
$ echo "" > UDC && ls /sys/class/udc > UDC
$ exit

After doing this I could see the MIDI device in MIDI monitor. Hurray!

Create MIDI device

Now that it works, we could turn that into config for the /boot/create-usb-gadgets script. I added this after the Ethernet config but before the # End functions lines:

# MIDI gadget
mkdir -p functions/midi.usb0

# id     - ID string for the USB MIDI adapter
echo "pi-zero-midi" > functions/midi.usb0/id
ln -s functions/midi.usb0 configs/c.1/

Reboot the Pi Zero and when it boots you'll be able to SSH into it and it will appear as a USB MIDI device and it will be powered by the same USB cable!

Demo

So, after ssh pi@raspberrypi.local I downloaded a nice MIDI file of Titanic - My Heart Will Go On (Techno Mix) from MIDI World onto the Pi.

    $ curl http://www.midiworld.com/download/3861 > techno.midi

Then I found out what the MIDI device is listed as:

    pi@raspberrypi:~ $ aplaymidi --list
     Port    Client name                      Port name
     14:0    Midi Through                     Midi Through Port-0
     20:0    f_midi                           f_midi

It seems to have moved to port 20, I reckon those config items could change that.

Then, I can play the MIDI file over the USB interface into the Web MIDI synth running on Google Chrome like this:

$ aplaymidi --port 20:0 techno.midi

The result? Behold the awful sound. 🙉

The terminal window at the top is the Pi Zero playing the MIDI and the Chrome window on the bottom is on the Mac.

Now we have a much better development setup with a single cable allowing us to access the Pi and to access the MIDI device. There are many other supported USB gadget functions so this is really promising.

Next, I need to work out how to send custom MIDI commands, rather than just MIDI files.