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