[NTVDM]
[reactos.git] / reactos / subsystems / mvdm / ntvdm / hardware / ps2.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: ps2.c
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)
8 */
9
10 /* INCLUDES *******************************************************************/
11
12 #define NDEBUG
13
14 #include "ntvdm.h"
15 #include "emulator.h"
16 #include "ps2.h"
17
18 #include "io.h"
19 #include "pic.h"
20 #include "clock.h"
21
22 /* PRIVATE VARIABLES **********************************************************/
23
24 #define BUFFER_SIZE 32
25
26 typedef struct _PS2_PORT
27 {
28 BOOLEAN IsEnabled;
29
30 BOOLEAN QueueEmpty;
31 BYTE Queue[BUFFER_SIZE];
32 UINT QueueStart;
33 UINT QueueEnd;
34 HANDLE QueueMutex;
35
36 LPVOID Param;
37 PS2_DEVICE_CMDPROC DeviceCommand;
38 } PS2_PORT, *PPS2_PORT;
39
40 /*
41 * Port 1: Keyboard
42 * Port 2: Mouse
43 */
44 #define PS2_PORTS 2
45 static PS2_PORT Ports[PS2_PORTS];
46
47 #define PS2_DEFAULT_CONFIG 0x45
48 static BYTE ControllerConfig = PS2_DEFAULT_CONFIG;
49 static BYTE ControllerCommand = 0x00;
50
51 static BYTE StatusRegister = 0x00;
52 // static BYTE InputBuffer = 0x00; // PS/2 Input Buffer
53 static BYTE OutputBuffer = 0x00; // PS/2 Output Buffer
54
55 static PHARDWARE_TIMER IrqTimer = NULL;
56
57 /* PRIVATE FUNCTIONS **********************************************************/
58
59 static VOID PS2SendCommand(PPS2_PORT Port, BYTE Command)
60 {
61 if (!Port->IsEnabled) return;
62
63 /* Call the device command */
64 if (Port->DeviceCommand) Port->DeviceCommand(Port->Param, Command);
65 }
66
67 static BYTE WINAPI PS2ReadPort(USHORT Port)
68 {
69 if (Port == PS2_CONTROL_PORT)
70 {
71 /* Be sure bit 2 is always set */
72 StatusRegister |= 1 << 2;
73
74 // FIXME: Should clear bits 6 and 7 because there are
75 // no timeouts and no parity errors.
76
77 return StatusRegister;
78 }
79 else if (Port == PS2_DATA_PORT)
80 {
81 /*
82 * If there is something to read (response byte from the
83 * controller or data from a PS/2 device), read it.
84 */
85 if (StatusRegister & (1 << 0)) // || StatusRegister & (1 << 5) for second PS/2 port
86 StatusRegister &= ~(1 << 0); // StatusRegister &= ~(1 << 5);
87
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...
90
91 /* Always return the available byte stored in the output buffer */
92 return OutputBuffer;
93 }
94
95 return 0x00;
96 }
97
98 static VOID WINAPI PS2WritePort(USHORT Port, BYTE Data)
99 {
100 if (Port == PS2_CONTROL_PORT)
101 {
102 switch (Data)
103 {
104 /* Read configuration byte */
105 case 0x20:
106 {
107 OutputBuffer = ControllerConfig;
108 StatusRegister |= (1 << 0); // There is something to read
109 break;
110 }
111
112 /* Write configuration byte */
113 case 0x60:
114 /* Write controller output port */
115 case 0xD1:
116 /* Write to the first PS/2 port output buffer */
117 case 0xD2:
118 /* Write to the second PS/2 port output buffer */
119 case 0xD3:
120 /* Write to the second PS/2 port input buffer */
121 case 0xD4:
122 {
123 /* These commands require a response */
124 ControllerCommand = Data;
125 StatusRegister |= (1 << 3); // This is a controller command
126 break;
127 }
128
129 /* Disable second PS/2 port */
130 case 0xA7:
131 {
132 Ports[1].IsEnabled = FALSE;
133 break;
134 }
135
136 /* Enable second PS/2 port */
137 case 0xA8:
138 {
139 Ports[1].IsEnabled = TRUE;
140 break;
141 }
142
143 /* Test second PS/2 port */
144 case 0xA9:
145 {
146 OutputBuffer = 0x00; // Success code
147 StatusRegister |= (1 << 0); // There is something to read
148 break;
149 }
150
151 /* Test PS/2 controller */
152 case 0xAA:
153 {
154 OutputBuffer = 0x55; // Success code
155 StatusRegister |= (1 << 0); // There is something to read
156 break;
157 }
158
159 /* Test first PS/2 port */
160 case 0xAB:
161 {
162 OutputBuffer = 0x00; // Success code
163 StatusRegister |= (1 << 0); // There is something to read
164 break;
165 }
166
167 /* Disable first PS/2 port */
168 case 0xAD:
169 {
170 Ports[0].IsEnabled = FALSE;
171 break;
172 }
173
174 /* Enable first PS/2 port */
175 case 0xAE:
176 {
177 Ports[0].IsEnabled = TRUE;
178 break;
179 }
180
181 /* Read controller output port */
182 case 0xD0:
183 {
184 // TODO: Not implemented
185 break;
186 }
187
188 /* CPU Reset */
189 case 0xF0:
190 case 0xF2:
191 case 0xF4:
192 case 0xF6:
193 case 0xF8:
194 case 0xFA:
195 case 0xFC:
196 case 0xFE:
197 {
198 /* Stop the VDM */
199 EmulatorTerminate();
200 break;
201 }
202 }
203 }
204 else if (Port == PS2_DATA_PORT)
205 {
206 /* Check if the controller is waiting for a response */
207 if (StatusRegister & (1 << 3)) // If we have data for the controller
208 {
209 StatusRegister &= ~(1 << 3);
210
211 /* Check which command it was */
212 switch (ControllerCommand)
213 {
214 /* Write configuration byte */
215 case 0x60:
216 {
217 ControllerConfig = Data;
218 break;
219 }
220
221 /* Write controller output */
222 case 0xD1:
223 {
224 /* Check if bit 0 is unset */
225 if (!(Data & (1 << 0)))
226 {
227 /* CPU disabled - Stop the VDM */
228 EmulatorTerminate();
229 }
230
231 /* Update the A20 line setting */
232 EmulatorSetA20(Data & (1 << 1));
233
234 break;
235 }
236
237 /* Push the data byte into the first PS/2 port queue */
238 case 0xD2:
239 {
240 PS2QueuePush(0, Data);
241 break;
242 }
243
244 /* Push the data byte into the second PS/2 port queue */
245 case 0xD3:
246 {
247 PS2QueuePush(1, Data);
248 break;
249 }
250
251 /*
252 * Send a command to the second PS/2 port (by default
253 * it is a command for the first PS/2 port)
254 */
255 case 0xD4:
256 {
257 PS2SendCommand(&Ports[1], Data);
258 break;
259 }
260 }
261
262 return;
263 }
264
265 /* By default, send a command to the first PS/2 port */
266 PS2SendCommand(&Ports[0], Data);
267 }
268 }
269
270 static VOID FASTCALL GeneratePS2Irq(ULONGLONG ElapsedTime)
271 {
272 UNREFERENCED_PARAMETER(ElapsedTime);
273
274 /* Generate an IRQ 1 if there is data ready in the output queue */
275 if (PS2PortQueueRead(0))
276 {
277 /* Generate an interrupt if interrupts for the first PS/2 port are enabled */
278 if (ControllerConfig & 0x01) PicInterruptRequest(1);
279 return;
280 }
281
282 /* Generate an IRQ 12 if there is data ready in the output queue */
283 if (PS2PortQueueRead(1))
284 {
285 /* Generate an interrupt if interrupts for the second PS/2 port are enabled */
286 if (ControllerConfig & 0x02) PicInterruptRequest(12);
287 return;
288 }
289 }
290
291 /* PUBLIC FUNCTIONS ***********************************************************/
292
293 BOOLEAN PS2PortQueueRead(BYTE PS2Port)
294 {
295 BOOLEAN Result = TRUE;
296 PPS2_PORT Port;
297
298 if (PS2Port >= PS2_PORTS) return FALSE;
299 Port = &Ports[PS2Port];
300
301 if (!Port->IsEnabled) return FALSE;
302
303 /* Make sure the queue is not empty (fast check) */
304 if (Port->QueueEmpty)
305 {
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??
309 if (PS2Port == 0)
310 {
311 OutputBuffer = Port->Queue[(Port->QueueStart - 1) % BUFFER_SIZE];
312 }
313
314 return FALSE;
315 }
316
317 WaitForSingleObject(Port->QueueMutex, INFINITE);
318
319 /*
320 * Recheck whether the queue is not empty (it may
321 * have changed after having grabbed the mutex).
322 */
323 if (Port->QueueEmpty)
324 {
325 Result = FALSE;
326 goto Done;
327 }
328
329 /* Get the data */
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
333
334 /* Remove the value from the queue */
335 Port->QueueStart++;
336 Port->QueueStart %= BUFFER_SIZE;
337
338 /* Check if the queue is now empty */
339 if (Port->QueueStart == Port->QueueEnd)
340 Port->QueueEmpty = TRUE;
341
342 Done:
343 ReleaseMutex(Port->QueueMutex);
344 return Result;
345 }
346
347 VOID PS2SetDeviceCmdProc(BYTE PS2Port, LPVOID Param, PS2_DEVICE_CMDPROC DeviceCommand)
348 {
349 if (PS2Port >= PS2_PORTS) return;
350
351 Ports[PS2Port].Param = Param;
352 Ports[PS2Port].DeviceCommand = DeviceCommand;
353 }
354
355 // PS2SendToPort
356 BOOLEAN PS2QueuePush(BYTE PS2Port, BYTE Data)
357 {
358 BOOLEAN Result = TRUE;
359 PPS2_PORT Port;
360
361 if (PS2Port >= PS2_PORTS) return FALSE;
362 Port = &Ports[PS2Port];
363
364 if (!Port->IsEnabled) return FALSE;
365
366 WaitForSingleObject(Port->QueueMutex, INFINITE);
367
368 /* Check if the queue is full */
369 if (!Port->QueueEmpty && (Port->QueueStart == Port->QueueEnd))
370 {
371 Result = FALSE;
372 goto Done;
373 }
374
375 /* Insert the value in the queue */
376 Port->Queue[Port->QueueEnd] = Data;
377 Port->QueueEnd++;
378 Port->QueueEnd %= BUFFER_SIZE;
379
380 /* The queue is not empty anymore */
381 Port->QueueEmpty = FALSE;
382
383 /* Schedule the IRQ */
384 EnableHardwareTimer(IrqTimer);
385
386 Done:
387 ReleaseMutex(Port->QueueMutex);
388 return Result;
389 }
390
391 BOOLEAN PS2Initialize(VOID)
392 {
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);
399
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);
405
406 /* Register the I/O Ports */
407 RegisterIoPort(PS2_CONTROL_PORT, PS2ReadPort, PS2WritePort);
408 RegisterIoPort(PS2_DATA_PORT , PS2ReadPort, PS2WritePort);
409
410 IrqTimer = CreateHardwareTimer(HARDWARE_TIMER_ONESHOT,
411 HZ_TO_NS(100),
412 GeneratePS2Irq);
413
414 return TRUE;
415 }
416
417 VOID PS2Cleanup(VOID)
418 {
419 DestroyHardwareTimer(IrqTimer);
420
421 CloseHandle(Ports[1].QueueMutex);
422 CloseHandle(Ports[0].QueueMutex);
423 }
424
425 /* EOF */