[NTVDM]
[reactos.git] / subsystems / ntvdm / 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 */
8
9 /* INCLUDES *******************************************************************/
10
11 #define NDEBUG
12
13 #include "emulator.h"
14 #include "io.h"
15 #include "ps2.h"
16 #include "pic.h"
17
18 /* PRIVATE VARIABLES **********************************************************/
19
20 static BYTE KeyboardQueue[KEYBOARD_BUFFER_SIZE];
21 static BOOLEAN KeyboardQueueEmpty = TRUE;
22 static UINT KeyboardQueueStart = 0;
23 static UINT KeyboardQueueEnd = 0;
24 static BYTE KeyboardData = 0, KeyboardResponse = 0;
25 static BOOLEAN KeyboardReadResponse = FALSE, KeyboardWriteResponse = FALSE;
26 static BYTE KeyboardConfig = PS2_DEFAULT_CONFIG;
27 static HANDLE QueueMutex = NULL;
28 static HANDLE InputThread = NULL;
29
30 /* PRIVATE FUNCTIONS **********************************************************/
31
32 static BOOLEAN KeyboardQueuePush(BYTE ScanCode)
33 {
34 BOOLEAN Result = TRUE;
35
36 WaitForSingleObject(QueueMutex, INFINITE);
37
38 /* Check if the keyboard queue is full */
39 if (!KeyboardQueueEmpty && (KeyboardQueueStart == KeyboardQueueEnd))
40 {
41 Result = FALSE;
42 goto Done;
43 }
44
45 /* Insert the value in the queue */
46 KeyboardQueue[KeyboardQueueEnd] = ScanCode;
47 KeyboardQueueEnd++;
48 KeyboardQueueEnd %= KEYBOARD_BUFFER_SIZE;
49
50 /* Since we inserted a value, it's not empty anymore */
51 KeyboardQueueEmpty = FALSE;
52
53 Done:
54 ReleaseMutex(QueueMutex);
55 return Result;
56 }
57
58 static BOOLEAN KeyboardQueuePop(BYTE *ScanCode)
59 {
60 BOOLEAN Result = TRUE;
61
62 /* Make sure the keyboard queue is not empty (fast check) */
63 if (KeyboardQueueEmpty) return FALSE;
64
65 WaitForSingleObject(QueueMutex, INFINITE);
66
67 /*
68 * Recheck whether keyboard queue is not empty (it
69 * may have changed after having grabbed the mutex).
70 */
71 if (KeyboardQueueEmpty)
72 {
73 Result = FALSE;
74 goto Done;
75 }
76
77 /* Get the scan code */
78 *ScanCode = KeyboardQueue[KeyboardQueueStart];
79
80 /* Remove the value from the queue */
81 KeyboardQueueStart++;
82 KeyboardQueueStart %= KEYBOARD_BUFFER_SIZE;
83
84 /* Check if the queue is now empty */
85 if (KeyboardQueueStart == KeyboardQueueEnd)
86 {
87 KeyboardQueueEmpty = TRUE;
88 }
89
90 Done:
91 ReleaseMutex(QueueMutex);
92 return Result;
93 }
94
95 static BYTE WINAPI PS2ReadPort(ULONG Port)
96 {
97 if (Port == PS2_CONTROL_PORT)
98 {
99 BYTE Status = 0;
100
101 /* Set the first bit if the data can be read */
102 if (KeyboardReadResponse || !KeyboardQueueEmpty) Status |= 1 << 0;
103
104 /* Always set bit 2 */
105 Status |= 1 << 2;
106
107 /* Set bit 3 if the next byte goes to the controller */
108 if (KeyboardWriteResponse) Status |= 1 << 3;
109
110 return Status;
111 }
112 else if (Port == PS2_DATA_PORT)
113 {
114 /* If there was a response byte from the controller, return it */
115 if (KeyboardReadResponse)
116 {
117 KeyboardReadResponse = FALSE;
118 KeyboardData = KeyboardResponse;
119 }
120
121 return KeyboardData;
122 }
123 else return 0;
124 }
125
126 static VOID WINAPI PS2WritePort(ULONG Port, BYTE Data)
127 {
128 if (Port == PS2_CONTROL_PORT)
129 {
130 switch (Data)
131 {
132 /* Read configuration byte */
133 case 0x20:
134 {
135 KeyboardResponse = KeyboardConfig;
136 KeyboardReadResponse = TRUE;
137 break;
138 }
139
140 /* Write configuration byte */
141 case 0x60:
142 /* Write controller output port */
143 case 0xD1:
144 /* Write keyboard output buffer */
145 case 0xD2:
146 /* Write mouse output buffer */
147 case 0xD3:
148 /* Write mouse input buffer */
149 case 0xD4:
150 {
151 /* These commands require a response */
152 KeyboardResponse = Data;
153 KeyboardWriteResponse = TRUE;
154 break;
155 }
156
157 /* Disable mouse */
158 case 0xA7:
159 {
160 // TODO: Mouse support
161 break;
162 }
163
164 /* Enable mouse */
165 case 0xA8:
166 {
167 // TODO: Mouse support
168 break;
169 }
170
171 /* Test mouse port */
172 case 0xA9:
173 {
174 KeyboardResponse = 0;
175 KeyboardReadResponse = TRUE;
176 break;
177 }
178
179 /* Test PS/2 controller */
180 case 0xAA:
181 {
182 KeyboardResponse = 0x55;
183 KeyboardReadResponse = TRUE;
184 break;
185 }
186
187 /* Disable keyboard */
188 case 0xAD:
189 {
190 // TODO: Not implemented
191 break;
192 }
193
194 /* Enable keyboard */
195 case 0xAE:
196 {
197 // TODO: Not implemented
198 break;
199 }
200
201 /* Read controller output port */
202 case 0xD0:
203 {
204 // TODO: Not implemented
205 break;
206 }
207
208 /* CPU Reset */
209 case 0xF0:
210 case 0xF2:
211 case 0xF4:
212 case 0xF6:
213 case 0xF8:
214 case 0xFA:
215 case 0xFC:
216 case 0xFE:
217 {
218 /* Stop the simulation */
219 VdmRunning = FALSE;
220 break;
221 }
222 }
223 }
224 else if (Port == PS2_DATA_PORT)
225 {
226 /* Check if the controller is waiting for a response */
227 if (KeyboardWriteResponse)
228 {
229 KeyboardWriteResponse = FALSE;
230
231 /* Check which command it was */
232 switch (KeyboardResponse)
233 {
234 /* Write configuration byte */
235 case 0x60:
236 {
237 KeyboardConfig = Data;
238 break;
239 }
240
241 /* Write controller output */
242 case 0xD1:
243 {
244 /* Check if bit 0 is unset */
245 if (!(Data & (1 << 0)))
246 {
247 /* CPU disabled - end simulation */
248 VdmRunning = FALSE;
249 }
250
251 /* Update the A20 line setting */
252 EmulatorSetA20(Data & (1 << 1));
253
254 break;
255 }
256
257 case 0xD2:
258 {
259 /* Push the data byte to the keyboard queue */
260 KeyboardQueuePush(Data);
261 break;
262 }
263
264 case 0xD3:
265 {
266 // TODO: Mouse support
267 break;
268 }
269
270 case 0xD4:
271 {
272 // TODO: Mouse support
273 break;
274 }
275 }
276
277 return;
278 }
279
280 // TODO: Implement PS/2 device commands
281 }
282 }
283
284 static DWORD WINAPI InputThreadProc(LPVOID Parameter)
285 {
286 INT i;
287 HANDLE ConsoleInput = (HANDLE)Parameter;
288 INPUT_RECORD InputRecord;
289 DWORD Count;
290
291 while (VdmRunning)
292 {
293 /* Wait for an input record */
294 if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
295 {
296 DWORD LastError = GetLastError();
297 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
298 return LastError;
299 }
300
301 ASSERT(Count != 0);
302
303 /* Check the event type */
304 switch (InputRecord.EventType)
305 {
306 case KEY_EVENT:
307 {
308 BYTE ScanCode = (BYTE)InputRecord.Event.KeyEvent.wVirtualScanCode;
309
310 /* If this is a key release, set the highest bit in the scan code */
311 if (!InputRecord.Event.KeyEvent.bKeyDown) ScanCode |= 0x80;
312
313 /* Push the scan code onto the keyboard queue */
314 for (i = 0; i < InputRecord.Event.KeyEvent.wRepeatCount; i++)
315 {
316 KeyboardQueuePush(ScanCode);
317 }
318
319 break;
320 }
321
322 case MOUSE_EVENT:
323 {
324 // TODO: NOT IMPLEMENTED
325 UNIMPLEMENTED;
326 break;
327 }
328
329 default:
330 {
331 /* Ignored */
332 break;
333 }
334 }
335 }
336
337 return 0;
338 }
339
340 /* PUBLIC FUNCTIONS ***********************************************************/
341
342 VOID GenerateKeyboardInterrupts(VOID)
343 {
344 if (KeyboardQueuePop(&KeyboardData))
345 {
346 /* IRQ 1 */
347 PicInterruptRequest(1);
348 }
349 }
350
351 BOOLEAN PS2Initialize(HANDLE ConsoleInput)
352 {
353 /* Create the mutex */
354 QueueMutex = CreateMutex(NULL, FALSE, NULL);
355
356 /* Start the input thread */
357 InputThread = CreateThread(NULL, 0, &InputThreadProc, ConsoleInput, 0, NULL);
358
359 // if (InputThread == NULL) return FALSE;
360
361 /* Register the I/O Ports */
362 RegisterIoPort(PS2_CONTROL_PORT, PS2ReadPort, PS2WritePort);
363 RegisterIoPort(PS2_DATA_PORT , PS2ReadPort, PS2WritePort);
364
365 return TRUE;
366 }
367
368 VOID PS2Cleanup(VOID)
369 {
370 /* Close the input thread handle */
371 if (InputThread != NULL) CloseHandle(InputThread);
372 InputThread = NULL;
373
374 CloseHandle(QueueMutex);
375 }
376
377 /* EOF */