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