2019-09-03 11:58:42 +02:00
/** @file
PS2 Mouse Communication Interface .
Copyright ( c ) 2006 - 2016 , Intel Corporation . All rights reserved . < BR >
SPDX - License - Identifier : BSD - 2 - Clause - Patent
* */
# include "Ps2Mouse.h"
# include "CommPs2.h"
UINT8 SampleRateTbl [ MaxSampleRate ] = { 0xa , 0x14 , 0x28 , 0x3c , 0x50 , 0x64 , 0xc8 } ;
UINT8 ResolutionTbl [ MaxResolution ] = { 0 , 1 , 2 , 3 } ;
/**
Issue self test command via IsaIo interface .
@ return EFI_SUCCESS Success to do keyboard self testing .
@ return others Fail to do keyboard self testing .
* */
EFI_STATUS
KbcSelfTest (
VOID
)
{
EFI_STATUS Status ;
UINT8 Data ;
//
// Keyboard controller self test
//
Status = Out8042Command ( SELF_TEST ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
//
// Read return code
//
Status = In8042Data ( & Data ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
if ( Data ! = 0x55 ) {
return EFI_DEVICE_ERROR ;
}
//
// Set system flag
//
Status = Out8042Command ( READ_CMD_BYTE ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
Status = In8042Data ( & Data ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
Status = Out8042Command ( WRITE_CMD_BYTE ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
Data | = CMD_SYS_FLAG ;
Status = Out8042Data ( Data ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
return EFI_SUCCESS ;
}
/**
Issue command to enable keyboard AUX functionality .
@ return Status of command issuing .
* */
EFI_STATUS
KbcEnableAux (
VOID
)
{
//
// Send 8042 enable mouse command
//
return Out8042Command ( ENABLE_AUX ) ;
}
/**
Issue command to disable keyboard AUX functionality .
@ param IsaIo Pointer to instance of EFI_ISA_IO_PROTOCOL
@ return Status of command issuing .
* */
EFI_STATUS
KbcDisableAux (
VOID
)
{
//
// Send 8042 disable mouse command
//
return Out8042Command ( DISABLE_AUX ) ;
}
/**
Issue command to enable keyboard .
@ param IsaIo Pointer to instance of EFI_ISA_IO_PROTOCOL
@ return Status of command issuing .
* */
EFI_STATUS
KbcEnableKb (
VOID
)
{
//
// Send 8042 enable keyboard command
//
return Out8042Command ( ENABLE_KB ) ;
}
/**
Issue command to disable keyboard .
@ return Status of command issuing .
* */
EFI_STATUS
KbcDisableKb (
VOID
)
{
//
// Send 8042 disable keyboard command
//
return Out8042Command ( DISABLE_KB ) ;
}
/**
Issue command to check keyboard status .
@ param KeyboardEnable return whether keyboard is enable .
@ return Status of command issuing .
* */
EFI_STATUS
CheckKbStatus (
OUT BOOLEAN * KeyboardEnable
)
{
EFI_STATUS Status ;
UINT8 Data ;
//
// Send command to read KBC command byte
//
Status = Out8042Command ( READ_CMD_BYTE ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
Status = In8042Data ( & Data ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
//
// Check keyboard enable or not
//
if ( ( Data & CMD_KB_STS ) = = CMD_KB_DIS ) {
* KeyboardEnable = FALSE ;
} else {
* KeyboardEnable = TRUE ;
}
return EFI_SUCCESS ;
}
/**
Issue command to reset keyboard .
@ return Status of command issuing .
* */
EFI_STATUS
PS2MouseReset (
VOID
)
{
EFI_STATUS Status ;
UINT8 Data ;
Status = Out8042AuxCommand ( RESET_CMD , FALSE ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
Status = In8042AuxData ( & Data ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
//
// Check BAT Complete Code
//
if ( Data ! = PS2MOUSE_BAT1 ) {
return EFI_DEVICE_ERROR ;
}
Status = In8042AuxData ( & Data ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
//
// Check BAT Complete Code
//
if ( Data ! = PS2MOUSE_BAT2 ) {
return EFI_DEVICE_ERROR ;
}
return EFI_SUCCESS ;
}
/**
Issue command to set mouse ' s sample rate
@ param SampleRate value of sample rate
@ return Status of command issuing .
* */
EFI_STATUS
PS2MouseSetSampleRate (
IN MOUSE_SR SampleRate
)
{
EFI_STATUS Status ;
//
// Send auxiliary command to set mouse sample rate
//
Status = Out8042AuxCommand ( SETSR_CMD , FALSE ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
Status = Out8042AuxData ( SampleRateTbl [ SampleRate ] ) ;
return Status ;
}
/**
Issue command to set mouse ' s resolution .
@ param Resolution value of resolution
@ return Status of command issuing .
* */
EFI_STATUS
PS2MouseSetResolution (
IN MOUSE_RE Resolution
)
{
EFI_STATUS Status ;
//
// Send auxiliary command to set mouse resolution
//
Status = Out8042AuxCommand ( SETRE_CMD , FALSE ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
Status = Out8042AuxData ( ResolutionTbl [ Resolution ] ) ;
return Status ;
}
/**
Issue command to set mouse ' s scaling .
@ param Scaling value of scaling
@ return Status of command issuing .
* */
EFI_STATUS
PS2MouseSetScaling (
IN MOUSE_SF Scaling
)
{
//
// Send auxiliary command to set mouse scaling data
//
return Out8042AuxCommand ( Scaling = = Scaling1 ? SETSF1_CMD : SETSF2_CMD , FALSE ) ;
}
/**
Issue command to enable Ps2 mouse .
@ return Status of command issuing .
* */
EFI_STATUS
PS2MouseEnable (
VOID
)
{
//
// Send auxiliary command to enable mouse
//
return Out8042AuxCommand ( ENABLE_CMD , FALSE ) ;
}
/**
Get mouse packet . Only care first 3 bytes
@ param MouseDev Pointer of PS2 Mouse Private Data Structure
@ retval EFI_NOT_READY Mouse Device not ready to input data packet , or some error happened during getting the packet
@ retval EFI_SUCCESS The data packet is gotten successfully .
* */
EFI_STATUS
PS2MouseGetPacket (
PS2_MOUSE_DEV * MouseDev
)
{
EFI_STATUS Status ;
BOOLEAN KeyboardEnable ;
UINT8 Packet [ PS2_PACKET_LENGTH ] ;
UINT8 Data ;
UINTN Count ;
UINTN State ;
INT16 RelativeMovementX ;
INT16 RelativeMovementY ;
BOOLEAN LButton ;
BOOLEAN RButton ;
KeyboardEnable = FALSE ;
State = PS2_READ_BYTE_ONE ;
//
// State machine to get mouse packet
//
while ( 1 ) {
switch ( State ) {
case PS2_READ_BYTE_ONE :
//
// Read mouse first byte data, if failed, immediately return
//
KbcDisableAux ( ) ;
Count = 1 ;
Status = PS2MouseRead ( & Data , & Count , State ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
KbcEnableAux ( ) ;
return EFI_NOT_READY ;
}
if ( Count ! = 1 ) {
KbcEnableAux ( ) ;
return EFI_NOT_READY ;
}
if ( IS_PS2_SYNC_BYTE ( Data ) ) {
Packet [ 0 ] = Data ;
State = PS2_READ_DATA_BYTE ;
CheckKbStatus ( & KeyboardEnable ) ;
KbcDisableKb ( ) ;
KbcEnableAux ( ) ;
}
break ;
case PS2_READ_DATA_BYTE :
Count = 2 ;
Status = PS2MouseRead ( ( Packet + 1 ) , & Count , State ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
if ( KeyboardEnable ) {
KbcEnableKb ( ) ;
}
return EFI_NOT_READY ;
}
if ( Count ! = 2 ) {
if ( KeyboardEnable ) {
KbcEnableKb ( ) ;
}
return EFI_NOT_READY ;
}
State = PS2_PROCESS_PACKET ;
break ;
case PS2_PROCESS_PACKET :
if ( KeyboardEnable ) {
KbcEnableKb ( ) ;
}
//
// Decode the packet
//
RelativeMovementX = Packet [ 1 ] ;
RelativeMovementY = Packet [ 2 ] ;
//
// Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0
// Byte 0 | Y overflow | X overflow | Y sign bit | X sign bit | Always 1 | Middle Btn | Right Btn | Left Btn
// Byte 1 | 8 bit X Movement
// Byte 2 | 8 bit Y Movement
//
// X sign bit + 8 bit X Movement : 9-bit signed twos complement integer that presents the relative displacement of the device in the X direction since the last data transmission.
// Y sign bit + 8 bit Y Movement : Same as X sign bit + 8 bit X Movement.
//
//
// First, Clear X and Y high 8 bits
//
RelativeMovementX = ( INT16 ) ( RelativeMovementX & 0xFF ) ;
RelativeMovementY = ( INT16 ) ( RelativeMovementY & 0xFF ) ;
//
// Second, if the 9-bit signed twos complement integer is negative, set the high 8 bit 0xff
//
if ( ( Packet [ 0 ] & 0x10 ) ! = 0 ) {
RelativeMovementX = ( INT16 ) ( RelativeMovementX | 0xFF00 ) ;
}
if ( ( Packet [ 0 ] & 0x20 ) ! = 0 ) {
RelativeMovementY = ( INT16 ) ( RelativeMovementY | 0xFF00 ) ;
}
RButton = ( UINT8 ) ( Packet [ 0 ] & 0x2 ) ;
LButton = ( UINT8 ) ( Packet [ 0 ] & 0x1 ) ;
//
// Update mouse state
//
MouseDev - > State . RelativeMovementX + = RelativeMovementX ;
MouseDev - > State . RelativeMovementY - = RelativeMovementY ;
MouseDev - > State . RightButton = ( UINT8 ) ( RButton ? TRUE : FALSE ) ;
MouseDev - > State . LeftButton = ( UINT8 ) ( LButton ? TRUE : FALSE ) ;
MouseDev - > StateChanged = TRUE ;
return EFI_SUCCESS ;
}
}
}
/**
Read data via IsaIo protocol with given number .
@ param Buffer Buffer receive data of mouse
@ param BufSize The size of buffer
@ param State Check input or read data
@ return status of reading mouse data .
* */
EFI_STATUS
PS2MouseRead (
OUT UINT8 * Buffer ,
IN OUT UINTN * BufSize ,
IN UINTN State
)
{
EFI_STATUS Status ;
UINTN BytesRead ;
Status = EFI_SUCCESS ;
if ( State = = PS2_READ_BYTE_ONE ) {
//
// Check input for mouse
//
Status = CheckForInput ( ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
}
for ( BytesRead = 0 ; BytesRead < * BufSize ; BytesRead + + ) {
Status = WaitOutputFull ( TIMEOUT ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
break ;
}
Buffer [ BytesRead ] = IoRead8 ( KBC_DATA_PORT ) ;
}
//
// Verify the correct number of bytes read
//
if ( BytesRead = = 0 | | BytesRead ! = * BufSize ) {
Status = EFI_NOT_FOUND ;
}
* BufSize = BytesRead ;
return Status ;
}
//
// 8042 I/O function
//
/**
I / O work flow of outing 8042 command .
@ param Command I / O command .
@ retval EFI_SUCCESS Success to execute I / O work flow
@ retval EFI_TIMEOUT Keyboard controller time out .
* */
EFI_STATUS
Out8042Command (
IN UINT8 Command
)
{
EFI_STATUS Status ;
//
// Wait keyboard controller input buffer empty
//
Status = WaitInputEmpty ( TIMEOUT ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
//
// Send command
//
IoWrite8 ( KBC_CMD_STS_PORT , Command ) ;
Status = WaitInputEmpty ( TIMEOUT ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
return EFI_SUCCESS ;
}
/**
I / O work flow of outing 8042 data .
@ param Data Data value
@ retval EFI_SUCCESS Success to execute I / O work flow
@ retval EFI_TIMEOUT Keyboard controller time out .
* */
EFI_STATUS
Out8042Data (
IN UINT8 Data
)
{
EFI_STATUS Status ;
//
// Wait keyboard controller input buffer empty
//
Status = WaitInputEmpty ( TIMEOUT ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
IoWrite8 ( KBC_DATA_PORT , Data ) ;
return WaitInputEmpty ( TIMEOUT ) ;
}
/**
I / O work flow of in 8042 data .
@ param Data Data value
@ retval EFI_SUCCESS Success to execute I / O work flow
@ retval EFI_TIMEOUT Keyboard controller time out .
* */
EFI_STATUS
In8042Data (
IN OUT UINT8 * Data
)
{
UINTN Delay ;
Delay = TIMEOUT / 50 ;
do {
//
// Check keyboard controller status bit 0(output buffer status)
//
if ( ( IoRead8 ( KBC_CMD_STS_PORT ) & KBC_OUTB ) = = KBC_OUTB ) {
break ;
}
gBS - > Stall ( 50 ) ;
Delay - - ;
} while ( Delay ! = 0 ) ;
if ( Delay = = 0 ) {
return EFI_TIMEOUT ;
}
* Data = IoRead8 ( KBC_DATA_PORT ) ;
return EFI_SUCCESS ;
}
/**
I / O work flow of outing 8042 Aux command .
@ param Command Aux I / O command
@ param Resend Whether need resend the Aux command .
@ retval EFI_SUCCESS Success to execute I / O work flow
@ retval EFI_TIMEOUT Keyboard controller time out .
* */
EFI_STATUS
Out8042AuxCommand (
IN UINT8 Command ,
IN BOOLEAN Resend
)
{
EFI_STATUS Status ;
UINT8 Data ;
//
// Wait keyboard controller input buffer empty
//
Status = WaitInputEmpty ( TIMEOUT ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
//
// Send write to auxiliary device command
//
IoWrite8 ( KBC_CMD_STS_PORT , WRITE_AUX_DEV ) ;
Status = WaitInputEmpty ( TIMEOUT ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
//
// Send auxiliary device command
//
IoWrite8 ( KBC_DATA_PORT , Command ) ;
//
// Read return code
//
Status = In8042AuxData ( & Data ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
if ( Data = = PS2_ACK ) {
//
// Receive mouse acknowledge, command send success
//
return EFI_SUCCESS ;
} else if ( Resend ) {
//
// Resend fail
//
return EFI_DEVICE_ERROR ;
} else if ( Data = = PS2_RESEND ) {
//
// Resend command
//
Status = Out8042AuxCommand ( Command , TRUE ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
} else {
//
// Invalid return code
//
return EFI_DEVICE_ERROR ;
}
return EFI_SUCCESS ;
}
/**
I / O work flow of outing 8042 Aux data .
@ param Data Buffer holding return value
@ retval EFI_SUCCESS Success to execute I / O work flow .
@ retval EFI_TIMEOUT Keyboard controller time out .
* */
EFI_STATUS
Out8042AuxData (
IN UINT8 Data
)
{
EFI_STATUS Status ;
//
// Wait keyboard controller input buffer empty
//
Status = WaitInputEmpty ( TIMEOUT ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
//
// Send write to auxiliary device command
//
IoWrite8 ( KBC_CMD_STS_PORT , WRITE_AUX_DEV ) ;
Status = WaitInputEmpty ( TIMEOUT ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
IoWrite8 ( KBC_DATA_PORT , Data ) ;
Status = WaitInputEmpty ( TIMEOUT ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
return EFI_SUCCESS ;
}
/**
I / O work flow of in 8042 Aux data .
@ param Data Buffer holding return value .
@ retval EFI_SUCCESS Success to execute I / O work flow
@ retval EFI_TIMEOUT Keyboard controller time out .
* */
EFI_STATUS
In8042AuxData (
IN OUT UINT8 * Data
)
{
EFI_STATUS Status ;
//
// wait for output data
//
Status = WaitOutputFull ( BAT_TIMEOUT ) ;
2020-04-23 11:08:10 +02:00
if ( EFI_ERROR ( Status ) ) {
2019-09-03 11:58:42 +02:00
return Status ;
}
* Data = IoRead8 ( KBC_DATA_PORT ) ;
return EFI_SUCCESS ;
}
/**
Check keyboard controller status , if it is output buffer full and for auxiliary device .
@ retval EFI_SUCCESS Keyboard controller is ready
@ retval EFI_NOT_READY Keyboard controller is not ready
* */
EFI_STATUS
CheckForInput (
VOID
)
{
UINT8 Data ;
Data = IoRead8 ( KBC_CMD_STS_PORT ) ;
//
// Check keyboard controller status, if it is output buffer full and for auxiliary device
//
if ( ( Data & ( KBC_OUTB | KBC_AUXB ) ) ! = ( KBC_OUTB | KBC_AUXB ) ) {
return EFI_NOT_READY ;
}
return EFI_SUCCESS ;
}
/**
I / O work flow to wait input buffer empty in given time .
@ param Timeout Wating time .
@ retval EFI_TIMEOUT if input is still not empty in given time .
@ retval EFI_SUCCESS input is empty .
* */
EFI_STATUS
WaitInputEmpty (
IN UINTN Timeout
)
{
UINTN Delay ;
UINT8 Data ;
Delay = Timeout / 50 ;
do {
Data = IoRead8 ( KBC_CMD_STS_PORT ) ;
//
// Check keyboard controller status bit 1(input buffer status)
//
if ( ( Data & KBC_INPB ) = = 0 ) {
break ;
}
gBS - > Stall ( 50 ) ;
Delay - - ;
} while ( Delay ! = 0 ) ;
if ( Delay = = 0 ) {
return EFI_TIMEOUT ;
}
return EFI_SUCCESS ;
}
/**
I / O work flow to wait output buffer full in given time .
@ param Timeout given time
@ retval EFI_TIMEOUT output is not full in given time
@ retval EFI_SUCCESS output is full in given time .
* */
EFI_STATUS
WaitOutputFull (
IN UINTN Timeout
)
{
UINTN Delay ;
UINT8 Data ;
Delay = Timeout / 50 ;
do {
Data = IoRead8 ( KBC_CMD_STS_PORT ) ;
//
// Check keyboard controller status bit 0(output buffer status)
// & bit5(output buffer for auxiliary device)
//
if ( ( Data & ( KBC_OUTB | KBC_AUXB ) ) = = ( KBC_OUTB | KBC_AUXB ) ) {
break ;
}
gBS - > Stall ( 50 ) ;
Delay - - ;
} while ( Delay ! = 0 ) ;
if ( Delay = = 0 ) {
return EFI_TIMEOUT ;
}
return EFI_SUCCESS ;
}