rsta2 / circle

A C++ bare metal environment for Raspberry Pi with USB (32 and 64 bit)
https://circle-rpi.readthedocs.io
GNU General Public License v3.0
1.83k stars 243 forks source link

USB bulk transfer timeout #467

Open giulioz opened 1 week ago

giulioz commented 1 week ago

I'm trying to implement support for the Komplete Kontrol A-series of USB keyboards. It exposes different USB interfaces, among those a MIDI standard interface (which works already) and a HID standard interface for controlling the additional buttons and LCD screen. I'm trying to add support for the latter (which I know already works fine on my laptop).

Working example with hidapi here: https://gist.github.com/giulioz/7c0d43d97477194a3e18bed37e4e992f

I'm experiencing some weird behavior when porting the code to Circle:

Is it possible that Circle only supports one pending USB async transfer at a time, even on different endpoints/interfaces?

Thanks for the help and for this very nice project!

giulioz commented 1 week ago

Potentially related: https://stackoverflow.com/questions/72236325/ruby-libusb-one-usb-device-two-input-endpoints-difficulties

rsta2 commented 1 week ago

If this USB keyboard provides an "int1-3-0" MIDI interface and an "int3-0-0" HID interface, it should normally load the CUSBMIDIHostDevice and the CUSBGamePadStandardDevice drivers. It depends of the HID report descriptor, if the gamepad driver is loaded successfully. If this does not work, there may be a problem in Circle USB. I read, that this USB keyboard is an USB 2.0 high-speed device. Normally MIDI and HID devices are full-speed (or low-speed), so this combination is rare and a potential problem might not be discovered so far. We could try to sort this out together, but this could be a longer process.

BTW. Circle USB does support transfer timeouts only for interrupt endpoints and only on RPi 1-3.

Thanks for appreciating Circle!

giulioz commented 1 week ago

The device does provide a int3-0-0 device, which gets recognized but then no upad1 device gets added to the devices list, so I guess I should change some things in the descriptor parsing to make it work?

BTW. Circle USB does support transfer timeouts only for interrupt endpoints and only on RPi 1-3.

How complicated do you think it would be to add support for that? It might be one of my easiest way out to finish my project.

The device shows up as full-speed when used on my laptop. Here's some more info:

Full Speed device @ 1 (0x01100000): .............................................   Miscellaneous/Common Class device: "KOMPLETE KONTROL A61"
    Port Information:   0x001a
           Not Captive
           Attached to Root Hub
           External Device
           Connected
           Enabled
    Number Of Endpoints (includes EP0):
        Total Endpoints for Configuration 1 (current):   5
    Device Descriptor
        Descriptor Version Number:   0x0200
        Device Class:   239   (Miscellaneous)
        Device Subclass:   2   (Common Class)
        Device Protocol:   1   (Interface Association)
        Device MaxPacketSize:   64
        Device VendorID/ProductID:   0x17CC/0x1750   (unknown vendor)
        Device Version Number:   0x0040
        Number of Configurations:   1
        Manufacturer String:   1 "Native Instruments"
        Product String:   2 "KOMPLETE KONTROL A61"
        Serial Number String:   3 "00005C26"
    Configuration Descriptor (current config)
        Length (and contents):   159
            Raw Descriptor (hex)    0000: 09 02 9F 00 04 01 00 80  F0 08 0B 00 02 01 00 00
            Raw Descriptor (hex)    0010: 00 09 04 00 00 00 01 01  00 04 09 24 01 00 01 09
            Raw Descriptor (hex)    0020: 00 01 01 09 04 01 00 02  01 03 00 00 07 24 01 00
            Raw Descriptor (hex)    0030: 01 41 00 06 24 02 01 01  00 06 24 02 02 02 00 09
            Raw Descriptor (hex)    0040: 24 03 01 03 01 02 01 00  09 24 03 02 04 01 01 01
            Raw Descriptor (hex)    0050: 00 09 05 02 02 40 00 00  00 00 05 25 01 01 01 09
            Raw Descriptor (hex)    0060: 05 82 02 40 00 00 00 00  05 25 01 01 03 09 04 02
            Raw Descriptor (hex)    0070: 00 02 03 00 00 05 09 21  11 01 00 01 22 F6 01 07
            Raw Descriptor (hex)    0080: 05 81 03 40 00 01 07 05  01 03 40 00 0A 09 04 03
            Raw Descriptor (hex)    0090: 00 00 FE 01 01 06 09 21  09 FF 00 40 00 10 01
        Number of Interfaces:   4
        Configuration Value:   1
        Attributes:   0x80 (bus-powered)
        MaxPower:   480 mA
        Interface Association   Audio/Unknown
            First Interface   0
            Interface Count   2
            Function Class   1   (Audio)
            Function Subclass   0   (Unknown)
            Interface Protocol   0
            Function String   0 (none)
        Interface #0 - Audio/Control ..............................................   "KOMPLETE KONTROL A61 MIDI"
            Alternate Setting   0
            Number of Endpoints   0
            Interface Class:   1   (Audio)
            Interface Subclass;   1   (Control)
            Interface Protocol:   0
            Audio Class 1.0 Control Interface
                Descriptor Version Number:   01.00
                Class Specific Size:   9
                Number of Audio Interfaces:   1
                Audio Interface Number:   1
                Dump Contents (hex):   09 24 01 00 01 09 00 01 01
        Interface #1 - Audio/Streaming
            Alternate Setting   0
            Number of Endpoints   2
            Interface Class:   1   (Audio)
            Interface Subclass;   3   (Streaming)
            Interface Protocol:   0
            Uknown Interface SubClass Type
            Uknown Interface SubClass Type
            Uknown Interface SubClass Type
            Uknown Interface SubClass Type
            Uknown Interface SubClass Type
            Endpoint 0x02 - Bulk Output
                Address:   0x02  (OUT)
                Attributes:   0x02  (Bulk)
                Max Packet Size:   64
                Polling Interval:   0 ms
            Class-Specific AS Audio EndPoint
                Attributes:   0x01  Sample Frequency,
                bLockDelayUnits:   0x01  (Milliseconds)
                wLockDelay:   1289 ms
            Endpoint 0x82 - Bulk Input
                Address:   0x82  (IN)
                Attributes:   0x02  (Bulk)
                Max Packet Size:   64
                Polling Interval:   0 ms
            Class-Specific AS Audio EndPoint
                Attributes:   0x01  Sample Frequency,
                bLockDelayUnits:   0x03  (RESERVED)
                wLockDelay:   1033
        Interface #2 - HID ..............................................   "KOMPLETE KONTROL A61 HID"
            Alternate Setting   0
            Number of Endpoints   2
            Interface Class:   3   (HID)
            Interface Subclass;   0
            Interface Protocol:   0
            HID Descriptor
                Descriptor Version Number:   0x0111
                Country Code:   0
                Descriptor Count:   1
                Descriptor 1
                    Type:   0x22  (Report Descriptor)
                    Length (and contents):   502
                        Raw Descriptor (hex)    0000: 06 01 FF 09 00 A1 01 09  01 A1 02 85 01 09 02 15
                        Raw Descriptor (hex)    0010: 00 25 01 75 01 95 28 81  02 09 06 15 00 26 E7 03
                        Raw Descriptor (hex)    0020: 75 10 95 08 81 02 09 08  15 00 26 FF 0F 75 10 95
                        Raw Descriptor (hex)    0030: 02 81 02 09 09 15 00 26  FF 0F 75 10 95 01 81 02
                        Raw Descriptor (hex)    0040: 09 03 15 00 25 0F 75 04  95 02 81 02 09 0C 15 00
                        Raw Descriptor (hex)    0050: 26 FF 00 75 08 95 01 81  02 C0 09 80 A1 02 85 80
                        Raw Descriptor (hex)    0060: 09 81 15 00 25 7F 75 08  95 15 91 02 C0 09 A0 A1
                        Raw Descriptor (hex)    0070: 02 85 A0 09 A2 15 00 26  FF 00 75 08 95 01 91 02
                        Raw Descriptor (hex)    0080: 09 A2 15 00 26 FF 00 75  08 95 01 91 02 C0 09 D0
                        Raw Descriptor (hex)    0090: A1 02 85 D0 09 D1 15 00  27 FF FF 00 00 75 10 95
                        Raw Descriptor (hex)    00a0: 02 B1 02 09 D1 15 00 26  FF 00 75 08 95 1C B1 02
                        Raw Descriptor (hex)    00b0: C0 09 D8 A1 02 85 D8 09  D1 15 00 27 FF FF 00 00
                        Raw Descriptor (hex)    00c0: 75 10 95 02 B1 03 09 D1  15 00 27 FF FF 00 00 75
                        Raw Descriptor (hex)    00d0: 10 95 02 B1 03 09 D1 15  00 27 FF FF 00 00 75 10
                        Raw Descriptor (hex)    00e0: 95 04 B1 03 09 D1 15 00  26 FF 00 75 08 95 01 B1
                        Raw Descriptor (hex)    00f0: 03 09 D1 15 00 26 FF 00  75 08 95 0F B1 03 C0 09
                        Raw Descriptor (hex)    0100: D8 A1 02 85 D9 09 D1 15  00 26 FF 00 75 08 95 20
                        Raw Descriptor (hex)    0110: B1 03 C0 09 E0 A1 02 85  E0 09 EB 15 00 27 FF FF
                        Raw Descriptor (hex)    0120: 00 00 75 10 95 01 91 02  09 EC 15 00 27 FF FF 00
                        Raw Descriptor (hex)    0130: 00 75 10 95 01 91 02 09  ED 15 00 27 FF FF 00 00
                        Raw Descriptor (hex)    0140: 75 10 95 01 91 02 09 EE  15 00 27 FF FF 00 00 75
                        Raw Descriptor (hex)    0150: 10 95 01 91 02 09 EF 15  00 26 FF 00 75 08 96 00
                        Raw Descriptor (hex)    0160: 01 91 02 C0 09 F0 A1 02  85 F4 09 F4 15 00 26 FF
                        Raw Descriptor (hex)    0170: 00 75 08 95 01 91 02 09  F5 15 00 26 FF 00 75 08
                        Raw Descriptor (hex)    0180: 95 1F 91 02 C0 09 E0 A1  02 85 F8 09 E1 15 00 27
                        Raw Descriptor (hex)    0190: FF FF 00 00 75 10 95 01  B1 03 09 E2 15 00 27 FF
                        Raw Descriptor (hex)    01a0: FF 00 00 75 10 95 01 B1  03 09 E3 15 00 26 FF 00
                        Raw Descriptor (hex)    01b0: 75 08 95 01 B1 03 09 E4  15 00 26 FF 00 75 08 95
                        Raw Descriptor (hex)    01c0: 01 B1 03 09 E5 15 00 26  FF 00 75 08 95 01 B1 03
                        Raw Descriptor (hex)    01d0: 09 E7 15 00 25 64 75 08  95 01 B1 02 09 E6 15 00
                        Raw Descriptor (hex)    01e0: 25 64 75 08 95 01 B1 02  09 E8 15 00 25 01 75 08
                        Raw Descriptor (hex)    01f0: 95 01 B1 02 C0 C0
             Endpoint 0x81 - Interrupt Input
                Address:   0x81  (IN)
                Attributes:   0x03  (Interrupt)
                Max Packet Size:   64
                Polling Interval:   1 ms
            Endpoint 0x01 - Interrupt Output
                Address:   0x01  (OUT)
                Attributes:   0x03  (Interrupt)
                Max Packet Size:   64
                Polling Interval:   10 ms
        Interface #3 - Application Specific/Device Firmware Update ..............................................   "KOMPLETE KONTROL A61 DFU"
            Alternate Setting   0
            Number of Endpoints   0
            Interface Class:   254   (Application Specific)
            Interface Subclass;   1   (Device Firmware Update)
            Interface Protocol:   1
            DFU Functional Descriptor
                bmAttributes:   0x09 (Download, No Upload, Not Manifestation Tolerant, Reserved bits: 0x08)
                wDetachTimeout:   255 ms
                wTransferSize:   64 bytes

