Hi,
For a LPC23xx device, I'm adding HID besides MSC. I got everything working fine, except for HID interrupt IN transfers. So I can get and set HID reports via control transfers. Also, I can set reports via interrupt OUT transfers, but not IN.
Using USBlyzer, I diagnosed the packet stream. As soon as I attach my device, I do see two interrupt IN transfer attempts at 01:00:81 (C:I:E). If my host application reads EP 1, USBlyzer does not show any packets passing by.
I'm a bit out of options. Does anyone have a clue where to look for a fix?
Thanks,
Mark
Connection Status Device connected Current Configuration 1 Speed Full Device Address 3 Number Of Open Pipes 4 Device Descriptor Swinxs Game Console Offset Field Size Value Description 0 bLength 1 12h 1 bDescriptorType 1 01h Device 2 bcdUSB 2 0200h USB Spec 2.0 4 bDeviceClass 1 00h Class info in Ifc Descriptors 5 bDeviceSubClass 1 00h 6 bDeviceProtocol 1 00h 7 bMaxPacketSize0 1 40h 64 bytes 8 idVendor 2 C251h Keil Software, Inc. 10 idProduct 2 1708h 12 bcdDevice 2 0203h 2.03 14 iManufacturer 1 04h "Swinxs BV " 15 iProduct 1 20h "Swinxs Game Console" 16 iSerialNumber 1 48h "000050020801" 17 bNumConfigurations 1 01h Configuration Descriptor 1 Bus Powered, 300 mA Offset Field Size Value Description 0 bLength 1 09h 1 bDescriptorType 1 02h Configuration 2 wTotalLength 2 0040h 4 bNumInterfaces 1 02h 5 bConfigurationValue 1 01h 6 iConfiguration 1 00h 7 bmAttributes 1 80h Bus Powered 4..0: Reserved ...00000 5: Remote Wakeup ..0..... No 6: Self Powered .0...... No, Bus Powered 7: Reserved (set to one) (bus-powered for 1.0) 1....... 8 bMaxPower 1 96h 300 mA Interface Descriptor 0/0 HID, 2 Endpoints Offset Field Size Value Description 0 bLength 1 09h 1 bDescriptorType 1 04h Interface 2 bInterfaceNumber 1 00h 3 bAlternateSetting 1 00h 4 bNumEndpoints 1 02h 5 bInterfaceClass 1 03h HID 6 bInterfaceSubClass 1 00h 7 bInterfaceProtocol 1 00h 8 iInterface 1 80h "Swinxs HID" HID Descriptor Offset Field Size Value Description 0 bLength 1 09h 1 bDescriptorType 1 21h HID 2 bcdHID 2 0110h 1.10 4 bCountryCode 1 00h 5 bNumDescriptors 1 01h 6 bDescriptorType 1 22h Report 7 wDescriptorLength 2 0045h 69 bytes Endpoint Descriptor 81 1 In, Interrupt Offset Field Size Value Description 0 bLength 1 07h 1 bDescriptorType 1 05h Endpoint 2 bEndpointAddress 1 81h 1 In 3 bmAttributes 1 03h Interrupt 1..0: Transfer Type ......11 Interrupt 7..2: Reserved 000000.. 4 wMaxPacketSize 2 0001h 1 byte 6 bInterval 1 00h Endpoint Descriptor 01 1 Out, Interrupt Offset Field Size Value Description 0 bLength 1 07h 1 bDescriptorType 1 05h Endpoint 2 bEndpointAddress 1 01h 1 Out 3 bmAttributes 1 03h Interrupt 1..0: Transfer Type ......11 Interrupt 7..2: Reserved 000000.. 4 wMaxPacketSize 2 0006h 6 bytes 6 bInterval 1 00h Interface Descriptor 1/0 Mass Storage, 2 Endpoints Offset Field Size Value Description 0 bLength 1 09h 1 bDescriptorType 1 04h Interface 2 bInterfaceNumber 1 01h 3 bAlternateSetting 1 00h 4 bNumEndpoints 1 02h 5 bInterfaceClass 1 08h Mass Storage 6 bInterfaceSubClass 1 06h SCSI Transparent Command Set 7 bInterfaceProtocol 1 50h Bulk-Only Transport 8 iInterface 1 62h "Swinxs Storage" Endpoint Descriptor 82 2 In, Bulk, 64 bytes Offset Field Size Value Description 0 bLength 1 07h 1 bDescriptorType 1 05h Endpoint 2 bEndpointAddress 1 82h 2 In 3 bmAttributes 1 02h Bulk 1..0: Transfer Type ......10 Bulk 7..2: Reserved 000000.. 4 wMaxPacketSize 2 0040h 64 bytes 6 bInterval 1 00h Endpoint Descriptor 02 2 Out, Bulk, 64 bytes Offset Field Size Value Description 0 bLength 1 07h 1 bDescriptorType 1 05h Endpoint 2 bEndpointAddress 1 02h 2 Out 3 bmAttributes 1 02h Bulk 1..0: Transfer Type ......10 Bulk 7..2: Reserved 000000.. 4 wMaxPacketSize 2 0040h 64 bytes 6 bInterval 1 00h Interface 0 HID Report Descriptor Item Tag (Value) Raw Data Usage Page (Vendor-Defined 1) 06 00 FF Usage (Undefined) 09 00 Collection (Application) A1 01 Usage Page (Vendor-Defined 2) 06 01 FF Usage (Vendor-Defined 1) 09 01 Logical Minimum (1) 15 01 Logical Maximum (16777215) 27 FF FF FF 00 Report Count (1) 95 01 Report Size (24) 75 18 Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) 81 02 Usage (Vendor-Defined 2) 09 02 Report Count (1) 95 01 Report Size (16) 75 10 Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) 81 02 Usage Minimum (Vendor-Defined 1) 19 01 Usage Maximum (Vendor-Defined 3) 29 03 Logical Minimum (0) 15 00 Logical Maximum (1) 25 01 Report Count (3) 95 03 Report Size (1) 75 01 Input (Data,Var,Abs,NWrp,Lin,Pref,NNul,Bit) 81 02 Report Count (1) 95 01 Report Size (5) 75 05 Input (Cnst,Ary,Abs) 81 01 Usage Page (Vendor-Defined 3) 06 02 FF Usage (Vendor-Defined 16) 09 10 Logical Minimum (0) 15 00 Logical Maximum (100) 25 64 Report Count (1) 95 01 Report Size (8) 75 08 Output (Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit) 91 02 End Collection C0
Endpoint Descriptor 81 1 In, Interrupt --- HID interrupt IN EP Offset Field Size Value Description 0 bLength 1 07h 1 bDescriptorType 1 05h Endpoint 2 bEndpointAddress 1 81h 1 In 3 bmAttributes 1 03h Interrupt 1..0: Transfer Type ......11 Interrupt 7..2: Reserved 000000.. 4 wMaxPacketSize 2 0001h 1 byte 6 bInterval 1 00h
bInterval field of EP1 is 00h. Set it more than 1 (ms)
Tsuneo
Ah, you have to fix EP1 OUT, too.
Endpoint Descriptor 01 1 Out, Interrupt ... 6 bInterval 1 00h
Earlier I did set the interval values for the EP1 descriptors to 0x20. So on your advise, I restored these values. The reason I set those to 0 was because I saw the following SetIdle packet passing by:
Offset Field Size Value Description 0 bmRequestType 1 21h 4..0: Recipient ...00001 Interface 6..5: Type .01..... Class 7: Direction 0....... Host-to-Device 1 bRequest 1 0Ah Set Idle 2 wValue.LowByte 1 00h Report ID 3 wValue.HiByte 1 00h Infinite Idle Rate 4 wIndex 2 0000h Interface 6 wLength 2 0000h
The OS HID driver sent it to my device. So I thought I could set it 0 already in the EP descriptor.
Anyway, even if I define bInterval by 0x20, I still can't send interrupt IN transfers to my device. The MSC interface has no problems sending IN-OUT bulk transfers. Strange... I used Jan Axelsson's Generic HID tester to test.
> The reason I set those to 0 was because I saw the following SetIdle packet passing by:
Just after enumeration, Windows puts SetIdle( 0 ) - indefinite, the device gives the timing when it sends input report MacOSX SetIdle( 9 ) - 36 ms, and Linux nothing.
bInterval of the interrupt endpoints are chosen independently from OS. The requirement of your application determines the value. When your application needs to send data from device frequently, smaller value is assigned. Otherwise, greater value. Anyway, 0 isn't allowed for bInterval of interrupt endpoints. > Anyway, even if I define bInterval by 0x20, I still can't send interrupt IN transfers to my device.
It is the device side problem, on the interrupt IN endpoint handling. Even if your PC application doesn't read any input report, HID device driver (*1) repeats IN transactions at the specified interval without break.
If you don't see any IN packet on the sniffer, - Your firmware puts nothing to the endpoint. If you see IN packet on the sniffer, but don't see any input report on the application, - Your firmware puts some data, but the format (data length) is wrong. The packet is dropped by the device driver.
I'll show you how HID example handles the interrupt IN endpoint.
This is an excerpt from KEIL HID implementation. LPC2368 / LPC2378 USB HID (Human Interface Device) Example http://www.keil.com/download/docs/335.asp
usbuser.c #if USB_CONFIGURE_EVENT void USB_Configure_Event (void) { if (USB_Configuration) { /* Check if USB is configured */ GetInReport(); USB_WriteEP(0x81, &InReport, sizeof(InReport)); } } #endif void USB_EndPoint1 (DWORD event) { switch (event) { case USB_EVT_IN: GetInReport(); USB_WriteEP(0x81, &InReport, sizeof(InReport)); break; } }
USB_Configure_Event() is called when Set_Configuration request comes from host on enumeration. In this routine, an input report is loaded to the EP1 IN buffer using USB_WriteEP(). This report is the first one. When host starts to repeat IN transactions just after enumeration, this report is sent to the host. And then, USB interrupt occurs at the IN EP1.
On this interrupt, USB_EndPoint1() is called. This routine fills the endpoint buffer with a report using USB_WriteEP(). This report is sent to host at the next IN transaction after the specified interval (bInterval). Another endpoint interrupt occurs, and USB_EndPoint1() is called again.
In this way, USB_EndPoint1() is called repeatedly, at bInterval.
If you put nothing to the endpoint buffer in USB_EndPoint1(), what happens? No report is sent to the host, and no endpoint interrupt occurs. The sequence stops, USB_EndPoint1() is never called any more, until you fill the endpoint buffer with USB_WriteEP().
I suppose this is your story, isn't it?
(*1) Actually, it is the host controller which repeats IN transactions periodically. HID device driver keeps IN transfer, putting one after another.
> I suppose this is your story, isn't it?
Tsuneo, right on the spot! Indeed filling in the code for USB_Configure_Event() started the EP1 IN polling. Thank you a lot for pointing it out to me, I don't think I would have found it myself.
Actually, I had to increase EP1-IN wMaxPacketSize from 6 (the size of the report) to 8. I was under the impression that wMaxPacketSize should be large enough to contain the report size (in my case 6). Can you advise me for the optimal wMaxPacketSize?
Another issue I encounter is that my application seems to miss some GetReports(). Probably because my application polls the reports less often then the host does. But I'm convident I can sort this out myself :)
The evil of this HID example is that it gives a fixed idea - read/write of endpoint buffer should be done just in the endpoint ISR. It's wrong. Rather, read/write in the endpoint ISR is a rare case in the real world code.
Consider on UART. If you would see a UART example in which UART TX ISR seamlessly puts character one after another, what do you think of the quality of the example? Is the example practical? Never. You'll take it that a beginner have made the example.
The quality of this Keil HID example is in such a level. Not just for Keil. Still low-quality USB examples have been spread out from other compiler/ chip vendors. Isn't it the time to brush them up?
You may write to the IN EP buffer using USB_WriteEP(), any where, any timing as you like (unless the EP buffer is still occupied by the last write). For example, you may write to the IN EP buffer in a timer ISR, which scans ports for keypads. You may write in ADC ISR, just when a sensor input shows change.
These ISRs should have the same priority (FAST/SLOW) as USB ISR, so that USB_WriteEP() calls from both ISRs don't interrupt each other. When you call USB_WriteEP() from main line code, disable USB interrupt temporarily around USB_WriteEP() call. Or make a SWI which calls USB_WriteEP(). > Actually, I had to increase EP1-IN wMaxPacketSize from 6 (the size of the report) to 8. I was under the impression that wMaxPacketSize should be large enough to contain the report size (in my case 6). Can you advise me for the optimal wMaxPacketSize?
wMaxPacketSize field on endpoint descriptor is set to greater or equal to the packet size you exchange. When you exchange 6 bytes, wMaxPacketSize is set to 6, or more. Greater size doesn't do any harm but no benefit. It just reserves more bandwidth on the bus. > Another issue I encounter is that my application seems to miss some GetReports(). Probably because my application polls the reports less often then the host does. But I'm convident I can sort this out myself :)
Get_Report request is sent from host just when PC app calls HidD_GetInputReport() (Windows XP and later). When the app calls it synchronously, the app is blocked until this call finishes. Then, you'd not miss it.
ReadFile() pulls an input report from the input buffer on the HID PC device driver, which is filled with the reports carried from the interrupt IN endpoint on the device. The number of input reports held in this input buffer is altered using HidD_SetNumInputBuffers(). The default is 32 reports.
> You may write to the IN EP buffer using USB_WriteEP(), any where, any timing as you like
Aha, now I see that it's a bit tricky to use the ISR to fill the buffer. I rewrote the device firmware to us USB_WriteEP() asynchronously: as soon as data for a new report becomes available, I write a report immediately. And my code got much cleaner now :)
> Or make a SWI which calls USB_WriteEP().
If I'm right, __swi is only available for the RTX kernel, which we don't use. So that leaves my with two options:
VICIntEnClr |= 1 << VIC_USB; /* Disable Interrupt */ .. VICIntEnable |= 1 << VIC_USB; /* Enable Interrupt */
or
int was_masked = __disable_irq(); .. if (!was_masked) __enable_irq();
Is one way preferred over the other?
> wMaxPacketSize field on endpoint descriptor is set to greater or equal to the packet size you exchange.
I found the solution to my problem. I defined the packet correctly. But because I used a char* as type for InReport and OutReport, these variables or byte aligned. What I didn't knew was that USB_WriteEP() really needs the buffer to be long-aligned (at least in the implementation I use). So adding __align(4) to the declarations fixed the bug.
> The number of input reports held in this input buffer is altered using HidD_SetNumInputBuffers().
Yes indeed. My application should read all available input reports every cycle, not just one. Then I'll receive all reports and won't miss one.
Thank you Tsuneo, you helped me again! I'm about to round up my project as the code seems stable now :)
No, __swi isn't only available with RTX. The main difference is that RTX reserves a couple of numbers. If I remember correct, __swi(0) to __swi(7) are served by RTX.
Hello,
Use of __swi does not depend on RTX. The difference is that RTX already contains a SWI handler and SWI functions __swi(0) to __swi(7) are reserved. If you do not use RTX and want to use SWI functions than you must include an own SWI handler. Please check example C:\Keil\ARM\Examples\SWI.
Best Regards, Martin Guenther
> now I see that it's a bit tricky to use the ISR to fill the buffer.
Natural. The endpoint ISR is triggered by the host. In most cases, the timing isn't suitable for the device side. Devices want to pass a packet to the USB engine, just at the timing when it is available on the device. And then, the code flow gets straighten out on the firmware source.
When you take this strategy, you don't need to put a packet in USB_Configure_Event(), any more.
To make this strategy robust, Shorter bInterval of the endpoint descriptor - less than or equal to 1/3 of expected interval on the firmware.
Suppose that, you have keypad scan in 20 ms interval. Just when any key change occurs, the result is sent to host using an input report over the HID interrupt IN EP. The least interval of the report is 20 ms. (with debounce, it may be longer, though) In this case, bInterval is set to 6 ms (<= 20 / 3), or less.
The magic number 3 derives as follows, The host repeats to poll the IN EP at bInterval (*1) using IN transactions. When noise disturbs the transaction, host retires it after bInterval. Host repeats three error-retry (including the first one - "Three strikes, OUT" rule), while error occurs. Until host succeeds, the EP buffer is occupied by the last packet. The transfer delays for three bInterval duration, at most.
(*1) USB spec doesn't guarantee that the IN EP is polled exactly at bInterval. It allows that the actual polling interval is equal to or less than bInterval. Actual interval is determined by the host controller spec and device driver implementation on the host side. OHCI clearly notes that it reduces bInterval to 1, 2, 4, 8, 16, 32 ms The Other inplementations of host controllers also follow this rule.
OpenHCI - OHCI spec (hcir1_0a.pdf) download.microsoft.com/.../hci_1.exe
5.2.7.2 Interrupt The lower five bits of the current frame number is used as an index into an array of 32 interrupt Endpoint Descriptor lists found in the HCCA. This means each list is revisited once every 32 ms.
5.2.7.2.1 Polling Rate Interrupt Endpoint Descriptors have a minimum rate for which they need to be scheduled. When this information is provided to Host Controller Driver, it determines the closest power of 2 rate below the endpoints requirement and determines which scheduling queue for that rate has the smallest committed bandwidth.
You'll find __swi version of USB_WriteEP in Keil USBCDC example, as follows.
C:\Keil\ARM\Boards\Keil\MCB2300\USBCDC usbhw.h extern DWORD __swi(8) USB_WriteEP (DWORD EPNum, BYTE *pData, DWORD cnt); usbhw.c /* * Write USB Endpoint Data * Parameters: EPNum: Endpoint Number * EPNum.0..3: Address * EPNum.7: Dir * pData: Pointer to Data Buffer * cnt: Number of bytes to write * Return Value: Number of bytes written */ DWORD __swi(8) USB_WriteEP (DWORD EPNum, BYTE *pData, DWORD cnt); DWORD __SWI_8 (DWORD EPNum, BYTE *pData, DWORD cnt) { //DWORD USB_WriteEP (DWORD EPNum, BYTE *pData, DWORD cnt) { DWORD n; USB_CTRL = ((EPNum & 0x0F) << 2) | CTRL_WR_EN; TX_PLENGTH = cnt; for (n = 0; n < (cnt + 3) / 4; n++) { TX_DATA = *((__packed DWORD *)pData); pData += 4; } USB_CTRL = 0; WrCmd(CMD_SEL_EP(EPAdr(EPNum))); WrCmd(CMD_VALID_BUF); return (cnt); }
In the background, SWI.s is dragged in from RL_ARM,
;/*****************************************************************************/ ;/* SWI.S: SWI Handler */ ;/*****************************************************************************/ ;/* This file is part of the uVision/ARM development tools. */ ;/* Copyright (c) 2005-2006 Keil Software. All rights reserved. */ ;/* This software may only be used under the terms of a valid, current, */ ;/* end user licence from KEIL for a compatible version of KEIL software */ ;/* development tools. Nothing else gives you the right to use this software. */ ;/*****************************************************************************/ T_Bit EQU 0x20 PRESERVE8 ; 8-Byte aligned Stack AREA SWI_Area, CODE, READONLY ARM EXPORT SWI_Handler SWI_Handler STMFD SP!, {R12, LR} ; Store R12, LR MRS R12, SPSR ; Get SPSR STMFD SP!, {R8, R12} ; Store R8, SPSR TST R12, #T_Bit ; Check Thumb Bit LDRNEH R12, [LR,#-2] ; Thumb: Load Halfword BICNE R12, R12, #0xFF00 ; Extract SWI Number LDREQ R12, [LR,#-4] ; ARM: Load Word BICEQ R12, R12, #0xFF000000 ; Extract SWI Number LDR R8, SWI_Count CMP R12, R8 BHS SWI_Dead ; Overflow ADR R8, SWI_Table LDR R12, [R8,R12,LSL #2] ; Load SWI Function Address MOV LR, PC ; Return Address BX R12 ; Call SWI Function LDMFD SP!, {R8, R12} ; Load R8, SPSR MSR SPSR_cxsf, R12 ; Set SPSR LDMFD SP!, {R12, PC}^ ; Restore R12 and Return SWI_Dead B SWI_Dead ; None Existing SWI SWI_Cnt EQU (SWI_End-SWI_Table)/4 SWI_Count DCD SWI_Cnt IMPORT __SWI_8 SWI_Table DCD SWI_Dead ; SWI 0 Function Entry used by RTX DCD SWI_Dead ; SWI 1 Function Entry used by RTX DCD SWI_Dead ; SWI 2 Function Entry used by RTX DCD SWI_Dead ; SWI 3 Function Entry used by RTX DCD SWI_Dead ; SWI 4 Function Entry used by RTX DCD SWI_Dead ; SWI 5 Function Entry used by RTX DCD SWI_Dead ; SWI 6 Function Entry used by RTX DCD SWI_Dead ; SWI 7 Function Entry used by RTX DCD __SWI_8 ; SWI 8 Function Entry ; ... SWI_End END
Also, LPC2300.s is modified a little to support SWI (SVC)
LPC2300.s SVC_Stack_Size EQU 0x00000100 Vectors LDR PC, Reset_Addr LDR PC, Undef_Addr ... ... IRQ_Addr DCD IRQ_Handler FIQ_Addr DCD FIQ_Handler IMPORT SWI_Handler Undef_Handler B Undef_Handler ;SWI_Handler B SWI_Handler PAbt_Handler B PAbt_Handler DAbt_Handler B DAbt_Handler IRQ_Handler B IRQ_Handler FIQ_Handler B FIQ_Handler