292 lines
6.5 KiB
C
292 lines
6.5 KiB
C
/* vim: tabstop=4 shiftwidth=4 noexpandtab
|
|
* This file is part of ToaruOS and is released under the terms
|
|
* of the NCSA / University of Illinois License - see LICENSE.md
|
|
* Copyright (C) 2017-2018 K. Lange
|
|
*
|
|
* VMWare absolute mouse driver.
|
|
*
|
|
* This device is also available by default in QEMU.
|
|
*
|
|
* Toggle off / back on with ioctl 1 and 2 respectively to /dev/vmmouse.
|
|
*
|
|
* Actually supports mouse buttons, unlike the one in VirtualBox.
|
|
*/
|
|
|
|
#include <kernel/system.h>
|
|
#include <kernel/fs.h>
|
|
#include <kernel/printf.h>
|
|
#include <kernel/types.h>
|
|
#include <kernel/logging.h>
|
|
#include <kernel/module.h>
|
|
#include <kernel/video.h>
|
|
#include <kernel/pipe.h>
|
|
#include <kernel/mouse.h>
|
|
#include <kernel/args.h>
|
|
|
|
#define VMWARE_MAGIC 0x564D5868
|
|
#define VMWARE_PORT 0x5658
|
|
|
|
#define PACKETS_IN_PIPE 1024
|
|
#define DISCARD_POINT 32
|
|
|
|
/* -Wpedantic complains about unnamed unions */
|
|
#pragma GCC diagnostic ignored "-Wpedantic"
|
|
|
|
extern void (*ps2_mouse_alternate)(void); /* modules/mouse.c */
|
|
|
|
static fs_node_t * mouse_pipe;
|
|
|
|
typedef struct {
|
|
union {
|
|
uint32_t ax;
|
|
uint32_t magic;
|
|
};
|
|
union {
|
|
uint32_t bx;
|
|
size_t size;
|
|
};
|
|
union {
|
|
uint32_t cx;
|
|
uint16_t command;
|
|
};
|
|
union {
|
|
uint32_t dx;
|
|
uint16_t port;
|
|
};
|
|
uint32_t si;
|
|
uint32_t di;
|
|
} vmware_cmd;
|
|
|
|
static void vmware_io(vmware_cmd * cmd) {
|
|
uint32_t dummy;
|
|
|
|
/* Now how's THAT for a VM backdoor... */
|
|
|
|
asm volatile(
|
|
"pushl %%ebx\n"
|
|
"pushl %%eax\n"
|
|
"movl 20(%%eax), %%edi\n" /* Load data into registers */
|
|
"movl 16(%%eax), %%esi\n"
|
|
"movl 12(%%eax), %%edx\n"
|
|
"movl 8(%%eax), %%ecx\n"
|
|
"movl 4(%%eax), %%ebx\n"
|
|
"movl (%%eax), %%eax\n"
|
|
"inl %%dx, %%eax\n" /* Then trip a magic i/o port */
|
|
"xchgl %%eax, (%%esp)\n"
|
|
"movl %%edi, 20(%%eax)\n" /* Data also comes back out by registers */
|
|
"movl %%esi, 16(%%eax)\n"
|
|
"movl %%edx, 12(%%eax)\n"
|
|
"movl %%ecx, 8(%%eax)\n"
|
|
"movl %%ebx, 4(%%eax)\n"
|
|
"popl (%%eax)\n"
|
|
"popl %%ebx\n"
|
|
: "=a"(dummy)
|
|
: "0"(cmd)
|
|
: "ecx", "edx", "esi", "edi", "memory" /* And vmware / qemu could trash anything they desire... */
|
|
);
|
|
}
|
|
|
|
static void vmware_send(vmware_cmd * cmd) {
|
|
cmd->magic = VMWARE_MAGIC;
|
|
cmd->port = VMWARE_PORT;
|
|
|
|
vmware_io(cmd);
|
|
}
|
|
|
|
static void mouse_on(void) {
|
|
vmware_cmd cmd;
|
|
|
|
/* Enable */
|
|
cmd.bx = 0x45414552;
|
|
cmd.command = 41;
|
|
vmware_send(&cmd);
|
|
|
|
/* Status */
|
|
cmd.bx = 0;
|
|
cmd.command = 40;
|
|
vmware_send(&cmd);
|
|
|
|
/* Read data (1) */
|
|
cmd.bx = 1;
|
|
cmd.command = 39;
|
|
vmware_send(&cmd);
|
|
|
|
debug_print(WARNING, "Enabled with version ID %x", cmd.ax);
|
|
}
|
|
|
|
static void mouse_off(void) {
|
|
/* Disable the absolute mouse */
|
|
vmware_cmd cmd;
|
|
cmd.bx = 0xf5;
|
|
cmd.command = 41;
|
|
vmware_send(&cmd);
|
|
}
|
|
|
|
static void mouse_absolute(void) {
|
|
/*
|
|
* Set the mouse to absolute.
|
|
*
|
|
* You can also set a relative mode, but there's not
|
|
* a lot of use in that as disabling the device just
|
|
* falls back to the PS/2 (or USB, I guess) device anyway,
|
|
* so instead of using that we just... turn it off.
|
|
*/
|
|
|
|
vmware_cmd cmd;
|
|
cmd.bx = 0x53424152; /* request absolute */
|
|
cmd.command = 41; /* request for abs/rel */
|
|
vmware_send(&cmd);
|
|
}
|
|
|
|
volatile int8_t vmware_mouse_byte;
|
|
|
|
static void vmware_mouse(void) {
|
|
/* unused, but we need to read the fake mouse event bytes from the PS/2 device. */
|
|
vmware_mouse_byte = inportb(0x60);
|
|
|
|
/* Read status byte. */
|
|
vmware_cmd cmd;
|
|
cmd.bx = 0;
|
|
cmd.command = 40;
|
|
vmware_send(&cmd);
|
|
|
|
if (cmd.ax == 0xffff0000) {
|
|
/* Device error; turn it off and back on again. */
|
|
mouse_off();
|
|
mouse_on();
|
|
mouse_absolute();
|
|
return;
|
|
}
|
|
|
|
int words = cmd.ax & 0xFFFF;
|
|
|
|
if (!words || words % 4) {
|
|
/* If we don't have data, or for some reason data isn't a multiple of 4... bail */
|
|
return;
|
|
}
|
|
|
|
/* Read 4 bytes of data */
|
|
cmd.bx = 4; /* how many */
|
|
cmd.command = 39; /* read */
|
|
vmware_send(&cmd);
|
|
|
|
/*
|
|
* I guess the flags tell you if this was relative or absolute, so if we
|
|
* actually used the relative mode, we'd want to check that, but...
|
|
*/
|
|
int flags = (cmd.ax & 0xFFFF0000) >> 16;
|
|
int buttons = (cmd.ax & 0x0000FFFF);
|
|
|
|
debug_print(INFO, "flags=%4x buttons=%4x", flags, buttons);
|
|
debug_print(INFO, "x=%x y=%x z=%x", cmd.bx, cmd.cx, cmd.dx);
|
|
|
|
if (lfb_vid_memory && lfb_resolution_x && lfb_resolution_y) {
|
|
/*
|
|
* Just like the virtualbox stuff, this is based on a mapping
|
|
* to the display resolution, independently scaled in
|
|
* each dimension...
|
|
*/
|
|
unsigned int x = ((unsigned int)cmd.bx * lfb_resolution_x) / 0xFFFF;
|
|
unsigned int y = ((unsigned int)cmd.cx * lfb_resolution_y) / 0xFFFF;
|
|
|
|
mouse_device_packet_t packet;
|
|
packet.magic = MOUSE_MAGIC;
|
|
packet.x_difference = x;
|
|
packet.y_difference = y;
|
|
packet.buttons = 0;
|
|
|
|
/* The particular bits for the buttons seem weird, but okay... */
|
|
if (buttons & 0x20) {
|
|
packet.buttons |= LEFT_CLICK;
|
|
}
|
|
if (buttons & 0x10) {
|
|
packet.buttons |= RIGHT_CLICK;
|
|
}
|
|
if (buttons & 0x08) {
|
|
packet.buttons |= MIDDLE_CLICK;
|
|
}
|
|
|
|
/* dx = z = scroll amount */
|
|
if ((int8_t)cmd.dx > 0) {
|
|
packet.buttons |= MOUSE_SCROLL_DOWN;
|
|
} else if ((int8_t)cmd.dx < 0) {
|
|
packet.buttons |= MOUSE_SCROLL_UP;
|
|
}
|
|
|
|
mouse_device_packet_t bitbucket;
|
|
while (pipe_size(mouse_pipe) > (int)(DISCARD_POINT * sizeof(packet))) {
|
|
read_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&bitbucket);
|
|
}
|
|
write_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&packet);
|
|
}
|
|
|
|
}
|
|
|
|
static int detect_device(void) {
|
|
vmware_cmd cmd;
|
|
|
|
/* read version */
|
|
cmd.bx = ~VMWARE_MAGIC;
|
|
cmd.command = 10;
|
|
vmware_send(&cmd);
|
|
|
|
if (cmd.bx != VMWARE_MAGIC || cmd.ax == 0xFFFFFFFF) {
|
|
/* Not a vmware device... */
|
|
return 0;
|
|
}
|
|
|
|
/* Good to go! */
|
|
return 1;
|
|
}
|
|
|
|
static int ioctl_mouse(fs_node_t * node, int request, void * argp) {
|
|
if (request == 1) {
|
|
/* Disable */
|
|
mouse_off();
|
|
ps2_mouse_alternate = NULL;
|
|
return 0;
|
|
}
|
|
if (request == 2) {
|
|
/* Enable */
|
|
ps2_mouse_alternate = vmware_mouse;
|
|
mouse_on();
|
|
mouse_absolute();
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int init(void) {
|
|
if (detect_device()) {
|
|
|
|
mouse_pipe = make_pipe(sizeof(mouse_device_packet_t) * PACKETS_IN_PIPE);
|
|
mouse_pipe->flags = FS_CHARDEVICE;
|
|
|
|
vfs_mount("/dev/vmmouse", mouse_pipe);
|
|
|
|
mouse_pipe->flags = FS_CHARDEVICE;
|
|
mouse_pipe->ioctl = ioctl_mouse;
|
|
|
|
/*
|
|
* We have a hack in the PS/2 mouse driver that lets us
|
|
* take over for the normal mouse driver and essential
|
|
* intercept the interrputs when they are valid.
|
|
*/
|
|
ps2_mouse_alternate = vmware_mouse;
|
|
|
|
mouse_on();
|
|
mouse_absolute();
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fini(void) {
|
|
return 0;
|
|
}
|
|
|
|
MODULE_DEF(vmmware, init, fini);
|
|
MODULE_DEPENDS(ps2mouse); /* For ps2_mouse_alternate */
|
|
MODULE_DEPENDS(lfbvideo); /* For lfb resolution */
|