irq: MSI first, and poll the INTx lines that never arrive
PCI drivers took their interrupt from INTERRUPT_LINE. On real hardware that is the 8259's number, not the IOAPIC pin the device is wired to (that is in ACPI _PRT, which needs AML), so nothing ever arrived. xHCI is entirely interrupt-driven, so the USB keyboard and a USB root would have hung even on a clean boot. Under QEMU the two numbers agree, which is why it never showed. - pci_irq_attach()/pci_irq_detach(): MSI, then MSI-X if the driver allows it, then INTx. The handler is registered before MSI is enabled, so no edge is lost. xHCI, e1000, r8169 and rtl8139 use it. - Lines 24-29 are MSI vectors. The LAPIC error vector moves from 19, which is a PCI pin on Intel chipsets, to 30. Nothing is placed at or above vector 64 (T_SYSCALL). - A storming line is masked and its handlers are polled instead of being left to eat the CPU. A level line that has had a handler for 2 s and never fired is polled every tick by a kernel thread. Tested in QEMU q35 with the root on a USB stick: xHCI and e1000e on MSI; then MSI disabled and INTx deliberately misrouted, where xHCI got zero interrupts and the root still mounted through the poller. i440fx INTx and the rtl8139 hand-over are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HH7xukt4aaiGj1nQEAoQf1
This commit is contained in:
parent
94c936ed98
commit
7e8d5e2821
11 changed files with 407 additions and 35 deletions
133
kernel/dev/pci.c
133
kernel/dev/pci.c
|
|
@ -24,6 +24,10 @@
|
|||
#include "sbuf.h"
|
||||
#include "dbg.h"
|
||||
#include "device.h"
|
||||
#include "traps.h"
|
||||
#include "irq.h"
|
||||
#include "memlayout.h"
|
||||
#include "proc.h"
|
||||
|
||||
static struct pci_dev devs[NPCI];
|
||||
static int ndevs;
|
||||
|
|
@ -178,6 +182,135 @@ pci_msi_enable(struct pci_dev *d, int vector, int cpu)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// The same for MSI-X, entry 0 only.
|
||||
//
|
||||
// MSI-X keeps its address/data pairs in a table inside one of the device's
|
||||
// own BARs rather than in config space, which is the whole difference. Some
|
||||
// controllers -- several AMD xHCIs among them -- offer MSI-X and no MSI, and
|
||||
// without this they had only INTx, which on real hardware is the line that
|
||||
// does not arrive.
|
||||
static int
|
||||
pci_msix_enable(struct pci_dev *d, int vector, int apicid)
|
||||
{
|
||||
uint8 cap = pci_find_cap(d, PCI_CAP_MSIX);
|
||||
if(cap == 0)
|
||||
return -1;
|
||||
|
||||
uint32 tbl = pci_read32(d->bus, d->slot, d->func, cap + 4);
|
||||
int bir = tbl & 7;
|
||||
uint64 off = tbl & ~7u;
|
||||
if(bir > 5 || d->bar[bir].base == 0 || d->bar[bir].is_io)
|
||||
return -1;
|
||||
|
||||
volatile uint32 *t = ioremap(d->bar[bir].base + off, 16);
|
||||
if(t == 0)
|
||||
return -1;
|
||||
|
||||
// Enable with the whole function masked, program entry 0, unmask it, then
|
||||
// drop the function mask: at no point is an entry live with half an
|
||||
// address in it. The other entries come out of reset masked and stay so.
|
||||
uint32 dw = pci_read32(d->bus, d->slot, d->func, cap);
|
||||
pci_write32(d->bus, d->slot, d->func, cap, dw | (3u << 30)); // enable + fmask
|
||||
t[0] = 0xFEE00000u | ((uint32)apicid << 12);
|
||||
t[1] = 0;
|
||||
t[2] = (uint32)vector;
|
||||
t[3] = 0; // unmask
|
||||
dw = pci_read32(d->bus, d->slot, d->func, cap);
|
||||
pci_write32(d->bus, d->slot, d->func, cap, (dw | (1u << 31)) & ~(1u << 30));
|
||||
|
||||
pci_write32(d->bus, d->slot, d->func, PCI_COMMAND,
|
||||
pci_read32(d->bus, d->slot, d->func, PCI_COMMAND) | (1 << 10));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Turn off whichever of MSI or MSI-X is on, and give the device back its INTx
|
||||
// -- the next driver to claim it may be one that only knows INTx.
|
||||
static void
|
||||
pci_msi_off(struct pci_dev *d)
|
||||
{
|
||||
uint8 cap;
|
||||
uint32 dw;
|
||||
|
||||
if((cap = pci_find_cap(d, PCI_CAP_MSI)) != 0){
|
||||
dw = pci_read32(d->bus, d->slot, d->func, cap);
|
||||
pci_write32(d->bus, d->slot, d->func, cap, dw & ~(1u << 16));
|
||||
}
|
||||
if((cap = pci_find_cap(d, PCI_CAP_MSIX)) != 0){
|
||||
dw = pci_read32(d->bus, d->slot, d->func, cap);
|
||||
pci_write32(d->bus, d->slot, d->func, cap, dw & ~(1u << 31));
|
||||
}
|
||||
pci_write32(d->bus, d->slot, d->func, PCI_COMMAND,
|
||||
pci_read32(d->bus, d->slot, d->func, PCI_COMMAND) & ~(1u << 10));
|
||||
}
|
||||
|
||||
int
|
||||
pci_irq_attach(struct pci_dev *d, irq_handler_t fn, void *arg,
|
||||
const char *name, int flags)
|
||||
{
|
||||
// The processor the interrupt goes to, by APIC ID: the boot processor's,
|
||||
// which is the one IOAPIC lines are routed to as well.
|
||||
int dest = cpus[0].apicid;
|
||||
int irq = irq_alloc_msi();
|
||||
|
||||
if(irq >= 0){
|
||||
// Handler first, then the device. An MSI is an edge: one that arrives
|
||||
// before anything is listening is gone, and an xHCI whose first event
|
||||
// interrupt is lost will not raise another until it is acknowledged.
|
||||
if(irq_register(irq, fn, arg, name, 0) == 0){
|
||||
int kind = PCI_IRQ_NONE;
|
||||
if(pci_msi_enable(d, T_IRQ0 + irq, dest) == 0)
|
||||
kind = PCI_IRQ_MSI;
|
||||
else if((flags & PCI_IRQF_MSIX) &&
|
||||
pci_msix_enable(d, T_IRQ0 + irq, dest) == 0)
|
||||
kind = PCI_IRQ_MSIX;
|
||||
if(kind != PCI_IRQ_NONE){
|
||||
d->irq_kind = kind;
|
||||
d->irq_line = irq;
|
||||
return irq;
|
||||
}
|
||||
irq_unregister(irq, fn, arg);
|
||||
}
|
||||
irq_free_msi(irq);
|
||||
}
|
||||
|
||||
if(d->irq == 0xFF || d->irq >= NIRQ_IOAPIC)
|
||||
return -1;
|
||||
if(irq_register(d->irq, fn, arg, name, 1) < 0)
|
||||
return -1;
|
||||
d->irq_kind = PCI_IRQ_INTX;
|
||||
d->irq_line = d->irq;
|
||||
return d->irq;
|
||||
}
|
||||
|
||||
void
|
||||
pci_irq_detach(struct pci_dev *d, irq_handler_t fn, void *arg)
|
||||
{
|
||||
switch(d->irq_kind){
|
||||
case PCI_IRQ_MSI:
|
||||
case PCI_IRQ_MSIX:
|
||||
// The device first, so nothing is written to a vector being taken away.
|
||||
pci_msi_off(d);
|
||||
irq_unregister(d->irq_line, fn, arg);
|
||||
irq_free_msi(d->irq_line);
|
||||
break;
|
||||
case PCI_IRQ_INTX:
|
||||
irq_unregister(d->irq_line, fn, arg);
|
||||
break;
|
||||
}
|
||||
d->irq_kind = PCI_IRQ_NONE;
|
||||
}
|
||||
|
||||
const char *
|
||||
pci_irq_kind(const struct pci_dev *d)
|
||||
{
|
||||
switch(d->irq_kind){
|
||||
case PCI_IRQ_MSI: return "msi";
|
||||
case PCI_IRQ_MSIX: return "msi-x";
|
||||
case PCI_IRQ_INTX: return "intx";
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Base address registers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue