SchOS/kernel/dev/mouse.c
Nexoniarz 94c936ed98 boot: stop assuming the machine is an emulator
Every one of these works under QEMU and stops a real machine:

- MADT processors that are "online capable" but not enabled were
  counted as CPUs. AMD firmware lists absent cores that way (so does
  QEMU with maxcpus), and startothers() then waited for ever for an
  AP that does not exist. Only enabled entries count now, x2APIC
  (type 9) entries are read too, and the wait is bounded to a second;
  an AP that misses it is parked with INIT and the boot goes on.
- The PIT calibration loop had no bound. Recent boards clock-gate the
  8254: the output never rises (a hang) or floats high (TSC "0 MHz"
  and every clock thousands of times fast). The loop is bounded and
  checked, and falls back to the ACPI PM timer, then CPUID 0x15/0x16.
  Under QEMU pit=off the PM timer gives the same figures the PIT did.
- Firmware that hands over in x2APIC mode left lapic[] reading
  garbage: cpunum() panicked or the timer never started. Each CPU is
  switched back to xAPIC first (x2APIC -> disabled -> xAPIC).
- The IOAPIC was always at 0xFEC00000 and interrupts were aimed at
  APIC ID "0". Both now come from the firmware's tables.
- INIT-to-STARTUP was 100 us ("Bochs is slow"); it is the 10 ms the
  MP specification asks for.
- mouseinit() drained the 8042 with an unbounded loop. With no
  controller the port reads 0xFF and the loop never ends. It now
  notices there is no controller and skips the mouse.

Tested in QEMU: i440fx and q35, -smp 4, -smp 2,maxcpus=4, pit=off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HH7xukt4aaiGj1nQEAoQf1
2026-10-22 10:29:12 +02:00

265 lines
No EOL
5.9 KiB
C

#include "types.h"
#include "defs.h"
#include "gfx.h"
#include "irq.h"
#include "x86.h"
#include "traps.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "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);
}
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);
}