Discussion:
[nuttx] USB Host XBox One S Controller
webbbn@gmail.com [nuttx]
2017-02-26 21:49:29 UTC
Permalink
I'm trying to write a driver for the XBox One S controller (it will likely work with other XBox controllers as well) on an STM32F4 discovery board. Unfortunately, The USB host driver is having issues reading the configuration descriptor. This is the error messages that it prints with error debug turned on:


OTGFS ERROR: Transfer Failed. ret=-5
OTGFS ERROR: ctrl_sendsetup() failed with: 5
OTGFS ERROR: Transfer Failed. ret=-5
OTGFS ERROR: ctrl_sendsetup() failed with: 5
OTGFS ERROR: Transfer Failed. ret=-5
OTGFS ERROR: ctrl_sendsetup() failed with: 5
usbhost_enumerate: ERROR: Failed to get configuration descriptor, length=9: -110
stm32_enumerate: ERROR: Enumeration failed: -110



The XBox controller has multiple configurations, which I see is not supported in the code, but I would think that it should be able to read the first one.


This is what lsusb shows on Linux:


Device: ID 045e:02ea Microsoft Corp.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 255 Vendor Specific Class
bDeviceSubClass 71
bDeviceProtocol 208
bMaxPacketSize0 64
idVendor 0x045e Microsoft Corp.
idProduct 0x02ea
bcdDevice 3.01
iManufacturer 1 Microsoft
iProduct 2 Controller
iSerial 3 3032363030353239303733363236
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 96
bNumInterfaces 3
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 500mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 71
bInterfaceProtocol 208
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 4
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 4
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 71
bInterfaceProtocol 208
iInterface 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 1
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 71
bInterfaceProtocol 208
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x03 EP 3 OUT
bmAttributes 1
Transfer Type Isochronous
Synch Type None
Usage Type Data
wMaxPacketSize 0x00e4 1x 228 bytes
bInterval 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 1
Transfer Type Isochronous
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 2
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 71
bInterfaceProtocol 208
iInterface 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 2
bAlternateSetting 1
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 71
bInterfaceProtocol 208
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x04 EP 4 OUT
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 0x84 EP 4 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Device Status: 0x0002
(Bus Powered)
Remote Wakeup Enabled
spudarnia@yahoo.com [nuttx]
2017-02-26 22:33:22 UTC
Permalink
The usual cause for this a hardware configuration issue such as clocking or USB pin configuration. Are you using a verified configuration? Does it work with other FS USB devices.


Another possibility is that the device may not support full speed (FS) operation. Perhaps it high speed only?


Perhaps it is not being supplied with sufficient power from the STM32F4 Discovery? Lots of possibilities.


I see that some of the configurations use isochronous endpoints. That could cause you some headaches too since it has never been testing (and most likely not even implemented).
spudarnia@yahoo.com [nuttx]
2017-02-26 22:48:14 UTC
Permalink
The Xbox One S Controller has USB 3.0 ports. You are using a USB 1.1 driver (OTGFS). USB 3.0 is supposed to support full backward compatibility to USB 2.0 and USB 1.1 but if the device is designed to work only with the Xbox One S, perhaps it does not support USB 1.1?
webbbn@gmail.com [nuttx]
2017-02-26 23:19:36 UTC
Permalink
I went back and tried inserting a USB flash drive that had worked previously and got the same result. It turned out to be caused by some debug statements that I put in the USB code. It appears to be hitting my (currently empty) driver, so I think it's working.

In this case, isochronous would probably be a good idea, but I'll probably see how/if it works with it's default configuration first.
spudarnia@yahoo.com [nuttx]
2017-02-26 23:37:01 UTC
Permalink
As a general rule, you should not put debug output in USB drivers. They are far too time critical and adding debug very often breaks the driver.

Instead, you should enable USB tracing. There is separate tracing logic for USB device and USB host. The USB device tracing is documented here: http://www.nuttx.org/doku.php?id=wiki:howtos:usbdevicetrace

The host trace works a little differently, but I don't have a document to point to.

Greg
webbbn@gmail.com [nuttx]
2017-03-05 16:55:56 UTC
Permalink
I've gotten much farther, and I am now able to send messages to the controller (at least the Xbox button lights up when I send the start controller packet), but I am unable to receive packets.

If I have tracing on I get a constant stream of these (EAGAIN errors):


OTGFS ERROR: Transfer Failed. ret=-11



