[NTVDM]
[reactos.git] / subsystems / ntvdm / hardware.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: hardware.c
5 * PURPOSE: Minimal hardware emulation
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #include "ntvdm.h"
12
13 typedef struct _PIC
14 {
15 BOOLEAN Initialization;
16 BYTE MaskRegister;
17 BYTE InServiceRegister;
18 BYTE IntOffset;
19 BYTE ConfigRegister;
20 BYTE CascadeRegister;
21 BOOLEAN CascadeRegisterSet;
22 BOOLEAN AutoEoi;
23 BOOLEAN Slave;
24 BOOLEAN ReadIsr;
25 } PIC, *PPIC;
26
27 enum
28 {
29 PIT_MODE_INT_ON_TERMINAL_COUNT,
30 PIT_MODE_HARDWARE_ONE_SHOT,
31 PIT_MODE_RATE_GENERATOR,
32 PIT_MODE_SQUARE_WAVE,
33 PIT_MODE_SOFTWARE_STROBE,
34 PIT_MODE_HARDWARE_STROBE
35 };
36
37 typedef struct _PIT_CHANNEL
38 {
39 WORD ReloadValue;
40 WORD CurrentValue;
41 WORD LatchedValue;
42 INT Mode;
43 BOOLEAN Pulsed;
44 BOOLEAN LatchSet;
45 BOOLEAN InputFlipFlop;
46 BOOLEAN OutputFlipFlop;
47 BYTE AccessMode;
48 } PIT_CHANNEL, *PPIT_CHANNEL;
49
50 static PIC MasterPic, SlavePic;
51 static PIT_CHANNEL PitChannels[PIT_CHANNELS];
52 static BYTE KeyboardQueue[KEYBOARD_BUFFER_SIZE];
53 static BOOLEAN KeyboardQueueEmpty = TRUE;
54 static UINT KeyboardQueueStart = 0;
55 static UINT KeyboardQueueEnd = 0;
56
57 static BOOLEAN KeyboardQueuePush(BYTE ScanCode)
58 {
59 /* Check if the keyboard queue is full */
60 if (!KeyboardQueueEmpty && (KeyboardQueueStart == KeyboardQueueEnd))
61 {
62 return FALSE;
63 }
64
65 /* Insert the value in the queue */
66 KeyboardQueue[KeyboardQueueEnd] = ScanCode;
67 KeyboardQueueEnd++;
68 KeyboardQueueEnd %= KEYBOARD_BUFFER_SIZE;
69
70 /* Since we inserted a value, it's not empty anymore */
71 KeyboardQueueEmpty = FALSE;
72
73 return TRUE;
74 }
75
76 #if 0
77 static BOOLEAN KeyboardQueuePop(BYTE *ScanCode)
78 {
79 /* Make sure the keyboard queue is not empty */
80 if (KeyboardQueueEmpty) return FALSE;
81
82 /* Get the scan code */
83 *ScanCode = KeyboardQueue[KeyboardQueueStart];
84
85 /* Remove the value from the queue */
86 KeyboardQueueStart++;
87 KeyboardQueueStart %= KEYBOARD_BUFFER_SIZE;
88
89 /* Check if the queue is now empty */
90 if (KeyboardQueueStart == KeyboardQueueEnd)
91 {
92 KeyboardQueueEmpty = TRUE;
93 }
94
95 return TRUE;
96 }
97 #endif
98
99 /* PUBLIC FUNCTIONS ***********************************************************/
100
101 BYTE PicReadCommand(BYTE Port)
102 {
103 PPIC Pic;
104
105 /* Which PIC are we accessing? */
106 if (Port == PIC_MASTER_CMD) Pic = &MasterPic;
107 else Pic = &SlavePic;
108
109 if (Pic->ReadIsr)
110 {
111 /* Read the in-service register */
112 Pic->ReadIsr = FALSE;
113 return Pic->InServiceRegister;
114 }
115 else
116 {
117 /* The IRR is always 0, as the emulated CPU receives the interrupt instantly */
118 return 0;
119 }
120 }
121
122 VOID PicWriteCommand(BYTE Port, BYTE Value)
123 {
124 PPIC Pic;
125
126 /* Which PIC are we accessing? */
127 if (Port == PIC_MASTER_CMD) Pic = &MasterPic;
128 else Pic = &SlavePic;
129
130 if (Value & PIC_ICW1)
131 {
132 /* Start initialization */
133 Pic->Initialization = TRUE;
134 Pic->IntOffset = 0xFF;
135 Pic->CascadeRegisterSet = FALSE;
136 Pic->ConfigRegister = Value;
137 return;
138 }
139
140 if (Value & PIC_OCW3)
141 {
142 /* This is an OCR3 */
143 if (Value == PIC_OCW3_READ_ISR)
144 {
145 /* Return the ISR on next read from command port */
146 Pic->ReadIsr = TRUE;
147 }
148
149 return;
150 }
151
152 /* This is an OCW2 */
153 if (Value & PIC_OCW2_EOI)
154 {
155 if (Value & PIC_OCW2_SL)
156 {
157 /* If the SL bit is set, clear a specific IRQ */
158 Pic->InServiceRegister &= ~(1 << (Value & PIC_OCW2_NUM_MASK));
159 }
160 else
161 {
162 /* Otherwise, clear all of them */
163 Pic->InServiceRegister = 0;
164 }
165 }
166 }
167
168 BYTE PicReadData(BYTE Port)
169 {
170 /* Read the mask register */
171 if (Port == PIC_MASTER_DATA) return MasterPic.MaskRegister;
172 else return SlavePic.MaskRegister;
173 }
174
175 VOID PicWriteData(BYTE Port, BYTE Value)
176 {
177 PPIC Pic;
178
179 /* Which PIC are we accessing? */
180 if (Port == PIC_MASTER_DATA) Pic = &MasterPic;
181 else Pic = &SlavePic;
182
183 /* Is the PIC ready? */
184 if (!Pic->Initialization)
185 {
186 /* Yes, this is an OCW1 */
187 Pic->MaskRegister = Value;
188 return;
189 }
190
191 /* Has the interrupt offset been set? */
192 if (Pic->IntOffset == 0xFF)
193 {
194 /* This is an ICW2, set the offset (last three bits always zero) */
195 Pic->IntOffset = Value & 0xF8;
196
197 /* Check if we are in single mode and don't need an ICW4 */
198 if ((Pic->ConfigRegister & PIC_ICW1_SINGLE)
199 && !(Pic->ConfigRegister & PIC_ICW1_ICW4))
200 {
201 /* Yes, done initializing */
202 Pic->Initialization = FALSE;
203 }
204 return;
205 }
206
207 /* Check if we are in cascade mode and the cascade register was not set */
208 if (!(Pic->ConfigRegister & PIC_ICW1_SINGLE) && !Pic->CascadeRegisterSet)
209 {
210 /* This is an ICW3 */
211 Pic->CascadeRegister = Value;
212 Pic->CascadeRegisterSet = TRUE;
213
214 /* Check if we need an ICW4 */
215 if (!(Pic->ConfigRegister & PIC_ICW1_ICW4))
216 {
217 /* No, done initializing */
218 Pic->Initialization = FALSE;
219 }
220 return;
221 }
222
223 /* This must be an ICW4, we will ignore the 8086 bit (assume always set) */
224 if (Value & PIC_ICW4_AEOI)
225 {
226 /* Use automatic end-of-interrupt */
227 Pic->AutoEoi = TRUE;
228 }
229
230 /* Done initializing */
231 Pic->Initialization = FALSE;
232 }
233
234 VOID PicInterruptRequest(BYTE Number)
235 {
236 BYTE i;
237
238 if (Number >= 0 && Number < 8)
239 {
240 /* Check if any of the higher-priorirty interrupts are busy */
241 for (i = 0; i <= Number ; i++)
242 {
243 if (MasterPic.InServiceRegister & (1 << Number)) return;
244 }
245
246 /* Check if the interrupt is masked */
247 if (MasterPic.MaskRegister & (1 << Number)) return;
248
249 /* Set the appropriate bit in the ISR and interrupt the CPU */
250 if (!MasterPic.AutoEoi) MasterPic.InServiceRegister |= 1 << Number;
251 EmulatorInterrupt(MasterPic.IntOffset + Number);
252 }
253 else if (Number >= 8 && Number < 16)
254 {
255 Number -= 8;
256
257 /*
258 * The slave PIC is connected to IRQ 2, always! If the master PIC
259 * was misconfigured, don't do anything.
260 */
261 if (!(MasterPic.CascadeRegister & (1 << 2))
262 || SlavePic.CascadeRegister != 2)
263 {
264 return;
265 }
266
267 /* Check if any of the higher-priorirty interrupts are busy */
268 if (MasterPic.InServiceRegister != 0) return;
269 for (i = 0; i <= Number ; i++)
270 {
271 if (SlavePic.InServiceRegister & (1 << Number)) return;
272 }
273
274 /* Check if the interrupt is masked */
275 if (SlavePic.MaskRegister & (1 << Number)) return;
276
277 /* Set the IRQ 2 bit in the master ISR */
278 if (!MasterPic.AutoEoi) MasterPic.InServiceRegister |= 1 << 2;
279
280 /* Set the appropriate bit in the ISR and interrupt the CPU */
281 if (!SlavePic.AutoEoi) SlavePic.InServiceRegister |= 1 << Number;
282 EmulatorInterrupt(SlavePic.IntOffset + Number);
283 }
284 }
285
286 VOID PitWriteCommand(BYTE Value)
287 {
288 BYTE Channel = Value >> 6;
289 BYTE Mode = (Value >> 1) & 0x07;
290
291 /* Check if this is a counter latch command */
292 if (((Value >> 4) & 3) == 0)
293 {
294 PitChannels[Channel].LatchSet = TRUE;
295 PitChannels[Channel].LatchedValue = PitChannels[Channel].CurrentValue;
296 return;
297 }
298
299 /* Set the access mode and reset flip-flops */
300 PitChannels[Channel].AccessMode = (Value >> 4) & 3;
301 PitChannels[Channel].Pulsed = FALSE;
302 PitChannels[Channel].LatchSet = FALSE;
303 PitChannels[Channel].InputFlipFlop = FALSE;
304 PitChannels[Channel].OutputFlipFlop = FALSE;
305
306 switch (Mode)
307 {
308 case 0:
309 case 1:
310 case 2:
311 case 3:
312 case 4:
313 case 5:
314 {
315 PitChannels[Channel].Mode = Mode;
316 break;
317 }
318
319 case 6:
320 {
321 PitChannels[Channel].Mode = PIT_MODE_RATE_GENERATOR;
322 break;
323 }
324
325 case 7:
326 {
327 PitChannels[Channel].Mode = PIT_MODE_SQUARE_WAVE;
328 break;
329 }
330 }
331 }
332
333 BYTE PitReadData(BYTE Channel)
334 {
335 WORD CurrentValue = PitChannels[Channel].CurrentValue;
336 BYTE AccessMode = PitChannels[Channel].AccessMode;
337
338 /* Check if the value was latched */
339 if (PitChannels[Channel].LatchSet)
340 {
341 CurrentValue = PitChannels[Channel].LatchedValue;
342
343 if (AccessMode == 1 || AccessMode == 2)
344 {
345 /* The latched value was read as one byte */
346 PitChannels[Channel].LatchSet = FALSE;
347 }
348 }
349
350 /* Use the flip-flop for access mode 3 */
351 if (AccessMode == 3)
352 {
353 AccessMode = PitChannels[Channel].InputFlipFlop ? 1 : 2;
354 PitChannels[Channel].InputFlipFlop = !PitChannels[Channel].InputFlipFlop;
355
356 /* Check if this was the last read for the latched value */
357 if (!PitChannels[Channel].InputFlipFlop)
358 {
359 /* Yes, the latch value was read as two bytes */
360 PitChannels[Channel].LatchSet = FALSE;
361 }
362 }
363
364 switch (AccessMode)
365 {
366 case 1:
367 {
368 /* Low byte */
369 return CurrentValue & 0x00FF;
370 }
371
372 case 2:
373 {
374 /* High byte */
375 return CurrentValue >> 8;
376 }
377 }
378
379 /* Shouldn't get here */
380 return 0;
381 }
382
383 VOID PitWriteData(BYTE Channel, BYTE Value)
384 {
385 BYTE AccessMode = PitChannels[Channel].AccessMode;
386
387 /* Use the flip-flop for access mode 3 */
388 if (PitChannels[Channel].AccessMode == 3)
389 {
390 AccessMode = PitChannels[Channel].InputFlipFlop ? 1 : 2;
391 PitChannels[Channel].InputFlipFlop = !PitChannels[Channel].InputFlipFlop;
392 }
393
394 switch (AccessMode)
395 {
396 case 1:
397 {
398 /* Low byte */
399 PitChannels[Channel].ReloadValue &= 0xFF00;
400 PitChannels[Channel].ReloadValue |= Value;
401 break;
402 }
403
404 case 2:
405 {
406 /* High byte */
407 PitChannels[Channel].ReloadValue &= 0x00FF;
408 PitChannels[Channel].ReloadValue |= Value << 8;
409 }
410 }
411 }
412
413 VOID PitDecrementCount()
414 {
415 INT i;
416
417 for (i = 0; i < PIT_CHANNELS; i++)
418 {
419 switch (PitChannels[i].Mode)
420 {
421 case PIT_MODE_INT_ON_TERMINAL_COUNT:
422 {
423 /* Decrement the value */
424 PitChannels[i].CurrentValue--;
425
426 /* Did it fall to the terminal count? */
427 if (PitChannels[i].CurrentValue == 0 && !PitChannels[i].Pulsed)
428 {
429 /* Yes, raise the output line */
430 if (i == 0) PicInterruptRequest(0);
431 PitChannels[i].Pulsed = TRUE;
432 }
433 break;
434 }
435
436 case PIT_MODE_RATE_GENERATOR:
437 {
438 /* Decrement the value */
439 PitChannels[i].CurrentValue--;
440
441 /* Did it fall to zero? */
442 if (PitChannels[i].CurrentValue != 0) break;
443
444 /* Yes, raise the output line and reload */
445 if (i == 0) PicInterruptRequest(0);
446 PitChannels[i].CurrentValue = PitChannels[i].ReloadValue;
447
448 break;
449 }
450
451 case PIT_MODE_SQUARE_WAVE:
452 {
453 /* Decrement the value by 2 */
454 PitChannels[i].CurrentValue -= 2;
455
456 /* Did it fall to zero? */
457 if (PitChannels[i].CurrentValue != 0) break;
458
459 /* Yes, toggle the flip-flop */
460 PitChannels[i].OutputFlipFlop = !PitChannels[i].OutputFlipFlop;
461
462 /* Did this create a rising edge in the signal? */
463 if (PitChannels[i].OutputFlipFlop)
464 {
465 /* Yes, IRQ 0 if this is channel 0 */
466 if (i == 0) PicInterruptRequest(0);
467 }
468
469 /* Reload the value, but make sure it's even */
470 if (PitChannels[i].ReloadValue % 2)
471 {
472 /* It's odd, reduce it by 1 */
473 PitChannels[i].CurrentValue = PitChannels[i].ReloadValue - 1;
474 }
475 else
476 {
477 /* It was even */
478 PitChannels[i].CurrentValue = PitChannels[i].ReloadValue;
479 }
480
481 break;
482 }
483
484 case PIT_MODE_SOFTWARE_STROBE:
485 {
486 // TODO: NOT IMPLEMENTED
487 break;
488 }
489
490 case PIT_MODE_HARDWARE_ONE_SHOT:
491 case PIT_MODE_HARDWARE_STROBE:
492 {
493 /* These modes do not work on x86 PCs */
494 break;
495 }
496 }
497 }
498 }
499
500 VOID CheckForInputEvents()
501 {
502 PINPUT_RECORD Buffer;
503 HANDLE ConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
504 DWORD i, j, Count, TotalEvents;
505 BYTE ScanCode;
506
507 /* Get the number of input events */
508 if (!GetNumberOfConsoleInputEvents(ConsoleInput, &Count)) return;
509 if (Count == 0) return;
510
511 /* Allocate the buffer */
512 Buffer = (PINPUT_RECORD)HeapAlloc(GetProcessHeap(), 0, Count * sizeof(INPUT_RECORD));
513 if (Buffer == NULL) return;
514
515 /* Peek the input events */
516 if (!ReadConsoleInput(ConsoleInput, Buffer, Count, &TotalEvents)) goto Cleanup;
517
518 for (i = 0; i < TotalEvents; i++)
519 {
520 /* Check if this is a key event */
521 if (Buffer[i].EventType != KEY_EVENT) continue;
522
523 /* Get the scan code */
524 ScanCode = Buffer[i].Event.KeyEvent.wVirtualScanCode;
525
526 /* If this is a key release, set the highest bit in the scan code */
527 if (!Buffer[i].Event.KeyEvent.bKeyDown) ScanCode |= 0x80;
528
529 /* Push the scan code onto the keyboard queue */
530 for (j = 0; j < Buffer[i].Event.KeyEvent.wRepeatCount; j++)
531 {
532 KeyboardQueuePush(ScanCode);
533 }
534
535 /* Yes, IRQ 1 */
536 PicInterruptRequest(1);
537
538 /* Stop the loop */
539 break;
540 }
541
542 Cleanup:
543 HeapFree(GetProcessHeap(), 0, Buffer);
544 }