setup PCI devices

setup interrupt line, PCI bridges, and IDE devices

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
diff --git a/Makefile b/Makefile
index 968c4c3..64da22a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,6 @@
 obj-y = code16.o entry.o main.o string.o printf.o cstart.o fw_cfg.o
 obj-y += linuxboot.o malloc.o pflash.o cbfs.o tables.o hwsetup.o
+obj-y += pci.o
 
 all-y = bios.bin
 all: $(all-y)
diff --git a/hwsetup.c b/hwsetup.c
index 77c6dec..6c3aef9 100644
--- a/hwsetup.c
+++ b/hwsetup.c
@@ -56,10 +56,6 @@
 	pci_config_writeb(bdf, ICH9_LPC_ACPI_CTRL, 0x80);
 }
 
-#define PCI_VENDOR_ID_INTEL		0x8086
-#define PCI_DEVICE_ID_INTEL_82441	0x1237
-#define PCI_DEVICE_ID_INTEL_Q35_MCH     0x29c0
-
 #define I440FX_PAM0                     0x59
 #define Q35_HOST_BRIDGE_PAM0            0x90
 
diff --git a/include/bios.h b/include/bios.h
index ff2064b..eddca6b 100644
--- a/include/bios.h
+++ b/include/bios.h
@@ -38,6 +38,7 @@
 extern void bios_int10(void);
 extern void bios_int15(void);
 
+extern void setup_pci(void);
 extern void setup_hw(void);
 extern void extract_acpi(void);
 extern void boot_from_fwcfg(void);
diff --git a/include/pci.h b/include/pci.h
index c12a784..6270ac4 100644
--- a/include/pci.h
+++ b/include/pci.h
@@ -39,6 +39,37 @@
 	return inb(0xcfc | (addr & 3));
 }
 
-#define PCI_VENDOR_ID 0
+#define PCI_VENDOR_ID 0x00
+#define PCI_DEVICE_ID 0x02
+#define PCI_COMMAND 0x04
+#define PCI_CLASS_DEVICE 0x0a
+#define PCI_HEADER_TYPE 0x0e
+#define PCI_PRIMARY_BUS 0x18
+#define PCI_SECONDARY_BUS 0x19
+#define PCI_SUBORDINATE_BUS 0x1a
+#define PCI_INTERRUPT_LINE 0x3c
+#define PCI_INTERRUPT_PIN 0x3d
+#define PCI_BRIDGE_CONTROL 0x3e
+
+/* PCI_COMMAND */
+#define PCI_COMMAND_DIS_INTX 0x400
+
+/* PCI_CLASS_DEVICE */
+#define PCI_CLASS_STORAGE_IDE 0x0101
+#define PCI_CLASS_BRIDGE_PCI 0x0604
+
+/* PCI_HEADER_TYPE */
+#define PCI_HEADER_TYPE_BRIDGE 1
+#define PCI_HEADER_TYPE_MULTI_FUNCTION 0x80
+
+/* PCI_BRIDGE_CONTROL */
+#define PCI_BRIDGE_CTL_SERR 0x02
+
+/* PCI_VENDOR_ID / PCI_DEVICE_ID */
+#define PCI_VENDOR_ID_INTEL		0x8086
+#define PCI_DEVICE_ID_INTEL_82441	0x1237
+#define PCI_DEVICE_ID_INTEL_Q35_MCH     0x29c0
+#define PCI_DEVICE_ID_INTEL_82371SB_1	0x7010
+#define PCI_DEVICE_ID_INTEL_82371AB	0x7111
 
 #endif
diff --git a/main.c b/main.c
index 95c33ce..b593237 100644
--- a/main.c
+++ b/main.c
@@ -86,6 +86,7 @@
 	// in order to probe CBFS!
 	asm("ljmp $0x8, $1f; 1:");
 
+	setup_pci();
 	setup_idt();
 	fw_cfg_setup();
 	extract_acpi();
