/* * 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 Bus Mouse devices. * * These devices were made by both Microsoft and Logitech. At * first, Microsoft used the same protocol as Logitech, but did * switch to their new protocol for their InPort interface. So, * although alike enough to be handled in the same driver, they * are not the same. * * This code is based on my Minix driver for the Logitech(-mode) * interface. Although that driver blindly took IRQ5, the board * seems to be able to tell the driver what IRQ it is set for. * When testing on MS-DOS (6.22), the 'mouse.exe' driver did not * want to start, and only after disassembling it and inspecting * the code it was discovered that driver actually does use the * IRQ reporting feature. In a really, really weird way, too: it * sets up the board, and then reads the CTRL register which is * supposed to return that IRQ value. Depending on whether or * not the FREEZE bit is set, it has to return either the two's * complemented (negated) value, or (if clear) just the value. * The mouse.com driver reads both values 10,000 times, and * then makes up its mind. Maybe an effort to 'debounce' the * reading of the DIP switches? Oh-well. * * NOTES: Verified with: * AMI WinBIOS 486 (5A, no IRQ detect, OK, IRQ5 only) * Microsoft Mouse.com V2.00 (DOS V6.22, 5A, OK) * Microsoft Mouse.exe V9.1 (DOS V6.22, A5, OK) * Logitech LMouse.com V6.02 (DOS V6.22) * Logitech LMouse.com V6.43 (DOS V6.22) * Microsoft WfW V3.11 on DOS V6.22 * GEOS V1.0 (OK, IRQ5 only) * GEOS V2.0 (OK, IRQ5 only) * Microsoft Windows 95 OSR2 * Microsoft Windows 98 SE * Microsoft Windows NT 3.1 * Microsoft Windows NT 3.51 * * The polling frequency for InPort controllers has to * be changed to programmable. Microsoft uses 30Hz, * but ATIXL ports are programmable 30-200Hz. * * Based on an early driver for MINIX 1.5. * * Version: @(#)mouse_bus.c 1.0.38 2018/05/23 * * Authors: Fred N. van Kempen, * * Copyright 1989-2018 Fred N. van Kempen. */ #include #include #include #include #include #include #include #define HAVE_STDARG_H #include "86box.h" #include "config.h" #include "io.h" #include "pic.h" #include "timer.h" #include "device.h" #include "mouse.h" #define MOUSE_PORT 0x023c /* default */ #define MOUSE_IRQ 5 /* default */ #define MOUSE_BUTTONS 2 /* default */ #define MOUSE_DEBUG 0 /* Our mouse device. */ typedef struct mouse { const char *name; /* name of this device */ int8_t type; /* type of this device */ uint16_t base; /* I/O port base to use */ int8_t irq; /* IRQ channel to use */ uint16_t flags; /* device flags */ uint8_t r_magic, /* MAGIC register */ r_ctrl, /* CONTROL register (WR) */ r_conf, /* CONFIG register */ r_cmd; /* (MS) current command */ uint16_t seq; /* general counter */ uint8_t but, /* current mouse status */ but_last; uint8_t cur_but; int8_t x, y; int x_delay, y_delay; uint8_t irq_num; int64_t timer; /* mouse event timer */ uint8_t (*read)(struct mouse *, uint16_t); void (*write)(struct mouse *, uint16_t, uint8_t); } mouse_t; #define FLAG_NEW 0x100 /* device is the newer variant */ #define FLAG_INPORT 0x80 /* device is MS InPort */ #define FLAG_3BTN 0x20 /* enable 3-button mode */ #define FLAG_SCALED 0x10 /* enable delta scaling */ #define FLAG_INTR 0x04 /* dev can send interrupts */ #define FLAG_FROZEN 0x02 /* do not update counters */ #define FLAG_ENABLED 0x01 /* dev is enabled for use */ /* Definitions for Logitech. */ #define LTMOUSE_DATA 0 /* DATA register */ #define LTMOUSE_MAGIC 1 /* signature magic register */ # define LTMAGIC_BYTE1 0xa5 /* most drivers use this */ # define LTMAGIC_BYTE2 0x5a /* some drivers use this */ #define LTMOUSE_CTRL 2 /* CTRL register */ # define LTCTRL_FREEZE 0x80 /* do not sample when set */ # define LTCTRL_RD_Y_HI 0x60 # define LTCTRL_RD_Y_LO 0x40 # define LTCTRL_RD_X_HI 0x20 # define LTCTRL_RD_X_LO 0x00 # define LTCTRL_RD_MASK 0x60 # define LTCTRL_IDIS 0x10 # define LTCTRL_IENB 0x00 #define LTMOUSE_CONFIG 3 /* CONFIG register */ /* Definitions for Microsoft. */ #define MSMOUSE_CTRL 0 /* CTRL register */ # define MSCTRL_RESET 0x80 /* reset controller */ # define MSCTRL_FREEZE 0x20 /* HOLD- freeze data */ # define MSCTRL_IENB_A 0x08 /* ATIXL intr enable */ # define MSCTRL_IENB_M 0x01 /* MS intr enable */ # define MSCTRL_COMMAND 0x07 # define MSCTRL_RD_Y 0x02 # define MSCTRL_RD_X 0x01 # define MSCTRL_RD_BUT 0x00 #define MSMOUSE_DATA 1 /* DATA register */ # define MSDATA_IRQ 0x16 # define MSDATA_BASE 0x10 /* MS InPort: 30Hz */ # define MSDATA_HZ30 0x01 /* ATIXL 30Hz */ # define MSDATA_HZ50 0x02 /* ATIXL 50Hz */ # define MSDATA_HZ100 0x03 /* ATIXL 100Hz */ # define MSDATA_HZ200 0x04 /* ATIXL 200Hz */ #define MSMOUSE_MAGIC 2 /* MAGIC register */ # define MAGIC_MSBYTE1 0xde /* indicates MS InPort */ # define MAGIC_MSBYTE2 0x12 #define MSMOUSE_CONFIG 3 /* CONFIG register */ #define ENABLE_MOUSE_BUS_LOG 1 #ifdef ENABLE_MOUSE_BUS_LOG int mouse_bus_do_log = ENABLE_MOUSE_BUS_LOG; #endif static void mouse_bus_log(const char *format, ...) { #ifdef ENABLE_MOUSE_BUS_LOG va_list ap; if (mouse_bus_do_log) { va_start(ap, format); pclog_ex(format, ap); va_end(ap); } #endif } /* Reset the controller state. */ static void ms_reset(mouse_t *dev) { dev->r_ctrl = 0x00; dev->r_cmd = 0x00; dev->seq = 0; dev->x = dev->y = 0; dev->but = 0x00; dev->flags &= 0xf0; dev->flags |= (FLAG_INTR | FLAG_ENABLED); dev->x_delay = dev->y_delay = 0; dev->cur_but = 0x00; } static void ms_update_data(mouse_t *dev) { int delta_x, delta_y; if (dev->x_delay > 127) { delta_x = 127; dev->x_delay -= 127; } else if (dev->x_delay < -128) { delta_x = -128; dev->x_delay += 128; } else { delta_x = dev->x_delay; dev->x_delay = 0; } if (dev->y_delay > 127) { delta_y = 127; dev->y_delay -= 127; } else if (dev->y_delay < -128) { delta_y = -128; dev->x_delay += 128; } else { delta_y = dev->y_delay; dev->y_delay = 0; } if (!(dev->flags & FLAG_FROZEN)) { dev->x = (int8_t) delta_x; dev->y = (int8_t) delta_y; dev->cur_but = dev->but; } } /* Handle a WRITE to an InPort register. */ static void ms_write(mouse_t *dev, uint16_t port, uint8_t val) { uint8_t valxor; switch (port) { case MSMOUSE_CTRL: /* Bit 7 is reset. */ if (val & MSCTRL_RESET) ms_reset(dev); /* Bits 0-2 are the internal register index. */ switch (val & 0x07) { case MSCTRL_COMMAND: case MSCTRL_RD_BUT: case MSCTRL_RD_X: case MSCTRL_RD_Y: dev->r_cmd = val & 0x07; break; } break; case MSMOUSE_DATA: picintc(1 << dev->irq); if (val == MSDATA_IRQ) picint(1 << dev->irq); else { switch (dev->r_cmd) { case MSCTRL_COMMAND: valxor = (dev->r_ctrl ^ val); if (valxor & MSCTRL_FREEZE) { if (val & MSCTRL_FREEZE) { /* Hold the sampling while we do something. */ dev->flags |= FLAG_FROZEN; } else { /* Reset current state. */ dev->flags &= ~FLAG_FROZEN; dev->x = dev->y = 0; dev->but = 0; } } if (val & (MSCTRL_IENB_M | MSCTRL_IENB_A)) dev->flags |= FLAG_INTR; else dev->flags &= ~FLAG_INTR; dev->r_ctrl = val; break; default: break; } } break; case MSMOUSE_MAGIC: break; case MSMOUSE_CONFIG: break; } } /* Handle a READ from an InPort register. */ static uint8_t ms_read(mouse_t *dev, uint16_t port) { uint8_t ret = 0x00; switch (port) { case MSMOUSE_CTRL: ret = dev->r_ctrl; break; case MSMOUSE_DATA: switch (dev->r_cmd) { case MSCTRL_RD_BUT: ret = dev->cur_but; if (dev->flags & FLAG_NEW) ret |= 0x40; /* On new InPort, always have bit 6 set. */ break; case MSCTRL_RD_X: ret = dev->x; break; case MSCTRL_RD_Y: ret = dev->y; break; case MSCTRL_COMMAND: ret = dev->r_ctrl; break; } break; case MSMOUSE_MAGIC: if (dev->seq & 0x01) ret = MAGIC_MSBYTE2; else ret = MAGIC_MSBYTE1; dev->seq ^= 1; break; case MSMOUSE_CONFIG: /* Not really present in real hardware. */ break; } return(ret); } /* Reset the controller state. */ static void lt_reset(mouse_t *dev) { dev->r_magic = 0x00; dev->r_ctrl = (LTCTRL_IENB); dev->r_conf = 0x00; dev->seq = 0; dev->x = dev->y = 0; dev->but = 0x00; dev->flags &= 0xf0; dev->flags |= (FLAG_INTR | FLAG_ENABLED); dev->irq_num = 0; } /* Called at 30hz */ static void bm_timer(void *priv) { mouse_t *dev = (mouse_t *)priv; if (dev->flags & FLAG_INPORT) { dev->timer = ((1000000LL * TIMER_USEC) / 30LL); ms_update_data(dev); if (dev->flags & FLAG_INTR) picint(1 << dev->irq); } else { picint(1 << dev->irq); if (dev->irq_num == 5) { mouse_bus_log("5th IRQ, enabling mouse...\n"); lt_reset(dev); dev->flags |= FLAG_ENABLED; } if (dev->irq_num == 4) { mouse_bus_log("4th IRQ, going for the 5th...\n"); dev->irq_num++; dev->timer = ((1000000LL * TIMER_USEC) / 30LL); } else { mouse_bus_log("IRQ before the 4th, disabling timer...\n"); dev->timer = 0; } } } /* Handle a WRITE to a Logitech register. */ static void lt_write(mouse_t *dev, uint16_t port, uint8_t val) { uint8_t b; switch (port) { case LTMOUSE_DATA: /* [00] data register */ break; case LTMOUSE_MAGIC: /* [01] magic data register */ switch(val) { case LTMAGIC_BYTE1: case LTMAGIC_BYTE2: lt_reset(dev); dev->r_magic = val; // dev->flags |= FLAG_ENABLED; break; } break; case LTMOUSE_CTRL: /* [02] control register */ #if 0 if (!(dev->flags & FLAG_ENABLED)) { dev->irq_num++; dev->timer = ((1000000LL * TIMER_USEC) / 30LL); break; } #endif b = (dev->r_ctrl ^ val); if (b & LTCTRL_FREEZE) { if (val & LTCTRL_FREEZE) { /* Hold the sampling while we do something. */ dev->flags |= FLAG_FROZEN; } else { /* Reset current state. */ dev->flags &= ~FLAG_FROZEN; dev->x = dev->y = 0; if (dev->but) dev->but |= 0x80; } } if (b & LTCTRL_IDIS) { /* Disable or enable interrupts. */ if (val & LTCTRL_IDIS) dev->flags &= ~FLAG_INTR; else dev->flags |= FLAG_INTR; } /* Save new register value. */ dev->r_ctrl = val | 0x0F; /* Clear any pending interrupts. */ if (val & LTCTRL_FREEZE) { mouse_bus_log("Freeze: Raise IRQ %i\n", dev->irq); picint(1 << dev->irq); /* Microsoft MOUSE.SYS v3.0 seems to expect every freeze to send an IRQ? */ } else { mouse_bus_log("No freeze: Lower IRQ %i\n", dev->irq); picintc(1 << dev->irq); } break; case LTMOUSE_CONFIG: /* [03] config register */ /* * The original Logitech design was based on using a * 8255 parallel I/O chip. This chip has to be set up * for proper operation, and this configuration data * is what is programmed into this register. * * A snippet of code found in the FreeBSD kernel source * explains the value: * * D7 = Mode set flag (1 = active) * D6,D5 = Mode selection (port A) * 00 = Mode 0 = Basic I/O * 01 = Mode 1 = Strobed I/O * 10 = Mode 2 = Bi-dir bus * D4 = Port A direction (1 = input) * D3 = Port C (upper 4 bits) direction. (1 = input) * D2 = Mode selection (port B & C) * 0 = Mode 0 = Basic I/O * 1 = Mode 1 = Strobed I/O * D1 = Port B direction (1 = input) * D0 = Port C (lower 4 bits) direction. (1 = input) * * So 91 means Basic I/O on all 3 ports, Port A is an input * port, B is an output port, C is split with upper 4 bits * being an output port and lower 4 bits an input port, and * enable the sucker. Courtesy Intel 8255 databook. Lars */ dev->r_conf = val; break; default: break; } } static int lt_read_int(mouse_t *dev) { if (!(dev->flags & FLAG_NEW)) return 1; /* On old LogiBus, read the IRQ bits always. */ if (dev->flags & FLAG_INTR) return 1; /* On new LogiBus, read the IRQ bits if interrupts are enabled. */ return 0; /* Otherwise, do not. */ } /* Handle a READ from a Logitech register. */ static uint8_t lt_read(mouse_t *dev, uint16_t port) { uint8_t ret = 0xff; /* The GEOS drivers actually check this. */ if (! (dev->flags & FLAG_ENABLED)) return(ret); switch (port) { case LTMOUSE_DATA: /* [00] data register */ ret = 0x07; if (dev->but & 0x01) /* LEFT */ ret &= ~0x04; if (dev->but & 0x02) /* RIGHT */ ret &= ~0x01; if (dev->flags & FLAG_3BTN) if (dev->but & 0x04) /* MIDDLE */ ret &= ~0x02; ret <<= 5; switch(dev->r_ctrl & LTCTRL_RD_MASK) { case LTCTRL_RD_X_LO: /* X, low bits */ ret |= (dev->x & 0x0f); break; case LTCTRL_RD_X_HI: /* X, high bits */ ret |= (dev->x >> 4) & 0x0f; break; case LTCTRL_RD_Y_LO: /* Y, low bits */ ret |= (dev->y & 0x0f); break; case LTCTRL_RD_Y_HI: /* Y, high bits */ ret |= (dev->y >> 4) & 0x0f; break; } break; case LTMOUSE_MAGIC: /* [01] magic data register */ /* * Drivers write a magic byte to this register, usually * this is either 5A (AMI WinBIOS, MS Mouse 2.0) or * A5 (MS Mouse 9.1, Windows drivers, UNIX/Linux/Minix.) */ ret = dev->r_magic; break; case LTMOUSE_CTRL: /* [02] control register */ ret = dev->r_ctrl; dev->r_ctrl |= 0x0F; if ((dev->seq > 0x3FF) && lt_read_int(dev)) { /* !IDIS, return DIP switch setting. */ switch(dev->irq) { case 2: dev->r_ctrl &= ~0x08; break; case 3: dev->r_ctrl &= ~0x04; break; case 4: dev->r_ctrl &= ~0x02; break; case 5: dev->r_ctrl &= ~0x01; break; } } dev->seq = (dev->seq + 1) & 0x7ff; break; case LTMOUSE_CONFIG: /* [03] config register */ ret = dev->r_conf; break; default: break; } return(ret); } /* Handle a WRITE operation to one of our registers. */ static void bm_write(uint16_t port, uint8_t val, void *priv) { mouse_t *dev = (mouse_t *)priv; mouse_bus_log("%s: write(%d,%02x)\n", dev->name, port & 0x03, val); dev->write(dev, port & 0x03, val); } /* Handle a READ operation from one of our registers. */ static uint8_t bm_read(uint16_t port, void *priv) { mouse_t *dev = (mouse_t *)priv; uint8_t ret; ret = dev->read(dev, port & 0x03); mouse_bus_log("%s: read(%d): %02x\n", dev->name, port & 0x03, ret); return(ret); } /* The emulator calls us with an update on the host mouse device. */ static int bm_poll(int x, int y, int z, int b, void *priv) { uint8_t b_last; mouse_t *dev = (mouse_t *)priv; b_last = dev->but; /* Return early if nothing to do. */ if (!x && !y && !z && (dev->but == b)) return(1); /* If we are not enabled, return. */ if (! (dev->flags & FLAG_ENABLED)) mouse_bus_log("bm_poll(): Mouse not enabled\n"); if (dev->flags & FLAG_SCALED) { /* Scale down the motion. */ if ((x < -1) || (x > 1)) x >>= 1; if ((y < -1) || (y > 1)) y >>= 1; } if (dev->flags & FLAG_INPORT) { if (x || y || z) dev->but = 0x40; /* Mouse has moved. */ else dev->but = 0x00; if (x > 127) x = 127; if (y > 127) y = 127; if (x < -128) x = -128; if (y < -128) y = -128; dev->x_delay += x; dev->y_delay += y; dev->but |= (uint8_t) (((b & 1) << 2) | ((b & 2) >> 1)); if (dev->flags & FLAG_3BTN) dev->but |= ((b & 4) >> 1); if ((b_last ^ dev->but) & 0x04) dev->but |= 0x20; /* Left button state has changed. */ if (((b_last ^ dev->but) & 0x02) && (dev->flags & FLAG_3BTN)) dev->but |= 0x10; /* Middle button state has changed. */ if ((b_last ^ dev->but) & 0x01) dev->but |= 0x08; /* Right button state has changed. */ dev->but |= 0x80; /* Packet complete. */ } else { /* If we are frozen, do not update the state. */ if (! (dev->flags & FLAG_FROZEN)) { /* Add the delta to our state. */ x += dev->x; if (x > 127) x = 127; if (x < -128) x = -128; dev->x = (int8_t)x; y += dev->y; if (y > 127) y = 127; if (y < -1287) y = -1287; dev->y = (int8_t)y; dev->x_delay += x; dev->y_delay += y; dev->but = b; } /* Either way, generate an interrupt. */ if ((dev->flags & FLAG_INTR) && !(dev->flags & FLAG_INPORT)) picint(1 << dev->irq); } return(0); } /* Release all resources held by the device. */ static void bm_close(void *priv) { mouse_t *dev = (mouse_t *)priv; /* Release our I/O range. */ io_removehandler(dev->base, 4, bm_read, NULL, NULL, bm_write, NULL, NULL, dev); free(dev); } /* Initialize the device for use by the user. */ static void * bm_init(const device_t *info) { mouse_t *dev; int i, j; dev = (mouse_t *)malloc(sizeof(mouse_t)); memset(dev, 0x00, sizeof(mouse_t)); dev->name = info->name; dev->type = info->local; dev->base = device_get_config_hex16("base"); dev->irq = device_get_config_int("irq"); i = device_get_config_int("buttons"); if (i > 2) dev->flags |= FLAG_3BTN; j = device_get_config_int("model"); if (j) dev->flags |= FLAG_NEW; switch(dev->type) { case MOUSE_TYPE_LOGIBUS: lt_reset(dev); /* Initialize I/O handlers. */ dev->read = lt_read; dev->write = lt_write; // dev->timer = 0; // timer_add(bm_timer, &dev->timer, &dev->timer, dev); break; case MOUSE_TYPE_INPORT: dev->flags |= FLAG_INPORT; ms_reset(dev); /* Initialize I/O handlers. */ dev->read = ms_read; dev->write = ms_write; dev->timer = (33334LL * TIMER_USEC); timer_add(bm_timer, &dev->timer, TIMER_ALWAYS_ENABLED, dev); break; } /* Request an I/O range. */ io_sethandler(dev->base, 4, bm_read, NULL, NULL, bm_write, NULL, NULL, dev); mouse_bus_log("%s: I/O=%04x, IRQ=%d, buttons=%d, model=%s\n", dev->name, dev->base, dev->irq, i, j ? "new" : "old"); /* Tell them how many buttons we have. */ mouse_set_buttons((dev->flags & FLAG_3BTN) ? 3 : 2); /* Return our private data to the I/O layer. */ return(dev); } static const device_config_t bm_config[] = { { "base", "Address", CONFIG_HEX16, "", MOUSE_PORT, { { "0x230", 0x230 }, { "0x234", 0x234 }, { "0x238", 0x238 }, { "0x23C", 0x23c }, { "" } } }, { "irq", "IRQ", CONFIG_SELECTION, "", MOUSE_IRQ, { { "IRQ 2", 2 }, { "IRQ 3", 3 }, { "IRQ 4", 4 }, { "IRQ 5", 5 }, { "" } } }, { "buttons", "Buttons", CONFIG_SELECTION, "", MOUSE_BUTTONS, { { "Two", 2 }, { "Three", 3 }, { "" } } }, { "model", "Model", CONFIG_SELECTION, "", 0, { { "Old", 0 }, { "New", 1 }, { "" } } }, { "", "", -1 } }; const device_t mouse_logibus_device = { "Logitech Bus Mouse", DEVICE_ISA, MOUSE_TYPE_LOGIBUS, bm_init, bm_close, NULL, bm_poll, NULL, NULL, bm_config }; const device_t mouse_msinport_device = { "Microsoft Bus Mouse (InPort)", DEVICE_ISA, MOUSE_TYPE_INPORT, bm_init, bm_close, NULL, bm_poll, NULL, NULL, bm_config };