USB HID OUT reports - which endpoint is right?
Asked Answered
G

2

6

We have an embedded device based on TI's CC2531 which has (apart from the control EP0 and a number of IN-only endpoints) an endpoint that is both IN and OUT. We have noticed a difference in how windows sends OUT reports and how linux does this. This has actually boggled us quite some time, but we have never been able to find an explanation.

It seems to me that linux does it the way it is supposed to be: the OUT report is transmitted through the endpoint that is associated with the HID report, as we get it from libusb:

Item             | Dev | EP | Status | Speed |Payload
-----------------+-----+----+--------+-------+-------------------------------
OUT transaction  | 13  | 4  |  ACK   |  FS   | 64 bytes (90 13 00 00 00 00 ..

Windows on the other hand, sends it through the control endpoint (EP0). We use the setup API to find the device with the usages we need, open it for IN and OUT and use the same file descriptor for reading and writing. The EP4 IN reports are nicely received through this file descriptor, but writing a report through the same file descriptor, ends up on EP0:

Item               | Dev | EP | Status | Speed |Payload
-------------------+-----+----+--------+-------+-------------------------------
Class request OUT  | 25  | 0  |   OK   |  FS   | 64 bytes (90 13 00 00 00 00 ..

(sorry, can't post pictures (yet). I copied the Ellisys reports by hand)

The embedded device does not check on which EP the OUT report is received (i.e. SET reports on EP0 will funnel into the same function as OUT reports found on the other endpoints when processing HID events), so it will respond either way.

My question is: are both ways correct, and if not, which is correct and which is not? Could it be that an error in our descriptors triggers this behaviour on windows?

To be complete: here is our descriptor: http://tny.cz/ac745a8f (stripped from vendor identification to keep my boss happy :) )

Zooming into the report when on windows: (Joy! I am allowed to do pictures now :) )

enter image description here

The whole transaction:

enter image description here

Used libraries on Windows: hid.lib, hidclass.lib and setupapi.lib. When writing a report we use the functions HidP_SetUsageValueArray and HidD_SetOutputReport. PHIDP_PREPARSED_DATA and HIDP_CAPS are found with the functions HidD_GetAttributes, HidD_GetPreparsedData and HidP_GetCaps. The file path for the device is found using SetupDiEnumDeviceInterfaces. If we find a device with correct VID, PID, caps.UsagePage and caps.Usage, that is the device we use.

On linux it is a bit trickier, as I am not the one who implemented the linux code. What I can tell is libusb-1.0.9 is used, the device is opened using libusb_open_device_with_vid_pid, reports are sent with libusb_fill_interrupt_transfer and libusb_submit_transfer. I see that libuwand_fill_interrupt_transfer accepts an endpoint as parameter, so I think with just the handle from libusb_open_device_with_vid_pid and passing the right parameter as endpoint, libusb will figure out where to put the report.

Germanic answered 10/2, 2015 at 9:59 Comment(2)
Endpoint 0 is reserved for control transfers, so there must be an 8-byte SETUP packet sent before the payload, with parameters like bRequestType, bRequest, wValue, and similar. Could you post the values of that SETUP packet? Having that information will allows us to look in the HID specification and see what request Windows sending, and whether it is valid. Also, what driver/library stacks are you using to send the data on Linux and Windows? The endpoint that Windows uses could depend on the driver (e.g. hidusb.sys) and the library you use to talk to the driver (e.g. libusb), and your code.Tagmeme
"The embedded device does not check on which EP the OUT report is received": That sounds unusual to me. How does that work? The structure of the control transfers sent on endpoint 0 are much more complicated than the data on a normal interrupt or bulk endpoint, so you need special code to process endpoint 0 data in the first place.Tagmeme
G
3

I think I have found the answer.

purely by coincidence I stumbled upon the Keil forum where I found the statement 'HidD_SetOutputReport will use the control endpoint, if you want it through another endpoint, use WriteFile'. I know that more than 5 years ago I had tried that road but I got stranded in asynchronous IO with overlapped structures. Since it appeared I had a way out (using HidD_SetOutputReport) I abandoned the WriteFile path. So now it is time to seek that path again, and I did. The code:

res = HidD_SetOutputReport(m_DeviceControl[dev], report, m_CapsControl[dev].OutputReportByteLength);

has been replaced by

                DWORD bytesWritten;
                OVERLAPPED eventWrite = {0};

                eventWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

                int rv3 = WriteFile(m_DeviceControl[dev], report, m_CapsControl[dev].OutputReportByteLength, &bytesWritten, &eventWrite);
                if (rv3 == 0)
                {
                    int err = GetLastError();

                    if (err == ERROR_IO_PENDING)
                    {
                        bool done = false;

                        do
                        {
                            // yes. Wait till pending state has gone
                            rv3 = WaitForSingleObject(eventWrite.hEvent, 25);
                            if (rv3 == WAIT_OBJECT_0)
                            {
                                GetOverlappedResult(m_DeviceControl[dev], &eventWrite, &bytesWritten, FALSE);
                                done = true;
                                res = TRUE;
                            }
                            else if (rv3 == WAIT_TIMEOUT)
                            {
                                // Need to try again.
                            }
                            else
                            {
                                m_StoppingControlOut = true;
                                done = true;
                            }
                        }
                        while (!done && !m_StoppingControlOut);
                    }
                }
            }

and this makes the request go over the proper endpoint.

I therefore come to the following conclusion:

  • I think it is wrong that the HID device interprets the OUT reports sent through the control endpoint.
  • Using WriteFile correctly (with overlapped IO) makes the OUT reports use the correct endpoint.
Germanic answered 24/3, 2015 at 9:35 Comment(0)
R
0

HID specification allows to transmit data to device by Control endpoint if there is no OUT endpoint. At the end it depends on USB descriptors and in case of alternative configuration can depend also on operating system and on driver. That means the USB stack on the device should support both ways and be easy to overlook by OS developers, because it just work whatever way data is sent.

Reactivate answered 1/5 at 8:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.