So this week I got my new Pi Zero set up (that's right! I have two! Not even sorry.)
I made a little case - whole thing is less than 10mm thick (full details forthcoming Update details here: https://hackaday.io/project/10540-gadget-case) and got to work on USB gadget support:
Actually setting up the gadget was easy. I enabled all the relevant config options in the kernel (CONFIG_USB_DWC_PERIPHERAL, CONFIG_USB_GADGET, CONFIG_USB_F_* and so on) and then you configure it in "configfs".
I'd never used configfs before, but it's a magical place. Creating a directory in configfs triggers some machinations in the kernel, creating a data structure (or structures) depending on where you create the directory. For USB gadget, you create directories in /sys/kernel/config/usb_gadget
For the full lowdown, the kernel documentation is long but useful: https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt
I wrote a little script which creates a combination ("composite") gadget with an RNDIS ethernet port and a ACM serial port:
#!/bin/bash CONFIGFS=/sys/kernel/config/usb_gadget GADGET=$CONFIGFS/gadget # Create a gadget mkdir $GADGET # Set the VID/PID - this is a pid.codes test code. I'll get a proper one later echo 0x1209 > $GADGET/idVendor echo 0x0001 > $GADGET/idProduct # Set strings - 0x409 is a magic number in the USB spec meaning "English (United States)" mkdir $GADGET/strings/0x409 echo "00000001" > $GADGET/strings/0x409/serialnumber echo "usedbytes" > $GADGET/strings/0x409/manufacturer echo "Gadget" > $GADGET/strings/0x409/product # Create a config called 'c.1'. Configs can have multiple functions. A device can have multiple configs. Only one config can be enabled at a time mkdir $GADGET/configs/c.1 mkdir $GADGET/configs/c.1/strings/0x409 echo "Config 1" > $GADGET/configs/c.1/strings/0x409/configuration echo 500 > $GADGET/configs/c.1/MaxPower # Create ACM and RNDIS functions, and add them to config 'c.1' # The 'acm' and 'rndis' parts of the directory name are important, they set the type of function. Other possibilities are 'lun' for mass storage, 'hid' for human interface.... mkdir $GADGET/functions/acm.usb0 ln -s $GADGET/functions/acm.usb0 $GADGET/configs/c.1 mkdir $GADGET/functions/rndis.0 ln -s $GADGET/functions/rndis.0 $GADGET/configs/c.1 # Finally, enable the gadget by setting its USB Device Controller. The 20980000.usb is the name of the (only) UDC on the Raspberry Pi echo 20980000.usb > $GADGET/UDCThis works a treat. The device enumerates on Linux fine (as long as you plug the cable into the right socket on the Zero... that took me longer to notice than I'd like to admit).
On the Pi, the two devices look like this:
# ls -l /dev/ttyGS0 crw------- 1 root root 251, 0 Feb 11 16:40 /dev/ttyGS0 # ip link 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: sit0@NONE: <NOARP> mtu 1480 qdisc noop qlen 1 link/sit 0.0.0.0 brd 0.0.0.0 3: usb0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop qlen 1000 link/ether 72:4b:a5:91:2c:ef brd ff:ff:ff:ff:ff:ff/dev/ttyGS0 is the serial port (Gadget Serial 0 I guess), and network device usb0 is the ethernet device.
On the host, they show up as /dev/ttyACM0 (because it's an ACM serial device) and as network interface usb0 again.
I wrote a simple one-shot systemd service to create my gadget at boot:
# cat /etc/systemd/system/gadget.service [Unit] Description=Initialise USB gadget [Service] Type=oneshot ExecStart=/root/make_gadget.sh [Install] WantedBy=multi-user.target # systemctl enable gadget Created symlink from /etc/systemd/system/multi-user.target.wants/gadget.service to /etc/systemd/system/gadget.service.
Then to get a login shell on the USB serial port, simply enable the systemd serial-getty service for the ttyGS0 serial device:
# systemctl enable serial-getty\@ttyGS0That will start automatically at the next boot and give you a login prompt on the USB serial port.
Ethernet works too. For now I did some manual configuration. On the Pi:
# ip addr add 192.168.3.142/24 dev usb0 # ip link set usb0 upand on the host:
# ip addr add 192.168.3.1/24 dev usb0 # ping 192.168.3.142 PING 192.168.3.142 (192.168.3.142) 56(84) bytes of data. 64 bytes from 192.168.3.142: icmp_seq=1 ttl=64 time=0.761 ms 64 bytes from 192.168.3.142: icmp_seq=2 ttl=64 time=0.331 ms 64 bytes from 192.168.3.142: icmp_seq=3 ttl=64 time=0.610 msHuzzah!
Next step is to autoconfigure the network, probably by running a DHCP server on the Pi
I had thought that both RNDIS and ACM serial should work driver-free on Windows. On one Windows computer, neither worked. On another, the serial port showed up and worked fine (eventually) but the RNDIS was "unknown". On both Windows computers, it showed the "USB device has malfunctioned" message.
So, I need to do some more investigation on the Windows front. I know that there are drivers, but I would really like Gadget to be driver-free if possible. This is a bit of a pain, because I don't actually have any boxes with Windows on, and I don't own any Windows licenses either. Huff.
I haven't tried a Mac yet, but I think it should more or less work out of the box. T.B.D.