Re: PLEASE TEST: acpi pci irq routing

From: Nate Lawson <nate_at_root.org>
Date: Mon, 09 Aug 2004 18:09:40 -0700
Thanks for all the feedback to everyone who responded.  I have finished 
reworking our PCI irq routing approach and the result is much simpler 
while retaining the existing weighting algorithm from acpi_pci_link.c 
(around since Oct. 2002).  It removes the hack which causes the first 
IRQ to be chosen from possible IRQs if the BIOS hasn't routed an 
interrupt.  The selection algorithm is to always use the BIOS irq, if 
valid, and then select from the weighted priority list if that fails. 
I've tested both approaches fully.

Please test this patch to be sure all PCI devices continue (or begin) to 
function as expected.  If something fails for you, please send me the 
dmesg from boot -v.

Thanks,
Nate

Index: acpi_pci_link.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpi_pci_link.c,v
retrieving revision 1.18
diff -u -r1.18 acpi_pci_link.c
--- acpi_pci_link.c	6 Aug 2004 04:50:56 -0000	1.18
+++ acpi_pci_link.c	10 Aug 2004 00:56:52 -0000
_at__at_ -24,9 +24,6 _at__at_
  * SUCH DAMAGE.
  */
 
-/* XXX Uncomment this if you have new PCI IRQ problems starting 2004/8/5. */
-/* #define ACPI_OLD_PCI_LINK 1 */
-
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD: src/sys/dev/acpica/acpi_pci_link.c,v 1.18 2004/08/06 04:50:56 njl Exp $");
 
_at__at_ -39,43 +36,27 _at__at_
 #include <dev/acpica/acpivar.h>
 #include <dev/acpica/acpi_pcibvar.h>
 
+#include <dev/pci/pcivar.h>
+#include "pcib_if.h"
+
 /* Hooks for the ACPI CA debugging infrastructure. */
 #define _COMPONENT	ACPI_BUS
 ACPI_MODULE_NAME("PCI_LINK")
 
-#define MAX_POSSIBLE_INTERRUPTS	16
-#define MAX_ISA_INTERRUPTS	16
-#define MAX_ACPI_INTERRUPTS	255
-
-struct acpi_pci_link_entry {
-	TAILQ_ENTRY(acpi_pci_link_entry) links;
-	ACPI_HANDLE	handle;
-	UINT8		current_irq;
-	UINT8		initial_irq;
-	ACPI_RESOURCE	possible_resources;
-	UINT8		number_of_interrupts;
-	UINT8		interrupts[MAX_POSSIBLE_INTERRUPTS];
-	UINT8		sorted_irq[MAX_POSSIBLE_INTERRUPTS];
-	int		references;
-	int		priority;
-};
-
 TAILQ_HEAD(acpi_pci_link_entries, acpi_pci_link_entry);
 static struct acpi_pci_link_entries acpi_pci_link_entries;
 
-struct acpi_prt_entry {
-	TAILQ_ENTRY(acpi_prt_entry) links;
-	device_t	pcidev;
-	int		busno;
-	ACPI_PCI_ROUTING_TABLE prt;
-	struct acpi_pci_link_entry *pci_link;
-};
-
 TAILQ_HEAD(acpi_prt_entries, acpi_prt_entry);
 static struct acpi_prt_entries acpi_prt_entries;
 
 static int	irq_penalty[MAX_ACPI_INTERRUPTS];
 
+static int	acpi_pci_link_is_valid_irq(struct acpi_pci_link_entry *link,
+		    UINT8 irq);
+static void	acpi_pci_link_update_irq_penalty(device_t dev, int busno);
+static void	acpi_pci_link_set_bootdisabled_priority(void);
+static void	acpi_pci_link_fixup_bootdisabled_link(void);
+
 /*
  * PCI link object management
  */
_at__at_ -137,27 +118,31 _at__at_
 	UINT8			i;
 	ACPI_RESOURCE_IRQ	*Irq;
 	ACPI_RESOURCE_EXT_IRQ	*ExtIrq;
+	struct acpi_pci_link_entry *link;
 
 	if (entry == NULL || entry->pci_link == NULL)
 		return;
+	link = entry->pci_link;
 
-	printf("%s irq %3d: ", acpi_name(entry->pci_link->handle),
-	    entry->pci_link->current_irq);
+	printf("%s irq%c%2d: ", acpi_name(link->handle),
+	    (link->flags & ACPI_LINK_ROUTED) ? '*' : ' ', link->current_irq);
 
 	printf("[");
-	for (i = 0; i < entry->pci_link->number_of_interrupts; i++)
-		printf("%3d", entry->pci_link->interrupts[i]);
-	printf("] ");
+	if (link->number_of_interrupts)
+		printf("%2d", link->interrupts[0]);
+	for (i = 1; i < link->number_of_interrupts; i++)
+		printf("%3d", link->interrupts[i]);
+	printf("] %2d+ ", link->initial_irq);
 
