posts - 20 , comments - 57 , trackbacks - 0

Monday, July 13, 2015

x86 ISR for WCE8

In Windows CE all hardware interrupts are handled by 1 Interrupt Service Routine (ISR) at the lowest level. Its main purpose is to identify the Interrupt Request (IRQ) source and handle some low level hardware clearing and resetting of the Interrupt Controller such that new interrupts can be triggered. Once the IRQ is recognized by the ISR, it is handed over to an Interrupt Service Thread (IST). This has the advantage that the hardware IRQ priority can be overruled by the IST thread priority scheduling. Moreover when IRQs of multiple devices are shared on the same IRQ pin, each device interrupt can be handled separately with its own priority in the IST. Typically the device driver handling the IST is responsible for clearing and resetting the device hardware itself such that a new interrupt can be triggered. (Note that on x86 hardware you typically need to clear and reset on 2 levels: the Interrupt Controller (PIC or APIC) and the Device Controller)

Some time ago I was porting a BSP from WCE6 to WCE8. This didn't involve much changes (most of the time just recompilation of the drivers). However when the new WCE8 NK.BIN image was running, randomly (but most of the time), the NIC and USB functionality didn't work. The NIC (RTL8139 compatible) was recognized, but simple pinging was not possible, nor any other NIC functionality was working properly. Also 2 of the 8 USB ports didn't work. This device used an Intel ICH7 chipset. Running the same image on an Intel NM10 chipset gave no problem. Running an older WCE6 image never gave any trouble on all chipsets.

What was going on?

From the start I had a feeling that the devices IRQs had something to do with it. Because the NIC, 1 USB UHCI chipset (out of 4) and the onboard APIC controller all shared the same IRQ (reported by the BIOS via ACPI). Here is what I found in the Intel chipset configuration registers and ACPI tables:

Chipset Configuration Register Interrupt Route Register LPC PCI Config register
INT A, B, C, D PIRQ A, B, C, D, E, F, G, H 60-63:PIRQ[A-D], 68-6B:PIRQ[E-H]
3100 SMBUS B 3140 INT D -> PIRQ A
3100 SATA SIP B 3140 INT C -> PIRQ A
3100 SATA PIP A 3140 INT B -> PIRQ D PIRQ D -> IRQ15
3100 PCI BRIDGE - 3140 INT A -> PIRQ C PIRQ C -> IRQ15
3104 AC97 B 3142 INT D -> PIRQ A
3104 AC97 A 3142 INT C -> PIRQ A
3104 - - 3142 INT B -> PIRQ E PIRQ E -> IRQ 5
3104 LPC BRIDGE - 3142 INT A -> PIRQ B PIRQ B -> IRQ10
3108 EHCI A 3144 INT D -> PIRQ A PIRQ H -> IRQ 9
3108 UHCI3 D 3144 INT C -> PIRQ C PITQ A -> IRQ11
3108 UHCI2 C 3144 INT B -> PIRQ D PIRQ C -> IRQ15
3108 UHCI1 B 3144 INT A -> PIRQ H PIRQ D -> IRQ15
3108 UHCI0 A PIRQ H -> IRQ 9
310C PCIEXPRESS #6 - 3146 INT D -> PIRQ D
310C PCIEXPRESS #5 - 3146 INT C -> PIRQ C
310C PCIEXPRESS #4 - 3146 INT B -> PIRQ B
310C PCIEXPRESS #3 - 3146 INT A -> PIRQ A

ACPI Control RegisterIRQ select
LPC I/F D31:F0 PCI Reg 44 IRQ 9

NIC RTL 8139 compatibleIRQ 9

From the tables we learn that EHCI, UHCI0, ACPI and NIC all shared IRQ9. That couldn't be a coincidence.

Let us take a look at 'WINCE800\platform\<yourbsp>\SRC\x86\COMMON\intr\fwpc.c'. It contains the function static ULONG PeRPISR() which is the ISR we talked about in the introduction. Comparing the source code between WCE6 and WCE8 revealed that the spurious interrupt handling code was changed (near the end of PeRPISR()). In WCE8 the reset of the 8259 PIC interrupt controller is not executed inside the ISR but instead delayed to a separate background thread. If we revert the code back to how it was on WCE6, the problems disappeared again.

Probably the delayed spurious interrupt handling resets the PIC too late (i.e. re-enable of PIC IRQ#), causing other interrupts to be lost. It is also worthwhile to mention that enabling the UHCI0 function in ICH7 caused an unwanted single occurrence spurious interrupt of the ACPI controller, causing this problem. Other Intel chipsets did not show this behaviour...

if (ulRet == SYSINTR_NOP) { // If SYSINTR_NOP, IRQ claimed by installed ISR, but no further action required PICEnableInterrupt(ucCurrentInterrupt, TRUE); } else if (ulRet == SYSINTR_UNDEFINED || !NKIsSysIntrValid(ulRet)) { // If SYSINTR_UNDEFINED, ignore // If SysIntr was never initialized, ignore OALMSGS(OAL_WARN&&OAL_INTR, (L"Spurious interrupt on IRQ %d\r\n", ucCurrentInterrupt)); #if 0 // This code is new since CE7, but gives sometimes trouble. When UHCI0 is enabled on ICH7 and its interrupt is shared with ACPI on IRQ9 // a spurious interrupt occurs (probably because of ACPI, although not enabled). // If the same IRQ is shared with other PCI devices (like ethernet), the device will also malfunction (losing interrupts) // When the next line is used, it will trigger OALUnknownIRQHandler() in map_refcount.c but it comes too late. // Hence many interrupts might already been masked out for a too long period... ulRet = OALMarkUnknownIRQ(ucCurrentInterrupt); #else // Instead it is better to use the pre CE7 code that re-enables interrupt on the PIC immediately. ulRet = SYSINTR_NOP; PICEnableInterrupt(ucCurrentInterrupt, TRUE); #endif }

Useful references:

Posted On Monday, July 13, 2015 10:23 PM | Comments (1) | Filed Under [ Windows CE Windows Embedded Compact USB UHCI EHCI APIC ACPI Interrupt IRQ PIC Intel ]

Powered by: