irq: turn a PS/2 line over when its device waits and nothing comes
An ASUS TUF A15 booted with its internal keyboard dead and a USB one fine. These AMD laptops' firmware describes IRQ 1's polarity wrongly -- Linux keeps a quirk list -- and an edge line programmed the wrong way up never fires: the controller holds its byte, and only the interrupt that never comes would read it. irq_watch() lets a driver say how to ask its device whether something is waiting. The poller asks every tick; 50 ms of waiting with nothing delivered turns the line's polarity over and drains the device, which re-arms the edge, and if it still says nothing the line is polled. Both are announced. The keyboard and the PS/2 mouse are watched. ISA lines now also take their mode from the MADT's override entries, which were parsed and never read. Tested in QEMU, which ignores polarity on edge lines, with the keyboard line masked: the first key turns it over and typing works; masked again after the flip, it is polled and typing still works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
726091000e
commit
8c426b2077
4 changed files with 123 additions and 0 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
Loading…
Reference in a new issue