VirtualBox

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

Last change on this file since 81521 was 80040, checked in by vboxsync, 5 years ago

scm.

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

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