PS2M.cpp revision 1e9e76e4273dcc2e3d560a0f3605c46f0013eb7b
/** @file
* PS2M - PS/2 auxiliary device (mouse) emulation.
*/
/*
* Copyright (C) 2007-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*
* References:
*
* The Undocumented PC (2nd Ed.), Frank van Gilluwe, Addison-Wesley, 1996.
* IBM TrackPoint System Version 4.0 Engineering Specification, 1999.
* ELAN Microelectronics eKM8025 USB & PS/2 Mouse Controller, 2006.
*
*
* Notes:
*
* - The auxiliary device commands are very similar to keyboard commands.
* Most keyboard commands which do not specifically deal with the keyboard
* (enable, disable, reset) have identical counterparts.
* - The code refers to 'auxiliary device' and 'mouse'; these terms are not
* quite interchangeable. 'Auxiliary device' is used when referring to the
* generic PS/2 auxiliary device interface and 'mouse' when referring to
* a mouse attached to the auxiliary port.
* - The basic modes of operation are reset, stream, and remote. Those are
* mutually exclusive. Stream and remote modes can additionally have wrap
* mode enabled.
* - The auxiliary device sends unsolicited data to the host only when it is
* both in stream mode and enabled. Otherwise it only responds to commands.
*
*
* There are three report packet formats supported by the emulated device. The
* standard three-byte PS/2 format (with middle button support), IntelliMouse
* four-byte format with added scroll wheel, and IntelliMouse Explorer four-byte
* format with reduced scroll wheel range but two additional buttons. Note that
* the first three bytes of the report are always the same.
*
* Upon reset, the mouse is always in the standard PS/2 mode. A special 'knock'
* sequence can be used to switch to ImPS/2 or ImEx mode. Three consecutive
* Set Sampling Rate (0F3h) commands with arguments 200, 100, 80 switch to ImPS/2
* mode. While in ImPS/2 mode, three consecutive Set Sampling Rate commands with
* arguments 200, 200, 80 switch to ImEx mode. The Read ID (0F2h) command will
* report the currently selected protocol.
*
*
* Standard PS/2 pointing device three-byte report packet format:
*
* +--------+--------+--------+--------+--------+--------+--------+--------+--------+
* +--------+--------+--------+--------+--------+--------+--------+--------+--------+
* | Byte 1 | Y ovfl | X ovfl | Y sign | X sign | Sync | M btn | R btn | L btn |
* +--------+--------+--------+--------+--------+--------+--------+--------+--------+
* | Byte 2 | X movement delta (two's complement) |
* +--------+--------+--------+--------+--------+--------+--------+--------+--------+
* | Byte 3 | Y movement delta (two's complement) |
* +--------+--------+--------+--------+--------+--------+--------+--------+--------+
*
* - The sync bit is always set. It allows software to synchronize data packets
* as the X/Y position data typically does not have bit 4 set.
* - The overflow bits are set if motion exceeds accumulator range. We use the
* maximum range (effectively 9 bits) and do not set the overflow bits.
*
*
* IntelliMouse PS/2 (ImPS/2) fourth report packet byte:
*
* +--------+--------+--------+--------+--------+--------+--------+--------+--------+
* +--------+--------+--------+--------+--------+--------+--------+--------+--------+
* | Byte 4 | Z movement delta (two's complement) |
* +--------+--------+--------+--------+--------+--------+--------+--------+--------+
*
* - The valid range for Z delta values is only -8/+7, i.e. 4 bits.
*
* IntelliMouse Explorer (ImEx) fourth report packet byte:
*
* +--------+--------+--------+--------+--------+--------+--------+--------+--------+
* +--------+--------+--------+--------+--------+--------+--------+--------+--------+
* | Byte 4 | 0 | 0 | Btn 5 | Btn 4 | Z mov't delta (two's complement) |
* +--------+--------+--------+--------+--------+--------+--------+--------+--------+
*
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DEV_KBD
#include "VBoxDD.h"
#define IN_PS2M
#include "PS2Dev.h"
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** @name Auxiliary device commands sent by the system.
* @{ */
#define ACMD_INVALID_1 0xED
#define ACMD_INVALID_2 0xEF
#define ACMD_INVALID_3 0xF1
#define ACMD_INVALID_4 0xF7
#define ACMD_INVALID_5 0xF8
#define ACMD_INVALID_6 0xF9
#define ACMD_INVALID_7 0xFA
#define ACMD_INVALID_8 0xFB
#define ACMD_INVALID_9 0xFC
#define ACMD_INVALID_10 0xFD
/** @} */
/** @name Auxiliary device responses sent to the system.
* @{ */
#define ARSP_ID 0x00
/** @} */
/** Define a simple PS/2 input device queue. */
typedef struct { \
} name
/* Internal mouse queue sizes. The input queue is relatively large,
* but the command queue only needs to handle a few bytes.
*/
#define AUX_EVT_QUEUE_SIZE 256
#define AUX_CMD_QUEUE_SIZE 8
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
#ifndef VBOX_DEVICE_STRUCT_TESTCASE //@todo: hack
#endif
/* Auxiliary device special modes of operation. */
typedef enum {
AUX_MODE_STD, /* Standard operation. */
AUX_MODE_RESET, /* Currently in reset. */
AUX_MODE_WRAP /* Wrap mode (echoing input). */
} PS2M_MODE;
/* Auxiliary device operational state. */
typedef enum {
} PS2M_STATE;
/* Protocols supported by the PS/2 mouse. */
typedef enum {
PS2M_PROTO_PS2STD = 0, /* Standard PS/2 mouse protocol. */
} PS2M_PROTO;
/* Protocol selection 'knock' states. */
typedef enum {
/**
* The PS/2 auxiliary device instance data.
*/
typedef struct PS2M
{
/** Pointer to parent device (keyboard controller). */
/** Operational state. */
/** Configured sampling rate. */
/** Configured resolution. */
/** Currently processed command (if any). */
/** Set if the throttle delay is active. */
bool fThrottleActive;
/** Operational mode. */
/** Currently used protocol. */
/** Currently used protocol. */
/** Buffer holding mouse events to be sent to the host. */
/** Command response queue (priority). */
/** Accumulated horizontal movement. */
/** Accumulated vertical movement. */
/** Accumulated Z axis movement. */
/** Accumulated button presses. */
/** Throttling delay in milliseconds. */
unsigned uThrottleDelay;
#if HC_ARCH_BITS == 32
#endif
/** The device critical section protecting everything - R3 Ptr */
/** Command delay timer - R3 Ptr. */
/** Interrupt throttling timer - R3 Ptr. */
/** Command delay timer - RC Ptr. */
/** Interrupt throttling timer - RC Ptr. */
/** Command delay timer - R0 Ptr. */
/** Interrupt throttling timer - R0 Ptr. */
/**
* Mouse port - LUN#1.
*
* @implements PDMIBASE
* @implements PDMIMOUSEPORT
*/
struct
{
/** The base interface for the mouse port. */
/** The keyboard port base interface. */
/** The base interface of the attached mouse driver. */
/** The keyboard interface of the attached mouse driver. */
} Mouse;
#ifndef VBOX_DEVICE_STRUCT_TESTCASE
/* Key type flags. */
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
/**
* Clear a queue.
*
* @param pQ Pointer to the queue.
*/
{
}
/**
* Add a byte to a queue.
*
* @param pQ Pointer to the queue.
* @param val The byte to store.
*/
{
/* Check if queue is full. */
{
return;
}
/* Insert data and update circular buffer write position. */
}
#ifdef IN_RING3
/**
* Save a queue state.
*
* @param pSSM SSM handle to write the state to.
* @param pQ Pointer to the queue.
*/
{
int i;
* positions aren't saved as they will be rebuilt on load.
*/
/* Save queue data - only the bytes actually used (typically zero). */
}
/**
* Load a queue state.
*
* @param pSSM SSM handle to read the state from.
* @param pQ Pointer to the queue.
*
*/
{
int rc;
/* On load, always put the read pointer at zero. */
{
}
/* Recalculate queue positions and load data in one go. */
return rc;
}
/* Report a change in status down (or is it up?) the driver chain. */
{
if (pDrv)
}
#endif /* IN_RING3 */
/**
* Retrieve a byte from a queue.
*
* @param pQ Pointer to the queue.
* @param pVal Pointer to storage for the byte.
*
* @return int VINF_TRY_AGAIN if queue is empty,
* VINF_SUCCESS if a byte was read.
*/
{
int rc = VINF_TRY_AGAIN;
{
rc = VINF_SUCCESS;
} else
return rc;
}
{
LogFlowFunc(("Sampling rate %u, throttle delay %u ms\n", pThis->u8SampleRate, pThis->uThrottleDelay));
}
{
LogFlowFunc(("Set mouse defaults\n"));
/* Standard protocol, reporting disabled, resolution 2, 1:1 scaling. */
/* Sample rate 100 reports per second. */
/* Accumulators and button status bits are cleared. */
//@todo accumulators/current state
}
/* Handle the sampling rate 'knock' sequence which selects protocol. */
{
{
switch (pThis->enmKnockState)
{
case PS2M_KNOCK_INITIAL:
if (rate == 200)
break;
case PS2M_KNOCK_IMPS2_1ST:
if (rate == 100)
else
break;
case PS2M_KNOCK_IMPS2_2ND:
if (rate == 80)
{
LogRelFlow(("PS2M: Switching mouse to ImPS/2 protocol.\n"));
}
default:
}
}
{
switch (pThis->enmKnockState)
{
case PS2M_KNOCK_INITIAL:
if (rate == 200)
break;
case PS2M_KNOCK_IMEX_1ST:
if (rate == 200)
else
break;
case PS2M_KNOCK_IMEX_2ND:
if (rate == 80)
{
LogRelFlow(("PS2M: Switching mouse to ImEx protocol.\n"));
}
default:
}
}
}
/**
* Receive and process a byte sent by the keyboard controller.
*
* @param pThis The PS/2 auxiliary device instance data.
* @param cmd The command (or data) byte.
*/
{
bool fHandled = true;
//LogRel(("aux: cmd=0x%02X, active cmd=0x%02X\n", cmd, pThis->u8CurrCmd));
{
/* In reset mode, do not respond at all. */
return VINF_SUCCESS;
}
{
/* In wrap mode, bounce most data right back.*/
; /* Handle as regular commands. */
else
{
return VINF_SUCCESS;
}
}
switch (cmd)
{
case ACMD_SET_SCALE_11:
break;
case ACMD_SET_SCALE_21:
break;
case ACMD_REQ_STATUS:
/* Report current status, sample rate, and resolution. */
//@todo: buttons
break;
case ACMD_SET_STREAM:
break;
case ACMD_RESET_WRAP:
/* NB: Stream mode reporting remains disabled! */
break;
case ACMD_SET_WRAP:
break;
case ACMD_SET_REMOTE:
break;
case ACMD_READ_ID:
break;
case ACMD_ENABLE:
//@todo: R3 only!
#ifdef IN_RING3
ps2mSetDriverState(pThis, true);
#endif
break;
case ACMD_DFLT_DISABLE:
break;
case ACMD_SET_DEFAULT:
break;
case ACMD_RESEND:
break;
case ACMD_RESET:
///@todo reset more?
/* Slightly delay reset completion; it might take hundreds of ms. */
break;
/* The following commands need a parameter. */
case ACMD_SET_RES:
case ACMD_SET_SAMP_RATE:
break;
default:
/* Sending a command instead of a parameter starts the new command. */
{
case ACMD_SET_RES:
//@todo reject unsupported resolutions
break;
case ACMD_SET_SAMP_RATE:
//@todo reject unsupported rates
break;
default:
fHandled = false;
}
/* Fall through only to handle unrecognized commands. */
if (fHandled)
break;
case ACMD_INVALID_1:
case ACMD_INVALID_2:
case ACMD_INVALID_3:
case ACMD_INVALID_4:
case ACMD_INVALID_5:
case ACMD_INVALID_6:
case ACMD_INVALID_7:
case ACMD_INVALID_8:
case ACMD_INVALID_9:
case ACMD_INVALID_10:
break;
}
// KBCUpdateInterrupts(pThis->pParent);
return VINF_SUCCESS;
}
/**
* Send a byte (keystroke or command response) to the keyboard controller.
*
* @returns VINF_SUCCESS or VINF_TRY_AGAIN.
* @param pThis The PS/2 auxiliary device instance data.
* @param pb Where to return the byte we've read.
* @remarks Caller must have entered the device critical section.
*/
{
int rc;
/* Anything in the command queue has priority over data
* in the event queue. Additionally, keystrokes are //@todo: true?
* blocked if a command is currently in progress, even if
* the command queue is empty.
*/
//if (rc == VINF_SUCCESS) LogRel(("aux: sends 0x%02X\n", *pb));
return rc;
}
#ifdef IN_RING3
/* Three-button event mask. */
/* Report accumulated movement and button presses, then clear the accumulators. */
{
/* Clamp the accumulated delta values to the allowed range. */
/* Start with the sync bit and buttons 1-3. */
/* Set the X/Y sign bits. */
if (dX < 0)
if (dY < 0)
/* Send the standard 3-byte packet (always the same). */
/* Add fourth byte if extended protocol is in use. */
{
else
{
/* Z value uses 4 bits; buttons 4/5 in bits 4 and 5. */
}
}
/* Clear the accumulators. */
/* Poke the KBC to update its state. */
}
/* Event rate throttling timer to emulate the auxiliary device sampling rate.
*/
{
/* Grab the lock to avoid races with PutEvent(). */
#if 0
/* If the input queue is not empty, restart the timer. */
#else
/* If more movement is accumulated, report it and restart the timer. */
if (uHaveEvents)
#endif
{
}
else
pThis->fThrottleActive = false;
}
/* The auxiliary device is specified to take up to about 500 milliseconds. We need
* to delay sending the result to the host for at least a tiny little while.
*/
{
///@todo Might want a PS2MCompleteCommand() to push last response, clear command, and kick the KBC...
/* Give the KBC a kick. */
//@todo: move to its proper home!
ps2mSetDriverState(pThis, true);
}
/**
* Debug device info handler. Prints basic auxiliary device state.
*
* @param pDevIns Device instance which registered the info.
* @param pHlp Callback functions for doing output.
* @param pszArgs Argument string. Optional and specific to the handler.
*/
static DECLCALLBACK(void) ps2mInfoState(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
{
}
/* -=-=-=-=-=- Mouse: IBase -=-=-=-=-=- */
/**
* @interface_method_impl{PDMIBASE,pfnQueryInterface}
*/
{
return NULL;
}
/* -=-=-=-=-=- Mouse: IMousePort -=-=-=-=-=- */
/**
* Mouse event handler.
*
* @returns VBox status code.
* @param pThis The PS/2 auxiliary device instance data.
* @param dx X direction movement delta.
* @param dy Y direction movement delta.
* @param dz Z (vertical scroll) movement delta.
* @param dw W (horizontal scroll) movement delta.
* @param fButtons Depressed button mask.
*/
{
int rc = VINF_SUCCESS;
/* Update internal accumulators and button state. */
#if 1
/* Report the event and the throttle timer unless it's already running. */
if (!pThis->fThrottleActive)
{
pThis->fThrottleActive = true;
}
#else
/* Clamp the delta values to the allowed range. */
/* Start with the sync bit. */
/* Add buttons 1-3. */
/* Set the X/Y sign bits. */
if (dx < 0)
if (dy < 0)
{
}
#endif
return rc;
}
/* -=-=-=-=-=- Mouse: IMousePort -=-=-=-=-=- */
/**
* @interface_method_impl{PDMIMOUSEPORT, pfnPutEvent}
*/
{
/* NB: The PS/2 Y axis direction is inverted relative to ours. */
return VINF_SUCCESS;
}
/**
* @interface_method_impl{PDMIMOUSEPORT, pfnPutEventAbs}
*/
{
}
/**
* @interface_method_impl{PDMIMOUSEPORT, pfnPutEventMultiTouch}
*/
{
}
/**
* Attach command.
*
* This is called to let the device attach to a driver for a
* specified LUN.
*
* This is like plugging in the mouse after turning on the
* system.
*
* @returns VBox status code.
* @param pThis The PS/2 auxiliary device instance data.
* @param pDevIns The device instance.
* @param iLUN The logical unit which is being detached.
* @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
*/
{
int rc;
/* The LUN must be 1, i.e. mouse. */
("PS/2 mouse does not support hotplugging\n"),
rc = PDMDevHlpDriverAttach(pDevIns, iLUN, &pThis->Mouse.IBase, &pThis->Mouse.pDrvBase, "Mouse Port");
if (RT_SUCCESS(rc))
{
{
}
}
else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
{
Log(("%s/%d: warning: no driver attached to LUN #1!\n", pDevIns->pReg->szName, pDevIns->iInstance));
rc = VINF_SUCCESS;
}
else
return rc;
}
{
LogFlowFunc(("Saving PS2M state\n"));
/* Save the core auxiliary device state. */
/* Save the command and event queues. */
/* Save the command delay timer. Note that the rate throttling
* timer is *not* saved.
*/
}
{
int rc;
/* Load the basic auxiliary device state. */
/* Load the command and event queues. */
/* Load the command delay timer, just in case. */
/* Recalculate the throttling delay. */
return rc;
}
{
LogFlowFunc(("Resetting PS2M\n"));
/* Clear the queues. */
/* Activate the PS/2 mouse by default. */
// if (pThis->Mouse.pDrv)
// pThis->Mouse.pDrv->pfnSetActive(pThis->Mouse.pDrv, true);
}
{
LogFlowFunc(("Relocating PS2M\n"));
}
{
int rc;
/* Initialize the queues. */
/*
* Initialize the critical section pointer(s).
*/
/*
* Create the input rate throttling timer. Does not use virtual time!
*/
if (RT_FAILURE(rc))
return rc;
/*
* Create the command delay timer.
*/
if (RT_FAILURE(rc))
return rc;
/*
* Register debugger info callbacks.
*/
//@todo: Where should we do this?
ps2mSetDriverState(pThis, true);
return rc;
}
#endif
#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */