Groundwork for usb-tablet, which is the fix for the guest cursor and the
host's never being in the same place: a PS/2 mouse reports how far it
moved, two pointers accumulating deltas independently drift apart, and no
amount of care in the compositor can make them agree. An absolute device
reports where it is, so they coincide by construction.
- the HID driver no longer matches the boot subclass only. usb-tablet
declares subclass 0 and was refused at the door; hid_probe decides
what an interface actually is, and still declines what it cannot make
sense of.
- a non-boot HID interface is taken to be an absolute pointer and is
not asked for boot protocol, which it does not have -- that request
failing is what stopped it binding.
- tablet_report decodes the fixed 6-byte layout (buttons, 16-bit X,
16-bit Y, wheel) and scales the logical range to pixels there,
because it is the only place that knows the range.
- input_mouse_abs reports EV_ABS/ABS_X/ABS_Y so a reader can tell an
absolute position from a delta, and mouse_set_abs places the pointer
rather than nudging it.
- Schcom treats EV_ABS as authoritative and EV_REL as a nudge.
The device binds and identifies correctly: "usb-hid: tablet (absolute) on
slot 1, endpoint 0x81 every 4 ms".
It is NOT finished, and QEMU_INPUT is empty by default because of it. No
reports have yet been observed arriving on the tablet's interrupt
endpoint, so making it the default pointer would trade a cursor that
drifts for one that does not move at all. Opt in with
make qemu QEMU_INPUT='-device qemu-xhci,id=xhci -device usb-tablet,bus=xhci.0'
Next step is to find out why nothing arrives: whether the first transfer
is queued but never completes, whether a completion needs re-arming for
this endpoint, or whether QEMU is not routing motion to the tablet at all
(`info mice`).
bootcheck 108 [ok], schcom-test passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
252 lines
No EOL
5.3 KiB
C
252 lines
No EOL
5.3 KiB
C
#include "types.h"
|
|
#include "defs.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)vga_get_width();
|
|
int maxy = (int)vga_get_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)vga_get_width();
|
|
int maxy = (int)vga_get_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, uint 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;
|
|
|
|
while(inb(KBD_S_STAT) & 0x01)
|
|
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);
|
|
} |