-	switch (entry->pci_link->possible_resources.Id) {
+	switch (link->possible_resources.Id) {
 	case ACPI_RSTYPE_IRQ:
-		Irq = &entry->pci_link->possible_resources.Data.Irq;
+		Irq = &link->possible_resources.Data.Irq;
 		acpi_pci_link_dump_polarity(Irq->ActiveHighLow);
 		acpi_pci_link_dump_trigger(Irq->EdgeLevel);
 		acpi_pci_link_dump_sharemode(Irq->SharedExclusive);
 		break;
 	case ACPI_RSTYPE_EXT_IRQ:
-		ExtIrq = &entry->pci_link->possible_resources.Data.ExtendedIrq;
+		ExtIrq = &link->possible_resources.Data.ExtendedIrq;
 		acpi_pci_link_dump_polarity(ExtIrq->ActiveHighLow);
 		acpi_pci_link_dump_trigger(ExtIrq->EdgeLevel);
 		acpi_pci_link_dump_sharemode(ExtIrq->SharedExclusive);
_at__at_ -370,17 +355,34 _at__at_
 	buf.Length = ACPI_ALLOCATE_BUFFER;
 
 	bzero(link, sizeof(struct acpi_pci_link_entry));
-
 	link->handle = handle;
 
+	/*
+	 * Get the IRQ configured at boot-time.  If successful, set this
+	 * as the initial IRQ.
+	 */
 	error = acpi_pci_link_get_current_irq(link, &link->current_irq);
-	if (ACPI_FAILURE(error)) {
+	if (ACPI_SUCCESS(error)) {
+		link->initial_irq = link->current_irq;
+	} else {
 		ACPI_DEBUG_PRINT((ACPI_DB_WARN,
 		    "couldn't get current IRQ from interrupt link %s - %s\n",
 		    acpi_name(handle), AcpiFormatException(error)));
+		link->initial_irq = 0;
 	}
 
-	link->initial_irq = link->current_irq;
+	/*
+	 * Try to disable this link.  If successful, set the current IRQ to
+	 * zero and flags to indicate this link is not routed.  If we can't
+	 * run _DIS (i.e., the method doesn't exist), assume the initial
+	 * IRQ was routed by the BIOS.
+	 */
+	if (ACPI_SUCCESS(AcpiEvaluateObject(handle, "_DIS", NULL, NULL))) {
+		link->current_irq = 0;
+		link->flags = ACPI_LINK_NONE;
+	} else {
+		link->flags = ACPI_LINK_ROUTED;
+	}
 
 	error = AcpiGetPossibleResources(handle, &buf);
 	if (ACPI_FAILURE(error)) {
_at__at_ -396,6 +398,7 _at__at_
 		goto out;
 	}
 
+	/* XXX This only handles one resource, ignoring SourceIndex. */
 	resources = (ACPI_RESOURCE *) buf.Pointer;
 	bcopy(resources, &link->possible_resources,
 	    sizeof(link->possible_resources));
_at__at_ -417,6 +420,19 _at__at_
 		goto out;
 	}
 
+	/*
+	 * If the initial IRQ is invalid (not in _PRS), set it to 0 and
+	 * mark this link as not routed.  We won't use it as the preferred
+	 * interrupt later when we route.
+	 */
+	if (!acpi_pci_link_is_valid_irq(link, link->initial_irq) &&
+	    link->initial_irq != 0) {
+		printf("ACPI link %s has invalid initial irq %d, ignoring\n",
+		    acpi_name(handle), link->initial_irq);
+		link->initial_irq = 0;
+		link->flags = ACPI_LINK_NONE;
+	}
+
 	link->references++;
 
 	TAILQ_INSERT_TAIL(&acpi_pci_link_entries, link, links);
_at__at_ -467,17 +483,9 _at__at_
 	 * PCI link status (_STA) is unreliable.  Many systems return
 	 * erroneous values so we ignore it.
 	 */
-	if ((sta & (ACPI_STA_PRESENT | ACPI_STA_FUNCTIONAL)) == 0) {
-#ifndef ACPI_OLD_PCI_LINK
+	if ((sta & (ACPI_STA_PRESENT | ACPI_STA_FUNCTIONAL)) == 0)
 		device_printf(pcidev, "acpi PRT ignoring status for %s\n",
 		    acpi_name(handle));
-#else
-		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
-		    "interrupt link is not functional - %s\n",
-		    acpi_name(handle)));
-		return_ACPI_STATUS (AE_ERROR);
-#endif /* !ACPI_OLD_PCI_LINK */
-	}
 
 	TAILQ_FOREACH(entry, &acpi_prt_entries, links) {
 		if (entry->busno == busno &&
_at__at_ -502,6 +510,17 _at__at_
 	entry->busno = busno;
 	bcopy(prt, &entry->prt, sizeof(entry->prt));
 
+	/*
+	 * Make sure the Source value is null-terminated.  It is really a
+	 * variable-length string (with a fixed size in the struct) so when
+	 * we copy the entire struct, we truncate the string.  Instead of
+	 * trying to make a variable-sized PRT object to handle the string,
+	 * we store its handle in prt_source.  Callers should use that to
+	 * look up the link object.
+	 */
+	entry->prt.Source[sizeof(prt->Source) - 1] = '\0';
+	entry->prt_source = handle;
+
 	error = acpi_pci_link_add_link(handle, entry);
 	if (ACPI_FAILURE(error)) {
 		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
_at__at_ -520,6 +539,12 _at__at_
 	return_ACPI_STATUS (error);
 }
 
+/*
+ * Look up the given interrupt in the list of possible settings for
+ * this link.  We don't special-case the initial link setting.  Some
+ * systems return current settings that are outside the list of valid
+ * settings so only allow choices explicitly specified in _PRS.
+ */
 static int
 acpi_pci_link_is_valid_irq(struct acpi_pci_link_entry *link, UINT8 irq)
 {
_at__at_ -528,28 +553,11 _at__at_
 	if (irq == 0)
 		return (FALSE);
 
-#ifndef ACPI_OLD_PCI_LINK
-	/*
-	 * Look up the given interrupt in the list of possible settings for
-	 * this link.  We don't special-case the initial link setting.  Some
-	 * systems return current settings that are outside the list of valid
-	 * settings so only allow choices explicitly specified in _PRS.
-	 */
-#endif
 	for (i = 0; i < link->number_of_interrupts; i++) {
 		if (link->interrupts[i] == irq)
 			return (TRUE);
 	}
 
-	/* allow initial IRQ as valid one. */
-	if (link->initial_irq == irq)
-#ifndef ACPI_OLD_PCI_LINK
-		printf("acpi link check: %d initial irq, %d irq to route\n",
-		    link->initial_irq, irq);
-#else
-		return (TRUE);
-#endif
-
 	return (FALSE);
 }
 
_at__at_ -559,27 +567,24 _at__at_
 	ACPI_STATUS		error;
 	ACPI_RESOURCE		resbuf;
 	ACPI_BUFFER		crsbuf;
-	UINT32			sta;
 
 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
 
+	/* Make sure the new IRQ is valid before routing. */
 	if (!acpi_pci_link_is_valid_irq(link, irq)) {
-		ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
-		    "couldn't set invalid IRQ %d - %s\n", irq,
-		    acpi_name(link->handle)));
+		printf("acpi link: can't set invalid IRQ %d on %s\n",
+		    irq, acpi_name(link->handle));
 		return_ACPI_STATUS (AE_BAD_PARAMETER);
 	}
 
-	error = acpi_pci_link_get_current_irq(link, &link->current_irq);
-	if (ACPI_FAILURE(error)) {
-		ACPI_DEBUG_PRINT((ACPI_DB_WARN,
-		    "couldn't get current IRQ from interrupt link %s - %s\n",
-		    acpi_name(link->handle), AcpiFormatException(error)));
-	}
-
-	if (link->current_irq == irq)
+	/* If this this link has already been routed, just return. */
+	if (link->flags & ACPI_LINK_ROUTED) {
+		printf("link %s already routed to %d\n",
+		    acpi_name(link->handle), link->current_irq);
 		return_ACPI_STATUS (AE_OK);
+	}
 
+	/* Set up the IRQ resource for _SRS. */
 	bzero(&resbuf, sizeof(resbuf));
 	crsbuf.Pointer = NULL;
 
_at__at_ -624,41 +629,16 _at__at_
 		return_ACPI_STATUS (AE_NO_MEMORY);
 	}
 
+	/* Make the new IRQ active via the link's _SRS method. */
 	error = AcpiSetCurrentResources(link->handle, &crsbuf);
 	if (ACPI_FAILURE(error)) {
 		ACPI_DEBUG_PRINT((ACPI_DB_WARN,
 		    "couldn't set link device _SRS %s - %s\n",
 		    acpi_name(link->handle), AcpiFormatException(error)));
-		return_ACPI_STATUS (error);
+		goto out;
 	}
-
-	AcpiOsFree(crsbuf.Pointer);
+	link->flags |= ACPI_LINK_ROUTED;
 	link->current_irq = 0;
-	error = AE_OK;
-
-	/*
-	 * PCI link status (_STA) is unreliable.  Many systems return
-	 * erroneous values so we ignore it.
-	 */
-	error = acpi_pci_link_get_object_status(link->handle, &sta);
-	if (ACPI_FAILURE(error)) {
-		ACPI_DEBUG_PRINT((ACPI_DB_WARN,
-		    "couldn't get object status %s - %s\n",
-		    acpi_name(link->handle), AcpiFormatException(error)));
-		return_ACPI_STATUS (error);
-	}
-
-	if ((sta & ACPI_STA_ENABLED) == 0) {
-#ifndef ACPI_OLD_PCI_LINK
-		printf("acpi link set: ignoring status for %s\n",
-		    acpi_name(link->handle));
-#else
-		ACPI_DEBUG_PRINT((ACPI_DB_WARN,
-		    "interrupt link %s is disabled\n",
-		    acpi_name(link->handle)));
-		return_ACPI_STATUS (AE_ERROR);
-#endif /* !ACPI_OLD_PCI_LINK */
-	}
 
 	/*
 	 * Many systems always return invalid values for current settings
_at__at_ -670,26 +650,17 _at__at_
 		ACPI_DEBUG_PRINT((ACPI_DB_WARN,
 		    "couldn't get current IRQ from interrupt link %s - %s\n",
 		    acpi_name(link->handle), AcpiFormatException(error)));
-		return_ACPI_STATUS (error);
+		goto out;
 	}
-
-	if (link->current_irq == irq) {
-		error = AE_OK;
-	} else {
-#ifndef ACPI_OLD_PCI_LINK
+	if (link->current_irq != irq) {
 		printf("acpi link set: curr irq %d != %d for %s (ignoring)\n",
 		    link->current_irq, irq, acpi_name(link->handle));
 		link->current_irq = irq;
-		error = AE_OK;
-#else
-		ACPI_DEBUG_PRINT((ACPI_DB_WARN,
-		    "couldn't set IRQ %d to PCI interrupt link %d - %s\n",
-		    irq, link->current_irq, acpi_name(link->handle)));
-		link->current_irq = 0;
-		error = AE_ERROR;
-#endif /* !ACPI_OLD_PCI_LINK */
 	}
 
+out:
+	if (crsbuf.Pointer)
+		AcpiOsFree(crsbuf.Pointer);
 	return_ACPI_STATUS (error);
 }
 
_at__at_ -709,49 +680,55 _at__at_
 		if (link->current_irq != 0)
 			continue;
 
-		printf("%s:\n", acpi_name(link->handle));
-		printf("	interrupts:	");
+		printf("%s (references %d, priority %d):\n",
+		    acpi_name(link->handle), link->references, link->priority);
+		printf("\tinterrupts:\t");
 		for (i = 0; i < link->number_of_interrupts; i++) {
 			irq = link->sorted_irq[i];
 			printf("%6d", irq);
 		}
 		printf("\n");
-		printf("	penalty:	");
+		printf("\tpenalty:\t");
 		for (i = 0; i < link->number_of_interrupts; i++) {
 			irq = link->sorted_irq[i];
 			printf("%6d", irq_penalty[irq]);
 		}
 		printf("\n");
-		printf("	references:	%d\n", link->references);
-		printf("	priority:	%d\n", link->priority);
 	}
 }
 
+/*
+ * Heuristics for choosing IRQs.  We start with some static penalties,
+ * update them based on what IRQs are currently in use, then sort the
+ * result.  This works ok but is not perfect.
+ *
+ * The PCI BIOS $PIR table offers "preferred PCI interrupts", but ACPI
+ * doesn't seem to offer a similar mechanism, so picking a good
+ * interrupt here is a difficult task.
+ */
 static void
 acpi_pci_link_init_irq_penalty(void)
 {
-	int			irq;
 
 	bzero(irq_penalty, sizeof(irq_penalty));
-	for (irq = 0; irq < MAX_ISA_INTERRUPTS; irq++) {
-		/* 0, 1, 2, 8:	timer, keyboard, cascade */
-		if (irq == 0 || irq == 1 || irq == 2 || irq == 8) {
-			irq_penalty[irq] = 100000;
-			continue;
-		}
-
-		/* 13, 14, 15:	npx, ATA controllers */
-		if (irq == 13 || irq == 14 || irq == 15) {
-			irq_penalty[irq] = 10000;
-			continue;
-		}
 
-		/* 3,4,6,7,12:	typicially used by legacy hardware */
-		if (irq == 3 || irq == 4 || irq == 6 || irq == 7 || irq == 12) {
-			irq_penalty[irq] = 1000;
-			continue;
-		}
-	}
+	/* 0, 1, 2, 8:  timer, keyboard, cascade, RTC */
+	irq_penalty[0] = 100000;
+	irq_penalty[1] = 100000;
+	irq_penalty[2] = 100000;
+	irq_penalty[8] = 100000;
+
+	/* 13, 14, 15:  npx, ATA controllers */
+	irq_penalty[13] = 10000;
+	irq_penalty[14] = 10000;
+	irq_penalty[15] = 10000;
+
+	/* 3, 4, 6, 7, 12:  typically used by legacy hardware */
+	irq_penalty[3] =   1000;
+	irq_penalty[4] =   1000;
+	irq_penalty[6] =   1000;
+	irq_penalty[7] =   1000;
+	irq_penalty[12] =  1000;
 }
 
 static int
