diff --git a/include/core/irq.h b/include/core/irq.h index 46e504d..2e443db 100644 --- a/include/core/irq.h +++ b/include/core/irq.h @@ -61,6 +61,12 @@ typedef void (*irq_handler_t)(void *arg); int irq_register(int irq, irq_handler_t fn, void *arg, const char *name, int level_low); void irq_unregister(int irq, irq_handler_t fn, void *arg); + +// For a device that can say it has something waiting: `waiting` reads its +// status and returns nonzero when an interrupt should have come. A line whose +// device keeps saying so while nothing arrives has its polarity turned over, +// and is polled if that does not help either. See irq_poll_thread(). +void irq_watch(int irq, int (*waiting)(void)); int irq_handler_count(int irq); // Called from trap() with the IOAPIC pin number, not the vector. diff --git a/kernel/core/console.c b/kernel/core/console.c index afb635e..b5e56ed 100644 --- a/kernel/core/console.c +++ b/kernel/core/console.c @@ -807,6 +807,16 @@ consoleclose(struct inode *ip) // The keyboard handler lives in kbd.c, but the console is what owns the // keyboard as an input source, so this is where the line gets claimed. +// A keyboard byte in the controller, which IRQ 1 should have announced. 0xFF +// is no controller at all: every status bit set, the auxiliary one included, +// so it does not count. +static int +kbd_waiting(void) +{ + uchar st = inb(KBSTATP); + return st != 0xFF && (st & 0x21) == 0x01; +} + static void kbd_irq(void *arg) { @@ -831,4 +841,5 @@ consoleinit(void) cons.locking = 1; irq_register(IRQ_KBD, kbd_irq, 0, "ps2-kbd", 0); + irq_watch(IRQ_KBD, kbd_waiting); } diff --git a/kernel/dev/irq.c b/kernel/dev/irq.c index e993869..dcf93fe 100644 --- a/kernel/dev/irq.c +++ b/kernel/dev/irq.c @@ -7,6 +7,7 @@ #include "core/spinlock.h" #include "core/irq.h" #include "arch/x86.h" +#include "dev/acpi.h" struct irq_action { irq_handler_t fn; @@ -27,6 +28,10 @@ static struct { uint64 delivered; // real interrupts, not polls uint64 since; // ticks when the first handler was attached uint64 unhandled; + int (*waiting)(void); // see irq_watch() + uint64 seen; // `delivered` when the poller last looked + int stuck; // ticks the device has waited with nothing delivered + int flipped; // polarity already turned over once } line[NIRQ]; static int msi_used[NIRQ]; @@ -72,6 +77,18 @@ irq_register(int irq, irq_handler_t fn, void *arg, const char *name, level_low &= ~IRQ_QUIET; if(irq < 0 || irq >= NIRQ || fn == 0) return -1; + + // An ISA line, as the MADT describes it. The override entries were parsed + // and never read, so a line the firmware says is active low or + // level-triggered was programmed edge, active high, regardless. Only the + // mode: a line moved to another pin would need the pin numbering here to + // stop being the IRQ numbering, and no machine seen so far does that for + // anything but the timer. + if(irq < 16 && level_low == IRQ_EDGE_HIGH){ + int mode; + if(acpi_isa_gsi(irq, IRQ_EDGE_HIGH, &mode) == irq) + level_low = mode; + } if(reserved(irq)){ cprintf("irq_register: %s wants irq %d, which is reserved\n", name, irq); return -1; @@ -152,6 +169,8 @@ irq_unregister(int irq, irq_handler_t fn, void *arg) } else { line[irq].polled = 0; line[irq].delivered = 0; + line[irq].waiting = 0; + line[irq].stuck = line[irq].flipped = 0; } break; } @@ -341,6 +360,18 @@ irq_info(char *buf, int cap) return off; } +void +irq_watch(int irq, int (*waiting)(void)) +{ + if(irq < 0 || irq >= NIRQ_IOAPIC) + return; + acquire(&irqlock); + line[irq].seen = line[irq].delivered; + line[irq].stuck = 0; + line[irq].waiting = waiting; + release(&irqlock); +} + // How many handlers are attached to a line. // // Asked by kernel/dev/pciuser.c before it gives a line to a userspace driver: @@ -412,7 +443,58 @@ irq_free_msi(int irq) // // The moment a real interrupt arrives the line stops being polled, unless it // was put here for storming. +// +// An *edge* line cannot be judged by silence -- a keyboard nobody types on is +// silent too -- so a driver that can tell whether its device has something +// waiting says so with irq_watch(), and the question becomes: has it waited +// with nothing delivered? That is a line programmed the wrong way up. The +// device asserts it, the IOAPIC is looking for the opposite edge, and the +// byte sits in the controller for ever, because only the interrupt that never +// comes would read it. An ASUS TUF A15's keyboard did exactly that -- the +// internal keyboard dead, a USB one fine -- and Linux keeps a list of AMD +// laptops whose firmware describes IRQ 1 wrongly one way or the other. +// +// So the polarity is turned over and the device drained, which re-arms the +// edge; if the line still says nothing, it is polled. Either way it says so. #define POLL_AFTER (2 * HZ) +#define STUCK_TICKS 5 // 50 ms waiting with nothing delivered + +static void +watch_line(int irq) +{ + if(line[irq].polled || !line[irq].waiting) + return; + if(line[irq].delivered != line[irq].seen || !line[irq].waiting()){ + line[irq].seen = line[irq].delivered; + line[irq].stuck = 0; + return; + } + if(++line[irq].stuck < STUCK_TICKS) + return; + line[irq].stuck = 0; + + if(!line[irq].flipped){ + static const int other[4] = { + [IRQ_EDGE_HIGH] = IRQ_EDGE_LOW, [IRQ_EDGE_LOW] = IRQ_EDGE_HIGH, + [IRQ_LEVEL_HIGH] = IRQ_LEVEL_LOW, [IRQ_LEVEL_LOW] = IRQ_LEVEL_HIGH, + }; + int was = line[irq].level_low; + line[irq].flipped = 1; + line[irq].level_low = other[was & 3]; + ioapicroute(irq, 0, line[irq].level_low); + cprintf("irq %d: '%s' had data waiting and no interrupt came; the line is " + "active %s, not %s, whatever the firmware says\n", irq, + line[irq].act[0].name, + (line[irq].level_low == IRQ_EDGE_LOW || + line[irq].level_low == IRQ_LEVEL_LOW) ? "low" : "high", + (was == IRQ_EDGE_LOW || was == IRQ_LEVEL_LOW) ? "low" : "high"); + } else { + ioapicmask(irq); + line[irq].polled = 1; + cprintf("irq %d: '%s' still gets no interrupts either way up; polling " + "it\n", irq, line[irq].act[0].name); + } +} static void irq_poll_thread(void) @@ -433,6 +515,20 @@ irq_poll_thread(void) // Silent by design, and only its own handlers on it. if(line[irq].nquiet == line[irq].n && !line[irq].polled) continue; + + int flipped = line[irq].flipped; + watch_line(irq); + if(line[irq].flipped != flipped){ + // Drain what was waiting: the new polarity sees the line already + // asserted, and an edge needs it to go back first. + pushcli(); + int n = line[irq].n; + for(int i = 0; i < n; i++) + line[irq].act[i].fn(line[irq].act[i].arg); + popcli(); + continue; + } + int poll = line[irq].polled || (IRQ_IS_LEVEL(line[irq].level_low) && line[irq].delivered == 0 && now - line[irq].since > POLL_AFTER); diff --git a/kernel/dev/mouse.c b/kernel/dev/mouse.c index aa3ee2d..77f2e30 100644 --- a/kernel/dev/mouse.c +++ b/kernel/dev/mouse.c @@ -48,6 +48,15 @@ mouse_read(void) return inb(KBD_S_DATA); } +// A byte from the auxiliary port, which IRQ 12 should have announced; see +// kbd_waiting() in console.c. +static int +mouse_waiting(void) +{ + uchar st = inb(KBD_S_STAT); + return st != 0xFF && (st & 0x21) == 0x21; +} + static void mouse_irq(void *arg) { @@ -262,4 +271,5 @@ mouseinit(void) mouse_read(); irq_register(IRQ_MOUSE, mouse_irq, 0, "ps2-mouse", 0); + irq_watch(IRQ_MOUSE, mouse_waiting); } \ No newline at end of file