This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

USB HID IN & OUT Reports through Control & Interrupt transfer

Hi,

dig around with/for HID reports, i ran into a strange problem within a WinXP test application that i wrote.

Following configuration:
1 (non compound) device with
1 config with
1 interface (HID) with
2 endpoints wheras
1 interrupt in (ep1) and
1 interrupt out (ep2)

Setting and requesting the reports through control transfer (ep0) works like a charme, but with interrupt transfers the host application first sets an report without any problem, but the request of the following in interrupt leads to a problem. I figured out that the in interrupt terminates on the wrong (ep2) endpoint.
within the win api i used readfile in an overlapped mode, but there is no chance to define a endpoint. why isn't the interface interpreted correctly by the hostapp? or is there something to define elsewhere?

thx & kr

Parents
  • This is another wide-spread misunderstanding.

    For IN endpoint, an endpoint interrupt occurs just when the endpoint becomes empty,
    by sending a packet (transaction) to the host.
    For OUT endpoint, it occurs just when the endpoint finishes to receive a new packet.

    This interrupt timing is same as UART TX/RX.
    For UART TX, interrupt occurs just after a byte puts out to the line.
    TX interrupt never occurs until the firmware writes a byte to the TX buffer.
    For UART RX, it occurs when a byte has received on the RX buffer.

    The major difference of USB endpoint from UART lies in the IN endpoint.
    UART TX starts just after firmware writes a byte to the buffer. But IN endpoint waits for an IN transaction from host.



    This misunderstanding comes from impractical examples by major USB-MCU and compiler manufacturers, including KEIL.
    I'll pick up KEIL one here,

    In this KEIL example, the packets are passed to the USB engine in the endpoint interrupt using USB_WriteEP().
    When users just see this part of the code, they misunderstand that
    endpoint interrupt comes first, and the firmware passes the packet in response to the interrupt (wrong idea).

    usbuser.c
    
    void USB_EndPoint1 (U32 event) {
    
      switch (event) {
        case USB_EVT_IN:
          GetInReport();
          USB_WriteEP(HID_EP_IN, &InReport, sizeof(InReport));
          break;
      }
    }
    

    But actually, the first packet is passed to the IN endpoint out side of the endpoint ISR,
    in the Set_Configuration handler, as follows. Users aren't aware of this code.
    Above code in the endpoint ISR is placed to repeat the report infinitely, ignoring device's requirement.

    usbuser.c
    
    #if USB_CONFIGURE_EVENT
    void USB_Configure_Event (void) {
    
      if (USB_Configuration) {             /* Check if USB is configured */
        GetInReport();
        USB_WriteEP(HID_EP_IN, &InReport, sizeof(InReport));  // <--- the first packet is passed here
      }
    }
    #endif
    

    Harmful example.
    I hope KEIL would refine it immediately.

    Tsuneo

Reply
  • This is another wide-spread misunderstanding.

    For IN endpoint, an endpoint interrupt occurs just when the endpoint becomes empty,
    by sending a packet (transaction) to the host.
    For OUT endpoint, it occurs just when the endpoint finishes to receive a new packet.

    This interrupt timing is same as UART TX/RX.
    For UART TX, interrupt occurs just after a byte puts out to the line.
    TX interrupt never occurs until the firmware writes a byte to the TX buffer.
    For UART RX, it occurs when a byte has received on the RX buffer.

    The major difference of USB endpoint from UART lies in the IN endpoint.
    UART TX starts just after firmware writes a byte to the buffer. But IN endpoint waits for an IN transaction from host.



    This misunderstanding comes from impractical examples by major USB-MCU and compiler manufacturers, including KEIL.
    I'll pick up KEIL one here,

    In this KEIL example, the packets are passed to the USB engine in the endpoint interrupt using USB_WriteEP().
    When users just see this part of the code, they misunderstand that
    endpoint interrupt comes first, and the firmware passes the packet in response to the interrupt (wrong idea).

    usbuser.c
    
    void USB_EndPoint1 (U32 event) {
    
      switch (event) {
        case USB_EVT_IN:
          GetInReport();
          USB_WriteEP(HID_EP_IN, &InReport, sizeof(InReport));
          break;
      }
    }
    

    But actually, the first packet is passed to the IN endpoint out side of the endpoint ISR,
    in the Set_Configuration handler, as follows. Users aren't aware of this code.
    Above code in the endpoint ISR is placed to repeat the report infinitely, ignoring device's requirement.

    usbuser.c
    
    #if USB_CONFIGURE_EVENT
    void USB_Configure_Event (void) {
    
      if (USB_Configuration) {             /* Check if USB is configured */
        GetInReport();
        USB_WriteEP(HID_EP_IN, &InReport, sizeof(InReport));  // <--- the first packet is passed here
      }
    }
    #endif
    

    Harmful example.
    I hope KEIL would refine it immediately.

    Tsuneo

