diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Devices/Input | |
parent | Initial commit. (diff) | |
download | virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.tar.xz virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.zip |
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Input')
-rw-r--r-- | src/VBox/Devices/Input/DevPS2.cpp | 1265 | ||||
-rw-r--r-- | src/VBox/Devices/Input/DevPS2.h | 417 | ||||
-rw-r--r-- | src/VBox/Devices/Input/DevPS2K.cpp | 1520 | ||||
-rw-r--r-- | src/VBox/Devices/Input/DevPS2M.cpp | 1196 | ||||
-rw-r--r-- | src/VBox/Devices/Input/DrvKeyboardQueue.cpp | 610 | ||||
-rw-r--r-- | src/VBox/Devices/Input/DrvMouseQueue.cpp | 468 | ||||
-rw-r--r-- | src/VBox/Devices/Input/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Devices/Input/UsbKbd.cpp | 1894 | ||||
-rw-r--r-- | src/VBox/Devices/Input/UsbMouse.cpp | 2881 | ||||
-rw-r--r-- | src/VBox/Devices/Input/testcase/Makefile.kmk | 44 | ||||
-rw-r--r-- | src/VBox/Devices/Input/testcase/tstUsbMouse.cpp | 412 |
11 files changed, 10707 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..94a87243 --- /dev/null +++ b/src/VBox/Devices/Input/DevPS2.cpp @@ -0,0 +1,1265 @@ +/* $Id: DevPS2.cpp $ */ +/** @file + * DevPS2 - PS/2 keyboard & mouse controller device. + */ + +/* + * Copyright (C) 2006-2022 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. + * + * @returns VBox status code. + * @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..283d57e6 --- /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-2022 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..29c0d96c --- /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-2022 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..1a05d0fd --- /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-2022 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..13ee299e --- /dev/null +++ b/src/VBox/Devices/Input/DrvKeyboardQueue.cpp @@ -0,0 +1,610 @@ +/* $Id: DrvKeyboardQueue.cpp $ */ +/** @file + * VBox input devices: Keyboard queue driver + */ + +/* + * Copyright (C) 2006-2022 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. + * + * @returns VBox status code. + * @param pDrvIns The drive instance data. + */ +static DECLCALLBACK(void) drvKbdQueuePowerOn(PPDMDRVINS pDrvIns) +{ + PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE); + pThis->fInactive = false; +} + + +/** + * Reset notification. + * + * @returns VBox status code. + * @param pDrvIns The drive instance data. + */ +static DECLCALLBACK(void) drvKbdQueueReset(PPDMDRVINS pDrvIns) +{ + //PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE); + /** @todo purge the queue on reset. */ + RT_NOREF(pDrvIns); +} + + +/** + * Suspend notification. + * + * @returns VBox status code. + * @param pDrvIns The drive instance data. + */ +static DECLCALLBACK(void) drvKbdQueueSuspend(PPDMDRVINS pDrvIns) +{ + PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE); + pThis->fSuspended = true; +} + + +/** + * Resume notification. + * + * @returns VBox status code. + * @param pDrvIns The drive instance data. + */ +static DECLCALLBACK(void) drvKbdQueueResume(PPDMDRVINS pDrvIns) +{ + PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE); + pThis->fInactive = false; + pThis->fSuspended = false; +} + + +/** + * Power Off notification. + * + * @param pDrvIns The drive instance data. + */ +static DECLCALLBACK(void) drvKbdQueuePowerOff(PPDMDRVINS pDrvIns) +{ + PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE); + pThis->fInactive = true; +} + + +/** + * Construct a keyboard driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvKbdQueueConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + 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..07ded5b2 --- /dev/null +++ b/src/VBox/Devices/Input/DrvMouseQueue.cpp @@ -0,0 +1,468 @@ +/* $Id: DrvMouseQueue.cpp $ */ +/** @file + * VBox input devices: Mouse queue driver + */ + +/* + * Copyright (C) 2006-2022 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. + * + * @returns VBox status code. + * @param pDrvIns The drive instance data. + */ +static DECLCALLBACK(void) drvMouseQueuePowerOn(PPDMDRVINS pDrvIns) +{ + PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE); + pThis->fInactive = false; +} + + +/** + * Reset notification. + * + * @returns VBox status code. + * @param pDrvIns The drive instance data. + */ +static DECLCALLBACK(void) drvMouseQueueReset(PPDMDRVINS pDrvIns) +{ + //PDRVKBDQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVKBDQUEUE); + /** @todo purge the queue on reset. */ + RT_NOREF(pDrvIns); +} + + +/** + * Suspend notification. + * + * @returns VBox status code. + * @param pDrvIns The drive instance data. + */ +static DECLCALLBACK(void) drvMouseQueueSuspend(PPDMDRVINS pDrvIns) +{ + PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE); + pThis->fInactive = true; +} + + +/** + * Resume notification. + * + * @returns VBox status code. + * @param pDrvIns The drive instance data. + */ +static DECLCALLBACK(void) drvMouseQueueResume(PPDMDRVINS pDrvIns) +{ + PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE); + pThis->fInactive = false; +} + + +/** + * Power Off notification. + * + * @param pDrvIns The drive instance data. + */ +static DECLCALLBACK(void) drvMouseQueuePowerOff(PPDMDRVINS pDrvIns) +{ + PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE); + pThis->fInactive = true; +} + + +/** + * Construct a mouse driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvMouseQueueConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + 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..8c95d33e --- /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-2022 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..99d8da2f --- /dev/null +++ b/src/VBox/Devices/Input/UsbMouse.cpp @@ -0,0 +1,2881 @@ +/* $Id: UsbMouse.cpp $ */ +/** @file + * UsbMouse - USB Human Interface Device Emulation (Mouse). + */ + +/* + * Copyright (C) 2007-2022 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 +/** @} */ + + +/********************************************************************************************************************************* +* 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; + + /** + * 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 + )); + } + } + } + } + + /* Mark still dirty existing contacts as cancelled, because a new set of contacts does not include them. */ + for (i = 0; i < RT_ELEMENTS(pThis->aCurrentContactState); i++) + { + pCurContact = &pThis->aCurrentContactState[i]; + if (pCurContact->status & MT_CONTACT_S_DIRTY) + { + pCurContact->status |= MT_CONTACT_S_CANCELLED; + pCurContact->status &= ~MT_CONTACT_S_DIRTY; + } + } + + pThis->u32LastTouchScanTime = u32ScanTime; + + LogRel3(("usbHid: scanTime (ms): %d\n", pThis->u32LastTouchScanTime)); + for (i = 0; i < RT_ELEMENTS(pThis->aCurrentContactState); i++) + { + LogRel3(("usbHid: contact state[%d]: %d,%d id %d flags %RX8 status %RX8 oldId %d\n", + i, + pThis->aCurrentContactState[i].x, + pThis->aCurrentContactState[i].y, + pThis->aCurrentContactState[i].id, + pThis->aCurrentContactState[i].flags, + pThis->aCurrentContactState[i].status, + pThis->aCurrentContactState[i].oldId + )); + } + + pThis->fTouchStateUpdated = true; + pThis->fHasPendingChanges = true; + + /* Send a report if possible. */ + usbHidSendReport(pThis); + + RTCritSectLeave(&pThis->CritSect); + + RTMemTmpFree(paNewContacts); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{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; +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnDestruct} + */ +static DECLCALLBACK(void) usbHidDestruct(PPDMUSBINS pUsbIns) +{ + PDMUSB_CHECK_VERSIONS_RETURN_VOID(pUsbIns); + PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID); + LogRelFlow(("usbHidDestruct/#%u:\n", pUsbIns->iInstance)); + + if (RTCritSectIsInitialized(&pThis->CritSect)) + { + RTCritSectEnter(&pThis->CritSect); + RTCritSectLeave(&pThis->CritSect); + RTCritSectDelete(&pThis->CritSect); + } + + if (pThis->hEvtDoneQueue != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pThis->hEvtDoneQueue); + pThis->hEvtDoneQueue = NIL_RTSEMEVENT; + } +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnConstruct} + */ +static DECLCALLBACK(int) usbHidConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal) +{ + RT_NOREF1(pCfgGlobal); + PDMUSB_CHECK_VERSIONS_RETURN(pUsbIns); + PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID); + 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")); + + 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..03de47b7 --- /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-2022 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..89e812c5 --- /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-2022 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); +} |