2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
5 * PURPOSE: PS/2 controller emulation
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
10 /* INCLUDES *******************************************************************/
22 /* PRIVATE VARIABLES **********************************************************/
24 #define BUFFER_SIZE 32
26 typedef struct _PS2_PORT
31 BYTE Queue
[BUFFER_SIZE
];
37 PS2_DEVICE_CMDPROC DeviceCommand
;
38 } PS2_PORT
, *PPS2_PORT
;
45 static PS2_PORT Ports
[PS2_PORTS
];
47 #define PS2_DEFAULT_CONFIG 0x45
48 static BYTE ControllerConfig
= PS2_DEFAULT_CONFIG
;
49 static BYTE ControllerCommand
= 0x00;
51 static BYTE StatusRegister
= 0x00;
52 // static BYTE InputBuffer = 0x00; // PS/2 Input Buffer
53 static BYTE OutputBuffer
= 0x00; // PS/2 Output Buffer
55 static PHARDWARE_TIMER IrqTimer
= NULL
;
57 /* PRIVATE FUNCTIONS **********************************************************/
59 static VOID
PS2SendCommand(PPS2_PORT Port
, BYTE Command
)
61 if (!Port
->IsEnabled
) return;
63 /* Call the device command */
64 if (Port
->DeviceCommand
) Port
->DeviceCommand(Port
->Param
, Command
);
67 static BYTE WINAPI
PS2ReadPort(USHORT Port
)
69 if (Port
== PS2_CONTROL_PORT
)
71 /* Be sure bit 2 is always set */
72 StatusRegister
|= 1 << 2;
74 // FIXME: Should clear bits 6 and 7 because there are
75 // no timeouts and no parity errors.
77 return StatusRegister
;
79 else if (Port
== PS2_DATA_PORT
)
82 * If there is something to read (response byte from the
83 * controller or data from a PS/2 device), read it.
85 if (StatusRegister
& (1 << 0)) // || StatusRegister & (1 << 5) for second PS/2 port
86 StatusRegister
&= ~(1 << 0); // StatusRegister &= ~(1 << 5);
88 // FIXME: We may check there whether there is data latched in
89 // PS2 ports 1 or 2 (keyboard or mouse) and retrieve it there...
91 /* Always return the available byte stored in the output buffer */
98 static VOID WINAPI
PS2WritePort(USHORT Port
, BYTE Data
)
100 if (Port
== PS2_CONTROL_PORT
)
104 /* Read configuration byte */
107 OutputBuffer
= ControllerConfig
;
108 StatusRegister
|= (1 << 0); // There is something to read
112 /* Write configuration byte */
114 /* Write controller output port */
116 /* Write to the first PS/2 port output buffer */
118 /* Write to the second PS/2 port output buffer */
120 /* Write to the second PS/2 port input buffer */
123 /* These commands require a response */
124 ControllerCommand
= Data
;
125 StatusRegister
|= (1 << 3); // This is a controller command
129 /* Disable second PS/2 port */
132 Ports
[1].IsEnabled
= FALSE
;
136 /* Enable second PS/2 port */
139 Ports
[1].IsEnabled
= TRUE
;
143 /* Test second PS/2 port */
146 OutputBuffer
= 0x00; // Success code
147 StatusRegister
|= (1 << 0); // There is something to read
151 /* Test PS/2 controller */
154 OutputBuffer
= 0x55; // Success code
155 StatusRegister
|= (1 << 0); // There is something to read
159 /* Test first PS/2 port */
162 OutputBuffer
= 0x00; // Success code
163 StatusRegister
|= (1 << 0); // There is something to read
167 /* Disable first PS/2 port */
170 Ports
[0].IsEnabled
= FALSE
;
174 /* Enable first PS/2 port */
177 Ports
[0].IsEnabled
= TRUE
;
181 /* Read controller output port */
184 // TODO: Not implemented
204 else if (Port
== PS2_DATA_PORT
)
206 /* Check if the controller is waiting for a response */
207 if (StatusRegister
& (1 << 3)) // If we have data for the controller
209 StatusRegister
&= ~(1 << 3);
211 /* Check which command it was */
212 switch (ControllerCommand
)
214 /* Write configuration byte */
217 ControllerConfig
= Data
;
221 /* Write controller output */
224 /* Check if bit 0 is unset */
225 if (!(Data
& (1 << 0)))
227 /* CPU disabled - Stop the VDM */
231 /* Update the A20 line setting */
232 EmulatorSetA20(Data
& (1 << 1));
237 /* Push the data byte into the first PS/2 port queue */
240 PS2QueuePush(0, Data
);
244 /* Push the data byte into the second PS/2 port queue */
247 PS2QueuePush(1, Data
);
252 * Send a command to the second PS/2 port (by default
253 * it is a command for the first PS/2 port)
257 PS2SendCommand(&Ports
[1], Data
);
265 /* By default, send a command to the first PS/2 port */
266 PS2SendCommand(&Ports
[0], Data
);
270 static VOID FASTCALL
GeneratePS2Irq(ULONGLONG ElapsedTime
)
272 UNREFERENCED_PARAMETER(ElapsedTime
);
274 /* Generate an IRQ 1 if there is data ready in the output queue */
275 if (PS2PortQueueRead(0))
277 /* Generate an interrupt if interrupts for the first PS/2 port are enabled */
278 if (ControllerConfig
& 0x01) PicInterruptRequest(1);
282 /* Generate an IRQ 12 if there is data ready in the output queue */
283 if (PS2PortQueueRead(1))
285 /* Generate an interrupt if interrupts for the second PS/2 port are enabled */
286 if (ControllerConfig
& 0x02) PicInterruptRequest(12);
291 /* PUBLIC FUNCTIONS ***********************************************************/
293 BOOLEAN
PS2PortQueueRead(BYTE PS2Port
)
295 BOOLEAN Result
= TRUE
;
298 if (PS2Port
>= PS2_PORTS
) return FALSE
;
299 Port
= &Ports
[PS2Port
];
301 if (!Port
->IsEnabled
) return FALSE
;
303 /* Make sure the queue is not empty (fast check) */
304 if (Port
->QueueEmpty
)
306 /* Only the keyboard should have its last data latched */
307 // FIXME: Alternatively this can be done in PS2ReadPort when
308 // we read PS2_DATA_PORT. What is the best solution??
311 OutputBuffer
= Port
->Queue
[(Port
->QueueStart
- 1) % BUFFER_SIZE
];
317 WaitForSingleObject(Port
->QueueMutex
, INFINITE
);
320 * Recheck whether the queue is not empty (it may
321 * have changed after having grabbed the mutex).
323 if (Port
->QueueEmpty
)
330 OutputBuffer
= Port
->Queue
[Port
->QueueStart
];
331 StatusRegister
|= (1 << 0); // There is something to read
332 // Sometimes StatusRegister |= (1 << 5); for the second PS/2 port
334 /* Remove the value from the queue */
336 Port
->QueueStart
%= BUFFER_SIZE
;
338 /* Check if the queue is now empty */
339 if (Port
->QueueStart
== Port
->QueueEnd
)
340 Port
->QueueEmpty
= TRUE
;
343 ReleaseMutex(Port
->QueueMutex
);
347 VOID
PS2SetDeviceCmdProc(BYTE PS2Port
, LPVOID Param
, PS2_DEVICE_CMDPROC DeviceCommand
)
349 if (PS2Port
>= PS2_PORTS
) return;
351 Ports
[PS2Port
].Param
= Param
;
352 Ports
[PS2Port
].DeviceCommand
= DeviceCommand
;
356 BOOLEAN
PS2QueuePush(BYTE PS2Port
, BYTE Data
)
358 BOOLEAN Result
= TRUE
;
361 if (PS2Port
>= PS2_PORTS
) return FALSE
;
362 Port
= &Ports
[PS2Port
];
364 if (!Port
->IsEnabled
) return FALSE
;
366 WaitForSingleObject(Port
->QueueMutex
, INFINITE
);
368 /* Check if the queue is full */
369 if (!Port
->QueueEmpty
&& (Port
->QueueStart
== Port
->QueueEnd
))
375 /* Insert the value in the queue */
376 Port
->Queue
[Port
->QueueEnd
] = Data
;
378 Port
->QueueEnd
%= BUFFER_SIZE
;
380 /* The queue is not empty anymore */
381 Port
->QueueEmpty
= FALSE
;
383 /* Schedule the IRQ */
384 EnableHardwareTimer(IrqTimer
);
387 ReleaseMutex(Port
->QueueMutex
);
391 BOOLEAN
PS2Initialize(VOID
)
393 /* Initialize the PS/2 ports */
394 Ports
[0].IsEnabled
= TRUE
;
395 Ports
[0].QueueEmpty
= TRUE
;
396 Ports
[0].QueueStart
= 0;
397 Ports
[0].QueueEnd
= 0;
398 Ports
[0].QueueMutex
= CreateMutex(NULL
, FALSE
, NULL
);
400 Ports
[1].IsEnabled
= TRUE
;
401 Ports
[1].QueueEmpty
= TRUE
;
402 Ports
[1].QueueStart
= 0;
403 Ports
[1].QueueEnd
= 0;
404 Ports
[1].QueueMutex
= CreateMutex(NULL
, FALSE
, NULL
);
406 /* Register the I/O Ports */
407 RegisterIoPort(PS2_CONTROL_PORT
, PS2ReadPort
, PS2WritePort
);
408 RegisterIoPort(PS2_DATA_PORT
, PS2ReadPort
, PS2WritePort
);
410 IrqTimer
= CreateHardwareTimer(HARDWARE_TIMER_ONESHOT
,
417 VOID
PS2Cleanup(VOID
)
419 DestroyHardwareTimer(IrqTimer
);
421 CloseHandle(Ports
[1].QueueMutex
);
422 CloseHandle(Ports
[0].QueueMutex
);