Thanks!

rsta2 commented 1 week ago

Thanks for the descriptor listing! Attached you find the decoded HID report descriptor. It uses a vendor-defined usage page and can't be decoded by the parser in the Circle driver. That's the reason, why no upad1 device is created. If no upad1 device is created, how do you access the int3-0-0 interface? I guess, you have additional info about the HID reports, otherwise you could not implement this?

Yes, it's a USB full-speed device. Supporting timeouts for bulk endpoints is complicated, because a bulk transfer cannot easily be canceled. That's why, this is not implemented.

My MiniSynth Pi program uses an USB MIDI interface and HID interface (for the USB mouse) parallel and that's working. But they are not in the same USB device, which is different.

Which Raspberry Pi model are you using and do you use an external USB hub?

HID-report-desc.txt

giulioz commented 1 week ago

If no upad1 device is created, how do you access the int3-0-0 interface?

I created a new class that inherits from CUSBHIDDevice and added it to the factory:

        else if (   pName->Compare ("int3-0-0") == 0
         || pName->Compare ("int3-0-2") == 0
         || pName->Compare ("int3-1-0") == 0)
    {
        CString *pVendor = pParent->GetDevice ()->GetName (DeviceNameVendor);
        assert (pVendor != 0);

        if (pVendor->Compare ("ven17cc-1750") == 0) // Komplete Kontrol A61
        {
            pResult = new CUSBKompleteKontrolDevice (pParent);
        }
        else if (pVendor->Compare ("ven5ac-21e") != 0)  // Apple Aluminum Mini Keyboard
        {
            pResult = GetGenericHIDDevice (pParent);
        }

        delete pVendor;
    }

