Monday 31 May 2010

Weekly report - Week 1

Status:
  • Managed to build Angstrom's console-image, as well as the kernel (outside OpenEmbedded), both work well on the BeagleBoard.
  • Wrote a prototype kernel proxy driver, and managed to get USB data to be forwarded properly between a USB mouse and a Linux PC, with the BeagleBoard in between (git tree here, but no really usable yet).
  • Modified the MUSB controller driver to disable high-speed USB if the gadget asks for full-speed (commit here).
Plans:
  • Get the proxy driver more generic, and get completely rid of the static descriptor copy.
  • Dynamically connect endpoints, according to the endpoint descriptor.
Risks:
  • Differences between low-speed, full-speed, high-speed, for example in the meaning of some descriptor fields (e.g., bInterval for endpoints): since the BeagleBoard MUSB cannot operate in low-speed, this would require some "rewriting" of the descriptors to get the device to work.
  • Also, my Linux PC asks for the Device_Qualifier descriptor (a high-speed only descriptor) when connected to the BB, but not when connected directly to the mouse, even though the device is advertised and detected as full-speed.

Thursday 27 May 2010

A first device works: USB mouse

Today I managed to get the following to work: I connect a USB mouse to the host port of my BeagleBoard (via a hub, since the BeagleBoard does not support low-speed USB), and the slave USB port of my BeagleBoard to my PC. Then, I can use that mouse to control the cursor of my PC.

For that, I have a "proxy" kernel driver on the BeagleBoard, that is registered both as a USB device driver (on the EHCI side), and as a USB gadget driver (on the MUSB/OTG side).

Most USB drivers register at the interface level (that is, as a struct usb_driver), so control only a part of a device (one device could have multiple interfaces, for multiple functions). That is not what we want, so, instead, the proxy driver registers as a struct usb_device_driver, that claims the device at a lower level. Normally, there is only one of such low-level drivers, found in drivers/usb/core/generic.c. This generic driver takes care of all USB devices attached to the system, and looks for interface-level drivers for each of the interfaces of these devices.

Since the generic driver claims all new devices (and never releases them), the trick is to unbind the device from the generic driver, so that it can be claimed by our proxy driver. This is done using a command like:
echo 1-2 > /sys/bus/usb/drivers/usb/unbind
Once "unbound", our driver is able to claim it.

On the gadget side, there is nothing special: the driver is registered as a struct usb_gadget_driver. Then, we do everything we can to make the PC believe that it is connected directly to a USB mouse. For that purpose, we reply to GET_DESCRIPTOR control requests from the PC with the following device descriptor:
Bus 001 Device 005: ID 046d:c00e Logitech, Inc. M-BJ58/M-BJ69 Optical Wheel Mouse
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x046d Logitech, Inc.
idProduct 0xc00e M-BJ58/M-BJ69 Optical Wheel Mouse
bcdDevice 11.10
iManufacturer 1 Logitech
iProduct 2 USB-PS/2 Optical Mouse
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 98mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 1 Boot Interface Subclass
bInterfaceProtocol 2 Mouse
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.10
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 52
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0004 1x 4 bytes
bInterval 10
Device Status: 0x0000
(Bus Powered)
Currently, I have a copy of the descriptor statically defined in the driver, but of course, in the future, this descriptor will have to be read from the device itself.

Then, whenever the drivers receives a control request it does not know how to answer (for example, a class-specific HID control request), it forwards the request to the device, waits for the reply, then writes back the reply to the PC.

With that, the mouse is fully recognized by the PC, but nothing happens when you move it, because mouse events use interrupt transfers on endpoint 1. That can be fixed by listening for interrupt transfers from the device, and forwarding those to the PC.

At that point, the cursor moved, but with some huge latency: clicking was fast, but there was a delay of a few seconds when moving the mouse around... This was due to the following: the mouse is a low-speed USB device, and its endpoint descriptor asks the host to poll it for events every 10 ms (bInterval = 10). However, the MUSB device controller on the BeagleBoard appears as a high-speed device (there is probably a way to force the controller in full-speed mode, but the current driver does not seem to support that), and the definition of the polling interval, for a high-speed device, is 0.125 ms * 2^(bInterval-1). By setting bInterval to 10, the BeagleBoard device controller only got polled every 64 ms, so, when I moved the mouse, this created a lot of events, which got buffered somewhere in the BeagleBoard, until they could be released to the PC.

I fixed the problem by modifying the descriptor, and putting bInterval = 7, that is a poll every 8 ms.

