- schinit, schinitd and vgpud build every message from pieces, one
write() each, so a job starting on another CPU landed mid-line:
"schinitd: starting vgpud: os.schos.resolvconfno virtio-gpu on this
machine". They now collect a line and write it at the newline;
resolvconf's verbose line is one write with its prefix.
- The ACPI SCI registers IRQ_QUIET. A desktop's SCI never fires, and the
poller took two seconds of silence for a misrouted line ("irq 9:
nothing has arrived on it") and called the handler every tick. A line
is only exempt while every handler on it is quiet.
- The loader claims each segment from where the last one's claim ended,
so two segments sharing a page no longer ask for it twice. When a
claim is still refused, it names the memory-map entries in the way.
- Stale divesymlink() declaration gone; the kernel.md struct table
re-measured (inode, buf and file pools grow from slabs now).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
462 lines
15 KiB
C
462 lines
15 KiB
C
// Hardware interrupt registration. See include/core/irq.h for why this exists.
|
|
|
|
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "arch/traps.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/irq.h"
|
|
#include "arch/x86.h"
|
|
|
|
struct irq_action {
|
|
irq_handler_t fn;
|
|
void *arg;
|
|
const char *name;
|
|
uint64 count;
|
|
int quiet; // registered IRQ_QUIET
|
|
};
|
|
|
|
static struct {
|
|
struct irq_action act[NCHAIN];
|
|
int n; // handlers installed; only ever grows during boot
|
|
int enabled;
|
|
int level_low;
|
|
int msi; // an MSI vector, not an IOAPIC pin: nothing to route
|
|
int polled; // handlers run from irq_poll_thread as well
|
|
int nquiet; // handlers registered IRQ_QUIET
|
|
uint64 delivered; // real interrupts, not polls
|
|
uint64 since; // ticks when the first handler was attached
|
|
uint64 unhandled;
|
|
} line[NIRQ];
|
|
|
|
static int msi_used[NIRQ];
|
|
|
|
// Guards registration against itself. The *dispatch* path deliberately does
|
|
// not take it: an interrupt handler that blocked on a lock held by whoever
|
|
// was registering would deadlock the CPU it fired on. Instead registration
|
|
// publishes an entry by writing all of its fields, issuing a barrier, and
|
|
// only then incrementing n -- so a concurrent dispatcher either does not see
|
|
// the entry at all or sees it complete.
|
|
//
|
|
// Removal is the harder direction, because compacting the array *does* move
|
|
// entries a dispatcher may be walking. irq_unregister therefore masks the
|
|
// line first and only re-routes it once the chain has been rewritten: with
|
|
// the line masked no new interrupt can arrive, and any handler already
|
|
// running holds no reference past its own return.
|
|
static struct spinlock irqlock;
|
|
|
|
void
|
|
irq_init(void)
|
|
{
|
|
initlock(&irqlock, "irq");
|
|
}
|
|
|
|
// IRQ_ERROR and IRQ_SPURIOUS are not IOAPIC pins -- they are local APIC
|
|
// vectors that xv6 numbered as though they were. The error vector used to be
|
|
// 19, which *is* a pin: one of the four an Intel chipset wires its PCI
|
|
// interrupts to, so a device there would have been refused. It is 30 now,
|
|
// above every pin; see include/arch/traps.h.
|
|
static int
|
|
reserved(int irq)
|
|
{
|
|
return irq == IRQ_ERROR || irq == IRQ_SPURIOUS || irq == IRQ_TIMER;
|
|
}
|
|
|
|
int
|
|
irq_register(int irq, irq_handler_t fn, void *arg, const char *name,
|
|
int level_low)
|
|
{
|
|
struct irq_action *a;
|
|
int quiet = (level_low & IRQ_QUIET) != 0;
|
|
|
|
level_low &= ~IRQ_QUIET;
|
|
if(irq < 0 || irq >= NIRQ || fn == 0)
|
|
return -1;
|
|
if(reserved(irq)){
|
|
cprintf("irq_register: %s wants irq %d, which is reserved\n", name, irq);
|
|
return -1;
|
|
}
|
|
|
|
acquire(&irqlock);
|
|
|
|
if(line[irq].n >= NCHAIN){
|
|
release(&irqlock);
|
|
cprintf("irq_register: irq %d chain full, %s not attached\n", irq, name);
|
|
return -1;
|
|
}
|
|
|
|
// Two devices sharing a line must agree on how it is signalled; they
|
|
// physically cannot be wired both ways.
|
|
if(line[irq].n > 0 && line[irq].level_low != level_low){
|
|
release(&irqlock);
|
|
cprintf("irq_register: irq %d shared with conflicting trigger mode "
|
|
"(%s)\n", irq, name);
|
|
return -1;
|
|
}
|
|
|
|
a = &line[irq].act[line[irq].n];
|
|
a->fn = fn;
|
|
a->arg = arg;
|
|
a->name = name;
|
|
a->count = 0;
|
|
a->quiet = quiet;
|
|
__sync_synchronize(); // publish the entry before it is reachable
|
|
line[irq].n++;
|
|
line[irq].nquiet += quiet;
|
|
if(line[irq].n == 1)
|
|
line[irq].since = ticks;
|
|
|
|
if(!line[irq].enabled){
|
|
line[irq].level_low = level_low;
|
|
if(!line[irq].msi)
|
|
ioapicroute(irq, 0, level_low);
|
|
line[irq].enabled = 1;
|
|
}
|
|
|
|
release(&irqlock);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
irq_unregister(int irq, irq_handler_t fn, void *arg)
|
|
{
|
|
int i, j;
|
|
|
|
if(irq < 0 || irq >= NIRQ)
|
|
return;
|
|
|
|
acquire(&irqlock);
|
|
for(i = 0; i < line[irq].n; i++){
|
|
if(line[irq].act[i].fn == fn && line[irq].act[i].arg == arg){
|
|
// Mask before touching the chain, always -- not only when this is the
|
|
// last handler. Compacting the array moves entries that a dispatcher on
|
|
// another CPU may be walking, so the line has to be quiet for the
|
|
// duration, and then brought back only if anything is left on it.
|
|
//
|
|
// An MSI vector has no pin to mask. Its caller, pci_irq_detach(), has
|
|
// already switched the device's MSI off, which is the same thing.
|
|
if(!line[irq].msi)
|
|
ioapicmask(irq);
|
|
line[irq].enabled = 0;
|
|
|
|
line[irq].nquiet -= line[irq].act[i].quiet;
|
|
for(j = i; j + 1 < line[irq].n; j++)
|
|
line[irq].act[j] = line[irq].act[j+1];
|
|
line[irq].n--;
|
|
__sync_synchronize();
|
|
|
|
if(line[irq].n > 0){
|
|
if(!line[irq].msi && !line[irq].polled)
|
|
ioapicroute(irq, 0, line[irq].level_low);
|
|
line[irq].enabled = 1;
|
|
} else {
|
|
line[irq].polled = 0;
|
|
line[irq].delivered = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
release(&irqlock);
|
|
}
|
|
|
|
// Notice a line that is firing far faster than any real device would, and say
|
|
// so once.
|
|
//
|
|
// A level-triggered PCI interrupt that nobody acknowledges re-fires the instant
|
|
// EOI is signalled, so the line runs at whatever rate the CPU can take traps --
|
|
// tens of thousands per second. That starves everything else on the machine,
|
|
// and it does so silently: the symptom appears wherever the missing CPU time
|
|
// hurts first, which is nowhere near the device at fault.
|
|
//
|
|
// It cost a long afternoon to find one of these. virtio-gpu shared IRQ 11 with
|
|
// the e1000 and registered no handler, so the line storm at ~72 kHz and took
|
|
// 83% of CPU0. What that looked like from userspace was a clock running seven
|
|
// times slow -- uptime, `time`, and ping's interval all wrong together, which
|
|
// reads as a timekeeping bug and sends you into the timer code. The timer was
|
|
// fine the whole time. This is the message that would have said so.
|
|
//
|
|
// The rate is measured against the TSC, deliberately: `ticks` is the first
|
|
// casualty of a storm, so a storm detector that counted ticks would go quiet
|
|
// exactly when it was needed.
|
|
#define STORM_SAMPLE 4096 // dispatches between rate checks
|
|
#define STORM_HZ 20000 // per second; no real device sustains this
|
|
|
|
static void
|
|
storm_check(int irq)
|
|
{
|
|
static uint64 sample_tsc[NIRQ];
|
|
static uint sample_n[NIRQ];
|
|
static uchar reported[NIRQ];
|
|
|
|
if(reported[irq] || !tsc_hz)
|
|
return;
|
|
if(++sample_n[irq] < STORM_SAMPLE)
|
|
return;
|
|
|
|
uint64 now = rdtsc(), prev = sample_tsc[irq];
|
|
sample_n[irq] = 0;
|
|
sample_tsc[irq] = now;
|
|
if(!prev)
|
|
return; // first sample: no interval to divide by
|
|
|
|
uint64 elapsed = now - prev;
|
|
if(elapsed == 0)
|
|
return;
|
|
uint64 rate = (uint64)STORM_SAMPLE * tsc_hz / elapsed;
|
|
if(rate < STORM_HZ)
|
|
return;
|
|
|
|
reported[irq] = 1;
|
|
cprintf("irq %d: %d interrupts/second -- a device on this line is not "
|
|
"acknowledging it.\n", irq, (int)rate);
|
|
for(int i = 0; i < line[irq].n; i++)
|
|
cprintf("irq %d: handler '%s' is attached\n", irq, line[irq].act[i].name);
|
|
if(line[irq].n == 0)
|
|
cprintf("irq %d: no handler is attached at all\n", irq);
|
|
|
|
// And stop it. Reporting a storm and carrying on is a machine that spends
|
|
// every cycle in this handler -- which is what a PCI line configured on the
|
|
// wrong pin does on real hardware, where an idle ISA input read active-low
|
|
// is asserted for ever. The handlers move to the poller, so a device that
|
|
// was really on this line still works, ten milliseconds at a time.
|
|
if(irq < NIRQ_IOAPIC && !line[irq].msi){
|
|
ioapicmask(irq);
|
|
line[irq].polled = 1;
|
|
cprintf("irq %d: masked; its handlers are polled from now on\n", irq);
|
|
}
|
|
}
|
|
|
|
void
|
|
irq_dispatch(int irq)
|
|
{
|
|
int i, n;
|
|
|
|
if(irq < 0 || irq >= NIRQ)
|
|
return;
|
|
|
|
n = line[irq].n;
|
|
if(n == 0){
|
|
line[irq].unhandled++;
|
|
storm_check(irq);
|
|
return;
|
|
}
|
|
|
|
line[irq].delivered++;
|
|
|
|
// Every handler on a shared line is offered the interrupt; each one is
|
|
// responsible for recognising that its device is idle and returning.
|
|
for(i = 0; i < n; i++){
|
|
line[irq].act[i].count++;
|
|
line[irq].act[i].fn(line[irq].act[i].arg);
|
|
}
|
|
storm_check(irq);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// /proc/interrupts
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static int
|
|
emit(char *buf, int cap, int off, const char *s)
|
|
{
|
|
while(*s && off < cap)
|
|
buf[off++] = *s++;
|
|
return off;
|
|
}
|
|
|
|
static int
|
|
emit_dec(char *buf, int cap, int off, uint64 v, int width)
|
|
{
|
|
char tmp[24];
|
|
int n = 0, i;
|
|
|
|
if(v == 0)
|
|
tmp[n++] = '0';
|
|
while(v > 0){
|
|
tmp[n++] = '0' + (v % 10);
|
|
v /= 10;
|
|
}
|
|
for(i = n; i < width && off < cap; i++)
|
|
buf[off++] = ' ';
|
|
while(n > 0 && off < cap)
|
|
buf[off++] = tmp[--n];
|
|
return off;
|
|
}
|
|
|
|
int
|
|
irq_info(char *buf, int cap)
|
|
{
|
|
int irq, i, off = 0;
|
|
|
|
for(irq = 0; irq < NIRQ; irq++){
|
|
if(line[irq].n == 0 && line[irq].unhandled == 0)
|
|
continue;
|
|
off = emit_dec(buf, cap, off, irq, 4);
|
|
off = emit(buf, cap, off, ": ");
|
|
for(i = 0; i < line[irq].n; i++){
|
|
off = emit_dec(buf, cap, off, line[irq].act[i].count, 10);
|
|
off = emit(buf, cap, off, " ");
|
|
off = emit(buf, cap, off,
|
|
line[irq].act[i].name ? line[irq].act[i].name : "?");
|
|
off = emit(buf, cap, off, i + 1 < line[irq].n ? ", " : "");
|
|
}
|
|
if(line[irq].n == 0)
|
|
off = emit(buf, cap, off, "(unhandled)");
|
|
if(line[irq].unhandled)
|
|
off = emit(buf, cap, off, " [spurious]");
|
|
if(line[irq].polled)
|
|
off = emit(buf, cap, off, " [polled]");
|
|
off = emit(buf, cap, off, line[irq].msi ? " msi\n" :
|
|
IRQ_IS_LEVEL(line[irq].level_low) ? " level\n" : " edge\n");
|
|
}
|
|
|
|
// How many timer interrupts were lost.
|
|
//
|
|
// The clock does not depend on this any more -- kernel/core/time.c reads the
|
|
// TSC -- but that makes lost ticks *invisible* rather than harmless, and
|
|
// they are not harmless: the scheduler preempts on them, and a machine
|
|
// dropping them is a machine spending too long with interrupts disabled.
|
|
//
|
|
// Twice now that has been diagnosed from the outside, by noticing a clock
|
|
// running slow, which is a terrible way to find out. This is the number to
|
|
// look at instead: expected is elapsed time times HZ, and the gap is how
|
|
// many the machine did not take.
|
|
{
|
|
uint64 sec, usec;
|
|
monotonic(&sec, &usec);
|
|
uint64 expect = sec * HZ + (usec * HZ) / 1000000;
|
|
uint64 got = ticks;
|
|
off = emit(buf, cap, off, "\ntimer: ");
|
|
off = emit_dec(buf, cap, off, got, 0);
|
|
off = emit(buf, cap, off, " ticks taken, ");
|
|
off = emit_dec(buf, cap, off, expect, 0);
|
|
off = emit(buf, cap, off, " expected");
|
|
if (expect > got + HZ) {
|
|
off = emit(buf, cap, off, " -- LOSING ");
|
|
off = emit_dec(buf, cap, off, expect - got, 0);
|
|
off = emit(buf, cap, off, ", interrupts are off too long somewhere");
|
|
}
|
|
off = emit(buf, cap, off, "\n");
|
|
}
|
|
return off;
|
|
}
|
|
|
|
// How many handlers are attached to a line.
|
|
//
|
|
// Asked by kernel/dev/pciuser.c before it gives a line to a userspace driver:
|
|
// that driver masks the line while it works, and a masked line is masked for
|
|
// everyone on it. Handing out a shared line would silence whichever kernel
|
|
// driver is also there, with no message and no obvious cause.
|
|
int
|
|
irq_handler_count(int irq)
|
|
{
|
|
if (irq < 0 || irq >= NIRQ)
|
|
return 0;
|
|
return line[irq].n;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// MSI vectors
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// A line that is an MSI vector rather than a pin. They are handed out from
|
|
// IRQ_MSI_FIRST..IRQ_MSI_LAST, which are vectors the IOAPIC never uses; see
|
|
// pci_irq_attach() in kernel/dev/pci.c, which is the only caller.
|
|
int
|
|
irq_alloc_msi(void)
|
|
{
|
|
acquire(&irqlock);
|
|
for(int irq = IRQ_MSI_FIRST; irq <= IRQ_MSI_LAST; irq++){
|
|
if(!msi_used[irq] && !reserved(irq)){
|
|
msi_used[irq] = 1;
|
|
line[irq].msi = 1;
|
|
release(&irqlock);
|
|
return irq;
|
|
}
|
|
}
|
|
release(&irqlock);
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
irq_free_msi(int irq)
|
|
{
|
|
if(irq < IRQ_MSI_FIRST || irq > IRQ_MSI_LAST)
|
|
return;
|
|
acquire(&irqlock);
|
|
if(line[irq].n == 0){
|
|
msi_used[irq] = 0;
|
|
line[irq].msi = 0;
|
|
}
|
|
release(&irqlock);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Polling the lines that do not interrupt
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// A PCI driver that cannot have MSI learns its line from the device's
|
|
// INTERRUPT_LINE register, and on real hardware that is the wrong answer.
|
|
// The register holds the number firmware gave the *8259* -- 10, 11 -- and in
|
|
// APIC mode the device is wired to an IOAPIC input the firmware only
|
|
// describes in ACPI's _PRT, which takes an AML interpreter to read. So the
|
|
// pin gets unmasked, the device's real pin stays masked, and nothing arrives.
|
|
// Under QEMU the two numbers happen to agree, which is why it never showed.
|
|
//
|
|
// Until there is an AML interpreter, a level-triggered line that has had a
|
|
// handler for two seconds and has not once fired is presumed to be that,
|
|
// and its handlers are called from here every tick. Every handler is already
|
|
// written to be called when its device has nothing to say -- a shared line
|
|
// needs that -- so being called spuriously costs a register read. A storm
|
|
// does the same from the other side; see storm_check().
|
|
//
|
|
// The moment a real interrupt arrives the line stops being polled, unless it
|
|
// was put here for storming.
|
|
#define POLL_AFTER (2 * HZ)
|
|
|
|
static void
|
|
irq_poll_thread(void)
|
|
{
|
|
static uchar said[NIRQ];
|
|
|
|
for(;;){
|
|
acquire(&tickslock);
|
|
uint start = ticks;
|
|
while(ticks == start)
|
|
sleep(&ticks, &tickslock);
|
|
uint now = ticks;
|
|
release(&tickslock);
|
|
|
|
for(int irq = 0; irq < NIRQ_IOAPIC; irq++){
|
|
if(line[irq].n == 0 || line[irq].msi || reserved(irq))
|
|
continue;
|
|
// Silent by design, and only its own handlers on it.
|
|
if(line[irq].nquiet == line[irq].n && !line[irq].polled)
|
|
continue;
|
|
int poll = line[irq].polled ||
|
|
(IRQ_IS_LEVEL(line[irq].level_low) && line[irq].delivered == 0 &&
|
|
now - line[irq].since > POLL_AFTER);
|
|
if(!poll)
|
|
continue;
|
|
if(!said[irq] && !line[irq].polled){
|
|
said[irq] = 1;
|
|
kinfo("irq %d: nothing has arrived on it; polling its handlers\n",
|
|
irq);
|
|
}
|
|
// As an interrupt would: on this CPU, with interrupts off. n is read
|
|
// once, as irq_dispatch() reads it.
|
|
pushcli();
|
|
int n = line[irq].n;
|
|
for(int i = 0; i < n; i++)
|
|
line[irq].act[i].fn(line[irq].act[i].arg);
|
|
popcli();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
irq_poll_start(void)
|
|
{
|
|
if(kthread_create(irq_poll_thread, "irqpoll") < 0)
|
|
cprintf("irq: cannot start the poller; misrouted PCI lines stay silent\n");
|
|
}
|