From 7a64d8d74c6ad29bbf63dad4964dd168372bf865 Mon Sep 17 00:00:00 2001
From: John Baldwin <jhb@FreeBSD.org>
Date: Tue, 4 May 2004 21:17:52 +0000
Subject: [PATCH] - Create a pir0 psuedo device as a child of legacy0 if we
 attach a legacy   host-PCI bridge device and find a valid $PIR. - Make
 pci_pir_parse() private to pci_pir.c and have pir0's attach routine   call it
 instead of having legacy_pcib_attach() call it. - Implement suspend/resume
 support for the $PIR by giving pir0 a resume   method that calls the BIOS to
 reroute each link that was already routed   before the machine was suspended.
 - Dump the state of the routed flag in the links display code. - If a link's
 IRQ is set by a tunable, then force that link to be re-routed   the first
 time it is used. - Move the 'Found $PIR' message under bootverbose as the
 pir0 description   line lists the number of entries already.  The pir0 line
 also only shows   up if we are actually using the $PIR which is a bonus. -
 Use BUS_CONFIG_INTR() to ensure that any IRQs used by a PCI link are   set to
 level/low trigger/polarity.

---
 sys/i386/include/pci_cfgreg.h |   1 -
 sys/i386/pci/pci_bus.c        |   8 ++-
 sys/i386/pci/pci_pir.c        | 125 ++++++++++++++++++++++++++++++++--
 3 files changed, 125 insertions(+), 9 deletions(-)

diff --git a/sys/i386/include/pci_cfgreg.h b/sys/i386/include/pci_cfgreg.h
index 721df1cba05b..4bbfe72cb0f8 100644
--- a/sys/i386/include/pci_cfgreg.h
+++ b/sys/i386/include/pci_cfgreg.h
@@ -51,6 +51,5 @@ int		pci_cfgregopen(void);
 u_int32_t	pci_cfgregread(int bus, int slot, int func, int reg, int bytes);
 void		pci_cfgregwrite(int bus, int slot, int func, int reg, u_int32_t data, int bytes);
 void		pci_pir_open(void);
-void		pci_pir_parse(void);
 int		pci_pir_probe(int bus, int require_parse);
 int		pci_pir_route_interrupt(int bus, int device, int func, int pin);
diff --git a/sys/i386/pci/pci_bus.c b/sys/i386/pci/pci_bus.c
index 6f2fc0c8347a..2460159260f0 100644
--- a/sys/i386/pci/pci_bus.c
+++ b/sys/i386/pci/pci_bus.c
@@ -424,6 +424,7 @@ legacy_pcib_probe(device_t dev)
 static int
 legacy_pcib_attach(device_t dev)
 {
+	device_t pir;
 	int bus;
 
 	/*
@@ -431,8 +432,11 @@ legacy_pcib_attach(device_t dev)
 	 * our method of routing interrupts if we have one.
 	 */
 	bus = pcib_get_bus(dev);
-	if (pci_pir_probe(bus, 0))
-		pci_pir_parse();
+	if (pci_pir_probe(bus, 0)) {
+		pir = BUS_ADD_CHILD(device_get_parent(dev), 0, "pir", 0);
+		KASSERT(pir != NULL, ("could not add pir0 device"));
+		device_probe_and_attach(pir);
+	}
 	device_add_child(dev, "pci", bus);
 	return bus_generic_attach(dev);
 }
diff --git a/sys/i386/pci/pci_pir.c b/sys/i386/pci/pci_pir.c
index f1f80cd4e9c7..4cf28cc03c7d 100644
--- a/sys/i386/pci/pci_pir.c
+++ b/sys/i386/pci/pci_pir.c
@@ -67,6 +67,13 @@ struct pci_link_lookup {
 	int		pin;
 };
 
+struct pci_dev_lookup {
+	uint8_t		link;
+	int		bus;
+	int		device;
+	int		pin;
+};
+
 typedef void pir_entry_handler(struct PIR_entry *entry,
     struct PIR_intpin* intpin, void *arg);
 
@@ -82,6 +89,7 @@ static void	pci_pir_find_link_handler(struct PIR_entry *entry,
 		    struct PIR_intpin *intpin, void *arg);
 static void	pci_pir_initial_irqs(struct PIR_entry *entry,
 		    struct PIR_intpin *intpin, void *arg);
+static void	pci_pir_parse(void);
 static void	pci_pir_print_intpin(struct PIR_entry *entry,
 		    struct PIR_intpin *intpin, void *arg);
 static void	pci_pir_print_table(void);
@@ -92,6 +100,7 @@ static void	pci_pir_walk_table(pir_entry_handler *handler, void *arg);
 MALLOC_DEFINE(M_PIR, "$PIR", "$PIR structures");
 
 static struct PIR_table *pci_route_table;
+static device_t pir_device;
 static int pci_route_count, pir_bios_irqs, pir_parsed;
 static TAILQ_HEAD(, pci_link) pci_links;
 static int pir_interrupt_weight[NUM_ISA_INTERRUPTS];
@@ -156,10 +165,11 @@ pci_pir_open(void)
 	pci_route_count = (pt->pt_header.ph_length -
 	    sizeof(struct PIR_header)) / 
 	    sizeof(struct PIR_entry);