I can write up some instructions about how to get this setup to work, but I guess this is not so useful if you do not have the same mouse as I have, or a similar enough mouse at least...

All the kernel modifications have been pushed in my git repository. The proxy driver code, in particular, can be found here, but the code is still far from clean and full of TODO/FIXME tags.

Tuesday 25 May 2010

Received my BeagleBoard!

This morning I received a package from Digikey (thanks to Cathy!), with a BeagleBoard, a 5V power adapter, and a IDC10-DB9 cable.

I connected the BeagleBoard to my PC, using the ICD10-DB9 cable, followed by a USB->RS232 adapter. Then I powered it on, some leds blinked, and, nothing on the terminal... I then tried connecting it to another PC, with a serial port, nothing again.

I started wondering if I needed a null-modem cable, so I checked the pinout on the ICD10-DB9 to check if it wasn't one already. And I realised that the pinout was completely non-sensical (ground connected to RX, ring signal connected to TX)... Since the colors on the RS232 wires are not standardized, it looks like the people who built the cable did not realize that the colors changed with their latest supply of cables...

It seems like the problem affect quite a few people who ordered the cable recently, so here is how to fix it:
  • Take out all the wires from the ICD10 connector (easy to do with a needle).
  • The red and green wires should be together on one pin. Cut the green one.
  • Then, connect the red wire (TX, pin 3) on pin 2 of the BeagleBoard (RX).
  • The brown one (RX, pin 2), on pin 3 of the BeagleBoard (TX).
  • The yellow one (GND, pin 5), on pin 5 of the BeagleBoard.
  • Leave the other wires dangling, you don't need them (beware of shorts though, I taped them to avoid problems).
A picture of what it looks like in the end:


Note that this is only for this batch of cables, and it's always better to check the wiring with a ohmmeter, if you have one (the RS232/DB9 pinout can be found here).

Monday 24 May 2010

Weekly report: Week 0

First, I should receive my BeagleBoard tomorrow, so I can get up to speed on the project.

Status:
  • Tried to build Angstrom (the latest git version), but ran into disk space problems (20+ gigs do not seem to be enough). Since I only need to do kernel development, I can also use demo images, and pre-built toolchains.
  • Downloaded the pre-built toolchain (angstrom-2010.4-test-20100421-x86_64-linux-armv7a-linux-gnueabi-toolchain-qte-4.6.2.tar.bz2).
  • Built a Beagleboard kernel based on angstrom-linux/beagleboardXM, using that toolchain.
  • Formatted a 1GiB SD-card according to the guide, and copied the content from the demo image to it. Since my SD-card is only 1GiB, and the image is 2GiB, I "dd"-ed the first FAT partition (to make sure that files are in the right order), but copied all the files on the ext3 partition.
Plans:
  • Try to finish building Angstrom (I freed some disk space, and maybe I messed up somewhere).
  • Test the SD-card I formatted.
  • Replace the kernel on the SD-card with the one I built.
  • Use the newest root filesystem (if I manage to finish building it).
  • Test the USB gadget and EHCI controllers with a few different drivers/devices.
  • Start working on the proxy driver (i.e., the project itself).
Risks:
  • Unable to build Angstrom: nevermind, I can use demo images (at least for now).
  • ?

Tuesday 18 May 2010

Device emulation

So, as it seems that hub emulation cannot be done, here is how device emulation would work.

We need a kernel driver that lies between the slave (gadget, MUSB) port and the master (EHCI) port. This "proxy" driver will be registered both as a USB device driver, and as a USB gadget driver. It will then "simply" forward USB packets back and forth between the 2 interfaces.

First, the sniffed device is connected to the master port of the BeagleBoard. The Linux kernel assign an address to it, reads its descriptors, select a configuration, and tries to find a driver for the new device. Reading the descriptors and assigning the address are probably fine (it needs to be done anyway, and it should be a rather standard operation), but we probably do not want to set a configuration, and clearly do not want any driver to initialize the device.

Then, we want to bind the device to our proxy driver (this can be done using echo [device] > /sys/bus/usb/drivers/xxx/[un]bind ).
The proxy driver will then copy the descriptors, and activate the slave port. The host PC enumerates the new device, sets an address to it, and obtains the copied descriptors (and hopefully believes that it is connected to the actual device).

Some additional control transfers (i.e. endpoint 0) may happen, such as configuration selection. These need to be forwarded to the real device.

To be able to actually use the device, other endpoints (in addition to the mandatory endpoint 0) need to be configured in the gadget driver, and registered on the device side, so that the data can be forwarded. The required endpoints can be obtained from the device descriptor (below, the (very simple) descriptor for a mouse):

