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 :) )
The whole transaction:
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.
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With