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