summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Input
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/Input
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
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.cpp1264
-rw-r--r--src/VBox/Devices/Input/DevPS2.h417
-rw-r--r--src/VBox/Devices/Input/DevPS2K.cpp1520
-rw-r--r--src/VBox/Devices/Input/DevPS2M.cpp1196
-rw-r--r--src/VBox/Devices/Input/DrvKeyboardQueue.cpp606
-rw-r--r--src/VBox/Devices/Input/DrvMouseQueue.cpp464
-rw-r--r--src/VBox/Devices/Input/Makefile.kup0
-rw-r--r--src/VBox/Devices/Input/UsbKbd.cpp1894
-rw-r--r--src/VBox/Devices/Input/UsbMouse.cpp2926
-rw-r--r--src/VBox/Devices/Input/testcase/Makefile.kmk44
-rw-r--r--src/VBox/Devices/Input/testcase/tstUsbMouse.cpp412
11 files changed, 10743 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..5e9c1746
--- /dev/null
+++ b/src/VBox/Devices/Input/DevPS2.cpp
@@ -0,0 +1,1264 @@
+/* $Id: DevPS2.cpp $ */
+/** @file
+ * DevPS2 - PS/2 keyboard & mouse controller device.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ * --------------------------------------------------------------------
+ *
+ * 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 <VBox/AssertGuest.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+#include "DevPS2.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/* 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
+
+/* 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
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** 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;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/* 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 const g_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.
+ */
+static int32_t kbcXlateAT2PC(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 = g_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;
+}
+
+
+/** update irq and KBD_STAT_[MOUSE_]OBF */
+static void kbd_update_irq(PPDMDEVINS pDevIns, PKBDSTATE 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(pDevIns, &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 = kbcXlateAT2PC(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(pDevIns, &s->Kbd, &val) == VINF_SUCCESS)
+ {
+ s->xlat_state = kbcXlateAT2PC(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(pDevIns, 1, irq1_level);
+ PDMDevHlpISASetIrq(pDevIns, 12, irq12_level);
+}
+
+void KBCUpdateInterrupts(PPDMDEVINS pDevIns)
+{
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ kbd_update_irq(pDevIns, pThis);
+}
+
+static void kbc_dbb_out(PPDMDEVINS pDevIns, PKBDSTATE s, uint8_t val)
+{
+ s->dbbout = val;
+ /* Set the OBF and raise IRQ. */
+ s->status |= KBD_STAT_OBF;
+ if (s->mode & KBD_MODE_KBD_INT)
+ PDMDevHlpISASetIrq(pDevIns, 1, 1);
+}
+
+static void kbc_dbb_out_aux(PPDMDEVINS pDevIns, PKBDSTATE s, uint8_t val)
+{
+ 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(pDevIns, 12, PDM_IRQ_LEVEL_HIGH);
+}
+
+static VBOXSTRICTRC kbd_write_command(PPDMDEVINS pDevIns, PKBDSTATE s, uint32_t val)
+{
+#ifdef DEBUG_KBD
+ Log(("kbd: write cmd=0x%02x\n", val));
+#endif
+ switch(val) {
+ case KBD_CCMD_READ_MODE:
+ kbc_dbb_out(pDevIns, 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;
+ PS2MLineDisable(&s->Aux);
+ break;
+ case KBD_CCMD_MOUSE_ENABLE:
+ PS2MLineEnable(&s->Aux);
+ s->mode &= ~KBD_MODE_DISABLE_MOUSE;
+ /* Check for queued input. */
+ /// @todo Can there actually be any?
+ kbd_update_irq(pDevIns, s);
+ break;
+ case KBD_CCMD_TEST_MOUSE:
+ kbc_dbb_out(pDevIns, s, 0x00);
+ break;
+ case KBD_CCMD_SELF_TEST:
+ /* Enable the A20 line - that is the power-on state(!). */
+# ifndef IN_RING3
+ if (!PDMDevHlpA20IsEnabled(pDevIns))
+ return VINF_IOM_R3_IOPORT_WRITE;
+# else /* IN_RING3 */
+ PDMDevHlpA20Set(pDevIns, true);
+# endif /* IN_RING3 */
+ s->status |= KBD_STAT_SELFTEST;
+ s->mode |= KBD_MODE_DISABLE_KBD;
+ kbc_dbb_out(pDevIns, s, 0x55);
+ break;
+ case KBD_CCMD_KBD_TEST:
+ kbc_dbb_out(pDevIns, 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(pDevIns, s);
+ break;
+ case KBD_CCMD_READ_INPORT:
+ kbc_dbb_out(pDevIns, s, 0xBF);
+ break;
+ case KBD_CCMD_READ_OUTPORT:
+ /* XXX: check that */
+#ifdef TARGET_I386
+ val = 0x01 | (PDMDevHlpA20IsEnabled(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(pDevIns, s, val);
+ break;
+#ifdef TARGET_I386
+ case KBD_CCMD_ENABLE_A20:
+# ifndef IN_RING3
+ if (!PDMDevHlpA20IsEnabled(pDevIns))
+ return VINF_IOM_R3_IOPORT_WRITE;
+# else /* IN_RING3 */
+ PDMDevHlpA20Set(pDevIns, true);
+# endif /* IN_RING3 */
+ break;
+ case KBD_CCMD_DISABLE_A20:
+# ifndef IN_RING3
+ if (PDMDevHlpA20IsEnabled(pDevIns))
+ return VINF_IOM_R3_IOPORT_WRITE;
+# else /* IN_RING3 */
+ PDMDevHlpA20Set(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(pDevIns, s, val);
+ break;
+ case KBD_CCMD_RESET:
+ case KBD_CCMD_RESET_ALT:
+#ifndef IN_RING3
+ return VINF_IOM_R3_IOPORT_WRITE;
+#else /* IN_RING3 */
+ LogRel(("Reset initiated by keyboard controller\n"));
+ return PDMDevHlpVMReset(pDevIns, PDMVMRESET_F_KBD);
+#endif /* IN_RING3 */
+ 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(pDevIns, 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 VINF_SUCCESS;
+}
+
+static uint32_t kbd_read_data(PPDMDEVINS pDevIns, PKBDSTATE s)
+{
+ uint32_t val;
+
+ /* Return the current DBB contents. */
+ val = s->dbbout;
+
+ /* Reading the DBB deasserts IRQs... */
+ if (s->status & KBD_STAT_MOUSE_OBF)
+ PDMDevHlpISASetIrq(pDevIns, 12, 0);
+ else
+ PDMDevHlpISASetIrq(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(pDevIns, s);
+#ifdef DEBUG_KBD
+ Log(("kbd: read data=0x%02x\n", val));
+#endif
+ return val;
+}
+
+static VBOXSTRICTRC kbd_write_data(PPDMDEVINS pDevIns, PKBDSTATE s, uint32_t val)
+{
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+
+#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(pDevIns, &s->Kbd, val);
+ if (rc == VINF_SUCCESS)
+ kbd_update_irq(pDevIns, s);
+ break;
+ case KBD_CCMD_WRITE_MODE:
+ s->mode = val;
+ s->translate = (s->mode & KBD_MODE_KCC) == KBD_MODE_KCC;
+ kbd_update_irq(pDevIns, s);
+ break;
+ case KBD_CCMD_WRITE_OBUF:
+ kbc_dbb_out(pDevIns, s, val);
+ break;
+ case KBD_CCMD_WRITE_AUX_OBUF:
+ kbc_dbb_out_aux(pDevIns, s, val);
+ break;
+ case KBD_CCMD_WRITE_OUTPORT:
+#ifdef TARGET_I386
+# ifndef IN_RING3
+ if (PDMDevHlpA20IsEnabled(pDevIns) != !!(val & 2))
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+# else /* IN_RING3 */
+ PDMDevHlpA20Set(pDevIns, !!(val & 2));
+# endif /* IN_RING3 */
+#endif
+ if (!(val & 1)) {
+# ifndef IN_RING3
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+# else
+ rc = PDMDevHlpVMReset(pDevIns, PDMVMRESET_F_KBD);
+# endif
+ }
+ break;
+ case KBD_CCMD_WRITE_MOUSE:
+ /* Automatically enables aux interface. */
+ if (s->mode & KBD_MODE_DISABLE_MOUSE)
+ {
+ PS2MLineEnable(&s->Aux);
+ s->mode &= ~KBD_MODE_DISABLE_MOUSE;
+ }
+ rc = PS2MByteToAux(pDevIns, &s->Aux, val);
+ if (rc == VINF_SUCCESS)
+ kbd_update_irq(pDevIns, s);
+ break;
+ default:
+ break;
+ }
+ if (rc != VINF_IOM_R3_IOPORT_WRITE)
+ s->write_cmd = 0;
+ return rc;
+}
+
+#ifdef IN_RING3
+
+static int kbd_load(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM, PKBDSTATE s, PKBDSTATER3 pThisCC, 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;
+ pHlp->pfnSSMGetU8(pSSM, &s->write_cmd);
+ pHlp->pfnSSMGetU8(pSSM, &s->status);
+ pHlp->pfnSSMGetU8(pSSM, &s->mode);
+ if (version_id <= 5)
+ {
+ pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ }
+ else
+ {
+ pHlp->pfnSSMGetU8(pSSM, &s->dbbout);
+ }
+ if (version_id <= 7)
+ {
+ int32_t i32Dummy;
+ uint8_t u8State;
+ uint8_t u8Rate;
+ uint8_t u8Proto;
+
+ pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ pHlp->pfnSSMGetU8(pSSM, &u8State);
+ pHlp->pfnSSMGetU8(pSSM, &u8Dummy);
+ pHlp->pfnSSMGetU8(pSSM, &u8Rate);
+ pHlp->pfnSSMGetU8(pSSM, &u8Dummy);
+ pHlp->pfnSSMGetU8(pSSM, &u8Proto);
+ pHlp->pfnSSMGetU8(pSSM, &u8Dummy);
+ pHlp->pfnSSMGetS32(pSSM, &i32Dummy);
+ pHlp->pfnSSMGetS32(pSSM, &i32Dummy);
+ pHlp->pfnSSMGetS32(pSSM, &i32Dummy);
+ if (version_id > 2)
+ {
+ pHlp->pfnSSMGetS32(pSSM, &i32Dummy);
+ pHlp->pfnSSMGetS32(pSSM, &i32Dummy);
+ }
+ rc = pHlp->pfnSSMGetU8(pSSM, &u8Dummy);
+ if (version_id == 4)
+ {
+ pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ rc = pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ }
+ if (version_id > 3)
+ rc = pHlp->pfnSSMGetU8(pSSM, &u8Dummy);
+ if (version_id == 4)
+ rc = pHlp->pfnSSMGetU8(pSSM, &u8Dummy);
+ AssertLogRelRCReturn(rc, rc);
+
+ PS2MR3FixupState(&s->Aux, &pThisCC->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 = pHlp->pfnSSMGetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ for (i = 0; i < u32; i++)
+ {
+ rc = pHlp->pfnSSMGetU8(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 = pHlp->pfnSSMGetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ for (i = 0; i < u32; i++)
+ {
+ rc = pHlp->pfnSSMGetU8(pSSM, &u8Dummy);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ Log(("kbd_load: %d mouse event queue items discarded from old saved state\n", u32));
+
+ rc = pHlp->pfnSSMGetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ for (i = 0; i < u32; i++)
+ {
+ rc = pHlp->pfnSSMGetU8(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 = pHlp->pfnSSMGetU32(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 -=-=-=-=-=- */
+
+/** Fluff bits indexed by size (1,2,4). */
+static uint32_t const g_afFluff[5] =
+{
+ /* [0] = */ 0,
+ /* [1] = */ 0,
+ /* [2] = */ UINT32_C(0xff00),
+ /* [3] = */ 0,
+ /* [4] = */ UINT32_C(0xffffff00) /* Crazy Apple (Darwin 6.0.2 and earlier). */
+};
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Port I/O Handler for keyboard data IN operations.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) kbdIOPortDataRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ RT_NOREF(pvUser, offPort);
+ Assert(offPort == 0);
+ Assert(cb == 1 || cb == 2 || cb == 4);
+
+ *pu32 = kbd_read_data(pDevIns, pThis) | g_afFluff[cb];
+ Log2(("kbdIOPortDataRead: cb=%u *pu32=%#x\n", cb, *pu32));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for keyboard data OUT operations.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) kbdIOPortDataWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ RT_NOREF(offPort, pvUser);
+ Assert(offPort == 0);
+
+ if (cb == 1 || cb == 2)
+ {
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ VBOXSTRICTRC rc = kbd_write_data(pDevIns, pThis, (uint8_t)u32);
+ Log2(("kbdIOPortDataWrite: Port=0x60+%x cb=%d u32=%#x rc=%Rrc\n", offPort, cb, u32, VBOXSTRICTRC_VAL(rc)));
+ return rc;
+ }
+ Assert(cb == 4);
+ ASSERT_GUEST_MSG_FAILED(("Port=0x60+%x cb=%d\n", offPort, cb));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Port I/O Handler for keyboard status IN operations.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) kbdIOPortStatusRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ RT_NOREF(offPort, pvUser);
+ Assert(offPort == 0);
+ Assert(cb == 1 || cb == 2 || cb == 4);
+
+ *pu32 = pThis->status | g_afFluff[cb];
+ Log2(("kbdIOPortStatusRead: cb=%u -> *pu32=%#x\n", cb, *pu32));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Port I/O Handler for keyboard command OUT operations.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) kbdIOPortCommandWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ RT_NOREF(offPort, pvUser);
+ Assert(offPort == 0);
+
+ if (cb == 1 || cb == 2)
+ {
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ VBOXSTRICTRC rc = kbd_write_command(pDevIns, pThis, (uint8_t)u32);
+ Log2(("kbdIOPortCommandWrite: cb=%d u32=%#x rc=%Rrc\n", cb, u32, VBOXSTRICTRC_VAL(rc)));
+ return rc;
+ }
+ Assert(cb == 4);
+ ASSERT_GUEST_MSG_FAILED(("offPort=0x64+%x cb=%d\n", offPort, cb));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Clear a queue.
+ *
+ * @param pQHdr The queue header.
+ * @param cElements The queue size.
+ */
+void PS2CmnClearQueue(PPS2QHDR pQHdr, size_t cElements)
+{
+ Assert(cElements > 0);
+ LogFlowFunc(("Clearing %s queue %p\n", R3STRING(pQHdr->pszDescR3), pQHdr));
+ pQHdr->wpos = pQHdr->rpos = pQHdr->rpos % cElements;
+ pQHdr->cUsed = 0;
+}
+
+
+/**
+ * Add a byte to a queue.
+ *
+ * @param pQHdr The queue header.
+ * @param cElements The queue size.
+ * @param pbElements The queue element array.
+ * @param bValue The byte to store.
+ */
+void PS2CmnInsertQueue(PPS2QHDR pQHdr, size_t cElements, uint8_t *pbElements, uint8_t bValue)
+{
+ Assert(cElements > 0);
+
+ /* Check that the queue is not full. */
+ uint32_t cUsed = pQHdr->cUsed;
+ if (cUsed < cElements)
+ {
+ /* Insert data and update circular buffer write position. */
+ uint32_t wpos = pQHdr->wpos % cElements;
+ pbElements[wpos] = bValue;
+
+ wpos += 1;
+ if (wpos < cElements)
+ pQHdr->wpos = wpos;
+ else
+ pQHdr->wpos = 0; /* Roll over. */
+ pQHdr->cUsed = cUsed + 1;
+
+ LogRelFlowFunc(("inserted %#04x into %s queue %p\n", bValue, R3STRING(pQHdr->pszDescR3), pQHdr));
+ }
+ else
+ {
+ Assert(cUsed == cElements);
+ LogRelFlowFunc(("%s queue %p full (%zu entries)\n", R3STRING(pQHdr->pszDescR3), pQHdr, cElements));
+ }
+}
+
+/**
+ * Retrieve a byte from a queue.
+ *
+ * @param pQHdr The queue header.
+ * @param cElements The queue size.
+ * @param pbElements The queue element array.
+ * @param pbValue Where to return the byte on success.
+ *
+ * @retval VINF_TRY_AGAIN if queue is empty,
+ * @retval VINF_SUCCESS if a byte was read.
+ */
+int PS2CmnRemoveQueue(PPS2QHDR pQHdr, size_t cElements, uint8_t const *pbElements, uint8_t *pbValue)
+{
+ int rc;
+
+ Assert(cElements > 0);
+ Assert(pbValue);
+
+ uint32_t cUsed = (uint32_t)RT_MIN(pQHdr->cUsed, cElements);
+ if (cUsed > 0)
+ {
+ uint32_t rpos = pQHdr->rpos % cElements;
+ *pbValue = pbElements[rpos];
+
+ rpos += 1;
+ if (rpos < cElements)
+ pQHdr->rpos = rpos;
+ else
+ pQHdr->rpos = 0; /* Roll over. */
+ pQHdr->cUsed = cUsed - 1;
+
+ LogFlowFunc(("removed 0x%02X from %s queue %p\n", *pbValue, R3STRING(pQHdr->pszDescR3), pQHdr));
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ LogFlowFunc(("%s queue %p empty\n", R3STRING(pQHdr->pszDescR3), pQHdr));
+ rc = VINF_TRY_AGAIN;
+ }
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Save a queue state.
+ *
+ * @param pHlp The device helpers.
+ * @param pSSM SSM handle to write the state to.
+ * @param pQHdr The queue header.
+ * @param cElements The queue size.
+ * @param pbElements The queue element array.
+ */
+void PS2CmnR3SaveQueue(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM, PPS2QHDR pQHdr, size_t cElements, uint8_t const *pbElements)
+{
+ uint32_t cItems = (uint32_t)RT_MIN(pQHdr->cUsed, cElements);
+
+ /* Only save the number of items. Note that the read/write
+ * positions aren't saved as they will be rebuilt on load.
+ */
+ pHlp->pfnSSMPutU32(pSSM, cItems);
+
+ LogFlow(("Storing %u items from %s queue %p\n", cItems, pQHdr->pszDescR3, pQHdr));
+
+ /* Save queue data - only the bytes actually used (typically zero). */
+ for (uint32_t i = pQHdr->rpos % cElements; cItems-- > 0; i = (i + 1) % cElements)
+ pHlp->pfnSSMPutU8(pSSM, pbElements[i]);
+}
+
+/**
+ * Load a queue state.
+ *
+ * @param pHlp The device helpers.
+ * @param pSSM SSM handle to read the state from.
+ * @param pQHdr The queue header.
+ * @param cElements The queue size.
+ * @param pbElements The queue element array.
+ *
+ * @returns VBox status/error code.
+ */
+int PS2CmnR3LoadQueue(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM, PPS2QHDR pQHdr, size_t cElements, uint8_t *pbElements)
+{
+ /* On load, always put the read pointer at zero. */
+ uint32_t cUsed;
+ int rc = pHlp->pfnSSMGetU32(pSSM, &cUsed);
+ AssertRCReturn(rc, rc);
+
+ LogFlow(("Loading %u items to %s queue %p\n", cUsed, pQHdr->pszDescR3, pQHdr));
+
+ AssertMsgReturn(cUsed <= cElements, ("Saved size=%u, actual=%zu\n", cUsed, cElements),
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+ /* Recalculate queue positions and load data in one go. */
+ pQHdr->rpos = 0;
+ pQHdr->wpos = cUsed;
+ pQHdr->cUsed = cUsed;
+ return pHlp->pfnSSMGetMem(pSSM, pbElements, cUsed);
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC, 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) kbdR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ pHlp->pfnSSMPutU8(pSSM, pThis->write_cmd);
+ pHlp->pfnSSMPutU8(pSSM, pThis->status);
+ pHlp->pfnSSMPutU8(pSSM, pThis->mode);
+ pHlp->pfnSSMPutU8(pSSM, pThis->dbbout);
+ /* terminator */
+ pHlp->pfnSSMPutU32(pSSM, UINT32_MAX);
+
+ PS2KR3SaveState(pDevIns, &pThis->Kbd, pSSM);
+ PS2MR3SaveState(pDevIns, &pThis->Aux, pSSM);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC, Loads a saved keyboard device state.}
+ */
+static DECLCALLBACK(int) kbdR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ PKBDSTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3);
+ int rc;
+
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+ rc = kbd_load(pDevIns->pHlpR3, pSSM, pThis, pThisCC, uVersion);
+ AssertRCReturn(rc, rc);
+
+ if (uVersion >= 6)
+ rc = PS2KR3LoadState(pDevIns, &pThis->Kbd, pSSM, uVersion);
+ AssertRCReturn(rc, rc);
+
+ if (uVersion >= 8)
+ rc = PS2MR3LoadState(pDevIns, &pThis->Aux, &pThisCC->Aux, pSSM, uVersion);
+ AssertRCReturn(rc, rc);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADDONE, Key state fix-up after loading}
+ */
+static DECLCALLBACK(int) kbdR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ PKBDSTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3);
+ RT_NOREF(pSSM);
+ if (pThis->mode & KBD_MODE_DISABLE_MOUSE)
+ PS2MLineDisable(&pThis->Aux);
+ if (pThis->mode & KBD_MODE_DISABLE_KBD)
+ PS2KLineDisable(&pThis->Kbd);
+ return PS2KR3LoadDone(pDevIns, &pThis->Kbd, &pThisCC->Kbd);
+}
+
+
+/**
+ * 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) kbdR3InfoState(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ NOREF(pszArgs);
+
+ pHlp->pfnPrintf(pHlp, "Keyboard controller: Active command %02X, DBB out %02X, translation %s\n",
+ pThis->write_cmd, pThis->dbbout, pThis->translate ? "on" : "off");
+
+ pHlp->pfnPrintf(pHlp, "Mode: %02X ( ", pThis->mode);
+ if (pThis->mode & KBD_MODE_DISABLE_KBD)
+ pHlp->pfnPrintf(pHlp, "DISABLE_KBD ");
+ if (pThis->mode & KBD_MODE_KBD_INT)
+ pHlp->pfnPrintf(pHlp, "KBD_INT ");
+ if (pThis->mode & KBD_MODE_MOUSE_INT)
+ pHlp->pfnPrintf(pHlp, "AUX_INT ");
+ if (pThis->mode & KBD_MODE_SYS)
+ pHlp->pfnPrintf(pHlp, "SYS ");
+ if (pThis->mode & KBD_MODE_NO_KEYLOCK)
+ pHlp->pfnPrintf(pHlp, "NO_KEYLOCK ");
+ if (pThis->mode & KBD_MODE_DISABLE_KBD)
+ pHlp->pfnPrintf(pHlp, "DISABLE_KBD ");
+ if (pThis->mode & KBD_MODE_DISABLE_MOUSE)
+ pHlp->pfnPrintf(pHlp, "DISABLE_AUX ");
+ if (pThis->mode & KBD_MODE_KCC)
+ pHlp->pfnPrintf(pHlp, "KCC ");
+ if (pThis->mode & KBD_MODE_RFU)
+ pHlp->pfnPrintf(pHlp, "RFU ");
+ pHlp->pfnPrintf(pHlp, " )\n");
+
+ pHlp->pfnPrintf(pHlp, "Status: %02X ( ", pThis->status);
+ if (pThis->status & KBD_STAT_OBF)
+ pHlp->pfnPrintf(pHlp, "OBF ");
+ if (pThis->status & KBD_STAT_IBF)
+ pHlp->pfnPrintf(pHlp, "IBF ");
+ if (pThis->status & KBD_STAT_SELFTEST)
+ pHlp->pfnPrintf(pHlp, "SELFTEST ");
+ if (pThis->status & KBD_STAT_CMD)
+ pHlp->pfnPrintf(pHlp, "CMD ");
+ if (pThis->status & KBD_STAT_UNLOCKED)
+ pHlp->pfnPrintf(pHlp, "UNLOCKED ");
+ if (pThis->status & KBD_STAT_MOUSE_OBF)
+ pHlp->pfnPrintf(pHlp, "AUX_OBF ");
+ if (pThis->status & KBD_STAT_GTO)
+ pHlp->pfnPrintf(pHlp, "GTO ");
+ if (pThis->status & KBD_STAT_PERR)
+ pHlp->pfnPrintf(pHlp, "PERR ");
+ pHlp->pfnPrintf(pHlp, " )\n");
+}
+
+
+/* -=-=-=-=-=- real code -=-=-=-=-=- */
+
+/**
+ * Reset notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) kbdR3Reset(PPDMDEVINS pDevIns)
+{
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ PKBDSTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3);
+
+ pThis->mode = KBD_MODE_KBD_INT | KBD_MODE_MOUSE_INT;
+ pThis->status = KBD_STAT_CMD | KBD_STAT_UNLOCKED;
+ /* Resetting everything, keyword was not working right on NT4 reboot. */
+ pThis->write_cmd = 0;
+ pThis->translate = 0;
+
+ PS2KR3Reset(pDevIns, &pThis->Kbd, &pThisCC->Kbd);
+ PS2MR3Reset(&pThis->Aux);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnAttach}
+ *
+ * @remark The keyboard controller doesn't support this action, this is just
+ * implemented to try out the driver<->device structure.
+ */
+static DECLCALLBACK(int) kbdR3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PKBDSTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3);
+ int rc;
+
+ 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 = PS2KR3Attach(pDevIns, &pThisCC->Kbd, iLUN, fFlags);
+ break;
+
+ /* LUN #1: aux/mouse */
+ case 1:
+ rc = PS2MR3Attach(pDevIns, &pThisCC->Aux, iLUN, fFlags);
+ break;
+
+ default:
+ AssertMsgFailed(("Invalid LUN #%d\n", iLUN));
+ return VERR_PDM_NO_SUCH_LUN;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnDetach}
+ * @remark The keyboard controller doesn't support this action, this is just
+ * implemented to try out the driver<->device structure.
+ */
+static DECLCALLBACK(void) kbdR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+#if 0
+ /*
+ * Reset the interfaces and update the controller state.
+ */
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ switch (iLUN)
+ {
+ /* LUN #0: keyboard */
+ case 0:
+ pThisCC->Keyboard.pDrv = NULL;
+ pThisCC->Keyboard.pDrvBase = NULL;
+ break;
+
+ /* LUN #1: aux/mouse */
+ case 1:
+ pThisCC->Mouse.pDrv = NULL;
+ pThisCC->Mouse.pDrvBase = NULL;
+ break;
+
+ default:
+ AssertMsgFailed(("Invalid LUN #%d\n", iLUN));
+ break;
+ }
+#else
+ NOREF(pDevIns); NOREF(iLUN); NOREF(fFlags);
+#endif
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnConstruct}
+ */
+static DECLCALLBACK(int) kbdR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ PKBDSTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3);
+ int rc;
+ RT_NOREF(iInstance);
+
+ Assert(iInstance == 0);
+
+ /*
+ * Validate and read the configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "KbdThrottleEnabled", "");
+ Log(("pckbd: fRCEnabled=%RTbool fR0Enabled=%RTbool\n", pDevIns->fRCEnabled, pDevIns->fR0Enabled));
+
+ /*
+ * Initialize the sub-components.
+ */
+ rc = PS2KR3Construct(pDevIns, &pThis->Kbd, &pThisCC->Kbd, pCfg);
+ AssertRCReturn(rc, rc);
+
+ rc = PS2MR3Construct(pDevIns, &pThis->Aux, &pThisCC->Aux);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register I/O ports.
+ */
+ rc = PDMDevHlpIoPortCreateAndMap(pDevIns, 0x60 /*uPort*/, 1 /*cPorts*/, kbdIOPortDataWrite, kbdIOPortDataRead,
+ "PC Keyboard - Data", NULL /*pExtDescs*/, &pThis->hIoPortData);
+ AssertRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortCreateAndMap(pDevIns, 0x64 /*uPort*/, 1 /*cPorts*/, kbdIOPortCommandWrite, kbdIOPortStatusRead,
+ "PC Keyboard - Command / Status", NULL /*pExtDescs*/, &pThis->hIoPortCmdStatus);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Saved state.
+ */
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, PCKBD_SAVED_STATE_VERSION, sizeof(*pThis), NULL,
+ NULL, NULL, NULL,
+ NULL, kbdR3SaveExec, NULL,
+ NULL, kbdR3LoadExec, kbdR3LoadDone);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register debugger info callbacks.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ps2c", "Display keyboard/mouse controller state.", kbdR3InfoState);
+
+ /*
+ * Attach to the keyboard and mouse drivers.
+ */
+ rc = kbdR3Attach(pDevIns, 0 /* keyboard LUN # */, PDM_TACH_FLAGS_NOT_HOT_PLUG);
+ AssertRCReturn(rc, rc);
+ rc = kbdR3Attach(pDevIns, 1 /* aux/mouse LUN # */, PDM_TACH_FLAGS_NOT_HOT_PLUG);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Initialize the device state.
+ */
+ kbdR3Reset(pDevIns);
+
+ return VINF_SUCCESS;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) kbdRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PKBDSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+
+ int rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortData, kbdIOPortDataWrite, kbdIOPortDataRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortCmdStatus, kbdIOPortCommandWrite, kbdIOPortStatusRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DevicePS2KeyboardMouse =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "pckbd",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE,
+ /* .fClass = */ PDM_DEVREG_CLASS_INPUT,
+ /* .cMaxInstances = */ 1,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(KBDSTATE),
+ /* .cbInstanceCC = */ CTX_EXPR(sizeof(KBDSTATER3), 0, 0),
+ /* .cbInstanceRC = */ 0,
+ /* .cMaxPciDevices = */ 0,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "PS/2 Keyboard and Mouse device. Emulates both the keyboard, mouse and the keyboard controller.\n"
+ "LUN #0 is the keyboard connector.\n"
+ "LUN #1 is the aux/mouse connector.",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ kbdR3Construct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ kbdR3Reset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ NULL,
+ /* .pfnAttach = */ kbdR3Attach,
+ /* .pfnDetach = */ kbdR3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ NULL,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ kbdRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ kbdRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Input/DevPS2.h b/src/VBox/Devices/Input/DevPS2.h
new file mode 100644
index 00000000..f6359b06
--- /dev/null
+++ b/src/VBox/Devices/Input/DevPS2.h
@@ -0,0 +1,417 @@
+/* $Id: DevPS2.h $ */
+/** @file
+ * PS/2 devices - Internal header file.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Input_DevPS2_h
+#define VBOX_INCLUDED_SRC_Input_DevPS2_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/** @defgroup grp_devps2 PS/2 Device
+ * @{
+ */
+
+/** Pointer to the shared keyboard (PS/2) controller / device state. */
+typedef struct KBDSTATE *PKBDSTATE;
+
+
+/** @name PS/2 Input Queue Primitive
+ * @{ */
+typedef struct PS2QHDR
+{
+ uint32_t volatile rpos;
+ uint32_t volatile wpos;
+ uint32_t volatile cUsed;
+ uint32_t uPadding;
+ R3PTRTYPE(const char *) pszDescR3;
+} PS2QHDR;
+/** Pointer to a queue header. */
+typedef PS2QHDR *PPS2QHDR;
+
+/** Define a simple PS/2 input device queue. */
+#define DEF_PS2Q_TYPE(name, size) \
+ typedef struct { \
+ PS2QHDR Hdr; \
+ uint8_t abQueue[size]; \
+ } name
+
+void PS2CmnClearQueue(PPS2QHDR pQHdr, size_t cElements);
+void PS2CmnInsertQueue(PPS2QHDR pQHdr, size_t cElements, uint8_t *pbElements, uint8_t bValue);
+int PS2CmnRemoveQueue(PPS2QHDR pQHdr, size_t cElements, uint8_t const *pbElements, uint8_t *pbValue);
+void PS2CmnR3SaveQueue(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM, PPS2QHDR pQHdr, size_t cElements, uint8_t const *pbElements);
+int PS2CmnR3LoadQueue(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM, PPS2QHDR pQHdr, size_t cElements, uint8_t *pbElements);
+
+#define PS2Q_CLEAR(a_pQueue) \
+ PS2CmnClearQueue(&(a_pQueue)->Hdr, RT_ELEMENTS((a_pQueue)->abQueue))
+#define PS2Q_INSERT(a_pQueue, a_bValue) \
+ PS2CmnInsertQueue(&(a_pQueue)->Hdr, RT_ELEMENTS((a_pQueue)->abQueue), (a_pQueue)->abQueue, (a_bValue))
+#define PS2Q_REMOVE(a_pQueue, a_pbValue) \
+ PS2CmnRemoveQueue(&(a_pQueue)->Hdr, RT_ELEMENTS((a_pQueue)->abQueue), (a_pQueue)->abQueue, (a_pbValue))
+#define PS2Q_SAVE(a_pHlp, a_pSSM, a_pQueue) \
+ PS2CmnR3SaveQueue((a_pHlp), (a_pSSM), &(a_pQueue)->Hdr, RT_ELEMENTS((a_pQueue)->abQueue), (a_pQueue)->abQueue)
+#define PS2Q_LOAD(a_pHlp, a_pSSM, a_pQueue) \
+ PS2CmnR3LoadQueue((a_pHlp), (a_pSSM), &(a_pQueue)->Hdr, RT_ELEMENTS((a_pQueue)->abQueue), (a_pQueue)->abQueue)
+#define PS2Q_SIZE(a_pQueue) RT_ELEMENTS((a_pQueue)->abQueue)
+#define PS2Q_COUNT(a_pQueue) ((a_pQueue)->Hdr.cUsed)
+#define PS2Q_RD_POS(a_pQueue) ((a_pQueue)->Hdr.rpos)
+#define PS2Q_WR_POS(a_pQueue) ((a_pQueue)->Hdr.wpos)
+/** @} */
+
+
+/** @defgroup grp_devps2k DevPS2K - Keyboard
+ * @{
+ */
+
+/** @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)
+/** @} */
+
+/* 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
+
+DEF_PS2Q_TYPE(KbdKeyQ, KBD_KEY_QUEUE_SIZE);
+DEF_PS2Q_TYPE(KbdCmdQ, KBD_CMD_QUEUE_SIZE);
+
+/** 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;
+
+
+/**
+ * The shared PS/2 keyboard instance data.
+ */
+typedef struct PS2K
+{
+ /** 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;
+ uint8_t bAlignment1;
+ /** Usage code of current typematic key, if any. */
+ uint32_t u32TypematicKey;
+ /** 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. */
+ uint32_t uTypematicDelay;
+ /** Typematic repeat period in milliseconds. */
+ uint32_t uTypematicRepeat;
+ /** Set if the throttle delay is currently active. */
+ bool fThrottleActive;
+ /** Set if the input rate should be throttled. */
+ bool fThrottleEnabled;
+ /** Set if the serial line is disabled on the KBC. */
+ bool fLineDisabled;
+ uint8_t abAlignment2[1];
+
+ /** Command delay timer. */
+ TMTIMERHANDLE hKbdDelayTimer;
+ /** Typematic timer. */
+ TMTIMERHANDLE hKbdTypematicTimer;
+ /** Input throttle timer. */
+ TMTIMERHANDLE hThrottleTimer;
+} PS2K;
+/** Pointer to the shared PS/2 keyboard instance data. */
+typedef PS2K *PPS2K;
+
+
+/**
+ * The PS/2 keyboard instance data for ring-3.
+ */
+typedef struct PS2KR3
+{
+ /** The device instance.
+ * @note Only for getting our bearings in interface methods. */
+ PPDMDEVINSR3 pDevIns;
+
+ /**
+ * 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;
+} PS2KR3;
+/** Pointer to the PS/2 keyboard instance data for ring-3. */
+typedef PS2KR3 *PPS2KR3;
+
+
+int PS2KByteToKbd(PPDMDEVINS pDevIns, PPS2K pThis, uint8_t cmd);
+int PS2KByteFromKbd(PPDMDEVINS pDevIns, PPS2K pThis, uint8_t *pVal);
+
+void PS2KLineDisable(PPS2K pThis);
+void PS2KLineEnable(PPS2K pThis);
+
+int PS2KR3Construct(PPDMDEVINS pDevIns, PPS2K pThis, PPS2KR3 pThisCC, PCFGMNODE pCfg);
+int PS2KR3Attach(PPDMDEVINS pDevIns, PPS2KR3 pThisCC, unsigned iLUN, uint32_t fFlags);
+void PS2KR3Reset(PPDMDEVINS pDevIns, PPS2K pThis, PPS2KR3 pThisCC);
+void PS2KR3SaveState(PPDMDEVINS pDevIns, PPS2K pThis, PSSMHANDLE pSSM);
+int PS2KR3LoadState(PPDMDEVINS pDevIns, PPS2K pThis, PSSMHANDLE pSSM, uint32_t uVersion);
+int PS2KR3LoadDone(PPDMDEVINS pDevIns, PPS2K pThis, PPS2KR3 pThisCC);
+/** @} */
+
+
+/** @defgroup grp_devps2m DevPS2M - Auxiliary Device (Mouse)
+ * @{
+ */
+
+/* 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
+
+DEF_PS2Q_TYPE(AuxEvtQ, AUX_EVT_QUEUE_SIZE);
+DEF_PS2Q_TYPE(AuxCmdQ, AUX_CMD_QUEUE_SIZE);
+
+/** 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_IMEX_HORZ = 5 /* IntelliMouse Explorer with horizontal reports. */
+} PS2M_PROTO;
+
+/** Protocol selection 'knock' states. */
+typedef enum {
+ PS2M_KNOCK_INITIAL,
+ PS2M_KNOCK_1ST,
+ PS2M_KNOCK_IMPS2_2ND,
+ PS2M_KNOCK_IMEX_2ND,
+ PS2M_KNOCK_IMEX_HORZ_2ND
+} PS2M_KNOCK_STATE;
+
+/**
+ * The shared PS/2 auxiliary device instance data.
+ */
+typedef struct PS2M
+{
+ /** 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 serial line is disabled on the KBC. */
+ bool fLineDisabled;
+ /** 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 (vertical scroll) movement. */
+ int32_t iAccumZ;
+ /** Accumulated W axis (horizontal scroll) movement. */
+ int32_t iAccumW;
+ /** 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;
+
+ /** Command delay timer. */
+ TMTIMERHANDLE hDelayTimer;
+ /** Interrupt throttling timer. */
+ TMTIMERHANDLE hThrottleTimer;
+} PS2M;
+/** Pointer to the shared PS/2 auxiliary device instance data. */
+typedef PS2M *PPS2M;
+
+/**
+ * The PS/2 auxiliary device instance data for ring-3.
+ */
+typedef struct PS2MR3
+{
+ /** The device instance.
+ * @note Only for getting our bearings in interface methods. */
+ PPDMDEVINSR3 pDevIns;
+
+ /**
+ * 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;
+} PS2MR3;
+/** Pointer to the PS/2 auxiliary device instance data for ring-3. */
+typedef PS2MR3 *PPS2MR3;
+
+int PS2MByteToAux(PPDMDEVINS pDevIns, PPS2M pThis, uint8_t cmd);
+int PS2MByteFromAux(PPS2M pThis, uint8_t *pVal);
+
+void PS2MLineDisable(PPS2M pThis);
+void PS2MLineEnable(PPS2M pThis);
+
+int PS2MR3Construct(PPDMDEVINS pDevIns, PPS2M pThis, PPS2MR3 pThisCC);
+int PS2MR3Attach(PPDMDEVINS pDevIns, PPS2MR3 pThisCC, unsigned iLUN, uint32_t fFlags);
+void PS2MR3Reset(PPS2M pThis);
+void PS2MR3SaveState(PPDMDEVINS pDevIns, PPS2M pThis, PSSMHANDLE pSSM);
+int PS2MR3LoadState(PPDMDEVINS pDevIns, PPS2M pThis, PPS2MR3 pThisCC, PSSMHANDLE pSSM, uint32_t uVersion);
+void PS2MR3FixupState(PPS2M pThis, PPS2MR3 pThisCC, uint8_t u8State, uint8_t u8Rate, uint8_t u8Proto);
+/** @} */
+
+
+/**
+ * The shared 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;
+
+ /** I/O port 60h. */
+ IOMIOPORTHANDLE hIoPortData;
+ /** I/O port 64h. */
+ IOMIOPORTHANDLE hIoPortCmdStatus;
+
+ /** Shared keyboard state (implemented in separate PS2K module). */
+ PS2K Kbd;
+ /** Shared mouse state (implemented in separate PS2M module). */
+ PS2M Aux;
+} KBDSTATE;
+
+
+/**
+ * The ring-3 keyboard controller/device state.
+ */
+typedef struct KBDSTATER3
+{
+ /** Keyboard state for ring-3 (implemented in separate PS2K module). */
+ PS2KR3 Kbd;
+ /** Mouse state for ring-3 (implemented in separate PS2M module). */
+ PS2MR3 Aux;
+} KBDSTATER3;
+/** Pointer to the keyboard (PS/2) controller / device state for ring-3. */
+typedef KBDSTATER3 *PKBDSTATER3;
+
+
+/* Shared keyboard/aux internal interface. */
+void KBCUpdateInterrupts(PPDMDEVINS pDevIns);
+
+/** @} */
+
+#endif /* !VBOX_INCLUDED_SRC_Input_DevPS2_h */
+
diff --git a/src/VBox/Devices/Input/DevPS2K.cpp b/src/VBox/Devices/Input/DevPS2K.cpp
new file mode 100644
index 00000000..5912de44
--- /dev/null
+++ b/src/VBox/Devices/Input/DevPS2K.cpp
@@ -0,0 +1,1520 @@
+/* $Id: DevPS2K.cpp $ */
+/** @file
+ * PS2K - PS/2 keyboard emulation.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * 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 "DevPS2.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 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
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/* 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 (HID Usage Page 7). */
+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 (HID Usage Page 7). */
+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 */
+};
+
+/* Extended key definition for sparse mapping. */
+typedef struct {
+ uint16_t usageId;
+ key_def kdef;
+} ext_key_def;
+
+
+/* USB to PS/2 conversion table for consumer control keys (HID Usage Page 12). */
+/* This usage page is very sparse so we'll just search through it. */
+static const ext_key_def aPS2CCKeys[] = {
+ {0x00B5, {0x19, 0x4D, UNKN, KF_E0, T_U}}, /* Scan Next Track */
+ {0x00B6, {0x10, 0x15, UNKN, KF_E0, T_U}}, /* Scan Previous Track */
+ {0x00B7, {0x24, 0x3B, UNKN, KF_E0, T_U}}, /* Stop */
+ {0x00CD, {0x22, 0x34, UNKN, KF_E0, T_U}}, /* Play/Pause */
+ {0x00E2, {0x20, 0x23, UNKN, KF_E0, T_U}}, /* Mute */
+ {0x00E5, {UNAS, UNAS, UNAS, 0, T_U}}, /* Bass Boost */
+ {0x00E7, {UNAS, UNAS, UNAS, 0, T_U}}, /* Loudness */
+ {0x00E9, {0x30, 0x32, UNKN, KF_E0, T_U}}, /* Volume Up */
+ {0x00EA, {0x2E, 0x21, UNKN, KF_E0, T_U}}, /* Volume Down */
+ {0x0152, {UNAS, UNAS, UNAS, 0, T_U}}, /* Bass Up */
+ {0x0153, {UNAS, UNAS, UNAS, 0, T_U}}, /* Bass Down */
+ {0x0154, {UNAS, UNAS, UNAS, 0, T_U}}, /* Treble Up */
+ {0x0155, {UNAS, UNAS, UNAS, 0, T_U}}, /* Treble Down */
+ {0x0183, {0x6D, 0x50, UNKN, KF_E0, T_U}}, /* Media Select */
+ {0x018A, {0x6C, 0x48, UNKN, KF_E0, T_U}}, /* Mail */
+ {0x0192, {0x21, 0x2B, UNKN, KF_E0, T_U}}, /* Calculator */
+ {0x0194, {0x6B, 0x40, UNKN, KF_E0, T_U}}, /* My Computer */
+ {0x0221, {0x65, 0x10, UNKN, KF_E0, T_U}}, /* WWW Search */
+ {0x0223, {0x32, 0x3A, UNKN, KF_E0, T_U}}, /* WWW Home */
+ {0x0224, {0x6A, 0x38, UNKN, KF_E0, T_U}}, /* WWW Back */
+ {0x0225, {0x69, 0x30, UNKN, KF_E0, T_U}}, /* WWW Forward */
+ {0x0226, {0x68, 0x28, UNKN, KF_E0, T_U}}, /* WWW Stop */
+ {0x0227, {0x67, 0x20, UNKN, KF_E0, T_U}}, /* WWW Refresh */
+ {0x022A, {0x66, 0x18, UNKN, KF_E0, T_U}}, /* WWW Favorites */
+};
+
+/* USB to PS/2 conversion table for Generic Desktop Control keys (HID Usage Page 1). */
+/* This usage page is tiny. */
+static const ext_key_def aPS2DCKeys[] = {
+ {0x81, {0x5E, 0x37, UNKN, KF_E0, T_U}}, /* System Power */
+ {0x82, {0x5F, 0x3F, UNKN, KF_E0, T_U}}, /* System Sleep */
+ {0x83, {0x63, 0x5E, UNKN, KF_E0, T_U}}, /* System Wake */
+};
+
+/* We somehow need to keep track of depressed keys. To keep the array size
+ * under control, and because the number of defined keys isn't massive, we'd
+ * like to use an 8-bit index into the array.
+ * For the main USB HID usage page 7 (keyboard), we deal with 8-bit HID codes
+ * in the range from 0 to 0xE7, and use the HID codes directly.
+ * There's a convenient gap in the 0xA5-0xDF range. We use that to stuff the
+ * USB HID usage page 12 (consumer control) into the gap starting at 0xC0;
+ * the consumer control codes are from 0xB5 to 0x22A, but very sparse, with
+ * only 24 codes defined. We use the aPS2CCKeys array to generate our own
+ * code in the 0xC0-0xD7 range.
+ * For the tiny USB HID usage page 1 (generic desktop system) we use a similar
+ * approach, translating these to codes 0xB0 to 0xB2.
+ */
+
+#define PS2K_PAGE_DC_START 0xb0
+#define PS2K_PAGE_DC_END (PS2K_PAGE_DC_START + RT_ELEMENTS(aPS2DCKeys))
+#define PS2K_PAGE_CC_START 0xc0
+#define PS2K_PAGE_CC_END (PS2K_PAGE_CC_START + RT_ELEMENTS(aPS2CCKeys))
+
+AssertCompile(RT_ELEMENTS(aPS2CCKeys) <= 0x20); /* Must fit between 0xC0-0xDF. */
+AssertCompile(RT_ELEMENTS(aPS2DCKeys) <= 0x10); /* Must fit between 0xB0-0xBF. */
+
+#endif /* IN_RING3 */
+
+#ifdef IN_RING3
+
+/**
+ * Add a null-terminated byte sequence to a queue if there is enough room.
+ *
+ * @param pQueue Pointer to the queue.
+ * @param pStr Pointer to the bytes to store.
+ * @param cbReserve Number of bytes that must still remain available in
+ * queue.
+ * @return VBox status/error code.
+ */
+static int ps2kR3InsertStrQueue(KbdKeyQ *pQueue, const uint8_t *pStr, uint32_t cbReserve)
+{
+ /* Check if queue has enough room. */
+ size_t const cbStr = (uint32_t)strlen((const char *)pStr);
+ uint32_t cUsed = RT_MIN(pQueue->Hdr.cUsed, RT_ELEMENTS(pQueue->abQueue));
+ if (cUsed + cbReserve + cbStr >= RT_ELEMENTS(pQueue->abQueue))
+ {
+ LogRelFlowFunc(("queue %p (KbdKeyQ) full (%u entries, want room for %u), cannot insert %zu entries\n",
+ pQueue, cUsed, cbReserve, cbStr));
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* Insert byte sequence and update circular buffer write position. */
+ uint32_t wpos = pQueue->Hdr.wpos % RT_ELEMENTS(pQueue->abQueue);
+ for (size_t i = 0; i < cbStr; i++)
+ {
+ pQueue->abQueue[wpos] = pStr[i];
+ wpos += 1;
+ if (wpos < RT_ELEMENTS(pQueue->abQueue))
+ { /* likely */ }
+ else
+ wpos = 0; /* Roll over. */
+ }
+
+ pQueue->Hdr.wpos = wpos;
+ pQueue->Hdr.cUsed = cUsed + (uint32_t)cbStr;
+
+ LogRelFlowFunc(("inserted %u bytes into queue %p (KbdKeyQ)\n", cbStr, pQueue));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Notify listener about LEDs state change.
+ *
+ * @param pThisCC The PS/2 keyboard instance data for ring-3.
+ * @param u8State Bitfield which reflects LEDs state.
+ */
+static void ps2kR3NotifyLedsState(PPS2KR3 pThisCC, 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);
+
+ if (pThisCC->Keyboard.pDrv)
+ pThisCC->Keyboard.pDrv->pfnLedStatusChange(pThisCC->Keyboard.pDrv, enmLeds);
+}
+
+#endif /* IN_RING3 */
+
+/** Clears the currently active typematic key, if any. */
+static void ps2kStopTypematicRepeat(PPDMDEVINS pDevIns, PPS2K pThis)
+{
+ if (pThis->u32TypematicKey)
+ {
+ LogFunc(("Typematic key %08X\n", pThis->u32TypematicKey));
+ pThis->enmTypematicState = KBD_TMS_IDLE;
+ pThis->u32TypematicKey = 0;
+ PDMDevHlpTimerStop(pDevIns, pThis->hKbdTypematicTimer);
+ }
+}
+
+/** 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(PPDMDEVINS pDevIns, PPS2K pThis)
+{
+ LogFlowFunc(("Set keyboard defaults\n"));
+ PS2Q_CLEAR(&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(pDevIns, pThis);
+}
+
+/**
+ * The keyboard controller disabled the keyboard serial line.
+ *
+ * @param pThis The keyboard device shared instance data.
+ */
+void PS2KLineDisable(PPS2K pThis)
+{
+ LogFlowFunc(("Disabling keyboard serial line\n"));
+
+ pThis->fLineDisabled = true;
+}
+
+/**
+ * The keyboard controller enabled the keyboard serial line.
+ *
+ * @param pThis The keyboard device shared instance data.
+ */
+void PS2KLineEnable(PPS2K pThis)
+{
+ LogFlowFunc(("Enabling keyboard serial line\n"));
+
+ pThis->fLineDisabled = false;
+
+ /* If there was anything in the input queue,
+ * consider it lost and throw it away.
+ */
+ PS2Q_CLEAR(&pThis->keyQ);
+}
+
+/**
+ * Receive and process a byte sent by the keyboard controller.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared PS/2 keyboard instance data.
+ * @param cmd The command (or data) byte.
+ */
+int PS2KByteToKbd(PPDMDEVINS pDevIns, 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:
+ PS2Q_INSERT(&pThis->cmdQ, KRSP_ECHO);
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_READ_ID:
+ PS2Q_INSERT(&pThis->cmdQ, KRSP_ACK);
+ PS2Q_INSERT(&pThis->cmdQ, KRSP_ID1);
+ PS2Q_INSERT(&pThis->cmdQ, KRSP_ID2);
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_ENABLE:
+ pThis->fScanning = true;
+ PS2Q_CLEAR(&pThis->keyQ);
+ ps2kStopTypematicRepeat(pDevIns, pThis);
+ PS2Q_INSERT(&pThis->cmdQ, KRSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_DFLT_DISABLE:
+ pThis->fScanning = false;
+ ps2kSetDefaults(pDevIns, pThis); /* Also clears buffer/typematic state. */
+ PS2Q_INSERT(&pThis->cmdQ, KRSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_SET_DEFAULT:
+ ps2kSetDefaults(pDevIns, pThis);
+ PS2Q_INSERT(&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.
+ PS2Q_INSERT(&pThis->cmdQ, KRSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_RESEND:
+ pThis->u8CurrCmd = 0;
+ break;
+ case KCMD_RESET:
+ pThis->u8ScanSet = 2;
+ ps2kSetDefaults(pDevIns, pThis);
+ /// @todo reset more?
+ PS2Q_INSERT(&pThis->cmdQ, KRSP_ACK);
+ pThis->u8CurrCmd = cmd;
+ /* Delay BAT completion; the test may take hundreds of ms. */
+ PDMDevHlpTimerSetMillies(pDevIns, pThis->hKbdDelayTimer, 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:
+ PS2Q_INSERT(&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
+ {
+ PPS2KR3 pThisCC = &PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3)->Kbd;
+ ps2kR3NotifyLedsState(pThisCC, cmd);
+ pThis->fNumLockOn = !!(cmd & 0x02); /* Sync internal Num Lock state. */
+ PS2Q_INSERT(&pThis->cmdQ, KRSP_ACK);
+ pThis->u8LEDs = cmd;
+ pThis->u8CurrCmd = 0;
+ }
+#endif
+ break;
+ case KCMD_SCANSET:
+ PS2Q_INSERT(&pThis->cmdQ, KRSP_ACK);
+ if (cmd == 0)
+ PS2Q_INSERT(&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);
+ PS2Q_INSERT(&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:
+ PS2Q_INSERT(&pThis->cmdQ, KRSP_RESEND);
+ pThis->u8CurrCmd = 0;
+ break;
+ }
+ LogFlowFunc(("Active cmd now 0x%02X; updating interrupts\n", pThis->u8CurrCmd));
+// KBCUpdateInterrupts(pDevIns);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Send a byte (keystroke or command response) to the keyboard controller.
+ *
+ * @returns VINF_SUCCESS or VINF_TRY_AGAIN.
+ * @param pDevIns The device instance.
+ * @param pThis The shared 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(PPDMDEVINS pDevIns, 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 = PS2Q_REMOVE(&pThis->cmdQ, pb);
+ if (rc != VINF_SUCCESS && !pThis->u8CurrCmd && pThis->fScanning)
+ if (!pThis->fThrottleActive)
+ {
+ rc = PS2Q_REMOVE(&pThis->keyQ, pb);
+ if (pThis->fThrottleEnabled)
+ {
+ pThis->fThrottleActive = true;
+ PDMDevHlpTimerSetMillies(pDevIns, pThis->hThrottleTimer, KBD_THROTTLE_DELAY);
+ }
+ }
+
+ LogFlowFunc(("keyboard sends 0x%02x (%svalid data)\n", *pb, rc == VINF_SUCCESS ? "" : "not "));
+ return rc;
+}
+
+#ifdef IN_RING3
+
+static int ps2kR3HidToInternalCode(uint32_t u32HidCode, key_def const **ppKeyDef)
+{
+ uint8_t u8HidPage;
+ uint16_t u16HidUsage;
+ int iKeyIndex = -1;
+ key_def const *pKeyDef = &aPS2Keys[0]; /* Dummy no-event key. */;
+
+ u8HidPage = RT_LOBYTE(RT_HIWORD(u32HidCode));
+ u16HidUsage = RT_LOWORD(u32HidCode);
+
+ if (u8HidPage == USB_HID_KB_PAGE)
+ {
+ if (u16HidUsage <= VBOX_USB_MAX_USAGE_CODE)
+ {
+ iKeyIndex = u16HidUsage; /* Direct mapping. */
+ pKeyDef = iKeyIndex >= HID_MODIFIER_FIRST ? &aPS2ModKeys[iKeyIndex - HID_MODIFIER_FIRST] : &aPS2Keys[iKeyIndex];
+ }
+ else
+ AssertMsgFailed(("u16HidUsage out of range! (%04X)\n", u16HidUsage));
+ }
+ else if (u8HidPage == USB_HID_CC_PAGE)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(aPS2CCKeys); ++i)
+ if (aPS2CCKeys[i].usageId == u16HidUsage)
+ {
+ pKeyDef = &aPS2CCKeys[i].kdef;
+ iKeyIndex = PS2K_PAGE_CC_START + i;
+ break;
+ }
+ AssertMsg(iKeyIndex > -1, ("Unsupported code in USB_HID_CC_PAGE! (%04X)\n", u16HidUsage));
+ }
+ else if (u8HidPage == USB_HID_DC_PAGE)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(aPS2DCKeys); ++i)
+ if (aPS2DCKeys[i].usageId == u16HidUsage)
+ {
+ pKeyDef = &aPS2DCKeys[i].kdef;
+ iKeyIndex = PS2K_PAGE_DC_START + i;
+ break;
+ }
+ AssertMsg(iKeyIndex > -1, ("Unsupported code in USB_HID_DC_PAGE! (%04X)\n", u16HidUsage));
+ }
+ else
+ {
+ AssertMsgFailed(("Unsupported u8HidPage! (%02X)\n", u8HidPage));
+ }
+
+ if (ppKeyDef)
+ *ppKeyDef = pKeyDef;
+
+ return iKeyIndex;
+}
+
+static uint32_t ps2kR3InternalCodeToHid(unsigned uKeyCode)
+{
+ uint16_t u16HidUsage;
+ uint32_t u32HidCode = 0;
+
+ if ((uKeyCode >= PS2K_PAGE_DC_START) && (uKeyCode <= PS2K_PAGE_DC_END))
+ {
+ u16HidUsage = aPS2DCKeys[uKeyCode - PS2K_PAGE_DC_START].usageId;
+ u32HidCode = RT_MAKE_U32(u16HidUsage, USB_HID_DC_PAGE);
+ }
+ else if ((uKeyCode >= PS2K_PAGE_CC_START) && (uKeyCode <= PS2K_PAGE_CC_END))
+ {
+ u16HidUsage = aPS2CCKeys[uKeyCode - PS2K_PAGE_CC_START].usageId;
+ u32HidCode = RT_MAKE_U32(u16HidUsage, USB_HID_CC_PAGE);
+ }
+ else /* Must be the keyboard usage page. */
+ {
+ if (uKeyCode <= VBOX_USB_MAX_USAGE_CODE)
+ u32HidCode = RT_MAKE_U32(uKeyCode, USB_HID_KB_PAGE);
+ else
+ AssertMsgFailed(("uKeyCode out of range! (%u)\n", uKeyCode));
+ }
+
+ return u32HidCode;
+}
+
+static int ps2kR3ProcessKeyEvent(PPDMDEVINS pDevIns, PPS2K pThis, uint32_t u32HidCode, bool fKeyDown)
+{
+ key_def const *pKeyDef;
+ uint8_t abCodes[16];
+ char *pCodes;
+ size_t cbLeft;
+ uint8_t abScan[2];
+ uint8_t u8HidPage;
+ uint16_t u16HidUsage;
+
+ u8HidPage = RT_LOBYTE(RT_HIWORD(u32HidCode));
+ u16HidUsage = RT_LOWORD(u32HidCode);
+
+ LogFlowFunc(("key %s: page 0x%02x ID 0x%04x (set %d)\n", fKeyDown ? "down" : "up", u8HidPage, u16HidUsage, pThis->u8ScanSet));
+
+ ps2kR3HidToInternalCode(u32HidCode, &pKeyDef);
+
+ /* 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 ((u8HidPage == USB_HID_KB_PAGE) && (u16HidUsage >= HID_MODIFIER_FIRST))
+ {
+ unsigned mod_bit = 1 << (u16HidUsage - HID_MODIFIER_FIRST);
+
+ Assert((u16HidUsage <= 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?
+ ps2kR3InsertStrQueue(&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?
+ ps2kR3InsertStrQueue(&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?
+ ps2kR3InsertStrQueue(&pThis->keyQ, abCodes, 0);
+ }
+
+ /* Set up or cancel typematic key repeat. For keyboard usage page only. */
+ if (u8HidPage == USB_HID_KB_PAGE)
+ {
+ if (fKeyDown)
+ {
+ if (pThis->u32TypematicKey != u32HidCode)
+ {
+ pThis->enmTypematicState = KBD_TMS_DELAY;
+ pThis->u32TypematicKey = u32HidCode;
+ PDMDevHlpTimerSetMillies(pDevIns, pThis->hKbdTypematicTimer, pThis->uTypematicDelay);
+ Log(("Typematic delay %u ms, key %08X\n", pThis->uTypematicDelay, u32HidCode));
+ }
+ }
+ 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->u32TypematicKey == u32HidCode)
+ {
+ /* This disables the typematic repeat. */
+ pThis->u32TypematicKey = 0;
+ pThis->enmTypematicState = KBD_TMS_IDLE;
+ /* For good measure, we cancel the timer, too. */
+ PDMDevHlpTimerStop(pDevIns, pThis->hKbdTypematicTimer);
+ Log(("Typematic action cleared for key %08X\n", u32HidCode));
+ }
+ }
+ }
+
+ /* Poke the KBC to update its state. */
+ KBCUpdateInterrupts(pDevIns);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV}
+ *
+ * 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
+ * responses) coming from PS/2 devices, both keyboard and auxiliary. That is not currently
+ * done because it would needlessly slow things down.
+ */
+static DECLCALLBACK(void) ps2kR3ThrottleTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PPS2K pThis = (PS2K *)pvUser;
+ unsigned uHaveData;
+ RT_NOREF(hTimer);
+
+ /* Grab the lock to avoid races with event delivery or EMTs. */
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_SEM_BUSY);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ /* If data is available, poke the KBC. Once the data
+ * is actually read, the timer may be re-triggered.
+ */
+ pThis->fThrottleActive = false;
+ uHaveData = PS2Q_COUNT(&pThis->keyQ);
+ LogFlowFunc(("Have%s bytes\n", uHaveData ? "" : " no"));
+ if (uHaveData)
+ KBCUpdateInterrupts(pDevIns);
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+}
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV,
+ * Timer handler for emulating typematic keys.}
+ *
+ * @note Note that only the last key held down repeats (if typematic).
+ */
+static DECLCALLBACK(void) ps2kR3TypematicTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PPS2K pThis = (PS2K *)pvUser;
+ Assert(hTimer == pThis->hKbdTypematicTimer);
+ LogFlowFunc(("Typematic state=%d, key %08X\n", pThis->enmTypematicState, pThis->u32TypematicKey));
+
+ /* 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->u32TypematicKey)
+ {
+ if (pThis->enmTypematicState == KBD_TMS_DELAY)
+ pThis->enmTypematicState = KBD_TMS_REPEAT;
+
+ if (pThis->enmTypematicState == KBD_TMS_REPEAT)
+ {
+ ps2kR3ProcessKeyEvent(pDevIns, pThis, pThis->u32TypematicKey, true /* Key down */ );
+ PDMDevHlpTimerSetMillies(pDevIns, hTimer, pThis->uTypematicRepeat);
+ }
+ }
+}
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV}
+ *
+ * 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) ps2kR3DelayTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PPS2K pThis = (PS2K *)pvUser;
+ RT_NOREF(hTimer);
+
+ LogFlowFunc(("Delay timer: cmd %02X\n", pThis->u8CurrCmd));
+
+ AssertMsg(pThis->u8CurrCmd == KCMD_RESET, ("u8CurrCmd=%02x\n", pThis->u8CurrCmd));
+ PS2Q_INSERT(&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(pDevIns);
+}
+
+/* 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 ps2kR3ReleaseKeys(PPDMDEVINS pDevIns, PPS2K pThis)
+{
+ LogFlowFunc(("Releasing keys...\n"));
+
+ for (unsigned uKey = 0; uKey < RT_ELEMENTS(pThis->abDepressedKeys); ++uKey)
+ if (pThis->abDepressedKeys[uKey])
+ {
+ ps2kR3ProcessKeyEvent(pDevIns, pThis, ps2kR3InternalCodeToHid(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) ps2kR3InfoState(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PKBDSTATE pParent = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ PPS2K pThis = &pParent->Kbd;
+ NOREF(pszArgs);
+
+ pHlp->pfnPrintf(pHlp, "PS/2 Keyboard: scan set %d, scanning %s, serial line %s\n",
+ pThis->u8ScanSet, pThis->fScanning ? "enabled" : "disabled",
+ pThis->fLineDisabled ? "disabled" : "enabled");
+ 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",
+ PS2Q_COUNT(&pThis->cmdQ), PS2Q_SIZE(&pThis->cmdQ));
+ pHlp->pfnPrintf(pHlp, "Input queue : %d items (%d max)\n",
+ PS2Q_COUNT(&pThis->keyQ), PS2Q_SIZE(&pThis->keyQ));
+ if (pThis->enmTypematicState != KBD_TMS_IDLE)
+ pHlp->pfnPrintf(pHlp, "Active typematic key %08X (%s)\n", pThis->u32TypematicKey,
+ pThis->enmTypematicState == KBD_TMS_DELAY ? "delay" : "repeat");
+}
+
+
+/* -=-=-=-=-=- Keyboard: IKeyboardPort -=-=-=-=-=- */
+
+/**
+ * Keyboard event handler.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The PS2 keyboard instance data.
+ * @param idUsage USB HID usage code with key press/release flag.
+ */
+static int ps2kR3PutEventWorker(PPDMDEVINS pDevIns, PPS2K pThis, uint32_t idUsage)
+{
+ uint32_t u32HidCode;
+ bool fKeyDown;
+ bool fHaveEvent = true;
+ int iKeyCode;
+ int rc = VINF_SUCCESS;
+
+ /* Extract the usage page and ID and ensure it's valid. */
+ fKeyDown = !(idUsage & PDMIKBDPORT_KEY_UP);
+ u32HidCode = idUsage & 0xFFFFFF;
+
+ iKeyCode = ps2kR3HidToInternalCode(u32HidCode, NULL);
+ AssertMsgReturn(iKeyCode > 0 && iKeyCode <= VBOX_USB_MAX_USAGE_CODE, ("iKeyCode=%#x idUsage=%#x\n", iKeyCode, idUsage),
+ 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[iKeyCode])
+ fHaveEvent = false;
+ pThis->abDepressedKeys[iKeyCode] = 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[iKeyCode] = 0;
+ }
+
+ /* Unless this is a new key press/release, don't even bother. */
+ if (fHaveEvent)
+ {
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, pDevIns->pCritSectRoR3));
+ rc = ps2kR3ProcessKeyEvent(pDevIns, pThis, u32HidCode, fKeyDown);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIKEYBOARDPORT,pfnPutEventHid}
+ */
+static DECLCALLBACK(int) ps2kR3KeyboardPort_PutEventHid(PPDMIKEYBOARDPORT pInterface, uint32_t idUsage)
+{
+ PPS2KR3 pThisCC = RT_FROM_MEMBER(pInterface, PS2KR3, Keyboard.IPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PPS2K pThis = &PDMDEVINS_2_DATA(pDevIns, PKBDSTATE)->Kbd;
+
+ LogRelFlowFunc(("key code %08X\n", idUsage));
+
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_SEM_BUSY);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ /* The 'BAT fail' scancode is reused as a signal to release keys. No actual
+ * key is allowed to use this scancode.
+ */
+ if (RT_LIKELY(!(idUsage & PDMIKBDPORT_RELEASE_KEYS)))
+ ps2kR3PutEventWorker(pDevIns, pThis, idUsage);
+ else
+ ps2kR3ReleaseKeys(pDevIns, pThis);
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+
+ return VINF_SUCCESS;
+}
+
+
+/* -=-=-=-=-=- Keyboard: IBase -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ps2kR3QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPS2KR3 pThisCC = RT_FROM_MEMBER(pInterface, PS2KR3, Keyboard.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->Keyboard.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIKEYBOARDPORT, &pThisCC->Keyboard.IPort);
+ return NULL;
+}
+
+
+/* -=-=-=-=-=- Device management -=-=-=-=-=- */
+
+/**
+ * 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 pDevIns The device instance.
+ * @param pThisCC The PS/2 keyboard instance data for ring-3.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+int PS2KR3Attach(PPDMDEVINS pDevIns, PPS2KR3 pThisCC, 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, &pThisCC->Keyboard.IBase, &pThisCC->Keyboard.pDrvBase, "Keyboard Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThisCC->Keyboard.pDrv = PDMIBASE_QUERY_INTERFACE(pThisCC->Keyboard.pDrvBase, PDMIKEYBOARDCONNECTOR);
+ if (!pThisCC->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 PS2KR3SaveState(PPDMDEVINS pDevIns, PPS2K pThis, PSSMHANDLE pSSM)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ uint32_t cPressed = 0;
+ uint32_t cbTMSSize = 0;
+
+ LogFlowFunc(("Saving PS2K state\n"));
+
+ /* Save the basic keyboard state. */
+ pHlp->pfnSSMPutU8(pSSM, pThis->u8CurrCmd);
+ pHlp->pfnSSMPutU8(pSSM, pThis->u8LEDs);
+ pHlp->pfnSSMPutU8(pSSM, pThis->u8TypematicCfg);
+ pHlp->pfnSSMPutU8(pSSM, (uint8_t)pThis->u32TypematicKey);
+ pHlp->pfnSSMPutU8(pSSM, pThis->u8Modifiers);
+ pHlp->pfnSSMPutU8(pSSM, pThis->u8ScanSet);
+ pHlp->pfnSSMPutU8(pSSM, pThis->enmTypematicState);
+ pHlp->pfnSSMPutBool(pSSM, pThis->fNumLockOn);
+ pHlp->pfnSSMPutBool(pSSM, pThis->fScanning);
+
+ /* Save the command and keystroke queues. */
+ PS2Q_SAVE(pHlp, pSSM, &pThis->cmdQ);
+ PS2Q_SAVE(pHlp, pSSM, &pThis->keyQ);
+
+ /* Save the command delay timer. Note that the typematic repeat
+ * timer is *not* saved.
+ */
+ PDMDevHlpTimerSave(pDevIns, pThis->hKbdDelayTimer, 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;
+
+ pHlp->pfnSSMPutU32(pSSM, cPressed);
+
+ for (unsigned uKey = 0; uKey < sizeof(pThis->abDepressedKeys); ++uKey)
+ if (pThis->abDepressedKeys[uKey])
+ pHlp->pfnSSMPutU8(pSSM, uKey);
+
+ /* Save the typematic settings for Scan Set 3. */
+ pHlp->pfnSSMPutU32(pSSM, cbTMSSize);
+ /* Currently not implemented. */
+}
+
+int PS2KR3LoadState(PPDMDEVINS pDevIns, PPS2K pThis, PSSMHANDLE pSSM, uint32_t uVersion)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ 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. */
+ pHlp->pfnSSMGetU8(pSSM, &pThis->u8CurrCmd);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->u8LEDs);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->u8TypematicCfg);
+ pHlp->pfnSSMGetU8(pSSM, &u8);
+ /* Reconstruct the 32-bit code from the 8-bit value in saved state. */
+ pThis->u32TypematicKey = u8 ? ps2kR3InternalCodeToHid(u8) : 0;
+ pHlp->pfnSSMGetU8(pSSM, &pThis->u8Modifiers);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->u8ScanSet);
+ pHlp->pfnSSMGetU8(pSSM, &u8);
+ pThis->enmTypematicState = (tmatic_state_t)u8;
+ pHlp->pfnSSMGetBool(pSSM, &pThis->fNumLockOn);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->fScanning);
+
+ /* Load the command and keystroke queues. */
+ rc = PS2Q_LOAD(pHlp, pSSM, &pThis->cmdQ);
+ AssertRCReturn(rc, rc);
+ rc = PS2Q_LOAD(pHlp, pSSM, &pThis->keyQ);
+ AssertRCReturn(rc, rc);
+
+ /* Load the command delay timer, just in case. */
+ rc = PDMDevHlpTimerLoad(pDevIns, pThis->hKbdDelayTimer, 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 = pHlp->pfnSSMGetU32(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 = pHlp->pfnSSMGetU8(pSSM, &u8);
+ AssertRCReturn(rc, rc);
+ pThis->abDepressedKeys[u8] = 1;
+ }
+ }
+
+ /* Load typematic settings for Scan Set 3. */
+ rc = pHlp->pfnSSMGetU32(pSSM, &cbTMSSize);
+ AssertRCReturn(rc, rc);
+
+ while (cbTMSSize--)
+ {
+ rc = pHlp->pfnSSMGetU8(pSSM, &u8);
+ AssertRCReturn(rc, rc);
+ }
+
+ return rc;
+}
+
+int PS2KR3LoadDone(PPDMDEVINS pDevIns, PPS2K pThis, PPS2KR3 pThisCC)
+{
+ /* This *must* be done after the inital load because it may trigger
+ * interrupts and change the interrupt controller state.
+ */
+ ps2kR3ReleaseKeys(pDevIns, pThis);
+ ps2kR3NotifyLedsState(pThisCC, pThis->u8LEDs);
+ return VINF_SUCCESS;
+}
+
+void PS2KR3Reset(PPDMDEVINS pDevIns, PPS2K pThis, PPS2KR3 pThisCC)
+{
+ LogFlowFunc(("Resetting PS2K\n"));
+
+ pThis->fScanning = true;
+ pThis->fThrottleActive = false;
+ pThis->u8ScanSet = 2;
+ pThis->u8CurrCmd = 0;
+ pThis->u8Modifiers = 0;
+ pThis->u32TypematicKey = 0;
+ pThis->enmTypematicState = KBD_TMS_IDLE;
+
+ /* Clear queues and any pressed keys. */
+ memset(pThis->abDepressedKeys, 0, sizeof(pThis->abDepressedKeys));
+ PS2Q_CLEAR(&pThis->cmdQ);
+ ps2kSetDefaults(pDevIns, pThis); /* Also clears keystroke queue. */
+
+ /* Activate the PS/2 keyboard by default. */
+ if (pThisCC->Keyboard.pDrv)
+ pThisCC->Keyboard.pDrv->pfnSetActive(pThisCC->Keyboard.pDrv, true /*fActive*/);
+}
+
+int PS2KR3Construct(PPDMDEVINS pDevIns, PPS2K pThis, PPS2KR3 pThisCC, PCFGMNODE pCfg)
+{
+ LogFlowFunc(("\n"));
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ /*
+ * Read configuration.
+ */
+ bool fThrottleEnabled;
+ int rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "KbdThrottleEnabled", &fThrottleEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"KbdThrottleEnabled\" from the config"));
+ Log(("KbdThrottleEnabled=%RTbool\n", fThrottleEnabled));
+ pThis->fThrottleEnabled = fThrottleEnabled;
+
+ /*
+ * Initialize state.
+ */
+ pThisCC->pDevIns = pDevIns;
+ pThisCC->Keyboard.IBase.pfnQueryInterface = ps2kR3QueryInterface;
+ pThisCC->Keyboard.IPort.pfnPutEventHid = ps2kR3KeyboardPort_PutEventHid;
+
+ pThis->cmdQ.Hdr.pszDescR3 = "Kbd Cmd";
+ pThis->keyQ.Hdr.pszDescR3 = "Kbd Key";
+
+ /*
+ * Create the input rate throttling timer.
+ */
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ps2kR3ThrottleTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_RING0,
+ "PS2K Throttle", &pThis->hThrottleTimer);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create the typematic delay/repeat timer.
+ */
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ps2kR3TypematicTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_RING0,
+ "PS2K Typematic", &pThis->hKbdTypematicTimer);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create the command delay timer.
+ */
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ps2kR3DelayTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_RING0,
+ "PS2K Delay", &pThis->hKbdDelayTimer);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register debugger info callbacks.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ps2k", "Display PS/2 keyboard state.", ps2kR3InfoState);
+
+ return rc;
+}
+
+#endif /* IN _RING3 */
+
diff --git a/src/VBox/Devices/Input/DevPS2M.cpp b/src/VBox/Devices/Input/DevPS2M.cpp
new file mode 100644
index 00000000..1d274e88
--- /dev/null
+++ b/src/VBox/Devices/Input/DevPS2M.cpp
@@ -0,0 +1,1196 @@
+/* $Id: DevPS2M.cpp $ */
+/** @file
+ * PS2M - PS/2 auxiliary device (mouse) emulation.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * 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.
+ *
+ * There is an extended ImEx mode with support for horizontal scrolling. It is
+ * entered from ImEx mode with a 200, 80, 40 sequence of Set Sampling Rate
+ * commands. It does not change the reported protocol (it remains 4, or ImEx)
+ * but changes the meaning of the 4th byte.
+ *
+ *
+ * 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) |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ *
+ * - The Z delta values are in practice only -1/+1; some mice (A4tech?) report
+ * horizontal scrolling as -2/+2.
+ *
+ * IntelliMouse Explorer (ImEx) fourth report packet byte when scrolling:
+ *
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ * |Bit/byte| bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ * | Byte 4 | V | H | Z or W movement delta (two's complement) |
+ * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
+ *
+ * - Buttons 4 and 5 are reported as with the regular ImEx protocol, but not when
+ * scrolling. This is a departure from the usual logic because when the mouse
+ * sends scroll events, the state of buttons 4/5 is not reported and the last
+ * reported state should be assumed.
+ *
+ * - When the V bit (bit 7) is set, vertical scroll (Z axis) is being reported.
+ * When the H bit (bit 6) is set, horizontal scroll (W axis) is being reported.
+ * The H and V bits are never set at the same time (also see below). When
+ * the H and V bits are both clear, button 4/5 state is being reported.
+ *
+ * - The Z/W delta is extended to 6 bits. Z (vertical) values are not restricted
+ * to -1/+1, although W (horizontal) values are. Z values of at least -20/+20
+ * can be seen in practice.
+ *
+ * - Horizontal and vertical scroll is mutually exclusive. When the button is
+ * tilted, no vertical scrolling is reported, i.e. horizontal scrolling
+ * has priority over vertical.
+ *
+ * - Positive values indicate down/right direction, negative values up/left.
+ *
+ * - When the scroll button is tilted to engage horizontal scrolling, the mouse
+ * keeps sending events at a rate of 4 or 5 per second as long as the button
+ * is tilted.
+ *
+ * All report formats were verified with a real Microsoft IntelliMouse Explorer 4.0
+ * mouse attached through a PS/2 port.
+ *
+ * The button "accumulator" is necessary to avoid missing brief button presses.
+ * Without it, a very fast mouse button press + release might be lost if it
+ * happened between sending reports. The accumulator latches button presses to
+ * prevent that.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* 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 "DevPS2.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. */
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Test code function declarations *
+*********************************************************************************************************************************/
+#if defined(RT_STRICT) && defined(IN_RING3)
+static void ps2mR3TestAccumulation(void);
+#endif
+
+
+#ifdef IN_RING3
+
+/* Report a change in status down (or is it up?) the driver chain. */
+static void ps2mR3SetDriverState(PPS2MR3 pThisCC, bool fEnabled)
+{
+ PPDMIMOUSECONNECTOR pDrv = pThisCC->Mouse.pDrv;
+ if (pDrv)
+ pDrv->pfnReportModes(pDrv, fEnabled, false, false, false);
+}
+
+/* Reset the pointing device. */
+static void ps2mR3Reset(PPS2M pThis, PPS2MR3 pThisCC)
+{
+ LogFlowFunc(("Reset"));
+
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_BAT_OK);
+ PS2Q_INSERT(&pThis->cmdQ, 0);
+ pThis->enmMode = AUX_MODE_STD;
+ pThis->u8CurrCmd = 0;
+
+ /// @todo move to its proper home!
+ ps2mR3SetDriverState(pThisCC, true);
+}
+
+#endif /* IN_RING3 */
+
+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. */
+ PS2Q_CLEAR(&pThis->evtQ);
+ pThis->iAccumX = pThis->iAccumY = pThis->iAccumZ = pThis->iAccumW = pThis->fAccumB = 0;
+}
+
+/* Handle the sampling rate 'knock' sequence which selects protocol. */
+static void ps2mRateProtocolKnock(PPS2M pThis, uint8_t rate)
+{
+ PS2M_PROTO enmOldProtocol = pThis->enmProtocol;
+ LogFlowFunc(("rate=%u\n", 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 if (rate == 80)
+ pThis->enmKnockState = PS2M_KNOCK_IMEX_HORZ_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"));
+ }
+ pThis->enmKnockState = PS2M_KNOCK_INITIAL;
+ break;
+ case PS2M_KNOCK_IMEX_HORZ_2ND:
+ if (rate == 40)
+ {
+ pThis->enmProtocol = PS2M_PROTO_IMEX_HORZ;
+ LogRelFlow(("PS2M: Switching mouse ImEx with horizontal scrolling.\n"));
+ }
+ RT_FALL_THRU();
+ default:
+ pThis->enmKnockState = PS2M_KNOCK_INITIAL;
+ }
+
+ /* If the protocol changed, throw away any queued input because it now
+ * has the wrong format, which could severely confuse the guest.
+ */
+ if (enmOldProtocol != pThis->enmProtocol)
+ PS2Q_CLEAR(&pThis->evtQ);
+}
+
+/* Three-button event mask. */
+#define PS2M_STD_BTN_MASK (RT_BIT(0) | RT_BIT(1) | RT_BIT(2))
+/* ImEx button 4/5 event mask. */
+#define PS2M_IMEX_BTN_MASK (RT_BIT(3) | RT_BIT(4))
+
+/** Report accumulated movement and button presses, then clear the accumulators. */
+static void ps2mReportAccumulatedEvents(PPS2M pThis, PPS2QHDR pQHdr, size_t cQElements, uint8_t *pbQElements, bool fAccumBtns)
+{
+ uint32_t fBtnState = fAccumBtns ? pThis->fAccumB : pThis->fCurrB;
+ uint8_t val;
+ int dX, dY, dZ, dW;
+
+ LogFlowFunc(("cQElements=%zu, fAccumBtns=%RTbool\n", cQElements, fAccumBtns));
+
+ /* 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);
+
+ /* 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). */
+ LogFlowFunc(("Queuing standard 3-byte packet\n"));
+ PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, val);
+ PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, dX);
+ PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, dY);
+
+ /* Add fourth byte if an extended protocol is in use. */
+ if (pThis->enmProtocol > PS2M_PROTO_PS2STD)
+ {
+ /* Start out with 4-bit dZ range. */
+ dZ = RT_MIN(RT_MAX(pThis->iAccumZ, -8), 7);
+
+ if (pThis->enmProtocol == PS2M_PROTO_IMPS2)
+ {
+ /* NB: Only uses 4-bit dZ range, despite using a full byte. */
+ LogFlowFunc(("Queuing ImPS/2 last byte\n"));
+ PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, dZ);
+ pThis->iAccumZ -= dZ;
+ }
+ else if (pThis->enmProtocol == PS2M_PROTO_IMEX)
+ {
+ /* Z value uses 4 bits; buttons 4/5 in bits 4 and 5. */
+ val = (fBtnState & PS2M_IMEX_BTN_MASK) << 1;
+ val |= dZ & 0x0f;
+ pThis->iAccumZ -= dZ;
+ LogFlowFunc(("Queuing ImEx last byte\n"));
+ PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, val);
+ }
+ else
+ {
+ Assert((pThis->enmProtocol == PS2M_PROTO_IMEX_HORZ));
+ /* With ImEx + horizontal reporting, prioritize buttons 4/5. */
+ if (pThis->iAccumZ || pThis->iAccumW)
+ {
+ /* ImEx + horizontal reporting Horizontal scroll has
+ * precedence over vertical. Buttons cannot be reported
+ * this way.
+ */
+ if (pThis->iAccumW)
+ {
+ dW = RT_MIN(RT_MAX(pThis->iAccumW, -32), 31);
+ val = (dW & 0x3F) | 0x40;
+ pThis->iAccumW -= dW;
+ }
+ else
+ {
+ Assert(pThis->iAccumZ);
+ /* We can use 6-bit dZ range. Wow! */
+ dZ = RT_MIN(RT_MAX(pThis->iAccumZ, -32), 31);
+ val = (dZ & 0x3F) | 0x80;
+ pThis->iAccumZ -= dZ;
+ }
+ }
+ else
+ {
+ /* Just Buttons 4/5 in bits 4 and 5. No scrolling. */
+ val = (fBtnState & PS2M_IMEX_BTN_MASK) << 1;
+ }
+ LogFlowFunc(("Queuing ImEx+horz last byte\n"));
+ PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, val);
+ }
+ }
+
+ /* Clear the movement accumulators, but not necessarily button state. */
+ pThis->iAccumX = pThis->iAccumY = 0;
+ /* Clear accumulated button state only when it's being used. */
+ if (fAccumBtns)
+ {
+ pThis->fReportedB = pThis->fCurrB | 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;
+}
+
+
+/**
+ * The keyboard controller disabled the auxiliary serial line.
+ *
+ * @param pThis The PS/2 auxiliary device shared instance data.
+ */
+void PS2MLineDisable(PPS2M pThis)
+{
+ LogFlowFunc(("Disabling mouse serial line\n"));
+
+ pThis->fLineDisabled = true;
+}
+
+/**
+ * The keyboard controller enabled the auxiliary serial line.
+ *
+ * @param pThis The PS/2 auxiliary device shared instance data.
+ */
+void PS2MLineEnable(PPS2M pThis)
+{
+ LogFlowFunc(("Enabling mouse serial line\n"));
+
+ pThis->fLineDisabled = false;
+
+ /* If there was anything in the input queue,
+ * consider it lost and throw it away.
+ */
+ PS2Q_CLEAR(&pThis->evtQ);
+}
+
+
+/**
+ * Receive and process a byte sent by the keyboard controller.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The PS/2 auxiliary device shared instance data.
+ * @param cmd The command (or data) byte.
+ */
+int PS2MByteToAux(PPDMDEVINS pDevIns, 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. */
+ PS2Q_CLEAR(&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
+ {
+ PS2Q_INSERT(&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;
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_SET_SCALE_21:
+ pThis->u8State |= AUX_STATE_SCALING;
+ PS2Q_INSERT(&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);
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
+ PS2Q_INSERT(&pThis->cmdQ, u8Val);
+ PS2Q_INSERT(&pThis->cmdQ, pThis->u8Resolution);
+ PS2Q_INSERT(&pThis->cmdQ, pThis->u8SampleRate);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_SET_STREAM:
+ pThis->u8State &= ~AUX_STATE_REMOTE;
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_READ_REMOTE:
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
+ ps2mReportAccumulatedEvents(pThis, &pThis->cmdQ.Hdr, RT_ELEMENTS(pThis->cmdQ.abQueue), pThis->cmdQ.abQueue, false);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_RESET_WRAP:
+ pThis->enmMode = AUX_MODE_STD;
+ /* NB: Stream mode reporting remains disabled! */
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_SET_WRAP:
+ pThis->enmMode = AUX_MODE_WRAP;
+ pThis->u8State &= ~AUX_STATE_ENABLED;
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_SET_REMOTE:
+ pThis->u8State |= AUX_STATE_REMOTE;
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_READ_ID:
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
+ /* ImEx + horizontal is protocol 4, just like plain ImEx. */
+ u8Val = pThis->enmProtocol == PS2M_PROTO_IMEX_HORZ ? PS2M_PROTO_IMEX : pThis->enmProtocol;
+ PS2Q_INSERT(&pThis->cmdQ, u8Val);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_ENABLE:
+ pThis->u8State |= AUX_STATE_ENABLED;
+#ifdef IN_RING3
+ ps2mR3SetDriverState(&PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3)->Aux, true);
+#else
+ AssertLogRelMsgFailed(("Invalid ACMD_ENABLE outside R3!\n"));
+#endif
+ PS2Q_CLEAR(&pThis->evtQ);
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_DISABLE:
+ pThis->u8State &= ~AUX_STATE_ENABLED;
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
+ pThis->u8CurrCmd = 0;
+ break;
+ case ACMD_SET_DEFAULT:
+ ps2mSetDefaults(pThis);
+ PS2Q_INSERT(&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;
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
+ if (pThis->fDelayReset)
+ /* Slightly delay reset completion; it might take hundreds of ms. */
+ PDMDevHlpTimerSetMillies(pDevIns, pThis->hDelayTimer, 1);
+ else
+#ifdef IN_RING3
+ ps2mR3Reset(pThis, &PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3)->Aux);
+#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:
+ PS2Q_INSERT(&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;
+ PS2Q_INSERT(&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;
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ERROR);
+ pThis->u8CurrCmd = 0;
+ }
+ else
+ {
+ pThis->u8State |= AUX_STATE_RES_ERR;
+ PS2Q_INSERT(&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);
+ PS2Q_INSERT(&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;
+ PS2Q_INSERT(&pThis->cmdQ, ARSP_ERROR);
+ pThis->u8CurrCmd = 0;
+ }
+ else
+ {
+ pThis->u8State |= AUX_STATE_RATE_ERR;
+ PS2Q_INSERT(&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));
+ PS2Q_INSERT(&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 shared 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 = PS2Q_REMOVE(&pThis->cmdQ, pb);
+ if (rc != VINF_SUCCESS && !pThis->u8CurrCmd && (pThis->u8State & AUX_STATE_ENABLED))
+ rc = PS2Q_REMOVE(&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 ps2mR3HaveEvents(PPS2M pThis)
+{
+/** @todo r=bird: Why is this returning uint32_t when you're calculating a
+ * boolean value here? Also, it's a predicate function... */
+ return pThis->iAccumX || pThis->iAccumY || pThis->iAccumZ || pThis->iAccumW
+ || ((pThis->fCurrB | pThis->fAccumB) != pThis->fReportedB);
+}
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV,
+ * Event rate throttling timer to emulate the auxiliary device sampling rate.}
+ */
+static DECLCALLBACK(void) ps2mR3ThrottleTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PPS2M pThis = (PS2M *)pvUser;
+ uint32_t uHaveEvents;
+ Assert(hTimer == pThis->hThrottleTimer);
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, pDevIns->pCritSectRoR3));
+
+ /* If more movement is accumulated, report it and restart the timer. */
+ uHaveEvents = ps2mR3HaveEvents(pThis);
+ LogFlowFunc(("Have%s events\n", uHaveEvents ? "" : " no"));
+
+ if (uHaveEvents)
+ {
+ /* Report accumulated data, poke the KBC, and start the timer. */
+ ps2mReportAccumulatedEvents(pThis, &pThis->evtQ.Hdr, RT_ELEMENTS(pThis->evtQ.abQueue), pThis->evtQ.abQueue, true);
+ KBCUpdateInterrupts(pDevIns);
+ PDMDevHlpTimerSetMillies(pDevIns, hTimer, pThis->uThrottleDelay);
+ }
+ else
+ pThis->fThrottleActive = false;
+}
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV}
+ *
+ * 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) ps2mR3DelayTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PPS2M pThis = &PDMDEVINS_2_DATA(pDevIns, PKBDSTATE)->Aux;
+ PPS2MR3 pThisCC = &PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3)->Aux;
+ RT_NOREF(pvUser, hTimer);
+
+ LogFlowFunc(("Delay timer: cmd %02X\n", pThis->u8CurrCmd));
+
+ Assert(pThis->u8CurrCmd == ACMD_RESET);
+ ps2mR3Reset(pThis, pThisCC);
+
+ /// @todo Might want a PS2MCompleteCommand() to push last response, clear command, and kick the KBC...
+ /* Give the KBC a kick. */
+ KBCUpdateInterrupts(pDevIns);
+}
+
+
+/**
+ * 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) ps2mR3InfoState(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ static const char * const s_apcszModes[] = { "normal", "reset", "wrap" };
+ static const char * const s_apcszProtocols[] = { "PS/2", NULL, NULL, "ImPS/2", "ImEx", "ImEx+horizontal" };
+ PKBDSTATE pParent = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
+ PPS2M pThis = &pParent->Aux;
+ NOREF(pszArgs);
+
+ Assert(pThis->enmMode < RT_ELEMENTS(s_apcszModes));
+ pHlp->pfnPrintf(pHlp, "PS/2 mouse state: %s, %s mode, reporting %s, serial line %s\n",
+ s_apcszModes[pThis->enmMode],
+ pThis->u8State & AUX_STATE_REMOTE ? "remote" : "stream",
+ pThis->u8State & AUX_STATE_ENABLED ? "enabled" : "disabled",
+ pThis->fLineDisabled ? "disabled" : "enabled");
+ Assert(pThis->enmProtocol < RT_ELEMENTS(s_apcszProtocols));
+ pHlp->pfnPrintf(pHlp, "Protocol: %s, scaling %u:1\n",
+ s_apcszProtocols[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",
+ PS2Q_COUNT(&pThis->cmdQ), PS2Q_SIZE(&pThis->cmdQ));
+ pHlp->pfnPrintf(pHlp, "Event queue : %d items (%d max)\n",
+ PS2Q_COUNT(&pThis->evtQ), PS2Q_SIZE(&pThis->evtQ));
+}
+
+
+/* -=-=-=-=-=- Mouse: IMousePort -=-=-=-=-=- */
+
+/**
+ * Mouse event handler.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The PS/2 auxiliary device shared 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 ps2mR3PutEventWorker(PPDMDEVINS pDevIns, PPS2M pThis, int32_t dx, int32_t dy, int32_t dz, int32_t dw, uint32_t fButtons)
+{
+ LogFlowFunc(("dx=%d, dy=%d, dz=%d, dw=%d, fButtons=%X\n", dx, dy, dz, dw, fButtons));
+
+ /* Update internal accumulators and button state. Ignore any buttons beyond 5. */
+ pThis->iAccumX += dx;
+ pThis->iAccumY += dy;
+ pThis->iAccumZ += dz;
+ pThis->iAccumW += dw;
+ pThis->fCurrB = fButtons & (PS2M_STD_BTN_MASK | PS2M_IMEX_BTN_MASK);
+ pThis->fAccumB |= pThis->fCurrB;
+
+ /* Ditch accumulated data that can't be reported by the current protocol.
+ * This avoids sending phantom empty reports when un-reportable events
+ * are received.
+ */
+ if (pThis->enmProtocol < PS2M_PROTO_IMEX_HORZ)
+ pThis->iAccumW = 0; /* No horizontal scroll. */
+
+ if (pThis->enmProtocol < PS2M_PROTO_IMEX)
+ {
+ pThis->fAccumB &= PS2M_STD_BTN_MASK; /* Only buttons 1-3. */
+ pThis->fCurrB &= PS2M_STD_BTN_MASK;
+ }
+
+ if (pThis->enmProtocol < PS2M_PROTO_IMPS2)
+ pThis->iAccumZ = 0; /* No vertical scroll. */
+
+ /* Report the event (if any) and start the throttle timer unless it's already running. */
+ if (!pThis->fThrottleActive && ps2mR3HaveEvents(pThis))
+ {
+ ps2mReportAccumulatedEvents(pThis, &pThis->evtQ.Hdr, RT_ELEMENTS(pThis->evtQ.abQueue), pThis->evtQ.abQueue, true);
+ KBCUpdateInterrupts(pDevIns);
+ pThis->fThrottleActive = true;
+ PDMDevHlpTimerSetMillies(pDevIns, pThis->hThrottleTimer, pThis->uThrottleDelay);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/* -=-=-=-=-=- Mouse: IMousePort -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEvent}
+ */
+static DECLCALLBACK(int) ps2mR3MousePort_PutEvent(PPDMIMOUSEPORT pInterface, int32_t dx, int32_t dy,
+ int32_t dz, int32_t dw, uint32_t fButtons)
+{
+ PPS2MR3 pThisCC = RT_FROM_MEMBER(pInterface, PS2MR3, Mouse.IPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PPS2M pThis = &PDMDEVINS_2_DATA(pDevIns, PKBDSTATE)->Aux;
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_SEM_BUSY);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ 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. */
+ ps2mR3PutEventWorker(pDevIns, pThis, dx, -dy, dz, dw, fButtons);
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventAbs}
+ */
+static DECLCALLBACK(int) ps2mR3MousePort_PutEventAbs(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,pfnPutEventTouchScreen}
+ */
+static DECLCALLBACK(int) ps2mR3MousePort_PutEventMTAbs(PPDMIMOUSEPORT pInterface, uint8_t cContacts,
+ const uint64_t *pau64Contacts, uint32_t u32ScanTime)
+{
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+ NOREF(pInterface); NOREF(cContacts); NOREF(pau64Contacts); NOREF(u32ScanTime);
+}
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventTouchPad}
+ */
+static DECLCALLBACK(int) ps2mR3MousePort_PutEventMTRel(PPDMIMOUSEPORT pInterface, uint8_t cContacts,
+ const uint64_t *pau64Contacts, uint32_t u32ScanTime)
+{
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+ NOREF(pInterface); NOREF(cContacts); NOREF(pau64Contacts); NOREF(u32ScanTime);
+}
+
+
+/* -=-=-=-=-=- Mouse: IBase -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ps2mR3QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPS2MR3 pThisCC = RT_FROM_MEMBER(pInterface, PS2MR3, Mouse.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->Mouse.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSEPORT, &pThisCC->Mouse.IPort);
+ return NULL;
+}
+
+
+/* -=-=-=-=-=- Device management -=-=-=-=-=- */
+
+/**
+ * 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 pDevIns The device instance.
+ * @param pThisCC The PS/2 auxiliary device instance data for ring-3.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+int PS2MR3Attach(PPDMDEVINS pDevIns, PPS2MR3 pThisCC, 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, &pThisCC->Mouse.IBase, &pThisCC->Mouse.pDrvBase, "Mouse Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThisCC->Mouse.pDrv = PDMIBASE_QUERY_INTERFACE(pThisCC->Mouse.pDrvBase, PDMIMOUSECONNECTOR);
+ if (!pThisCC->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 PS2MR3SaveState(PPDMDEVINS pDevIns, PPS2M pThis, PSSMHANDLE pSSM)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ LogFlowFunc(("Saving PS2M state\n"));
+
+ /* Save the core auxiliary device state. */
+ pHlp->pfnSSMPutU8(pSSM, pThis->u8State);
+ pHlp->pfnSSMPutU8(pSSM, pThis->u8SampleRate);
+ pHlp->pfnSSMPutU8(pSSM, pThis->u8Resolution);
+ pHlp->pfnSSMPutU8(pSSM, pThis->u8CurrCmd);
+ pHlp->pfnSSMPutU8(pSSM, pThis->enmMode);
+ pHlp->pfnSSMPutU8(pSSM, pThis->enmProtocol);
+ pHlp->pfnSSMPutU8(pSSM, pThis->enmKnockState);
+
+ /* Save the command and event queues. */
+ PS2Q_SAVE(pHlp, pSSM, &pThis->cmdQ);
+ PS2Q_SAVE(pHlp, pSSM, &pThis->evtQ);
+
+ /* Save the command delay timer. Note that the rate throttling
+ * timer is *not* saved.
+ */
+ PDMDevHlpTimerSave(pDevIns, pThis->hDelayTimer, pSSM);
+}
+
+int PS2MR3LoadState(PPDMDEVINS pDevIns, PPS2M pThis, PPS2MR3 pThisCC, PSSMHANDLE pSSM, uint32_t uVersion)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ uint8_t u8;
+ int rc;
+
+ NOREF(uVersion);
+ LogFlowFunc(("Loading PS2M state version %u\n", uVersion));
+
+ /* Load the basic auxiliary device state. */
+ pHlp->pfnSSMGetU8(pSSM, &pThis->u8State);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->u8SampleRate);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->u8Resolution);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->u8CurrCmd);
+ pHlp->pfnSSMGetU8(pSSM, &u8);
+ pThis->enmMode = (PS2M_MODE)u8;
+ pHlp->pfnSSMGetU8(pSSM, &u8);
+ pThis->enmProtocol = (PS2M_PROTO)u8;
+ pHlp->pfnSSMGetU8(pSSM, &u8);
+ pThis->enmKnockState = (PS2M_KNOCK_STATE)u8;
+
+ /* Load the command and event queues. */
+ rc = PS2Q_LOAD(pHlp, pSSM, &pThis->cmdQ);
+ AssertRCReturn(rc, rc);
+ rc = PS2Q_LOAD(pHlp, pSSM, &pThis->evtQ);
+ AssertRCReturn(rc, rc);
+
+ /* Load the command delay timer, just in case. */
+ rc = PDMDevHlpTimerLoad(pDevIns, pThis->hDelayTimer, pSSM);
+ AssertRCReturn(rc, rc);
+
+ /* Recalculate the throttling delay. */
+ ps2mSetRate(pThis, pThis->u8SampleRate);
+
+ ps2mR3SetDriverState(pThisCC, !!(pThis->u8State & AUX_STATE_ENABLED));
+
+ return VINF_SUCCESS;
+}
+
+void PS2MR3FixupState(PPS2M pThis, PPS2MR3 pThisCC, 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);
+
+ ps2mR3SetDriverState(pThisCC, !!(pThis->u8State & AUX_STATE_ENABLED));
+}
+
+void PS2MR3Reset(PPS2M pThis)
+{
+ LogFlowFunc(("Resetting PS2M\n"));
+
+ pThis->u8CurrCmd = 0;
+
+ /* Clear the queues. */
+ PS2Q_CLEAR(&pThis->cmdQ);
+ ps2mSetDefaults(pThis); /* Also clears event queue. */
+}
+
+int PS2MR3Construct(PPDMDEVINS pDevIns, PPS2M pThis, PPS2MR3 pThisCC)
+{
+ LogFlowFunc(("\n"));
+
+ pThis->cmdQ.Hdr.pszDescR3 = "Aux Cmd";
+ pThis->evtQ.Hdr.pszDescR3 = "Aux Evt";
+
+#ifdef RT_STRICT
+ ps2mR3TestAccumulation();
+#endif
+
+ /*
+ * Initialize the state.
+ */
+ pThisCC->pDevIns = pDevIns;
+ pThisCC->Mouse.IBase.pfnQueryInterface = ps2mR3QueryInterface;
+ pThisCC->Mouse.IPort.pfnPutEvent = ps2mR3MousePort_PutEvent;
+ pThisCC->Mouse.IPort.pfnPutEventAbs = ps2mR3MousePort_PutEventAbs;
+ pThisCC->Mouse.IPort.pfnPutEventTouchScreen = ps2mR3MousePort_PutEventMTAbs;
+ pThisCC->Mouse.IPort.pfnPutEventTouchPad = ps2mR3MousePort_PutEventMTRel;
+
+ /*
+ * Create the input rate throttling timer. Does not use virtual time!
+ */
+ int rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_REAL, ps2mR3ThrottleTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_NO_RING0,
+ "PS2M Throttle", &pThis->hThrottleTimer);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create the command delay timer.
+ */
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ps2mR3DelayTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_RING0, "PS2M Delay", &pThis->hDelayTimer);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register debugger info callbacks.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ps2m", "Display PS/2 mouse state.", ps2mR3InfoState);
+
+ /// @todo Where should we do this?
+ ps2mR3SetDriverState(pThisCC, 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 ps2mR3PutEventWorker() 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 ps2mR3TestAccumulation(void)
+{
+ PS2M This;
+ unsigned i;
+ int rc;
+ uint8_t b;
+
+ RT_ZERO(This);
+ This.u8State = AUX_STATE_ENABLED;
+ This.fThrottleActive = true;
+ This.cmdQ.Hdr.pszDescR3 = "Test Aux Cmd";
+ This.evtQ.Hdr.pszDescR3 = "Test Aux Evt";
+ /* 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. */
+ ps2mR3PutEventWorker(NULL, &This, 0, 0, 0, 0, 1);
+ if (ps2mR3HaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, true);
+ ps2mR3PutEventWorker(NULL, &This, 0, 0, 0, 0, 0);
+ if (ps2mR3HaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, true);
+ ps2mR3PutEventWorker(NULL, &This, 0, 0, 0, 0, 1);
+ ps2mR3PutEventWorker(NULL, &This, 0, 0, 0, 0, 0);
+ if (ps2mR3HaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, true);
+ if (ps2mR3HaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, 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. */
+ ps2mR3PutEventWorker(NULL, &This, 0, 0, 0, 0, 1);
+ if (ps2mR3HaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, true);
+ if (ps2mR3HaveEvents(&This))
+ ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, 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 */
+
diff --git a/src/VBox/Devices/Input/DrvKeyboardQueue.cpp b/src/VBox/Devices/Input/DrvKeyboardQueue.cpp
new file mode 100644
index 00000000..d05b2ecb
--- /dev/null
+++ b/src/VBox/Devices/Input/DrvKeyboardQueue.cpp
@@ -0,0 +1,606 @@
+/* $Id: DrvKeyboardQueue.cpp $ */
+/** @file
+ * VBox input devices: Keyboard queue driver
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* 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"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** Keyboard usage page bits to be OR-ed into the code. */
+#define HID_PG_KB_BITS RT_MAKE_U32(0, USB_HID_KB_PAGE)
+
+
+/*********************************************************************************************************************************
+* 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. */
+ PDMQUEUEHANDLE hQueue;
+ /** 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 idUsage;
+} 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 */
+};
+
+/* Keyboard usage page (07h). */
+#define KB(key) (RT_MAKE_U32(0, USB_HID_KB_PAGE) | (uint16_t)key)
+/* Consumer Control usage page (0Ch). */
+#define CC(key) (RT_MAKE_U32(0, USB_HID_CC_PAGE) | (uint16_t)key)
+/* Generic Desktop Control usage page (01h). */
+#define DC(key) (RT_MAKE_U32(0, USB_HID_DC_PAGE) | (uint16_t)key)
+/* Untranslated/unised, shouldn't be encountered. */
+#define XX(key) 0
+
+/** Lookup table for extended scancodes (arrow keys etc.).
+ * Some of these keys use HID usage pages other than the
+ * standard (07). */
+static const uint32_t aExtScan2Hid[] =
+{
+ XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), /* 00-07 */
+ XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), /* 08-1F */
+ CC(0x0B6), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), /* 10-17 */
+ XX(0x000), CC(0x0B5), XX(0x000), XX(0x000), KB(0x058), KB(0x0e4), XX(0x000), XX(0x000), /* 18-1F */
+ CC(0x0E2), CC(0x192), CC(0x0CD), XX(0x000), CC(0x0B7), XX(0x000), XX(0x000), XX(0x000), /* 20-27 */
+ XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), CC(0x0EA), XX(0x000), /* 28-2F */
+ CC(0x0E9), XX(0x000), CC(0x223), XX(0x000), XX(0x000), KB(0x054), XX(0x000), KB(0x046), /* 30-37 */
+ /* Sun-specific keys. Most of the XT codes are made up */
+ KB(0x0e6), XX(0x000), XX(0x000), KB(0x075), KB(0x076), KB(0x077), KB(0x0A3), KB(0x078), /* 38-3F */
+ KB(0x080), KB(0x081), KB(0x082), KB(0x079), XX(0x000), XX(0x000), KB(0x048), KB(0x04a), /* 40-47 */
+ KB(0x052), KB(0x04b), XX(0x000), KB(0x050), XX(0x000), KB(0x04f), XX(0x000), KB(0x04d), /* 48-4F */
+ KB(0x051), KB(0x04e), KB(0x049), KB(0x04c), XX(0x000), XX(0x000), XX(0x000), XX(0x000), /* 50-57 */
+ XX(0x000), XX(0x000), XX(0x000), KB(0x0e3), KB(0x0e7), KB(0x065), KB(0x066), DC(0x082), /* 58-5F */
+ XX(0x000), XX(0x000), XX(0x000), DC(0x083), XX(0x000), CC(0x221), CC(0x22A), CC(0x227), /* 60-67 */
+ CC(0x226), CC(0x225), CC(0x224), CC(0x194), CC(0x18A), CC(0x183), XX(0x000), XX(0x000), /* 68-6F */
+ XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), /* 70-77 */
+ XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000), XX(0x000) /* 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;
+ uint32_t usagePg;
+ uint8_t usage;
+
+ Assert(pUsage);
+
+ /* Isolate the scan code and key break flag. */
+ keyUp = (scanCode & 0x80) ? PDMIKBDPORT_KEY_UP : 0;
+
+ switch (state) {
+ case SS_IDLE:
+ if (scanCode == 0xE0) {
+ state = SS_EXT;
+ } else if (scanCode == 0xE1) {
+ state = SS_EXT1;
+ } else {
+ usage = aScancode2Hid[scanCode & 0x7F];
+ AssertMsg(usage, ("SS_IDLE: scanCode=%02X\n", scanCode));
+ *pUsage = usage | keyUp | HID_PG_KB_BITS;
+ /* Remain in SS_IDLE state. */
+ }
+ break;
+ case SS_EXT:
+ usagePg = aExtScan2Hid[scanCode & 0x7F];
+ AssertMsg(usagePg, ("SS_EXT: scanCode=%02X\n", scanCode));
+ *pUsage = usagePg | 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 | HID_PG_KB_BITS;
+ 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)) )
+
+
+/**
+ * @interface_method_impl{PDMIKEYBOARDPORT,pfnPutEventScan}
+ *
+ * Because of the event queueing the EMT context requirement is lifted.
+ * @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 idUsage = 0;
+ pDrv->XlatState = ScancodeToHidUsage(pDrv->XlatState, u8ScanCode, &idUsage);
+
+ if (pDrv->XlatState == SS_IDLE)
+ {
+ PDRVKBDQUEUEITEM pItem = (PDRVKBDQUEUEITEM)PDMDrvHlpQueueAlloc(pDrv->pDrvIns, pDrv->hQueue);
+ 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 ( (idUsage == (PDMIKBDPORT_KEY_UP | HID_PG_KB_BITS | 0x90))
+ || (idUsage == (PDMIKBDPORT_KEY_UP | HID_PG_KB_BITS | 0x91)))
+ {
+ PDRVKBDQUEUEITEM pItem2 = (PDRVKBDQUEUEITEM)PDMDrvHlpQueueAlloc(pDrv->pDrvIns, pDrv->hQueue);
+ /*
+ * 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->idUsage = idUsage & ~PDMIKBDPORT_KEY_UP;
+ PDMDrvHlpQueueInsert(pDrv->pDrvIns, pDrv->hQueue, &pItem2->Core);
+ }
+ }
+
+ pItem->idUsage = idUsage;
+ PDMDrvHlpQueueInsert(pDrv->pDrvIns, pDrv->hQueue, &pItem->Core);
+
+ return VINF_SUCCESS;
+ }
+ if (!pDrv->fSuspended)
+ AssertMsgFailed(("drvKbdQueuePutEventScan: Queue is full!!!!\n"));
+ return VERR_PDM_NO_QUEUE_ITEMS;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIKEYBOARDPORT,pfnPutEventHid}
+ *
+ * Because of the event queueing the EMT context requirement is lifted.
+ * @thread Any thread.
+ */
+static DECLCALLBACK(int) drvKbdQueuePutEventHid(PPDMIKEYBOARDPORT pInterface, uint32_t idUsage)
+{
+ PDRVKBDQUEUE pDrv = IKEYBOARDPORT_2_DRVKBDQUEUE(pInterface);
+ /* Ignore any attempt to send events if queue is inactive. */
+ if (pDrv->fInactive)
+ return VINF_SUCCESS;
+
+ PDRVKBDQUEUEITEM pItem = (PDRVKBDQUEUEITEM)PDMDrvHlpQueueAlloc(pDrv->pDrvIns, pDrv->hQueue);
+ if (pItem)
+ {
+ pItem->idUsage = idUsage;
+ PDMDrvHlpQueueInsert(pDrv->pDrvIns, pDrv->hQueue, &pItem->Core);
+
+ return VINF_SUCCESS;
+ }
+ AssertMsg(pDrv->fSuspended, ("drvKbdQueuePutEventHid: Queue is full!!!!\n"));
+ return VERR_PDM_NO_QUEUE_ITEMS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIKEYBOARDPORT,pfnReleaseKeys}
+ *
+ * Because of the event queueing the EMT context requirement is lifted.
+ * @thread Any thread.
+ */
+static DECLCALLBACK(int) drvKbdQueueReleaseKeys(PPDMIKEYBOARDPORT pInterface)
+{
+ PDRVKBDQUEUE pDrv = IKEYBOARDPORT_2_DRVKBDQUEUE(pInterface);
+
+ /* Ignore any attempt to send events if queue is inactive. */
+ if (pDrv->fInactive)
+ return VINF_SUCCESS;
+
+ PDRVKBDQUEUEITEM pItem = (PDRVKBDQUEUEITEM)PDMDrvHlpQueueAlloc(pDrv->pDrvIns, pDrv->hQueue);
+ if (pItem)
+ {
+ /* Send a special key event that forces all keys to be released.
+ * Goes through the queue so that it would take effect only after
+ * any key events that might already be queued up.
+ */
+ pItem->idUsage = PDMIKBDPORT_RELEASE_KEYS | HID_PG_KB_BITS;
+ PDMDrvHlpQueueInsert(pDrv->pDrvIns, pDrv->hQueue, &pItem->Core);
+
+ return VINF_SUCCESS;
+ }
+ AssertMsg(pDrv->fSuspended, ("drvKbdQueueReleaseKeys: Queue is full!!!!\n"));
+ return VERR_PDM_NO_QUEUE_ITEMS;
+}
+
+
+/* -=-=-=-=- 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);
+
+ PDMDrvHlpQueueFlushIfNecessary(pDrv->pDrvIns, pDrv->hQueue);
+}
+
+
+/* -=-=-=-=- 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->idUsage);
+ return rc != VERR_TRY_AGAIN;
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+/**
+ * Power On notification.
+ *
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvKbdQueuePowerOn(PPDMDRVINS pDrvIns)
+{
+ PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+ pThis->fInactive = false;
+}
+
+
+/**
+ * Reset notification.
+ *
+ * @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.
+ *
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvKbdQueueSuspend(PPDMDRVINS pDrvIns)
+{
+ PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+ pThis->fSuspended = true;
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @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)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVKBDQUEUE pDrv = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ LogFlow(("drvKbdQueueConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "QueueSize|Interval", "");
+
+ /*
+ * Init basic data members and interfaces.
+ */
+ pDrv->pDrvIns = pDrvIns;
+ 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;
+ pDrv->IPort.pfnPutEventHid = drvKbdQueuePutEventHid;
+ pDrv->IPort.pfnReleaseKeys = drvKbdQueueReleaseKeys;
+
+ /*
+ * Get the IKeyboardPort interface of the above driver/device.
+ */
+ pDrv->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIKEYBOARDPORT);
+ AssertMsgReturn(pDrv->pUpPort, ("Configuration error: No keyboard port interface above!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+ /*
+ * Attach driver below and query it's connector interface.
+ */
+ PPDMIBASE pDownBase;
+ int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase);
+ AssertMsgRCReturn(rc, ("Failed to attach driver below us! rc=%Rra\n", rc), rc);
+
+ pDrv->pDownConnector = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIKEYBOARDCONNECTOR);
+ AssertMsgReturn(pDrv->pDownConnector, ("Configuration error: No keyboard connector interface below!\n"),
+ VERR_PDM_MISSING_INTERFACE_BELOW);
+
+ /*
+ * Create the queue.
+ */
+ uint32_t cMilliesInterval = 0;
+ rc = pHlp->pfnCFGMQueryU32(pCfg, "Interval", &cMilliesInterval);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ cMilliesInterval = 0;
+ else
+ AssertMsgRCReturn(rc, ("Configuration error: 32-bit \"Interval\" -> rc=%Rrc\n", rc), rc);
+
+ uint32_t cItems = 0;
+ rc = pHlp->pfnCFGMQueryU32(pCfg, "QueueSize", &cItems);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ cItems = 128;
+ else
+ AssertMsgRCReturn(rc, ("Configuration error: 32-bit \"QueueSize\" -> rc=%Rrc\n", rc), rc);
+
+ rc = PDMDrvHlpQueueCreate(pDrvIns, sizeof(DRVKBDQUEUEITEM), cItems, cMilliesInterval,
+ drvKbdQueueConsumer, "Keyboard", &pDrv->hQueue);
+ AssertMsgRCReturn(rc, ("Failed to create driver: cItems=%d cMilliesInterval=%d rc=%Rrc\n", cItems, cMilliesInterval, rc), 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..a625e399
--- /dev/null
+++ b/src/VBox/Devices/Input/DrvMouseQueue.cpp
@@ -0,0 +1,464 @@
+/* $Id: DrvMouseQueue.cpp $ */
+/** @file
+ * VBox input devices: Mouse queue driver
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* 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. */
+ PDMQUEUEHANDLE hQueue;
+ /** 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)PDMDrvHlpQueueAlloc(pDrv->pDrvIns, pDrv->hQueue);
+ 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;
+ PDMDrvHlpQueueInsert(pDrv->pDrvIns, pDrv->hQueue, &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)PDMDrvHlpQueueAlloc(pDrv->pDrvIns, pDrv->hQueue);
+ 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;
+ PDMDrvHlpQueueInsert(pDrv->pDrvIns, pDrv->hQueue, &pItem->Core);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_NO_QUEUE_ITEMS;
+}
+
+
+static DECLCALLBACK(int) drvMouseQueuePutEventMTAbs(PPDMIMOUSEPORT pInterface,
+ uint8_t cContacts,
+ const uint64_t *pau64Contacts,
+ uint32_t u32ScanTime)
+{
+ PDRVMOUSEQUEUE pThis = IMOUSEPORT_2_DRVMOUSEQUEUE(pInterface);
+ return pThis->pUpPort->pfnPutEventTouchScreen(pThis->pUpPort, cContacts, pau64Contacts, u32ScanTime);
+}
+
+static DECLCALLBACK(int) drvMouseQueuePutEventMTRel(PPDMIMOUSEPORT pInterface,
+ uint8_t cContacts,
+ const uint64_t *pau64Contacts,
+ uint32_t u32ScanTime)
+{
+ PDRVMOUSEQUEUE pThis = IMOUSEPORT_2_DRVMOUSEQUEUE(pInterface);
+ return pThis->pUpPort->pfnPutEventTouchPad(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 fMTAbs Is absolute multi-touch reporting supported?
+ * @param fMTRel Is relative multi-touch reporting supported?
+ */
+static DECLCALLBACK(void) drvMousePassThruReportModes(PPDMIMOUSECONNECTOR pInterface, bool fRel, bool fAbs, bool fMTAbs, bool fMTRel)
+{
+ PDRVMOUSEQUEUE pDrv = PPDMIMOUSECONNECTOR_2_DRVMOUSEQUEUE(pInterface);
+ pDrv->pDownConnector->pfnReportModes(pDrv->pDownConnector, fRel, fAbs, fMTAbs, fMTRel);
+}
+
+
+/**
+ * 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);
+
+ PDMDrvHlpQueueFlushIfNecessary(pDrv->pDrvIns, pDrv->hQueue);
+}
+
+
+
+/* -=-=-=-=- 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
+ AssertMsgFailedReturn(("enmType=%d\n", pItem->enmType), true /* remove buggy data */);
+ return rc != VERR_TRY_AGAIN;
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+/**
+ * Power On notification.
+ *
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvMouseQueuePowerOn(PPDMDRVINS pDrvIns)
+{
+ PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE);
+ pThis->fInactive = false;
+}
+
+
+/**
+ * Reset notification.
+ *
+ * @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.
+ *
+ * @param pDrvIns The drive instance data.
+ */
+static DECLCALLBACK(void) drvMouseQueueSuspend(PPDMDRVINS pDrvIns)
+{
+ PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE);
+ pThis->fInactive = true;
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @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)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVMOUSEQUEUE pDrv = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ LogFlow(("drvMouseQueueConstruct: iInstance=%d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "QueueSize|Interval", "");
+
+ /*
+ * Init basic data members and interfaces.
+ */
+ pDrv->pDrvIns = pDrvIns;
+ 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.pfnPutEventTouchScreen = drvMouseQueuePutEventMTAbs;
+ pDrv->IPort.pfnPutEventTouchPad = drvMouseQueuePutEventMTRel;
+
+ /*
+ * Get the IMousePort interface of the above driver/device.
+ */
+ pDrv->pUpPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMOUSEPORT);
+ AssertMsgReturn(pDrv->pUpPort, ("Configuration error: No mouse port interface above!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+ /*
+ * Attach driver below and query it's connector interface.
+ */
+ PPDMIBASE pDownBase;
+ int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase);
+ AssertMsgRCReturn(rc, ("Failed to attach driver below us! rc=%Rra\n", rc), rc);
+
+ pDrv->pDownConnector = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIMOUSECONNECTOR);
+ AssertMsgReturn(pDrv->pDownConnector, ("Configuration error: No mouse connector interface below!\n"),
+ VERR_PDM_MISSING_INTERFACE_BELOW);
+
+ /*
+ * Create the queue.
+ */
+ uint32_t cMilliesInterval = 0;
+ rc = pHlp->pfnCFGMQueryU32(pCfg, "Interval", &cMilliesInterval);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ cMilliesInterval = 0;
+ else
+ AssertMsgRCReturn(rc, ("Configuration error: 32-bit \"Interval\" -> rc=%Rrc\n", rc), rc);
+
+ uint32_t cItems = 0;
+ rc = pHlp->pfnCFGMQueryU32(pCfg, "QueueSize", &cItems);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ cItems = 128;
+ else
+ AssertMsgRCReturn(rc, ("Configuration error: 32-bit \"QueueSize\" -> rc=%Rrc\n", rc), rc);
+
+ rc = PDMDrvHlpQueueCreate(pDrvIns, sizeof(DRVMOUSEQUEUEITEM), cItems, cMilliesInterval,
+ drvMouseQueueConsumer, "Mouse", &pDrv->hQueue);
+ AssertMsgRCReturn(rc, ("Failed to create driver: cItems=%d cMilliesInterval=%d rc=%Rrc\n", cItems, cMilliesInterval, rc), 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/UsbKbd.cpp b/src/VBox/Devices/Input/UsbKbd.cpp
new file mode 100644
index 00000000..e9b8d59e
--- /dev/null
+++ b/src/VBox/Devices/Input/UsbKbd.cpp
@@ -0,0 +1,1894 @@
+/* $Id: UsbKbd.cpp $ */
+/** @file
+ * UsbKbd - USB Human Interface Device Emulation, Keyboard.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @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.
+ *
+ * A second interface with its own interrupt endpoint is used to deliver
+ * additional key events for media and system control keys. This adds
+ * considerable complexity to the emulated device, but unfortunately the
+ * keyboard boot interface is fixed and fairly limited.
+ *
+ * The second interface is only exposed if the device is configured in
+ * "extended" mode, with a different USB product ID and different
+ * descriptors. The "basic" mode should be indistinguishable from the original
+ * implementation.
+ *
+ * There are various options available for reporting media keys. We chose
+ * a very basic approach which reports system control keys as a bit-field
+ * (since there are only 3 keys defined) and consumer control keys as just
+ * a single 16-bit value.
+ *
+ * As a consequence, only one consumer control key can be reported as
+ * pressed at any one time. While this may seem limiting, the usefulness of
+ * being able to report e.g. volume-up at the same time as volume-down or
+ * mute is highly questionable.
+ *
+ * System control and consumer control keys are reported in a single
+ * 4-byte report in order to avoid sending multiple separate report types.
+ *
+ * There is a slight complication in that both interfaces are configured
+ * together, but a guest does not necessarily "listen" on both (e.g. EFI).
+ * Since all events come through a single queue, we can't just push back
+ * events for the secondary interface because the entire keyboard would be
+ * blocked. After the device is reset/configured, we drop any events destined
+ * for the secondary interface until a URB is actually queued on the second
+ * interrupt endpoint. Once that happens, we assume the guest will be
+ * receiving data on the second endpoint until the next reset/reconfig.
+ *
+ * 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
+#define USBHID_STR_ID_IF_KBD 3
+#define USBHID_STR_ID_IF_EXT 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_BAS_KEYBOARD 0x0010
+#define USBHID_PID_EXT_KEYBOARD 0x0011
+/** @} */
+
+/** @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 device mode.
+ */
+typedef enum USBKBDMODE
+{
+ /** Basic keyboard only, backward compatible. */
+ USBKBDMODE_BASIC = 0,
+ /** Extended 2nd interface for consumer control and power. */
+ USBKBDMODE_EXTENDED,
+} USBKBDMODE;
+
+
+/**
+ * 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;
+
+
+/**
+ * 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;
+
+
+/**
+ * Endpoint state.
+ */
+typedef struct USBHIDEP
+{
+ /** Endpoint halt flag.*/
+ bool fHalted;
+} USBHIDEP;
+/** Pointer to the endpoint status. */
+typedef USBHIDEP *PUSBHIDEP;
+
+
+/**
+ * Interface state.
+ */
+typedef struct USBHIDIF
+{
+ /** If interface has pending changes. */
+ bool fHasPendingChanges;
+ /** 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;
+} USBHIDIF;
+/** Pointer to the endpoint status. */
+typedef USBHIDIF *PUSBHIDIF;
+
+
+/**
+ * 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;
+
+/* Must match 8-byte packet size. */
+AssertCompile(sizeof(USBHIDK_REPORT) == 8);
+
+
+/**
+ * The USB HID report structure for extra keys.
+ */
+typedef struct USBHIDX_REPORT
+{
+ uint16_t uKeyCC; /**< Consumer Control key code */
+ uint8_t uSCKeys; /**< System Control keys bit map */
+ uint8_t Reserved; /**< Unused */
+} USBHIDX_REPORT, *PUSBHIDX_REPORT;
+
+/* Must match 4-byte packet size. */
+AssertCompile(sizeof(USBHIDX_REPORT) == 4);
+
+
+/**
+ * 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;
+ /** Is this a relative, absolute or multi-touch pointing device? */
+ USBKBDMODE enmMode;
+ /** Endpoint 0 is the default control pipe, 1 is the dev->host interrupt one
+ * for standard keys, 1 is the interrupt EP for extra keys. */
+ USBHIDEP aEps[3];
+ /** Interface 0 is the standard keyboard interface, 1 is the additional
+ * control/media key interface. */
+ USBHIDIF aIfs[2];
+
+ /** 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;
+ /** The guest expects data coming over second endpoint/pipe. */
+ bool fExtPipeActive;
+ /** 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" },
+ { USBHID_STR_ID_IF_KBD, "Keyboard" },
+ { USBHID_STR_ID_IF_EXT, "System Control"},
+};
+
+static const PDMUSBDESCCACHELANG g_aUsbHidLanguages[] =
+{
+ { 0x0409, RT_ELEMENTS(g_aUsbHidStrings_en_US), g_aUsbHidStrings_en_US }
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbHidEndpointDescsKbd[] =
+{
+ {
+ {
+ /* .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_aUsbHidEndpointDescsExt[] =
+{
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x82 /* ep=2, in */,
+ /* .bmAttributes = */ 3 /* interrupt */,
+ /* .wMaxPacketSize = */ 4,
+ /* .bInterval = */ 10,
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0
+ },
+};
+
+/** HID report descriptor for standard keys. */
+static const uint8_t g_UsbHidReportDescKbd[] =
+{
+ /* 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,
+};
+
+/** HID report descriptor for extra multimedia/system keys. */
+static const uint8_t g_UsbHidReportDescExt[] =
+{
+ /* Usage Page */ 0x05, 0x0C, /* Consumer */
+ /* Usage */ 0x09, 0x01, /* Consumer Control */
+ /* Collection */ 0xA1, 0x01, /* Application */
+
+ /* Usage Page */ 0x05, 0x0C, /* Consumer */
+ /* Usage Minimum */ 0x19, 0x00, /* 0 */
+ /* Usage Maximum */ 0x2A, 0x3C, 0x02, /* 572 */
+ /* Logical Minimum */ 0x15, 0x00, /* 0 */
+ /* Logical Maximum */ 0x26, 0x3C, 0x02, /* 572 */
+ /* Report Count */ 0x95, 0x01, /* 1 */
+ /* Report Size */ 0x75, 0x10, /* 16 */
+ /* Input */ 0x81, 0x80, /* Data, Array, Absolute, Bytes */
+
+ /* Usage Page */ 0x05, 0x01, /* Generic Desktop */
+ /* Usage Minimum */ 0x19, 0x81, /* 129 */
+ /* Usage Maximum */ 0x29, 0x83, /* 131 */
+ /* Logical Minimum */ 0x15, 0x00, /* 0 */
+ /* Logical Maximum */ 0x25, 0x01, /* 1 */
+ /* Report Size */ 0x75, 0x01, /* 1 */
+ /* Report Count */ 0x95, 0x03, /* 3 */
+ /* Input */ 0x81, 0x02, /* Data, Value, Absolute, Bit field */
+ /* Report Count */ 0x95, 0x05, /* 5 */
+ /* Input */ 0x81, 0x01, /* Constant, Array, Absolute, Bit field */
+ /* Report Count */ 0x95, 0x01, /* 1 */
+ /* Report Size */ 0x75, 0x08, /* 8 (padding bits) */
+ /* Input */ 0x81, 0x01, /* Constant, Array, Absolute, Bit field */
+
+ /* End Collection */ 0xC0,
+};
+
+/** Additional HID class interface descriptor for standard keys. */
+static const uint8_t g_UsbHidIfHidDescKbd[] =
+{
+ /* .bLength = */ 0x09,
+ /* .bDescriptorType = */ 0x21, /* HID */
+ /* .bcdHID = */ 0x10, 0x01, /* 1.1 */
+ /* .bCountryCode = */ 0x0D, /* International (ISO) */
+ /* .bNumDescriptors = */ 1,
+ /* .bDescriptorType = */ 0x22, /* Report */
+ /* .wDescriptorLength = */ sizeof(g_UsbHidReportDescKbd), 0x00
+};
+
+/** Additional HID class interface descriptor for extra keys. */
+static const uint8_t g_UsbHidIfHidDescExt[] =
+{
+ /* .bLength = */ 0x09,
+ /* .bDescriptorType = */ 0x21, /* HID */
+ /* .bcdHID = */ 0x10, 0x01, /* 1.1 */
+ /* .bCountryCode = */ 0,
+ /* .bNumDescriptors = */ 1,
+ /* .bDescriptorType = */ 0x22, /* Report */
+ /* .wDescriptorLength = */ sizeof(g_UsbHidReportDescExt), 0x00
+};
+
+/** Standard keyboard interface. */
+static const VUSBDESCINTERFACEEX g_UsbHidInterfaceDescKbd =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 0,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 1,
+ /* .bInterfaceClass = */ 3 /* HID */,
+ /* .bInterfaceSubClass = */ 1 /* Boot Interface */,
+ /* .bInterfaceProtocol = */ 1 /* Keyboard */,
+ /* .iInterface = */ USBHID_STR_ID_IF_KBD
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ &g_UsbHidIfHidDescKbd,
+ /* .cbClass = */ sizeof(g_UsbHidIfHidDescKbd),
+ &g_aUsbHidEndpointDescsKbd[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+/** Extra keys (multimedia/system) interface. */
+static const VUSBDESCINTERFACEEX g_UsbHidInterfaceDescExt =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 1,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 1,
+ /* .bInterfaceClass = */ 3 /* HID */,
+ /* .bInterfaceSubClass = */ 0 /* None */,
+ /* .bInterfaceProtocol = */ 0 /* Unspecified */,
+ /* .iInterface = */ USBHID_STR_ID_IF_EXT
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ &g_UsbHidIfHidDescExt,
+ /* .cbClass = */ sizeof(g_UsbHidIfHidDescExt),
+ &g_aUsbHidEndpointDescsExt[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+static const VUSBINTERFACE g_aUsbHidBasInterfaces[] =
+{
+ { &g_UsbHidInterfaceDescKbd, /* .cSettings = */ 1 },
+};
+
+static const VUSBINTERFACE g_aUsbHidExtInterfaces[] =
+{
+ { &g_UsbHidInterfaceDescKbd, /* .cSettings = */ 1 },
+ { &g_UsbHidInterfaceDescExt, /* .cSettings = */ 1 },
+};
+
+static const VUSBDESCCONFIGEX g_UsbHidBasConfigDesc =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbHidBasInterfaces),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7), /* bus-powered */
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbHidBasInterfaces[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCCONFIGEX g_UsbHidExtConfigDesc =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbHidExtInterfaces),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7), /* bus-powered */
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbHidExtInterfaces[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCDEVICE g_UsbHidBasDeviceDesc =
+{
+ /* .bLength = */ sizeof(g_UsbHidBasDeviceDesc),
+ /* .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_BAS_KEYBOARD,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBHID_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBHID_STR_ID_PRODUCT,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDESCDEVICE g_UsbHidExtDeviceDesc =
+{
+ /* .bLength = */ sizeof(g_UsbHidExtDeviceDesc),
+ /* .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_EXT_KEYBOARD,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBHID_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBHID_STR_ID_PRODUCT,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const PDMUSBDESCCACHE g_UsbHidBasDescCache =
+{
+ /* .pDevice = */ &g_UsbHidBasDeviceDesc,
+ /* .paConfigs = */ &g_UsbHidBasConfigDesc,
+ /* .paLanguages = */ g_aUsbHidLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbHidLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbHidExtDescCache =
+{
+ /* .pDevice = */ &g_UsbHidExtDeviceDesc,
+ /* .paConfigs = */ &g_UsbHidExtConfigDesc,
+ /* .paLanguages = */ g_aUsbHidLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbHidLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+/**
+ * Conversion table for consumer control keys (HID Usage Page 12).
+ * Used to 'compress' the USB HID usage code into a single 8-bit
+ * value. See also PS2CCKeys in the PS/2 keyboard emulation.
+ */
+static const uint16_t aHidCCKeys[] = {
+ 0x00B5, /* Scan Next Track */
+ 0x00B6, /* Scan Previous Track */
+ 0x00B7, /* Stop */
+ 0x00CD, /* Play/Pause */
+ 0x00E2, /* Mute */
+ 0x00E5, /* Bass Boost */
+ 0x00E7, /* Loudness */
+ 0x00E9, /* Volume Up */
+ 0x00EA, /* Volume Down */
+ 0x0152, /* Bass Up */
+ 0x0153, /* Bass Down */
+ 0x0154, /* Treble Up */
+ 0x0155, /* Treble Down */
+ 0x0183, /* Media Select */
+ 0x018A, /* Mail */
+ 0x0192, /* Calculator */
+ 0x0194, /* My Computer */
+ 0x0221, /* WWW Search */
+ 0x0223, /* WWW Home */
+ 0x0224, /* WWW Back */
+ 0x0225, /* WWW Forward */
+ 0x0226, /* WWW Stop */
+ 0x0227, /* WWW Refresh */
+ 0x022A, /* WWW Favorites */
+};
+
+/**
+ * Conversion table for generic desktop control keys (HID Usage Page 1).
+ * Used to 'compress' the USB HID usage code into a single 8-bit
+ * value. See also PS2DCKeys in the PS/2 keyboard emulation.
+ */
+static const uint16_t aHidDCKeys[] = {
+ 0x81, /* System Power */
+ 0x82, /* System Sleep */
+ 0x83, /* System Wake */
+};
+
+#define USBHID_PAGE_DC_START 0xb0
+#define USBHID_PAGE_DC_END (USBHID_PAGE_DC_START + RT_ELEMENTS(aHidDCKeys))
+#define USBHID_PAGE_CC_START 0xc0
+#define USBHID_PAGE_CC_END (USBHID_PAGE_CC_START + RT_ELEMENTS(aHidCCKeys))
+
+AssertCompile(RT_ELEMENTS(aHidCCKeys) <= 0x20); /* Must fit between 0xC0-0xDF. */
+AssertCompile(RT_ELEMENTS(aHidDCKeys) <= 0x10); /* Must fit between 0xB0-0xBF. */
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Converts a 32-bit USB HID code to an internal 8-bit value.
+ *
+ * @returns 8-bit internal key code/index. -1 if not found.
+ * @param u32HidCode 32-bit USB HID code.
+ */
+static int usbHidToInternalCode(uint32_t u32HidCode)
+{
+ uint8_t u8HidPage;
+ uint16_t u16HidUsage;
+ int iKeyIndex = -1;
+
+ u8HidPage = RT_LOBYTE(RT_HIWORD(u32HidCode));
+ u16HidUsage = RT_LOWORD(u32HidCode);
+
+ if (u8HidPage == USB_HID_KB_PAGE)
+ {
+ if (u16HidUsage <= VBOX_USB_MAX_USAGE_CODE)
+ iKeyIndex = u16HidUsage; /* Direct mapping. */
+ else
+ AssertMsgFailed(("u16HidUsage out of range! (%04X)\n", u16HidUsage));
+ }
+ else if (u8HidPage == USB_HID_CC_PAGE)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(aHidCCKeys); ++i)
+ if (aHidCCKeys[i] == u16HidUsage)
+ {
+ iKeyIndex = USBHID_PAGE_CC_START + i;
+ break;
+ }
+ AssertMsg(iKeyIndex > -1, ("Unsupported code in USB_HID_CC_PAGE! (%04X)\n", u16HidUsage));
+ }
+ else if (u8HidPage == USB_HID_DC_PAGE)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(aHidDCKeys); ++i)
+ if (aHidDCKeys[i] == u16HidUsage)
+ {
+ iKeyIndex = USBHID_PAGE_DC_START + i;
+ break;
+ }
+ AssertMsg(iKeyIndex > -1, ("Unsupported code in USB_HID_DC_PAGE! (%04X)\n", u16HidUsage));
+ }
+ else
+ {
+ AssertMsgFailed(("Unsupported u8HidPage! (%02X)\n", u8HidPage));
+ }
+
+ return iKeyIndex;
+}
+
+
+/**
+ * Converts an internal 8-bit key index back to a 32-bit USB HID code.
+ *
+ * @returns 32-bit USB HID code. Zero if not found.
+ * @param uKeyCode Internal key code/index.
+ */
+static uint32_t usbInternalCodeToHid(unsigned uKeyCode)
+{
+ uint16_t u16HidUsage;
+ uint32_t u32HidCode = 0;
+
+ if ((uKeyCode >= USBHID_PAGE_DC_START) && (uKeyCode <= USBHID_PAGE_DC_END))
+ {
+ u16HidUsage = aHidDCKeys[uKeyCode - USBHID_PAGE_DC_START];
+ u32HidCode = RT_MAKE_U32(u16HidUsage, USB_HID_DC_PAGE);
+ }
+ else if ((uKeyCode >= USBHID_PAGE_CC_START) && (uKeyCode <= USBHID_PAGE_CC_END))
+ {
+ u16HidUsage = aHidCCKeys[uKeyCode - USBHID_PAGE_CC_START];
+ u32HidCode = RT_MAKE_U32(u16HidUsage, USB_HID_CC_PAGE);
+ }
+ else /* Must be the keyboard usage page. */
+ {
+ if (uKeyCode <= VBOX_USB_MAX_USAGE_CODE)
+ u32HidCode = RT_MAKE_U32(uKeyCode, USB_HID_KB_PAGE);
+ else
+ AssertMsgFailed(("uKeyCode out of range! (%u)\n", uKeyCode));
+ }
+
+ return u32HidCode;
+}
+
+
+/**
+ * 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)
+{
+ 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
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aEps); i++)
+ pThis->aEps[i].fHalted = true;
+ }
+
+ usbHidLinkDone(pThis, pUrb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Completes the URB after device successfully processed it. Optionally copies data
+ * into the URB. May still generate an error if the URB is not big enough.
+ */
+static int usbHidCompleteOk(PUSBHID pThis, PVUSBURB pUrb, const void *pSrc, size_t cbSrc)
+{
+ Log(("usbHidCompleteOk/#%u: pUrb=%p:%s (cbData=%#x) cbSrc=%#zx\n", pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, pUrb->cbData, cbSrc));
+
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ size_t cbCopy = 0;
+ size_t cbSetup = 0;
+
+ if (pSrc) /* Can be NULL if not copying anything. */
+ {
+ Assert(cbSrc);
+ uint8_t *pDst = pUrb->abData;
+
+ /* Returned data is written after the setup message in control URBs. */
+ if (pUrb->enmType == VUSBXFERTYPE_MSG)
+ cbSetup = sizeof(VUSBSETUP);
+
+ Assert(pUrb->cbData >= cbSetup); /* Only triggers if URB is corrupted. */
+
+ if (pUrb->cbData > cbSetup)
+ {
+ /* There is at least one byte of room in the URB. */
+ cbCopy = RT_MIN(pUrb->cbData - cbSetup, cbSrc);
+ memcpy(pDst + cbSetup, pSrc, cbCopy);
+ pUrb->cbData = (uint32_t)(cbCopy + cbSetup);
+ Log(("Copied %zu bytes to pUrb->abData[%zu], source had %zu bytes\n", cbCopy, cbSetup, cbSrc));
+ }
+
+ /* Need to check length differences. If cbSrc is less than what
+ * the URB has space for, it'll be resolved as a short packet. But
+ * if cbSrc is bigger, there is a real problem and the host needs
+ * to see an overrun/babble error.
+ */
+ if (RT_UNLIKELY(cbSrc > cbCopy))
+ pUrb->enmStatus = VUSBSTATUS_DATA_OVERRUN;
+ }
+ else
+ Assert(cbSrc == 0); /* Make up your mind, caller! */
+
+ 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->bIdle = 0;
+ pThis->fExtPipeActive = false;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aEps); i++)
+ pThis->aEps[i].fHalted = false;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aIfs); i++)
+ {
+ pThis->aIfs[i].fHasPendingChanges = false;
+ pThis->aIfs[i].enmState = USBHIDREQSTATE_READY;
+ }
+
+ if (!pUrb && !fSetConfig) /* (only device reset) */
+ pThis->bConfigurationValue = 0; /* default */
+
+ /*
+ * Ditch all pending URBs.
+ */
+ PVUSBURB pCurUrb;
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aIfs); i++)
+ while ((pCurUrb = usbHidQueueRemoveHead(&pThis->aIfs[i].ToHostQueue)) != NULL)
+ {
+ pCurUrb->enmStatus = VUSBSTATUS_CRC;
+ usbHidLinkDone(pThis, pCurUrb);
+ }
+
+ if (pUrb)
+ return usbHidCompleteOk(pThis, pUrb, NULL, 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);
+}
+
+/**
+ * Returns true if the usage code corresponds to a System Control key.
+ * The usage codes for these keys are the range 0x81 to 0x83.
+ */
+static bool usbHidUsageCodeIsSCKey(uint16_t u16Usage)
+{
+ return u16Usage >= 0x81 && u16Usage <= 0x83;
+}
+
+/**
+ * Convert a USB HID usage code to a system control key mask. The system control
+ * keys have usage codes from 0x81 to 0x83, and the lower nibble is the bit
+ * position plus one.
+ */
+static uint8_t usbHidSCKeyToMask(uint16_t u16Usage)
+{
+ Assert(usbHidUsageCodeIsSCKey(u16Usage));
+ return RT_BIT((u16Usage & 0xf) - 1);
+}
+
+/**
+ * Create a USB HID keyboard report reflecting the current state of the
+ * standard keyboard (up/down keys).
+ */
+static void usbHidBuildReportKbd(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
+ {
+ /* Key index back to 32-bit HID code. */
+ uint32_t u32HidCode = usbInternalCodeToHid(iKey);
+ uint8_t u8HidPage = RT_LOBYTE(RT_HIWORD(u32HidCode));
+ uint16_t u16HidUsage = RT_LOWORD(u32HidCode);
+
+ if (u8HidPage == USB_HID_KB_PAGE)
+ {
+ pReport->aKeys[iBuf] = (uint8_t)u16HidUsage;
+ ++iBuf;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Create a USB HID keyboard report reflecting the current state of the
+ * consumer control keys. This is very easy as we have a bit mask that fully
+ * reflects the state of all defined system control keys.
+ */
+static void usbHidBuildReportExt(PUSBHIDX_REPORT pReport, uint8_t *pabDepressedKeys)
+{
+ RT_ZERO(*pReport);
+
+ for (unsigned iKey = 0; iKey < VBOX_USB_USAGE_ARRAY_SIZE; ++iKey)
+ {
+ if (pabDepressedKeys[iKey])
+ {
+ /* Key index back to 32-bit HID code. */
+ uint32_t u32HidCode = usbInternalCodeToHid(iKey);
+ uint8_t u8HidPage = RT_LOBYTE(RT_HIWORD(u32HidCode));
+ uint16_t u16HidUsage = RT_LOWORD(u32HidCode);
+
+ if (u8HidPage == USB_HID_CC_PAGE)
+ pReport->uKeyCC = u16HidUsage;
+ else if (u8HidPage == USB_HID_DC_PAGE)
+ if (usbHidUsageCodeIsSCKey(u16HidUsage))
+ pReport->uSCKeys |= usbHidSCKeyToMask(u16HidUsage);
+ }
+ }
+}
+
+/**
+ * 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, PUSBHIDIF pIf)
+{
+ PVUSBURB pUrb = usbHidQueueRemoveHead(&pIf->ToHostQueue);
+ if (pUrb)
+ {
+ pIf->fHasPendingChanges = false;
+ if (pIf == &pThis->aIfs[0])
+ {
+ USBHIDK_REPORT ReportKbd;
+
+ usbHidBuildReportKbd(&ReportKbd, pThis->abDepressedKeys);
+ usbHidCompleteOk(pThis, pUrb, &ReportKbd, sizeof(ReportKbd));
+ }
+ else
+ {
+ Assert(pIf == &pThis->aIfs[1]);
+ USBHIDX_REPORT ReportExt;
+
+ usbHidBuildReportExt(&ReportExt, pThis->abDepressedKeys);
+ usbHidCompleteOk(pThis, pUrb, &ReportExt, sizeof(ReportExt));
+ }
+ }
+ else
+ {
+ Log2(("No available URB for USB kbd\n"));
+ pIf->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;
+}
+
+/**
+ * @interface_method_impl{PDMIKEYBOARDPORT,pfnPutEventHid}
+ */
+static DECLCALLBACK(int) usbHidKeyboardPutEvent(PPDMIKEYBOARDPORT pInterface, uint32_t idUsage)
+{
+ PUSBHID pThis = RT_FROM_MEMBER(pInterface, USBHID, Lun0.IPort);
+ PUSBHIDIF pIf;
+ bool fKeyDown;
+ bool fHaveEvent = true;
+ int rc = VINF_SUCCESS;
+ int iKeyCode;
+ uint8_t u8HidPage = RT_LOBYTE(RT_HIWORD(idUsage));
+
+ /* Let's see what we got... */
+ fKeyDown = !(idUsage & PDMIKBDPORT_KEY_UP);
+
+ /* Always respond to USB_HID_KB_PAGE, but quietly drop USB_HID_CC_PAGE/USB_HID_DC_PAGE
+ * events unless the device is in the extended mode. And drop anything else, too.
+ */
+ if (u8HidPage == USB_HID_KB_PAGE)
+ pIf = &pThis->aIfs[0];
+ else
+ {
+ if ( pThis->fExtPipeActive
+ && ((u8HidPage == USB_HID_CC_PAGE) || (u8HidPage == USB_HID_DC_PAGE)))
+ pIf = &pThis->aIfs[1];
+ else
+ return VINF_SUCCESS; /* Must consume data to avoid blockage. */
+ }
+
+ iKeyCode = usbHidToInternalCode(idUsage);
+ AssertReturn((iKeyCode > 0 && iKeyCode <= VBOX_USB_MAX_USAGE_CODE) || (idUsage & PDMIKBDPORT_RELEASE_KEYS), VERR_INTERNAL_ERROR);
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ if (RT_LIKELY(!(idUsage & PDMIKBDPORT_RELEASE_KEYS)))
+ {
+ LogFlowFunc(("key %s: %08X (iKeyCode 0x%x)\n", fKeyDown ? "down" : "up", idUsage, iKeyCode));
+
+ /*
+ * Due to host key repeat, we can get key events for keys which are
+ * already depressed. Drop those right here.
+ */
+ if (fKeyDown && pThis->abDepressedKeys[iKeyCode])
+ fHaveEvent = false;
+
+ /* If there is already a pending event, we won't accept a new one yet. */
+ if (pIf->fHasPendingChanges && fHaveEvent)
+ {
+ rc = VERR_TRY_AGAIN;
+ }
+ else if (fHaveEvent)
+ {
+ /* Regular key event - update keyboard state. */
+ if (fKeyDown)
+ pThis->abDepressedKeys[iKeyCode] = 1;
+ else
+ pThis->abDepressedKeys[iKeyCode] = 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, pIf);
+ }
+ }
+ else
+ {
+ LogFlowFunc(("Release all keys.\n"));
+
+ /* Clear all currently depressed keys. */
+ RT_ZERO(pThis->abDepressedKeys);
+ }
+
+ 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 its to-host queue and move it onto the done queue.
+ */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aIfs); i++)
+ if (usbHidQueueRemove(&pThis->aIfs[i].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, PUSBHIDIF pIf, 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 endpoint/interface state.
+ */
+ switch (pIf->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, NULL, 0);
+ }
+
+ /*
+ * Status transfer.
+ */
+ case USBHIDREQSTATE_STATUS:
+ {
+ AssertFailed();
+ Log(("usbHidHandleIntrDevToHost: Entering READY\n"));
+ pIf->enmState = USBHIDREQSTATE_READY;
+ return usbHidCompleteOk(pThis, pUrb, NULL, 0);
+ }
+
+ case USBHIDREQSTATE_READY:
+ usbHidQueueAddTail(&pIf->ToHostQueue, pUrb);
+ /* If device was not set idle, send the current report right away. */
+ if (pThis->bIdle != 0 || pIf->fHasPendingChanges)
+ {
+ usbHidSendReport(pThis, pIf);
+ LogFlow(("usbHidHandleIntrDevToHost: Sent report via %p:%s\n", pUrb, pUrb->pszDesc));
+ Assert(!pIf->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", pIf->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 cbSrc;
+ const void *pSrc;
+
+ if (pSetup->wIndex == 0)
+ {
+ cbSrc = RT_MIN(pSetup->wLength, sizeof(g_UsbHidIfHidDescKbd));
+ pSrc = &g_UsbHidIfHidDescKbd;
+ }
+ else
+ {
+ cbSrc = RT_MIN(pSetup->wLength, sizeof(g_UsbHidIfHidDescExt));
+ pSrc = &g_UsbHidIfHidDescExt;
+ }
+ Log(("usbHidKbd: GET_DESCRIPTOR DT_IF_HID_DESCRIPTOR wValue=%#x wIndex=%#x cbSrc=%#x\n", pSetup->wValue, pSetup->wIndex, cbSrc));
+ return usbHidCompleteOk(pThis, pUrb, pSrc, cbSrc);
+ }
+
+ case DT_IF_HID_REPORT:
+ {
+ uint32_t cbSrc;
+ const void *pSrc;
+
+ /* Returned data is written after the setup message. */
+ if (pSetup->wIndex == 0)
+ {
+ cbSrc = RT_MIN(pSetup->wLength, sizeof(g_UsbHidReportDescKbd));
+ pSrc = &g_UsbHidReportDescKbd;
+ }
+ else
+ {
+ cbSrc = RT_MIN(pSetup->wLength, sizeof(g_UsbHidReportDescExt));
+ pSrc = &g_UsbHidReportDescExt;
+ }
+
+ Log(("usbHid: GET_DESCRIPTOR DT_IF_HID_REPORT wValue=%#x wIndex=%#x cbSrc=%#x\n", pSetup->wValue, pSetup->wIndex, cbSrc));
+ return usbHidCompleteOk(pThis, pUrb, pSrc, cbSrc);
+ }
+
+ 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. */
+ return usbHidCompleteOk(pThis, pUrb, &wRet, sizeof(wRet));
+ }
+
+ case VUSB_TO_INTERFACE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ if (pSetup->wIndex == 0)
+ {
+ return usbHidCompleteOk(pThis, pUrb, &wRet, sizeof(wRet));
+ }
+ 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;
+ return usbHidCompleteOk(pThis, pUrb, &wRet, sizeof(wRet));
+ }
+ 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, NULL, 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));
+ return usbHidCompleteOk(pThis, pUrb, &pThis->bIdle, sizeof(pThis->bIdle));
+ }
+ 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, NULL, 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) usbHidQueueUrb(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-endpoint basis.
+ */
+ int rc;
+ switch (pUrb->EndPt)
+ {
+ case 0:
+ rc = usbHidHandleDefaultPipe(pThis, &pThis->aEps[0], pUrb);
+ break;
+
+ /* Standard keyboard interface. */
+ case 0x81:
+ AssertFailed();
+ RT_FALL_THRU();
+ case 0x01:
+ rc = usbHidHandleIntrDevToHost(pThis, &pThis->aEps[1], &pThis->aIfs[0], pUrb);
+ break;
+
+ /* Extended multimedia/control keys interface. */
+ case 0x82:
+ AssertFailed();
+ RT_FALL_THRU();
+ case 0x02:
+ if (pThis->enmMode == USBKBDMODE_EXTENDED)
+ {
+ rc = usbHidHandleIntrDevToHost(pThis, &pThis->aEps[2], &pThis->aIfs[1], pUrb);
+ pThis->fExtPipeActive = true;
+ break;
+ }
+ RT_FALL_THRU();
+ 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);
+ LogRelFlow(("usbHidUsbGetDescriptorCache/#%u:\n", pUsbIns->iInstance));
+ switch (pThis->enmMode)
+ {
+ case USBKBDMODE_BASIC:
+ return &g_UsbHidBasDescCache;
+ case USBKBDMODE_EXTENDED:
+ return &g_UsbHidExtDescCache;
+ 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);
+ 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);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+ 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->DoneQueue);
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aIfs); i++)
+ usbHidQueueInit(&pThis->aIfs[i].ToHostQueue);
+
+ int rc = RTCritSectInit(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ rc = RTSemEventCreate(&pThis->hEvtDoneQueue);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Validate and read the configuration.
+ */
+ rc = pHlp->pfnCFGMValidateConfig(pCfg, "/", "Mode", "Config", "UsbHid", iInstance);
+ if (RT_FAILURE(rc))
+ return rc;
+ char szMode[64];
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "Mode", szMode, sizeof(szMode), "basic");
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS, N_("HID failed to query settings"));
+ if (!RTStrCmp(szMode, "basic"))
+ pThis->enmMode = USBKBDMODE_BASIC;
+ else if (!RTStrCmp(szMode, "extended"))
+ pThis->enmMode = USBKBDMODE_EXTENDED;
+ else
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS,
+ N_("Invalid HID mode"));
+
+ 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 */
+ usbHidQueueUrb,
+ /* 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..39e9e171
--- /dev/null
+++ b/src/VBox/Devices/Input/UsbMouse.cpp
@@ -0,0 +1,2926 @@
+/* $Id: UsbMouse.cpp $ */
+/** @file
+ * UsbMouse - USB Human Interface Device Emulation (Mouse).
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* 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
+#define USBHID_STR_ID_PRODUCT_TP 5
+/** @} */
+
+/** @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_MT_TOUCHSCREEN 0x0022
+#define USBHID_PID_MT_TOUCHPAD 0x0023
+/** @} */
+
+#define TOUCH_TIMER_MSEC 20 /* 50 Hz touch contact repeat timer. */
+
+
+/*********************************************************************************************************************************
+* 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 Touchscreen. */
+ USBHIDMODE_MT_ABSOLUTE,
+ /** Multi-touch Touchpad. */
+ USBHIDMODE_MT_RELATIVE,
+} 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 TPAD_CONTACT_MAX_COUNT 5
+
+#define MT_CONTACT_F_IN_CONTACT 0x01
+#define MT_CONTACT_F_IN_RANGE 0x02
+#define MT_CONTACT_F_CONFIDENCE 0x04
+
+#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;
+
+ /** Contact repeat timer. */
+ TMTIMERHANDLE hContactTimer;
+
+ /**
+ * 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;
+
+/**
+ * The USB HID report structure for the touchpad device.
+ * It is a superset of the multi-touch report.
+ */
+typedef struct USBHIDTP_REPORT
+{
+ USBHIDMT_REPORT mt;
+ uint8_t buttons; /* Required by Win10, not used. */
+} USBHIDTP_REPORT, *PUSBHIDTP_REPORT;
+
+typedef union USBHIDALL_REPORT
+{
+ USBHIDM_REPORT m;
+ USBHIDT_REPORT t;
+ USBHIDMT_REPORT mt;
+ USBHIDMT_REPORT_POINTER mp;
+ USBHIDTP_REPORT tp;
+} USBHIDALL_REPORT, *PUSBHIDALL_REPORT;
+
+#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" },
+ { USBHID_STR_ID_PRODUCT_TP, "USB Touchpad" },
+};
+
+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
+ },
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbHidTPEndpointDescs[] =
+{
+ {
+ {
+ /* .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
+};
+
+
+#define TOUCHPAD_REPORT_FINGER_USAGE \
+/* 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, \
+ \
+/* Note: In Range not required */ \
+/* Report Count (1) */ 0x95, 0x01, \
+/* Input (Cnst,Var) */ 0x81, 0x03, \
+ \
+/* Usage (Confidence) */ 0x09, 0x47, \
+/* 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 (5) */ 0x95, 0x05, \
+/* 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 Minimum (0) */ 0x15, 0x00, \
+/* Logical Maximum (65535) */ 0x27, 0xFF, 0xFF, 0x00, 0x00, \
+/* Report Size (16) */ 0x75, 0x10, \
+/* Unit Exponent (-2) */ 0x55, 0x0e, \
+/* Unit (Eng Lin: Length (in)) */ 0x65, 0x13, \
+/* Usage (X) */ 0x09, 0x30, \
+/* Physical Minimum (0) */ 0x35, 0x00, \
+/* Physical Maximum (461) */ 0x46, 0xcd, 0x01, \
+/* Report Count (1) */ 0x95, 0x01, \
+/* Input (Var) */ 0x81, 0x02, \
+/* Usage (Y) */ 0x09, 0x31, \
+/* Physical Maximum (346) */ 0x46, 0x5a, 0x01, \
+/* Input (Var) */ 0x81, 0x02, \
+/* End Collection */ 0xC0,
+
+static const uint8_t g_UsbHidTPReportDesc[] =
+ {
+/* Usage Page (Digitizer) */ 0x05, 0x0D,
+/* Usage (Touch Pad) */ 0x09, 0x05,
+/* 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 */
+TOUCHPAD_REPORT_FINGER_USAGE
+TOUCHPAD_REPORT_FINGER_USAGE
+TOUCHPAD_REPORT_FINGER_USAGE
+TOUCHPAD_REPORT_FINGER_USAGE
+TOUCHPAD_REPORT_FINGER_USAGE
+
+/* 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,
+
+/* Note: Button required by Windows 10 Precision Touchpad */
+/* Usage Page (Button) */ 0x05, 0x09,
+/* Usage (Button 1) */ 0x09, 0x01,
+/* Logical Maximum (1) */ 0x25, 0x01,
+/* Report Size (1) */ 0x75, 0x01,
+/* Report Count (1) */ 0x95, 0x01,
+/* Input (Var) */ 0x81, 0x02,
+/* Report Count (7) */ 0x95, 0x07,
+/* Input (Cnst,Var) */ 0x81, 0x03,
+
+/* Usage Page (Digitizer) */ 0x05, 0x0D,
+/* 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)
+};
+
+/* Additional HID class interface descriptor. */
+static const uint8_t g_UsbHidTPIfHidDesc[] =
+{
+ /* .bLength = */ 0x09,
+ /* .bDescriptorType = */ 0x21, /* HID */
+ /* .bcdHID = */ 0x10, 0x02, /* 2.1 */
+ /* .bCountryCode = */ 0,
+ /* .bNumDescriptors = */ 1,
+ /* .bDescriptorType = */ 0x22, /* Report */
+ /* .wDescriptorLength = */ (uint8_t)(sizeof(g_UsbHidTPReportDesc) & 0xFF),
+ (uint8_t)((sizeof(g_UsbHidTPReportDesc) >> 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 VUSBDESCINTERFACEEX g_UsbHidTPInterfaceDesc =
+{
+ {
+ /* .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_UsbHidTPIfHidDesc,
+ /* .cbClass = */ sizeof(g_UsbHidTPIfHidDesc),
+ &g_aUsbHidTPEndpointDescs[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 VUSBINTERFACE g_aUsbHidTPInterfaces[] =
+{
+ { &g_UsbHidTPInterfaceDesc, /* .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 VUSBDESCCONFIGEX g_UsbHidTPConfigDesc =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbHidTPInterfaces),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7),
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbHidTPInterfaces[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_MT_TOUCHSCREEN,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBHID_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBHID_STR_ID_PRODUCT_MT,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDESCDEVICE g_UsbHidTPDeviceDesc =
+{
+ /* .bLength = */ sizeof(g_UsbHidTPDeviceDesc),
+ /* .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_MT_TOUCHPAD,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBHID_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBHID_STR_ID_PRODUCT_TP,
+ /* .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
+};
+
+static const PDMUSBDESCCACHE g_UsbHidTPDescCache =
+{
+ /* .pDevice = */ &g_UsbHidTPDeviceDesc,
+ /* .paConfigs = */ &g_UsbHidTPConfigDesc,
+ /* .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 after device successfully processed it. Optionally copies data
+ * into the URB. May still generate an error if the URB is not big enough.
+ */
+static int usbHidCompleteOk(PUSBHID pThis, PVUSBURB pUrb, const void *pSrc, size_t cbSrc)
+{
+ Log(("usbHidCompleteOk/#%u: pUrb=%p:%s (cbData=%#x) cbSrc=%#zx\n", pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, pUrb->cbData, cbSrc));
+
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ size_t cbCopy = 0;
+ size_t cbSetup = 0;
+
+ if (pSrc) /* Can be NULL if not copying anything. */
+ {
+ Assert(cbSrc);
+ uint8_t *pDst = pUrb->abData;
+
+ /* Returned data is written after the setup message in control URBs. */
+ if (pUrb->enmType == VUSBXFERTYPE_MSG)
+ cbSetup = sizeof(VUSBSETUP);
+
+ Assert(pUrb->cbData >= cbSetup); /* Only triggers if URB is corrupted. */
+
+ if (pUrb->cbData > cbSetup)
+ {
+ /* There is at least one byte of room in the URB. */
+ cbCopy = RT_MIN(pUrb->cbData - cbSetup, cbSrc);
+ memcpy(pDst + cbSetup, pSrc, cbCopy);
+ pUrb->cbData = (uint32_t)(cbCopy + cbSetup);
+ Log(("Copied %zu bytes to pUrb->abData[%zu], source had %zu bytes\n", cbCopy, cbSetup, cbSrc));
+ }
+
+ /* Need to check length differences. If cbSrc is less than what
+ * the URB has space for, it'll be resolved as a short packet. But
+ * if cbSrc is bigger, there is a real problem and the host needs
+ * to see an overrun/babble error.
+ */
+ if (RT_UNLIKELY(cbSrc > cbCopy))
+ pUrb->enmStatus = VUSBSTATUS_DATA_OVERRUN;
+ }
+ else
+ Assert(cbSrc == 0); /* Make up your mind, caller! */
+
+ 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, NULL, 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;
+
+ uint8_t cMaxContacts = pThis->enmMode == USBHIDMODE_MT_RELATIVE ? TPAD_CONTACT_MAX_COUNT : MT_CONTACT_MAX_COUNT;
+ size_t cbReport = pThis->enmMode == USBHIDMODE_MT_RELATIVE ? sizeof(USBHIDTP_REPORT) : sizeof(USBHIDMT_REPORT);
+
+ 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 < cMaxContacts; 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. */
+ USBHIDTP_REPORT r;
+ USBHIDTP_REPORT *p = &r;
+ RT_ZERO(*p);
+
+ p->mt.idReport = REPORTID_TOUCH_EVENT;
+ p->mt.cContacts = cContacts;
+ p->buttons = 0; /* Not currently used. */
+
+ 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->mt.aContacts[iReportedContact].fContact = pRepContact->flags;
+ p->mt.aContacts[iReportedContact].cContact = pRepContact->id;
+ p->mt.aContacts[iReportedContact].x = pRepContact->x >> pThis->u8CoordShift;
+ p->mt.aContacts[iReportedContact].y = pRepContact->y >> pThis->u8CoordShift;
+
+ if (pThis->enmMode == USBHIDMODE_MT_RELATIVE) {
+ /** @todo Parse touch confidence in Qt frontend */
+ p->mt.aContacts[iReportedContact].fContact |= MT_CONTACT_F_CONFIDENCE;
+ }
+ }
+
+ p->mt.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", cbReport, p));
+ return usbHidCompleteOk(pThis, pUrb, p, cbReport);
+}
+
+
+/**
+ * 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_MT_ABSOLUTE || pThis->enmMode == USBHIDMODE_MT_RELATIVE)
+ {
+ /* These modes use a different reporting method and maintain fHasPendingChanges. */
+ if (pUrb)
+ return usbHidSendMultiTouchReport(pThis, pUrb);
+ return VINF_SUCCESS;
+ }
+
+ if (pUrb)
+ {
+ USBHIDTM_REPORT report;
+ PUSBHIDTM_REPORT pReport = &report;
+ size_t cbCopy;
+
+ cbCopy = usbHidFillReport(pReport, &pThis->PtrDelta, pThis->enmMode);
+ pThis->fHasPendingChanges = false;
+ return usbHidCompleteOk(pThis, pUrb, pReport, 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;
+}
+
+/**
+ * Worker for usbHidMousePutEventTouchScreen and
+ * usbHidMousePutEventTouchPad.
+ */
+static DECLCALLBACK(int) usbHidMousePutEventMultiTouch(PUSBHID pThis,
+ 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);
+ paNewContacts[i].status = MT_CONTACT_S_DIRTY;
+ paNewContacts[i].oldId = 0; /* Not used. */
+
+ if (pThis->enmMode == USBHIDMODE_MT_ABSOLUTE)
+ {
+ paNewContacts[i].flags &= MT_CONTACT_F_IN_CONTACT | MT_CONTACT_F_IN_RANGE;
+ if (paNewContacts[i].flags & MT_CONTACT_F_IN_CONTACT)
+ {
+ paNewContacts[i].flags |= MT_CONTACT_F_IN_RANGE;
+ }
+ }
+ else
+ {
+ Assert(pThis->enmMode == USBHIDMODE_MT_RELATIVE);
+ paNewContacts[i].flags &= MT_CONTACT_F_IN_CONTACT;
+ }
+ }
+
+ MTCONTACT *pCurContact = NULL;
+ MTCONTACT *pNewContact = NULL;
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ /* 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
+ ));
+ }
+ }
+ }
+ }
+
+ bool fTouchActive = false;
+
+ /* 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;
+ }
+ if (pCurContact->flags & MT_CONTACT_F_IN_CONTACT)
+ fTouchActive = true;
+ }
+
+ 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);
+
+ /* If there is an active contact, set up a timer. Windows requires that touch input
+ * gets repeated as long as there's contact, otherwise the guest decides that there
+ * is no contact anymore, even though it was never told that.
+ */
+ if (fTouchActive)
+ PDMUsbHlpTimerSetMillies(pThis->pUsbIns, pThis->hContactTimer, TOUCH_TIMER_MSEC);
+ else
+ PDMUsbHlpTimerStop(pThis->pUsbIns, pThis->hContactTimer);
+
+ RTCritSectLeave(&pThis->CritSect);
+
+ RTMemTmpFree(paNewContacts);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventTouchScreen}
+ */
+static DECLCALLBACK(int) usbHidMousePutEventTouchScreen(PPDMIMOUSEPORT pInterface,
+ uint8_t cContacts,
+ const uint64_t *pau64Contacts,
+ uint32_t u32ScanTime)
+{
+ PUSBHID pThis = RT_FROM_MEMBER(pInterface, USBHID, Lun0.IPort);
+
+ Assert(pThis->enmMode == USBHIDMODE_MT_ABSOLUTE);
+
+ return usbHidMousePutEventMultiTouch(pThis, cContacts, pau64Contacts, u32ScanTime);
+}
+
+/**
+ * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventTouchPad}
+ */
+static DECLCALLBACK(int) usbHidMousePutEventTouchPad(PPDMIMOUSEPORT pInterface,
+ uint8_t cContacts,
+ const uint64_t *pau64Contacts,
+ uint32_t u32ScanTime)
+{
+ PUSBHID pThis = RT_FROM_MEMBER(pInterface, USBHID, Lun0.IPort);
+
+ Assert(pThis->enmMode == USBHIDMODE_MT_RELATIVE);
+
+ return usbHidMousePutEventMultiTouch(pThis, cContacts, pau64Contacts, u32ScanTime);
+}
+
+/**
+ * @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, NULL, 0);
+ }
+
+ /*
+ * Status transfer.
+ */
+ case USBHIDREQSTATE_STATUS:
+ {
+ AssertFailed();
+ LogRelFlow(("usbHidHandleIntrDevToHost: Entering READY\n"));
+ pThis->enmState = USBHIDREQSTATE_READY;
+ return usbHidCompleteOk(pThis, pUrb, NULL, 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 + 1] =
+{
+ REPORTID_TOUCH_QABLOB, /* Report Id. */
+ 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_MT_ABSOLUTE) && (pThis->enmMode != USBHIDMODE_MT_RELATIVE))
+ {
+ 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)
+ {
+ uint8_t abData[sizeof(USBHIDALL_REPORT)];
+ uint8_t *pData = (uint8_t *)&abData;
+ 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 *)&abData;
+ /* 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)
+ {
+ switch (pThis->enmMode)
+ {
+ case USBHIDMODE_MT_ABSOLUTE:
+ {
+ USBHIDMT_REPORT *p = (USBHIDMT_REPORT *)&abData;
+ /* The actual state should be reported here. */
+ RT_ZERO(*p);
+ p->idReport = REPORTID_TOUCH_EVENT;
+ cbData = sizeof(USBHIDMT_REPORT);
+ break;
+ }
+ case USBHIDMODE_MT_RELATIVE:
+ {
+ USBHIDTP_REPORT *p = (USBHIDTP_REPORT *)&abData;
+ /* The actual state should be reported here. */
+ RT_ZERO(*p);
+ p->mt.idReport = REPORTID_TOUCH_EVENT;
+ cbData = sizeof(USBHIDTP_REPORT);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid HID mode %d\n", pThis->enmMode));
+ break;
+ }
+ }
+ else if (u8ReportType == 3 && u8ReportID == REPORTID_TOUCH_MAX_COUNT)
+ {
+ uint8_t cMaxContacts = 0;
+ switch (pThis->enmMode)
+ {
+ case USBHIDMODE_MT_ABSOLUTE:
+ cMaxContacts = MT_CONTACT_MAX_COUNT;
+ break;
+ case USBHIDMODE_MT_RELATIVE:
+ cMaxContacts = TPAD_CONTACT_MAX_COUNT;
+ break;
+ default:
+ AssertMsgFailed(("Invalid HID mode %d\n", pThis->enmMode));
+ break;
+ }
+ abData[0] = REPORTID_TOUCH_MAX_COUNT;
+ abData[1] = cMaxContacts; /* Contact count maximum. */
+ abData[2] = 0; /* Device identifier */
+ cbData = 3;
+ }
+ else if (u8ReportType == 3 && u8ReportID == REPORTID_TOUCH_QABLOB)
+ {
+ pData = (uint8_t *)&g_abQASampleBlob;
+ cbData = sizeof(g_abQASampleBlob);
+ }
+ else if (u8ReportType == 3 && u8ReportID == REPORTID_TOUCH_DEVCONFIG)
+ {
+ abData[0] = REPORTID_TOUCH_DEVCONFIG;
+ abData[1] = 2; /* Device mode:
+ * "HID touch device supporting contact
+ * identifier and contact count maximum."
+ */
+ abData[2] = 0; /* Device identifier */
+ cbData = 3;
+ }
+
+ if (cbData > 0)
+ {
+ rc = usbHidCompleteOk(pThis, pUrb, pData, cbData);
+ }
+ else
+ {
+ rc = usbHidCompleteStall(pThis, pEp, pUrb, "Unsupported GET_REPORT MT");
+ }
+ }
+ else
+ {
+ /* SET_REPORT */
+ rc = usbHidCompleteOk(pThis, pUrb, NULL, 0);
+ }
+ } 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_MT_ABSOLUTE:
+ cbDesc = sizeof(g_UsbHidMTIfHidDesc);
+ pDesc = (const uint8_t *)&g_UsbHidMTIfHidDesc;
+ break;
+ case USBHIDMODE_MT_RELATIVE:
+ cbDesc = sizeof(g_UsbHidTPIfHidDesc);
+ pDesc = (const uint8_t *)&g_UsbHidTPIfHidDesc;
+ break;
+ default:
+ cbDesc = 0;
+ pDesc = 0;
+ break;
+ }
+ /* Returned data is written after the setup message. */
+ cbCopy = RT_MIN(pSetup->wValue, cbDesc);
+ LogRelFlow(("usbHidMouse: GET_DESCRIPTOR DT_IF_HID_DESCRIPTOR wValue=%#x wIndex=%#x cbCopy=%#x\n",
+ pSetup->wValue, pSetup->wIndex,
+ cbCopy));
+ return usbHidCompleteOk(pThis, pUrb, pDesc, cbCopy);
+ }
+
+ 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_MT_ABSOLUTE:
+ cbDesc = sizeof(g_UsbHidMTReportDesc);
+ pDesc = (const uint8_t *)&g_UsbHidMTReportDesc;
+ break;
+ case USBHIDMODE_MT_RELATIVE:
+ cbDesc = sizeof(g_UsbHidTPReportDesc);
+ pDesc = (const uint8_t *)&g_UsbHidTPReportDesc;
+ break;
+ default:
+ cbDesc = 0;
+ pDesc = 0;
+ break;
+ }
+ /* Returned data is written after the setup message. */
+ cbCopy = RT_MIN(pSetup->wLength, cbDesc);
+ LogRelFlow(("usbHid: GET_DESCRIPTOR DT_IF_HID_REPORT wValue=%#x wIndex=%#x cbCopy=%#x\n",
+ pSetup->wValue, pSetup->wIndex,
+ cbCopy));
+ return usbHidCompleteOk(pThis, pUrb, pDesc, cbCopy);
+ }
+
+ 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. */
+ return usbHidCompleteOk(pThis, pUrb, &wRet, sizeof(wRet));
+ }
+
+ case VUSB_TO_INTERFACE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST:
+ {
+ if (pSetup->wIndex == 0)
+ {
+ return usbHidCompleteOk(pThis, pUrb, &wRet, sizeof(wRet));
+ }
+ 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;
+ return usbHidCompleteOk(pThis, pUrb, &wRet, sizeof(wRet));
+ }
+ 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_MT_ABSOLUTE,
+ pThis->enmMode == USBHIDMODE_MT_RELATIVE);
+
+ 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_MT_ABSOLUTE:
+ return &g_UsbHidMTDescCache;
+ case USBHIDMODE_MT_RELATIVE:
+ return &g_UsbHidTPDescCache;
+ 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, false);
+
+ int rc = usbHidResetWorker(pThis, NULL, false /*fSetConfig*/);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNTMTIMERUSB}
+ *
+ * A touchscreen needs to repeatedly sent contact information as long
+ * as the contact is maintained.
+ */
+static DECLCALLBACK(void) usbHidContactTimer(PPDMUSBINS pUsbIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PUSBHID pThis = (PUSBHID)pvUser;
+
+ LogRel3(("usbHid: contact repeat timer\n"));
+ usbHidSendReport(pThis);
+
+ PDMUsbHlpTimerSetMillies(pUsbIns, hTimer, TOUCH_TIMER_MSEC);
+}
+
+
+/**
+ * @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;
+ }
+
+ PDMUsbHlpTimerDestroy(pUsbIns, pThis->hContactTimer);
+}
+
+
+/**
+ * @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);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ 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 = pHlp->pfnCFGMValidateConfig(pCfg, "/", "Mode|CoordShift", "Config", "UsbHid", iInstance);
+ if (RT_FAILURE(rc))
+ return rc;
+ char szMode[64];
+ rc = pHlp->pfnCFGMQueryStringDef(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_MT_ABSOLUTE;
+ else if (!RTStrCmp(szMode, "touchpad"))
+ pThis->enmMode = USBHIDMODE_MT_RELATIVE;
+ 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.pfnPutEventTouchScreen = usbHidMousePutEventTouchScreen;
+ pThis->Lun0.IPort.pfnPutEventTouchPad = usbHidMousePutEventTouchPad;
+
+ /*
+ * 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 = pHlp->pfnCFGMQueryU8Def(pCfg, "CoordShift", &pThis->u8CoordShift, 1);
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS, N_("HID failed to query shift factor"));
+
+ /*
+ * Create the touchscreen contact repeat timer.
+ */
+ rc = PDMUsbHlpTimerCreate(pUsbIns, TMCLOCK_VIRTUAL, usbHidContactTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT,
+ "Touchscreen Contact", &pThis->hContactTimer);
+ AssertRCReturn(rc, rc);
+
+ 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..1c6468d2
--- /dev/null
+++ b/src/VBox/Devices/Input/testcase/Makefile.kmk
@@ -0,0 +1,44 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-makefile for input test cases.
+#
+
+#
+# Copyright (C) 2013-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+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..1b411583
--- /dev/null
+++ b/src/VBox/Devices/Input/testcase/tstUsbMouse.cpp
@@ -0,0 +1,412 @@
+/* $Id: tstUsbMouse.cpp $ */
+/** @file
+ * tstUsbMouse.cpp - testcase USB mouse and tablet devices.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxDD.h"
+#include <VBox/vmm/pdmdrv.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/test.h>
+#include <iprt/uuid.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** 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 absolute multi-touch mode currently supported? */
+ bool fMTAbs;
+ /** Is relative multi-touch mode currently supported? */
+ bool fMTRel;
+} 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 fMTAbs, bool fMTRel)
+{
+ PDRVTSTMOUSE pDrv = RT_FROM_MEMBER(pInterface, DRVTSTMOUSE, IConnector);
+ pDrv->fRel = fRel;
+ pDrv->fAbs = fAbs;
+ pDrv->fMTAbs = fMTAbs;
+ pDrv->fMTRel = fMTRel;
+}
+
+
+static int tstMouseConstruct(RTTEST hTest, int iInstance, const char *pcszMode,
+ uint8_t u8CoordShift, PPDMUSBINS *ppThis,
+ uint32_t uInstanceVersion = PDM_USBINS_VERSION)
+{
+ size_t cbUsbIns = RT_UOFFSETOF(PDMUSBINS, achInstanceData) + g_UsbHidMou.cbInstance;
+ PPDMUSBINS pUsbIns;
+ int rc = RTTestGuardedAlloc(hTest, cbUsbIns, 1, RTRandU32Ex(0, 1) != 0 /*fHead*/, (void **)&pUsbIns);
+ if (RT_SUCCESS(rc))
+ {
+ RT_BZERO(pUsbIns, cbUsbIns);
+
+ PCFGMNODE 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;
+ pUsbIns->pvInstanceDataR3 = pUsbIns->achInstanceData;
+ pUsbIns->pCfg = pCfg;
+ rc = g_UsbHidMou.pfnConstruct(pUsbIns, iInstance, pCfg, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ *ppThis = pUsbIns;
+ return rc;
+ }
+ }
+ /* Failure */
+ CFGMR3DestroyTree(pCfg);
+ }
+ }
+ RTTestGuardedFree(hTest, pUsbIns);
+ return rc;
+}
+
+
+static void tstMouseDestruct(RTTEST hTest, PPDMUSBINS pUsbIns)
+{
+ if (pUsbIns)
+ {
+ g_UsbHidMou.pfnDestruct(pUsbIns);
+ CFGMR3DestroyTree(pUsbIns->pCfg);
+ RTTestGuardedFree(hTest, pUsbIns);
+ }
+}
+
+
+static void testConstructAndDestruct(RTTEST hTest)
+{
+ RTTestSub(hTest, "simple construction and destruction");
+
+ /*
+ * Normal check first.
+ */
+ PPDMUSBINS pUsbIns = NULL;
+ RTTEST_CHECK_RC(hTest, tstMouseConstruct(hTest, 0, "relative", 1, &pUsbIns), VINF_SUCCESS);
+ tstMouseDestruct(hTest, 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(hTest, 0, "relative", 1, &pUsbIns, s_aVersionTests[i].uInsVersion),
+ s_aVersionTests[i].rc);
+ tstMouseDestruct(hTest, pUsbIns);
+ }
+ 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(hTest, 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;
+ Urb.enmType = VUSBXFERTYPE_INTR;
+ Urb.cbData = 4;
+ 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);
+ tstMouseDestruct(hTest, pUsbIns);
+}
+
+
+static void testSendPositionAbs(RTTEST hTest)
+{
+ PPDMUSBINS pUsbIns = NULL;
+ VUSBURB Urb;
+ RTTestSub(hTest, "sending an absolute position event");
+ int rc = tstMouseConstruct(hTest, 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;
+ Urb.enmType = VUSBXFERTYPE_INTR;
+ Urb.cbData = 8;
+ 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);
+ tstMouseDestruct(hTest, 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(hTest, 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;
+ Urb.enmType = VUSBXFERTYPE_INTR;
+ Urb.cbData = 8;
+ 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);
+ tstMouseDestruct(hTest, 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.pfnCFGMValidateConfig = CFGMR3ValidateConfig;
+ g_tstUsbHlp.pfnCFGMQueryStringDef = CFGMR3QueryStringDef;
+ g_tstUsbHlp.pfnCFGMQueryU8Def = CFGMR3QueryU8Def;
+ 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);
+}