/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * Implementation of PS/2 series Mouse devices. * * * * Authors: Fred N. van Kempen, */ #include #include #include #include #include #include #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/device.h> #include <86box/keyboard.h> #include <86box/mouse.h> enum { MODE_STREAM, MODE_REMOTE, MODE_ECHO }; typedef struct { const char *name; /* name of this device */ int8_t type; /* type of this device */ int mode; uint16_t flags; uint8_t resolution; uint8_t sample_rate; uint8_t command; int x, y, z, b; uint8_t last_data[6]; } mouse_t; #define FLAG_5BTN 0x100 /* using Intellimouse Optical mode */ #define FLAG_INTELLI 0x80 /* device is IntelliMouse */ #define FLAG_INTMODE 0x40 /* using Intellimouse mode */ #define FLAG_SCALED 0x20 /* enable delta scaling */ #define FLAG_ENABLED 0x10 /* dev is enabled for use */ #define FLAG_CTRLDAT 0x08 /* ctrl or data mode */ int mouse_scan = 0; #ifdef ENABLE_MOUSE_PS2_LOG int mouse_ps2_do_log = ENABLE_MOUSE_PS2_LOG; static void mouse_ps2_log(const char *fmt, ...) { va_list ap; if (mouse_ps2_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define mouse_ps2_log(fmt, ...) #endif void mouse_clear_data(void *priv) { mouse_t *dev = (mouse_t *) priv; dev->flags &= ~FLAG_CTRLDAT; } static void ps2_report_coordinates(mouse_t *dev, int cmd) { uint8_t buff[3] = { 0x08, 0x00, 0x00 }; int temp_z; if (dev->x > 255) { dev->x = 255; buff[0] |= 0x40; } if (dev->x < -256) { dev->x = -256; buff[0] |= 0x40; } if (dev->y > 255) { dev->y = 255; buff[0] |= 0x80; } if (dev->y < -256) { dev->y = -256; buff[0] |= 0x80; } if (dev->z < -8) dev->z = -8; if (dev->z > 7) dev->z = 7; if (dev->x < 0) buff[0] |= 0x10; if (dev->y < 0) buff[0] |= 0x20; if (mouse_buttons & 0x01) buff[0] |= 0x01; if (mouse_buttons & 0x02) buff[0] |= 0x02; if (dev->flags & FLAG_INTELLI) { if (mouse_buttons & 0x04) buff[0] |= 0x04; } buff[1] = (dev->x & 0xff); buff[2] = (dev->y & 0xff); if (cmd) { keyboard_at_adddata_mouse_cmd(buff[0]); keyboard_at_adddata_mouse_cmd(buff[1]); keyboard_at_adddata_mouse_cmd(buff[2]); } else { keyboard_at_adddata_mouse(buff[0]); keyboard_at_adddata_mouse(buff[1]); keyboard_at_adddata_mouse(buff[2]); } if (dev->flags & FLAG_INTMODE) { temp_z = dev->z & 0x0f; if ((dev->flags & FLAG_5BTN)) { if (mouse_buttons & 8) temp_z |= 0x10; if (mouse_buttons & 16) temp_z |= 0x20; } else { /* The wheel coordinate is sign-extended. */ if (temp_z & 0x08) temp_z |= 0xf0; } if (cmd) keyboard_at_adddata_mouse_cmd(temp_z); else keyboard_at_adddata_mouse(temp_z); } dev->x = dev->y = dev->z = 0; } static void ps2_write(uint8_t val, void *priv) { mouse_t *dev = (mouse_t *) priv; uint8_t temp; if (dev->flags & FLAG_CTRLDAT) { dev->flags &= ~FLAG_CTRLDAT; if (val == 0xff) goto mouse_reset; switch (dev->command) { case 0xe8: /* set mouse resolution */ dev->resolution = val; keyboard_at_adddata_mouse_cmd(0xfa); break; case 0xf3: /* set sample rate */ dev->sample_rate = val; keyboard_at_adddata_mouse_cmd(0xfa); /* Command response */ break; default: keyboard_at_adddata_mouse_cmd(0xfc); } } else { dev->command = val; switch (dev->command) { case 0xe6: /* set scaling to 1:1 */ dev->flags &= ~FLAG_SCALED; keyboard_at_adddata_mouse_cmd(0xfa); break; case 0xe7: /* set scaling to 2:1 */ dev->flags |= FLAG_SCALED; keyboard_at_adddata_mouse_cmd(0xfa); break; case 0xe8: /* set mouse resolution */ dev->flags |= FLAG_CTRLDAT; keyboard_at_adddata_mouse_cmd(0xfa); break; case 0xe9: /* status request */ keyboard_at_adddata_mouse_cmd(0xfa); temp = (dev->flags & 0x30); if (mouse_buttons & 1) temp |= 4; if (mouse_buttons & 2) temp |= 1; if ((mouse_buttons & 4) && (dev->flags & FLAG_INTELLI)) temp |= 2; keyboard_at_adddata_mouse_cmd(temp); keyboard_at_adddata_mouse_cmd(dev->resolution); keyboard_at_adddata_mouse_cmd(dev->sample_rate); break; case 0xea: /* set stream */ dev->flags &= ~FLAG_CTRLDAT; mouse_scan = 1; keyboard_at_adddata_mouse_cmd(0xfa); /* ACK for command byte */ break; case 0xeb: /* Get mouse data */ keyboard_at_adddata_mouse_cmd(0xfa); ps2_report_coordinates(dev, 1); break; case 0xf2: /* read ID */ keyboard_at_adddata_mouse_cmd(0xfa); if (dev->flags & FLAG_INTMODE) keyboard_at_adddata_mouse_cmd((dev->flags & FLAG_5BTN) ? 0x04 : 0x03); else keyboard_at_adddata_mouse_cmd(0x00); break; case 0xf3: /* set command mode */ dev->flags |= FLAG_CTRLDAT; keyboard_at_adddata_mouse_cmd(0xfa); /* ACK for command byte */ break; case 0xf4: /* enable */ dev->flags |= FLAG_ENABLED; mouse_scan = 1; keyboard_at_adddata_mouse_cmd(0xfa); break; case 0xf5: /* disable */ dev->flags &= ~FLAG_ENABLED; mouse_scan = 0; keyboard_at_adddata_mouse_cmd(0xfa); break; case 0xf6: /* set defaults */ case 0xff: /* reset */ mouse_reset: dev->mode = MODE_STREAM; dev->flags &= 0x88; mouse_scan = 1; keyboard_at_mouse_reset(); keyboard_at_adddata_mouse_cmd(0xfa); if (dev->command == 0xff) { keyboard_at_adddata_mouse_cmd(0xaa); keyboard_at_adddata_mouse_cmd(0x00); } break; default: keyboard_at_adddata_mouse_cmd(0xfe); } } if (dev->flags & FLAG_INTELLI) { for (temp = 0; temp < 5; temp++) dev->last_data[temp] = dev->last_data[temp + 1]; dev->last_data[5] = val; if ((dev->last_data[0] == 0xf3) && (dev->last_data[1] == 0xc8) && (dev->last_data[2] == 0xf3) && (dev->last_data[3] == 0x64) && (dev->last_data[4] == 0xf3) && (dev->last_data[5] == 0x50)) dev->flags |= FLAG_INTMODE; if ((dev->flags & FLAG_INTMODE) && (dev->last_data[0] == 0xf3) && (dev->last_data[1] == 0xc8) && (dev->last_data[2] == 0xf3) && (dev->last_data[3] == 0xc8) && (dev->last_data[4] == 0xf3) && (dev->last_data[5] == 0x50)) dev->flags |= FLAG_5BTN; } } static int ps2_poll(int x, int y, int z, int b, double abs_x, double abs_y, void *priv) { mouse_t *dev = (mouse_t *) priv; if (!x && !y && !z && (b == dev->b)) return (0xff); #if 0 if (!(dev->flags & FLAG_ENABLED)) return(0xff); #endif if (!mouse_scan) return (0xff); dev->x += x; dev->y -= y; dev->z -= z; #if 0 if ((dev->mode == MODE_STREAM) && (dev->flags & FLAG_ENABLED) && (keyboard_at_mouse_pos() < 13)) { #else if ((dev->mode == MODE_STREAM) && (keyboard_at_mouse_pos() < 13)) { #endif dev->b = b; ps2_report_coordinates(dev, 0); } return (0); } /* * Initialize the device for use by the user. * * We also get called from the various machines. */ void * mouse_ps2_init(const device_t *info) { mouse_t *dev; int i; dev = (mouse_t *) malloc(sizeof(mouse_t)); memset(dev, 0x00, sizeof(mouse_t)); dev->name = info->name; dev->type = info->local; dev->mode = MODE_STREAM; i = device_get_config_int("buttons"); if (i > 2) dev->flags |= FLAG_INTELLI; if (i == 4) i = 3; /* Hook into the general AT Keyboard driver. */ keyboard_at_set_mouse(ps2_write, dev); mouse_ps2_log("%s: buttons=%d\n", dev->name, i); /* Tell them how many buttons we have. */ mouse_set_buttons(i); /* Return our private data to the I/O layer. */ return (dev); } static void ps2_close(void *priv) { mouse_t *dev = (mouse_t *) priv; /* Unhook from the general AT Keyboard driver. */ keyboard_at_set_mouse(NULL, NULL); free(dev); } static const device_config_t ps2_config[] = { // clang-format off { .name = "buttons", .description = "Buttons", .type = CONFIG_SELECTION, .default_string = "", .default_int = 2, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "Two", .value = 2 }, { .description = "Three", .value = 3 }, { .description = "Wheel", .value = 4 }, { .description = "Five + Wheel", .value = 5 }, { .description = "" } } }, { .name = "", .description = "", .type = CONFIG_END } // clang-format on }; const device_t mouse_ps2_device = { .name = "Standard PS/2 Mouse", .internal_name = "ps2", .flags = DEVICE_PS2, .local = MOUSE_TYPE_PS2, .init = mouse_ps2_init, .close = ps2_close, .reset = NULL, { .poll = ps2_poll }, .speed_changed = NULL, .force_redraw = NULL, .config = ps2_config };