_at__at_ -761,15 +738,15 _at__at_
 	if (res == NULL ||
 	    (res->Id != ACPI_RSTYPE_IRQ &&
 	    res->Id != ACPI_RSTYPE_EXT_IRQ))
-		return (0);
+		return (FALSE);
 
 	if ((res->Id == ACPI_RSTYPE_IRQ &&
 	    res->Data.Irq.SharedExclusive == ACPI_EXCLUSIVE) ||
 	    (res->Id == ACPI_RSTYPE_EXT_IRQ &&
 	    res->Data.ExtendedIrq.SharedExclusive == ACPI_EXCLUSIVE))
-		return (1);
+		return (TRUE);
 
-	return (0);
+	return (FALSE);
 }
 
 static void
_at__at_ -791,13 +768,7 _at__at_
 		if (link == NULL)
 			continue;
 
-		if (link->current_irq != 0) {
-			/* not boot-disabled link, we will use this IRQ. */
-			irq_penalty[link->current_irq] += 100;
-			continue;
-		}
-
-		/* boot-disabled link */
+		/* Update penalties for all possible settings of this link. */
 		for (i = 0; i < link->number_of_interrupts; i++) {
 			/* give 10 for each possible IRQs. */
 			irq = link->interrupts[i];
_at__at_ -815,8 +786,8 _at__at_
 				bus_release_resource(dev, SYS_RES_IRQ,
 				    rid, res);
 			} else {
-				/* this is in use, give 100. */
-				irq_penalty[irq] += 100;
+				/* this is in use, give 10. */
+				irq_penalty[irq] += 10;
 			}
 		}
 
_at__at_ -835,18 +806,13 _at__at_
 	struct acpi_pci_link_entry *link, *link_pri;
 	TAILQ_HEAD(, acpi_pci_link_entry) sorted_list;
 
-	if (bootverbose) {
-		printf("ACPI PCI link before setting link priority:\n");
-		acpi_pci_link_bootdisabled_dump();
-	}
-
 	/* reset priority for all links. */
 	TAILQ_FOREACH(link, &acpi_pci_link_entries, links)
 		link->priority = 0;
 
 	TAILQ_FOREACH(link, &acpi_pci_link_entries, links) {
-		/* not boot-disabled link, give no chance to be arbitrated. */
-		if (link->current_irq != 0) {
+		/* If already routed, don't include in arbitration. */
+		if (link->flags & ACPI_LINK_ROUTED) {
 			link->priority = 0;
 			continue;
 		}
_at__at_ -898,16 +864,10 _at__at_
 	int			i, j;
 	int			irq1, irq2;
 	struct acpi_pci_link_entry *link;
-	ACPI_STATUS		error;
-
-	if (bootverbose) {
-		printf("ACPI PCI link before fixup for boot-disabled links:\n");
-		acpi_pci_link_bootdisabled_dump();
-	}
 
 	TAILQ_FOREACH(link, &acpi_pci_link_entries, links) {
-		/* ignore non boot-disabled links. */
-		if (link->current_irq != 0)
+		/* Ignore links that have been routed already. */
+		if (link->flags & ACPI_LINK_ROUTED)
 			continue;
 
 		/* sort IRQs based on their penalty descending. */
_at__at_ -923,21 +883,10 _at__at_
 				irq1 = irq2;
 			}
 		}
-
-		/* try with lower penalty IRQ. */
-		for (i = 0; i < link->number_of_interrupts; i++) {
-			irq1 = link->sorted_irq[i];
-			error = acpi_pci_link_set_irq(link, irq1);
-			if (error == AE_OK) {
-				/* OK, we use this.  give another penalty. */
-				irq_penalty[irq1] += 100 * link->references;
-				break;
-			}
-		}
 	}
 
 	if (bootverbose) {
-		printf("ACPI PCI link after fixup for boot-disabled links:\n");
+		printf("ACPI PCI link arbitrated settings:\n");
 		acpi_pci_link_bootdisabled_dump();
 	}
 }
_at__at_ -953,7 +902,7 _at__at_
 	ACPI_PCI_ROUTING_TABLE	*prt;
 	u_int8_t		*prtp;
 	ACPI_STATUS		error;
-	static int		first_time =1;
+	static int		first_time = 1;
 
 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
 
_at__at_ -1036,27 +985,14 _at__at_
 			entry->pci_link->current_irq = 0;
 	}
 
-	/* auto arbitration */
-	acpi_pci_link_update_irq_penalty(dev, busno);
-	acpi_pci_link_set_bootdisabled_priority();
-	acpi_pci_link_fixup_bootdisabled_link();
-
-	if (bootverbose) {
-		printf("ACPI PCI link arbitrated configuration:\n");
-		TAILQ_FOREACH(entry, &acpi_prt_entries, links) {
-			if (entry->busno != busno)
-				continue;
-			acpi_pci_link_entry_dump(entry);
-		}
-	}
-
 	return (0);
 }
 
 int
-acpi_pci_link_resume(device_t dev, ACPI_BUFFER *prtbuf, int busno)
+acpi_pci_link_resume(device_t dev)
 {
 	struct acpi_prt_entry	*entry;
+	struct acpi_pci_link_entry *link;
 	ACPI_STATUS		error;
 
 	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
_at__at_ -1064,19 +1000,103 _at__at_
 	if (acpi_disabled("pci_link"))
 		return (0);
 
+	/* Walk through all PRT entries for this PCI bridge. */
 	TAILQ_FOREACH(entry, &acpi_prt_entries, links) {
-		if (entry->pcidev != dev)
+		if (entry->pcidev == dev || entry->pci_link == NULL)
+			continue;
+		link = entry->pci_link;
+
+		/* If it's not routed, skip re-programming. */
+		if ((link->flags & ACPI_LINK_ROUTED) == 0)
 			continue;
+		link->flags &= ~ACPI_LINK_ROUTED;
 
-		error = acpi_pci_link_set_irq(entry->pci_link,
-			    entry->pci_link->current_irq);
+		/* Program it to the same setting as before suspend. */
+		error = acpi_pci_link_set_irq(link, link->current_irq);
 		if (ACPI_FAILURE(error)) {
 			ACPI_DEBUG_PRINT((ACPI_DB_WARN,
 			    "couldn't set IRQ to link entry %s - %s\n",
-			    acpi_name(entry->pci_link->handle),
+			    acpi_name(link->handle),
 			    AcpiFormatException(error)));
 		}
 	}
 
 	return (0);
 }
+
+/*
+ * Look up a PRT entry for the given device.  We match based on the slot
+ * number (high word of Address) and pin number (note that ACPI uses 0
+ * for INTA).
+ *
+ * Note that the low word of the Address field (function number) is
+ * required by the specification to be 0xffff.  We don't risk checking
+ * it here.
+ */
+struct acpi_prt_entry *
+acpi_pci_find_prt(device_t pcibdev, device_t dev, int pin)
+{
+	struct acpi_prt_entry *entry;
+	ACPI_PCI_ROUTING_TABLE *prt;
+
+	TAILQ_FOREACH(entry, &acpi_prt_entries, links) {
+		prt = &entry->prt;
+		if ((prt->Address & 0xffff0000) >> 16 == pci_get_slot(dev) &&
+		    prt->Pin == pin)
+			return (entry);
+	}
+	return (NULL);
+}
+
+/*
+ * Perform the actual programming for this link.  We attempt to route an
+ * IRQ, first the one set by the BIOS, and then a priority-sorted list.
+ * Only do the programming once per link.
+ */
+int
+acpi_pci_link_route(device_t dev, struct acpi_prt_entry *prt)
+{
+	struct acpi_pci_link_entry *link;
+	int busno, i, irq;
+	ACPI_STATUS status;
+
+	busno = pci_get_bus(dev);
+	link = prt->pci_link;
+	if (link == NULL || link->number_of_interrupts == 0)
+		return (PCI_INVALID_IRQ);
+
+	/* If already routed, just return the current setting. */
+	if (link->flags & ACPI_LINK_ROUTED)
+		return (link->current_irq);
+
+	/* Update all IRQ weights to determine our priority list. */
+	acpi_pci_link_update_irq_penalty(prt->pcidev, busno);
+	acpi_pci_link_set_bootdisabled_priority();
+	acpi_pci_link_fixup_bootdisabled_link();
+
+	/*
+	 * First, attempt to route the initial IRQ, if valid, since it was
+	 * the one set up by the BIOS.  If this fails, route according to
+	 * our priority-sorted list of IRQs.
+	 */
+	status = AE_NOT_FOUND;
+	irq = link->initial_irq;
+	if (irq)
+		status = acpi_pci_link_set_irq(link, irq);
+	for (i = 0; ACPI_FAILURE(status) && i < link->number_of_interrupts;
+	    i++) {
+		irq = link->sorted_irq[i];
+		status = acpi_pci_link_set_irq(link, irq);
+		if (ACPI_FAILURE(status)) {
+			device_printf(dev, "_SRS failed, irq %d via %s\n",
+			    irq, acpi_name(link->handle));
+		}
+	}
+	if (ACPI_FAILURE(status))
+		return (PCI_INVALID_IRQ);
+
+	/* Update the penalty now that there's another user for this IRQ. */
+	irq_penalty[irq] += 100 * link->references;
+
+	return (irq);
+}
Index: acpi_pcib.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpi_pcib.c,v
retrieving revision 1.46
diff -u -r1.46 acpi_pcib.c
--- acpi_pcib.c	1 Jul 2004 07:46:27 -0000	1.46
+++ acpi_pcib.c	10 Aug 2004 00:04:33 -0000
_at__at_ -90,316 +90,106 _at__at_
 }
 
 int
-acpi_pcib_resume(device_t dev, ACPI_BUFFER *prt, int busno)
+acpi_pcib_resume(device_t dev)
 {
-    acpi_pci_link_resume(dev, prt, busno);
+    acpi_pci_link_resume(dev);
     return (bus_generic_resume(dev));
 }
 
 /*
  * Route an interrupt for a child of the bridge.
- *
- * XXX clean up error messages
- *
- * XXX this function is somewhat bulky
  */
 int
