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/dma.h"
26 #include "hardware/keyboard.h"
27 #include "hardware/mouse.h"
28 #include "hardware/pic.h"
29 #include "hardware/ps2.h"
30 #include "hardware/sound/speaker.h"
31 #include "hardware/pit.h"
32 #include "hardware/video/vga.h"
38 /* Extra PSDK/NDK Headers */
39 #include <ndk/psfuncs.h>
40 #include <ndk/mmfuncs.h>
42 /* PRIVATE VARIABLES **********************************************************/
44 LPVOID BaseAddress
= NULL
;
45 BOOLEAN VdmRunning
= TRUE
;
47 static BOOLEAN A20Line
= FALSE
;
48 static BYTE Port61hState
= 0x00;
50 static HANDLE InputThread
= NULL
;
52 LPCWSTR ExceptionName
[] =
59 L
"Bound Range Exceeded",
65 #define BOP_DEBUGGER 0x56 // Break into the debugger from a 16-bit app
67 /* PRIVATE FUNCTIONS **********************************************************/
70 EmulatorMoveMemory(OUT VOID UNALIGNED
*Destination
,
71 IN
const VOID UNALIGNED
*Source
,
76 * We use a switch here to detect small moves of memory, as these
77 * constitute the bulk of our moves.
78 * Using RtlMoveMemory for all these small moves would be slow otherwise.
86 *(PUCHAR
)Destination
= *(PUCHAR
)Source
;
90 *(PUSHORT
)Destination
= *(PUSHORT
)Source
;
94 *(PULONG
)Destination
= *(PULONG
)Source
;
97 case sizeof(ULONGLONG
):
98 *(PULONGLONG
)Destination
= *(PULONGLONG
)Source
;
102 #if defined(__GNUC__)
103 __builtin_memmove(Destination
, Source
, Length
);
105 RtlMoveMemory(Destination
, Source
, Length
);
109 #else // defined(_MSC_VER)
111 PUCHAR Dest
= (PUCHAR
)Destination
;
112 PUCHAR Src
= (PUCHAR
)Source
;
114 SIZE_T Count
, NewSize
= Length
;
117 Count
= NewSize
>> 2; // NewSize / sizeof(ULONG);
118 NewSize
= NewSize
& 3; // NewSize % sizeof(ULONG);
119 __movsd(Dest
, Src
, Count
);
120 Dest
+= Count
<< 2; // Count * sizeof(ULONG);
124 Count
= NewSize
>> 1; // NewSize / sizeof(USHORT);
125 NewSize
= NewSize
& 1; // NewSize % sizeof(USHORT);
126 __movsw(Dest
, Src
, Count
);
127 Dest
+= Count
<< 1; // Count * sizeof(USHORT);
131 Count
= NewSize
; // NewSize / sizeof(UCHAR);
132 // NewSize = NewSize; // NewSize % sizeof(UCHAR);
133 __movsb(Dest
, Src
, Count
);
138 VOID WINAPI
EmulatorReadMemory(PFAST486_STATE State
, ULONG Address
, PVOID Buffer
, ULONG Size
)
140 UNREFERENCED_PARAMETER(State
);
142 // BIG HACK!!!! To make BIOS images working correctly,
143 // until Aleksander rewrites memory management!!
144 if (Address
>= 0xFFFFFFF0) Address
-= 0xFFF00000;
146 /* If the A20 line is disabled, mask bit 20 */
147 if (!A20Line
) Address
&= ~(1 << 20);
149 /* Make sure the requested address is valid */
150 if ((Address
+ Size
) >= MAX_ADDRESS
) return;
153 * Check if we are going to read the VGA memory and
154 * copy it into the virtual address space if needed.
156 if (((Address
+ Size
) >= VgaGetVideoBaseAddress())
157 && (Address
< VgaGetVideoLimitAddress()))
159 DWORD VgaAddress
= max(Address
, VgaGetVideoBaseAddress());
160 DWORD ActualSize
= min(Address
+ Size
- 1, VgaGetVideoLimitAddress())
162 LPBYTE DestBuffer
= (LPBYTE
)REAL_TO_PHYS(VgaAddress
);
164 /* Read from the VGA memory */
165 VgaReadMemory(VgaAddress
, DestBuffer
, ActualSize
);
168 /* Read the data from the virtual address space and store it in the buffer */
169 EmulatorMoveMemory(Buffer
, REAL_TO_PHYS(Address
), Size
);
172 VOID WINAPI
EmulatorWriteMemory(PFAST486_STATE State
, ULONG Address
, PVOID Buffer
, ULONG Size
)
174 UNREFERENCED_PARAMETER(State
);
176 // BIG HACK!!!! To make BIOS images working correctly,
177 // until Aleksander rewrites memory management!!
178 if (Address
>= 0xFFFFFFF0) Address
-= 0xFFF00000;
180 /* If the A20 line is disabled, mask bit 20 */
181 if (!A20Line
) Address
&= ~(1 << 20);
183 /* Make sure the requested address is valid */
184 if ((Address
+ Size
) >= MAX_ADDRESS
) return;
186 /* Make sure we don't write to the ROM area */
187 if ((Address
+ Size
) >= ROM_AREA_START
&& (Address
< ROM_AREA_END
)) return;
189 /* Read the data from the buffer and store it in the virtual address space */
190 EmulatorMoveMemory(REAL_TO_PHYS(Address
), Buffer
, Size
);
193 * Check if we modified the VGA memory.
195 if (((Address
+ Size
) >= VgaGetVideoBaseAddress())
196 && (Address
< VgaGetVideoLimitAddress()))
198 DWORD VgaAddress
= max(Address
, VgaGetVideoBaseAddress());
199 DWORD ActualSize
= min(Address
+ Size
- 1, VgaGetVideoLimitAddress())
201 LPBYTE SrcBuffer
= (LPBYTE
)REAL_TO_PHYS(VgaAddress
);
203 /* Write to the VGA memory */
204 VgaWriteMemory(VgaAddress
, SrcBuffer
, ActualSize
);
208 UCHAR WINAPI
EmulatorIntAcknowledge(PFAST486_STATE State
)
210 UNREFERENCED_PARAMETER(State
);
212 /* Get the interrupt number from the PIC */
213 return PicGetInterrupt();
216 VOID WINAPI
EmulatorFpu(PFAST486_STATE State
)
218 /* The FPU is wired to IRQ 13 */
219 PicInterruptRequest(13);
222 VOID
EmulatorException(BYTE ExceptionNumber
, LPWORD Stack
)
224 WORD CodeSegment
, InstructionPointer
;
227 ASSERT(ExceptionNumber
< 8);
230 InstructionPointer
= Stack
[STACK_IP
];
231 CodeSegment
= Stack
[STACK_CS
];
232 Opcode
= (PBYTE
)SEG_OFF_TO_PTR(CodeSegment
, InstructionPointer
);
234 /* Display a message to the user */
235 DisplayMessage(L
"Exception: %s occured at %04X:%04X\n"
236 L
"Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
237 ExceptionName
[ExceptionNumber
],
251 Fast486DumpState(&EmulatorContext
);
258 VOID
EmulatorTerminate(VOID
)
261 CpuUnsimulate(); // Halt the CPU
265 VOID
EmulatorInterruptSignal(VOID
)
267 /* Call the Fast486 API */
268 Fast486InterruptSignal(&EmulatorContext
);
271 VOID
EmulatorSetA20(BOOLEAN Enabled
)
276 static VOID WINAPI
EmulatorDebugBreakBop(LPWORD Stack
)
278 DPRINT1("NTVDM: BOP_DEBUGGER\n");
282 static BYTE WINAPI
Port61hRead(USHORT Port
)
287 static VOID WINAPI
Port61hWrite(USHORT Port
, BYTE Data
)
289 // BOOLEAN SpeakerStateChange = FALSE;
290 BYTE OldPort61hState
= Port61hState
;
292 /* Only the four lowest bytes can be written */
293 Port61hState
= (Port61hState
& 0xF0) | (Data
& 0x0F);
295 if ((OldPort61hState
^ Port61hState
) & 0x01)
297 DPRINT("PIT 2 Gate %s\n", Port61hState
& 0x01 ? "on" : "off");
298 PitSetGate(2, !!(Port61hState
& 0x01));
299 // SpeakerStateChange = TRUE;
302 if ((OldPort61hState
^ Port61hState
) & 0x02)
304 /* There were some change for the speaker... */
305 DPRINT("Speaker %s\n", Port61hState
& 0x02 ? "on" : "off");
306 // SpeakerStateChange = TRUE;
308 // if (SpeakerStateChange) SpeakerChange(Port61hState);
309 SpeakerChange(Port61hState
);
312 static VOID WINAPI
PitChan0Out(LPVOID Param
, BOOLEAN State
)
316 DPRINT("PicInterruptRequest\n");
317 PicInterruptRequest(0); // Raise IRQ 0
319 // else < Lower IRQ 0 >
322 static VOID WINAPI
PitChan1Out(LPVOID Param
, BOOLEAN State
)
327 /* Set bit 4 of Port 61h */
328 Port61hState
|= 1 << 4;
332 /* Clear bit 4 of Port 61h */
333 Port61hState
&= ~(1 << 4);
336 Port61hState
= (Port61hState
& 0xEF) | (State
<< 4);
340 static VOID WINAPI
PitChan2Out(LPVOID Param
, BOOLEAN State
)
342 BYTE OldPort61hState
= Port61hState
;
347 /* Set bit 5 of Port 61h */
348 Port61hState
|= 1 << 5;
352 /* Clear bit 5 of Port 61h */
353 Port61hState
&= ~(1 << 5);
356 Port61hState
= (Port61hState
& 0xDF) | (State
<< 5);
359 if ((OldPort61hState
^ Port61hState
) & 0x20)
361 DPRINT("PitChan2Out -- Port61hState changed\n");
362 SpeakerChange(Port61hState
);
369 PumpConsoleInput(LPVOID Parameter
)
371 HANDLE ConsoleInput
= (HANDLE
)Parameter
;
372 INPUT_RECORD InputRecord
;
377 /* Make sure the task event is signaled */
378 WaitForSingleObject(VdmTaskEvent
, INFINITE
);
380 /* Wait for an input record */
381 if (!ReadConsoleInput(ConsoleInput
, &InputRecord
, 1, &Count
))
383 DWORD LastError
= GetLastError();
384 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput
, Count
, LastError
);
390 /* Check the event type */
391 switch (InputRecord
.EventType
)
397 KeyboardEventHandler(&InputRecord
.Event
.KeyEvent
);
401 MouseEventHandler(&InputRecord
.Event
.MouseEvent
);
404 case WINDOW_BUFFER_SIZE_EVENT
:
405 ScreenEventHandler(&InputRecord
.Event
.WindowBufferSizeEvent
);
412 MenuEventHandler(&InputRecord
.Event
.MenuEvent
);
416 FocusEventHandler(&InputRecord
.Event
.FocusEvent
);
427 static VOID
EnableExtraHardware(HANDLE ConsoleInput
)
431 if (GetConsoleMode(ConsoleInput
, &ConInMode
))
434 // GetNumberOfConsoleMouseButtons();
435 // GetSystemMetrics(SM_CMOUSEBUTTONS);
436 // GetSystemMetrics(SM_MOUSEPRESENT);
440 /* Support mouse input events if there is a mouse on the system */
441 ConInMode
|= ENABLE_MOUSE_INPUT
;
446 /* Do not support mouse input events if there is no mouse on the system */
447 ConInMode
&= ~ENABLE_MOUSE_INPUT
;
451 SetConsoleMode(ConsoleInput
, ConInMode
);
455 /* PUBLIC FUNCTIONS ***********************************************************/
458 DumpMemoryRaw(HANDLE hFile
)
463 /* Dump the VM memory */
464 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
465 Buffer
= REAL_TO_PHYS(NULL
);
466 Size
= MAX_ADDRESS
- (ULONG_PTR
)(NULL
);
467 WriteFile(hFile
, Buffer
, Size
, &Size
, NULL
);
471 DumpMemoryTxt(HANDLE hFile
)
473 #define LINE_SIZE 75 + 2
476 CHAR LineBuffer
[LINE_SIZE
];
480 /* Dump the VM memory */
481 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
482 Ptr1
= Ptr2
= REAL_TO_PHYS(NULL
);
483 while (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr1
) > 0)
488 /* Print the address */
489 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, "%08x ", PHYS_TO_REAL(Ptr1
));
491 /* Print up to 16 bytes... */
493 /* ... in hexadecimal form first... */
495 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr1
) > 0))
497 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, " %02x", *Ptr1
);
501 /* ... align with spaces if needed... */
502 RtlFillMemory(Line
, 0x0F + 4 - i
, ' ');
503 Line
+= 0x0F + 4 - i
;
505 /* ... then in character form. */
507 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr2
) > 0))
509 *Line
++ = ((*Ptr2
>= 0x20 && *Ptr2
<= 0x7E) || (*Ptr2
>= 0x80 && *Ptr2
< 0xFF) ? *Ptr2
: '.');
517 /* Finally write the line to the file */
518 LineSize
= Line
- LineBuffer
;
519 WriteFile(hFile
, LineBuffer
, LineSize
, &LineSize
, NULL
);
523 VOID
DumpMemory(BOOLEAN TextFormat
)
525 static ULONG DumpNumber
= 0;
528 WCHAR FileName
[MAX_PATH
];
530 /* Build a suitable file name */
531 _snwprintf(FileName
, MAX_PATH
,
534 TextFormat
? L
"txt" : L
"dat");
537 DPRINT1("Creating memory dump file '%S'...\n", FileName
);
539 /* Always create the dump file */
540 hFile
= CreateFileW(FileName
,
545 FILE_ATTRIBUTE_NORMAL
,
548 if (hFile
== INVALID_HANDLE_VALUE
)
550 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
551 FileName
, GetLastError());
555 /* Dump the VM memory in the chosen format */
557 DumpMemoryTxt(hFile
);
559 DumpMemoryRaw(hFile
);
564 DPRINT1("Memory dump done\n");
567 BOOLEAN
EmulatorInitialize(HANDLE ConsoleInput
, HANDLE ConsoleOutput
)
571 /* Allocate 16 MB memory for the 16-bit address space */
572 BaseAddress
= HeapAlloc(GetProcessHeap(), /*HEAP_ZERO_MEMORY*/ 0, MAX_ADDRESS
);
573 if (BaseAddress
== NULL
)
575 wprintf(L
"FATAL: Failed to allocate VDM memory.\n");
582 SIZE_T MemorySize
= MAX_ADDRESS
; // See: kernel32/client/vdm.c!BaseGetVdmConfigInfo
585 * The reserved region starts from the very first page.
586 * We need to commit the reserved first 16 MB virtual address.
588 BaseAddress
= (PVOID
)1; // NULL has another signification for NtAllocateVirtualMemory
591 * Since to get NULL, we allocated from 0x1, account for this.
592 * See also: kernel32/client/proc.c!CreateProcessInternalW
596 /* Commit the reserved memory */
597 Status
= NtAllocateVirtualMemory(NtCurrentProcess(),
602 PAGE_EXECUTE_READWRITE
);
603 if (!NT_SUCCESS(Status
))
605 wprintf(L
"FATAL: Failed to commit VDM memory, Status 0x%08lx\n", Status
);
609 ASSERT(BaseAddress
== NULL
);
614 * For diagnostics purposes, we fill the memory with INT 0x03 codes
615 * so that if a program wants to execute random code in memory, we can
616 * retrieve the exact CS:IP where the problem happens.
618 RtlFillMemory(BaseAddress
, MAX_ADDRESS
, 0xCC);
620 /* Initialize I/O ports */
626 /* Initialize the CPU */
628 /* Initialize the internal clock */
629 if (!ClockInitialize())
631 wprintf(L
"FATAL: Failed to initialize the clock\n");
636 /* Initialize the CPU */
642 /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
648 /* Set output functions */
649 PitSetOutFunction(0, NULL
, PitChan0Out
);
650 PitSetOutFunction(1, NULL
, PitChan1Out
);
651 PitSetOutFunction(2, NULL
, PitChan2Out
);
653 /* Register the I/O Ports */
654 RegisterIoPort(CONTROL_SYSTEM_PORT61H
, Port61hRead
, Port61hWrite
);
656 /* Set the console input mode */
657 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
658 // upon console window events (screen buffer resize, ...).
659 SetConsoleMode(ConsoleInput
, ENABLE_PROCESSED_INPUT
/* | ENABLE_WINDOW_INPUT */);
660 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
662 /**/EnableExtraHardware(ConsoleInput
);/**/
664 /* Initialize the PS/2 port */
667 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
671 /**************** ATTACH INPUT WITH CONSOLE *****************/
672 /* Start the input thread */
673 InputThread
= CreateThread(NULL
, 0, &PumpConsoleInput
, ConsoleInput
, 0, NULL
);
674 if (InputThread
== NULL
)
676 DisplayMessage(L
"Failed to create the console input thread.");
680 /************************************************************/
682 /* Initialize the VGA */
683 if (!VgaInitialize(ConsoleOutput
))
685 DisplayMessage(L
"Failed to initialize VGA support.");
690 /* Initialize the software callback system and register the emulator BOPs */
692 RegisterBop(BOP_DEBUGGER
, EmulatorDebugBreakBop
);
693 // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop);
695 /* Initialize VDD support */
701 VOID
EmulatorCleanup(VOID
)
705 SIZE_T MemorySize
= MAX_ADDRESS
;
710 /* Close the input thread handle */
711 if (InputThread
!= NULL
) CloseHandle(InputThread
);
727 /* Free the memory allocated for the 16-bit address space */
728 if (BaseAddress
!= NULL
) HeapFree(GetProcessHeap(), 0, BaseAddress
);
732 /* The reserved region starts from the very first page */
733 // BaseAddress = (PVOID)1;
735 /* Since to get NULL, we allocated from 0x1, account for this */
738 Status
= NtFreeVirtualMemory(NtCurrentProcess(),
742 if (!NT_SUCCESS(Status
))
744 DPRINT1("NTVDM: Failed to decommit VDM memory, Status 0x%08lx\n", Status
);
760 VDDTerminateVDM(VOID
)
768 Sim32pGetVDMPointer(IN ULONG Address
,
769 IN BOOLEAN ProtectedMode
)
772 UNREFERENCED_PARAMETER(ProtectedMode
);
775 * HIWORD(Address) == Segment (if ProtectedMode == FALSE)
776 * or Selector (if ProtectedMode == TRUE )
777 * LOWORD(Address) == Offset
779 return (PBYTE
)FAR_POINTER(Address
);
784 MGetVdmPointer(IN ULONG Address
,
786 IN BOOLEAN ProtectedMode
)
788 UNREFERENCED_PARAMETER(Size
);
789 return Sim32pGetVDMPointer(Address
, ProtectedMode
);
794 VdmMapFlat(IN USHORT Segment
,
799 UNREFERENCED_PARAMETER(Mode
);
801 return SEG_OFF_TO_PTR(Segment
, Offset
);
806 VdmFlushCache(IN USHORT Segment
,
818 VdmUnmapFlat(IN USHORT Segment
,