2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
5 * PURPOSE: Minimal x86 machine emulator for the VDM
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
9 /* INCLUDES *******************************************************************/
18 #include "hardware/cmos.h"
19 #include "hardware/keyboard.h"
20 #include "hardware/mouse.h"
21 #include "hardware/pic.h"
22 #include "hardware/ps2.h"
23 #include "hardware/speaker.h"
24 #include "hardware/timer.h"
25 #include "hardware/vga.h"
33 /* PRIVATE VARIABLES **********************************************************/
35 FAST486_STATE EmulatorContext
;
36 BOOLEAN CpuSimulate
= FALSE
;
38 /* No more than 'MaxCpuCallLevel' recursive CPU calls are allowed */
39 static const INT MaxCpuCallLevel
= 32;
40 static INT CpuCallLevel
= 0;
42 LPVOID BaseAddress
= NULL
;
43 BOOLEAN VdmRunning
= TRUE
;
45 static BOOLEAN A20Line
= FALSE
;
46 static BYTE Port61hState
= 0x00;
48 static HANDLE InputThread
= NULL
;
50 LPCWSTR ExceptionName
[] =
57 L
"Bound Range Exceeded",
63 #define BOP_DEBUGGER 0x56 // Break into the debugger from a 16-bit app
65 /* PRIVATE FUNCTIONS **********************************************************/
67 VOID WINAPI
EmulatorReadMemory(PFAST486_STATE State
, ULONG Address
, PVOID Buffer
, ULONG Size
)
69 UNREFERENCED_PARAMETER(State
);
71 // BIG HACK!!!! To make BIOS images working correctly,
72 // until Aleksander rewrites memory management!!
73 if (Address
>= 0xFFFFFFF0) Address
-= 0xFFF00000;
75 /* If the A20 line is disabled, mask bit 20 */
76 if (!A20Line
) Address
&= ~(1 << 20);
78 /* Make sure the requested address is valid */
79 if ((Address
+ Size
) >= MAX_ADDRESS
) return;
82 * Check if we are going to read the VGA memory and
83 * copy it into the virtual address space if needed.
85 if (((Address
+ Size
) >= VgaGetVideoBaseAddress())
86 && (Address
< VgaGetVideoLimitAddress()))
88 DWORD VgaAddress
= max(Address
, VgaGetVideoBaseAddress());
89 DWORD ActualSize
= min(Address
+ Size
- 1, VgaGetVideoLimitAddress())
91 LPBYTE DestBuffer
= (LPBYTE
)REAL_TO_PHYS(VgaAddress
);
93 /* Read from the VGA memory */
94 VgaReadMemory(VgaAddress
, DestBuffer
, ActualSize
);
97 /* Read the data from the virtual address space and store it in the buffer */
98 RtlCopyMemory(Buffer
, REAL_TO_PHYS(Address
), Size
);
101 VOID WINAPI
EmulatorWriteMemory(PFAST486_STATE State
, ULONG Address
, PVOID Buffer
, ULONG Size
)
103 UNREFERENCED_PARAMETER(State
);
105 // BIG HACK!!!! To make BIOS images working correctly,
106 // until Aleksander rewrites memory management!!
107 if (Address
>= 0xFFFFFFF0) Address
-= 0xFFF00000;
109 /* If the A20 line is disabled, mask bit 20 */
110 if (!A20Line
) Address
&= ~(1 << 20);
112 /* Make sure the requested address is valid */
113 if ((Address
+ Size
) >= MAX_ADDRESS
) return;
115 /* Make sure we don't write to the ROM area */
116 if ((Address
+ Size
) >= ROM_AREA_START
&& (Address
< ROM_AREA_END
)) return;
118 /* Read the data from the buffer and store it in the virtual address space */
119 RtlCopyMemory(REAL_TO_PHYS(Address
), Buffer
, Size
);
122 * Check if we modified the VGA memory.
124 if (((Address
+ Size
) >= VgaGetVideoBaseAddress())
125 && (Address
< VgaGetVideoLimitAddress()))
127 DWORD VgaAddress
= max(Address
, VgaGetVideoBaseAddress());
128 DWORD ActualSize
= min(Address
+ Size
- 1, VgaGetVideoLimitAddress())
130 LPBYTE SrcBuffer
= (LPBYTE
)REAL_TO_PHYS(VgaAddress
);
132 /* Write to the VGA memory */
133 VgaWriteMemory(VgaAddress
, SrcBuffer
, ActualSize
);
137 UCHAR WINAPI
EmulatorIntAcknowledge(PFAST486_STATE State
)
139 UNREFERENCED_PARAMETER(State
);
141 /* Get the interrupt number from the PIC */
142 return PicGetInterrupt();
145 VOID
EmulatorException(BYTE ExceptionNumber
, LPWORD Stack
)
147 WORD CodeSegment
, InstructionPointer
;
150 ASSERT(ExceptionNumber
< 8);
153 InstructionPointer
= Stack
[STACK_IP
];
154 CodeSegment
= Stack
[STACK_CS
];
155 Opcode
= (PBYTE
)SEG_OFF_TO_PTR(CodeSegment
, InstructionPointer
);
157 /* Display a message to the user */
158 DisplayMessage(L
"Exception: %s occured at %04X:%04X\n"
159 L
"Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
160 ExceptionName
[ExceptionNumber
],
179 // FIXME: This function assumes 16-bit mode!!!
180 VOID
EmulatorExecute(WORD Segment
, WORD Offset
)
182 /* Tell Fast486 to move the instruction pointer */
183 Fast486ExecuteAt(&EmulatorContext
, Segment
, Offset
);
186 VOID
EmulatorStep(VOID
)
188 /* Dump the state for debugging purposes */
189 // Fast486DumpState(&EmulatorContext);
191 /* Execute the next instruction */
192 Fast486StepInto(&EmulatorContext
);
195 VOID
EmulatorSimulate(VOID
)
197 if (CpuCallLevel
> MaxCpuCallLevel
)
199 DisplayMessage(L
"Too many CPU levels of recursion (%d, expected maximum %d)",
200 CpuCallLevel
, MaxCpuCallLevel
);
209 while (VdmRunning
&& CpuSimulate
) ClockUpdate();
212 if (CpuCallLevel
< 0) CpuCallLevel
= 0;
214 /* This takes into account for reentrance */
218 VOID
EmulatorUnsimulate(VOID
)
220 /* Stop simulation */
224 VOID
EmulatorTerminate(VOID
)
230 VOID
EmulatorInterrupt(BYTE Number
)
232 /* Call the Fast486 API */
233 Fast486Interrupt(&EmulatorContext
, Number
);
236 VOID
EmulatorInterruptSignal(VOID
)
238 /* Call the Fast486 API */
239 Fast486InterruptSignal(&EmulatorContext
);
242 VOID
EmulatorSetA20(BOOLEAN Enabled
)
247 static VOID WINAPI
EmulatorDebugBreakBop(LPWORD Stack
)
249 DPRINT1("NTVDM: BOP_DEBUGGER\n");
253 static VOID WINAPI
EmulatorUnsimulateBop(LPWORD Stack
)
255 EmulatorUnsimulate();
258 static BYTE WINAPI
Port61hRead(ULONG Port
)
263 static VOID WINAPI
Port61hWrite(ULONG Port
, BYTE Data
)
265 // BOOLEAN SpeakerStateChange = FALSE;
266 BYTE OldPort61hState
= Port61hState
;
268 /* Only the four lowest bytes can be written */
269 Port61hState
= (Port61hState
& 0xF0) | (Data
& 0x0F);
271 if ((OldPort61hState
^ Port61hState
) & 0x01)
273 DPRINT("PIT 2 Gate %s\n", Port61hState
& 0x01 ? "on" : "off");
274 PitSetGate(2, !!(Port61hState
& 0x01));
275 // SpeakerStateChange = TRUE;
278 if ((OldPort61hState
^ Port61hState
) & 0x02)
280 /* There were some change for the speaker... */
281 DPRINT("Speaker %s\n", Port61hState
& 0x02 ? "on" : "off");
282 // SpeakerStateChange = TRUE;
284 // if (SpeakerStateChange) SpeakerChange();
288 static VOID WINAPI
PitChan0Out(LPVOID Param
, BOOLEAN State
)
292 DPRINT("PicInterruptRequest\n");
293 PicInterruptRequest(0); // Raise IRQ 0
295 // else < Lower IRQ 0 >
298 static VOID WINAPI
PitChan1Out(LPVOID Param
, BOOLEAN State
)
303 /* Set bit 4 of Port 61h */
304 Port61hState
|= 1 << 4;
308 /* Clear bit 4 of Port 61h */
309 Port61hState
&= ~(1 << 4);
312 Port61hState
= (Port61hState
& 0xEF) | (State
<< 4);
316 static VOID WINAPI
PitChan2Out(LPVOID Param
, BOOLEAN State
)
318 BYTE OldPort61hState
= Port61hState
;
323 /* Set bit 5 of Port 61h */
324 Port61hState
|= 1 << 5;
328 /* Clear bit 5 of Port 61h */
329 Port61hState
&= ~(1 << 5);
332 Port61hState
= (Port61hState
& 0xDF) | (State
<< 5);
335 if ((OldPort61hState
^ Port61hState
) & 0x20)
337 DPRINT("PitChan2Out -- Port61hState changed\n");
345 PumpConsoleInput(LPVOID Parameter
)
347 HANDLE ConsoleInput
= (HANDLE
)Parameter
;
348 INPUT_RECORD InputRecord
;
353 /* Make sure the task event is signaled */
354 WaitForSingleObject(VdmTaskEvent
, INFINITE
);
356 /* Wait for an input record */
357 if (!ReadConsoleInput(ConsoleInput
, &InputRecord
, 1, &Count
))
359 DWORD LastError
= GetLastError();
360 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput
, Count
, LastError
);
366 /* Check the event type */
367 switch (InputRecord
.EventType
)
373 KeyboardEventHandler(&InputRecord
.Event
.KeyEvent
);
377 MouseEventHandler(&InputRecord
.Event
.MouseEvent
);
380 case WINDOW_BUFFER_SIZE_EVENT
:
381 ScreenEventHandler(&InputRecord
.Event
.WindowBufferSizeEvent
);
388 MenuEventHandler(&InputRecord
.Event
.MenuEvent
);
392 FocusEventHandler(&InputRecord
.Event
.FocusEvent
);
403 static VOID
EnableExtraHardware(HANDLE ConsoleInput
)
407 if (GetConsoleMode(ConsoleInput
, &ConInMode
))
410 // GetNumberOfConsoleMouseButtons();
411 // GetSystemMetrics(SM_CMOUSEBUTTONS);
412 // GetSystemMetrics(SM_MOUSEPRESENT);
416 /* Support mouse input events if there is a mouse on the system */
417 ConInMode
|= ENABLE_MOUSE_INPUT
;
422 /* Do not support mouse input events if there is no mouse on the system */
423 ConInMode
&= ~ENABLE_MOUSE_INPUT
;
427 SetConsoleMode(ConsoleInput
, ConInMode
);
431 /* PUBLIC FUNCTIONS ***********************************************************/
434 DumpMemoryRaw(HANDLE hFile
)
439 /* Dump the VM memory */
440 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
441 Buffer
= REAL_TO_PHYS(NULL
);
442 Size
= MAX_ADDRESS
- (ULONG_PTR
)(NULL
);
443 WriteFile(hFile
, Buffer
, Size
, &Size
, NULL
);
447 DumpMemoryTxt(HANDLE hFile
)
449 #define LINE_SIZE 75 + 2
452 CHAR LineBuffer
[LINE_SIZE
];
456 /* Dump the VM memory */
457 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
458 Ptr1
= Ptr2
= REAL_TO_PHYS(NULL
);
459 while (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr1
) > 0)
464 /* Print the address */
465 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, "%08x ", PHYS_TO_REAL(Ptr1
));
467 /* Print up to 16 bytes... */
469 /* ... in hexadecimal form first... */
471 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr1
) > 0))
473 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, " %02x", *Ptr1
);
477 /* ... align with spaces if needed... */
478 RtlFillMemory(Line
, 0x0F + 4 - i
, ' ');
479 Line
+= 0x0F + 4 - i
;
481 /* ... then in character form. */
483 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr2
) > 0))
485 *Line
++ = ((*Ptr2
>= 0x20 && *Ptr2
<= 0x7E) || (*Ptr2
>= 0x80 && *Ptr2
< 0xFF) ? *Ptr2
: '.');
493 /* Finally write the line to the file */
494 LineSize
= Line
- LineBuffer
;
495 WriteFile(hFile
, LineBuffer
, LineSize
, &LineSize
, NULL
);
499 VOID
DumpMemory(BOOLEAN TextFormat
)
501 static ULONG DumpNumber
= 0;
504 WCHAR FileName
[MAX_PATH
];
506 /* Build a suitable file name */
507 _snwprintf(FileName
, MAX_PATH
,
510 TextFormat
? L
"txt" : L
"dat");
513 DPRINT1("Creating memory dump file '%S'...\n", FileName
);
515 /* Always create the dump file */
516 hFile
= CreateFileW(FileName
,
521 FILE_ATTRIBUTE_NORMAL
,
524 if (hFile
== INVALID_HANDLE_VALUE
)
526 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
527 FileName
, GetLastError());
531 /* Dump the VM memory in the chosen format */
533 DumpMemoryTxt(hFile
);
535 DumpMemoryRaw(hFile
);
540 DPRINT1("Memory dump done\n");
543 BOOLEAN
EmulatorInitialize(HANDLE ConsoleInput
, HANDLE ConsoleOutput
)
545 /* Allocate memory for the 16-bit address space */
546 BaseAddress
= HeapAlloc(GetProcessHeap(), /*HEAP_ZERO_MEMORY*/ 0, MAX_ADDRESS
);
547 if (BaseAddress
== NULL
)
549 wprintf(L
"FATAL: Failed to allocate VDM memory.\n");
553 * For diagnostics purposes, we fill the memory with INT 0x03 codes
554 * so that if a program wants to execute random code in memory, we can
555 * retrieve the exact CS:IP where the problem happens.
557 RtlFillMemory(BaseAddress
, MAX_ADDRESS
, 0xCC);
559 /* Initialize I/O ports */
562 /* Initialize the internal clock */
563 if (!ClockInitialize())
565 wprintf(L
"FATAL: Failed to initialize the clock\n");
569 /* Initialize the CPU */
570 Fast486Initialize(&EmulatorContext
,
576 EmulatorBiosOperation
,
577 EmulatorIntAcknowledge
,
578 NULL
/* TODO: Use a TLB */);
582 /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
588 /* Set output functions */
589 PitSetOutFunction(0, NULL
, PitChan0Out
);
590 PitSetOutFunction(1, NULL
, PitChan1Out
);
591 PitSetOutFunction(2, NULL
, PitChan2Out
);
593 /* Register the I/O Ports */
594 RegisterIoPort(CONTROL_SYSTEM_PORT61H
, Port61hRead
, Port61hWrite
);
596 /* Set the console input mode */
597 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
598 // upon console window events (screen buffer resize, ...).
599 SetConsoleMode(ConsoleInput
, ENABLE_PROCESSED_INPUT
/* | ENABLE_WINDOW_INPUT */);
600 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
602 /**/EnableExtraHardware(ConsoleInput
);/**/
604 /* Initialize the PS/2 port */
607 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
611 /**************** ATTACH INPUT WITH CONSOLE *****************/
612 /* Start the input thread */
613 InputThread
= CreateThread(NULL
, 0, &PumpConsoleInput
, ConsoleInput
, 0, NULL
);
614 if (InputThread
== NULL
)
616 DisplayMessage(L
"Failed to create the console input thread.");
619 /************************************************************/
621 /* Initialize the VGA */
622 if (!VgaInitialize(ConsoleOutput
))
624 DisplayMessage(L
"Failed to initialize VGA support.");
628 /* Initialize the software callback system and register the emulator BOPs */
629 InitializeCallbacks();
630 RegisterBop(BOP_DEBUGGER
, EmulatorDebugBreakBop
);
631 RegisterBop(BOP_UNSIMULATE
, EmulatorUnsimulateBop
);
633 /* Initialize VDD support */
639 VOID
EmulatorCleanup(VOID
)
643 /* Close the input thread handle */
644 if (InputThread
!= NULL
) CloseHandle(InputThread
);
656 /* Free the memory allocated for the 16-bit address space */
657 if (BaseAddress
!= NULL
) HeapFree(GetProcessHeap(), 0, BaseAddress
);
671 VDDTerminateVDM(VOID
)
679 Sim32pGetVDMPointer(IN ULONG Address
,
680 IN BOOLEAN ProtectedMode
)
683 UNREFERENCED_PARAMETER(ProtectedMode
);
686 * HIWORD(Address) == Segment (if ProtectedMode == FALSE)
687 * or Selector (if ProtectedMode == TRUE )
688 * LOWORD(Address) == Offset
690 return (PBYTE
)FAR_POINTER(Address
);
695 MGetVdmPointer(IN ULONG Address
,
697 IN BOOLEAN ProtectedMode
)
699 UNREFERENCED_PARAMETER(Size
);
700 return Sim32pGetVDMPointer(Address
, ProtectedMode
);
705 VdmMapFlat(IN USHORT Segment
,
710 UNREFERENCED_PARAMETER(Mode
);
712 return SEG_OFF_TO_PTR(Segment
, Offset
);
717 VdmFlushCache(IN USHORT Segment
,
729 VdmUnmapFlat(IN USHORT Segment
,