patch-2.3.20 linux/arch/i386/kernel/pci-pc.c

Next file: linux/arch/i386/kernel/pci-visws.c
Previous file: linux/arch/i386/kernel/pci-i386.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.19/linux/arch/i386/kernel/pci-pc.c linux/arch/i386/kernel/pci-pc.c
@@ -0,0 +1,1225 @@
+/*
+ *	Low-Level PCI Support for PC
+ *
+ *	(c) 1999 Martin Mares <mj@ucw.cz>
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/malloc.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+
+#include <asm/segment.h>
+#include <asm/io.h>
+#include <asm/smp.h>
+
+#include "pci-i386.h"
+
+unsigned int pci_probe = PCI_PROBE_BIOS | PCI_PROBE_CONF1 | PCI_PROBE_CONF2;
+
+/*
+ * IRQ routing table provided by the BIOS
+ */
+
+struct irq_info {
+	u8 bus, devfn;			/* Bus, device and function */
+	struct {
+		u8 link;		/* IRQ line ID, chipset dependent, 0=not routed */
+		u16 bitmap;		/* Available IRQs */
+	} __attribute__((packed)) irq[4];
+	u8 slot;			/* Slot number, 0=onboard */
+	u8 rfu;
+} __attribute__((packed));
+
+struct irq_routing_table {
+	u32 signature;			/* PIRQ_SIGNATURE should be here */
+	u16 version;			/* PIRQ_VERSION */
+	u16 size;			/* Table size in bytes */
+	u8 rtr_bus, rtr_devfn;		/* Where the interrupt router lies */
+	u16 exclusive_irqs;		/* IRQs devoted exclusively to PCI usage */
+	u16 rtr_vendor, rtr_device;	/* Vendor and device ID of interrupt router */
+	u32 miniport_data;		/* Crap */
+	u8 rfu[11];
+	u8 checksum;			/* Modulo 256 checksum must give zero */
+	struct irq_info slots[0];
+} __attribute__((packed));
+
+/*
+ * Direct access to PCI hardware...
+ */
+
+#ifdef CONFIG_PCI_DIRECT
+
+/*
+ * Functions for accessing PCI configuration space with type 1 accesses
+ */
+
+#define CONFIG_CMD(dev, where)   (0x80000000 | (dev->bus->number << 16) | (dev->devfn << 8) | (where & ~3))
+
+static int pci_conf1_read_config_byte(struct pci_dev *dev, int where, u8 *value)
+{
+	outl(CONFIG_CMD(dev,where), 0xCF8);
+	*value = inb(0xCFC + (where&3));
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static int pci_conf1_read_config_word(struct pci_dev *dev, int where, u16 *value)
+{
+	outl(CONFIG_CMD(dev,where), 0xCF8);    
+	*value = inw(0xCFC + (where&2));
+	return PCIBIOS_SUCCESSFUL;    
+}
+
+static int pci_conf1_read_config_dword(struct pci_dev *dev, int where, u32 *value)
+{
+	outl(CONFIG_CMD(dev,where), 0xCF8);
+	*value = inl(0xCFC);
+	return PCIBIOS_SUCCESSFUL;    
+}
+
+static int pci_conf1_write_config_byte(struct pci_dev *dev, int where, u8 value)
+{
+	outl(CONFIG_CMD(dev,where), 0xCF8);    
+	outb(value, 0xCFC + (where&3));
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static int pci_conf1_write_config_word(struct pci_dev *dev, int where, u16 value)
+{
+	outl(CONFIG_CMD(dev,where), 0xCF8);
+	outw(value, 0xCFC + (where&2));
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static int pci_conf1_write_config_dword(struct pci_dev *dev, int where, u32 value)
+{
+	outl(CONFIG_CMD(dev,where), 0xCF8);
+	outl(value, 0xCFC);
+	return PCIBIOS_SUCCESSFUL;
+}
+
+#undef CONFIG_CMD
+
+static struct pci_ops pci_direct_conf1 = {
+	pci_conf1_read_config_byte,
+	pci_conf1_read_config_word,
+	pci_conf1_read_config_dword,
+	pci_conf1_write_config_byte,
+	pci_conf1_write_config_word,
+	pci_conf1_write_config_dword
+};
+
+/*
+ * Functions for accessing PCI configuration space with type 2 accesses
+ */
+
+#define IOADDR(devfn, where)	((0xC000 | ((devfn & 0x78) << 5)) + where)
+#define FUNC(devfn)		(((devfn & 7) << 1) | 0xf0)
+#define SET(dev)		if (dev->devfn) return PCIBIOS_DEVICE_NOT_FOUND;		\
+				outb(FUNC(dev->devfn), 0xCF8);					\
+				outb(dev->bus->number, 0xCFA);
+
+static int pci_conf2_read_config_byte(struct pci_dev *dev, int where, u8 *value)
+{
+	SET(dev);
+	*value = inb(IOADDR(dev->devfn,where));
+	outb (0, 0xCF8);
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static int pci_conf2_read_config_word(struct pci_dev *dev, int where, u16 *value)
+{
+	SET(dev);
+	*value = inw(IOADDR(dev->devfn,where));
+	outb (0, 0xCF8);
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static int pci_conf2_read_config_dword(struct pci_dev *dev, int where, u32 *value)
+{
+	SET(dev);
+	*value = inl (IOADDR(dev->devfn,where));    
+	outb (0, 0xCF8);    
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static int pci_conf2_write_config_byte(struct pci_dev *dev, int where, u8 value)
+{
+	SET(dev);
+	outb (value, IOADDR(dev->devfn,where));
+	outb (0, 0xCF8);    
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static int pci_conf2_write_config_word(struct pci_dev *dev, int where, u16 value)
+{
+	SET(dev);
+	outw (value, IOADDR(dev->devfn,where));
+	outb (0, 0xCF8);    
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static int pci_conf2_write_config_dword(struct pci_dev *dev, int where, u32 value)
+{
+	SET(dev);
+	outl (value, IOADDR(dev->devfn,where));    
+	outb (0, 0xCF8);    
+	return PCIBIOS_SUCCESSFUL;
+}
+
+#undef SET
+#undef IOADDR
+#undef FUNC
+
+static struct pci_ops pci_direct_conf2 = {
+	pci_conf2_read_config_byte,
+	pci_conf2_read_config_word,
+	pci_conf2_read_config_dword,
+	pci_conf2_write_config_byte,
+	pci_conf2_write_config_word,
+	pci_conf2_write_config_dword
+};
+
+/*
+ * Before we decide to use direct hardware access mechanisms, we try to do some
+ * trivial checks to ensure it at least _seems_ to be working -- we just test
+ * whether bus 00 contains a host bridge (this is similar to checking
+ * techniques used in XFree86, but ours should be more reliable since we
+ * attempt to make use of direct access hints provided by the PCI BIOS).
+ *
+ * This should be close to trivial, but it isn't, because there are buggy
+ * chipsets (yes, you guessed it, by Intel and Compaq) that have no class ID.
+ */
+static int __init pci_sanity_check(struct pci_ops *o)
+{
+	u16 x;
+	struct pci_bus bus;		/* Fake bus and device */
+	struct pci_dev dev;
+
+	if (pci_probe & PCI_NO_CHECKS)
+		return 1;
+	bus.number = 0;
+	dev.bus = &bus;
+	for(dev.devfn=0; dev.devfn < 0x100; dev.devfn++)
+		if ((!o->read_word(&dev, PCI_CLASS_DEVICE, &x) &&
+		     (x == PCI_CLASS_BRIDGE_HOST || x == PCI_CLASS_DISPLAY_VGA)) ||
+		    (!o->read_word(&dev, PCI_VENDOR_ID, &x) &&
+		     (x == PCI_VENDOR_ID_INTEL || x == PCI_VENDOR_ID_COMPAQ)))
+			return 1;
+	DBG("PCI: Sanity check failed\n");
+	return 0;
+}
+
+static struct pci_ops * __init pci_check_direct(void)
+{
+	unsigned int tmp;
+	unsigned long flags;
+
+	__save_flags(flags); __cli();
+
+	/*
+	 * Check if configuration type 1 works.
+	 */
+	if (pci_probe & PCI_PROBE_CONF1) {
+		outb (0x01, 0xCFB);
+		tmp = inl (0xCF8);
+		outl (0x80000000, 0xCF8);
+		if (inl (0xCF8) == 0x80000000 &&
+		    pci_sanity_check(&pci_direct_conf1)) {
+			outl (tmp, 0xCF8);
+			__restore_flags(flags);
+			printk("PCI: Using configuration type 1\n");
+			return &pci_direct_conf1;
+		}
+		outl (tmp, 0xCF8);
+	}
+
+	/*
+	 * Check if configuration type 2 works.
+	 */
+	if (pci_probe & PCI_PROBE_CONF2) {
+		outb (0x00, 0xCFB);
+		outb (0x00, 0xCF8);
+		outb (0x00, 0xCFA);
+		if (inb (0xCF8) == 0x00 && inb (0xCFA) == 0x00 &&
+		    pci_sanity_check(&pci_direct_conf2)) {
+			__restore_flags(flags);
+			printk("PCI: Using configuration type 2\n");
+			return &pci_direct_conf2;
+		}
+	}
+
+	__restore_flags(flags);
+	return NULL;
+}
+
+#endif
+
+/*
+ * BIOS32 and PCI BIOS handling.
+ */
+
+#ifdef CONFIG_PCI_BIOS
+
+#define PCIBIOS_PCI_FUNCTION_ID 	0xb1XX
+#define PCIBIOS_PCI_BIOS_PRESENT 	0xb101
+#define PCIBIOS_FIND_PCI_DEVICE		0xb102
+#define PCIBIOS_FIND_PCI_CLASS_CODE	0xb103
+#define PCIBIOS_GENERATE_SPECIAL_CYCLE	0xb106
+#define PCIBIOS_READ_CONFIG_BYTE	0xb108
+#define PCIBIOS_READ_CONFIG_WORD	0xb109
+#define PCIBIOS_READ_CONFIG_DWORD	0xb10a
+#define PCIBIOS_WRITE_CONFIG_BYTE	0xb10b
+#define PCIBIOS_WRITE_CONFIG_WORD	0xb10c
+#define PCIBIOS_WRITE_CONFIG_DWORD	0xb10d
+#define PCIBIOS_GET_ROUTING_OPTIONS	0xb10e
+#define PCIBIOS_SET_PCI_HW_INT		0xb10f
+
+/* BIOS32 signature: "_32_" */
+#define BIOS32_SIGNATURE	(('_' << 0) + ('3' << 8) + ('2' << 16) + ('_' << 24))
+
+/* PCI signature: "PCI " */
+#define PCI_SIGNATURE		(('P' << 0) + ('C' << 8) + ('I' << 16) + (' ' << 24))
+
+/* PCI service signature: "$PCI" */
+#define PCI_SERVICE		(('$' << 0) + ('P' << 8) + ('C' << 16) + ('I' << 24))
+
+/* PCI BIOS hardware mechanism flags */
+#define PCIBIOS_HW_TYPE1		0x01
+#define PCIBIOS_HW_TYPE2		0x02
+#define PCIBIOS_HW_TYPE1_SPEC		0x10
+#define PCIBIOS_HW_TYPE2_SPEC		0x20
+
+/*
+ * This is the standard structure used to identify the entry point
+ * to the BIOS32 Service Directory, as documented in
+ * 	Standard BIOS 32-bit Service Directory Proposal
+ * 	Revision 0.4 May 24, 1993
+ * 	Phoenix Technologies Ltd.
+ *	Norwood, MA
+ * and the PCI BIOS specification.
+ */
+
+union bios32 {
+	struct {
+		unsigned long signature;	/* _32_ */
+		unsigned long entry;		/* 32 bit physical address */
+		unsigned char revision;		/* Revision level, 0 */
+		unsigned char length;		/* Length in paragraphs should be 01 */
+		unsigned char checksum;		/* All bytes must add up to zero */
+		unsigned char reserved[5]; 	/* Must be zero */
+	} fields;
+	char chars[16];
+};
+
+/*
+ * Physical address of the service directory.  I don't know if we're
+ * allowed to have more than one of these or not, so just in case
+ * we'll make pcibios_present() take a memory start parameter and store
+ * the array there.
+ */
+
+static struct {
+	unsigned long address;
+	unsigned short segment;
+} bios32_indirect = { 0, __KERNEL_CS };
+
+/*
+ * Returns the entry point for the given service, NULL on error
+ */
+
+static unsigned long bios32_service(unsigned long service)
+{
+	unsigned char return_code;	/* %al */
+	unsigned long address;		/* %ebx */
+	unsigned long length;		/* %ecx */
+	unsigned long entry;		/* %edx */
+	unsigned long flags;
+
+	__save_flags(flags); __cli();
+	__asm__("lcall (%%edi)"
+		: "=a" (return_code),
+		  "=b" (address),
+		  "=c" (length),
+		  "=d" (entry)
+		: "0" (service),
+		  "1" (0),
+		  "D" (&bios32_indirect));
+	__restore_flags(flags);
+
+	switch (return_code) {
+		case 0:
+			return address + entry;
+		case 0x80:	/* Not present */
+			printk("bios32_service(0x%lx): not present\n", service);
+			return 0;
+		default: /* Shouldn't happen */
+			printk("bios32_service(0x%lx): returned 0x%x -- BIOS bug!\n",
+				service, return_code);
+			return 0;
+	}
+}
+
+static struct {
+	unsigned long address;
+	unsigned short segment;
+} pci_indirect = { 0, __KERNEL_CS };
+
+static int pci_bios_present;
+
+static int __init check_pcibios(void)
+{
+	u32 signature, eax, ebx, ecx;
+	u8 status, major_ver, minor_ver, hw_mech, last_bus;
+	unsigned long flags, pcibios_entry;
+
+	if ((pcibios_entry = bios32_service(PCI_SERVICE))) {
+		pci_indirect.address = pcibios_entry + PAGE_OFFSET;
+
+		__save_flags(flags); __cli();
+		__asm__(
+			"lcall (%%edi)\n\t"
+			"jc 1f\n\t"
+			"xor %%ah, %%ah\n"
+			"1:"
+			: "=d" (signature),
+			  "=a" (eax),
+			  "=b" (ebx),
+			  "=c" (ecx)
+			: "1" (PCIBIOS_PCI_BIOS_PRESENT),
+			  "D" (&pci_indirect)
+			: "memory");
+		__restore_flags(flags);
+
+		status = (eax >> 8) & 0xff;
+		hw_mech = eax & 0xff;
+		major_ver = (ebx >> 8) & 0xff;
+		minor_ver = ebx & 0xff;
+		last_bus = ecx & 0xff;
+		DBG("PCI: BIOS probe returned s=%02x hw=%02x ver=%02x.%02x l=%02x\n",
+			status, hw_mech, major_ver, minor_ver, last_bus);
+		if (status || signature != PCI_SIGNATURE) {
+			printk (KERN_ERR "PCI: BIOS BUG #%x[%08x] found, report to <mj@ucw.cz>\n",
+				status, signature);
+			return 0;
+		}
+		printk("PCI: PCI BIOS revision %x.%02x entry at 0x%lx\n",
+			major_ver, minor_ver, pcibios_entry);
+#ifdef CONFIG_PCI_DIRECT
+		if (!(hw_mech & PCIBIOS_HW_TYPE1))
+			pci_probe &= ~PCI_PROBE_CONF1;
+		if (!(hw_mech & PCIBIOS_HW_TYPE2))
+			pci_probe &= ~PCI_PROBE_CONF2;
+#endif
+		return 1;
+	}
+	return 0;
+}
+
+static int __init pci_bios_find_device (unsigned short vendor, unsigned short device_id,
+					unsigned short index, unsigned char *bus, unsigned char *device_fn)
+{
+	unsigned short bx;
+	unsigned short ret;
+
+	__asm__("lcall (%%edi)\n\t"
+		"jc 1f\n\t"
+		"xor %%ah, %%ah\n"
+		"1:"
+		: "=b" (bx),
+		  "=a" (ret)
+		: "1" (PCIBIOS_FIND_PCI_DEVICE),
+		  "c" (device_id),
+		  "d" (vendor),
+		  "S" ((int) index),
+		  "D" (&pci_indirect));
+	*bus = (bx >> 8) & 0xff;
+	*device_fn = bx & 0xff;
+	return (int) (ret & 0xff00) >> 8;
+}
+
+static int pci_bios_read_config_byte(struct pci_dev *dev, int where, u8 *value)
+{
+	unsigned long ret;
+	unsigned long bx = (dev->bus->number << 8) | dev->devfn;
+
+	__asm__("lcall (%%esi)\n\t"
+		"jc 1f\n\t"
+		"xor %%ah, %%ah\n"
+		"1:"
+		: "=c" (*value),
+		  "=a" (ret)
+		: "1" (PCIBIOS_READ_CONFIG_BYTE),
+		  "b" (bx),
+		  "D" ((long) where),
+		  "S" (&pci_indirect));
+	return (int) (ret & 0xff00) >> 8;
+}
+
+static int pci_bios_read_config_word(struct pci_dev *dev, int where, u16 *value)
+{
+	unsigned long ret;
+	unsigned long bx = (dev->bus->number << 8) | dev->devfn;
+
+	__asm__("lcall (%%esi)\n\t"
+		"jc 1f\n\t"
+		"xor %%ah, %%ah\n"
+		"1:"
+		: "=c" (*value),
+		  "=a" (ret)
+		: "1" (PCIBIOS_READ_CONFIG_WORD),
+		  "b" (bx),
+		  "D" ((long) where),
+		  "S" (&pci_indirect));
+	return (int) (ret & 0xff00) >> 8;
+}
+
+static int pci_bios_read_config_dword(struct pci_dev *dev, int where, u32 *value)
+{
+	unsigned long ret;
+	unsigned long bx = (dev->bus->number << 8) | dev->devfn;
+
+	__asm__("lcall (%%esi)\n\t"
+		"jc 1f\n\t"
+		"xor %%ah, %%ah\n"
+		"1:"
+		: "=c" (*value),
+		  "=a" (ret)
+		: "1" (PCIBIOS_READ_CONFIG_DWORD),
+		  "b" (bx),
+		  "D" ((long) where),
+		  "S" (&pci_indirect));
+	return (int) (ret & 0xff00) >> 8;
+}
+
+static int pci_bios_write_config_byte(struct pci_dev *dev, int where, u8 value)
+{
+	unsigned long ret;
+	unsigned long bx = (dev->bus->number << 8) | dev->devfn;
+
+	__asm__("lcall (%%esi)\n\t"
+		"jc 1f\n\t"
+		"xor %%ah, %%ah\n"
+		"1:"
+		: "=a" (ret)
+		: "0" (PCIBIOS_WRITE_CONFIG_BYTE),
+		  "c" (value),
+		  "b" (bx),
+		  "D" ((long) where),
+		  "S" (&pci_indirect));
+	return (int) (ret & 0xff00) >> 8;
+}
+
+static int pci_bios_write_config_word(struct pci_dev *dev, int where, u16 value)
+{
+	unsigned long ret;
+	unsigned long bx = (dev->bus->number << 8) | dev->devfn;
+
+	__asm__("lcall (%%esi)\n\t"
+		"jc 1f\n\t"
+		"xor %%ah, %%ah\n"
+		"1:"
+		: "=a" (ret)
+		: "0" (PCIBIOS_WRITE_CONFIG_WORD),
+		  "c" (value),
+		  "b" (bx),
+		  "D" ((long) where),
+		  "S" (&pci_indirect));
+	return (int) (ret & 0xff00) >> 8;
+}
+
+static int pci_bios_write_config_dword(struct pci_dev *dev, int where, u32 value)
+{
+	unsigned long ret;
+	unsigned long bx = (dev->bus->number << 8) | dev->devfn;
+
+	__asm__("lcall (%%esi)\n\t"
+		"jc 1f\n\t"
+		"xor %%ah, %%ah\n"
+		"1:"
+		: "=a" (ret)
+		: "0" (PCIBIOS_WRITE_CONFIG_DWORD),
+		  "c" (value),
+		  "b" (bx),
+		  "D" ((long) where),
+		  "S" (&pci_indirect));
+	return (int) (ret & 0xff00) >> 8;
+}
+
+/*
+ * Function table for BIOS32 access
+ */
+
+static struct pci_ops pci_bios_access = {
+      pci_bios_read_config_byte,
+      pci_bios_read_config_word,
+      pci_bios_read_config_dword,
+      pci_bios_write_config_byte,
+      pci_bios_write_config_word,
+      pci_bios_write_config_dword
+};
+
+/*
+ * Try to find PCI BIOS.
+ */
+
+static struct pci_ops * __init pci_find_bios(void)
+{
+	union bios32 *check;
+	unsigned char sum;
+	int i, length;
+
+	/*
+	 * Follow the standard procedure for locating the BIOS32 Service
+	 * directory by scanning the permissible address range from
+	 * 0xe0000 through 0xfffff for a valid BIOS32 structure.
+	 */
+
+	for (check = (union bios32 *) __va(0xe0000);
+	     check <= (union bios32 *) __va(0xffff0);
+	     ++check) {
+		if (check->fields.signature != BIOS32_SIGNATURE)
+			continue;
+		length = check->fields.length * 16;
+		if (!length)
+			continue;
+		sum = 0;
+		for (i = 0; i < length ; ++i)
+			sum += check->chars[i];
+		if (sum != 0)
+			continue;
+		if (check->fields.revision != 0) {
+			printk("PCI: unsupported BIOS32 revision %d at 0x%p, report to <mj@ucw.cz>\n",
+				check->fields.revision, check);
+			continue;
+		}
+		DBG("PCI: BIOS32 Service Directory structure at 0x%p\n", check);
+		if (check->fields.entry >= 0x100000) {
+			printk("PCI: BIOS32 entry (0x%p) in high memory, cannot use.\n", check);
+			return NULL;
+		} else {
+			unsigned long bios32_entry = check->fields.entry;
+			DBG("PCI: BIOS32 Service Directory entry at 0x%lx\n", bios32_entry);
+			bios32_indirect.address = bios32_entry + PAGE_OFFSET;
+			if (check_pcibios())
+				return &pci_bios_access;
+		}
+		break;	/* Hopefully more than one BIOS32 cannot happen... */
+	}
+
+	return NULL;
+}
+
+/*
+ * Sort the device list according to PCI BIOS. Nasty hack, but since some
+ * fool forgot to define the `correct' device order in the PCI BIOS specs
+ * and we want to be (possibly bug-to-bug ;-]) compatible with older kernels
+ * which used BIOS ordering, we are bound to do this...
+ */
+
+static void __init pcibios_sort(void)
+{
+	struct pci_dev *dev = pci_devices;
+	struct pci_dev **last = &pci_devices;
+	struct pci_dev *d, **dd, *e;
+	int idx;
+	unsigned char bus, devfn;
+
+	DBG("PCI: Sorting device list...\n");
+	while ((e = dev)) {
+		idx = 0;
+		while (pci_bios_find_device(e->vendor, e->device, idx, &bus, &devfn) == PCIBIOS_SUCCESSFUL) {
+			idx++;
+			for(dd=&dev; (d = *dd); dd = &d->next) {
+				if (d->bus->number == bus && d->devfn == devfn) {
+					*dd = d->next;
+					*last = d;
+					last = &d->next;
+					break;
+				}
+			}
+			if (!d) {
+				printk("PCI: BIOS reporting unknown device %02x:%02x\n", bus, devfn);
+				/*
+				 * We must not continue scanning as several buggy BIOSes
+				 * return garbage after the last device. Grr.
+				 */
+				break;
+			}
+		}
+		if (e == dev) {
+			printk("PCI: Device %02x:%02x not found by BIOS\n",
+				dev->bus->number, dev->devfn);
+			d = dev;
+			dev = dev->next;
+			*last = d;
+			last = &d->next;
+		}
+	}
+	*last = NULL;
+}
+
+/*
+ *  Ask BIOS for IRQ Routing Table
+ */
+
+struct irq_routing_options {
+	u16 size;
+	struct irq_info *table;
+	u16 segment;
+} __attribute__((packed));
+
+static unsigned long pcibios_irq_page __initdata = 0;
+
+static inline void __init pcibios_free_irq_routing_table(void)
+{
+	if (pcibios_irq_page)
+		free_page(pcibios_irq_page);
+}
+
+static struct irq_routing_table * __init pcibios_get_irq_routing_table(void)
+{
+	struct irq_routing_options opt;
+	struct irq_routing_table *rt;
+	int ret, map;
+
+	if (pci_probe & PCI_NO_IRQ_SCAN)
+		return NULL;
+	pcibios_irq_page = __get_free_page(GFP_KERNEL);
+	if (!pcibios_irq_page)
+		return 0;
+	rt = (void *) pcibios_irq_page;
+	opt.table = rt->slots;
+	opt.size = PAGE_SIZE - sizeof(struct irq_routing_table);
+	opt.segment = __KERNEL_DS;
+
+	DBG("PCI: Fetching IRQ routing table... ");
+	__asm__("push %%es\n\t"
+		"push %%ds\n\t"
+		"pop  %%es\n\t"
+		"lcall (%%esi)\n\t"
+		"pop %%es\n\t"
+		"jc 1f\n\t"
+		"xor %%ah, %%ah\n"
+		"1:"
+		: "=a" (ret),
+		  "=b" (map)
+		: "0" (PCIBIOS_GET_ROUTING_OPTIONS),
+		  "1" (0),
+		  "D" ((long) &opt),
+		  "S" (&pci_indirect));
+	DBG("OK  ret=%d, size=%d, map=%x\n", ret, opt.size, map);
+	if (ret & 0xff00) {
+		printk(KERN_ERR "PCI: Error %02x when fetching IRQ routing table.\n", (ret >> 8) & 0xff);
+		return 0;
+	}
+
+	memset(rt, 0, sizeof(struct irq_routing_table));
+	rt->size = opt.size + sizeof(struct irq_routing_table);
+	printk("PCI: Using BIOS Interrupt Routing Table\n");
+	return rt;
+}
+
+#endif
+
+/*
+ * Several buggy motherboards address only 16 devices and mirror
+ * them to next 16 IDs. We try to detect this `feature' on all
+ * primary buses (those containing host bridges as they are
+ * expected to be unique) and remove the ghost devices.
+ */
+
+static void __init pcibios_fixup_ghosts(struct pci_bus *b)
+{
+	struct pci_dev *d, *e, **z;
+	int mirror = PCI_DEVFN(16,0);
+	int seen_host_bridge = 0;
+	int i;
+
+	DBG("PCI: Scanning for ghost devices on bus %d\n", b->number);
+	for(d=b->devices; d && d->devfn < mirror; d=d->sibling) {
+		if ((d->class >> 8) == PCI_CLASS_BRIDGE_HOST)
+			seen_host_bridge++;
+		for(e=d->next; e; e=e->sibling) {
+			if (e->devfn != d->devfn + mirror ||
+			    e->vendor != d->vendor ||
+			    e->device != d->device ||
+			    e->class != d->class)
+				continue;
+			for(i=0; i<PCI_NUM_RESOURCES; i++)
+				if (e->resource[i].start != d->resource[i].start ||
+				    e->resource[i].end != d->resource[i].end ||
+				    e->resource[i].flags != d->resource[i].flags)
+					continue;
+			break;
+		}
+		if (!e)
+			return;
+	}
+	if (!seen_host_bridge)
+		return;
+	printk("PCI: Ignoring ghost devices on bus %02x\n", b->number);
+	for(e=b->devices; e->sibling != d; e=e->sibling);
+	e->sibling = NULL;
+	for(z=&pci_devices; (d=*z);)
+		if (d->bus == b && d->devfn >= mirror) {
+			*z = d->next;
+			kfree_s(d, sizeof(*d));
+		} else
+			z = &d->next;
+}
+
+/*
+ * In case there are peer host bridges, scan bus behind each of them.
+ * Although several sources claim that the host bridges should have
+ * header type 1 and be assigned a bus number as for PCI2PCI bridges,
+ * the reality doesn't pass this test and the bus number is usually
+ * set by BIOS to the first free value.
+ */
+static void __init pcibios_fixup_peer_bridges(void)
+{
+	struct pci_bus *b = pci_root;
+	int n, cnt=-1;
+	struct pci_dev *d;
+	struct pci_ops *ops = pci_root->ops;
+
+#ifdef CONFIG_PCI_DIRECT
+	/*
+	 * Don't search for peer host bridges if we use config type 2
+	 * since it reads bogus values for non-existent buses and
+	 * chipsets supporting multiple primary buses use conf1 anyway.
+	 */
+	if (ops == &pci_direct_conf2)
+		return;
+#endif
+
+	for(d=b->devices; d; d=d->sibling)
+		if ((d->class >> 8) == PCI_CLASS_BRIDGE_HOST)
+			cnt++;
+	n = b->subordinate + 1;
+	while (n <= 0xff) {
+		int found = 0;
+		u16 l;
+		struct pci_bus bus;
+		struct pci_dev dev;
+		bus.number = n;
+		bus.ops = ops;
+		dev.bus = &bus;
+		for(dev.devfn=0; dev.devfn<256; dev.devfn += 8)
+			if (!pci_read_config_word(&dev, PCI_VENDOR_ID, &l) &&
+			    l != 0x0000 && l != 0xffff) {
+#ifdef CONFIG_PCI_BIOS
+				if (pci_bios_present) {
+					int err, idx = 0;
+					u8 bios_bus, bios_dfn;
+					u16 d;
+					pci_read_config_word(&dev, PCI_DEVICE_ID, &d);
+					DBG("BIOS test for %02x:%02x (%04x:%04x)\n", n, dev.devfn, l, d);
+					while (!(err = pci_bios_find_device(l, d, idx, &bios_bus, &bios_dfn)) &&
+					       (bios_bus != n || bios_dfn != dev.devfn))
+						idx++;
+					if (err)
+						break;
+				}
+#endif
+				DBG("Found device at %02x:%02x\n", n, dev.devfn);
+				found++;
+				if (!pci_read_config_word(&dev, PCI_CLASS_DEVICE, &l) &&
+				    l == PCI_CLASS_BRIDGE_HOST)
+					cnt++;
+			}
+		if (cnt-- <= 0)
+			break;
+		if (found) {
+			printk("PCI: Discovered primary peer bus %02x\n", n);
+			b = pci_scan_bus(n, ops, NULL);
+			if (b)
+				n = b->subordinate;
+		}
+		n++;
+	}
+}
+
+/*
+ * Exceptions for specific devices. Usually work-arounds for fatal design flaws.
+ */
+
+static void __init pci_fixup_i450nx(struct pci_dev *d)
+{
+	/*
+	 * i450NX -- Find and scan all secondary buses on all PXB's.
+	 */
+	int pxb, reg;
+	u8 busno, suba, subb;
+	printk("PCI: Searching for i450NX host bridges on %s\n", d->slot_name);
+	reg = 0xd0;
+	for(pxb=0; pxb<2; pxb++) {
+		pci_read_config_byte(d, reg++, &busno);
+		pci_read_config_byte(d, reg++, &suba);
+		pci_read_config_byte(d, reg++, &subb);
+		DBG("i450NX PXB %d: %02x/%02x/%02x\n", pxb, busno, suba, subb);
+		if (busno)
+			pci_scan_bus(busno, pci_root->ops, NULL);	/* Bus A */
+		if (suba < subb)
+			pci_scan_bus(suba+1, pci_root->ops, NULL);	/* Bus B */
+	}
+	pci_probe |= PCI_NO_PEER_FIXUP;
+}
+
+static void __init pci_fixup_umc_ide(struct pci_dev *d)
+{
+	/*
+	 * UM8886BF IDE controller sets region type bits incorrectly,
+	 * therefore they look like memory despite of them being I/O.
+	 */
+	int i;
+
+	printk("PCI: Fixing base address flags for device %s\n", d->slot_name);
+	for(i=0; i<4; i++)
+		d->resource[i].flags |= PCI_BASE_ADDRESS_SPACE_IO;
+}
+
+static void __init pci_fixup_ide_bases(struct pci_dev *d)
+{
+	int i;
+
+	/*
+	 * PCI IDE controllers use non-standard I/O port decoding, respect it.
+	 */
+	if ((d->class >> 8) != PCI_CLASS_STORAGE_IDE)
+		return;
+	DBG("PCI: IDE base address fixup for %s\n", d->slot_name);
+	for(i=0; i<4; i++) {
+		struct resource *r = &d->resource[i];
+		if ((r->start & ~0x80) == 0x374) {
+			r->start |= 2;
+			r->end = r->start;
+		}
+	}
+}
+
+struct pci_fixup pcibios_fixups[] = {
+	{ PCI_FIXUP_HEADER,	PCI_VENDOR_ID_INTEL,	PCI_DEVICE_ID_INTEL_82451NX,	pci_fixup_i450nx },
+	{ PCI_FIXUP_HEADER,	PCI_VENDOR_ID_UMC,	PCI_DEVICE_ID_UMC_UM8886BF,	pci_fixup_umc_ide },
+	{ PCI_FIXUP_HEADER,	PCI_ANY_ID,		PCI_ANY_ID,			pci_fixup_ide_bases },
+	{ 0 }
+};
+
+/*
+ *  Fix up IRQs of all PCI devices.
+ */
+
+extern int skip_ioapic_setup;
+
+#define PIRQ_SIGNATURE	(('$' << 0) + ('P' << 8) + ('I' << 16) + ('R' << 24))
+#define PIRQ_VERSION 0x0100
+
+/*
+ *  Search 0xf0000 -- 0xfffff for the PCI IRQ Routing Table.
+ */
+
+static struct irq_routing_table * __init pcibios_find_irq_routing_table(void)
+{
+	u8 *addr;
+	struct irq_routing_table *rt;
+	int i;
+	u8 sum;
+
+	for(addr = (u8 *) __va(0xf0000); addr < (u8 *) __va(0x100000); addr += 16) {
+		rt = (struct irq_routing_table *) addr;
+		if (rt->signature != PIRQ_SIGNATURE ||
+		    rt->version != PIRQ_VERSION ||
+		    rt->size % 16 ||
+		    rt->size < sizeof(struct irq_routing_table))
+			continue;
+		sum = 0;
+		for(i=0; i<rt->size; i++)
+			sum += addr[i];
+		if (!sum) {
+			printk("PCI: Interrupt Routing Table found at 0x%p [router type %04x/%04x]\n",
+			       rt, rt->rtr_vendor, rt->rtr_device);
+			return rt;
+		}
+	}
+	return NULL;
+}
+
+/*
+ *  If we have a IRQ routing table, use it to search for peer host
+ *  bridges.  It's a gross hack, but since there are no other known
+ *  ways how to get a list of buses, we have to go this way.
+ */
+
+static void __init pcibios_irq_peer_trick(struct irq_routing_table *rt)
+{
+	u8 busmap[256];
+	int i;
+	struct irq_info *e;
+
+	memset(busmap, 0, sizeof(busmap));
+	for(i=0; i < (rt->size - sizeof(struct irq_routing_table)) / sizeof(struct irq_info); i++) {
+		e = &rt->slots[i];
+		DBG("b=%02x d=%02x s=%02x\n", e->bus, e->devfn, e->slot);
+		busmap[e->bus] = 1;
+	}
+	for(i=1; i<256; i++)
+		/*
+		 *  It might be a secondary bus, but in this case its parent is already
+		 *  known (ascending bus order) and therefore pci_scan_bus returns immediately.
+		 */
+		if (busmap[i] && pci_scan_bus(i, pci_root->ops, NULL))
+			printk("PCI: Discovered primary peer bus %02x [IRQ]\n", i);
+	pci_probe |= PCI_NO_PEER_FIXUP;
+}
+
+/*
+ *  In case BIOS forgets to tell us about IRQ, we try to look it up in the routing
+ *  table, but unfortunately we have to know the interrupt router chip.
+ */
+
+static char * __init pcibios_lookup_irq(struct pci_dev *dev, struct irq_routing_table *rt, int pin)
+{
+	struct irq_info *q;
+	struct pci_dev *router;
+	int i, pirq, newirq;
+	u32 rtrid, mask;
+	u8 x;
+
+	pin--;
+	DBG("IRQ for %s(%d)", dev->slot_name, pin);
+	while (dev->bus->self) {
+		pin = (pin + PCI_SLOT(dev->devfn)) % 4;
+		dev = dev->bus->self;
+		DBG(" -> %s(%d)", dev->slot_name, pin);
+	}
+	for(q = rt->slots, i = rt->size - sizeof(struct irq_routing_table);
+	    i && (q->bus != dev->bus->number || PCI_SLOT(q->devfn) != PCI_SLOT(dev->devfn));
+	    i -= sizeof(struct irq_info), q++)
+		;
+	if (!i) {
+		DBG(" -> not found in routing table\n");
+		return NULL;
+	}
+	pirq = q->irq[pin].link;
+	mask = q->irq[pin].bitmap;
+	if (!pirq) {
+		DBG(" -> not routed\n");
+		return NULL;
+	}
+	DBG(" -> PIRQ %02x, mask %04x", pirq, mask);
+	if ((dev->class >> 8) == PCI_CLASS_DISPLAY_VGA)
+		newirq = 0;
+	else for(newirq = 15; newirq && !(mask & (1 << newirq)); newirq--)
+		;
+	if (!(router = pci_find_slot(rt->rtr_bus, rt->rtr_devfn))) {
+		DBG(" -> router not found\n");
+		return NULL;
+	}
+#define ID(x,y) ((x << 16) | y)
+	rtrid = ID(rt->rtr_vendor, rt->rtr_device);
+	if (!rtrid) {
+		/*
+		 * Several BIOSes forget to set the router type. In such cases, we
+		 * use chip vendor/device. This doesn't guarantee us semantics of
+		 * PIRQ values, but was found to work in practice and it's still
+		 * better than not trying.
+		 */
+		DBG(" [%s]", router->slot_name);
+		rtrid = ID(router->vendor, router->device);
+	}
+	switch (rtrid) {
+	case ID(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371FB_0):
+	case ID(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371SB_0):
+	case ID(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_0):
+		/* Intel PIIX: PIRQ holds configuration register address */
+		pci_read_config_byte(router, pirq, &x);
+		if (x < 16) {
+			DBG(" -> [PIIX] %02x\n", x);
+			dev->irq = x;
+			return "PIIX";
+		} else if (newirq) {
+			DBG(" -> [PIIX] set to %02x\n", newirq);
+			pci_write_config_byte(router, pirq, newirq);
+			dev->irq = newirq;
+			return "PIIX-NEW";
+		}
+		DBG(" -> [PIIX] sink\n");
+		return NULL;
+	default:
+		DBG(" -> unknown router %04x/%04x\n", rt->rtr_vendor, rt->rtr_device);
+		if (newirq && mask == (1 << newirq)) {
+			/* Only one IRQ available -> use it */
+			dev->irq = newirq;
+			return "guess";
+		}
+		return NULL;
+	}
+#undef ID
+}
+
+static void __init pcibios_fixup_irqs(void)
+{
+	struct irq_routing_table *rtable;
+	struct pci_dev *dev;
+	u8 pin;
+
+	rtable = pcibios_find_irq_routing_table();
+#ifdef CONFIG_PCI_BIOS
+	if (!rtable && pci_bios_present)
+		rtable = pcibios_get_irq_routing_table();
+#endif
+
+	if (rtable)
+		pcibios_irq_peer_trick(rtable);
+
+	for(dev=pci_devices; dev; dev=dev->next) {
+		pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
+#if defined(CONFIG_X86_IO_APIC)
+		/*
+		 * Recalculate IRQ numbers if we use the I/O APIC.
+		 */
+		if(!skip_ioapic_setup)
+		{
+			int irq;
+
+			if (pin) {
+				pin--;		/* interrupt pins are numbered starting from 1 */
+				irq = IO_APIC_get_PCI_irq_vector(dev->bus->number, PCI_SLOT(dev->devfn), pin);
+				if (irq < 0 && dev->bus->parent) { /* go back to the bridge */
+					struct pci_dev * bridge = dev->bus->self;
+
+					pin = (pin + PCI_SLOT(dev->devfn)) % 4;
+					irq = IO_APIC_get_PCI_irq_vector(bridge->bus->number, 
+							PCI_SLOT(bridge->devfn), pin);
+					if (irq >= 0)
+						printk(KERN_WARNING "PCI: using PPB(B%d,I%d,P%d) to get irq %d\n", 
+							bridge->bus->number, PCI_SLOT(bridge->devfn), pin, irq);
+				}
+				if (irq >= 0) {
+					printk("PCI->APIC IRQ transform: (B%d,I%d,P%d) -> %d\n",
+						dev->bus->number, PCI_SLOT(dev->devfn), pin, irq);
+					dev->irq = irq;
+				}
+			}
+			rtable = NULL;	/* Avoid IRQ assignment below */
+		}
+#endif
+		/*
+		 * Fix out-of-range IRQ numbers and missing IRQs.
+		 */
+		if (dev->irq >= NR_IRQS)
+			dev->irq = 0;
+		if (pin && !dev->irq && rtable && rtable->version) {
+			char *msg = pcibios_lookup_irq(dev, rtable, pin);
+			if (msg)
+				printk("PCI: Assigned IRQ %d to device %s [%s]\n", dev->irq, dev->slot_name, msg);
+		}
+	}
+
+#ifdef CONFIG_PCI_BIOS
+	pcibios_free_irq_routing_table();
+#endif
+}
+
+/*
+ *  Called after each bus is probed, but before its children
+ *  are examined.
+ */
+
+void __init pcibios_fixup_bus(struct pci_bus *b)
+{
+	pcibios_fixup_ghosts(b);
+	pci_read_bridge_bases(b);
+}
+
+/*
+ * Initialization. Try all known PCI access methods. Note that we support
+ * using both PCI BIOS and direct access: in such cases, we use I/O ports
+ * to access config space, but we still keep BIOS order of cards to be
+ * compatible with 2.0.X. This should go away some day.
+ */
+
+void __init pcibios_init(void)
+{
+	struct pci_ops *bios = NULL;
+	struct pci_ops *dir = NULL;
+	struct pci_ops *ops;
+
+#ifdef CONFIG_PCI_BIOS
+	if ((pci_probe & PCI_PROBE_BIOS) && ((bios = pci_find_bios()))) {
+		pci_probe |= PCI_BIOS_SORT;
+		pci_bios_present = 1;
+	}
+#endif
+#ifdef CONFIG_PCI_DIRECT
+	if (pci_probe & (PCI_PROBE_CONF1 | PCI_PROBE_CONF2))
+		dir = pci_check_direct();
+#endif
+	if (dir)
+		ops = dir;
+	else if (bios)
+		ops = bios;
+	else {
+		printk("PCI: No PCI bus detected\n");
+		return;
+	}
+
+	printk("PCI: Probing PCI hardware\n");
+	pci_scan_bus(0, ops, NULL);
+
+	pcibios_fixup_irqs();
+	if (!(pci_probe & PCI_NO_PEER_FIXUP))
+		pcibios_fixup_peer_bridges();
+	pcibios_resource_survey();
+
+#ifdef CONFIG_PCI_BIOS
+	if ((pci_probe & PCI_BIOS_SORT) && !(pci_probe & PCI_NO_SORT))
+		pcibios_sort();
+#endif
+}
+
+char * __init pcibios_setup(char *str)
+{
+	if (!strcmp(str, "off")) {
+		pci_probe = 0;
+		return NULL;
+	}
+#ifdef CONFIG_PCI_BIOS
+	else if (!strcmp(str, "bios")) {
+		pci_probe = PCI_PROBE_BIOS;
+		return NULL;
+	} else if (!strcmp(str, "nobios")) {
+		pci_probe &= ~PCI_PROBE_BIOS;
+		return NULL;
+	} else if (!strcmp(str, "nosort")) {
+		pci_probe |= PCI_NO_SORT;
+		return NULL;
+	} else if (!strcmp(str, "noirq")) {
+		pci_probe |= PCI_NO_IRQ_SCAN;
+		return NULL;
+	}
+#endif
+#ifdef CONFIG_PCI_DIRECT
+	else if (!strcmp(str, "conf1")) {
+		pci_probe = PCI_PROBE_CONF1 | PCI_NO_CHECKS;
+		return NULL;
+	}
+	else if (!strcmp(str, "conf2")) {
+		pci_probe = PCI_PROBE_CONF2 | PCI_NO_CHECKS;
+		return NULL;
+	}
+#endif
+	else if (!strcmp(str, "nopeer")) {
+		pci_probe |= PCI_NO_PEER_FIXUP;
+		return NULL;
+	} else if (!strcmp(str, "rom")) {
+		pci_probe |= PCI_ASSIGN_ROMS;
+		return NULL;
+	}
+	return str;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)