b90d8e8313342854675d3a8383dddec23f053e69
[reactos.git] / subsystems / mvdm / ntvdm / emulator.c
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 SIZE_T 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 SIZE_T 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, "%08x ", 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 != '\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 */