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>
14 /* PRIVATE VARIABLES **********************************************************/
16 LPVOID BaseAddress
= NULL
;
17 BOOLEAN VdmRunning
= TRUE
;
19 HANDLE VdmTaskEvent
= NULL
;
20 static HANDLE InputThread
= NULL
;
22 LPCWSTR ExceptionName
[] =
29 L
"Bound Range Exceeded",
35 #define BOP_DEBUGGER 0x56 // Break into the debugger from a 16-bit app
37 /* PRIVATE FUNCTIONS **********************************************************/
39 UCHAR FASTCALL
EmulatorIntAcknowledge(PFAST486_STATE State
)
41 UNREFERENCED_PARAMETER(State
);
43 /* Get the interrupt number from the PIC */
44 return PicGetInterrupt();
47 VOID FASTCALL
EmulatorFpu(PFAST486_STATE State
)
49 /* The FPU is wired to IRQ 13 */
50 PicInterruptRequest(13);
53 VOID
EmulatorException(BYTE ExceptionNumber
, LPWORD Stack
)
55 WORD CodeSegment
, InstructionPointer
;
58 ASSERT(ExceptionNumber
< 8);
61 InstructionPointer
= Stack
[STACK_IP
];
62 CodeSegment
= Stack
[STACK_CS
];
63 Opcode
= (PBYTE
)SEG_OFF_TO_PTR(CodeSegment
, InstructionPointer
);
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
],
82 Fast486DumpState(&EmulatorContext
);
88 VOID
EmulatorInterruptSignal(VOID
)
90 /* Call the Fast486 API */
91 Fast486InterruptSignal(&EmulatorContext
);
94 static VOID WINAPI
EmulatorDebugBreakBop(LPWORD Stack
)
96 DPRINT1("NTVDM: BOP_DEBUGGER\n");
100 static VOID WINAPI
PitChan0Out(LPVOID Param
, BOOLEAN State
)
104 DPRINT("PicInterruptRequest\n");
105 PicInterruptRequest(0); // Raise IRQ 0
107 // else < Lower IRQ 0 >
110 static VOID WINAPI
PitChan1Out(LPVOID Param
, BOOLEAN State
)
115 /* Set bit 4 of Port 61h */
116 Port61hState
|= 1 << 4;
120 /* Clear bit 4 of Port 61h */
121 Port61hState
&= ~(1 << 4);
124 Port61hState
= (Port61hState
& 0xEF) | (State
<< 4);
128 static VOID WINAPI
PitChan2Out(LPVOID Param
, BOOLEAN State
)
130 BYTE OldPort61hState
= Port61hState
;
135 /* Set bit 5 of Port 61h */
136 Port61hState
|= 1 << 5;
140 /* Clear bit 5 of Port 61h */
141 Port61hState
&= ~(1 << 5);
144 Port61hState
= (Port61hState
& 0xDF) | (State
<< 5);
147 if ((OldPort61hState
^ Port61hState
) & 0x20)
149 DPRINT("PitChan2Out -- Port61hState changed\n");
150 SpeakerChange(Port61hState
);
157 ConsoleEventThread(LPVOID Parameter
)
159 HANDLE ConsoleInput
= (HANDLE
)Parameter
;
160 HANDLE WaitHandles
[2];
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
170 * We exploit here this optimization by also using a buffer of 5 records.
172 INPUT_RECORD InputRecords
[5];
175 WaitHandles
[0] = VdmTaskEvent
;
176 WaitHandles
[1] = GetConsoleInputWaitHandle();
180 /* Make sure the task event is signaled */
181 WaitResult
= WaitForMultipleObjects(ARRAYSIZE(WaitHandles
),
187 case WAIT_OBJECT_0
+ 0:
188 case WAIT_OBJECT_0
+ 1:
191 return GetLastError();
194 /* Wait for an input record */
195 if (!ReadConsoleInputExW(ConsoleInput
,
197 ARRAYSIZE(InputRecords
),
199 CONSOLE_READ_CONTINUE
))
201 DWORD LastError
= GetLastError();
202 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput
, NumRecords
, LastError
);
206 // ASSERT(NumRecords != 0);
209 DPRINT1("Got NumRecords == 0!\n");
213 /* Dispatch the events */
214 for (i
= 0; i
< NumRecords
; i
++)
216 /* Check the event type */
217 switch (InputRecords
[i
].EventType
)
223 KeyboardEventHandler(&InputRecords
[i
].Event
.KeyEvent
);
227 MouseEventHandler(&InputRecords
[i
].Event
.MouseEvent
);
230 case WINDOW_BUFFER_SIZE_EVENT
:
231 ScreenEventHandler(&InputRecords
[i
].Event
.WindowBufferSizeEvent
);
238 MenuEventHandler(&InputRecords
[i
].Event
.MenuEvent
);
242 FocusEventHandler(&InputRecords
[i
].Event
.FocusEvent
);
246 DPRINT1("Unknown input event type 0x%04x\n", InputRecords
[i
].EventType
);
251 /* Let the console subsystem queue some new events */
258 static VOID
PauseEventThread(VOID
)
260 ResetEvent(VdmTaskEvent
);
263 static VOID
ResumeEventThread(VOID
)
265 SetEvent(VdmTaskEvent
);
269 /* PUBLIC FUNCTIONS ***********************************************************/
272 DumpMemoryRaw(HANDLE hFile
)
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
);
285 DumpMemoryTxt(HANDLE hFile
)
287 #define LINE_SIZE 75 + 2
290 CHAR LineBuffer
[LINE_SIZE
];
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)
302 /* Print the address */
303 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, "%08x ", PHYS_TO_REAL(Ptr1
));
305 /* Print up to 16 bytes... */
307 /* ... in hexadecimal form first... */
309 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr1
) > 0))
311 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, " %02x", *Ptr1
);
315 /* ... align with spaces if needed... */
316 RtlFillMemory(Line
, 0x0F + 4 - i
, ' ');
317 Line
+= 0x0F + 4 - i
;
319 /* ... then in character form. */
321 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr2
) > 0))
323 *Line
++ = ((*Ptr2
>= 0x20 && *Ptr2
<= 0x7E) || (*Ptr2
>= 0x80 && *Ptr2
< 0xFF) ? *Ptr2
: '.');
331 /* Finally write the line to the file */
332 LineSize
= Line
- LineBuffer
;
333 WriteFile(hFile
, LineBuffer
, LineSize
, &LineSize
, NULL
);
337 VOID
DumpMemory(BOOLEAN TextFormat
)
339 static ULONG DumpNumber
= 0;
342 WCHAR FileName
[MAX_PATH
];
344 /* Build a suitable file name */
345 _snwprintf(FileName
, MAX_PATH
,
348 TextFormat
? L
"txt" : L
"dat");
351 DPRINT1("Creating memory dump file '%S'...\n", FileName
);
353 /* Always create the dump file */
354 hFile
= CreateFileW(FileName
,
359 FILE_ATTRIBUTE_NORMAL
,
362 if (hFile
== INVALID_HANDLE_VALUE
)
364 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
365 FileName
, GetLastError());
369 /* Dump the VM memory in the chosen format */
371 DumpMemoryTxt(hFile
);
373 DumpMemoryRaw(hFile
);
378 DPRINT1("Memory dump done\n");
381 VOID
MountFloppy(IN ULONG DiskNumber
)
383 // FIXME: This should be present in PSDK commdlg.h
386 #if (_WIN32_WINNT >= 0x0500)
387 #define OFN_EX_NOPLACESBAR 0x00000001
388 #endif // (_WIN32_WINNT >= 0x0500)
391 WCHAR szFile
[MAX_PATH
] = L
"";
392 UNICODE_STRING ValueString
;
394 ASSERT(DiskNumber
< ARRAYSIZE(GlobalSettings
.FloppyDisks
));
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
);
408 if (!GetOpenFileNameW(&ofn
))
410 DPRINT1("CommDlgExtendedError = %d\n", CommDlgExtendedError());
414 /* Free the old string */
415 if (GlobalSettings
.FloppyDisks
[DiskNumber
].Buffer
)
416 RtlFreeAnsiString(&GlobalSettings
.FloppyDisks
[DiskNumber
]);
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
);
424 if (!MountDisk(FLOPPY_DISK
, DiskNumber
, GlobalSettings
.FloppyDisks
[DiskNumber
].Buffer
, !!(ofn
.Flags
& OFN_READONLY
)))
426 DisplayMessage(L
"An error happened when mounting disk %d", DiskNumber
);
427 RtlFreeAnsiString(&GlobalSettings
.FloppyDisks
[DiskNumber
]);
428 RtlInitEmptyAnsiString(&GlobalSettings
.FloppyDisks
[DiskNumber
], NULL
, 0);
432 /* Refresh the menu state */
433 UpdateVdmMenuDisks();
436 VOID
EjectFloppy(IN ULONG DiskNumber
)
438 ASSERT(DiskNumber
< ARRAYSIZE(GlobalSettings
.FloppyDisks
));
440 /* Unmount the disk */
441 if (!UnmountDisk(FLOPPY_DISK
, DiskNumber
))
442 DisplayMessage(L
"An error happened when ejecting disk %d", DiskNumber
);
444 /* Free the old string */
445 if (GlobalSettings
.FloppyDisks
[DiskNumber
].Buffer
)
447 RtlFreeAnsiString(&GlobalSettings
.FloppyDisks
[DiskNumber
]);
448 RtlInitEmptyAnsiString(&GlobalSettings
.FloppyDisks
[DiskNumber
], NULL
, 0);
451 /* Refresh the menu state */
452 UpdateVdmMenuDisks();
456 VOID
EmulatorPause(VOID
)
464 VOID
EmulatorResume(VOID
)
472 VOID
EmulatorTerminate(VOID
)
475 CpuUnsimulate(); // Halt the CPU
479 BOOLEAN
EmulatorInitialize(HANDLE ConsoleInput
, HANDLE ConsoleOutput
)
483 /* Initialize memory */
484 if (!MemInitialize())
486 wprintf(L
"Memory initialization failed.\n");
490 /* Initialize I/O ports */
493 /* Initialize the CPU */
495 /* Initialize the internal clock */
496 if (!ClockInitialize())
498 wprintf(L
"FATAL: Failed to initialize the clock\n");
503 /* Initialize the CPU */
509 /* Initialize PIC, PIT, CMOS, PC Speaker and PS/2 */
513 PitSetOutFunction(0, NULL
, PitChan0Out
);
514 PitSetOutFunction(1, NULL
, PitChan1Out
);
515 PitSetOutFunction(2, NULL
, PitChan2Out
);
523 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
527 /**************** ATTACH INPUT WITH CONSOLE *****************/
528 /* Create the task event */
529 VdmTaskEvent
= CreateEventW(NULL
, TRUE
, FALSE
, NULL
);
530 ASSERT(VdmTaskEvent
!= NULL
);
532 /* Start the input thread */
533 InputThread
= CreateThread(NULL
, 0, &ConsoleEventThread
, ConsoleInput
, 0, NULL
);
534 if (InputThread
== NULL
)
536 wprintf(L
"FATAL: Failed to create the console input thread.\n");
541 /************************************************************/
543 /* Initialize the VGA */
544 if (!VgaInitialize(ConsoleOutput
))
546 wprintf(L
"FATAL: Failed to initialize VGA support.\n");
551 /* Initialize the disk controller */
552 if (!DiskCtrlInitialize())
554 wprintf(L
"FATAL: Failed to completely initialize the disk controller.\n");
559 /* Mount the available floppy disks */
560 for (i
= 0; i
< ARRAYSIZE(GlobalSettings
.FloppyDisks
); ++i
)
562 if (GlobalSettings
.FloppyDisks
[i
].Length
!= 0 &&
563 GlobalSettings
.FloppyDisks
[i
].Buffer
&&
564 GlobalSettings
.FloppyDisks
[i
].Buffer
!= '\0')
566 if (!MountDisk(FLOPPY_DISK
, i
, GlobalSettings
.FloppyDisks
[i
].Buffer
, FALSE
))
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);
576 * Mount the available hard disks. Contrary to floppies, failing
577 * mounting a hard disk is considered as an unrecoverable error.
579 for (i
= 0; i
< ARRAYSIZE(GlobalSettings
.HardDisks
); ++i
)
581 if (GlobalSettings
.HardDisks
[i
].Length
!= 0 &&
582 GlobalSettings
.HardDisks
[i
].Buffer
&&
583 GlobalSettings
.HardDisks
[i
].Buffer
!= '\0')
585 if (!MountDisk(HARD_DISK
, i
, GlobalSettings
.HardDisks
[i
].Buffer
, FALSE
))
587 wprintf(L
"FATAL: Failed to mount hard disk file '%Z'.\n", &GlobalSettings
.HardDisks
[i
]);
594 /* Refresh the menu state */
595 UpdateVdmMenuDisks();
597 /* Initialize the software callback system and register the emulator BOPs */
599 RegisterBop(BOP_DEBUGGER
, EmulatorDebugBreakBop
);
600 // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop);
602 /* Initialize VDD support */
608 VOID
EmulatorCleanup(VOID
)
614 /* Close the input thread handle */
615 if (InputThread
!= NULL
) CloseHandle(InputThread
);
618 /* Close the task event */
619 if (VdmTaskEvent
!= NULL
) CloseHandle(VdmTaskEvent
);
646 VDDTerminateVDM(VOID
)