Sunday, 27 June 2010

Weekly report - Week 5

  • Managed to fix the throughput issue with my USB flash device. I now get 19.11 MB/sec, close to the maximum throughput when the USB device is directly connected to the host.
    In my previous version, I used only one urb/request pair per endpoint: when data came from the device (through an urb), it was forwarded to the host (as a request), and the driver would only resubmit the urb when the data was properly sent to the host.

    • I fixed this by having 8 urb/requests submitted to the USB controller driver, to make sure that there is always some buffer available to write incoming data to. This alone improved the performance to 12.79 MB/sec.
    • The second thing I did is to have a larger buffer per urb/request: instead of allocating 512 bytes (i.e. wMaxPacketSize) per urb/request, I allocated 4096 bytes. I guess that this reduces the number of interrupts (and the number of times my handlers are called), giving an extra performance gain. This should also be transparent to the host/device, as transfers larger than wMaxPacketSize are split into smaller packets of size wMaxPacketSize (except the last one). Since our buffer size is a multiple of wMaxPacketSize, this should not cause problems.

  • SET_INTERFACE command works (this is a prerequisite for isochronous transfers). This was a bit tricky, as disabling endpoints cannot be done in interrupt context, so I submit a work_struct to do that in process context.
  • I started working on isochronous transfers. I got clean audio coming from the microphone integrated in a webcam. The video still does not work, but I am working on it.
  • Fix isochronous transfers, as the urb buffer is filled in a special way in these cases (I'll write about that next week), my code works by chance with the audio from the webcam.
  • Code cleanup, again. Some parts are unstable again, causing oopses and warning, after some of my recent updates.
  • Maybe, collect statistics on urb/request usage, to see how many urb/requests are needed for maximum performance.
  • From what I see in MUSB, the driver does not support high-bandwidth endpoints (i.e. isochronous transfers with maxPacketSize > 1024 bytes), that are used by High-Speed device. The driver may need to be updated.
  • Bandwidth allocation: with this proxy model, there is nothing much with can do if bandwidth allocation fails on the BeagleBoard. If the host believes enough bandwidth is available, it will use high-throughput interfaces, and there is nothing that can be done on the BeagleBoard to prevent that, in case there is not enough bandwidth available on the EHCI controller of the BeagleBoard. This causes problems with the video on the USB webcam, but the configuration option CONFIG_USB_EHCI_TT_NEWSCHED seems to help, though.

Sunday, 20 June 2010

Weekly report - Week 4

  • Did some code cleanup, hopefully the code should not crash anymore, and be free of memory leaks. Instructions on how to use my code can be found here, and you should use this branch of my git tree.
  • Documented a bit more the data flow, in the code.
  • Tested more systematically with a few different devices. A table of those devices can be found on the wiki page.
  • Improve performance with the USB flash device (current speed: 3.77MB/sec, whereas we can get as high as 19.76 MB/sec without proxy).
  • Isochronous transfers (see last week).
  • Risks from last week are still around.

Sunday, 13 June 2010

Weekly report - Week 3

  • I now have a more general proxy driver, that should work with any device, as long as it does not use any isochronous endpoints, and does not require any change in configuration or interface (i.e., only one SET_CONFIGURATION control request is used, and no SET_INTERFACE).
  • Managed to get a DVB-T tuner to work properly (mine only uses bulk transfers). There is no noticeable difference between connecting the tuner directly to a PC, or through the BeagleBoard, even though only a single transfer is buffered in the proxy driver.
  • Code cleanup. There are frequent kernel oops (notably upon module unloading), a few memory leaks I know about, and the code is lacking error checking in many places.
  • Some documentation about the data flow in the code (a transfer goes through a few different callbacks, on the gadget and device sides, and it is a bit hard to follow what is happening by looking at the code).
  • Isochronous transfers seems to work differently from control/interrupt/bulk transfers. I need to look at that, and implement them (I have a USB webcam and a headset, which would make good test cases). Since isochronous endpoints cannot be part of the default interface of a device, I need to implement SET_INTERFACE as well.
  • Endpoints maximum packet size. With the current FIFO configuration, the MUSB block probably only supports a maximum of 512 bytes per transfer. More would be needed for the webcam to work (768 at max, the standard allowing up to 3072 in High-Speed mode).
  • I tried the g_audio gadget, which uses isochronous transfers, but experienced some corruption (the music plays, but with noticeable corruption), I do not know if it is due to the MUSB, or to the g_audio gadget.
  • Bandwidth allocation. I picked some functions from the USB core to initialize the USB device and its endpoints correctly, but I may have left some parts aside, one of them being the function that allocates USB bandwidth.

Wednesday, 9 June 2010

Bidirectional communication with an Arduino

I now have transparent communication with an Arduino (programming the device work, as well as serial communication with the loaded program). You can find the proxy driver code here, or fetch the whole branch here.

Basically, the Arduino appears as a USB->serial converter to the host, with 2 bulk endpoints, with directions IN and OUT (for both directions of the serial transfers):

Bus 007 Device 120: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0403 Future Technology Devices International, Ltd
idProduct 0x6001 FT232 USB-Serial (UART) IC
bcdDevice 6.00
iManufacturer 1 FTDI
iProduct 2 FT232R USB UART
iSerial 3 A800evGn
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 32
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 90mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 255 Vendor Specific Subclass
bInterfaceProtocol 255 Vendor Specific Protocol
iInterface 2 FT232R USB UART
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Device Status: 0x0000
(Bus Powered)

The first endpoint (EP 1 IN), for communication from the Arduino to the host, worked almost immediately: apart from the transfer type, this is the same as for HID devices.

Getting the second endpoint (EP 2 OUT) to be configured properly on the gadget side was a little tricky. In fact, the infrastucture provided in drivers/usb/gadget/epautoconf.c, namely the function usb_ep_autoconfig, does not allow to choose an endpoint address: This function looks at the list of endpoints provided by the gadget controller driver, and simply selects the first endpoint that supports the request transfer type, direction and maximum packet size (here, Bulk OUT, 64 bytes). In our case, the function selects EP 1 OUT, while the host still thinks transfers should be done on EP 2 OUT.

To get it to work, I had to rewrite usb_ep_autoconfig (see find_gadget_endpoint), and I was a little horrified to see the way gadget controllers endpoints are advertised: Each endpoint structure has a char* name field, which tells the endpoint address, direction, and possibly the supported transfer types. To find EP 2 OUT, the function has to go through all the endpoints, until it finds one whose name is ep2out. A bit kludgy, but well, it works, at least for the MUSB controller...

Data transfers are handled differently, whether they happens on a IN, or OUT, endpoint. For IN endpoints, we follow the following procedure:
1. Initialization (see bridge_endpoint): We submit an URB (USB request block) to the device, asking for data.
2. Device callback (see device_epin_irq): We got some data. Copy it to a request (the gadget equivalent of an URB), and submit it.
3. Gadget callback (see gadget_epin_complete): The request was submitted correctly. Resubmit the URB, so we can receive the next packet.

OUT endpoints are handled similarly: Upon initialization, a request is submitted (bridge_endpoint), the gadget callback (gadget_epout_complete) submits an URB. When the URB completes (device_epout_irq), the request is resubmitted.

This method, that is, with only one URB/request "flying" at the time, seems to work well for low throughput applications, but buffering may be required for higher throughput.

Sunday, 6 June 2010

Weekly report - Week 2

  • Modified the proxy driver to be more generic: descriptors requests are forwarded to the device, and the descriptors are parsed (and modified if required) on the fly: but still, only interrupt transfers from the device to the host on endpoint 1 are supported.
  • Ported my set of patches to the kernel 2.6.35-rc1 (i.e., the latest vanilla tree): a few functions got renamed, but nothing major. I compiled and ran that kernel on the BeagleBoard. I had to tweak some files to get the MUSB block to accept being run in peripheral-only mode (commit), but the MUSB block seems buggy (even with the Ethernet gadget), so I gave up on that. From a discussion on #beagle, it seems like it would be better to use the linux-omap git tree (but I don't know which branch), and apply "some patches" from Angstrom.
  • Started working with a new device: the FTDI USB->Serial converter found, for example, on Arduino boards. Device->PC communication uses endpoint 1, in bulk mode, and worked with little modifications of the existing code.
  • Get PC->Device communication to work with the Arduino, which uses endpoint 2 in bulk mode (to get at least one OUT endpoint working).
  • From there, dynamically connect all endpoints advertised in the configuration.
  • So far I've been targeting kernel 2.6.32 (from angstrom-linux/beagleboardXM), but at some point I would like to target the latest kernel, so I would need to get the MUSB block to work properly with a recent kernel.
  • I have encountered strange problems with a keyboard: pressing num/caps lock only takes effect at the next key stroke, I need to investigate this.

Wednesday, 2 June 2010

Towards a more general solution

I spent the last few days generalizing my proxy driver, in such a way that most HID devices (mice, keyboards), should now be working. Actually, any device that uses only endpoint 1 in interrupt mode and IN direction should be supported by the current implementation.

There are no static descriptors hard-coded in the driver anymore, so GET_DESCRIPTOR requests are intercepted, and the configuration descriptors are parsed and cached (using functions found in the kernel). Also, the descriptors are modified on the fly, to compensate for the fact that the communication between the PC and the BeagleBoard happens in Full Speed, while the BeagleBoard-device communication uses Low Speed.

Two fields are dynamically modified:
  • bMaxPacketSize0 in the device descriptor. It seems like the MUSB driver only support 64 bytes as maximum packet size (which is what is mandated by the High Speed mode), while the mouse uses 8 bytes (the only possible value for Low Speed devices). In theory, Full Speed devices can advertise a maximum packet size between 8 and 64 bytes, but the MUSB driver does not seem to support that.
  • bInterval in endpoints descriptors. As mentionned before, the meaning of that field varies with the device speed.
To get the proxy driver to work, you need to follow these steps:
  • Clone my kernel git tree. Use the backup-20100602 branch.
  • Compile the kernel with the default beagleboard configuration (see here). You just need to add CONFIG_USB_G_PROXY=m. I also disabled MUSB in host and OTG mode, as well as USB suspend, but this may not be necessary.
  • Install the new kernel on the board.
  • Clone the helper scripts git tree, and copy the content of the arm directory to the BeagleBoard (you need to modify load/setup scripts if you do not have have a copy of musb_hdrc.ko and g_proxy.ko in the same directory).
  • Run ./setup on the BeagleBoard, this will unload the g_ether gadget driver.
  • Plug your USB hub + a HID mouse/keyboard to the BeagleBoard (this can be done earlier).
  • Plug your PC to the BeagleBoard USB slave port (this can be done earlier as well).
  • Run ./unbind: This will unbind the device from the normal Linux driver.
  • Run ./load: this will (re)load the g_proxy driver.
  • Use the mouse/keyboard, it should work.
The code is still full of TODO/FIXME, and is rather likely to cause kernel panics.