06d1e234676336f0dd7edc2ad60e1e16eeda99ea
[reactos.git] / reactos / subsystems / ntvdm / emulator.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: emulator.c
5 * PURPOSE: Minimal x86 machine emulator for the VDM
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 "callback.h"
15
16 #include "clock.h"
17 #include "bios/rom.h"
18 #include "hardware/cmos.h"
19 #include "hardware/keyboard.h"
20 #include "hardware/mouse.h"
21 #include "hardware/pic.h"
22 #include "hardware/ps2.h"
23 #include "hardware/speaker.h"
24 #include "hardware/timer.h"
25 #include "hardware/vga.h"
26
27 #include "bop.h"
28 #include "vddsup.h"
29 #include "io.h"
30
31 #include <isvbop.h>
32
33 /* PRIVATE VARIABLES **********************************************************/
34
35 FAST486_STATE EmulatorContext;
36 BOOLEAN CpuSimulate = FALSE;
37
38 /* No more than 'MaxCpuCallLevel' recursive CPU calls are allowed */
39 static const INT MaxCpuCallLevel = 32;
40 static INT CpuCallLevel = 0;
41
42 LPVOID BaseAddress = NULL;
43 BOOLEAN VdmRunning = TRUE;
44
45 static BOOLEAN A20Line = FALSE;
46 static BYTE Port61hState = 0x00;
47
48 static HANDLE InputThread = NULL;
49
50 LPCWSTR ExceptionName[] =
51 {
52 L"Division By Zero",
53 L"Debug",
54 L"Unexpected Error",
55 L"Breakpoint",
56 L"Integer Overflow",
57 L"Bound Range Exceeded",
58 L"Invalid Opcode",
59 L"FPU Not Available"
60 };
61
62 /* BOP Identifiers */
63 #define BOP_DEBUGGER 0x56 // Break into the debugger from a 16-bit app
64
65 /* PRIVATE FUNCTIONS **********************************************************/
66
67 VOID WINAPI EmulatorReadMemory(PFAST486_STATE State, ULONG Address, PVOID Buffer, ULONG Size)
68 {
69 UNREFERENCED_PARAMETER(State);
70
71 // BIG HACK!!!! To make BIOS images working correctly,
72 // until Aleksander rewrites memory management!!
73 if (Address >= 0xFFFFFFF0) Address -= 0xFFF00000;
74
75 /* If the A20 line is disabled, mask bit 20 */
76 if (!A20Line) Address &= ~(1 << 20);
77
78 /* Make sure the requested address is valid */
79 if ((Address + Size) >= MAX_ADDRESS) return;
80
81 /*
82 * Check if we are going to read the VGA memory and
83 * copy it into the virtual address space if needed.
84 */
85 if (((Address + Size) >= VgaGetVideoBaseAddress())
86 && (Address < VgaGetVideoLimitAddress()))
87 {
88 DWORD VgaAddress = max(Address, VgaGetVideoBaseAddress());
89 DWORD ActualSize = min(Address + Size - 1, VgaGetVideoLimitAddress())
90 - VgaAddress + 1;
91 LPBYTE DestBuffer = (LPBYTE)REAL_TO_PHYS(VgaAddress);
92
93 /* Read from the VGA memory */
94 VgaReadMemory(VgaAddress, DestBuffer, ActualSize);
95 }
96
97 /* Read the data from the virtual address space and store it in the buffer */
98 RtlCopyMemory(Buffer, REAL_TO_PHYS(Address), Size);
99 }
100
101 VOID WINAPI EmulatorWriteMemory(PFAST486_STATE State, ULONG Address, PVOID Buffer, ULONG Size)
102 {
103 UNREFERENCED_PARAMETER(State);
104
105 // BIG HACK!!!! To make BIOS images working correctly,
106 // until Aleksander rewrites memory management!!
107 if (Address >= 0xFFFFFFF0) Address -= 0xFFF00000;
108
109 /* If the A20 line is disabled, mask bit 20 */
110 if (!A20Line) Address &= ~(1 << 20);
111
112 /* Make sure the requested address is valid */
113 if ((Address + Size) >= MAX_ADDRESS) return;
114
115 /* Make sure we don't write to the ROM area */
116 if ((Address + Size) >= ROM_AREA_START && (Address < ROM_AREA_END)) return;
117
118 /* Read the data from the buffer and store it in the virtual address space */
119 RtlCopyMemory(REAL_TO_PHYS(Address), Buffer, Size);
120
121 /*
122 * Check if we modified the VGA memory.
123 */
124 if (((Address + Size) >= VgaGetVideoBaseAddress())
125 && (Address < VgaGetVideoLimitAddress()))
126 {
127 DWORD VgaAddress = max(Address, VgaGetVideoBaseAddress());
128 DWORD ActualSize = min(Address + Size - 1, VgaGetVideoLimitAddress())
129 - VgaAddress + 1;
130 LPBYTE SrcBuffer = (LPBYTE)REAL_TO_PHYS(VgaAddress);
131
132 /* Write to the VGA memory */
133 VgaWriteMemory(VgaAddress, SrcBuffer, ActualSize);
134 }
135 }
136
137 UCHAR WINAPI EmulatorIntAcknowledge(PFAST486_STATE State)
138 {
139 UNREFERENCED_PARAMETER(State);
140
141 /* Get the interrupt number from the PIC */
142 return PicGetInterrupt();
143 }
144
145 VOID EmulatorException(BYTE ExceptionNumber, LPWORD Stack)
146 {
147 WORD CodeSegment, InstructionPointer;
148 PBYTE Opcode;
149
150 ASSERT(ExceptionNumber < 8);
151
152 /* Get the CS:IP */
153 InstructionPointer = Stack[STACK_IP];
154 CodeSegment = Stack[STACK_CS];
155 Opcode = (PBYTE)SEG_OFF_TO_PTR(CodeSegment, InstructionPointer);
156
157 /* Display a message to the user */
158 DisplayMessage(L"Exception: %s occured at %04X:%04X\n"
159 L"Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
160 ExceptionName[ExceptionNumber],
161 CodeSegment,
162 InstructionPointer,
163 Opcode[0],
164 Opcode[1],
165 Opcode[2],
166 Opcode[3],
167 Opcode[4],
168 Opcode[5],
169 Opcode[6],
170 Opcode[7],
171 Opcode[8],
172 Opcode[9]);
173
174 /* Stop the VDM */
175 EmulatorTerminate();
176 return;
177 }
178
179 // FIXME: This function assumes 16-bit mode!!!
180 VOID EmulatorExecute(WORD Segment, WORD Offset)
181 {
182 /* Tell Fast486 to move the instruction pointer */
183 Fast486ExecuteAt(&EmulatorContext, Segment, Offset);
184 }
185
186 VOID EmulatorStep(VOID)
187 {
188 /* Dump the state for debugging purposes */
189 // Fast486DumpState(&EmulatorContext);
190
191 /* Execute the next instruction */
192 Fast486StepInto(&EmulatorContext);
193 }
194
195 VOID EmulatorSimulate(VOID)
196 {
197 if (CpuCallLevel > MaxCpuCallLevel)
198 {
199 DisplayMessage(L"Too many CPU levels of recursion (%d, expected maximum %d)",
200 CpuCallLevel, MaxCpuCallLevel);
201
202 /* Stop the VDM */
203 EmulatorTerminate();
204 return;
205 }
206 CpuCallLevel++;
207
208 CpuSimulate = TRUE;
209 while (VdmRunning && CpuSimulate) ClockUpdate();
210
211 CpuCallLevel--;
212 if (CpuCallLevel < 0) CpuCallLevel = 0;
213
214 /* This takes into account for reentrance */
215 CpuSimulate = TRUE;
216 }
217
218 VOID EmulatorUnsimulate(VOID)
219 {
220 /* Stop simulation */
221 CpuSimulate = FALSE;
222 }
223
224 VOID EmulatorTerminate(VOID)
225 {
226 /* Stop the VDM */
227 VdmRunning = FALSE;
228 }
229
230 VOID EmulatorInterrupt(BYTE Number)
231 {
232 /* Call the Fast486 API */
233 Fast486Interrupt(&EmulatorContext, Number);
234 }
235
236 VOID EmulatorInterruptSignal(VOID)
237 {
238 /* Call the Fast486 API */
239 Fast486InterruptSignal(&EmulatorContext);
240 }
241
242 VOID EmulatorSetA20(BOOLEAN Enabled)
243 {
244 A20Line = Enabled;
245 }
246
247 static VOID WINAPI EmulatorDebugBreakBop(LPWORD Stack)
248 {
249 DPRINT1("NTVDM: BOP_DEBUGGER\n");
250 DebugBreak();
251 }
252
253 static VOID WINAPI EmulatorUnsimulateBop(LPWORD Stack)
254 {
255 EmulatorUnsimulate();
256 }
257
258 static BYTE WINAPI Port61hRead(ULONG Port)
259 {
260 return Port61hState;
261 }
262
263 static VOID WINAPI Port61hWrite(ULONG Port, BYTE Data)
264 {
265 // BOOLEAN SpeakerStateChange = FALSE;
266 BYTE OldPort61hState = Port61hState;
267
268 /* Only the four lowest bytes can be written */
269 Port61hState = (Port61hState & 0xF0) | (Data & 0x0F);
270
271 if ((OldPort61hState ^ Port61hState) & 0x01)
272 {
273 DPRINT("PIT 2 Gate %s\n", Port61hState & 0x01 ? "on" : "off");
274 PitSetGate(2, !!(Port61hState & 0x01));
275 // SpeakerStateChange = TRUE;
276 }
277
278 if ((OldPort61hState ^ Port61hState) & 0x02)
279 {
280 /* There were some change for the speaker... */
281 DPRINT("Speaker %s\n", Port61hState & 0x02 ? "on" : "off");
282 // SpeakerStateChange = TRUE;
283 }
284 // if (SpeakerStateChange) SpeakerChange();
285 SpeakerChange();
286 }
287
288 static VOID WINAPI PitChan0Out(LPVOID Param, BOOLEAN State)
289 {
290 if (State)
291 {
292 DPRINT("PicInterruptRequest\n");
293 PicInterruptRequest(0); // Raise IRQ 0
294 }
295 // else < Lower IRQ 0 >
296 }
297
298 static VOID WINAPI PitChan1Out(LPVOID Param, BOOLEAN State)
299 {
300 #if 0
301 if (State)
302 {
303 /* Set bit 4 of Port 61h */
304 Port61hState |= 1 << 4;
305 }
306 else
307 {
308 /* Clear bit 4 of Port 61h */
309 Port61hState &= ~(1 << 4);
310 }
311 #else
312 Port61hState = (Port61hState & 0xEF) | (State << 4);
313 #endif
314 }
315
316 static VOID WINAPI PitChan2Out(LPVOID Param, BOOLEAN State)
317 {
318 BYTE OldPort61hState = Port61hState;
319
320 #if 0
321 if (State)
322 {
323 /* Set bit 5 of Port 61h */
324 Port61hState |= 1 << 5;
325 }
326 else
327 {
328 /* Clear bit 5 of Port 61h */
329 Port61hState &= ~(1 << 5);
330 }
331 #else
332 Port61hState = (Port61hState & 0xDF) | (State << 5);
333 #endif
334
335 if ((OldPort61hState ^ Port61hState) & 0x20)
336 {
337 DPRINT("PitChan2Out -- Port61hState changed\n");
338 SpeakerChange();
339 }
340 }
341
342
343 static DWORD
344 WINAPI
345 PumpConsoleInput(LPVOID Parameter)
346 {
347 HANDLE ConsoleInput = (HANDLE)Parameter;
348 INPUT_RECORD InputRecord;
349 DWORD Count;
350
351 while (VdmRunning)
352 {
353 /* Make sure the task event is signaled */
354 WaitForSingleObject(VdmTaskEvent, INFINITE);
355
356 /* Wait for an input record */
357 if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
358 {
359 DWORD LastError = GetLastError();
360 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
361 return LastError;
362 }
363
364 ASSERT(Count != 0);
365
366 /* Check the event type */
367 switch (InputRecord.EventType)
368 {
369 /*
370 * Hardware events
371 */
372 case KEY_EVENT:
373 KeyboardEventHandler(&InputRecord.Event.KeyEvent);
374 break;
375
376 case MOUSE_EVENT:
377 MouseEventHandler(&InputRecord.Event.MouseEvent);
378 break;
379
380 case WINDOW_BUFFER_SIZE_EVENT:
381 ScreenEventHandler(&InputRecord.Event.WindowBufferSizeEvent);
382 break;
383
384 /*
385 * Interface events
386 */
387 case MENU_EVENT:
388 MenuEventHandler(&InputRecord.Event.MenuEvent);
389 break;
390
391 case FOCUS_EVENT:
392 FocusEventHandler(&InputRecord.Event.FocusEvent);
393 break;
394
395 default:
396 break;
397 }
398 }
399
400 return 0;
401 }
402
403 static VOID EnableExtraHardware(HANDLE ConsoleInput)
404 {
405 DWORD ConInMode;
406
407 if (GetConsoleMode(ConsoleInput, &ConInMode))
408 {
409 #if 0
410 // GetNumberOfConsoleMouseButtons();
411 // GetSystemMetrics(SM_CMOUSEBUTTONS);
412 // GetSystemMetrics(SM_MOUSEPRESENT);
413 if (MousePresent)
414 {
415 #endif
416 /* Support mouse input events if there is a mouse on the system */
417 ConInMode |= ENABLE_MOUSE_INPUT;
418 #if 0
419 }
420 else
421 {
422 /* Do not support mouse input events if there is no mouse on the system */
423 ConInMode &= ~ENABLE_MOUSE_INPUT;
424 }
425 #endif
426
427 SetConsoleMode(ConsoleInput, ConInMode);
428 }
429 }
430
431 /* PUBLIC FUNCTIONS ***********************************************************/
432
433 static VOID
434 DumpMemoryRaw(HANDLE hFile)
435 {
436 PVOID Buffer;
437 SIZE_T Size;
438
439 /* Dump the VM memory */
440 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
441 Buffer = REAL_TO_PHYS(NULL);
442 Size = MAX_ADDRESS - (ULONG_PTR)(NULL);
443 WriteFile(hFile, Buffer, Size, &Size, NULL);
444 }
445
446 static VOID
447 DumpMemoryTxt(HANDLE hFile)
448 {
449 #define LINE_SIZE 75 + 2
450 ULONG i;
451 PBYTE Ptr1, Ptr2;
452 CHAR LineBuffer[LINE_SIZE];
453 PCHAR Line;
454 SIZE_T LineSize;
455
456 /* Dump the VM memory */
457 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
458 Ptr1 = Ptr2 = REAL_TO_PHYS(NULL);
459 while (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0)
460 {
461 Ptr1 = Ptr2;
462 Line = LineBuffer;
463
464 /* Print the address */
465 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, "%08x ", PHYS_TO_REAL(Ptr1));
466
467 /* Print up to 16 bytes... */
468
469 /* ... in hexadecimal form first... */
470 i = 0;
471 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0))
472 {
473 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, " %02x", *Ptr1);
474 ++Ptr1;
475 }
476
477 /* ... align with spaces if needed... */
478 RtlFillMemory(Line, 0x0F + 4 - i, ' ');
479 Line += 0x0F + 4 - i;
480
481 /* ... then in character form. */
482 i = 0;
483 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr2) > 0))
484 {
485 *Line++ = ((*Ptr2 >= 0x20 && *Ptr2 <= 0x7E) || (*Ptr2 >= 0x80 && *Ptr2 < 0xFF) ? *Ptr2 : '.');
486 ++Ptr2;
487 }
488
489 /* Newline */
490 *Line++ = '\r';
491 *Line++ = '\n';
492
493 /* Finally write the line to the file */
494 LineSize = Line - LineBuffer;
495 WriteFile(hFile, LineBuffer, LineSize, &LineSize, NULL);
496 }
497 }
498
499 VOID DumpMemory(BOOLEAN TextFormat)
500 {
501 static ULONG DumpNumber = 0;
502
503 HANDLE hFile;
504 WCHAR FileName[MAX_PATH];
505
506 /* Build a suitable file name */
507 _snwprintf(FileName, MAX_PATH,
508 L"memdump%lu.%s",
509 DumpNumber,
510 TextFormat ? L"txt" : L"dat");
511 ++DumpNumber;
512
513 DPRINT1("Creating memory dump file '%S'...\n", FileName);
514
515 /* Always create the dump file */
516 hFile = CreateFileW(FileName,
517 GENERIC_WRITE,
518 0,
519 NULL,
520 CREATE_ALWAYS,
521 FILE_ATTRIBUTE_NORMAL,
522 NULL);
523
524 if (hFile == INVALID_HANDLE_VALUE)
525 {
526 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
527 FileName, GetLastError());
528 return;
529 }
530
531 /* Dump the VM memory in the chosen format */
532 if (TextFormat)
533 DumpMemoryTxt(hFile);
534 else
535 DumpMemoryRaw(hFile);
536
537 /* Close the file */
538 CloseHandle(hFile);
539
540 DPRINT1("Memory dump done\n");
541 }
542
543 BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
544 {
545 /* Allocate memory for the 16-bit address space */
546 BaseAddress = HeapAlloc(GetProcessHeap(), /*HEAP_ZERO_MEMORY*/ 0, MAX_ADDRESS);
547 if (BaseAddress == NULL)
548 {
549 wprintf(L"FATAL: Failed to allocate VDM memory.\n");
550 return FALSE;
551 }
552 /*
553 * For diagnostics purposes, we fill the memory with INT 0x03 codes
554 * so that if a program wants to execute random code in memory, we can
555 * retrieve the exact CS:IP where the problem happens.
556 */
557 RtlFillMemory(BaseAddress, MAX_ADDRESS, 0xCC);
558
559 /* Initialize I/O ports */
560 /* Initialize RAM */
561
562 /* Initialize the internal clock */
563 if (!ClockInitialize())
564 {
565 wprintf(L"FATAL: Failed to initialize the clock\n");
566 return FALSE;
567 }
568
569 /* Initialize the CPU */
570 Fast486Initialize(&EmulatorContext,
571 EmulatorReadMemory,
572 EmulatorWriteMemory,
573 EmulatorReadIo,
574 EmulatorWriteIo,
575 NULL,
576 EmulatorBiosOperation,
577 EmulatorIntAcknowledge,
578 NULL /* TODO: Use a TLB */);
579
580 /* Initialize DMA */
581
582 /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
583 PicInitialize();
584 PitInitialize();
585 CmosInitialize();
586 SpeakerInitialize();
587
588 /* Set output functions */
589 PitSetOutFunction(0, NULL, PitChan0Out);
590 PitSetOutFunction(1, NULL, PitChan1Out);
591 PitSetOutFunction(2, NULL, PitChan2Out);
592
593 /* Register the I/O Ports */
594 RegisterIoPort(CONTROL_SYSTEM_PORT61H, Port61hRead, Port61hWrite);
595
596 /* Set the console input mode */
597 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
598 // upon console window events (screen buffer resize, ...).
599 SetConsoleMode(ConsoleInput, ENABLE_PROCESSED_INPUT /* | ENABLE_WINDOW_INPUT */);
600 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
601
602 /**/EnableExtraHardware(ConsoleInput);/**/
603
604 /* Initialize the PS/2 port */
605 PS2Initialize();
606
607 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
608 KeyboardInit(0);
609 MouseInit(1);
610
611 /**************** ATTACH INPUT WITH CONSOLE *****************/
612 /* Start the input thread */
613 InputThread = CreateThread(NULL, 0, &PumpConsoleInput, ConsoleInput, 0, NULL);
614 if (InputThread == NULL)
615 {
616 DisplayMessage(L"Failed to create the console input thread.");
617 return FALSE;
618 }
619 /************************************************************/
620
621 /* Initialize the VGA */
622 if (!VgaInitialize(ConsoleOutput))
623 {
624 DisplayMessage(L"Failed to initialize VGA support.");
625 return FALSE;
626 }
627
628 /* Initialize the software callback system and register the emulator BOPs */
629 InitializeCallbacks();
630 RegisterBop(BOP_DEBUGGER , EmulatorDebugBreakBop);
631 RegisterBop(BOP_UNSIMULATE, EmulatorUnsimulateBop);
632
633 /* Initialize VDD support */
634 VDDSupInitialize();
635
636 return TRUE;
637 }
638
639 VOID EmulatorCleanup(VOID)
640 {
641 VgaCleanup();
642
643 /* Close the input thread handle */
644 if (InputThread != NULL) CloseHandle(InputThread);
645 InputThread = NULL;
646
647 PS2Cleanup();
648
649 SpeakerCleanup();
650 CmosCleanup();
651 // PitCleanup();
652 // PicCleanup();
653
654 // Fast486Cleanup();
655
656 /* Free the memory allocated for the 16-bit address space */
657 if (BaseAddress != NULL) HeapFree(GetProcessHeap(), 0, BaseAddress);
658 }
659
660
661
662 VOID
663 WINAPI
664 VDDSimulate16(VOID)
665 {
666 EmulatorSimulate();
667 }
668
669 VOID
670 WINAPI
671 VDDTerminateVDM(VOID)
672 {
673 /* Stop the VDM */
674 EmulatorTerminate();
675 }
676
677 PBYTE
678 WINAPI
679 Sim32pGetVDMPointer(IN ULONG Address,
680 IN BOOLEAN ProtectedMode)
681 {
682 // FIXME
683 UNREFERENCED_PARAMETER(ProtectedMode);
684
685 /*
686 * HIWORD(Address) == Segment (if ProtectedMode == FALSE)
687 * or Selector (if ProtectedMode == TRUE )
688 * LOWORD(Address) == Offset
689 */
690 return (PBYTE)FAR_POINTER(Address);
691 }
692
693 PBYTE
694 WINAPI
695 MGetVdmPointer(IN ULONG Address,
696 IN ULONG Size,
697 IN BOOLEAN ProtectedMode)
698 {
699 UNREFERENCED_PARAMETER(Size);
700 return Sim32pGetVDMPointer(Address, ProtectedMode);
701 }
702
703 PVOID
704 WINAPI
705 VdmMapFlat(IN USHORT Segment,
706 IN ULONG Offset,
707 IN VDM_MODE Mode)
708 {
709 // FIXME
710 UNREFERENCED_PARAMETER(Mode);
711
712 return SEG_OFF_TO_PTR(Segment, Offset);
713 }
714
715 BOOL
716 WINAPI
717 VdmFlushCache(IN USHORT Segment,
718 IN ULONG Offset,
719 IN ULONG Size,
720 IN VDM_MODE Mode)
721 {
722 // FIXME
723 UNIMPLEMENTED;
724 return TRUE;
725 }
726
727 BOOL
728 WINAPI
729 VdmUnmapFlat(IN USHORT Segment,
730 IN ULONG Offset,
731 IN PVOID Buffer,
732 IN VDM_MODE Mode)
733 {
734 // FIXME
735 UNIMPLEMENTED;
736 return TRUE;
737 }
738
739 /* EOF */