Children
  • Hi Tsuneo,

    many thx for the explanations, but nevertheless

    .) neither STALL nor NAK is sent during the enumeration, in fact it looks like a "normal" time out within the host, and therefor the tranfer is CHANCELLED. I know this because i didn't see any received packet on the ep1 - if there is one, i see a rx flag withtin the controller (also if the controller handles it on its own).
    .) ok, the "imaginary" STALL during the enumeration is(was) my fault, because the application logic implements a flowchart which looks like this (after enumeration):

    1) host sends data using the OUT report over the INTERRUPT OUT ep or the ep0
    2) device decodes the data and acknowledge this within a new data packet that is generated and loaded into the INTERRUPT IN ep or the ep0 (REPORT IN structure). The decision which endpoint has to be used is made by interpreting the received data.
    3) the host requests an REPORT IN using either INTERRUPT IN or ep0 (knowing which one because it knows the previous packets).

    for testing just commented out the STALL - no change (isn't reached because no interrupt appears).

    Again, the first interrupt within the device controller (INTERRUPT out or ep0) is generated normally, data is received, decoded and a answer is prepared. The second interrupt using ep0 works without problems, but using the INTERRUPT IN ep, no interrupt is generated. I think it is the same problem as during the enumeration (there also the INTERRUPT IN is requested). A STALL was wrongly implemented by me within the interrupt routine, if there was no data. But therefor a interrupt has to be generated by the controller which isn't there.

    Back to the source some questions:
    .) report size is 64(id1-in/out) / 1024(id2-in) byte
    .) therefor the hostapp has to malloc a buffer of that size
    .) sending an out report by writing the id (1) in the first byte leads to 63 byte data
    .) requesting an in report also writing the id (1 or 2) in the first byte leads to an 63 or 1023 byte report (always stored in a 1024 byte "frame" within the host's memory)

    right?

    kr

  • > in fact it looks like a "normal" time out within the host, and therefor the tranfer is CHANCELLED.

    PC HID device driver doesn't set timeout on the interrupt IN endpoint (EP).

    As I'm not using USBLyzer usually, I don't know how it looks like exactly (though sniffers display is almost similar)
    For confirmation, I made up a HID device, which does just enumeration but nothing else.
    And ran it on USBLyzer (trial version) and on a hardware bus analyzer.
    The device is left connected for 10 min after enumeration.

    Hardware bus analyzer:
    - After enumeration, NAKs to the interrupt IN EP repeated at the rate of bInterval for these 10 min seamlessly.
    USBLyzer:
    - After enumeration, URB "Bulk or Interrupt Transfer" is issued to the interrupt IN EP, immediately.
    But no completion appears, including (Canceled), for this 10 min.

    Next, I modified the device firmware to put STALL to the interrupt IN EP, 1 sec after enumeration.
    Here is the USBLyzer trace

    URB  0051        1:42:26.306 Bulk or Interrupt Transfer 1024 bytes buffer in 01:00:81 85673DE8h USBPDO-5 usbhub 8561EE48h
    URB  0052        1:42:26.306 Bulk or Interrupt Transfer 1024 bytes buffer in 01:00:81 85673DE8h USBPDO-5 usbhub 8564D5C8h
    ...
    ...
    URB  0070-0051   1:42:27.134 Bulk or Interrupt Transfer                   in 01:00:81 85673DE8h USBPDO-5 usbhub 8561EE48h Unsuccessful (Stall PID)
    KmIO 0071        1:42:27.134 Internal USB Get Port Status                             85673DE8h USBPDO-5 usbhub 8562B880h
    KmIO 0072-0071   1:42:27.134 Internal USB Get Port Status                             85673DE8h USBPDO-5 usbhub 8562B880h Success
    URB  0073        1:42:27.134 Abort Pipe                                      01:00:81 85673DE8h USBPDO-5 usbhub 8562B880h
    URB  0074-0052   1:42:27.134 Bulk or Interrupt Transfer                   in 01:00:81 85673DE8h USBPDO-5 usbhub 8564D5C8h Cancelled (Canceled)
    URB  0075-0073   1:42:27.134 Abort Pipe                                      01:00:81 85673DE8h USBPDO-5 usbhub 8562B880h Success (Success)
    


    The hardware bus analyzer caught STALL exactly.
    USBLyzer reports "Unsuccessful (Stall PID) - USBD_STATUS_STALL_PID" for STALL from the device.
    At the same time, it reports "Cancelled (Canceled) - USBD_STATUS_CANCELED" to the paired URB.

    OK, "Cancelled" doesn't mean STALL. Instead, it means canceled (USBD_STATUS_CANCELED) by HID device driver.
    Anyway, this pattern doesn't match to your trace.

    "Cancelled" is caused by the PC side.
    Selective Suspend?
    Please post more trace around "Cancelled", including PnP events.



    > Back to the source some questions:
    > .) report size is 64(id1-in/out) / 1024(id2-in) byte
    > .) therefor the hostapp has to malloc a buffer of that size
    > .) sending an out report by writing the id (1) in the first byte leads to 63 byte data
    > .) requesting an in report also writing the id (1 or 2) in the first byte leads to an 63 or 1023 byte report (always stored in a 1024 byte "frame" within the host's memory)

    > right?

    Yes. PC HID driver works so.

    Tsuneo

  • hi,

    short answer only because i'm out of time today. i think i found a minor problem because the CHANCELLED disappered within the actual devstate. But the major problem still appears when an IN REPORT is requested through the interrupt ep. I now receive the interrupt within the dsp - fifo of the ep was loaded - but there is a permanent UNDERRUN. An underrun during the enumeration is ok (leads to an open transaction as mentioned above)

    Things i've changed until now:
    .) As mentioned before recode the STALL/NAK behaviour of ep's => should be OK now
    .) doublecheck the ZLPs => OK
    .) doublecheck the fifo loading => OK (hopefully) i will check this again tomorrow.

    i will post a longer trace tomorrow also.

    kr

  • Hi Tsuneo,

    did some checks on the source, but didn't find any further problem. But to be sure please confirm or correct the following IN REPORT using interrupt ep.
    .) report descriptor with in 2 reports different size & directions (as above) .)1 in/out(64) .)2 in(1024)
    .) during enum host gets HID CAPS and therefore adds one byte (why? i though the 64 is with id)
    .) the device writes data into the ep fifo and signals readiness (TX_READY). when data is smaller then the max packet size of the ep nothing has to be done. if data is bigger than maxp also nothing has to be done. only when the data size is a multiple of the maxp an aditional ZLP has to be sent.
    .) host writing report id in the first byte of buffer and readfile() from device.

    many thx!

  • > .) during enum host gets HID CAPS and therefore adds one byte (why? i though the 64 is with id)

    For this report descriptor (copied from the trace), HidP_GetCaps return these size,
    HIDP_CAPS.InputReportByteLength = 64 (= 1 ID + 63 body) bytes
    HIDP_CAPS.OutputReportByteLength = 1024 (= 1 ID + 1023 body, the greatest) bytes

    Interface 0 HID Report Descriptor Vendor-Defined 1
    Item Tag (Value) Raw Data
    Usage Page (Vendor-Defined 1) 06 00 FF
    Usage (Vendor-Defined 1) 09 01
    Collection (Application) A1 01
        Logical Minimum (0) 15 00
        Logical Maximum (255) 26 FF 00
        Report Size (8) 75 08
        Report ID (1) 85 01
        Report Count (63) 96 3F 00
        Usage (Vendor-Defined 1) 09 01
        Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) 81 02
        Usage (Vendor-Defined 1) 09 01
        Output (Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) 91 02
        Report ID (2) 85 02
        Report Count (1023) 96 FF 03
        Usage (Vendor-Defined 1) 09 01
        Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) 81 02
    End Collection C0
    

    > .) the device writes data into the ep fifo and signals readiness (TX_READY). when data is smaller then the max packet size of the ep nothing has to be done.
    fine.

    > if data is bigger than maxp also nothing has to be done.
    Don't write greater size than endpoint wMaxPacketSize to the endpoint.
    The rest is written when the next IN endpoint interrupt comes.

    > only when the data size is a multiple of the maxp an aditional ZLP has to be sent.
    When you send the input report of shorter size, it's fine.
    For the greatest size report, ZLP is not required.

    > .) host writing report id in the first byte of buffer and readfile() from device.
    No.
    Host can't specify coming report.
    Host always passes a buffer of the greatest size (+ report ID).
    When ReadFile returns, the first byte shows the report ID of received report.

    Tsuneo