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"
37 /* Extra PSDK/NDK Headers */
38 #include <ndk/psfuncs.h>
39 #include <ndk/mmfuncs.h>
41 /* PRIVATE VARIABLES **********************************************************/
43 LPVOID BaseAddress
= NULL
;
44 BOOLEAN VdmRunning
= TRUE
;
46 static BOOLEAN A20Line
= FALSE
;
47 static BYTE Port61hState
= 0x00;
49 static HANDLE InputThread
= NULL
;
51 LPCWSTR ExceptionName
[] =
58 L
"Bound Range Exceeded",
64 #define BOP_DEBUGGER 0x56 // Break into the debugger from a 16-bit app
66 /* PRIVATE FUNCTIONS **********************************************************/
69 EmulatorMoveMemory(OUT VOID UNALIGNED
*Destination
,
70 IN
const VOID UNALIGNED
*Source
,
75 * We use a switch here to detect small moves of memory, as these
76 * constitute the bulk of our moves.
77 * Using RtlMoveMemory for all these small moves would be slow otherwise.
85 *(PUCHAR
)Destination
= *(PUCHAR
)Source
;
89 *(PUSHORT
)Destination
= *(PUSHORT
)Source
;
93 *(PULONG
)Destination
= *(PULONG
)Source
;
96 case sizeof(ULONGLONG
):
97 *(PULONGLONG
)Destination
= *(PULONGLONG
)Source
;
101 #if defined(__GNUC__)
102 __builtin_memmove(Destination
, Source
, Length
);
104 RtlMoveMemory(Destination
, Source
, Length
);
108 #else // defined(_MSC_VER)
110 PUCHAR Dest
= (PUCHAR
)Destination
;
111 PUCHAR Src
= (PUCHAR
)Source
;
113 SIZE_T Count
, NewSize
= Length
;
116 Count
= NewSize
>> 2; // NewSize / sizeof(ULONG);
117 NewSize
= NewSize
& 3; // NewSize % sizeof(ULONG);
118 __movsd(Dest
, Src
, Count
);
119 Dest
+= Count
<< 2; // Count * sizeof(ULONG);
123 Count
= NewSize
>> 1; // NewSize / sizeof(USHORT);
124 NewSize
= NewSize
& 1; // NewSize % sizeof(USHORT);
125 __movsw(Dest
, Src
, Count
);
126 Dest
+= Count
<< 1; // Count * sizeof(USHORT);
130 Count
= NewSize
; // NewSize / sizeof(UCHAR);
131 // NewSize = NewSize; // NewSize % sizeof(UCHAR);
132 __movsb(Dest
, Src
, Count
);
137 VOID WINAPI
EmulatorReadMemory(PFAST486_STATE State
, ULONG Address
, PVOID Buffer
, ULONG Size
)
139 UNREFERENCED_PARAMETER(State
);
141 // BIG HACK!!!! To make BIOS images working correctly,
142 // until Aleksander rewrites memory management!!
143 if (Address
>= 0xFFFFFFF0) Address
-= 0xFFF00000;
145 /* If the A20 line is disabled, mask bit 20 */
146 if (!A20Line
) Address
&= ~(1 << 20);
148 /* Make sure the requested address is valid */
149 if ((Address
+ Size
) >= MAX_ADDRESS
) return;
152 * Check if we are going to read the VGA memory and
153 * copy it into the virtual address space if needed.
155 if (((Address
+ Size
) >= VgaGetVideoBaseAddress())
156 && (Address
< VgaGetVideoLimitAddress()))
158 DWORD VgaAddress
= max(Address
, VgaGetVideoBaseAddress());
159 DWORD ActualSize
= min(Address
+ Size
- 1, VgaGetVideoLimitAddress())
161 LPBYTE DestBuffer
= (LPBYTE
)REAL_TO_PHYS(VgaAddress
);
163 /* Read from the VGA memory */
164 VgaReadMemory(VgaAddress
, DestBuffer
, ActualSize
);
167 /* Read the data from the virtual address space and store it in the buffer */
168 EmulatorMoveMemory(Buffer
, REAL_TO_PHYS(Address
), Size
);
171 VOID WINAPI
EmulatorWriteMemory(PFAST486_STATE State
, ULONG Address
, PVOID Buffer
, ULONG Size
)
173 UNREFERENCED_PARAMETER(State
);
175 // BIG HACK!!!! To make BIOS images working correctly,
176 // until Aleksander rewrites memory management!!
177 if (Address
>= 0xFFFFFFF0) Address
-= 0xFFF00000;
179 /* If the A20 line is disabled, mask bit 20 */
180 if (!A20Line
) Address
&= ~(1 << 20);
182 /* Make sure the requested address is valid */
183 if ((Address
+ Size
) >= MAX_ADDRESS
) return;
185 /* Make sure we don't write to the ROM area */
186 if ((Address
+ Size
) >= ROM_AREA_START
&& (Address
< ROM_AREA_END
)) return;
188 /* Read the data from the buffer and store it in the virtual address space */
189 EmulatorMoveMemory(REAL_TO_PHYS(Address
), Buffer
, Size
);
192 * Check if we modified the VGA memory.
194 if (((Address
+ Size
) >= VgaGetVideoBaseAddress())
195 && (Address
< VgaGetVideoLimitAddress()))
197 DWORD VgaAddress
= max(Address
, VgaGetVideoBaseAddress());
198 DWORD ActualSize
= min(Address
+ Size
- 1, VgaGetVideoLimitAddress())
200 LPBYTE SrcBuffer
= (LPBYTE
)REAL_TO_PHYS(VgaAddress
);
202 /* Write to the VGA memory */
203 VgaWriteMemory(VgaAddress
, SrcBuffer
, ActualSize
);
207 UCHAR WINAPI
EmulatorIntAcknowledge(PFAST486_STATE State
)
209 UNREFERENCED_PARAMETER(State
);
211 /* Get the interrupt number from the PIC */
212 return PicGetInterrupt();
215 VOID WINAPI
EmulatorFpu(PFAST486_STATE State
)
217 /* The FPU is wired to IRQ 13 */
218 PicInterruptRequest(13);
221 VOID
EmulatorException(BYTE ExceptionNumber
, LPWORD Stack
)
223 WORD CodeSegment
, InstructionPointer
;
226 ASSERT(ExceptionNumber
< 8);
229 InstructionPointer
= Stack
[STACK_IP
];
230 CodeSegment
= Stack
[STACK_CS
];
231 Opcode
= (PBYTE
)SEG_OFF_TO_PTR(CodeSegment
, InstructionPointer
);
233 /* Display a message to the user */
234 DisplayMessage(L
"Exception: %s occured at %04X:%04X\n"
235 L
"Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
236 ExceptionName
[ExceptionNumber
],
250 Fast486DumpState(&EmulatorContext
);
257 VOID
EmulatorTerminate(VOID
)
260 CpuUnsimulate(); // Halt the CPU
264 VOID
EmulatorInterruptSignal(VOID
)
266 /* Call the Fast486 API */
267 Fast486InterruptSignal(&EmulatorContext
);
270 VOID
EmulatorSetA20(BOOLEAN Enabled
)
275 static VOID WINAPI
EmulatorDebugBreakBop(LPWORD Stack
)
277 DPRINT1("NTVDM: BOP_DEBUGGER\n");
281 static BYTE WINAPI
Port61hRead(USHORT Port
)
286 static VOID WINAPI
Port61hWrite(USHORT Port
, BYTE Data
)
288 // BOOLEAN SpeakerStateChange = FALSE;
289 BYTE OldPort61hState
= Port61hState
;
291 /* Only the four lowest bytes can be written */
292 Port61hState
= (Port61hState
& 0xF0) | (Data
& 0x0F);
294 if ((OldPort61hState
^ Port61hState
) & 0x01)
296 DPRINT("PIT 2 Gate %s\n", Port61hState
& 0x01 ? "on" : "off");
297 PitSetGate(2, !!(Port61hState
& 0x01));
298 // SpeakerStateChange = TRUE;
301 if ((OldPort61hState
^ Port61hState
) & 0x02)
303 /* There were some change for the speaker... */
304 DPRINT("Speaker %s\n", Port61hState
& 0x02 ? "on" : "off");
305 // SpeakerStateChange = TRUE;
307 // if (SpeakerStateChange) SpeakerChange(Port61hState);
308 SpeakerChange(Port61hState
);
311 static VOID WINAPI
PitChan0Out(LPVOID Param
, BOOLEAN State
)
315 DPRINT("PicInterruptRequest\n");
316 PicInterruptRequest(0); // Raise IRQ 0
318 // else < Lower IRQ 0 >
321 static VOID WINAPI
PitChan1Out(LPVOID Param
, BOOLEAN State
)
326 /* Set bit 4 of Port 61h */
327 Port61hState
|= 1 << 4;
331 /* Clear bit 4 of Port 61h */
332 Port61hState
&= ~(1 << 4);
335 Port61hState
= (Port61hState
& 0xEF) | (State
<< 4);
339 static VOID WINAPI
PitChan2Out(LPVOID Param
, BOOLEAN State
)
341 BYTE OldPort61hState
= Port61hState
;
346 /* Set bit 5 of Port 61h */
347 Port61hState
|= 1 << 5;
351 /* Clear bit 5 of Port 61h */
352 Port61hState
&= ~(1 << 5);
355 Port61hState
= (Port61hState
& 0xDF) | (State
<< 5);
358 if ((OldPort61hState
^ Port61hState
) & 0x20)
360 DPRINT("PitChan2Out -- Port61hState changed\n");
361 SpeakerChange(Port61hState
);
368 PumpConsoleInput(LPVOID Parameter
)
370 HANDLE ConsoleInput
= (HANDLE
)Parameter
;
371 INPUT_RECORD InputRecord
;
376 /* Make sure the task event is signaled */
377 WaitForSingleObject(VdmTaskEvent
, INFINITE
);
379 /* Wait for an input record */
380 if (!ReadConsoleInput(ConsoleInput
, &InputRecord
, 1, &Count
))
382 DWORD LastError
= GetLastError();
383 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput
, Count
, LastError
);
389 /* Check the event type */
390 switch (InputRecord
.EventType
)
396 KeyboardEventHandler(&InputRecord
.Event
.KeyEvent
);
400 MouseEventHandler(&InputRecord
.Event
.MouseEvent
);
403 case WINDOW_BUFFER_SIZE_EVENT
:
404 ScreenEventHandler(&InputRecord
.Event
.WindowBufferSizeEvent
);
411 MenuEventHandler(&InputRecord
.Event
.MenuEvent
);
415 FocusEventHandler(&InputRecord
.Event
.FocusEvent
);
426 static VOID
EnableExtraHardware(HANDLE ConsoleInput
)
430 if (GetConsoleMode(ConsoleInput
, &ConInMode
))
433 // GetNumberOfConsoleMouseButtons();
434 // GetSystemMetrics(SM_CMOUSEBUTTONS);
435 // GetSystemMetrics(SM_MOUSEPRESENT);
439 /* Support mouse input events if there is a mouse on the system */
440 ConInMode
|= ENABLE_MOUSE_INPUT
;
445 /* Do not support mouse input events if there is no mouse on the system */
446 ConInMode
&= ~ENABLE_MOUSE_INPUT
;
450 SetConsoleMode(ConsoleInput
, ConInMode
);
454 /* PUBLIC FUNCTIONS ***********************************************************/
457 DumpMemoryRaw(HANDLE hFile
)
462 /* Dump the VM memory */
463 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
464 Buffer
= REAL_TO_PHYS(NULL
);
465 Size
= MAX_ADDRESS
- (ULONG_PTR
)(NULL
);
466 WriteFile(hFile
, Buffer
, Size
, &Size
, NULL
);
470 DumpMemoryTxt(HANDLE hFile
)
472 #define LINE_SIZE 75 + 2
475 CHAR LineBuffer
[LINE_SIZE
];
479 /* Dump the VM memory */
480 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
481 Ptr1
= Ptr2
= REAL_TO_PHYS(NULL
);
482 while (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr1
) > 0)
487 /* Print the address */
488 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, "%08x ", PHYS_TO_REAL(Ptr1
));
490 /* Print up to 16 bytes... */
492 /* ... in hexadecimal form first... */
494 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr1
) > 0))
496 Line
+= snprintf(Line
, LINE_SIZE
+ LineBuffer
- Line
, " %02x", *Ptr1
);
500 /* ... align with spaces if needed... */
501 RtlFillMemory(Line
, 0x0F + 4 - i
, ' ');
502 Line
+= 0x0F + 4 - i
;
504 /* ... then in character form. */
506 while (i
++ <= 0x0F && (MAX_ADDRESS
- (ULONG_PTR
)PHYS_TO_REAL(Ptr2
) > 0))
508 *Line
++ = ((*Ptr2
>= 0x20 && *Ptr2
<= 0x7E) || (*Ptr2
>= 0x80 && *Ptr2
< 0xFF) ? *Ptr2
: '.');
516 /* Finally write the line to the file */
517 LineSize
= Line
- LineBuffer
;
518 WriteFile(hFile
, LineBuffer
, LineSize
, &LineSize
, NULL
);
522 VOID
DumpMemory(BOOLEAN TextFormat
)
524 static ULONG DumpNumber
= 0;
527 WCHAR FileName
[MAX_PATH
];
529 /* Build a suitable file name */
530 _snwprintf(FileName
, MAX_PATH
,
533 TextFormat
? L
"txt" : L
"dat");
536 DPRINT1("Creating memory dump file '%S'...\n", FileName
);
538 /* Always create the dump file */
539 hFile
= CreateFileW(FileName
,
544 FILE_ATTRIBUTE_NORMAL
,
547 if (hFile
== INVALID_HANDLE_VALUE
)
549 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
550 FileName
, GetLastError());
554 /* Dump the VM memory in the chosen format */
556 DumpMemoryTxt(hFile
);
558 DumpMemoryRaw(hFile
);
563 DPRINT1("Memory dump done\n");
566 BOOLEAN
EmulatorInitialize(HANDLE ConsoleInput
, HANDLE ConsoleOutput
)
570 /* Allocate 16 MB memory for the 16-bit address space */
571 BaseAddress
= HeapAlloc(GetProcessHeap(), /*HEAP_ZERO_MEMORY*/ 0, MAX_ADDRESS
);
572 if (BaseAddress
== NULL
)
574 wprintf(L
"FATAL: Failed to allocate VDM memory.\n");
581 SIZE_T MemorySize
= MAX_ADDRESS
; // See: kernel32/client/vdm.c!BaseGetVdmConfigInfo
584 * The reserved region starts from the very first page.
585 * We need to commit the reserved first 16 MB virtual address.
587 BaseAddress
= (PVOID
)1; // NULL has another signification for NtAllocateVirtualMemory
590 * Since to get NULL, we allocated from 0x1, account for this.
591 * See also: kernel32/client/proc.c!CreateProcessInternalW
595 /* Commit the reserved memory */
596 Status
= NtAllocateVirtualMemory(NtCurrentProcess(),
601 PAGE_EXECUTE_READWRITE
);
602 if (!NT_SUCCESS(Status
))
604 wprintf(L
"FATAL: Failed to commit VDM memory, Status 0x%08lx\n", Status
);
608 ASSERT(BaseAddress
== NULL
);
613 * For diagnostics purposes, we fill the memory with INT 0x03 codes
614 * so that if a program wants to execute random code in memory, we can
615 * retrieve the exact CS:IP where the problem happens.
617 RtlFillMemory(BaseAddress
, MAX_ADDRESS
, 0xCC);
619 /* Initialize I/O ports */
622 /* Initialize the CPU */
624 /* Initialize the internal clock */
625 if (!ClockInitialize())
627 wprintf(L
"FATAL: Failed to initialize the clock\n");
632 /* Initialize the CPU */
638 /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
644 /* Set output functions */
645 PitSetOutFunction(0, NULL
, PitChan0Out
);
646 PitSetOutFunction(1, NULL
, PitChan1Out
);
647 PitSetOutFunction(2, NULL
, PitChan2Out
);
649 /* Register the I/O Ports */
650 RegisterIoPort(CONTROL_SYSTEM_PORT61H
, Port61hRead
, Port61hWrite
);
652 /* Set the console input mode */
653 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
654 // upon console window events (screen buffer resize, ...).
655 SetConsoleMode(ConsoleInput
, ENABLE_PROCESSED_INPUT
/* | ENABLE_WINDOW_INPUT */);
656 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
658 /**/EnableExtraHardware(ConsoleInput
);/**/
660 /* Initialize the PS/2 port */
663 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
667 /**************** ATTACH INPUT WITH CONSOLE *****************/
668 /* Start the input thread */
669 InputThread
= CreateThread(NULL
, 0, &PumpConsoleInput
, ConsoleInput
, 0, NULL
);
670 if (InputThread
== NULL
)
672 DisplayMessage(L
"Failed to create the console input thread.");
676 /************************************************************/
678 /* Initialize the VGA */
679 if (!VgaInitialize(ConsoleOutput
))
681 DisplayMessage(L
"Failed to initialize VGA support.");
686 /* Initialize the software callback system and register the emulator BOPs */
688 RegisterBop(BOP_DEBUGGER
, EmulatorDebugBreakBop
);
689 // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop);
691 /* Initialize VDD support */
697 VOID
EmulatorCleanup(VOID
)
701 SIZE_T MemorySize
= MAX_ADDRESS
;
706 /* Close the input thread handle */
707 if (InputThread
!= NULL
) CloseHandle(InputThread
);
723 /* Free the memory allocated for the 16-bit address space */
724 if (BaseAddress
!= NULL
) HeapFree(GetProcessHeap(), 0, BaseAddress
);
728 /* The reserved region starts from the very first page */
729 // BaseAddress = (PVOID)1;
731 /* Since to get NULL, we allocated from 0x1, account for this */
734 Status
= NtFreeVirtualMemory(NtCurrentProcess(),
738 if (!NT_SUCCESS(Status
))
740 DPRINT1("NTVDM: Failed to decommit VDM memory, Status 0x%08lx\n", Status
);
756 VDDTerminateVDM(VOID
)
764 Sim32pGetVDMPointer(IN ULONG Address
,
765 IN BOOLEAN ProtectedMode
)
768 UNREFERENCED_PARAMETER(ProtectedMode
);
771 * HIWORD(Address) == Segment (if ProtectedMode == FALSE)
772 * or Selector (if ProtectedMode == TRUE )
773 * LOWORD(Address) == Offset
775 return (PBYTE
)FAR_POINTER(Address
);
780 MGetVdmPointer(IN ULONG Address
,
782 IN BOOLEAN ProtectedMode
)
784 UNREFERENCED_PARAMETER(Size
);
785 return Sim32pGetVDMPointer(Address
, ProtectedMode
);
790 VdmMapFlat(IN USHORT Segment
,
795 UNREFERENCED_PARAMETER(Mode
);
797 return SEG_OFF_TO_PTR(Segment
, Offset
);
802 VdmFlushCache(IN USHORT Segment
,
814 VdmUnmapFlat(IN USHORT Segment
,