-acpi_pcib_route_interrupt(device_t pcib, device_t dev, int pin,
-    ACPI_BUFFER *prtbuf)
+acpi_pcib_route_interrupt(device_t pcib, device_t dev, int pin)
 {
+    struct acpi_prt_entry	*entry;
+    int				i, interrupt;
+    struct acpi_pci_link_entry	*link;
+    ACPI_RESOURCE		*prsres;
     ACPI_PCI_ROUTING_TABLE	*prt;
-    ACPI_HANDLE			lnkdev;
-    ACPI_BUFFER			crsbuf, prsbuf, buf;
-    ACPI_RESOURCE		*crsres, *prsres, resbuf;
-    ACPI_DEVICE_INFO		*devinfo;
-    ACPI_STATUS			status;
-    UINT32			NumberOfInterrupts;
-    UINT32			*Interrupts;
-    u_int8_t			*prtp;
-    int				interrupt;
-    int				i;
 
     ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
-    
-    crsres = NULL;
-    buf.Pointer = NULL;
-    crsbuf.Pointer = NULL;
-    prsbuf.Pointer = NULL;
+
+    prsres = NULL;
     interrupt = PCI_INVALID_IRQ;
 
     /* ACPI numbers pins 0-3, not 1-4 like the BIOS. */
     pin--;
 
-    /* We failed to retrieve the routing table. */
-    prtp = prtbuf->Pointer;
-    if (prtp == NULL)
-	goto out;
-
-    /* Scan the table to look for this device. */
-    for (;;) {
-	prt = (ACPI_PCI_ROUTING_TABLE *)prtp;
-
-	/* We hit the end of the table. */
-	if (prt->Length == 0)
-	    goto out;
-
-	/*
-	 * Compare the slot number (high word of Address) and pin number
-	 * (note that ACPI uses 0 for INTA) to check for a match.
-	 *
-	 * Note that the low word of the Address field (function number)
-	 * is required by the specification to be 0xffff.  We don't risk
-	 * checking it here.
-	 */
-	if (((prt->Address & 0xffff0000) >> 16) == pci_get_slot(dev) &&
-	    prt->Pin == pin) {
-	    if (bootverbose)
-		device_printf(pcib, "matched entry for %d.%d.INT%c (src %s)\n",
-			      pci_get_bus(dev), pci_get_slot(dev), 'A' + pin,
-			      prt->Source);
-	    break;
-	}
-	
-	/* Skip to the next entry. */
-	prtp += prt->Length;
-    }
+    /* Look up the PRT entry for this device. */
+    entry = acpi_pci_find_prt(pcib, dev, pin);
+    if (entry == NULL)
+	return (AE_ERROR);
+    prt = &entry->prt;
+    link = entry->pci_link;
+    if (bootverbose)
+	device_printf(pcib, "matched entry for %d.%d.INT%c (src %s)\n",
+	    pci_get_bus(dev), pci_get_slot(dev), 'A' + pin,
+	    acpi_name(entry->prt_source));
 
     /*
-     * If source is empty/NULL, the source index is the global IRQ number.
+     * If source is empty/NULL, the source index is a global IRQ number
+     * and it's hard-wired so we're done.
      */
     if (prt->Source == NULL || prt->Source[0] == '\0') {
 	if (bootverbose)
 	    device_printf(pcib, "device is hardwired to IRQ %d\n",
-			  prt->SourceIndex);
-	interrupt = prt->SourceIndex;
-	goto out;
-    }
-    
-    /*
-     * We have to find the source device (PCI interrupt link device).
-     */
-    if (ACPI_FAILURE(AcpiGetHandle(ACPI_ROOT_OBJECT, prt->Source, &lnkdev))) {
-	device_printf(pcib, "couldn't find PCI interrupt link device %s\n",
-	    prt->Source);
-	goto out;
-    }
-
-    /*
-     * Verify that this is a PCI link device and that it's present.
-     */
-    buf.Length = ACPI_ALLOCATE_BUFFER;
-    if (ACPI_FAILURE(AcpiGetObjectInfo(lnkdev, &buf))) {
-	device_printf(pcib, "couldn't validate PCI interrupt link device %s\n",
-		      prt->Source);
-	goto out;
-    }
-    devinfo = (ACPI_DEVICE_INFO *)buf.Pointer;
-    if ((devinfo->Valid & ACPI_VALID_HID) == 0 ||
-	strcmp("PNP0C0F", devinfo->HardwareId.Value) != 0) {
-	device_printf(pcib, "PCI interrupt link %s has invalid _HID (%s)\n",
-		      prt->Source, devinfo->HardwareId.Value);
-	goto out;
-    }
-    if ((devinfo->Valid & ACPI_VALID_STA) != 0 &&
-	(devinfo->CurrentStatus & 0x9) != 0x9) {
-	device_printf(pcib, "PCI interrupt link device %s not present\n",
-		      prt->Source);
-	goto out;
-    }
-
-    /*
-     * Get the current and possible resources for the interrupt link device.
-     * If we fail to get the current resources, this is a fatal error.
-     */
-    crsbuf.Length = ACPI_ALLOCATE_BUFFER;
-    if (ACPI_FAILURE(status = AcpiGetCurrentResources(lnkdev, &crsbuf))) {
-	device_printf(pcib, "PCI interrupt link device _CRS failed - %s\n",
-		      AcpiFormatException(status));
-	goto out;
-    }
-    prsbuf.Length = ACPI_ALLOCATE_BUFFER;
-    if (ACPI_FAILURE(status = AcpiGetPossibleResources(lnkdev, &prsbuf))) {
-	device_printf(pcib, "PCI interrupt link device _PRS failed - %s\n",
-		      AcpiFormatException(status));
-    }
-    ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "got %ld bytes for %s._CRS\n",
-		     (long)crsbuf.Length, acpi_name(lnkdev)));
-    ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "got %ld bytes for %s._PRS\n",
-		     (long)prsbuf.Length, acpi_name(lnkdev)));
-
-    /*
-     * The interrupt may already be routed, so check _CRS first.  We don't
-     * check the 'decoding' bit in the _STA result, since there's nothing in
-     * the spec that mandates it be set, however some BIOS' will set it if
-     * the decode is active.
-     *
-     * The Source Index points to the particular resource entry we're
-     * interested in.
-     */
-    if (ACPI_FAILURE(acpi_FindIndexedResource(&crsbuf, prt->SourceIndex,
-	&crsres))) {
-	device_printf(pcib, "_CRS buffer corrupt, cannot route interrupt\n");
+		prt->SourceIndex);
+	if (prt->SourceIndex)
+	    interrupt = prt->SourceIndex;
+	else
+	    device_printf(pcib, "invalid hard-wired IRQ of 0\n");
 	goto out;
     }
 
