When assigning an IRQ for classic PIC routing to an ACPI link dev,

make sure to update the PCI Interrupt Register of all PCI devices using
it. This fixes interrupt with ACPI and w/o IOAPIC, when the BIOS didn't
route all interrupts correctly.

Reported by sborill@ and by Mathias De Belder in PR 37001, fix tested
by sborill@.
This commit is contained in:
joerg 2007-11-07 15:29:15 +00:00
parent 54221c3e0f
commit bedc8f14a0

View File

@ -1,4 +1,4 @@
/* $NetBSD: acpi_pci_link.c,v 1.8 2007/11/05 10:30:44 joerg Exp $ */
/* $NetBSD: acpi_pci_link.c,v 1.9 2007/11/07 15:29:15 joerg Exp $ */
/*-
* Copyright (c) 2002 Mitsuru IWASAKI <iwasaki@jp.freebsd.org>
@ -27,7 +27,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: acpi_pci_link.c,v 1.8 2007/11/05 10:30:44 joerg Exp $");
__KERNEL_RCSID(0, "$NetBSD: acpi_pci_link.c,v 1.9 2007/11/07 15:29:15 joerg Exp $");
#include "opt_acpi.h"
#include <sys/param.h>
@ -110,6 +110,8 @@ struct link {
int l_num_irqs;
int *l_irqs;
int l_references;
int l_dev_count;
pcitag_t *l_devices;
int l_routed:1;
int l_isa_irq:1;
ACPI_RESOURCE l_prs_template;
@ -468,6 +470,8 @@ acpi_pci_link_attach(struct acpi_pci_link_softc *sc)
sc->pl_links[i].l_sc = sc;
sc->pl_links[i].l_isa_irq = FALSE;
sc->pl_links[i].l_res_index = -1;
sc->pl_links[i].l_dev_count = 0;
sc->pl_links[i].l_devices = NULL;
}
/* Try to read the current settings from _CRS if it is valid. */
@ -546,13 +550,59 @@ acpi_pci_link_attach(struct acpi_pci_link_softc *sc)
return (0);
fail:
ACPI_SERIAL_END(pci_link);
for (i = 0; i < sc->pl_num_links; i++)
for (i = 0; i < sc->pl_num_links; i++) {
if (sc->pl_links[i].l_irqs != NULL)
free(sc->pl_links[i].l_irqs, M_PCI_LINK);
if (sc->pl_links[i].l_devices != NULL)
free(sc->pl_links[i].l_devices, M_PCI_LINK);
}
free(sc->pl_links, M_PCI_LINK);
return (ENXIO);
}
static void
acpi_pci_link_add_functions(struct acpi_pci_link_softc *sc, struct link *link,
int bus, int device, int pin)
{
uint32_t value;
uint8_t func, maxfunc, ipin;
pcitag_t tag;
tag = pci_make_tag(acpi_softc->sc_pc, bus, device, 0);
/* See if we have a valid device at function 0. */
value = pci_conf_read(acpi_softc->sc_pc, tag, PCI_BHLC_REG);
if (PCI_HDRTYPE_TYPE(value) > PCI_HDRTYPE_PCB)
return;
if (PCI_HDRTYPE_MULTIFN(value))
maxfunc = 7;
else
maxfunc = 0;
/* Scan all possible functions at this device. */
for (func = 0; func <= maxfunc; func++) {
tag = pci_make_tag(acpi_softc->sc_pc, bus, device, func);
value = pci_conf_read(acpi_softc->sc_pc, tag, PCI_ID_REG);
if (PCI_VENDOR(value) == 0xffff)
continue;
value = pci_conf_read(acpi_softc->sc_pc, tag,
PCI_INTERRUPT_REG);
ipin = PCI_INTERRUPT_PIN(value);
/*
* See if it uses the pin in question. Note that the passed
* in pin uses 0 for A, .. 3 for D whereas the intpin
* register uses 0 for no interrupt, 1 for A, .. 4 for D.
*/
if (ipin != pin + 1)
continue;
link->l_devices = realloc(link->l_devices,
sizeof(pcitag_t) * (link->l_dev_count + 1),
M_PCI_LINK, M_WAITOK);
link->l_devices[link->l_dev_count] = tag;
++link->l_dev_count;
}
}
static uint8_t
acpi_pci_link_search_irq(struct acpi_pci_link_softc *sc, int bus, int device,
int pin)
@ -630,6 +680,7 @@ acpi_pci_link_add_reference(void *v, int index, int bus, int slot, int pin)
return;
}
link->l_references++;
acpi_pci_link_add_functions(sc, link, bus, slot, pin);
if (link->l_routed)
pci_link_interrupt_weights[link->l_irq]++;
@ -1009,6 +1060,8 @@ acpi_pci_link_route_interrupt(void *v, int index, int *irq, int *pol, int *trig)
{
struct acpi_pci_link_softc *sc = v;
struct link *link;
int i;
pcireg_t reg;
ACPI_SERIAL_BEGIN(pci_link);
link = acpi_pci_link_lookup(sc, index);
@ -1029,23 +1082,36 @@ acpi_pci_link_route_interrupt(void *v, int index, int *irq, int *pol, int *trig)
}
/* Choose an IRQ if we need one. */
if (!PCI_INTERRUPT_VALID(link->l_irq)) {
link->l_irq = acpi_pci_link_choose_irq(sc, link);
if (PCI_INTERRUPT_VALID(link->l_irq))
goto done;
/*
* Try to route the interrupt we picked. If it fails, then
* assume the interrupt is not routed.
*/
if (PCI_INTERRUPT_VALID(link->l_irq)) {
acpi_pci_link_route_irqs(sc, irq, pol, trig);
if (!link->l_routed)
link->l_irq = PCI_INVALID_IRQ;
else {
link->l_pol = *pol;
link->l_trig = *trig;
}
}
link->l_irq = acpi_pci_link_choose_irq(sc, link);
/*
* Try to route the interrupt we picked. If it fails, then
* assume the interrupt is not routed.
*/
if (!PCI_INTERRUPT_VALID(link->l_irq))
goto done;
acpi_pci_link_route_irqs(sc, irq, pol, trig);
if (!link->l_routed) {
link->l_irq = PCI_INVALID_IRQ;
goto done;
}
link->l_pol = *pol;
link->l_trig = *trig;
for (i = 0; i < link->l_dev_count; ++i) {
reg = pci_conf_read(acpi_softc->sc_pc, link->l_devices[i],
PCI_INTERRUPT_REG);
reg &= ~(PCI_INTERRUPT_LINE_MASK << PCI_INTERRUPT_LINE_SHIFT);
reg |= link->l_irq << PCI_INTERRUPT_LINE_SHIFT;
pci_conf_write(acpi_softc->sc_pc, link->l_devices[i],
PCI_INTERRUPT_REG, reg);
}
done:
ACPI_SERIAL_END(pci_link);
return (link->l_irq);