Files
86Box/src/device/mouse_bus.c

856 lines
27 KiB
C

/*
* 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.
*
* NOTES: Ported from Bochs with extensive modifications per testing
* of the real hardware, testing of drivers, and the old code.
*
* Logitech Bus Mouse verified with:
* Linux Slackware 3.0
* Logitech LMouse.com 3.12
* Logitech LMouse.com 3.30
* Logitech LMouse.com 3.41
* Logitech LMouse.com 3.42
* Logitech LMouse.com 4.00
* Logitech LMouse.com 5.00
* Logitech LMouse.com 6.00
* Logitech LMouse.com 6.02 Beta
* Logitech LMouse.com 6.02
* Logitech LMouse.com 6.12
* Logitech LMouse.com 6.20
* Logitech LMouse.com 6.23
* Logitech LMouse.com 6.30
* Logitech LMouse.com 6.31E
* Logitech LMouse.com 6.34
* Logitech Mouse.exe 6.40
* Logitech Mouse.exe 6.41
* Logitech Mouse.exe 6.44
* Logitech Mouse.exe 6.46
* Logitech Mouse.exe 6.50
* Microsoft Mouse.com 2.00
* Microsoft Mouse.sys 3.00
* Microsoft Mouse.com 7.04
* Microsoft Mouse.com 8.21J
* Microsoft Windows 1.00 DR5
* Microsoft Windows 3.10.026
* Microsoft Windows 3.10.068 both MOUSE.DRV and LMOUSE.DRV
* Microsoft Windows NT 3.1
* Microsoft Windows 95
*
* InPort verified with:
* Linux Slackware 3.0
* Logitech LMouse.com 6.12
* Logitech LMouse.com 6.41
* Microsoft Windows 3.10.068 both MOUSE.DRV and LMOUSE.DRV
* Microsoft Windows NT 3.1
* Microsoft Windows 98 SE
*
*
*
* Authors: Miran Grca, <mgrca8@gmail.com>
* Fred N. van Kempen, <decwiz@yahoo.com>
*
* Copyright 200?-2019 Bochs.
* Copyright 2017-2019 Miran Grca.
* Copyright 1989-2019 Fred N. van Kempen.
*/
#include <inttypes.h>
#include <stdarg.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/io.h>
#include <86box/pic.h>
#include <86box/timer.h>
#include <86box/device.h>
#include <86box/mouse.h>
#include <86box/plat.h>
#include <86box/plat_unused.h>
#include <86box/random.h>
#define IRQ_MASK ((1 << 5) >> dev->irq)
/* MS Inport Bus Mouse Adapter */
#define INP_PORT_CONTROL 0x0000
#define INP_PORT_DATA 0x0001
#define INP_PORT_SIGNATURE 0x0002
#define INP_PORT_CONFIG 0x0003
#define INP_CTRL_READ_BUTTONS 0x00
#define INP_CTRL_READ_X 0x01
#define INP_CTRL_READ_Y 0x02
#define INP_CTRL_COMMAND 0x07
#define INP_CTRL_RAISE_IRQ 0x16
#define INP_CTRL_RESET 0x80
#define INP_HOLD_COUNTER (1 << 5)
#define INP_ENABLE_TIMER_IRQ (1 << 4)
#define INP_ENABLE_DATA_IRQ (1 << 3)
#define INP_PERIOD_MASK 0x07
/* MS/Logictech Standard Bus Mouse Adapter */
#define BUSM_PORT_DATA 0x0000
#define BUSM_PORT_SIGNATURE 0x0001
#define BUSM_PORT_CONTROL 0x0002
#define BUSM_PORT_CONFIG 0x0003
#define HOLD_COUNTER (1 << 7)
#define READ_X (0 << 6)
#define READ_Y (1 << 6)
#define READ_LOW (0 << 5)
#define READ_HIGH (1 << 5)
#define DISABLE_IRQ (1 << 4)
#define DEVICE_ACTIVE (1 << 7)
#define READ_X_LOW (READ_X | READ_LOW)
#define READ_X_HIGH (READ_X | READ_HIGH)
#define READ_Y_LOW (READ_Y | READ_LOW)
#define READ_Y_HIGH (READ_Y | READ_HIGH)
#define FLAG_INPORT (1 << 0)
#define FLAG_ENABLED (1 << 1)
#define FLAG_HOLD (1 << 2)
#define FLAG_TIMER_INT (1 << 3)
#define FLAG_DATA_INT (1 << 4)
static const uint8_t periods[4] = { 30, 50, 100, 200 };
/* Our mouse device. */
typedef struct mouse {
uint8_t current_b;
uint8_t control_val;
uint8_t config_val;
uint8_t sig_val;
uint8_t command_val;
uint8_t pad;
int8_t current_x;
int8_t current_y;
int base;
int irq;
int bn;
int flags;
int mouse_buttons;
int mouse_buttons_last;
int toggle_counter;
int timer_enabled;
double period;
pc_timer_t timer; /* mouse event timer */
} mouse_t;
#ifdef ENABLE_MOUSE_BUS_LOG
int bm_do_log = ENABLE_MOUSE_BUS_LOG;
static void
bm_log(const char *fmt, ...)
{
va_list ap;
if (bm_do_log) {
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
}
#else
# define bm_log(fmt, ...)
#endif
/* Handle a READ operation from one of our registers. */
static uint8_t
lt_read(uint16_t port, void *priv)
{
mouse_t *dev = (mouse_t *) priv;
uint8_t value = 0xff;
switch (port & 0x03) {
case BUSM_PORT_DATA:
/* Testing and another source confirm that the buttons are
*ALWAYS* present, so I'm going to change this a bit. */
switch (dev->control_val & 0x60) {
case READ_X_LOW:
value = dev->current_x & 0x0F;
dev->current_x &= ~0x0F;
break;
case READ_X_HIGH:
value = (dev->current_x >> 4) & 0x0F;
dev->current_x &= ~0xF0;
break;
case READ_Y_LOW:
value = dev->current_y & 0x0F;
dev->current_y &= ~0x0F;
break;
case READ_Y_HIGH:
value = (dev->current_y >> 4) & 0x0F;
dev->current_y &= ~0xF0;
break;
default:
bm_log("ERROR: Reading data port in unsupported mode 0x%02x\n", dev->control_val);
}
value |= ((dev->current_b ^ 7) << 5);
break;
case BUSM_PORT_SIGNATURE:
value = dev->sig_val;
break;
case BUSM_PORT_CONTROL:
value = dev->control_val;
dev->control_val |= 0x0F;
/* If the conditions are right, simulate the flakiness of the correct IRQ bit. */
if (dev->flags & FLAG_TIMER_INT)
dev->control_val = (dev->control_val & ~IRQ_MASK) | (random_generate() & IRQ_MASK);
break;
case BUSM_PORT_CONFIG:
/* Read from config port returns control_val in the upper 4 bits when enabled,
possibly solid interrupt readout in the lower 4 bits, 0xff when not (at power-up). */
if (dev->flags & FLAG_ENABLED)
return (dev->control_val | 0x0F) & ~IRQ_MASK;
else
return 0xff;
default:
break;
}
bm_log("DEBUG: read from address 0x%04x, value = 0x%02x\n", port, value);
return value;
}
static uint8_t
ms_read(uint16_t port, void *priv)
{
mouse_t *dev = (mouse_t *) priv;
uint8_t value = 0xff;
switch (port & 0x03) {
case INP_PORT_CONTROL:
value = dev->control_val;
break;
case INP_PORT_DATA:
switch (dev->command_val) {
case INP_CTRL_READ_BUTTONS:
value = dev->current_b;
break;
case INP_CTRL_READ_X:
value = dev->current_x;
dev->current_x = 0;
break;
case INP_CTRL_READ_Y:
value = dev->current_y;
dev->current_y = 0;
break;
case INP_CTRL_COMMAND:
value = dev->control_val;
break;
default:
bm_log("ERROR: Reading data port in unsupported mode 0x%02x\n", dev->control_val);
}
break;
case INP_PORT_SIGNATURE:
if (dev->toggle_counter)
value = 0x12;
else
value = 0xDE;
dev->toggle_counter ^= 1;
break;
case INP_PORT_CONFIG:
bm_log("ERROR: Unsupported read from port 0x%04x\n", port);
break;
default:
break;
}
bm_log("DEBUG: read from address 0x%04x, value = 0x%02x\n", port, value);
return value;
}
/* Handle a WRITE operation to one of our registers. */
static void
lt_write(uint16_t port, uint8_t val, void *priv)
{
mouse_t *dev = (mouse_t *) priv;
uint8_t bit;
bm_log("DEBUG: write to address 0x%04x, value = 0x%02x\n", port, val);
switch (port & 0x03) {
case BUSM_PORT_DATA:
bm_log("ERROR: Unsupported write to port 0x%04x (value = 0x%02x)\n", port, val);
break;
case BUSM_PORT_SIGNATURE:
dev->sig_val = val;
break;
case BUSM_PORT_CONTROL:
dev->control_val = val | 0x0F;
if (!(val & DISABLE_IRQ))
dev->flags |= FLAG_TIMER_INT;
else
dev->flags &= ~FLAG_TIMER_INT;
if (val & HOLD_COUNTER)
dev->flags |= FLAG_HOLD;
else
dev->flags &= ~FLAG_HOLD;
if (dev->irq != -1)
picintc(1 << dev->irq);
break;
case BUSM_PORT_CONFIG:
/*
* 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)
* This indicates the mode of operation of D7:
* 1 = Mode set, 0 = Bit set/reset
* 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
*
* 1001 1011 9B 1111 Default state
* 1001 0001 91 1001 Driver-initialized state
* The only difference is - port C upper and port B go from
* input to output.
*/
if (val & DEVICE_ACTIVE) {
/* Mode set/reset - enable this */
dev->config_val = val;
if (dev->timer_enabled)
dev->flags |= (FLAG_ENABLED | FLAG_TIMER_INT);
else
dev->flags |= FLAG_ENABLED;
dev->control_val = 0x0F & ~IRQ_MASK;
} else {
/* Single bit set/reset */
bit = 1 << ((val >> 1) & 0x07); /* Bits 3-1 specify the target bit */
if (val & 1)
dev->control_val |= bit; /* Set */
else
dev->control_val &= ~bit; /* Reset */
}
break;
default:
break;
}
}
/* Handle a WRITE operation to one of our registers. */
static void
ms_write(uint16_t port, uint8_t val, void *priv)
{
mouse_t *dev = (mouse_t *) priv;
bm_log("DEBUG: write to address 0x%04x, value = 0x%02x\n", port, val);
switch (port & 0x03) {
case INP_PORT_CONTROL:
/* Bit 7 is reset. */
if (val & INP_CTRL_RESET)
dev->control_val = 0;
/* Bits 0-2 are the internal register index. */
switch (val & 0x07) {
case INP_CTRL_COMMAND:
case INP_CTRL_READ_BUTTONS:
case INP_CTRL_READ_X:
case INP_CTRL_READ_Y:
dev->command_val = val & 0x07;
break;
default:
bm_log("ERROR: Unsupported command written to port 0x%04x (value = 0x%02x)\n", port, val);
}
break;
case INP_PORT_DATA:
if (dev->irq != -1)
picintc(1 << dev->irq);
switch (dev->command_val) {
case INP_CTRL_COMMAND:
if (val & INP_HOLD_COUNTER)
dev->flags |= FLAG_HOLD;
else
dev->flags &= ~FLAG_HOLD;
if (val & INP_ENABLE_TIMER_IRQ)
dev->flags |= FLAG_TIMER_INT;
else
dev->flags &= ~FLAG_TIMER_INT;
if (val & INP_ENABLE_DATA_IRQ)
dev->flags |= FLAG_DATA_INT;
else
dev->flags &= ~FLAG_DATA_INT;
switch (val & INP_PERIOD_MASK) {
case 0:
dev->period = 0.0;
timer_disable(&dev->timer);
dev->timer_enabled = 0;
break;
case 1:
case 2:
case 3:
case 4:
dev->period = (1000000.0 / (double) periods[(val & INP_PERIOD_MASK) - 1]);
dev->timer_enabled = (val & INP_ENABLE_TIMER_IRQ) ? 1 : 0;
timer_disable(&dev->timer);
if (dev->timer_enabled)
timer_set_delay_u64(&dev->timer, (uint64_t) (dev->period * (double) TIMER_USEC));
bm_log("DEBUG: Timer is now %sabled at period %i\n", (val & INP_ENABLE_TIMER_IRQ) ? "en" : "dis", (int32_t) dev->period);
break;
case 6:
if ((val & INP_ENABLE_TIMER_IRQ) && (dev->irq != -1))
picint(1 << dev->irq);
dev->control_val &= INP_PERIOD_MASK;
dev->control_val |= (val & ~INP_PERIOD_MASK);
return;
default:
bm_log("ERROR: Unsupported period written to port 0x%04x (value = 0x%02x)\n", port, val);
}
dev->control_val = val;
break;
default:
bm_log("ERROR: Unsupported write to port 0x%04x (value = 0x%02x)\n", port, val);
}
break;
case INP_PORT_SIGNATURE:
case INP_PORT_CONFIG:
bm_log("ERROR: Unsupported write to port 0x%04x (value = 0x%02x)\n", port, val);
break;
default:
break;
}
}
/* The emulator calls us with an update on the host mouse device. */
static int
bm_poll(void *priv)
{
mouse_t *dev = (mouse_t *) priv;
int delta_x;
int delta_y;
int xor;
int b = mouse_get_buttons_ex();
if (!mouse_capture && !video_fullscreen)
return 1;
if (!(dev->flags & FLAG_ENABLED))
return 1; /* Mouse is disabled, do nothing. */
if (!mouse_state_changed()) {
dev->mouse_buttons_last = 0x00;
return 1; /* State has not changed, do nothing. */
}
/* Converts button states from MRL to LMR. */
dev->mouse_buttons = (uint8_t) (((b & 1) << 2) | ((b & 2) >> 1));
if (dev->bn == 3)
dev->mouse_buttons |= ((b & 4) >> 1);
if ((dev->flags & FLAG_INPORT) && !dev->timer_enabled) {
/* This is an InPort mouse in data interrupt mode,
so update bits 6-3 here. */
/* If the mouse has moved, set bit 6. */
if (mouse_moved())
dev->mouse_buttons |= 0x40;
/* Set bits 3-5 according to button state changes. */
xor = ((dev->current_b ^ mouse_get_buttons_ex()) & 0x07) << 3;
dev->mouse_buttons |= xor;
}
dev->mouse_buttons_last = b;
if (!dev->timer_enabled) {
/* If the counters are not frozen, update them. */
if (!(dev->flags & FLAG_HOLD)) {
mouse_subtract_coords(&delta_x, &delta_y, NULL, NULL, -128, 127, 0, 0);
dev->current_x = (int8_t) delta_x;
dev->current_y = (int8_t) delta_y;
dev->current_b = dev->mouse_buttons;
}
/* Send interrupt. */
if ((dev->flags & FLAG_DATA_INT) && (dev->irq != -1)) {
picint(1 << dev->irq);
bm_log("DEBUG: Data Interrupt Fired...\n");
}
}
return 0;
}
/* The timer calls us on every tick if the mouse is in timer mode
(InPort mouse is so configured, MS/Logitech Bus mouse always). */
static void
bm_update_data(mouse_t *dev)
{
int delta_x;
int delta_y;
int xor;
/* If the counters are not frozen, update them. */
if ((mouse_capture || video_fullscreen) && !(dev->flags & FLAG_HOLD)) {
/* Update the deltas and the delays. */
mouse_subtract_coords(&delta_x, &delta_y, NULL, NULL, -128, 127, 0, 0);
dev->current_x = (int8_t) delta_x;
dev->current_y = (int8_t) delta_y;
} else
delta_x = delta_y = 0;
if (dev->flags & FLAG_INPORT) {
/* This is an InPort mouse in timer mode, so update current_b always,
and update bits 6-3 (mouse moved and button state changed) here. */
xor = ((dev->current_b ^ dev->mouse_buttons) & 0x07) << 3;
dev->current_b = (dev->mouse_buttons & 0x87) | xor;
if (delta_x || delta_y)
dev->current_b |= 0x40;
} else if (!(dev->flags & FLAG_HOLD)) {
/* This is a MS/Logitech Bus Mouse, so only update current_b if the
counters are frozen. */
dev->current_b = dev->mouse_buttons;
}
}
/* Called at the configured period (InPort mouse) or 45 times per second (MS/Logitech Bus mouse). */
static void
bm_timer(void *priv)
{
mouse_t *dev = (mouse_t *) priv;
bm_log("DEBUG: Timer Tick (flags=%08X)...\n", dev->flags);
/* The period is configured either via emulator settings (for MS/Logitech Bus mouse)
or via software (for InPort mouse). */
timer_advance_u64(&dev->timer, (uint64_t) (dev->period * (double) TIMER_USEC));
if ((dev->flags & FLAG_TIMER_INT) && (dev->irq != -1)) {
picint(1 << dev->irq);
bm_log("DEBUG: Timer Interrupt Fired...\n");
}
bm_update_data(dev);
}
/* Release all resources held by the device. */
static void
bm_close(void *priv)
{
mouse_t *dev = (mouse_t *) priv;
if (dev)
free(dev);
}
/* Set the mouse's IRQ. */
void
mouse_bus_set_irq(void *priv, int irq)
{
mouse_t *dev = (mouse_t *) priv;
dev->irq = irq;
}
/* Initialize the device for use by the user. */
static void *
bm_init(const device_t *info)
{
mouse_t *dev;
int hz;
dev = (mouse_t *) calloc(1, sizeof(mouse_t));
if ((info->local & ~MOUSE_TYPE_ONBOARD) == MOUSE_TYPE_INPORT)
dev->flags = FLAG_INPORT;
else
dev->flags = 0;
if (info->local & MOUSE_TYPE_ONBOARD) {
dev->base = 0x023c;
dev->irq = -1;
dev->bn = 2;
} else {
dev->base = device_get_config_hex16("base");
dev->irq = device_get_config_int("irq");
dev->bn = device_get_config_int("buttons");
}
mouse_set_buttons(dev->bn);
dev->mouse_buttons = 0;
dev->mouse_buttons_last = 0;
dev->sig_val = 0; /* the signature port value */
dev->current_x = dev->current_y = 0;
dev->current_b = 0;
dev->command_val = 0; /* command byte */
dev->toggle_counter = 0; /* signature byte / IRQ bit toggle */
dev->period = 0.0;
timer_add(&dev->timer, bm_timer, dev, 0);
if (dev->flags & FLAG_INPORT) {
dev->control_val = 0; /* the control port value */
dev->flags |= FLAG_ENABLED;
io_sethandler(dev->base, 4,
ms_read, NULL, NULL, ms_write, NULL, NULL, dev);
dev->timer_enabled = 0;
} else {
dev->control_val = 0x0f; /* the control port value */
dev->config_val = 0x9b; /* the config port value - 0x9b is the
default state of the 8255: all ports
are set to input */
hz = device_get_config_int("hz");
if (hz > 0)
dev->period = (1000000.0 / (double) hz);
io_sethandler(dev->base, 4,
lt_read, NULL, NULL, lt_write, NULL, NULL, dev);
if (hz > 0) {
timer_set_delay_u64(&dev->timer, (uint64_t) (dev->period * (double) TIMER_USEC));
dev->timer_enabled = 1;
} else {
dev->flags |= FLAG_DATA_INT;
dev->timer_enabled = 0;
}
}
if (dev->flags & FLAG_INPORT)
bm_log("MS Inport BusMouse initialized\n");
else
bm_log("Standard MS/Logitech BusMouse initialized\n");
mouse_set_sample_rate(0.0);
mouse_set_poll(bm_poll, dev);
return dev;
}
static const device_config_t lt_config[] = {
// clang-format off
{
.name = "base",
.description = "Address",
.type = CONFIG_HEX16,
.default_string = NULL,
.default_int = 0x23c,
.file_filter = NULL,
.spinner = { 0 },
.selection = {
{ .description = "0x230", .value = 0x230 },
{ .description = "0x234", .value = 0x234 },
{ .description = "0x238", .value = 0x238 },
{ .description = "0x23C", .value = 0x23c },
{ .description = "" }
},
.bios = { { 0 } }
},
{
.name = "irq",
.description = "IRQ",
.type = CONFIG_SELECTION,
.default_string = NULL,
.default_int = 5,
.file_filter = NULL,
.spinner = { 0 },
.selection = {
{ .description = "IRQ 2", .value = 2 },
{ .description = "IRQ 3", .value = 3 },
{ .description = "IRQ 4", .value = 4 },
{ .description = "IRQ 5", .value = 5 },
{ .description = "" }
},
.bios = { { 0 } }
},
{
.name = "hz",
.description = "Hz",
.type = CONFIG_SELECTION,
.default_string = NULL,
.default_int = 45,
.file_filter = NULL,
.spinner = { 0 },
.selection = {
{ .description = "Non-timed (original)", .value = 0 },
{ .description = "30 Hz (JMP2 = 1)", .value = 30 },
{ .description = "45 Hz (JMP2 not populated)", .value = 45 },
{ .description = "60 Hz (JMP2 = 2)", .value = 60 },
{ .description = "" }
},
.bios = { { 0 } }
},
{
.name = "buttons",
.description = "Buttons",
.type = CONFIG_SELECTION,
.default_string = NULL,
.default_int = 2,
.file_filter = NULL,
.spinner = { 0 },
.selection = {
{ .description = "Two", .value = 2 },
{ .description = "Three", .value = 3 },
{ .description = "" }
},
.bios = { { 0 } }
},
{ .name = "", .description = "", .type = CONFIG_END }
// clang-format on
};
static const device_config_t ms_config[] = {
// clang-format off
{
.name = "base",
.description = "Address",
.type = CONFIG_HEX16,
.default_string = NULL,
.default_int = 0x23c,
.file_filter = NULL,
.spinner = { 0 },
.selection = {
{ .description = "0x230", .value = 0x230 },
{ .description = "0x234", .value = 0x234 },
{ .description = "0x238", .value = 0x238 },
{ .description = "0x23C", .value = 0x23c },
{ .description = "" }
},
.bios = { { 0 } }
},
{
.name = "irq",
.description = "IRQ",
.type = CONFIG_SELECTION,
.default_string = NULL,
.default_int = 5,
.file_filter = NULL,
.spinner = { 0 },
.selection = {
{ .description = "IRQ 2", .value = 2 },
{ .description = "IRQ 3", .value = 3 },
{ .description = "IRQ 4", .value = 4 },
{ .description = "IRQ 5", .value = 5 },
{ .description = "" }
},
.bios = { { 0 } }
},
{
.name = "buttons",
.description = "Buttons",
.type = CONFIG_SELECTION,
.default_string = NULL,
.default_int = 2,
.file_filter = NULL,
.spinner = { 0 },
.selection = {
{ .description = "Two", .value = 2 },
{ .description = "Three", .value = 3 },
{ .description = "" }
},
.bios = { { 0 } }
},
{ .name = "", .description = "", .type = CONFIG_END }
// clang-format on
};
const device_t mouse_logibus_device = {
.name = "Logitech/Microsoft Bus Mouse",
.internal_name = "logibus",
.flags = DEVICE_ISA,
.local = MOUSE_TYPE_LOGIBUS,
.init = bm_init,
.close = bm_close,
.reset = NULL,
.available = NULL,
.speed_changed = NULL,
.force_redraw = NULL,
.config = lt_config
};
const device_t mouse_logibus_onboard_device = {
.name = "Logitech Bus Mouse (On-Board)",
.internal_name = "logibus_onboard",
.flags = DEVICE_ISA,
.local = MOUSE_TYPE_LOGIBUS | MOUSE_TYPE_ONBOARD,
.init = bm_init,
.close = bm_close,
.reset = NULL,
.available = NULL,
.speed_changed = NULL,
.force_redraw = NULL,
.config = NULL
};
const device_t mouse_msinport_device = {
.name = "Microsoft Bus Mouse (InPort)",
.internal_name = "msbus",
.flags = DEVICE_ISA,
.local = MOUSE_TYPE_INPORT,
.init = bm_init,
.close = bm_close,
.reset = NULL,
.available = NULL,
.speed_changed = NULL,
.force_redraw = NULL,
.config = ms_config
};