Start source tree (final, I hope!) restructuration. Part 1/X
[reactos.git] / reactos / subsystems / mvdm / ntvdm / hardware / ps2.c
diff --git a/reactos/subsystems/mvdm/ntvdm/hardware/ps2.c b/reactos/subsystems/mvdm/ntvdm/hardware/ps2.c
new file mode 100644 (file)
index 0000000..b7e6c13
--- /dev/null
@@ -0,0 +1,422 @@
+/*
+ * 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 */