Creating Valid IRQ Tables
Generating a valid interrupt routing table
This article should help people who can't read back a valid interrupt routing table from their system.
What you will need
- current Linux kernel source (this is for 2.6.21)
- LinuxBIOS for your mainboard with highest debug level output
- (cross) toolchain to build the kernel and LinuxBIOS
- a fast method to boot and reboot your mainboard (for example etherboot and NFS root filesystem)
- some data about your chipset and how it routes exernal interrupts
- and very important: Ensure the kernel knows your interrupt router
For documentation only: Here is the generic routing for up to four slots
Patching the development kernel
We start with a faked pirq table for LinuxBIOS to make its build system happy. It really doesn't matter what's in it, due to we will provide Linux itself with its own table.
- copy this file as my_pirq_table.c into kernel's arch/i386/pci/
- open arch/i386/pci/irq.c with your favorite editor and search for the functions pirq_find_routing_table() and pirq_check_routing_table().
- Remove them with the well known #if 0 / #endif directives.
- add a #include "my_pirq_table.c" to the code (after the #defines for PIRQ_SIGNATURE/PIRQ_VERSION)
- modify arch/i386/pci/pci.h and define the DEBUG macro. Linux will output helpful info, when this macro is enabled.
- modify my_pirq_table.c for your requirements. Search for TODO in the source where to change something
- define slot/device count
- define the interrupt router
- select some IRQ channels you like to use for the PCI devices
- select only one IRQ channel per INT?# in (it makes this job easier)
Here's what I did
I have two PCI devices connected to my Geode GX1 based board (CS5530 chipset):
- The internal USB OHCI
- and an external Realtek 8139 network chip.
There is no additional slot, it is a very small Win-Terminal.
I assumed the Realtek outputs its interrupt on its INTA# pin (the starting kernel will state this if the DEBUG macro in pci.h is defined, so read the dmesg output if you're unsure). The same I assumed for the USB device. Also I assumed RealTek's INTA# output is connected to chipset's INTA# input, and USB's INTA# output is connected to chipset's INTB# input.
To find a start, I tried to use IRQ15 and IRQ11 for these devices. I wanted the network device connected to IRQ15 (green line) and the USB device connected to IRQ11 (red line). The PCI chip info the table needs I got from LinuxBIOS, when it scans the PCI bus. So activate a higher level of debug output in LinuxBIOS to collect all information you need here. Ignore the black lines in the picture. They may or may not exist.
So my first routing table in "my_pirq_table.c" looked like this:
00:0f slot=00 0:01/8000 1:02/0800 2:03/0000 3:04/0000 00:13 slot=00 0:02/0800 1:03/0000 2:04/0000 3:01/8000
Building this kernel and preparing it for boot
$ mkelfImage "--command-line=console=ttyS0,115200 ip=dhcp rw root=/dev/nfs irqpoll" --type=bzImage-i386 --kernel=arch/i386/boot/bzImage --output=/tftpboot/igel-kernel
The important kernel parameter is irqpoll. This lets the network card do its job without a valid interrupt routing! It slows down the system, but makes it work! (in my case the NFS root filesystem)
Boot this kernel (I did it with etherboot). When the system is up and running, login to it and run a
$ cat /proc/interrupts
to see what happens with the interrupts.
- "ping" the network card and check if its interrupt count increases. Check all the other PCI devices in your system and let them generate interrupts. Always check /proc/interrupts to see which interrupt get this signal.
- In my case I got nothing both on the network and the USB interrupt. A "ping" does not increase any interrupt count. Then I attached an USB hub to my system and IRQ15 count increases. BINGO! This means the network device isn't connected to chipset's INTA# input, its the USB device instead! And it also means INTB# does not receive any interrupt, so it seems not connected to any device. For the next step I used INTC# for the network device instead.
Note: Check the interrupt steering setting the Linux routing setup stores into the IRQ router! In my case it took hours to see, Linux didn't know my IRQ router! So the steering registers were still at their reset value, which means "No routing"! After adding the IRQ router support for my chipset it routes the interrupts!
- I changed the routing table in "my_pirq_table.c" again to the routing shown below, rebuilt the kernel and reboot
00:13 slot=00 0:01/0800 1:02/0000 2:03/8000 3:04/0000 00:0f slot=00 0:03/8000 1:04/0000 2:01/0800 3:02/0000
This is the source I used in this case.
With this table also the network interrupt receives signals. Then I removed the "irqpoll" option and rebooted this kernel again. The system runs with interrupts only (and no more polling).
The interrupt routing table from my_pirq_table.c is now ready to be used in LinuxBIOS. Only the checksum may not be usable.
My kernel patch ignores a wrong checksum but calculates the right one instead! See dmesg's output, search for the right checksum and change it in the source file. Rebuild LinuxBIOS with this new IRQ routing table and flash it into your target.
Now you can run an unpatched kernel on your system.
Required patches for Geode's companion CS5530
- This patch is needed to let Linux know the Cyrix 5530 interrupt router.