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
275 lines
No EOL
6.2 KiB
C
275 lines
No EOL
6.2 KiB
C
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "gfx/gfx.h"
|
|
#include "core/irq.h"
|
|
#include "arch/x86.h"
|
|
#include "arch/traps.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/fs.h"
|
|
#include "fs/file.h"
|
|
|
|
#define KBD_S_STAT 0x64
|
|
#define KBD_S_DATA 0x60
|
|
#define KBD_C_WRITE 0xD4
|
|
#define MOUSE_ACK 0xFA
|
|
|
|
struct {
|
|
struct spinlock lock;
|
|
int x;
|
|
int y;
|
|
uint8 buttons;
|
|
} mouse_state;
|
|
|
|
static void
|
|
mouse_wait(int type)
|
|
{
|
|
int timeout = 100000;
|
|
if(type == 0){
|
|
while(timeout-- && !(inb(KBD_S_STAT) & 0x01));
|
|
} else {
|
|
while(timeout-- && (inb(KBD_S_STAT) & 0x02));
|
|
}
|
|
}
|
|
|
|
static void
|
|
mouse_write(uchar data)
|
|
{
|
|
mouse_wait(1);
|
|
outb(KBD_S_STAT, KBD_C_WRITE);
|
|
mouse_wait(1);
|
|
outb(KBD_S_DATA, data);
|
|
}
|
|
|
|
static uchar
|
|
mouse_read(void)
|
|
{
|
|
mouse_wait(0);
|
|
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)
|
|
{
|
|
(void)arg;
|
|
mouseintr();
|
|
}
|
|
|
|
// Move the pointer. Caller holds mouse_state.lock.
|
|
//
|
|
// Positive dy is *down*, matching both the screen and USB HID. The PS/2
|
|
// packet reports the opposite, which is why its caller negates first --
|
|
// there is one pointer and two protocols disagreeing about which way is up,
|
|
// and the conversion belongs at the edge rather than in here.
|
|
static void
|
|
mouse_move_locked(int dx, int dy)
|
|
{
|
|
mouse_state.x += dx;
|
|
mouse_state.y += dy;
|
|
|
|
// Clamp to the screen that actually exists. These were the literals 800 and
|
|
// 600, which was correct while that was the only mode the loader could set.
|
|
// It is a boot-time choice now (press R), and at 1280x800 the pointer simply
|
|
// stopped two thirds of the way across -- which reads as the cursor drifting
|
|
// away from the host's, rather than as a clamp.
|
|
int maxx = (int)gfx_width();
|
|
int maxy = (int)gfx_height();
|
|
if(maxx <= 0) maxx = 800;
|
|
if(maxy <= 0) maxy = 600;
|
|
|
|
if(mouse_state.x < 0) mouse_state.x = 0;
|
|
if(mouse_state.y < 0) mouse_state.y = 0;
|
|
if(mouse_state.x >= maxx) mouse_state.x = maxx - 1;
|
|
if(mouse_state.y >= maxy) mouse_state.y = maxy - 1;
|
|
}
|
|
|
|
// The same pointer, moved by something that is not the PS/2 port. USB HID
|
|
// uses this; anything else that produces motion events should too.
|
|
void
|
|
mouse_inject(int dx, int dy, int buttons)
|
|
{
|
|
acquire(&mouse_state.lock);
|
|
mouse_state.buttons = buttons;
|
|
mouse_move_locked(dx, dy);
|
|
release(&mouse_state.lock);
|
|
|
|
input_mouse(dx, dy, buttons);
|
|
}
|
|
|
|
// Put the pointer somewhere, rather than nudging it.
|
|
//
|
|
// The clamp is the same as the relative path's, and for the same reason: the
|
|
// screen size is a boot-time choice now. The coordinates arrive already scaled
|
|
// to pixels -- the HID driver does that, because it is the only place that
|
|
// knows the device's logical range.
|
|
void
|
|
mouse_set_abs(int x, int y, int buttons)
|
|
{
|
|
int maxx = (int)gfx_width();
|
|
int maxy = (int)gfx_height();
|
|
if(maxx <= 0) maxx = 800;
|
|
if(maxy <= 0) maxy = 600;
|
|
|
|
if(x < 0) x = 0;
|
|
if(y < 0) y = 0;
|
|
if(x >= maxx) x = maxx - 1;
|
|
if(y >= maxy) y = maxy - 1;
|
|
|
|
acquire(&mouse_state.lock);
|
|
mouse_state.buttons = buttons;
|
|
mouse_state.x = x;
|
|
mouse_state.y = y;
|
|
release(&mouse_state.lock);
|
|
|
|
input_mouse_abs(x, y, buttons);
|
|
}
|
|
|
|
void
|
|
mouseintr(void)
|
|
{
|
|
static uchar buf[3];
|
|
static int phase = 0;
|
|
uchar status;
|
|
|
|
status = inb(KBD_S_STAT);
|
|
|
|
if(!(status & 0x01) || !(status & 0x20)){
|
|
return;
|
|
}
|
|
|
|
uchar data = inb(KBD_S_DATA);
|
|
|
|
acquire(&mouse_state.lock);
|
|
|
|
if(phase == 0 && !(data & 0x08)){
|
|
release(&mouse_state.lock);
|
|
return;
|
|
}
|
|
|
|
buf[phase++] = data;
|
|
|
|
if(phase == 3){
|
|
phase = 0;
|
|
|
|
mouse_state.buttons = buf[0] & 0x07;
|
|
int dx = (buf[0] & 0x10) ? (int)buf[1] - 256 : buf[1];
|
|
int dy = (buf[0] & 0x20) ? (int)buf[2] - 256 : buf[2];
|
|
|
|
// The overflow bits mean the delta saturated and is meaningless; moving
|
|
// by it would throw the pointer across the screen.
|
|
if(!(buf[0] & 0x40) && !(buf[0] & 0x80))
|
|
mouse_move_locked(dx, -dy);
|
|
else
|
|
dx = dy = 0;
|
|
|
|
release(&mouse_state.lock);
|
|
|
|
// Outside the lock: input_report takes its own, and holding two at once
|
|
// is how a lock ordering rule gets invented by accident.
|
|
input_mouse(dx, -dy, buf[0] & 0x07);
|
|
return;
|
|
}
|
|
|
|
release(&mouse_state.lock);
|
|
}
|
|
|
|
int get_mouse_x(void) {
|
|
int x;
|
|
acquire(&mouse_state.lock);
|
|
x = mouse_state.x;
|
|
release(&mouse_state.lock);
|
|
return x;
|
|
}
|
|
|
|
int get_mouse_y(void) {
|
|
int y;
|
|
acquire(&mouse_state.lock);
|
|
y = mouse_state.y;
|
|
release(&mouse_state.lock);
|
|
return y;
|
|
}
|
|
|
|
uint8 get_mouse_buttons(void) {
|
|
uint8 b;
|
|
acquire(&mouse_state.lock);
|
|
b = mouse_state.buttons;
|
|
release(&mouse_state.lock);
|
|
return b;
|
|
}
|
|
|
|
int
|
|
mouseread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
struct mouse_data m;
|
|
int size = sizeof(struct mouse_data);
|
|
|
|
if(n < size) {
|
|
return -1;
|
|
}
|
|
|
|
m.x = get_mouse_x();
|
|
m.y = get_mouse_y();
|
|
m.buttons = get_mouse_buttons();
|
|
|
|
memmove(dst, &m, size);
|
|
|
|
return size;
|
|
}
|
|
|
|
void
|
|
mouseinit(void)
|
|
{
|
|
initlock(&mouse_state.lock, "mouse");
|
|
mouse_state.x = 400;
|
|
mouse_state.y = 300;
|
|
|
|
// No 8042 at all: the port floats and reads 0xFF. That is a machine with
|
|
// USB input only -- most laptops and desktops built in the last few years,
|
|
// whenever the firmware's legacy emulation is off -- and every bit set means
|
|
// "output full" for ever, so the flush below never ended and boot stopped
|
|
// here. There is no mouse to set up, so do not.
|
|
if(inb(KBD_S_STAT) == 0xFF){
|
|
kinfo("ps2: no controller; mouse not set up\n");
|
|
return;
|
|
}
|
|
|
|
// Drain whatever the controller is holding. Bounded, because a controller
|
|
// that keeps producing bytes is broken and still not a reason to hang.
|
|
for(int i = 0; i < 1024 && (inb(KBD_S_STAT) & 0x01); i++)
|
|
inb(KBD_S_DATA);
|
|
|
|
mouse_wait(1);
|
|
outb(KBD_S_STAT, 0xA8);
|
|
|
|
mouse_wait(1);
|
|
outb(KBD_S_STAT, 0x20);
|
|
mouse_wait(0);
|
|
uchar cb = inb(KBD_S_DATA);
|
|
|
|
cb |= 0x02;
|
|
cb |= 0x01;
|
|
cb &= ~0x20;
|
|
|
|
mouse_wait(1);
|
|
outb(KBD_S_STAT, 0x60);
|
|
mouse_wait(1);
|
|
outb(KBD_S_DATA, cb);
|
|
|
|
mouse_write(0xF6);
|
|
mouse_read();
|
|
|
|
mouse_write(0xF4);
|
|
mouse_read();
|
|
|
|
irq_register(IRQ_MOUSE, mouse_irq, 0, "ps2-mouse", 0);
|
|
irq_watch(IRQ_MOUSE, mouse_waiting);
|
|
} |