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