-	printf("Found $PIR table, %d entries at %p\n",
-	    pci_route_count, pci_route_table);
-	if (bootverbose)
+	if (bootverbose) {
+		printf("Found $PIR table, %d entries at %p\n",
+		    pci_route_count, pci_route_table);
 		pci_pir_print_table();
+	}
 }
 
 /*
@@ -336,7 +346,7 @@ pci_pir_initial_irqs(struct PIR_entry *entry, struct PIR_intpin *intpin,
  * various interrupt routers as they could read the initial IRQ for each
  * link.
  */
-void
+static void
 pci_pir_parse(void)
 {
 	char tunable_buffer[64];
@@ -388,6 +398,7 @@ pci_pir_parse(void)
 			irq = PCI_INVALID_IRQ;
 		if (irq == PCI_INVALID_IRQ ||
 		    pci_pir_valid_irq(pci_link, irq)) {
+			pci_link->pl_routed = 0;
 			pci_link->pl_irq = irq;
 			i = 1;
 		}
@@ -506,6 +517,11 @@ pci_pir_route_interrupt(int bus, int device, int func, int pin)
 			return (PCI_INVALID_IRQ);
 		}
 		pci_link->pl_routed = 1;
+
+		/* Ensure the interrupt is set to level/low trigger. */
+		KASSERT(pir_device != NULL, ("missing pir device"));
+		BUS_CONFIG_INTR(pir_device, pci_link->pl_irq,
+		    INTR_TRIGGER_LEVEL, INTR_POLARITY_LOW);
 	}
 	printf("$PIR: %d:%d INT%c routed to irq %d\n", bus, device,
 	    pin - 1 + 'A', pci_link->pl_irq);
@@ -603,9 +619,10 @@ pci_pir_dump_links(void)
 {
 	struct pci_link *pci_link;
 
-	printf("Link  IRQ  Ref  IRQs\n");
+	printf("Link  IRQ  Rtd  Ref  IRQs\n");
 	TAILQ_FOREACH(pci_link, &pci_links, pl_links) {
-		printf("%#4x  %3d  %3d  ", pci_link->pl_id, pci_link->pl_irq,
+		printf("%#4x  %3d   %c   %3d  ", pci_link->pl_id,
+		    pci_link->pl_irq, pci_link->pl_routed ? 'Y' : 'N',
 		    pci_link->pl_references);
 		pci_print_irqmask(pci_link->pl_irqmask);
 		printf("\n");
@@ -630,3 +647,99 @@ pci_pir_probe(int bus, int require_parse)
 			return (1);
 	return (0);
 }
+
+/*
+ * The driver for the new-bus psuedo device pir0 for the $PIR table.
+ */
+
+static int
+pir_probe(device_t dev)
+{
+	char buf[64];
+
+	snprintf(buf, sizeof(buf), "PCI Interrupt Routing Table: %d Entries",
+	    pci_route_count);
+	device_set_desc_copy(dev, buf);
+	return (0);
+}
+
+static int
+pir_attach(device_t dev)
+{
+
+	pci_pir_parse();
+	KASSERT(pir_device == NULL, ("Multiple pir devices"));
+	pir_device = dev;
+	return (0);
+}
+
+static void
+pir_resume_find_device(struct PIR_entry *entry, struct PIR_intpin *intpin,
+    void *arg)
+{
+	struct pci_dev_lookup *pd;
+
+	pd = (struct pci_dev_lookup *)arg;
+	if (intpin->link != pd->link || pd->bus != -1)
+		return;
+	pd->bus = entry->pe_bus;
+	pd->device = entry->pe_device;
+	pd->pin = intpin - entry->pe_intpin;
+}
+
+static int
+pir_resume(device_t dev)
+{
+	struct pci_dev_lookup pd;
+	struct pci_link *pci_link;
+	int error;
+
+	/* Ask the BIOS to re-route each link that was already routed. */
+	TAILQ_FOREACH(pci_link, &pci_links, pl_links) {
+		if (!PCI_INTERRUPT_VALID(pci_link->pl_irq)) {
+			KASSERT(!pci_link->pl_routed,
+			    ("link %#x is routed but has invalid PCI IRQ",
+			    pci_link->pl_id));
+			continue;
+		}
+		if (pci_link->pl_routed) {
+			pd.bus = -1;
+			pd.link = pci_link->pl_id;
+			pci_pir_walk_table(pir_resume_find_device, &pd);
+			KASSERT(pd.bus != -1,
+		("did not find matching entry for link %#x in the $PIR table",
+			    pci_link->pl_id));
+			if (bootverbose)
+				device_printf(dev,
+			    "Using %d.%d.INT%c to route link %#x to IRQ %d\n",
+				    pd.bus, pd.device, pd.pin + 'A',
+				    pci_link->pl_id, pci_link->pl_irq);
+			error = pci_pir_biosroute(pd.bus, pd.device, 0, pd.pin,
+			    pci_link->pl_irq);
+			if (error)
+				device_printf(dev,
+			    "ROUTE_INTERRUPT on resume for link %#x failed.\n",
+				    pci_link->pl_id);
+		}
+	}
+	return (0);
+}
+
+static device_method_t pir_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,		pir_probe),
+	DEVMETHOD(device_attach,	pir_attach),
+	DEVMETHOD(device_resume,	pir_resume),
+
+	{ 0, 0 }
+};
+
+static driver_t pir_driver = {
+	"pir",
+	pir_methods,
+	1,
+};
+
+static devclass_t pir_devclass;
+
+DRIVER_MODULE(pir, legacy, pir_driver, pir_devclass, 0, 0);