Bus 006 Device 003: ID 046d:c018 Logitech, Inc. Optical Wheel Mouse
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x046d Logitech, Inc.
idProduct 0xc018 Optical Wheel Mouse
bcdDevice 43.01
iManufacturer 1 Logitech
iProduct 2 USB Optical Mouse
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 1 Boot Interface Subclass
bInterfaceProtocol 2 Mouse
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 52
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0005 1x 5 bytes
bInterval 10
Device Status: 0x0000
(Bus Powered)

From that descriptor, we can see that we need to configure the gadget driver to handle outgoing interrupt transfers on endpoint 1, and that we need to listen for interrupt transfers on endpoint 1, coming from the device.

My plan for the following weeks:
  • Manually copy the descriptor of a simple device (a mouse/keyboard would do)
  • Handle control transfers forwarding
  • Connect one endpoint on the slave and master sides (EP 1 IN, interrupt for mouse/keyboard)
  • Forward interrupt transfers
Some other thoughts:
  • Mice and keyboards are usually Low Speed devices, which is not supported by many gadget controllers (including MUSB), does it hurt if we advertise them as Full Speed devices on the gadget-host link?
  • If a device has multiple configurations, an endpoint may have different types and/or directions depending on the configuration, we need to be able to handle that (and intercept SET_CONFIGURATION requests).
  • Some devices change descriptor (and disconnect/reconnect) after a firmware load (e.g., FX2-based devices), we need to find a way to handle that, and reattach to the "new" device automatically.

Saturday 15 May 2010

Device vs hub emulation

There are 2 main alternatives to implement the USB sniffer:
  • Device emulation: Emulate the sniffed device itself: That is, pretend that we are the device itself, and forward packets back and forth between the device and the host.
  • Hub emulation: Emulate a USB hub. That is, act as if we were a USB hub, with several ports. One (or more) of these ports would be connected to the device to be analysed. Other ports would still be available for other USB gadgets (e.g., Ethernet, serial gadgets).
For the hub emulation, I should first try to explain how a USB hub works (or at least, how I understand it works, skipping details):
An USB hub is a simple repeater: that is, each packet coming from the host is broadcasted to all the devices connected to it. An USB token packet contains, among other things, an address field. As each device is assigned an unique address, it can safely ignore transfers that are not meant for it. Packets coming from the device are not broadcasted (it would not make any sense), and are simply forwarded back to the host.

When a new device is connected to a hub, the hub informs the host. The new device, by default, takes address 0, and the host can then initiate basic communication with it using that address. One of the first thing the host does it to send a SET_ADDRESS command to the device, the new device will then answer on that new address (and not on address 0 anymore).

Now, the question is, can we implement this on the BeagleBoard, with the MUSB controller? For this, the MUSB controller needs to be able to listen on all possible USB addresses (similar to a promiscuous mode on an Ethernet controller). Depending on the address, we could then forward the packet to the sniffed device, or on any other virtual gadget device (since we have more processing power than a normal USB hub, we can be more clever and avoid broadcasting the data to every device).

From what I see in the code of the MUSB controller driver, there seems to be only one register containing the USB address to listen on. This register (F_ADDR) is updated at line 706, after a SET_ADDRESS command is received from the host:
 if (musb->set_address) {
musb->set_address = false;
musb_writeb(mbase, MUSB_FADDR, musb->address);
}
Therefore, it seems like the MUSB controller is only able to listen on a single address, so it looks like it may not be possible to implement a USB hub.

Thursday 13 May 2010

Project Goal

The goal of this Google Summer of Code 2010 project is to have some hardware USB sniffer on the Beagle Board. The host PC is connected on the slave USB port of the Beagle Board, and the device to be analysed on the host port of the Beagle Board.

The goal is to have a completely transparent solution to forward USB packets from the host PC to the device (and back), while logging the data: that is, neither the host PC nor the device must be able to realise that there is a sniffer in the middle, intercepting packets.

Software solutions to capture packets already exist (e.g., USB Snoopy on Windows, USB monitor on Linux), but a hardware solution would have the advantage of:
  • Not requiring any software modification
  • Supporting proprietary OSes, where you may not have access to the software
  • Allowing debugging of a new USB stack: If, let's say, you are developing a new USB stack for your preferred operating system, any software logging solution would obviously be as reliable as the stack you are currently developing, hence the usefulness of a hardware solution.
  • Depending on the capabilities of the OTG and EHCI chips, being able to capture USB frames at a lower level, and maybe monitor for transmission errors for example.