If I turn it off I get (EIO errors):


stm32_in_transfer: ERROR: stm32_chan_wait failed: -5


These are all from the input DRVR_TRANSFER call.


I'm mimicking the hidmouse driver, which seems very similar to what I need. In fact, I even tried using the hidmouse driver directly by changing the IDs to see if I get the same errors, and I do.


Any ideas what might be wrong?
spudarnia@yahoo.com [nuttx]
2017-03-05 17:04:27 UTC
Permalink
I think that is normal behavior on endpoints where it is necessary to poll the device for data. Basically, the host asks the device if it has data with an IN request, the device responds that it does not have any data by sending a NAK. The driver reports the absences of data for the IN request by reporting EGAIN. For example:

https://bitbucket.org/nuttx/nuttx/src/810fe33c3cd9eafba15350fe583d14f9ec575ad5/arch/arm/src/stm32/stm32_otgfshost.c?at=master&fileviewer=file-view-default#stm32_otgfshost.c-1868
spudarnia@yahoo.com [nuttx]
2017-03-05 17:06:58 UTC
Permalink
Ooops. wrong error. Not EAGAIN, EIO.


EIO is reserved for reporting TX error or RX data toggle errors as is noted throughout that same file:


EIO - On a TX or data toggle error
webbbn@gmail.com [nuttx]
2017-03-05 20:21:54 UTC
Permalink
I'm trying to receive data, so I don't think it's a TX error. Why would I be getting a RX data toggle error?
spudarnia@yahoo.com [nuttx]
2017-03-05 20:34:03 UTC
Permalink
In the USB protocol, each data packet is either token DATA1 or DATA2. Each toggle from packet to packet in order to detect data transfer errors.


The error could happen because:


1. The USB host driver is expecting one token, but receives the other from the USB device. Either the host or device may be at fault.


2. There are certain events where the data toggle is reset to DATA1. Perhaps the one side is not following the correct protocol.


These kinds of errors are almost impossible to analyze with a full speed USB data analyzer.


Greg
spudarnia@yahoo.com [nuttx]
2017-03-05 20:35:18 UTC
Permalink
And also, the comment could be wrong. Perhaps EIO is return in some other condition that was not documented in the HCD. You would have to review the code to know.


In any event, EIO means a data transfer or protocol error.


Greg
Janne Rosberg janne.rosberg@offcode.fi [nuttx]
2017-03-06 21:06:27 UTC
Permalink
Hi (Brian?)

I think you maybe run on the same problem i found when testing the cdc-acm host driver.
In your class driver are you trying to read less than ep size?
or the device transfers less than max-ep size of data.

in: arch/arm/src/stm32/stm32_otgfshost.c
stm32_in_transfer() is not returning correct number of bytes read when NAK happens.

please see attached preliminary patch for fixing that.
NOTE: the patch is for the OTGHS (stm32_otghshost.c)
For OTGFS you need to adapt the patch for that.
stm32_in_transfer() function is exaclty same on both just line numbers are little bit off.

I have been super, super busy and haven’t have enough time to fully test/publish this (and some other related) fix to upstream yet.
but I hope this could get you forward...

—Janne
Post by ***@gmail.com [nuttx]
I've gotten much farther, and I am now able to send messages to the controller (at least the Xbox button lights up when I send the start controller packet), but I am unable to receive packets.
OTGFS ERROR: Transfer Failed. ret=-11
stm32_in_transfer: ERROR: stm32_chan_wait failed: -5
These are all from the input DRVR_TRANSFER call.
I'm mimicking the hidmouse driver, which seems very similar to what I need. In fact, I even tried using the hidmouse driver directly by changing the IDs to see if I get the same errors, and I do.
Any ideas what might be wrong?
spudarnia@yahoo.com [nuttx]
2017-03-06 22:46:51 UTC
Permalink
Hmmm... I suspect that this patch will not solve webbbn's problem. He is reporting a -EIO error. This change would not work around an EIO error would it? It would handle EGAIN (NAK) to terminate the data transfer early, but not EIO (data toggle or protocol).


Greg
spudarnia@yahoo.com [nuttx]
2017-03-06 23:05:52 UTC
Permalink
This looks to me like a good change and I will probably incorporate it.


webbbn... Can you let me know if you are happy with the patch? There are three possibilities:


- It works and your probably goes away.
- It works, but your problem persists. In this case, the patch is probably still good, but might not be addressing your specific problem.
- It doesn't work.


