xref: /reactos/subsystems/mvdm/ntvdm/emulator.c (revision d2c71d76)
1 /*
2  * COPYRIGHT:       GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/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 #include "ntvdm.h"
12 
13 #define NDEBUG
14 #include <debug.h>
15 
16 #include "emulator.h"
17 #include "memory.h"
18 
19 #include "cpu/callback.h"
20 #include "cpu/cpu.h"
21 #include "cpu/bop.h"
22 #include <isvbop.h>
23 
24 #include "int32.h"
25 
26 #include "clock.h"
27 #include "bios/rom.h"
28 #include "hardware/cmos.h"
29 #include "hardware/disk.h"
30 #include "hardware/dma.h"
31 #include "hardware/keyboard.h"
32 #include "hardware/mouse.h"
33 #include "hardware/pic.h"
34 #include "hardware/pit.h"
35 #include "hardware/ppi.h"
36 #include "hardware/ps2.h"
37 #include "hardware/sound/speaker.h"
38 #include "hardware/video/svga.h"
39 /**/
40 #include "./console/video.h"
41 /**/
42 
43 #include "vddsup.h"
44 #include "io.h"
45 
46 /* PRIVATE VARIABLES **********************************************************/
47 
48 LPVOID  BaseAddress = NULL;
49 BOOLEAN VdmRunning  = TRUE;
50 
51 HANDLE VdmTaskEvent = NULL;
52 static HANDLE InputThread = NULL;
53 
54 LPCWSTR ExceptionName[] =
55 {
56     L"Division By Zero",
57     L"Debug",
58     L"Unexpected Error",
59     L"Breakpoint",
60     L"Integer Overflow",
61     L"Bound Range Exceeded",
62     L"Invalid Opcode",
63     L"FPU Not Available"
64 };
65 
66 /* BOP Identifiers */
67 #define BOP_DEBUGGER    0x56    // Break into the debugger from a 16-bit app
68 
69 /* PRIVATE FUNCTIONS **********************************************************/
70 
71 UCHAR FASTCALL EmulatorIntAcknowledge(PFAST486_STATE State)
72 {
73     UNREFERENCED_PARAMETER(State);
74 
75     /* Get the interrupt number from the PIC */
76     return PicGetInterrupt();
77 }
78 
79 VOID FASTCALL EmulatorFpu(PFAST486_STATE State)
80 {
81     /* The FPU is wired to IRQ 13 */
82     PicInterruptRequest(13);
83 }
84 
85 VOID EmulatorException(BYTE ExceptionNumber, LPWORD Stack)
86 {
87     WORD CodeSegment, InstructionPointer;
88     PBYTE Opcode;
89 
90     ASSERT(ExceptionNumber < 8);
91 
92     /* Get the CS:IP */
93     InstructionPointer = Stack[STACK_IP];
94     CodeSegment = Stack[STACK_CS];
95     Opcode = (PBYTE)SEG_OFF_TO_PTR(CodeSegment, InstructionPointer);
96 
97     /* Display a message to the user */
98     DisplayMessage(L"Exception: %s occurred at %04X:%04X\n"
99                    L"Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
100                    ExceptionName[ExceptionNumber],
101                    CodeSegment,
102                    InstructionPointer,
103                    Opcode[0],
104                    Opcode[1],
105                    Opcode[2],
106                    Opcode[3],
107                    Opcode[4],
108                    Opcode[5],
109                    Opcode[6],
110                    Opcode[7],
111                    Opcode[8],
112                    Opcode[9]);
113 
114     Fast486DumpState(&EmulatorContext);
115 
116     /* Stop the VDM */
117     EmulatorTerminate();
118 }
119 
120 VOID EmulatorInterruptSignal(VOID)
121 {
122     /* Call the Fast486 API */
123     Fast486InterruptSignal(&EmulatorContext);
124 }
125 
126 static VOID WINAPI EmulatorDebugBreakBop(LPWORD Stack)
127 {
128     DPRINT1("NTVDM: BOP_DEBUGGER\n");
129     DebugBreak();
130 }
131 
132 static VOID WINAPI PitChan0Out(LPVOID Param, BOOLEAN State)
133 {
134     if (State)
135     {
136         DPRINT("PicInterruptRequest\n");
137         PicInterruptRequest(0); // Raise IRQ 0
138     }
139     // else < Lower IRQ 0 >
140 }
141 
142 static VOID WINAPI PitChan1Out(LPVOID Param, BOOLEAN State)
143 {
144 #if 0
145     if (State)
146     {
147         /* Set bit 4 of Port 61h */
148         Port61hState |= 1 << 4;
149     }
150     else
151     {
152         /* Clear bit 4 of Port 61h */
153         Port61hState &= ~(1 << 4);
154     }
155 #else
156     Port61hState = (Port61hState & 0xEF) | (State << 4);
157 #endif
158 }
159 
160 static VOID WINAPI PitChan2Out(LPVOID Param, BOOLEAN State)
161 {
162     BYTE OldPort61hState = Port61hState;
163 
164 #if 0
165     if (State)
166     {
167         /* Set bit 5 of Port 61h */
168         Port61hState |= 1 << 5;
169     }
170     else
171     {
172         /* Clear bit 5 of Port 61h */
173         Port61hState &= ~(1 << 5);
174     }
175 #else
176     Port61hState = (Port61hState & 0xDF) | (State << 5);
177 #endif
178 
179     if ((OldPort61hState ^ Port61hState) & 0x20)
180     {
181         DPRINT("PitChan2Out -- Port61hState changed\n");
182         SpeakerChange(Port61hState);
183     }
184 }
185 
186 
187 static DWORD
188 WINAPI
189 ConsoleEventThread(LPVOID Parameter)
190 {
191     HANDLE ConsoleInput = (HANDLE)Parameter;
192     HANDLE WaitHandles[2];
193     DWORD  WaitResult;
194 
195     /*
196      * For optimization purposes, Windows (and hence ReactOS, too, for
197      * compatibility reasons) uses a static buffer if no more than five
198      * input records are read. Otherwise a new buffer is used.
199      * The client-side expects that we know this behaviour.
200      * See consrv/coninput.c
201      *
202      * We exploit here this optimization by also using a buffer of 5 records.
203      */
204     INPUT_RECORD InputRecords[5];
205     ULONG NumRecords, i;
206 
207     WaitHandles[0] = VdmTaskEvent;
208     WaitHandles[1] = GetConsoleInputWaitHandle();
209 
210     while (VdmRunning)
211     {
212         /* Make sure the task event is signaled */
213         WaitResult = WaitForMultipleObjects(ARRAYSIZE(WaitHandles),
214                                             WaitHandles,
215                                             TRUE,
216                                             INFINITE);
217         switch (WaitResult)
218         {
219             case WAIT_OBJECT_0 + 0:
220             case WAIT_OBJECT_0 + 1:
221                 break;
222             default:
223                 return GetLastError();
224         }
225 
226         /* Wait for an input record */
227         if (!ReadConsoleInputExW(ConsoleInput,
228                                  InputRecords,
229                                  ARRAYSIZE(InputRecords),
230                                  &NumRecords,
231                                  CONSOLE_READ_CONTINUE))
232         {
233             DWORD LastError = GetLastError();
234             DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, NumRecords, LastError);
235             return LastError;
236         }
237 
238         // ASSERT(NumRecords != 0);
239         if (NumRecords == 0)
240         {
241             DPRINT1("Got NumRecords == 0!\n");
242             continue;
243         }
244 
245         /* Dispatch the events */
246         for (i = 0; i < NumRecords; i++)
247         {
248             /* Check the event type */
249             switch (InputRecords[i].EventType)
250             {
251                 /*
252                  * Hardware events
253                  */
254                 case KEY_EVENT:
255                     KeyboardEventHandler(&InputRecords[i].Event.KeyEvent);
256                     break;
257 
258                 case MOUSE_EVENT:
259                     MouseEventHandler(&InputRecords[i].Event.MouseEvent);
260                     break;
261 
262                 case WINDOW_BUFFER_SIZE_EVENT:
263                     ScreenEventHandler(&InputRecords[i].Event.WindowBufferSizeEvent);
264                     break;
265 
266                 /*
267                  * Interface events
268                  */
269                 case MENU_EVENT:
270                     MenuEventHandler(&InputRecords[i].Event.MenuEvent);
271                     break;
272 
273                 case FOCUS_EVENT:
274                     FocusEventHandler(&InputRecords[i].Event.FocusEvent);
275                     break;
276 
277                 default:
278                     DPRINT1("Unknown input event type 0x%04x\n", InputRecords[i].EventType);
279                     break;
280             }
281         }
282 
283         /* Let the console subsystem queue some new events */
284         Sleep(10);
285     }
286 
287     return 0;
288 }
289 
290 static VOID PauseEventThread(VOID)
291 {
292     ResetEvent(VdmTaskEvent);
293 }
294 
295 static VOID ResumeEventThread(VOID)
296 {
297     SetEvent(VdmTaskEvent);
298 }
299 
300 
301 /* PUBLIC FUNCTIONS ***********************************************************/
302 
303 static VOID
304 DumpMemoryRaw(HANDLE hFile)
305 {
306     PVOID  Buffer;
307     DWORD Size;
308 
309     /* Dump the VM memory */
310     SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
311     Buffer = REAL_TO_PHYS(NULL);
312     Size   = MAX_ADDRESS - (ULONG_PTR)(NULL);
313     WriteFile(hFile, Buffer, Size, &Size, NULL);
314 }
315 
316 static VOID
317 DumpMemoryTxt(HANDLE hFile)
318 {
319 #define LINE_SIZE   75 + 2
320     ULONG  i;
321     PBYTE  Ptr1, Ptr2;
322     CHAR   LineBuffer[LINE_SIZE];
323     PCHAR  Line;
324     DWORD LineSize;
325 
326     /* Dump the VM memory */
327     SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
328     Ptr1 = Ptr2 = REAL_TO_PHYS(NULL);
329     while (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0)
330     {
331         Ptr1 = Ptr2;
332         Line = LineBuffer;
333 
334         /* Print the address */
335         Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, "%08Ix ", (ULONG_PTR)PHYS_TO_REAL(Ptr1));
336 
337         /* Print up to 16 bytes... */
338 
339         /* ... in hexadecimal form first... */
340         i = 0;
341         while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0))
342         {
343             Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, " %02x", *Ptr1);
344             ++Ptr1;
345         }
346 
347         /* ... align with spaces if needed... */
348         RtlFillMemory(Line, (0x0F + 2 - i) * 3 + 2, ' ');
349         Line += (0x0F + 2 - i) * 3 + 2;
350 
351         /* ... then in character form. */
352         i = 0;
353         while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr2) > 0))
354         {
355             *Line++ = ((*Ptr2 >= 0x20 && *Ptr2 <= 0x7E) || (*Ptr2 >= 0x80 && *Ptr2 < 0xFF) ? *Ptr2 : '.');
356             ++Ptr2;
357         }
358 
359         /* Newline */
360         *Line++ = '\r';
361         *Line++ = '\n';
362 
363         /* Finally write the line to the file */
364         LineSize = Line - LineBuffer;
365         WriteFile(hFile, LineBuffer, LineSize, &LineSize, NULL);
366     }
367 }
368 
369 VOID DumpMemory(BOOLEAN TextFormat)
370 {
371     static ULONG DumpNumber = 0;
372 
373     HANDLE hFile;
374     WCHAR  FileName[MAX_PATH];
375 
376     /* Build a suitable file name */
377     _snwprintf(FileName, MAX_PATH,
378                L"memdump%lu.%s",
379                DumpNumber,
380                TextFormat ? L"txt" : L"dat");
381     ++DumpNumber;
382 
383     DPRINT1("Creating memory dump file '%S'...\n", FileName);
384 
385     /* Always create the dump file */
386     hFile = CreateFileW(FileName,
387                         GENERIC_WRITE,
388                         0,
389                         NULL,
390                         CREATE_ALWAYS,
391                         FILE_ATTRIBUTE_NORMAL,
392                         NULL);
393 
394     if (hFile == INVALID_HANDLE_VALUE)
395     {
396         DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
397                 FileName, GetLastError());
398         return;
399     }
400 
401     /* Dump the VM memory in the chosen format */
402     if (TextFormat)
403         DumpMemoryTxt(hFile);
404     else
405         DumpMemoryRaw(hFile);
406 
407     /* Close the file */
408     CloseHandle(hFile);
409 
410     DPRINT1("Memory dump done\n");
411 }
412 
413 VOID MountFloppy(IN ULONG DiskNumber)
414 {
415 // FIXME: This should be present in PSDK commdlg.h
416 //
417 // FlagsEx Values
418 #if (_WIN32_WINNT >= 0x0500)
419 #define  OFN_EX_NOPLACESBAR         0x00000001
420 #endif // (_WIN32_WINNT >= 0x0500)
421 
422     BOOLEAN Success;
423     OPENFILENAMEW ofn;
424     WCHAR szFile[MAX_PATH] = L"";
425 
426     ASSERT(DiskNumber < ARRAYSIZE(GlobalSettings.FloppyDisks));
427 
428     RtlZeroMemory(&ofn, sizeof(ofn));
429     ofn.lStructSize  = sizeof(ofn);
430     ofn.hwndOwner    = hConsoleWnd;
431     ofn.lpstrTitle   = L"Select a virtual floppy image";
432     ofn.Flags        = OFN_EXPLORER | OFN_ENABLESIZING | OFN_LONGNAMES | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
433 //  ofn.FlagsEx      = OFN_EX_NOPLACESBAR;
434     ofn.lpstrFilter  = L"Virtual floppy images (*.vfd;*.img;*.ima;*.dsk)\0*.vfd;*.img;*.ima;*.dsk\0All files (*.*)\0*.*\0\0";
435     ofn.lpstrDefExt  = L"vfd";
436     ofn.nFilterIndex = 0;
437     ofn.lpstrFile    = szFile;
438     ofn.nMaxFile     = ARRAYSIZE(szFile);
439 
440     if (!GetOpenFileNameW(&ofn))
441     {
442         DPRINT1("CommDlgExtendedError = %d\n", CommDlgExtendedError());
443         return;
444     }
445 
446     /* Free the old string */
447     if (GlobalSettings.FloppyDisks[DiskNumber].Buffer)
448         RtlFreeUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber]);
449 
450     /* Reinitialize the string */
451     Success = RtlCreateUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber], szFile);
452     ASSERT(Success);
453 
454     /* Mount the disk */
455     if (!MountDisk(FLOPPY_DISK, DiskNumber, GlobalSettings.FloppyDisks[DiskNumber].Buffer, !!(ofn.Flags & OFN_READONLY)))
456     {
457         DisplayMessage(L"An error happened when mounting disk %d", DiskNumber);
458         RtlFreeUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber]);
459         RtlInitEmptyUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber], NULL, 0);
460         return;
461     }
462 
463     /* Refresh the menu state */
464     UpdateVdmMenuDisks();
465 }
466 
467 VOID EjectFloppy(IN ULONG DiskNumber)
468 {
469     ASSERT(DiskNumber < ARRAYSIZE(GlobalSettings.FloppyDisks));
470 
471     /* Unmount the disk */
472     if (!UnmountDisk(FLOPPY_DISK, DiskNumber))
473         DisplayMessage(L"An error happened when ejecting disk %d", DiskNumber);
474 
475     /* Free the old string */
476     if (GlobalSettings.FloppyDisks[DiskNumber].Buffer)
477     {
478         RtlFreeUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber]);
479         RtlInitEmptyUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber], NULL, 0);
480     }
481 
482     /* Refresh the menu state */
483     UpdateVdmMenuDisks();
484 }
485 
486 
487 VOID EmulatorPause(VOID)
488 {
489     /* Pause the VDM */
490     VDDBlockUserHook();
491     VgaRefreshDisplay();
492     PauseEventThread();
493 }
494 
495 VOID EmulatorResume(VOID)
496 {
497     /* Resume the VDM */
498     ResumeEventThread();
499     VgaRefreshDisplay();
500     VDDResumeUserHook();
501 }
502 
503 VOID EmulatorTerminate(VOID)
504 {
505     /* Stop the VDM */
506     CpuUnsimulate(); // Halt the CPU
507     VdmRunning = FALSE;
508 }
509 
510 BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
511 {
512     USHORT i;
513 
514     /* Initialize memory */
515     if (!MemInitialize())
516     {
517         wprintf(L"Memory initialization failed.\n");
518         return FALSE;
519     }
520 
521     /* Initialize I/O ports */
522     /* Initialize RAM */
523 
524     /* Initialize the CPU */
525 
526     /* Initialize the internal clock */
527     if (!ClockInitialize())
528     {
529         wprintf(L"FATAL: Failed to initialize the clock\n");
530         EmulatorCleanup();
531         return FALSE;
532     }
533 
534     /* Initialize the CPU */
535     CpuInitialize();
536 
537     /* Initialize DMA */
538     DmaInitialize();
539 
540     /* Initialize PIC, PIT, CMOS, PC Speaker and PS/2 */
541     PicInitialize();
542 
543     PitInitialize();
544     PitSetOutFunction(0, NULL, PitChan0Out);
545     PitSetOutFunction(1, NULL, PitChan1Out);
546     PitSetOutFunction(2, NULL, PitChan2Out);
547 
548     CmosInitialize();
549     SpeakerInitialize();
550     PpiInitialize();
551 
552     PS2Initialize();
553 
554     /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
555     KeyboardInit(0);
556     MouseInit(1);
557 
558     /**************** ATTACH INPUT WITH CONSOLE *****************/
559     /* Create the task event */
560     VdmTaskEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
561     ASSERT(VdmTaskEvent != NULL);
562 
563     /* Start the input thread */
564     InputThread = CreateThread(NULL, 0, &ConsoleEventThread, ConsoleInput, 0, NULL);
565     if (InputThread == NULL)
566     {
567         wprintf(L"FATAL: Failed to create the console input thread.\n");
568         EmulatorCleanup();
569         return FALSE;
570     }
571     ResumeEventThread();
572     /************************************************************/
573 
574     /* Initialize the VGA */
575     if (!VgaInitialize(ConsoleOutput))
576     {
577         wprintf(L"FATAL: Failed to initialize VGA support.\n");
578         EmulatorCleanup();
579         return FALSE;
580     }
581 
582     /* Initialize the disk controller */
583     if (!DiskCtrlInitialize())
584     {
585         wprintf(L"FATAL: Failed to completely initialize the disk controller.\n");
586         EmulatorCleanup();
587         return FALSE;
588     }
589 
590     /* Mount the available floppy disks */
591     for (i = 0; i < ARRAYSIZE(GlobalSettings.FloppyDisks); ++i)
592     {
593         if (GlobalSettings.FloppyDisks[i].Length != 0 &&
594             GlobalSettings.FloppyDisks[i].Buffer      &&
595            *GlobalSettings.FloppyDisks[i].Buffer != L'\0')
596         {
597             if (!MountDisk(FLOPPY_DISK, i, GlobalSettings.FloppyDisks[i].Buffer, FALSE))
598             {
599                 DPRINT1("Failed to mount floppy disk file '%wZ'.\n", &GlobalSettings.FloppyDisks[i]);
600                 RtlFreeUnicodeString(&GlobalSettings.FloppyDisks[i]);
601                 RtlInitEmptyUnicodeString(&GlobalSettings.FloppyDisks[i], NULL, 0);
602             }
603         }
604     }
605 
606     /*
607      * Mount the available hard disks. Contrary to floppies, failing
608      * mounting a hard disk is considered as an unrecoverable error.
609      */
610     for (i = 0; i < ARRAYSIZE(GlobalSettings.HardDisks); ++i)
611     {
612         if (GlobalSettings.HardDisks[i].Length != 0 &&
613             GlobalSettings.HardDisks[i].Buffer      &&
614            *GlobalSettings.HardDisks[i].Buffer != L'\0')
615         {
616             if (!MountDisk(HARD_DISK, i, GlobalSettings.HardDisks[i].Buffer, FALSE))
617             {
618                 wprintf(L"FATAL: Failed to mount hard disk file '%wZ'.\n", &GlobalSettings.HardDisks[i]);
619                 EmulatorCleanup();
620                 return FALSE;
621             }
622         }
623     }
624 
625     /* Refresh the menu state */
626     UpdateVdmMenuDisks();
627 
628     /* Initialize the software callback system and register the emulator BOPs */
629     InitializeInt32();
630     RegisterBop(BOP_DEBUGGER  , EmulatorDebugBreakBop);
631     // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop);
632 
633     /* Initialize VDD support */
634     VDDSupInitialize();
635 
636     return TRUE;
637 }
638 
639 VOID EmulatorCleanup(VOID)
640 {
641     DiskCtrlCleanup();
642 
643     VgaCleanup();
644 
645     /* Close the input thread handle */
646     if (InputThread != NULL) CloseHandle(InputThread);
647     InputThread = NULL;
648 
649     /* Close the task event */
650     if (VdmTaskEvent != NULL) CloseHandle(VdmTaskEvent);
651     VdmTaskEvent = NULL;
652 
653     PS2Cleanup();
654 
655     SpeakerCleanup();
656     CmosCleanup();
657     // PitCleanup();
658     // PicCleanup();
659 
660     // DmaCleanup();
661 
662     CpuCleanup();
663     MemCleanup();
664 }
665 
666 
667 
668 VOID
669 WINAPI
670 VDDSimulate16(VOID)
671 {
672     CpuSimulate();
673 }
674 
675 VOID
676 WINAPI
677 VDDTerminateVDM(VOID)
678 {
679     /* Stop the VDM */
680     EmulatorTerminate();
681 }
682 
683 /* EOF */
684