/* * COPYRIGHT: GPL - See COPYING in the top level directory * PROJECT: ReactOS Virtual DOS Machine * FILE: emulator.c * PURPOSE: Minimal x86 machine emulator for the VDM * PROGRAMMERS: Aleksandar Andrejevic */ /* INCLUDES *******************************************************************/ #define NDEBUG #include "emulator.h" #include "cpu/callback.h" #include "cpu/cpu.h" #include "cpu/bop.h" #include #include "int32.h" #include "clock.h" #include "bios/rom.h" #include "hardware/cmos.h" #include "hardware/keyboard.h" #include "hardware/mouse.h" #include "hardware/pic.h" #include "hardware/ps2.h" #include "hardware/speaker.h" #include "hardware/timer.h" #include "hardware/vga.h" #include "vddsup.h" #include "io.h" /* PRIVATE VARIABLES **********************************************************/ LPVOID BaseAddress = NULL; BOOLEAN VdmRunning = TRUE; static BOOLEAN A20Line = FALSE; static BYTE Port61hState = 0x00; static HANDLE InputThread = NULL; LPCWSTR ExceptionName[] = { L"Division By Zero", L"Debug", L"Unexpected Error", L"Breakpoint", L"Integer Overflow", L"Bound Range Exceeded", L"Invalid Opcode", L"FPU Not Available" }; /* BOP Identifiers */ #define BOP_DEBUGGER 0x56 // Break into the debugger from a 16-bit app /* PRIVATE FUNCTIONS **********************************************************/ static inline VOID EmulatorMoveMemory(OUT VOID UNALIGNED *Destination, IN const VOID UNALIGNED *Source, IN SIZE_T Length) { #if 1 /* * We use a switch here to detect small moves of memory, as these * constitute the bulk of our moves. * Using RtlMoveMemory for all these small moves would be slow otherwise. */ switch (Length) { case 0: return; case sizeof(UCHAR): *(PUCHAR)Destination = *(PUCHAR)Source; return; case sizeof(USHORT): *(PUSHORT)Destination = *(PUSHORT)Source; return; case sizeof(ULONG): *(PULONG)Destination = *(PULONG)Source; return; case sizeof(ULONGLONG): *(PULONGLONG)Destination = *(PULONGLONG)Source; return; default: #if defined(__GNUC__) __builtin_memmove(Destination, Source, Length); #else RtlMoveMemory(Destination, Source, Length); #endif } #else // defined(_MSC_VER) PUCHAR Dest = (PUCHAR)Destination; PUCHAR Src = (PUCHAR)Source; SIZE_T Count, NewSize = Length; /* Move dword */ Count = NewSize >> 2; // NewSize / sizeof(ULONG); NewSize = NewSize & 3; // NewSize % sizeof(ULONG); __movsd(Dest, Src, Count); Dest += Count << 2; // Count * sizeof(ULONG); Src += Count << 2; /* Move word */ Count = NewSize >> 1; // NewSize / sizeof(USHORT); NewSize = NewSize & 1; // NewSize % sizeof(USHORT); __movsw(Dest, Src, Count); Dest += Count << 1; // Count * sizeof(USHORT); Src += Count << 1; /* Move byte */ Count = NewSize; // NewSize / sizeof(UCHAR); // NewSize = NewSize; // NewSize % sizeof(UCHAR); __movsb(Dest, Src, Count); #endif } VOID WINAPI EmulatorReadMemory(PFAST486_STATE State, ULONG Address, PVOID Buffer, ULONG Size) { UNREFERENCED_PARAMETER(State); // BIG HACK!!!! To make BIOS images working correctly, // until Aleksander rewrites memory management!! if (Address >= 0xFFFFFFF0) Address -= 0xFFF00000; /* If the A20 line is disabled, mask bit 20 */ if (!A20Line) Address &= ~(1 << 20); /* Make sure the requested address is valid */ if ((Address + Size) >= MAX_ADDRESS) return; /* * Check if we are going to read the VGA memory and * copy it into the virtual address space if needed. */ if (((Address + Size) >= VgaGetVideoBaseAddress()) && (Address < VgaGetVideoLimitAddress())) { DWORD VgaAddress = max(Address, VgaGetVideoBaseAddress()); DWORD ActualSize = min(Address + Size - 1, VgaGetVideoLimitAddress()) - VgaAddress + 1; LPBYTE DestBuffer = (LPBYTE)REAL_TO_PHYS(VgaAddress); /* Read from the VGA memory */ VgaReadMemory(VgaAddress, DestBuffer, ActualSize); } /* Read the data from the virtual address space and store it in the buffer */ EmulatorMoveMemory(Buffer, REAL_TO_PHYS(Address), Size); } VOID WINAPI EmulatorWriteMemory(PFAST486_STATE State, ULONG Address, PVOID Buffer, ULONG Size) { UNREFERENCED_PARAMETER(State); // BIG HACK!!!! To make BIOS images working correctly, // until Aleksander rewrites memory management!! if (Address >= 0xFFFFFFF0) Address -= 0xFFF00000; /* If the A20 line is disabled, mask bit 20 */ if (!A20Line) Address &= ~(1 << 20); /* Make sure the requested address is valid */ if ((Address + Size) >= MAX_ADDRESS) return; /* Make sure we don't write to the ROM area */ if ((Address + Size) >= ROM_AREA_START && (Address < ROM_AREA_END)) return; /* Read the data from the buffer and store it in the virtual address space */ EmulatorMoveMemory(REAL_TO_PHYS(Address), Buffer, Size); /* * Check if we modified the VGA memory. */ if (((Address + Size) >= VgaGetVideoBaseAddress()) && (Address < VgaGetVideoLimitAddress())) { DWORD VgaAddress = max(Address, VgaGetVideoBaseAddress()); DWORD ActualSize = min(Address + Size - 1, VgaGetVideoLimitAddress()) - VgaAddress + 1; LPBYTE SrcBuffer = (LPBYTE)REAL_TO_PHYS(VgaAddress); /* Write to the VGA memory */ VgaWriteMemory(VgaAddress, SrcBuffer, ActualSize); } } UCHAR WINAPI EmulatorIntAcknowledge(PFAST486_STATE State) { UNREFERENCED_PARAMETER(State); /* Get the interrupt number from the PIC */ return PicGetInterrupt(); } VOID EmulatorException(BYTE ExceptionNumber, LPWORD Stack) { WORD CodeSegment, InstructionPointer; PBYTE Opcode; ASSERT(ExceptionNumber < 8); /* Get the CS:IP */ InstructionPointer = Stack[STACK_IP]; CodeSegment = Stack[STACK_CS]; Opcode = (PBYTE)SEG_OFF_TO_PTR(CodeSegment, InstructionPointer); /* Display a message to the user */ DisplayMessage(L"Exception: %s occured at %04X:%04X\n" L"Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", ExceptionName[ExceptionNumber], CodeSegment, InstructionPointer, Opcode[0], Opcode[1], Opcode[2], Opcode[3], Opcode[4], Opcode[5], Opcode[6], Opcode[7], Opcode[8], Opcode[9]); Fast486DumpState(&EmulatorContext); /* Stop the VDM */ EmulatorTerminate(); return; } VOID EmulatorTerminate(VOID) { /* Stop the VDM */ CpuUnsimulate(); // Halt the CPU VdmRunning = FALSE; } VOID EmulatorInterruptSignal(VOID) { /* Call the Fast486 API */ Fast486InterruptSignal(&EmulatorContext); } VOID EmulatorSetA20(BOOLEAN Enabled) { A20Line = Enabled; } static VOID WINAPI EmulatorDebugBreakBop(LPWORD Stack) { DPRINT1("NTVDM: BOP_DEBUGGER\n"); DebugBreak(); } static BYTE WINAPI Port61hRead(USHORT Port) { return Port61hState; } static VOID WINAPI Port61hWrite(USHORT Port, BYTE Data) { // BOOLEAN SpeakerStateChange = FALSE; BYTE OldPort61hState = Port61hState; /* Only the four lowest bytes can be written */ Port61hState = (Port61hState & 0xF0) | (Data & 0x0F); if ((OldPort61hState ^ Port61hState) & 0x01) { DPRINT("PIT 2 Gate %s\n", Port61hState & 0x01 ? "on" : "off"); PitSetGate(2, !!(Port61hState & 0x01)); // SpeakerStateChange = TRUE; } if ((OldPort61hState ^ Port61hState) & 0x02) { /* There were some change for the speaker... */ DPRINT("Speaker %s\n", Port61hState & 0x02 ? "on" : "off"); // SpeakerStateChange = TRUE; } // if (SpeakerStateChange) SpeakerChange(); SpeakerChange(); } static VOID WINAPI PitChan0Out(LPVOID Param, BOOLEAN State) { if (State) { DPRINT("PicInterruptRequest\n"); PicInterruptRequest(0); // Raise IRQ 0 } // else < Lower IRQ 0 > } static VOID WINAPI PitChan1Out(LPVOID Param, BOOLEAN State) { #if 0 if (State) { /* Set bit 4 of Port 61h */ Port61hState |= 1 << 4; } else { /* Clear bit 4 of Port 61h */ Port61hState &= ~(1 << 4); } #else Port61hState = (Port61hState & 0xEF) | (State << 4); #endif } static VOID WINAPI PitChan2Out(LPVOID Param, BOOLEAN State) { BYTE OldPort61hState = Port61hState; #if 0 if (State) { /* Set bit 5 of Port 61h */ Port61hState |= 1 << 5; } else { /* Clear bit 5 of Port 61h */ Port61hState &= ~(1 << 5); } #else Port61hState = (Port61hState & 0xDF) | (State << 5); #endif if ((OldPort61hState ^ Port61hState) & 0x20) { DPRINT("PitChan2Out -- Port61hState changed\n"); SpeakerChange(); } } static DWORD WINAPI PumpConsoleInput(LPVOID Parameter) { HANDLE ConsoleInput = (HANDLE)Parameter; INPUT_RECORD InputRecord; DWORD Count; while (VdmRunning) { /* Make sure the task event is signaled */ WaitForSingleObject(VdmTaskEvent, INFINITE); /* Wait for an input record */ if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count)) { DWORD LastError = GetLastError(); DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError); return LastError; } ASSERT(Count != 0); /* Check the event type */ switch (InputRecord.EventType) { /* * Hardware events */ case KEY_EVENT: KeyboardEventHandler(&InputRecord.Event.KeyEvent); break; case MOUSE_EVENT: MouseEventHandler(&InputRecord.Event.MouseEvent); break; case WINDOW_BUFFER_SIZE_EVENT: ScreenEventHandler(&InputRecord.Event.WindowBufferSizeEvent); break; /* * Interface events */ case MENU_EVENT: MenuEventHandler(&InputRecord.Event.MenuEvent); break; case FOCUS_EVENT: FocusEventHandler(&InputRecord.Event.FocusEvent); break; default: break; } } return 0; } static VOID EnableExtraHardware(HANDLE ConsoleInput) { DWORD ConInMode; if (GetConsoleMode(ConsoleInput, &ConInMode)) { #if 0 // GetNumberOfConsoleMouseButtons(); // GetSystemMetrics(SM_CMOUSEBUTTONS); // GetSystemMetrics(SM_MOUSEPRESENT); if (MousePresent) { #endif /* Support mouse input events if there is a mouse on the system */ ConInMode |= ENABLE_MOUSE_INPUT; #if 0 } else { /* Do not support mouse input events if there is no mouse on the system */ ConInMode &= ~ENABLE_MOUSE_INPUT; } #endif SetConsoleMode(ConsoleInput, ConInMode); } } /* PUBLIC FUNCTIONS ***********************************************************/ static VOID DumpMemoryRaw(HANDLE hFile) { PVOID Buffer; SIZE_T Size; /* Dump the VM memory */ SetFilePointer(hFile, 0, NULL, FILE_BEGIN); Buffer = REAL_TO_PHYS(NULL); Size = MAX_ADDRESS - (ULONG_PTR)(NULL); WriteFile(hFile, Buffer, Size, &Size, NULL); } static VOID DumpMemoryTxt(HANDLE hFile) { #define LINE_SIZE 75 + 2 ULONG i; PBYTE Ptr1, Ptr2; CHAR LineBuffer[LINE_SIZE]; PCHAR Line; SIZE_T LineSize; /* Dump the VM memory */ SetFilePointer(hFile, 0, NULL, FILE_BEGIN); Ptr1 = Ptr2 = REAL_TO_PHYS(NULL); while (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0) { Ptr1 = Ptr2; Line = LineBuffer; /* Print the address */ Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, "%08x ", PHYS_TO_REAL(Ptr1)); /* Print up to 16 bytes... */ /* ... in hexadecimal form first... */ i = 0; while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0)) { Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, " %02x", *Ptr1); ++Ptr1; } /* ... align with spaces if needed... */ RtlFillMemory(Line, 0x0F + 4 - i, ' '); Line += 0x0F + 4 - i; /* ... then in character form. */ i = 0; while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr2) > 0)) { *Line++ = ((*Ptr2 >= 0x20 && *Ptr2 <= 0x7E) || (*Ptr2 >= 0x80 && *Ptr2 < 0xFF) ? *Ptr2 : '.'); ++Ptr2; } /* Newline */ *Line++ = '\r'; *Line++ = '\n'; /* Finally write the line to the file */ LineSize = Line - LineBuffer; WriteFile(hFile, LineBuffer, LineSize, &LineSize, NULL); } } VOID DumpMemory(BOOLEAN TextFormat) { static ULONG DumpNumber = 0; HANDLE hFile; WCHAR FileName[MAX_PATH]; /* Build a suitable file name */ _snwprintf(FileName, MAX_PATH, L"memdump%lu.%s", DumpNumber, TextFormat ? L"txt" : L"dat"); ++DumpNumber; DPRINT1("Creating memory dump file '%S'...\n", FileName); /* Always create the dump file */ hFile = CreateFileW(FileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n", FileName, GetLastError()); return; } /* Dump the VM memory in the chosen format */ if (TextFormat) DumpMemoryTxt(hFile); else DumpMemoryRaw(hFile); /* Close the file */ CloseHandle(hFile); DPRINT1("Memory dump done\n"); } BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput) { /* Allocate memory for the 16-bit address space */ BaseAddress = HeapAlloc(GetProcessHeap(), /*HEAP_ZERO_MEMORY*/ 0, MAX_ADDRESS); if (BaseAddress == NULL) { wprintf(L"FATAL: Failed to allocate VDM memory.\n"); return FALSE; } /* * For diagnostics purposes, we fill the memory with INT 0x03 codes * so that if a program wants to execute random code in memory, we can * retrieve the exact CS:IP where the problem happens. */ RtlFillMemory(BaseAddress, MAX_ADDRESS, 0xCC); /* Initialize I/O ports */ /* Initialize RAM */ /* Initialize the CPU */ /* Initialize the internal clock */ if (!ClockInitialize()) { wprintf(L"FATAL: Failed to initialize the clock\n"); return FALSE; } /* Initialize the CPU */ CpuInitialize(); // Fast486Initialize(&EmulatorContext, // EmulatorReadMemory, // EmulatorWriteMemory, // EmulatorReadIo, // EmulatorWriteIo, // NULL, // EmulatorBiosOperation, // EmulatorIntAcknowledge, // NULL /* TODO: Use a TLB */); /* Initialize DMA */ /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */ PicInitialize(); PitInitialize(); CmosInitialize(); SpeakerInitialize(); /* Set output functions */ PitSetOutFunction(0, NULL, PitChan0Out); PitSetOutFunction(1, NULL, PitChan1Out); PitSetOutFunction(2, NULL, PitChan2Out); /* Register the I/O Ports */ RegisterIoPort(CONTROL_SYSTEM_PORT61H, Port61hRead, Port61hWrite); /* Set the console input mode */ // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions // upon console window events (screen buffer resize, ...). SetConsoleMode(ConsoleInput, ENABLE_PROCESSED_INPUT /* | ENABLE_WINDOW_INPUT */); // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); /**/EnableExtraHardware(ConsoleInput);/**/ /* Initialize the PS/2 port */ PS2Initialize(); /* Initialize the keyboard and mouse and connect them to their PS/2 ports */ KeyboardInit(0); MouseInit(1); /**************** ATTACH INPUT WITH CONSOLE *****************/ /* Start the input thread */ InputThread = CreateThread(NULL, 0, &PumpConsoleInput, ConsoleInput, 0, NULL); if (InputThread == NULL) { DisplayMessage(L"Failed to create the console input thread."); return FALSE; } /************************************************************/ /* Initialize the VGA */ if (!VgaInitialize(ConsoleOutput)) { DisplayMessage(L"Failed to initialize VGA support."); return FALSE; } /* Initialize the software callback system and register the emulator BOPs */ InitializeInt32(); RegisterBop(BOP_DEBUGGER , EmulatorDebugBreakBop); // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop); /* Initialize VDD support */ VDDSupInitialize(); return TRUE; } VOID EmulatorCleanup(VOID) { VgaCleanup(); /* Close the input thread handle */ if (InputThread != NULL) CloseHandle(InputThread); InputThread = NULL; PS2Cleanup(); SpeakerCleanup(); CmosCleanup(); // PitCleanup(); // PicCleanup(); CpuCleanup(); /* Free the memory allocated for the 16-bit address space */ if (BaseAddress != NULL) HeapFree(GetProcessHeap(), 0, BaseAddress); } VOID WINAPI VDDSimulate16(VOID) { CpuSimulate(); } VOID WINAPI VDDTerminateVDM(VOID) { /* Stop the VDM */ EmulatorTerminate(); } PBYTE WINAPI Sim32pGetVDMPointer(IN ULONG Address, IN BOOLEAN ProtectedMode) { // FIXME UNREFERENCED_PARAMETER(ProtectedMode); /* * HIWORD(Address) == Segment (if ProtectedMode == FALSE) * or Selector (if ProtectedMode == TRUE ) * LOWORD(Address) == Offset */ return (PBYTE)FAR_POINTER(Address); } PBYTE WINAPI MGetVdmPointer(IN ULONG Address, IN ULONG Size, IN BOOLEAN ProtectedMode) { UNREFERENCED_PARAMETER(Size); return Sim32pGetVDMPointer(Address, ProtectedMode); } PVOID WINAPI VdmMapFlat(IN USHORT Segment, IN ULONG Offset, IN VDM_MODE Mode) { // FIXME UNREFERENCED_PARAMETER(Mode); return SEG_OFF_TO_PTR(Segment, Offset); } BOOL WINAPI VdmFlushCache(IN USHORT Segment, IN ULONG Offset, IN ULONG Size, IN VDM_MODE Mode) { // FIXME UNIMPLEMENTED; return TRUE; } BOOL WINAPI VdmUnmapFlat(IN USHORT Segment, IN ULONG Offset, IN PVOID Buffer, IN VDM_MODE Mode) { // FIXME UNIMPLEMENTED; return TRUE; } /* EOF */