Close

On Windows, drivers and USB gadgets

A project log for Gadget

USB-gadget-based Bus Pirate-style hacker toolkit for Linux boards (targeting Pi Zero for now)

usedbytesusedbytes 03/23/2016 at 22:534 Comments

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:

Slim Pi Zero Case

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/UDC
This 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\@ttyGS0
That 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 up
and 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 ms
Huzzah!

Next step is to autoconfigure the network, probably by running a DHCP server on the Pi

Windows

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.

Discussions

Eric Hertz wrote 03/28/2016 at 03:15 point

Hah, yeah, still not a Pi-owner, here, but, besides looking slick, I'd bet that case would come in handy! And, at that point, it kinda makes the PiZero look a bit more like I've been visualizing it, like a chip that happens to have an OS running on it ;) Great gadget-goings, as well!

  Are you sure? yes | no

Craig Hissett wrote 03/24/2016 at 11:19 point

This is sweet! I love that case and the right angle pins.

If this can work driver free I'd do this in a heartbeat; it'll let me access my Pi on my works PCs!

  Are you sure? yes | no

usedbytes wrote 03/25/2016 at 22:22 point

Thanks, just uploaded the files for the case if that's any use to you: https://hackaday.io/project/10540-gadget-case

I've not had a chance to have another look at drivers, but I guess worst-case I can do what 3G dongles do and use USB modeswitch - when you plug the device in it shows you a USB mass storage with the drivers on, then you can mode-switch into the ethernet/serial shebang.



I really want to avoid that if possible though, because it's filthy ;-)

  Are you sure? yes | no

Craig Hissett wrote 03/26/2016 at 00:09 point

That's great man, thank you - will have a look at getting one or two cut somewhere soon!

I'd normally say there's nothing wrong with filthy ha ha, but in this instance I wholeheartedly agree. A clean, plug and play operation will be beautiful for this. Im going to do some more research and see what I can find on this.

  Are you sure? yes | no