-    /* Type-check the resource we've found. */
-    if (crsres->Id != ACPI_RSTYPE_IRQ && crsres->Id != ACPI_RSTYPE_EXT_IRQ) {
-	device_printf(pcib, "_CRS resource entry has unsupported type %d\n",
-		      crsres->Id);
+    /* XXX Support for multiple resources must be added to the link code. */
+    if (prt->SourceIndex) {
+	device_printf(pcib, "src index %d not yet supported\n",
+	    prt->SourceIndex);
 	goto out;
     }
 
-    /* Set variables based on resource type. */
-    if (crsres->Id == ACPI_RSTYPE_IRQ) {
-	NumberOfInterrupts = crsres->Data.Irq.NumberOfInterrupts;
-	Interrupts = crsres->Data.Irq.Interrupts;
-    } else {
-	NumberOfInterrupts = crsres->Data.ExtendedIrq.NumberOfInterrupts;
-	Interrupts = crsres->Data.ExtendedIrq.Interrupts;
-    }
-
-    /* If there's more than one interrupt, this is an error. */
-    if (NumberOfInterrupts > 1) {
-	device_printf(pcib, "device has too many interrupts (%d)\n",
-		      NumberOfInterrupts);
+    /* There has to be at least one interrupt available. */
+    if (link->number_of_interrupts == 0) {
+	device_printf(pcib, "device has no interrupts\n");
 	goto out;
     }
 
     /* 
-     * If there's only one interrupt, and it's not zero, then it's already
-     * routed.
-     *
-     * Note that we could also check the 'decoding' bit in _STA, but can't
-     * depend on it since it's not part of the spec.
-     *
-     * XXX check ASL examples to see if this is an acceptable set of tests
+     * If the current interrupt has been routed, we're done.  This is the
+     * case when the BIOS initializes it and we didn't disable it.
      */
-    if (NumberOfInterrupts == 1 && Interrupts[0] != 0) {
+    if (link->flags & ACPI_LINK_ROUTED) {
+	interrupt = link->current_irq;
 	if (bootverbose)
-	    device_printf(pcib, "slot %d INT%c is routed to irq %d\n",
-		pci_get_slot(dev), 'A' + pin, Interrupts[0]);
-	interrupt = Interrupts[0];
-	goto out;
-    }
-    
-    /* 
-     * There isn't an interrupt, so we have to look at _PRS to get one.
-     * Get the set of allowed interrupts from the _PRS resource indexed
-     * by SourceIndex.
-     */
-    if (prsbuf.Pointer == NULL) {
-	device_printf(pcib, "no routed irq and no _PRS on irq link device\n");
-	goto out;
-    }
-
-    /*
-     * Search through the _PRS resources, looking for an IRQ or extended
-     * IRQ resource.  Skip dependent function resources for now.  In the
-     * future, we might use these for priority but this is good enough for
-     * now until BIOS vendors actually mean something by using them.
-     */
-    prsres = NULL;
-    for (i = prt->SourceIndex; prsres == NULL; i++) {
-	if (ACPI_FAILURE(acpi_FindIndexedResource(&prsbuf, i, &prsres))) {
-	    device_printf(pcib, "_PRS lacks IRQ resource, routing failed\n");
-	    goto out;
-	}
-	switch (prsres->Id) {
-	case ACPI_RSTYPE_IRQ:
-	    NumberOfInterrupts = prsres->Data.Irq.NumberOfInterrupts;
-	    Interrupts = prsres->Data.Irq.Interrupts;
-	    break;
-	case ACPI_RSTYPE_EXT_IRQ:
-	    NumberOfInterrupts = prsres->Data.ExtendedIrq.NumberOfInterrupts;
-	    Interrupts = prsres->Data.ExtendedIrq.Interrupts;
-	    break;
-	case ACPI_RSTYPE_START_DPF:
-	    prsres = NULL;
-	    continue;
-	default:
-	    device_printf(pcib, "_PRS has invalid type %d\n", prsres->Id);
-	    goto out;
-	}
-    }
-
-    /* There has to be at least one interrupt available. */
-    if (NumberOfInterrupts < 1) {
-	device_printf(pcib, "device has no interrupts\n");
+	    device_printf(pcib, "slot %d INT%c is already routed to irq %d\n",
+		pci_get_slot(dev), 'A' + pin, interrupt);
 	goto out;
     }
 
-    /*
-     * Pick an interrupt to use.  Note that a more scientific approach than
-     * just taking the first one available would be desirable.
-     *
-     * The PCI BIOS $PIR table offers "preferred PCI interrupts", but ACPI
-     * doesn't seem to offer a similar mechanism, so picking a "good"
-     * interrupt here is a difficult task.
-     *
-     * Build a resource buffer and pass it to AcpiSetCurrentResources to
-     * route the new interrupt.
-     */
     if (bootverbose) {
 	device_printf(pcib, "possible interrupts:");
-	for (i = 0; i < NumberOfInterrupts; i++)
-	    printf("  %d", Interrupts[i]);
+	for (i = 0; i < link->number_of_interrupts; i++)
+	    printf("%3d", link->interrupts[i]);
 	printf("\n");
     }
 
-    /* This should never happen. */
-    if (crsbuf.Pointer != NULL)
-	AcpiOsFree(crsbuf.Pointer);
-
-    /* XXX Data.Irq and Data.ExtendedIrq are implicitly structure-copied. */
-    crsbuf.Pointer = NULL;
-    crsres = NULL;
-    if (prsres->Id == ACPI_RSTYPE_IRQ) {
-	resbuf.Id = ACPI_RSTYPE_IRQ;
-	resbuf.Length = ACPI_SIZEOF_RESOURCE(ACPI_RESOURCE_IRQ);
-	resbuf.Data.Irq = prsres->Data.Irq;
-	resbuf.Data.Irq.NumberOfInterrupts = 1;
-	resbuf.Data.Irq.Interrupts[0] = Interrupts[0];
-    } else {
-	resbuf.Id = ACPI_RSTYPE_EXT_IRQ;
-	resbuf.Length = ACPI_SIZEOF_RESOURCE(ACPI_RESOURCE_EXT_IRQ);
-	resbuf.Data.ExtendedIrq = prsres->Data.ExtendedIrq;
-	resbuf.Data.ExtendedIrq.NumberOfInterrupts = 1;
-	resbuf.Data.ExtendedIrq.Interrupts[0] = Interrupts[0];
-    }
-    if (ACPI_FAILURE(status = acpi_AppendBufferResource(&crsbuf, &resbuf))) {
-	device_printf(pcib, "buf append failed for interrupt %d via %s - %s\n",
-		      Interrupts[0], acpi_name(lnkdev),
-		      AcpiFormatException(status));
-	goto out;
-    }
-    /* XXX Figure out how this is happening when the append succeeds. */
-    if (crsbuf.Pointer == NULL) {
-	device_printf(pcib, "_CRS buf NULL after append?\n");
-	goto out;
-    }
-    if (ACPI_FAILURE(status = AcpiSetCurrentResources(lnkdev, &crsbuf))) {
-	device_printf(pcib, "_SRS failed for interrupt %d via %s - %s\n",
-		      Interrupts[0], acpi_name(lnkdev),
-		      AcpiFormatException(status));
-	goto out;
-    }
-    crsres = &resbuf;
-    
-    /* Return the interrupt we just routed. */
+    /*
+     * Perform the link routing.  If successful, use the _PRS value for
+     * setting the trigger/polarity via acpi_config_intr() below.
+     */
+    interrupt = acpi_pci_link_route(dev, entry);
+    if (interrupt)
+	prsres = &link->possible_resources;
+
     if (bootverbose)
 	device_printf(pcib, "slot %d INT%c routed to irq %d via %s\n",
-	    pci_get_slot(dev), 'A' + pin, Interrupts[0], acpi_name(lnkdev));
-    interrupt = Interrupts[0];
+	    pci_get_slot(dev), 'A' + pin, interrupt,
+	    acpi_name(entry->prt_source));
 
 out:
-    if (PCI_INTERRUPT_VALID(interrupt) && crsres != NULL)
-	acpi_config_intr(dev, crsres);
-    if (crsbuf.Pointer != NULL)
-	AcpiOsFree(crsbuf.Pointer);
-    if (prsbuf.Pointer != NULL)
-	AcpiOsFree(prsbuf.Pointer);
-    if (buf.Pointer != NULL)
-	AcpiOsFree(buf.Pointer);
+    if (PCI_INTERRUPT_VALID(interrupt) && prsres)
+	acpi_config_intr(dev, prsres);
 
     return_VALUE (interrupt);
 }
Index: acpi_pcib_acpi.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpi_pcib_acpi.c,v
retrieving revision 1.38
diff -u -r1.38 acpi_pcib_acpi.c
--- acpi_pcib_acpi.c	4 Jul 2004 16:23:25 -0000	1.38
+++ acpi_pcib_acpi.c	7 Aug 2004 04:58:12 -0000
_at__at_ -234,9 +234,8 _at__at_
 static int
 acpi_pcib_acpi_resume(device_t dev)
 {
-    struct acpi_hpcib_softc	*sc = device_get_softc(dev);
 
-    return (acpi_pcib_resume(dev, &sc->ap_prt, sc->ap_bus));
+    return (acpi_pcib_resume(dev));
 }
 
 /*
_at__at_ -297,11 +296,8 _at__at_
 static int
 acpi_pcib_acpi_route_interrupt(device_t pcib, device_t dev, int pin)
 {
-    struct acpi_hpcib_softc *sc;
 
-    /* Find the bridge softc. */
-    sc = device_get_softc(pcib);
-    return (acpi_pcib_route_interrupt(pcib, dev, pin, &sc->ap_prt));
+    return (acpi_pcib_route_interrupt(pcib, dev, pin));
 }
 
 struct resource *
