freebsd-skq/sys/dev/acpica/acpi_pci_link.c

1108 lines
27 KiB
C

/*-
* Copyright (c) 2002 Mitsuru IWASAKI <iwasaki@jp.freebsd.org>
* 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.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "opt_acpi.h"
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include "acpi.h"
#include <dev/acpica/acpivar.h>
#include <dev/acpica/acpi_pcibvar.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];
#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", entry->busno,
(int)((entry->prt.Address & 0xffff0000) >> 16),
(int)entry->prt.Pin);
printf("\n");
}
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 PCI 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 PCI 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 PCI 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 PCI interrupt link %s - %s\n",
acpi_name(handle), AcpiFormatException(error)));
goto out;
}
if (link->number_of_interrupts == 0) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN,
"PCI 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 acpi 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))) {
ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
"PCI 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,
"PCI 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 pci 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 PCI 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) {
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);
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;
}
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,
"buffer setup by acpi_AppendBufferResource is corrupted - %s\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 PCI interrupt 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)) {
ACPI_DEBUG_PRINT((ACPI_DB_WARN,
"PCI interrupt link is disabled - %s\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 PCI 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
acpi_pci_link_is_irq_exclusive(ACPI_RESOURCE *res)
{
if (res == NULL) {
return (0);
}
if (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) {
return (1);
}
if (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;
}
link = entry->pci_link;
if (link == NULL) {
continue; /* impossible... */
}
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 (acpi_pci_link_is_irq_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("---- before setting priority for links ------------\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("---- before fixup 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;
}
/* NG, try next IRQ... */
}
}
if (bootverbose) {
printf("---- after fixup 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("---- 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) {
UINT8 irq;
char *irqstr, *op;
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);
irqstr = getenv(prthint);
if (irqstr == NULL) {
continue;
}
irq = strtoul(irqstr, &op, 0);
if (*op != '\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 PCI interrupt 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("---- 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 PCI interrupt link entry %s - %s\n",
acpi_name(entry->pci_link->handle),
AcpiFormatException(error)));
}
}
return (0);
}