Creating Valid IRQ Tables: Difference between revisions

From coreboot
Jump to navigation Jump to search
Line 20: Line 20:
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.
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 [[Media:my_irq_table.c|this]] file as '''my_pirq_table.c''' into kernel's '''arch/i386/pci/'''
* copy [[http://www.pengutronix.de/software/ptxdist/temporary-src/references/my_irq_table.c 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()'''.
* 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.
* Remove them with the well known '''#if 0 / #endif''' directives.

Revision as of 07:55, 16 May 2007

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 do you need

  • current Linux kernel source (this is for 2.6.21)
  • LinuxBios for your card with highest debug level output
  • (cross) toolchain to build the kernel and LinuxBios
  • a fast method to boot and reboot your card (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
  • 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 what I did

I have two PCI devices connected to my Geode GX1 based board (Companion 5530 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 states this if the DEGUB 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 exist or not.

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 prepare 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 where still at their reset value, what 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" and reboot 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 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.

Subject: [PATCH 001/001] i386/pci: fix nybble permutation and add Cyrix 5530 IRQ router
From: Juergen Beisert <juergen@kreuzholzen.de>

This patch adds CYRIX_5530_LEGACY to the list of known PCI interrupt router,
to setup chipset's routing register with valid data. It seems never be a
problem if the BIOS sets up these registers. But in the presence of LinuxBios
it fails for Cyrix 5530, due to LinuxBios does not setup these registers
(it leave it at their reset values).

I have no Cyrix 5520 to check, but as the comment in the source states the
Cyrix 5520 and Cyrix 5530 do interrupt routing in the same way. But the
(pirq-1)^1 expression to set a route always sets the wrong nibble, so
INTA/INTB and INTC/INTD are permuted and do not work as expected.

Signed-off-by: Juergen Beisert <juergen@kreuzholzen.de>

Index: arch/i386/pci/irq.c
===================================================================
--- arch/i386/pci/irq.c
+++ arch/i386/pci/irq.c
@@ -306,12 +306,12 @@ static int pirq_opti_set(struct pci_dev
  */
 static int pirq_cyrix_get(struct pci_dev *router, struct pci_dev *dev, int pirq)
 {
-	return read_config_nybble(router, 0x5C, (pirq-1)^1);
+	return read_config_nybble(router, 0x5C, pirq-1);
 }

 static int pirq_cyrix_set(struct pci_dev *router, struct pci_dev *dev, int pirq, int irq)
 {
-	write_config_nybble(router, 0x5C, (pirq-1)^1, irq);
+	write_config_nybble(router, 0x5C, pirq-1, irq);
 	return 1;
 }

@@ -642,6 +642,7 @@ static __init int cyrix_router_probe(str
 {
 	switch(device)
 	{
+		case PCI_DEVICE_ID_CYRIX_5530_LEGACY:
 		case PCI_DEVICE_ID_CYRIX_5520:
 			r->name = "NatSemi";
 			r->get = pirq_cyrix_get;