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