I guess, you have additional info about the HID reports, otherwise you could not implement this?

Yeah I reverse engineered it using Wireshark. The report is 30 bytes long and contains a status byte (always 0x01) and then the status of all buttons and knobs.

image

Supporting timeouts for bulk endpoints is complicated, because a bulk transfer cannot easily be canceled. That's why, this is not implemented.

Ah, I see. Seems strange thought that a pending request on one interface blocks another other on another endpoint. Maybe there is some deadlocking issue? I can try to put more log points around and look into it but maybe you have some better insight.

I'm now trying to make the two requests to the two endpoints in different cores. It seems like the HID request only works after the MIDI one gets completed. So like the HID buttons only gets received after a MIDI event is fired as well.

Which Raspberry Pi model are you using and do you use an external USB hub?

Pi Zero 2 W in 64 bit mode, USB device connected directly to the USB port.

rsta2 commented 1 week ago

This looks good. And yes, the USB MIDI bulk-in transfer blocked and never completed, when there was no MIDI event available. I've found a problem in Circle USB. The device sends a NAK, when there is no data to be sent, and Circle USB did not enable the NAK detection in your configuration (without USB hub). Can you please apply the following patch and try again:

diff --git a/lib/usb/dwhcidevice.cpp b/lib/usb/dwhcidevice.cpp
index 7cdd35e..d1f82ee 100644
--- a/lib/usb/dwhcidevice.cpp
+++ b/lib/usb/dwhcidevice.cpp
@@ -1088,7 +1088,8 @@ void CDWHCIDevice::ChannelInterruptHandler (unsigned nChannel)
    }

    unsigned nStatus;
-   
+   CDWHCIRegister Character (DWHCI_HOST_CHAN_CHARACTER (nChannel));
+
    switch (pStageData->GetState ())
    {
    case StageStateNoSplitTransfer:
@@ -1168,6 +1169,15 @@ void CDWHCIDevice::ChannelInterruptHandler (unsigned nChannel)

        DisableChannelInterrupt (nChannel);

+       // if transaction was completed on NAK, channel is not disabled yet
+       Character.Read ();
+       if (Character.IsSet (DWHCI_HOST_CHAN_CHARACTER_ENABLE))
+       {
+           Character.And (~DWHCI_HOST_CHAN_CHARACTER_ENABLE);
+           Character.Or (DWHCI_HOST_CHAN_CHARACTER_DISABLE);
+           Character.Write ();
+       }
+
        delete pStageData;
        m_pStageData[nChannel] = 0;

diff --git a/lib/usb/dwhcixferstagedata.cpp b/lib/usb/dwhcixferstagedata.cpp
index 5efd9e3..c7c2636 100644
--- a/lib/usb/dwhcixferstagedata.cpp
+++ b/lib/usb/dwhcixferstagedata.cpp
@@ -518,8 +518,12 @@ u32 CDWHCITransferStageData::GetStatusMask (void) const
             | DWHCI_HOST_CHAN_INT_NAK
             | DWHCI_HOST_CHAN_INT_NYET;
    }
-   
-   return  nMask;
+   else if (m_pURB->IsCompleteOnNAK ())
+   {
+       nMask |= DWHCI_HOST_CHAN_INT_NAK;
+   }
+
+   return nMask;
 }

 u32 CDWHCITransferStageData::GetTransactionStatus (void) const

Remains the question, why the reporting mechanism in your initial attempt to derive CUSBHIDDevice did not work. Did you specify the nMaxReportSize in the constructor as 30?

giulioz commented 1 week ago

Holy heck it works now! The MIDI driver doesn't block the hid request anymore and works without any change. I did originally specify the size to the constructor, but maybe I still had the midi driver loaded so that interfered? I will try later.

For now thank you so much!

rsta2 commented 1 week ago

You are welcome. I'm glad, that it did help.