mirror of
https://github.com/reactos/reactos.git
synced 2024-09-18 00:33:04 +00:00
9919d4fa10
svn path=/branches/condrv_restructure/; revision=65567
423 lines
11 KiB
C
423 lines
11 KiB
C
/*
|
|
* COPYRIGHT: GPL - See COPYING in the top level directory
|
|
* PROJECT: ReactOS Virtual DOS Machine
|
|
* FILE: ps2.c
|
|
* PURPOSE: PS/2 controller emulation
|
|
* PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
|
|
* Hermes Belusca-Maito (hermes.belusca@sfr.fr)
|
|
*/
|
|
|
|
/* INCLUDES *******************************************************************/
|
|
|
|
#define NDEBUG
|
|
|
|
#include "emulator.h"
|
|
#include "io.h"
|
|
#include "ps2.h"
|
|
#include "pic.h"
|
|
|
|
/* PRIVATE VARIABLES **********************************************************/
|
|
|
|
#define BUFFER_SIZE 32
|
|
|
|
typedef struct _PS2_PORT
|
|
{
|
|
BOOLEAN IsEnabled;
|
|
|
|
BOOLEAN QueueEmpty;
|
|
BYTE Queue[BUFFER_SIZE];
|
|
UINT QueueStart;
|
|
UINT QueueEnd;
|
|
HANDLE QueueMutex;
|
|
|
|
LPVOID Param;
|
|
PS2_DEVICE_CMDPROC DeviceCommand;
|
|
} PS2_PORT, *PPS2_PORT;
|
|
|
|
/*
|
|
* Port 1: Keyboard
|
|
* Port 2: Mouse
|
|
*/
|
|
#define PS2_PORTS 2
|
|
static PS2_PORT Ports[PS2_PORTS];
|
|
|
|
#define PS2_DEFAULT_CONFIG 0x47
|
|
static BYTE ControllerConfig = PS2_DEFAULT_CONFIG;
|
|
static BYTE ControllerCommand = 0x00;
|
|
|
|
static BYTE StatusRegister = 0x00;
|
|
// static BYTE InputBuffer = 0x00; // PS/2 Input Buffer
|
|
static BYTE OutputBuffer = 0x00; // PS/2 Output Buffer
|
|
|
|
/* PRIVATE FUNCTIONS **********************************************************/
|
|
|
|
static VOID PS2SendCommand(PPS2_PORT Port, BYTE Command)
|
|
{
|
|
if (!Port->IsEnabled) return;
|
|
|
|
/* Call the device command */
|
|
if (Port->DeviceCommand) Port->DeviceCommand(Port->Param, Command);
|
|
}
|
|
|
|
static BYTE WINAPI PS2ReadPort(USHORT Port)
|
|
{
|
|
if (Port == PS2_CONTROL_PORT)
|
|
{
|
|
/* Be sure bit 2 is always set */
|
|
StatusRegister |= 1 << 2;
|
|
|
|
// FIXME: Should clear bits 6 and 7 because there are
|
|
// no timeouts and no parity errors.
|
|
|
|
return StatusRegister;
|
|
}
|
|
else if (Port == PS2_DATA_PORT)
|
|
{
|
|
/*
|
|
* If there is something to read (response byte from the
|
|
* controller or data from a PS/2 device), read it.
|
|
*/
|
|
if (StatusRegister & (1 << 0)) // || StatusRegister & (1 << 5) for second PS/2 port
|
|
StatusRegister &= ~(1 << 0); // StatusRegister &= ~(1 << 5);
|
|
|
|
// FIXME: We may check there whether there is data latched in
|
|
// PS2 ports 1 or 2 (keyboard or mouse) and retrieve it there...
|
|
|
|
/* Always return the available byte stored in the output buffer */
|
|
return OutputBuffer;
|
|
}
|
|
|
|
return 0x00;
|
|
}
|
|
|
|
static VOID WINAPI PS2WritePort(USHORT Port, BYTE Data)
|
|
{
|
|
if (Port == PS2_CONTROL_PORT)
|
|
{
|
|
switch (Data)
|
|
{
|
|
/* Read configuration byte */
|
|
case 0x20:
|
|
{
|
|
OutputBuffer = ControllerConfig;
|
|
StatusRegister |= (1 << 0); // There is something to read
|
|
break;
|
|
}
|
|
|
|
/* Write configuration byte */
|
|
case 0x60:
|
|
/* Write controller output port */
|
|
case 0xD1:
|
|
/* Write to the first PS/2 port output buffer */
|
|
case 0xD2:
|
|
/* Write to the second PS/2 port output buffer */
|
|
case 0xD3:
|
|
/* Write to the second PS/2 port input buffer */
|
|
case 0xD4:
|
|
{
|
|
/* These commands require a response */
|
|
ControllerCommand = Data;
|
|
StatusRegister |= (1 << 3); // This is a controller command
|
|
break;
|
|
}
|
|
|
|
/* Disable second PS/2 port */
|
|
case 0xA7:
|
|
{
|
|
Ports[1].IsEnabled = FALSE;
|
|
break;
|
|
}
|
|
|
|
/* Enable second PS/2 port */
|
|
case 0xA8:
|
|
{
|
|
Ports[1].IsEnabled = TRUE;
|
|
break;
|
|
}
|
|
|
|
/* Test second PS/2 port */
|
|
case 0xA9:
|
|
{
|
|
OutputBuffer = 0x00; // Success code
|
|
StatusRegister |= (1 << 0); // There is something to read
|
|
break;
|
|
}
|
|
|
|
/* Test PS/2 controller */
|
|
case 0xAA:
|
|
{
|
|
OutputBuffer = 0x55; // Success code
|
|
StatusRegister |= (1 << 0); // There is something to read
|
|
break;
|
|
}
|
|
|
|
/* Test first PS/2 port */
|
|
case 0xAB:
|
|
{
|
|
OutputBuffer = 0x00; // Success code
|
|
StatusRegister |= (1 << 0); // There is something to read
|
|
break;
|
|
}
|
|
|
|
/* Disable first PS/2 port */
|
|
case 0xAD:
|
|
{
|
|
Ports[0].IsEnabled = FALSE;
|
|
break;
|
|
}
|
|
|
|
/* Enable first PS/2 port */
|
|
case 0xAE:
|
|
{
|
|
Ports[0].IsEnabled = TRUE;
|
|
break;
|
|
}
|
|
|
|
/* Read controller output port */
|
|
case 0xD0:
|
|
{
|
|
// TODO: Not implemented
|
|
break;
|
|
}
|
|
|
|
/* CPU Reset */
|
|
case 0xF0:
|
|
case 0xF2:
|
|
case 0xF4:
|
|
case 0xF6:
|
|
case 0xF8:
|
|
case 0xFA:
|
|
case 0xFC:
|
|
case 0xFE:
|
|
{
|
|
/* Stop the VDM */
|
|
EmulatorTerminate();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (Port == PS2_DATA_PORT)
|
|
{
|
|
/* Check if the controller is waiting for a response */
|
|
if (StatusRegister & (1 << 3)) // If we have data for the controller
|
|
{
|
|
StatusRegister &= ~(1 << 3);
|
|
|
|
/* Check which command it was */
|
|
switch (ControllerCommand)
|
|
{
|
|
/* Write configuration byte */
|
|
case 0x60:
|
|
{
|
|
ControllerConfig = Data;
|
|
break;
|
|
}
|
|
|
|
/* Write controller output */
|
|
case 0xD1:
|
|
{
|
|
/* Check if bit 0 is unset */
|
|
if (!(Data & (1 << 0)))
|
|
{
|
|
/* CPU disabled - Stop the VDM */
|
|
EmulatorTerminate();
|
|
}
|
|
|
|
/* Update the A20 line setting */
|
|
EmulatorSetA20(Data & (1 << 1));
|
|
|
|
break;
|
|
}
|
|
|
|
/* Push the data byte into the first PS/2 port queue */
|
|
case 0xD2:
|
|
{
|
|
PS2QueuePush(0, Data);
|
|
break;
|
|
}
|
|
|
|
/* Push the data byte into the second PS/2 port queue */
|
|
case 0xD3:
|
|
{
|
|
PS2QueuePush(1, Data);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Send a command to the second PS/2 port (by default
|
|
* it is a command for the first PS/2 port)
|
|
*/
|
|
case 0xD4:
|
|
{
|
|
PS2SendCommand(&Ports[1], Data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* By default, send a command to the first PS/2 port */
|
|
PS2SendCommand(&Ports[0], Data);
|
|
}
|
|
}
|
|
|
|
static BOOLEAN PS2PortQueueRead(BYTE PS2Port)
|
|
{
|
|
BOOLEAN Result = TRUE;
|
|
PPS2_PORT Port;
|
|
|
|
if (PS2Port >= PS2_PORTS) return FALSE;
|
|
Port = &Ports[PS2Port];
|
|
|
|
if (!Port->IsEnabled) return FALSE;
|
|
|
|
/* Make sure the queue is not empty (fast check) */
|
|
if (Port->QueueEmpty)
|
|
{
|
|
/* Only the keyboard should have its last data latched */
|
|
// FIXME: Alternatively this can be done in PS2ReadPort when
|
|
// we read PS2_DATA_PORT. What is the best solution??
|
|
if (PS2Port == 0)
|
|
{
|
|
OutputBuffer = Port->Queue[(Port->QueueStart - 1) % BUFFER_SIZE];
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
WaitForSingleObject(Port->QueueMutex, INFINITE);
|
|
|
|
/*
|
|
* Recheck whether the queue is not empty (it may
|
|
* have changed after having grabbed the mutex).
|
|
*/
|
|
if (Port->QueueEmpty)
|
|
{
|
|
Result = FALSE;
|
|
goto Done;
|
|
}
|
|
|
|
/* Get the data */
|
|
OutputBuffer = Port->Queue[Port->QueueStart];
|
|
StatusRegister |= (1 << 0); // There is something to read
|
|
// Sometimes StatusRegister |= (1 << 5); for the second PS/2 port
|
|
|
|
/* Remove the value from the queue */
|
|
Port->QueueStart++;
|
|
Port->QueueStart %= BUFFER_SIZE;
|
|
|
|
/* Check if the queue is now empty */
|
|
if (Port->QueueStart == Port->QueueEnd)
|
|
Port->QueueEmpty = TRUE;
|
|
|
|
Done:
|
|
ReleaseMutex(Port->QueueMutex);
|
|
return Result;
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS ***********************************************************/
|
|
|
|
VOID PS2SetDeviceCmdProc(BYTE PS2Port, LPVOID Param, PS2_DEVICE_CMDPROC DeviceCommand)
|
|
{
|
|
if (PS2Port >= PS2_PORTS) return;
|
|
|
|
Ports[PS2Port].Param = Param;
|
|
Ports[PS2Port].DeviceCommand = DeviceCommand;
|
|
}
|
|
|
|
// PS2SendToPort
|
|
BOOLEAN PS2QueuePush(BYTE PS2Port, BYTE Data)
|
|
{
|
|
BOOLEAN Result = TRUE;
|
|
PPS2_PORT Port;
|
|
|
|
if (PS2Port >= PS2_PORTS) return FALSE;
|
|
Port = &Ports[PS2Port];
|
|
|
|
if (!Port->IsEnabled) return FALSE;
|
|
|
|
WaitForSingleObject(Port->QueueMutex, INFINITE);
|
|
|
|
/* Check if the queue is full */
|
|
if (!Port->QueueEmpty && (Port->QueueStart == Port->QueueEnd))
|
|
{
|
|
Result = FALSE;
|
|
goto Done;
|
|
}
|
|
|
|
/* Insert the value in the queue */
|
|
Port->Queue[Port->QueueEnd] = Data;
|
|
Port->QueueEnd++;
|
|
Port->QueueEnd %= BUFFER_SIZE;
|
|
|
|
/* The queue is not empty anymore */
|
|
Port->QueueEmpty = FALSE;
|
|
|
|
/*
|
|
// Get the data
|
|
OutputBuffer = Port->Queue[Port->QueueStart];
|
|
StatusRegister |= (1 << 0); // There is something to read
|
|
// FIXME: Sometimes StatusRegister |= (1 << 5); for the second PS/2 port
|
|
|
|
if (PS2Port == 0)
|
|
PicInterruptRequest(1);
|
|
else if (PS2Port == 1)
|
|
PicInterruptRequest(12);
|
|
*/
|
|
|
|
Done:
|
|
ReleaseMutex(Port->QueueMutex);
|
|
return Result;
|
|
}
|
|
|
|
VOID GenerateIrq1(VOID)
|
|
{
|
|
/* Generate an interrupt if interrupts for the first PS/2 port are enabled */
|
|
if (ControllerConfig & 0x01)
|
|
{
|
|
/* Generate an IRQ 1 if there is data ready in the output queue */
|
|
if (PS2PortQueueRead(0)) PicInterruptRequest(1);
|
|
}
|
|
}
|
|
|
|
VOID GenerateIrq12(VOID)
|
|
{
|
|
/* Generate an interrupt if interrupts for the second PS/2 port are enabled */
|
|
if (ControllerConfig & 0x02)
|
|
{
|
|
/* Generate an IRQ 12 if there is data ready in the output queue */
|
|
if (PS2PortQueueRead(1)) PicInterruptRequest(12);
|
|
}
|
|
}
|
|
|
|
BOOLEAN PS2Initialize(VOID)
|
|
{
|
|
/* Initialize the PS/2 ports */
|
|
Ports[0].IsEnabled = TRUE;
|
|
Ports[0].QueueEmpty = TRUE;
|
|
Ports[0].QueueStart = 0;
|
|
Ports[0].QueueEnd = 0;
|
|
Ports[0].QueueMutex = CreateMutex(NULL, FALSE, NULL);
|
|
|
|
Ports[1].IsEnabled = TRUE;
|
|
Ports[1].QueueEmpty = TRUE;
|
|
Ports[1].QueueStart = 0;
|
|
Ports[1].QueueEnd = 0;
|
|
Ports[1].QueueMutex = CreateMutex(NULL, FALSE, NULL);
|
|
|
|
/* Register the I/O Ports */
|
|
RegisterIoPort(PS2_CONTROL_PORT, PS2ReadPort, PS2WritePort);
|
|
RegisterIoPort(PS2_DATA_PORT , PS2ReadPort, PS2WritePort);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID PS2Cleanup(VOID)
|
|
{
|
|
CloseHandle(Ports[1].QueueMutex);
|
|
CloseHandle(Ports[0].QueueMutex);
|
|
}
|
|
|
|
/* EOF */
|