Let me know. I intend to incorporate the changes unless you have some objection.


Greg
webbbn@gmail.com [nuttx]
2017-03-08 01:15:15 UTC
Permalink
It looks like the patch was already applied. Is that right?

In any case, I updated my source to the latest from git, and it appears to be working better.


Now, after a few seconds, I get this:


usbhost_xboxcontroller_poll: Got 8 bytes
usbhost_xboxcontroller_poll: Got 18 bytes
up_assert: Assertion failed at file:semaphore/sem_post.c line: 118 task: xbc
up_dumpstate: sp: 10005768
up_dumpstate: stack base: 100057b8
up_dumpstate: stack size: 000003ec
up_stackdump: 10005760: 0801469c 000003ec 0801469c 000003ec 0801469c 000003ec 0801469c 000003ec
up_stackdump: 10005780: 0801469c 000003ec 10004f38 080022c3 10004f20 08012117 00000000 00000000
up_stackdump: 100057a0: 00000000 00000000 00000000 080017b5 00000000 00000000 00000000 100057c4


At least it received some data, which I haven't seen before. I'll look into what might be causing this, which might be my bug.
webbbn@gmail.com [nuttx]
2017-03-08 02:47:35 UTC
Permalink
I fixed the semaphore issue, and now it appears to be working. I've even getting valid data packets, but I'm also getting EIO errors.
webbbn@gmail.com [nuttx]
2017-03-09 00:40:00 UTC
Permalink
I made the EIO errors go away by commenting out the following from stm32_otgfshost.c (around line 2520):

regval = stm32_getreg(STM32_OTGFS_HCCHAR(chidx));
if ((regval & OTGFS_HCCHAR_EPTYP_MASK) == OTGFS_HCCHAR_EPTYP_INTR)
{
/* Toggle the IN data toggle (Used by Bulk and INTR only) */


chan->indata1 ^= true;
}


I don't know what the register flag check does or if it's correct, but, at least for the xbox controller, data toggle doesn't need to happen on a NAK.


Any ideas on this?
spudarnia@yahoo.com [nuttx]
2017-03-09 13:25:27 UTC
Permalink
I am glad to see you are making progress.
I made the EIO errors go away by commenting out the following from stm32_otgfshost.c (around line 2520): ...
I don't know what the register flag check does or if it's correct, but, at least for the xbox controller, data toggle doesn't need to happen on a NAK.
I don't understand the context, but the check is if the endpoint is an interrupt endpoint and, if so, it then toggles the expected DATA toggle for the next packet.

Your change may or may not be correct. I cannot say. But there are other USB class drivers that use interrupt IN endpoints and do not seem to have this problem:

HID input devices are use an interrupt IN endpoint:
usbhost_hidkbd.c: HID keyboard
usbhost_hidmouse.c: HID mouse

And also these:
usbhost_cdcacm.c: Uses an interrupt IN endpoint for out-of-band notifications in some configurations.
usb_hub.c: Also uses an in interrupt IN endpoint.

So I could not commit this change without some confidence that it does not also break the other devices that seem to function correctly with the code you have commented out.

It appears to me that the commented-out code is not arbitrary. It must be there or it must be removed. It is either correct or it is incorrect. I could not be both.

Greg
spudarnia@yahoo.com [nuttx]
2017-03-09 15:00:40 UTC
Permalink
There is a pretty discussion of data toggle synchronization here: http://wiki.osdev.org/Universal_Serial_Bus#Data_Toggle_Synchronization

It even goes through what you should do in the event of a NAK or other error events. This figure would seem to say that your change is correct: Loading Image...

But I wonder why it is special cased just for interrupt endpoints. I would still want to verify the other HID devices and maybe a hub.

...

I am thinking that this may be a difference in interrupt endpoint synchronization protocols: See http://wiki.osdev.org/Universal_Serial_Bus#Interrupt_Data_Transfers:

"Interrupt data transfers may use one of two data toggle bit schemes to ensure successful data transmission. Devices that require higher through-put may choose to toggle every transmission rather than perform a handshake with the host. This method is more susceptible to errors than the alternative method of toggling bits upon successful transaction (after a handshake)."

The logic in the OTGFS driver handles the first scheme: It expects a data toggle on every transmission? Your hardware expects an ACK handshake before each toggle. Is that right?

I think that you are on to something and headed in the right direction. I just need to come up with a way to have confidence that the change will not break anything.

Greg
webbbn@gmail.com [nuttx]
2017-03-09 16:53:25 UTC
Permalink
According the the USB 2.0 spec (section 8.6):

"Receiver sequence bits toggle only when the receiver is able to accept data and
receives an error-free data packet with the correct data PID. Transmitter sequence bits toggle only when the
data transmitter receives a valid ACK handshake."


In this case, the receiver (controller) is requesting a packet and receiving a NAK, so I don't think the toggle bit should be changed.


I don't understand the description from osdev. If a device can choose either of the two schemes, how does the host know when to toggle?
spudarnia@yahoo.com [nuttx]
2017-03-09 17:00:45 UTC
Permalink
Okay, send me the verified patch and I will make sure that other HID devices still work. I suppose I would need to verify the HUB too.


The hub never worked well with STM32 mostly because of the limited number of channels (that is another discussion).


Greg
spudarnia@yahoo.com [nuttx]
2017-03-09 21:08:58 UTC
Permalink
This post might be inappropriate. Click to display it.
webbbn@gmail.com [nuttx]
2017-03-10 00:46:43 UTC
Permalink
Thanks!
webbbn@gmail.com [nuttx]
2017-03-11 17:48:28 UTC
Permalink
On to the next issue...

It looks like the XBox One S controller is working well, but I can't even get a couple of older XBox One controllers to enumerate. These controllers should use the same protocol, but have a different PID. Unfortunately, the enumeration appears to be failing very early in the detection step.


This is what I see with the (working) One S controller if I turn on tracing:


stm32_gint_isr: pending: 1000000
OTGFS Handle the host port interrupt.
OTGFS HPRT: Port Connect Detect.
OTGFS Host Port 0 connected.
stm32_gint_isr: pending: 1000000
OTGFS Handle the host port interrupt.
OTGFS HPRT: Port Enable Changed.
OTGFS HPRT: Full Speed Device Connected.
stm32_gint_isr: pending: 1000000
OTGFS Handle the host port interrupt.
OTGFS HPRT: Port Enable Changed.
stm32_gint_isr: pending: 1000000
OTGFS Handle the host port interrupt.
OTGFS HPRT: Port Enable Changed.
OTGFS HPRT: Full Speed Device Connected.
OTGFS Channel configured. chidx: 0: (EP0,OUT,CTRL)
OTGFS Channel configured. chidx: 1: (EP0,IN ,CTRL)
OTGFS Channel configured. chidx: 0: (EP0,OUT,CTRL)
OTGFS Channel configured. chidx: 1: (EP0,IN ,CTRL)
OTGFS CTRL_IN type: 80 req: 06
OTGFS Transfer chidx: 0 buflen: 8
stm32_gint_isr: pending: 2000000
OTGFS Handle the host channels interrupt.
OTGFS Channel halted. chidx: 0, reason: 2
stm32_gint_isr: pending: 10
OTGFS Handle the RxFIFO non-empty interrupt.
stm32_gint_isr: pending: 2000000


With the older (non-working) controller I get this:


stm32_gint_isr: pending: 1000000
OTGFS Handle the host port interrupt.
OTGFS HPRT: Port Connect Detect.
OTGFS Host Port 0 connected.
stm32_gint_isr: pending: 21000000
OTGFS Handle the host port interrupt.
OTGFS HPRT: Port Enable Changed.
OTGFS HPRT: Full Speed Device Connected.
OTGFS Handle the disconnect detected interrupt.
OTGFS Host Port 0 disconnected.
OTGFS Channel halted. chidx: 2, reason: 1
OTGFS Channel halted. chidx: 3, reason: 1
OTGFS Channel halted. chidx: 4, reason: 1
OTGFS Channel halted. chidx: 5, reason: 1
OTGFS Channel halted. chidx: 6, reason: 1
OTGFS Channel halted. chidx: 7, reason: 1
stm32_gint_isr: pending: 10
OTGFS Handle the RxFIFO non-empty interrupt.
stm32_gint_isr: pending: 2000010
OTGFS Handle the RxFIFO non-empty interrupt.
OTGFS Handle the host channels interrupt.
stm32_gint_isr: pending: 10



For some reason, the disconnect flag is being set during the connection process. I tried just ignoring it (by commenting out the disconnect call), but it gets much further, although I'm not sure it actually enumerates.


