summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Patrick Mackinlay <pmackinlay@hotmail.com>2019-05-31 16:01:54 +0700
committer Patrick Mackinlay <pmackinlay@hotmail.com>2019-05-31 16:01:54 +0700
commitd00c294732f799bd452918e1aa55545d3ae77a0e (patch)
tree56b1ed86ee0046a72708ff007785a7fdc2a7e189
parenta389d4bef36c8ebe0666a3f81794235fbf33b6a3 (diff)
hle_mouse: high-level emulation of PS/2 mouse
-rw-r--r--scripts/src/bus.lua2
-rw-r--r--src/devices/bus/pc_kbd/hle_mouse.cpp537
-rw-r--r--src/devices/bus/pc_kbd/hle_mouse.h98
-rw-r--r--src/devices/bus/pc_kbd/keyboards.cpp6
-rw-r--r--src/devices/bus/pc_kbd/keyboards.h5
5 files changed, 648 insertions, 0 deletions
diff --git a/scripts/src/bus.lua b/scripts/src/bus.lua
index 4ff7760533d..f63d4e43b9a 100644
--- a/scripts/src/bus.lua
+++ b/scripts/src/bus.lua
@@ -1573,6 +1573,8 @@ if (BUSES["PC_KBD"]~=null) then
MAME_DIR .. "src/devices/bus/pc_kbd/pcxt83.h",
MAME_DIR .. "src/devices/bus/pc_kbd/pcat101.cpp",
MAME_DIR .. "src/devices/bus/pc_kbd/pcat101.h",
+ MAME_DIR .. "src/devices/bus/pc_kbd/hle_mouse.cpp",
+ MAME_DIR .. "src/devices/bus/pc_kbd/hle_mouse.h",
}
end
diff --git a/src/devices/bus/pc_kbd/hle_mouse.cpp b/src/devices/bus/pc_kbd/hle_mouse.cpp
new file mode 100644
index 00000000000..e7de97e61dd
--- /dev/null
+++ b/src/devices/bus/pc_kbd/hle_mouse.cpp
@@ -0,0 +1,537 @@
+// license:BSD-3-Clause
+// copyright-holders:Patrick Mackinlay
+
+/*
+ * PS/2 mouse high-level emulation.
+ *
+ * This device emulates a mouse with three buttons, using the original three-
+ * byte PS/2 mouse protocol. Serial I/O is driven at 10kHz by a 40kHz timer
+ * to generate somewhat accurate clock rising and falling edges, as well as
+ * sampling or writing the data line in the middle of each high or low cycle
+ * as exoected by the protocol.
+ *
+ * The original IBM PS/2 mouse had only two buttons and the documented protocol
+ * reflects this, however it also allows a third button to be added without any
+ * significant changes. IBM later produced three-button mice which apparently
+ * took advantage of this, making it about as standard as it gets.
+ *
+ * Microsoft introduced the IntelliMouse in 1996 which adds another two buttons
+ * and a scroll wheel, requiring a change to the protocol from three to four
+ * byte data packets. The IntelliMouse protocol is only enabled after sending
+ * a specific sequence of "set sample rate" commands, without which the mouse
+ * uses the original protocol.
+ *
+ * Sources:
+ *
+ * https://web.archive.org/web/20180126072045/http://www.computer-engineering.org/ps2mouse/
+ * https://wiki.osdev.org/PS/2_Mouse
+ * https://www.win.tue.nl/~aeb/linux/kbd/scancodes-13.html
+ * http://read.pudn.com/downloads136/ebook/579116/docs/Designing%20a%20low%20cost%20CY7C63723%20combination%20mouse.pdf
+ *
+ * TODO
+ * - IntelliMouse device/protocol (4-byte packet, 5 buttons, scroll wheel)
+ * - configurable clock (10kHz-16.7kHz)
+ * - receive parity error handling
+ * - resolve issue with earlier Indy/Indigo² PROM versions (IBF ignored)
+ */
+
+#include "emu.h"
+#include "hle_mouse.h"
+
+#define LOG_GENERAL (1U << 0)
+#define LOG_RXTX (1U << 1)
+#define LOG_COMMAND (1U << 2)
+#define LOG_REPORT (1U << 3)
+#define LOG_STATE (1U << 4)
+
+//#define VERBOSE (LOG_COMMAND)
+
+#include "logmacro.h"
+
+DEFINE_DEVICE_TYPE(HLE_PS2_MOUSE, hle_ps2_mouse_device, "hle_ps2_mouse", "HLE PS/2 Mouse")
+
+ALLOW_SAVE_TYPE(hle_ps2_mouse_device::serial_state);
+
+INPUT_PORTS_START(hle_ps2_mouse_device)
+ PORT_START("mouse_x_axis")
+ PORT_BIT(0xffff, 0, IPT_MOUSE_X) PORT_SENSITIVITY(100)
+
+ PORT_START("mouse_y_axis")
+ PORT_BIT(0xffff, 0, IPT_MOUSE_Y) PORT_SENSITIVITY(100)
+
+ PORT_START("mouse_buttons")
+ PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_CODE(MOUSECODE_BUTTON1) PORT_NAME("Left Button")
+ PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_BUTTON2) PORT_CODE(MOUSECODE_BUTTON2) PORT_NAME("Right Button")
+ PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_BUTTON3) PORT_CODE(MOUSECODE_BUTTON3) PORT_NAME("Middle Button")
+ PORT_BIT(0xf8, IP_ACTIVE_HIGH, IPT_UNUSED)
+INPUT_PORTS_END
+
+static constexpr attotime serial_cycle = attotime::from_usec(25);
+
+hle_ps2_mouse_device::hle_ps2_mouse_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : device_t(mconfig, HLE_PS2_MOUSE, tag, owner, clock)
+ , device_pc_kbd_interface(mconfig, *this)
+ , m_port_x_axis(*this, "mouse_x_axis")
+ , m_port_y_axis(*this, "mouse_y_axis")
+ , m_port_buttons(*this, "mouse_buttons")
+{
+}
+
+ioport_constructor hle_ps2_mouse_device::device_input_ports() const
+{
+ return INPUT_PORTS_NAME(hle_ps2_mouse_device);
+}
+
+void hle_ps2_mouse_device::device_start()
+{
+ save_item(NAME(m_state));
+ save_item(NAME(m_bit));
+
+ save_item(NAME(m_mode));
+ save_item(NAME(m_sample_rate));
+ save_item(NAME(m_resolution));
+
+ save_item(NAME(m_rx_len));
+ save_item(NAME(m_rx_buf));
+ save_item(NAME(m_tx_len));
+ save_item(NAME(m_tx_pos));
+ save_item(NAME(m_data));
+ save_item(NAME(m_parity));
+
+ save_item(NAME(m_mouse_x));
+ save_item(NAME(m_mouse_y));
+ save_item(NAME(m_mouse_b));
+
+ set_pc_kbdc_device();
+
+ m_serial = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(hle_ps2_mouse_device::serial), this));
+ m_sample = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(hle_ps2_mouse_device::sample), this));
+}
+
+void hle_ps2_mouse_device::device_reset()
+{
+ // configure default settings
+ defaults();
+
+ // reset mouse state
+ update();
+
+ // clear tx/rx buffers
+ m_rx_len = 0;
+ m_tx_len = 0;
+ m_tx_pos = 0;
+
+ // enqueue bat result
+ m_tx_buf[m_tx_len++] = 0xaa;
+ m_tx_buf[m_tx_len++] = 0x00;
+
+ m_state = IDLE;
+
+ // release clock and data lines
+ m_pc_kbdc->data_write_from_kb(1);
+ m_pc_kbdc->clock_write_from_kb(1);
+}
+
+void hle_ps2_mouse_device::clock_write(int state)
+{
+ // record when the clock signal last changed state
+ m_clock_changed = machine().time();
+
+ // resume serial communication
+ resume();
+}
+
+void hle_ps2_mouse_device::resume()
+{
+ if (clock_signal() && m_state == IDLE)
+ {
+ // check if receiving a start bit
+ if (!data_signal())
+ m_state = RX_START;
+
+ // start serial communication
+ if (!m_serial->enabled())
+ m_serial->adjust(serial_cycle, 0, serial_cycle);
+ }
+}
+
+void hle_ps2_mouse_device::serial(void *ptr, s32 param)
+{
+ // host may inhibit device communication by holding the clock low for 100µs
+ if (!clock_signal() && clock_held(100))
+ {
+ m_state = IDLE;
+
+ // release clock and data lines
+ m_pc_kbdc->data_write_from_kb(1);
+ m_pc_kbdc->clock_write_from_kb(1);
+
+ // stop serial communication
+ m_serial->enable(false);
+ return;
+ }
+
+ LOGMASKED(LOG_STATE, "state %d clock %d data %d\n", m_state, clock_signal(), data_signal());
+
+ switch (m_state)
+ {
+ case IDLE:
+ if (clock_signal())
+ {
+ // stop serial communication if nothing to transmit
+ if (m_tx_pos == m_tx_len)
+ m_serial->enable(false);
+ else
+ // device may transmit after clock is high for 50µs
+ if (clock_held(50))
+ m_state = TX_START;
+ }
+ break;
+
+ case RX_START:
+ // check for start bit
+ if (!data_signal())
+ {
+ m_state = RX_CLOCK_LO0;
+
+ // prepare data
+ m_data = 0;
+ m_parity = 1;
+ m_bit = 0;
+ }
+ break;
+
+ case RX_CLOCK_LO0:
+ // assert clock
+ m_state = RX_CLOCK_LO1;
+ m_pc_kbdc->clock_write_from_kb(0);
+ break;
+
+ case RX_CLOCK_LO1:
+ // hold clock
+ m_state = RX_CLOCK_HI0;
+ break;
+
+ case RX_CLOCK_HI0:
+ // release clock
+ m_state = RX_CLOCK_HI1;
+ m_pc_kbdc->clock_write_from_kb(1);
+ break;
+
+ case RX_CLOCK_HI1:
+ switch (m_bit)
+ {
+ case 8: // parity bit
+ m_state = RX_CLOCK_LO0;
+
+ if (data_signal() == (m_parity & 1))
+ {
+ LOGMASKED(LOG_RXTX, "rx data 0x%02x\n", m_data);
+ m_rx_buf[m_rx_len++] = m_data;
+ }
+ else
+ // TODO: transmit unbuffered resend command
+ LOGMASKED(LOG_RXTX, "rx error data 0x%02x parity %d\n", m_data, data_signal());
+ m_bit++;
+ break;
+
+ case 9: // acknowledge
+ if (data_signal())
+ {
+ m_state = RX_CLOCK_LO0;
+ m_pc_kbdc->data_write_from_kb(0);
+ m_bit++;
+ }
+ break;
+
+ case 10: // finished
+ if (m_rx_len)
+ m_state = COMMAND;
+ else
+ m_state = IDLE;
+ m_pc_kbdc->data_write_from_kb(1);
+ break;
+
+ default: // data bit
+ m_state = RX_CLOCK_LO0;
+ m_parity += data_signal();
+ m_data |= (data_signal() << m_bit++);
+ break;
+ }
+ break;
+
+ case COMMAND:
+ m_state = IDLE;
+
+ // execute the command
+ command(m_rx_buf[0]);
+
+ // reset mouse state
+ update();
+ break;
+
+ case TX_START:
+ // prepare data
+ m_data = m_tx_buf[m_tx_pos++];
+ m_parity = 1;
+ m_bit = 0;
+
+ // start bit
+ m_state = TX_CLOCK_LO0;
+ m_pc_kbdc->data_write_from_kb(0);
+ break;
+
+ case TX_CLOCK_LO0:
+ // assert clock
+ m_state = TX_CLOCK_LO1;
+ m_pc_kbdc->clock_write_from_kb(0);
+ break;
+
+ case TX_CLOCK_LO1:
+ // hold clock
+ m_state = TX_CLOCK_HI0;
+ break;
+
+ case TX_CLOCK_HI0:
+ // release clock
+ m_state = TX_CLOCK_HI1;
+ m_pc_kbdc->clock_write_from_kb(1);
+ break;
+
+ case TX_CLOCK_HI1:
+ switch (m_bit)
+ {
+ case 8: // parity bit
+ m_state = TX_CLOCK_LO0;
+ m_pc_kbdc->data_write_from_kb(m_parity & 1);
+ LOGMASKED(LOG_RXTX, "tx data 0x%02x\n", m_data);
+ m_bit++;
+ break;
+
+ case 9: // stop bit
+ m_state = TX_CLOCK_LO0;
+ m_pc_kbdc->data_write_from_kb(1);
+ m_bit++;
+ break;
+
+ case 10: // finished
+ m_state = IDLE;
+ break;
+
+ default: // data
+ m_state = TX_CLOCK_LO0;
+ m_parity += BIT(m_data, m_bit);
+ m_pc_kbdc->data_write_from_kb(BIT(m_data, m_bit));
+ m_bit++;
+ break;
+ }
+ }
+}
+
+void hle_ps2_mouse_device::command(u8 const command)
+{
+ // consume the command byte
+ m_rx_len--;
+
+ // reset the transmit position
+ m_tx_pos = 0;
+
+ // special case for resend
+ if (command == 0xfe)
+ {
+ LOGMASKED(LOG_COMMAND, "resend\n");
+ return;
+ }
+ else
+ m_tx_len = 0;
+
+ // special case for wrap mode
+ if ((m_mode & WRAP) && command != 0xff && command != 0xec)
+ {
+ // echo the command
+ m_tx_buf[m_tx_len++] = command;
+ return;
+ }
+
+ // handle the command
+ switch (command)
+ {
+ case 0xe6: // set scaling 1:1
+ LOGMASKED(LOG_COMMAND, "set scaling 1:1\n");
+ m_mode &= ~SCALE;
+ m_tx_buf[m_tx_len++] = 0xfa;
+ break;
+
+ case 0xe7: // set scaling 2:1
+ LOGMASKED(LOG_COMMAND, "set scaling 2:1\n");
+ m_mode |= SCALE;
+ m_tx_buf[m_tx_len++] = 0xfa;
+ break;
+
+ case 0xe8: // set resolution
+ if (m_rx_len == 1)
+ {
+ m_resolution = m_rx_buf[m_rx_len--];
+ LOGMASKED(LOG_COMMAND, "set resolution 0x%02x\n", m_resolution);
+ }
+ else
+ // re-enqueue the command and wait for the parameter
+ m_rx_len++;
+ m_tx_buf[m_tx_len++] = 0xfa;
+ break;
+
+ case 0xe9: // status request
+ LOGMASKED(LOG_COMMAND, "status request\n");
+ m_tx_buf[m_tx_len++] = 0xfa;
+ m_tx_buf[m_tx_len++] = m_mode | m_port_buttons->read();
+ m_tx_buf[m_tx_len++] = m_resolution;
+ m_tx_buf[m_tx_len++] = m_sample_rate;
+ break;
+
+ case 0xea: // set stream mode
+ LOGMASKED(LOG_COMMAND, "set stream mode\n");
+ m_mode &= ~REMOTE;
+ m_tx_buf[m_tx_len++] = 0xfa;
+ break;
+
+ case 0xeb: // read data
+ LOGMASKED(LOG_COMMAND, "read data\n");
+ m_tx_buf[m_tx_len++] = 0xfa;
+
+ // force data sample after acknowledge transmitted
+ m_sample->adjust(serial_cycle * 48, 1);
+ break;
+
+ case 0xec: // reset wrap mode
+ LOGMASKED(LOG_COMMAND, "reset wrap mode\n");
+ m_mode &= ~WRAP;
+ m_tx_buf[m_tx_len++] = 0xfa;
+ break;
+
+ case 0xee: // set wrap mode
+ LOGMASKED(LOG_COMMAND, "set wrap mode\n");
+ m_mode |= WRAP;
+ m_tx_buf[m_tx_len++] = 0xfa;
+ break;
+
+ case 0xf0: // set remote mode
+ LOGMASKED(LOG_COMMAND, "set remote mode\n");
+ m_mode |= REMOTE;
+ m_tx_buf[m_tx_len++] = 0xfa;
+ break;
+
+ case 0xf2: // get device id
+ LOGMASKED(LOG_COMMAND, "get device id\n");
+ m_tx_buf[m_tx_len++] = 0xfa;
+ m_tx_buf[m_tx_len++] = 0x00;
+ break;
+
+ case 0xf3: // set sample rate
+ if (m_rx_len == 1)
+ {
+ m_sample_rate = m_rx_buf[m_rx_len--];
+ LOGMASKED(LOG_COMMAND, "set sample rate %d\n", m_sample_rate);
+ }
+ else
+ // re-enqueue the command and wait for the parameter
+ m_rx_len++;
+ m_tx_buf[m_tx_len++] = 0xfa;
+ break;
+
+ case 0xf4: // enable data reporting
+ LOGMASKED(LOG_COMMAND, "enable data reporting\n");
+ m_mode |= ENABLE;
+ m_sample->adjust(attotime::from_hz(m_sample_rate), 0, attotime::from_hz(m_sample_rate));
+ m_tx_buf[m_tx_len++] = 0xfa;
+ break;
+
+ case 0xf5: // disable data reporting
+ LOGMASKED(LOG_COMMAND, "disable data reporting\n");
+ m_mode &= ~ENABLE;
+ m_sample->enable(false);
+ m_tx_buf[m_tx_len++] = 0xfa;
+ break;
+
+ case 0xf6: // set defaults
+ LOGMASKED(LOG_COMMAND, "set defaults\n");
+ defaults();
+ m_tx_buf[m_tx_len++] = 0xfa;
+ break;
+
+ case 0xff: // reset
+ LOGMASKED(LOG_COMMAND, "reset\n");
+ defaults();
+ m_tx_buf[m_tx_len++] = 0xfa;
+ m_tx_buf[m_tx_len++] = 0xaa; // bat successful
+ m_tx_buf[m_tx_len++] = 0x00; // device id
+ break;
+
+ default:
+ LOGMASKED(LOG_COMMAND, "unrecognized command 0x%02x\n", command);
+ m_tx_buf[m_tx_len++] = 0xfc; // error
+ break;
+ }
+}
+
+void hle_ps2_mouse_device::sample(void *ptr, s32 param)
+{
+ // read mouse state
+ s16 const x = m_port_x_axis->read();
+ s16 const y = m_port_y_axis->read();
+ u8 const b = m_port_buttons->read();
+
+ // compute delta
+ s16 dx = x - m_mouse_x;
+ s16 dy = m_mouse_y - y;
+ u8 const db = b ^ m_mouse_b;
+
+ // data report if transmit buffer empty and position or buttons changed
+ if ((m_tx_pos == m_tx_len) && (dx || dy || db || param))
+ {
+ LOGMASKED(LOG_REPORT, "data report dx %d dy %d db %d\n", dx, dy, db);
+
+ // compute sign and overflow
+ u8 const sx = (dx < 0) ? 0x10 : 0x00;
+ u8 const sy = (dy < 0) ? 0x20 : 0x00;
+ u8 const ox = ((dx < -256) || (dx > 255)) ? 0x40 : 0x00;
+ u8 const oy = ((dy < -256) || (dy > 255)) ? 0x80 : 0x00;
+
+ // apply scaling
+ if (m_mode & SCALE)
+ {
+ static s32 const scale[] = { -9, -6, -3, -1, -1, 0, 1, 1, 3, 6, 9 };
+
+ dx = (std::abs(dx) > 5) ? dx * 2 : scale[dx + 5];
+ dy = (std::abs(dy) > 5) ? dy * 2 : scale[dy + 5];
+ }
+
+ // transmit data report
+ m_tx_len = 0;
+ m_tx_buf[m_tx_len++] = oy | ox | sy | sx | 0x08 | b;
+ m_tx_buf[m_tx_len++] = u8(dx);
+ m_tx_buf[m_tx_len++] = u8(dy);
+ m_tx_pos = 0;
+
+ // record mouse state
+ m_mouse_x = x;
+ m_mouse_y = y;
+ m_mouse_b = b;
+
+ // resume serial communication
+ resume();
+ }
+}
+
+void hle_ps2_mouse_device::defaults()
+{
+ m_mode = 0;
+ m_sample_rate = 100;
+ m_resolution = 2;
+}
+
+void hle_ps2_mouse_device::update()
+{
+ m_mouse_x = m_port_x_axis->read();
+ m_mouse_y = m_port_y_axis->read();
+ m_mouse_b = m_port_buttons->read();
+}
diff --git a/src/devices/bus/pc_kbd/hle_mouse.h b/src/devices/bus/pc_kbd/hle_mouse.h
new file mode 100644
index 00000000000..d4222a35fd2
--- /dev/null
+++ b/src/devices/bus/pc_kbd/hle_mouse.h
@@ -0,0 +1,98 @@
+// license:BSD-3-Clause
+// copyright-holders:Patrick Mackinlay
+
+#ifndef MAME_BUS_HLE_PS2_MOUSE_H
+#define MAME_BUS_HLE_PS2_MOUSE_H
+
+#pragma once
+
+#include "pc_kbdc.h"
+
+class hle_ps2_mouse_device
+ : public device_t
+ , public device_pc_kbd_interface
+{
+public:
+ hle_ps2_mouse_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
+
+protected:
+ // device_t overrides
+ virtual ioport_constructor device_input_ports() const override;
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+ // device_pc_kbd_interface overrides
+ virtual void clock_write(int state) override;
+
+private:
+ bool const clock_held(unsigned usec) { return (machine().time() - m_clock_changed) >= attotime::from_usec(usec); }
+
+ void resume();
+ TIMER_CALLBACK_MEMBER(serial);
+ void command(u8 const command);
+
+ TIMER_CALLBACK_MEMBER(sample);
+ void defaults();
+ void update();
+
+ required_ioport m_port_x_axis;
+ required_ioport m_port_y_axis;
+ required_ioport m_port_buttons;
+
+ emu_timer *m_serial;
+ emu_timer *m_sample;
+
+ attotime m_clock_changed;
+
+ // serial state
+ enum serial_state : unsigned
+ {
+ IDLE,
+ COMMAND,
+
+ RX_START,
+ RX_CLOCK_LO0,
+ RX_CLOCK_LO1,
+ RX_CLOCK_HI0,
+ RX_CLOCK_HI1,
+
+ TX_START,
+ TX_CLOCK_LO0,
+ TX_CLOCK_LO1,
+ TX_CLOCK_HI0,
+ TX_CLOCK_HI1,
+ }
+ m_state;
+ unsigned m_bit;
+
+ // setting state
+ enum mode_mask : u8
+ {
+ SCALE = 0x10, // 2:1 scaling
+ ENABLE = 0x20, // data reporting
+ REMOTE = 0x40,
+ WRAP = 0x80,
+ };
+ u8 m_mode;
+ u8 m_sample_rate;
+ u8 m_resolution;
+
+ // tx/rx state
+ unsigned m_rx_len;
+ u8 m_rx_buf[2];
+ unsigned m_tx_len;
+ unsigned m_tx_pos;
+ u8 m_tx_buf[4];
+ u8 m_data;
+ unsigned m_parity;
+
+ // mouse state
+ s16 m_mouse_x;
+ s16 m_mouse_y;
+ u8 m_mouse_b;
+};
+
+// device type definition
+DECLARE_DEVICE_TYPE(HLE_PS2_MOUSE, hle_ps2_mouse_device)
+
+#endif // MAME_BUS_HLE_PS2_MOUSE_H
diff --git a/src/devices/bus/pc_kbd/keyboards.cpp b/src/devices/bus/pc_kbd/keyboards.cpp
index 98be3e2e304..ff6e5a65d0a 100644
--- a/src/devices/bus/pc_kbd/keyboards.cpp
+++ b/src/devices/bus/pc_kbd/keyboards.cpp
@@ -11,6 +11,7 @@
#include "pcxt83.h"
#include "pcat84.h"
#include "pcat101.h"
+#include "hle_mouse.h"
void pc_xt_keyboards(device_slot_interface &device)
{
@@ -30,3 +31,8 @@ void pc_at_keyboards(device_slot_interface &device)
device.option_add(STR_KBD_IBM_3270PC_122, PC_KBD_IBM_3270PC_122);
device.option_add(STR_KBD_IBM_PC_AT_101, PC_KBD_IBM_PC_AT_101);
}
+
+void ps2_mice(device_slot_interface &device)
+{
+ device.option_add(STR_HLE_PS2_MOUSE, HLE_PS2_MOUSE);
+}
diff --git a/src/devices/bus/pc_kbd/keyboards.h b/src/devices/bus/pc_kbd/keyboards.h
index ca106926499..408a98628c4 100644
--- a/src/devices/bus/pc_kbd/keyboards.h
+++ b/src/devices/bus/pc_kbd/keyboards.h
@@ -31,4 +31,9 @@ void pc_xt_keyboards(device_slot_interface &device);
void pc_at_keyboards(device_slot_interface &device);
+// PS/2 protocol mice
+#define STR_HLE_PS2_MOUSE "hle_ps2_mouse"
+
+void ps2_mice(device_slot_interface &device);
+
#endif // MAME_BUS_PC_KBD_KEYBOARDS_H