summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Input
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
commitf8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch)
tree26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Devices/Input
parentInitial commit. (diff)
downloadvirtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz
virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Input')
-rw-r--r--src/VBox/Devices/Input/DevPS2.cpp1103
-rw-r--r--src/VBox/Devices/Input/DrvKeyboardQueue.cpp539
-rw-r--r--src/VBox/Devices/Input/DrvMouseQueue.cpp465
-rw-r--r--src/VBox/Devices/Input/Makefile.kup0
-rw-r--r--src/VBox/Devices/Input/PS2Dev.h94
-rw-r--r--src/VBox/Devices/Input/PS2K.cpp1661
-rw-r--r--src/VBox/Devices/Input/PS2M.cpp1293
-rw-r--r--src/VBox/Devices/Input/UsbKbd.cpp1358
-rw-r--r--src/VBox/Devices/Input/UsbMouse.cpp2452
-rw-r--r--src/VBox/Devices/Input/testcase/Makefile.kmk34
-rw-r--r--src/VBox/Devices/Input/testcase/tstUsbMouse.cpp374
11 files changed, 9373 insertions, 0 deletions
diff --git a/src/VBox/Devices/Input/DevPS2.cpp b/src/VBox/Devices/Input/DevPS2.cpp
new file mode 100644
index 00000000..41d756a1
--- /dev/null
+++ b/src/VBox/Devices/Input/DevPS2.cpp
@@ -0,0 +1,1103 @@
+/* $Id: DevPS2.cpp $ */
+/** @file
+ * DevPS2 - PS/2 keyboard & mouse controller device.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ * --------------------------------------------------------------------
+ *
+ * This code is based on:
+ *
+ * QEMU PC keyboard emulation (revision 1.12)
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_KBD
+#include <VBox/vmm/pdmdev.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+#include "PS2Dev.h"
+
+/* Do not remove this (unless eliminating the corresponding ifdefs), it will
+ * cause instant triple faults when booting Windows VMs. */
+#define TARGET_I386
+
+#define PCKBD_SAVED_STATE_VERSION 8
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+RT_C_DECLS_BEGIN
+PDMBOTHCBDECL(int) kbdIOPortDataRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb);
+PDMBOTHCBDECL(int) kbdIOPortDataWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+PDMBOTHCBDECL(int) kbdIOPortStatusRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb);
+PDMBOTHCBDECL(int) kbdIOPortCommandWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb);
+RT_C_DECLS_END
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
+
+/* debug PC keyboard */
+#define DEBUG_KBD
+
+/* debug PC keyboard : only mouse */
+#define DEBUG_MOUSE
+
+/* Keyboard Controller Commands */
+#define KBD_CCMD_READ_MODE 0x20 /* Read mode bits */
+#define KBD_CCMD_WRITE_MODE 0x60 /* Write mode bits */
+#define KBD_CCMD_GET_VERSION 0xA1 /* Get controller version */
+#define KBD_CCMD_MOUSE_DISABLE 0xA7 /* Disable mouse interface */
+#define KBD_CCMD_MOUSE_ENABLE 0xA8 /* Enable mouse interface */
+#define KBD_CCMD_TEST_MOUSE 0xA9 /* Mouse interface test */
+#define KBD_CCMD_SELF_TEST 0xAA /* Controller self test */
+#define KBD_CCMD_KBD_TEST 0xAB /* Keyboard interface test */
+#define KBD_CCMD_KBD_DISABLE 0xAD /* Keyboard interface disable */
+#define KBD_CCMD_KBD_ENABLE 0xAE /* Keyboard interface enable */
+#define KBD_CCMD_READ_INPORT 0xC0 /* read input port */
+#define KBD_CCMD_READ_OUTPORT 0xD0 /* read output port */
+#define KBD_CCMD_WRITE_OUTPORT 0xD1 /* write output port */
+#define KBD_CCMD_WRITE_OBUF 0xD2
+#define KBD_CCMD_WRITE_AUX_OBUF 0xD3 /* Write to output buffer as if
+ initiated by the auxiliary device */
+#define KBD_CCMD_WRITE_MOUSE 0xD4 /* Write the following byte to the mouse */
+#define KBD_CCMD_DISABLE_A20 0xDD /* HP vectra only ? */
+#define KBD_CCMD_ENABLE_A20 0xDF /* HP vectra only ? */
+#define KBD_CCMD_READ_TSTINP 0xE0 /* Read test inputs T0, T1 */
+#define KBD_CCMD_RESET_ALT 0xF0
+#define KBD_CCMD_RESET 0xFE
+
+/* Status Register Bits */
+#define KBD_STAT_OBF 0x01 /* Keyboard output buffer full */
+#define KBD_STAT_IBF 0x02 /* Keyboard input buffer full */
+#define KBD_STAT_SELFTEST 0x04 /* Self test successful */
+#define KBD_STAT_CMD 0x08 /* Last write was a command write (0=data) */
+#define KBD_STAT_UNLOCKED 0x10 /* Zero if keyboard locked */
+#define KBD_STAT_MOUSE_OBF 0x20 /* Mouse output buffer full */
+#define KBD_STAT_GTO 0x40 /* General receive/xmit timeout */
+#define KBD_STAT_PERR 0x80 /* Parity error */
+
+/* Controller Mode Register Bits */
+#define KBD_MODE_KBD_INT 0x01 /* Keyboard data generate IRQ1 */
+#define KBD_MODE_MOUSE_INT 0x02 /* Mouse data generate IRQ12 */
+#define KBD_MODE_SYS 0x04 /* The system flag (?) */
+#define KBD_MODE_NO_KEYLOCK 0x08 /* The keylock doesn't affect the keyboard if set */
+#define KBD_MODE_DISABLE_KBD 0x10 /* Disable keyboard interface */
+#define KBD_MODE_DISABLE_MOUSE 0x20 /* Disable mouse interface */
+#define KBD_MODE_KCC 0x40 /* Scan code conversion to PC format */
+#define KBD_MODE_RFU 0x80
+
+
+/**
+ * The keyboard controller/device state.
+ *
+ * @note We use the default critical section for serialize data access.
+ */
+typedef struct KBDState
+{
+ uint8_t write_cmd; /* if non zero, write data to port 60 is expected */
+ uint8_t status;
+ uint8_t mode;
+ uint8_t dbbout; /* data buffer byte */
+ /* keyboard state */
+ int32_t translate;
+ int32_t xlat_state;
+
+ /** Pointer to the device instance - RC. */
+ PPDMDEVINSRC pDevInsRC;
+ /** Pointer to the device instance - R3 . */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to the device instance. */
+ PPDMDEVINSR0 pDevInsR0;
+
+ /** Keyboard state (implemented in separate PS2K module). */
+#ifdef VBOX_DEVICE_STRUCT_TESTCASE
+ uint8_t KbdFiller[PS2K_STRUCT_FILLER];
+#else
+ PS2K Kbd;
+#endif
+
+ /** Mouse state (implemented in separate PS2M module). */
+#ifdef VBOX_DEVICE_STRUCT_TESTCASE
+ uint8_t AuxFiller[PS2M_STRUCT_FILLER];
+#else
+ PS2M Aux;
+#endif
+} KBDState;
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+/* update irq and KBD_STAT_[MOUSE_]OBF */
+static void kbd_update_irq(KBDState *s)
+{
+ int irq12_level, irq1_level;
+ uint8_t val;
+
+ irq1_level = 0;
+ irq12_level = 0;
+
+ /* Determine new OBF state, but only if OBF is clear. If OBF was already
+ * set, we cannot risk changing the event type after an ISR potentially
+ * started executing! Only kbd_read_data() clears the OBF bits.
+ */
+ if (!(s->status & KBD_STAT_OBF)) {
+ s->status &= ~KBD_STAT_MOUSE_OBF;
+ /* Keyboard data has priority if both kbd and aux data is available. */
+ if (!(s->mode & KBD_MODE_DISABLE_KBD) && PS2KByteFromKbd(&s->Kbd, &val) == VINF_SUCCESS)
+ {
+ bool fHaveData = true;
+
+ /* If scancode translation is on (it usually is), there's more work to do. */
+ if (s->translate)
+ {
+ uint8_t xlated_val;
+
+ s->xlat_state = XlateAT2PC(s->xlat_state, val, &xlated_val);
+ val = xlated_val;
+
+ /* If the translation state is XS_BREAK, there's nothing to report
+ * and we keep going until the state changes or there's no more data.
+ */
+ while (s->xlat_state == XS_BREAK && PS2KByteFromKbd(&s->Kbd, &val) == VINF_SUCCESS)
+ {
+ s->xlat_state = XlateAT2PC(s->xlat_state, val, &xlated_val);
+ val = xlated_val;
+ }
+ /* This can happen if the last byte in the queue is F0... */
+ if (s->xlat_state == XS_BREAK)
+ fHaveData = false;
+ }
+ if (fHaveData)
+ {
+ s->dbbout = val;
+ s->status |= KBD_STAT_OBF;
+ }
+ }
+ else if (!(s->mode & KBD_MODE_DISABLE_MOUSE) && PS2MByteFromAux(&s->Aux, &val) == VINF_SUCCESS)
+ {
+ s->dbbout = val;
+ s->status |= KBD_STAT_OBF | KBD_STAT_MOUSE_OBF;
+ }
+ }
+ /* Determine new IRQ state. */
+ if (s->status & KBD_STAT_OBF) {
+ if (s->status & KBD_STAT_MOUSE_OBF)
+ {
+ if (s->mode & KBD_MODE_MOUSE_INT)
+ irq12_level = 1;
+ }
+ else
+ { /* KBD_STAT_OBF set but KBD_STAT_MOUSE_OBF isn't. */
+ if (s->mode & KBD_MODE_KBD_INT)
+ irq1_level = 1;
+ }
+ }
+ PDMDevHlpISASetIrq(s->CTX_SUFF(pDevIns), 1, irq1_level);
+ PDMDevHlpISASetIrq(s->CTX_SUFF(pDevIns), 12, irq12_level);
+}
+
+void KBCUpdateInterrupts(void *pKbc)
+{
+ KBDState *s = (KBDState *)pKbc;
+ kbd_update_irq(s);
+}
+
+static void kbc_dbb_out(void *opaque, uint8_t val)
+{
+ KBDState *s = (KBDState*)opaque;
+
+ s->dbbout = val;
+ /* Set the OBF and raise IRQ. */
+ s->status |= KBD_STAT_OBF;
+ if (s->mode & KBD_MODE_KBD_INT)
+ PDMDevHlpISASetIrq(s->CTX_SUFF(pDevIns), 1, 1);
+}
+
+static void kbc_dbb_out_aux(void *opaque, uint8_t val)
+{
+ KBDState *s = (KBDState*)opaque;
+
+ s->dbbout = val;
+ /* Set the aux OBF and raise IRQ. */
+ s->status |= KBD_STAT_OBF | KBD_STAT_MOUSE_OBF;
+ if (s->mode & KBD_MODE_MOUSE_INT)
+ PDMDevHlpISASetIrq(s->CTX_SUFF(pDevIns), 12, PDM_IRQ_LEVEL_HIGH);
+}
+
+static uint32_t kbd_read_status(void *opaque, uint32_t addr)
+{
+ KBDState *s = (KBDState*)opaque;
+ int val = s->status;
+ NOREF(addr);
+
+#if defined(DEBUG_KBD)
+ Log(("kbd: read status=0x%02x\n", val));
+#endif
+ return val;
+}
+
+static int kbd_write_command(void *opaque, uint32_t addr, uint32_t val)
+{
+ int rc = VINF_SUCCESS;
+ KBDState *s = (KBDState*)opaque;
+ NOREF(addr);
+
+#ifdef DEBUG_KBD
+ Log(("kbd: write cmd=0x%02x\n", val));
+#endif
+ switch(val) {
+ case KBD_CCMD_READ_MODE:
+ kbc_dbb_out(s, s->mode);
+ break;
+ case KBD_CCMD_WRITE_MODE:
+ case KBD_CCMD_WRITE_OBUF:
+ case KBD_CCMD_WRITE_AUX_OBUF:
+ case KBD_CCMD_WRITE_MOUSE:
+ case KBD_CCMD_WRITE_OUTPORT:
+ s->write_cmd = val;
+ break;
+ case KBD_CCMD_MOUSE_DISABLE:
+ s->mode |= KBD_MODE_DISABLE_MOUSE;
+ break;
+ case KBD_CCMD_MOUSE_ENABLE:
+ s->mode &= ~KBD_MODE_DISABLE_MOUSE;
+ /* Check for queued input. */
+ kbd_update_irq(s);
+ break;
+ case KBD_CCMD_TEST_MOUSE:
+ kbc_dbb_out(s, 0x00);
+ break;
+ case KBD_CCMD_SELF_TEST:
+ /* Enable the A20 line - that is the power-on state(!). */
+# ifndef IN_RING3
+ if (!PDMDevHlpA20IsEnabled(s->CTX_SUFF(pDevIns)))
+ {
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ break;
+ }
+# else /* IN_RING3 */
+ PDMDevHlpA20Set(s->CTX_SUFF(pDevIns), true);
+# endif /* IN_RING3 */
+ s->status |= KBD_STAT_SELFTEST;
+ s->mode |= KBD_MODE_DISABLE_KBD;
+ kbc_dbb_out(s, 0x55);
+ break;
+ case KBD_CCMD_KBD_TEST:
+ kbc_dbb_out(s, 0x00);
+ break;
+ case KBD_CCMD_KBD_DISABLE:
+ s->mode |= KBD_MODE_DISABLE_KBD;
+ break;
+ case KBD_CCMD_KBD_ENABLE:
+ s->mode &= ~KBD_MODE_DISABLE_KBD;
+ /* Check for queued input. */
+ kbd_update_irq(s);
+ break;
+ case KBD_CCMD_READ_INPORT:
+ kbc_dbb_out(s, 0xBF);
+ break;
+ case KBD_CCMD_READ_OUTPORT:
+ /* XXX: check that */
+#ifdef TARGET_I386
+ val = 0x01 | (PDMDevHlpA20IsEnabled(s->CTX_SUFF(pDevIns)) << 1);
+#else
+ val = 0x01;
+#endif
+ if (s->status & KBD_STAT_OBF)
+ val |= 0x10;
+ if (s->status & KBD_STAT_MOUSE_OBF)
+ val |= 0x20;
+ kbc_dbb_out(s, val);
+ break;
+#ifdef TARGET_I386
+ case KBD_CCMD_ENABLE_A20:
+# ifndef IN_RING3
+ if (!PDMDevHlpA20IsEnabled(s->CTX_SUFF(pDevIns)))
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+# else /* IN_RING3 */
+ PDMDevHlpA20Set(s->CTX_SUFF(pDevIns), true);
+# endif /* IN_RING3 */
+ break;
+ case KBD_CCMD_DISABLE_A20:
+# ifndef IN_RING3
+ if (PDMDevHlpA20IsEnabled(s->CTX_SUFF(pDevIns)))
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+# else /* IN_RING3 */
+ PDMDevHlpA20Set(s->CTX_SUFF(pDevIns), false);
+# endif /* !IN_RING3 */
+ break;
+#endif
+ case KBD_CCMD_READ_TSTINP:
+ /* Keyboard clock line is zero IFF keyboard is disabled */
+ val = (s->mode & KBD_MODE_DISABLE_KBD) ? 0 : 1;
+ kbc_dbb_out(s, val);
+ break;
+ case KBD_CCMD_RESET:
+ case KBD_CCMD_RESET_ALT:
+#ifndef IN_RING3
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#else /* IN_RING3 */
+ LogRel(("Reset initiated by keyboard controller\n"));
+ rc = PDMDevHlpVMReset(s->CTX_SUFF(pDevIns), PDMVMRESET_F_KBD);
+#endif /* !IN_RING3 */
+ break;
+ case 0xff:
+ /* ignore that - I don't know what is its use */
+ break;
+ /* Make OS/2 happy. */
+ /* The 8042 RAM is readable using commands 0x20 thru 0x3f, and writable
+ by 0x60 thru 0x7f. Now days only the first byte, the mode, is used.
+ We'll ignore the writes (0x61..7f) and return 0 for all the reads
+ just to make some OS/2 debug stuff a bit happier. */
+ case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
+ case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f:
+ case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
+ case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f:
+ kbc_dbb_out(s, 0);
+ Log(("kbd: reading non-standard RAM addr %#x\n", val & 0x1f));
+ break;
+ default:
+ Log(("kbd: unsupported keyboard cmd=0x%02x\n", val));
+ break;
+ }
+ return rc;
+}
+
+static uint32_t kbd_read_data(void *opaque, uint32_t addr)
+{
+ KBDState *s = (KBDState*)opaque;
+ uint32_t val;
+ NOREF(addr);
+
+ /* Return the current DBB contents. */
+ val = s->dbbout;
+
+ /* Reading the DBB deasserts IRQs... */
+ if (s->status & KBD_STAT_MOUSE_OBF)
+ PDMDevHlpISASetIrq(s->CTX_SUFF(pDevIns), 12, 0);
+ else
+ PDMDevHlpISASetIrq(s->CTX_SUFF(pDevIns), 1, 0);
+ /* ...and clears the OBF bits. */
+ s->status &= ~(KBD_STAT_OBF | KBD_STAT_MOUSE_OBF);
+
+ /* Check if more data is available. */
+ kbd_update_irq(s);
+#ifdef DEBUG_KBD
+ Log(("kbd: read data=0x%02x\n", val));
+#endif
+ return val;
+}
+
+PS2K *KBDGetPS2KFromDevIns(PPDMDEVINS pDevIns)
+{
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+ return &pThis->Kbd;
+}
+
+PS2M *KBDGetPS2MFromDevIns(PPDMDEVINS pDevIns)
+{
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+ return &pThis->Aux;
+}
+
+static int kbd_write_data(void *opaque, uint32_t addr, uint32_t val)
+{
+ int rc = VINF_SUCCESS;
+ KBDState *s = (KBDState*)opaque;
+ NOREF(addr);
+
+#ifdef DEBUG_KBD
+ Log(("kbd: write data=0x%02x\n", val));
+#endif
+
+ switch(s->write_cmd) {
+ case 0:
+ /* Automatically enables keyboard interface. */
+ s->mode &= ~KBD_MODE_DISABLE_KBD;
+ rc = PS2KByteToKbd(&s->Kbd, val);
+ if (rc == VINF_SUCCESS)
+ kbd_update_irq(s);
+ break;
+ case KBD_CCMD_WRITE_MODE:
+ s->mode = val;
+ s->translate = (s->mode & KBD_MODE_KCC) == KBD_MODE_KCC;
+ kbd_update_irq(s);
+ break;
+ case KBD_CCMD_WRITE_OBUF:
+ kbc_dbb_out(s, val);
+ break;
+ case KBD_CCMD_WRITE_AUX_OBUF:
+ kbc_dbb_out_aux(s, val);
+ break;
+ case KBD_CCMD_WRITE_OUTPORT:
+#ifdef TARGET_I386
+# ifndef IN_RING3
+ if (PDMDevHlpA20IsEnabled(s->CTX_SUFF(pDevIns)) != !!(val & 2))
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+# else /* IN_RING3 */
+ PDMDevHlpA20Set(s->CTX_SUFF(pDevIns), !!(val & 2));
+# endif /* !IN_RING3 */
+#endif
+ if (!(val & 1)) {
+# ifndef IN_RING3
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+# else
+ rc = PDMDevHlpVMReset(s->CTX_SUFF(pDevIns), PDMVMRESET_F_KBD);
+# endif
+ }
+ break;
+ case KBD_CCMD_WRITE_MOUSE:
+ /* Automatically enables aux interface. */
+ s->mode &= ~KBD_MODE_DISABLE_MOUSE;
+ rc = PS2MByteToAux(&s->Aux, val);
+ if (rc == VINF_SUCCESS)
+ kbd_update_irq(s);
+ break;
+ default:
+ break;
+ }
+ if (rc != VINF_IOM_R3_IOPORT_WRITE)
+ s->write_cmd = 0;
+ return rc;
+}
+
+#ifdef IN_RING3
+
+static void kbd_reset(void *opaque)
+{
+ KBDState *s = (KBDState*)opaque;
+ s->mode = KBD_MODE_KBD_INT | KBD_MODE_MOUSE_INT;
+ s->status = KBD_STAT_CMD | KBD_STAT_UNLOCKED;
+ /* Resetting everything, keyword was not working right on NT4 reboot. */
+ s->write_cmd = 0;
+ s->translate = 0;
+}
+
+static void kbd_save(PSSMHANDLE pSSM, KBDState *s)
+{
+ SSMR3PutU8(pSSM, s->write_cmd);
+ SSMR3PutU8(pSSM, s->status);
+ SSMR3PutU8(pSSM, s->mode);
+ SSMR3PutU8(pSSM, s->dbbout);
+
+ /* terminator */
+ SSMR3PutU32(pSSM, UINT32_MAX);
+}
+
+static int kbd_load(PSSMHANDLE pSSM, KBDState *s, uint32_t version_id)
+{
+ uint32_t u32, i;
+ uint8_t u8Dummy;
+ uint32_t u32Dummy;
+ int rc;
+
+#if 0
+ /** @todo enable this and remove the "if (version_id == 4)" code at some
+ * later time */
+ /* Version 4 was never created by any publicly released version of VBox */
+ AssertReturn(version_id != 4, VERR_NOT_SUPPORTED);
+#endif
+ if (version_id < 2 || version_id > PCKBD_SAVED_STATE_VERSION)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ SSMR3GetU8(pSSM, &s->write_cmd);
+ SSMR3GetU8(pSSM, &s->status);
+ SSMR3GetU8(pSSM, &s->mode);
+ if (version_id <= 5)
+ {
+ SSMR3GetU32(pSSM, (uint32_t *)&u32Dummy);
+ SSMR3GetU32(pSSM, (uint32_t *)&u32Dummy);
+ }
+ else
+ {
+ SSMR3GetU8(pSSM, &s->dbbout);
+ }
+ if (version_id <= 7)
+ {
+ int32_t i32Dummy;
+ uint8_t u8State;
+ uint8_t u8Rate;
+ uint8_t u8Proto;
+
+ SSMR3GetU32(pSSM, &u32Dummy);
+ SSMR3GetU8(pSSM, &u8State);
+ SSMR3GetU8(pSSM, &u8Dummy);
+ SSMR3GetU8(pSSM, &u8Rate);
+ SSMR3GetU8(pSSM, &u8Dummy);
+ SSMR3GetU8(pSSM, &u8Proto);
+ SSMR3GetU8(pSSM, &u8Dummy);
+ SSMR3GetS32(pSSM, &i32Dummy);
+ SSMR3GetS32(pSSM, &i32Dummy);
+ SSMR3GetS32(pSSM, &i32Dummy);
+ if (version_id > 2)
+ {
+ SSMR3GetS32(pSSM, &i32Dummy);
+ SSMR3GetS32(pSSM, &i32Dummy);
+ }
+ rc = SSMR3GetU8(pSSM, &u8Dummy);
+ if (version_id == 4)
+ {
+ SSMR3GetU32(pSSM, &u32Dummy);
+ rc = SSMR3GetU32(pSSM, &u32Dummy);
+ }
+ if (version_id > 3)
+ rc = SSMR3GetU8(pSSM, &u8Dummy);
+ if (version_id == 4)
+ rc = SSMR3GetU8(pSSM, &u8Dummy);
+ AssertLogRelRCReturn(rc, rc);
+
+ PS2MFixupState(&s->Aux, u8State, u8Rate, u8Proto);
+ }
+
+ /* Determine the translation state. */
+ s->translate = (s->mode & KBD_MODE_KCC) == KBD_MODE_KCC;
+
+ /*
+ * Load the queues
+ */
+ if (version_id <= 5)
+ {
+ rc = SSMR3GetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ for (i = 0; i < u32; i++)
+ {
+ rc = SSMR3GetU8(pSSM, &u8Dummy);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ Log(("kbd_load: %d keyboard queue items discarded from old saved state\n", u32));
+ }
+
+ if (version_id <= 7)
+ {
+ rc = SSMR3GetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ for (i = 0; i < u32; i++)
+ {
+ rc = SSMR3GetU8(pSSM, &u8Dummy);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ Log(("kbd_load: %d mouse event queue items discarded from old saved state\n", u32));
+
+ rc = SSMR3GetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ for (i = 0; i < u32; i++)
+ {
+ rc = SSMR3GetU8(pSSM, &u8Dummy);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ Log(("kbd_load: %d mouse command queue items discarded from old saved state\n", u32));
+ }
+
+ /* terminator */
+ rc = SSMR3GetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (u32 != ~0U)
+ {
+ AssertMsgFailed(("u32=%#x\n", u32));
+ return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ }
+ return 0;
+}
+#endif /* IN_RING3 */
+
+
+/* VirtualBox code start */
+
+/* -=-=-=-=-=- wrappers -=-=-=-=-=- */
+
+/**
+ * Port I/O Handler for keyboard data IN operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument - ignored.
+ * @param Port Port number used for the IN operation.
+ * @param pu32 Where to store the result.
+ * @param cb Number of bytes read.
+ */
+PDMBOTHCBDECL(int) kbdIOPortDataRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ uint32_t fluff = 0;
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+
+ NOREF(pvUser);
+ switch (cb) {
+ case 4:
+ fluff |= 0xffff0000; /* Crazy Apple (Darwin 6.0.2 and earlier). */
+ RT_FALL_THRU();
+ case 2:
+ fluff |= 0x0000ff00;
+ RT_FALL_THRU();
+ case 1:
+ *pu32 = fluff | kbd_read_data(pThis, Port);
+ Log2(("kbdIOPortDataRead: Port=%#x cb=%d *pu32=%#x\n", Port, cb, *pu32));
+ return VINF_SUCCESS;
+ default:
+ AssertMsgFailed(("Port=%#x cb=%d\n", Port, cb));
+ return VERR_IOM_IOPORT_UNUSED;
+ }
+}
+
+/**
+ * Port I/O Handler for keyboard data OUT operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument - ignored.
+ * @param Port Port number used for the IN operation.
+ * @param u32 The value to output.
+ * @param cb The value size in bytes.
+ */
+PDMBOTHCBDECL(int) kbdIOPortDataWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ int rc = VINF_SUCCESS;
+ NOREF(pvUser);
+ if (cb == 1 || cb == 2)
+ {
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+ rc = kbd_write_data(pThis, Port, (uint8_t)u32);
+ Log2(("kbdIOPortDataWrite: Port=%#x cb=%d u32=%#x\n", Port, cb, u32));
+ }
+ else
+ AssertMsgFailed(("Port=%#x cb=%d\n", Port, cb));
+ return rc;
+}
+
+/**
+ * Port I/O Handler for keyboard status IN operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument - ignored.
+ * @param Port Port number used for the IN operation.
+ * @param pu32 Where to store the result.
+ * @param cb Number of bytes read.
+ */
+PDMBOTHCBDECL(int) kbdIOPortStatusRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
+{
+ uint32_t fluff = 0;
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+
+ NOREF(pvUser);
+ switch (cb) {
+ case 4:
+ fluff |= 0xffff0000; /* Crazy Apple (Darwin 6.0.2 and earlier). */
+ RT_FALL_THRU();
+ case 2:
+ fluff |= 0x0000ff00;
+ RT_FALL_THRU();
+ case 1:
+ *pu32 = fluff | kbd_read_status(pThis, Port);
+ Log2(("kbdIOPortStatusRead: Port=%#x cb=%d -> *pu32=%#x\n", Port, cb, *pu32));
+ return VINF_SUCCESS;
+ default:
+ AssertMsgFailed(("Port=%#x cb=%d\n", Port, cb));
+ return VERR_IOM_IOPORT_UNUSED;
+ }
+}
+
+/**
+ * Port I/O Handler for keyboard command OUT operations.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument - ignored.
+ * @param Port Port number used for the IN operation.
+ * @param u32 The value to output.
+ * @param cb The value size in bytes.
+ */
+PDMBOTHCBDECL(int) kbdIOPortCommandWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
+{
+ int rc = VINF_SUCCESS;
+ NOREF(pvUser);
+ if (cb == 1 || cb == 2)
+ {
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+ rc = kbd_write_command(pThis, Port, (uint8_t)u32);
+ Log2(("kbdIOPortCommandWrite: Port=%#x cb=%d u32=%#x rc=%Rrc\n", Port, cb, u32, rc));
+ }
+ else
+ AssertMsgFailed(("Port=%#x cb=%d\n", Port, cb));
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Saves a state of the keyboard device.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pSSM The handle to save the state to.
+ */
+static DECLCALLBACK(int) kbdSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+ kbd_save(pSSM, pThis);
+ PS2KSaveState(&pThis->Kbd, pSSM);
+ PS2MSaveState(&pThis->Aux, pSSM);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Loads a saved keyboard device state.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pSSM The handle to the saved state.
+ * @param uVersion The data unit version number.
+ * @param uPass The data pass.
+ */
+static DECLCALLBACK(int) kbdLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+ int rc;
+
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+ rc = kbd_load(pSSM, pThis, uVersion);
+ if (uVersion >= 6)
+ rc = PS2KLoadState(&pThis->Kbd, pSSM, uVersion);
+ if (uVersion >= 8)
+ rc = PS2MLoadState(&pThis->Aux, pSSM, uVersion);
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADDONE, Key state fix-up after loading}
+ */
+static DECLCALLBACK(int) kbdLoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+ int rc;
+
+ rc = PS2KLoadDone(&pThis->Kbd, pSSM);
+ return rc;
+}
+
+/**
+ * Reset notification.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) kbdReset(PPDMDEVINS pDevIns)
+{
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+
+ kbd_reset(pThis);
+ PS2KReset(&pThis->Kbd);
+ PS2MReset(&pThis->Aux);
+}
+
+
+/* -=-=-=-=-=- real code -=-=-=-=-=- */
+
+
+/**
+ * Attach command.
+ *
+ * This is called to let the device attach to a driver for a specified LUN
+ * during runtime. This is not called during VM construction, the device
+ * constructor have to attach to all the available drivers.
+ *
+ * This is like plugging in the keyboard or mouse after turning on the PC.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ * @remark The keyboard controller doesn't support this action, this is just
+ * implemented to try out the driver<->device structure.
+ */
+static DECLCALLBACK(int) kbdAttach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ int rc;
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+
+ AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("PS/2 device does not support hotplugging\n"),
+ VERR_INVALID_PARAMETER);
+
+ switch (iLUN)
+ {
+ /* LUN #0: keyboard */
+ case 0:
+ rc = PS2KAttach(&pThis->Kbd, pDevIns, iLUN, fFlags);
+ if (RT_FAILURE(rc))
+ return rc;
+ break;
+
+ /* LUN #1: aux/mouse */
+ case 1:
+ rc = PS2MAttach(&pThis->Aux, pDevIns, iLUN, fFlags);
+ break;
+
+ default:
+ AssertMsgFailed(("Invalid LUN #%d\n", iLUN));
+ return VERR_PDM_NO_SUCH_LUN;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Detach notification.
+ *
+ * This is called when a driver is detaching itself from a LUN of the device.
+ * The device should adjust it's state to reflect this.
+ *
+ * This is like unplugging the network cable to use it for the laptop or
+ * something while the PC is still running.
+ *
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ * @remark The keyboard controller doesn't support this action, this is just
+ * implemented to try out the driver<->device structure.
+ */
+static DECLCALLBACK(void) kbdDetach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+#if 0
+ /*
+ * Reset the interfaces and update the controller state.
+ */
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+ switch (iLUN)
+ {
+ /* LUN #0: keyboard */
+ case 0:
+ pThis->Keyboard.pDrv = NULL;
+ pThis->Keyboard.pDrvBase = NULL;
+ break;
+
+ /* LUN #1: aux/mouse */
+ case 1:
+ pThis->Mouse.pDrv = NULL;
+ pThis->Mouse.pDrvBase = NULL;
+ break;
+
+ default:
+ AssertMsgFailed(("Invalid LUN #%d\n", iLUN));
+ break;
+ }
+#else
+ NOREF(pDevIns); NOREF(iLUN); NOREF(fFlags);
+#endif
+}
+
+
+/**
+ * @copydoc FNPDMDEVRELOCATE
+ */
+static DECLCALLBACK(void) kbdRelocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ PS2KRelocate(&pThis->Kbd, offDelta, pDevIns);
+ PS2MRelocate(&pThis->Aux, offDelta, pDevIns);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) kbdConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *);
+ int rc;
+ bool fGCEnabled;
+ bool fR0Enabled;
+ Assert(iInstance == 0);
+
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+
+ /*
+ * Validate and read the configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "GCEnabled\0R0Enabled\0KbdThrottleEnabled\0"))
+ return VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES;
+ rc = CFGMR3QueryBoolDef(pCfg, "GCEnabled", &fGCEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"GCEnabled\" from the config"));
+ rc = CFGMR3QueryBoolDef(pCfg, "R0Enabled", &fR0Enabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"R0Enabled\" from the config"));
+ Log(("pckbd: fGCEnabled=%RTbool fR0Enabled=%RTbool\n", fGCEnabled, fR0Enabled));
+
+
+ /*
+ * Initialize the interfaces.
+ */
+ pThis->pDevInsR3 = pDevIns;
+ pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+
+ rc = PS2KConstruct(&pThis->Kbd, pDevIns, pThis, iInstance, pCfg);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PS2MConstruct(&pThis->Aux, pDevIns, pThis, iInstance);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Register I/O ports, save state, keyboard event handler and mouse event handlers.
+ */
+ rc = PDMDevHlpIOPortRegister(pDevIns, 0x60, 1, NULL, kbdIOPortDataWrite, kbdIOPortDataRead, NULL, NULL, "PC Keyboard - Data");
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = PDMDevHlpIOPortRegister(pDevIns, 0x64, 1, NULL, kbdIOPortCommandWrite, kbdIOPortStatusRead, NULL, NULL, "PC Keyboard - Command / Status");
+ if (RT_FAILURE(rc))
+ return rc;
+ if (fGCEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, 0x60, 1, 0, "kbdIOPortDataWrite", "kbdIOPortDataRead", NULL, NULL, "PC Keyboard - Data");
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, 0x64, 1, 0, "kbdIOPortCommandWrite", "kbdIOPortStatusRead", NULL, NULL, "PC Keyboard - Command / Status");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ if (fR0Enabled)
+ {
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, 0x60, 1, 0, "kbdIOPortDataWrite", "kbdIOPortDataRead", NULL, NULL, "PC Keyboard - Data");
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, 0x64, 1, 0, "kbdIOPortCommandWrite", "kbdIOPortStatusRead", NULL, NULL, "PC Keyboard - Command / Status");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, PCKBD_SAVED_STATE_VERSION, sizeof(*pThis), NULL,
+ NULL, NULL, NULL,
+ NULL, kbdSaveExec, NULL,
+ NULL, kbdLoadExec, kbdLoadDone);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Attach to the keyboard and mouse drivers.
+ */
+ rc = kbdAttach(pDevIns, 0 /* keyboard LUN # */, PDM_TACH_FLAGS_NOT_HOT_PLUG);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = kbdAttach(pDevIns, 1 /* aux/mouse LUN # */, PDM_TACH_FLAGS_NOT_HOT_PLUG);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Initialize the device state.
+ */
+ kbdReset(pDevIns);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DevicePS2KeyboardMouse =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "pckbd",
+ /* szRCMod */
+ "VBoxDDRC.rc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "PS/2 Keyboard and Mouse device. Emulates both the keyboard, mouse and the keyboard controller. "
+ "LUN #0 is the keyboard connector. "
+ "LUN #1 is the aux/mouse connector.",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_HOST_BITS_DEFAULT | PDM_DEVREG_FLAGS_GUEST_BITS_32_64 | PDM_DEVREG_FLAGS_PAE36 | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0,
+ /* fClass */
+ PDM_DEVREG_CLASS_INPUT,
+ /* cMaxInstances */
+ 1,
+ /* cbInstance */
+ sizeof(KBDState),
+ /* pfnConstruct */
+ kbdConstruct,
+ /* pfnDestruct */
+ NULL,
+ /* pfnRelocate */
+ kbdRelocate,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ kbdReset,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ kbdAttach,
+ /* pfnDetach */
+ kbdDetach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+
+#endif /* IN_RING3 */
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
+
diff --git a/src/VBox/Devices/Input/DrvKeyboardQueue.cpp b/src/VBox/Devices/Input/DrvKeyboardQueue.cpp
new file mode 100644
index 00000000..44517a29
--- /dev/null
+++ b/src/VBox/Devices/Input/DrvKeyboardQueue.cpp
@@ -0,0 +1,539 @@
+/* $Id: DrvKeyboardQueue.cpp $ */
+/** @file
+ * VBox input devices: Keyboard queue driver
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_KBD_QUEUE
+#include <VBox/vmm/pdmdrv.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** Scancode translator state. */
+typedef enum {
+ SS_IDLE, /**< Starting state. */
+ SS_EXT, /**< E0 byte was received. */
+ SS_EXT1 /**< E1 byte was received. */
+} scan_state_t;
+
+/**
+ * Keyboard queue driver instance data.
+ *
+ * @implements PDMIKEYBOARDCONNECTOR
+ * @implements PDMIKEYBOARDPORT
+ */
+typedef struct DRVKBDQUEUE
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the keyboard port interface of the driver/device above us. */
+ PPDMIKEYBOARDPORT pUpPort;
+ /** Pointer to the keyboard port interface of the driver/device below us. */
+ PPDMIKEYBOARDCONNECTOR pDownConnector;
+ /** Our keyboard connector interface. */
+ PDMIKEYBOARDCONNECTOR IConnector;
+ /** Our keyboard port interface. */
+ PDMIKEYBOARDPORT IPort;
+ /** The queue handle. */
+ PPDMQUEUE pQueue;
+ /** State of the scancode translation. */
+ scan_state_t XlatState;
+ /** Discard input when this flag is set. */
+ bool fInactive;
+ /** When VM is suspended, queue full errors are not fatal. */
+ bool fSuspended;
+} DRVKBDQUEUE, *PDRVKBDQUEUE;
+
+
+/**
+ * Keyboard queue item.
+ */
+typedef struct DRVKBDQUEUEITEM
+{
+ /** The core part owned by the queue manager. */
+ PDMQUEUEITEMCORE Core;
+ /** The keycode. */
+ uint32_t u32UsageCode;
+} DRVKBDQUEUEITEM, *PDRVKBDQUEUEITEM;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** Lookup table for converting PC/XT scan codes to USB HID usage codes. */
+static const uint8_t aScancode2Hid[] =
+{
+ 0x00, 0x29, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, /* 00-07 */
+ 0x24, 0x25, 0x26, 0x27, 0x2d, 0x2e, 0x2a, 0x2b, /* 08-1F */
+ 0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c, /* 10-17 */
+ 0x12, 0x13, 0x2f, 0x30, 0x28, 0xe0, 0x04, 0x16, /* 18-1F */
+ 0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, 0x33, /* 20-27 */
+ 0x34, 0x35, 0xe1, 0x31, 0x1d, 0x1b, 0x06, 0x19, /* 28-2F */
+ 0x05, 0x11, 0x10, 0x36, 0x37, 0x38, 0xe5, 0x55, /* 30-37 */
+ 0xe2, 0x2c, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, /* 38-3F */
+ 0x3f, 0x40, 0x41, 0x42, 0x43, 0x53, 0x47, 0x5f, /* 40-47 */
+ 0x60, 0x61, 0x56, 0x5c, 0x5d, 0x5e, 0x57, 0x59, /* 48-4F */
+ 0x5a, 0x5b, 0x62, 0x63, 0x46, 0x00, 0x64, 0x44, /* 50-57 */
+ 0x45, 0x67, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, /* 58-5F */
+ 0x00, 0x00, 0x00, 0x00, 0x68, 0x69, 0x6a, 0x6b, /* 60-67 */
+ 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x00, /* 68-6F */
+ 0x88, 0x91, 0x90, 0x87, 0x00, 0x00, 0x00, 0x00, /* 70-77 */
+ 0x00, 0x8a, 0x00, 0x8b, 0x00, 0x89, 0x85, 0x00 /* 78-7F */
+};
+
+/** Lookup table for extended scancodes (arrow keys etc.). */
+static const uint8_t aExtScan2Hid[] =
+{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00-07 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 08-1F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10-17 */
+ 0x00, 0x00, 0x00, 0x00, 0x58, 0xe4, 0x00, 0x00, /* 18-1F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 20-27 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 28-2F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x46, /* 30-37 */
+ /* Sun-specific keys. Most of the XT codes are made up */
+ 0xe6, 0x00, 0x00, 0x75, 0x76, 0x77, 0xA3, 0x78, /* 38-3F */
+ 0x80, 0x81, 0x82, 0x79, 0x00, 0x00, 0x48, 0x4a, /* 40-47 */
+ 0x52, 0x4b, 0x00, 0x50, 0x00, 0x4f, 0x00, 0x4d, /* 48-4F */
+ 0x51, 0x4e, 0x49, 0x4c, 0x00, 0x00, 0x00, 0x00, /* 50-57 */
+ 0x00, 0x00, 0x00, 0xe3, 0xe7, 0x65, 0x66, 0x00, /* 58-5F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 60-67 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 68-6F */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 70-77 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 /* 78-7F */
+};
+
+/**
+ * Convert a PC scan code to a USB HID usage byte.
+ *
+ * @param state Current state of the translator (scan_state_t).
+ * @param scanCode Incoming scan code.
+ * @param pUsage Pointer to usage; high bit set for key up events. The
+ * contents are only valid if returned state is SS_IDLE.
+ *
+ * @return scan_state_t New state of the translator.
+ */
+static scan_state_t ScancodeToHidUsage(scan_state_t state, uint8_t scanCode, uint32_t *pUsage)
+{
+ uint32_t keyUp;
+ uint8_t usage;
+
+ Assert(pUsage);
+
+ /* Isolate the scan code and key break flag. */
+ keyUp = (scanCode & 0x80) << 24;
+
+ switch (state) {
+ case SS_IDLE:
+ if (scanCode == 0xE0) {
+ state = SS_EXT;
+ } else if (scanCode == 0xE1) {
+ state = SS_EXT1;
+ } else {
+ usage = aScancode2Hid[scanCode & 0x7F];
+ *pUsage = usage | keyUp;
+ /* Remain in SS_IDLE state. */
+ }
+ break;
+ case SS_EXT:
+ usage = aExtScan2Hid[scanCode & 0x7F];
+ *pUsage = usage | keyUp;
+ state = SS_IDLE;
+ break;
+ case SS_EXT1:
+ /* The sequence is E1 1D 45 E1 9D C5. We take the easy way out and remain
+ * in the SS_EXT1 state until 45 or C5 is received.
+ */
+ if ((scanCode & 0x7F) == 0x45) {
+ *pUsage = 0x48;
+ if (scanCode == 0xC5)
+ *pUsage |= keyUp;
+ state = SS_IDLE;
+ }
+ /* Else remain in SS_EXT1 state. */
+ break;
+ }
+ return state;
+}
+
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvKbdQueueQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIKEYBOARDCONNECTOR, &pThis->IConnector);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIKEYBOARDPORT, &pThis->IPort);
+ return NULL;
+}
+
+
+/* -=-=-=-=- IKeyboardPort -=-=-=-=- */
+
+/** Converts a pointer to DRVKBDQUEUE::IPort to a DRVKBDQUEUE pointer. */
+#define IKEYBOARDPORT_2_DRVKBDQUEUE(pInterface) ( (PDRVKBDQUEUE)((char *)(pInterface) - RT_UOFFSETOF(DRVKBDQUEUE, IPort)) )
+
+
+/**
+ * Queues a keyboard event.
+ * Because of the event queueing the EMT context requirement is lifted.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this interface structure.
+ * @param u8ScanCode The scan code to translate/queue.
+ * @thread Any thread.
+ */
+static DECLCALLBACK(int) drvKbdQueuePutEventScan(PPDMIKEYBOARDPORT pInterface, uint8_t u8ScanCode)
+{
+ PDRVKBDQUEUE pDrv = IKEYBOARDPORT_2_DRVKBDQUEUE(pInterface);
+ /* Ignore any attempt to send events if queue is inactive. */
+ if (pDrv->fInactive)
+ return VINF_SUCCESS;
+
+ uint32_t u32Usage = 0;
+ pDrv->XlatState = ScancodeToHidUsage(pDrv->XlatState, u8ScanCode, &u32Usage);
+
+ if (pDrv->XlatState == SS_IDLE) {
+ PDRVKBDQUEUEITEM pItem = (PDRVKBDQUEUEITEM)PDMQueueAlloc(pDrv->pQueue);
+ if (pItem)
+ {
+ /*
+ * Work around incredibly poorly desgined Korean keyboards which
+ * only send break events for Hangul/Hanja keys -- convert a lone
+ * key up into a key up/key down sequence.
+ */
+ if (u32Usage == 0x80000090 || u32Usage == 0x80000091)
+ {
+ PDRVKBDQUEUEITEM pItem2 = (PDRVKBDQUEUEITEM)PDMQueueAlloc(pDrv->pQueue);
+ /*
+ * NB: If there's no room in the queue, we will drop the faked
+ * key down event. Probably less bad than the alternatives.
+ */
+ if (pItem2)
+ {
+ /* Manufacture a key down event. */
+ pItem2->u32UsageCode = u32Usage & ~0x80000000;
+ PDMQueueInsert(pDrv->pQueue, &pItem2->Core);
+ }
+ }
+
+ pItem->u32UsageCode = u32Usage;
+ PDMQueueInsert(pDrv->pQueue, &pItem->Core);
+
+ return VINF_SUCCESS;
+ }
+ if (!pDrv->fSuspended)
+ AssertMsgFailed(("drvKbdQueuePutEventScan: Queue is full!!!!\n"));
+ return VERR_PDM_NO_QUEUE_ITEMS;
+ }
+ else
+ return VINF_SUCCESS;
+}
+
+
+/* -=-=-=-=- IConnector -=-=-=-=- */
+
+#define PPDMIKEYBOARDCONNECTOR_2_DRVKBDQUEUE(pInterface) ( (PDRVKBDQUEUE)((char *)(pInterface) - RT_UOFFSETOF(DRVKBDQUEUE, IConnector)) )
+
+
+/**
+ * Pass LED status changes from the guest thru to the frontend driver.
+ *
+ * @param pInterface Pointer to the keyboard connector interface structure.
+ * @param enmLeds The new LED mask.
+ */
+static DECLCALLBACK(void) drvKbdPassThruLedsChange(PPDMIKEYBOARDCONNECTOR pInterface, PDMKEYBLEDS enmLeds)
+{
+ PDRVKBDQUEUE pDrv = PPDMIKEYBOARDCONNECTOR_2_DRVKBDQUEUE(pInterface);
+ pDrv->pDownConnector->pfnLedStatusChange(pDrv->pDownConnector, enmLeds);
+}
+
+/**
+ * Pass keyboard state changes from the guest thru to the frontend driver.
+ *
+ * @param pInterface Pointer to the keyboard connector interface structure.
+ * @param fActive The new active/inactive state.
+ */
+static DECLCALLBACK(void) drvKbdPassThruSetActive(PPDMIKEYBOARDCONNECTOR pInterface, bool fActive)
+{
+ PDRVKBDQUEUE pDrv = PPDMIKEYBOARDCONNECTOR_2_DRVKBDQUEUE(pInterface);
+
+ AssertPtr(pDrv->pDownConnector->pfnSetActive);
+ pDrv->pDownConnector->pfnSetActive(pDrv->pDownConnector, fActive);
+}
+
+/**
+ * Flush the keyboard queue if there are pending events.
+ *
+ * @param pInterface Pointer to the keyboard connector interface structure.
+ */
+static DECLCALLBACK(void) drvKbdFlushQueue(PPDMIKEYBOARDCONNECTOR pInterface)
+{
+ PDRVKBDQUEUE pDrv = PPDMIKEYBOARDCONNECTOR_2_DRVKBDQUEUE(pInterface);
+
+ AssertPtr(pDrv->pQueue);
+ PDMQueueFlushIfNecessary(pDrv->pQueue);
+}
+
+
+/* -=-=-=-=- queue -=-=-=-=- */
+
+/**
+ * Queue callback for processing a queued item.
+ *
+ * @returns Success indicator.
+ * If false the item will not be removed and the flushing will stop.
+ * @param pDrvIns The driver instance.
+ * @param pItemCore Pointer to the queue item to process.
+ */
+static DECLCALLBACK(bool) drvKbdQueueConsumer(PPDMDRVINS pDrvIns, PPDMQUEUEITEMCORE pItemCore)
+{
+ PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+ PDRVKBDQUEUEITEM pItem = (PDRVKBDQUEUEITEM)pItemCore;
+ int rc = pThis->pUpPort->pfnPutEventHid(pThis->pUpPort, pItem->u32UsageCode);
+ return RT_SUCCESS(rc);
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+/**
+ * Power On notification.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvKbdQueuePowerOn(PPDMDRVINS pDrvIns)
+{
+ PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+ pThis->fInactive = false;
+}
+
+
+/**
+ * Reset notification.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvKbdQueueReset(PPDMDRVINS pDrvIns)
+{
+ //PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+ /** @todo purge the queue on reset. */
+ RT_NOREF(pDrvIns);
+}
+
+
+/**
+ * Suspend notification.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvKbdQueueSuspend(PPDMDRVINS pDrvIns)
+{
+ PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+ pThis->fSuspended = true;
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvKbdQueueResume(PPDMDRVINS pDrvIns)
+{
+ PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+ pThis->fInactive = false;
+ pThis->fSuspended = false;
+}
+
+
+/**
+ * Power Off notification.
+ *
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvKbdQueuePowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+ pThis->fInactive = true;
+}
+
+
+/**
+ * Construct a keyboard driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvKbdQueueConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDRVKBDQUEUE pDrv = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+ LogFlow(("drvKbdQueueConstruct: iInstance=%d\n", pDrvIns->iInstance));
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+
+ /*
+ * Validate configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "QueueSize\0Interval\0"))
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+
+ /*
+ * Init basic data members and interfaces.
+ */
+ pDrv->fInactive = true;
+ pDrv->fSuspended = false;
+ pDrv->XlatState = SS_IDLE;
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvKbdQueueQueryInterface;
+ /* IKeyboardConnector. */
+ pDrv->IConnector.pfnLedStatusChange = drvKbdPassThruLedsChange;
+ pDrv->IConnector.pfnSetActive = drvKbdPassThruSetActive;
+ pDrv->IConnector.pfnFlushQueue = drvKbdFlushQueue;
+ /* IKeyboardPort. */
+ pDrv->IPort.pfnPutEventScan = drvKbdQueuePutEventScan;
+
+ /*
+ * Get the IKeyboardPort interface of the above driver/device.
+ */
+ pDrv->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIKEYBOARDPORT);
+ if (!pDrv->pUpPort)
+ {
+ AssertMsgFailed(("Configuration error: No keyboard port interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+
+ /*
+ * Attach driver below and query it's connector interface.
+ */
+ PPDMIBASE pDownBase;
+ int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Failed to attach driver below us! rc=%Rra\n", rc));
+ return rc;
+ }
+ pDrv->pDownConnector = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIKEYBOARDCONNECTOR);
+ if (!pDrv->pDownConnector)
+ {
+ AssertMsgFailed(("Configuration error: No keyboard connector interface below!\n"));
+ return VERR_PDM_MISSING_INTERFACE_BELOW;
+ }
+
+ /*
+ * Create the queue.
+ */
+ uint32_t cMilliesInterval = 0;
+ rc = CFGMR3QueryU32(pCfg, "Interval", &cMilliesInterval);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ cMilliesInterval = 0;
+ else if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: 32-bit \"Interval\" -> rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ uint32_t cItems = 0;
+ rc = CFGMR3QueryU32(pCfg, "QueueSize", &cItems);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ cItems = 128;
+ else if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: 32-bit \"QueueSize\" -> rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ rc = PDMDrvHlpQueueCreate(pDrvIns, sizeof(DRVKBDQUEUEITEM), cItems, cMilliesInterval, drvKbdQueueConsumer, "Keyboard", &pDrv->pQueue);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Failed to create driver: cItems=%d cMilliesInterval=%d rc=%Rrc\n", cItems, cMilliesInterval, rc));
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Keyboard queue driver registration record.
+ */
+const PDMDRVREG g_DrvKeyboardQueue =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "KeyboardQueue",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Keyboard queue driver to plug in between the key source and the device to do queueing and inter-thread transport.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_KEYBOARD,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVKBDQUEUE),
+ /* pfnConstruct */
+ drvKbdQueueConstruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnDestruct */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ drvKbdQueuePowerOn,
+ /* pfnReset */
+ drvKbdQueueReset,
+ /* pfnSuspend */
+ drvKbdQueueSuspend,
+ /* pfnResume */
+ drvKbdQueueResume,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ drvKbdQueuePowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Input/DrvMouseQueue.cpp b/src/VBox/Devices/Input/DrvMouseQueue.cpp
new file mode 100644
index 00000000..0c527a7f
--- /dev/null
+++ b/src/VBox/Devices/Input/DrvMouseQueue.cpp
@@ -0,0 +1,465 @@
+/* $Id: DrvMouseQueue.cpp $ */
+/** @file
+ * VBox input devices: Mouse queue driver
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_MOUSE_QUEUE
+#include <VBox/vmm/pdmdrv.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Mouse queue driver instance data.
+ *
+ * @implements PDMIMOUSECONNECTOR
+ * @implements PDMIMOUSEPORT
+ */
+typedef struct DRVMOUSEQUEUE
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the mouse port interface of the driver/device above us. */
+ PPDMIMOUSEPORT pUpPort;
+ /** Pointer to the mouse port interface of the driver/device below us. */
+ PPDMIMOUSECONNECTOR pDownConnector;
+ /** Our mouse connector interface. */
+ PDMIMOUSECONNECTOR IConnector;
+ /** Our mouse port interface. */
+ PDMIMOUSEPORT IPort;
+ /** The queue handle. */
+ PPDMQUEUE pQueue;
+ /** Discard input when this flag is set.
+ * We only accept input when the VM is running. */
+ bool fInactive;
+} DRVMOUSEQUEUE, *PDRVMOUSEQUEUE;
+
+
+/**
+ * Event type for @a DRVMOUSEQUEUEITEM
+ */
+enum EVENTTYPE { RELATIVE, ABSOLUTE };
+
+/**
+ * Mouse queue item.
+ */
+typedef struct DRVMOUSEQUEUEITEM
+{
+ /** The core part owned by the queue manager. */
+ PDMQUEUEITEMCORE Core;
+ enum EVENTTYPE enmType;
+ union
+ {
+ uint32_t padding[5];
+ struct
+ {
+ uint32_t fButtons;
+ int32_t dx;
+ int32_t dy;
+ int32_t dz;
+ int32_t dw;
+ } Relative;
+ struct
+ {
+ uint32_t fButtons;
+ uint32_t x;
+ uint32_t y;
+ int32_t dz;
+ int32_t dw;
+ } Absolute;
+ } u;
+} DRVMOUSEQUEUEITEM, *PDRVMOUSEQUEUEITEM;
+
+
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvMouseQueueQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSEPORT, &pThis->IPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSECONNECTOR, &pThis->IConnector);
+ return NULL;
+}
+
+
+/* -=-=-=-=- IMousePort -=-=-=-=- */
+
+/** Converts a pointer to DRVMOUSEQUEUE::Port to a DRVMOUSEQUEUE pointer. */
+#define IMOUSEPORT_2_DRVMOUSEQUEUE(pInterface) ( (PDRVMOUSEQUEUE)((char *)(pInterface) - RT_UOFFSETOF(DRVMOUSEQUEUE, IPort)) )
+
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEvent}
+ */
+static DECLCALLBACK(int) drvMouseQueuePutEvent(PPDMIMOUSEPORT pInterface,
+ int32_t dx, int32_t dy,
+ int32_t dz, int32_t dw,
+ uint32_t fButtons)
+{
+ PDRVMOUSEQUEUE pDrv = IMOUSEPORT_2_DRVMOUSEQUEUE(pInterface);
+ if (pDrv->fInactive)
+ return VINF_SUCCESS;
+
+ PDRVMOUSEQUEUEITEM pItem = (PDRVMOUSEQUEUEITEM)PDMQueueAlloc(pDrv->pQueue);
+ if (pItem)
+ {
+ RT_ZERO(pItem->u.padding);
+ pItem->enmType = RELATIVE;
+ pItem->u.Relative.dx = dx;
+ pItem->u.Relative.dy = dy;
+ pItem->u.Relative.dz = dz;
+ pItem->u.Relative.dw = dw;
+ pItem->u.Relative.fButtons = fButtons;
+ PDMQueueInsert(pDrv->pQueue, &pItem->Core);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_NO_QUEUE_ITEMS;
+}
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventAbs}
+ */
+static DECLCALLBACK(int) drvMouseQueuePutEventAbs(PPDMIMOUSEPORT pInterface,
+ uint32_t x, uint32_t y,
+ int32_t dz, int32_t dw,
+ uint32_t fButtons)
+{
+ PDRVMOUSEQUEUE pDrv = IMOUSEPORT_2_DRVMOUSEQUEUE(pInterface);
+ if (pDrv->fInactive)
+ return VINF_SUCCESS;
+
+ PDRVMOUSEQUEUEITEM pItem = (PDRVMOUSEQUEUEITEM)PDMQueueAlloc(pDrv->pQueue);
+ if (pItem)
+ {
+ RT_ZERO(pItem->u.padding);
+ pItem->enmType = ABSOLUTE;
+ pItem->u.Absolute.x = x;
+ pItem->u.Absolute.y = y;
+ pItem->u.Absolute.dz = dz;
+ pItem->u.Absolute.dw = dw;
+ pItem->u.Absolute.fButtons = fButtons;
+ PDMQueueInsert(pDrv->pQueue, &pItem->Core);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_NO_QUEUE_ITEMS;
+}
+
+
+static DECLCALLBACK(int) drvMouseQueuePutEventMultiTouch(PPDMIMOUSEPORT pInterface,
+ uint8_t cContacts,
+ const uint64_t *pau64Contacts,
+ uint32_t u32ScanTime)
+{
+ PDRVMOUSEQUEUE pThis = IMOUSEPORT_2_DRVMOUSEQUEUE(pInterface);
+ return pThis->pUpPort->pfnPutEventMultiTouch(pThis->pUpPort, cContacts, pau64Contacts, u32ScanTime);
+}
+
+/* -=-=-=-=- IConnector -=-=-=-=- */
+
+#define PPDMIMOUSECONNECTOR_2_DRVMOUSEQUEUE(pInterface) ( (PDRVMOUSEQUEUE)((char *)(pInterface) - RT_UOFFSETOF(DRVMOUSEQUEUE, IConnector)) )
+
+
+/**
+ * Pass absolute mode status changes from the guest through to the frontend
+ * driver.
+ *
+ * @param pInterface Pointer to the mouse connector interface structure.
+ * @param fRel Is relative reporting supported?
+ * @param fAbs Is absolute reporting supported?
+ * @param fMT Is multi-touch reporting supported?
+ */
+static DECLCALLBACK(void) drvMousePassThruReportModes(PPDMIMOUSECONNECTOR pInterface, bool fRel, bool fAbs, bool fMT)
+{
+ PDRVMOUSEQUEUE pDrv = PPDMIMOUSECONNECTOR_2_DRVMOUSEQUEUE(pInterface);
+ pDrv->pDownConnector->pfnReportModes(pDrv->pDownConnector, fRel, fAbs, fMT);
+}
+
+
+/**
+ * Flush the mouse queue if there are pending events.
+ *
+ * @param pInterface Pointer to the mouse connector interface structure.
+ */
+static DECLCALLBACK(void) drvMouseFlushQueue(PPDMIMOUSECONNECTOR pInterface)
+{
+ PDRVMOUSEQUEUE pDrv = PPDMIMOUSECONNECTOR_2_DRVMOUSEQUEUE(pInterface);
+
+ AssertPtr(pDrv->pQueue);
+ PDMQueueFlushIfNecessary(pDrv->pQueue);
+}
+
+
+
+/* -=-=-=-=- queue -=-=-=-=- */
+
+/**
+ * Queue callback for processing a queued item.
+ *
+ * @returns Success indicator.
+ * If false the item will not be removed and the flushing will stop.
+ * @param pDrvIns The driver instance.
+ * @param pItemCore Pointer to the queue item to process.
+ */
+static DECLCALLBACK(bool) drvMouseQueueConsumer(PPDMDRVINS pDrvIns, PPDMQUEUEITEMCORE pItemCore)
+{
+ PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE);
+ PDRVMOUSEQUEUEITEM pItem = (PDRVMOUSEQUEUEITEM)pItemCore;
+ int rc;
+ if (pItem->enmType == RELATIVE)
+ rc = pThis->pUpPort->pfnPutEvent(pThis->pUpPort,
+ pItem->u.Relative.dx,
+ pItem->u.Relative.dy,
+ pItem->u.Relative.dz,
+ pItem->u.Relative.dw,
+ pItem->u.Relative.fButtons);
+ else if (pItem->enmType == ABSOLUTE)
+ rc = pThis->pUpPort->pfnPutEventAbs(pThis->pUpPort,
+ pItem->u.Absolute.x,
+ pItem->u.Absolute.y,
+ pItem->u.Absolute.dz,
+ pItem->u.Absolute.dw,
+ pItem->u.Absolute.fButtons);
+ else
+ return false;
+ return RT_SUCCESS(rc);
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+/**
+ * Power On notification.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvMouseQueuePowerOn(PPDMDRVINS pDrvIns)
+{
+ PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE);
+ pThis->fInactive = false;
+}
+
+
+/**
+ * Reset notification.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvMouseQueueReset(PPDMDRVINS pDrvIns)
+{
+ //PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+ /** @todo purge the queue on reset. */
+ RT_NOREF(pDrvIns);
+}
+
+
+/**
+ * Suspend notification.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvMouseQueueSuspend(PPDMDRVINS pDrvIns)
+{
+ PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE);
+ pThis->fInactive = true;
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvMouseQueueResume(PPDMDRVINS pDrvIns)
+{
+ PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE);
+ pThis->fInactive = false;
+}
+
+
+/**
+ * Power Off notification.
+ *
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvMouseQueuePowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE);
+ pThis->fInactive = true;
+}
+
+
+/**
+ * Construct a mouse driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvMouseQueueConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDRVMOUSEQUEUE pDrv = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE);
+ LogFlow(("drvMouseQueueConstruct: iInstance=%d\n", pDrvIns->iInstance));
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+
+ /*
+ * Validate configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "QueueSize\0Interval\0"))
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+
+ /*
+ * Init basic data members and interfaces.
+ */
+ pDrv->fInactive = true;
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvMouseQueueQueryInterface;
+ /* IMouseConnector. */
+ pDrv->IConnector.pfnReportModes = drvMousePassThruReportModes;
+ pDrv->IConnector.pfnFlushQueue = drvMouseFlushQueue;
+ /* IMousePort. */
+ pDrv->IPort.pfnPutEvent = drvMouseQueuePutEvent;
+ pDrv->IPort.pfnPutEventAbs = drvMouseQueuePutEventAbs;
+ pDrv->IPort.pfnPutEventMultiTouch = drvMouseQueuePutEventMultiTouch;
+
+ /*
+ * Get the IMousePort interface of the above driver/device.
+ */
+ pDrv->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMOUSEPORT);
+ if (!pDrv->pUpPort)
+ {
+ AssertMsgFailed(("Configuration error: No mouse port interface above!\n"));
+ return VERR_PDM_MISSING_INTERFACE_ABOVE;
+ }
+
+ /*
+ * Attach driver below and query it's connector interface.
+ */
+ PPDMIBASE pDownBase;
+ int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Failed to attach driver below us! rc=%Rra\n", rc));
+ return rc;
+ }
+ pDrv->pDownConnector = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIMOUSECONNECTOR);
+ if (!pDrv->pDownConnector)
+ {
+ AssertMsgFailed(("Configuration error: No mouse connector interface below!\n"));
+ return VERR_PDM_MISSING_INTERFACE_BELOW;
+ }
+
+ /*
+ * Create the queue.
+ */
+ uint32_t cMilliesInterval = 0;
+ rc = CFGMR3QueryU32(pCfg, "Interval", &cMilliesInterval);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ cMilliesInterval = 0;
+ else if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: 32-bit \"Interval\" -> rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ uint32_t cItems = 0;
+ rc = CFGMR3QueryU32(pCfg, "QueueSize", &cItems);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ cItems = 128;
+ else if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: 32-bit \"QueueSize\" -> rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ rc = PDMDrvHlpQueueCreate(pDrvIns, sizeof(DRVMOUSEQUEUEITEM), cItems, cMilliesInterval, drvMouseQueueConsumer, "Mouse", &pDrv->pQueue);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Failed to create driver: cItems=%d cMilliesInterval=%d rc=%Rrc\n", cItems, cMilliesInterval, rc));
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Mouse queue driver registration record.
+ */
+const PDMDRVREG g_DrvMouseQueue =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "MouseQueue",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Mouse queue driver to plug in between the key source and the device to do queueing and inter-thread transport.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_MOUSE,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVMOUSEQUEUE),
+ /* pfnConstruct */
+ drvMouseQueueConstruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnDestruct */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ drvMouseQueuePowerOn,
+ /* pfnReset */
+ drvMouseQueueReset,
+ /* pfnSuspend */
+ drvMouseQueueSuspend,
+ /* pfnResume */
+ drvMouseQueueResume,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ drvMouseQueuePowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Input/Makefile.kup b/src/VBox/Devices/Input/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/Input/Makefile.kup
diff --git a/src/VBox/Devices/Input/PS2Dev.h b/src/VBox/Devices/Input/PS2Dev.h
new file mode 100644
index 00000000..b90b9735
--- /dev/null
+++ b/src/VBox/Devices/Input/PS2Dev.h
@@ -0,0 +1,94 @@
+/* $Id: PS2Dev.h $ */
+/** @file
+ * PS/2 devices - Internal header file.
+ */
+
+/*
+ * Copyright (C) 2007-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Input_PS2Dev_h
+#define VBOX_INCLUDED_SRC_Input_PS2Dev_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/** The size of the PS2K/PS2M structure fillers.
+ * @note Must be at least as big as the real struct. Compile time assert
+ * makes sure this is so. */
+#define PS2K_STRUCT_FILLER 512
+#define PS2M_STRUCT_FILLER 512
+
+/* Hide the internal structure. */
+#if !(defined(IN_PS2K) || defined(VBOX_DEVICE_STRUCT_TESTCASE))
+typedef struct PS2K
+{
+ uint8_t abFiller[PS2K_STRUCT_FILLER];
+} PS2K;
+#endif
+
+#if !(defined(IN_PS2M) || defined(VBOX_DEVICE_STRUCT_TESTCASE))
+typedef struct PS2M
+{
+ uint8_t abFiller[PS2M_STRUCT_FILLER];
+} PS2M;
+#endif
+
+/* Internal PS/2 Keyboard interface. */
+typedef struct PS2K *PPS2K;
+
+int PS2KByteToKbd(PPS2K pThis, uint8_t cmd);
+int PS2KByteFromKbd(PPS2K pThis, uint8_t *pVal);
+
+int PS2KConstruct(PPS2K pThis, PPDMDEVINS pDevIns, void *pParent, int iInstance, PCFGMNODE pCfg);
+int PS2KAttach(PPS2K pThis, PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags);
+void PS2KReset(PPS2K pThis);
+void PS2KRelocate(PPS2K pThis, RTGCINTPTR offDelta, PPDMDEVINS pDevIns);
+void PS2KSaveState(PPS2K pThis, PSSMHANDLE pSSM);
+int PS2KLoadState(PPS2K pThis, PSSMHANDLE pSSM, uint32_t uVersion);
+int PS2KLoadDone(PPS2K pThis, PSSMHANDLE pSSM);
+
+PS2K *KBDGetPS2KFromDevIns(PPDMDEVINS pDevIns);
+
+
+/* Internal PS/2 Auxiliary device interface. */
+typedef struct PS2M *PPS2M;
+
+int PS2MByteToAux(PPS2M pThis, uint8_t cmd);
+int PS2MByteFromAux(PPS2M pThis, uint8_t *pVal);
+
+int PS2MConstruct(PPS2M pThis, PPDMDEVINS pDevIns, void *pParent, int iInstance);
+int PS2MAttach(PPS2M pThis, PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags);
+void PS2MReset(PPS2M pThis);
+void PS2MRelocate(PPS2M pThis, RTGCINTPTR offDelta, PPDMDEVINS pDevIns);
+void PS2MSaveState(PPS2M pThis, PSSMHANDLE pSSM);
+int PS2MLoadState(PPS2M pThis, PSSMHANDLE pSSM, uint32_t uVersion);
+void PS2MFixupState(PPS2M pThis, uint8_t u8State, uint8_t u8Rate, uint8_t u8Proto);
+
+PS2M *KBDGetPS2MFromDevIns(PPDMDEVINS pDevIns);
+
+
+/* Shared keyboard/aux internal interface. */
+void KBCUpdateInterrupts(void *pKbc);
+
+
+///@todo: This should live with the KBC implementation.
+/** AT to PC scancode translator state. */
+typedef enum
+{
+ XS_IDLE, /**< Starting state. */
+ XS_BREAK, /**< F0 break byte was received. */
+ XS_HIBIT /**< Break code still active. */
+} xlat_state_t;
+
+int32_t XlateAT2PC(int32_t state, uint8_t scanIn, uint8_t *pScanOut);
+
+#endif /* !VBOX_INCLUDED_SRC_Input_PS2Dev_h */
diff --git a/src/VBox/Devices/Input/PS2K.cpp b/src/VBox/Devices/Input/PS2K.cpp
new file mode 100644
index 00000000..3a0a8d58
--- /dev/null
+++ b/src/VBox/Devices/Input/PS2K.cpp
@@ -0,0 +1,1661 @@
+/* $Id: PS2K.cpp $ */
+/** @file
+ * PS2K - PS/2 keyboard emulation.
+ */
+
+/*
+ * Copyright (C) 2007-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/*
+ * References:
+ *
+ * IBM PS/2 Technical Reference, Keyboards (101- and 102-Key), 1990
+ * Keyboard Scan Code Specification, Microsoft, 2000
+ *
+ * Notes:
+ * - The keyboard never sends partial scan-code sequences; if there isn't enough
+ * room left in the buffer for the entire sequence, the keystroke is discarded
+ * and an overrun code is sent instead.
+ * - Command responses do not disturb stored keystrokes and always have priority.
+ * - Caps Lock and Scroll Lock are normal keys from the keyboard's point of view.
+ * However, Num Lock is not and the keyboard internally tracks its state.
+ * - The way Print Screen works in scan set 1/2 is totally insane.
+ * - A PS/2 keyboard can send at most 1,000 to 1,500 bytes per second. There is
+ * software which relies on that fact and assumes that a scan code can be
+ * read twice before the next scan code comes in.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_KBD
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/err.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include "VBoxDD.h"
+#define IN_PS2K
+#include "PS2Dev.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name Keyboard commands sent by the system.
+ * @{ */
+#define KCMD_LEDS 0xED
+#define KCMD_ECHO 0xEE
+#define KCMD_INVALID_1 0xEF
+#define KCMD_SCANSET 0xF0
+#define KCMD_INVALID_2 0xF1
+#define KCMD_READ_ID 0xF2
+#define KCMD_RATE_DELAY 0xF3
+#define KCMD_ENABLE 0xF4
+#define KCMD_DFLT_DISABLE 0xF5
+#define KCMD_SET_DEFAULT 0xF6
+#define KCMD_ALL_TYPEMATIC 0xF7
+#define KCMD_ALL_MK_BRK 0xF8
+#define KCMD_ALL_MAKE 0xF9
+#define KCMD_ALL_TMB 0xFA
+#define KCMD_TYPE_MATIC 0xFB
+#define KCMD_TYPE_MK_BRK 0xFC
+#define KCMD_TYPE_MAKE 0xFD
+#define KCMD_RESEND 0xFE
+#define KCMD_RESET 0xFF
+/** @} */
+
+/** @name Keyboard responses sent to the system.
+ * @{ */
+#define KRSP_ID1 0xAB
+#define KRSP_ID2 0x83
+#define KRSP_BAT_OK 0xAA
+#define KRSP_BAT_FAIL 0xFC /* Also a 'release keys' signal. */
+#define KRSP_ECHO 0xEE
+#define KRSP_ACK 0xFA
+#define KRSP_RESEND 0xFE
+/** @} */
+
+/** @name HID modifier range.
+ * @{ */
+#define HID_MODIFIER_FIRST 0xE0
+#define HID_MODIFIER_LAST 0xE8
+/** @} */
+
+/** @name USB HID additional constants
+ * @{ */
+/** The highest USB usage code reported by VirtualBox. */
+#define VBOX_USB_MAX_USAGE_CODE 0xE7
+/** The size of an array needed to store all USB usage codes */
+#define VBOX_USB_USAGE_ARRAY_SIZE (VBOX_USB_MAX_USAGE_CODE + 1)
+/** @} */
+
+/** @name Modifier key states. Sorted in USB HID code order.
+ * @{ */
+#define MOD_LCTRL 0x01
+#define MOD_LSHIFT 0x02
+#define MOD_LALT 0x04
+#define MOD_LGUI 0x08
+#define MOD_RCTRL 0x10
+#define MOD_RSHIFT 0x20
+#define MOD_RALT 0x40
+#define MOD_RGUI 0x80
+/** @} */
+
+/* Default typematic value. */
+#define KBD_DFL_RATE_DELAY 0x2B
+
+/* Input throttling delay in milliseconds. */
+#define KBD_THROTTLE_DELAY 1
+
+/** Define a simple PS/2 input device queue. */
+#define DEF_PS2Q_TYPE(name, size) \
+ typedef struct { \
+ uint32_t rpos; \
+ uint32_t wpos; \
+ uint32_t cUsed; \
+ uint32_t cSize; \
+ uint8_t abQueue[size]; \
+ } name
+
+/* Internal keyboard queue sizes. The input queue doesn't need to be
+ * extra huge and the command queue only needs to handle a few bytes.
+ */
+#define KBD_KEY_QUEUE_SIZE 64
+#define KBD_CMD_QUEUE_SIZE 4
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** Typematic state. */
+typedef enum {
+ KBD_TMS_IDLE = 0, /* No typematic key active. */
+ KBD_TMS_DELAY = 1, /* In the initial delay period. */
+ KBD_TMS_REPEAT = 2, /* Key repeating at set rate. */
+ KBD_TMS_32BIT_HACK = 0x7fffffff
+} tmatic_state_t;
+
+
+DEF_PS2Q_TYPE(KbdKeyQ, KBD_KEY_QUEUE_SIZE);
+DEF_PS2Q_TYPE(KbdCmdQ, KBD_CMD_QUEUE_SIZE);
+DEF_PS2Q_TYPE(GeneriQ, 1);
+
+/**
+ * The PS/2 keyboard instance data.
+ */
+typedef struct PS2K
+{
+ /** Pointer to parent device (keyboard controller). */
+ R3PTRTYPE(void *) pParent;
+ /** Set if keyboard is enabled ('scans' for input). */
+ bool fScanning;
+ /** Set NumLock is on. */
+ bool fNumLockOn;
+ /** Selected scan set. */
+ uint8_t u8ScanSet;
+ /** Modifier key state. */
+ uint8_t u8Modifiers;
+ /** Currently processed command (if any). */
+ uint8_t u8CurrCmd;
+ /** Status indicator (LED) state. */
+ uint8_t u8LEDs;
+ /** Selected typematic delay/rate. */
+ uint8_t u8TypematicCfg;
+ /** Usage code of current typematic key, if any. */
+ uint8_t u8TypematicKey;
+ /** Current typematic repeat state. */
+ tmatic_state_t enmTypematicState;
+ /** Buffer holding scan codes to be sent to the host. */
+ KbdKeyQ keyQ;
+ /** Command response queue (priority). */
+ KbdCmdQ cmdQ;
+ /** Currently depressed keys. */
+ uint8_t abDepressedKeys[VBOX_USB_USAGE_ARRAY_SIZE];
+ /** Typematic delay in milliseconds. */
+ unsigned uTypematicDelay;
+ /** Typematic repeat period in milliseconds. */
+ unsigned uTypematicRepeat;
+ /** Set if the throttle delay is currently active. */
+ bool fThrottleActive;
+ /** Set if the input rate should be throttled. */
+ bool fThrottleEnabled;
+
+ uint8_t Alignment0[2];
+
+ /** Command delay timer - RC Ptr. */
+ PTMTIMERRC pKbdDelayTimerRC;
+ /** Typematic timer - RC Ptr. */
+ PTMTIMERRC pKbdTypematicTimerRC;
+ /** Input throttle timer - RC Ptr. */
+ PTMTIMERRC pThrottleTimerRC;
+
+ /** The device critical section protecting everything - R3 Ptr */
+ R3PTRTYPE(PPDMCRITSECT) pCritSectR3;
+
+ /** Command delay timer - R3 Ptr. */
+ PTMTIMERR3 pKbdDelayTimerR3;
+ /** Typematic timer - R3 Ptr. */
+ PTMTIMERR3 pKbdTypematicTimerR3;
+ /** Input throttle timer - R3 Ptr. */
+ PTMTIMERR3 pThrottleTimerR3;
+
+ /** Command delay timer - R0 Ptr. */
+ PTMTIMERR0 pKbdDelayTimerR0;
+ /** Typematic timer - R0 Ptr. */
+ PTMTIMERR0 pKbdTypematicTimerR0;
+ /** Input throttle timer - R0 Ptr. */
+ PTMTIMERR0 pThrottleTimerR0;
+
+ /**
+ * Keyboard port - LUN#0.
+ *
+ * @implements PDMIBASE
+ * @implements PDMIKEYBOARDPORT
+ */
+ struct
+ {
+ /** The base interface for the keyboard port. */
+ PDMIBASE IBase;
+ /** The keyboard port base interface. */
+ PDMIKEYBOARDPORT IPort;
+
+ /** The base interface of the attached keyboard driver. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** The keyboard interface of the attached keyboard driver. */
+ R3PTRTYPE(PPDMIKEYBOARDCONNECTOR) pDrv;
+ } Keyboard;
+} PS2K, *PPS2K;
+
+AssertCompile(PS2K_STRUCT_FILLER >= sizeof(PS2K));
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+/* Key type flags. */
+#define KF_E0 0x01 /* E0 prefix. */
+#define KF_NB 0x02 /* No break code. */
+#define KF_GK 0x04 /* Gray navigation key. */
+#define KF_PS 0x08 /* Print Screen key. */
+#define KF_PB 0x10 /* Pause/Break key. */
+#define KF_NL 0x20 /* Num Lock key. */
+#define KF_NS 0x40 /* NumPad '/' key. */
+
+/* Scan Set 3 typematic defaults. */
+#define T_U 0x00 /* Unknown value. */
+#define T_T 0x01 /* Key is typematic. */
+#define T_M 0x02 /* Key is make only. */
+#define T_B 0x04 /* Key is make/break. */
+
+/* Special key values. */
+#define NONE 0x93 /* No PS/2 scan code returned. */
+#define UNAS 0x94 /* No PS/2 scan assigned to key. */
+#define RSVD 0x95 /* Reserved, do not use. */
+#define UNKN 0x96 /* Translation unknown. */
+
+/* Key definition structure. */
+typedef struct {
+ uint8_t makeS1; /* Set 1 make code. */
+ uint8_t makeS2; /* Set 2 make code. */
+ uint8_t makeS3; /* Set 3 make code. */
+ uint8_t keyFlags; /* Key flags. */
+ uint8_t keyMatic; /* Set 3 typematic default. */
+} key_def;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef IN_RING3
+/* USB to PS/2 conversion table for regular keys. */
+static const key_def aPS2Keys[] = {
+ /* 00 */ {NONE, NONE, NONE, KF_NB, T_U }, /* Key N/A: No Event */
+ /* 01 */ {0xFF, 0x00, 0x00, KF_NB, T_U }, /* Key N/A: Overrun Error */
+ /* 02 */ {0xFC, 0xFC, 0xFC, KF_NB, T_U }, /* Key N/A: POST Fail */
+ /* 03 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key N/A: ErrorUndefined */
+ /* 04 */ {0x1E, 0x1C, 0x1C, 0, T_T }, /* Key 31: a A */
+ /* 05 */ {0x30, 0x32, 0x32, 0, T_T }, /* Key 50: b B */
+ /* 06 */ {0x2E, 0x21, 0x21, 0, T_T }, /* Key 48: c C */
+ /* 07 */ {0x20, 0x23, 0x23, 0, T_T }, /* Key 33: d D */
+ /* 08 */ {0x12, 0x24, 0x24, 0, T_T }, /* Key 19: e E */
+ /* 09 */ {0x21, 0x2B, 0x2B, 0, T_T }, /* Key 34: f F */
+ /* 0A */ {0x22, 0x34, 0x34, 0, T_T }, /* Key 35: g G */
+ /* 0B */ {0x23, 0x33, 0x33, 0, T_T }, /* Key 36: h H */
+ /* 0C */ {0x17, 0x43, 0x43, 0, T_T }, /* Key 24: i I */
+ /* 0D */ {0x24, 0x3B, 0x3B, 0, T_T }, /* Key 37: j J */
+ /* 0E */ {0x25, 0x42, 0x42, 0, T_T }, /* Key 38: k K */
+ /* 0F */ {0x26, 0x4B, 0x4B, 0, T_T }, /* Key 39: l L */
+ /* 10 */ {0x32, 0x3A, 0x3A, 0, T_T }, /* Key 52: m M */
+ /* 11 */ {0x31, 0x31, 0x31, 0, T_T }, /* Key 51: n N */
+ /* 12 */ {0x18, 0x44, 0x44, 0, T_T }, /* Key 25: o O */
+ /* 13 */ {0x19, 0x4D, 0x4D, 0, T_T }, /* Key 26: p P */
+ /* 14 */ {0x10, 0x15, 0x15, 0, T_T }, /* Key 17: q Q */
+ /* 15 */ {0x13, 0x2D, 0x2D, 0, T_T }, /* Key 20: r R */
+ /* 16 */ {0x1F, 0x1B, 0x1B, 0, T_T }, /* Key 32: s S */
+ /* 17 */ {0x14, 0x2C, 0x2C, 0, T_T }, /* Key 21: t T */
+ /* 18 */ {0x16, 0x3C, 0x3C, 0, T_T }, /* Key 23: u U */
+ /* 19 */ {0x2F, 0x2A, 0x2A, 0, T_T }, /* Key 49: v V */
+ /* 1A */ {0x11, 0x1D, 0x1D, 0, T_T }, /* Key 18: w W */
+ /* 1B */ {0x2D, 0x22, 0x22, 0, T_T }, /* Key 47: x X */
+ /* 1C */ {0x15, 0x35, 0x35, 0, T_T }, /* Key 22: y Y */
+ /* 1D */ {0x2C, 0x1A, 0x1A, 0, T_T }, /* Key 46: z Z */
+ /* 1E */ {0x02, 0x16, 0x16, 0, T_T }, /* Key 2: 1 ! */
+ /* 1F */ {0x03, 0x1E, 0x1E, 0, T_T }, /* Key 3: 2 @ */
+ /* 20 */ {0x04, 0x26, 0x26, 0, T_T }, /* Key 4: 3 # */
+ /* 21 */ {0x05, 0x25, 0x25, 0, T_T }, /* Key 5: 4 $ */
+ /* 22 */ {0x06, 0x2E, 0x2E, 0, T_T }, /* Key 6: 5 % */
+ /* 23 */ {0x07, 0x36, 0x36, 0, T_T }, /* Key 7: 6 ^ */
+ /* 24 */ {0x08, 0x3D, 0x3D, 0, T_T }, /* Key 8: 7 & */
+ /* 25 */ {0x09, 0x3E, 0x3E, 0, T_T }, /* Key 9: 8 * */
+ /* 26 */ {0x0A, 0x46, 0x46, 0, T_T }, /* Key 10: 9 ( */
+ /* 27 */ {0x0B, 0x45, 0x45, 0, T_T }, /* Key 11: 0 ) */
+ /* 28 */ {0x1C, 0x5A, 0x5A, 0, T_T }, /* Key 43: Return */
+ /* 29 */ {0x01, 0x76, 0x08, 0, T_M }, /* Key 110: Escape */
+ /* 2A */ {0x0E, 0x66, 0x66, 0, T_T }, /* Key 15: Backspace */
+ /* 2B */ {0x0F, 0x0D, 0x0D, 0, T_T }, /* Key 16: Tab */
+ /* 2C */ {0x39, 0x29, 0x29, 0, T_T }, /* Key 61: Space */
+ /* 2D */ {0x0C, 0x4E, 0x4E, 0, T_T }, /* Key 12: - _ */
+ /* 2E */ {0x0D, 0x55, 0x55, 0, T_T }, /* Key 13: = + */
+ /* 2F */ {0x1A, 0x54, 0x54, 0, T_T }, /* Key 27: [ { */
+ /* 30 */ {0x1B, 0x5B, 0x5B, 0, T_T }, /* Key 28: ] } */
+ /* 31 */ {0x2B, 0x5D, 0x5C, 0, T_T }, /* Key 29: \ | */
+ /* 32 */ {0x2B, 0x5D, 0x5D, 0, T_T }, /* Key 42: Europe 1 (Note 2) */
+ /* 33 */ {0x27, 0x4C, 0x4C, 0, T_T }, /* Key 40: ; : */
+ /* 34 */ {0x28, 0x52, 0x52, 0, T_T }, /* Key 41: ' " */
+ /* 35 */ {0x29, 0x0E, 0x0E, 0, T_T }, /* Key 1: ` ~ */
+ /* 36 */ {0x33, 0x41, 0x41, 0, T_T }, /* Key 53: , < */
+ /* 37 */ {0x34, 0x49, 0x49, 0, T_T }, /* Key 54: . > */
+ /* 38 */ {0x35, 0x4A, 0x4A, 0, T_T }, /* Key 55: / ? */
+ /* 39 */ {0x3A, 0x58, 0x14, 0, T_B }, /* Key 30: Caps Lock */
+ /* 3A */ {0x3B, 0x05, 0x07, 0, T_M }, /* Key 112: F1 */
+ /* 3B */ {0x3C, 0x06, 0x0F, 0, T_M }, /* Key 113: F2 */
+ /* 3C */ {0x3D, 0x04, 0x17, 0, T_M }, /* Key 114: F3 */
+ /* 3D */ {0x3E, 0x0C, 0x1F, 0, T_M }, /* Key 115: F4 */
+ /* 3E */ {0x3F, 0x03, 0x27, 0, T_M }, /* Key 116: F5 */
+ /* 3F */ {0x40, 0x0B, 0x2F, 0, T_M }, /* Key 117: F6 */
+ /* 40 */ {0x41, 0x83, 0x37, 0, T_M }, /* Key 118: F7 */
+ /* 41 */ {0x42, 0x0A, 0x3F, 0, T_M }, /* Key 119: F8 */
+ /* 42 */ {0x43, 0x01, 0x47, 0, T_M }, /* Key 120: F9 */
+ /* 43 */ {0x44, 0x09, 0x4F, 0, T_M }, /* Key 121: F10 */
+ /* 44 */ {0x57, 0x78, 0x56, 0, T_M }, /* Key 122: F11 */
+ /* 45 */ {0x58, 0x07, 0x5E, 0, T_M }, /* Key 123: F12 */
+ /* 46 */ {0x37, 0x7C, 0x57, KF_PS, T_M }, /* Key 124: Print Screen (Note 1) */
+ /* 47 */ {0x46, 0x7E, 0x5F, 0, T_M }, /* Key 125: Scroll Lock */
+ /* 48 */ {RSVD, RSVD, RSVD, KF_PB, T_M }, /* Key 126: Break (Ctrl-Pause) */
+ /* 49 */ {0x52, 0x70, 0x67, KF_GK, T_M }, /* Key 75: Insert (Note 1) */
+ /* 4A */ {0x47, 0x6C, 0x6E, KF_GK, T_M }, /* Key 80: Home (Note 1) */
+ /* 4B */ {0x49, 0x7D, 0x6F, KF_GK, T_M }, /* Key 85: Page Up (Note 1) */
+ /* 4C */ {0x53, 0x71, 0x64, KF_GK, T_T }, /* Key 76: Delete (Note 1) */
+ /* 4D */ {0x4F, 0x69, 0x65, KF_GK, T_M }, /* Key 81: End (Note 1) */
+ /* 4E */ {0x51, 0x7A, 0x6D, KF_GK, T_M }, /* Key 86: Page Down (Note 1) */
+ /* 4F */ {0x4D, 0x74, 0x6A, KF_GK, T_T }, /* Key 89: Right Arrow (Note 1) */
+ /* 50 */ {0x4B, 0x6B, 0x61, KF_GK, T_T }, /* Key 79: Left Arrow (Note 1) */
+ /* 51 */ {0x50, 0x72, 0x60, KF_GK, T_T }, /* Key 84: Down Arrow (Note 1) */
+ /* 52 */ {0x48, 0x75, 0x63, KF_GK, T_T }, /* Key 83: Up Arrow (Note 1) */
+ /* 53 */ {0x45, 0x77, 0x76, KF_NL, T_M }, /* Key 90: Num Lock */
+ /* 54 */ {0x35, 0x4A, 0x77, KF_NS, T_M }, /* Key 95: Keypad / (Note 1) */
+ /* 55 */ {0x37, 0x7C, 0x7E, 0, T_M }, /* Key 100: Keypad * */
+ /* 56 */ {0x4A, 0x7B, 0x84, 0, T_M }, /* Key 105: Keypad - */
+ /* 57 */ {0x4E, 0x79, 0x7C, 0, T_T }, /* Key 106: Keypad + */
+ /* 58 */ {0x1C, 0x5A, 0x79, KF_E0, T_M }, /* Key 108: Keypad Enter */
+ /* 59 */ {0x4F, 0x69, 0x69, 0, T_M }, /* Key 93: Keypad 1 End */
+ /* 5A */ {0x50, 0x72, 0x72, 0, T_M }, /* Key 98: Keypad 2 Down */
+ /* 5B */ {0x51, 0x7A, 0x7A, 0, T_M }, /* Key 103: Keypad 3 PageDn */
+ /* 5C */ {0x4B, 0x6B, 0x6B, 0, T_M }, /* Key 92: Keypad 4 Left */
+ /* 5D */ {0x4C, 0x73, 0x73, 0, T_M }, /* Key 97: Keypad 5 */
+ /* 5E */ {0x4D, 0x74, 0x74, 0, T_M }, /* Key 102: Keypad 6 Right */
+ /* 5F */ {0x47, 0x6C, 0x6C, 0, T_M }, /* Key 91: Keypad 7 Home */
+ /* 60 */ {0x48, 0x75, 0x75, 0, T_M }, /* Key 96: Keypad 8 Up */
+ /* 61 */ {0x49, 0x7D, 0x7D, 0, T_M }, /* Key 101: Keypad 9 PageUp */
+ /* 62 */ {0x52, 0x70, 0x70, 0, T_M }, /* Key 99: Keypad 0 Insert */
+ /* 63 */ {0x53, 0x71, 0x71, 0, T_M }, /* Key 104: Keypad . Delete */
+ /* 64 */ {0x56, 0x61, 0x13, 0, T_T }, /* Key 45: Europe 2 (Note 2) */
+ /* 65 */ {0x5D, 0x2F, UNKN, KF_E0, T_U }, /* Key 129: App */
+ /* 66 */ {0x5E, 0x37, UNKN, KF_E0, T_U }, /* Key Unk: Keyboard Power */
+ /* 67 */ {0x59, 0x0F, UNKN, 0, T_U }, /* Key Unk: Keypad = */
+ /* 68 */ {0x64, 0x08, UNKN, 0, T_U }, /* Key Unk: F13 */
+ /* 69 */ {0x65, 0x10, UNKN, 0, T_U }, /* Key Unk: F14 */
+ /* 6A */ {0x66, 0x18, UNKN, 0, T_U }, /* Key Unk: F15 */
+ /* 6B */ {0x67, 0x20, UNKN, 0, T_U }, /* Key Unk: F16 */
+ /* 6C */ {0x68, 0x28, UNKN, 0, T_U }, /* Key Unk: F17 */
+ /* 6D */ {0x69, 0x30, UNKN, 0, T_U }, /* Key Unk: F18 */
+ /* 6E */ {0x6A, 0x38, UNKN, 0, T_U }, /* Key Unk: F19 */
+ /* 6F */ {0x6B, 0x40, UNKN, 0, T_U }, /* Key Unk: F20 */
+ /* 70 */ {0x6C, 0x48, UNKN, 0, T_U }, /* Key Unk: F21 */
+ /* 71 */ {0x6D, 0x50, UNKN, 0, T_U }, /* Key Unk: F22 */
+ /* 72 */ {0x6E, 0x57, UNKN, 0, T_U }, /* Key Unk: F23 */
+ /* 73 */ {0x76, 0x5F, UNKN, 0, T_U }, /* Key Unk: F24 */
+ /* 74 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Execute */
+ /* 75 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Help */
+ /* 76 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Menu */
+ /* 77 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Select */
+ /* 78 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Stop */
+ /* 79 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Again */
+ /* 7A */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Undo */
+ /* 7B */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Cut */
+ /* 7C */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Copy */
+ /* 7D */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Paste */
+ /* 7E */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Find */
+ /* 7F */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Mute */
+ /* 80 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Volume Up */
+ /* 81 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Volume Dn */
+ /* 82 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Locking Caps Lock */
+ /* 83 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Locking Num Lock */
+ /* 84 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Locking Scroll Lock */
+ /* 85 */ {0x7E, 0x6D, UNKN, 0, T_U }, /* Key Unk: Keypad , (Brazilian Keypad .) */
+ /* 86 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Equal Sign */
+ /* 87 */ {0x73, 0x51, UNKN, 0, T_U }, /* Key Unk: Keyboard Intl 1 (Ro) */
+ /* 88 */ {0x70, 0x13, UNKN, 0, T_U }, /* Key Unk: Keyboard Intl2 (K'kana/H'gana) */
+ /* 89 */ {0x7D, 0x6A, UNKN, 0, T_U }, /* Key Unk: Keyboard Intl 2 (Yen) */
+ /* 8A */ {0x79, 0x64, UNKN, 0, T_U }, /* Key Unk: Keyboard Intl 4 (Henkan) */
+ /* 8B */ {0x7B, 0x67, UNKN, 0, T_U }, /* Key Unk: Keyboard Intl 5 (Muhenkan) */
+ /* 8C */ {0x5C, 0x27, UNKN, 0, T_U }, /* Key Unk: Keyboard Intl 6 (PC9800 Pad ,) */
+ /* 8D */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Intl 7 */
+ /* 8E */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Intl 8 */
+ /* 8F */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Intl 9 */
+ /* 90 */ {0xF2, 0xF2, UNKN, KF_NB, T_U }, /* Key Unk: Keyboard Lang 1 (Hang'l/Engl) */
+ /* 91 */ {0xF1, 0xF1, UNKN, KF_NB, T_U }, /* Key Unk: Keyboard Lang 2 (Hanja) */
+ /* 92 */ {0x78, 0x63, UNKN, 0, T_U }, /* Key Unk: Keyboard Lang 3 (Katakana) */
+ /* 93 */ {0x77, 0x62, UNKN, 0, T_U }, /* Key Unk: Keyboard Lang 4 (Hiragana) */
+ /* 94 */ {0x76, 0x5F, UNKN, 0, T_U }, /* Key Unk: Keyboard Lang 5 (Zen/Han) */
+ /* 95 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Lang 6 */
+ /* 96 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Lang 7 */
+ /* 97 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Lang 8 */
+ /* 98 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Lang 9 */
+ /* 99 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Alternate Erase */
+ /* 9A */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard SysReq/Attention (Note 3) */
+ /* 9B */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Cancel */
+ /* 9C */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Clear */
+ /* 9D */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Prior */
+ /* 9E */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Return */
+ /* 9F */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Separator */
+ /* A0 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Out */
+ /* A1 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Oper */
+ /* A2 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard Clear/Again */
+ /* A3 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard CrSel/Props */
+ /* A4 */ {UNAS, UNAS, UNAS, 0, T_U }, /* Key Unk: Keyboard ExSel */
+};
+
+/*
+ * Note 1: The behavior of these keys depends on the state of modifier keys
+ * at the time the key was pressed.
+ *
+ * Note 2: The key label depends on the national version of the keyboard.
+ *
+ * Note 3: Certain keys which have their own PS/2 scancodes do not exist on
+ * USB keyboards; the SysReq key is an example. The SysReq key scancode needs
+ * to be translated to the Print Screen HID usage code. The HID usage to PS/2
+ * scancode conversion then generates the correct sequence depending on the
+ * keyboard state.
+ */
+
+/* USB to PS/2 conversion table for modifier keys. */
+static const key_def aPS2ModKeys[] = {
+ /* E0 */ {0x1D, 0x14, 0x11, 0, T_B }, /* Key 58: Left Control */
+ /* E1 */ {0x2A, 0x12, 0x12, 0, T_B }, /* Key 44: Left Shift */
+ /* E2 */ {0x38, 0x11, 0x19, 0, T_B }, /* Key 60: Left Alt */
+ /* E3 */ {0x5B, 0x1F, UNKN, KF_E0, T_U }, /* Key 127: Left GUI */
+ /* E4 */ {0x1D, 0x14, 0x58, KF_E0, T_M }, /* Key 64: Right Control */
+ /* E5 */ {0x36, 0x59, 0x59, 0, T_B }, /* Key 57: Right Shift */
+ /* E6 */ {0x38, 0x11, 0x39, KF_E0, T_M }, /* Key 62: Right Alt */
+ /* E7 */ {0x5C, 0x27, UNKN, KF_E0, T_U }, /* Key 128: Right GUI */
+};
+
+#endif /* IN_RING3 */
+
+
+
+/**
+ * Clear a queue.
+ *
+ * @param pQ Pointer to the queue.
+ */
+static void ps2kClearQueue(GeneriQ *pQ)
+{
+ LogFlowFunc(("Clearing queue %p\n", pQ));
+ pQ->wpos = pQ->rpos;
+ pQ->cUsed = 0;
+}
+
+
+/**
+ * Add a byte to a queue.
+ *
+ * @param pQ Pointer to the queue.
+ * @param val The byte to store.
+ */
+static void ps2kInsertQueue(GeneriQ *pQ, uint8_t val)
+{
+ /* Check if queue is full. */
+ if (pQ->cUsed >= pQ->cSize)
+ {
+ LogRelFlowFunc(("queue %p full (%d entries)\n", pQ, pQ->cUsed));
+ return;
+ }
+ /* Insert data and update circular buffer write position. */
+ pQ->abQueue[pQ->wpos] = val;
+ if (++pQ->wpos == pQ->cSize)
+ pQ->wpos = 0; /* Roll over. */
+ ++pQ->cUsed;
+ LogRelFlowFunc(("inserted 0x%02X into queue %p\n", val, pQ));
+}
+
+#ifdef IN_RING3
+
+/**
+ * Add a null-terminated byte sequence to a queue if there is enough room.
+ *
+ * @param pQ Pointer to the queue.
+ * @param pStr Pointer to the bytes to store.
+ * @param uReserve Number of bytes that must still remain
+ * available in queue.
+ * @return int VBox status/error code.
+ */
+static int ps2kInsertStrQueue(GeneriQ *pQ, const uint8_t *pStr, uint32_t uReserve)
+{
+ uint32_t cbStr;
+ unsigned i;
+
+ cbStr = (uint32_t)strlen((const char *)pStr);
+
+ /* Check if queue has enough room. */
+ if (pQ->cUsed + uReserve + cbStr >= pQ->cSize)
+ {
+ LogRelFlowFunc(("queue %p full (%u entries, want room for %u), cannot insert %u entries\n",
+ pQ, pQ->cUsed, uReserve, cbStr));
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* Insert byte sequence and update circular buffer write position. */
+ for (i = 0; i < cbStr; ++i) {
+ pQ->abQueue[pQ->wpos] = pStr[i];
+ if (++pQ->wpos == pQ->cSize)
+ pQ->wpos = 0; /* Roll over. */
+ }
+ pQ->cUsed += cbStr;
+ LogRelFlowFunc(("inserted %u bytes into queue %p\n", cbStr, pQ));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Save a queue state.
+ *
+ * @param pSSM SSM handle to write the state to.
+ * @param pQ Pointer to the queue.
+ */
+static void ps2kSaveQueue(PSSMHANDLE pSSM, GeneriQ *pQ)
+{
+ uint32_t cItems = pQ->cUsed;
+ int i;
+
+ /* Only save the number of items. Note that the read/write
+ * positions aren't saved as they will be rebuilt on load.
+ */
+ SSMR3PutU32(pSSM, cItems);
+
+ LogFlow(("Storing %d items from queue %p\n", cItems, pQ));
+
+ /* Save queue data - only the bytes actually used (typically zero). */
+ for (i = pQ->rpos; cItems-- > 0; i = (i + 1) % pQ->cSize)
+ SSMR3PutU8(pSSM, pQ->abQueue[i]);
+}
+
+/**
+ * Load a queue state.
+ *
+ * @param pSSM SSM handle to read the state from.
+ * @param pQ Pointer to the queue.
+ *
+ * @return int VBox status/error code.
+ */
+static int ps2kLoadQueue(PSSMHANDLE pSSM, GeneriQ *pQ)
+{
+ /* On load, always put the read pointer at zero. */
+ int rc = SSMR3GetU32(pSSM, &pQ->cUsed);
+ AssertRCReturn(rc, rc);
+ LogFlow(("Loading %u items to queue %p\n", pQ->cUsed, pQ));
+ AssertMsgReturn(pQ->cUsed <= pQ->cSize, ("Saved size=%u, actual=%u\n", pQ->cUsed, pQ->cSize),
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+ /* Recalculate queue positions and load data in one go. */
+ pQ->rpos = 0;
+ pQ->wpos = pQ->cUsed;
+ rc = SSMR3GetMem(pSSM, pQ->abQueue, pQ->cUsed);
+
+ return rc;
+}
+
+/**
+ * Notify listener about LEDs state change.
+ *
+ * @param pThis The PS/2 keyboard instance data.
+ * @param u8State Bitfield which reflects LEDs state.
+ */
+static void ps2kNotifyLedsState(PPS2K pThis, uint8_t u8State)
+{
+
+ PDMKEYBLEDS enmLeds = PDMKEYBLEDS_NONE;
+
+ if (u8State & 0x01)
+ enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_SCROLLLOCK);
+ if (u8State & 0x02)
+ enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_NUMLOCK);
+ if (u8State & 0x04)
+ enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_CAPSLOCK);
+
+ pThis->Keyboard.pDrv->pfnLedStatusChange(pThis->Keyboard.pDrv, enmLeds);
+
+}
+
+/**
+ * Query the number of items currently in a queue.
+ *
+ * @param pQ Pointer to the queue.
+ *
+ * @return uint32_t Number of items in queue.
+ */
+static uint32_t ps2kInQueue(GeneriQ *pQ)
+{
+ return pQ->cUsed;
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Retrieve a byte from a queue.
+ *
+ * @param pQ Pointer to the queue.
+ * @param pVal Pointer to storage for the byte.
+ *
+ * @return int VINF_TRY_AGAIN if queue is empty,
+ * VINF_SUCCESS if a byte was read.
+ */
+static int ps2kRemoveQueue(GeneriQ *pQ, uint8_t *pVal)
+{
+ int rc = VINF_TRY_AGAIN;
+
+ Assert(pVal);
+ if (pQ->cUsed)
+ {
+ *pVal = pQ->abQueue[pQ->rpos];
+ if (++pQ->rpos == pQ->cSize)
+ pQ->rpos = 0; /* Roll over. */
+ --pQ->cUsed;
+ rc = VINF_SUCCESS;
+ LogFlowFunc(("removed 0x%02X from queue %p\n", *pVal, pQ));
+ } else
+ LogFlowFunc(("queue %p empty\n", pQ));
+ return rc;
+}
+
+/* Clears the currently active typematic key, if any. */
+static void ps2kStopTypematicRepeat(PPS2K pThis)
+{
+ if (pThis->u8TypematicKey)
+ {
+ LogFunc(("Typematic key %02X\n", pThis->u8TypematicKey));
+ pThis->enmTypematicState = KBD_TMS_IDLE;
+ pThis->u8TypematicKey = 0;
+ TMTimerStop(pThis->CTX_SUFF(pKbdTypematicTimer));
+ }
+}
+
+/* Convert encoded typematic value to milliseconds. Note that the values are rated
+ * with +/- 20% accuracy, so there's no need for high precision.
+ */
+static void ps2kSetupTypematic(PPS2K pThis, uint8_t val)
+{
+ int A, B;
+ unsigned period;
+
+ pThis->u8TypematicCfg = val;
+ /* The delay is easy: (1 + value) * 250 ms */
+ pThis->uTypematicDelay = (1 + ((val >> 5) & 3)) * 250;
+ /* The rate is more complicated: (8 + A) * 2^B * 4.17 ms */
+ A = val & 7;
+ B = (val >> 3) & 3;
+ period = (8 + A) * (1 << B) * 417 / 100;
+ pThis->uTypematicRepeat = period;
+ Log(("Typematic delay %u ms, repeat period %u ms\n",
+ pThis->uTypematicDelay, pThis->uTypematicRepeat));
+}
+
+static void ps2kSetDefaults(PPS2K pThis)
+{
+ LogFlowFunc(("Set keyboard defaults\n"));
+ ps2kClearQueue((GeneriQ *)&pThis->keyQ);
+ /* Set default Scan Set 3 typematic values. */
+ /* Set default typematic rate/delay. */
+ ps2kSetupTypematic(pThis, KBD_DFL_RATE_DELAY);
+ /* Clear last typematic key?? */
+ ps2kStopTypematicRepeat(pThis);
+}
+
+/**
+ * Receive and process a byte sent by the keyboard controller.
+ *
+ * @param pThis The PS/2 keyboard instance data.
+ * @param cmd The command (or data) byte.
+ */
+int PS2KByteToKbd(PPS2K pThis, uint8_t cmd)
+{
+ bool fHandled = true;
+
+ LogFlowFunc(("new cmd=0x%02X, active cmd=0x%02X\n", cmd, pThis->u8CurrCmd));
+
+ if (pThis->u8CurrCmd == KCMD_RESET)
+ /* In reset mode, do not respond at all. */
+ return VINF_SUCCESS;
+
+ switch (cmd)
+ {
+ case KCMD_ECHO:
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ECHO);
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_READ_ID:
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ID1);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ID2);
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_ENABLE:
+ pThis->fScanning = true;
+ ps2kClearQueue((GeneriQ *)&pThis->keyQ);
+ ps2kStopTypematicRepeat(pThis);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_DFLT_DISABLE:
+ pThis->fScanning = false;
+ ps2kSetDefaults(pThis); /* Also clears buffer/typematic state. */
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_SET_DEFAULT:
+ ps2kSetDefaults(pThis);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_ALL_TYPEMATIC:
+ case KCMD_ALL_MK_BRK:
+ case KCMD_ALL_MAKE:
+ case KCMD_ALL_TMB:
+ /// @todo Set the key types here.
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_RESEND:
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_RESET:
+ pThis->u8ScanSet = 2;
+ ps2kSetDefaults(pThis);
+ /// @todo reset more?
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK);
+ pThis->u8CurrCmd = cmd;
+ /* Delay BAT completion; the test may take hundreds of ms. */
+ TMTimerSetMillies(pThis->CTX_SUFF(pKbdDelayTimer), 2);
+ break;
+ /* The following commands need a parameter. */
+ case KCMD_LEDS:
+ case KCMD_SCANSET:
+ case KCMD_RATE_DELAY:
+ case KCMD_TYPE_MATIC:
+ case KCMD_TYPE_MK_BRK:
+ case KCMD_TYPE_MAKE:
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK);
+ pThis->u8CurrCmd = cmd;
+ break;
+ default:
+ /* Sending a command instead of a parameter starts the new command. */
+ switch (pThis->u8CurrCmd)
+ {
+ case KCMD_LEDS:
+#ifndef IN_RING3
+ return VINF_IOM_R3_IOPORT_WRITE;
+#else
+ {
+ ps2kNotifyLedsState(pThis, cmd);
+ pThis->fNumLockOn = !!(cmd & 0x02); /* Sync internal Num Lock state. */
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK);
+ pThis->u8LEDs = cmd;
+ pThis->u8CurrCmd = 0;
+ }
+#endif
+ break;
+ case KCMD_SCANSET:
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK);
+ if (cmd == 0)
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->u8ScanSet);
+ else if (cmd < 4)
+ {
+ pThis->u8ScanSet = cmd;
+ LogRel(("PS2K: Selected scan set %d\n", cmd));
+ }
+ /* Other values are simply ignored. */
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_RATE_DELAY:
+ ps2kSetupTypematic(pThis, cmd);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ default:
+ fHandled = false;
+ }
+ /* Fall through only to handle unrecognized commands. */
+ if (fHandled)
+ break;
+ RT_FALL_THRU();
+
+ case KCMD_INVALID_1:
+ case KCMD_INVALID_2:
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_RESEND);
+ pThis->u8CurrCmd = 0;
+ break;
+ }
+ LogFlowFunc(("Active cmd now 0x%02X; updating interrupts\n", pThis->u8CurrCmd));
+// KBCUpdateInterrupts(pThis->pParent);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Send a byte (keystroke or command response) to the keyboard controller.
+ *
+ * @returns VINF_SUCCESS or VINF_TRY_AGAIN.
+ * @param pThis The PS/2 keyboard instance data.
+ * @param pb Where to return the byte we've read.
+ * @remarks Caller must have entered the device critical section.
+ */
+int PS2KByteFromKbd(PPS2K pThis, uint8_t *pb)
+{
+ int rc;
+
+ AssertPtr(pb);
+
+ /* Anything in the command queue has priority over data
+ * in the keystroke queue. Additionally, keystrokes are
+ * blocked if a command is currently in progress, even if
+ * the command queue is empty.
+ */
+ rc = ps2kRemoveQueue((GeneriQ *)&pThis->cmdQ, pb);
+ if (rc != VINF_SUCCESS && !pThis->u8CurrCmd && pThis->fScanning)
+ if (!pThis->fThrottleActive)
+ {
+ rc = ps2kRemoveQueue((GeneriQ *)&pThis->keyQ, pb);
+ if (pThis->fThrottleEnabled) {
+ pThis->fThrottleActive = true;
+ TMTimerSetMillies(pThis->CTX_SUFF(pThrottleTimer), KBD_THROTTLE_DELAY);
+ }
+ }
+
+ LogFlowFunc(("keyboard sends 0x%02x (%svalid data)\n", *pb, rc == VINF_SUCCESS ? "" : "not "));
+ return rc;
+}
+
+#ifdef IN_RING3
+
+static int ps2kProcessKeyEvent(PPS2K pThis, uint8_t u8HidCode, bool fKeyDown)
+{
+ key_def const *pKeyDef;
+ uint8_t abCodes[16];
+ char *pCodes;
+ size_t cbLeft;
+ uint8_t abScan[2];
+
+ LogFlowFunc(("key %s: 0x%02x (set %d)\n", fKeyDown ? "down" : "up", u8HidCode, pThis->u8ScanSet));
+
+ /* Find the key definition in somewhat sparse storage. */
+ pKeyDef = u8HidCode >= HID_MODIFIER_FIRST ? &aPS2ModKeys[u8HidCode - HID_MODIFIER_FIRST] : &aPS2Keys[u8HidCode];
+
+ /* Some keys are not processed at all; early return. */
+ if (pKeyDef->makeS1 == NONE)
+ {
+ LogFlow(("Skipping key processing.\n"));
+ return VINF_SUCCESS;
+ }
+
+ /* Handle modifier keys (Ctrl/Alt/Shift/GUI). We need to keep track
+ * of their state in addition to sending the scan code.
+ */
+ if (u8HidCode >= HID_MODIFIER_FIRST)
+ {
+ unsigned mod_bit = 1 << (u8HidCode - HID_MODIFIER_FIRST);
+
+ Assert((u8HidCode <= HID_MODIFIER_LAST));
+ if (fKeyDown)
+ pThis->u8Modifiers |= mod_bit;
+ else
+ pThis->u8Modifiers &= ~mod_bit;
+ }
+
+ /* Toggle NumLock state. */
+ if ((pKeyDef->keyFlags & KF_NL) && fKeyDown)
+ pThis->fNumLockOn ^= true;
+
+ abCodes[0] = 0;
+ pCodes = (char *)abCodes;
+ cbLeft = sizeof(abCodes);
+
+ if (pThis->u8ScanSet == 1 || pThis->u8ScanSet == 2)
+ {
+ /* The basic scan set 1 and 2 logic is the same, only the scan codes differ.
+ * Since scan set 2 is used almost all the time, that case is handled first.
+ */
+ if (fKeyDown)
+ {
+ /* Process key down event. */
+ if (pKeyDef->keyFlags & KF_PB)
+ {
+ /* Pause/Break sends different data if either Ctrl is held. */
+ if (pThis->u8Modifiers & (MOD_LCTRL | MOD_RCTRL))
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE0\x7E\xE0\xF0\x7E" : "\xE0\x46\xE0\xC6");
+ else
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE1\x14\x77\xE1\xF0\x14\xF0\x77" : "\xE1\x1D\x45\xE1\x9D\xC5");
+ }
+ else if (pKeyDef->keyFlags & KF_PS)
+ {
+ /* Print Screen depends on all of Ctrl, Shift, *and* Alt! */
+ if (pThis->u8Modifiers & (MOD_LALT | MOD_RALT))
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\x84" : "\x54");
+ else if (pThis->u8Modifiers & (MOD_LSHIFT | MOD_RSHIFT))
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE0\x7C" : "\xE0\x37");
+ else
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE0\x12\xE0\x7C" : "\xE0\x2A\xE0\x37");
+ }
+ else if (pKeyDef->keyFlags & (KF_GK | KF_NS))
+ {
+ /* The numeric pad keys fake Shift presses or releases
+ * depending on Num Lock and Shift key state. The '/'
+ * key behaves in a similar manner but does not depend on
+ * the Num Lock state.
+ */
+ if (!pThis->fNumLockOn || (pKeyDef->keyFlags & KF_NS))
+ {
+ if (pThis->u8Modifiers & MOD_LSHIFT)
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE0\xF0\x12" : "\xE0\xAA");
+ if (pThis->u8Modifiers & MOD_RSHIFT)
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE0\xF0\x59" : "\xE0\xB6");
+ }
+ else
+ {
+ Assert(pThis->fNumLockOn); /* Not for KF_NS! */
+ if ((pThis->u8Modifiers & (MOD_LSHIFT | MOD_RSHIFT)) == 0)
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE0\x12" : "\xE0\x2A");
+ /* Else Shift cancels NumLock, so no prefix! */
+ }
+ }
+
+ /* Standard processing for regular keys only. */
+ abScan[0] = pThis->u8ScanSet == 2 ? pKeyDef->makeS2 : pKeyDef->makeS1;
+ abScan[1] = '\0';
+ if (!(pKeyDef->keyFlags & (KF_PB | KF_PS)))
+ {
+ if (pKeyDef->keyFlags & (KF_E0 | KF_GK | KF_NS))
+ RTStrCatP(&pCodes, &cbLeft, "\xE0");
+ RTStrCatP(&pCodes, &cbLeft, (const char *)abScan);
+ }
+
+ /* Feed the bytes to the queue if there is room. */
+ /// @todo Send overrun code if sequence won't fit?
+ ps2kInsertStrQueue((GeneriQ *)&pThis->keyQ, abCodes, 0);
+ }
+ else if (!(pKeyDef->keyFlags & (KF_NB | KF_PB)))
+ {
+ /* Process key up event except for keys which produce none. */
+
+ /* Handle Print Screen release. */
+ if (pKeyDef->keyFlags & KF_PS)
+ {
+ /* Undo faked Print Screen state as needed. */
+ if (pThis->u8Modifiers & (MOD_LALT | MOD_RALT))
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xF0\x84" : "\xD4");
+ else if (pThis->u8Modifiers & (MOD_LSHIFT | MOD_RSHIFT))
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE0\xF0\x7C" : "\xE0\xB7");
+ else
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE0\xF0\x7C\xE0\xF0\x12" : "\xE0\xB7\xE0\xAA");
+ }
+ else
+ {
+ /* Process base scan code for less unusual keys. */
+ abScan[0] = pThis->u8ScanSet == 2 ? pKeyDef->makeS2 : pKeyDef->makeS1 | 0x80;
+ abScan[1] = '\0';
+ if (pKeyDef->keyFlags & (KF_E0 | KF_GK | KF_NS))
+ RTStrCatP(&pCodes, &cbLeft, "\xE0");
+ if (pThis->u8ScanSet == 2)
+ RTStrCatP(&pCodes, &cbLeft, "\xF0");
+ RTStrCatP(&pCodes, &cbLeft, (const char *)abScan);
+
+ /* Restore shift state for gray keys. */
+ if (pKeyDef->keyFlags & (KF_GK | KF_NS))
+ {
+ if (!pThis->fNumLockOn || (pKeyDef->keyFlags & KF_NS))
+ {
+ if (pThis->u8Modifiers & MOD_LSHIFT)
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE0\x12" : "\xE0\x2A");
+ if (pThis->u8Modifiers & MOD_RSHIFT)
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE0\x59" : "\xE0\x36");
+ }
+ else
+ {
+ Assert(pThis->fNumLockOn); /* Not for KF_NS! */
+ if ((pThis->u8Modifiers & (MOD_LSHIFT | MOD_RSHIFT)) == 0)
+ RTStrCatP(&pCodes, &cbLeft, pThis->u8ScanSet == 2 ?
+ "\xE0\xF0\x12" : "\xE0\xAA");
+ }
+ }
+ }
+
+ /* Feed the bytes to the queue if there is room. */
+ /// @todo Send overrun code if sequence won't fit?
+ ps2kInsertStrQueue((GeneriQ *)&pThis->keyQ, abCodes, 0);
+ }
+ }
+ else
+ {
+ /* Handle Scan Set 3 - very straightforward. */
+ Assert(pThis->u8ScanSet == 3);
+ abScan[0] = pKeyDef->makeS3;
+ abScan[1] = '\0';
+ if (fKeyDown)
+ {
+ RTStrCatP(&pCodes, &cbLeft, (const char *)abScan);
+ }
+ else
+ {
+ /* Send a key release code unless it's a make only key. */
+ /// @todo Look up the current typematic setting, not the default!
+ if (pKeyDef->keyMatic != T_M)
+ {
+ RTStrCatP(&pCodes, &cbLeft, "\xF0");
+ RTStrCatP(&pCodes, &cbLeft, (const char *)abScan);
+ }
+ }
+ /* Feed the bytes to the queue if there is room. */
+ /// @todo Send overrun code if sequence won't fit?
+ ps2kInsertStrQueue((GeneriQ *)&pThis->keyQ, abCodes, 0);
+ }
+
+ /* Set up or cancel typematic key repeat. */
+ if (fKeyDown)
+ {
+ if (pThis->u8TypematicKey != u8HidCode)
+ {
+ pThis->enmTypematicState = KBD_TMS_DELAY;
+ pThis->u8TypematicKey = u8HidCode;
+ TMTimerSetMillies(pThis->CTX_SUFF(pKbdTypematicTimer), pThis->uTypematicDelay);
+ Log(("Typematic delay %u ms, key %02X\n", pThis->uTypematicDelay, u8HidCode));
+ }
+ }
+ else
+ {
+ /* "Typematic operation stops when the last key pressed is released, even
+ * if other keys are still held down." (IBM PS/2 Tech Ref). The last key pressed
+ * is the one that's being repeated.
+ */
+ if (pThis->u8TypematicKey == u8HidCode)
+ {
+ /* This disables the typematic repeat. */
+ pThis->u8TypematicKey = 0;
+ pThis->enmTypematicState = KBD_TMS_IDLE;
+ /* For good measure, we cancel the timer, too. */
+ TMTimerStop(pThis->CTX_SUFF(pKbdTypematicTimer));
+ Log(("Typematic action cleared for key %02X\n", u8HidCode));
+ }
+ }
+
+ /* Poke the KBC to update its state. */
+ KBCUpdateInterrupts(pThis->pParent);
+
+ return VINF_SUCCESS;
+}
+
+/* Throttling timer to emulate the finite keyboard communication speed. A PS/2 keyboard is
+ * limited by the serial link speed and cannot send much more than 1,000 bytes per second.
+ * Some software (notably Borland Pascal and programs built with its run-time) relies on
+ * being able to read an incoming scan-code twice. Throttling the data rate enables such
+ * software to function, while human typists cannot tell any difference.
+ *
+ * Note: The throttling is currently only done for keyboard data, not command responses.
+ * The throttling could and perhaps should be done for any data (including command
+ * response) scoming from PS/2 devices, both keyboard and auxiliary. That is not currently
+ * done because it would needlessly slow things down.
+ */
+static DECLCALLBACK(void) ps2kThrottleTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF2(pDevIns, pTimer);
+ PPS2K pThis = (PS2K *)pvUser;
+ unsigned uHaveData;
+
+ /* Grab the lock to avoid races with event delivery or EMTs. */
+ int rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY);
+ AssertReleaseRC(rc);
+
+ /* If data is available, poke the KBC. Once the data
+ * is actually read, the timer may be re-triggered.
+ */
+ pThis->fThrottleActive = false;
+ uHaveData = ps2kInQueue((GeneriQ *)&pThis->keyQ);
+ LogFlowFunc(("Have%s bytes\n", uHaveData ? "" : " no"));
+ if (uHaveData)
+ KBCUpdateInterrupts(pThis->pParent);
+
+ PDMCritSectLeave(pThis->pCritSectR3);
+}
+
+/* Timer handler for emulating typematic keys. Note that only the last key
+ * held down repeats (if typematic).
+ */
+static DECLCALLBACK(void) ps2kTypematicTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF2(pDevIns, pTimer);
+ PPS2K pThis = (PS2K *)pvUser;
+ LogFlowFunc(("Typematic state=%d, key %02X\n", pThis->enmTypematicState, pThis->u8TypematicKey));
+
+ /* If the current typematic key is zero, the repeat was canceled just when
+ * the timer was about to run. In that case, do nothing.
+ */
+ if (pThis->u8TypematicKey)
+ {
+ if (pThis->enmTypematicState == KBD_TMS_DELAY)
+ pThis->enmTypematicState = KBD_TMS_REPEAT;
+
+ if (pThis->enmTypematicState == KBD_TMS_REPEAT)
+ {
+ ps2kProcessKeyEvent(pThis, pThis->u8TypematicKey, true /* Key down */ );
+ TMTimerSetMillies(pThis->CTX_SUFF(pKbdTypematicTimer), pThis->uTypematicRepeat);
+ }
+ }
+}
+
+/* The keyboard BAT is specified to take several hundred milliseconds. We need
+ * to delay sending the result to the host for at least a tiny little while.
+ */
+static DECLCALLBACK(void) ps2kDelayTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF2(pDevIns, pTimer);
+ PPS2K pThis = (PS2K *)pvUser;
+
+ LogFlowFunc(("Delay timer: cmd %02X\n", pThis->u8CurrCmd));
+
+ AssertMsg(pThis->u8CurrCmd == KCMD_RESET, ("u8CurrCmd=%02x\n", pThis->u8CurrCmd));
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_BAT_OK);
+ pThis->fScanning = true; /* BAT completion enables scanning! */
+ pThis->u8CurrCmd = 0;
+
+ /// @todo Might want a PS2KCompleteCommand() to push last response, clear command, and kick the KBC...
+ /* Give the KBC a kick. */
+ KBCUpdateInterrupts(pThis->pParent);
+}
+
+/* Release any and all currently depressed keys. Used whenever the guest keyboard
+ * is likely to be out of sync with the host, such as when loading a saved state
+ * or resuming a suspended host.
+ */
+static void ps2kReleaseKeys(PPS2K pThis)
+{
+ LogFlowFunc(("Releasing keys...\n"));
+
+ for (unsigned uKey = 0; uKey < sizeof(pThis->abDepressedKeys); ++uKey)
+ if (pThis->abDepressedKeys[uKey])
+ {
+ ps2kProcessKeyEvent(pThis, uKey, false /* key up */);
+ pThis->abDepressedKeys[uKey] = 0;
+ }
+ LogFlowFunc(("Done releasing keys\n"));
+}
+
+
+/**
+ * Debug device info handler. Prints basic keyboard state.
+ *
+ * @param pDevIns Device instance which registered the info.
+ * @param pHlp Callback functions for doing output.
+ * @param pszArgs Argument string. Optional and specific to the handler.
+ */
+static DECLCALLBACK(void) ps2kInfoState(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PPS2K pThis = KBDGetPS2KFromDevIns(pDevIns);
+ NOREF(pszArgs);
+
+ pHlp->pfnPrintf(pHlp, "PS/2 Keyboard: scan set %d, scanning %s\n",
+ pThis->u8ScanSet, pThis->fScanning ? "enabled" : "disabled");
+ pHlp->pfnPrintf(pHlp, "Active command %02X\n", pThis->u8CurrCmd);
+ pHlp->pfnPrintf(pHlp, "LED state %02X, Num Lock %s\n", pThis->u8LEDs,
+ pThis->fNumLockOn ? "on" : "off");
+ pHlp->pfnPrintf(pHlp, "Typematic delay %ums, repeat period %ums\n",
+ pThis->uTypematicDelay, pThis->uTypematicRepeat);
+ pHlp->pfnPrintf(pHlp, "Command queue: %d items (%d max)\n",
+ pThis->cmdQ.cUsed, pThis->cmdQ.cSize);
+ pHlp->pfnPrintf(pHlp, "Input queue : %d items (%d max)\n",
+ pThis->keyQ.cUsed, pThis->keyQ.cSize);
+ if (pThis->enmTypematicState != KBD_TMS_IDLE)
+ pHlp->pfnPrintf(pHlp, "Active typematic key %02X (%s)\n", pThis->u8TypematicKey,
+ pThis->enmTypematicState == KBD_TMS_DELAY ? "delay" : "repeat");
+}
+
+/* -=-=-=-=-=- Keyboard: IBase -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ps2kQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPS2K pThis = RT_FROM_MEMBER(pInterface, PS2K, Keyboard.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->Keyboard.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIKEYBOARDPORT, &pThis->Keyboard.IPort);
+ return NULL;
+}
+
+
+/* -=-=-=-=-=- Keyboard: IKeyboardPort -=-=-=-=-=- */
+
+/**
+ * Keyboard event handler.
+ *
+ * @returns VBox status code.
+ * @param pThis The PS2 keyboard instance data.
+ * @param u32Usage USB HID usage code with key
+ * press/release flag.
+ */
+static int ps2kPutEventWorker(PPS2K pThis, uint32_t u32Usage)
+{
+ uint8_t u8HidCode;
+ bool fKeyDown;
+ bool fHaveEvent = true;
+ int rc = VINF_SUCCESS;
+
+ /* Extract the usage code and ensure it's valid. */
+ fKeyDown = !(u32Usage & 0x80000000);
+ u8HidCode = u32Usage & 0xFF;
+ AssertReturn(u8HidCode <= VBOX_USB_MAX_USAGE_CODE, VERR_INTERNAL_ERROR);
+
+ if (fKeyDown)
+ {
+ /* Due to host key repeat, we can get key events for keys which are
+ * already depressed. We need to ignore those. */
+ if (pThis->abDepressedKeys[u8HidCode])
+ fHaveEvent = false;
+ pThis->abDepressedKeys[u8HidCode] = 1;
+ }
+ else
+ {
+ /* NB: We allow key release events for keys which aren't depressed.
+ * That is unlikely to happen and should not cause trouble.
+ */
+ pThis->abDepressedKeys[u8HidCode] = 0;
+ }
+
+ /* Unless this is a new key press/release, don't even bother. */
+ if (fHaveEvent)
+ {
+ rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY);
+ AssertReleaseRC(rc);
+
+ rc = ps2kProcessKeyEvent(pThis, u8HidCode, fKeyDown);
+
+ PDMCritSectLeave(pThis->pCritSectR3);
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) ps2kPutEventWrapper(PPDMIKEYBOARDPORT pInterface, uint32_t u32UsageCode)
+{
+ PPS2K pThis = RT_FROM_MEMBER(pInterface, PS2K, Keyboard.IPort);
+ int rc;
+
+ LogRelFlowFunc(("key code %08X\n", u32UsageCode));
+
+ rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY);
+ AssertReleaseRC(rc);
+
+ /* The 'BAT fail' scancode is reused as a signal to release keys. No actual
+ * key is allowed to use this scancode.
+ */
+ if (RT_UNLIKELY(u32UsageCode == KRSP_BAT_FAIL))
+ {
+ ps2kReleaseKeys(pThis);
+ }
+ else
+ {
+ ps2kPutEventWorker(pThis, u32UsageCode);
+ }
+
+ PDMCritSectLeave(pThis->pCritSectR3);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Attach command.
+ *
+ * This is called to let the device attach to a driver for a
+ * specified LUN.
+ *
+ * This is like plugging in the keyboard after turning on the
+ * system.
+ *
+ * @returns VBox status code.
+ * @param pThis The PS/2 keyboard instance data.
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+int PS2KAttach(PPS2K pThis, PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ int rc;
+
+ /* The LUN must be 0, i.e. keyboard. */
+ Assert(iLUN == 0);
+ AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("PS/2 keyboard does not support hotplugging\n"),
+ VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("iLUN=%d\n", iLUN));
+
+ rc = PDMDevHlpDriverAttach(pDevIns, iLUN, &pThis->Keyboard.IBase, &pThis->Keyboard.pDrvBase, "Keyboard Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Keyboard.pDrv = PDMIBASE_QUERY_INTERFACE(pThis->Keyboard.pDrvBase, PDMIKEYBOARDCONNECTOR);
+ if (!pThis->Keyboard.pDrv)
+ {
+ AssertLogRelMsgFailed(("LUN #0 doesn't have a keyboard interface! rc=%Rrc\n", rc));
+ rc = VERR_PDM_MISSING_INTERFACE;
+ }
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ Log(("%s/%d: warning: no driver attached to LUN #0!\n", pDevIns->pReg->szName, pDevIns->iInstance));
+ rc = VINF_SUCCESS;
+ }
+ else
+ AssertLogRelMsgFailed(("Failed to attach LUN #0! rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+void PS2KSaveState(PPS2K pThis, PSSMHANDLE pSSM)
+{
+ uint32_t cPressed = 0;
+ uint32_t cbTMSSize = 0;
+
+ LogFlowFunc(("Saving PS2K state\n"));
+
+ /* Save the basic keyboard state. */
+ SSMR3PutU8(pSSM, pThis->u8CurrCmd);
+ SSMR3PutU8(pSSM, pThis->u8LEDs);
+ SSMR3PutU8(pSSM, pThis->u8TypematicCfg);
+ SSMR3PutU8(pSSM, pThis->u8TypematicKey);
+ SSMR3PutU8(pSSM, pThis->u8Modifiers);
+ SSMR3PutU8(pSSM, pThis->u8ScanSet);
+ SSMR3PutU8(pSSM, pThis->enmTypematicState);
+ SSMR3PutBool(pSSM, pThis->fNumLockOn);
+ SSMR3PutBool(pSSM, pThis->fScanning);
+
+ /* Save the command and keystroke queues. */
+ ps2kSaveQueue(pSSM, (GeneriQ *)&pThis->cmdQ);
+ ps2kSaveQueue(pSSM, (GeneriQ *)&pThis->keyQ);
+
+ /* Save the command delay timer. Note that the typematic repeat
+ * timer is *not* saved.
+ */
+ TMR3TimerSave(pThis->CTX_SUFF(pKbdDelayTimer), pSSM);
+
+ /* Save any pressed keys. This is necessary to avoid "stuck"
+ * keys after a restore. Needs two passes.
+ */
+ for (unsigned i = 0; i < sizeof(pThis->abDepressedKeys); ++i)
+ if (pThis->abDepressedKeys[i])
+ ++cPressed;
+
+ SSMR3PutU32(pSSM, cPressed);
+
+ for (unsigned uKey = 0; uKey < sizeof(pThis->abDepressedKeys); ++uKey)
+ if (pThis->abDepressedKeys[uKey])
+ SSMR3PutU8(pSSM, uKey);
+
+ /* Save the typematic settings for Scan Set 3. */
+ SSMR3PutU32(pSSM, cbTMSSize);
+ /* Currently not implemented. */
+}
+
+int PS2KLoadState(PPS2K pThis, PSSMHANDLE pSSM, uint32_t uVersion)
+{
+ uint8_t u8;
+ uint32_t cPressed;
+ uint32_t cbTMSSize;
+ int rc;
+
+ NOREF(uVersion);
+ LogFlowFunc(("Loading PS2K state version %u\n", uVersion));
+
+ /* Load the basic keyboard state. */
+ SSMR3GetU8(pSSM, &pThis->u8CurrCmd);
+ SSMR3GetU8(pSSM, &pThis->u8LEDs);
+ SSMR3GetU8(pSSM, &pThis->u8TypematicCfg);
+ SSMR3GetU8(pSSM, &pThis->u8TypematicKey);
+ SSMR3GetU8(pSSM, &pThis->u8Modifiers);
+ SSMR3GetU8(pSSM, &pThis->u8ScanSet);
+ SSMR3GetU8(pSSM, &u8);
+ pThis->enmTypematicState = (tmatic_state_t)u8;
+ SSMR3GetBool(pSSM, &pThis->fNumLockOn);
+ SSMR3GetBool(pSSM, &pThis->fScanning);
+
+ /* Load the command and keystroke queues. */
+ rc = ps2kLoadQueue(pSSM, (GeneriQ *)&pThis->cmdQ);
+ AssertRCReturn(rc, rc);
+ rc = ps2kLoadQueue(pSSM, (GeneriQ *)&pThis->keyQ);
+ AssertRCReturn(rc, rc);
+
+ /* Load the command delay timer, just in case. */
+ rc = TMR3TimerLoad(pThis->CTX_SUFF(pKbdDelayTimer), pSSM);
+ AssertRCReturn(rc, rc);
+
+ /* Recalculate the typematic delay/rate. */
+ ps2kSetupTypematic(pThis, pThis->u8TypematicCfg);
+
+ /* Fake key up events for keys that were held down at the time the state was saved. */
+ rc = SSMR3GetU32(pSSM, &cPressed);
+ AssertRCReturn(rc, rc);
+
+ /* If any keys were down, load and then release them. */
+ if (cPressed)
+ {
+ for (unsigned i = 0; i < cPressed; ++i)
+ {
+ rc = SSMR3GetU8(pSSM, &u8);
+ AssertRCReturn(rc, rc);
+ pThis->abDepressedKeys[u8] = 1;
+ }
+ }
+
+ /* Load typematic settings for Scan Set 3. */
+ rc = SSMR3GetU32(pSSM, &cbTMSSize);
+ AssertRCReturn(rc, rc);
+
+ while (cbTMSSize--)
+ {
+ rc = SSMR3GetU8(pSSM, &u8);
+ AssertRCReturn(rc, rc);
+ }
+
+ return rc;
+}
+
+int PS2KLoadDone(PPS2K pThis, PSSMHANDLE pSSM)
+{
+ RT_NOREF1(pSSM);
+
+ /* This *must* be done after the inital load because it may trigger
+ * interrupts and change the interrupt controller state.
+ */
+ ps2kReleaseKeys(pThis);
+ ps2kNotifyLedsState(pThis, pThis->u8LEDs);
+ return VINF_SUCCESS;
+}
+
+void PS2KReset(PPS2K pThis)
+{
+ LogFlowFunc(("Resetting PS2K\n"));
+
+ pThis->fScanning = true;
+ pThis->fThrottleActive = false;
+ pThis->u8ScanSet = 2;
+ pThis->u8CurrCmd = 0;
+ pThis->u8Modifiers = 0;
+ pThis->u8TypematicKey = 0;
+ pThis->enmTypematicState = KBD_TMS_IDLE;
+
+ /* Clear queues and any pressed keys. */
+ memset(pThis->abDepressedKeys, 0, sizeof(pThis->abDepressedKeys));
+ ps2kClearQueue((GeneriQ *)&pThis->cmdQ);
+ ps2kSetDefaults(pThis); /* Also clears keystroke queue. */
+
+ /* Activate the PS/2 keyboard by default. */
+ if (pThis->Keyboard.pDrv)
+ pThis->Keyboard.pDrv->pfnSetActive(pThis->Keyboard.pDrv, true);
+}
+
+void PS2KRelocate(PPS2K pThis, RTGCINTPTR offDelta, PPDMDEVINS pDevIns)
+{
+ RT_NOREF1(pDevIns);
+ LogFlowFunc(("Relocating PS2K\n"));
+ pThis->pKbdDelayTimerRC = TMTimerRCPtr(pThis->pKbdDelayTimerR3);
+ pThis->pKbdTypematicTimerRC = TMTimerRCPtr(pThis->pKbdTypematicTimerR3);
+ pThis->pThrottleTimerRC = TMTimerRCPtr(pThis->pThrottleTimerR3);
+ NOREF(offDelta);
+}
+
+int PS2KConstruct(PPS2K pThis, PPDMDEVINS pDevIns, void *pParent, int iInstance, PCFGMNODE pCfg)
+{
+ RT_NOREF2(pDevIns, iInstance);
+ LogFlowFunc(("iInstance=%d\n", iInstance));
+
+ pThis->pParent = pParent;
+
+ bool fThrottleEnabled;
+ int rc = CFGMR3QueryBoolDef(pCfg, "KbdThrottleEnabled", &fThrottleEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"KbdThrottleEnabled\" from the config"));
+ Log(("KbdThrottleEnabled=%u\n", fThrottleEnabled));
+ pThis->fThrottleEnabled = fThrottleEnabled;
+
+ /* Initialize the queues. */
+ pThis->keyQ.cSize = KBD_KEY_QUEUE_SIZE;
+ pThis->cmdQ.cSize = KBD_CMD_QUEUE_SIZE;
+
+ pThis->Keyboard.IBase.pfnQueryInterface = ps2kQueryInterface;
+ pThis->Keyboard.IPort.pfnPutEventHid = ps2kPutEventWrapper;
+
+ /*
+ * Initialize the critical section pointer(s).
+ */
+ pThis->pCritSectR3 = pDevIns->pCritSectRoR3;
+
+ /*
+ * Create the input rate throttling timer.
+ */
+ PTMTIMER pTimer;
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ps2kThrottleTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "PS2K Throttle Timer", &pTimer);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pThis->pThrottleTimerR3 = pTimer;
+ pThis->pThrottleTimerR0 = TMTimerR0Ptr(pTimer);
+ pThis->pThrottleTimerRC = TMTimerRCPtr(pTimer);
+
+ /*
+ * Create the typematic delay/repeat timer.
+ */
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ps2kTypematicTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "PS2K Typematic Timer", &pTimer);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pThis->pKbdTypematicTimerR3 = pTimer;
+ pThis->pKbdTypematicTimerR0 = TMTimerR0Ptr(pTimer);
+ pThis->pKbdTypematicTimerRC = TMTimerRCPtr(pTimer);
+
+ /*
+ * Create the command delay timer.
+ */
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ps2kDelayTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "PS2K Delay Timer", &pTimer);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pThis->pKbdDelayTimerR3 = pTimer;
+ pThis->pKbdDelayTimerR0 = TMTimerR0Ptr(pTimer);
+ pThis->pKbdDelayTimerRC = TMTimerRCPtr(pTimer);
+
+ /*
+ * Register debugger info callbacks.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ps2k", "Display PS/2 keyboard state.", ps2kInfoState);
+
+ return rc;
+}
+
+#endif
+
+/// @todo The following should live with the KBC implementation.
+
+/* Table used by the keyboard controller to optionally translate the incoming
+ * keyboard data. Note that the translation is designed for essentially taking
+ * Scan Set 2 input and producing Scan Set 1 output, but can be turned on and
+ * off regardless of what the keyboard is sending.
+ */
+static uint8_t aAT2PC[128] = {
+ 0xff,0x43,0x41,0x3f,0x3d,0x3b,0x3c,0x58,0x64,0x44,0x42,0x40,0x3e,0x0f,0x29,0x59,
+ 0x65,0x38,0x2a,0x70,0x1d,0x10,0x02,0x5a,0x66,0x71,0x2c,0x1f,0x1e,0x11,0x03,0x5b,
+ 0x67,0x2e,0x2d,0x20,0x12,0x05,0x04,0x5c,0x68,0x39,0x2f,0x21,0x14,0x13,0x06,0x5d,
+ 0x69,0x31,0x30,0x23,0x22,0x15,0x07,0x5e,0x6a,0x72,0x32,0x24,0x16,0x08,0x09,0x5f,
+ 0x6b,0x33,0x25,0x17,0x18,0x0b,0x0a,0x60,0x6c,0x34,0x35,0x26,0x27,0x19,0x0c,0x61,
+ 0x6d,0x73,0x28,0x74,0x1a,0x0d,0x62,0x6e,0x3a,0x36,0x1c,0x1b,0x75,0x2b,0x63,0x76,
+ 0x55,0x56,0x77,0x78,0x79,0x7a,0x0e,0x7b,0x7c,0x4f,0x7d,0x4b,0x47,0x7e,0x7f,0x6f,
+ 0x52,0x53,0x50,0x4c,0x4d,0x48,0x01,0x45,0x57,0x4e,0x51,0x4a,0x37,0x49,0x46,0x54
+};
+
+/**
+ * Convert an AT (Scan Set 2) scancode to PC (Scan Set 1).
+ *
+ * @param state Current state of the translator
+ * (xlat_state_t).
+ * @param scanIn Incoming scan code.
+ * @param pScanOut Pointer to outgoing scan code. The
+ * contents are only valid if returned
+ * state is not XS_BREAK.
+ *
+ * @return xlat_state_t New state of the translator.
+ */
+int32_t XlateAT2PC(int32_t state, uint8_t scanIn, uint8_t *pScanOut)
+{
+ uint8_t scan_in;
+ uint8_t scan_out;
+
+ Assert(pScanOut);
+ Assert(state == XS_IDLE || state == XS_BREAK || state == XS_HIBIT);
+
+ /* Preprocess the scan code for a 128-entry translation table. */
+ if (scanIn == 0x83) /* Check for F7 key. */
+ scan_in = 0x02;
+ else if (scanIn == 0x84) /* Check for SysRq key. */
+ scan_in = 0x7f;
+ else
+ scan_in = scanIn;
+
+ /* Values 0x80 and above are passed through, except for 0xF0
+ * which indicates a key release.
+ */
+ if (scan_in < 0x80)
+ {
+ scan_out = aAT2PC[scan_in];
+ /* Turn into break code if required. */
+ if (state == XS_BREAK || state == XS_HIBIT)
+ scan_out |= 0x80;
+
+ state = XS_IDLE;
+ }
+ else
+ {
+ /* NB: F0 E0 10 will be translated to E0 E5 (high bit set on last byte)! */
+ if (scan_in == 0xF0) /* Check for break code. */
+ state = XS_BREAK;
+ else if (state == XS_BREAK)
+ state = XS_HIBIT; /* Remember the break bit. */
+ scan_out = scan_in;
+ }
+ LogFlowFunc(("scan code %02X translated to %02X; new state is %d\n",
+ scanIn, scan_out, state));
+
+ *pScanOut = scan_out;
+ return state;
+}
+
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
diff --git a/src/VBox/Devices/Input/PS2M.cpp b/src/VBox/Devices/Input/PS2M.cpp
new file mode 100644
index 00000000..8288cf02
--- /dev/null
+++ b/src/VBox/Devices/Input/PS2M.cpp
@@ -0,0 +1,1293 @@
+/* $Id: PS2M.cpp $ */
+/** @file
+ * PS2M - PS/2 auxiliary device (mouse) emulation.
+ */
+
+/*
+ * Copyright (C) 2007-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/*
+ * References:
+ *
+ * The Undocumented PC (2nd Ed.), Frank van Gilluwe, Addison-Wesley, 1996.
+ * IBM TrackPoint System Version 4.0 Engineering Specification, 1999.
+ * ELAN Microelectronics eKM8025 USB & PS/2 Mouse Controller, 2006.
+ *
+ *
+ * Notes:
+ *
+ * - The auxiliary device commands are very similar to keyboard commands.
+ * Most keyboard commands which do not specifically deal with the keyboard
+ * (enable, disable, reset) have identical counterparts.
+ * - The code refers to 'auxiliary device' and 'mouse'; these terms are not
+ * quite interchangeable. 'Auxiliary device' is used when referring to the
+ * generic PS/2 auxiliary device interface and 'mouse' when referring to
+ * a mouse attached to the auxiliary port.
+ * - The basic modes of operation are reset, stream, and remote. Those are
+ * mutually exclusive. Stream and remote modes can additionally have wrap
+ * mode enabled.
+ * - The auxiliary device sends unsolicited data to the host only when it is
+ * both in stream mode and enabled. Otherwise it only responds to commands.
+ *
+ *
+ * There are three report packet formats supported by the emulated device. The
+ * standard three-byte PS/2 format (with middle button support), IntelliMouse
+ * four-byte format with added scroll wheel, and IntelliMouse Explorer four-byte
+ * format with reduced scroll wheel range but two additional buttons. Note that
+ * the first three bytes of the report are always the same.
+ *
+ * Upon reset, the mouse is always in the standard PS/2 mode. A special 'knock'
+ * sequence can be used to switch to ImPS/2 or ImEx mode. Three consecutive
+ * Set Sampling Rate (0F3h) commands with arguments 200, 100, 80 switch to ImPS/2
+ * mode. While in ImPS/2 or PS/2 mode, three consecutive Set Sampling Rate
+ * commands with arguments 200, 200, 80 switch to ImEx mode. The Read ID (0F2h)
+ * command will report the currently selected protocol.
+ *
+ *
+ * Standard PS/2 pointing device three-byte report packet format:
+ *
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ * |Bit/byte| bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ * | Byte 1 | Y ovfl | X ovfl | Y sign | X sign | Sync | M btn | R btn | L btn |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ * | Byte 2 | X movement delta (two's complement) |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ * | Byte 3 | Y movement delta (two's complement) |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ *
+ * - The sync bit is always set. It allows software to synchronize data packets
+ * as the X/Y position data typically does not have bit 4 set.
+ * - The overflow bits are set if motion exceeds accumulator range. We use the
+ * maximum range (effectively 9 bits) and do not set the overflow bits.
+ * - Movement in the up/right direction is defined as having positive sign.
+ *
+ *
+ * IntelliMouse PS/2 (ImPS/2) fourth report packet byte:
+ *
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ * |Bit/byte| bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ * | Byte 4 | Z movement delta (two's complement) |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ *
+ * - The valid range for Z delta values is only -8/+7, i.e. 4 bits.
+ *
+ * IntelliMouse Explorer (ImEx) fourth report packet byte:
+ *
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ * |Bit/byte| bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ * | Byte 4 | 0 | 0 | Btn 5 | Btn 4 | Z mov't delta (two's complement) |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_KBD
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/err.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include "VBoxDD.h"
+#define IN_PS2M
+#include "PS2Dev.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name Auxiliary device commands sent by the system.
+ * @{ */
+#define ACMD_SET_SCALE_11 0xE6 /* Set 1:1 scaling. */
+#define ACMD_SET_SCALE_21 0xE7 /* Set 2:1 scaling. */
+#define ACMD_SET_RES 0xE8 /* Set resolution. */
+#define ACMD_REQ_STATUS 0xE9 /* Get device status. */
+#define ACMD_SET_STREAM 0xEA /* Set stream mode. */
+#define ACMD_READ_REMOTE 0xEB /* Read remote data. */
+#define ACMD_RESET_WRAP 0xEC /* Exit wrap mode. */
+#define ACMD_INVALID_1 0xED
+#define ACMD_SET_WRAP 0xEE /* Set wrap (echo) mode. */
+#define ACMD_INVALID_2 0xEF
+#define ACMD_SET_REMOTE 0xF0 /* Set remote mode. */
+#define ACMD_INVALID_3 0xF1
+#define ACMD_READ_ID 0xF2 /* Read device ID. */
+#define ACMD_SET_SAMP_RATE 0xF3 /* Set sampling rate. */
+#define ACMD_ENABLE 0xF4 /* Enable (streaming mode). */
+#define ACMD_DISABLE 0xF5 /* Disable (streaming mode). */
+#define ACMD_SET_DEFAULT 0xF6 /* Set defaults. */
+#define ACMD_INVALID_4 0xF7
+#define ACMD_INVALID_5 0xF8
+#define ACMD_INVALID_6 0xF9
+#define ACMD_INVALID_7 0xFA
+#define ACMD_INVALID_8 0xFB
+#define ACMD_INVALID_9 0xFC
+#define ACMD_INVALID_10 0xFD
+#define ACMD_RESEND 0xFE /* Resend response. */
+#define ACMD_RESET 0xFF /* Reset device. */
+/** @} */
+
+/** @name Auxiliary device responses sent to the system.
+ * @{ */
+#define ARSP_ID 0x00
+#define ARSP_BAT_OK 0xAA /* Self-test passed. */
+#define ARSP_ACK 0xFA /* Command acknowledged. */
+#define ARSP_ERROR 0xFC /* Bad command. */
+#define ARSP_RESEND 0xFE /* Requesting resend. */
+/** @} */
+
+/** Define a simple PS/2 input device queue. */
+#define DEF_PS2Q_TYPE(name, size) \
+ typedef struct { \
+ uint32_t rpos; \
+ uint32_t wpos; \
+ uint32_t cUsed; \
+ uint32_t cSize; \
+ uint8_t abQueue[size]; \
+ } name
+
+/* Internal mouse queue sizes. The input queue is relatively large,
+ * but the command queue only needs to handle a few bytes.
+ */
+#define AUX_EVT_QUEUE_SIZE 256
+#define AUX_CMD_QUEUE_SIZE 8
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+DEF_PS2Q_TYPE(AuxEvtQ, AUX_EVT_QUEUE_SIZE);
+DEF_PS2Q_TYPE(AuxCmdQ, AUX_CMD_QUEUE_SIZE);
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE /// @todo hack
+DEF_PS2Q_TYPE(GeneriQ, 1);
+#endif
+
+/* Auxiliary device special modes of operation. */
+typedef enum {
+ AUX_MODE_STD, /* Standard operation. */
+ AUX_MODE_RESET, /* Currently in reset. */
+ AUX_MODE_WRAP /* Wrap mode (echoing input). */
+} PS2M_MODE;
+
+/* Auxiliary device operational state. */
+typedef enum {
+ AUX_STATE_RATE_ERR = RT_BIT(0), /* Invalid rate received. */
+ AUX_STATE_RES_ERR = RT_BIT(1), /* Invalid resolution received. */
+ AUX_STATE_SCALING = RT_BIT(4), /* 2:1 scaling in effect. */
+ AUX_STATE_ENABLED = RT_BIT(5), /* Reporting enabled in stream mode. */
+ AUX_STATE_REMOTE = RT_BIT(6) /* Remote mode (reports on request). */
+} PS2M_STATE;
+
+/* Externally visible state bits. */
+#define AUX_STATE_EXTERNAL (AUX_STATE_SCALING | AUX_STATE_ENABLED | AUX_STATE_REMOTE)
+
+/* Protocols supported by the PS/2 mouse. */
+typedef enum {
+ PS2M_PROTO_PS2STD = 0, /* Standard PS/2 mouse protocol. */
+ PS2M_PROTO_IMPS2 = 3, /* IntelliMouse PS/2 protocol. */
+ PS2M_PROTO_IMEX = 4 /* IntelliMouse Explorer protocol. */
+} PS2M_PROTO;
+
+/* Protocol selection 'knock' states. */
+typedef enum {
+ PS2M_KNOCK_INITIAL,
+ PS2M_KNOCK_1ST,
+ PS2M_KNOCK_IMPS2_2ND,
+ PS2M_KNOCK_IMEX_2ND
+} PS2M_KNOCK_STATE;
+
+/**
+ * The PS/2 auxiliary device instance data.
+ */
+typedef struct PS2M
+{
+ /** Pointer to parent device (keyboard controller). */
+ R3PTRTYPE(void *) pParent;
+ /** Operational state. */
+ uint8_t u8State;
+ /** Configured sampling rate. */
+ uint8_t u8SampleRate;
+ /** Configured resolution. */
+ uint8_t u8Resolution;
+ /** Currently processed command (if any). */
+ uint8_t u8CurrCmd;
+ /** Set if the throttle delay is active. */
+ bool fThrottleActive;
+ /** Set if the throttle delay is active. */
+ bool fDelayReset;
+ /** Operational mode. */
+ PS2M_MODE enmMode;
+ /** Currently used protocol. */
+ PS2M_PROTO enmProtocol;
+ /** Currently used protocol. */
+ PS2M_KNOCK_STATE enmKnockState;
+ /** Buffer holding mouse events to be sent to the host. */
+ AuxEvtQ evtQ;
+ /** Command response queue (priority). */
+ AuxCmdQ cmdQ;
+ /** Accumulated horizontal movement. */
+ int32_t iAccumX;
+ /** Accumulated vertical movement. */
+ int32_t iAccumY;
+ /** Accumulated Z axis movement. */
+ int32_t iAccumZ;
+ /** Accumulated button presses. */
+ uint32_t fAccumB;
+ /** Instantaneous button data. */
+ uint32_t fCurrB;
+ /** Button state last sent to the guest. */
+ uint32_t fReportedB;
+ /** Throttling delay in milliseconds. */
+ uint32_t uThrottleDelay;
+
+ /** The device critical section protecting everything - R3 Ptr */
+ R3PTRTYPE(PPDMCRITSECT) pCritSectR3;
+ /** Command delay timer - R3 Ptr. */
+ PTMTIMERR3 pDelayTimerR3;
+ /** Interrupt throttling timer - R3 Ptr. */
+ PTMTIMERR3 pThrottleTimerR3;
+ RTR3PTR Alignment1;
+
+ /** Command delay timer - RC Ptr. */
+ PTMTIMERRC pDelayTimerRC;
+ /** Interrupt throttling timer - RC Ptr. */
+ PTMTIMERRC pThrottleTimerRC;
+
+ /** Command delay timer - R0 Ptr. */
+ PTMTIMERR0 pDelayTimerR0;
+ /** Interrupt throttling timer - R0 Ptr. */
+ PTMTIMERR0 pThrottleTimerR0;
+
+ /**
+ * Mouse port - LUN#1.
+ *
+ * @implements PDMIBASE
+ * @implements PDMIMOUSEPORT
+ */
+ struct
+ {
+ /** The base interface for the mouse port. */
+ PDMIBASE IBase;
+ /** The keyboard port base interface. */
+ PDMIMOUSEPORT IPort;
+
+ /** The base interface of the attached mouse driver. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** The keyboard interface of the attached mouse driver. */
+ R3PTRTYPE(PPDMIMOUSECONNECTOR) pDrv;
+ } Mouse;
+} PS2M, *PPS2M;
+
+AssertCompile(PS2M_STRUCT_FILLER >= sizeof(PS2M));
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Test code function declarations *
+*********************************************************************************************************************************/
+#if defined(RT_STRICT) && defined(IN_RING3)
+static void ps2mTestAccumulation(void);
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Clear a queue.
+ *
+ * @param pQ Pointer to the queue.
+ */
+static void ps2kClearQueue(GeneriQ *pQ)
+{
+ LogFlowFunc(("Clearing queue %p\n", pQ));
+ pQ->wpos = pQ->rpos;
+ pQ->cUsed = 0;
+}
+
+
+/**
+ * Add a byte to a queue.
+ *
+ * @param pQ Pointer to the queue.
+ * @param val The byte to store.
+ */
+static void ps2kInsertQueue(GeneriQ *pQ, uint8_t val)
+{
+ /* Check if queue is full. */
+ if (pQ->cUsed >= pQ->cSize)
+ {
+ LogRelFlowFunc(("queue %p full (%d entries)\n", pQ, pQ->cUsed));
+ return;
+ }
+ /* Insert data and update circular buffer write position. */
+ pQ->abQueue[pQ->wpos] = val;
+ if (++pQ->wpos == pQ->cSize)
+ pQ->wpos = 0; /* Roll over. */
+ ++pQ->cUsed;
+ LogRelFlowFunc(("inserted 0x%02X into queue %p\n", val, pQ));
+}
+
+#ifdef IN_RING3
+
+/**
+ * Save a queue state.
+ *
+ * @param pSSM SSM handle to write the state to.
+ * @param pQ Pointer to the queue.
+ */
+static void ps2kSaveQueue(PSSMHANDLE pSSM, GeneriQ *pQ)
+{
+ uint32_t cItems = pQ->cUsed;
+ int i;
+
+ /* Only save the number of items. Note that the read/write
+ * positions aren't saved as they will be rebuilt on load.
+ */
+ SSMR3PutU32(pSSM, cItems);
+
+ LogFlow(("Storing %d items from queue %p\n", cItems, pQ));
+
+ /* Save queue data - only the bytes actually used (typically zero). */
+ for (i = pQ->rpos; cItems-- > 0; i = (i + 1) % pQ->cSize)
+ SSMR3PutU8(pSSM, pQ->abQueue[i]);
+}
+
+/**
+ * Load a queue state.
+ *
+ * @param pSSM SSM handle to read the state from.
+ * @param pQ Pointer to the queue.
+ *
+ * @return int VBox status/error code.
+ */
+static int ps2kLoadQueue(PSSMHANDLE pSSM, GeneriQ *pQ)
+{
+ int rc;
+
+ /* On load, always put the read pointer at zero. */
+ SSMR3GetU32(pSSM, &pQ->cUsed);
+
+ LogFlow(("Loading %d items to queue %p\n", pQ->cUsed, pQ));
+
+ if (pQ->cUsed > pQ->cSize)
+ {
+ AssertMsgFailed(("Saved size=%u, actual=%u\n", pQ->cUsed, pQ->cSize));
+ return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ }
+
+ /* Recalculate queue positions and load data in one go. */
+ pQ->rpos = 0;
+ pQ->wpos = pQ->cUsed;
+ rc = SSMR3GetMem(pSSM, pQ->abQueue, pQ->cUsed);
+
+ return rc;
+}
+
+/* Report a change in status down (or is it up?) the driver chain. */
+static void ps2mSetDriverState(PPS2M pThis, bool fEnabled)
+{
+ PPDMIMOUSECONNECTOR pDrv = pThis->Mouse.pDrv;
+ if (pDrv)
+ pDrv->pfnReportModes(pDrv, fEnabled, false, false);
+}
+
+/* Reset the pointing device. */
+static void ps2mReset(PPS2M pThis)
+{
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_BAT_OK);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, 0);
+ pThis->enmMode = AUX_MODE_STD;
+ pThis->u8CurrCmd = 0;
+
+ /// @todo move to its proper home!
+ ps2mSetDriverState(pThis, true);
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Retrieve a byte from a queue.
+ *
+ * @param pQ Pointer to the queue.
+ * @param pVal Pointer to storage for the byte.
+ *
+ * @return int VINF_TRY_AGAIN if queue is empty,
+ * VINF_SUCCESS if a byte was read.
+ */
+static int ps2kRemoveQueue(GeneriQ *pQ, uint8_t *pVal)
+{
+ int rc = VINF_TRY_AGAIN;
+
+ Assert(pVal);
+ if (pQ->cUsed)
+ {
+ *pVal = pQ->abQueue[pQ->rpos];
+ if (++pQ->rpos == pQ->cSize)
+ pQ->rpos = 0; /* Roll over. */
+ --pQ->cUsed;
+ rc = VINF_SUCCESS;
+ LogFlowFunc(("removed 0x%02X from queue %p\n", *pVal, pQ));
+ } else
+ LogFlowFunc(("queue %p empty\n", pQ));
+ return rc;
+}
+
+static void ps2mSetRate(PPS2M pThis, uint8_t rate)
+{
+ Assert(rate);
+ pThis->uThrottleDelay = rate ? 1000 / rate : 0;
+ pThis->u8SampleRate = rate;
+ LogFlowFunc(("Sampling rate %u, throttle delay %u ms\n", pThis->u8SampleRate, pThis->uThrottleDelay));
+}
+
+static void ps2mSetDefaults(PPS2M pThis)
+{
+ LogFlowFunc(("Set mouse defaults\n"));
+ /* Standard protocol, reporting disabled, resolution 2, 1:1 scaling. */
+ pThis->enmProtocol = PS2M_PROTO_PS2STD;
+ pThis->u8State = 0;
+ pThis->u8Resolution = 2;
+
+ /* Sample rate 100 reports per second. */
+ ps2mSetRate(pThis, 100);
+
+ /* Event queue, eccumulators, and button status bits are cleared. */
+ ps2kClearQueue((GeneriQ *)&pThis->evtQ);
+ pThis->iAccumX = pThis->iAccumY = pThis->iAccumZ = pThis->fAccumB;
+}
+
+/* Handle the sampling rate 'knock' sequence which selects protocol. */
+static void ps2mRateProtocolKnock(PPS2M pThis, uint8_t rate)
+{
+ switch (pThis->enmKnockState)
+ {
+ case PS2M_KNOCK_INITIAL:
+ if (rate == 200)
+ pThis->enmKnockState = PS2M_KNOCK_1ST;
+ break;
+ case PS2M_KNOCK_1ST:
+ if (rate == 100)
+ pThis->enmKnockState = PS2M_KNOCK_IMPS2_2ND;
+ else if (rate == 200)
+ pThis->enmKnockState = PS2M_KNOCK_IMEX_2ND;
+ else
+ pThis->enmKnockState = PS2M_KNOCK_INITIAL;
+ break;
+ case PS2M_KNOCK_IMPS2_2ND:
+ if (rate == 80)
+ {
+ pThis->enmProtocol = PS2M_PROTO_IMPS2;
+ LogRelFlow(("PS2M: Switching mouse to ImPS/2 protocol.\n"));
+ }
+ pThis->enmKnockState = PS2M_KNOCK_INITIAL;
+ break;
+ case PS2M_KNOCK_IMEX_2ND:
+ if (rate == 80)
+ {
+ pThis->enmProtocol = PS2M_PROTO_IMEX;
+ LogRelFlow(("PS2M: Switching mouse to ImEx protocol.\n"));
+ }
+ RT_FALL_THRU();
+ default:
+ pThis->enmKnockState = PS2M_KNOCK_INITIAL;
+ }
+}
+
+/* Three-button event mask. */
+#define PS2M_STD_BTN_MASK (RT_BIT(0) | RT_BIT(1) | RT_BIT(2))
+
+/* Report accumulated movement and button presses, then clear the accumulators. */
+static void ps2mReportAccumulatedEvents(PPS2M pThis, GeneriQ *pQueue, bool fAccumBtns)
+{
+ uint32_t fBtnState = fAccumBtns ? pThis->fAccumB : pThis->fCurrB;
+ uint8_t val;
+ int dX, dY, dZ;
+
+ /* Clamp the accumulated delta values to the allowed range. */
+ dX = RT_MIN(RT_MAX(pThis->iAccumX, -255), 255);
+ dY = RT_MIN(RT_MAX(pThis->iAccumY, -255), 255);
+ dZ = RT_MIN(RT_MAX(pThis->iAccumZ, -8), 7);
+
+ /* Start with the sync bit and buttons 1-3. */
+ val = RT_BIT(3) | (fBtnState & PS2M_STD_BTN_MASK);
+ /* Set the X/Y sign bits. */
+ if (dX < 0)
+ val |= RT_BIT(4);
+ if (dY < 0)
+ val |= RT_BIT(5);
+
+ /* Send the standard 3-byte packet (always the same). */
+ ps2kInsertQueue(pQueue, val);
+ ps2kInsertQueue(pQueue, dX);
+ ps2kInsertQueue(pQueue, dY);
+
+ /* Add fourth byte if extended protocol is in use. */
+ if (pThis->enmProtocol > PS2M_PROTO_PS2STD)
+ {
+ if (pThis->enmProtocol == PS2M_PROTO_IMPS2)
+ ps2kInsertQueue(pQueue, dZ);
+ else
+ {
+ Assert(pThis->enmProtocol == PS2M_PROTO_IMEX);
+ /* Z value uses 4 bits; buttons 4/5 in bits 4 and 5. */
+ val = dZ & 0x0f;
+ val |= (fBtnState << 1) & (RT_BIT(4) | RT_BIT(5));
+ ps2kInsertQueue(pQueue, val);
+ }
+ }
+
+ /* Clear the movement accumulators, but not necessarily button state. */
+ pThis->iAccumX = pThis->iAccumY = pThis->iAccumZ = 0;
+ /* Clear accumulated button state only when it's being used. */
+ if (fAccumBtns)
+ {
+ pThis->fReportedB = pThis->fAccumB;
+ pThis->fAccumB = 0;
+ }
+}
+
+
+/* Determine whether a reporting rate is one of the valid ones. */
+bool ps2mIsRateSupported(uint8_t rate)
+{
+ static uint8_t aValidRates[] = { 10, 20, 40, 60, 80, 100, 200 };
+ size_t i;
+ bool fValid = false;
+
+ for (i = 0; i < RT_ELEMENTS(aValidRates); ++i)
+ if (aValidRates[i] == rate)
+ {
+ fValid = true;
+ break;
+ }
+
+ return fValid;
+}
+
+/**
+ * Receive and process a byte sent by the keyboard controller.
+ *
+ * @param pThis The PS/2 auxiliary device instance data.
+ * @param cmd The command (or data) byte.
+ */
+int PS2MByteToAux(PPS2M pThis, uint8_t cmd)
+{
+ uint8_t u8Val;
+ bool fHandled = true;
+
+ LogFlowFunc(("cmd=0x%02X, active cmd=0x%02X\n", cmd, pThis->u8CurrCmd));
+
+ if (pThis->enmMode == AUX_MODE_RESET)
+ /* In reset mode, do not respond at all. */
+ return VINF_SUCCESS;
+
+ /* If there's anything left in the command response queue, trash it. */
+ ps2kClearQueue((GeneriQ *)&pThis->cmdQ);
+
+ if (pThis->enmMode == AUX_MODE_WRAP)
+ {
+ /* In wrap mode, bounce most data right back.*/
+ if (cmd == ACMD_RESET || cmd == ACMD_RESET_WRAP)
+ ; /* Handle as regular commands. */
+ else
+ {
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, cmd);
+ return VINF_SUCCESS;
+ }
+ }
+
+#ifndef IN_RING3
+ /* Reset, Enable, and Set Default commands must be run in R3. */
+ if (cmd == ACMD_RESET || cmd == ACMD_ENABLE || cmd == ACMD_SET_DEFAULT)
+ return VINF_IOM_R3_IOPORT_WRITE;
+#endif
+
+ switch (cmd)
+ {
+ case ACMD_SET_SCALE_11:
+ pThis->u8State &= ~AUX_STATE_SCALING;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_SET_SCALE_21:
+ pThis->u8State |= AUX_STATE_SCALING;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_REQ_STATUS:
+ /* Report current status, sample rate, and resolution. */
+ u8Val = (pThis->u8State & AUX_STATE_EXTERNAL) | (pThis->fCurrB & PS2M_STD_BTN_MASK);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, u8Val);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->u8Resolution);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->u8SampleRate);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_SET_STREAM:
+ pThis->u8State &= ~AUX_STATE_REMOTE;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_READ_REMOTE:
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ ps2mReportAccumulatedEvents(pThis, (GeneriQ *)&pThis->cmdQ, false);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_RESET_WRAP:
+ pThis->enmMode = AUX_MODE_STD;
+ /* NB: Stream mode reporting remains disabled! */
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_SET_WRAP:
+ pThis->enmMode = AUX_MODE_WRAP;
+ pThis->u8State &= ~AUX_STATE_ENABLED;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_SET_REMOTE:
+ pThis->u8State |= AUX_STATE_REMOTE;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_READ_ID:
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->enmProtocol);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_ENABLE:
+ pThis->u8State |= AUX_STATE_ENABLED;
+#ifdef IN_RING3
+ ps2mSetDriverState(pThis, true);
+#else
+ AssertLogRelMsgFailed(("Invalid ACMD_ENABLE outside R3!\n"));
+#endif
+ ps2kClearQueue((GeneriQ *)&pThis->evtQ);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_DISABLE:
+ pThis->u8State &= ~AUX_STATE_ENABLED;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_SET_DEFAULT:
+ ps2mSetDefaults(pThis);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_RESEND:
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_RESET:
+ ps2mSetDefaults(pThis);
+ /// @todo reset more?
+ pThis->u8CurrCmd = cmd;
+ pThis->enmMode = AUX_MODE_RESET;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ if (pThis->fDelayReset)
+ /* Slightly delay reset completion; it might take hundreds of ms. */
+ TMTimerSetMillies(pThis->CTX_SUFF(pDelayTimer), 1);
+ else
+#ifdef IN_RING3
+ ps2mReset(pThis);
+#else
+ AssertLogRelMsgFailed(("Invalid ACMD_RESET outside R3!\n"));
+#endif
+ break;
+ /* The following commands need a parameter. */
+ case ACMD_SET_RES:
+ case ACMD_SET_SAMP_RATE:
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = cmd;
+ break;
+ default:
+ /* Sending a command instead of a parameter starts the new command. */
+ switch (pThis->u8CurrCmd)
+ {
+ case ACMD_SET_RES:
+ if (cmd < 4) /* Valid resolutions are 0-3. */
+ {
+ pThis->u8Resolution = cmd;
+ pThis->u8State &= ~AUX_STATE_RES_ERR;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ }
+ else
+ {
+ /* Bad resolution. Reply with Resend or Error. */
+ if (pThis->u8State & AUX_STATE_RES_ERR)
+ {
+ pThis->u8State &= ~AUX_STATE_RES_ERR;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ERROR);
+ pThis->u8CurrCmd = 0;
+ }
+ else
+ {
+ pThis->u8State |= AUX_STATE_RES_ERR;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_RESEND);
+ /* NB: Current command remains unchanged. */
+ }
+ }
+ break;
+ case ACMD_SET_SAMP_RATE:
+ if (ps2mIsRateSupported(cmd))
+ {
+ pThis->u8State &= ~AUX_STATE_RATE_ERR;
+ ps2mSetRate(pThis, cmd);
+ ps2mRateProtocolKnock(pThis, cmd);
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ }
+ else
+ {
+ /* Bad rate. Reply with Resend or Error. */
+ if (pThis->u8State & AUX_STATE_RATE_ERR)
+ {
+ pThis->u8State &= ~AUX_STATE_RATE_ERR;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ERROR);
+ pThis->u8CurrCmd = 0;
+ }
+ else
+ {
+ pThis->u8State |= AUX_STATE_RATE_ERR;
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_RESEND);
+ /* NB: Current command remains unchanged. */
+ }
+ }
+ break;
+ default:
+ fHandled = false;
+ }
+ /* Fall through only to handle unrecognized commands. */
+ if (fHandled)
+ break;
+ RT_FALL_THRU();
+
+ case ACMD_INVALID_1:
+ case ACMD_INVALID_2:
+ case ACMD_INVALID_3:
+ case ACMD_INVALID_4:
+ case ACMD_INVALID_5:
+ case ACMD_INVALID_6:
+ case ACMD_INVALID_7:
+ case ACMD_INVALID_8:
+ case ACMD_INVALID_9:
+ case ACMD_INVALID_10:
+ Log(("Unsupported command 0x%02X!\n", cmd));
+ ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_RESEND);
+ pThis->u8CurrCmd = 0;
+ break;
+ }
+ LogFlowFunc(("Active cmd now 0x%02X; updating interrupts\n", pThis->u8CurrCmd));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Send a byte (packet data or command response) to the keyboard controller.
+ *
+ * @returns VINF_SUCCESS or VINF_TRY_AGAIN.
+ * @param pThis The PS/2 auxiliary device instance data.
+ * @param pb Where to return the byte we've read.
+ * @remarks Caller must have entered the device critical section.
+ */
+int PS2MByteFromAux(PPS2M pThis, uint8_t *pb)
+{
+ int rc;
+
+ AssertPtr(pb);
+
+ /* Anything in the command queue has priority over data
+ * in the event queue. Additionally, packet data are
+ * blocked if a command is currently in progress, even if
+ * the command queue is empty.
+ */
+ /// @todo Probably should flush/not fill queue if stream mode reporting disabled?!
+ rc = ps2kRemoveQueue((GeneriQ *)&pThis->cmdQ, pb);
+ if (rc != VINF_SUCCESS && !pThis->u8CurrCmd && (pThis->u8State & AUX_STATE_ENABLED))
+ rc = ps2kRemoveQueue((GeneriQ *)&pThis->evtQ, pb);
+
+ LogFlowFunc(("mouse sends 0x%02x (%svalid data)\n", *pb, rc == VINF_SUCCESS ? "" : "not "));
+
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/** Is there any state change to send as events to the guest? */
+static uint32_t ps2mHaveEvents(PPS2M pThis)
+{
+ return pThis->iAccumX | pThis->iAccumY | pThis->iAccumZ
+ | (pThis->fCurrB != pThis->fReportedB) | (pThis->fAccumB != 0);
+}
+
+/* Event rate throttling timer to emulate the auxiliary device sampling rate.
+ */
+static DECLCALLBACK(void) ps2mThrottleTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF2(pDevIns, pTimer);
+ PPS2M pThis = (PS2M *)pvUser;
+ uint32_t uHaveEvents;
+
+ /* Grab the lock to avoid races with PutEvent(). */
+ int rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY);
+ AssertReleaseRC(rc);
+
+ /* If more movement is accumulated, report it and restart the timer. */
+ uHaveEvents = ps2mHaveEvents(pThis);
+ LogFlowFunc(("Have%s events\n", uHaveEvents ? "" : " no"));
+
+ if (uHaveEvents)
+ {
+ /* Report accumulated data, poke the KBC, and start the timer. */
+ ps2mReportAccumulatedEvents(pThis, (GeneriQ *)&pThis->evtQ, true);
+ KBCUpdateInterrupts(pThis->pParent);
+ TMTimerSetMillies(pThis->CTX_SUFF(pThrottleTimer), pThis->uThrottleDelay);
+ }
+ else
+ pThis->fThrottleActive = false;
+
+ PDMCritSectLeave(pThis->pCritSectR3);
+}
+
+/* The auxiliary device reset is specified to take up to about 500 milliseconds. We need
+ * to delay sending the result to the host for at least a tiny little while.
+ */
+static DECLCALLBACK(void) ps2mDelayTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF2(pDevIns, pTimer);
+ PPS2M pThis = (PS2M *)pvUser;
+
+ LogFlowFunc(("Delay timer: cmd %02X\n", pThis->u8CurrCmd));
+
+ Assert(pThis->u8CurrCmd == ACMD_RESET);
+ ps2mReset(pThis);
+
+ /// @todo Might want a PS2MCompleteCommand() to push last response, clear command, and kick the KBC...
+ /* Give the KBC a kick. */
+ KBCUpdateInterrupts(pThis->pParent);
+}
+
+
+/**
+ * Debug device info handler. Prints basic auxiliary device state.
+ *
+ * @param pDevIns Device instance which registered the info.
+ * @param pHlp Callback functions for doing output.
+ * @param pszArgs Argument string. Optional and specific to the handler.
+ */
+static DECLCALLBACK(void) ps2mInfoState(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ static const char *pcszModes[] = { "normal", "reset", "wrap" };
+ static const char *pcszProtocols[] = { "PS/2", NULL, NULL, "ImPS/2", "ImEx" };
+ PPS2M pThis = KBDGetPS2MFromDevIns(pDevIns);
+ NOREF(pszArgs);
+
+ Assert(pThis->enmMode <= RT_ELEMENTS(pcszModes));
+ Assert(pThis->enmProtocol <= RT_ELEMENTS(pcszProtocols));
+ pHlp->pfnPrintf(pHlp, "PS/2 mouse state: %s, %s mode, reporting %s\n",
+ pcszModes[pThis->enmMode],
+ pThis->u8State & AUX_STATE_REMOTE ? "remote" : "stream",
+ pThis->u8State & AUX_STATE_ENABLED ? "enabled" : "disabled");
+ pHlp->pfnPrintf(pHlp, "Protocol: %s, scaling %u:1\n",
+ pcszProtocols[pThis->enmProtocol], pThis->u8State & AUX_STATE_SCALING ? 2 : 1);
+ pHlp->pfnPrintf(pHlp, "Active command %02X\n", pThis->u8CurrCmd);
+ pHlp->pfnPrintf(pHlp, "Sampling rate %u reports/sec, resolution %u counts/mm\n",
+ pThis->u8SampleRate, 1 << pThis->u8Resolution);
+ pHlp->pfnPrintf(pHlp, "Command queue: %d items (%d max)\n",
+ pThis->cmdQ.cUsed, pThis->cmdQ.cSize);
+ pHlp->pfnPrintf(pHlp, "Event queue : %d items (%d max)\n",
+ pThis->evtQ.cUsed, pThis->evtQ.cSize);
+}
+
+/* -=-=-=-=-=- Mouse: IBase -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ps2mQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPS2M pThis = RT_FROM_MEMBER(pInterface, PS2M, Mouse.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->Mouse.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSEPORT, &pThis->Mouse.IPort);
+ return NULL;
+}
+
+
+/* -=-=-=-=-=- Mouse: IMousePort -=-=-=-=-=- */
+
+/**
+ * Mouse event handler.
+ *
+ * @returns VBox status code.
+ * @param pThis The PS/2 auxiliary device instance data.
+ * @param dx X direction movement delta.
+ * @param dy Y direction movement delta.
+ * @param dz Z (vertical scroll) movement delta.
+ * @param dw W (horizontal scroll) movement delta.
+ * @param fButtons Depressed button mask.
+ */
+static int ps2mPutEventWorker(PPS2M pThis, int32_t dx, int32_t dy,
+ int32_t dz, int32_t dw, uint32_t fButtons)
+{
+ RT_NOREF1(dw);
+ int rc = VINF_SUCCESS;
+
+ /* Update internal accumulators and button state. */
+ pThis->iAccumX += dx;
+ pThis->iAccumY += dy;
+ pThis->iAccumZ += dz;
+ pThis->fAccumB |= fButtons; /// @todo accumulate based on current protocol?
+ pThis->fCurrB = fButtons;
+
+ /* Report the event and start the throttle timer unless it's already running. */
+ if (!pThis->fThrottleActive)
+ {
+ ps2mReportAccumulatedEvents(pThis, (GeneriQ *)&pThis->evtQ, true);
+ KBCUpdateInterrupts(pThis->pParent);
+ pThis->fThrottleActive = true;
+ TMTimerSetMillies(pThis->CTX_SUFF(pThrottleTimer), pThis->uThrottleDelay);
+ }
+
+ return rc;
+}
+
+/* -=-=-=-=-=- Mouse: IMousePort -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEvent}
+ */
+static DECLCALLBACK(int) ps2mPutEvent(PPDMIMOUSEPORT pInterface, int32_t dx, int32_t dy,
+ int32_t dz, int32_t dw, uint32_t fButtons)
+{
+ PPS2M pThis = RT_FROM_MEMBER(pInterface, PS2M, Mouse.IPort);
+ int rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY);
+ AssertReleaseRC(rc);
+
+ LogRelFlowFunc(("dX=%d dY=%d dZ=%d dW=%d buttons=%02X\n", dx, dy, dz, dw, fButtons));
+ /* NB: The PS/2 Y axis direction is inverted relative to ours. */
+ ps2mPutEventWorker(pThis, dx, -dy, dz, dw, fButtons);
+
+ PDMCritSectLeave(pThis->pCritSectR3);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventAbs}
+ */
+static DECLCALLBACK(int) ps2mPutEventAbs(PPDMIMOUSEPORT pInterface, uint32_t x, uint32_t y,
+ int32_t dz, int32_t dw, uint32_t fButtons)
+{
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+ NOREF(pInterface); NOREF(x); NOREF(y); NOREF(dz); NOREF(dw); NOREF(fButtons);
+}
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventMultiTouch}
+ */
+static DECLCALLBACK(int) ps2mPutEventMT(PPDMIMOUSEPORT pInterface, uint8_t cContacts,
+ const uint64_t *pau64Contacts, uint32_t u32ScanTime)
+{
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+ NOREF(pInterface); NOREF(cContacts); NOREF(pau64Contacts); NOREF(u32ScanTime);
+}
+
+
+
+/**
+ * Attach command.
+ *
+ * This is called to let the device attach to a driver for a
+ * specified LUN.
+ *
+ * This is like plugging in the mouse after turning on the
+ * system.
+ *
+ * @returns VBox status code.
+ * @param pThis The PS/2 auxiliary device instance data.
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+int PS2MAttach(PPS2M pThis, PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ int rc;
+
+ /* The LUN must be 1, i.e. mouse. */
+ Assert(iLUN == 1);
+ AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("PS/2 mouse does not support hotplugging\n"),
+ VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("iLUN=%d\n", iLUN));
+
+ rc = PDMDevHlpDriverAttach(pDevIns, iLUN, &pThis->Mouse.IBase, &pThis->Mouse.pDrvBase, "Mouse Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Mouse.pDrv = PDMIBASE_QUERY_INTERFACE(pThis->Mouse.pDrvBase, PDMIMOUSECONNECTOR);
+ if (!pThis->Mouse.pDrv)
+ {
+ AssertLogRelMsgFailed(("LUN #1 doesn't have a mouse interface! rc=%Rrc\n", rc));
+ rc = VERR_PDM_MISSING_INTERFACE;
+ }
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ Log(("%s/%d: warning: no driver attached to LUN #1!\n", pDevIns->pReg->szName, pDevIns->iInstance));
+ rc = VINF_SUCCESS;
+ }
+ else
+ AssertLogRelMsgFailed(("Failed to attach LUN #1! rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+void PS2MSaveState(PPS2M pThis, PSSMHANDLE pSSM)
+{
+ LogFlowFunc(("Saving PS2M state\n"));
+
+ /* Save the core auxiliary device state. */
+ SSMR3PutU8(pSSM, pThis->u8State);
+ SSMR3PutU8(pSSM, pThis->u8SampleRate);
+ SSMR3PutU8(pSSM, pThis->u8Resolution);
+ SSMR3PutU8(pSSM, pThis->u8CurrCmd);
+ SSMR3PutU8(pSSM, pThis->enmMode);
+ SSMR3PutU8(pSSM, pThis->enmProtocol);
+ SSMR3PutU8(pSSM, pThis->enmKnockState);
+
+ /* Save the command and event queues. */
+ ps2kSaveQueue(pSSM, (GeneriQ *)&pThis->cmdQ);
+ ps2kSaveQueue(pSSM, (GeneriQ *)&pThis->evtQ);
+
+ /* Save the command delay timer. Note that the rate throttling
+ * timer is *not* saved.
+ */
+ TMR3TimerSave(pThis->CTX_SUFF(pDelayTimer), pSSM);
+}
+
+int PS2MLoadState(PPS2M pThis, PSSMHANDLE pSSM, uint32_t uVersion)
+{
+ uint8_t u8;
+ int rc;
+
+ NOREF(uVersion);
+ LogFlowFunc(("Loading PS2M state version %u\n", uVersion));
+
+ /* Load the basic auxiliary device state. */
+ SSMR3GetU8(pSSM, &pThis->u8State);
+ SSMR3GetU8(pSSM, &pThis->u8SampleRate);
+ SSMR3GetU8(pSSM, &pThis->u8Resolution);
+ SSMR3GetU8(pSSM, &pThis->u8CurrCmd);
+ SSMR3GetU8(pSSM, &u8);
+ pThis->enmMode = (PS2M_MODE)u8;
+ SSMR3GetU8(pSSM, &u8);
+ pThis->enmProtocol = (PS2M_PROTO)u8;
+ SSMR3GetU8(pSSM, &u8);
+ pThis->enmKnockState = (PS2M_KNOCK_STATE)u8;
+
+ /* Load the command and event queues. */
+ rc = ps2kLoadQueue(pSSM, (GeneriQ *)&pThis->cmdQ);
+ AssertRCReturn(rc, rc);
+ rc = ps2kLoadQueue(pSSM, (GeneriQ *)&pThis->evtQ);
+ AssertRCReturn(rc, rc);
+
+ /* Load the command delay timer, just in case. */
+ rc = TMR3TimerLoad(pThis->CTX_SUFF(pDelayTimer), pSSM);
+ AssertRCReturn(rc, rc);
+
+ /* Recalculate the throttling delay. */
+ ps2mSetRate(pThis, pThis->u8SampleRate);
+
+ ps2mSetDriverState(pThis, !!(pThis->u8State & AUX_STATE_ENABLED));
+
+ return rc;
+}
+
+void PS2MFixupState(PPS2M pThis, uint8_t u8State, uint8_t u8Rate, uint8_t u8Proto)
+{
+ LogFlowFunc(("Fixing up old PS2M state version\n"));
+
+ /* Load the basic auxiliary device state. */
+ pThis->u8State = u8State;
+ pThis->u8SampleRate = u8Rate ? u8Rate : 40; /* In case it wasn't saved right. */
+ pThis->enmProtocol = (PS2M_PROTO)u8Proto;
+
+ /* Recalculate the throttling delay. */
+ ps2mSetRate(pThis, pThis->u8SampleRate);
+
+ ps2mSetDriverState(pThis, !!(pThis->u8State & AUX_STATE_ENABLED));
+}
+
+void PS2MReset(PPS2M pThis)
+{
+ LogFlowFunc(("Resetting PS2M\n"));
+
+ pThis->u8CurrCmd = 0;
+
+ /* Clear the queues. */
+ ps2kClearQueue((GeneriQ *)&pThis->cmdQ);
+ ps2mSetDefaults(pThis); /* Also clears event queue. */
+}
+
+void PS2MRelocate(PPS2M pThis, RTGCINTPTR offDelta, PPDMDEVINS pDevIns)
+{
+ RT_NOREF2(pDevIns, offDelta);
+ LogFlowFunc(("Relocating PS2M\n"));
+ pThis->pDelayTimerRC = TMTimerRCPtr(pThis->pDelayTimerR3);
+ pThis->pThrottleTimerRC = TMTimerRCPtr(pThis->pThrottleTimerR3);
+}
+
+int PS2MConstruct(PPS2M pThis, PPDMDEVINS pDevIns, void *pParent, int iInstance)
+{
+ RT_NOREF1(iInstance);
+
+ LogFlowFunc(("iInstance=%d\n", iInstance));
+
+#ifdef RT_STRICT
+ ps2mTestAccumulation();
+#endif
+
+ pThis->pParent = pParent;
+
+ /* Initialize the queues. */
+ pThis->evtQ.cSize = AUX_EVT_QUEUE_SIZE;
+ pThis->cmdQ.cSize = AUX_CMD_QUEUE_SIZE;
+
+ pThis->Mouse.IBase.pfnQueryInterface = ps2mQueryInterface;
+ pThis->Mouse.IPort.pfnPutEvent = ps2mPutEvent;
+ pThis->Mouse.IPort.pfnPutEventAbs = ps2mPutEventAbs;
+ pThis->Mouse.IPort.pfnPutEventMultiTouch = ps2mPutEventMT;
+
+ /*
+ * Initialize the critical section pointer(s).
+ */
+ pThis->pCritSectR3 = pDevIns->pCritSectRoR3;
+
+ /*
+ * Create the input rate throttling timer. Does not use virtual time!
+ */
+ PTMTIMER pTimer;
+ int rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_REAL, ps2mThrottleTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "PS2M Throttle Timer", &pTimer);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pThis->pThrottleTimerR3 = pTimer;
+ pThis->pThrottleTimerR0 = TMTimerR0Ptr(pTimer);
+ pThis->pThrottleTimerRC = TMTimerRCPtr(pTimer);
+
+ /*
+ * Create the command delay timer.
+ */
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ps2mDelayTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "PS2M Delay Timer", &pTimer);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pThis->pDelayTimerR3 = pTimer;
+ pThis->pDelayTimerR0 = TMTimerR0Ptr(pTimer);
+ pThis->pDelayTimerRC = TMTimerRCPtr(pTimer);
+
+ /*
+ * Register debugger info callbacks.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ps2m", "Display PS/2 mouse state.", ps2mInfoState);
+
+ /// @todo Where should we do this?
+ ps2mSetDriverState(pThis, true);
+ pThis->u8State = 0;
+ pThis->enmMode = AUX_MODE_STD;
+
+ return rc;
+}
+
+#endif
+
+#if defined(RT_STRICT) && defined(IN_RING3)
+/* -=-=-=-=-=- Test code -=-=-=-=-=- */
+
+/** Test the event accumulation mechanism which we use to delay events going
+ * to the guest to one per 10ms (the default PS/2 mouse event rate). This
+ * test depends on ps2mPutEventWorker() not touching the timer if
+ * This.fThrottleActive is true. */
+/** @todo if we add any more tests it might be worth using a table of test
+ * operations and checks. */
+static void ps2mTestAccumulation(void)
+{
+ PS2M This;
+ unsigned i;
+ int rc;
+ uint8_t b;
+
+ RT_ZERO(This);
+ This.evtQ.cSize = AUX_EVT_QUEUE_SIZE;
+ This.u8State = AUX_STATE_ENABLED;
+ This.fThrottleActive = true;
+ /* Certain Windows touch pad drivers report a double tap as a press, then
+ * a release-press-release all within a single 10ms interval. Simulate
+ * this to check that it is handled right. */
+ ps2mPutEventWorker(&This, 0, 0, 0, 0, 1);
+ if (ps2mHaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
+ ps2mPutEventWorker(&This, 0, 0, 0, 0, 0);
+ if (ps2mHaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
+ ps2mPutEventWorker(&This, 0, 0, 0, 0, 1);
+ ps2mPutEventWorker(&This, 0, 0, 0, 0, 0);
+ if (ps2mHaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
+ if (ps2mHaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
+ for (i = 0; i < 12; ++i)
+ {
+ const uint8_t abExpected[] = { 9, 0, 0, 8, 0, 0, 9, 0, 0, 8, 0, 0};
+
+ rc = PS2MByteFromAux(&This, &b);
+ AssertRCSuccess(rc);
+ Assert(b == abExpected[i]);
+ }
+ rc = PS2MByteFromAux(&This, &b);
+ Assert(rc != VINF_SUCCESS);
+ /* Button hold down during mouse drags was broken at some point during
+ * testing fixes for the previous issue. Test that that works. */
+ ps2mPutEventWorker(&This, 0, 0, 0, 0, 1);
+ if (ps2mHaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
+ if (ps2mHaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
+ for (i = 0; i < 3; ++i)
+ {
+ const uint8_t abExpected[] = { 9, 0, 0 };
+
+ rc = PS2MByteFromAux(&This, &b);
+ AssertRCSuccess(rc);
+ Assert(b == abExpected[i]);
+ }
+ rc = PS2MByteFromAux(&This, &b);
+ Assert(rc != VINF_SUCCESS);
+}
+#endif /* RT_STRICT && IN_RING3 */
+
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
diff --git a/src/VBox/Devices/Input/UsbKbd.cpp b/src/VBox/Devices/Input/UsbKbd.cpp
new file mode 100644
index 00000000..376927c5
--- /dev/null
+++ b/src/VBox/Devices/Input/UsbKbd.cpp
@@ -0,0 +1,1358 @@
+/* $Id: UsbKbd.cpp $ */
+/** @file
+ * UsbKbd - USB Human Interface Device Emulation, Keyboard.
+ */
+
+/*
+ * Copyright (C) 2007-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/** @page pg_usb_kbd USB Keyboard Device Emulation.
+ *
+ * This module implements a standard USB keyboard which uses the boot
+ * interface. The keyboard sends reports which have room for up to six
+ * normal keys and all standard modifier keys. A report always reflects the
+ * current state of the keyboard and indicates which keys are held down.
+ *
+ * Software normally utilizes the keyboard's interrupt endpoint to request
+ * reports to be sent whenever a state change occurs. However, reports can
+ * also be sent whenever an interrupt transfer is initiated (the keyboard is
+ * not "idle") or requested via the control endpoint (polling).
+ *
+ * Because turnaround on USB is relatively slow, the keyboard often ends up
+ * in a situation where new input arrived but there is no URB available
+ * where a report could be written to. The PDM queue maintained by the
+ * keyboard driver is utilized to provide buffering and hold incoming events
+ * until they can be passed along. The USB keyboard can effectively buffer
+ * up to one event.
+ *
+ * If there is a pending event and a new URB becomes available, a report is
+ * built and the keyboard queue is flushed. This ensures that queued events
+ * are processed as quickly as possible.
+ *
+ *
+ * References:
+ *
+ * Device Class Definition for Human Interface Devices (HID), Version 1.11
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_USB_KBD
+#include <VBox/vmm/pdmusb.h>
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name USB HID string IDs
+ * @{ */
+#define USBHID_STR_ID_MANUFACTURER 1
+#define USBHID_STR_ID_PRODUCT 2
+/** @} */
+
+/** @name USB HID specific descriptor types
+ * @{ */
+#define DT_IF_HID_DESCRIPTOR 0x21
+#define DT_IF_HID_REPORT 0x22
+/** @} */
+
+/** @name USB HID vendor and product IDs
+ * @{ */
+#define VBOX_USB_VENDOR 0x80EE
+#define USBHID_PID_KEYBOARD 0x0010
+/** @} */
+
+/** @name USB HID class specific requests
+ * @{ */
+#define HID_REQ_GET_REPORT 0x01
+#define HID_REQ_GET_IDLE 0x02
+#define HID_REQ_SET_REPORT 0x09
+#define HID_REQ_SET_IDLE 0x0A
+/** @} */
+
+/** @name USB HID additional constants
+ * @{ */
+/** The highest USB usage code reported by the VBox emulated keyboard */
+#define VBOX_USB_MAX_USAGE_CODE 0xE7
+/** The size of an array needed to store all USB usage codes */
+#define VBOX_USB_USAGE_ARRAY_SIZE (VBOX_USB_MAX_USAGE_CODE + 1)
+#define USBHID_USAGE_ROLL_OVER 1
+/** The usage code of the first modifier key. */
+#define USBHID_MODIFIER_FIRST 0xE0
+/** The usage code of the last modifier key. */
+#define USBHID_MODIFIER_LAST 0xE7
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * The USB HID request state.
+ */
+typedef enum USBHIDREQSTATE
+{
+ /** Invalid status. */
+ USBHIDREQSTATE_INVALID = 0,
+ /** Ready to receive a new read request. */
+ USBHIDREQSTATE_READY,
+ /** Have (more) data for the host. */
+ USBHIDREQSTATE_DATA_TO_HOST,
+ /** Waiting to supply status information to the host. */
+ USBHIDREQSTATE_STATUS,
+ /** The end of the valid states. */
+ USBHIDREQSTATE_END
+} USBHIDREQSTATE;
+
+
+/**
+ * Endpoint status data.
+ */
+typedef struct USBHIDEP
+{
+ bool fHalted;
+} USBHIDEP;
+/** Pointer to the endpoint status. */
+typedef USBHIDEP *PUSBHIDEP;
+
+
+/**
+ * A URB queue.
+ */
+typedef struct USBHIDURBQUEUE
+{
+ /** The head pointer. */
+ PVUSBURB pHead;
+ /** Where to insert the next entry. */
+ PVUSBURB *ppTail;
+} USBHIDURBQUEUE;
+/** Pointer to a URB queue. */
+typedef USBHIDURBQUEUE *PUSBHIDURBQUEUE;
+/** Pointer to a const URB queue. */
+typedef USBHIDURBQUEUE const *PCUSBHIDURBQUEUE;
+
+
+/**
+ * The USB HID report structure for regular keys.
+ */
+typedef struct USBHIDK_REPORT
+{
+ uint8_t ShiftState; /**< Modifier keys bitfield */
+ uint8_t Reserved; /**< Currently unused */
+ uint8_t aKeys[6]; /**< Normal keys */
+} USBHIDK_REPORT, *PUSBHIDK_REPORT;
+
+/**
+ * The USB HID instance data.
+ */
+typedef struct USBHID
+{
+ /** Pointer back to the PDM USB Device instance structure. */
+ PPDMUSBINS pUsbIns;
+ /** Critical section protecting the device state. */
+ RTCRITSECT CritSect;
+
+ /** The current configuration.
+ * (0 - default, 1 - the one supported configuration, i.e configured.) */
+ uint8_t bConfigurationValue;
+ /** USB HID Idle value.
+ * (0 - only report state change, !=0 - report in bIdle * 4ms intervals.) */
+ uint8_t bIdle;
+ /** Endpoint 0 is the default control pipe, 1 is the dev->host interrupt one. */
+ USBHIDEP aEps[2];
+ /** The state of the HID (state machine).*/
+ USBHIDREQSTATE enmState;
+
+ /** Pending to-host queue.
+ * The URBs waiting here are waiting for data to become available.
+ */
+ USBHIDURBQUEUE ToHostQueue;
+
+ /** Done queue
+ * The URBs stashed here are waiting to be reaped. */
+ USBHIDURBQUEUE DoneQueue;
+ /** Signalled when adding an URB to the done queue and fHaveDoneQueueWaiter
+ * is set. */
+ RTSEMEVENT hEvtDoneQueue;
+ /** Someone is waiting on the done queue. */
+ bool fHaveDoneQueueWaiter;
+ /** If device has pending changes. */
+ bool fHasPendingChanges;
+ /** Currently depressed keys */
+ uint8_t abDepressedKeys[VBOX_USB_USAGE_ARRAY_SIZE];
+
+ /**
+ * Keyboard port - LUN#0.
+ *
+ * @implements PDMIBASE
+ * @implements PDMIKEYBOARDPORT
+ */
+ struct
+ {
+ /** The base interface for the keyboard port. */
+ PDMIBASE IBase;
+ /** The keyboard port base interface. */
+ PDMIKEYBOARDPORT IPort;
+
+ /** The base interface of the attached keyboard driver. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** The keyboard interface of the attached keyboard driver. */
+ R3PTRTYPE(PPDMIKEYBOARDCONNECTOR) pDrv;
+ } Lun0;
+} USBHID;
+/** Pointer to the USB HID instance data. */
+typedef USBHID *PUSBHID;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const PDMUSBDESCCACHESTRING g_aUsbHidStrings_en_US[] =
+{
+ { USBHID_STR_ID_MANUFACTURER, "VirtualBox" },
+ { USBHID_STR_ID_PRODUCT, "USB Keyboard" },
+};
+
+static const PDMUSBDESCCACHELANG g_aUsbHidLanguages[] =
+{
+ { 0x0409, RT_ELEMENTS(g_aUsbHidStrings_en_US), g_aUsbHidStrings_en_US }
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbHidEndpointDescs[] =
+{
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x81 /* ep=1, in */,
+ /* .bmAttributes = */ 3 /* interrupt */,
+ /* .wMaxPacketSize = */ 8,
+ /* .bInterval = */ 10,
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0
+ },
+};
+
+/** HID report descriptor. */
+static const uint8_t g_UsbHidReportDesc[] =
+{
+ /* Usage Page */ 0x05, 0x01, /* Generic Desktop */
+ /* Usage */ 0x09, 0x06, /* Keyboard */
+ /* Collection */ 0xA1, 0x01, /* Application */
+ /* Usage Page */ 0x05, 0x07, /* Keyboard */
+ /* Usage Minimum */ 0x19, 0xE0, /* Left Ctrl Key */
+ /* Usage Maximum */ 0x29, 0xE7, /* Right GUI Key */
+ /* Logical Minimum */ 0x15, 0x00, /* 0 */
+ /* Logical Maximum */ 0x25, 0x01, /* 1 */
+ /* Report Count */ 0x95, 0x08, /* 8 */
+ /* Report Size */ 0x75, 0x01, /* 1 */
+ /* Input */ 0x81, 0x02, /* Data, Value, Absolute, Bit field */
+ /* Report Count */ 0x95, 0x01, /* 1 */
+ /* Report Size */ 0x75, 0x08, /* 8 (padding bits) */
+ /* Input */ 0x81, 0x01, /* Constant, Array, Absolute, Bit field */
+ /* Report Count */ 0x95, 0x05, /* 5 */
+ /* Report Size */ 0x75, 0x01, /* 1 */
+ /* Usage Page */ 0x05, 0x08, /* LEDs */
+ /* Usage Minimum */ 0x19, 0x01, /* Num Lock */
+ /* Usage Maximum */ 0x29, 0x05, /* Kana */
+ /* Output */ 0x91, 0x02, /* Data, Value, Absolute, Non-volatile,Bit field */
+ /* Report Count */ 0x95, 0x01, /* 1 */
+ /* Report Size */ 0x75, 0x03, /* 3 */
+ /* Output */ 0x91, 0x01, /* Constant, Value, Absolute, Non-volatile, Bit field */
+ /* Report Count */ 0x95, 0x06, /* 6 */
+ /* Report Size */ 0x75, 0x08, /* 8 */
+ /* Logical Minimum */ 0x15, 0x00, /* 0 */
+ /* Logical Maximum */ 0x26, 0xFF,0x00,/* 255 */
+ /* Usage Page */ 0x05, 0x07, /* Keyboard */
+ /* Usage Minimum */ 0x19, 0x00, /* 0 */
+ /* Usage Maximum */ 0x29, 0xFF, /* 255 */
+ /* Input */ 0x81, 0x00, /* Data, Array, Absolute, Bit field */
+ /* End Collection */ 0xC0,
+};
+
+/** Additional HID class interface descriptor. */
+static const uint8_t g_UsbHidIfHidDesc[] =
+{
+ /* .bLength = */ 0x09,
+ /* .bDescriptorType = */ 0x21, /* HID */
+ /* .bcdHID = */ 0x10, 0x01, /* 1.1 */
+ /* .bCountryCode = */ 0x0D, /* International (ISO) */
+ /* .bNumDescriptors = */ 1,
+ /* .bDescriptorType = */ 0x22, /* Report */
+ /* .wDescriptorLength = */ sizeof(g_UsbHidReportDesc), 0x00
+};
+
+static const VUSBDESCINTERFACEEX g_UsbHidInterfaceDesc =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 0,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 1,
+ /* .bInterfaceClass = */ 3 /* HID */,
+ /* .bInterfaceSubClass = */ 1 /* Boot Interface */,
+ /* .bInterfaceProtocol = */ 1 /* Keyboard */,
+ /* .iInterface = */ 0
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ &g_UsbHidIfHidDesc,
+ /* .cbClass = */ sizeof(g_UsbHidIfHidDesc),
+ &g_aUsbHidEndpointDescs[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+static const VUSBINTERFACE g_aUsbHidInterfaces[] =
+{
+ { &g_UsbHidInterfaceDesc, /* .cSettings = */ 1 },
+};
+
+static const VUSBDESCCONFIGEX g_UsbHidConfigDesc =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbHidInterfaces),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7),
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbHidInterfaces[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCDEVICE g_UsbHidDeviceDesc =
+{
+ /* .bLength = */ sizeof(g_UsbHidDeviceDesc),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x110, /* 1.1 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 8,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBHID_PID_KEYBOARD,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBHID_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBHID_STR_ID_PRODUCT,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const PDMUSBDESCCACHE g_UsbHidDescCache =
+{
+ /* .pDevice = */ &g_UsbHidDeviceDesc,
+ /* .paConfigs = */ &g_UsbHidConfigDesc,
+ /* .paLanguages = */ g_aUsbHidLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbHidLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Initializes an URB queue.
+ *
+ * @param pQueue The URB queue.
+ */
+static void usbHidQueueInit(PUSBHIDURBQUEUE pQueue)
+{
+ pQueue->pHead = NULL;
+ pQueue->ppTail = &pQueue->pHead;
+}
+
+/**
+ * Inserts an URB at the end of the queue.
+ *
+ * @param pQueue The URB queue.
+ * @param pUrb The URB to insert.
+ */
+DECLINLINE(void) usbHidQueueAddTail(PUSBHIDURBQUEUE pQueue, PVUSBURB pUrb)
+{
+ pUrb->Dev.pNext = NULL;
+ *pQueue->ppTail = pUrb;
+ pQueue->ppTail = &pUrb->Dev.pNext;
+}
+
+
+/**
+ * Unlinks the head of the queue and returns it.
+ *
+ * @returns The head entry.
+ * @param pQueue The URB queue.
+ */
+DECLINLINE(PVUSBURB) usbHidQueueRemoveHead(PUSBHIDURBQUEUE pQueue)
+{
+ PVUSBURB pUrb = pQueue->pHead;
+ if (pUrb)
+ {
+ PVUSBURB pNext = pUrb->Dev.pNext;
+ pQueue->pHead = pNext;
+ if (!pNext)
+ pQueue->ppTail = &pQueue->pHead;
+ else
+ pUrb->Dev.pNext = NULL;
+ }
+ return pUrb;
+}
+
+
+/**
+ * Removes an URB from anywhere in the queue.
+ *
+ * @returns true if found, false if not.
+ * @param pQueue The URB queue.
+ * @param pUrb The URB to remove.
+ */
+DECLINLINE(bool) usbHidQueueRemove(PUSBHIDURBQUEUE pQueue, PVUSBURB pUrb)
+{
+ PVUSBURB pCur = pQueue->pHead;
+ if (pCur == pUrb)
+ pQueue->pHead = pUrb->Dev.pNext;
+ else
+ {
+ while (pCur)
+ {
+ if (pCur->Dev.pNext == pUrb)
+ {
+ pCur->Dev.pNext = pUrb->Dev.pNext;
+ break;
+ }
+ pCur = pCur->Dev.pNext;
+ }
+ if (!pCur)
+ return false;
+ }
+ if (!pUrb->Dev.pNext)
+ pQueue->ppTail = &pQueue->pHead;
+ return true;
+}
+
+
+#if 0 /* unused */
+/**
+ * Checks if the queue is empty or not.
+ *
+ * @returns true if it is, false if it isn't.
+ * @param pQueue The URB queue.
+ */
+DECLINLINE(bool) usbHidQueueIsEmpty(PCUSBHIDURBQUEUE pQueue)
+{
+ return pQueue->pHead == NULL;
+}
+#endif /* unused */
+
+
+/**
+ * Links an URB into the done queue.
+ *
+ * @param pThis The HID instance.
+ * @param pUrb The URB.
+ */
+static void usbHidLinkDone(PUSBHID pThis, PVUSBURB pUrb)
+{
+ usbHidQueueAddTail(&pThis->DoneQueue, pUrb);
+
+ if (pThis->fHaveDoneQueueWaiter)
+ {
+ int rc = RTSemEventSignal(pThis->hEvtDoneQueue);
+ AssertRC(rc);
+ }
+}
+
+
+/**
+ * Completes the URB with a stalled state, halting the pipe.
+ */
+static int usbHidCompleteStall(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb, const char *pszWhy)
+{
+ RT_NOREF1(pszWhy);
+ Log(("usbHidCompleteStall/#%u: pUrb=%p:%s: %s\n", pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, pszWhy));
+
+ pUrb->enmStatus = VUSBSTATUS_STALL;
+
+ /** @todo figure out if the stall is global or pipe-specific or both. */
+ if (pEp)
+ pEp->fHalted = true;
+ else
+ {
+ pThis->aEps[0].fHalted = true;
+ pThis->aEps[1].fHalted = true;
+ }
+
+ usbHidLinkDone(pThis, pUrb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Completes the URB with a OK state.
+ */
+static int usbHidCompleteOk(PUSBHID pThis, PVUSBURB pUrb, size_t cbData)
+{
+ Log(("usbHidCompleteOk/#%u: pUrb=%p:%s cbData=%#zx\n", pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, cbData));
+
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->cbData = (uint32_t)cbData;
+
+ usbHidLinkDone(pThis, pUrb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reset worker for usbHidUsbReset, usbHidUsbSetConfiguration and
+ * usbHidHandleDefaultPipe.
+ *
+ * @returns VBox status code.
+ * @param pThis The HID instance.
+ * @param pUrb Set when usbHidHandleDefaultPipe is the
+ * caller.
+ * @param fSetConfig Set when usbHidUsbSetConfiguration is the
+ * caller.
+ */
+static int usbHidResetWorker(PUSBHID pThis, PVUSBURB pUrb, bool fSetConfig)
+{
+ /*
+ * Deactivate the keyboard.
+ */
+ pThis->Lun0.pDrv->pfnSetActive(pThis->Lun0.pDrv, false);
+
+ /*
+ * Reset the device state.
+ */
+ pThis->enmState = USBHIDREQSTATE_READY;
+ pThis->bIdle = 0;
+ pThis->fHasPendingChanges = false;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aEps); i++)
+ pThis->aEps[i].fHalted = false;
+
+ if (!pUrb && !fSetConfig) /* (only device reset) */
+ pThis->bConfigurationValue = 0; /* default */
+
+ /*
+ * Ditch all pending URBs.
+ */
+ PVUSBURB pCurUrb;
+ while ((pCurUrb = usbHidQueueRemoveHead(&pThis->ToHostQueue)) != NULL)
+ {
+ pCurUrb->enmStatus = VUSBSTATUS_CRC;
+ usbHidLinkDone(pThis, pCurUrb);
+ }
+
+ if (pUrb)
+ return usbHidCompleteOk(pThis, pUrb, 0);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns true if the usage code corresponds to a keyboard modifier key
+ * (left or right ctrl, shift, alt or GUI). The usage codes for these keys
+ * are the range 0xe0 to 0xe7.
+ */
+static bool usbHidUsageCodeIsModifier(uint8_t u8Usage)
+{
+ return u8Usage >= USBHID_MODIFIER_FIRST && u8Usage <= USBHID_MODIFIER_LAST;
+}
+
+/**
+ * Convert a USB HID usage code to a keyboard modifier flag. The arithmetic
+ * is simple: the modifier keys have usage codes from 0xe0 to 0xe7, and the
+ * lower nibble is the bit number of the flag.
+ */
+static uint8_t usbHidModifierToFlag(uint8_t u8Usage)
+{
+ Assert(usbHidUsageCodeIsModifier(u8Usage));
+ return RT_BIT(u8Usage & 0xf);
+}
+
+/**
+ * Create a USB HID keyboard report reflecting the current state of the
+ * keyboard (up/down keys).
+ */
+static void usbHidBuildReport(PUSBHIDK_REPORT pReport, uint8_t *pabDepressedKeys)
+{
+ unsigned iBuf = 0;
+ RT_ZERO(*pReport);
+ for (unsigned iKey = 0; iKey < VBOX_USB_USAGE_ARRAY_SIZE; ++iKey)
+ {
+ Assert(iBuf <= RT_ELEMENTS(pReport->aKeys));
+ if (pabDepressedKeys[iKey])
+ {
+ if (usbHidUsageCodeIsModifier(iKey))
+ pReport->ShiftState |= usbHidModifierToFlag(iKey);
+ else if (iBuf == RT_ELEMENTS(pReport->aKeys))
+ {
+ /* The USB HID spec says that the entire vector should be
+ * set to ErrorRollOver on overflow. We don't mind if this
+ * path is taken several times for one report. */
+ for (unsigned iBuf2 = 0;
+ iBuf2 < RT_ELEMENTS(pReport->aKeys); ++iBuf2)
+ pReport->aKeys[iBuf2] = USBHID_USAGE_ROLL_OVER;
+ }
+ else
+ {
+ pReport->aKeys[iBuf] = iKey;
+ ++iBuf;
+ }
+ }
+ }
+}
+
+/**
+ * Handles a SET_REPORT request sent to the default control pipe. Note
+ * that unrecognized requests are ignored without reporting an error.
+ */
+static void usbHidSetReport(PUSBHID pThis, PVUSBURB pUrb)
+{
+ PVUSBSETUP pSetup = (PVUSBSETUP)&pUrb->abData[0];
+ Assert(pSetup->bRequest == HID_REQ_SET_REPORT);
+
+ /* The LED report is the 3rd report, ID 0 (-> wValue 0x200). */
+ if (pSetup->wIndex == 0 && pSetup->wLength == 1 && pSetup->wValue == 0x200)
+ {
+ PDMKEYBLEDS enmLeds = PDMKEYBLEDS_NONE;
+ uint8_t u8LEDs = pUrb->abData[sizeof(*pSetup)];
+ LogFlowFunc(("Setting keybooard LEDs to u8LEDs=%02X\n", u8LEDs));
+
+ /* Translate LED state to PDM format and send upstream. */
+ if (u8LEDs & 0x01)
+ enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_NUMLOCK);
+ if (u8LEDs & 0x02)
+ enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_CAPSLOCK);
+ if (u8LEDs & 0x04)
+ enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_SCROLLLOCK);
+
+ pThis->Lun0.pDrv->pfnLedStatusChange(pThis->Lun0.pDrv, enmLeds);
+ }
+}
+
+/**
+ * Sends a state report to the guest if there is a URB available.
+ */
+static void usbHidSendReport(PUSBHID pThis)
+{
+ PVUSBURB pUrb = usbHidQueueRemoveHead(&pThis->ToHostQueue);
+ if (pUrb)
+ {
+ PUSBHIDK_REPORT pReport = (PUSBHIDK_REPORT)&pUrb->abData[0];
+
+ usbHidBuildReport(pReport, pThis->abDepressedKeys);
+ pThis->fHasPendingChanges = false;
+ usbHidCompleteOk(pThis, pUrb, sizeof(*pReport));
+ }
+ else
+ {
+ Log2(("No available URB for USB kbd\n"));
+ pThis->fHasPendingChanges = true;
+ }
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) usbHidKeyboardQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PUSBHID pThis = RT_FROM_MEMBER(pInterface, USBHID, Lun0.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->Lun0.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIKEYBOARDPORT, &pThis->Lun0.IPort);
+ return NULL;
+}
+
+/* See the PS2K device. */
+#define KRSP_BAT_FAIL 0xFC /* Also a 'release keys' signal. */
+
+/**
+ * Keyboard event handler.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the keyboard port interface (KBDState::Keyboard.IPort).
+ * @param u32UsageCode The key usage ID.
+ */
+static DECLCALLBACK(int) usbHidKeyboardPutEvent(PPDMIKEYBOARDPORT pInterface, uint32_t u32UsageCode)
+{
+ PUSBHID pThis = RT_FROM_MEMBER(pInterface, USBHID, Lun0.IPort);
+ uint8_t u8HidCode;
+ bool fKeyDown;
+ bool fHaveEvent = true;
+ int rc = VINF_SUCCESS;
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ /* Let's see what we got... */
+ fKeyDown = !(u32UsageCode & 0x80000000);
+ u8HidCode = u32UsageCode & 0xFF;
+ AssertReturn(u8HidCode <= VBOX_USB_MAX_USAGE_CODE, VERR_INTERNAL_ERROR);
+
+ LogFlowFunc(("key %s: 0x%x\n", fKeyDown ? "down" : "up", u8HidCode));
+
+ /*
+ * Due to host key repeat, we can get key events for keys which are
+ * already depressed. Drop those right here.
+ */
+ if (fKeyDown && pThis->abDepressedKeys[u8HidCode])
+ fHaveEvent = false;
+
+ /* If there is already a pending event, we won't accept a new one yet. */
+ if (pThis->fHasPendingChanges && fHaveEvent)
+ {
+ rc = VERR_TRY_AGAIN;
+ }
+ else if (fHaveEvent)
+ {
+ if (RT_UNLIKELY(u32UsageCode == KRSP_BAT_FAIL))
+ {
+ /* Clear all currently depressed and unreported keys. */
+ RT_ZERO(pThis->abDepressedKeys);
+ }
+ else
+ {
+ /* Regular key event - update keyboard state. */
+ if (fKeyDown)
+ pThis->abDepressedKeys[u8HidCode] = 1;
+ else
+ pThis->abDepressedKeys[u8HidCode] = 0;
+ }
+
+ /*
+ * Try sending a report. Note that we already decided to consume the
+ * event regardless of whether a URB is available or not. If it's not,
+ * we will simply not accept any further events.
+ */
+ usbHidSendReport(pThis);
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbReap}
+ */
+static DECLCALLBACK(PVUSBURB) usbHidUrbReap(PPDMUSBINS pUsbIns, RTMSINTERVAL cMillies)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ //LogFlow(("usbHidUrbReap/#%u: cMillies=%u\n", pUsbIns->iInstance, cMillies));
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ PVUSBURB pUrb = usbHidQueueRemoveHead(&pThis->DoneQueue);
+ if (!pUrb && cMillies)
+ {
+ /* Wait */
+ pThis->fHaveDoneQueueWaiter = true;
+ RTCritSectLeave(&pThis->CritSect);
+
+ RTSemEventWait(pThis->hEvtDoneQueue, cMillies);
+
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->fHaveDoneQueueWaiter = false;
+
+ pUrb = usbHidQueueRemoveHead(&pThis->DoneQueue);
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+
+ if (pUrb)
+ Log(("usbHidUrbReap/#%u: pUrb=%p:%s\n", pUsbIns->iInstance, pUrb, pUrb->pszDesc));
+ return pUrb;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnWakeup}
+ */
+static DECLCALLBACK(int) usbHidWakeup(PPDMUSBINS pUsbIns)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+
+ return RTSemEventSignal(pThis->hEvtDoneQueue);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbCancel}
+ */
+static DECLCALLBACK(int) usbHidUrbCancel(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogFlow(("usbHidUrbCancel/#%u: pUrb=%p:%s\n", pUsbIns->iInstance, pUrb, pUrb->pszDesc));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Remove the URB from the to-host queue and move it onto the done queue.
+ */
+ if (usbHidQueueRemove(&pThis->ToHostQueue, pUrb))
+ usbHidLinkDone(pThis, pUrb);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Handles request sent to the inbound (device to host) interrupt pipe. This is
+ * rather different from bulk requests because an interrupt read URB may complete
+ * after arbitrarily long time.
+ */
+static int usbHidHandleIntrDevToHost(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb)
+{
+ /*
+ * Stall the request if the pipe is halted.
+ */
+ if (RT_UNLIKELY(pEp->fHalted))
+ return usbHidCompleteStall(pThis, NULL, pUrb, "Halted pipe");
+
+ /*
+ * Deal with the URB according to the state.
+ */
+ switch (pThis->enmState)
+ {
+ /*
+ * We've data left to transfer to the host.
+ */
+ case USBHIDREQSTATE_DATA_TO_HOST:
+ {
+ AssertFailed();
+ Log(("usbHidHandleIntrDevToHost: Entering STATUS\n"));
+ return usbHidCompleteOk(pThis, pUrb, 0);
+ }
+
+ /*
+ * Status transfer.
+ */
+ case USBHIDREQSTATE_STATUS:
+ {
+ AssertFailed();
+ Log(("usbHidHandleIntrDevToHost: Entering READY\n"));
+ pThis->enmState = USBHIDREQSTATE_READY;
+ return usbHidCompleteOk(pThis, pUrb, 0);
+ }
+
+ case USBHIDREQSTATE_READY:
+ usbHidQueueAddTail(&pThis->ToHostQueue, pUrb);
+ /* If device was not set idle, send the current report right away. */
+ if (pThis->bIdle != 0 || pThis->fHasPendingChanges)
+ {
+ usbHidSendReport(pThis);
+ LogFlow(("usbHidHandleIntrDevToHost: Sent report via %p:%s\n", pUrb, pUrb->pszDesc));
+ Assert(!pThis->fHasPendingChanges); /* Since we just got a URB... */
+ /* There may be more input queued up. Ask for it now. */
+ pThis->Lun0.pDrv->pfnFlushQueue(pThis->Lun0.pDrv);
+ }
+ return VINF_SUCCESS;
+
+ /*
+ * Bad states, stall.
+ */
+ default:
+ Log(("usbHidHandleIntrDevToHost: enmState=%d cbData=%#x\n", pThis->enmState, pUrb->cbData));
+ return usbHidCompleteStall(pThis, NULL, pUrb, "Really bad state (D2H)!");
+ }
+}
+
+
+/**
+ * Handles request sent to the default control pipe.
+ */
+static int usbHidHandleDefaultPipe(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb)
+{
+ PVUSBSETUP pSetup = (PVUSBSETUP)&pUrb->abData[0];
+ LogFlow(("usbHidHandleDefaultPipe: cbData=%d\n", pUrb->cbData));
+
+ AssertReturn(pUrb->cbData >= sizeof(*pSetup), VERR_VUSB_FAILED_TO_QUEUE_URB);
+
+ if ((pSetup->bmRequestType & VUSB_REQ_MASK) == VUSB_REQ_STANDARD)
+ {
+ switch (pSetup->bRequest)
+ {
+ case VUSB_REQ_GET_DESCRIPTOR:
+ {
+ switch (pSetup->bmRequestType)
+ {
+ case VUSB_TO_DEVICE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ switch (pSetup->wValue >> 8)
+ {
+ case VUSB_DT_STRING:
+ Log(("usbHid: GET_DESCRIPTOR DT_STRING wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ break;
+ default:
+ Log(("usbHid: GET_DESCRIPTOR, huh? wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ break;
+ }
+ break;
+ }
+
+ case VUSB_TO_INTERFACE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ switch (pSetup->wValue >> 8)
+ {
+ case DT_IF_HID_DESCRIPTOR:
+ {
+ uint32_t cbCopy;
+
+ /* Returned data is written after the setup message. */
+ cbCopy = pUrb->cbData - sizeof(*pSetup);
+ cbCopy = RT_MIN(cbCopy, sizeof(g_UsbHidIfHidDesc));
+ Log(("usbHidKbd: GET_DESCRIPTOR DT_IF_HID_DESCRIPTOR wValue=%#x wIndex=%#x cbCopy=%#x\n", pSetup->wValue, pSetup->wIndex, cbCopy));
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &g_UsbHidIfHidDesc, cbCopy);
+ return usbHidCompleteOk(pThis, pUrb, cbCopy + sizeof(*pSetup));
+ }
+
+ case DT_IF_HID_REPORT:
+ {
+ uint32_t cbCopy;
+
+ /* Returned data is written after the setup message. */
+ cbCopy = pUrb->cbData - sizeof(*pSetup);
+ cbCopy = RT_MIN(cbCopy, sizeof(g_UsbHidReportDesc));
+ Log(("usbHid: GET_DESCRIPTOR DT_IF_HID_REPORT wValue=%#x wIndex=%#x cbCopy=%#x\n", pSetup->wValue, pSetup->wIndex, cbCopy));
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &g_UsbHidReportDesc, cbCopy);
+ return usbHidCompleteOk(pThis, pUrb, cbCopy + sizeof(*pSetup));
+ }
+
+ default:
+ Log(("usbHid: GET_DESCRIPTOR, huh? wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ break;
+ }
+ break;
+ }
+
+ default:
+ Log(("usbHid: Bad GET_DESCRIPTOR req: bmRequestType=%#x\n", pSetup->bmRequestType));
+ return usbHidCompleteStall(pThis, pEp, pUrb, "Bad GET_DESCRIPTOR");
+ }
+ break;
+ }
+
+ case VUSB_REQ_GET_STATUS:
+ {
+ uint16_t wRet = 0;
+
+ if (pSetup->wLength != 2)
+ {
+ Log(("usbHid: Bad GET_STATUS req: wLength=%#x\n", pSetup->wLength));
+ break;
+ }
+ Assert(pSetup->wValue == 0);
+ switch (pSetup->bmRequestType)
+ {
+ case VUSB_TO_DEVICE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ Assert(pSetup->wIndex == 0);
+ Log(("usbHid: GET_STATUS (device)\n"));
+ wRet = 0; /* Not self-powered, no remote wakeup. */
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &wRet, sizeof(wRet));
+ return usbHidCompleteOk(pThis, pUrb, sizeof(wRet) + sizeof(*pSetup));
+ }
+
+ case VUSB_TO_INTERFACE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ if (pSetup->wIndex == 0)
+ {
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &wRet, sizeof(wRet));
+ return usbHidCompleteOk(pThis, pUrb, sizeof(wRet) + sizeof(*pSetup));
+ }
+ Log(("usbHid: GET_STATUS (interface) invalid, wIndex=%#x\n", pSetup->wIndex));
+ break;
+ }
+
+ case VUSB_TO_ENDPOINT | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ if (pSetup->wIndex < RT_ELEMENTS(pThis->aEps))
+ {
+ wRet = pThis->aEps[pSetup->wIndex].fHalted ? 1 : 0;
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &wRet, sizeof(wRet));
+ return usbHidCompleteOk(pThis, pUrb, sizeof(wRet) + sizeof(*pSetup));
+ }
+ Log(("usbHid: GET_STATUS (endpoint) invalid, wIndex=%#x\n", pSetup->wIndex));
+ break;
+ }
+
+ default:
+ Log(("usbHid: Bad GET_STATUS req: bmRequestType=%#x\n", pSetup->bmRequestType));
+ return usbHidCompleteStall(pThis, pEp, pUrb, "Bad GET_STATUS");
+ }
+ break;
+ }
+
+ case VUSB_REQ_CLEAR_FEATURE:
+ break;
+ }
+
+ /** @todo implement this. */
+ Log(("usbHid: Implement standard request: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength));
+
+ usbHidCompleteStall(pThis, pEp, pUrb, "TODO: standard request stuff");
+ }
+ else if ((pSetup->bmRequestType & VUSB_REQ_MASK) == VUSB_REQ_CLASS)
+ {
+ switch (pSetup->bRequest)
+ {
+ case HID_REQ_SET_IDLE:
+ {
+ switch (pSetup->bmRequestType)
+ {
+ case VUSB_TO_INTERFACE | VUSB_REQ_CLASS | VUSB_DIR_TO_DEVICE:
+ {
+ Log(("usbHid: SET_IDLE wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ pThis->bIdle = pSetup->wValue >> 8;
+ /* Consider 24ms to mean zero for keyboards (see IOUSBHIDDriver) */
+ if (pThis->bIdle == 6) pThis->bIdle = 0;
+ return usbHidCompleteOk(pThis, pUrb, 0);
+ }
+ break;
+ }
+ break;
+ }
+ case HID_REQ_GET_IDLE:
+ {
+ switch (pSetup->bmRequestType)
+ {
+ case VUSB_TO_INTERFACE | VUSB_REQ_CLASS | VUSB_DIR_TO_HOST:
+ {
+ Log(("usbHid: GET_IDLE wValue=%#x wIndex=%#x, returning %#x\n", pSetup->wValue, pSetup->wIndex, pThis->bIdle));
+ pUrb->abData[sizeof(*pSetup)] = pThis->bIdle;
+ return usbHidCompleteOk(pThis, pUrb, 1);
+ }
+ break;
+ }
+ break;
+ }
+ case HID_REQ_SET_REPORT:
+ {
+ switch (pSetup->bmRequestType)
+ {
+ case VUSB_TO_INTERFACE | VUSB_REQ_CLASS | VUSB_DIR_TO_DEVICE:
+ {
+ Log(("usbHid: SET_REPORT wValue=%#x wIndex=%#x wLength=%#x\n", pSetup->wValue, pSetup->wIndex, pSetup->wLength));
+ usbHidSetReport(pThis, pUrb);
+ return usbHidCompleteOk(pThis, pUrb, 0);
+ }
+ break;
+ }
+ break;
+ }
+ }
+ Log(("usbHid: Unimplemented class request: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength));
+
+ usbHidCompleteStall(pThis, pEp, pUrb, "TODO: class request stuff");
+ }
+ else
+ {
+ Log(("usbHid: Unknown control msg: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength));
+ return usbHidCompleteStall(pThis, pEp, pUrb, "Unknown control msg");
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbQueue}
+ */
+static DECLCALLBACK(int) usbHidQueue(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogFlow(("usbHidQueue/#%u: pUrb=%p:%s EndPt=%#x\n", pUsbIns->iInstance, pUrb, pUrb->pszDesc, pUrb->EndPt));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Parse on a per end-point basis.
+ */
+ int rc;
+ switch (pUrb->EndPt)
+ {
+ case 0:
+ rc = usbHidHandleDefaultPipe(pThis, &pThis->aEps[0], pUrb);
+ break;
+
+ case 0x81:
+ AssertFailed();
+ RT_FALL_THRU();
+ case 0x01:
+ rc = usbHidHandleIntrDevToHost(pThis, &pThis->aEps[1], pUrb);
+ break;
+
+ default:
+ AssertMsgFailed(("EndPt=%d\n", pUrb->EndPt));
+ rc = VERR_VUSB_FAILED_TO_QUEUE_URB;
+ break;
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbClearHaltedEndpoint}
+ */
+static DECLCALLBACK(int) usbHidUsbClearHaltedEndpoint(PPDMUSBINS pUsbIns, unsigned uEndpoint)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogFlow(("usbHidUsbClearHaltedEndpoint/#%u: uEndpoint=%#x\n", pUsbIns->iInstance, uEndpoint));
+
+ if ((uEndpoint & ~0x80) < RT_ELEMENTS(pThis->aEps))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->aEps[(uEndpoint & ~0x80)].fHalted = false;
+ RTCritSectLeave(&pThis->CritSect);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbSetInterface}
+ */
+static DECLCALLBACK(int) usbHidUsbSetInterface(PPDMUSBINS pUsbIns, uint8_t bInterfaceNumber, uint8_t bAlternateSetting)
+{
+ RT_NOREF3(pUsbIns, bInterfaceNumber, bAlternateSetting);
+ LogFlow(("usbHidUsbSetInterface/#%u: bInterfaceNumber=%u bAlternateSetting=%u\n", pUsbIns->iInstance, bInterfaceNumber, bAlternateSetting));
+ Assert(bAlternateSetting == 0);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbSetConfiguration}
+ */
+static DECLCALLBACK(int) usbHidUsbSetConfiguration(PPDMUSBINS pUsbIns, uint8_t bConfigurationValue,
+ const void *pvOldCfgDesc, const void *pvOldIfState, const void *pvNewCfgDesc)
+{
+ RT_NOREF3(pvOldCfgDesc, pvOldIfState, pvNewCfgDesc);
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogFlow(("usbHidUsbSetConfiguration/#%u: bConfigurationValue=%u\n", pUsbIns->iInstance, bConfigurationValue));
+ Assert(bConfigurationValue == 1);
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * If the same config is applied more than once, it's a kind of reset.
+ */
+ if (pThis->bConfigurationValue == bConfigurationValue)
+ usbHidResetWorker(pThis, NULL, true /*fSetConfig*/); /** @todo figure out the exact difference */
+ pThis->bConfigurationValue = bConfigurationValue;
+
+ /*
+ * Tell the other end that the keyboard is now enabled and wants
+ * to receive keystrokes.
+ */
+ pThis->Lun0.pDrv->pfnSetActive(pThis->Lun0.pDrv, true);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbGetDescriptorCache}
+ */
+static DECLCALLBACK(PCPDMUSBDESCCACHE) usbHidUsbGetDescriptorCache(PPDMUSBINS pUsbIns)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID); RT_NOREF_PV(pThis);
+ LogFlow(("usbHidUsbGetDescriptorCache/#%u:\n", pUsbIns->iInstance));
+ return &g_UsbHidDescCache;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbReset}
+ */
+static DECLCALLBACK(int) usbHidUsbReset(PPDMUSBINS pUsbIns, bool fResetOnLinux)
+{
+ RT_NOREF1(fResetOnLinux);
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogFlow(("usbHidUsbReset/#%u:\n", pUsbIns->iInstance));
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = usbHidResetWorker(pThis, NULL, false /*fSetConfig*/);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnDestruct}
+ */
+static DECLCALLBACK(void) usbHidDestruct(PPDMUSBINS pUsbIns)
+{
+ PDMUSB_CHECK_VERSIONS_RETURN_VOID(pUsbIns);
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogFlow(("usbHidDestruct/#%u:\n", pUsbIns->iInstance));
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ {
+ /* Let whoever runs in this critical section complete. */
+ RTCritSectEnter(&pThis->CritSect);
+ RTCritSectLeave(&pThis->CritSect);
+ RTCritSectDelete(&pThis->CritSect);
+ }
+
+ if (pThis->hEvtDoneQueue != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(pThis->hEvtDoneQueue);
+ pThis->hEvtDoneQueue = NIL_RTSEMEVENT;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) usbHidConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal)
+{
+ RT_NOREF1(pCfgGlobal);
+ PDMUSB_CHECK_VERSIONS_RETURN(pUsbIns);
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ Log(("usbHidConstruct/#%u:\n", iInstance));
+
+ /*
+ * Perform the basic structure initialization first so the destructor
+ * will not misbehave.
+ */
+ pThis->pUsbIns = pUsbIns;
+ pThis->hEvtDoneQueue = NIL_RTSEMEVENT;
+ usbHidQueueInit(&pThis->ToHostQueue);
+ usbHidQueueInit(&pThis->DoneQueue);
+
+ int rc = RTCritSectInit(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ rc = RTSemEventCreate(&pThis->hEvtDoneQueue);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Validate and read the configuration.
+ */
+ rc = CFGMR3ValidateConfig(pCfg, "/", "", "", "UsbHid", iInstance);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pThis->Lun0.IBase.pfnQueryInterface = usbHidKeyboardQueryInterface;
+ pThis->Lun0.IPort.pfnPutEventHid = usbHidKeyboardPutEvent;
+
+ /*
+ * Attach the keyboard driver.
+ */
+ rc = PDMUsbHlpDriverAttach(pUsbIns, 0 /*iLun*/, &pThis->Lun0.IBase, &pThis->Lun0.pDrvBase, "Keyboard Port");
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS, N_("HID failed to attach keyboard driver"));
+
+ pThis->Lun0.pDrv = PDMIBASE_QUERY_INTERFACE(pThis->Lun0.pDrvBase, PDMIKEYBOARDCONNECTOR);
+ if (!pThis->Lun0.pDrv)
+ return PDMUsbHlpVMSetError(pUsbIns, VERR_PDM_MISSING_INTERFACE, RT_SRC_POS, N_("HID failed to query keyboard interface"));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The USB Human Interface Device (HID) Keyboard registration record.
+ */
+const PDMUSBREG g_UsbHidKbd =
+{
+ /* u32Version */
+ PDM_USBREG_VERSION,
+ /* szName */
+ "HidKeyboard",
+ /* pszDescription */
+ "USB HID Keyboard.",
+ /* fFlags */
+ 0,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(USBHID),
+ /* pfnConstruct */
+ usbHidConstruct,
+ /* pfnDestruct */
+ usbHidDestruct,
+ /* pfnVMInitComplete */
+ NULL,
+ /* pfnVMPowerOn */
+ NULL,
+ /* pfnVMReset */
+ NULL,
+ /* pfnVMSuspend */
+ NULL,
+ /* pfnVMResume */
+ NULL,
+ /* pfnVMPowerOff */
+ NULL,
+ /* pfnHotPlugged */
+ NULL,
+ /* pfnHotUnplugged */
+ NULL,
+ /* pfnDriverAttach */
+ NULL,
+ /* pfnDriverDetach */
+ NULL,
+ /* pfnQueryInterface */
+ NULL,
+ /* pfnUsbReset */
+ usbHidUsbReset,
+ /* pfnUsbGetDescriptorCache */
+ usbHidUsbGetDescriptorCache,
+ /* pfnUsbSetConfiguration */
+ usbHidUsbSetConfiguration,
+ /* pfnUsbSetInterface */
+ usbHidUsbSetInterface,
+ /* pfnUsbClearHaltedEndpoint */
+ usbHidUsbClearHaltedEndpoint,
+ /* pfnUrbNew */
+ NULL/*usbHidUrbNew*/,
+ /* pfnUrbQueue */
+ usbHidQueue,
+ /* pfnUrbCancel */
+ usbHidUrbCancel,
+ /* pfnUrbReap */
+ usbHidUrbReap,
+ /* pfnWakeup */
+ usbHidWakeup,
+ /* u32TheEnd */
+ PDM_USBREG_VERSION
+};
diff --git a/src/VBox/Devices/Input/UsbMouse.cpp b/src/VBox/Devices/Input/UsbMouse.cpp
new file mode 100644
index 00000000..7cf83fb3
--- /dev/null
+++ b/src/VBox/Devices/Input/UsbMouse.cpp
@@ -0,0 +1,2452 @@
+/* $Id: UsbMouse.cpp $ */
+/** @file
+ * UsbMouse - USB Human Interface Device Emulation (Mouse).
+ */
+
+/*
+ * Copyright (C) 2007-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_USB_MOUSE
+#include <VBox/vmm/pdmusb.h>
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name USB HID string IDs
+ * @{ */
+#define USBHID_STR_ID_MANUFACTURER 1
+#define USBHID_STR_ID_PRODUCT_M 2
+#define USBHID_STR_ID_PRODUCT_T 3
+#define USBHID_STR_ID_PRODUCT_MT 4
+/** @} */
+
+/** @name USB HID specific descriptor types
+ * @{ */
+#define DT_IF_HID_DESCRIPTOR 0x21
+#define DT_IF_HID_REPORT 0x22
+/** @} */
+
+/** @name USB HID vendor and product IDs
+ * @{ */
+#define VBOX_USB_VENDOR 0x80EE
+#define USBHID_PID_MOUSE 0x0020
+#define USBHID_PID_TABLET 0x0021
+#define USBHID_PID_MULTI_TOUCH 0x0022
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * The USB HID request state.
+ */
+typedef enum USBHIDREQSTATE
+{
+ /** Invalid status. */
+ USBHIDREQSTATE_INVALID = 0,
+ /** Ready to receive a new read request. */
+ USBHIDREQSTATE_READY,
+ /** Have (more) data for the host. */
+ USBHIDREQSTATE_DATA_TO_HOST,
+ /** Waiting to supply status information to the host. */
+ USBHIDREQSTATE_STATUS,
+ /** The end of the valid states. */
+ USBHIDREQSTATE_END
+} USBHIDREQSTATE;
+
+/**
+ * The device reporting mode.
+ * @todo Use an interface instead of an enum and switches.
+ */
+typedef enum USBHIDMODE
+{
+ /** Relative. */
+ USBHIDMODE_RELATIVE = 0,
+ /** Absolute. */
+ USBHIDMODE_ABSOLUTE,
+ /** Multi-touch. */
+ USBHIDMODE_MULTI_TOUCH
+} USBHIDMODE;
+
+
+/**
+ * Endpoint status data.
+ */
+typedef struct USBHIDEP
+{
+ bool fHalted;
+} USBHIDEP;
+/** Pointer to the endpoint status. */
+typedef USBHIDEP *PUSBHIDEP;
+
+
+/**
+ * A URB queue.
+ */
+typedef struct USBHIDURBQUEUE
+{
+ /** The head pointer. */
+ PVUSBURB pHead;
+ /** Where to insert the next entry. */
+ PVUSBURB *ppTail;
+} USBHIDURBQUEUE;
+/** Pointer to a URB queue. */
+typedef USBHIDURBQUEUE *PUSBHIDURBQUEUE;
+/** Pointer to a const URB queue. */
+typedef USBHIDURBQUEUE const *PCUSBHIDURBQUEUE;
+
+
+/**
+ * Mouse movement accumulator.
+ */
+typedef struct USBHIDM_ACCUM
+{
+ union
+ {
+ struct
+ {
+ uint32_t fButtons;
+ int32_t dx;
+ int32_t dy;
+ int32_t dz;
+ } Relative;
+ struct
+ {
+ uint32_t fButtons;
+ int32_t dz;
+ int32_t dw;
+ uint32_t x;
+ uint32_t y;
+ } Absolute;
+ } u;
+} USBHIDM_ACCUM, *PUSBHIDM_ACCUM;
+
+#define MT_CONTACTS_PER_REPORT 5
+
+#define MT_CONTACT_MAX_COUNT 10
+
+#define MT_CONTACT_F_IN_CONTACT 0x01
+#define MT_CONTACT_F_IN_RANGE 0x02
+
+#define MT_CONTACT_S_ACTIVE 0x01 /* Contact must be reported to the guest. */
+#define MT_CONTACT_S_CANCELLED 0x02 /* Contact loss must be reported to the guest. */
+#define MT_CONTACT_S_REUSED 0x04 /* Report contact loss for the oldId and then new contact for the id. */
+#define MT_CONTACT_S_DIRTY 0x08 /* Temporary flag used to track already processed elements. */
+
+typedef struct MTCONTACT
+{
+ uint16_t x;
+ uint16_t y;
+ uint8_t id;
+ uint8_t flags;
+ uint8_t status;
+ uint8_t oldId; /* Valid only if MT_CONTACT_S_REUSED is set. */
+} MTCONTACT;
+
+
+/**
+ * The USB HID instance data.
+ */
+typedef struct USBHID
+{
+ /** Pointer back to the PDM USB Device instance structure. */
+ PPDMUSBINS pUsbIns;
+ /** Critical section protecting the device state. */
+ RTCRITSECT CritSect;
+
+ /** The current configuration.
+ * (0 - default, 1 - the one supported configuration, i.e configured.) */
+ uint8_t bConfigurationValue;
+ /** Endpoint 0 is the default control pipe, 1 is the dev->host interrupt one. */
+ USBHIDEP aEps[2];
+ /** The state of the HID (state machine).*/
+ USBHIDREQSTATE enmState;
+
+ /** Pointer movement accumulator. */
+ USBHIDM_ACCUM PtrDelta;
+
+ /** Pending to-host queue.
+ * The URBs waiting here are waiting for data to become available.
+ */
+ USBHIDURBQUEUE ToHostQueue;
+
+ /** Done queue
+ * The URBs stashed here are waiting to be reaped. */
+ USBHIDURBQUEUE DoneQueue;
+ /** Signalled when adding an URB to the done queue and fHaveDoneQueueWaiter
+ * is set. */
+ RTSEMEVENT hEvtDoneQueue;
+
+ /** Someone is waiting on the done queue. */
+ bool fHaveDoneQueueWaiter;
+ /** If device has pending changes. */
+ bool fHasPendingChanges;
+ /** Is this a relative, absolute or multi-touch pointing device? */
+ USBHIDMODE enmMode;
+ /** Tablet coordinate shift factor for old and broken operating systems. */
+ uint8_t u8CoordShift;
+
+ /**
+ * Mouse port - LUN#0.
+ *
+ * @implements PDMIBASE
+ * @implements PDMIMOUSEPORT
+ */
+ struct
+ {
+ /** The base interface for the mouse port. */
+ PDMIBASE IBase;
+ /** The mouse port base interface. */
+ PDMIMOUSEPORT IPort;
+
+ /** The base interface of the attached mouse driver. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** The mouse interface of the attached mouse driver. */
+ R3PTRTYPE(PPDMIMOUSECONNECTOR) pDrv;
+ } Lun0;
+
+ MTCONTACT aCurrentContactState[MT_CONTACT_MAX_COUNT];
+ MTCONTACT aReportingContactState[MT_CONTACT_MAX_COUNT];
+ uint32_t u32LastTouchScanTime;
+ bool fTouchReporting;
+ bool fTouchStateUpdated;
+} USBHID;
+/** Pointer to the USB HID instance data. */
+typedef USBHID *PUSBHID;
+
+#pragma pack(1)
+/**
+ * The USB HID report structure for relative device.
+ */
+typedef struct USBHIDM_REPORT
+{
+ uint8_t fButtons;
+ int8_t dx;
+ int8_t dy;
+ int8_t dz;
+} USBHIDM_REPORT, *PUSBHIDM_REPORT;
+
+/**
+ * The USB HID report structure for absolute device.
+ */
+
+typedef struct USBHIDT_REPORT
+{
+ uint8_t fButtons;
+ int8_t dz;
+ int8_t dw;
+ uint8_t padding;
+ uint16_t x;
+ uint16_t y;
+} USBHIDT_REPORT, *PUSBHIDT_REPORT;
+
+/**
+ * The combined USB HID report union for relative and absolute
+ * devices.
+ */
+typedef union USBHIDTM_REPORT
+{
+ USBHIDM_REPORT m;
+ USBHIDT_REPORT t;
+} USBHIDTM_REPORT, *PUSBHIDTM_REPORT;
+
+/**
+ * The USB HID report structure for the multi-touch device.
+ */
+typedef struct USBHIDMT_REPORT
+{
+ uint8_t idReport;
+ uint8_t cContacts;
+ struct
+ {
+ uint8_t fContact;
+ uint8_t cContact;
+ uint16_t x;
+ uint16_t y;
+ } aContacts[MT_CONTACTS_PER_REPORT];
+ uint32_t u32ScanTime;
+} USBHIDMT_REPORT, *PUSBHIDMT_REPORT;
+
+typedef struct USBHIDMT_REPORT_POINTER
+{
+ uint8_t idReport;
+ uint8_t fButtons;
+ uint16_t x;
+ uint16_t y;
+} USBHIDMT_REPORT_POINTER;
+#pragma pack()
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const PDMUSBDESCCACHESTRING g_aUsbHidStrings_en_US[] =
+{
+ { USBHID_STR_ID_MANUFACTURER, "VirtualBox" },
+ { USBHID_STR_ID_PRODUCT_M, "USB Mouse" },
+ { USBHID_STR_ID_PRODUCT_T, "USB Tablet" },
+ { USBHID_STR_ID_PRODUCT_MT, "USB Multi-Touch" },
+};
+
+static const PDMUSBDESCCACHELANG g_aUsbHidLanguages[] =
+{
+ { 0x0409, RT_ELEMENTS(g_aUsbHidStrings_en_US), g_aUsbHidStrings_en_US }
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbHidMEndpointDescs[] =
+{
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x81 /* ep=1, in */,
+ /* .bmAttributes = */ 3 /* interrupt */,
+ /* .wMaxPacketSize = */ 4,
+ /* .bInterval = */ 10,
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0
+ },
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbHidTEndpointDescs[] =
+{
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x81 /* ep=1, in */,
+ /* .bmAttributes = */ 3 /* interrupt */,
+ /* .wMaxPacketSize = */ 8,
+ /* .bInterval = */ 10,
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0
+ },
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbHidMTEndpointDescs[] =
+{
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x81 /* ep=1, in */,
+ /* .bmAttributes = */ 3 /* interrupt */,
+ /* .wMaxPacketSize = */ 64,
+ /* .bInterval = */ 10,
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0
+ },
+};
+
+/* HID report descriptor (mouse). */
+static const uint8_t g_UsbHidMReportDesc[] =
+{
+ /* Usage Page */ 0x05, 0x01, /* Generic Desktop */
+ /* Usage */ 0x09, 0x02, /* Mouse */
+ /* Collection */ 0xA1, 0x01, /* Application */
+ /* Usage */ 0x09, 0x01, /* Pointer */
+ /* Collection */ 0xA1, 0x00, /* Physical */
+ /* Usage Page */ 0x05, 0x09, /* Button */
+ /* Usage Minimum */ 0x19, 0x01, /* Button 1 */
+ /* Usage Maximum */ 0x29, 0x05, /* Button 5 */
+ /* Logical Minimum */ 0x15, 0x00, /* 0 */
+ /* Logical Maximum */ 0x25, 0x01, /* 1 */
+ /* Report Count */ 0x95, 0x05, /* 5 */
+ /* Report Size */ 0x75, 0x01, /* 1 */
+ /* Input */ 0x81, 0x02, /* Data, Value, Absolute, Bit field */
+ /* Report Count */ 0x95, 0x01, /* 1 */
+ /* Report Size */ 0x75, 0x03, /* 3 (padding bits) */
+ /* Input */ 0x81, 0x03, /* Constant, Value, Absolute, Bit field */
+ /* Usage Page */ 0x05, 0x01, /* Generic Desktop */
+ /* Usage */ 0x09, 0x30, /* X */
+ /* Usage */ 0x09, 0x31, /* Y */
+ /* Usage */ 0x09, 0x38, /* Z (wheel) */
+ /* Logical Minimum */ 0x15, 0x81, /* -127 */
+ /* Logical Maximum */ 0x25, 0x7F, /* +127 */
+ /* Report Size */ 0x75, 0x08, /* 8 */
+ /* Report Count */ 0x95, 0x03, /* 3 */
+ /* Input */ 0x81, 0x06, /* Data, Value, Relative, Bit field */
+ /* End Collection */ 0xC0,
+ /* End Collection */ 0xC0,
+};
+
+/* HID report descriptor (tablet). */
+/* NB: The layout is far from random. Having the buttons and Z axis grouped
+ * together avoids alignment issues. Also, if X/Y is reported first, followed
+ * by buttons/Z, Windows gets phantom Z movement. That is likely a bug in Windows
+ * as OS X shows no such problem. When X/Y is reported last, Windows behaves
+ * properly.
+ */
+
+static const uint8_t g_UsbHidTReportDesc[] =
+{
+ /* Usage Page */ 0x05, 0x01, /* Generic Desktop */
+ /* Usage */ 0x09, 0x02, /* Mouse */
+ /* Collection */ 0xA1, 0x01, /* Application */
+ /* Usage */ 0x09, 0x01, /* Pointer */
+ /* Collection */ 0xA1, 0x00, /* Physical */
+ /* Usage Page */ 0x05, 0x09, /* Button */
+ /* Usage Minimum */ 0x19, 0x01, /* Button 1 */
+ /* Usage Maximum */ 0x29, 0x05, /* Button 5 */
+ /* Logical Minimum */ 0x15, 0x00, /* 0 */
+ /* Logical Maximum */ 0x25, 0x01, /* 1 */
+ /* Report Count */ 0x95, 0x05, /* 5 */
+ /* Report Size */ 0x75, 0x01, /* 1 */
+ /* Input */ 0x81, 0x02, /* Data, Value, Absolute, Bit field */
+ /* Report Count */ 0x95, 0x01, /* 1 */
+ /* Report Size */ 0x75, 0x03, /* 3 (padding bits) */
+ /* Input */ 0x81, 0x03, /* Constant, Value, Absolute, Bit field */
+ /* Usage Page */ 0x05, 0x01, /* Generic Desktop */
+ /* Usage */ 0x09, 0x38, /* Z (wheel) */
+ /* Logical Minimum */ 0x15, 0x81, /* -127 */
+ /* Logical Maximum */ 0x25, 0x7F, /* +127 */
+ /* Report Size */ 0x75, 0x08, /* 8 */
+ /* Report Count */ 0x95, 0x01, /* 1 */
+ /* Input */ 0x81, 0x06, /* Data, Value, Relative, Bit field */
+ /* Usage Page */ 0x05, 0x0C, /* Consumer Devices */
+ /* Usage */ 0x0A, 0x38, 0x02,/* AC Pan (horizontal wheel) */
+ /* Report Count */ 0x95, 0x01, /* 1 */
+ /* Input */ 0x81, 0x06, /* Data, Value, Relative, Bit field */
+ /* Report Size */ 0x75, 0x08, /* 8 (padding byte) */
+ /* Report Count */ 0x95, 0x01, /* 1 */
+ /* Input */ 0x81, 0x03, /* Constant, Value, Absolute, Bit field */
+ /* Usage Page */ 0x05, 0x01, /* Generic Desktop */
+ /* Usage */ 0x09, 0x30, /* X */
+ /* Usage */ 0x09, 0x31, /* Y */
+ /* Logical Minimum */ 0x15, 0x00, /* 0 */
+ /* Logical Maximum */ 0x26, 0xFF,0x7F,/* 0x7fff */
+ /* Physical Minimum */ 0x35, 0x00, /* 0 */
+ /* Physical Maximum */ 0x46, 0xFF,0x7F,/* 0x7fff */
+ /* Report Size */ 0x75, 0x10, /* 16 */
+ /* Report Count */ 0x95, 0x02, /* 2 */
+ /* Input */ 0x81, 0x02, /* Data, Value, Absolute, Bit field */
+ /* End Collection */ 0xC0,
+ /* End Collection */ 0xC0,
+};
+
+/*
+ * Multi-touch device implementation based on "Windows Pointer Device Data Delivery Protocol"
+ * specification.
+ */
+
+#define REPORTID_TOUCH_POINTER 1
+#define REPORTID_TOUCH_EVENT 2
+#define REPORTID_TOUCH_MAX_COUNT 3
+#define REPORTID_TOUCH_QABLOB 4
+#define REPORTID_TOUCH_DEVCONFIG 5
+
+static const uint8_t g_UsbHidMTReportDesc[] =
+{
+/* Usage Page (Digitizer) */ 0x05, 0x0D,
+/* Usage (Touch Screen) */ 0x09, 0x04,
+/* Collection (Application) */ 0xA1, 0x01,
+/* Report ID */ 0x85, REPORTID_TOUCH_EVENT,
+/* Usage Page (Digitizer) */ 0x05, 0x0D,
+/* Usage (Contact count) */ 0x09, 0x54,
+/* Report Size (8) */ 0x75, 0x08,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (12) */ 0x25, 0x0C,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+
+/* MT_CONTACTS_PER_REPORT structs u8TipSwitch, u8ContactIdentifier, u16X, u16Y */
+/* 1 of 5 */
+/* Usage (Finger) */ 0x09, 0x22,
+/* Collection (Logical) */ 0xA1, 0x02,
+/* Usage (Tip Switch) */ 0x09, 0x42,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Size (1) */ 0x75, 0x01,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+
+/* Usage (In Range) */ 0x09, 0x32,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Size (1) */ 0x75, 0x01,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+
+/* Report Count (6) */ 0x95, 0x06,
+/* Input (Cnst,Var) */ 0x81, 0x03,
+
+/* Report Size (8) */ 0x75, 0x08,
+/* Usage (Contact identifier) */ 0x09, 0x51,
+/* Report Count (1) */ 0x95, 0x01,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (32) */ 0x25, 0x20,
+/* Input (Var) */ 0x81, 0x02,
+
+/* Usage Page (Generic Desktop) */ 0x05, 0x01,
+/* Logical Maximum (32K) */ 0x26, 0xFF, 0x7F,
+/* Report Size (16) */ 0x75, 0x10,
+/* Usage (X) */ 0x09, 0x30,
+/* Input (Var) */ 0x81, 0x02,
+
+/* Usage (Y) */ 0x09, 0x31,
+/* Input (Var) */ 0x81, 0x02,
+/* End Collection */ 0xC0,
+/* 2 of 5 */
+/* Usage Page (Digitizer) */ 0x05, 0x0D,
+/* Usage (Finger) */ 0x09, 0x22,
+/* Collection (Logical) */ 0xA1, 0x02,
+/* Usage (Tip Switch) */ 0x09, 0x42,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Size (1) */ 0x75, 0x01,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage (In Range) */ 0x09, 0x32,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Size (1) */ 0x75, 0x01,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+/* Report Count (6) */ 0x95, 0x06,
+/* Input (Cnst,Var) */ 0x81, 0x03,
+/* Report Size (8) */ 0x75, 0x08,
+/* Usage (Contact identifier) */ 0x09, 0x51,
+/* Report Count (1) */ 0x95, 0x01,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (32) */ 0x25, 0x20,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage Page (Generic Desktop) */ 0x05, 0x01,
+/* Logical Maximum (32K) */ 0x26, 0xFF, 0x7F,
+/* Report Size (16) */ 0x75, 0x10,
+/* Usage (X) */ 0x09, 0x30,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage (Y) */ 0x09, 0x31,
+/* Input (Var) */ 0x81, 0x02,
+/* End Collection */ 0xC0,
+/* 3 of 5 */
+/* Usage Page (Digitizer) */ 0x05, 0x0D,
+/* Usage (Finger) */ 0x09, 0x22,
+/* Collection (Logical) */ 0xA1, 0x02,
+/* Usage (Tip Switch) */ 0x09, 0x42,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Size (1) */ 0x75, 0x01,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage (In Range) */ 0x09, 0x32,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Size (1) */ 0x75, 0x01,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+/* Report Count (6) */ 0x95, 0x06,
+/* Input (Cnst,Var) */ 0x81, 0x03,
+/* Report Size (8) */ 0x75, 0x08,
+/* Usage (Contact identifier) */ 0x09, 0x51,
+/* Report Count (1) */ 0x95, 0x01,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (32) */ 0x25, 0x20,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage Page (Generic Desktop) */ 0x05, 0x01,
+/* Logical Maximum (32K) */ 0x26, 0xFF, 0x7F,
+/* Report Size (16) */ 0x75, 0x10,
+/* Usage (X) */ 0x09, 0x30,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage (Y) */ 0x09, 0x31,
+/* Input (Var) */ 0x81, 0x02,
+/* End Collection */ 0xC0,
+/* 4 of 5 */
+/* Usage Page (Digitizer) */ 0x05, 0x0D,
+/* Usage (Finger) */ 0x09, 0x22,
+/* Collection (Logical) */ 0xA1, 0x02,
+/* Usage (Tip Switch) */ 0x09, 0x42,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Size (1) */ 0x75, 0x01,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage (In Range) */ 0x09, 0x32,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Size (1) */ 0x75, 0x01,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+/* Report Count (6) */ 0x95, 0x06,
+/* Input (Cnst,Var) */ 0x81, 0x03,
+/* Report Size (8) */ 0x75, 0x08,
+/* Usage (Contact identifier) */ 0x09, 0x51,
+/* Report Count (1) */ 0x95, 0x01,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (32) */ 0x25, 0x20,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage Page (Generic Desktop) */ 0x05, 0x01,
+/* Logical Maximum (32K) */ 0x26, 0xFF, 0x7F,
+/* Report Size (16) */ 0x75, 0x10,
+/* Usage (X) */ 0x09, 0x30,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage (Y) */ 0x09, 0x31,
+/* Input (Var) */ 0x81, 0x02,
+/* End Collection */ 0xC0,
+/* 5 of 5 */
+/* Usage Page (Digitizer) */ 0x05, 0x0D,
+/* Usage (Finger) */ 0x09, 0x22,
+/* Collection (Logical) */ 0xA1, 0x02,
+/* Usage (Tip Switch) */ 0x09, 0x42,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Size (1) */ 0x75, 0x01,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage (In Range) */ 0x09, 0x32,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Size (1) */ 0x75, 0x01,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+/* Report Count (6) */ 0x95, 0x06,
+/* Input (Cnst,Var) */ 0x81, 0x03,
+/* Report Size (8) */ 0x75, 0x08,
+/* Usage (Contact identifier) */ 0x09, 0x51,
+/* Report Count (1) */ 0x95, 0x01,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (32) */ 0x25, 0x20,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage Page (Generic Desktop) */ 0x05, 0x01,
+/* Logical Maximum (32K) */ 0x26, 0xFF, 0x7F,
+/* Report Size (16) */ 0x75, 0x10,
+/* Usage (X) */ 0x09, 0x30,
+/* Input (Var) */ 0x81, 0x02,
+/* Usage (Y) */ 0x09, 0x31,
+/* Input (Var) */ 0x81, 0x02,
+/* End Collection */ 0xC0,
+
+/* Note: "Scan time" usage is required for all touch devices (in 100microseconds units). */
+/* Usage Page (Digitizer) */ 0x05, 0x0D,
+/* Logical Minimum (0) */ 0x17, 0x00, 0x00, 0x00, 0x00,
+/* Logical Maximum (2147483647) */ 0x27, 0xFF, 0xFF, 0xFF, 0x7F,
+/* Report Size (32) */ 0x75, 0x20,
+/* Report Count (1) */ 0x95, 0x01,
+/* Unit Exponent (0) */ 0x55, 0x00,
+/* Unit (None) */ 0x65, 0x00,
+/* Usage (Scan time) */ 0x09, 0x56,
+/* Input (Var) */ 0x81, 0x02,
+
+/* Report ID */ 0x85, REPORTID_TOUCH_MAX_COUNT,
+/* Usage (Contact count maximum) */ 0x09, 0x55,
+/* Usage (Device identifier) */ 0x09, 0x53,
+/* Report Size (8) */ 0x75, 0x08,
+/* Report Count (2) */ 0x95, 0x02,
+/* Logical Maximum (255) */ 0x26, 0xFF, 0x00,
+/* Feature (Var) */ 0xB1, 0x02,
+
+/* Usage Page (Vendor-Defined 1) */ 0x06, 0x00, 0xFF,
+/* Usage (QA blob) */ 0x09, 0xC5,
+/* Report ID */ 0x85, REPORTID_TOUCH_QABLOB,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (255) */ 0x26, 0xFF, 0x00,
+/* Report Size (8) */ 0x75, 0x08,
+/* Report Count (256) */ 0x96, 0x00, 0x01,
+/* Feature (Var) */ 0xB1, 0x02,
+/* End Collection */ 0xC0,
+
+/* Note: the pointer report is required by specification:
+ * "The report descriptor for a multiple input device must include at least
+ * one top-level collection for the primary device and a separate top-level
+ * collection for the mouse."
+ */
+/* Usage Page (Generic Desktop) */ 0x05, 0x01,
+/* Usage (Pointer) */ 0x09, 0x01,
+/* Collection (Application) */ 0xA1, 0x01,
+/* Report ID */ 0x85, REPORTID_TOUCH_POINTER,
+/* Usage (Pointer) */ 0x09, 0x01,
+/* Collection (Logical) */ 0xA1, 0x02,
+/* Usage Page (Button) */ 0x05, 0x09,
+/* Usage Minimum (Button 1) */ 0x19, 0x01,
+/* Usage Maximum (Button 2) */ 0x29, 0x02,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Count (2) */ 0x95, 0x02,
+/* Report Size (1) */ 0x75, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+/* Report Count (1) */ 0x95, 0x01,
+/* Report Size (6) */ 0x75, 0x06,
+/* Input (Cnst,Ary,Abs) */ 0x81, 0x01,
+/* Usage Page (Generic Desktop) */ 0x05, 0x01,
+/* Usage (X) */ 0x09, 0x30,
+/* Usage (Y) */ 0x09, 0x31,
+/* Logical Minimum (0) */ 0x16, 0x00, 0x00,
+/* Logical Maximum (32K) */ 0x26, 0xFF, 0x7F,
+/* Physical Minimum (0) */ 0x36, 0x00, 0x00,
+/* Physical Maximum (32K) */ 0x46, 0xFF, 0x7F,
+/* Unit (None) */ 0x66, 0x00, 0x00,
+/* Report Size (16) */ 0x75, 0x10,
+/* Report Count (2) */ 0x95, 0x02,
+/* Input (Var) */ 0x81, 0x02,
+/* End Collection */ 0xC0,
+/* End Collection */ 0xC0,
+
+/* Usage Page (Digitizer) */ 0x05, 0x0D,
+/* Usage (Device configuration) */ 0x09, 0x0E,
+/* Collection (Application) */ 0xA1, 0x01,
+/* Report ID */ 0x85, REPORTID_TOUCH_DEVCONFIG,
+/* Usage (Device settings) */ 0x09, 0x23,
+/* Collection (Logical) */ 0xA1, 0x02,
+/* Usage (Device mode) */ 0x09, 0x52,
+/* Usage (Device identifier) */ 0x09, 0x53,
+/* Logical Minimum (0) */ 0x15, 0x00,
+/* Logical Maximum (10) */ 0x25, 0x0A,
+/* Report Size (8) */ 0x75, 0x08,
+/* Report Count (2) */ 0x95, 0x02,
+/* Feature (Var) */ 0xB1, 0x02,
+/* End Collection */ 0xC0,
+/* End Collection */ 0xC0
+};
+
+/** @todo Do these really have to all be duplicated three times? */
+/* Additional HID class interface descriptor. */
+static const uint8_t g_UsbHidMIfHidDesc[] =
+{
+ /* .bLength = */ 0x09,
+ /* .bDescriptorType = */ 0x21, /* HID */
+ /* .bcdHID = */ 0x10, 0x01, /* 1.1 */
+ /* .bCountryCode = */ 0,
+ /* .bNumDescriptors = */ 1,
+ /* .bDescriptorType = */ 0x22, /* Report */
+ /* .wDescriptorLength = */ sizeof(g_UsbHidMReportDesc), 0x00
+};
+
+/* Additional HID class interface descriptor. */
+static const uint8_t g_UsbHidTIfHidDesc[] =
+{
+ /* .bLength = */ 0x09,
+ /* .bDescriptorType = */ 0x21, /* HID */
+ /* .bcdHID = */ 0x10, 0x01, /* 1.1 */
+ /* .bCountryCode = */ 0,
+ /* .bNumDescriptors = */ 1,
+ /* .bDescriptorType = */ 0x22, /* Report */
+ /* .wDescriptorLength = */ sizeof(g_UsbHidTReportDesc), 0x00
+};
+
+/* Additional HID class interface descriptor. */
+static const uint8_t g_UsbHidMTIfHidDesc[] =
+{
+ /* .bLength = */ 0x09,
+ /* .bDescriptorType = */ 0x21, /* HID */
+ /* .bcdHID = */ 0x10, 0x02, /* 2.1 */
+ /* .bCountryCode = */ 0,
+ /* .bNumDescriptors = */ 1,
+ /* .bDescriptorType = */ 0x22, /* Report */
+ /* .wDescriptorLength = */ (uint8_t)(sizeof(g_UsbHidMTReportDesc) & 0xFF),
+ (uint8_t)((sizeof(g_UsbHidMTReportDesc) >> 8) & 0xFF)
+};
+
+static const VUSBDESCINTERFACEEX g_UsbHidMInterfaceDesc =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 0,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 1,
+ /* .bInterfaceClass = */ 3 /* HID */,
+ /* .bInterfaceSubClass = */ 1 /* Boot Interface */,
+ /* .bInterfaceProtocol = */ 2 /* Mouse */,
+ /* .iInterface = */ 0
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ &g_UsbHidMIfHidDesc,
+ /* .cbClass = */ sizeof(g_UsbHidMIfHidDesc),
+ &g_aUsbHidMEndpointDescs[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+static const VUSBDESCINTERFACEEX g_UsbHidTInterfaceDesc =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 0,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 1,
+ /* .bInterfaceClass = */ 3 /* HID */,
+ /* .bInterfaceSubClass = */ 0 /* No subclass - no boot interface. */,
+ /* .bInterfaceProtocol = */ 0 /* No protocol - no boot interface. */,
+ /* .iInterface = */ 0
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ &g_UsbHidTIfHidDesc,
+ /* .cbClass = */ sizeof(g_UsbHidTIfHidDesc),
+ &g_aUsbHidTEndpointDescs[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+static const VUSBDESCINTERFACEEX g_UsbHidMTInterfaceDesc =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 0,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 1,
+ /* .bInterfaceClass = */ 3 /* HID */,
+ /* .bInterfaceSubClass = */ 0 /* No subclass - no boot interface. */,
+ /* .bInterfaceProtocol = */ 0 /* No protocol - no boot interface. */,
+ /* .iInterface = */ 0
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ &g_UsbHidMTIfHidDesc,
+ /* .cbClass = */ sizeof(g_UsbHidMTIfHidDesc),
+ &g_aUsbHidMTEndpointDescs[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+static const VUSBINTERFACE g_aUsbHidMInterfaces[] =
+{
+ { &g_UsbHidMInterfaceDesc, /* .cSettings = */ 1 },
+};
+
+static const VUSBINTERFACE g_aUsbHidTInterfaces[] =
+{
+ { &g_UsbHidTInterfaceDesc, /* .cSettings = */ 1 },
+};
+
+static const VUSBINTERFACE g_aUsbHidMTInterfaces[] =
+{
+ { &g_UsbHidMTInterfaceDesc, /* .cSettings = */ 1 },
+};
+
+static const VUSBDESCCONFIGEX g_UsbHidMConfigDesc =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbHidMInterfaces),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7),
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbHidMInterfaces[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCCONFIGEX g_UsbHidTConfigDesc =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbHidTInterfaces),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7),
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbHidTInterfaces[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCCONFIGEX g_UsbHidMTConfigDesc =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbHidMTInterfaces),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7),
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbHidMTInterfaces[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCDEVICE g_UsbHidMDeviceDesc =
+{
+ /* .bLength = */ sizeof(g_UsbHidMDeviceDesc),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x110, /* 1.1 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 8,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBHID_PID_MOUSE,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBHID_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBHID_STR_ID_PRODUCT_M,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDESCDEVICE g_UsbHidTDeviceDesc =
+{
+ /* .bLength = */ sizeof(g_UsbHidTDeviceDesc),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x110, /* 1.1 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 8,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBHID_PID_TABLET,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBHID_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBHID_STR_ID_PRODUCT_T,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDESCDEVICE g_UsbHidMTDeviceDesc =
+{
+ /* .bLength = */ sizeof(g_UsbHidMTDeviceDesc),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x110, /* 1.1 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 8,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBHID_PID_MULTI_TOUCH,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBHID_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBHID_STR_ID_PRODUCT_MT,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const PDMUSBDESCCACHE g_UsbHidMDescCache =
+{
+ /* .pDevice = */ &g_UsbHidMDeviceDesc,
+ /* .paConfigs = */ &g_UsbHidMConfigDesc,
+ /* .paLanguages = */ g_aUsbHidLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbHidLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbHidTDescCache =
+{
+ /* .pDevice = */ &g_UsbHidTDeviceDesc,
+ /* .paConfigs = */ &g_UsbHidTConfigDesc,
+ /* .paLanguages = */ g_aUsbHidLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbHidLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbHidMTDescCache =
+{
+ /* .pDevice = */ &g_UsbHidMTDeviceDesc,
+ /* .paConfigs = */ &g_UsbHidMTConfigDesc,
+ /* .paLanguages = */ g_aUsbHidLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbHidLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Initializes an URB queue.
+ *
+ * @param pQueue The URB queue.
+ */
+static void usbHidQueueInit(PUSBHIDURBQUEUE pQueue)
+{
+ pQueue->pHead = NULL;
+ pQueue->ppTail = &pQueue->pHead;
+}
+
+
+
+/**
+ * Inserts an URB at the end of the queue.
+ *
+ * @param pQueue The URB queue.
+ * @param pUrb The URB to insert.
+ */
+DECLINLINE(void) usbHidQueueAddTail(PUSBHIDURBQUEUE pQueue, PVUSBURB pUrb)
+{
+ pUrb->Dev.pNext = NULL;
+ *pQueue->ppTail = pUrb;
+ pQueue->ppTail = &pUrb->Dev.pNext;
+}
+
+
+/**
+ * Unlinks the head of the queue and returns it.
+ *
+ * @returns The head entry.
+ * @param pQueue The URB queue.
+ */
+DECLINLINE(PVUSBURB) usbHidQueueRemoveHead(PUSBHIDURBQUEUE pQueue)
+{
+ PVUSBURB pUrb = pQueue->pHead;
+ if (pUrb)
+ {
+ PVUSBURB pNext = pUrb->Dev.pNext;
+ pQueue->pHead = pNext;
+ if (!pNext)
+ pQueue->ppTail = &pQueue->pHead;
+ else
+ pUrb->Dev.pNext = NULL;
+ }
+ return pUrb;
+}
+
+
+/**
+ * Removes an URB from anywhere in the queue.
+ *
+ * @returns true if found, false if not.
+ * @param pQueue The URB queue.
+ * @param pUrb The URB to remove.
+ */
+DECLINLINE(bool) usbHidQueueRemove(PUSBHIDURBQUEUE pQueue, PVUSBURB pUrb)
+{
+ PVUSBURB pCur = pQueue->pHead;
+ if (pCur == pUrb)
+ {
+ pQueue->pHead = pUrb->Dev.pNext;
+ if (!pUrb->Dev.pNext)
+ pQueue->ppTail = &pQueue->pHead;
+ }
+ else
+ {
+ while (pCur)
+ {
+ if (pCur->Dev.pNext == pUrb)
+ {
+ pCur->Dev.pNext = pUrb->Dev.pNext;
+ break;
+ }
+ pCur = pCur->Dev.pNext;
+ }
+ if (!pCur)
+ return false;
+ if (!pUrb->Dev.pNext)
+ pQueue->ppTail = &pCur->Dev.pNext;
+ }
+ pUrb->Dev.pNext = NULL;
+ return true;
+}
+
+
+#if 0 /* unused */
+/**
+ * Checks if the queue is empty or not.
+ *
+ * @returns true if it is, false if it isn't.
+ * @param pQueue The URB queue.
+ */
+DECLINLINE(bool) usbHidQueueIsEmpty(PCUSBHIDURBQUEUE pQueue)
+{
+ return pQueue->pHead == NULL;
+}
+#endif /* unused */
+
+
+/**
+ * Links an URB into the done queue.
+ *
+ * @param pThis The HID instance.
+ * @param pUrb The URB.
+ */
+static void usbHidLinkDone(PUSBHID pThis, PVUSBURB pUrb)
+{
+ usbHidQueueAddTail(&pThis->DoneQueue, pUrb);
+
+ if (pThis->fHaveDoneQueueWaiter)
+ {
+ int rc = RTSemEventSignal(pThis->hEvtDoneQueue);
+ AssertRC(rc);
+ }
+}
+
+
+
+/**
+ * Completes the URB with a stalled state, halting the pipe.
+ */
+static int usbHidCompleteStall(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb, const char *pszWhy)
+{
+ LogRelFlow(("usbHidCompleteStall/#%u: pUrb=%p:%s: %s\n",
+ pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, pszWhy));
+
+ pUrb->enmStatus = VUSBSTATUS_STALL;
+
+ /** @todo figure out if the stall is global or pipe-specific or both. */
+ if (pEp)
+ pEp->fHalted = true;
+ else
+ {
+ pThis->aEps[0].fHalted = true;
+ pThis->aEps[1].fHalted = true;
+ }
+
+ usbHidLinkDone(pThis, pUrb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Completes the URB with a OK state.
+ */
+static int usbHidCompleteOk(PUSBHID pThis, PVUSBURB pUrb, size_t cbData)
+{
+ LogRelFlow(("usbHidCompleteOk/#%u: pUrb=%p:%s cbData=%#zx\n",
+ pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, cbData));
+
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->cbData = (uint32_t)cbData;
+
+ usbHidLinkDone(pThis, pUrb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reset worker for usbHidUsbReset, usbHidUsbSetConfiguration and
+ * usbHidHandleDefaultPipe.
+ *
+ * @returns VBox status code.
+ * @param pThis The HID instance.
+ * @param pUrb Set when usbHidHandleDefaultPipe is the
+ * caller.
+ * @param fSetConfig Set when usbHidUsbSetConfiguration is the
+ * caller.
+ */
+static int usbHidResetWorker(PUSBHID pThis, PVUSBURB pUrb, bool fSetConfig)
+{
+ /*
+ * Wait for the any command currently executing to complete before
+ * resetting. (We cannot cancel its execution.) How we do this depends
+ * on the reset method.
+ */
+
+ /*
+ * Reset the device state.
+ */
+ pThis->enmState = USBHIDREQSTATE_READY;
+ pThis->fHasPendingChanges = false;
+ pThis->fTouchStateUpdated = false;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aEps); i++)
+ pThis->aEps[i].fHalted = false;
+
+ if (!pUrb && !fSetConfig) /* (only device reset) */
+ pThis->bConfigurationValue = 0; /* default */
+
+ /*
+ * Ditch all pending URBs.
+ */
+ PVUSBURB pCurUrb;
+ while ((pCurUrb = usbHidQueueRemoveHead(&pThis->ToHostQueue)) != NULL)
+ {
+ pCurUrb->enmStatus = VUSBSTATUS_CRC;
+ usbHidLinkDone(pThis, pCurUrb);
+ }
+
+ if (pUrb)
+ return usbHidCompleteOk(pThis, pUrb, 0);
+ return VINF_SUCCESS;
+}
+
+static int8_t clamp_i8(int32_t val)
+{
+ if (val > 127) {
+ val = 127;
+ } else if (val < -127) {
+ val = -127;
+ }
+ return val;
+}
+
+/**
+ * Create a USB HID report report based on the currently accumulated data.
+ */
+static size_t usbHidFillReport(PUSBHIDTM_REPORT pReport,
+ PUSBHIDM_ACCUM pAccumulated, USBHIDMODE enmMode)
+{
+ size_t cbCopy;
+
+ switch (enmMode)
+ {
+ case USBHIDMODE_ABSOLUTE:
+ pReport->t.fButtons = pAccumulated->u.Absolute.fButtons;
+ pReport->t.dz = clamp_i8(pAccumulated->u.Absolute.dz);
+ pReport->t.dw = clamp_i8(pAccumulated->u.Absolute.dw);
+ pReport->t.padding = 0;
+ pReport->t.x = pAccumulated->u.Absolute.x;
+ pReport->t.y = pAccumulated->u.Absolute.y;
+
+ cbCopy = sizeof(pReport->t);
+ LogRel3(("Abs event, x=%d, y=%d, fButtons=%02x, report size %d\n",
+ pReport->t.x, pReport->t.y, pReport->t.fButtons,
+ cbCopy));
+ break;
+ case USBHIDMODE_RELATIVE:
+ pReport->m.fButtons = pAccumulated->u.Relative.fButtons;
+ pReport->m.dx = clamp_i8(pAccumulated->u.Relative.dx);
+ pReport->m.dy = clamp_i8(pAccumulated->u.Relative.dy);
+ pReport->m.dz = clamp_i8(pAccumulated->u.Relative.dz);
+
+ cbCopy = sizeof(pReport->m);
+ LogRel3(("Rel event, dx=%d, dy=%d, dz=%d, fButtons=%02x, report size %d\n",
+ pReport->m.dx, pReport->m.dy, pReport->m.dz,
+ pReport->m.fButtons, cbCopy));
+ break;
+ default:
+ AssertFailed(); /* Unexpected here. */
+ cbCopy = 0;
+ break;
+ }
+
+ /* Clear the accumulated movement. */
+ RT_ZERO(*pAccumulated);
+
+ return cbCopy;
+}
+
+DECLINLINE(MTCONTACT *) usbHidFindMTContact(MTCONTACT *paContacts, size_t cContacts,
+ uint8_t u8Mask, uint8_t u8Value)
+{
+ size_t i;
+ for (i = 0; i < cContacts; i++)
+ {
+ if ((paContacts[i].status & u8Mask) == u8Value)
+ {
+ return &paContacts[i];
+ }
+ }
+
+ return NULL;
+}
+
+static int usbHidSendMultiTouchReport(PUSBHID pThis, PVUSBURB pUrb)
+{
+ uint8_t i;
+ MTCONTACT *pRepContact;
+ MTCONTACT *pCurContact;
+
+ /* Number of contacts to be reported. In hybrid mode the first report contains
+ * total number of contacts and subsequent reports contain 0.
+ */
+ uint8_t cContacts = 0;
+
+ Assert(pThis->fHasPendingChanges);
+
+ if (!pThis->fTouchReporting)
+ {
+ pThis->fTouchReporting = true;
+ pThis->fTouchStateUpdated = false;
+
+ /* Update the reporting state with the new current state.
+ * Also mark all active contacts in reporting state as dirty,
+ * that is they must be reported to the guest.
+ */
+ for (i = 0; i < MT_CONTACT_MAX_COUNT; i++)
+ {
+ pRepContact = &pThis->aReportingContactState[i];
+ pCurContact = &pThis->aCurrentContactState[i];
+
+ if (pCurContact->status & MT_CONTACT_S_ACTIVE)
+ {
+ if (pCurContact->status & MT_CONTACT_S_REUSED)
+ {
+ pCurContact->status &= ~MT_CONTACT_S_REUSED;
+
+ /* Keep x,y. Will report lost contact at this point. */
+ pRepContact->id = pCurContact->oldId;
+ pRepContact->flags = 0;
+ pRepContact->status = MT_CONTACT_S_REUSED;
+ }
+ else if (pThis->aCurrentContactState[i].status & MT_CONTACT_S_CANCELLED)
+ {
+ pCurContact->status &= ~(MT_CONTACT_S_CANCELLED | MT_CONTACT_S_ACTIVE);
+
+ /* Keep x,y. Will report lost contact at this point. */
+ pRepContact->id = pCurContact->id;
+ pRepContact->flags = 0;
+ pRepContact->status = 0;
+ }
+ else
+ {
+ if (pCurContact->flags == 0)
+ {
+ pCurContact->status &= ~MT_CONTACT_S_ACTIVE; /* Contact disapeared. */
+ }
+
+ pRepContact->x = pCurContact->x;
+ pRepContact->y = pCurContact->y;
+ pRepContact->id = pCurContact->id;
+ pRepContact->flags = pCurContact->flags;
+ pRepContact->status = 0;
+ }
+
+ cContacts++;
+
+ pRepContact->status |= MT_CONTACT_S_DIRTY;
+ }
+ else
+ {
+ pRepContact->status = 0;
+ }
+ }
+ }
+
+ /* Report current state. */
+ USBHIDMT_REPORT *p = (USBHIDMT_REPORT *)&pUrb->abData[0];
+ RT_ZERO(*p);
+
+ p->idReport = REPORTID_TOUCH_EVENT;
+ p->cContacts = cContacts;
+
+ uint8_t iReportedContact;
+ for (iReportedContact = 0; iReportedContact < MT_CONTACTS_PER_REPORT; iReportedContact++)
+ {
+ /* Find the next not reported contact. */
+ pRepContact = usbHidFindMTContact(pThis->aReportingContactState, RT_ELEMENTS(pThis->aReportingContactState),
+ MT_CONTACT_S_DIRTY, MT_CONTACT_S_DIRTY);
+
+ if (!pRepContact)
+ {
+ LogRel3(("usbHid: no more touch contacts to report\n"));
+ break;
+ }
+
+ if (pRepContact->status & MT_CONTACT_S_REUSED)
+ {
+ /* Do not clear DIRTY flag for contacts which were reused.
+ * Because two reports must be generated:
+ * one for old contact off, and the second for new contact on.
+ */
+ pRepContact->status &= ~MT_CONTACT_S_REUSED;
+ }
+ else
+ {
+ pRepContact->status &= ~MT_CONTACT_S_DIRTY;
+ }
+
+ p->aContacts[iReportedContact].fContact = pRepContact->flags;
+ p->aContacts[iReportedContact].cContact = pRepContact->id;
+ p->aContacts[iReportedContact].x = pRepContact->x >> pThis->u8CoordShift;
+ p->aContacts[iReportedContact].y = pRepContact->y >> pThis->u8CoordShift;
+ }
+
+ p->u32ScanTime = pThis->u32LastTouchScanTime * 10;
+
+ Assert(iReportedContact > 0);
+
+ /* Reset TouchReporting if all contacts reported. */
+ pRepContact = usbHidFindMTContact(pThis->aReportingContactState, RT_ELEMENTS(pThis->aReportingContactState),
+ MT_CONTACT_S_DIRTY, MT_CONTACT_S_DIRTY);
+
+ if (!pRepContact)
+ {
+ LogRel3(("usbHid: all touch contacts reported\n"));
+ pThis->fTouchReporting = false;
+ pThis->fHasPendingChanges = pThis->fTouchStateUpdated;
+ }
+ else
+ {
+ pThis->fHasPendingChanges = true;
+ }
+
+ LogRel3(("usbHid: reporting touch contact:\n%.*Rhxd\n", sizeof(USBHIDMT_REPORT), p));
+ return usbHidCompleteOk(pThis, pUrb, sizeof(USBHIDMT_REPORT));
+}
+
+/**
+ * Sends a state report to the host if there is a pending URB.
+ */
+static int usbHidSendReport(PUSBHID pThis)
+{
+ PVUSBURB pUrb = usbHidQueueRemoveHead(&pThis->ToHostQueue);
+
+ if (pThis->enmMode == USBHIDMODE_MULTI_TOUCH)
+ {
+ /* This device uses a different reporting method and fHasPendingChanges maintenance. */
+ if (pUrb)
+ return usbHidSendMultiTouchReport(pThis, pUrb);
+ return VINF_SUCCESS;
+ }
+
+ if (pUrb)
+ {
+ PUSBHIDTM_REPORT pReport = (PUSBHIDTM_REPORT)&pUrb->abData[0];
+ size_t cbCopy;
+
+ cbCopy = usbHidFillReport(pReport, &pThis->PtrDelta, pThis->enmMode);
+ pThis->fHasPendingChanges = false;
+ return usbHidCompleteOk(pThis, pUrb, cbCopy);
+ }
+ else
+ {
+ LogRelFlow(("No available URB for USB mouse\n"));
+ pThis->fHasPendingChanges = true;
+ }
+ return VINF_EOF;
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) usbHidMouseQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PUSBHID pThis = RT_FROM_MEMBER(pInterface, USBHID, Lun0.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->Lun0.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSEPORT, &pThis->Lun0.IPort);
+ return NULL;
+}
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEvent}
+ */
+static DECLCALLBACK(int) usbHidMousePutEvent(PPDMIMOUSEPORT pInterface, int32_t dx, int32_t dy,
+ int32_t dz, int32_t dw, uint32_t fButtons)
+{
+ RT_NOREF1(dw);
+ PUSBHID pThis = RT_FROM_MEMBER(pInterface, USBHID, Lun0.IPort);
+ RTCritSectEnter(&pThis->CritSect);
+
+ /* Accumulate movement - the events from the front end may arrive
+ * at a much higher rate than USB can handle.
+ */
+ pThis->PtrDelta.u.Relative.fButtons = fButtons;
+ pThis->PtrDelta.u.Relative.dx += dx;
+ pThis->PtrDelta.u.Relative.dy += dy;
+ pThis->PtrDelta.u.Relative.dz -= dz; /* Inverted! */
+
+ /* Send a report if possible. */
+ usbHidSendReport(pThis);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventAbs}
+ */
+static DECLCALLBACK(int) usbHidMousePutEventAbs(PPDMIMOUSEPORT pInterface,
+ uint32_t x, uint32_t y,
+ int32_t dz, int32_t dw,
+ uint32_t fButtons)
+{
+ PUSBHID pThis = RT_FROM_MEMBER(pInterface, USBHID, Lun0.IPort);
+ RTCritSectEnter(&pThis->CritSect);
+
+ Assert(pThis->enmMode == USBHIDMODE_ABSOLUTE);
+
+ /* Accumulate movement - the events from the front end may arrive
+ * at a much higher rate than USB can handle. Probably not a real issue
+ * when only the Z axis is relative (X/Y movement isn't technically
+ * accumulated and only the last value is used).
+ */
+ pThis->PtrDelta.u.Absolute.fButtons = fButtons;
+ pThis->PtrDelta.u.Absolute.x = x >> pThis->u8CoordShift;
+ pThis->PtrDelta.u.Absolute.y = y >> pThis->u8CoordShift;
+ pThis->PtrDelta.u.Absolute.dz -= dz; /* Inverted! */
+ pThis->PtrDelta.u.Absolute.dw -= dw; /* Inverted! */
+
+ /* Send a report if possible. */
+ usbHidSendReport(pThis);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventMultiTouch}
+ */
+static DECLCALLBACK(int) usbHidMousePutEventMultiTouch(PPDMIMOUSEPORT pInterface,
+ uint8_t cContacts,
+ const uint64_t *pau64Contacts,
+ uint32_t u32ScanTime)
+{
+ uint8_t i;
+ uint8_t j;
+
+ /* Make a copy of new contacts */
+ MTCONTACT *paNewContacts = (MTCONTACT *)RTMemTmpAlloc(sizeof(MTCONTACT) * cContacts);
+ if (!paNewContacts)
+ return VERR_NO_MEMORY;
+
+ for (i = 0; i < cContacts; i++)
+ {
+ uint32_t u32Lo = RT_LO_U32(pau64Contacts[i]);
+ uint32_t u32Hi = RT_HI_U32(pau64Contacts[i]);
+ paNewContacts[i].x = (uint16_t)u32Lo;
+ paNewContacts[i].y = (uint16_t)(u32Lo >> 16);
+ paNewContacts[i].id = RT_BYTE1(u32Hi);
+ paNewContacts[i].flags = RT_BYTE2(u32Hi) & (MT_CONTACT_F_IN_CONTACT | MT_CONTACT_F_IN_RANGE);
+ paNewContacts[i].status = MT_CONTACT_S_DIRTY;
+ paNewContacts[i].oldId = 0; /* Not used. */
+ if (paNewContacts[i].flags & MT_CONTACT_F_IN_CONTACT)
+ {
+ paNewContacts[i].flags |= MT_CONTACT_F_IN_RANGE;
+ }
+ }
+
+ PUSBHID pThis = RT_FROM_MEMBER(pInterface, USBHID, Lun0.IPort);
+ MTCONTACT *pCurContact = NULL;
+ MTCONTACT *pNewContact = NULL;
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ Assert(pThis->enmMode == USBHIDMODE_MULTI_TOUCH);
+
+ /* Maintain a state of all current contacts.
+ * Intr URBs will be completed according to the state.
+ */
+
+ /* Mark all existing contacts as dirty. */
+ for (i = 0; i < RT_ELEMENTS(pThis->aCurrentContactState); i++)
+ pThis->aCurrentContactState[i].status |= MT_CONTACT_S_DIRTY;
+
+ /* Update existing contacts and mark new contacts. */
+ for (i = 0; i < cContacts; i++)
+ {
+ pNewContact = &paNewContacts[i];
+
+ /* Find existing contact with the same id. */
+ pCurContact = NULL;
+ for (j = 0; j < RT_ELEMENTS(pThis->aCurrentContactState); j++)
+ {
+ if ( (pThis->aCurrentContactState[j].status & MT_CONTACT_S_ACTIVE) != 0
+ && pThis->aCurrentContactState[j].id == pNewContact->id)
+ {
+ pCurContact = &pThis->aCurrentContactState[j];
+ break;
+ }
+ }
+
+ if (pCurContact)
+ {
+ pNewContact->status &= ~MT_CONTACT_S_DIRTY;
+
+ pCurContact->x = pNewContact->x;
+ pCurContact->y = pNewContact->y;
+ if (pCurContact->flags == 0) /* Contact disappeared already. */
+ {
+ if ((pCurContact->status & MT_CONTACT_S_REUSED) == 0)
+ {
+ pCurContact->status |= MT_CONTACT_S_REUSED; /* Report to the guest that the contact not in touch. */
+ pCurContact->oldId = pCurContact->id;
+ }
+ }
+ pCurContact->flags = pNewContact->flags;
+ pCurContact->status &= ~MT_CONTACT_S_DIRTY;
+ }
+ }
+
+ /* Append new contacts (the dirty one in the paNewContacts). */
+ for (i = 0; i < cContacts; i++)
+ {
+ pNewContact = &paNewContacts[i];
+
+ if (pNewContact->status & MT_CONTACT_S_DIRTY)
+ {
+ /* It is a new contact, copy is to one of not ACTIVE or not updated existing contacts. */
+ pCurContact = usbHidFindMTContact(pThis->aCurrentContactState, RT_ELEMENTS(pThis->aCurrentContactState),
+ MT_CONTACT_S_ACTIVE, 0);
+
+ if (pCurContact)
+ {
+ *pCurContact = *pNewContact;
+ pCurContact->status = MT_CONTACT_S_ACTIVE; /* Reset status. */
+ }
+ else
+ {
+ /* Dirty existing contacts can be reused. */
+ pCurContact = usbHidFindMTContact(pThis->aCurrentContactState, RT_ELEMENTS(pThis->aCurrentContactState),
+ MT_CONTACT_S_ACTIVE | MT_CONTACT_S_DIRTY,
+ MT_CONTACT_S_ACTIVE | MT_CONTACT_S_DIRTY);
+
+ if (pCurContact)
+ {
+ pCurContact->x = pNewContact->x;
+ pCurContact->y = pNewContact->y;
+ if ((pCurContact->status & MT_CONTACT_S_REUSED) == 0)
+ {
+ pCurContact->status |= MT_CONTACT_S_REUSED; /* Report to the guest that the contact not in touch. */
+ pCurContact->oldId = pCurContact->id;
+ }
+ pCurContact->flags = pNewContact->flags;
+ pCurContact->status &= ~MT_CONTACT_S_DIRTY;
+ }
+ else
+ {
+ LogRel3(("usbHid: dropped new contact: %d,%d id %d flags %RX8 status %RX8 oldId %d\n",
+ pNewContact->x,
+ pNewContact->y,
+ pNewContact->id,
+ pNewContact->flags,
+ pNewContact->status,
+ pNewContact->oldId
+ ));
+ }
+ }
+ }
+ }
+
+ /* Mark still dirty existing contacts as cancelled, because a new set of contacts does not include them. */
+ for (i = 0; i < RT_ELEMENTS(pThis->aCurrentContactState); i++)
+ {
+ pCurContact = &pThis->aCurrentContactState[i];
+ if (pCurContact->status & MT_CONTACT_S_DIRTY)
+ {
+ pCurContact->status |= MT_CONTACT_S_CANCELLED;
+ pCurContact->status &= ~MT_CONTACT_S_DIRTY;
+ }
+ }
+
+ pThis->u32LastTouchScanTime = u32ScanTime;
+
+ LogRel3(("usbHid: scanTime (ms): %d\n", pThis->u32LastTouchScanTime));
+ for (i = 0; i < RT_ELEMENTS(pThis->aCurrentContactState); i++)
+ {
+ LogRel3(("usbHid: contact state[%d]: %d,%d id %d flags %RX8 status %RX8 oldId %d\n",
+ i,
+ pThis->aCurrentContactState[i].x,
+ pThis->aCurrentContactState[i].y,
+ pThis->aCurrentContactState[i].id,
+ pThis->aCurrentContactState[i].flags,
+ pThis->aCurrentContactState[i].status,
+ pThis->aCurrentContactState[i].oldId
+ ));
+ }
+
+ pThis->fTouchStateUpdated = true;
+ pThis->fHasPendingChanges = true;
+
+ /* Send a report if possible. */
+ usbHidSendReport(pThis);
+
+ RTCritSectLeave(&pThis->CritSect);
+
+ RTMemTmpFree(paNewContacts);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbReap}
+ */
+static DECLCALLBACK(PVUSBURB) usbHidUrbReap(PPDMUSBINS pUsbIns, RTMSINTERVAL cMillies)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+
+ LogFlowFunc(("pUsbIns=%p cMillies=%u\n", pUsbIns, cMillies));
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ PVUSBURB pUrb = usbHidQueueRemoveHead(&pThis->DoneQueue);
+ if (!pUrb && cMillies)
+ {
+ /* Wait */
+ pThis->fHaveDoneQueueWaiter = true;
+ RTCritSectLeave(&pThis->CritSect);
+
+ RTSemEventWait(pThis->hEvtDoneQueue, cMillies);
+
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->fHaveDoneQueueWaiter = false;
+
+ pUrb = usbHidQueueRemoveHead(&pThis->DoneQueue);
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+
+ if (pUrb)
+ LogRelFlow(("usbHidUrbReap/#%u: pUrb=%p:%s\n", pUsbIns->iInstance, pUrb,
+ pUrb->pszDesc));
+ return pUrb;
+}
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnWakeup}
+ */
+static DECLCALLBACK(int) usbHidWakeup(PPDMUSBINS pUsbIns)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+
+ return RTSemEventSignal(pThis->hEvtDoneQueue);
+}
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbCancel}
+ */
+static DECLCALLBACK(int) usbHidUrbCancel(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogRelFlow(("usbHidUrbCancel/#%u: pUrb=%p:%s\n", pUsbIns->iInstance, pUrb,
+ pUrb->pszDesc));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Remove the URB from the to-host queue and move it onto the done queue.
+ */
+ if (usbHidQueueRemove(&pThis->ToHostQueue, pUrb))
+ usbHidLinkDone(pThis, pUrb);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Handles request sent to the inbound (device to host) interrupt pipe. This is
+ * rather different from bulk requests because an interrupt read URB may complete
+ * after arbitrarily long time.
+ */
+static int usbHidHandleIntrDevToHost(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb)
+{
+ /*
+ * Stall the request if the pipe is halted.
+ */
+ if (RT_UNLIKELY(pEp->fHalted))
+ return usbHidCompleteStall(pThis, NULL, pUrb, "Halted pipe");
+
+ /*
+ * Deal with the URB according to the state.
+ */
+ switch (pThis->enmState)
+ {
+ /*
+ * We've data left to transfer to the host.
+ */
+ case USBHIDREQSTATE_DATA_TO_HOST:
+ {
+ AssertFailed();
+ LogRelFlow(("usbHidHandleIntrDevToHost: Entering STATUS\n"));
+ return usbHidCompleteOk(pThis, pUrb, 0);
+ }
+
+ /*
+ * Status transfer.
+ */
+ case USBHIDREQSTATE_STATUS:
+ {
+ AssertFailed();
+ LogRelFlow(("usbHidHandleIntrDevToHost: Entering READY\n"));
+ pThis->enmState = USBHIDREQSTATE_READY;
+ return usbHidCompleteOk(pThis, pUrb, 0);
+ }
+
+ case USBHIDREQSTATE_READY:
+ usbHidQueueAddTail(&pThis->ToHostQueue, pUrb);
+ LogRelFlow(("usbHidHandleIntrDevToHost: Added %p:%s to the queue\n",
+ pUrb, pUrb->pszDesc));
+ /* If a report is pending, send it right away. */
+ if (pThis->fHasPendingChanges)
+ usbHidSendReport(pThis);
+ return VINF_SUCCESS;
+
+ /*
+ * Bad states, stall.
+ */
+ default:
+ LogRelFlow(("usbHidHandleIntrDevToHost: enmState=%d cbData=%#x\n",
+ pThis->enmState, pUrb->cbData));
+ return usbHidCompleteStall(pThis, NULL, pUrb, "Really bad state (D2H)!");
+ }
+}
+
+#define GET_REPORT 0x01
+#define GET_IDLE 0x02
+#define GET_PROTOCOL 0x03
+#define SET_REPORT 0x09
+#define SET_IDLE 0x0A
+#define SET_PROTOCOL 0x0B
+
+static uint8_t const g_abQASampleBlob[256] =
+{
+ 0xfc, 0x28, 0xfe, 0x84, 0x40, 0xcb, 0x9a, 0x87,
+ 0x0d, 0xbe, 0x57, 0x3c, 0xb6, 0x70, 0x09, 0x88,
+ 0x07, 0x97, 0x2d, 0x2b, 0xe3, 0x38, 0x34, 0xb6,
+ 0x6c, 0xed, 0xb0, 0xf7, 0xe5, 0x9c, 0xf6, 0xc2,
+ 0x2e, 0x84, 0x1b, 0xe8, 0xb4, 0x51, 0x78, 0x43,
+ 0x1f, 0x28, 0x4b, 0x7c, 0x2d, 0x53, 0xaf, 0xfc,
+ 0x47, 0x70, 0x1b, 0x59, 0x6f, 0x74, 0x43, 0xc4,
+ 0xf3, 0x47, 0x18, 0x53, 0x1a, 0xa2, 0xa1, 0x71,
+ 0xc7, 0x95, 0x0e, 0x31, 0x55, 0x21, 0xd3, 0xb5,
+ 0x1e, 0xe9, 0x0c, 0xba, 0xec, 0xb8, 0x89, 0x19,
+ 0x3e, 0xb3, 0xaf, 0x75, 0x81, 0x9d, 0x53, 0xb9,
+ 0x41, 0x57, 0xf4, 0x6d, 0x39, 0x25, 0x29, 0x7c,
+ 0x87, 0xd9, 0xb4, 0x98, 0x45, 0x7d, 0xa7, 0x26,
+ 0x9c, 0x65, 0x3b, 0x85, 0x68, 0x89, 0xd7, 0x3b,
+ 0xbd, 0xff, 0x14, 0x67, 0xf2, 0x2b, 0xf0, 0x2a,
+ 0x41, 0x54, 0xf0, 0xfd, 0x2c, 0x66, 0x7c, 0xf8,
+ 0xc0, 0x8f, 0x33, 0x13, 0x03, 0xf1, 0xd3, 0xc1,
+ 0x0b, 0x89, 0xd9, 0x1b, 0x62, 0xcd, 0x51, 0xb7,
+ 0x80, 0xb8, 0xaf, 0x3a, 0x10, 0xc1, 0x8a, 0x5b,
+ 0xe8, 0x8a, 0x56, 0xf0, 0x8c, 0xaa, 0xfa, 0x35,
+ 0xe9, 0x42, 0xc4, 0xd8, 0x55, 0xc3, 0x38, 0xcc,
+ 0x2b, 0x53, 0x5c, 0x69, 0x52, 0xd5, 0xc8, 0x73,
+ 0x02, 0x38, 0x7c, 0x73, 0xb6, 0x41, 0xe7, 0xff,
+ 0x05, 0xd8, 0x2b, 0x79, 0x9a, 0xe2, 0x34, 0x60,
+ 0x8f, 0xa3, 0x32, 0x1f, 0x09, 0x78, 0x62, 0xbc,
+ 0x80, 0xe3, 0x0f, 0xbd, 0x65, 0x20, 0x08, 0x13,
+ 0xc1, 0xe2, 0xee, 0x53, 0x2d, 0x86, 0x7e, 0xa7,
+ 0x5a, 0xc5, 0xd3, 0x7d, 0x98, 0xbe, 0x31, 0x48,
+ 0x1f, 0xfb, 0xda, 0xaf, 0xa2, 0xa8, 0x6a, 0x89,
+ 0xd6, 0xbf, 0xf2, 0xd3, 0x32, 0x2a, 0x9a, 0xe4,
+ 0xcf, 0x17, 0xb7, 0xb8, 0xf4, 0xe1, 0x33, 0x08,
+ 0x24, 0x8b, 0xc4, 0x43, 0xa5, 0xe5, 0x24, 0xc2
+};
+
+static int usbHidRequestClass(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb)
+{
+ PVUSBSETUP pSetup = (PVUSBSETUP)&pUrb->abData[0];
+
+ if (pThis->enmMode != USBHIDMODE_MULTI_TOUCH)
+ {
+ LogRelFlow(("usbHid: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue,
+ pSetup->wIndex, pSetup->wLength));
+ return usbHidCompleteStall(pThis, pEp, pUrb, "Unsupported class req");
+ }
+
+ int rc = VINF_SUCCESS;
+
+ switch (pSetup->bRequest)
+ {
+ case SET_REPORT:
+ case GET_REPORT:
+ {
+ uint8_t u8ReportType = RT_HI_U8(pSetup->wValue);
+ uint8_t u8ReportID = RT_LO_U8(pSetup->wValue);
+ LogRelFlow(("usbHid: %s: type %d, ID %d, data\n%.*Rhxd\n",
+ pSetup->bRequest == GET_REPORT? "GET_REPORT": "SET_REPORT",
+ u8ReportType, u8ReportID,
+ pUrb->cbData - sizeof(VUSBSETUP), &pUrb->abData[sizeof(VUSBSETUP)]));
+ if (pSetup->bRequest == GET_REPORT)
+ {
+ uint32_t cbData = 0; /* 0 means that the report is unsupported. */
+
+ if (u8ReportType == 1 && u8ReportID == REPORTID_TOUCH_POINTER)
+ {
+ USBHIDMT_REPORT_POINTER *p = (USBHIDMT_REPORT_POINTER *)&pUrb->abData[sizeof(VUSBSETUP)];
+ /* The actual state should be reported here. */
+ p->idReport = REPORTID_TOUCH_POINTER;
+ p->fButtons = 0;
+ p->x = 0;
+ p->y = 0;
+ cbData = sizeof(USBHIDMT_REPORT_POINTER);
+ }
+ else if (u8ReportType == 1 && u8ReportID == REPORTID_TOUCH_EVENT)
+ {
+ USBHIDMT_REPORT *p = (USBHIDMT_REPORT *)&pUrb->abData[sizeof(VUSBSETUP)];
+ /* The actual state should be reported here. */
+ RT_ZERO(*p);
+ p->idReport = REPORTID_TOUCH_EVENT;
+ cbData = sizeof(USBHIDMT_REPORT);
+ }
+ else if (u8ReportType == 3 && u8ReportID == REPORTID_TOUCH_MAX_COUNT)
+ {
+ pUrb->abData[sizeof(VUSBSETUP) + 0] = REPORTID_TOUCH_MAX_COUNT;
+ pUrb->abData[sizeof(VUSBSETUP) + 1] = MT_CONTACT_MAX_COUNT; /* Contact count maximum. */
+ pUrb->abData[sizeof(VUSBSETUP) + 2] = 0; /* Device identifier */
+ cbData = 3;
+ }
+ else if (u8ReportType == 3 && u8ReportID == REPORTID_TOUCH_QABLOB)
+ {
+ pUrb->abData[sizeof(VUSBSETUP) + 0] = REPORTID_TOUCH_QABLOB; /* Report Id. */
+ memcpy(&pUrb->abData[sizeof(VUSBSETUP) + 1],
+ g_abQASampleBlob, sizeof(g_abQASampleBlob));
+ cbData = sizeof(g_abQASampleBlob) + 1;
+ }
+ else if (u8ReportType == 3 && u8ReportID == REPORTID_TOUCH_DEVCONFIG)
+ {
+ pUrb->abData[sizeof(VUSBSETUP) + 0] = REPORTID_TOUCH_DEVCONFIG;
+ pUrb->abData[sizeof(VUSBSETUP) + 1] = 2; /* Device mode:
+ * "HID touch device supporting contact
+ * identifier and contact count maximum."
+ */
+ pUrb->abData[sizeof(VUSBSETUP) + 2] = 0; /* Device identifier */
+ cbData = 3;
+ }
+
+ if (cbData > 0)
+ {
+ rc = usbHidCompleteOk(pThis, pUrb, sizeof(VUSBSETUP) + cbData);
+ }
+ else
+ {
+ rc = usbHidCompleteStall(pThis, pEp, pUrb, "Unsupported GET_REPORT MT");
+ }
+ }
+ else
+ {
+ /* SET_REPORT */
+ rc = usbHidCompleteOk(pThis, pUrb, pUrb->cbData);
+ }
+ } break;
+ default:
+ {
+ LogRelFlow(("usbHid: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue,
+ pSetup->wIndex, pSetup->wLength));
+ rc = usbHidCompleteStall(pThis, pEp, pUrb, "Unsupported class req MT");
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Handles request sent to the default control pipe.
+ */
+static int usbHidHandleDefaultPipe(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb)
+{
+ PVUSBSETUP pSetup = (PVUSBSETUP)&pUrb->abData[0];
+ AssertReturn(pUrb->cbData >= sizeof(*pSetup), VERR_VUSB_FAILED_TO_QUEUE_URB);
+
+ if ((pSetup->bmRequestType & VUSB_REQ_MASK) == VUSB_REQ_STANDARD)
+ {
+ switch (pSetup->bRequest)
+ {
+ case VUSB_REQ_GET_DESCRIPTOR:
+ {
+ switch (pSetup->bmRequestType)
+ {
+ case VUSB_TO_DEVICE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ switch (pSetup->wValue >> 8)
+ {
+ case VUSB_DT_STRING:
+ LogRelFlow(("usbHid: GET_DESCRIPTOR DT_STRING wValue=%#x wIndex=%#x\n",
+ pSetup->wValue, pSetup->wIndex));
+ break;
+ default:
+ LogRelFlow(("usbHid: GET_DESCRIPTOR, huh? wValue=%#x wIndex=%#x\n",
+ pSetup->wValue, pSetup->wIndex));
+ break;
+ }
+ break;
+ }
+
+ case VUSB_TO_INTERFACE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ switch (pSetup->wValue >> 8)
+ {
+ uint32_t cbCopy;
+ uint32_t cbDesc;
+ const uint8_t *pDesc;
+
+ case DT_IF_HID_DESCRIPTOR:
+ {
+ switch (pThis->enmMode)
+ {
+ case USBHIDMODE_ABSOLUTE:
+ cbDesc = sizeof(g_UsbHidTIfHidDesc);
+ pDesc = (const uint8_t *)&g_UsbHidTIfHidDesc;
+ break;
+ case USBHIDMODE_RELATIVE:
+ cbDesc = sizeof(g_UsbHidMIfHidDesc);
+ pDesc = (const uint8_t *)&g_UsbHidMIfHidDesc;
+ break;
+ case USBHIDMODE_MULTI_TOUCH:
+ cbDesc = sizeof(g_UsbHidMTIfHidDesc);
+ pDesc = (const uint8_t *)&g_UsbHidMTIfHidDesc;
+ break;
+ default:
+ cbDesc = 0;
+ pDesc = 0;
+ break;
+ }
+ /* Returned data is written after the setup message. */
+ cbCopy = pUrb->cbData - sizeof(*pSetup);
+ cbCopy = RT_MIN(cbCopy, cbDesc);
+ LogRelFlow(("usbHidMouse: GET_DESCRIPTOR DT_IF_HID_DESCRIPTOR wValue=%#x wIndex=%#x cbCopy=%#x\n",
+ pSetup->wValue, pSetup->wIndex,
+ cbCopy));
+ memcpy(&pUrb->abData[sizeof(*pSetup)], pDesc, cbCopy);
+ return usbHidCompleteOk(pThis, pUrb, cbCopy + sizeof(*pSetup));
+ }
+
+ case DT_IF_HID_REPORT:
+ {
+ switch (pThis->enmMode)
+ {
+ case USBHIDMODE_ABSOLUTE:
+ cbDesc = sizeof(g_UsbHidTReportDesc);
+ pDesc = (const uint8_t *)&g_UsbHidTReportDesc;
+ break;
+ case USBHIDMODE_RELATIVE:
+ cbDesc = sizeof(g_UsbHidMReportDesc);
+ pDesc = (const uint8_t *)&g_UsbHidMReportDesc;
+ break;
+ case USBHIDMODE_MULTI_TOUCH:
+ cbDesc = sizeof(g_UsbHidMTReportDesc);
+ pDesc = (const uint8_t *)&g_UsbHidMTReportDesc;
+ break;
+ default:
+ cbDesc = 0;
+ pDesc = 0;
+ break;
+ }
+ /* Returned data is written after the setup message. */
+ cbCopy = pUrb->cbData - sizeof(*pSetup);
+ cbCopy = RT_MIN(cbCopy, cbDesc);
+ LogRelFlow(("usbHid: GET_DESCRIPTOR DT_IF_HID_REPORT wValue=%#x wIndex=%#x cbCopy=%#x\n",
+ pSetup->wValue, pSetup->wIndex,
+ cbCopy));
+ memcpy(&pUrb->abData[sizeof(*pSetup)], pDesc, cbCopy);
+ return usbHidCompleteOk(pThis, pUrb, cbCopy + sizeof(*pSetup));
+ }
+
+ default:
+ LogRelFlow(("usbHid: GET_DESCRIPTOR, huh? wValue=%#x wIndex=%#x\n",
+ pSetup->wValue, pSetup->wIndex));
+ break;
+ }
+ break;
+ }
+
+ default:
+ LogRelFlow(("usbHid: Bad GET_DESCRIPTOR req: bmRequestType=%#x\n",
+ pSetup->bmRequestType));
+ return usbHidCompleteStall(pThis, pEp, pUrb, "Bad GET_DESCRIPTOR");
+ }
+ break;
+ }
+
+ case VUSB_REQ_GET_STATUS:
+ {
+ uint16_t wRet = 0;
+
+ if (pSetup->wLength != 2)
+ {
+ LogRelFlow(("usbHid: Bad GET_STATUS req: wLength=%#x\n",
+ pSetup->wLength));
+ break;
+ }
+ Assert(pSetup->wValue == 0);
+ switch (pSetup->bmRequestType)
+ {
+ case VUSB_TO_DEVICE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ Assert(pSetup->wIndex == 0);
+ LogRelFlow(("usbHid: GET_STATUS (device)\n"));
+ wRet = 0; /* Not self-powered, no remote wakeup. */
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &wRet, sizeof(wRet));
+ return usbHidCompleteOk(pThis, pUrb, sizeof(wRet) + sizeof(*pSetup));
+ }
+
+ case VUSB_TO_INTERFACE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ if (pSetup->wIndex == 0)
+ {
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &wRet, sizeof(wRet));
+ return usbHidCompleteOk(pThis, pUrb, sizeof(wRet) + sizeof(*pSetup));
+ }
+ LogRelFlow(("usbHid: GET_STATUS (interface) invalid, wIndex=%#x\n", pSetup->wIndex));
+ break;
+ }
+
+ case VUSB_TO_ENDPOINT | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ if (pSetup->wIndex < RT_ELEMENTS(pThis->aEps))
+ {
+ wRet = pThis->aEps[pSetup->wIndex].fHalted ? 1 : 0;
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &wRet, sizeof(wRet));
+ return usbHidCompleteOk(pThis, pUrb, sizeof(wRet) + sizeof(*pSetup));
+ }
+ LogRelFlow(("usbHid: GET_STATUS (endpoint) invalid, wIndex=%#x\n", pSetup->wIndex));
+ break;
+ }
+
+ default:
+ LogRelFlow(("usbHid: Bad GET_STATUS req: bmRequestType=%#x\n",
+ pSetup->bmRequestType));
+ return usbHidCompleteStall(pThis, pEp, pUrb, "Bad GET_STATUS");
+ }
+ break;
+ }
+
+ case VUSB_REQ_CLEAR_FEATURE:
+ break;
+ }
+
+ /** @todo implement this. */
+ LogRelFlow(("usbHid: Implement standard request: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue,
+ pSetup->wIndex, pSetup->wLength));
+
+ usbHidCompleteStall(pThis, pEp, pUrb, "TODO: standard request stuff");
+ }
+ else if ((pSetup->bmRequestType & VUSB_REQ_MASK) == VUSB_REQ_CLASS)
+ {
+ /* Only VUSB_TO_INTERFACE is allowed. */
+ if ((pSetup->bmRequestType & VUSB_RECIP_MASK) == VUSB_TO_INTERFACE)
+ {
+ return usbHidRequestClass(pThis, pEp, pUrb);
+ }
+
+ LogRelFlow(("usbHid: invalid recipient of class req: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue,
+ pSetup->wIndex, pSetup->wLength));
+ return usbHidCompleteStall(pThis, pEp, pUrb, "Invalid recip");
+ }
+ else
+ {
+ LogRelFlow(("usbHid: Unknown control msg: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue,
+ pSetup->wIndex, pSetup->wLength));
+ return usbHidCompleteStall(pThis, pEp, pUrb, "Unknown control msg");
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbQueue}
+ */
+static DECLCALLBACK(int) usbHidQueue(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogRelFlow(("usbHidQueue/#%u: pUrb=%p:%s EndPt=%#x\n", pUsbIns->iInstance,
+ pUrb, pUrb->pszDesc, pUrb->EndPt));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Parse on a per end-point basis.
+ */
+ int rc;
+ switch (pUrb->EndPt)
+ {
+ case 0:
+ rc = usbHidHandleDefaultPipe(pThis, &pThis->aEps[0], pUrb);
+ break;
+
+ case 0x81:
+ AssertFailed();
+ RT_FALL_THRU();
+ case 0x01:
+ rc = usbHidHandleIntrDevToHost(pThis, &pThis->aEps[1], pUrb);
+ break;
+
+ default:
+ AssertMsgFailed(("EndPt=%d\n", pUrb->EndPt));
+ rc = VERR_VUSB_FAILED_TO_QUEUE_URB;
+ break;
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbClearHaltedEndpoint}
+ */
+static DECLCALLBACK(int) usbHidUsbClearHaltedEndpoint(PPDMUSBINS pUsbIns, unsigned uEndpoint)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogRelFlow(("usbHidUsbClearHaltedEndpoint/#%u: uEndpoint=%#x\n",
+ pUsbIns->iInstance, uEndpoint));
+
+ if ((uEndpoint & ~0x80) < RT_ELEMENTS(pThis->aEps))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->aEps[(uEndpoint & ~0x80)].fHalted = false;
+ RTCritSectLeave(&pThis->CritSect);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbSetInterface}
+ */
+static DECLCALLBACK(int) usbHidUsbSetInterface(PPDMUSBINS pUsbIns, uint8_t bInterfaceNumber, uint8_t bAlternateSetting)
+{
+ LogRelFlow(("usbHidUsbSetInterface/#%u: bInterfaceNumber=%u bAlternateSetting=%u\n",
+ pUsbIns->iInstance, bInterfaceNumber, bAlternateSetting));
+ Assert(bAlternateSetting == 0);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbSetConfiguration}
+ */
+static DECLCALLBACK(int) usbHidUsbSetConfiguration(PPDMUSBINS pUsbIns, uint8_t bConfigurationValue,
+ const void *pvOldCfgDesc, const void *pvOldIfState, const void *pvNewCfgDesc)
+{
+ RT_NOREF3(pvOldCfgDesc, pvOldIfState, pvNewCfgDesc);
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogRelFlow(("usbHidUsbSetConfiguration/#%u: bConfigurationValue=%u\n",
+ pUsbIns->iInstance, bConfigurationValue));
+ Assert(bConfigurationValue == 1);
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * If the same config is applied more than once, it's a kind of reset.
+ */
+ if (pThis->bConfigurationValue == bConfigurationValue)
+ usbHidResetWorker(pThis, NULL, true /*fSetConfig*/); /** @todo figure out the exact difference */
+ pThis->bConfigurationValue = bConfigurationValue;
+
+ /*
+ * Set received event type to absolute or relative.
+ */
+ pThis->Lun0.pDrv->pfnReportModes(pThis->Lun0.pDrv,
+ pThis->enmMode == USBHIDMODE_RELATIVE,
+ pThis->enmMode == USBHIDMODE_ABSOLUTE,
+ pThis->enmMode == USBHIDMODE_MULTI_TOUCH);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbGetDescriptorCache}
+ */
+static DECLCALLBACK(PCPDMUSBDESCCACHE) usbHidUsbGetDescriptorCache(PPDMUSBINS pUsbIns)
+{
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogRelFlow(("usbHidUsbGetDescriptorCache/#%u:\n", pUsbIns->iInstance));
+ switch (pThis->enmMode)
+ {
+ case USBHIDMODE_ABSOLUTE:
+ return &g_UsbHidTDescCache;
+ case USBHIDMODE_RELATIVE:
+ return &g_UsbHidMDescCache;
+ case USBHIDMODE_MULTI_TOUCH:
+ return &g_UsbHidMTDescCache;
+ default:
+ return NULL;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbReset}
+ */
+static DECLCALLBACK(int) usbHidUsbReset(PPDMUSBINS pUsbIns, bool fResetOnLinux)
+{
+ RT_NOREF1(fResetOnLinux);
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogRelFlow(("usbHidUsbReset/#%u:\n", pUsbIns->iInstance));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /* We can not handle any input until device is configured again. */
+ pThis->Lun0.pDrv->pfnReportModes(pThis->Lun0.pDrv, false, false, false);
+
+ int rc = usbHidResetWorker(pThis, NULL, false /*fSetConfig*/);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnDestruct}
+ */
+static DECLCALLBACK(void) usbHidDestruct(PPDMUSBINS pUsbIns)
+{
+ PDMUSB_CHECK_VERSIONS_RETURN_VOID(pUsbIns);
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogRelFlow(("usbHidDestruct/#%u:\n", pUsbIns->iInstance));
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+ RTCritSectLeave(&pThis->CritSect);
+ RTCritSectDelete(&pThis->CritSect);
+ }
+
+ if (pThis->hEvtDoneQueue != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(pThis->hEvtDoneQueue);
+ pThis->hEvtDoneQueue = NIL_RTSEMEVENT;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) usbHidConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal)
+{
+ RT_NOREF1(pCfgGlobal);
+ PDMUSB_CHECK_VERSIONS_RETURN(pUsbIns);
+ PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID);
+ LogRelFlow(("usbHidConstruct/#%u:\n", iInstance));
+
+ /*
+ * Perform the basic structure initialization first so the destructor
+ * will not misbehave.
+ */
+ pThis->pUsbIns = pUsbIns;
+ pThis->hEvtDoneQueue = NIL_RTSEMEVENT;
+ usbHidQueueInit(&pThis->ToHostQueue);
+ usbHidQueueInit(&pThis->DoneQueue);
+
+ int rc = RTCritSectInit(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ rc = RTSemEventCreate(&pThis->hEvtDoneQueue);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Validate and read the configuration.
+ */
+ rc = CFGMR3ValidateConfig(pCfg, "/", "Mode|CoordShift", "Config", "UsbHid", iInstance);
+ if (RT_FAILURE(rc))
+ return rc;
+ char szMode[64];
+ rc = CFGMR3QueryStringDef(pCfg, "Mode", szMode, sizeof(szMode), "relative");
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS, N_("HID failed to query settings"));
+ if (!RTStrCmp(szMode, "relative"))
+ pThis->enmMode = USBHIDMODE_RELATIVE;
+ else if (!RTStrCmp(szMode, "absolute"))
+ pThis->enmMode = USBHIDMODE_ABSOLUTE;
+ else if (!RTStrCmp(szMode, "multitouch"))
+ pThis->enmMode = USBHIDMODE_MULTI_TOUCH;
+ else
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS,
+ N_("Invalid HID device mode"));
+
+ LogRelFlow(("usbHidConstruct/#%u: mode '%s'\n", iInstance, szMode));
+
+ pThis->Lun0.IBase.pfnQueryInterface = usbHidMouseQueryInterface;
+ pThis->Lun0.IPort.pfnPutEvent = usbHidMousePutEvent;
+ pThis->Lun0.IPort.pfnPutEventAbs = usbHidMousePutEventAbs;
+ pThis->Lun0.IPort.pfnPutEventMultiTouch = usbHidMousePutEventMultiTouch;
+
+ /*
+ * Attach the mouse driver.
+ */
+ rc = PDMUsbHlpDriverAttach(pUsbIns, 0 /*iLun*/, &pThis->Lun0.IBase, &pThis->Lun0.pDrvBase, "Mouse Port");
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS, N_("HID failed to attach mouse driver"));
+
+ pThis->Lun0.pDrv = PDMIBASE_QUERY_INTERFACE(pThis->Lun0.pDrvBase, PDMIMOUSECONNECTOR);
+ if (!pThis->Lun0.pDrv)
+ return PDMUsbHlpVMSetError(pUsbIns, VERR_PDM_MISSING_INTERFACE, RT_SRC_POS, N_("HID failed to query mouse interface"));
+
+ rc = CFGMR3QueryU8Def(pCfg, "CoordShift", &pThis->u8CoordShift, 1);
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS, N_("HID failed to query shift factor"));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The USB Human Interface Device (HID) Mouse registration record.
+ */
+const PDMUSBREG g_UsbHidMou =
+{
+ /* u32Version */
+ PDM_USBREG_VERSION,
+ /* szName */
+ "HidMouse",
+ /* pszDescription */
+ "USB HID Mouse.",
+ /* fFlags */
+ 0,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(USBHID),
+ /* pfnConstruct */
+ usbHidConstruct,
+ /* pfnDestruct */
+ usbHidDestruct,
+ /* pfnVMInitComplete */
+ NULL,
+ /* pfnVMPowerOn */
+ NULL,
+ /* pfnVMReset */
+ NULL,
+ /* pfnVMSuspend */
+ NULL,
+ /* pfnVMResume */
+ NULL,
+ /* pfnVMPowerOff */
+ NULL,
+ /* pfnHotPlugged */
+ NULL,
+ /* pfnHotUnplugged */
+ NULL,
+ /* pfnDriverAttach */
+ NULL,
+ /* pfnDriverDetach */
+ NULL,
+ /* pfnQueryInterface */
+ NULL,
+ /* pfnUsbReset */
+ usbHidUsbReset,
+ /* pfnUsbGetDescriptorCache */
+ usbHidUsbGetDescriptorCache,
+ /* pfnUsbSetConfiguration */
+ usbHidUsbSetConfiguration,
+ /* pfnUsbSetInterface */
+ usbHidUsbSetInterface,
+ /* pfnUsbClearHaltedEndpoint */
+ usbHidUsbClearHaltedEndpoint,
+ /* pfnUrbNew */
+ NULL/*usbHidUrbNew*/,
+ /* pfnUrbQueue */
+ usbHidQueue,
+ /* pfnUrbCancel */
+ usbHidUrbCancel,
+ /* pfnUrbReap */
+ usbHidUrbReap,
+ /* pfnWakeup */
+ usbHidWakeup,
+ /* u32TheEnd */
+ PDM_USBREG_VERSION
+};
diff --git a/src/VBox/Devices/Input/testcase/Makefile.kmk b/src/VBox/Devices/Input/testcase/Makefile.kmk
new file mode 100644
index 00000000..599aa240
--- /dev/null
+++ b/src/VBox/Devices/Input/testcase/Makefile.kmk
@@ -0,0 +1,34 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-makefile for input test cases.
+#
+
+#
+# Copyright (C) 2013-2019 Oracle Corporation
+#
+# This file is part of VirtualBox Open Source Edition (OSE), as
+# available from http://www.virtualbox.org. This file is free software;
+# you can redistribute it and/or modify it under the terms of the GNU
+# General Public License (GPL) as published by the Free Software
+# Foundation, in version 2 as it comes in the "COPYING" file of the
+# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK)
+ PROGRAMS += tstUsbMouse
+endif
+tstUsbMouse_TEMPLATE = VBOXR3TSTEXE
+tstUsbMouse_DEFS = VBOX_WITH_VUSB
+tstUsbMouse_INCS = \
+ ../../build
+tstUsbMouse_LIBS = $(LIB_VMM) $(LIB_REM)
+tstUsbMouse_SOURCES = \
+ tstUsbMouse.cpp \
+ ../UsbMouse.cpp
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Devices/Input/testcase/tstUsbMouse.cpp b/src/VBox/Devices/Input/testcase/tstUsbMouse.cpp
new file mode 100644
index 00000000..8f78bcac
--- /dev/null
+++ b/src/VBox/Devices/Input/testcase/tstUsbMouse.cpp
@@ -0,0 +1,374 @@
+/* $Id: tstUsbMouse.cpp $ */
+/** @file
+ * tstUsbMouse.cpp - testcase USB mouse and tablet devices.
+ */
+
+/*
+ * Copyright (C) 2013-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxDD.h"
+#include <VBox/vmm/pdmdrv.h>
+#include <iprt/alloc.h>
+#include <iprt/stream.h>
+#include <iprt/test.h>
+#include <iprt/uuid.h>
+
+/** Test mouse driver structure. */
+typedef struct DRVTSTMOUSE
+{
+ /** The USBHID structure. */
+ struct USBHID *pUsbHid;
+ /** The base interface for the mouse driver. */
+ PDMIBASE IBase;
+ /** Our mouse connector interface. */
+ PDMIMOUSECONNECTOR IConnector;
+ /** The base interface of the attached mouse port. */
+ PPDMIBASE pDrvBase;
+ /** The mouse port interface of the attached mouse port. */
+ PPDMIMOUSEPORT pDrv;
+ /** Is relative mode currently supported? */
+ bool fRel;
+ /** Is absolute mode currently supported? */
+ bool fAbs;
+ /** Is multi-touch mode currently supported? */
+ bool fMT;
+} DRVTSTMOUSE;
+typedef DRVTSTMOUSE *PDRVTSTMOUSE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static PDMUSBHLP g_tstUsbHlp;
+/** Global mouse driver variable.
+ * @todo To be improved some time. */
+static DRVTSTMOUSE g_drvTstMouse;
+
+
+/** @interface_method_impl{PDMUSBHLPR3,pfnVMSetErrorV} */
+static DECLCALLBACK(int) tstVMSetErrorV(PPDMUSBINS pUsbIns, int rc,
+ RT_SRC_POS_DECL, const char *pszFormat,
+ va_list va)
+{
+ RT_NOREF(pUsbIns);
+ RTPrintf("Error: %s:%u:%s:", RT_SRC_POS_ARGS);
+ RTPrintfV(pszFormat, va);
+ return rc;
+}
+
+/** @interface_method_impl{PDMUSBHLPR3,pfnDriverAttach} */
+/** @todo We currently just take the driver interface from the global
+ * variable. This is sufficient for a unit test but still a bit sad. */
+static DECLCALLBACK(int) tstDriverAttach(PPDMUSBINS pUsbIns, RTUINT iLun, PPDMIBASE pBaseInterface,
+ PPDMIBASE *ppBaseInterface, const char *pszDesc)
+{
+ RT_NOREF3(pUsbIns, iLun, pszDesc);
+ g_drvTstMouse.pDrvBase = pBaseInterface;
+ g_drvTstMouse.pDrv = PDMIBASE_QUERY_INTERFACE(pBaseInterface, PDMIMOUSEPORT);
+ *ppBaseInterface = &g_drvTstMouse.IBase;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) tstMouseQueryInterface(PPDMIBASE pInterface,
+ const char *pszIID)
+{
+ PDRVTSTMOUSE pUsbIns = RT_FROM_MEMBER(pInterface, DRVTSTMOUSE, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pUsbIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSECONNECTOR, &pUsbIns->IConnector);
+ return NULL;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMOUSECONNECTOR,pfnReportModes}
+ */
+static DECLCALLBACK(void) tstMouseReportModes(PPDMIMOUSECONNECTOR pInterface,
+ bool fRel, bool fAbs, bool fMT)
+{
+ PDRVTSTMOUSE pDrv = RT_FROM_MEMBER(pInterface, DRVTSTMOUSE, IConnector);
+ pDrv->fRel = fRel;
+ pDrv->fAbs = fAbs;
+ pDrv->fMT = fMT;
+}
+
+
+static int tstMouseConstruct(int iInstance, const char *pcszMode,
+ uint8_t u8CoordShift, PPDMUSBINS *ppThis,
+ uint32_t uInstanceVersion = PDM_USBINS_VERSION)
+{
+ int rc = VERR_NO_MEMORY;
+ PPDMUSBINS pUsbIns = (PPDMUSBINS)RTMemAllocZ( sizeof(*pUsbIns)
+ + g_UsbHidMou.cbInstance);
+ PCFGMNODE pCfg = NULL;
+ if (pUsbIns)
+ pCfg = CFGMR3CreateTree(NULL);
+ if (pCfg)
+ rc = CFGMR3InsertString(pCfg, "Mode", pcszMode);
+ if (RT_SUCCESS(rc))
+ rc = CFGMR3InsertInteger(pCfg, "CoordShift", u8CoordShift);
+ if (RT_SUCCESS(rc))
+ {
+ g_drvTstMouse.pDrv = NULL;
+ g_drvTstMouse.pDrvBase = NULL;
+ pUsbIns->u32Version = uInstanceVersion;
+ pUsbIns->iInstance = iInstance;
+ pUsbIns->pHlpR3 = &g_tstUsbHlp;
+ rc = g_UsbHidMou.pfnConstruct(pUsbIns, iInstance, pCfg, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ *ppThis = pUsbIns;
+ return rc;
+ }
+ }
+ /* Failure */
+ if (pCfg)
+ CFGMR3DestroyTree(pCfg);
+ if (pUsbIns)
+ RTMemFree(pUsbIns);
+ return rc;
+}
+
+
+static void testConstructAndDestruct(RTTEST hTest)
+{
+ RTTestSub(hTest, "simple construction and destruction");
+
+ /*
+ * Normal check first.
+ */
+ PPDMUSBINS pUsbIns = NULL;
+ RTTEST_CHECK_RC(hTest, tstMouseConstruct(0, "relative", 1, &pUsbIns), VINF_SUCCESS);
+ if (pUsbIns)
+ g_UsbHidMou.pfnDestruct(pUsbIns);
+
+ /*
+ * Modify the dev hlp version.
+ */
+ static struct
+ {
+ int rc;
+ uint32_t uInsVersion;
+ uint32_t uHlpVersion;
+ } const s_aVersionTests[] =
+ {
+ { VERR_PDM_USBHLPR3_VERSION_MISMATCH, PDM_USBINS_VERSION, 0 },
+ { VERR_PDM_USBHLPR3_VERSION_MISMATCH, PDM_USBINS_VERSION, PDM_USBHLP_VERSION - PDM_VERSION_MAKE(0, 1, 0) },
+ { VERR_PDM_USBHLPR3_VERSION_MISMATCH, PDM_USBINS_VERSION, PDM_USBHLP_VERSION + PDM_VERSION_MAKE(0, 1, 0) },
+ { VERR_PDM_USBHLPR3_VERSION_MISMATCH, PDM_USBINS_VERSION, PDM_USBHLP_VERSION + PDM_VERSION_MAKE(0, 1, 1) },
+ { VERR_PDM_USBHLPR3_VERSION_MISMATCH, PDM_USBINS_VERSION, PDM_USBHLP_VERSION + PDM_VERSION_MAKE(1, 0, 0) },
+ { VERR_PDM_USBHLPR3_VERSION_MISMATCH, PDM_USBINS_VERSION, PDM_USBHLP_VERSION - PDM_VERSION_MAKE(1, 0, 0) },
+ { VINF_SUCCESS, PDM_USBINS_VERSION, PDM_USBHLP_VERSION + PDM_VERSION_MAKE(0, 0, 1) },
+ { VERR_PDM_USBINS_VERSION_MISMATCH, PDM_USBINS_VERSION - PDM_VERSION_MAKE(0, 1, 0), PDM_USBHLP_VERSION },
+ { VERR_PDM_USBINS_VERSION_MISMATCH, PDM_USBINS_VERSION + PDM_VERSION_MAKE(0, 1, 0), PDM_USBHLP_VERSION },
+ { VERR_PDM_USBINS_VERSION_MISMATCH, PDM_USBINS_VERSION + PDM_VERSION_MAKE(0, 1, 1), PDM_USBHLP_VERSION },
+ { VERR_PDM_USBINS_VERSION_MISMATCH, PDM_USBINS_VERSION + PDM_VERSION_MAKE(1, 0, 0), PDM_USBHLP_VERSION },
+ { VERR_PDM_USBINS_VERSION_MISMATCH, PDM_USBINS_VERSION - PDM_VERSION_MAKE(1, 0, 0), PDM_USBHLP_VERSION },
+ { VINF_SUCCESS, PDM_USBINS_VERSION + PDM_VERSION_MAKE(0, 0, 1), PDM_USBHLP_VERSION },
+ { VINF_SUCCESS,
+ PDM_USBINS_VERSION + PDM_VERSION_MAKE(0, 0, 1), PDM_USBHLP_VERSION + PDM_VERSION_MAKE(0, 0, 1) },
+ };
+ bool const fSavedMayPanic = RTAssertSetMayPanic(false);
+ bool const fSavedQuiet = RTAssertSetQuiet(true);
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aVersionTests); i++)
+ {
+ g_tstUsbHlp.u32Version = g_tstUsbHlp.u32TheEnd = s_aVersionTests[i].uHlpVersion;
+ pUsbIns = NULL;
+ RTTEST_CHECK_RC(hTest, tstMouseConstruct(0, "relative", 1, &pUsbIns, s_aVersionTests[i].uInsVersion),
+ s_aVersionTests[i].rc);
+ }
+ RTAssertSetMayPanic(fSavedMayPanic);
+ RTAssertSetQuiet(fSavedQuiet);
+
+ g_tstUsbHlp.u32Version = g_tstUsbHlp.u32TheEnd = PDM_USBHLP_VERSION;
+}
+
+
+static void testSendPositionRel(RTTEST hTest)
+{
+ PPDMUSBINS pUsbIns = NULL;
+ VUSBURB Urb;
+ RTTestSub(hTest, "sending a relative position event");
+ int rc = tstMouseConstruct(0, "relative", 1, &pUsbIns);
+ RT_ZERO(Urb);
+ if (RT_SUCCESS(rc))
+ rc = g_UsbHidMou.pfnUsbReset(pUsbIns, false);
+ if (RT_SUCCESS(rc) && !g_drvTstMouse.pDrv)
+ rc = VERR_PDM_MISSING_INTERFACE;
+ RTTEST_CHECK_RC_OK(hTest, rc);
+ if (RT_SUCCESS(rc))
+ {
+ g_drvTstMouse.pDrv->pfnPutEvent(g_drvTstMouse.pDrv, 123, -16, 1, -1, 3);
+ Urb.EndPt = 0x01;
+ rc = g_UsbHidMou.pfnUrbQueue(pUsbIns, &Urb);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ PVUSBURB pUrb = g_UsbHidMou.pfnUrbReap(pUsbIns, 0);
+ if (pUrb)
+ {
+ if (pUrb == &Urb)
+ {
+ if ( Urb.abData[0] != 3 /* Buttons */
+ || Urb.abData[1] != 123 /* x */
+ || Urb.abData[2] != 240 /* 256 - y */
+ || Urb.abData[3] != 255 /* z */)
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else
+ rc = VERR_GENERAL_FAILURE;
+ }
+ RTTEST_CHECK_RC_OK(hTest, rc);
+ if (pUsbIns)
+ g_UsbHidMou.pfnDestruct(pUsbIns);
+}
+
+
+static void testSendPositionAbs(RTTEST hTest)
+{
+ PPDMUSBINS pUsbIns = NULL;
+ VUSBURB Urb;
+ RTTestSub(hTest, "sending an absolute position event");
+ int rc = tstMouseConstruct(0, "absolute", 1, &pUsbIns);
+ RT_ZERO(Urb);
+ if (RT_SUCCESS(rc))
+ {
+ rc = g_UsbHidMou.pfnUsbReset(pUsbIns, false);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ if (g_drvTstMouse.pDrv)
+ g_drvTstMouse.pDrv->pfnPutEventAbs(g_drvTstMouse.pDrv, 300, 200, 1,
+ 3, 3);
+ else
+ rc = VERR_PDM_MISSING_INTERFACE;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ Urb.EndPt = 0x01;
+ rc = g_UsbHidMou.pfnUrbQueue(pUsbIns, &Urb);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ PVUSBURB pUrb = g_UsbHidMou.pfnUrbReap(pUsbIns, 0);
+ if (pUrb)
+ {
+ if (pUrb == &Urb)
+ {
+ if ( Urb.abData[0] != 3 /* Buttons */
+ || (int8_t)Urb.abData[1] != -1 /* dz */
+ || (int8_t)Urb.abData[2] != -3 /* dw */
+ || *(uint16_t *)&Urb.abData[4] != 150 /* x >> 1 */
+ || *(uint16_t *)&Urb.abData[6] != 100 /* y >> 1 */)
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else
+ rc = VERR_GENERAL_FAILURE;
+ }
+ RTTEST_CHECK_RC_OK(hTest, rc);
+ if (pUsbIns)
+ g_UsbHidMou.pfnDestruct(pUsbIns);
+}
+
+#if 0
+/** @todo PDM interface was updated. This is not working anymore. */
+static void testSendPositionMT(RTTEST hTest)
+{
+ PPDMUSBINS pUsbIns = NULL;
+ VUSBURB Urb;
+ RTTestSub(hTest, "sending a multi-touch position event");
+ int rc = tstMouseConstruct(0, "multitouch", 1, &pUsbIns);
+ RT_ZERO(Urb);
+ if (RT_SUCCESS(rc))
+ {
+ rc = g_UsbHidMou.pfnUsbReset(pUsbIns, false);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ if (g_drvTstMouse.pDrv)
+ g_drvTstMouse.pDrv->pfnPutEventMT(g_drvTstMouse.pDrv, 300, 200, 2,
+ 3);
+ else
+ rc = VERR_PDM_MISSING_INTERFACE;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ Urb.EndPt = 0x01;
+ rc = g_UsbHidMou.pfnUrbQueue(pUsbIns, &Urb);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ PVUSBURB pUrb = g_UsbHidMou.pfnUrbReap(pUsbIns, 0);
+ if (pUrb)
+ {
+ if (pUrb == &Urb)
+ {
+ if ( Urb.abData[0] != 1 /* Report ID */
+ || Urb.abData[1] != 3 /* Contact flags */
+ || *(uint16_t *)&Urb.abData[2] != 150 /* x >> 1 */
+ || *(uint16_t *)&Urb.abData[4] != 100 /* y >> 1 */
+ || Urb.abData[6] != 2 /* Contact number */)
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else
+ rc = VERR_GENERAL_FAILURE;
+ }
+ RTTEST_CHECK_RC_OK(hTest, rc);
+ if (pUsbIns)
+ g_UsbHidMou.pfnDestruct(pUsbIns);
+}
+#endif
+
+int main()
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstUsbMouse", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+ /* Set up our faked PDMUSBHLP interface. */
+ g_tstUsbHlp.u32Version = PDM_USBHLP_VERSION;
+ g_tstUsbHlp.pfnVMSetErrorV = tstVMSetErrorV;
+ g_tstUsbHlp.pfnDriverAttach = tstDriverAttach;
+ g_tstUsbHlp.u32TheEnd = PDM_USBHLP_VERSION;
+ /* Set up our global mouse driver */
+ g_drvTstMouse.IBase.pfnQueryInterface = tstMouseQueryInterface;
+ g_drvTstMouse.IConnector.pfnReportModes = tstMouseReportModes;
+
+ /*
+ * Run the tests.
+ */
+ testConstructAndDestruct(hTest);
+ testSendPositionRel(hTest);
+ testSendPositionAbs(hTest);
+ /* testSendPositionMT(hTest); */
+ return RTTestSummaryAndDestroy(hTest);
+}