85cdc19d00
Tested by: gj
1147 lines
30 KiB
C
1147 lines
30 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>
|
|
|
|
#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")
|
|
|
|
TAILQ_HEAD(acpi_pci_link_entries, acpi_pci_link_entry);
|
|
static struct acpi_pci_link_entries acpi_pci_link_entries;
|
|
ACPI_SERIAL_DECL(pci_link, "ACPI 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
|
|
*/
|
|
|
|
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;
|
|
struct acpi_pci_link_entry *link;
|
|
|
|
if (entry == NULL || entry->pci_link == NULL)
|
|
return;
|
|
link = entry->pci_link;
|
|
|
|
printf("%s irq%c%2d: ", acpi_name(link->handle),
|
|
(link->flags & ACPI_LINK_ROUTED) ? '*' : ' ', link->current_irq);
|
|
|
|
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 (link->possible_resources.Id) {
|
|
case ACPI_RSTYPE_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 = &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) {
|
|
printf("acpi link get: resource %d is not an IRQ\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) {
|
|
printf("acpi link get: empty 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__);
|
|
ACPI_SERIAL_ASSERT(pci_link);
|
|
|
|
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;
|
|
|
|
/*
|
|
* 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_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;
|
|
}
|
|
|
|
/*
|
|
* 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)) {
|
|
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;
|
|
}
|
|
|
|
/* Skip any DPF descriptors. XXX We should centralize this code. */
|
|
resources = (ACPI_RESOURCE *) buf.Pointer;
|
|
if (resources->Id == ACPI_RSTYPE_START_DPF)
|
|
resources = ACPI_NEXT_RESOURCE(resources);
|
|
|
|
/* XXX This only handles one resource, ignoring SourceIndex. */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
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__);
|
|
ACPI_SERIAL_ASSERT(pci_link);
|
|
|
|
if (prt == NULL) {
|
|
device_printf(pcidev, "NULL PRT entry\n");
|
|
return_ACPI_STATUS (AE_BAD_PARAMETER);
|
|
}
|
|
|
|
/* Bail out if attempting to add a duplicate PRT entry. */
|
|
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,
|
|
"PRT entry already exists\n"));
|
|
return_ACPI_STATUS (AE_ALREADY_EXISTS);
|
|
}
|
|
}
|
|
|
|
/* Allocate and initialize our new PRT entry. */
|
|
entry = AcpiOsAllocate(sizeof(struct acpi_prt_entry));
|
|
if (entry == NULL) {
|
|
ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "can't allocate memory\n"));
|
|
return_ACPI_STATUS (AE_NO_MEMORY);
|
|
}
|
|
bzero(entry, sizeof(struct acpi_prt_entry));
|
|
|
|
/*
|
|
* If the source link is NULL, then this IRQ is hardwired so skip
|
|
* initializing the link but still add it to the list.
|
|
*/
|
|
if (prt->Source[0] != '\0') {
|
|
/* Get a handle for the link source. */
|
|
error = AcpiGetHandle(acpi_get_handle(pcidev), prt->Source,
|
|
&handle);
|
|
if (ACPI_FAILURE(error)) {
|
|
device_printf(pcidev, "get handle for %s - %s\n",
|
|
prt->Source, AcpiFormatException(error));
|
|
goto out;
|
|
}
|
|
|
|
error = acpi_pci_link_get_object_status(handle, &sta);
|
|
if (ACPI_FAILURE(error)) {
|
|
device_printf(pcidev, "can't get status for %s - %s\n",
|
|
acpi_name(handle), AcpiFormatException(error));
|
|
goto out;
|
|
}
|
|
|
|
/* Probe/initialize the link. */
|
|
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;
|
|
}
|
|
}
|
|
|
|
entry->pcidev = pcidev;
|
|
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;
|
|
|
|
TAILQ_INSERT_TAIL(&acpi_prt_entries, entry, links);
|
|
error = AE_OK;
|
|
|
|
out:
|
|
if (error != AE_OK && entry != NULL)
|
|
AcpiOsFree(entry);
|
|
|
|
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)
|
|
{
|
|
UINT8 i;
|
|
|
|
if (irq == 0)
|
|
return (FALSE);
|
|
|
|
for (i = 0; i < link->number_of_interrupts; i++) {
|
|
if (link->interrupts[i] == irq)
|
|
return (TRUE);
|
|
}
|
|
|
|
return (FALSE);
|
|
}
|
|
|
|
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;
|
|
|
|
ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
|
|
ACPI_SERIAL_ASSERT(pci_link);
|
|
|
|
/* Make sure the new IRQ is valid before routing. */
|
|
if (!acpi_pci_link_is_valid_irq(link, irq)) {
|
|
printf("acpi link set: invalid IRQ %d on %s\n",
|
|
irq, acpi_name(link->handle));
|
|
return_ACPI_STATUS (AE_BAD_PARAMETER);
|
|
}
|
|
|
|
/* If this this link has already been routed, just return. */
|
|
if (link->flags & ACPI_LINK_ROUTED) {
|
|
printf("acpi link set: %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;
|
|
|
|
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:
|
|
printf("acpi link set: %s resource is not an IRQ (%d)\n",
|
|
acpi_name(link->handle), link->possible_resources.Id);
|
|
return_ACPI_STATUS (AE_TYPE);
|
|
}
|
|
|
|
error = acpi_AppendBufferResource(&crsbuf, &resbuf);
|
|
if (ACPI_FAILURE(error)) {
|
|
printf("acpi link set: AppendBuffer failed for %s\n",
|
|
acpi_name(link->handle));
|
|
return_ACPI_STATUS (error);
|
|
}
|
|
if (crsbuf.Pointer == NULL) {
|
|
printf("acpi link set: AppendBuffer returned empty for %s\n",
|
|
acpi_name(link->handle));
|
|
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)) {
|
|
printf("acpi link set: _SRS failed for link %s - %s\n",
|
|
acpi_name(link->handle), AcpiFormatException(error));
|
|
goto out;
|
|
}
|
|
link->flags |= ACPI_LINK_ROUTED;
|
|
link->current_irq = 0;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
error = acpi_pci_link_get_current_irq(link, &link->current_irq);
|
|
if (ACPI_FAILURE(error)) {
|
|
if (bootverbose)
|
|
printf("acpi link set: _CRS failed for link %s - %s\n",
|
|
acpi_name(link->handle),
|
|
AcpiFormatException(error));
|
|
error = AE_OK;
|
|
}
|
|
if (link->current_irq != irq) {
|
|
if (bootverbose)
|
|
printf("acpi link set: curr irq %d != %d for %s\n",
|
|
link->current_irq, irq, acpi_name(link->handle));
|
|
link->current_irq = irq;
|
|
}
|
|
|
|
out:
|
|
if (crsbuf.Pointer)
|
|
AcpiOsFree(crsbuf.Pointer);
|
|
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;
|
|
|
|
ACPI_SERIAL_ASSERT(pci_link);
|
|
TAILQ_FOREACH(link, &acpi_pci_link_entries, links) {
|
|
/* boot-disabled link only. */
|
|
if (link->current_irq != 0)
|
|
continue;
|
|
|
|
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("\tpenalty:\t");
|
|
for (i = 0; i < link->number_of_interrupts; i++) {
|
|
irq = link->sorted_irq[i];
|
|
printf("%6d", irq_penalty[irq]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
|
|
bzero(irq_penalty, sizeof(irq_penalty));
|
|
|
|
/* 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] = 50000;
|
|
irq_penalty[14] = 50000;
|
|
irq_penalty[15] = 50000;
|
|
|
|
/* 3, 4, 6, 7, 12: typically used by legacy hardware */
|
|
irq_penalty[3] = 5000;
|
|
irq_penalty[4] = 5000;
|
|
irq_penalty[6] = 5000;
|
|
irq_penalty[7] = 5000;
|
|
irq_penalty[12] = 5000;
|
|
|
|
/* 5: sometimes legacy sound cards */
|
|
irq_penalty[5] = 50;
|
|
}
|
|
|
|
static int
|
|
link_exclusive(ACPI_RESOURCE *res)
|
|
{
|
|
|
|
if (res == NULL ||
|
|
(res->Id != ACPI_RSTYPE_IRQ &&
|
|
res->Id != ACPI_RSTYPE_EXT_IRQ))
|
|
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 (TRUE);
|
|
|
|
return (FALSE);
|
|
}
|
|
|
|
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;
|
|
|
|
ACPI_SERIAL_ASSERT(pci_link);
|
|
TAILQ_FOREACH(entry, &acpi_prt_entries, links) {
|
|
if (entry->busno != busno)
|
|
continue;
|
|
|
|
/* Impossible? */
|
|
link = entry->pci_link;
|
|
if (link == NULL)
|
|
continue;
|
|
|
|
/* 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];
|
|
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 10. */
|
|
irq_penalty[irq] += 10;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
|
|
ACPI_SERIAL_ASSERT(pci_link);
|
|
|
|
/* reset priority for all links. */
|
|
TAILQ_FOREACH(link, &acpi_pci_link_entries, links)
|
|
link->priority = 0;
|
|
|
|
TAILQ_FOREACH(link, &acpi_pci_link_entries, links) {
|
|
/* If already routed, don't include in arbitration. */
|
|
if (link->flags & ACPI_LINK_ROUTED) {
|
|
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_SERIAL_ASSERT(pci_link);
|
|
|
|
TAILQ_FOREACH(link, &acpi_pci_link_entries, links) {
|
|
/* Ignore links that have been routed already. */
|
|
if (link->flags & ACPI_LINK_ROUTED)
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bootverbose) {
|
|
printf("ACPI PCI link arbitrated settings:\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;
|
|
int ret;
|
|
static int first_time = 1;
|
|
|
|
ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
|
|
|
|
if (acpi_disabled("pci_link"))
|
|
return (0);
|
|
|
|
ret = -1;
|
|
ACPI_SERIAL_BEGIN(pci_link);
|
|
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)
|
|
goto out;
|
|
|
|
prtp = prtbuf->Pointer;
|
|
if (prtp == NULL) /* didn't get routing table */
|
|
goto out;
|
|
|
|
/* 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;
|
|
}
|
|
ret = 0;
|
|
|
|
out:
|
|
ACPI_SERIAL_END(pci_link);
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
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__);
|
|
|
|
if (acpi_disabled("pci_link"))
|
|
return (0);
|
|
|
|
/* Walk through all PRT entries for this PCI bridge. */
|
|
ACPI_SERIAL_BEGIN(pci_link);
|
|
TAILQ_FOREACH(entry, &acpi_prt_entries, links) {
|
|
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;
|
|
|
|
/* 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(link->handle),
|
|
AcpiFormatException(error)));
|
|
}
|
|
}
|
|
ACPI_SERIAL_END(pci_link);
|
|
|
|
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;
|
|
|
|
ACPI_SERIAL_BEGIN(pci_link);
|
|
TAILQ_FOREACH(entry, &acpi_prt_entries, links) {
|
|
prt = &entry->prt;
|
|
if (entry->busno == pci_get_bus(dev) &&
|
|
(prt->Address & 0xffff0000) >> 16 == pci_get_slot(dev) &&
|
|
prt->Pin == pin)
|
|
break;
|
|
}
|
|
ACPI_SERIAL_END(pci_link);
|
|
return (entry);
|
|
}
|
|
|
|
/*
|
|
* 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_RESOURCE crsres;
|
|
ACPI_STATUS status;
|
|
|
|
busno = pci_get_bus(dev);
|
|
link = prt->pci_link;
|
|
irq = PCI_INVALID_IRQ;
|
|
ACPI_SERIAL_BEGIN(pci_link);
|
|
if (link == NULL || link->number_of_interrupts == 0)
|
|
goto out;
|
|
|
|
/* If already routed, just return the current setting. */
|
|
if (link->flags & ACPI_LINK_ROUTED) {
|
|
irq = link->current_irq;
|
|
goto out;
|
|
}
|
|
|
|
/* 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)) {
|
|
irq = PCI_INVALID_IRQ;
|
|
goto out;
|
|
}
|
|
|
|
/* Update the penalty now that there's another user for this IRQ. */
|
|
irq_penalty[irq] += 10 * link->references;
|
|
|
|
/* Configure trigger/polarity for the new IRQ. */
|
|
bcopy(&link->possible_resources, &crsres, sizeof(crsres));
|
|
if (crsres.Id == ACPI_RSTYPE_IRQ) {
|
|
crsres.Data.Irq.NumberOfInterrupts = 1;
|
|
crsres.Data.Irq.Interrupts[0] = irq;
|
|
} else {
|
|
crsres.Data.ExtendedIrq.NumberOfInterrupts = 1;
|
|
crsres.Data.ExtendedIrq.Interrupts[0] = irq;
|
|
}
|
|
acpi_config_intr(dev, &crsres);
|
|
|
|
out:
|
|
ACPI_SERIAL_END(pci_link);
|
|
return (irq);
|
|
}
|