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 *******************************************************************/
15 #include "cpu/callback.h"
24 #include "hardware/cmos.h"
25 #include "hardware/keyboard.h"
26 #include "hardware/mouse.h"
27 #include "hardware/pic.h"
28 #include "hardware/ps2.h"
29 #include "hardware/sound/speaker.h"
30 #include "hardware/pit.h"
31 #include "hardware/video/vga.h"
36 /* Extra PSDK/NDK Headers */
37 #include <ndk/psfuncs.h>
38 #include <ndk/mmfuncs.h>
40 /* PRIVATE VARIABLES **********************************************************/
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 **********************************************************/
68 EmulatorMoveMemory(OUT VOID UNALIGNED
*Destination
,
69 IN
const VOID UNALIGNED
*Source
,
74 * We use a switch here to detect small moves of memory, as these
75 * constitute the bulk of our moves.
76 * Using RtlMoveMemory for all these small moves would be slow otherwise.
84 *(PUCHAR
)Destination
= *(PUCHAR
)Source
;
88 *(PUSHORT
)Destination
= *(PUSHORT
)Source
;
92 *(PULONG
)Destination
= *(PULONG
)Source
;
95 case sizeof(ULONGLONG
):
96 *(PULONGLONG
)Destination
= *(PULONGLONG
)Source
;
100 #if defined(__GNUC__)
101 __builtin_memmove(Destination
, Source
, Length
);
103 RtlMoveMemory(Destination
, Source
, Length
);
107 #else // defined(_MSC_VER)
109 PUCHAR Dest
= (PUCHAR
)Destination
;
110 PUCHAR Src
= (PUCHAR
)Source
;
112 SIZE_T Count
, NewSize
= Length
;
115 Count
= NewSize
>> 2; // NewSize / sizeof(ULONG);
116 NewSize
= NewSize
& 3; // NewSize % sizeof(ULONG);
117 __movsd(Dest
, Src
, Count
);
118 Dest
+= Count
<< 2; // Count * sizeof(ULONG);
122 Count
= NewSize
>> 1; // NewSize / sizeof(USHORT);
123 NewSize
= NewSize
& 1; // NewSize % sizeof(USHORT);
124 __movsw(Dest
, Src
, Count
);
125 Dest
+= Count
<< 1; // Count * sizeof(USHORT);
129 Count
= NewSize
; // NewSize / sizeof(UCHAR);
130 // NewSize = NewSize; // NewSize % sizeof(UCHAR);
131 __movsb(Dest
, Src
, Count
);
136 VOID WINAPI
EmulatorReadMemory(PFAST486_STATE State
, ULONG Address
, PVOID Buffer
, ULONG Size
)
138 UNREFERENCED_PARAMETER(State
);
140 // BIG HACK!!!! To make BIOS images working correctly,
141 // until Aleksander rewrites memory management!!
142 if (Address
>= 0xFFFFFFF0) Address
-= 0xFFF00000;
144 /* If the A20 line is disabled, mask bit 20 */
145 if (!A20Line
) Address
&= ~(1 << 20);
147 /* Make sure the requested address is valid */
148 if ((Address
+ Size
) >= MAX_ADDRESS
) return;
151 * Check if we are going to read the VGA memory and
152 * copy it into the virtual address space if needed.
154 if (((Address
+ Size
) >= VgaGetVideoBaseAddress())
155 && (Address
< VgaGetVideoLimitAddress()))
157 DWORD VgaAddress
= max(Address
, VgaGetVideoBaseAddress());
158 DWORD ActualSize
= min(Address
+ Size
- 1, VgaGetVideoLimitAddress())
160 LPBYTE DestBuffer
= (LPBYTE
)REAL_TO_PHYS(VgaAddress
);
162 /* Read from the VGA memory */
163 VgaReadMemory(VgaAddress
, DestBuffer
, ActualSize
);
166 /* Read the data from the virtual address space and store it in the buffer */
167 EmulatorMoveMemory(Buffer
, REAL_TO_PHYS(Address
), Size
);
170 VOID WINAPI
EmulatorWriteMemory(PFAST486_STATE State
, ULONG Address
, PVOID Buffer
, ULONG Size
)
172 UNREFERENCED_PARAMETER(State
);
174 // BIG HACK!!!! To make BIOS images working correctly,
175 // until Aleksander rewrites memory management!!
176 if (Address
>= 0xFFFFFFF0) Address
-= 0xFFF00000;
178 /* If the A20 line is disabled, mask bit 20 */
179 if (!A20Line
) Address
&= ~(1 << 20);
181 /* Make sure the requested address is valid */
182 if ((Address
+ Size
) >= MAX_ADDRESS
) return;
184 /* Make sure we don't write to the ROM area */
185 if ((Address
+ Size
) >= ROM_AREA_START
&& (Address
< ROM_AREA_END
)) return;
187 /* Read the data from the buffer and store it in the virtual address space */
188 EmulatorMoveMemory(REAL_TO_PHYS(Address
), Buffer
, Size
);
191 * Check if we modified the VGA memory.
193 if (((Address
+ Size
) >= VgaGetVideoBaseAddress())
194 && (Address
< VgaGetVideoLimitAddress()))
196 DWORD VgaAddress
= max(Address
, VgaGetVideoBaseAddress());
197 DWORD ActualSize
= min(Address
+ Size
- 1, VgaGetVideoLimitAddress())
199 LPBYTE SrcBuffer
= (LPBYTE
)REAL_TO_PHYS(VgaAddress
);
201 /* Write to the VGA memory */
202 VgaWriteMemory(VgaAddress
, SrcBuffer
, ActualSize
);
206 UCHAR WINAPI
EmulatorIntAcknowledge(PFAST486_STATE State
)
208 UNREFERENCED_PARAMETER(State
);
210 /* Get the interrupt number from the PIC */
211 return PicGetInterrupt();
214 VOID
EmulatorException(BYTE ExceptionNumber
, LPWORD Stack
)
216 WORD CodeSegment
, InstructionPointer
;
219 ASSERT(ExceptionNumber
< 8);
222 InstructionPointer
= Stack
[STACK_IP
];
223 CodeSegment
= Stack
[STACK_CS
];
224 Opcode
= (PBYTE
)SEG_OFF_TO_PTR(CodeSegment
, InstructionPointer
);
226 /* Display a message to the user */
227 DisplayMessage(L
"Exception: %s occured at %04X:%04X\n"
228 L
"Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
229 ExceptionName
[ExceptionNumber
],
243 Fast486DumpState(&EmulatorContext
);
250 VOID
EmulatorTerminate(VOID
)
253 CpuUnsimulate(); // Halt the CPU
257 VOID
EmulatorInterruptSignal(VOID
)
259 /* Call the Fast486 API */
260 Fast486InterruptSignal(&EmulatorContext
);
263 VOID
EmulatorSetA20(BOOLEAN Enabled
)
268 static VOID WINAPI
EmulatorDebugBreakBop(LPWORD Stack
)
270 DPRINT1("NTVDM: BOP_DEBUGGER\n");
274 static BYTE WINAPI
Port61hRead(USHORT Port
)
279 static VOID WINAPI
Port61hWrite(USHORT Port
, BYTE Data
)
281 // BOOLEAN SpeakerStateChange = FALSE;
282 BYTE OldPort61hState
= Port61hState
;
284 /* Only the four lowest bytes can be written */
285 Port61hState
= (Port61hState
& 0xF0) | (Data
& 0x0F);
287 if ((OldPort61hState
^ Port61hState
) & 0x01)
289 DPRINT("PIT 2 Gate %s\n", Port61hState
& 0x01 ? "on" : "off");
290 PitSetGate(2, !!(Port61hState
& 0x01));
291 // SpeakerStateChange = TRUE;
294 if ((OldPort61hState
^ Port61hState
) & 0x02)
296 /* There were some change for the speaker... */
297 DPRINT("Speaker %s\n", Port61hState
& 0x02 ? "on" : "off");
298 // SpeakerStateChange = TRUE;
300 // if (SpeakerStateChange) SpeakerChange(Port61hState);
301 SpeakerChange(Port61hState
);
304 static VOID WINAPI
PitChan0Out(LPVOID Param
, BOOLEAN State
)
308 DPRINT("PicInterruptRequest\n");
309 PicInterruptRequest(0); // Raise IRQ 0
311 // else < Lower IRQ 0 >
314 static VOID WINAPI
PitChan1Out(LPVOID Param
, BOOLEAN State
)
319 /* Set bit 4 of Port 61h */
320 Port61hState
|= 1 << 4;
324 /* Clear bit 4 of Port 61h */
325 Port61hState
&= ~(1 << 4);
328 Port61hState
= (Port61hState
& 0xEF) | (State
<< 4);
332 static VOID WINAPI
PitChan2Out(LPVOID Param
, BOOLEAN State
)
334 BYTE OldPort61hState
= Port61hState
;
339 /* Set bit 5 of Port 61h */
340 Port61hState
|= 1 << 5;
344 /* Clear bit 5 of Port 61h */
345 Port61hState
&= ~(1 << 5);
348 Port61hState
= (Port61hState
& 0xDF) | (State
<< 5);
351 if ((OldPort61hState
^ Port61hState
) & 0x20)
353 DPRINT("PitChan2Out -- Port61hState changed\n");
354 SpeakerChange(Port61hState
);
361 PumpConsoleInput(LPVOID Parameter
)
363 HANDLE ConsoleInput
= (HANDLE
)Parameter
;
364 INPUT_RECORD InputRecord
;
369 /* Make sure the task event is signaled */
370 WaitForSingleObject(VdmTaskEvent
, INFINITE
);
372 /* Wait for an input record */
373 if (!ReadConsoleInput(ConsoleInput
, &InputRecord
, 1, &Count
))
375 DWORD LastError
= GetLastError();
376 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput
, Count
, LastError
);
382 /* Check the event type */
383 switch (InputRecord
.EventType
)
389 KeyboardEventHandler(&InputRecord
.Event
.KeyEvent
);
393 MouseEventHandler(&InputRecord
.Event
.MouseEvent
);
396 case WINDOW_BUFFER_SIZE_EVENT
:
397 ScreenEventHandler(&InputRecord
.Event
.WindowBufferSizeEvent
);
404 MenuEventHandler(&InputRecord
.Event
.MenuEvent
);
408 FocusEventHandler(&InputRecord
.Event
.FocusEvent
);
419 static VOID
EnableExtraHardware(HANDLE ConsoleInput
)
423 if (GetConsoleMode(ConsoleInput
, &ConInMode
))
426 // GetNumberOfConsoleMouseButtons();
427 // GetSystemMetrics(SM_CMOUSEBUTTONS);
428 // GetSystemMetrics(SM_MOUSEPRESENT);
432 /* Support mouse input events if there is a mouse on the system */
433 ConInMode
|= ENABLE_MOUSE_INPUT
;
438 /* Do not support mouse input events if there is no mouse on the system */
439 ConInMode
&= ~ENABLE_MOUSE_INPUT
;
443 SetConsoleMode(ConsoleInput
, ConInMode
);
447 /* PUBLIC FUNCTIONS ***********************************************************/
450 DumpMemoryRaw(HANDLE hFile
)
455 /* Dump the VM memory */
456 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
457 Buffer
= REAL_TO_PHYS(NULL
);
458 Size
= MAX_ADDRESS
- (ULONG_PTR
)(NULL
);
459 WriteFile(hFile
, Buffer
, Size
, &Size
, NULL
);
463 DumpMemoryTxt(HANDLE hFile
)
465 #define LINE_SIZE 75 + 2
468 CHAR LineBuffer
[LINE_SIZE
];
472 /* Dump the VM memory */
473 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
474 Ptr1
= Ptr2
= REAL_TO_PHYS(NULL
);
475 while (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr1
) > 0)
480 /* Print the address */
481 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, "%08x ", PHYS_TO_REAL(Ptr1
));
483 /* Print up to 16 bytes... */
485 /* ... in hexadecimal form first... */
487 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr1
) > 0))
489 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, " %02x", *Ptr1
);
493 /* ... align with spaces if needed... */
494 RtlFillMemory(Line
, 0x0F + 4 - i
, ' ');
495 Line
+= 0x0F + 4 - i
;
497 /* ... then in character form. */
499 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr2
) > 0))
501 *Line
++ = ((*Ptr2
>= 0x20 && *Ptr2
<= 0x7E) || (*Ptr2
>= 0x80 && *Ptr2
< 0xFF) ? *Ptr2
: '.');
509 /* Finally write the line to the file */
510 LineSize
= Line
- LineBuffer
;
511 WriteFile(hFile
, LineBuffer
, LineSize
, &LineSize
, NULL
);
515 VOID
DumpMemory(BOOLEAN TextFormat
)
517 static ULONG DumpNumber
= 0;
520 WCHAR FileName
[MAX_PATH
];
522 /* Build a suitable file name */
523 _snwprintf(FileName
, MAX_PATH
,
526 TextFormat
? L
"txt" : L
"dat");
529 DPRINT1("Creating memory dump file '%S'...\n", FileName
);
531 /* Always create the dump file */
532 hFile
= CreateFileW(FileName
,
537 FILE_ATTRIBUTE_NORMAL
,
540 if (hFile
== INVALID_HANDLE_VALUE
)
542 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
543 FileName
, GetLastError());
547 /* Dump the VM memory in the chosen format */
549 DumpMemoryTxt(hFile
);
551 DumpMemoryRaw(hFile
);
556 DPRINT1("Memory dump done\n");
559 BOOLEAN
EmulatorInitialize(HANDLE ConsoleInput
, HANDLE ConsoleOutput
)
563 /* Allocate 16 MB memory for the 16-bit address space */
564 BaseAddress
= HeapAlloc(GetProcessHeap(), /*HEAP_ZERO_MEMORY*/ 0, MAX_ADDRESS
);
565 if (BaseAddress
== NULL
)
567 wprintf(L
"FATAL: Failed to allocate VDM memory.\n");
574 SIZE_T MemorySize
= MAX_ADDRESS
; // See: kernel32/client/vdm.c!BaseGetVdmConfigInfo
577 * The reserved region starts from the very first page.
578 * We need to commit the reserved first 16 MB virtual address.
580 BaseAddress
= (PVOID
)1; // NULL has another signification for NtAllocateVirtualMemory
583 * Since to get NULL, we allocated from 0x1, account for this.
584 * See also: kernel32/client/proc.c!CreateProcessInternalW
588 /* Commit the reserved memory */
589 Status
= NtAllocateVirtualMemory(NtCurrentProcess(),
594 PAGE_EXECUTE_READWRITE
);
595 if (!NT_SUCCESS(Status
))
597 wprintf(L
"FATAL: Failed to commit VDM memory, Status 0x%08lx\n", Status
);
601 ASSERT(BaseAddress
== NULL
);
606 * For diagnostics purposes, we fill the memory with INT 0x03 codes
607 * so that if a program wants to execute random code in memory, we can
608 * retrieve the exact CS:IP where the problem happens.
610 RtlFillMemory(BaseAddress
, MAX_ADDRESS
, 0xCC);
612 /* Initialize I/O ports */
615 /* Initialize the CPU */
617 /* Initialize the internal clock */
618 if (!ClockInitialize())
620 wprintf(L
"FATAL: Failed to initialize the clock\n");
625 /* Initialize the CPU */
630 /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
636 /* Set output functions */
637 PitSetOutFunction(0, NULL
, PitChan0Out
);
638 PitSetOutFunction(1, NULL
, PitChan1Out
);
639 PitSetOutFunction(2, NULL
, PitChan2Out
);
641 /* Register the I/O Ports */
642 RegisterIoPort(CONTROL_SYSTEM_PORT61H
, Port61hRead
, Port61hWrite
);
644 /* Set the console input mode */
645 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
646 // upon console window events (screen buffer resize, ...).
647 SetConsoleMode(ConsoleInput
, ENABLE_PROCESSED_INPUT
/* | ENABLE_WINDOW_INPUT */);
648 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
650 /**/EnableExtraHardware(ConsoleInput
);/**/
652 /* Initialize the PS/2 port */
655 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
659 /**************** ATTACH INPUT WITH CONSOLE *****************/
660 /* Start the input thread */
661 InputThread
= CreateThread(NULL
, 0, &PumpConsoleInput
, ConsoleInput
, 0, NULL
);
662 if (InputThread
== NULL
)
664 DisplayMessage(L
"Failed to create the console input thread.");
668 /************************************************************/
670 /* Initialize the VGA */
671 if (!VgaInitialize(ConsoleOutput
))
673 DisplayMessage(L
"Failed to initialize VGA support.");
678 /* Initialize the software callback system and register the emulator BOPs */
680 RegisterBop(BOP_DEBUGGER
, EmulatorDebugBreakBop
);
681 // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop);
683 /* Initialize VDD support */
689 VOID
EmulatorCleanup(VOID
)
693 SIZE_T MemorySize
= MAX_ADDRESS
;
698 /* Close the input thread handle */
699 if (InputThread
!= NULL
) CloseHandle(InputThread
);
713 /* Free the memory allocated for the 16-bit address space */
714 if (BaseAddress
!= NULL
) HeapFree(GetProcessHeap(), 0, BaseAddress
);
718 /* The reserved region starts from the very first page */
719 // BaseAddress = (PVOID)1;
721 /* Since to get NULL, we allocated from 0x1, account for this */
724 Status
= NtFreeVirtualMemory(NtCurrentProcess(),
728 if (!NT_SUCCESS(Status
))
730 DPRINT1("NTVDM: Failed to decommit VDM memory, Status 0x%08lx\n", Status
);
746 VDDTerminateVDM(VOID
)
754 Sim32pGetVDMPointer(IN ULONG Address
,
755 IN BOOLEAN ProtectedMode
)
758 UNREFERENCED_PARAMETER(ProtectedMode
);
761 * HIWORD(Address) == Segment (if ProtectedMode == FALSE)
762 * or Selector (if ProtectedMode == TRUE )
763 * LOWORD(Address) == Offset
765 return (PBYTE
)FAR_POINTER(Address
);
770 MGetVdmPointer(IN ULONG Address
,
772 IN BOOLEAN ProtectedMode
)
774 UNREFERENCED_PARAMETER(Size
);
775 return Sim32pGetVDMPointer(Address
, ProtectedMode
);
780 VdmMapFlat(IN USHORT Segment
,
785 UNREFERENCED_PARAMETER(Mode
);
787 return SEG_OFF_TO_PTR(Segment
, Offset
);
792 VdmFlushCache(IN USHORT Segment
,
804 VdmUnmapFlat(IN USHORT Segment
,