VirtualBox

source: vbox/trunk/src/VBox/Devices/Input/PS2M.cpp@ 68100

Last change on this file since 68100 was 68100, checked in by vboxsync, 8 years ago

PS/2 mouse emulation: correct comment in test case and move test code to end.

bugref:8778: Double click not working with precision touchpad (user report)
Comments in the test case incorrectly stated that the interval between events
is 50ms. In fact it is 10ms by default and adjustable by the guest operating
system.
Also move the test case code to the end of the file for added clarity.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 47.2 KB
Line 
1/* $Id: PS2M.cpp 68100 2017-07-25 09:20:08Z vboxsync $ */
2/** @file
3 * PS2M - PS/2 auxiliary device (mouse) emulation.
4 */
5
6/*
7 * Copyright (C) 2007-2016 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/*
19 * References:
20 *
21 * The Undocumented PC (2nd Ed.), Frank van Gilluwe, Addison-Wesley, 1996.
22 * IBM TrackPoint System Version 4.0 Engineering Specification, 1999.
23 * ELAN Microelectronics eKM8025 USB & PS/2 Mouse Controller, 2006.
24 *
25 *
26 * Notes:
27 *
28 * - The auxiliary device commands are very similar to keyboard commands.
29 * Most keyboard commands which do not specifically deal with the keyboard
30 * (enable, disable, reset) have identical counterparts.
31 * - The code refers to 'auxiliary device' and 'mouse'; these terms are not
32 * quite interchangeable. 'Auxiliary device' is used when referring to the
33 * generic PS/2 auxiliary device interface and 'mouse' when referring to
34 * a mouse attached to the auxiliary port.
35 * - The basic modes of operation are reset, stream, and remote. Those are
36 * mutually exclusive. Stream and remote modes can additionally have wrap
37 * mode enabled.
38 * - The auxiliary device sends unsolicited data to the host only when it is
39 * both in stream mode and enabled. Otherwise it only responds to commands.
40 *
41 *
42 * There are three report packet formats supported by the emulated device. The
43 * standard three-byte PS/2 format (with middle button support), IntelliMouse
44 * four-byte format with added scroll wheel, and IntelliMouse Explorer four-byte
45 * format with reduced scroll wheel range but two additional buttons. Note that
46 * the first three bytes of the report are always the same.
47 *
48 * Upon reset, the mouse is always in the standard PS/2 mode. A special 'knock'
49 * sequence can be used to switch to ImPS/2 or ImEx mode. Three consecutive
50 * Set Sampling Rate (0F3h) commands with arguments 200, 100, 80 switch to ImPS/2
51 * mode. While in ImPS/2 or PS/2 mode, three consecutive Set Sampling Rate
52 * commands with arguments 200, 200, 80 switch to ImEx mode. The Read ID (0F2h)
53 * command will report the currently selected protocol.
54 *
55 *
56 * Standard PS/2 pointing device three-byte report packet format:
57 *
58 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
59 * |Bit/byte| bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
60 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
61 * | Byte 1 | Y ovfl | X ovfl | Y sign | X sign | Sync | M btn | R btn | L btn |
62 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
63 * | Byte 2 | X movement delta (two's complement) |
64 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
65 * | Byte 3 | Y movement delta (two's complement) |
66 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
67 *
68 * - The sync bit is always set. It allows software to synchronize data packets
69 * as the X/Y position data typically does not have bit 4 set.
70 * - The overflow bits are set if motion exceeds accumulator range. We use the
71 * maximum range (effectively 9 bits) and do not set the overflow bits.
72 * - Movement in the up/right direction is defined as having positive sign.
73 *
74 *
75 * IntelliMouse PS/2 (ImPS/2) fourth report packet byte:
76 *
77 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
78 * |Bit/byte| bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
79 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
80 * | Byte 4 | Z movement delta (two's complement) |
81 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
82 *
83 * - The valid range for Z delta values is only -8/+7, i.e. 4 bits.
84 *
85 * IntelliMouse Explorer (ImEx) fourth report packet byte:
86 *
87 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
88 * |Bit/byte| bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
89 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
90 * | Byte 4 | 0 | 0 | Btn 5 | Btn 4 | Z mov't delta (two's complement) |
91 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
92 *
93 */
94
95
96/*********************************************************************************************************************************
97* Header Files *
98*********************************************************************************************************************************/
99#define LOG_GROUP LOG_GROUP_DEV_KBD
100#include <VBox/vmm/pdmdev.h>
101#include <VBox/err.h>
102#include <iprt/assert.h>
103#include <iprt/uuid.h>
104#include "VBoxDD.h"
105#define IN_PS2M
106#include "PS2Dev.h"
107
108/*********************************************************************************************************************************
109* Defined Constants And Macros *
110*********************************************************************************************************************************/
111/** @name Auxiliary device commands sent by the system.
112 * @{ */
113#define ACMD_SET_SCALE_11 0xE6 /* Set 1:1 scaling. */
114#define ACMD_SET_SCALE_21 0xE7 /* Set 2:1 scaling. */
115#define ACMD_SET_RES 0xE8 /* Set resolution. */
116#define ACMD_REQ_STATUS 0xE9 /* Get device status. */
117#define ACMD_SET_STREAM 0xEA /* Set stream mode. */
118#define ACMD_READ_REMOTE 0xEB /* Read remote data. */
119#define ACMD_RESET_WRAP 0xEC /* Exit wrap mode. */
120#define ACMD_INVALID_1 0xED
121#define ACMD_SET_WRAP 0xEE /* Set wrap (echo) mode. */
122#define ACMD_INVALID_2 0xEF
123#define ACMD_SET_REMOTE 0xF0 /* Set remote mode. */
124#define ACMD_INVALID_3 0xF1
125#define ACMD_READ_ID 0xF2 /* Read device ID. */
126#define ACMD_SET_SAMP_RATE 0xF3 /* Set sampling rate. */
127#define ACMD_ENABLE 0xF4 /* Enable (streaming mode). */
128#define ACMD_DISABLE 0xF5 /* Disable (streaming mode). */
129#define ACMD_SET_DEFAULT 0xF6 /* Set defaults. */
130#define ACMD_INVALID_4 0xF7
131#define ACMD_INVALID_5 0xF8
132#define ACMD_INVALID_6 0xF9
133#define ACMD_INVALID_7 0xFA
134#define ACMD_INVALID_8 0xFB
135#define ACMD_INVALID_9 0xFC
136#define ACMD_INVALID_10 0xFD
137#define ACMD_RESEND 0xFE /* Resend response. */
138#define ACMD_RESET 0xFF /* Reset device. */
139/** @} */
140
141/** @name Auxiliary device responses sent to the system.
142 * @{ */
143#define ARSP_ID 0x00
144#define ARSP_BAT_OK 0xAA /* Self-test passed. */
145#define ARSP_ACK 0xFA /* Command acknowledged. */
146#define ARSP_ERROR 0xFC /* Bad command. */
147#define ARSP_RESEND 0xFE /* Requesting resend. */
148/** @} */
149
150/** Define a simple PS/2 input device queue. */
151#define DEF_PS2Q_TYPE(name, size) \
152 typedef struct { \
153 uint32_t rpos; \
154 uint32_t wpos; \
155 uint32_t cUsed; \
156 uint32_t cSize; \
157 uint8_t abQueue[size]; \
158 } name
159
160/* Internal mouse queue sizes. The input queue is relatively large,
161 * but the command queue only needs to handle a few bytes.
162 */
163#define AUX_EVT_QUEUE_SIZE 256
164#define AUX_CMD_QUEUE_SIZE 8
165
166
167/*********************************************************************************************************************************
168* Structures and Typedefs *
169*********************************************************************************************************************************/
170
171DEF_PS2Q_TYPE(AuxEvtQ, AUX_EVT_QUEUE_SIZE);
172DEF_PS2Q_TYPE(AuxCmdQ, AUX_CMD_QUEUE_SIZE);
173#ifndef VBOX_DEVICE_STRUCT_TESTCASE /// @todo hack
174DEF_PS2Q_TYPE(GeneriQ, 1);
175#endif
176
177/* Auxiliary device special modes of operation. */
178typedef enum {
179 AUX_MODE_STD, /* Standard operation. */
180 AUX_MODE_RESET, /* Currently in reset. */
181 AUX_MODE_WRAP /* Wrap mode (echoing input). */
182} PS2M_MODE;
183
184/* Auxiliary device operational state. */
185typedef enum {
186 AUX_STATE_RATE_ERR = RT_BIT(0), /* Invalid rate received. */
187 AUX_STATE_RES_ERR = RT_BIT(1), /* Invalid resolution received. */
188 AUX_STATE_SCALING = RT_BIT(4), /* 2:1 scaling in effect. */
189 AUX_STATE_ENABLED = RT_BIT(5), /* Reporting enabled in stream mode. */
190 AUX_STATE_REMOTE = RT_BIT(6) /* Remote mode (reports on request). */
191} PS2M_STATE;
192
193/* Externally visible state bits. */
194#define AUX_STATE_EXTERNAL (AUX_STATE_SCALING | AUX_STATE_ENABLED | AUX_STATE_REMOTE)
195
196/* Protocols supported by the PS/2 mouse. */
197typedef enum {
198 PS2M_PROTO_PS2STD = 0, /* Standard PS/2 mouse protocol. */
199 PS2M_PROTO_IMPS2 = 3, /* IntelliMouse PS/2 protocol. */
200 PS2M_PROTO_IMEX = 4 /* IntelliMouse Explorer protocol. */
201} PS2M_PROTO;
202
203/* Protocol selection 'knock' states. */
204typedef enum {
205 PS2M_KNOCK_INITIAL,
206 PS2M_KNOCK_1ST,
207 PS2M_KNOCK_IMPS2_2ND,
208 PS2M_KNOCK_IMEX_2ND
209} PS2M_KNOCK_STATE;
210
211/**
212 * The PS/2 auxiliary device instance data.
213 */
214typedef struct PS2M
215{
216 /** Pointer to parent device (keyboard controller). */
217 R3PTRTYPE(void *) pParent;
218 /** Operational state. */
219 uint8_t u8State;
220 /** Configured sampling rate. */
221 uint8_t u8SampleRate;
222 /** Configured resolution. */
223 uint8_t u8Resolution;
224 /** Currently processed command (if any). */
225 uint8_t u8CurrCmd;
226 /** Set if the throttle delay is active. */
227 bool fThrottleActive;
228 /** Set if the throttle delay is active. */
229 bool fDelayReset;
230 /** Operational mode. */
231 PS2M_MODE enmMode;
232 /** Currently used protocol. */
233 PS2M_PROTO enmProtocol;
234 /** Currently used protocol. */
235 PS2M_KNOCK_STATE enmKnockState;
236 /** Buffer holding mouse events to be sent to the host. */
237 AuxEvtQ evtQ;
238 /** Command response queue (priority). */
239 AuxCmdQ cmdQ;
240 /** Accumulated horizontal movement. */
241 int32_t iAccumX;
242 /** Accumulated vertical movement. */
243 int32_t iAccumY;
244 /** Accumulated Z axis movement. */
245 int32_t iAccumZ;
246 /** Accumulated button presses. */
247 uint32_t fAccumB;
248 /** Instantaneous button data. */
249 uint32_t fCurrB;
250 /** Button state last sent to the guest. */
251 uint32_t fReportedB;
252 /** Throttling delay in milliseconds. */
253 uint32_t uThrottleDelay;
254
255 /** The device critical section protecting everything - R3 Ptr */
256 R3PTRTYPE(PPDMCRITSECT) pCritSectR3;
257 /** Command delay timer - R3 Ptr. */
258 PTMTIMERR3 pDelayTimerR3;
259 /** Interrupt throttling timer - R3 Ptr. */
260 PTMTIMERR3 pThrottleTimerR3;
261 RTR3PTR Alignment1;
262
263 /** Command delay timer - RC Ptr. */
264 PTMTIMERRC pDelayTimerRC;
265 /** Interrupt throttling timer - RC Ptr. */
266 PTMTIMERRC pThrottleTimerRC;
267
268 /** Command delay timer - R0 Ptr. */
269 PTMTIMERR0 pDelayTimerR0;
270 /** Interrupt throttling timer - R0 Ptr. */
271 PTMTIMERR0 pThrottleTimerR0;
272
273 /**
274 * Mouse port - LUN#1.
275 *
276 * @implements PDMIBASE
277 * @implements PDMIMOUSEPORT
278 */
279 struct
280 {
281 /** The base interface for the mouse port. */
282 PDMIBASE IBase;
283 /** The keyboard port base interface. */
284 PDMIMOUSEPORT IPort;
285
286 /** The base interface of the attached mouse driver. */
287 R3PTRTYPE(PPDMIBASE) pDrvBase;
288 /** The keyboard interface of the attached mouse driver. */
289 R3PTRTYPE(PPDMIMOUSECONNECTOR) pDrv;
290 } Mouse;
291} PS2M, *PPS2M;
292
293AssertCompile(PS2M_STRUCT_FILLER >= sizeof(PS2M));
294
295#ifndef VBOX_DEVICE_STRUCT_TESTCASE
296
297
298/*********************************************************************************************************************************
299* Test code function declarations *
300*********************************************************************************************************************************/
301#if defined(RT_STRICT) && defined(IN_RING3)
302static void ps2mTestAccumulation(void);
303#endif
304
305/*********************************************************************************************************************************
306* Global Variables *
307*********************************************************************************************************************************/
308
309
310/*********************************************************************************************************************************
311* Internal Functions *
312*********************************************************************************************************************************/
313
314
315/**
316 * Clear a queue.
317 *
318 * @param pQ Pointer to the queue.
319 */
320static void ps2kClearQueue(GeneriQ *pQ)
321{
322 LogFlowFunc(("Clearing queue %p\n", pQ));
323 pQ->wpos = pQ->rpos;
324 pQ->cUsed = 0;
325}
326
327
328/**
329 * Add a byte to a queue.
330 *
331 * @param pQ Pointer to the queue.
332 * @param val The byte to store.
333 */
334static void ps2kInsertQueue(GeneriQ *pQ, uint8_t val)
335{
336 /* Check if queue is full. */
337 if (pQ->cUsed >= pQ->cSize)
338 {
339 LogRelFlowFunc(("queue %p full (%d entries)\n", pQ, pQ->cUsed));
340 return;
341 }
342 /* Insert data and update circular buffer write position. */
343 pQ->abQueue[pQ->wpos] = val;
344 if (++pQ->wpos == pQ->cSize)
345 pQ->wpos = 0; /* Roll over. */
346 ++pQ->cUsed;
347 LogRelFlowFunc(("inserted 0x%02X into queue %p\n", val, pQ));
348}
349
350#ifdef IN_RING3
351
352/**
353 * Save a queue state.
354 *
355 * @param pSSM SSM handle to write the state to.
356 * @param pQ Pointer to the queue.
357 */
358static void ps2kSaveQueue(PSSMHANDLE pSSM, GeneriQ *pQ)
359{
360 uint32_t cItems = pQ->cUsed;
361 int i;
362
363 /* Only save the number of items. Note that the read/write
364 * positions aren't saved as they will be rebuilt on load.
365 */
366 SSMR3PutU32(pSSM, cItems);
367
368 LogFlow(("Storing %d items from queue %p\n", cItems, pQ));
369
370 /* Save queue data - only the bytes actually used (typically zero). */
371 for (i = pQ->rpos; cItems-- > 0; i = (i + 1) % pQ->cSize)
372 SSMR3PutU8(pSSM, pQ->abQueue[i]);
373}
374
375/**
376 * Load a queue state.
377 *
378 * @param pSSM SSM handle to read the state from.
379 * @param pQ Pointer to the queue.
380 *
381 * @return int VBox status/error code.
382 */
383static int ps2kLoadQueue(PSSMHANDLE pSSM, GeneriQ *pQ)
384{
385 int rc;
386
387 /* On load, always put the read pointer at zero. */
388 SSMR3GetU32(pSSM, &pQ->cUsed);
389
390 LogFlow(("Loading %d items to queue %p\n", pQ->cUsed, pQ));
391
392 if (pQ->cUsed > pQ->cSize)
393 {
394 AssertMsgFailed(("Saved size=%u, actual=%u\n", pQ->cUsed, pQ->cSize));
395 return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
396 }
397
398 /* Recalculate queue positions and load data in one go. */
399 pQ->rpos = 0;
400 pQ->wpos = pQ->cUsed;
401 rc = SSMR3GetMem(pSSM, pQ->abQueue, pQ->cUsed);
402
403 return rc;
404}
405
406/* Report a change in status down (or is it up?) the driver chain. */
407static void ps2mSetDriverState(PPS2M pThis, bool fEnabled)
408{
409 PPDMIMOUSECONNECTOR pDrv = pThis->Mouse.pDrv;
410 if (pDrv)
411 pDrv->pfnReportModes(pDrv, fEnabled, false, false);
412}
413
414/* Reset the pointing device. */
415static void ps2mReset(PPS2M pThis)
416{
417 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_BAT_OK);
418 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, 0);
419 pThis->enmMode = AUX_MODE_STD;
420 pThis->u8CurrCmd = 0;
421
422 /// @todo move to its proper home!
423 ps2mSetDriverState(pThis, true);
424}
425
426#endif /* IN_RING3 */
427
428/**
429 * Retrieve a byte from a queue.
430 *
431 * @param pQ Pointer to the queue.
432 * @param pVal Pointer to storage for the byte.
433 *
434 * @return int VINF_TRY_AGAIN if queue is empty,
435 * VINF_SUCCESS if a byte was read.
436 */
437static int ps2kRemoveQueue(GeneriQ *pQ, uint8_t *pVal)
438{
439 int rc = VINF_TRY_AGAIN;
440
441 Assert(pVal);
442 if (pQ->cUsed)
443 {
444 *pVal = pQ->abQueue[pQ->rpos];
445 if (++pQ->rpos == pQ->cSize)
446 pQ->rpos = 0; /* Roll over. */
447 --pQ->cUsed;
448 rc = VINF_SUCCESS;
449 LogFlowFunc(("removed 0x%02X from queue %p\n", *pVal, pQ));
450 } else
451 LogFlowFunc(("queue %p empty\n", pQ));
452 return rc;
453}
454
455static void ps2mSetRate(PPS2M pThis, uint8_t rate)
456{
457 Assert(rate);
458 pThis->uThrottleDelay = rate ? 1000 / rate : 0;
459 pThis->u8SampleRate = rate;
460 LogFlowFunc(("Sampling rate %u, throttle delay %u ms\n", pThis->u8SampleRate, pThis->uThrottleDelay));
461}
462
463static void ps2mSetDefaults(PPS2M pThis)
464{
465 LogFlowFunc(("Set mouse defaults\n"));
466 /* Standard protocol, reporting disabled, resolution 2, 1:1 scaling. */
467 pThis->enmProtocol = PS2M_PROTO_PS2STD;
468 pThis->u8State = 0;
469 pThis->u8Resolution = 2;
470
471 /* Sample rate 100 reports per second. */
472 ps2mSetRate(pThis, 100);
473
474 /* Event queue, eccumulators, and button status bits are cleared. */
475 ps2kClearQueue((GeneriQ *)&pThis->evtQ);
476 pThis->iAccumX = pThis->iAccumY = pThis->iAccumZ = pThis->fAccumB;
477}
478
479/* Handle the sampling rate 'knock' sequence which selects protocol. */
480static void ps2mRateProtocolKnock(PPS2M pThis, uint8_t rate)
481{
482 switch (pThis->enmKnockState)
483 {
484 case PS2M_KNOCK_INITIAL:
485 if (rate == 200)
486 pThis->enmKnockState = PS2M_KNOCK_1ST;
487 break;
488 case PS2M_KNOCK_1ST:
489 if (rate == 100)
490 pThis->enmKnockState = PS2M_KNOCK_IMPS2_2ND;
491 else if (rate == 200)
492 pThis->enmKnockState = PS2M_KNOCK_IMEX_2ND;
493 else
494 pThis->enmKnockState = PS2M_KNOCK_INITIAL;
495 break;
496 case PS2M_KNOCK_IMPS2_2ND:
497 if (rate == 80)
498 {
499 pThis->enmProtocol = PS2M_PROTO_IMPS2;
500 LogRelFlow(("PS2M: Switching mouse to ImPS/2 protocol.\n"));
501 }
502 pThis->enmKnockState = PS2M_KNOCK_INITIAL;
503 break;
504 case PS2M_KNOCK_IMEX_2ND:
505 if (rate == 80)
506 {
507 pThis->enmProtocol = PS2M_PROTO_IMEX;
508 LogRelFlow(("PS2M: Switching mouse to ImEx protocol.\n"));
509 }
510 /* Fall through! */
511 default:
512 pThis->enmKnockState = PS2M_KNOCK_INITIAL;
513 }
514}
515
516/* Three-button event mask. */
517#define PS2M_STD_BTN_MASK (RT_BIT(0) | RT_BIT(1) | RT_BIT(2))
518
519/* Report accumulated movement and button presses, then clear the accumulators. */
520static void ps2mReportAccumulatedEvents(PPS2M pThis, GeneriQ *pQueue, bool fAccumBtns)
521{
522 uint32_t fBtnState = fAccumBtns ? pThis->fAccumB : pThis->fCurrB;
523 uint8_t val;
524 int dX, dY, dZ;
525
526 /* Clamp the accumulated delta values to the allowed range. */
527 dX = RT_MIN(RT_MAX(pThis->iAccumX, -255), 255);
528 dY = RT_MIN(RT_MAX(pThis->iAccumY, -255), 255);
529 dZ = RT_MIN(RT_MAX(pThis->iAccumZ, -8), 7);
530
531 /* Start with the sync bit and buttons 1-3. */
532 val = RT_BIT(3) | (fBtnState & PS2M_STD_BTN_MASK);
533 /* Set the X/Y sign bits. */
534 if (dX < 0)
535 val |= RT_BIT(4);
536 if (dY < 0)
537 val |= RT_BIT(5);
538
539 /* Send the standard 3-byte packet (always the same). */
540 ps2kInsertQueue(pQueue, val);
541 ps2kInsertQueue(pQueue, dX);
542 ps2kInsertQueue(pQueue, dY);
543
544 /* Add fourth byte if extended protocol is in use. */
545 if (pThis->enmProtocol > PS2M_PROTO_PS2STD)
546 {
547 if (pThis->enmProtocol == PS2M_PROTO_IMPS2)
548 ps2kInsertQueue(pQueue, dZ);
549 else
550 {
551 Assert(pThis->enmProtocol == PS2M_PROTO_IMEX);
552 /* Z value uses 4 bits; buttons 4/5 in bits 4 and 5. */
553 val = dZ & 0x0f;
554 val |= (fBtnState << 1) & (RT_BIT(4) | RT_BIT(5));
555 ps2kInsertQueue(pQueue, val);
556 }
557 }
558
559 /* Clear the movement accumulators, but not necessarily button state. */
560 pThis->iAccumX = pThis->iAccumY = pThis->iAccumZ = 0;
561 /* Clear accumulated button state only when it's being used. */
562 if (fAccumBtns)
563 {
564 pThis->fReportedB = pThis->fAccumB;
565 pThis->fAccumB = 0;
566 }
567}
568
569
570/* Determine whether a reporting rate is one of the valid ones. */
571bool ps2mIsRateSupported(uint8_t rate)
572{
573 static uint8_t aValidRates[] = { 10, 20, 40, 60, 80, 100, 200 };
574 size_t i;
575 bool fValid = false;
576
577 for (i = 0; i < RT_ELEMENTS(aValidRates); ++i)
578 if (aValidRates[i] == rate)
579 {
580 fValid = true;
581 break;
582 }
583
584 return fValid;
585}
586
587/**
588 * Receive and process a byte sent by the keyboard controller.
589 *
590 * @param pThis The PS/2 auxiliary device instance data.
591 * @param cmd The command (or data) byte.
592 */
593int PS2MByteToAux(PPS2M pThis, uint8_t cmd)
594{
595 uint8_t u8Val;
596 bool fHandled = true;
597
598 LogFlowFunc(("cmd=0x%02X, active cmd=0x%02X\n", cmd, pThis->u8CurrCmd));
599//LogRel(("aux: cmd=0x%02X, active cmd=0x%02X\n", cmd, pThis->u8CurrCmd));
600
601 if (pThis->enmMode == AUX_MODE_RESET)
602 /* In reset mode, do not respond at all. */
603 return VINF_SUCCESS;
604
605 /* If there's anything left in the command response queue, trash it. */
606 ps2kClearQueue((GeneriQ *)&pThis->cmdQ);
607
608 if (pThis->enmMode == AUX_MODE_WRAP)
609 {
610 /* In wrap mode, bounce most data right back.*/
611 if (cmd == ACMD_RESET || cmd == ACMD_RESET_WRAP)
612 ; /* Handle as regular commands. */
613 else
614 {
615 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, cmd);
616 return VINF_SUCCESS;
617 }
618 }
619
620#ifndef IN_RING3
621 /* Reset, Enable, and Set Default commands must be run in R3. */
622 if (cmd == ACMD_RESET || cmd == ACMD_ENABLE || cmd == ACMD_SET_DEFAULT)
623 return VINF_IOM_R3_IOPORT_WRITE;
624#endif
625
626 switch (cmd)
627 {
628 case ACMD_SET_SCALE_11:
629 pThis->u8State &= ~AUX_STATE_SCALING;
630 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
631 pThis->u8CurrCmd = 0;
632 break;
633 case ACMD_SET_SCALE_21:
634 pThis->u8State |= AUX_STATE_SCALING;
635 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
636 pThis->u8CurrCmd = 0;
637 break;
638 case ACMD_REQ_STATUS:
639 /* Report current status, sample rate, and resolution. */
640 u8Val = (pThis->u8State & AUX_STATE_EXTERNAL) | (pThis->fCurrB & PS2M_STD_BTN_MASK);
641 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
642 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, u8Val);
643 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->u8Resolution);
644 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->u8SampleRate);
645 pThis->u8CurrCmd = 0;
646 break;
647 case ACMD_SET_STREAM:
648 pThis->u8State &= ~AUX_STATE_REMOTE;
649 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
650 pThis->u8CurrCmd = 0;
651 break;
652 case ACMD_READ_REMOTE:
653 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
654 ps2mReportAccumulatedEvents(pThis, (GeneriQ *)&pThis->cmdQ, false);
655 pThis->u8CurrCmd = 0;
656 break;
657 case ACMD_RESET_WRAP:
658 pThis->enmMode = AUX_MODE_STD;
659 /* NB: Stream mode reporting remains disabled! */
660 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
661 pThis->u8CurrCmd = 0;
662 break;
663 case ACMD_SET_WRAP:
664 pThis->enmMode = AUX_MODE_WRAP;
665 pThis->u8State &= ~AUX_STATE_ENABLED;
666 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
667 pThis->u8CurrCmd = 0;
668 break;
669 case ACMD_SET_REMOTE:
670 pThis->u8State |= AUX_STATE_REMOTE;
671 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
672 pThis->u8CurrCmd = 0;
673 break;
674 case ACMD_READ_ID:
675 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
676 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, pThis->enmProtocol);
677 pThis->u8CurrCmd = 0;
678 break;
679 case ACMD_ENABLE:
680 pThis->u8State |= AUX_STATE_ENABLED;
681#ifdef IN_RING3
682 ps2mSetDriverState(pThis, true);
683#else
684 AssertLogRelMsgFailed(("Invalid ACMD_ENABLE outside R3!\n"));
685#endif
686 ps2kClearQueue((GeneriQ *)&pThis->evtQ);
687 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
688 pThis->u8CurrCmd = 0;
689 break;
690 case ACMD_DISABLE:
691 pThis->u8State &= ~AUX_STATE_ENABLED;
692 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
693 pThis->u8CurrCmd = 0;
694 break;
695 case ACMD_SET_DEFAULT:
696 ps2mSetDefaults(pThis);
697 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
698 pThis->u8CurrCmd = 0;
699 break;
700 case ACMD_RESEND:
701 pThis->u8CurrCmd = 0;
702 break;
703 case ACMD_RESET:
704 ps2mSetDefaults(pThis);
705 /// @todo reset more?
706 pThis->u8CurrCmd = cmd;
707 pThis->enmMode = AUX_MODE_RESET;
708 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
709 if (pThis->fDelayReset)
710 /* Slightly delay reset completion; it might take hundreds of ms. */
711 TMTimerSetMillies(pThis->CTX_SUFF(pDelayTimer), 1);
712 else
713#ifdef IN_RING3
714 ps2mReset(pThis);
715#else
716 AssertLogRelMsgFailed(("Invalid ACMD_RESET outside R3!\n"));
717#endif
718 break;
719 /* The following commands need a parameter. */
720 case ACMD_SET_RES:
721 case ACMD_SET_SAMP_RATE:
722 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
723 pThis->u8CurrCmd = cmd;
724 break;
725 default:
726 /* Sending a command instead of a parameter starts the new command. */
727 switch (pThis->u8CurrCmd)
728 {
729 case ACMD_SET_RES:
730 if (cmd < 4) /* Valid resolutions are 0-3. */
731 {
732 pThis->u8Resolution = cmd;
733 pThis->u8State &= ~AUX_STATE_RES_ERR;
734 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
735 pThis->u8CurrCmd = 0;
736 }
737 else
738 {
739 /* Bad resolution. Reply with Resend or Error. */
740 if (pThis->u8State & AUX_STATE_RES_ERR)
741 {
742 pThis->u8State &= ~AUX_STATE_RES_ERR;
743 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ERROR);
744 pThis->u8CurrCmd = 0;
745 }
746 else
747 {
748 pThis->u8State |= AUX_STATE_RES_ERR;
749 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_RESEND);
750 /* NB: Current command remains unchanged. */
751 }
752 }
753 break;
754 case ACMD_SET_SAMP_RATE:
755 if (ps2mIsRateSupported(cmd))
756 {
757 pThis->u8State &= ~AUX_STATE_RATE_ERR;
758 ps2mSetRate(pThis, cmd);
759 ps2mRateProtocolKnock(pThis, cmd);
760 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ACK);
761 pThis->u8CurrCmd = 0;
762 }
763 else
764 {
765 /* Bad rate. Reply with Resend or Error. */
766 if (pThis->u8State & AUX_STATE_RATE_ERR)
767 {
768 pThis->u8State &= ~AUX_STATE_RATE_ERR;
769 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_ERROR);
770 pThis->u8CurrCmd = 0;
771 }
772 else
773 {
774 pThis->u8State |= AUX_STATE_RATE_ERR;
775 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_RESEND);
776 /* NB: Current command remains unchanged. */
777 }
778 }
779 break;
780 default:
781 fHandled = false;
782 }
783 /* Fall through only to handle unrecognized commands. */
784 if (fHandled)
785 break;
786 /* fall thru */
787
788 case ACMD_INVALID_1:
789 case ACMD_INVALID_2:
790 case ACMD_INVALID_3:
791 case ACMD_INVALID_4:
792 case ACMD_INVALID_5:
793 case ACMD_INVALID_6:
794 case ACMD_INVALID_7:
795 case ACMD_INVALID_8:
796 case ACMD_INVALID_9:
797 case ACMD_INVALID_10:
798 Log(("Unsupported command 0x%02X!\n", cmd));
799 ps2kInsertQueue((GeneriQ *)&pThis->cmdQ, ARSP_RESEND);
800 pThis->u8CurrCmd = 0;
801 break;
802 }
803 LogFlowFunc(("Active cmd now 0x%02X; updating interrupts\n", pThis->u8CurrCmd));
804// KBCUpdateInterrupts(pThis->pParent);
805 return VINF_SUCCESS;
806}
807
808/**
809 * Send a byte (keystroke or command response) to the keyboard controller.
810 *
811 * @returns VINF_SUCCESS or VINF_TRY_AGAIN.
812 * @param pThis The PS/2 auxiliary device instance data.
813 * @param pb Where to return the byte we've read.
814 * @remarks Caller must have entered the device critical section.
815 */
816int PS2MByteFromAux(PPS2M pThis, uint8_t *pb)
817{
818 int rc;
819
820 AssertPtr(pb);
821
822 /* Anything in the command queue has priority over data
823 * in the event queue. Additionally, keystrokes are /// @todo true?
824 * blocked if a command is currently in progress, even if
825 * the command queue is empty.
826 */
827 /// @todo Probably should flush/not fill queue if stream mode reporting disabled?!
828 rc = ps2kRemoveQueue((GeneriQ *)&pThis->cmdQ, pb);
829 if (rc != VINF_SUCCESS && !pThis->u8CurrCmd && (pThis->u8State & AUX_STATE_ENABLED))
830 rc = ps2kRemoveQueue((GeneriQ *)&pThis->evtQ, pb);
831
832 LogFlowFunc(("mouse sends 0x%02x (%svalid data)\n", *pb, rc == VINF_SUCCESS ? "" : "not "));
833//if (rc == VINF_SUCCESS) LogRel(("aux: sends 0x%02X\n", *pb));
834
835 return rc;
836}
837
838#ifdef IN_RING3
839
840/** Is there any state change to send as events to the guest? */
841static uint32_t ps2mHaveEvents(PPS2M pThis)
842{
843 return pThis->iAccumX | pThis->iAccumY | pThis->iAccumZ
844 | (pThis->fCurrB != pThis->fReportedB) | (pThis->fAccumB != 0);
845}
846
847/* Event rate throttling timer to emulate the auxiliary device sampling rate.
848 */
849static DECLCALLBACK(void) ps2mThrottleTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
850{
851 RT_NOREF2(pDevIns, pTimer);
852 PPS2M pThis = (PS2M *)pvUser;
853 uint32_t uHaveEvents;
854
855 /* Grab the lock to avoid races with PutEvent(). */
856 int rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY);
857 AssertReleaseRC(rc);
858
859#if 0
860 /* If the input queue is not empty, restart the timer. */
861#else
862 /* If more movement is accumulated, report it and restart the timer. */
863 uHaveEvents = ps2mHaveEvents(pThis);
864 LogFlowFunc(("Have%s events\n", uHaveEvents ? "" : " no"));
865
866 if (uHaveEvents)
867#endif
868 {
869 /* Report accumulated data, poke the KBC, and start the timer. */
870 ps2mReportAccumulatedEvents(pThis, (GeneriQ *)&pThis->evtQ, true);
871 KBCUpdateInterrupts(pThis->pParent);
872 TMTimerSetMillies(pThis->CTX_SUFF(pThrottleTimer), pThis->uThrottleDelay);
873 }
874 else
875 pThis->fThrottleActive = false;
876
877 PDMCritSectLeave(pThis->pCritSectR3);
878}
879
880/* The auxiliary device is specified to take up to about 500 milliseconds. We need
881 * to delay sending the result to the host for at least a tiny little while.
882 */
883static DECLCALLBACK(void) ps2mDelayTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
884{
885 RT_NOREF2(pDevIns, pTimer);
886 PPS2M pThis = (PS2M *)pvUser;
887
888 LogFlowFunc(("Delay timer: cmd %02X\n", pThis->u8CurrCmd));
889
890 Assert(pThis->u8CurrCmd == ACMD_RESET);
891 ps2mReset(pThis);
892
893 /// @todo Might want a PS2MCompleteCommand() to push last response, clear command, and kick the KBC...
894 /* Give the KBC a kick. */
895 KBCUpdateInterrupts(pThis->pParent);
896}
897
898
899/**
900 * Debug device info handler. Prints basic auxiliary device state.
901 *
902 * @param pDevIns Device instance which registered the info.
903 * @param pHlp Callback functions for doing output.
904 * @param pszArgs Argument string. Optional and specific to the handler.
905 */
906static DECLCALLBACK(void) ps2mInfoState(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
907{
908 static const char *pcszModes[] = { "normal", "reset", "wrap" };
909 static const char *pcszProtocols[] = { "PS/2", NULL, NULL, "ImPS/2", "ImEx" };
910 PPS2M pThis = KBDGetPS2MFromDevIns(pDevIns);
911 NOREF(pszArgs);
912
913 Assert(pThis->enmMode <= RT_ELEMENTS(pcszModes));
914 Assert(pThis->enmProtocol <= RT_ELEMENTS(pcszProtocols));
915 pHlp->pfnPrintf(pHlp, "PS/2 mouse state: %s, %s mode, reporting %s\n",
916 pcszModes[pThis->enmMode],
917 pThis->u8State & AUX_STATE_REMOTE ? "remote" : "stream",
918 pThis->u8State & AUX_STATE_ENABLED ? "enabled" : "disabled");
919 pHlp->pfnPrintf(pHlp, "Protocol: %s, scaling %u:1\n",
920 pcszProtocols[pThis->enmProtocol], pThis->u8State & AUX_STATE_SCALING ? 2 : 1);
921 pHlp->pfnPrintf(pHlp, "Active command %02X\n", pThis->u8CurrCmd);
922 pHlp->pfnPrintf(pHlp, "Sampling rate %u reports/sec, resolution %u counts/mm\n",
923 pThis->u8SampleRate, 1 << pThis->u8Resolution);
924 pHlp->pfnPrintf(pHlp, "Command queue: %d items (%d max)\n",
925 pThis->cmdQ.cUsed, pThis->cmdQ.cSize);
926 pHlp->pfnPrintf(pHlp, "Event queue : %d items (%d max)\n",
927 pThis->evtQ.cUsed, pThis->evtQ.cSize);
928}
929
930/* -=-=-=-=-=- Mouse: IBase -=-=-=-=-=- */
931
932/**
933 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
934 */
935static DECLCALLBACK(void *) ps2mQueryInterface(PPDMIBASE pInterface, const char *pszIID)
936{
937 PPS2M pThis = RT_FROM_MEMBER(pInterface, PS2M, Mouse.IBase);
938 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->Mouse.IBase);
939 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSEPORT, &pThis->Mouse.IPort);
940 return NULL;
941}
942
943
944/* -=-=-=-=-=- Mouse: IMousePort -=-=-=-=-=- */
945
946/**
947 * Mouse event handler.
948 *
949 * @returns VBox status code.
950 * @param pThis The PS/2 auxiliary device instance data.
951 * @param dx X direction movement delta.
952 * @param dy Y direction movement delta.
953 * @param dz Z (vertical scroll) movement delta.
954 * @param dw W (horizontal scroll) movement delta.
955 * @param fButtons Depressed button mask.
956 */
957static int ps2mPutEventWorker(PPS2M pThis, int32_t dx, int32_t dy,
958 int32_t dz, int32_t dw, uint32_t fButtons)
959{
960 RT_NOREF1(dw);
961 int rc = VINF_SUCCESS;
962
963 /* Update internal accumulators and button state. */
964 pThis->iAccumX += dx;
965 pThis->iAccumY += dy;
966 pThis->iAccumZ += dz;
967 pThis->fAccumB |= fButtons; /// @todo accumulate based on current protocol?
968 pThis->fCurrB = fButtons;
969
970#if 1
971 /* Report the event and start the throttle timer unless it's already running. */
972 if (!pThis->fThrottleActive)
973 {
974 ps2mReportAccumulatedEvents(pThis, (GeneriQ *)&pThis->evtQ, true);
975 KBCUpdateInterrupts(pThis->pParent);
976 pThis->fThrottleActive = true;
977 TMTimerSetMillies(pThis->CTX_SUFF(pThrottleTimer), pThis->uThrottleDelay);
978 }
979#else
980 /* Clamp the delta values to the allowed range. */
981 dx = RT_MIN(RT_MAX(dx, -256), 255);
982 dy = RT_MIN(RT_MAX(dy, -256), 255);
983
984 /* Start with the sync bit. */
985 val = RT_BIT(3);
986 /* Add buttons 1-3. */
987 val |= fButtons & PS2M_STD_BTN_MASK;
988 /* Set the X/Y sign bits. */
989 if (dx < 0)
990 val |= RT_BIT(4);
991 if (dy < 0)
992 val |= RT_BIT(5);
993
994 ps2kInsertQueue((GeneriQ *)&pThis->evtQ, val);
995 ps2kInsertQueue((GeneriQ *)&pThis->evtQ, (uint8_t)dx);
996 ps2kInsertQueue((GeneriQ *)&pThis->evtQ, (uint8_t)dy);
997 if (pThis->enmProtocol > PS2M_PROTO_PS2STD)
998 {
999 ps2kInsertQueue((GeneriQ *)&pThis->evtQ, (uint8_t)dz);
1000 }
1001#endif
1002
1003 return rc;
1004}
1005
1006/* -=-=-=-=-=- Mouse: IMousePort -=-=-=-=-=- */
1007
1008/**
1009 * @interface_method_impl{PDMIMOUSEPORT,pfnPutEvent}
1010 */
1011static DECLCALLBACK(int) ps2mPutEvent(PPDMIMOUSEPORT pInterface, int32_t dx, int32_t dy,
1012 int32_t dz, int32_t dw, uint32_t fButtons)
1013{
1014 PPS2M pThis = RT_FROM_MEMBER(pInterface, PS2M, Mouse.IPort);
1015 int rc = PDMCritSectEnter(pThis->pCritSectR3, VERR_SEM_BUSY);
1016 AssertReleaseRC(rc);
1017
1018 LogRelFlowFunc(("dX=%d dY=%d dZ=%d dW=%d buttons=%02X\n", dx, dy, dz, dw, fButtons));
1019 /* NB: The PS/2 Y axis direction is inverted relative to ours. */
1020 ps2mPutEventWorker(pThis, dx, -dy, dz, dw, fButtons);
1021
1022 PDMCritSectLeave(pThis->pCritSectR3);
1023 return VINF_SUCCESS;
1024}
1025
1026/**
1027 * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventAbs}
1028 */
1029static DECLCALLBACK(int) ps2mPutEventAbs(PPDMIMOUSEPORT pInterface, uint32_t x, uint32_t y,
1030 int32_t dz, int32_t dw, uint32_t fButtons)
1031{
1032 AssertFailedReturn(VERR_NOT_SUPPORTED);
1033 NOREF(pInterface); NOREF(x); NOREF(y); NOREF(dz); NOREF(dw); NOREF(fButtons);
1034}
1035
1036/**
1037 * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventMultiTouch}
1038 */
1039static DECLCALLBACK(int) ps2mPutEventMT(PPDMIMOUSEPORT pInterface, uint8_t cContacts,
1040 const uint64_t *pau64Contacts, uint32_t u32ScanTime)
1041{
1042 AssertFailedReturn(VERR_NOT_SUPPORTED);
1043 NOREF(pInterface); NOREF(cContacts); NOREF(pau64Contacts); NOREF(u32ScanTime);
1044}
1045
1046
1047
1048/**
1049 * Attach command.
1050 *
1051 * This is called to let the device attach to a driver for a
1052 * specified LUN.
1053 *
1054 * This is like plugging in the mouse after turning on the
1055 * system.
1056 *
1057 * @returns VBox status code.
1058 * @param pThis The PS/2 auxiliary device instance data.
1059 * @param pDevIns The device instance.
1060 * @param iLUN The logical unit which is being detached.
1061 * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
1062 */
1063int PS2MAttach(PPS2M pThis, PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
1064{
1065 int rc;
1066
1067 /* The LUN must be 1, i.e. mouse. */
1068 Assert(iLUN == 1);
1069 AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
1070 ("PS/2 mouse does not support hotplugging\n"),
1071 VERR_INVALID_PARAMETER);
1072
1073 LogFlowFunc(("iLUN=%d\n", iLUN));
1074
1075 rc = PDMDevHlpDriverAttach(pDevIns, iLUN, &pThis->Mouse.IBase, &pThis->Mouse.pDrvBase, "Mouse Port");
1076 if (RT_SUCCESS(rc))
1077 {
1078 pThis->Mouse.pDrv = PDMIBASE_QUERY_INTERFACE(pThis->Mouse.pDrvBase, PDMIMOUSECONNECTOR);
1079 if (!pThis->Mouse.pDrv)
1080 {
1081 AssertLogRelMsgFailed(("LUN #1 doesn't have a mouse interface! rc=%Rrc\n", rc));
1082 rc = VERR_PDM_MISSING_INTERFACE;
1083 }
1084 }
1085 else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
1086 {
1087 Log(("%s/%d: warning: no driver attached to LUN #1!\n", pDevIns->pReg->szName, pDevIns->iInstance));
1088 rc = VINF_SUCCESS;
1089 }
1090 else
1091 AssertLogRelMsgFailed(("Failed to attach LUN #1! rc=%Rrc\n", rc));
1092
1093 return rc;
1094}
1095
1096void PS2MSaveState(PPS2M pThis, PSSMHANDLE pSSM)
1097{
1098 LogFlowFunc(("Saving PS2M state\n"));
1099
1100 /* Save the core auxiliary device state. */
1101 SSMR3PutU8(pSSM, pThis->u8State);
1102 SSMR3PutU8(pSSM, pThis->u8SampleRate);
1103 SSMR3PutU8(pSSM, pThis->u8Resolution);
1104 SSMR3PutU8(pSSM, pThis->u8CurrCmd);
1105 SSMR3PutU8(pSSM, pThis->enmMode);
1106 SSMR3PutU8(pSSM, pThis->enmProtocol);
1107 SSMR3PutU8(pSSM, pThis->enmKnockState);
1108
1109 /* Save the command and event queues. */
1110 ps2kSaveQueue(pSSM, (GeneriQ *)&pThis->cmdQ);
1111 ps2kSaveQueue(pSSM, (GeneriQ *)&pThis->evtQ);
1112
1113 /* Save the command delay timer. Note that the rate throttling
1114 * timer is *not* saved.
1115 */
1116 TMR3TimerSave(pThis->CTX_SUFF(pDelayTimer), pSSM);
1117}
1118
1119int PS2MLoadState(PPS2M pThis, PSSMHANDLE pSSM, uint32_t uVersion)
1120{
1121 uint8_t u8;
1122 int rc;
1123
1124 NOREF(uVersion);
1125 LogFlowFunc(("Loading PS2M state version %u\n", uVersion));
1126
1127 /* Load the basic auxiliary device state. */
1128 SSMR3GetU8(pSSM, &pThis->u8State);
1129 SSMR3GetU8(pSSM, &pThis->u8SampleRate);
1130 SSMR3GetU8(pSSM, &pThis->u8Resolution);
1131 SSMR3GetU8(pSSM, &pThis->u8CurrCmd);
1132 SSMR3GetU8(pSSM, &u8);
1133 pThis->enmMode = (PS2M_MODE)u8;
1134 SSMR3GetU8(pSSM, &u8);
1135 pThis->enmProtocol = (PS2M_PROTO)u8;
1136 SSMR3GetU8(pSSM, &u8);
1137 pThis->enmKnockState = (PS2M_KNOCK_STATE)u8;
1138
1139 /* Load the command and event queues. */
1140 rc = ps2kLoadQueue(pSSM, (GeneriQ *)&pThis->cmdQ);
1141 AssertRCReturn(rc, rc);
1142 rc = ps2kLoadQueue(pSSM, (GeneriQ *)&pThis->evtQ);
1143 AssertRCReturn(rc, rc);
1144
1145 /* Load the command delay timer, just in case. */
1146 rc = TMR3TimerLoad(pThis->CTX_SUFF(pDelayTimer), pSSM);
1147 AssertRCReturn(rc, rc);
1148
1149 /* Recalculate the throttling delay. */
1150 ps2mSetRate(pThis, pThis->u8SampleRate);
1151
1152 ps2mSetDriverState(pThis, !!(pThis->u8State & AUX_STATE_ENABLED));
1153
1154 return rc;
1155}
1156
1157void PS2MFixupState(PPS2M pThis, uint8_t u8State, uint8_t u8Rate, uint8_t u8Proto)
1158{
1159 LogFlowFunc(("Fixing up old PS2M state version\n"));
1160
1161 /* Load the basic auxiliary device state. */
1162 pThis->u8State = u8State;
1163 pThis->u8SampleRate = u8Rate ? u8Rate : 40; /* In case it wasn't saved right. */
1164 pThis->enmProtocol = (PS2M_PROTO)u8Proto;
1165
1166 /* Recalculate the throttling delay. */
1167 ps2mSetRate(pThis, pThis->u8SampleRate);
1168
1169 ps2mSetDriverState(pThis, !!(pThis->u8State & AUX_STATE_ENABLED));
1170}
1171
1172void PS2MReset(PPS2M pThis)
1173{
1174 LogFlowFunc(("Resetting PS2M\n"));
1175
1176 pThis->u8CurrCmd = 0;
1177
1178 /* Clear the queues. */
1179 ps2kClearQueue((GeneriQ *)&pThis->cmdQ);
1180 ps2mSetDefaults(pThis); /* Also clears event queue. */
1181
1182 /* Activate the PS/2 mouse by default. */
1183// if (pThis->Mouse.pDrv)
1184// pThis->Mouse.pDrv->pfnSetActive(pThis->Mouse.pDrv, true);
1185}
1186
1187void PS2MRelocate(PPS2M pThis, RTGCINTPTR offDelta, PPDMDEVINS pDevIns)
1188{
1189 RT_NOREF2(pDevIns, offDelta);
1190 LogFlowFunc(("Relocating PS2M\n"));
1191 pThis->pDelayTimerRC = TMTimerRCPtr(pThis->pDelayTimerR3);
1192 pThis->pThrottleTimerRC = TMTimerRCPtr(pThis->pThrottleTimerR3);
1193}
1194
1195int PS2MConstruct(PPS2M pThis, PPDMDEVINS pDevIns, void *pParent, int iInstance)
1196{
1197 RT_NOREF1(iInstance);
1198
1199 LogFlowFunc(("iInstance=%d\n", iInstance));
1200
1201#ifdef RT_STRICT
1202 ps2mTestAccumulation();
1203#endif
1204
1205 pThis->pParent = pParent;
1206
1207 /* Initialize the queues. */
1208 pThis->evtQ.cSize = AUX_EVT_QUEUE_SIZE;
1209 pThis->cmdQ.cSize = AUX_CMD_QUEUE_SIZE;
1210
1211 pThis->Mouse.IBase.pfnQueryInterface = ps2mQueryInterface;
1212 pThis->Mouse.IPort.pfnPutEvent = ps2mPutEvent;
1213 pThis->Mouse.IPort.pfnPutEventAbs = ps2mPutEventAbs;
1214 pThis->Mouse.IPort.pfnPutEventMultiTouch = ps2mPutEventMT;
1215
1216 /*
1217 * Initialize the critical section pointer(s).
1218 */
1219 pThis->pCritSectR3 = pDevIns->pCritSectRoR3;
1220
1221 /*
1222 * Create the input rate throttling timer. Does not use virtual time!
1223 */
1224 PTMTIMER pTimer;
1225 int rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_REAL, ps2mThrottleTimer, pThis,
1226 TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "PS2M Throttle Timer", &pTimer);
1227 if (RT_FAILURE(rc))
1228 return rc;
1229
1230 pThis->pThrottleTimerR3 = pTimer;
1231 pThis->pThrottleTimerR0 = TMTimerR0Ptr(pTimer);
1232 pThis->pThrottleTimerRC = TMTimerRCPtr(pTimer);
1233
1234 /*
1235 * Create the command delay timer.
1236 */
1237 rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ps2mDelayTimer, pThis,
1238 TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "PS2M Delay Timer", &pTimer);
1239 if (RT_FAILURE(rc))
1240 return rc;
1241
1242 pThis->pDelayTimerR3 = pTimer;
1243 pThis->pDelayTimerR0 = TMTimerR0Ptr(pTimer);
1244 pThis->pDelayTimerRC = TMTimerRCPtr(pTimer);
1245
1246 /*
1247 * Register debugger info callbacks.
1248 */
1249 PDMDevHlpDBGFInfoRegister(pDevIns, "ps2m", "Display PS/2 mouse state.", ps2mInfoState);
1250
1251 /// @todo Where should we do this?
1252 ps2mSetDriverState(pThis, true);
1253 pThis->u8State = 0;
1254 pThis->enmMode = AUX_MODE_STD;
1255
1256 return rc;
1257}
1258
1259#endif
1260
1261#if defined(RT_STRICT) && defined(IN_RING3)
1262/* -=-=-=-=-=- Test code -=-=-=-=-=- */
1263
1264/** Test the event accumulation mechanism which we use to delay events going
1265 * to the guest to one per 10ms (the default PS/2 mouse event rate). This
1266 * test depends on ps2mPutEventWorker() not touching the timer if
1267 * This.fThrottleActive is true. */
1268/** @todo if we add any more tests it might be worth using a table of test
1269 * operations and checks. */
1270static void ps2mTestAccumulation(void)
1271{
1272 PS2M This;
1273 unsigned i;
1274 int rc;
1275 uint8_t b;
1276
1277 RT_ZERO(This);
1278 This.evtQ.cSize = AUX_EVT_QUEUE_SIZE;
1279 This.u8State = AUX_STATE_ENABLED;
1280 This.fThrottleActive = true;
1281 /* Certain Windows touch pad drivers report a double tap as a press, then
1282 * a release-press-release all within a single 10ms interval. Simulate
1283 * this to check that it is handled right. */
1284 ps2mPutEventWorker(&This, 0, 0, 0, 0, 1);
1285 if (ps2mHaveEvents(&This))
1286 ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
1287 ps2mPutEventWorker(&This, 0, 0, 0, 0, 0);
1288 if (ps2mHaveEvents(&This))
1289 ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
1290 ps2mPutEventWorker(&This, 0, 0, 0, 0, 1);
1291 ps2mPutEventWorker(&This, 0, 0, 0, 0, 0);
1292 if (ps2mHaveEvents(&This))
1293 ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
1294 if (ps2mHaveEvents(&This))
1295 ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
1296 for (i = 0; i < 12; ++i)
1297 {
1298 const uint8_t abExpected[] = { 9, 0, 0, 8, 0, 0, 9, 0, 0, 8, 0, 0};
1299
1300 rc = PS2MByteFromAux(&This, &b);
1301 AssertRCSuccess(rc);
1302 Assert(b == abExpected[i]);
1303 }
1304 rc = PS2MByteFromAux(&This, &b);
1305 Assert(rc != VINF_SUCCESS);
1306 /* Button hold down during mouse drags was broken at some point during
1307 * testing fixes for the previous issue. Test that that works. */
1308 ps2mPutEventWorker(&This, 0, 0, 0, 0, 1);
1309 if (ps2mHaveEvents(&This))
1310 ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
1311 if (ps2mHaveEvents(&This))
1312 ps2mReportAccumulatedEvents(&This, (GeneriQ *)&This.evtQ, true);
1313 for (i = 0; i < 3; ++i)
1314 {
1315 const uint8_t abExpected[] = { 9, 0, 0 };
1316
1317 rc = PS2MByteFromAux(&This, &b);
1318 AssertRCSuccess(rc);
1319 Assert(b == abExpected[i]);
1320 }
1321 rc = PS2MByteFromAux(&This, &b);
1322 Assert(rc != VINF_SUCCESS);
1323}
1324#endif /* RT_STRICT && IN_RING3 */
1325
1326#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette