diff options
Diffstat (limited to 'src/VBox/Devices/Input')
-rw-r--r-- | src/VBox/Devices/Input/DevPS2.cpp | 277 | ||||
-rw-r--r-- | src/VBox/Devices/Input/DrvKeyboardQueue.cpp | 2 | ||||
-rw-r--r-- | src/VBox/Devices/Input/DrvMouseQueue.cpp | 112 | ||||
-rw-r--r-- | src/VBox/Devices/Input/PS2Dev.h | 55 | ||||
-rw-r--r-- | src/VBox/Devices/Input/PS2K.cpp | 615 | ||||
-rw-r--r-- | src/VBox/Devices/Input/PS2M.cpp | 1151 | ||||
-rw-r--r-- | src/VBox/Devices/Input/UsbKbd.cpp | 82 | ||||
-rw-r--r-- | src/VBox/Devices/Input/UsbMouse.cpp | 1270 | ||||
-rw-r--r-- | src/VBox/Devices/Input/testcase/Makefile.kmk | 34 | ||||
-rw-r--r-- | src/VBox/Devices/Input/testcase/tstUsbMouse.cpp | 329 |
10 files changed, 3399 insertions, 528 deletions
diff --git a/src/VBox/Devices/Input/DevPS2.cpp b/src/VBox/Devices/Input/DevPS2.cpp index d63bfa5c..12d58bf2 100644 --- a/src/VBox/Devices/Input/DevPS2.cpp +++ b/src/VBox/Devices/Input/DevPS2.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2006-2010 Oracle Corporation + * Copyright (C) 2006-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -53,7 +53,7 @@ #include "VBoxDD.h" #include "PS2Dev.h" -#define PCKBD_SAVED_STATE_VERSION 6 +#define PCKBD_SAVED_STATE_VERSION 7 #ifndef VBOX_DEVICE_STRUCT_TESTCASE @@ -95,6 +95,7 @@ RT_C_DECLS_END #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 */ @@ -117,6 +118,7 @@ RT_C_DECLS_END #define KBD_MODE_KCC 0x40 /* Scan code conversion to PC format */ #define KBD_MODE_RFU 0x80 +#ifndef VBOX_WITH_NEW_PS2M /* Mouse Commands */ #define AUX_SET_SCALE11 0xE6 /* Set 1:1 scaling */ #define AUX_SET_SCALE21 0xE7 /* Set 2:1 scaling */ @@ -164,14 +166,26 @@ typedef struct { #define MOUSE_EVENT_QUEUE_SIZE 256 -typedef struct { +typedef struct +{ uint8_t data[MOUSE_EVENT_QUEUE_SIZE]; - int rptr, wptr, count; + int rptr; + int wptr; + int count; } MouseEventQueue; +#endif -typedef struct KBDState { +/** + * The keyboard controller/device state. + * + * @note We use the default critical section for serialize data access. + */ +typedef struct KBDState +{ +#ifndef VBOX_WITH_NEW_PS2M MouseCmdQueue mouse_command_queue; MouseEventQueue mouse_event_queue; +#endif uint8_t write_cmd; /* if non zero, write data to port 60 is expected */ uint8_t status; uint8_t mode; @@ -179,6 +193,7 @@ typedef struct KBDState { /* keyboard state */ int32_t translate; int32_t xlat_state; +#ifndef VBOX_WITH_NEW_PS2M /* mouse state */ int32_t mouse_write_cmd; uint8_t mouse_status; @@ -194,6 +209,7 @@ typedef struct KBDState { int32_t mouse_flags; uint8_t mouse_buttons; uint8_t mouse_buttons_reported; +#endif uint32_t Alignment0; @@ -204,9 +220,6 @@ typedef struct KBDState { /** Pointer to the device instance. */ PPDMDEVINSR0 pDevInsR0; - /** Critical section protecting the state. */ - PDMCRITSECT CritSect; - /** Keyboard state (implemented in separate PS2K module). */ #ifdef VBOX_DEVICE_STRUCT_TESTCASE uint8_t KbdFiller[PS2K_STRUCT_FILLER]; @@ -214,6 +227,14 @@ typedef struct KBDState { PS2K Kbd; #endif +#ifdef VBOX_WITH_NEW_PS2M + /** Mouse state (implemented in separate PS2M module). */ +#ifdef VBOX_DEVICE_STRUCT_TESTCASE + uint8_t AuxFiller[PS2M_STRUCT_FILLER]; +#else + PS2M Aux; +#endif +#else /** * Mouse port - LUN#1. * @@ -232,6 +253,7 @@ typedef struct KBDState { /** The mouse interface of the attached mouse driver. */ R3PTRTYPE(PPDMIMOUSECONNECTOR) pDrv; } Mouse; +#endif } KBDState; #ifndef VBOX_DEVICE_STRUCT_TESTCASE @@ -239,8 +261,10 @@ typedef struct KBDState { /* update irq and KBD_STAT_[MOUSE_]OBF */ static void kbd_update_irq(KBDState *s) { +#ifndef VBOX_WITH_NEW_PS2M MouseCmdQueue *mcq = &s->mouse_command_queue; MouseEventQueue *meq = &s->mouse_event_queue; +#endif int irq12_level, irq1_level; uint8_t val; @@ -284,6 +308,13 @@ static void kbd_update_irq(KBDState *s) s->status |= KBD_STAT_OBF; } } +#ifdef VBOX_WITH_NEW_PS2M + 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; + } +#else else if ((mcq->count || meq->count) && !(s->mode & KBD_MODE_DISABLE_MOUSE)) { s->status |= KBD_STAT_OBF | KBD_STAT_MOUSE_OBF; @@ -302,6 +333,7 @@ static void kbd_update_irq(KBDState *s) meq->count--; } } +#endif } /* Determine new IRQ state. */ if (s->status & KBD_STAT_OBF) { @@ -326,6 +358,7 @@ void KBCUpdateInterrupts(void *pKbc) kbd_update_irq(s); } +#ifndef VBOX_WITH_NEW_PS2M static void kbd_queue(KBDState *s, int b, int aux) { MouseCmdQueue *mcq = &s->mouse_command_queue; @@ -367,6 +400,7 @@ static void kbd_queue(KBDState *s, int b, int aux) } kbd_update_irq(s); } +#endif static void kbc_dbb_out(void *opaque, uint8_t val) { @@ -379,6 +413,17 @@ static void kbc_dbb_out(void *opaque, uint8_t val) 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; @@ -488,6 +533,7 @@ static int kbd_write_command(void *opaque, uint32_t addr, uint32_t val) 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 */ @@ -542,12 +588,23 @@ static uint32_t kbd_read_data(void *opaque, uint32_t addr) return val; } -PS2K *GetPS2KFromDevIns(PPDMDEVINS pDevIns) +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 *); +#ifdef VBOX_WITH_NEW_PS2M + return &pThis->Aux; +#else + return NULL; +#endif +} + +#ifndef VBOX_WITH_NEW_PS2M static void kbd_mouse_set_reported_buttons(KBDState *s, unsigned fButtons, unsigned fButtonMask) { s->mouse_buttons_reported |= (fButtons & fButtonMask); @@ -698,7 +755,8 @@ static void kbd_mouse_update_downstream_status(KBDState *pThis) { PPDMIMOUSECONNECTOR pDrv = pThis->Mouse.pDrv; bool fEnabled = !!(pThis->mouse_status & MOUSE_STATUS_ENABLED); - pDrv->pfnReportModes(pDrv, fEnabled, false); + if (pDrv) + pDrv->pfnReportModes(pDrv, fEnabled, false, false); } #endif /* IN_RING3 */ @@ -904,6 +962,7 @@ static int kbd_write_mouse(KBDState *s, int val) } return rc; } +#endif static int kbd_write_data(void *opaque, uint32_t addr, uint32_t val) { @@ -932,7 +991,7 @@ static int kbd_write_data(void *opaque, uint32_t addr, uint32_t val) kbc_dbb_out(s, val); break; case KBD_CCMD_WRITE_AUX_OBUF: - kbd_queue(s, val, 1); + kbc_dbb_out_aux(s, val); break; case KBD_CCMD_WRITE_OUTPORT: #ifdef TARGET_I386 @@ -954,7 +1013,13 @@ static int kbd_write_data(void *opaque, uint32_t addr, uint32_t val) case KBD_CCMD_WRITE_MOUSE: /* Automatically enables aux interface. */ s->mode &= ~KBD_MODE_DISABLE_MOUSE; +#ifdef VBOX_WITH_NEW_PS2M + rc = PS2MByteToAux(&s->Aux, val); + if (rc == VINF_SUCCESS) + kbd_update_irq(s); +#else rc = kbd_write_mouse(s, val); +#endif break; default: break; @@ -969,15 +1034,18 @@ static int kbd_write_data(void *opaque, uint32_t addr, uint32_t val) static void kbd_reset(void *opaque) { KBDState *s = (KBDState*)opaque; +#ifndef VBOX_WITH_NEW_PS2M MouseCmdQueue *mcq; MouseEventQueue *meq; s->mouse_write_cmd = -1; +#endif 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; +#ifndef VBOX_WITH_NEW_PS2M if (s->mouse_status) { s->mouse_status = 0; @@ -1003,18 +1071,22 @@ static void kbd_reset(void *opaque) meq->rptr = 0; meq->wptr = 0; meq->count = 0; +#endif } static void kbd_save(QEMUFile* f, void* opaque) { +#ifndef VBOX_WITH_NEW_PS2M uint32_t cItems; int i; +#endif KBDState *s = (KBDState*)opaque; qemu_put_8s(f, &s->write_cmd); qemu_put_8s(f, &s->status); qemu_put_8s(f, &s->mode); qemu_put_8s(f, &s->dbbout); +#ifndef VBOX_WITH_NEW_PS2M qemu_put_be32s(f, &s->mouse_write_cmd); qemu_put_8s(f, &s->mouse_status); qemu_put_8s(f, &s->mouse_resolution); @@ -1041,6 +1113,7 @@ static void kbd_save(QEMUFile* f, void* opaque) for (i = s->mouse_event_queue.rptr; cItems-- > 0; i = (i + 1) % RT_ELEMENTS(s->mouse_event_queue.data)) SSMR3PutU8(f, s->mouse_event_queue.data[i]); Log(("kbd_save: %d mouse event queue items stored\n", s->mouse_event_queue.count)); +#endif /* terminator */ SSMR3PutU32(f, ~0); @@ -1074,6 +1147,7 @@ static int kbd_load(QEMUFile* f, void* opaque, int version_id) { qemu_get_8s(f, &s->dbbout); } +#ifndef VBOX_WITH_NEW_PS2M qemu_get_be32s(f, (uint32_t *)&s->mouse_write_cmd); qemu_get_8s(f, &s->mouse_status); qemu_get_8s(f, &s->mouse_resolution); @@ -1105,6 +1179,7 @@ static int kbd_load(QEMUFile* f, void* opaque, int version_id) s->mouse_event_queue.count = 0; s->mouse_event_queue.rptr = 0; s->mouse_event_queue.wptr = 0; +#endif /* Determine the translation state. */ s->translate = (s->mode & KBD_MODE_KCC) == KBD_MODE_KCC; @@ -1126,6 +1201,7 @@ static int kbd_load(QEMUFile* f, void* opaque, int version_id) Log(("kbd_load: %d keyboard queue items discarded from old saved state\n", u32)); } +#ifndef VBOX_WITH_NEW_PS2M rc = SSMR3GetU32(f, &u32); if (RT_FAILURE(rc)) return rc; @@ -1161,6 +1237,21 @@ static int kbd_load(QEMUFile* f, void* opaque, int version_id) s->mouse_event_queue.wptr = u32 % RT_ELEMENTS(s->mouse_event_queue.data); s->mouse_event_queue.count = u32; Log(("kbd_load: %d mouse event queue items loaded\n", u32)); +#else + if (version_id <= 6) + { + rc = SSMR3GetU32(f, &u32); + if (RT_FAILURE(rc)) + return rc; + for (i = 0; i < u32; i++) + { + rc = SSMR3GetU8(f, &u8Dummy); + if (RT_FAILURE(rc)) + return rc; + } + Log(("kbd_load: %d mouse event queue items discarded from old saved state\n", u32)); + } +#endif /* terminator */ rc = SSMR3GetU32(f, &u32); @@ -1171,8 +1262,10 @@ static int kbd_load(QEMUFile* f, void* opaque, int version_id) AssertMsgFailed(("u32=%#x\n", u32)); return VERR_SSM_DATA_UNIT_FORMAT_CHANGED; } +#ifndef VBOX_WITH_NEW_PS2M /* Resend a notification to Main if the device is active */ kbd_mouse_update_downstream_status(s); +#endif return 0; } #endif /* IN_RING3 */ @@ -1199,14 +1292,9 @@ PDMBOTHCBDECL(int) kbdIOPortDataRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT if (cb == 1) { KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *); - int rc = PDMCritSectEnter(&pThis->CritSect, VINF_IOM_R3_IOPORT_READ); - if (RT_LIKELY(rc == VINF_SUCCESS)) - { - *pu32 = kbd_read_data(pThis, Port); - PDMCritSectLeave(&pThis->CritSect); - Log2(("kbdIOPortDataRead: Port=%#x cb=%d *pu32=%#x\n", Port, cb, *pu32)); - } - return rc; + *pu32 = kbd_read_data(pThis, Port); + Log2(("kbdIOPortDataRead: Port=%#x cb=%d *pu32=%#x\n", Port, cb, *pu32)); + return VINF_SUCCESS; } AssertMsgFailed(("Port=%#x cb=%d\n", Port, cb)); return VERR_IOM_IOPORT_UNUSED; @@ -1230,13 +1318,8 @@ PDMBOTHCBDECL(int) kbdIOPortDataWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT if (cb == 1) { KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *); - rc = PDMCritSectEnter(&pThis->CritSect, VINF_IOM_R3_IOPORT_WRITE); - if (RT_LIKELY(rc == VINF_SUCCESS)) - { - rc = kbd_write_data(pThis, Port, u32); - PDMCritSectLeave(&pThis->CritSect); - Log2(("kbdIOPortDataWrite: Port=%#x cb=%d u32=%#x\n", Port, cb, u32)); - } + rc = kbd_write_data(pThis, Port, u32); + Log2(("kbdIOPortDataWrite: Port=%#x cb=%d u32=%#x\n", Port, cb, u32)); } else AssertMsgFailed(("Port=%#x cb=%d\n", Port, cb)); @@ -1260,14 +1343,9 @@ PDMBOTHCBDECL(int) kbdIOPortStatusRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPOR if (cb == 1) { KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *); - int rc = PDMCritSectEnter(&pThis->CritSect, VINF_IOM_R3_IOPORT_READ); - if (RT_LIKELY(rc == VINF_SUCCESS)) - { - *pu32 = kbd_read_status(pThis, Port); - PDMCritSectLeave(&pThis->CritSect); - Log2(("kbdIOPortStatusRead: Port=%#x cb=%d -> *pu32=%#x\n", Port, cb, *pu32)); - } - return rc; + *pu32 = kbd_read_status(pThis, Port); + Log2(("kbdIOPortStatusRead: Port=%#x cb=%d -> *pu32=%#x\n", Port, cb, *pu32)); + return VINF_SUCCESS; } AssertMsgFailed(("Port=%#x cb=%d\n", Port, cb)); return VERR_IOM_IOPORT_UNUSED; @@ -1291,13 +1369,8 @@ PDMBOTHCBDECL(int) kbdIOPortCommandWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOP if (cb == 1) { KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *); - rc = PDMCritSectEnter(&pThis->CritSect, VINF_IOM_R3_IOPORT_WRITE); - if (RT_LIKELY(rc == VINF_SUCCESS)) - { - rc = kbd_write_command(pThis, Port, u32); - PDMCritSectLeave(&pThis->CritSect); - Log2(("kbdIOPortCommandWrite: Port=%#x cb=%d u32=%#x rc=%Rrc\n", Port, cb, u32, rc)); - } + rc = kbd_write_command(pThis, Port, 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)); @@ -1311,13 +1384,16 @@ PDMBOTHCBDECL(int) kbdIOPortCommandWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOP * * @returns VBox status code. * @param pDevIns The device instance. - * @param pSSMHandle The handle to save the state to. + * @param pSSM The handle to save the state to. */ -static DECLCALLBACK(int) kbdSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle) +static DECLCALLBACK(int) kbdSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) { KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *); - kbd_save(pSSMHandle, pThis); - PS2KSaveState(pSSMHandle, &pThis->Kbd); + kbd_save(pSSM, pThis); + PS2KSaveState(&pThis->Kbd, pSSM); +#ifdef VBOX_WITH_NEW_PS2M + PS2MSaveState(&pThis->Aux, pSSM); +#endif return VINF_SUCCESS; } @@ -1327,19 +1403,35 @@ static DECLCALLBACK(int) kbdSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle) * * @returns VBox status code. * @param pDevIns The device instance. - * @param pSSMHandle The handle to the saved state. + * @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 pSSMHandle, uint32_t uVersion, uint32_t uPass) +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(pSSMHandle, pThis, uVersion); + rc = kbd_load(pSSM, pThis, uVersion); if (uVersion >= 6) - rc = PS2KLoadState(pSSMHandle, &pThis->Kbd, uVersion); + rc = PS2KLoadState(&pThis->Kbd, pSSM, uVersion); +#ifdef VBOX_WITH_NEW_PS2M + if (uVersion >= 7) + rc = PS2MLoadState(&pThis->Aux, pSSM, uVersion); +#endif + 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; } @@ -1355,9 +1447,13 @@ static DECLCALLBACK(void) kbdReset(PPDMDEVINS pDevIns) kbd_reset(pThis); PS2KReset(&pThis->Kbd); +#ifdef VBOX_WITH_NEW_PS2M + PS2MReset(&pThis->Aux); +#endif } +#ifndef VBOX_WITH_NEW_PS2M /* -=-=-=-=-=- Mouse: IBase -=-=-=-=-=- */ /** @@ -1377,28 +1473,43 @@ static DECLCALLBACK(void *) kbdMouseQueryInterface(PPDMIBASE pInterface, const /** * @interface_method_impl{PDMIMOUSEPORT, pfnPutEvent} */ -static DECLCALLBACK(int) kbdMousePutEvent(PPDMIMOUSEPORT pInterface, int32_t iDeltaX, int32_t iDeltaY, - int32_t iDeltaZ, int32_t iDeltaW, uint32_t fButtonStates) +static DECLCALLBACK(int) kbdMousePutEvent(PPDMIMOUSEPORT pInterface, int32_t dx, + int32_t dy, int32_t dz, int32_t dw, + uint32_t fButtons) { KBDState *pThis = RT_FROM_MEMBER(pInterface, KBDState, Mouse.IPort); - int rc = PDMCritSectEnter(&pThis->CritSect, VERR_SEM_BUSY); + int rc = PDMCritSectEnter(pThis->pDevInsR3->pCritSectRoR3, VERR_SEM_BUSY); AssertReleaseRC(rc); - pc_kbd_mouse_event(pThis, iDeltaX, iDeltaY, iDeltaZ, iDeltaW, fButtonStates); + pc_kbd_mouse_event(pThis, dx, dy, dz, dw, fButtons); - PDMCritSectLeave(&pThis->CritSect); + PDMCritSectLeave(pThis->pDevInsR3->pCritSectRoR3); return VINF_SUCCESS; } /** * @interface_method_impl{PDMIMOUSEPORT, pfnPutEventAbs} */ -static DECLCALLBACK(int) kbdMousePutEventAbs(PPDMIMOUSEPORT pInterface, uint32_t uX, uint32_t uY, int32_t iDeltaZ, int32_t iDeltaW, uint32_t fButtons) +static DECLCALLBACK(int) kbdMousePutEventAbs(PPDMIMOUSEPORT pInterface, + uint32_t x, uint32_t y, int32_t dz, + int32_t dw, uint32_t fButtons) { AssertFailedReturn(VERR_NOT_SUPPORTED); - NOREF(pInterface); NOREF(uX); NOREF(uY); NOREF(iDeltaZ); NOREF(iDeltaW); NOREF(fButtons); + NOREF(pInterface); NOREF(x); NOREF(y); NOREF(dz); NOREF(dw); NOREF(fButtons); } +/** + * @interface_method_impl{PDMIMOUSEPORT, pfnPutEventMultiTouch} + */ +static DECLCALLBACK(int) kbdMousePutEventMultiTouch(PPDMIMOUSEPORT pInterface, + uint8_t cContacts, + const uint64_t *pau64Contacts, + uint32_t u32ScanTime) +{ + AssertFailedReturn(VERR_NOT_SUPPORTED); + NOREF(pInterface); NOREF(cContacts); NOREF(pau64Contacts); NOREF(u32ScanTime); +} +#endif /* -=-=-=-=-=- real code -=-=-=-=-=- */ @@ -1432,13 +1543,16 @@ static DECLCALLBACK(int) kbdAttach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t { /* LUN #0: keyboard */ case 0: - rc = PS2KAttach(pDevIns, &pThis->Kbd, iLUN, fFlags); + rc = PS2KAttach(&pThis->Kbd, pDevIns, iLUN, fFlags); if (RT_FAILURE(rc)) return rc; break; /* LUN #1: aux/mouse */ case 1: +#ifdef VBOX_WITH_NEW_PS2M + rc = PS2MAttach(&pThis->Aux, pDevIns, iLUN, fFlags); +#else rc = PDMDevHlpDriverAttach(pDevIns, iLUN, &pThis->Mouse.IBase, &pThis->Mouse.pDrvBase, "Aux (Mouse) Port"); if (RT_SUCCESS(rc)) { @@ -1456,6 +1570,7 @@ static DECLCALLBACK(int) kbdAttach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t } else AssertLogRelMsgFailed(("Failed to attach LUN #1! rc=%Rrc\n", rc)); +#endif break; default: @@ -1520,24 +1635,10 @@ 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); -} - - -/** - * Destruct a device instance for a VM. - * - * @returns VBox status. - * @param pDevIns The device instance data. - */ -static DECLCALLBACK(int) kbdDestruct(PPDMDEVINS pDevIns) -{ - KBDState *pThis = PDMINS_2_DATA(pDevIns, KBDState *); - PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); - - PDMR3CritSectDelete(&pThis->CritSect); - - return VINF_SUCCESS; + PS2KRelocate(&pThis->Kbd, offDelta, pDevIns); +#ifdef VBOX_WITH_NEW_PS2M + PS2MRelocate(&pThis->Aux, offDelta, pDevIns); +#endif } @@ -1565,6 +1666,7 @@ static DECLCALLBACK(int) kbdConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNO rc = CFGMR3QueryBoolDef(pCfg, "R0Enabled", &fR0Enabled, true); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"R0Enabled\" from the config")); +fGCEnabled = fR0Enabled = false; Log(("pckbd: fGCEnabled=%RTbool fR0Enabled=%RTbool\n", fGCEnabled, fR0Enabled)); @@ -1575,20 +1677,20 @@ static DECLCALLBACK(int) kbdConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNO pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns); pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns); - rc = PS2KConstruct(pDevIns, &pThis->Kbd, pThis, iInstance); + rc = PS2KConstruct(&pThis->Kbd, pDevIns, pThis, iInstance); if (RT_FAILURE(rc)) return rc; +#ifdef VBOX_WITH_NEW_PS2M + rc = PS2MConstruct(&pThis->Aux, pDevIns, pThis, iInstance); + if (RT_FAILURE(rc)) + return rc; +#else pThis->Mouse.IBase.pfnQueryInterface = kbdMouseQueryInterface; pThis->Mouse.IPort.pfnPutEvent = kbdMousePutEvent; pThis->Mouse.IPort.pfnPutEventAbs = kbdMousePutEventAbs; - - /* - * Initialize the critical section. - */ - rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CritSect, RT_SRC_POS, "PS2KM#%u", iInstance); - if (RT_FAILURE(rc)) - return rc; + pThis->Mouse.IPort.pfnPutEventMultiTouch = kbdMousePutEventMultiTouch; +#endif /* * Register I/O ports, save state, keyboard event handler and mouse event handlers. @@ -1617,7 +1719,10 @@ static DECLCALLBACK(int) kbdConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNO if (RT_FAILURE(rc)) return rc; } - rc = PDMDevHlpSSMRegister(pDevIns, PCKBD_SAVED_STATE_VERSION, sizeof(*pThis), kbdSaveExec, kbdLoadExec); + 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; @@ -1668,10 +1773,10 @@ const PDMDEVREG g_DevicePS2KeyboardMouse = /* pfnConstruct */ kbdConstruct, /* pfnDestruct */ - kbdDestruct, + NULL, /* pfnRelocate */ kbdRelocate, - /* pfnIOCtl */ + /* pfnMemSetup */ NULL, /* pfnPowerOn */ NULL, diff --git a/src/VBox/Devices/Input/DrvKeyboardQueue.cpp b/src/VBox/Devices/Input/DrvKeyboardQueue.cpp index e0ceb231..379a339d 100644 --- a/src/VBox/Devices/Input/DrvKeyboardQueue.cpp +++ b/src/VBox/Devices/Input/DrvKeyboardQueue.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2006-2010 Oracle Corporation + * Copyright (C) 2006-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; diff --git a/src/VBox/Devices/Input/DrvMouseQueue.cpp b/src/VBox/Devices/Input/DrvMouseQueue.cpp index 19a84310..9d0a8d26 100644 --- a/src/VBox/Devices/Input/DrvMouseQueue.cpp +++ b/src/VBox/Devices/Input/DrvMouseQueue.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2006-2010 Oracle Corporation + * Copyright (C) 2006-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -58,20 +58,38 @@ typedef struct DRVMOUSEQUEUE /** + * 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; - uint32_t fAbs; - int32_t iDeltaX; - int32_t iDeltaY; - int32_t iDeltaZ; - int32_t iDeltaW; - uint32_t fButtonStates; - uint32_t uX; - uint32_t uY; + 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; @@ -101,7 +119,10 @@ static DECLCALLBACK(void *) drvMouseQueueQueryInterface(PPDMIBASE pInterface, c /** * @interface_method_impl{PDMIMOUSEPORT,pfnPutEvent} */ -static DECLCALLBACK(int) drvMouseQueuePutEvent(PPDMIMOUSEPORT pInterface, int32_t iDeltaX, int32_t iDeltaY, int32_t iDeltaZ, int32_t iDeltaW, uint32_t fButtonStates) +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) @@ -110,14 +131,13 @@ static DECLCALLBACK(int) drvMouseQueuePutEvent(PPDMIMOUSEPORT pInterface, int32_ PDRVMOUSEQUEUEITEM pItem = (PDRVMOUSEQUEUEITEM)PDMQueueAlloc(pDrv->pQueue); if (pItem) { - pItem->fAbs = 0; - pItem->iDeltaX = iDeltaX; - pItem->iDeltaY = iDeltaY; - pItem->iDeltaZ = iDeltaZ; - pItem->iDeltaW = iDeltaW; - pItem->fButtonStates = fButtonStates; - pItem->uX = 0; - pItem->uY = 0; + 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; } @@ -127,7 +147,10 @@ static DECLCALLBACK(int) drvMouseQueuePutEvent(PPDMIMOUSEPORT pInterface, int32_ /** * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventAbs} */ -static DECLCALLBACK(int) drvMouseQueuePutEventAbs(PPDMIMOUSEPORT pInterface, uint32_t uX, uint32_t uY, int32_t iDeltaZ, int32_t iDeltaW, uint32_t fButtonStates) +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) @@ -136,14 +159,13 @@ static DECLCALLBACK(int) drvMouseQueuePutEventAbs(PPDMIMOUSEPORT pInterface, uin PDRVMOUSEQUEUEITEM pItem = (PDRVMOUSEQUEUEITEM)PDMQueueAlloc(pDrv->pQueue); if (pItem) { - pItem->fAbs = 1; - pItem->iDeltaX = 0; - pItem->iDeltaY = 0; - pItem->iDeltaZ = iDeltaZ; - pItem->iDeltaW = iDeltaW; - pItem->fButtonStates = fButtonStates; - pItem->uX = uX; - pItem->uY = uY; + 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; } @@ -151,6 +173,15 @@ static DECLCALLBACK(int) drvMouseQueuePutEventAbs(PPDMIMOUSEPORT pInterface, uin } +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_OFFSETOF(DRVMOUSEQUEUE, IConnector)) ) @@ -161,12 +192,14 @@ static DECLCALLBACK(int) drvMouseQueuePutEventAbs(PPDMIMOUSEPORT pInterface, uin * driver. * * @param pInterface Pointer to the mouse connector interface structure. - * @param fAbs The new absolute mode state. + * @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) +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); + pDrv->pDownConnector->pfnReportModes(pDrv->pDownConnector, fRel, fAbs, fMT); } @@ -186,10 +219,22 @@ static DECLCALLBACK(bool) drvMouseQueueConsumer(PPDMDRVINS pDrvIns, PPDMQUEUEITE PDRVMOUSEQUEUE pThis = PDMINS_2_DATA(pDrvIns, PDRVMOUSEQUEUE); PDRVMOUSEQUEUEITEM pItem = (PDRVMOUSEQUEUEITEM)pItemCore; int rc; - if (!pItem->fAbs) - rc = pThis->pUpPort->pfnPutEvent(pThis->pUpPort, pItem->iDeltaX, pItem->iDeltaY, pItem->iDeltaZ, pItem->iDeltaW, pItem->fButtonStates); + 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 - rc = pThis->pUpPort->pfnPutEventAbs(pThis->pUpPort, pItem->uX, pItem->uY, pItem->iDeltaZ, pItem->iDeltaW, pItem->fButtonStates); + return false; return RT_SUCCESS(rc); } @@ -288,6 +333,7 @@ static DECLCALLBACK(int) drvMouseQueueConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pC /* IMousePort. */ pDrv->IPort.pfnPutEvent = drvMouseQueuePutEvent; pDrv->IPort.pfnPutEventAbs = drvMouseQueuePutEventAbs; + pDrv->IPort.pfnPutEventMultiTouch = drvMouseQueuePutEventMultiTouch; /* * Get the IMousePort interface of the above driver/device. diff --git a/src/VBox/Devices/Input/PS2Dev.h b/src/VBox/Devices/Input/PS2Dev.h index 392095a7..bdb32430 100644 --- a/src/VBox/Devices/Input/PS2Dev.h +++ b/src/VBox/Devices/Input/PS2Dev.h @@ -17,35 +17,68 @@ #ifndef PS2DEV_H #define PS2DEV_H -/* Must be at least as big as the real struct. */ -#define PS2K_STRUCT_FILLER 768 +/** 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 { +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(PPDMDEVINS pDevIns, PPS2K pThis, void *pParent, int iInstance); -int PS2KAttach(PPDMDEVINS pDevIns, PPS2K pThis, unsigned iLUN, uint32_t fFlags); +int PS2KConstruct(PPS2K pThis, PPDMDEVINS pDevIns, void *pParent, int iInstance); +int PS2KAttach(PPS2K pThis, PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags); void PS2KReset(PPS2K pThis); -void PS2KRelocate(PPS2K pThis, RTGCINTPTR offDelta); -void PS2KSaveState(PSSMHANDLE pSSM, PPS2K pThis); -int PS2KLoadState(PSSMHANDLE pSSM, PPS2K pThis, uint32_t uVersion); +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); + +PS2M *KBDGetPS2MFromDevIns(PPDMDEVINS pDevIns); + +/* Shared keyboard/aux internal interface. */ void KBCUpdateInterrupts(void *pKbc); -PS2K *GetPS2KFromDevIns(PPDMDEVINS pDevIns); -//@todo: This should live with the KBC implementation. +///@todo: This should live with the KBC implementation. /** AT to PC scancode translator state. */ -typedef enum { +typedef enum +{ XS_IDLE, /**< Starting state. */ XS_BREAK, /**< F0 break byte was received. */ XS_HIBIT /**< Break code still active. */ diff --git a/src/VBox/Devices/Input/PS2K.cpp b/src/VBox/Devices/Input/PS2K.cpp index 1df1ddb2..e933fbc4 100644 --- a/src/VBox/Devices/Input/PS2K.cpp +++ b/src/VBox/Devices/Input/PS2K.cpp @@ -74,7 +74,7 @@ #define KRSP_ID1 0xAB #define KRSP_ID2 0x83 #define KRSP_BAT_OK 0xAA -#define KRSP_BAT_FAIL 0xFC +#define KRSP_BAT_FAIL 0xFC /* Also a 'release keys' signal. */ #define KRSP_ECHO 0xEE #define KRSP_ACK 0xFA #define KRSP_RESEND 0xFE @@ -187,22 +187,26 @@ typedef struct PS2K #if HC_ARCH_BITS == 32 uint32_t Alignment0; #endif - /** Critical section protecting the state. */ - PDMCRITSECT KbdCritSect; + /** Command delay timer - RC Ptr. */ PTMTIMERRC pKbdDelayTimerRC; /** Typematic timer - RC Ptr. */ PTMTIMERRC pKbdTypematicTimerRC; + + /** The device critical section protecting everything - R3 Ptr */ + R3PTRTYPE(PPDMCRITSECT) pCritSectR3; /** Command delay timer - R3 Ptr. */ PTMTIMERR3 pKbdDelayTimerR3; /** Typematic timer - R3 Ptr. */ PTMTIMERR3 pKbdTypematicTimerR3; + RTR3PTR Alignment2; + /** Command delay timer - R0 Ptr. */ PTMTIMERR0 pKbdDelayTimerR0; /** Typematic timer - R0 Ptr. */ PTMTIMERR0 pKbdTypematicTimerR0; - scan_state_t XlatState; //@todo: temporary + scan_state_t XlatState; ///@todo: temporary uint32_t Alignment1; /** @@ -415,7 +419,7 @@ static const key_def aPS2Keys[] = { /* 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 */ + /* 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 */ @@ -428,6 +432,19 @@ static const key_def aPS2Keys[] = { /* 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 */ @@ -467,7 +484,7 @@ static uint8_t aScancode2Hid[] = 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, 0x00, 0x00, 0x64, 0x44, /* 50-57 */ + 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 */ @@ -560,7 +577,7 @@ static scan_state_t ScancodeToHidUsage(scan_state_t state, uint8_t scanCode, uin * * @param pQ Pointer to the queue. */ -static void PS2ClearQueue(GeneriQ *pQ) +static void ps2kClearQueue(GeneriQ *pQ) { LogFlowFunc(("Clearing queue %p\n", pQ)); pQ->wpos = pQ->rpos; @@ -574,7 +591,7 @@ static void PS2ClearQueue(GeneriQ *pQ) * @param pQ Pointer to the queue. * @param val The byte to store. */ -static void PS2InsertQueue(GeneriQ *pQ, uint8_t val) +static void ps2kInsertQueue(GeneriQ *pQ, uint8_t val) { /* Check if queue is full. */ if (pQ->cUsed >= pQ->cSize) @@ -598,7 +615,7 @@ static void PS2InsertQueue(GeneriQ *pQ, uint8_t val) * @param pSSM SSM handle to write the state to. * @param pQ Pointer to the queue. */ -static void PS2SaveQueue(PSSMHANDLE pSSM, GeneriQ *pQ) +static void ps2kSaveQueue(PSSMHANDLE pSSM, GeneriQ *pQ) { uint32_t cItems = pQ->cUsed; int i; @@ -623,7 +640,7 @@ static void PS2SaveQueue(PSSMHANDLE pSSM, GeneriQ *pQ) * * @return int VBox status/error code. */ -static int PS2LoadQueue(PSSMHANDLE pSSM, GeneriQ *pQ) +static int ps2kLoadQueue(PSSMHANDLE pSSM, GeneriQ *pQ) { int rc; @@ -646,7 +663,7 @@ static int PS2LoadQueue(PSSMHANDLE pSSM, GeneriQ *pQ) return rc; } -#endif +#endif /* IN_RING3 */ /** * Retrieve a byte from a queue. @@ -657,7 +674,7 @@ static int PS2LoadQueue(PSSMHANDLE pSSM, GeneriQ *pQ) * @return int VINF_TRY_AGAIN if queue is empty, * VINF_SUCCESS if a byte was read. */ -int PS2RemoveQueue(GeneriQ *pQ, uint8_t *pVal) +static int ps2kRemoveQueue(GeneriQ *pQ, uint8_t *pVal) { int rc = VINF_TRY_AGAIN; @@ -678,7 +695,7 @@ int PS2RemoveQueue(GeneriQ *pQ, uint8_t *pVal) /* 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) +static void ps2kSetupTypematic(PPS2K pThis, uint8_t val) { int A, B; unsigned period; @@ -692,23 +709,23 @@ static void PS2KSetupTypematic(PPS2K pThis, uint8_t val) period = (8 + A) * (1 << B) * 417 / 100; pThis->uTypematicRepeat = period; Log(("Typematic delay %u ms, repeat period %u ms\n", - pThis->uTypematicDelay, pThis->uTypematicRepeat)); + pThis->uTypematicDelay, pThis->uTypematicRepeat)); } -static void PS2KSetDefaults(PPS2K pThis) +static void ps2kSetDefaults(PPS2K pThis) { LogFlowFunc(("Set keyboard defaults\n")); - PS2ClearQueue((GeneriQ *)&pThis->keyQ); + ps2kClearQueue((GeneriQ *)&pThis->keyQ); /* Set default Scan Set 3 typematic values. */ /* Set default typematic rate/delay. */ - PS2KSetupTypematic(pThis, KBD_DFL_RATE_DELAY); + ps2kSetupTypematic(pThis, KBD_DFL_RATE_DELAY); /* Clear last typematic key?? */ } /** * Receive and process a byte sent by the keyboard controller. * - * @param pThis The keyboard. + * @param pThis The PS/2 keyboard instance data. * @param cmd The command (or data) byte. */ int PS2KByteToKbd(PPS2K pThis, uint8_t cmd) @@ -717,118 +734,120 @@ int PS2KByteToKbd(PPS2K pThis, uint8_t cmd) LogFlowFunc(("new cmd=0x%02X, active cmd=0x%02X\n", cmd, pThis->u8CurrCmd)); - switch (cmd) { - case KCMD_ECHO: - PS2InsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ECHO); - pThis->u8CurrCmd = 0; - break; - case KCMD_READ_ID: - PS2InsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK); - PS2InsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ID1); - PS2InsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ID2); - pThis->u8CurrCmd = 0; - break; - case KCMD_ENABLE: - pThis->fScanning = true; - PS2ClearQueue((GeneriQ *)&pThis->keyQ); - /* Clear last typematic key?? */ - PS2InsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK); - pThis->u8CurrCmd = 0; - break; - case KCMD_DFLT_DISABLE: - pThis->fScanning = false; - PS2KSetDefaults(pThis); - PS2InsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK); - pThis->u8CurrCmd = 0; - break; - case KCMD_SET_DEFAULT: - PS2KSetDefaults(pThis); - PS2InsertQueue((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. - PS2InsertQueue((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? - PS2InsertQueue((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: - PS2InsertQueue((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 - { - PDMKEYBLEDS enmLeds = PDMKEYBLEDS_NONE; - - if (cmd & 0x01) - enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_SCROLLLOCK); - if (cmd & 0x02) - enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_NUMLOCK); - if (cmd & 0x04) - enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_CAPSLOCK); - pThis->Keyboard.pDrv->pfnLedStatusChange(pThis->Keyboard.pDrv, enmLeds); - pThis->fNumLockOn = !!(cmd & 0x02); /* Sync internal Num Lock state. */ - PS2InsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK); - pThis->u8LEDs = cmd; - pThis->u8CurrCmd = 0; - } -#endif + switch (cmd) + { + case KCMD_ECHO: + ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ECHO); + pThis->u8CurrCmd = 0; break; - case KCMD_SCANSET: - PS2InsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK); - if (cmd == 0) - PS2InsertQueue((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. */ + 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_RATE_DELAY: - PS2KSetupTypematic(pThis, cmd); - PS2InsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK); + case KCMD_ENABLE: + pThis->fScanning = true; + ps2kClearQueue((GeneriQ *)&pThis->keyQ); + /* Clear last typematic key?? */ + ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_ACK); pThis->u8CurrCmd = 0; break; - default: - fHandled = false; - } - /* Fall through only to handle unrecognized commands. */ - if (fHandled) + case KCMD_DFLT_DISABLE: + pThis->fScanning = false; + ps2kSetDefaults(pThis); + 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 + { + PDMKEYBLEDS enmLeds = PDMKEYBLEDS_NONE; + + if (cmd & 0x01) + enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_SCROLLLOCK); + if (cmd & 0x02) + enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_NUMLOCK); + if (cmd & 0x04) + enmLeds = (PDMKEYBLEDS)(enmLeds | PDMKEYBLEDS_CAPSLOCK); + pThis->Keyboard.pDrv->pfnLedStatusChange(pThis->Keyboard.pDrv, enmLeds); + 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; - case KCMD_INVALID_1: - case KCMD_INVALID_2: - PS2InsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_RESEND); - pThis->u8CurrCmd = 0; - break; + 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); @@ -838,34 +857,38 @@ int PS2KByteToKbd(PPS2K pThis, uint8_t cmd) /** * Send a byte (keystroke or command response) to the keyboard controller. * - * @param pThis The keyboard. + * @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 *pVal) +int PS2KByteFromKbd(PPS2K pThis, uint8_t *pb) { int rc; - Assert(pVal); + 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 = PS2RemoveQueue((GeneriQ *)&pThis->cmdQ, pVal); + rc = ps2kRemoveQueue((GeneriQ *)&pThis->cmdQ, pb); if (rc != VINF_SUCCESS && !pThis->u8CurrCmd && pThis->fScanning) - rc = PS2RemoveQueue((GeneriQ *)&pThis->keyQ, pVal); + rc = ps2kRemoveQueue((GeneriQ *)&pThis->keyQ, pb); - LogFlowFunc(("keyboard sends 0x%02x (%svalid data)\n", *pVal, rc == VINF_SUCCESS ? "" : "not ")); + 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) +static int psk2ProcessKeyEvent(PPS2K pThis, uint8_t u8HidCode, bool fKeyDown) { unsigned int i = 0; key_def const *pKeyDef; uint8_t abCodes[16]; + uint8_t u8MakeCode; LogFlowFunc(("key %s: 0x%02x (set %d)\n", fKeyDown ? "down" : "up", u8HidCode, pThis->u8ScanSet)); @@ -897,9 +920,11 @@ static int PS2KProcessKeyEvent(PPS2K pThis, uint8_t u8HidCode, bool fKeyDown) if ((pKeyDef->keyFlags & KF_NL) && fKeyDown) pThis->fNumLockOn ^= true; - if (pThis->u8ScanSet == 2) + if (pThis->u8ScanSet == 1 || pThis->u8ScanSet == 2) { - /* Handle Scan Set 2 - used almost all the time. */ + /* 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. + */ abCodes[0] = 0; if (fKeyDown) { @@ -908,47 +933,63 @@ static int PS2KProcessKeyEvent(PPS2K pThis, uint8_t u8HidCode, bool fKeyDown) { /* Pause/Break sends different data if either Ctrl is held. */ if (pThis->u8Modifiers & (MOD_LCTRL | MOD_RCTRL)) - strcpy((char *)abCodes, "\xE0\x7E\xE0\xF0\x7E"); + strcpy((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xE0\x7E\xE0\xF0\x7E" : "\xE0\x46\xE0\xC6"); else - strcpy((char *)abCodes, "\xE1\x14\x77\xE1\xF0\x14\xF0\x77"); + strcpy((char *)abCodes, 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 Ctrl, Shift, *and* Alt! */ + /* Print Screen depends on all of Ctrl, Shift, *and* Alt! */ if (pThis->u8Modifiers & (MOD_LALT | MOD_RALT)) - strcpy((char *)abCodes, "\x84"); + strcpy((char *)abCodes, pThis->u8ScanSet == 2 ? + "\x84" : "\x54"); else if (pThis->u8Modifiers & (MOD_LSHIFT | MOD_RSHIFT)) - strcpy((char *)abCodes, "\xE0\x7C"); + strcpy((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xE0\x7C" : "\xE0\x37"); else - strcpy((char *)abCodes, "\xE0\x12\xE0\x7C"); + strcpy((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xE0\x12\xE0\x7C" : "\xE0\x2A\xE0\x37"); } - else if (pKeyDef->keyFlags & KF_GK) + else if (pKeyDef->keyFlags & (KF_GK | KF_NS)) { - if (pThis->fNumLockOn) + /* 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 | MOD_RSHIFT)) == 0) - strcpy((char *)abCodes, "\xE0\x12"); + if (pThis->u8Modifiers & MOD_LSHIFT) + strcat((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xE0\xF0\x12" : "\xE0\xAA"); + if (pThis->u8Modifiers & MOD_RSHIFT) + strcat((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xE0\xF0\x59" : "\xE0\xB6"); } else { - if (pThis->u8Modifiers & MOD_LSHIFT) - strcat((char *)abCodes, "\xE0\xF0\x12"); - if (pThis->u8Modifiers & MOD_RSHIFT) - strcat((char *)abCodes, "\xE0\xF0\x59"); + Assert(pThis->fNumLockOn); /* Not for KF_NS! */ + if ((pThis->u8Modifiers & (MOD_LSHIFT | MOD_RSHIFT)) == 0) + strcpy((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xE0\x12" : "\xE0\x2A"); + /* Else Shift cancels NumLock, so no prefix! */ } } /* Feed the bytes to the queue if there is room. */ - //@todo: check empty space! + ///@todo check empty space! while (abCodes[i]) - PS2InsertQueue((GeneriQ *)&pThis->keyQ, abCodes[i++]); + ps2kInsertQueue((GeneriQ *)&pThis->keyQ, abCodes[i++]); Assert(i < sizeof(abCodes)); /* Standard processing for regular keys only. */ + u8MakeCode = pThis->u8ScanSet == 2 ? pKeyDef->makeS2 : pKeyDef->makeS1; if (!(pKeyDef->keyFlags & (KF_PB | KF_PS))) { if (pKeyDef->keyFlags & (KF_E0 | KF_GK | KF_NS)) - PS2InsertQueue((GeneriQ *)&pThis->keyQ, 0xE0); - PS2InsertQueue((GeneriQ *)&pThis->keyQ, pKeyDef->makeS2); + ps2kInsertQueue((GeneriQ *)&pThis->keyQ, 0xE0); + ps2kInsertQueue((GeneriQ *)&pThis->keyQ, u8MakeCode); } } else if (!(pKeyDef->keyFlags & (KF_NB | KF_PB))) @@ -960,75 +1001,73 @@ static int PS2KProcessKeyEvent(PPS2K pThis, uint8_t u8HidCode, bool fKeyDown) { /* Undo faked Print Screen state as needed. */ if (pThis->u8Modifiers & (MOD_LALT | MOD_RALT)) - strcpy((char *)abCodes, "\xF0\x84"); + strcpy((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xF0\x84" : "\xD4"); else if (pThis->u8Modifiers & (MOD_LSHIFT | MOD_RSHIFT)) - strcpy((char *)abCodes, "\xE0\xF0\x7C"); + strcpy((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xE0\xF0\x7C" : "\xE0\xB7"); else - strcpy((char *)abCodes, "\xE0\xF0\x7C\xE0\xF0\x12"); + strcpy((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xE0\xF0\x7C\xE0\xF0\x12" : "\xE0\xB7\xE0\xAA"); } else { /* Process base scan code for less unusual keys. */ if (pKeyDef->keyFlags & (KF_E0 | KF_GK | KF_NS)) - PS2InsertQueue((GeneriQ *)&pThis->keyQ, 0xE0); - PS2InsertQueue((GeneriQ *)&pThis->keyQ, 0xF0); - PS2InsertQueue((GeneriQ *)&pThis->keyQ, pKeyDef->makeS2); + ps2kInsertQueue((GeneriQ *)&pThis->keyQ, 0xE0); + if (pThis->u8ScanSet == 2) { + ps2kInsertQueue((GeneriQ *)&pThis->keyQ, 0xF0); + ps2kInsertQueue((GeneriQ *)&pThis->keyQ, pKeyDef->makeS2); + } else { + Assert(pThis->u8ScanSet == 1); + ps2kInsertQueue((GeneriQ *)&pThis->keyQ, pKeyDef->makeS1 | 0x80); + } /* Restore shift state for gray keys. */ - if (pKeyDef->keyFlags & KF_GK) + if (pKeyDef->keyFlags & (KF_GK | KF_NS)) { - if (pThis->fNumLockOn) + if (!pThis->fNumLockOn || (pKeyDef->keyFlags & KF_NS)) { - if ((pThis->u8Modifiers & (MOD_LSHIFT | MOD_RSHIFT)) == 0) - strcpy((char *)abCodes, "\xE0\xF0\x12"); + if (pThis->u8Modifiers & MOD_LSHIFT) + strcat((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xE0\x12" : "\xE0\x2A"); + if (pThis->u8Modifiers & MOD_RSHIFT) + strcat((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xE0\x59" : "\xE0\x36"); } else { - if (pThis->u8Modifiers & MOD_RSHIFT) - strcat((char *)abCodes, "\xE0\x59"); - if (pThis->u8Modifiers & MOD_LSHIFT) - strcat((char *)abCodes, "\xE0\x12"); + Assert(pThis->fNumLockOn); /* Not for KF_NS! */ + if ((pThis->u8Modifiers & (MOD_LSHIFT | MOD_RSHIFT)) == 0) + strcpy((char *)abCodes, pThis->u8ScanSet == 2 ? + "\xE0\xF0\x12" : "\xE0\xAA"); } } } /* Feed any additional bytes to the queue if there is room. */ - //@todo: check empty space! + ///@todo check empty space! while (abCodes[i]) - PS2InsertQueue((GeneriQ *)&pThis->keyQ, abCodes[i++]); + ps2kInsertQueue((GeneriQ *)&pThis->keyQ, abCodes[i++]); Assert(i < sizeof(abCodes)); } } - else if (pThis->u8ScanSet == 1) - { - /* Handle Scan Set 1 - similar in complexity to Set 2. */ - if (fKeyDown) - { - if (pKeyDef->keyFlags & (KF_E0 | KF_GK | KF_NS | KF_PS)) - PS2InsertQueue((GeneriQ *)&pThis->keyQ, 0xE0); - PS2InsertQueue((GeneriQ *)&pThis->keyQ, pKeyDef->makeS1); - } - else if (!(pKeyDef->keyFlags & (KF_NB | KF_PB))) { - if (pKeyDef->keyFlags & (KF_E0 | KF_GK | KF_NS | KF_PS)) - PS2InsertQueue((GeneriQ *)&pThis->keyQ, 0xE0); - PS2InsertQueue((GeneriQ *)&pThis->keyQ, pKeyDef->makeS1 | 0x80); - } - } else { /* Handle Scan Set 3 - very straightforward. */ + Assert(pThis->u8ScanSet == 3); if (fKeyDown) { - PS2InsertQueue((GeneriQ *)&pThis->keyQ, pKeyDef->makeS3); + ps2kInsertQueue((GeneriQ *)&pThis->keyQ, pKeyDef->makeS3); } else { /* Send a key release code unless it's a make only key. */ - //@todo: Look up the current typematic setting, not the default! + ///@todo Look up the current typematic setting, not the default! if (pKeyDef->keyMatic != T_M) { - PS2InsertQueue((GeneriQ *)&pThis->keyQ, 0xF0); - PS2InsertQueue((GeneriQ *)&pThis->keyQ, pKeyDef->makeS3); + ps2kInsertQueue((GeneriQ *)&pThis->keyQ, 0xF0); + ps2kInsertQueue((GeneriQ *)&pThis->keyQ, pKeyDef->makeS3); } } } @@ -1048,8 +1087,8 @@ static int PS2KProcessKeyEvent(PPS2K pThis, uint8_t u8HidCode, bool fKeyDown) { pThis->u8TypematicKey = 0; pThis->enmTypematicState = KBD_TMS_IDLE; - //@todo: Cancel timer right away? - //@todo: Cancel timer before pushing key up code!? + ///@todo Cancel timer right away? + ///@todo Cancel timer before pushing key up code!? } /* Poke the KBC to update its state. */ @@ -1061,12 +1100,9 @@ static int PS2KProcessKeyEvent(PPS2K pThis, uint8_t u8HidCode, bool fKeyDown) /* 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) +static DECLCALLBACK(void) ps2kTypematicTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) { - PPS2K pThis = (PS2K *)pvUser; //PDMINS_2_DATA(pDevIns, PS2K *); - int rc = PDMCritSectEnter(&pThis->KbdCritSect, VERR_SEM_BUSY); - AssertReleaseRC(rc); - + PPS2K pThis = (PS2K *)pvUser; NOREF(pDevIns); LogFlowFunc(("Typematic state=%d, key %02X\n", pThis->enmTypematicState, pThis->u8TypematicKey)); /* If the current typematic key is zero, the repeat was canceled just when @@ -1079,35 +1115,48 @@ static DECLCALLBACK(void) PS2KTypematicTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer if (pThis->enmTypematicState == KBD_TMS_REPEAT) { - PS2KProcessKeyEvent(pThis, pThis->u8TypematicKey, true /* Key down */ ); + psk2ProcessKeyEvent(pThis, pThis->u8TypematicKey, true /* Key down */ ); TMTimerSetMillies(pThis->CTX_SUFF(pKbdTypematicTimer), pThis->uTypematicRepeat); } } - - PDMCritSectLeave(&pThis->KbdCritSect); } /* 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) +static DECLCALLBACK(void) ps2kDelayTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) { - PPS2K pThis = GetPS2KFromDevIns(pDevIns); - int rc = PDMCritSectEnter(&pThis->KbdCritSect, VERR_SEM_BUSY); - AssertReleaseRC(rc); + PPS2K pThis = (PS2K *)pvUser; NOREF(pDevIns); LogFlowFunc(("Delay timer: cmd %02X\n", pThis->u8CurrCmd)); Assert(pThis->u8CurrCmd == KCMD_RESET); - PS2InsertQueue((GeneriQ *)&pThis->cmdQ, KRSP_BAT_OK); + 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... + ///@todo Might want a PS2KCompleteCommand() to push last response, clear command, and kick the KBC... /* Give the KBC a kick. */ KBCUpdateInterrupts(pThis->pParent); +} - PDMCritSectLeave(&pThis->KbdCritSect); +/* 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")); + LogRel(("Releasing keys...\n")); + + for (unsigned uKey = 0; uKey < sizeof(pThis->abDepressedKeys); ++uKey) + if (pThis->abDepressedKeys[uKey]) + { + LogRel(("Releasing key %02X\n", uKey)); + psk2ProcessKeyEvent(pThis, uKey, false /* key up */); + pThis->abDepressedKeys[uKey] = 0; + } + LogFlowFunc(("Done releasing keys\n")); } @@ -1118,9 +1167,9 @@ static DECLCALLBACK(void) PS2KDelayTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, vo * @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) +static DECLCALLBACK(void) ps2kInfoState(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) { - PPS2K pThis = GetPS2KFromDevIns(pDevIns); + PPS2K pThis = KBDGetPS2KFromDevIns(pDevIns); NOREF(pszArgs); pHlp->pfnPrintf(pHlp, "PS/2 Keyboard: scan set %d, scanning %s\n", @@ -1144,7 +1193,7 @@ static DECLCALLBACK(void) PS2KInfoState(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, /** * @interface_method_impl{PDMIBASE,pfnQueryInterface} */ -static DECLCALLBACK(void *) PS2KQueryInterface(PPDMIBASE pInterface, const char *pszIID) +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); @@ -1159,13 +1208,12 @@ static DECLCALLBACK(void *) PS2KQueryInterface(PPDMIBASE pInterface, const char * Keyboard event handler. * * @returns VBox status code. - * @param pInterface Pointer to the keyboard port interface (KBDState::Keyboard.IPort). + * @param pThis The PS2 keyboard instance data. * @param u32Usage USB HID usage code with key * press/release flag. */ -static DECLCALLBACK(int) PS2KPutEvent(PPDMIKEYBOARDPORT pInterface, uint32_t u32Usage) +static int ps2kPutEventWorker(PPS2K pThis, uint32_t u32Usage) { - PPS2K pThis = RT_FROM_MEMBER(pInterface, PS2K, Keyboard.IPort); uint8_t u8HidCode; bool fKeyDown; bool fHaveEvent = true; @@ -1195,28 +1243,49 @@ static DECLCALLBACK(int) PS2KPutEvent(PPDMIKEYBOARDPORT pInterface, uint32_t u32 /* Unless this is a new key press/release, don't even bother. */ if (fHaveEvent) { - rc = PDMCritSectEnter(&pThis->KbdCritSect, VERR_SEM_BUSY); + rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY); AssertReleaseRC(rc); - rc = PS2KProcessKeyEvent(pThis, u8HidCode, fKeyDown); + rc = psk2ProcessKeyEvent(pThis, u8HidCode, fKeyDown); - PDMCritSectLeave(&pThis->KbdCritSect); + PDMCritSectLeave(pThis->pCritSectR3); } return rc; } -static DECLCALLBACK(int) PS2KPutEventWrapper(PPDMIKEYBOARDPORT pInterface, uint8_t u8KeyCode) +static DECLCALLBACK(int) ps2kPutEventWrapper(PPDMIKEYBOARDPORT pInterface, uint8_t u8KeyCode) { PPS2K pThis = RT_FROM_MEMBER(pInterface, PS2K, Keyboard.IPort); uint32_t u32Usage = 0; + int rc; + + LogFlowFunc(("key code %02X\n", u8KeyCode)); - LogFlowFunc(("key code %02X\n", u8KeyCode )); - pThis->XlatState = ScancodeToHidUsage(pThis->XlatState, u8KeyCode, &u32Usage); + /* The 'BAT fail' scancode is reused as a signal to release keys. No actual + * key is allowed to use this scancode. + */ + if (RT_UNLIKELY(u8KeyCode == KRSP_BAT_FAIL)) + { + rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY); + AssertReleaseRC(rc); + + ps2kReleaseKeys(pThis); - if (pThis->XlatState == SS_IDLE) + PDMCritSectLeave(pThis->pCritSectR3); + } + else { - PS2KPutEvent(pInterface, u32Usage); + pThis->XlatState = ScancodeToHidUsage(pThis->XlatState, u8KeyCode, &u32Usage); + + if (pThis->XlatState == SS_IDLE) + { + /* Stupid Korean key hack: convert a lone break key into a press/release sequence. */ + if (u32Usage == 0x80000090 || u32Usage == 0x80000091) + ps2kPutEventWorker(pThis, u32Usage & ~0x80000000); + + ps2kPutEventWorker(pThis, u32Usage); + } } return VINF_SUCCESS; @@ -1233,11 +1302,12 @@ static DECLCALLBACK(int) PS2KPutEventWrapper(PPDMIKEYBOARDPORT pInterface, uint8 * 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(PPDMDEVINS pDevIns, PPS2K pThis, unsigned iLUN, uint32_t fFlags) +int PS2KAttach(PPS2K pThis, PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags) { int rc; @@ -1270,7 +1340,7 @@ int PS2KAttach(PPDMDEVINS pDevIns, PPS2K pThis, unsigned iLUN, uint32_t fFlags) return rc; } -void PS2KSaveState(PSSMHANDLE pSSM, PPS2K pThis) +void PS2KSaveState(PPS2K pThis, PSSMHANDLE pSSM) { uint32_t cPressed = 0; uint32_t cbTMSSize = 0; @@ -1289,8 +1359,8 @@ void PS2KSaveState(PSSMHANDLE pSSM, PPS2K pThis) SSMR3PutBool(pSSM, pThis->fScanning); /* Save the command and keystroke queues. */ - PS2SaveQueue(pSSM, (GeneriQ *)&pThis->cmdQ); - PS2SaveQueue(pSSM, (GeneriQ *)&pThis->keyQ); + ps2kSaveQueue(pSSM, (GeneriQ *)&pThis->cmdQ); + ps2kSaveQueue(pSSM, (GeneriQ *)&pThis->keyQ); /* Save the command delay timer. Note that the typematic repeat * timer is *not* saved. @@ -1306,16 +1376,16 @@ void PS2KSaveState(PSSMHANDLE pSSM, PPS2K pThis) SSMR3PutU32(pSSM, cPressed); - for (unsigned i = 0; i < sizeof(pThis->abDepressedKeys); ++i) - if (pThis->abDepressedKeys[i]) - SSMR3PutU8(pSSM, pThis->abDepressedKeys[i]); + 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(PSSMHANDLE pSSM, PPS2K pThis, uint32_t uVersion) +int PS2KLoadState(PPS2K pThis, PSSMHANDLE pSSM, uint32_t uVersion) { uint8_t u8; uint32_t cPressed; @@ -1337,43 +1407,56 @@ int PS2KLoadState(PSSMHANDLE pSSM, PPS2K pThis, uint32_t uVersion) SSMR3GetBool(pSSM, &pThis->fNumLockOn); SSMR3GetBool(pSSM, &pThis->fScanning); - do { - /* Load the command and keystroke queues. */ - rc = PS2LoadQueue(pSSM, (GeneriQ *)&pThis->cmdQ); - if (RT_FAILURE(rc)) break; - rc = PS2LoadQueue(pSSM, (GeneriQ *)&pThis->keyQ); - if (RT_FAILURE(rc)) break; + /* 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); - if (RT_FAILURE(rc)) break; + /* Load the command delay timer, just in case. */ + rc = TMR3TimerLoad(pThis->CTX_SUFF(pKbdDelayTimer), pSSM); + AssertRCReturn(rc, rc); - /* Fake key up events for keys that were held down at the time the state was saved. */ - rc = SSMR3GetU32(pSSM, &cPressed); - if (RT_FAILURE(rc)) break; + /* Recalculate the typematic delay/rate. */ + ps2kSetupTypematic(pThis, pThis->u8Typematic); - while (cPressed--) + /* 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); - if (RT_FAILURE(rc)) break; - PS2KProcessKeyEvent(pThis, u8, false /* key up */); + AssertRCReturn(rc, rc); + pThis->abDepressedKeys[u8] = 1; } - if (RT_FAILURE(rc)) break; + } - /* Load typematic settings for Scan Set 3. */ - rc = SSMR3GetU32(pSSM, &cbTMSSize); - if (RT_FAILURE(rc)) break; + /* Load typematic settings for Scan Set 3. */ + rc = SSMR3GetU32(pSSM, &cbTMSSize); + AssertRCReturn(rc, rc); - while (cbTMSSize--) - { - rc = SSMR3GetU8(pSSM, &u8); - if (RT_FAILURE(rc)) break; - } - } while (0); + while (cbTMSSize--) + { + rc = SSMR3GetU8(pSSM, &u8); + AssertRCReturn(rc, rc); + } return rc; } +int PS2KLoadDone(PPS2K pThis, PSSMHANDLE pSSM) +{ + /* This *must* be done after the inital load because it may trigger + * interrupts and change the interrupt controller state. + */ + ps2kReleaseKeys(pThis); + return VINF_SUCCESS; +} + void PS2KReset(PPS2K pThis) { LogFlowFunc(("Resetting PS2K\n")); @@ -1387,15 +1470,15 @@ void PS2KReset(PPS2K pThis) /* Clear queues and any pressed keys. */ memset(pThis->abDepressedKeys, 0, sizeof(pThis->abDepressedKeys)); - PS2ClearQueue((GeneriQ *)&pThis->cmdQ); - PS2KSetDefaults(pThis); /* Also clears keystroke queue. */ + 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) +void PS2KRelocate(PPS2K pThis, RTGCINTPTR offDelta, PPDMDEVINS pDevIns) { LogFlowFunc(("Relocating PS2K\n")); pThis->pKbdDelayTimerRC = TMTimerRCPtr(pThis->pKbdDelayTimerR3); @@ -1403,7 +1486,7 @@ void PS2KRelocate(PPS2K pThis, RTGCINTPTR offDelta) NOREF(offDelta); } -int PS2KConstruct(PPDMDEVINS pDevIns, PPS2K pThis, void *pParent, int iInstance) +int PS2KConstruct(PPS2K pThis, PPDMDEVINS pDevIns, void *pParent, int iInstance) { int rc; @@ -1415,23 +1498,21 @@ int PS2KConstruct(PPDMDEVINS pDevIns, PPS2K pThis, void *pParent, int iInstance) pThis->keyQ.cSize = KBD_KEY_QUEUE_SIZE; pThis->cmdQ.cSize = KBD_CMD_QUEUE_SIZE; - pThis->Keyboard.IBase.pfnQueryInterface = PS2KQueryInterface; - pThis->Keyboard.IPort.pfnPutEvent = PS2KPutEventWrapper; + pThis->Keyboard.IBase.pfnQueryInterface = ps2kQueryInterface; + pThis->Keyboard.IPort.pfnPutEvent = ps2kPutEventWrapper; /* - * Initialize the critical section. + * Initialize the critical section pointer(s). */ - rc = PDMDevHlpCritSectInit(pDevIns, &pThis->KbdCritSect, RT_SRC_POS, "PS2K#%u", iInstance); - if (RT_FAILURE(rc)) - return rc; + pThis->pCritSectR3 = pDevIns->pCritSectRoR3; /* * Create the typematic delay/repeat timer. Does not use virtual time! */ PTMTIMER pTimer; - rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_REAL, PS2KTypematicTimer, pThis, - TMTIMER_FLAGS_NO_CRIT_SECT, "PS2K Typematic Timer", &pTimer); - if (RT_FAILURE (rc)) + rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_REAL, ps2kTypematicTimer, pThis, + TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "PS2K Typematic Timer", &pTimer); + if (RT_FAILURE(rc)) return rc; pThis->pKbdTypematicTimerR3 = pTimer; @@ -1441,9 +1522,9 @@ int PS2KConstruct(PPDMDEVINS pDevIns, PPS2K pThis, void *pParent, int iInstance) /* * Create the command delay timer. */ - rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, PS2KDelayTimer, pThis, - TMTIMER_FLAGS_NO_CRIT_SECT, "PS2K Delay Timer", &pTimer); - if (RT_FAILURE (rc)) + 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; @@ -1453,14 +1534,14 @@ int PS2KConstruct(PPDMDEVINS pDevIns, PPS2K pThis, void *pParent, int iInstance) /* * Register debugger info callbacks. */ - PDMDevHlpDBGFInfoRegister(pDevIns, "ps2k", "Display PS/2 keyboard state.", PS2KInfoState); + PDMDevHlpDBGFInfoRegister(pDevIns, "ps2k", "Display PS/2 keyboard state.", ps2kInfoState); return rc; } #endif -//@todo: The following should live with the KBC implementation. +///@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 diff --git a/src/VBox/Devices/Input/PS2M.cpp b/src/VBox/Devices/Input/PS2M.cpp new file mode 100644 index 00000000..6de654b7 --- /dev/null +++ b/src/VBox/Devices/Input/PS2M.cpp @@ -0,0 +1,1151 @@ +/** @file + * PS2M - PS/2 auxiliary device (mouse) emulation. + */ + +/* + * Copyright (C) 2007-2013 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 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_DFLT_DISABLE 0xF5 /* Disable and set defaults. */ +#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_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; + +/* 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_IMPS2_1ST, + PS2M_KNOCK_IMPS2_2ND, + PS2M_KNOCK_IMEX_1ST, + 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; + /** 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; + /** Throttling delay in milliseconds. */ + unsigned uThrottleDelay; +#if HC_ARCH_BITS == 32 + uint32_t Alignment0; +#endif + + /** 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 + +/* 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. */ + +/******************************************************************************* +* 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) + { + LogFlowFunc(("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; + LogFlowFunc(("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); +} + +#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) +{ + pThis->uThrottleDelay = 1000 / rate; + 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); + + /* Accumulators and button status bits are cleared. */ + ps2kClearQueue((GeneriQ *)&pThis->evtQ); + //@todo accumulators/current state +} + +/* Handle the sampling rate 'knock' sequence which selects protocol. */ +static void ps2mRateProtocolKnock(PPS2M pThis, uint8_t rate) +{ + if (pThis->enmProtocol == PS2M_PROTO_PS2STD) + { + switch (pThis->enmKnockState) + { + case PS2M_KNOCK_INITIAL: + if (rate == 200) + pThis->enmKnockState = PS2M_KNOCK_IMPS2_1ST; + break; + case PS2M_KNOCK_IMPS2_1ST: + if (rate == 100) + pThis->enmKnockState = PS2M_KNOCK_IMPS2_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")); + } + default: + pThis->enmKnockState = PS2M_KNOCK_INITIAL; + } + } + else if (pThis->enmProtocol == PS2M_PROTO_IMPS2) + { + switch (pThis->enmKnockState) + { + case PS2M_KNOCK_INITIAL: + if (rate == 200) + pThis->enmKnockState = PS2M_KNOCK_IMEX_1ST; + break; + case PS2M_KNOCK_IMEX_1ST: + if (rate == 200) + pThis->enmKnockState = PS2M_KNOCK_IMEX_2ND; + else + 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")); + } + default: + pThis->enmKnockState = PS2M_KNOCK_INITIAL; + } + } +} + +/** + * 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)); +//LogRel(("aux: 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; + } + else 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; + } + } + + 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. */ + //@todo: buttons + u8Val = pThis->u8State; + 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_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; + //@todo: R3 only! +#ifdef IN_RING3 + ps2mSetDriverState(pThis, true); +#endif + ps2kClearQueue((GeneriQ *)&pThis->evtQ); + ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); + pThis->u8CurrCmd = 0; + break; + case ACMD_DFLT_DISABLE: + ps2mSetDefaults(pThis); + 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); + /* Slightly delay reset completion; it might take hundreds of ms. */ + TMTimerSetMillies(pThis->CTX_SUFF(pDelayTimer), 1); + 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: + //@todo reject unsupported resolutions + pThis->u8Resolution = cmd; + ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); + pThis->u8CurrCmd = 0; + break; + case ACMD_SET_SAMP_RATE: + //@todo reject unsupported rates + ps2mSetRate(pThis, cmd); + ps2mRateProtocolKnock(pThis, cmd); + ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK); + pThis->u8CurrCmd = 0; + break; + default: + fHandled = false; + } + /* Fall through only to handle unrecognized commands. */ + if (fHandled) + break; + + 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)); +// 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 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, keystrokes are //@todo: true? + * 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 ")); +//if (rc == VINF_SUCCESS) LogRel(("aux: sends 0x%02X\n", *pb)); + + return rc; +} + +#ifdef IN_RING3 + +/* 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) +{ + uint8_t val; + int8_t dX, dY, dZ; + + /* Clamp the accumulated delta values to the allowed range. */ + dX = RT_MIN(RT_MAX(pThis->iAccumX, -256), 255); + dY = RT_MIN(RT_MAX(pThis->iAccumY, -256), 255); + dZ = RT_MIN(RT_MAX(pThis->iAccumZ, -8), 7); + + /* Start with the sync bit and buttons 1-3. */ + val = RT_BIT(3) | (pThis->fAccumB & 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((GeneriQ *)&pThis->evtQ, val); + ps2kInsertQueue((GeneriQ *)&pThis->evtQ, dX); + ps2kInsertQueue((GeneriQ *)&pThis->evtQ, dY); + + /* Add fourth byte if extended protocol is in use. */ + if (pThis->enmProtocol > PS2M_PROTO_PS2STD) + { + if (pThis->enmProtocol == PS2M_PROTO_IMPS2) + ps2kInsertQueue((GeneriQ *)&pThis->evtQ, 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 |= (pThis->fAccumB << 1) & (RT_BIT(4) | RT_BIT(5)); + ps2kInsertQueue((GeneriQ *)&pThis->evtQ, dZ); + } + } + + /* Clear the accumulators. */ + pThis->iAccumX = pThis->iAccumY = pThis->iAccumZ = pThis->fAccumB = 0; + + /* Poke the KBC to update its state. */ + KBCUpdateInterrupts(pThis->pParent); +} + +/* Event rate throttling timer to emulate the auxiliary device sampling rate. + */ +static DECLCALLBACK(void) ps2mThrottleTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) +{ + PPS2M pThis = (PS2M *)pvUser; NOREF(pDevIns); + uint32_t uHaveEvents; + + /* Grab the lock to avoid races with PutEvent(). */ + int rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY); + AssertReleaseRC(rc); + +#if 0 + /* If the input queue is not empty, restart the timer. */ +#else + /* If more movement is accumulated, report it and restart the timer. */ + uHaveEvents = pThis->iAccumX | pThis->iAccumY | pThis->iAccumZ | pThis->fAccumB; + LogFlowFunc(("Have%s events\n", uHaveEvents ? "" : " no")); + + if (uHaveEvents) +#endif + { + ps2mReportAccumulatedEvents(pThis); + TMTimerSetMillies(pThis->CTX_SUFF(pThrottleTimer), pThis->uThrottleDelay); + } + else + pThis->fThrottleActive = false; + + PDMCritSectLeave(pThis->pCritSectR3); +} + +/* The auxiliary device 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) +{ + PPS2M pThis = (PS2M *)pvUser; NOREF(pDevIns); + + LogFlowFunc(("Delay timer: cmd %02X\n", pThis->u8CurrCmd)); + + Assert(pThis->u8CurrCmd == ACMD_RESET); + ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_BAT_OK); + ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, 0); + pThis->enmMode = AUX_MODE_STD; + pThis->u8CurrCmd = 0; + + ///@todo Might want a PS2MCompleteCommand() to push last response, clear command, and kick the KBC... + /* Give the KBC a kick. */ + KBCUpdateInterrupts(pThis->pParent); + + //@todo: move to its proper home! + ps2mSetDriverState(pThis, true); +} + + +/** + * 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) +{ + int rc = VINF_SUCCESS; + + /* Update internal accumulators and button state. */ + pThis->iAccumX += dx; + pThis->iAccumY += dy; + pThis->iAccumZ += dz; + pThis->fAccumB |= fButtons & PS2M_STD_BTN_MASK; //@todo: accumulate based on current protocol? + +#if 1 + /* Report the event and the throttle timer unless it's already running. */ + if (!pThis->fThrottleActive) + { + ps2mReportAccumulatedEvents(pThis); + pThis->fThrottleActive = true; + TMTimerSetMillies(pThis->CTX_SUFF(pThrottleTimer), pThis->uThrottleDelay); + } +#else + /* Clamp the delta values to the allowed range. */ + dx = RT_MIN(RT_MAX(dx, -256), 255); + dy = RT_MIN(RT_MAX(dy, -256), 255); + + /* Start with the sync bit. */ + val = RT_BIT(3); + /* Add buttons 1-3. */ + val |= fButtons & PS2M_STD_BTN_MASK; + /* Set the X/Y sign bits. */ + if (dx < 0) + val |= RT_BIT(4); + if (dy < 0) + val |= RT_BIT(5); + + ps2kInsertQueue((GeneriQ *)&pThis->evtQ, val); + ps2kInsertQueue((GeneriQ *)&pThis->evtQ, (uint8_t)dx); + ps2kInsertQueue((GeneriQ *)&pThis->evtQ, (uint8_t)dy); + if (pThis->enmProtocol > PS2M_PROTO_PS2STD) + { + ps2kInsertQueue((GeneriQ *)&pThis->evtQ, (uint8_t)dz); + } +#endif + + 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); + + LogFlowFunc(("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) +{ + uint32_t cPressed = 0; + + 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); + + //@todo: Is this the right place/logic? + ps2mSetDriverState(pThis, !!(pThis->u8State & AUX_STATE_ENABLED)); + + return rc; +} + +void PS2MReset(PPS2M pThis) +{ + LogFlowFunc(("Resetting PS2M\n")); + + pThis->u8CurrCmd = 0; + + /* Clear the queues. */ + ps2kClearQueue((GeneriQ *)&pThis->cmdQ); + ps2mSetDefaults(pThis); /* Also clears event queue. */ + + /* Activate the PS/2 mouse by default. */ +// if (pThis->Mouse.pDrv) +// pThis->Mouse.pDrv->pfnSetActive(pThis->Mouse.pDrv, true); +} + +void PS2MRelocate(PPS2M pThis, RTGCINTPTR offDelta, PPDMDEVINS pDevIns) +{ + LogFlowFunc(("Relocating PS2M\n")); + pThis->pDelayTimerRC = TMTimerRCPtr(pThis->pDelayTimerR3); + pThis->pThrottleTimerRC = TMTimerRCPtr(pThis->pThrottleTimerR3); + NOREF(offDelta); +} + +int PS2MConstruct(PPS2M pThis, PPDMDEVINS pDevIns, void *pParent, int iInstance) +{ + int rc; + + LogFlowFunc(("iInstance=%d\n", iInstance)); + + 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; + rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_REAL, ps2mThrottleTimer, pThis, + TMTIMER_FLAGS_NO_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_NO_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 + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ diff --git a/src/VBox/Devices/Input/UsbKbd.cpp b/src/VBox/Devices/Input/UsbKbd.cpp index e2c18e44..37ff8104 100644 --- a/src/VBox/Devices/Input/UsbKbd.cpp +++ b/src/VBox/Devices/Input/UsbKbd.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2007-2010 Oracle Corporation + * Copyright (C) 2007-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -67,6 +67,10 @@ /** 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 /** @} */ /******************************************************************************* @@ -298,7 +302,9 @@ static const VUSBDESCINTERFACEEX g_UsbHidInterfaceDesc = /* .pvMore = */ NULL, /* .pvClass = */ &g_UsbHidIfHidDesc, /* .cbClass = */ sizeof(g_UsbHidIfHidDesc), - &g_aUsbHidEndpointDescs[0] + &g_aUsbHidEndpointDescs[0], + /* .pIAD = */ NULL, + /* .cbIAD = */ 0 }; static const VUSBINTERFACE g_aUsbHidInterfaces[] = @@ -379,7 +385,7 @@ static uint8_t aScancode2Hid[] = 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, 0x00, 0x00, 0x64, 0x44, /* 50-57 */ + 0x5a, 0x5b, 0x62, 0x63, 0x46, 0x00, 0x64, 0x44, /* 50-57 */ 0x45, 0x67, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, /* 58-5F */ /* Sun keys: Props Undo Front Copy */ 0x00, 0x00, 0x00, 0x00, 0x76, 0x7a, 0x77, 0x7c, /* 60-67 */ @@ -685,7 +691,7 @@ static void usbHidComputePressed(PUSBHIDK_REPORT pReport, char* pszBuf, unsigned */ static bool usbHidUsageCodeIsModifier(uint8_t u8Usage) { - return u8Usage >= 0xe0 && u8Usage <= 0xe7; + return u8Usage >= USBHID_MODIFIER_FIRST && u8Usage <= USBHID_MODIFIER_LAST; } /** @@ -740,6 +746,12 @@ static int usbHidFillReport(PUSBHIDK_REPORT pReport, if (iKey == 0x90 || iKey == 0x91) rc = true; } + /* Avoid "hanging" keys: If a key is unreported but no longer + * depressed, we'll need to report the key-up state, too. + */ + if (pabUnreportedKeys[iKey] && !pabDepressedKeys[iKey]) + rc = true; + pabUnreportedKeys[iKey] = 0; } } @@ -848,6 +860,34 @@ static testUsbHidFillReport gsTestUsbHidFillReport; #endif /** + * 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 host if there is a pending URB. */ static int usbHidSendReport(PUSBHID pThis) @@ -918,7 +958,25 @@ static DECLCALLBACK(int) usbHidKeyboardPutEvent(PPDMIKEYBOARDPORT pInterface, ui /* Due to host key repeat, we can get key events for keys which are * already depressed. */ if (!pThis->abDepressedKeys[u8HidCode]) + { pThis->abUnreportedKeys[u8HidCode] = 1; + + /* If a non-modifier key is being marked as unreported, also set + * all currently depressed modifer keys as unreported. This avoids + * problems where a simulated key sequence is sent too fast and + * by the time the key is reported, some previously reported + * modifiers are already released. This helps ensure that the guest + * sees the entire modifier(s)+key sequence in a single report. + */ + if (!usbHidUsageCodeIsModifier(u8HidCode)) + { + int iModKey; + + for (iModKey = USBHID_MODIFIER_FIRST; iModKey <= USBHID_MODIFIER_LAST; ++iModKey) + if (pThis->abDepressedKeys[iModKey]) + pThis->abUnreportedKeys[iModKey] = 1; + } + } else fHaveEvent = false; pThis->abDepressedKeys[u8HidCode] = 1; @@ -1038,7 +1096,7 @@ static int usbHidHandleIntrDevToHost(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb case USBHIDREQSTATE_READY: usbHidQueueAddTail(&pThis->ToHostQueue, pUrb); - /* If device was not set idle, sent the current report right away. */ + /* 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)); @@ -1229,6 +1287,20 @@ static int usbHidHandleDefaultPipe(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb) } 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)); diff --git a/src/VBox/Devices/Input/UsbMouse.cpp b/src/VBox/Devices/Input/UsbMouse.cpp index c85da4da..9a657839 100644 --- a/src/VBox/Devices/Input/UsbMouse.cpp +++ b/src/VBox/Devices/Input/UsbMouse.cpp @@ -3,7 +3,7 @@ */ /* - * Copyright (C) 2007-2010 Oracle Corporation + * Copyright (C) 2007-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -17,7 +17,7 @@ /******************************************************************************* * Header Files * *******************************************************************************/ -#define LOG_GROUP LOG_GROUP_USB_MSD +#define LOG_GROUP LOG_GROUP_USB_MOUSE #include <VBox/vmm/pdmusb.h> #include <VBox/log.h> #include <VBox/err.h> @@ -38,6 +38,7 @@ #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 @@ -51,6 +52,7 @@ #define VBOX_USB_VENDOR 0x80EE #define USBHID_PID_MOUSE 0x0020 #define USBHID_PID_TABLET 0x0021 +#define USBHID_PID_MULTI_TOUCH 0x0022 /** @} */ /******************************************************************************* @@ -74,6 +76,20 @@ typedef enum USBHIDREQSTATE 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. @@ -107,12 +123,48 @@ typedef USBHIDURBQUEUE const *PCUSBHIDURBQUEUE; */ typedef struct USBHIDM_ACCUM { - uint32_t btn; - int32_t dX; - int32_t dY; - int32_t dZ; + 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. @@ -150,9 +202,9 @@ typedef struct USBHID /** Someone is waiting on the done queue. */ bool fHaveDoneQueueWaiter; /** If device has pending changes. */ - bool fHasPendingChanges; - /** Is this an absolute pointing device (tablet)? Relative (mouse) otherwise. */ - bool isAbsolute; + 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; @@ -175,50 +227,86 @@ typedef struct USBHID 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 btn; + uint8_t fButtons; int8_t dx; int8_t dy; int8_t dz; } USBHIDM_REPORT, *PUSBHIDM_REPORT; /** - * The USB HID report structure for relative device. + * The USB HID report structure for absolute device. */ typedef struct USBHIDT_REPORT { - uint8_t btn; + uint8_t fButtons; int8_t dz; - uint16_t cx; - uint16_t cy; + 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 device. + * The combined USB HID report union for relative and absolute + * devices. */ typedef union USBHIDTM_REPORT { - USBHIDT_REPORT t; 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_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[] = @@ -251,7 +339,24 @@ static const VUSBDESCENDPOINTEX g_aUsbHidTEndpointDescs[] = /* .bDescriptorType = */ VUSB_DT_ENDPOINT, /* .bEndpointAddress = */ 0x81 /* ep=1, in */, /* .bmAttributes = */ 3 /* interrupt */, - /* .wMaxPacketSize = */ 6, + /* .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, @@ -299,6 +404,7 @@ static const uint8_t g_UsbHidMReportDesc[] = * 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 */ @@ -324,6 +430,13 @@ static const uint8_t g_UsbHidTReportDesc[] = /* 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 */ @@ -338,6 +451,280 @@ static const uint8_t g_UsbHidTReportDesc[] = /* 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[] = { @@ -362,6 +749,19 @@ static const uint8_t g_UsbHidTIfHidDesc[] = /* .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 = { { @@ -378,7 +778,9 @@ static const VUSBDESCINTERFACEEX g_UsbHidMInterfaceDesc = /* .pvMore = */ NULL, /* .pvClass = */ &g_UsbHidMIfHidDesc, /* .cbClass = */ sizeof(g_UsbHidMIfHidDesc), - &g_aUsbHidMEndpointDescs[0] + &g_aUsbHidMEndpointDescs[0], + /* .pIAD = */ NULL, + /* .cbIAD = */ 0 }; static const VUSBDESCINTERFACEEX g_UsbHidTInterfaceDesc = @@ -397,7 +799,30 @@ static const VUSBDESCINTERFACEEX g_UsbHidTInterfaceDesc = /* .pvMore = */ NULL, /* .pvClass = */ &g_UsbHidTIfHidDesc, /* .cbClass = */ sizeof(g_UsbHidTIfHidDesc), - &g_aUsbHidTEndpointDescs[0] + &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[] = @@ -410,6 +835,11 @@ static const VUSBINTERFACE g_aUsbHidTInterfaces[] = { &g_UsbHidTInterfaceDesc, /* .cSettings = */ 1 }, }; +static const VUSBINTERFACE g_aUsbHidMTInterfaces[] = +{ + { &g_UsbHidMTInterfaceDesc, /* .cSettings = */ 1 }, +}; + static const VUSBDESCCONFIGEX g_UsbHidMConfigDesc = { { @@ -444,6 +874,23 @@ static const VUSBDESCCONFIGEX g_UsbHidTConfigDesc = 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 */ + &g_aUsbHidMTInterfaces[0], + NULL /* pvOriginal */ +}; + static const VUSBDESCDEVICE g_UsbHidMDeviceDesc = { /* .bLength = */ sizeof(g_UsbHidMDeviceDesc), @@ -480,6 +927,24 @@ static const VUSBDESCDEVICE g_UsbHidTDeviceDesc = /* .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, @@ -500,6 +965,16 @@ static const PDMUSBDESCCACHE g_UsbHidTDescCache = /* .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 * @@ -565,7 +1040,11 @@ 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) @@ -579,9 +1058,10 @@ DECLINLINE(bool) usbHidQueueRemove(PUSBHIDURBQUEUE pQueue, PVUSBURB pUrb) } if (!pCur) return false; + if (!pUrb->Dev.pNext) + pQueue->ppTail = &pCur->Dev.pNext; } - if (!pUrb->Dev.pNext) - pQueue->ppTail = &pQueue->pHead; + pUrb->Dev.pNext = NULL; return true; } @@ -622,7 +1102,8 @@ static void usbHidLinkDone(PUSBHID pThis, PVUSBURB pUrb) */ static int usbHidCompleteStall(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb, const char *pszWhy) { - Log(("usbHidCompleteStall/#%u: pUrb=%p:%s: %s\n", pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, pszWhy)); + LogRelFlow(("usbHidCompleteStall/#%u: pUrb=%p:%s: %s\n", + pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, pszWhy)); pUrb->enmStatus = VUSBSTATUS_STALL; @@ -645,7 +1126,8 @@ static int usbHidCompleteStall(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb, cons */ 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)); + 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; @@ -679,6 +1161,7 @@ static int usbHidResetWorker(PUSBHID pThis, PVUSBURB pUrb, bool fSetConfig) */ 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; @@ -714,29 +1197,41 @@ static int8_t clamp_i8(int32_t val) /** * Create a USB HID report report based on the currently accumulated data. */ -static size_t usbHidFillReport(PUSBHIDTM_REPORT pReport, PUSBHIDM_ACCUM pAccumulated, bool isAbsolute) +static size_t usbHidFillReport(PUSBHIDTM_REPORT pReport, + PUSBHIDM_ACCUM pAccumulated, USBHIDMODE enmMode) { size_t cbCopy; - if (isAbsolute) + switch (enmMode) { - pReport->t.btn = pAccumulated->btn; - pReport->t.cx = pAccumulated->dX; - pReport->t.cy = pAccumulated->dY; - pReport->t.dz = clamp_i8(pAccumulated->dZ); + 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); -// LogRel(("Abs movement, X=%d, Y=%d, dZ=%d, btn=%02x, report size %d\n", pReport->t.cx, pReport->t.cy, pReport->t.dz, pReport->t.btn, cbCopy)); - } - else - { - pReport->m.btn = pAccumulated->btn; - pReport->m.dx = clamp_i8(pAccumulated->dX); - pReport->m.dy = clamp_i8(pAccumulated->dY); - pReport->m.dz = clamp_i8(pAccumulated->dZ); - + 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); -// LogRel(("Rel movement, dX=%d, dY=%d, dZ=%d, btn=%02x, report size %d\n", pReport->m.dx, pReport->m.dy, pReport->m.dz, pReport->m.btn, cbCopy)); + 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. */ @@ -745,6 +1240,155 @@ static size_t usbHidFillReport(PUSBHIDTM_REPORT pReport, PUSBHIDM_ACCUM pAccumul 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. */ @@ -752,18 +1396,26 @@ 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->isAbsolute); + cbCopy = usbHidFillReport(pReport, &pThis->PtrDelta, pThis->enmMode); pThis->fHasPendingChanges = false; return usbHidCompleteOk(pThis, pUrb, cbCopy); } else { - Log2(("No available URB for USB mouse\n")); + LogRelFlow(("No available URB for USB mouse\n")); pThis->fHasPendingChanges = true; } return VINF_EOF; @@ -781,17 +1433,11 @@ static DECLCALLBACK(void *) usbHidMouseQueryInterface(PPDMIBASE pInterface, cons } /** - * Relative mouse event handler. - * - * @returns VBox status code. - * @param pInterface Pointer to the mouse port interface (KBDState::Mouse.iPort). - * @param i32DeltaX The X delta. - * @param i32DeltaY The Y delta. - * @param i32DeltaZ The Z delta. - * @param i32DeltaW The W delta. - * @param fButtonStates The button states. + * @interface_method_impl{PDMIMOUSEPORT,pfnPutEvent} */ -static DECLCALLBACK(int) usbHidMousePutEvent(PPDMIMOUSEPORT pInterface, int32_t i32DeltaX, int32_t i32DeltaY, int32_t i32DeltaZ, int32_t i32DeltaW, uint32_t fButtonStates) +static DECLCALLBACK(int) usbHidMousePutEvent(PPDMIMOUSEPORT pInterface, + int32_t dx, int32_t dy, int32_t dz, + int32_t dw, uint32_t fButtons) { PUSBHID pThis = RT_FROM_MEMBER(pInterface, USBHID, Lun0.IPort); RTCritSectEnter(&pThis->CritSect); @@ -799,10 +1445,10 @@ static DECLCALLBACK(int) usbHidMousePutEvent(PPDMIMOUSEPORT pInterface, int32_t /* Accumulate movement - the events from the front end may arrive * at a much higher rate than USB can handle. */ - pThis->PtrDelta.btn = fButtonStates; - pThis->PtrDelta.dX += i32DeltaX; - pThis->PtrDelta.dY += i32DeltaY; - pThis->PtrDelta.dZ -= i32DeltaZ; /* Inverted! */ + 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); @@ -812,37 +1458,206 @@ static DECLCALLBACK(int) usbHidMousePutEvent(PPDMIMOUSEPORT pInterface, int32_t } /** - * Absolute mouse event handler. - * - * @returns VBox status code. - * @param pInterface Pointer to the mouse port interface (KBDState::Mouse.iPort). - * @param u32X The X coordinate. - * @param u32Y The Y coordinate. - * @param i32DeltaZ The Z delta. - * @param i32DeltaW The W delta. - * @param fButtonStates The button states. + * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventAbs} */ -static DECLCALLBACK(int) usbHidMousePutEventAbs(PPDMIMOUSEPORT pInterface, uint32_t u32X, uint32_t u32Y, int32_t i32DeltaZ, int32_t i32DeltaW, uint32_t fButtonStates) +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->isAbsolute); + 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.btn = fButtonStates; - pThis->PtrDelta.dX = u32X >> pThis->u8CoordShift; - pThis->PtrDelta.dY = u32Y >> pThis->u8CoordShift; - pThis->PtrDelta.dZ -= i32DeltaZ; /* Inverted! */ + 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; } @@ -852,7 +1667,6 @@ static DECLCALLBACK(int) usbHidMousePutEventAbs(PPDMIMOUSEPORT pInterface, uint3 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); @@ -874,7 +1688,8 @@ static DECLCALLBACK(PVUSBURB) usbHidUrbReap(PPDMUSBINS pUsbIns, RTMSINTERVAL cMi RTCritSectLeave(&pThis->CritSect); if (pUrb) - Log(("usbHidUrbReap/#%u: pUrb=%p:%s\n", pUsbIns->iInstance, pUrb, pUrb->pszDesc)); + LogRelFlow(("usbHidUrbReap/#%u: pUrb=%p:%s\n", pUsbIns->iInstance, pUrb, + pUrb->pszDesc)); return pUrb; } @@ -885,7 +1700,8 @@ static DECLCALLBACK(PVUSBURB) usbHidUrbReap(PPDMUSBINS pUsbIns, RTMSINTERVAL cMi 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)); + LogRelFlow(("usbHidUrbCancel/#%u: pUrb=%p:%s\n", pUsbIns->iInstance, pUrb, + pUrb->pszDesc)); RTCritSectEnter(&pThis->CritSect); /* @@ -923,7 +1739,7 @@ static int usbHidHandleIntrDevToHost(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb case USBHIDREQSTATE_DATA_TO_HOST: { AssertFailed(); - Log(("usbHidHandleIntrDevToHost: Entering STATUS\n")); + LogRelFlow(("usbHidHandleIntrDevToHost: Entering STATUS\n")); return usbHidCompleteOk(pThis, pUrb, 0); } @@ -933,28 +1749,172 @@ static int usbHidHandleIntrDevToHost(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb case USBHIDREQSTATE_STATUS: { AssertFailed(); - Log(("usbHidHandleIntrDevToHost: Entering READY\n")); + 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); - LogFlow(("usbHidHandleIntrDevToHost: Added %p:%s to the queue\n", pUrb, pUrb->pszDesc)); return VINF_SUCCESS; /* * Bad states, stall. */ default: - Log(("usbHidHandleIntrDevToHost: enmState=%d cbData=%#x\n", pThis->enmState, pUrb->cbData)); + 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 sau8QASampleBlob[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) + { + uint32_t cbLeft = pUrb->cbData; + pUrb->abData[sizeof(VUSBSETUP) + 0] = REPORTID_TOUCH_QABLOB; /* Report Id. */ + memcpy(&pUrb->abData[sizeof(VUSBSETUP) + 1], + sau8QASampleBlob, sizeof(sau8QASampleBlob)); + cbData = sizeof(sau8QASampleBlob) + 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. @@ -977,10 +1937,12 @@ static int usbHidHandleDefaultPipe(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb) switch (pSetup->wValue >> 8) { case VUSB_DT_STRING: - Log(("usbHid: GET_DESCRIPTOR DT_STRING wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex)); + LogRelFlow(("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)); + LogRelFlow(("usbHid: GET_DESCRIPTOR, huh? wValue=%#x wIndex=%#x\n", + pSetup->wValue, pSetup->wIndex)); break; } break; @@ -996,53 +1958,77 @@ static int usbHidHandleDefaultPipe(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb) case DT_IF_HID_DESCRIPTOR: { - if (pThis->isAbsolute) + switch (pThis->enmMode) { + case USBHIDMODE_ABSOLUTE: cbDesc = sizeof(g_UsbHidTIfHidDesc); pDesc = (const uint8_t *)&g_UsbHidTIfHidDesc; - } - else - { + 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); - Log(("usbHidMouse: GET_DESCRIPTOR DT_IF_HID_DESCRIPTOR wValue=%#x wIndex=%#x cbCopy=%#x\n", pSetup->wValue, pSetup->wIndex, cbCopy)); + 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: { - if (pThis->isAbsolute) + switch (pThis->enmMode) { + case USBHIDMODE_ABSOLUTE: cbDesc = sizeof(g_UsbHidTReportDesc); pDesc = (const uint8_t *)&g_UsbHidTReportDesc; - } - else - { + 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); - Log(("usbHid: GET_DESCRIPTOR DT_IF_HID_REPORT wValue=%#x wIndex=%#x cbCopy=%#x\n", pSetup->wValue, pSetup->wIndex, cbCopy)); + 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: - Log(("usbHid: GET_DESCRIPTOR, huh? wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex)); + LogRelFlow(("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)); + LogRelFlow(("usbHid: Bad GET_DESCRIPTOR req: bmRequestType=%#x\n", + pSetup->bmRequestType)); return usbHidCompleteStall(pThis, pEp, pUrb, "Bad GET_DESCRIPTOR"); } break; @@ -1054,7 +2040,8 @@ static int usbHidHandleDefaultPipe(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb) if (pSetup->wLength != 2) { - Log(("usbHid: Bad GET_STATUS req: wLength=%#x\n", pSetup->wLength)); + LogRelFlow(("usbHid: Bad GET_STATUS req: wLength=%#x\n", + pSetup->wLength)); break; } Assert(pSetup->wValue == 0); @@ -1063,7 +2050,7 @@ static int usbHidHandleDefaultPipe(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb) case VUSB_TO_DEVICE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST: { Assert(pSetup->wIndex == 0); - Log(("usbHid: GET_STATUS (device)\n")); + 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)); @@ -1078,7 +2065,8 @@ static int usbHidHandleDefaultPipe(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb) } else { - Log(("usbHid: GET_STATUS (interface) invalid, wIndex=%#x\n", pSetup->wIndex)); + LogRelFlow(("usbHid: GET_STATUS (interface) invalid, wIndex=%#x\n", + pSetup->wIndex)); } break; } @@ -1093,13 +2081,15 @@ static int usbHidHandleDefaultPipe(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb) } else { - Log(("usbHid: GET_STATUS (endpoint) invalid, wIndex=%#x\n", pSetup->wIndex)); + LogRelFlow(("usbHid: GET_STATUS (endpoint) invalid, wIndex=%#x\n", + pSetup->wIndex)); } break; } default: - Log(("usbHid: Bad GET_STATUS req: bmRequestType=%#x\n", pSetup->bmRequestType)); + LogRelFlow(("usbHid: Bad GET_STATUS req: bmRequestType=%#x\n", + pSetup->bmRequestType)); return usbHidCompleteStall(pThis, pEp, pUrb, "Bad GET_STATUS"); } break; @@ -1110,25 +2100,30 @@ static int usbHidHandleDefaultPipe(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb) } /** @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)); + 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"); } - /* 3.1 Bulk-Only Mass Storage Reset */ - else if ( pSetup->bmRequestType == (VUSB_REQ_CLASS | VUSB_TO_INTERFACE) - && pSetup->bRequest == 0xff - && !pSetup->wValue - && !pSetup->wLength - && pSetup->wIndex == 0) + else if ((pSetup->bmRequestType & VUSB_REQ_MASK) == VUSB_REQ_CLASS) { - Log(("usbHidHandleDefaultPipe: Bulk-Only Mass Storage Reset\n")); - return usbHidResetWorker(pThis, pUrb, false /*fSetConfig*/); + /* 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 { - 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)); + 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"); } @@ -1142,7 +2137,8 @@ static int usbHidHandleDefaultPipe(PUSBHID pThis, PUSBHIDEP pEp, PVUSBURB pUrb) 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)); + LogRelFlow(("usbHidQueue/#%u: pUrb=%p:%s EndPt=%#x\n", pUsbIns->iInstance, + pUrb, pUrb->pszDesc, pUrb->EndPt)); RTCritSectEnter(&pThis->CritSect); /* @@ -1178,7 +2174,8 @@ static DECLCALLBACK(int) usbHidQueue(PPDMUSBINS pUsbIns, PVUSBURB pUrb) static DECLCALLBACK(int) usbHidUsbClearHaltedEndpoint(PPDMUSBINS pUsbIns, unsigned uEndpoint) { PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID); - LogFlow(("usbHidUsbClearHaltedEndpoint/#%u: uEndpoint=%#x\n", pUsbIns->iInstance, uEndpoint)); + LogRelFlow(("usbHidUsbClearHaltedEndpoint/#%u: uEndpoint=%#x\n", + pUsbIns->iInstance, uEndpoint)); if ((uEndpoint & ~0x80) < RT_ELEMENTS(pThis->aEps)) { @@ -1196,7 +2193,8 @@ static DECLCALLBACK(int) usbHidUsbClearHaltedEndpoint(PPDMUSBINS pUsbIns, unsign */ static DECLCALLBACK(int) usbHidUsbSetInterface(PPDMUSBINS pUsbIns, uint8_t bInterfaceNumber, uint8_t bAlternateSetting) { - LogFlow(("usbHidUsbSetInterface/#%u: bInterfaceNumber=%u bAlternateSetting=%u\n", pUsbIns->iInstance, bInterfaceNumber, bAlternateSetting)); + LogRelFlow(("usbHidUsbSetInterface/#%u: bInterfaceNumber=%u bAlternateSetting=%u\n", + pUsbIns->iInstance, bInterfaceNumber, bAlternateSetting)); Assert(bAlternateSetting == 0); return VINF_SUCCESS; } @@ -1209,7 +2207,8 @@ static DECLCALLBACK(int) usbHidUsbSetConfiguration(PPDMUSBINS pUsbIns, uint8_t b const void *pvOldCfgDesc, const void *pvOldIfState, const void *pvNewCfgDesc) { PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID); - LogFlow(("usbHidUsbSetConfiguration/#%u: bConfigurationValue=%u\n", pUsbIns->iInstance, bConfigurationValue)); + LogRelFlow(("usbHidUsbSetConfiguration/#%u: bConfigurationValue=%u\n", + pUsbIns->iInstance, bConfigurationValue)); Assert(bConfigurationValue == 1); RTCritSectEnter(&pThis->CritSect); @@ -1223,8 +2222,10 @@ static DECLCALLBACK(int) usbHidUsbSetConfiguration(PPDMUSBINS pUsbIns, uint8_t b /* * Set received event type to absolute or relative. */ - pThis->Lun0.pDrv->pfnReportModes(pThis->Lun0.pDrv, !pThis->isAbsolute, - pThis->isAbsolute); + 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; @@ -1237,11 +2238,17 @@ static DECLCALLBACK(int) usbHidUsbSetConfiguration(PPDMUSBINS pUsbIns, uint8_t b static DECLCALLBACK(PCPDMUSBDESCCACHE) usbHidUsbGetDescriptorCache(PPDMUSBINS pUsbIns) { PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID); - LogFlow(("usbHidUsbGetDescriptorCache/#%u:\n", pUsbIns->iInstance)); - if (pThis->isAbsolute) { + LogRelFlow(("usbHidUsbGetDescriptorCache/#%u:\n", pUsbIns->iInstance)); + switch (pThis->enmMode) + { + case USBHIDMODE_ABSOLUTE: return &g_UsbHidTDescCache; - } else { + case USBHIDMODE_RELATIVE: return &g_UsbHidMDescCache; + case USBHIDMODE_MULTI_TOUCH: + return &g_UsbHidMTDescCache; + default: + return NULL; } } @@ -1252,7 +2259,7 @@ static DECLCALLBACK(PCPDMUSBDESCCACHE) usbHidUsbGetDescriptorCache(PPDMUSBINS pU static DECLCALLBACK(int) usbHidUsbReset(PPDMUSBINS pUsbIns, bool fResetOnLinux) { PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID); - LogFlow(("usbHidUsbReset/#%u:\n", pUsbIns->iInstance)); + LogRelFlow(("usbHidUsbReset/#%u:\n", pUsbIns->iInstance)); RTCritSectEnter(&pThis->CritSect); int rc = usbHidResetWorker(pThis, NULL, false /*fSetConfig*/); @@ -1268,7 +2275,7 @@ static DECLCALLBACK(int) usbHidUsbReset(PPDMUSBINS pUsbIns, bool fResetOnLinux) static void usbHidDestruct(PPDMUSBINS pUsbIns) { PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID); - LogFlow(("usbHidDestruct/#%u:\n", pUsbIns->iInstance)); + LogRelFlow(("usbHidDestruct/#%u:\n", pUsbIns->iInstance)); if (RTCritSectIsInitialized(&pThis->CritSect)) { @@ -1291,7 +2298,8 @@ static void usbHidDestruct(PPDMUSBINS pUsbIns) static DECLCALLBACK(int) usbHidConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal) { PUSBHID pThis = PDMINS_2_DATA(pUsbIns, PUSBHID); - Log(("usbHidConstruct/#%u:\n", iInstance)); + char szMode[64]; + LogRelFlow(("usbHidConstruct/#%u:\n", iInstance)); /* * Perform the basic structure initialization first so the destructor @@ -1311,16 +2319,28 @@ static DECLCALLBACK(int) usbHidConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFG /* * Validate and read the configuration. */ - rc = CFGMR3ValidateConfig(pCfg, "/", "Absolute|CoordShift", "Config", "UsbHid", iInstance); + rc = CFGMR3ValidateConfig(pCfg, "/", "Mode|CoordShift", "Config", "UsbHid", iInstance); if (RT_FAILURE(rc)) return rc; - rc = CFGMR3QueryBoolDef(pCfg, "Absolute", &pThis->isAbsolute, false); + 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. diff --git a/src/VBox/Devices/Input/testcase/Makefile.kmk b/src/VBox/Devices/Input/testcase/Makefile.kmk new file mode 100644 index 00000000..48e59399 --- /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 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) +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..8b733c4b --- /dev/null +++ b/src/VBox/Devices/Input/testcase/tstUsbMouse.cpp @@ -0,0 +1,329 @@ +/* $Id: tstUsbMouse.cpp $ */ +/** @file + * tstUsbMouse.cpp - testcase USB mouse and tablet devices. + */ + +/* + * Copyright (C) 2013 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, *PDRVTSTMOUSE; + + +/** Global mouse driver variable. + * @todo To be improved some time. */ +static DRVTSTMOUSE s_drvTstMouse; + + +/** @interface_method_impl{PDMUSBHLPR3,pfnVMSetErrorV} */ +static DECLCALLBACK(int) tstVMSetErrorV(PPDMUSBINS pUsbIns, int rc, + RT_SRC_POS_DECL, const char *pszFormat, + va_list va) +{ + 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) +{ + NOREF(iLun); + NOREF(pszDesc); + s_drvTstMouse.pDrvBase = pBaseInterface; + s_drvTstMouse.pDrv = PDMIBASE_QUERY_INTERFACE(pBaseInterface, + PDMIMOUSEPORT); + *ppBaseInterface = &s_drvTstMouse.IBase; + return VINF_SUCCESS; +} + + +static PDMUSBHLP s_tstUsbHlp; + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) tstMouseQueryInterface(PPDMIBASE pInterface, + const char *pszIID) +{ + PDRVTSTMOUSE pThis = RT_FROM_MEMBER(pInterface, DRVTSTMOUSE, IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSECONNECTOR, &pThis->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) +{ + int rc = VERR_NO_MEMORY; + PPDMUSBINS pThis = (PPDMUSBINS)RTMemAllocZ( sizeof(*pThis) + + g_UsbHidMou.cbInstance); + PCFGMNODE pCfg = NULL; + if (pThis) + pCfg = CFGMR3CreateTree(NULL); + if (pCfg) + rc = CFGMR3InsertString(pCfg, "Mode", pcszMode); + if (RT_SUCCESS(rc)) + rc = CFGMR3InsertInteger(pCfg, "CoordShift", u8CoordShift); + if (RT_SUCCESS(rc)) + { + s_drvTstMouse.pDrv = NULL; + s_drvTstMouse.pDrvBase = NULL; + pThis->iInstance = iInstance; + pThis->pHlpR3 = &s_tstUsbHlp; + rc = g_UsbHidMou.pfnConstruct(pThis, iInstance, pCfg, NULL); + if (RT_SUCCESS(rc)) + { + *ppThis = pThis; + return rc; + } + } + /* Failure */ + if (pCfg) + CFGMR3DestroyTree(pCfg); + if (pThis) + RTMemFree(pThis); + return rc; +} + + +static void testConstructAndDestruct(RTTEST hTest) +{ + PPDMUSBINS pThis; + RTTestSub(hTest, "simple construction and destruction"); + int rc = tstMouseConstruct(0, "relative", 1, &pThis); + RTTEST_CHECK_RC_OK(hTest, rc); + if (pThis) + g_UsbHidMou.pfnDestruct(pThis); +} + + +static void testSendPositionRel(RTTEST hTest) +{ + PPDMUSBINS pThis = NULL; + VUSBURB Urb; + RTTestSub(hTest, "sending a relative position event"); + int rc = tstMouseConstruct(0, "relative", 1, &pThis); + RT_ZERO(Urb); + if (RT_SUCCESS(rc)) + rc = g_UsbHidMou.pfnUsbReset(pThis, false); + if (RT_SUCCESS(rc) && !s_drvTstMouse.pDrv) + rc = VERR_PDM_MISSING_INTERFACE; + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + { + s_drvTstMouse.pDrv->pfnPutEvent(s_drvTstMouse.pDrv, 123, -16, 1, -1, 3); + Urb.EndPt = 0x01; + rc = g_UsbHidMou.pfnUrbQueue(pThis, &Urb); + } + if (RT_SUCCESS(rc)) + { + PVUSBURB pUrb = g_UsbHidMou.pfnUrbReap(pThis, 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 (pThis) + g_UsbHidMou.pfnDestruct(pThis); +} + + +static void testSendPositionAbs(RTTEST hTest) +{ + PPDMUSBINS pThis = NULL; + VUSBURB Urb; + RTTestSub(hTest, "sending an absolute position event"); + int rc = tstMouseConstruct(0, "absolute", 1, &pThis); + RT_ZERO(Urb); + if (RT_SUCCESS(rc)) + { + rc = g_UsbHidMou.pfnUsbReset(pThis, false); + } + if (RT_SUCCESS(rc)) + { + if (s_drvTstMouse.pDrv) + s_drvTstMouse.pDrv->pfnPutEventAbs(s_drvTstMouse.pDrv, 300, 200, 1, + 3, 3); + else + rc = VERR_PDM_MISSING_INTERFACE; + } + if (RT_SUCCESS(rc)) + { + Urb.EndPt = 0x01; + rc = g_UsbHidMou.pfnUrbQueue(pThis, &Urb); + } + if (RT_SUCCESS(rc)) + { + PVUSBURB pUrb = g_UsbHidMou.pfnUrbReap(pThis, 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 (pThis) + g_UsbHidMou.pfnDestruct(pThis); +} + +#if 0 +/** @todo PDM interface was updated. This is not working anymore. */ +static void testSendPositionMT(RTTEST hTest) +{ + PPDMUSBINS pThis = NULL; + VUSBURB Urb; + RTTestSub(hTest, "sending a multi-touch position event"); + int rc = tstMouseConstruct(0, "multitouch", 1, &pThis); + RT_ZERO(Urb); + if (RT_SUCCESS(rc)) + { + rc = g_UsbHidMou.pfnUsbReset(pThis, false); + } + if (RT_SUCCESS(rc)) + { + if (s_drvTstMouse.pDrv) + s_drvTstMouse.pDrv->pfnPutEventMT(s_drvTstMouse.pDrv, 300, 200, 2, + 3); + else + rc = VERR_PDM_MISSING_INTERFACE; + } + if (RT_SUCCESS(rc)) + { + Urb.EndPt = 0x01; + rc = g_UsbHidMou.pfnUrbQueue(pThis, &Urb); + } + if (RT_SUCCESS(rc)) + { + PVUSBURB pUrb = g_UsbHidMou.pfnUrbReap(pThis, 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 (pThis) + g_UsbHidMou.pfnDestruct(pThis); +} +#endif + +int main() +{ + /* + * Init the runtime, test and say hello. + */ + RTTEST hTest; + PDRVTSTMOUSE pThis; + int rc = RTTestInitAndCreate("tstUsbMouse", &hTest); + if (rc) + return rc; + RTTestBanner(hTest); + /* Set up our faked PDMUSBHLP interface. */ + s_tstUsbHlp.pfnVMSetErrorV = tstVMSetErrorV; + s_tstUsbHlp.pfnDriverAttach = tstDriverAttach; + /* Set up our global mouse driver */ + s_drvTstMouse.IBase.pfnQueryInterface = tstMouseQueryInterface; + s_drvTstMouse.IConnector.pfnReportModes = tstMouseReportModes; + + /* + * Run the tests. + */ + testConstructAndDestruct(hTest); + testSendPositionRel(hTest); + testSendPositionAbs(hTest); + /* testSendPositionMT(hTest); */ + return RTTestSummaryAndDestroy(hTest); +} |