diff --git a/pci.c b/pci.c
new file mode 100644
index 0000000..3976578
--- /dev/null
+++ b/pci.c
@@ -0,0 +1,141 @@
+#include "bios.h"
+#include "ioport.h"
+#include "pci.h"
+
+static uint16_t addend;
+static uint8_t bus, max_bus;
+static bool use_i440fx_routing;
+
+static void pci_foreach(void(*fn)(uint32_t bdf, uint32_t id, uint8_t type))
+{
+	int d, f;
+	for (d = 0; d < 32; d++) {
+		for (f = 0; f < 8; f++) {
+			uint32_t bdf = (bus * 256) + (d * 8) + f;
+			uint32_t id = pci_config_readl(bdf, PCI_VENDOR_ID);
+			uint16_t vendor;
+			uint8_t type;
+
+			/* 0x0000 or 0xFFFF?  Skip.  */
+			vendor = id & 0xFFFF;
+			if ((uint16_t)(vendor + 1) <= 1) {
+				if (f == 0)
+					break;
+				else
+					continue;
+			}
+
+			type = pci_config_readb(bdf, PCI_HEADER_TYPE);
+			fn(bdf, id, type);
+
+			if (f == 0 && !(type & PCI_HEADER_TYPE_MULTI_FUNCTION))
+				break;
+		}
+	}
+}
+
+static void do_setup_pci_irq(uint32_t bdf, int pin)
+{
+	int dev = (bdf >> 3) & 0x1f;
+	int lnk, irq;
+
+	irq = pci_config_readb(bdf, PCI_INTERRUPT_LINE);
+	if (irq != 0)
+		return;
+
+	lnk = addend + pin;
+	if (use_i440fx_routing)
+		lnk += dev - 1;
+	else {
+		/* Q35 devices 25-31 all use LNKA.  Devices 0-24 have
+		 * a slightly different mapping.
+		 */
+		if (dev <= 24)
+			lnk += dev;
+	}
+	lnk &= 3;
+
+	irq = lnk & 2 ? 11 : 10;
+	pci_config_writeb(bdf, PCI_INTERRUPT_LINE, irq);
+}
+
+static void do_block_pci_bridges(uint32_t bdf, uint32_t id, uint8_t type)
+{
+	uint16_t class;
+
+	class = pci_config_readw(bdf, PCI_CLASS_DEVICE);
+	switch (class) {
+	case PCI_CLASS_BRIDGE_PCI:
+		/* prevent accidental access to unintended devices */
+		pci_config_writeb(bdf, PCI_SECONDARY_BUS, 255);
+		pci_config_writeb(bdf, PCI_SUBORDINATE_BUS, 0);
+		break;
+	}
+}
+
+static void do_setup_pci(uint32_t bdf, uint32_t id, uint8_t type)
+{
+	uint16_t class;
+	uint8_t pin;
+	int save_bus;
+
+	pin = pci_config_readb(bdf, PCI_INTERRUPT_PIN);
+        if (pin != 0)
+		do_setup_pci_irq(bdf, pin);
+
+	if (type & PCI_HEADER_TYPE_BRIDGE) {
+		uint32_t ctl;
+
+		ctl = pci_config_readw(bdf, PCI_BRIDGE_CONTROL);
+		pci_config_writew(bdf, PCI_BRIDGE_CONTROL,
+				  ctl | PCI_BRIDGE_CTL_SERR);
+	}
+
+	class = pci_config_readw(bdf, PCI_CLASS_DEVICE);
+	switch (class) {
+	case PCI_CLASS_STORAGE_IDE:
+		pci_config_writel(bdf, 0x10, 0x1f0);
+		pci_config_writel(bdf, 0x14, 0x3f4);
+		pci_config_writel(bdf, 0x18, 0x170);
+		pci_config_writel(bdf, 0x1c, 0x374);
+		if (id == (PCI_VENDOR_ID_INTEL | (PCI_DEVICE_ID_INTEL_82371SB_1 << 16))
+		    || id == (PCI_VENDOR_ID_INTEL | (PCI_DEVICE_ID_INTEL_82371AB << 16))) {
+			/* Enable IDE0 and IDE1.  */
+			pci_config_writew(bdf, 0x40, 0x8000);
+			pci_config_writew(bdf, 0x42, 0x8000);
+		}
+		break;
+
+	case PCI_CLASS_BRIDGE_PCI:
+		save_bus = bus;
+		pci_config_writeb(bdf, PCI_PRIMARY_BUS, bus);
+		bus = ++max_bus;
+		pci_config_writeb(bdf, PCI_SECONDARY_BUS, bus);
+		pci_config_writeb(bdf, PCI_SUBORDINATE_BUS, 255);
+
+		/* Add PCI bridge device id for the recursive call.  */
+		addend += (bdf >> 3) & 0x1f;
+		pci_foreach(do_setup_pci);
+		addend -= (bdf >> 3) & 0x1f;
+
+		pci_config_writeb(bdf, PCI_SUBORDINATE_BUS, max_bus);
+		bus = save_bus;
+		break;
+	}
+}
+
+void setup_pci(void)
+{
+	const int bdf = 0;
+
+        uint32_t id = pci_config_readl(bdf, 0);
+        if (id == (PCI_VENDOR_ID_INTEL | (PCI_DEVICE_ID_INTEL_82441 << 16)))
+		use_i440fx_routing = true;
+	else if (id == (PCI_VENDOR_ID_INTEL | (PCI_DEVICE_ID_INTEL_Q35_MCH << 16)))
+		use_i440fx_routing = false;
+	else
+		panic();
+
+	pci_foreach(do_block_pci_bridges);
+	pci_foreach(do_setup_pci);
+}