Index: acpi_pcib_pci.c
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpi_pcib_pci.c,v
retrieving revision 1.9
diff -u -r1.9 acpi_pcib_pci.c
--- acpi_pcib_pci.c	30 May 2004 20:08:23 -0000	1.9
+++ acpi_pcib_pci.c	7 Aug 2004 04:57:49 -0000
_at__at_ -139,9 +139,8 _at__at_
 static int
 acpi_pcib_pci_resume(device_t dev)
 {
-    struct acpi_pcib_softc *sc = device_get_softc(dev);
 
-    return (acpi_pcib_resume(dev, &sc->ap_prt, sc->ap_pcibsc.secbus));
+    return (acpi_pcib_resume(dev));
 }
 
 static int
_at__at_ -171,5 +170,5 _at__at_
     if (sc->ap_prt.Pointer == NULL)
 	return (pcib_route_interrupt(pcib, dev, pin));
     else
-	return (acpi_pcib_route_interrupt(pcib, dev, pin, &sc->ap_prt));
+	return (acpi_pcib_route_interrupt(pcib, dev, pin));
 }
Index: acpi_pcibvar.h
===================================================================
RCS file: /home/ncvs/src/sys/dev/acpica/acpi_pcibvar.h,v
retrieving revision 1.2
diff -u -r1.2 acpi_pcibvar.h
--- acpi_pcibvar.h	5 Oct 2002 02:01:02 -0000	1.2
+++ acpi_pcibvar.h	10 Aug 2004 00:48:31 -0000
_at__at_ -31,11 +31,42 _at__at_
 #define	_ACPI_PCIBVAR_H_
 
 int	acpi_pcib_attach(device_t bus, ACPI_BUFFER *prt, int busno);
-int	acpi_pcib_route_interrupt(device_t pcib, device_t dev, int pin,
-    ACPI_BUFFER *ptrbuf);
-int	acpi_pcib_resume(device_t bus, ACPI_BUFFER *prt, int busno);
+int	acpi_pcib_route_interrupt(device_t pcib, device_t dev, int pin);
+int	acpi_pcib_resume(device_t dev);
+
+#define MAX_POSSIBLE_INTERRUPTS	16
+#define MAX_ISA_INTERRUPTS	16
+#define MAX_ACPI_INTERRUPTS	255
+
+struct acpi_pci_link_entry {
+	TAILQ_ENTRY(acpi_pci_link_entry) links;
+	ACPI_HANDLE	handle;
+	UINT8		current_irq;
+	UINT8		initial_irq;
+	ACPI_RESOURCE	possible_resources;
+	UINT8		number_of_interrupts;
+	UINT8		interrupts[MAX_POSSIBLE_INTERRUPTS];
+	UINT8		sorted_irq[MAX_POSSIBLE_INTERRUPTS];
+	int		references;
+	int		priority; 
+	int		flags;
+#define ACPI_LINK_NONE		0
+#define ACPI_LINK_ROUTED	(1 << 0)
+};
+
+struct acpi_prt_entry {
+	TAILQ_ENTRY(acpi_prt_entry) links;
+	device_t	pcidev;
+	int		busno;
+	ACPI_PCI_ROUTING_TABLE prt;
+	ACPI_HANDLE	prt_source;
+	struct acpi_pci_link_entry *pci_link;
+};
 
 int	acpi_pci_link_config(device_t pcib, ACPI_BUFFER *prt, int busno);
-int	acpi_pci_link_resume(device_t pcib, ACPI_BUFFER *prt, int busno);
+int	acpi_pci_link_resume(device_t pcib);
+struct	acpi_prt_entry *acpi_pci_find_prt(device_t pcibdev, device_t dev,
+	    int pin);
+int	acpi_pci_link_route(device_t dev, struct acpi_prt_entry *prt);
 
 #endif
Received on Mon Aug 09 2004 - 23:09:47 UTC

This archive was generated by hypermail 2.4.0 : Wed May 19 2021 - 11:38:05 UTC