/*- * Copyright (c) 2002 Mitsuru IWASAKI * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD: src/sys/dev/acpica/acpi_pci_link.c,v 1.16 2004/06/14 18:54:14 jhb Exp $ * $DragonFly: src/sys/dev/acpica5/acpi_pci_link.c,v 1.3 2004/07/05 00:07:35 dillon Exp $ */ #include "opt_acpi.h" #include #include #include #include "acpi.h" #include #include /* 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]; #define ACPI_STA_PRESENT 0x00000001 #define ACPI_STA_ENABLE 0x00000002 #define ACPI_STA_SHOWINUI 0x00000004 #define ACPI_STA_FUNCTIONAL 0x00000008 /* * PCI link object management */ static void acpi_pci_link_dump_polarity(UINT32 ActiveHighLow) { switch (ActiveHighLow) { case ACPI_ACTIVE_HIGH: printf("high,"); break; case ACPI_ACTIVE_LOW: printf("low,"); break; default: printf("unknown,"); break; } } static void acpi_pci_link_dump_trigger(UINT32 EdgeLevel) { switch (EdgeLevel) { case ACPI_EDGE_SENSITIVE: printf("edge,"); break; case ACPI_LEVEL_SENSITIVE: printf("level,"); break; default: printf("unknown,"); break; } } static void acpi_pci_link_dump_sharemode(UINT32 SharedExclusive) { switch (SharedExclusive) { case ACPI_EXCLUSIVE: printf("exclusive"); break; case ACPI_SHARED: printf("sharable"); break; default: printf("unknown"); break; } } static void acpi_pci_link_entry_dump(struct acpi_prt_entry *entry) { UINT8 i; ACPI_RESOURCE_IRQ *Irq; ACPI_RESOURCE_EXT_IRQ *ExtIrq; if (entry == NULL || entry->pci_link == NULL) return; printf("%s irq %3d: ", acpi_name(entry->pci_link->handle), entry->pci_link->current_irq); printf("["); for (i = 0; i < entry->pci_link->number_of_interrupts; i++) printf("%3d", entry->pci_link->interrupts[i]); printf("] "); switch (entry->pci_link->possible_resources.Id) { case ACPI_RSTYPE_IRQ: Irq = &entry->pci_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; acpi_pci_link_dump_polarity(ExtIrq->ActiveHighLow); acpi_pci_link_dump_trigger(ExtIrq->EdgeLevel); acpi_pci_link_dump_sharemode(ExtIrq->SharedExclusive); break; } printf(" %d.%d.%d\n", entry->busno, (int)((entry->prt.Address & 0xffff0000) >> 16), (int)entry->prt.Pin); } static ACPI_STATUS acpi_pci_link_get_object_status(ACPI_HANDLE handle, UINT32 *sta) { ACPI_DEVICE_INFO *devinfo; ACPI_BUFFER buf; ACPI_STATUS error; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (handle == NULL || sta == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "invalid argument\n")); return_ACPI_STATUS (AE_BAD_PARAMETER); } buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; error = AcpiGetObjectInfo(handle, &buf); if (ACPI_FAILURE(error)) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "couldn't get object info %s - %s\n", acpi_name(handle), AcpiFormatException(error))); return_ACPI_STATUS (error); } devinfo = (ACPI_DEVICE_INFO *)buf.Pointer; if ((devinfo->Valid & ACPI_VALID_HID) == 0 || strcmp(devinfo->HardwareId.Value, "PNP0C0F") != 0) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "invalid hardware ID - %s\n", acpi_name(handle))); AcpiOsFree(buf.Pointer); return_ACPI_STATUS (AE_TYPE); } if ((devinfo->Valid & ACPI_VALID_STA) != 0) { *sta = devinfo->CurrentStatus; } else { ACPI_DEBUG_PRINT((ACPI_DB_WARN, "invalid status - %s\n", acpi_name(handle))); *sta = 0; } AcpiOsFree(buf.Pointer); return_ACPI_STATUS (AE_OK); } static ACPI_STATUS acpi_pci_link_get_irq_resources(ACPI_RESOURCE *resources, UINT8 *number_of_interrupts, UINT8 interrupts[]) { UINT8 count; UINT8 i; UINT32 NumberOfInterrupts; UINT32 *Interrupts; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (resources == NULL || number_of_interrupts == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "invalid argument\n")); return_ACPI_STATUS (AE_BAD_PARAMETER); } *number_of_interrupts = 0; NumberOfInterrupts = 0; Interrupts = NULL; if (resources->Id == ACPI_RSTYPE_START_DPF) resources = ACPI_NEXT_RESOURCE(resources); if (resources->Id != ACPI_RSTYPE_IRQ && resources->Id != ACPI_RSTYPE_EXT_IRQ) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Resource is not an IRQ entry - %d\n", resources->Id)); return_ACPI_STATUS (AE_TYPE); } switch (resources->Id) { case ACPI_RSTYPE_IRQ: NumberOfInterrupts = resources->Data.Irq.NumberOfInterrupts; Interrupts = resources->Data.Irq.Interrupts; break; case ACPI_RSTYPE_EXT_IRQ: NumberOfInterrupts = resources->Data.ExtendedIrq.NumberOfInterrupts; Interrupts = resources->Data.ExtendedIrq.Interrupts; break; } if (NumberOfInterrupts == 0) { ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Blank IRQ resource\n")); return_ACPI_STATUS (AE_NULL_ENTRY); } count = 0; for (i = 0; i < NumberOfInterrupts; i++) { if (i >= MAX_POSSIBLE_INTERRUPTS) { ACPI_DEBUG_PRINT((ACPI_DB_WARN, "too many IRQs (%d)\n", i)); break; } if (Interrupts[i] == 0) { ACPI_DEBUG_PRINT((ACPI_DB_WARN, "invalid IRQ %d\n", Interrupts[i])); continue; } interrupts[count] = Interrupts[i]; count++; } *number_of_interrupts = count; return_ACPI_STATUS (AE_OK); } static ACPI_STATUS acpi_pci_link_get_current_irq(struct acpi_pci_link_entry *link, UINT8 *irq) { ACPI_STATUS error; ACPI_BUFFER buf; ACPI_RESOURCE *resources; UINT8 number_of_interrupts; UINT8 interrupts[MAX_POSSIBLE_INTERRUPTS];; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (link == NULL || irq == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "invalid argument\n")); return_ACPI_STATUS (AE_BAD_PARAMETER); } *irq = 0; buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; error = AcpiGetCurrentResources(link->handle, &buf); if (ACPI_FAILURE(error)) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "couldn't get PCI interrupt link device _CRS %s - %s\n", acpi_name(link->handle), AcpiFormatException(error))); return_ACPI_STATUS (error); } if (buf.Pointer == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "couldn't allocate memory - %s\n", acpi_name(link->handle))); return_ACPI_STATUS (AE_NO_MEMORY); } resources = (ACPI_RESOURCE *) buf.Pointer; number_of_interrupts = 0; bzero(interrupts, sizeof(interrupts)); error = acpi_pci_link_get_irq_resources(resources, &number_of_interrupts, interrupts); AcpiOsFree(buf.Pointer); 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))); return_ACPI_STATUS (error); } if (number_of_interrupts == 0) { ACPI_DEBUG_PRINT((ACPI_DB_WARN, "PCI interrupt link device _CRS data is corrupted - %s\n", acpi_name(link->handle))); return_ACPI_STATUS (AE_NULL_ENTRY); } *irq = interrupts[0]; return_ACPI_STATUS (AE_OK); } static ACPI_STATUS acpi_pci_link_add_link(ACPI_HANDLE handle, struct acpi_prt_entry *entry) { ACPI_STATUS error; ACPI_BUFFER buf; ACPI_RESOURCE *resources; struct acpi_pci_link_entry *link; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); entry->pci_link = NULL; TAILQ_FOREACH(link, &acpi_pci_link_entries, links) { if (link->handle == handle) { entry->pci_link = link; link->references++; return_ACPI_STATUS (AE_OK); } } link = AcpiOsAllocate(sizeof(struct acpi_pci_link_entry)); if (link == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "couldn't allocate memory - %s\n", acpi_name(handle))); return_ACPI_STATUS (AE_NO_MEMORY); } buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; bzero(link, sizeof(struct acpi_pci_link_entry)); link->handle = handle; 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(handle), AcpiFormatException(error))); } link->initial_irq = link->current_irq; error = AcpiGetPossibleResources(handle, &buf); if (ACPI_FAILURE(error)) { ACPI_DEBUG_PRINT((ACPI_DB_WARN, "couldn't get interrupt link device _PRS data %s - %s\n", acpi_name(handle), AcpiFormatException(error))); goto out; } if (buf.Pointer == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_WARN, "_PRS nuffer is empty - %s\n", acpi_name(handle))); error = AE_NO_MEMORY; goto out; } resources = (ACPI_RESOURCE *) buf.Pointer; bcopy(resources, &link->possible_resources, sizeof(link->possible_resources)); error = acpi_pci_link_get_irq_resources(resources, &link->number_of_interrupts, link->interrupts); if (ACPI_FAILURE(error)) { ACPI_DEBUG_PRINT((ACPI_DB_WARN, "couldn't get possible IRQs from interrupt link %s - %s\n", acpi_name(handle), AcpiFormatException(error))); goto out; } if (link->number_of_interrupts == 0) { ACPI_DEBUG_PRINT((ACPI_DB_WARN, "interrupt link device _PRS data is corrupted - %s\n", acpi_name(handle))); error = AE_NULL_ENTRY; goto out; } link->references++; TAILQ_INSERT_TAIL(&acpi_pci_link_entries, link, links); entry->pci_link = link; error = AE_OK; out: if (buf.Pointer != NULL) AcpiOsFree(buf.Pointer); if (error != AE_OK && link != NULL) AcpiOsFree(link); return_ACPI_STATUS (error); } static ACPI_STATUS acpi_pci_link_add_prt(device_t pcidev, ACPI_PCI_ROUTING_TABLE *prt, int busno) { ACPI_HANDLE handle; ACPI_STATUS error; UINT32 sta; struct acpi_prt_entry *entry; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (prt == NULL || prt->Source == NULL || prt->Source[0] == '\0') { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "couldn't handle this routing table - hardwired\n")); return_ACPI_STATUS (AE_BAD_PARAMETER); } error = AcpiGetHandle(acpi_get_handle(pcidev), prt->Source, &handle); if (ACPI_FAILURE(error)) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "couldn't get handle - %s\n", AcpiFormatException(error))); return_ACPI_STATUS (error); } error = acpi_pci_link_get_object_status(handle, &sta); if (ACPI_FAILURE(error)) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "couldn't get object status %s - %s\n", acpi_name(handle), AcpiFormatException(error))); return_ACPI_STATUS (error); } if ((sta & (ACPI_STA_PRESENT | ACPI_STA_FUNCTIONAL)) == 0) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "interrupt link is not functional - %s\n", acpi_name(handle))); return_ACPI_STATUS (AE_ERROR); } TAILQ_FOREACH(entry, &acpi_prt_entries, links) { if (entry->busno == busno && entry->prt.Address == prt->Address && entry->prt.Pin == prt->Pin) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "interrupt link entry already exists - %s\n", acpi_name(handle))); return_ACPI_STATUS (AE_ALREADY_EXISTS); } } entry = AcpiOsAllocate(sizeof(struct acpi_prt_entry)); if (entry == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "couldn't allocate memory - %s\n", acpi_name(handle))); return_ACPI_STATUS (AE_NO_MEMORY); } bzero(entry, sizeof(struct acpi_prt_entry)); entry->pcidev = pcidev; entry->busno = busno; bcopy(prt, &entry->prt, sizeof(entry->prt)); error = acpi_pci_link_add_link(handle, entry); if (ACPI_FAILURE(error)) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "couldn't add _PRT entry to link %s - %s\n", acpi_name(handle), AcpiFormatException(error))); goto out; } TAILQ_INSERT_TAIL(&acpi_prt_entries, entry, links); error = AE_OK; out: if (error != AE_OK && entry != NULL) AcpiOsFree(entry); return_ACPI_STATUS (error); } static int acpi_pci_link_is_valid_irq(struct acpi_pci_link_entry *link, UINT8 irq) { UINT8 i; if (irq == 0) return (0); for (i = 0; i < link->number_of_interrupts; i++) { if (link->interrupts[i] == irq) return (1); } /* allow initial IRQ as valid one. */ if (link->initial_irq == irq) return (1); return (0); } static ACPI_STATUS acpi_pci_link_set_irq(struct acpi_pci_link_entry *link, UINT8 irq) { ACPI_STATUS error; ACPI_RESOURCE resbuf; ACPI_BUFFER crsbuf; UINT32 sta; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 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))); 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) return_ACPI_STATUS (AE_OK); bzero(&resbuf, sizeof(resbuf)); crsbuf.Pointer = NULL; switch (link->possible_resources.Id) { case ACPI_RSTYPE_IRQ: resbuf.Id = ACPI_RSTYPE_IRQ; resbuf.Length = ACPI_SIZEOF_RESOURCE(ACPI_RESOURCE_IRQ); /* structure copy other fields */ resbuf.Data.Irq = link->possible_resources.Data.Irq; resbuf.Data.Irq.NumberOfInterrupts = 1; resbuf.Data.Irq.Interrupts[0] = irq; break; case ACPI_RSTYPE_EXT_IRQ: resbuf.Id = ACPI_RSTYPE_EXT_IRQ; resbuf.Length = ACPI_SIZEOF_RESOURCE(ACPI_RESOURCE_EXT_IRQ); /* structure copy other fields */ resbuf.Data.ExtendedIrq = link->possible_resources.Data.ExtendedIrq; resbuf.Data.ExtendedIrq.NumberOfInterrupts = 1; resbuf.Data.ExtendedIrq.Interrupts[0] = irq; break; default: ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Resource is not an IRQ entry %s - %d\n", acpi_name(link->handle), link->possible_resources.Id)); return_ACPI_STATUS (AE_TYPE); } error = acpi_AppendBufferResource(&crsbuf, &resbuf); if (ACPI_FAILURE(error)) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "couldn't setup buffer by acpi_AppendBufferResource - %s\n", acpi_name(link->handle))); return_ACPI_STATUS (error); } if (crsbuf.Pointer == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "appended buffer for %s is corrupted\n", acpi_name(link->handle))); return_ACPI_STATUS (AE_NO_MEMORY); } 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); } AcpiOsFree(crsbuf.Pointer); link->current_irq = 0; 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_ENABLE) == 0) { ACPI_DEBUG_PRINT((ACPI_DB_WARN, "interrupt link %s is disabled\n", acpi_name(link->handle))); return_ACPI_STATUS (AE_ERROR); } 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))); return_ACPI_STATUS (error); } if (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; } return_ACPI_STATUS (error); } /* * Auto arbitration for boot-disabled devices */ static void acpi_pci_link_bootdisabled_dump(void) { int i; int irq; struct acpi_pci_link_entry *link; TAILQ_FOREACH(link, &acpi_pci_link_entries, links) { /* boot-disabled link only. */ if (link->current_irq != 0) continue; printf("%s:\n", acpi_name(link->handle)); printf(" interrupts: "); for (i = 0; i < link->number_of_interrupts; i++) { irq = link->sorted_irq[i]; printf("%6d", irq); } printf("\n"); printf(" penalty: "); 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); } } 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; } } } static int link_exclusive(ACPI_RESOURCE *res) { if (res == NULL || (res->Id != ACPI_RSTYPE_IRQ && res->Id != ACPI_RSTYPE_EXT_IRQ)) return (0); 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 (0); } static void acpi_pci_link_update_irq_penalty(device_t dev, int busno) { int i; int irq; int rid; struct resource *res; struct acpi_prt_entry *entry; struct acpi_pci_link_entry *link; TAILQ_FOREACH(entry, &acpi_prt_entries, links) { if (entry->busno != busno) continue; /* Impossible? */ link = entry->pci_link; 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 */ for (i = 0; i < link->number_of_interrupts; i++) { /* give 10 for each possible IRQs. */ irq = link->interrupts[i]; irq_penalty[irq] += 10; /* higher penalty if exclusive. */ if (link_exclusive(&link->possible_resources)) irq_penalty[irq] += 100; /* XXX try to get this IRQ in non-sharable mode. */ rid = 0; res = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, irq, irq, 1, 0); if (res != NULL) { bus_release_resource(dev, SYS_RES_IRQ, rid, res); } else { /* this is in use, give 100. */ irq_penalty[irq] += 100; } } /* initialize `sorted' possible IRQs. */ bcopy(link->interrupts, link->sorted_irq, sizeof(link->sorted_irq)); } } static void acpi_pci_link_set_bootdisabled_priority(void) { int sum_penalty; int i; int irq; 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) { link->priority = 0; continue; } /* * Calculate the priority for each boot-disabled links. * o IRQ penalty indicates difficulty to use. * o #references for devices indicates importance of the link. * o #interrupts indicates flexibility of the link. */ sum_penalty = 0; for (i = 0; i < link->number_of_interrupts; i++) { irq = link->interrupts[i]; sum_penalty += irq_penalty[irq]; } link->priority = (sum_penalty * link->references) / link->number_of_interrupts; } /* * Sort PCI links based on the priority. * XXX Any other better ways rather than using work list? */ TAILQ_INIT(&sorted_list); while (!TAILQ_EMPTY(&acpi_pci_link_entries)) { link = TAILQ_FIRST(&acpi_pci_link_entries); /* find an entry which has the highest priority. */ TAILQ_FOREACH(link_pri, &acpi_pci_link_entries, links) if (link->priority < link_pri->priority) link = link_pri; /* move to work list. */ TAILQ_REMOVE(&acpi_pci_link_entries, link, links); TAILQ_INSERT_TAIL(&sorted_list, link, links); } while (!TAILQ_EMPTY(&sorted_list)) { /* move them back to the list, one by one... */ link = TAILQ_FIRST(&sorted_list); TAILQ_REMOVE(&sorted_list, link, links); TAILQ_INSERT_TAIL(&acpi_pci_link_entries, link, links); } } static void acpi_pci_link_fixup_bootdisabled_link(void) { 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) continue; /* sort IRQs based on their penalty descending. */ for (i = 0; i < link->number_of_interrupts; i++) { irq1 = link->sorted_irq[i]; for (j = i + 1; j < link->number_of_interrupts; j++) { irq2 = link->sorted_irq[j]; if (irq_penalty[irq1] < irq_penalty[irq2]) { continue; } link->sorted_irq[i] = irq2; link->sorted_irq[j] = irq1; 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"); acpi_pci_link_bootdisabled_dump(); } } /* * Public interface */ int acpi_pci_link_config(device_t dev, ACPI_BUFFER *prtbuf, int busno) { struct acpi_prt_entry *entry; ACPI_PCI_ROUTING_TABLE *prt; u_int8_t *prtp; ACPI_STATUS error; static int first_time =1; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (acpi_disabled("pci_link")) return (0); if (first_time) { TAILQ_INIT(&acpi_prt_entries); TAILQ_INIT(&acpi_pci_link_entries); acpi_pci_link_init_irq_penalty(); first_time = 0; } if (prtbuf == NULL) return (-1); prtp = prtbuf->Pointer; if (prtp == NULL) /* didn't get routing table */ return (-1); /* scan the PCI Routing Table */ for (;;) { prt = (ACPI_PCI_ROUTING_TABLE *)prtp; if (prt->Length == 0) /* end of table */ break; error = acpi_pci_link_add_prt(dev, prt, busno); if (ACPI_FAILURE(error)) { ACPI_DEBUG_PRINT((ACPI_DB_WARN, "couldn't add PCI interrupt link entry - %s\n", AcpiFormatException(error))); } /* skip to next entry */ prtp += prt->Length; } if (bootverbose) { printf("ACPI PCI link initial configuration:\n"); TAILQ_FOREACH(entry, &acpi_prt_entries, links) { if (entry->busno != busno) continue; acpi_pci_link_entry_dump(entry); } } /* manual configuration. */ TAILQ_FOREACH(entry, &acpi_prt_entries, links) { int irq; char prthint[32]; if (entry->busno != busno) continue; snprintf(prthint, sizeof(prthint), "hw.acpi.pci.link.%d.%d.%d.irq", entry->busno, (int)((entry->prt.Address & 0xffff0000) >> 16), (int)entry->prt.Pin); if (getenv_int(prthint, &irq) == 0) continue; if (acpi_pci_link_is_valid_irq(entry->pci_link, irq)) { error = acpi_pci_link_set_irq(entry->pci_link, 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), AcpiFormatException(error))); } continue; } /* * Do auto arbitration for this device's PCI link * if hint value 0 is specified. */ if (irq == 0) 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) { struct acpi_prt_entry *entry; ACPI_STATUS error; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (acpi_disabled("pci_link")) return (0); TAILQ_FOREACH(entry, &acpi_prt_entries, links) { if (entry->pcidev != dev) continue; error = acpi_pci_link_set_irq(entry->pci_link, entry->pci_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), AcpiFormatException(error))); } } return (0); }