following behavior: * Link devices return invalid status (_STA) values. The results are very unreliable -- sometimes never present. Just ignore the status and pick the best configuration from _PRS. * Link devices return invalid current settings (_CRS). Even after setting the link value, many systems still return a different setting for _CRS. When setting an IRQ, don't bother to check _CRS to see if we succeeded. Note that we still check _CRS before routing and this should be addressed as well. Since this is a sensitive area, leave the old behavior accessible via uncommenting the define for ACPI_OLD_PCI_LINK at the top of the file. Once this has been thoroughly tested, this option and the code it covers will be removed. Thanks to Len Brown at Intel for informing us of these issues as he worked around them in Linux.
1072 lines
27 KiB
C
1072 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.
|
|
*/
|
|
|
|
/* 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$");
|
|
|
|
#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];
|
|
|
|
#ifdef ACPI_OLD_PCI_LINK
|
|
#define ACPI_STA_PRESENT 0x00000001
|
|
#define ACPI_STA_ENABLE 0x00000002
|
|
#define ACPI_STA_SHOWINUI 0x00000004
|
|
#define ACPI_STA_FUNCTIONAL 0x00000008
|
|
#endif /* ACPI_OLD_PCI_LINK */
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
#ifdef ACPI_OLD_PCI_LINK
|
|
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);
|
|
}
|
|
#endif /* ACPI_OLD_PCI_LINK */
|
|
|
|
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;
|
|
#ifdef ACPI_OLD_PCI_LINK
|
|
UINT32 sta;
|
|
#endif
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* PCI link status (_STA) is unreliable. Many systems return
|
|
* erroneous values so we ignore it.
|
|
*/
|
|
#ifdef ACPI_OLD_PCI_LINK
|
|
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);
|
|
}
|
|
#endif /* ACPI_OLD_PCI_LINK */
|
|
|
|
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;
|
|
#ifdef ACPI_OLD_PCI_LINK
|
|
UINT32 sta;
|
|
#endif
|
|
|
|
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 = AE_OK;
|
|
|
|
/*
|
|
* PCI link status (_STA) is unreliable. Many systems return
|
|
* erroneous values so we ignore it.
|
|
*/
|
|
#ifdef ACPI_OLD_PCI_LINK
|
|
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);
|
|
}
|
|
#endif /* ACPI_OLD_PCI_LINK */
|
|
|
|
/*
|
|
* Many systems always return invalid values for current settings
|
|
* (_CRS). Since we can't trust the value returned, we have to
|
|
* assume we were successful.
|
|
*/
|
|
#ifdef ACPI_OLD_PCI_LINK
|
|
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;
|
|
}
|
|
#endif /* ACPI_OLD_PCI_LINK */
|
|
|
|
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);
|
|
}
|