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 *******************************************************************/
17 #include "cpu/callback.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"
41 /* PRIVATE VARIABLES **********************************************************/
43 LPVOID BaseAddress
= NULL
;
44 BOOLEAN VdmRunning
= TRUE
;
46 HANDLE VdmTaskEvent
= NULL
;
47 static HANDLE InputThread
= NULL
;
49 LPCWSTR ExceptionName
[] =
56 L
"Bound Range Exceeded",
62 #define BOP_DEBUGGER 0x56 // Break into the debugger from a 16-bit app
64 /* PRIVATE FUNCTIONS **********************************************************/
66 UCHAR FASTCALL
EmulatorIntAcknowledge(PFAST486_STATE State
)
68 UNREFERENCED_PARAMETER(State
);
70 /* Get the interrupt number from the PIC */
71 return PicGetInterrupt();
74 VOID FASTCALL
EmulatorFpu(PFAST486_STATE State
)
76 /* The FPU is wired to IRQ 13 */
77 PicInterruptRequest(13);
80 VOID
EmulatorException(BYTE ExceptionNumber
, LPWORD Stack
)
82 WORD CodeSegment
, InstructionPointer
;
85 ASSERT(ExceptionNumber
< 8);
88 InstructionPointer
= Stack
[STACK_IP
];
89 CodeSegment
= Stack
[STACK_CS
];
90 Opcode
= (PBYTE
)SEG_OFF_TO_PTR(CodeSegment
, InstructionPointer
);
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
],
109 Fast486DumpState(&EmulatorContext
);
115 VOID
EmulatorInterruptSignal(VOID
)
117 /* Call the Fast486 API */
118 Fast486InterruptSignal(&EmulatorContext
);
121 static VOID WINAPI
EmulatorDebugBreakBop(LPWORD Stack
)
123 DPRINT1("NTVDM: BOP_DEBUGGER\n");
127 static VOID WINAPI
PitChan0Out(LPVOID Param
, BOOLEAN State
)
131 DPRINT("PicInterruptRequest\n");
132 PicInterruptRequest(0); // Raise IRQ 0
134 // else < Lower IRQ 0 >
137 static VOID WINAPI
PitChan1Out(LPVOID Param
, BOOLEAN State
)
142 /* Set bit 4 of Port 61h */
143 Port61hState
|= 1 << 4;
147 /* Clear bit 4 of Port 61h */
148 Port61hState
&= ~(1 << 4);
151 Port61hState
= (Port61hState
& 0xEF) | (State
<< 4);
155 static VOID WINAPI
PitChan2Out(LPVOID Param
, BOOLEAN State
)
157 BYTE OldPort61hState
= Port61hState
;
162 /* Set bit 5 of Port 61h */
163 Port61hState
|= 1 << 5;
167 /* Clear bit 5 of Port 61h */
168 Port61hState
&= ~(1 << 5);
171 Port61hState
= (Port61hState
& 0xDF) | (State
<< 5);
174 if ((OldPort61hState
^ Port61hState
) & 0x20)
176 DPRINT("PitChan2Out -- Port61hState changed\n");
177 SpeakerChange(Port61hState
);
184 ConsoleEventThread(LPVOID Parameter
)
186 HANDLE ConsoleInput
= (HANDLE
)Parameter
;
187 HANDLE WaitHandles
[2];
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
197 * We exploit here this optimization by also using a buffer of 5 records.
199 INPUT_RECORD InputRecords
[5];
202 WaitHandles
[0] = VdmTaskEvent
;
203 WaitHandles
[1] = GetConsoleInputWaitHandle();
207 /* Make sure the task event is signaled */
208 WaitResult
= WaitForMultipleObjects(ARRAYSIZE(WaitHandles
),
214 case WAIT_OBJECT_0
+ 0:
215 case WAIT_OBJECT_0
+ 1:
218 return GetLastError();
221 /* Wait for an input record */
222 if (!ReadConsoleInputExW(ConsoleInput
,
224 ARRAYSIZE(InputRecords
),
226 CONSOLE_READ_CONTINUE
))
228 DWORD LastError
= GetLastError();
229 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput
, NumRecords
, LastError
);
233 // ASSERT(NumRecords != 0);
236 DPRINT1("Got NumRecords == 0!\n");
240 /* Dispatch the events */
241 for (i
= 0; i
< NumRecords
; i
++)
243 /* Check the event type */
244 switch (InputRecords
[i
].EventType
)
250 KeyboardEventHandler(&InputRecords
[i
].Event
.KeyEvent
);
254 MouseEventHandler(&InputRecords
[i
].Event
.MouseEvent
);
257 case WINDOW_BUFFER_SIZE_EVENT
:
258 ScreenEventHandler(&InputRecords
[i
].Event
.WindowBufferSizeEvent
);
265 MenuEventHandler(&InputRecords
[i
].Event
.MenuEvent
);
269 FocusEventHandler(&InputRecords
[i
].Event
.FocusEvent
);
273 DPRINT1("Unknown input event type 0x%04x\n", InputRecords
[i
].EventType
);
278 /* Let the console subsystem queue some new events */
285 static VOID
PauseEventThread(VOID
)
287 ResetEvent(VdmTaskEvent
);
290 static VOID
ResumeEventThread(VOID
)
292 SetEvent(VdmTaskEvent
);
296 /* PUBLIC FUNCTIONS ***********************************************************/
299 DumpMemoryRaw(HANDLE hFile
)
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
);
312 DumpMemoryTxt(HANDLE hFile
)
314 #define LINE_SIZE 75 + 2
317 CHAR LineBuffer
[LINE_SIZE
];
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)
329 /* Print the address */
330 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, "%08x ", PHYS_TO_REAL(Ptr1
));
332 /* Print up to 16 bytes... */
334 /* ... in hexadecimal form first... */
336 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr1
) > 0))
338 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, " %02x", *Ptr1
);
342 /* ... align with spaces if needed... */
343 RtlFillMemory(Line
, 0x0F + 4 - i
, ' ');
344 Line
+= 0x0F + 4 - i
;
346 /* ... then in character form. */
348 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr2
) > 0))
350 *Line
++ = ((*Ptr2
>= 0x20 && *Ptr2
<= 0x7E) || (*Ptr2
>= 0x80 && *Ptr2
< 0xFF) ? *Ptr2
: '.');
358 /* Finally write the line to the file */
359 LineSize
= Line
- LineBuffer
;
360 WriteFile(hFile
, LineBuffer
, LineSize
, &LineSize
, NULL
);
364 VOID
DumpMemory(BOOLEAN TextFormat
)
366 static ULONG DumpNumber
= 0;
369 WCHAR FileName
[MAX_PATH
];
371 /* Build a suitable file name */
372 _snwprintf(FileName
, MAX_PATH
,
375 TextFormat
? L
"txt" : L
"dat");
378 DPRINT1("Creating memory dump file '%S'...\n", FileName
);
380 /* Always create the dump file */
381 hFile
= CreateFileW(FileName
,
386 FILE_ATTRIBUTE_NORMAL
,
389 if (hFile
== INVALID_HANDLE_VALUE
)
391 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
392 FileName
, GetLastError());
396 /* Dump the VM memory in the chosen format */
398 DumpMemoryTxt(hFile
);
400 DumpMemoryRaw(hFile
);
405 DPRINT1("Memory dump done\n");
408 VOID
MountFloppy(IN ULONG DiskNumber
)
410 // FIXME: This should be present in PSDK commdlg.h
413 #if (_WIN32_WINNT >= 0x0500)
414 #define OFN_EX_NOPLACESBAR 0x00000001
415 #endif // (_WIN32_WINNT >= 0x0500)
418 CHAR szFile
[MAX_PATH
] = "";
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
);
432 if (!GetOpenFileNameA(&ofn
))
434 DPRINT1("CommDlgExtendedError = %d\n", CommDlgExtendedError());
438 // TODO: Refresh the menu state
440 if (!MountDisk(FLOPPY_DISK
, DiskNumber
, szFile
, !!(ofn
.Flags
& OFN_READONLY
)))
441 DisplayMessage(L
"An error happened when mounting disk %d", DiskNumber
);
444 VOID
EjectFloppy(IN ULONG DiskNumber
)
446 // TODO: Refresh the menu state
448 if (!UnmountDisk(FLOPPY_DISK
, DiskNumber
))
449 DisplayMessage(L
"An error happened when ejecting disk %d", DiskNumber
);
453 VOID
EmulatorPause(VOID
)
461 VOID
EmulatorResume(VOID
)
469 VOID
EmulatorTerminate(VOID
)
472 CpuUnsimulate(); // Halt the CPU
476 BOOLEAN
EmulatorInitialize(HANDLE ConsoleInput
, HANDLE ConsoleOutput
)
478 /* Initialize memory */
479 if (!MemInitialize())
481 wprintf(L
"Memory initialization failed.\n");
485 /* Initialize I/O ports */
488 /* Initialize the CPU */
490 /* Initialize the internal clock */
491 if (!ClockInitialize())
493 wprintf(L
"FATAL: Failed to initialize the clock\n");
498 /* Initialize the CPU */
504 /* Initialize PIC, PIT, CMOS, PC Speaker and PS/2 */
508 PitSetOutFunction(0, NULL
, PitChan0Out
);
509 PitSetOutFunction(1, NULL
, PitChan1Out
);
510 PitSetOutFunction(2, NULL
, PitChan2Out
);
518 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
522 /**************** ATTACH INPUT WITH CONSOLE *****************/
523 /* Create the task event */
524 VdmTaskEvent
= CreateEventW(NULL
, TRUE
, FALSE
, NULL
);
525 ASSERT(VdmTaskEvent
!= NULL
);
527 /* Start the input thread */
528 InputThread
= CreateThread(NULL
, 0, &ConsoleEventThread
, ConsoleInput
, 0, NULL
);
529 if (InputThread
== NULL
)
531 wprintf(L
"FATAL: Failed to create the console input thread.\n");
536 /************************************************************/
538 /* Initialize the VGA */
539 if (!VgaInitialize(ConsoleOutput
))
541 wprintf(L
"FATAL: Failed to initialize VGA support.\n");
546 /* Initialize the disk controller */
547 if (!DiskCtrlInitialize())
549 wprintf(L
"FATAL: Failed to completely initialize the disk controller.\n");
555 // The following commands are examples of MountDisk usage.
556 // NOTE: Those values are hardcoded paths on my local test machines!!
558 // MountDisk(FLOPPY_DISK, 0, "H:\\trunk\\ntvdm_studies\\diskette_high.vfd", TRUE);
559 // MountDisk(FLOPPY_DISK, 0, "H:\\DOS_tests\\Dos5.0.img", TRUE);
560 // MountDisk(FLOPPY_DISK, 0, "H:\\trunk\\ntvdm_studies\\hdd_10Mo_fixed.vhd", TRUE);
561 // MountDisk(FLOPPY_DISK, 0, "H:\\DOS_tests\\diskette_test.vfd", FALSE);
563 MountDisk(HARD_DISK
, 0, "H:\\DOS_tests\\MS-DOS 6_fixed_size.vhd", FALSE
);
566 /* Initialize the software callback system and register the emulator BOPs */
568 RegisterBop(BOP_DEBUGGER
, EmulatorDebugBreakBop
);
569 // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop);
571 /* Initialize VDD support */
577 VOID
EmulatorCleanup(VOID
)
583 /* Close the input thread handle */
584 if (InputThread
!= NULL
) CloseHandle(InputThread
);
587 /* Close the task event */
588 if (VdmTaskEvent
!= NULL
) CloseHandle(VdmTaskEvent
);
615 VDDTerminateVDM(VOID
)