I know that printing out the trace can cause problems, but it appears to do the same thing if I see what's happening in the debugger.
webbbn@gmail.com [nuttx]
2017-03-14 18:39:26 UTC
Permalink
I'm not getting anywhere with the older (original) XBox One controller, so I think I might move on and finish up the driver for the XBox One S controller. I believe the older controller should work with the same driver if someone can figure out how to get it to enumerate correctly.

At this point, all I need to do is write the /dev interface code, which looks relatively straight-forward. I see an interface definition for an analog joystick, but it appears to only support a single analog stick and a set of buttons. A game controller should have at least two analog sticks, and possibly other analog channels. The XBox controllers have a pair of analog trigger buttons. I'm not sure I understand the upper half and lower half components of that interface either.


Should I create something like a nutts/input/game_controller.h that is similar to mouse.h, but that has all the fields required for a game controller, or are there other suggestions?
spudarnia@yahoo.com [nuttx]
2017-03-14 19:25:46 UTC
Permalink
Post by ***@gmail.com [nuttx]
Should I create something like a nutts/input/game_controller.h that is similar to mouse.h, but that has all the fields required for a game controller, or are there other suggestions?
That sounds like basically the right way to go. But let me add some thoughts.

The ajoystick driver could be extended as you need. As far as undertanding the upper and lower half stuff, the idea is that the lower half could be anything and provided by any board-specific logic, depending on the available hardware.

For the ajoystick, you can see implementations of the lower half here:

$ find . -name "*ajoy*.c"
./nucleo-f4x1re/src/stm32_ajoystick.c
./nucleo-l476rg/src/stm32_ajoystick.c
./sama5d3-xplained/src/sam_ajoystick.c

The lower half does all of the dirty work interfacing with A-to-D converters and GPIO interrupts to implement the device interface. Those things all depend on board connections and MCU peripheral support. A generic game controller could be implemented from a collection of separate interfaces like this.

In the case of a USB-based game controller, of course, things works differently. You don't have to deal with any kind of lower-level board interface. Once the USB communicates, you instead deal with the logical device interface. The board level logic implement nothing other than the initialization of the USB class driver. The USB class driver then has to implement the low level interface and has to export the driver to support application interface.

In this case, I don't really see the upper-half/lower-half division a very useful concept. If you really want to implement a "generic" game controller interface that could interface with any kind of hardware, then you would need such a division to separate the low-level interface. But I don't think you really want to do that; abstracting all game controller features would be complex.

But an XBox Game Controller only interfaces via USB. There will never be an XBox Game Controller that you interface with via low level discretes and A-to-D. So, if you want for simplify your job, forget about the generic game controller interface. Instead, define a custom nuttx/input/xbox-controller.h interface. Only the application interface needs to be defined there and the probably just consists of IOCTL command definitions.

The character driver and device interface would then be built into your USB host game controller class driver. nothing else needs to know anything about that internal implementation. It would call register_driver() to create a character driver in /dev/xbox0.

The application would open() /dev/xbox0 and would interface with it using the IOCTL commands and other definitions in custom nuttx/input/xbox-controller.h. I think that would make your job much easier.

For examples, look at the usbhost_hidmouse.c and usbhost_hidkbd.c class drivers. These register character drivers just as you need to and do not have any upper-/lower-half separation:

$ grep register_driver *
usbhost_hidkbd.c: (void)unregister_driver(devname);
usbhost_hidkbd.c: ret = register_driver(devname, &g_hidkbd_fops, 0666, priv);
usbhost_hidmouse.c: (void)unregister_driver(devname);
usbhost_hidmouse.c: ret = register_driver(devname, &g_hidmouse_fops, 0666, priv);

Greg
webbbn@gmail.com [nuttx]
2017-03-18 03:57:31 UTC
Permalink
I created a pull request for the XBox controller driver. It seems to work well with the One S controller, but still not with the older XBox One controller.

I even added an ioctl command that is supposed to activate the rumble. It does sort of work, but not exactly like I would expect. I'm not sure if it's a bug in my interface, or just that I don't understand how it should work.


I also created a simple test program and submitted a pull request on the apps repository for it.


Please let me know if you would like me to change/fix anything, and, of course, you're free to clean up / fix anything as you wish.
spudarnia@yahoo.com [nuttx]
2017-03-18 04:23:30 UTC
Permalink
I merged in your code. It looks good. Thanks for following the coding standard! I really appreciate that!


Greg

Loading...