VirtualBox

source: vbox/trunk/src/VBox/Devices/Input/DevPS2M.cpp

Last change on this file was 99775, checked in by vboxsync, 12 months ago

*: Mark functions as static if not used outside of a given compilation unit. Enables the compiler to optimize inlining, reduces the symbol tables, exposes unused functions and in some rare cases exposes mismtaches between function declarations and definitions, but most importantly reduces the number of parfait reports for the extern-function-no-forward-declaration category. This should not result in any functional changes, bugref:3409

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 45.3 KB
Line 
1/* $Id: DevPS2M.cpp 99775 2023-05-12 12:21:58Z vboxsync $ */
2/** @file
3 * PS2M - PS/2 auxiliary device (mouse) emulation.
4 */
5
6/*
7 * Copyright (C) 2007-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/*
29 * References:
30 *
31 * The Undocumented PC (2nd Ed.), Frank van Gilluwe, Addison-Wesley, 1996.
32 * IBM TrackPoint System Version 4.0 Engineering Specification, 1999.
33 * ELAN Microelectronics eKM8025 USB & PS/2 Mouse Controller, 2006.
34 *
35 *
36 * Notes:
37 *
38 * - The auxiliary device commands are very similar to keyboard commands.
39 * Most keyboard commands which do not specifically deal with the keyboard
40 * (enable, disable, reset) have identical counterparts.
41 * - The code refers to 'auxiliary device' and 'mouse'; these terms are not
42 * quite interchangeable. 'Auxiliary device' is used when referring to the
43 * generic PS/2 auxiliary device interface and 'mouse' when referring to
44 * a mouse attached to the auxiliary port.
45 * - The basic modes of operation are reset, stream, and remote. Those are
46 * mutually exclusive. Stream and remote modes can additionally have wrap
47 * mode enabled.
48 * - The auxiliary device sends unsolicited data to the host only when it is
49 * both in stream mode and enabled. Otherwise it only responds to commands.
50 *
51 *
52 * There are three report packet formats supported by the emulated device. The
53 * standard three-byte PS/2 format (with middle button support), IntelliMouse
54 * four-byte format with added scroll wheel, and IntelliMouse Explorer four-byte
55 * format with reduced scroll wheel range but two additional buttons. Note that
56 * the first three bytes of the report are always the same.
57 *
58 * Upon reset, the mouse is always in the standard PS/2 mode. A special 'knock'
59 * sequence can be used to switch to ImPS/2 or ImEx mode. Three consecutive
60 * Set Sampling Rate (0F3h) commands with arguments 200, 100, 80 switch to ImPS/2
61 * mode. While in ImPS/2 or PS/2 mode, three consecutive Set Sampling Rate
62 * commands with arguments 200, 200, 80 switch to ImEx mode. The Read ID (0F2h)
63 * command will report the currently selected protocol.
64 *
65 * There is an extended ImEx mode with support for horizontal scrolling. It is
66 * entered from ImEx mode with a 200, 80, 40 sequence of Set Sampling Rate
67 * commands. It does not change the reported protocol (it remains 4, or ImEx)
68 * but changes the meaning of the 4th byte.
69 *
70 *
71 * Standard PS/2 pointing device three-byte report packet format:
72 *
73 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
74 * |Bit/byte| bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
75 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
76 * | Byte 1 | Y ovfl | X ovfl | Y sign | X sign | Sync | M btn | R btn | L btn |
77 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
78 * | Byte 2 | X movement delta (two's complement) |
79 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
80 * | Byte 3 | Y movement delta (two's complement) |
81 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
82 *
83 * - The sync bit is always set. It allows software to synchronize data packets
84 * as the X/Y position data typically does not have bit 4 set.
85 * - The overflow bits are set if motion exceeds accumulator range. We use the
86 * maximum range (effectively 9 bits) and do not set the overflow bits.
87 * - Movement in the up/right direction is defined as having positive sign.
88 *
89 *
90 * IntelliMouse PS/2 (ImPS/2) 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 | Z movement delta (two's complement) |
96 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
97 *
98 * - The valid range for Z delta values is only -8/+7, i.e. 4 bits.
99 *
100 * IntelliMouse Explorer (ImEx) fourth report packet byte:
101 *
102 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
103 * |Bit/byte| bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
104 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
105 * | Byte 4 | 0 | 0 | Btn 5 | Btn 4 | Z mov't delta (two's complement) |
106 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
107 *
108 * - The Z delta values are in practice only -1/+1; some mice (A4tech?) report
109 * horizontal scrolling as -2/+2.
110 *
111 * IntelliMouse Explorer (ImEx) fourth report packet byte when scrolling:
112 *
113 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
114 * |Bit/byte| bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
115 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
116 * | Byte 4 | V | H | Z or W movement delta (two's complement) |
117 * +--------+--------+--------+--------+--------+--------+--------+--------+--------+
118 *
119 * - Buttons 4 and 5 are reported as with the regular ImEx protocol, but not when
120 * scrolling. This is a departure from the usual logic because when the mouse
121 * sends scroll events, the state of buttons 4/5 is not reported and the last
122 * reported state should be assumed.
123 *
124 * - When the V bit (bit 7) is set, vertical scroll (Z axis) is being reported.
125 * When the H bit (bit 6) is set, horizontal scroll (W axis) is being reported.
126 * The H and V bits are never set at the same time (also see below). When
127 * the H and V bits are both clear, button 4/5 state is being reported.
128 *
129 * - The Z/W delta is extended to 6 bits. Z (vertical) values are not restricted
130 * to -1/+1, although W (horizontal) values are. Z values of at least -20/+20
131 * can be seen in practice.
132 *
133 * - Horizontal and vertical scroll is mutually exclusive. When the button is
134 * tilted, no vertical scrolling is reported, i.e. horizontal scrolling
135 * has priority over vertical.
136 *
137 * - Positive values indicate down/right direction, negative values up/left.
138 *
139 * - When the scroll button is tilted to engage horizontal scrolling, the mouse
140 * keeps sending events at a rate of 4 or 5 per second as long as the button
141 * is tilted.
142 *
143 * All report formats were verified with a real Microsoft IntelliMouse Explorer 4.0
144 * mouse attached through a PS/2 port.
145 *
146 * The button "accumulator" is necessary to avoid missing brief button presses.
147 * Without it, a very fast mouse button press + release might be lost if it
148 * happened between sending reports. The accumulator latches button presses to
149 * prevent that.
150 *
151 */
152
153
154/*********************************************************************************************************************************
155* Header Files *
156*********************************************************************************************************************************/
157#define LOG_GROUP LOG_GROUP_DEV_KBD
158#include <VBox/vmm/pdmdev.h>
159#include <VBox/err.h>
160#include <iprt/assert.h>
161#include <iprt/uuid.h>
162#include "VBoxDD.h"
163#define IN_PS2M
164#include "DevPS2.h"
165
166
167/*********************************************************************************************************************************
168* Defined Constants And Macros *
169*********************************************************************************************************************************/
170/** @name Auxiliary device commands sent by the system.
171 * @{ */
172#define ACMD_SET_SCALE_11 0xE6 /* Set 1:1 scaling. */
173#define ACMD_SET_SCALE_21 0xE7 /* Set 2:1 scaling. */
174#define ACMD_SET_RES 0xE8 /* Set resolution. */
175#define ACMD_REQ_STATUS 0xE9 /* Get device status. */
176#define ACMD_SET_STREAM 0xEA /* Set stream mode. */
177#define ACMD_READ_REMOTE 0xEB /* Read remote data. */
178#define ACMD_RESET_WRAP 0xEC /* Exit wrap mode. */
179#define ACMD_INVALID_1 0xED
180#define ACMD_SET_WRAP 0xEE /* Set wrap (echo) mode. */
181#define ACMD_INVALID_2 0xEF
182#define ACMD_SET_REMOTE 0xF0 /* Set remote mode. */
183#define ACMD_INVALID_3 0xF1
184#define ACMD_READ_ID 0xF2 /* Read device ID. */
185#define ACMD_SET_SAMP_RATE 0xF3 /* Set sampling rate. */
186#define ACMD_ENABLE 0xF4 /* Enable (streaming mode). */
187#define ACMD_DISABLE 0xF5 /* Disable (streaming mode). */
188#define ACMD_SET_DEFAULT 0xF6 /* Set defaults. */
189#define ACMD_INVALID_4 0xF7
190#define ACMD_INVALID_5 0xF8
191#define ACMD_INVALID_6 0xF9
192#define ACMD_INVALID_7 0xFA
193#define ACMD_INVALID_8 0xFB
194#define ACMD_INVALID_9 0xFC
195#define ACMD_INVALID_10 0xFD
196#define ACMD_RESEND 0xFE /* Resend response. */
197#define ACMD_RESET 0xFF /* Reset device. */
198/** @} */
199
200/** @name Auxiliary device responses sent to the system.
201 * @{ */
202#define ARSP_ID 0x00
203#define ARSP_BAT_OK 0xAA /* Self-test passed. */
204#define ARSP_ACK 0xFA /* Command acknowledged. */
205#define ARSP_ERROR 0xFC /* Bad command. */
206#define ARSP_RESEND 0xFE /* Requesting resend. */
207/** @} */
208
209
210/*********************************************************************************************************************************
211* Test code function declarations *
212*********************************************************************************************************************************/
213#if defined(RT_STRICT) && defined(IN_RING3)
214static void ps2mR3TestAccumulation(void);
215#endif
216
217
218#ifdef IN_RING3
219
220/* Report a change in status down (or is it up?) the driver chain. */
221static void ps2mR3SetDriverState(PPS2MR3 pThisCC, bool fEnabled)
222{
223 PPDMIMOUSECONNECTOR pDrv = pThisCC->Mouse.pDrv;
224 if (pDrv)
225 pDrv->pfnReportModes(pDrv, fEnabled, false, false, false);
226}
227
228/* Reset the pointing device. */
229static void ps2mR3Reset(PPS2M pThis, PPS2MR3 pThisCC)
230{
231 LogFlowFunc(("Reset"));
232
233 PS2Q_INSERT(&pThis->cmdQ, ARSP_BAT_OK);
234 PS2Q_INSERT(&pThis->cmdQ, 0);
235 pThis->enmMode = AUX_MODE_STD;
236 pThis->u8CurrCmd = 0;
237
238 /// @todo move to its proper home!
239 ps2mR3SetDriverState(pThisCC, true);
240}
241
242#endif /* IN_RING3 */
243
244static void ps2mSetRate(PPS2M pThis, uint8_t rate)
245{
246 Assert(rate);
247 pThis->uThrottleDelay = rate ? 1000 / rate : 0;
248 pThis->u8SampleRate = rate;
249 LogFlowFunc(("Sampling rate %u, throttle delay %u ms\n", pThis->u8SampleRate, pThis->uThrottleDelay));
250}
251
252static void ps2mSetDefaults(PPS2M pThis)
253{
254 LogFlowFunc(("Set mouse defaults\n"));
255 /* Standard protocol, reporting disabled, resolution 2, 1:1 scaling. */
256 pThis->enmProtocol = PS2M_PROTO_PS2STD;
257 pThis->u8State = 0;
258 pThis->u8Resolution = 2;
259
260 /* Sample rate 100 reports per second. */
261 ps2mSetRate(pThis, 100);
262
263 /* Event queue, eccumulators, and button status bits are cleared. */
264 PS2Q_CLEAR(&pThis->evtQ);
265 pThis->iAccumX = pThis->iAccumY = pThis->iAccumZ = pThis->iAccumW = pThis->fAccumB = 0;
266}
267
268/* Handle the sampling rate 'knock' sequence which selects protocol. */
269static void ps2mRateProtocolKnock(PPS2M pThis, uint8_t rate)
270{
271 PS2M_PROTO enmOldProtocol = pThis->enmProtocol;
272 LogFlowFunc(("rate=%u\n", rate));
273
274 switch (pThis->enmKnockState)
275 {
276 case PS2M_KNOCK_INITIAL:
277 if (rate == 200)
278 pThis->enmKnockState = PS2M_KNOCK_1ST;
279 break;
280 case PS2M_KNOCK_1ST:
281 if (rate == 100)
282 pThis->enmKnockState = PS2M_KNOCK_IMPS2_2ND;
283 else if (rate == 200)
284 pThis->enmKnockState = PS2M_KNOCK_IMEX_2ND;
285 else if (rate == 80)
286 pThis->enmKnockState = PS2M_KNOCK_IMEX_HORZ_2ND;
287 else
288 pThis->enmKnockState = PS2M_KNOCK_INITIAL;
289 break;
290 case PS2M_KNOCK_IMPS2_2ND:
291 if (rate == 80)
292 {
293 pThis->enmProtocol = PS2M_PROTO_IMPS2;
294 LogRelFlow(("PS2M: Switching mouse to ImPS/2 protocol.\n"));
295 }
296 pThis->enmKnockState = PS2M_KNOCK_INITIAL;
297 break;
298 case PS2M_KNOCK_IMEX_2ND:
299 if (rate == 80)
300 {
301 pThis->enmProtocol = PS2M_PROTO_IMEX;
302 LogRelFlow(("PS2M: Switching mouse to ImEx protocol.\n"));
303 }
304 pThis->enmKnockState = PS2M_KNOCK_INITIAL;
305 break;
306 case PS2M_KNOCK_IMEX_HORZ_2ND:
307 if (rate == 40)
308 {
309 pThis->enmProtocol = PS2M_PROTO_IMEX_HORZ;
310 LogRelFlow(("PS2M: Switching mouse ImEx with horizontal scrolling.\n"));
311 }
312 RT_FALL_THRU();
313 default:
314 pThis->enmKnockState = PS2M_KNOCK_INITIAL;
315 }
316
317 /* If the protocol changed, throw away any queued input because it now
318 * has the wrong format, which could severely confuse the guest.
319 */
320 if (enmOldProtocol != pThis->enmProtocol)
321 PS2Q_CLEAR(&pThis->evtQ);
322}
323
324/* Three-button event mask. */
325#define PS2M_STD_BTN_MASK (RT_BIT(0) | RT_BIT(1) | RT_BIT(2))
326/* ImEx button 4/5 event mask. */
327#define PS2M_IMEX_BTN_MASK (RT_BIT(3) | RT_BIT(4))
328
329/** Report accumulated movement and button presses, then clear the accumulators. */
330static void ps2mReportAccumulatedEvents(PPS2M pThis, PPS2QHDR pQHdr, size_t cQElements, uint8_t *pbQElements, bool fAccumBtns)
331{
332 uint32_t fBtnState = fAccumBtns ? pThis->fAccumB : pThis->fCurrB;
333 uint8_t val;
334 int dX, dY, dZ, dW;
335
336 LogFlowFunc(("cQElements=%zu, fAccumBtns=%RTbool\n", cQElements, fAccumBtns));
337
338 /* Clamp the accumulated delta values to the allowed range. */
339 dX = RT_MIN(RT_MAX(pThis->iAccumX, -255), 255);
340 dY = RT_MIN(RT_MAX(pThis->iAccumY, -255), 255);
341
342 /* Start with the sync bit and buttons 1-3. */
343 val = RT_BIT(3) | (fBtnState & PS2M_STD_BTN_MASK);
344 /* Set the X/Y sign bits. */
345 if (dX < 0)
346 val |= RT_BIT(4);
347 if (dY < 0)
348 val |= RT_BIT(5);
349
350 /* Send the standard 3-byte packet (always the same). */
351 LogFlowFunc(("Queuing standard 3-byte packet\n"));
352 PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, val);
353 PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, dX);
354 PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, dY);
355
356 /* Add fourth byte if an extended protocol is in use. */
357 if (pThis->enmProtocol > PS2M_PROTO_PS2STD)
358 {
359 /* Start out with 4-bit dZ range. */
360 dZ = RT_MIN(RT_MAX(pThis->iAccumZ, -8), 7);
361
362 if (pThis->enmProtocol == PS2M_PROTO_IMPS2)
363 {
364 /* NB: Only uses 4-bit dZ range, despite using a full byte. */
365 LogFlowFunc(("Queuing ImPS/2 last byte\n"));
366 PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, dZ);
367 pThis->iAccumZ -= dZ;
368 }
369 else if (pThis->enmProtocol == PS2M_PROTO_IMEX)
370 {
371 /* Z value uses 4 bits; buttons 4/5 in bits 4 and 5. */
372 val = (fBtnState & PS2M_IMEX_BTN_MASK) << 1;
373 val |= dZ & 0x0f;
374 pThis->iAccumZ -= dZ;
375 LogFlowFunc(("Queuing ImEx last byte\n"));
376 PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, val);
377 }
378 else
379 {
380 Assert((pThis->enmProtocol == PS2M_PROTO_IMEX_HORZ));
381 /* With ImEx + horizontal reporting, prioritize buttons 4/5. */
382 if (pThis->iAccumZ || pThis->iAccumW)
383 {
384 /* ImEx + horizontal reporting Horizontal scroll has
385 * precedence over vertical. Buttons cannot be reported
386 * this way.
387 */
388 if (pThis->iAccumW)
389 {
390 dW = RT_MIN(RT_MAX(pThis->iAccumW, -32), 31);
391 val = (dW & 0x3F) | 0x40;
392 pThis->iAccumW -= dW;
393 }
394 else
395 {
396 Assert(pThis->iAccumZ);
397 /* We can use 6-bit dZ range. Wow! */
398 dZ = RT_MIN(RT_MAX(pThis->iAccumZ, -32), 31);
399 val = (dZ & 0x3F) | 0x80;
400 pThis->iAccumZ -= dZ;
401 }
402 }
403 else
404 {
405 /* Just Buttons 4/5 in bits 4 and 5. No scrolling. */
406 val = (fBtnState & PS2M_IMEX_BTN_MASK) << 1;
407 }
408 LogFlowFunc(("Queuing ImEx+horz last byte\n"));
409 PS2CmnInsertQueue(pQHdr, cQElements, pbQElements, val);
410 }
411 }
412
413 /* Clear the movement accumulators, but not necessarily button state. */
414 pThis->iAccumX = pThis->iAccumY = 0;
415 /* Clear accumulated button state only when it's being used. */
416 if (fAccumBtns)
417 {
418 pThis->fReportedB = pThis->fCurrB | pThis->fAccumB;
419 pThis->fAccumB = 0;
420 }
421}
422
423
424/* Determine whether a reporting rate is one of the valid ones. */
425static bool ps2mIsRateSupported(uint8_t rate)
426{
427 static uint8_t aValidRates[] = { 10, 20, 40, 60, 80, 100, 200 };
428 size_t i;
429 bool fValid = false;
430
431 for (i = 0; i < RT_ELEMENTS(aValidRates); ++i)
432 if (aValidRates[i] == rate)
433 {
434 fValid = true;
435 break;
436 }
437
438 return fValid;
439}
440
441
442/**
443 * The keyboard controller disabled the auxiliary serial line.
444 *
445 * @param pThis The PS/2 auxiliary device shared instance data.
446 */
447void PS2MLineDisable(PPS2M pThis)
448{
449 LogFlowFunc(("Disabling mouse serial line\n"));
450
451 pThis->fLineDisabled = true;
452}
453
454/**
455 * The keyboard controller enabled the auxiliary serial line.
456 *
457 * @param pThis The PS/2 auxiliary device shared instance data.
458 */
459void PS2MLineEnable(PPS2M pThis)
460{
461 LogFlowFunc(("Enabling mouse serial line\n"));
462
463 pThis->fLineDisabled = false;
464
465 /* If there was anything in the input queue,
466 * consider it lost and throw it away.
467 */
468 PS2Q_CLEAR(&pThis->evtQ);
469}
470
471
472/**
473 * Receive and process a byte sent by the keyboard controller.
474 *
475 * @param pDevIns The device instance.
476 * @param pThis The PS/2 auxiliary device shared instance data.
477 * @param cmd The command (or data) byte.
478 */
479int PS2MByteToAux(PPDMDEVINS pDevIns, PPS2M pThis, uint8_t cmd)
480{
481 uint8_t u8Val;
482 bool fHandled = true;
483
484 LogFlowFunc(("cmd=0x%02X, active cmd=0x%02X\n", cmd, pThis->u8CurrCmd));
485
486 if (pThis->enmMode == AUX_MODE_RESET)
487 /* In reset mode, do not respond at all. */
488 return VINF_SUCCESS;
489
490 /* If there's anything left in the command response queue, trash it. */
491 PS2Q_CLEAR(&pThis->cmdQ);
492
493 if (pThis->enmMode == AUX_MODE_WRAP)
494 {
495 /* In wrap mode, bounce most data right back.*/
496 if (cmd == ACMD_RESET || cmd == ACMD_RESET_WRAP)
497 ; /* Handle as regular commands. */
498 else
499 {
500 PS2Q_INSERT(&pThis->cmdQ, cmd);
501 return VINF_SUCCESS;
502 }
503 }
504
505#ifndef IN_RING3
506 /* Reset, Enable, and Set Default commands must be run in R3. */
507 if (cmd == ACMD_RESET || cmd == ACMD_ENABLE || cmd == ACMD_SET_DEFAULT)
508 return VINF_IOM_R3_IOPORT_WRITE;
509#endif
510
511 switch (cmd)
512 {
513 case ACMD_SET_SCALE_11:
514 pThis->u8State &= ~AUX_STATE_SCALING;
515 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
516 pThis->u8CurrCmd = 0;
517 break;
518 case ACMD_SET_SCALE_21:
519 pThis->u8State |= AUX_STATE_SCALING;
520 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
521 pThis->u8CurrCmd = 0;
522 break;
523 case ACMD_REQ_STATUS:
524 /* Report current status, sample rate, and resolution. */
525 u8Val = (pThis->u8State & AUX_STATE_EXTERNAL) | (pThis->fCurrB & PS2M_STD_BTN_MASK);
526 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
527 PS2Q_INSERT(&pThis->cmdQ, u8Val);
528 PS2Q_INSERT(&pThis->cmdQ, pThis->u8Resolution);
529 PS2Q_INSERT(&pThis->cmdQ, pThis->u8SampleRate);
530 pThis->u8CurrCmd = 0;
531 break;
532 case ACMD_SET_STREAM:
533 pThis->u8State &= ~AUX_STATE_REMOTE;
534 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
535 pThis->u8CurrCmd = 0;
536 break;
537 case ACMD_READ_REMOTE:
538 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
539 ps2mReportAccumulatedEvents(pThis, &pThis->cmdQ.Hdr, RT_ELEMENTS(pThis->cmdQ.abQueue), pThis->cmdQ.abQueue, false);
540 pThis->u8CurrCmd = 0;
541 break;
542 case ACMD_RESET_WRAP:
543 pThis->enmMode = AUX_MODE_STD;
544 /* NB: Stream mode reporting remains disabled! */
545 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
546 pThis->u8CurrCmd = 0;
547 break;
548 case ACMD_SET_WRAP:
549 pThis->enmMode = AUX_MODE_WRAP;
550 pThis->u8State &= ~AUX_STATE_ENABLED;
551 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
552 pThis->u8CurrCmd = 0;
553 break;
554 case ACMD_SET_REMOTE:
555 pThis->u8State |= AUX_STATE_REMOTE;
556 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
557 pThis->u8CurrCmd = 0;
558 break;
559 case ACMD_READ_ID:
560 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
561 /* ImEx + horizontal is protocol 4, just like plain ImEx. */
562 u8Val = pThis->enmProtocol == PS2M_PROTO_IMEX_HORZ ? PS2M_PROTO_IMEX : pThis->enmProtocol;
563 PS2Q_INSERT(&pThis->cmdQ, u8Val);
564 pThis->u8CurrCmd = 0;
565 break;
566 case ACMD_ENABLE:
567 pThis->u8State |= AUX_STATE_ENABLED;
568#ifdef IN_RING3
569 ps2mR3SetDriverState(&PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3)->Aux, true);
570#else
571 AssertLogRelMsgFailed(("Invalid ACMD_ENABLE outside R3!\n"));
572#endif
573 PS2Q_CLEAR(&pThis->evtQ);
574 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
575 pThis->u8CurrCmd = 0;
576 break;
577 case ACMD_DISABLE:
578 pThis->u8State &= ~AUX_STATE_ENABLED;
579 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
580 pThis->u8CurrCmd = 0;
581 break;
582 case ACMD_SET_DEFAULT:
583 ps2mSetDefaults(pThis);
584 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
585 pThis->u8CurrCmd = 0;
586 break;
587 case ACMD_RESEND:
588 pThis->u8CurrCmd = 0;
589 break;
590 case ACMD_RESET:
591 ps2mSetDefaults(pThis);
592 /// @todo reset more?
593 pThis->u8CurrCmd = cmd;
594 pThis->enmMode = AUX_MODE_RESET;
595 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
596 if (pThis->fDelayReset)
597 /* Slightly delay reset completion; it might take hundreds of ms. */
598 PDMDevHlpTimerSetMillies(pDevIns, pThis->hDelayTimer, 1);
599 else
600#ifdef IN_RING3
601 ps2mR3Reset(pThis, &PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3)->Aux);
602#else
603 AssertLogRelMsgFailed(("Invalid ACMD_RESET outside R3!\n"));
604#endif
605 break;
606 /* The following commands need a parameter. */
607 case ACMD_SET_RES:
608 case ACMD_SET_SAMP_RATE:
609 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
610 pThis->u8CurrCmd = cmd;
611 break;
612 default:
613 /* Sending a command instead of a parameter starts the new command. */
614 switch (pThis->u8CurrCmd)
615 {
616 case ACMD_SET_RES:
617 if (cmd < 4) /* Valid resolutions are 0-3. */
618 {
619 pThis->u8Resolution = cmd;
620 pThis->u8State &= ~AUX_STATE_RES_ERR;
621 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
622 pThis->u8CurrCmd = 0;
623 }
624 else
625 {
626 /* Bad resolution. Reply with Resend or Error. */
627 if (pThis->u8State & AUX_STATE_RES_ERR)
628 {
629 pThis->u8State &= ~AUX_STATE_RES_ERR;
630 PS2Q_INSERT(&pThis->cmdQ, ARSP_ERROR);
631 pThis->u8CurrCmd = 0;
632 }
633 else
634 {
635 pThis->u8State |= AUX_STATE_RES_ERR;
636 PS2Q_INSERT(&pThis->cmdQ, ARSP_RESEND);
637 /* NB: Current command remains unchanged. */
638 }
639 }
640 break;
641 case ACMD_SET_SAMP_RATE:
642 if (ps2mIsRateSupported(cmd))
643 {
644 pThis->u8State &= ~AUX_STATE_RATE_ERR;
645 ps2mSetRate(pThis, cmd);
646 ps2mRateProtocolKnock(pThis, cmd);
647 PS2Q_INSERT(&pThis->cmdQ, ARSP_ACK);
648 pThis->u8CurrCmd = 0;
649 }
650 else
651 {
652 /* Bad rate. Reply with Resend or Error. */
653 if (pThis->u8State & AUX_STATE_RATE_ERR)
654 {
655 pThis->u8State &= ~AUX_STATE_RATE_ERR;
656 PS2Q_INSERT(&pThis->cmdQ, ARSP_ERROR);
657 pThis->u8CurrCmd = 0;
658 }
659 else
660 {
661 pThis->u8State |= AUX_STATE_RATE_ERR;
662 PS2Q_INSERT(&pThis->cmdQ, ARSP_RESEND);
663 /* NB: Current command remains unchanged. */
664 }
665 }
666 break;
667 default:
668 fHandled = false;
669 }
670 /* Fall through only to handle unrecognized commands. */
671 if (fHandled)
672 break;
673 RT_FALL_THRU();
674
675 case ACMD_INVALID_1:
676 case ACMD_INVALID_2:
677 case ACMD_INVALID_3:
678 case ACMD_INVALID_4:
679 case ACMD_INVALID_5:
680 case ACMD_INVALID_6:
681 case ACMD_INVALID_7:
682 case ACMD_INVALID_8:
683 case ACMD_INVALID_9:
684 case ACMD_INVALID_10:
685 Log(("Unsupported command 0x%02X!\n", cmd));
686 PS2Q_INSERT(&pThis->cmdQ, ARSP_RESEND);
687 pThis->u8CurrCmd = 0;
688 break;
689 }
690 LogFlowFunc(("Active cmd now 0x%02X; updating interrupts\n", pThis->u8CurrCmd));
691 return VINF_SUCCESS;
692}
693
694/**
695 * Send a byte (packet data or command response) to the keyboard controller.
696 *
697 * @returns VINF_SUCCESS or VINF_TRY_AGAIN.
698 * @param pThis The PS/2 auxiliary device shared instance data.
699 * @param pb Where to return the byte we've read.
700 * @remarks Caller must have entered the device critical section.
701 */
702int PS2MByteFromAux(PPS2M pThis, uint8_t *pb)
703{
704 int rc;
705
706 AssertPtr(pb);
707
708 /* Anything in the command queue has priority over data
709 * in the event queue. Additionally, packet data are
710 * blocked if a command is currently in progress, even if
711 * the command queue is empty.
712 */
713 /// @todo Probably should flush/not fill queue if stream mode reporting disabled?!
714 rc = PS2Q_REMOVE(&pThis->cmdQ, pb);
715 if (rc != VINF_SUCCESS && !pThis->u8CurrCmd && (pThis->u8State & AUX_STATE_ENABLED))
716 rc = PS2Q_REMOVE(&pThis->evtQ, pb);
717
718 LogFlowFunc(("mouse sends 0x%02x (%svalid data)\n", *pb, rc == VINF_SUCCESS ? "" : "not "));
719
720 return rc;
721}
722
723#ifdef IN_RING3
724
725/** Is there any state change to send as events to the guest? */
726static uint32_t ps2mR3HaveEvents(PPS2M pThis)
727{
728/** @todo r=bird: Why is this returning uint32_t when you're calculating a
729 * boolean value here? Also, it's a predicate function... */
730 return pThis->iAccumX || pThis->iAccumY || pThis->iAccumZ || pThis->iAccumW
731 || ((pThis->fCurrB | pThis->fAccumB) != pThis->fReportedB);
732}
733
734/**
735 * @callback_method_impl{FNTMTIMERDEV,
736 * Event rate throttling timer to emulate the auxiliary device sampling rate.}
737 */
738static DECLCALLBACK(void) ps2mR3ThrottleTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
739{
740 PPS2M pThis = (PS2M *)pvUser;
741 uint32_t uHaveEvents;
742 Assert(hTimer == pThis->hThrottleTimer);
743 Assert(PDMDevHlpCritSectIsOwner(pDevIns, pDevIns->pCritSectRoR3));
744
745 /* If more movement is accumulated, report it and restart the timer. */
746 uHaveEvents = ps2mR3HaveEvents(pThis);
747 LogFlowFunc(("Have%s events\n", uHaveEvents ? "" : " no"));
748
749 if (uHaveEvents)
750 {
751 /* Report accumulated data, poke the KBC, and start the timer. */
752 ps2mReportAccumulatedEvents(pThis, &pThis->evtQ.Hdr, RT_ELEMENTS(pThis->evtQ.abQueue), pThis->evtQ.abQueue, true);
753 KBCUpdateInterrupts(pDevIns);
754 PDMDevHlpTimerSetMillies(pDevIns, hTimer, pThis->uThrottleDelay);
755 }
756 else
757 pThis->fThrottleActive = false;
758}
759
760/**
761 * @callback_method_impl{FNTMTIMERDEV}
762 *
763 * The auxiliary device reset is specified to take up to about 500 milliseconds.
764 * We need to delay sending the result to the host for at least a tiny little
765 * while.
766 */
767static DECLCALLBACK(void) ps2mR3DelayTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
768{
769 PPS2M pThis = &PDMDEVINS_2_DATA(pDevIns, PKBDSTATE)->Aux;
770 PPS2MR3 pThisCC = &PDMDEVINS_2_DATA_CC(pDevIns, PKBDSTATER3)->Aux;
771 RT_NOREF(pvUser, hTimer);
772
773 LogFlowFunc(("Delay timer: cmd %02X\n", pThis->u8CurrCmd));
774
775 Assert(pThis->u8CurrCmd == ACMD_RESET);
776 ps2mR3Reset(pThis, pThisCC);
777
778 /// @todo Might want a PS2MCompleteCommand() to push last response, clear command, and kick the KBC...
779 /* Give the KBC a kick. */
780 KBCUpdateInterrupts(pDevIns);
781}
782
783
784/**
785 * Debug device info handler. Prints basic auxiliary device state.
786 *
787 * @param pDevIns Device instance which registered the info.
788 * @param pHlp Callback functions for doing output.
789 * @param pszArgs Argument string. Optional and specific to the handler.
790 */
791static DECLCALLBACK(void) ps2mR3InfoState(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
792{
793 static const char * const s_apcszModes[] = { "normal", "reset", "wrap" };
794 static const char * const s_apcszProtocols[] = { "PS/2", NULL, NULL, "ImPS/2", "ImEx", "ImEx+horizontal" };
795 PKBDSTATE pParent = PDMDEVINS_2_DATA(pDevIns, PKBDSTATE);
796 PPS2M pThis = &pParent->Aux;
797 NOREF(pszArgs);
798
799 Assert(pThis->enmMode < RT_ELEMENTS(s_apcszModes));
800 pHlp->pfnPrintf(pHlp, "PS/2 mouse state: %s, %s mode, reporting %s, serial line %s\n",
801 s_apcszModes[pThis->enmMode],
802 pThis->u8State & AUX_STATE_REMOTE ? "remote" : "stream",
803 pThis->u8State & AUX_STATE_ENABLED ? "enabled" : "disabled",
804 pThis->fLineDisabled ? "disabled" : "enabled");
805 Assert(pThis->enmProtocol < RT_ELEMENTS(s_apcszProtocols));
806 pHlp->pfnPrintf(pHlp, "Protocol: %s, scaling %u:1\n",
807 s_apcszProtocols[pThis->enmProtocol],
808 pThis->u8State & AUX_STATE_SCALING ? 2 : 1);
809 pHlp->pfnPrintf(pHlp, "Active command %02X\n", pThis->u8CurrCmd);
810 pHlp->pfnPrintf(pHlp, "Sampling rate %u reports/sec, resolution %u counts/mm\n",
811 pThis->u8SampleRate, 1 << pThis->u8Resolution);
812 pHlp->pfnPrintf(pHlp, "Command queue: %d items (%d max)\n",
813 PS2Q_COUNT(&pThis->cmdQ), PS2Q_SIZE(&pThis->cmdQ));
814 pHlp->pfnPrintf(pHlp, "Event queue : %d items (%d max)\n",
815 PS2Q_COUNT(&pThis->evtQ), PS2Q_SIZE(&pThis->evtQ));
816}
817
818
819/* -=-=-=-=-=- Mouse: IMousePort -=-=-=-=-=- */
820
821/**
822 * Mouse event handler.
823 *
824 * @returns VBox status code.
825 * @param pDevIns The device instance.
826 * @param pThis The PS/2 auxiliary device shared instance data.
827 * @param dx X direction movement delta.
828 * @param dy Y direction movement delta.
829 * @param dz Z (vertical scroll) movement delta.
830 * @param dw W (horizontal scroll) movement delta.
831 * @param fButtons Depressed button mask.
832 */
833static int ps2mR3PutEventWorker(PPDMDEVINS pDevIns, PPS2M pThis, int32_t dx, int32_t dy, int32_t dz, int32_t dw, uint32_t fButtons)
834{
835 LogFlowFunc(("dx=%d, dy=%d, dz=%d, dw=%d, fButtons=%X\n", dx, dy, dz, dw, fButtons));
836
837 /* Update internal accumulators and button state. Ignore any buttons beyond 5. */
838 pThis->iAccumX += dx;
839 pThis->iAccumY += dy;
840 pThis->iAccumZ += dz;
841 pThis->iAccumW += dw;
842 pThis->fCurrB = fButtons & (PS2M_STD_BTN_MASK | PS2M_IMEX_BTN_MASK);
843 pThis->fAccumB |= pThis->fCurrB;
844
845 /* Ditch accumulated data that can't be reported by the current protocol.
846 * This avoids sending phantom empty reports when un-reportable events
847 * are received.
848 */
849 if (pThis->enmProtocol < PS2M_PROTO_IMEX_HORZ)
850 pThis->iAccumW = 0; /* No horizontal scroll. */
851
852 if (pThis->enmProtocol < PS2M_PROTO_IMEX)
853 {
854 pThis->fAccumB &= PS2M_STD_BTN_MASK; /* Only buttons 1-3. */
855 pThis->fCurrB &= PS2M_STD_BTN_MASK;
856 }
857
858 if (pThis->enmProtocol < PS2M_PROTO_IMPS2)
859 pThis->iAccumZ = 0; /* No vertical scroll. */
860
861 /* Report the event (if any) and start the throttle timer unless it's already running. */
862 if (!pThis->fThrottleActive && ps2mR3HaveEvents(pThis))
863 {
864 ps2mReportAccumulatedEvents(pThis, &pThis->evtQ.Hdr, RT_ELEMENTS(pThis->evtQ.abQueue), pThis->evtQ.abQueue, true);
865 KBCUpdateInterrupts(pDevIns);
866 pThis->fThrottleActive = true;
867 PDMDevHlpTimerSetMillies(pDevIns, pThis->hThrottleTimer, pThis->uThrottleDelay);
868 }
869
870 return VINF_SUCCESS;
871}
872
873
874/* -=-=-=-=-=- Mouse: IMousePort -=-=-=-=-=- */
875
876/**
877 * @interface_method_impl{PDMIMOUSEPORT,pfnPutEvent}
878 */
879static DECLCALLBACK(int) ps2mR3MousePort_PutEvent(PPDMIMOUSEPORT pInterface, int32_t dx, int32_t dy,
880 int32_t dz, int32_t dw, uint32_t fButtons)
881{
882 PPS2MR3 pThisCC = RT_FROM_MEMBER(pInterface, PS2MR3, Mouse.IPort);
883 PPDMDEVINS pDevIns = pThisCC->pDevIns;
884 PPS2M pThis = &PDMDEVINS_2_DATA(pDevIns, PKBDSTATE)->Aux;
885 int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_SEM_BUSY);
886 PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
887
888 LogRelFlowFunc(("dX=%d dY=%d dZ=%d dW=%d buttons=%02X\n", dx, dy, dz, dw, fButtons));
889 /* NB: The PS/2 Y axis direction is inverted relative to ours. */
890 ps2mR3PutEventWorker(pDevIns, pThis, dx, -dy, dz, dw, fButtons);
891
892 PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
893 return VINF_SUCCESS;
894}
895
896/**
897 * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventAbs}
898 */
899static DECLCALLBACK(int) ps2mR3MousePort_PutEventAbs(PPDMIMOUSEPORT pInterface, uint32_t x, uint32_t y,
900 int32_t dz, int32_t dw, uint32_t fButtons)
901{
902 AssertFailedReturn(VERR_NOT_SUPPORTED);
903 NOREF(pInterface); NOREF(x); NOREF(y); NOREF(dz); NOREF(dw); NOREF(fButtons);
904}
905
906/**
907 * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventTouchScreen}
908 */
909static DECLCALLBACK(int) ps2mR3MousePort_PutEventMTAbs(PPDMIMOUSEPORT pInterface, uint8_t cContacts,
910 const uint64_t *pau64Contacts, uint32_t u32ScanTime)
911{
912 AssertFailedReturn(VERR_NOT_SUPPORTED);
913 NOREF(pInterface); NOREF(cContacts); NOREF(pau64Contacts); NOREF(u32ScanTime);
914}
915
916/**
917 * @interface_method_impl{PDMIMOUSEPORT,pfnPutEventTouchPad}
918 */
919static DECLCALLBACK(int) ps2mR3MousePort_PutEventMTRel(PPDMIMOUSEPORT pInterface, uint8_t cContacts,
920 const uint64_t *pau64Contacts, uint32_t u32ScanTime)
921{
922 AssertFailedReturn(VERR_NOT_SUPPORTED);
923 NOREF(pInterface); NOREF(cContacts); NOREF(pau64Contacts); NOREF(u32ScanTime);
924}
925
926
927/* -=-=-=-=-=- Mouse: IBase -=-=-=-=-=- */
928
929/**
930 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
931 */
932static DECLCALLBACK(void *) ps2mR3QueryInterface(PPDMIBASE pInterface, const char *pszIID)
933{
934 PPS2MR3 pThisCC = RT_FROM_MEMBER(pInterface, PS2MR3, Mouse.IBase);
935 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->Mouse.IBase);
936 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUSEPORT, &pThisCC->Mouse.IPort);
937 return NULL;
938}
939
940
941/* -=-=-=-=-=- Device management -=-=-=-=-=- */
942
943/**
944 * Attach command.
945 *
946 * This is called to let the device attach to a driver for a
947 * specified LUN.
948 *
949 * This is like plugging in the mouse after turning on the
950 * system.
951 *
952 * @returns VBox status code.
953 * @param pDevIns The device instance.
954 * @param pThisCC The PS/2 auxiliary device instance data for ring-3.
955 * @param iLUN The logical unit which is being detached.
956 * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
957 */
958int PS2MR3Attach(PPDMDEVINS pDevIns, PPS2MR3 pThisCC, unsigned iLUN, uint32_t fFlags)
959{
960 int rc;
961
962 /* The LUN must be 1, i.e. mouse. */
963 Assert(iLUN == 1);
964 AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
965 ("PS/2 mouse does not support hotplugging\n"),
966 VERR_INVALID_PARAMETER);
967
968 LogFlowFunc(("iLUN=%d\n", iLUN));
969
970 rc = PDMDevHlpDriverAttach(pDevIns, iLUN, &pThisCC->Mouse.IBase, &pThisCC->Mouse.pDrvBase, "Mouse Port");
971 if (RT_SUCCESS(rc))
972 {
973 pThisCC->Mouse.pDrv = PDMIBASE_QUERY_INTERFACE(pThisCC->Mouse.pDrvBase, PDMIMOUSECONNECTOR);
974 if (!pThisCC->Mouse.pDrv)
975 {
976 AssertLogRelMsgFailed(("LUN #1 doesn't have a mouse interface! rc=%Rrc\n", rc));
977 rc = VERR_PDM_MISSING_INTERFACE;
978 }
979 }
980 else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
981 {
982 Log(("%s/%d: warning: no driver attached to LUN #1!\n", pDevIns->pReg->szName, pDevIns->iInstance));
983 rc = VINF_SUCCESS;
984 }
985 else
986 AssertLogRelMsgFailed(("Failed to attach LUN #1! rc=%Rrc\n", rc));
987
988 return rc;
989}
990
991void PS2MR3SaveState(PPDMDEVINS pDevIns, PPS2M pThis, PSSMHANDLE pSSM)
992{
993 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
994 LogFlowFunc(("Saving PS2M state\n"));
995
996 /* Save the core auxiliary device state. */
997 pHlp->pfnSSMPutU8(pSSM, pThis->u8State);
998 pHlp->pfnSSMPutU8(pSSM, pThis->u8SampleRate);
999 pHlp->pfnSSMPutU8(pSSM, pThis->u8Resolution);
1000 pHlp->pfnSSMPutU8(pSSM, pThis->u8CurrCmd);
1001 pHlp->pfnSSMPutU8(pSSM, pThis->enmMode);
1002 pHlp->pfnSSMPutU8(pSSM, pThis->enmProtocol);
1003 pHlp->pfnSSMPutU8(pSSM, pThis->enmKnockState);
1004
1005 /* Save the command and event queues. */
1006 PS2Q_SAVE(pHlp, pSSM, &pThis->cmdQ);
1007 PS2Q_SAVE(pHlp, pSSM, &pThis->evtQ);
1008
1009 /* Save the command delay timer. Note that the rate throttling
1010 * timer is *not* saved.
1011 */
1012 PDMDevHlpTimerSave(pDevIns, pThis->hDelayTimer, pSSM);
1013}
1014
1015int PS2MR3LoadState(PPDMDEVINS pDevIns, PPS2M pThis, PPS2MR3 pThisCC, PSSMHANDLE pSSM, uint32_t uVersion)
1016{
1017 PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
1018 uint8_t u8;
1019 int rc;
1020
1021 NOREF(uVersion);
1022 LogFlowFunc(("Loading PS2M state version %u\n", uVersion));
1023
1024 /* Load the basic auxiliary device state. */
1025 pHlp->pfnSSMGetU8(pSSM, &pThis->u8State);
1026 pHlp->pfnSSMGetU8(pSSM, &pThis->u8SampleRate);
1027 pHlp->pfnSSMGetU8(pSSM, &pThis->u8Resolution);
1028 pHlp->pfnSSMGetU8(pSSM, &pThis->u8CurrCmd);
1029 pHlp->pfnSSMGetU8(pSSM, &u8);
1030 pThis->enmMode = (PS2M_MODE)u8;
1031 pHlp->pfnSSMGetU8(pSSM, &u8);
1032 pThis->enmProtocol = (PS2M_PROTO)u8;
1033 pHlp->pfnSSMGetU8(pSSM, &u8);
1034 pThis->enmKnockState = (PS2M_KNOCK_STATE)u8;
1035
1036 /* Load the command and event queues. */
1037 rc = PS2Q_LOAD(pHlp, pSSM, &pThis->cmdQ);
1038 AssertRCReturn(rc, rc);
1039 rc = PS2Q_LOAD(pHlp, pSSM, &pThis->evtQ);
1040 AssertRCReturn(rc, rc);
1041
1042 /* Load the command delay timer, just in case. */
1043 rc = PDMDevHlpTimerLoad(pDevIns, pThis->hDelayTimer, pSSM);
1044 AssertRCReturn(rc, rc);
1045
1046 /* Recalculate the throttling delay. */
1047 ps2mSetRate(pThis, pThis->u8SampleRate);
1048
1049 ps2mR3SetDriverState(pThisCC, !!(pThis->u8State & AUX_STATE_ENABLED));
1050
1051 return VINF_SUCCESS;
1052}
1053
1054void PS2MR3FixupState(PPS2M pThis, PPS2MR3 pThisCC, uint8_t u8State, uint8_t u8Rate, uint8_t u8Proto)
1055{
1056 LogFlowFunc(("Fixing up old PS2M state version\n"));
1057
1058 /* Load the basic auxiliary device state. */
1059 pThis->u8State = u8State;
1060 pThis->u8SampleRate = u8Rate ? u8Rate : 40; /* In case it wasn't saved right. */
1061 pThis->enmProtocol = (PS2M_PROTO)u8Proto;
1062
1063 /* Recalculate the throttling delay. */
1064 ps2mSetRate(pThis, pThis->u8SampleRate);
1065
1066 ps2mR3SetDriverState(pThisCC, !!(pThis->u8State & AUX_STATE_ENABLED));
1067}
1068
1069void PS2MR3Reset(PPS2M pThis)
1070{
1071 LogFlowFunc(("Resetting PS2M\n"));
1072
1073 pThis->u8CurrCmd = 0;
1074
1075 /* Clear the queues. */
1076 PS2Q_CLEAR(&pThis->cmdQ);
1077 ps2mSetDefaults(pThis); /* Also clears event queue. */
1078}
1079
1080int PS2MR3Construct(PPDMDEVINS pDevIns, PPS2M pThis, PPS2MR3 pThisCC)
1081{
1082 LogFlowFunc(("\n"));
1083
1084 pThis->cmdQ.Hdr.pszDescR3 = "Aux Cmd";
1085 pThis->evtQ.Hdr.pszDescR3 = "Aux Evt";
1086
1087#ifdef RT_STRICT
1088 ps2mR3TestAccumulation();
1089#endif
1090
1091 /*
1092 * Initialize the state.
1093 */
1094 pThisCC->pDevIns = pDevIns;
1095 pThisCC->Mouse.IBase.pfnQueryInterface = ps2mR3QueryInterface;
1096 pThisCC->Mouse.IPort.pfnPutEvent = ps2mR3MousePort_PutEvent;
1097 pThisCC->Mouse.IPort.pfnPutEventAbs = ps2mR3MousePort_PutEventAbs;
1098 pThisCC->Mouse.IPort.pfnPutEventTouchScreen = ps2mR3MousePort_PutEventMTAbs;
1099 pThisCC->Mouse.IPort.pfnPutEventTouchPad = ps2mR3MousePort_PutEventMTRel;
1100
1101 /*
1102 * Create the input rate throttling timer. Does not use virtual time!
1103 */
1104 int rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_REAL, ps2mR3ThrottleTimer, pThis,
1105 TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_NO_RING0,
1106 "PS2M Throttle", &pThis->hThrottleTimer);
1107 AssertRCReturn(rc, rc);
1108
1109 /*
1110 * Create the command delay timer.
1111 */
1112 rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ps2mR3DelayTimer, pThis,
1113 TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_RING0, "PS2M Delay", &pThis->hDelayTimer);
1114 AssertRCReturn(rc, rc);
1115
1116 /*
1117 * Register debugger info callbacks.
1118 */
1119 PDMDevHlpDBGFInfoRegister(pDevIns, "ps2m", "Display PS/2 mouse state.", ps2mR3InfoState);
1120
1121 /// @todo Where should we do this?
1122 ps2mR3SetDriverState(pThisCC, true);
1123 pThis->u8State = 0;
1124 pThis->enmMode = AUX_MODE_STD;
1125
1126 return rc;
1127}
1128
1129#endif
1130
1131#if defined(RT_STRICT) && defined(IN_RING3)
1132/* -=-=-=-=-=- Test code -=-=-=-=-=- */
1133
1134/** Test the event accumulation mechanism which we use to delay events going
1135 * to the guest to one per 10ms (the default PS/2 mouse event rate). This
1136 * test depends on ps2mR3PutEventWorker() not touching the timer if
1137 * This.fThrottleActive is true. */
1138/** @todo if we add any more tests it might be worth using a table of test
1139 * operations and checks. */
1140static void ps2mR3TestAccumulation(void)
1141{
1142 PS2M This;
1143 unsigned i;
1144 int rc;
1145 uint8_t b;
1146
1147 RT_ZERO(This);
1148 This.u8State = AUX_STATE_ENABLED;
1149 This.fThrottleActive = true;
1150 This.cmdQ.Hdr.pszDescR3 = "Test Aux Cmd";
1151 This.evtQ.Hdr.pszDescR3 = "Test Aux Evt";
1152 /* Certain Windows touch pad drivers report a double tap as a press, then
1153 * a release-press-release all within a single 10ms interval. Simulate
1154 * this to check that it is handled right. */
1155 ps2mR3PutEventWorker(NULL, &This, 0, 0, 0, 0, 1);
1156 if (ps2mR3HaveEvents(&This))
1157 ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, true);
1158 ps2mR3PutEventWorker(NULL, &This, 0, 0, 0, 0, 0);
1159 if (ps2mR3HaveEvents(&This))
1160 ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, true);
1161 ps2mR3PutEventWorker(NULL, &This, 0, 0, 0, 0, 1);
1162 ps2mR3PutEventWorker(NULL, &This, 0, 0, 0, 0, 0);
1163 if (ps2mR3HaveEvents(&This))
1164 ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, true);
1165 if (ps2mR3HaveEvents(&This))
1166 ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, true);
1167 for (i = 0; i < 12; ++i)
1168 {
1169 const uint8_t abExpected[] = { 9, 0, 0, 8, 0, 0, 9, 0, 0, 8, 0, 0};
1170
1171 rc = PS2MByteFromAux(&This, &b);
1172 AssertRCSuccess(rc);
1173 Assert(b == abExpected[i]);
1174 }
1175 rc = PS2MByteFromAux(&This, &b);
1176 Assert(rc != VINF_SUCCESS);
1177 /* Button hold down during mouse drags was broken at some point during
1178 * testing fixes for the previous issue. Test that that works. */
1179 ps2mR3PutEventWorker(NULL, &This, 0, 0, 0, 0, 1);
1180 if (ps2mR3HaveEvents(&This))
1181 ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, true);
1182 if (ps2mR3HaveEvents(&This))
1183 ps2mReportAccumulatedEvents(&This, &This.evtQ.Hdr, RT_ELEMENTS(This.evtQ.abQueue), This.evtQ.abQueue, true);
1184 for (i = 0; i < 3; ++i)
1185 {
1186 const uint8_t abExpected[] = { 9, 0, 0 };
1187
1188 rc = PS2MByteFromAux(&This, &b);
1189 AssertRCSuccess(rc);
1190 Assert(b == abExpected[i]);
1191 }
1192 rc = PS2MByteFromAux(&This, &b);
1193 Assert(rc != VINF_SUCCESS);
1194}
1195#endif /* RT_STRICT && IN_RING3 */
1196
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use