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>
9 /* INCLUDES *******************************************************************/
19 #include "cpu/callback.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"
40 #include "./console/video.h"
46 /* PRIVATE VARIABLES **********************************************************/
48 LPVOID BaseAddress
= NULL
;
49 BOOLEAN VdmRunning
= TRUE
;
51 HANDLE VdmTaskEvent
= NULL
;
52 static HANDLE InputThread
= NULL
;
54 LPCWSTR ExceptionName
[] =
61 L
"Bound Range Exceeded",
67 #define BOP_DEBUGGER 0x56 // Break into the debugger from a 16-bit app
69 /* PRIVATE FUNCTIONS **********************************************************/
71 UCHAR FASTCALL
EmulatorIntAcknowledge(PFAST486_STATE State
)
73 UNREFERENCED_PARAMETER(State
);
75 /* Get the interrupt number from the PIC */
76 return PicGetInterrupt();
79 VOID FASTCALL
EmulatorFpu(PFAST486_STATE State
)
81 /* The FPU is wired to IRQ 13 */
82 PicInterruptRequest(13);
85 VOID
EmulatorException(BYTE ExceptionNumber
, LPWORD Stack
)
87 WORD CodeSegment
, InstructionPointer
;
90 ASSERT(ExceptionNumber
< 8);
93 InstructionPointer
= Stack
[STACK_IP
];
94 CodeSegment
= Stack
[STACK_CS
];
95 Opcode
= (PBYTE
)SEG_OFF_TO_PTR(CodeSegment
, InstructionPointer
);
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
],
114 Fast486DumpState(&EmulatorContext
);
120 VOID
EmulatorInterruptSignal(VOID
)
122 /* Call the Fast486 API */
123 Fast486InterruptSignal(&EmulatorContext
);
126 static VOID WINAPI
EmulatorDebugBreakBop(LPWORD Stack
)
128 DPRINT1("NTVDM: BOP_DEBUGGER\n");
132 static VOID WINAPI
PitChan0Out(LPVOID Param
, BOOLEAN State
)
136 DPRINT("PicInterruptRequest\n");
137 PicInterruptRequest(0); // Raise IRQ 0
139 // else < Lower IRQ 0 >
142 static VOID WINAPI
PitChan1Out(LPVOID Param
, BOOLEAN State
)
147 /* Set bit 4 of Port 61h */
148 Port61hState
|= 1 << 4;
152 /* Clear bit 4 of Port 61h */
153 Port61hState
&= ~(1 << 4);
156 Port61hState
= (Port61hState
& 0xEF) | (State
<< 4);
160 static VOID WINAPI
PitChan2Out(LPVOID Param
, BOOLEAN State
)
162 BYTE OldPort61hState
= Port61hState
;
167 /* Set bit 5 of Port 61h */
168 Port61hState
|= 1 << 5;
172 /* Clear bit 5 of Port 61h */
173 Port61hState
&= ~(1 << 5);
176 Port61hState
= (Port61hState
& 0xDF) | (State
<< 5);
179 if ((OldPort61hState
^ Port61hState
) & 0x20)
181 DPRINT("PitChan2Out -- Port61hState changed\n");
182 SpeakerChange(Port61hState
);
189 ConsoleEventThread(LPVOID Parameter
)
191 HANDLE ConsoleInput
= (HANDLE
)Parameter
;
192 HANDLE WaitHandles
[2];
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
202 * We exploit here this optimization by also using a buffer of 5 records.
204 INPUT_RECORD InputRecords
[5];
207 WaitHandles
[0] = VdmTaskEvent
;
208 WaitHandles
[1] = GetConsoleInputWaitHandle();
212 /* Make sure the task event is signaled */
213 WaitResult
= WaitForMultipleObjects(ARRAYSIZE(WaitHandles
),
219 case WAIT_OBJECT_0
+ 0:
220 case WAIT_OBJECT_0
+ 1:
223 return GetLastError();
226 /* Wait for an input record */
227 if (!ReadConsoleInputExW(ConsoleInput
,
229 ARRAYSIZE(InputRecords
),
231 CONSOLE_READ_CONTINUE
))
233 DWORD LastError
= GetLastError();
234 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput
, NumRecords
, LastError
);
238 // ASSERT(NumRecords != 0);
241 DPRINT1("Got NumRecords == 0!\n");
245 /* Dispatch the events */
246 for (i
= 0; i
< NumRecords
; i
++)
248 /* Check the event type */
249 switch (InputRecords
[i
].EventType
)
255 KeyboardEventHandler(&InputRecords
[i
].Event
.KeyEvent
);
259 MouseEventHandler(&InputRecords
[i
].Event
.MouseEvent
);
262 case WINDOW_BUFFER_SIZE_EVENT
:
263 ScreenEventHandler(&InputRecords
[i
].Event
.WindowBufferSizeEvent
);
270 MenuEventHandler(&InputRecords
[i
].Event
.MenuEvent
);
274 FocusEventHandler(&InputRecords
[i
].Event
.FocusEvent
);
278 DPRINT1("Unknown input event type 0x%04x\n", InputRecords
[i
].EventType
);
283 /* Let the console subsystem queue some new events */
290 static VOID
PauseEventThread(VOID
)
292 ResetEvent(VdmTaskEvent
);
295 static VOID
ResumeEventThread(VOID
)
297 SetEvent(VdmTaskEvent
);
301 /* PUBLIC FUNCTIONS ***********************************************************/
304 DumpMemoryRaw(HANDLE hFile
)
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
);
317 DumpMemoryTxt(HANDLE hFile
)
319 #define LINE_SIZE 75 + 2
322 CHAR LineBuffer
[LINE_SIZE
];
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)
334 /* Print the address */
335 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, "%08x ", PHYS_TO_REAL(Ptr1
));
337 /* Print up to 16 bytes... */
339 /* ... in hexadecimal form first... */
341 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr1
) > 0))
343 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, " %02x", *Ptr1
);
347 /* ... align with spaces if needed... */
348 RtlFillMemory(Line
, (0x0F + 2 - i
) * 3 + 2, ' ');
349 Line
+= (0x0F + 2 - i
) * 3 + 2;
351 /* ... then in character form. */
353 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr2
) > 0))
355 *Line
++ = ((*Ptr2
>= 0x20 && *Ptr2
<= 0x7E) || (*Ptr2
>= 0x80 && *Ptr2
< 0xFF) ? *Ptr2
: '.');
363 /* Finally write the line to the file */
364 LineSize
= Line
- LineBuffer
;
365 WriteFile(hFile
, LineBuffer
, LineSize
, &LineSize
, NULL
);
369 VOID
DumpMemory(BOOLEAN TextFormat
)
371 static ULONG DumpNumber
= 0;
374 WCHAR FileName
[MAX_PATH
];
376 /* Build a suitable file name */
377 _snwprintf(FileName
, MAX_PATH
,
380 TextFormat
? L
"txt" : L
"dat");
383 DPRINT1("Creating memory dump file '%S'...\n", FileName
);
385 /* Always create the dump file */
386 hFile
= CreateFileW(FileName
,
391 FILE_ATTRIBUTE_NORMAL
,
394 if (hFile
== INVALID_HANDLE_VALUE
)
396 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
397 FileName
, GetLastError());
401 /* Dump the VM memory in the chosen format */
403 DumpMemoryTxt(hFile
);
405 DumpMemoryRaw(hFile
);
410 DPRINT1("Memory dump done\n");
413 VOID
MountFloppy(IN ULONG DiskNumber
)
415 // FIXME: This should be present in PSDK commdlg.h
418 #if (_WIN32_WINNT >= 0x0500)
419 #define OFN_EX_NOPLACESBAR 0x00000001
420 #endif // (_WIN32_WINNT >= 0x0500)
424 WCHAR szFile
[MAX_PATH
] = L
"";
426 ASSERT(DiskNumber
< ARRAYSIZE(GlobalSettings
.FloppyDisks
));
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
);
440 if (!GetOpenFileNameW(&ofn
))
442 DPRINT1("CommDlgExtendedError = %d\n", CommDlgExtendedError());
446 /* Free the old string */
447 if (GlobalSettings
.FloppyDisks
[DiskNumber
].Buffer
)
448 RtlFreeUnicodeString(&GlobalSettings
.FloppyDisks
[DiskNumber
]);
450 /* Reinitialize the string */
451 Success
= RtlCreateUnicodeString(&GlobalSettings
.FloppyDisks
[DiskNumber
], szFile
);
455 if (!MountDisk(FLOPPY_DISK
, DiskNumber
, GlobalSettings
.FloppyDisks
[DiskNumber
].Buffer
, !!(ofn
.Flags
& OFN_READONLY
)))
457 DisplayMessage(L
"An error happened when mounting disk %d", DiskNumber
);
458 RtlFreeUnicodeString(&GlobalSettings
.FloppyDisks
[DiskNumber
]);
459 RtlInitEmptyUnicodeString(&GlobalSettings
.FloppyDisks
[DiskNumber
], NULL
, 0);
463 /* Refresh the menu state */
464 UpdateVdmMenuDisks();
467 VOID
EjectFloppy(IN ULONG DiskNumber
)
469 ASSERT(DiskNumber
< ARRAYSIZE(GlobalSettings
.FloppyDisks
));
471 /* Unmount the disk */
472 if (!UnmountDisk(FLOPPY_DISK
, DiskNumber
))
473 DisplayMessage(L
"An error happened when ejecting disk %d", DiskNumber
);
475 /* Free the old string */
476 if (GlobalSettings
.FloppyDisks
[DiskNumber
].Buffer
)
478 RtlFreeUnicodeString(&GlobalSettings
.FloppyDisks
[DiskNumber
]);
479 RtlInitEmptyUnicodeString(&GlobalSettings
.FloppyDisks
[DiskNumber
], NULL
, 0);
482 /* Refresh the menu state */
483 UpdateVdmMenuDisks();
487 VOID
EmulatorPause(VOID
)
495 VOID
EmulatorResume(VOID
)
503 VOID
EmulatorTerminate(VOID
)
506 CpuUnsimulate(); // Halt the CPU
510 BOOLEAN
EmulatorInitialize(HANDLE ConsoleInput
, HANDLE ConsoleOutput
)
514 /* Initialize memory */
515 if (!MemInitialize())
517 wprintf(L
"Memory initialization failed.\n");
521 /* Initialize I/O ports */
524 /* Initialize the CPU */
526 /* Initialize the internal clock */
527 if (!ClockInitialize())
529 wprintf(L
"FATAL: Failed to initialize the clock\n");
534 /* Initialize the CPU */
540 /* Initialize PIC, PIT, CMOS, PC Speaker and PS/2 */
544 PitSetOutFunction(0, NULL
, PitChan0Out
);
545 PitSetOutFunction(1, NULL
, PitChan1Out
);
546 PitSetOutFunction(2, NULL
, PitChan2Out
);
554 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
558 /**************** ATTACH INPUT WITH CONSOLE *****************/
559 /* Create the task event */
560 VdmTaskEvent
= CreateEventW(NULL
, TRUE
, FALSE
, NULL
);
561 ASSERT(VdmTaskEvent
!= NULL
);
563 /* Start the input thread */
564 InputThread
= CreateThread(NULL
, 0, &ConsoleEventThread
, ConsoleInput
, 0, NULL
);
565 if (InputThread
== NULL
)
567 wprintf(L
"FATAL: Failed to create the console input thread.\n");
572 /************************************************************/
574 /* Initialize the VGA */
575 if (!VgaInitialize(ConsoleOutput
))
577 wprintf(L
"FATAL: Failed to initialize VGA support.\n");
582 /* Initialize the disk controller */
583 if (!DiskCtrlInitialize())
585 wprintf(L
"FATAL: Failed to completely initialize the disk controller.\n");
590 /* Mount the available floppy disks */
591 for (i
= 0; i
< ARRAYSIZE(GlobalSettings
.FloppyDisks
); ++i
)
593 if (GlobalSettings
.FloppyDisks
[i
].Length
!= 0 &&
594 GlobalSettings
.FloppyDisks
[i
].Buffer
&&
595 GlobalSettings
.FloppyDisks
[i
].Buffer
!= '\0')
597 if (!MountDisk(FLOPPY_DISK
, i
, GlobalSettings
.FloppyDisks
[i
].Buffer
, FALSE
))
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);
607 * Mount the available hard disks. Contrary to floppies, failing
608 * mounting a hard disk is considered as an unrecoverable error.
610 for (i
= 0; i
< ARRAYSIZE(GlobalSettings
.HardDisks
); ++i
)
612 if (GlobalSettings
.HardDisks
[i
].Length
!= 0 &&
613 GlobalSettings
.HardDisks
[i
].Buffer
&&
614 GlobalSettings
.HardDisks
[i
].Buffer
!= L
'\0')
616 if (!MountDisk(HARD_DISK
, i
, GlobalSettings
.HardDisks
[i
].Buffer
, FALSE
))
618 wprintf(L
"FATAL: Failed to mount hard disk file '%wZ'.\n", &GlobalSettings
.HardDisks
[i
]);
625 /* Refresh the menu state */
626 UpdateVdmMenuDisks();
628 /* Initialize the software callback system and register the emulator BOPs */
630 RegisterBop(BOP_DEBUGGER
, EmulatorDebugBreakBop
);
631 // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop);
633 /* Initialize VDD support */
639 VOID
EmulatorCleanup(VOID
)
645 /* Close the input thread handle */
646 if (InputThread
!= NULL
) CloseHandle(InputThread
);
649 /* Close the task event */
650 if (VdmTaskEvent
!= NULL
) CloseHandle(VdmTaskEvent
);
677 VDDTerminateVDM(VOID
)