/*
* COPYRIGHT: GPL - See COPYING in the top level directory
* PROJECT: ReactOS Virtual DOS Machine
- * FILE: emulator.c
+ * FILE: subsystems/mvdm/ntvdm/emulator.c
* PURPOSE: Minimal x86 machine emulator for the VDM
* PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
*/
#include "clock.h"
#include "bios/rom.h"
#include "hardware/cmos.h"
+#include "hardware/disk.h"
#include "hardware/dma.h"
#include "hardware/keyboard.h"
#include "hardware/mouse.h"
#include "hardware/pic.h"
+#include "hardware/pit.h"
+#include "hardware/ppi.h"
#include "hardware/ps2.h"
#include "hardware/sound/speaker.h"
-#include "hardware/pit.h"
-#include "hardware/video/vga.h"
+#include "hardware/video/svga.h"
#include "vddsup.h"
#include "io.h"
LPVOID BaseAddress = NULL;
BOOLEAN VdmRunning = TRUE;
-static BOOLEAN A20Line = FALSE;
-static BYTE Port61hState = 0x00;
-
+HANDLE VdmTaskEvent = NULL;
static HANDLE InputThread = NULL;
LPCWSTR ExceptionName[] =
/* PRIVATE FUNCTIONS **********************************************************/
-VOID WINAPI EmulatorReadMemory(PFAST486_STATE State, ULONG Address, PVOID Buffer, ULONG Size)
-{
- UNREFERENCED_PARAMETER(State);
-
- /* Mirror 0x000FFFF0 at 0xFFFFFFF0 */
- if (Address >= 0xFFFFFFF0) Address -= 0xFFF00000;
-
- /* If the A20 line is disabled, mask bit 20 */
- if (!A20Line) Address &= ~(1 << 20);
-
- if (Address >= MAX_ADDRESS) return;
- Size = min(Size, MAX_ADDRESS - Address);
-
- /* Read while calling fast memory hooks */
- MemRead(Address, Buffer, Size);
-}
-
-VOID WINAPI EmulatorWriteMemory(PFAST486_STATE State, ULONG Address, PVOID Buffer, ULONG Size)
-{
- UNREFERENCED_PARAMETER(State);
-
- /* If the A20 line is disabled, mask bit 20 */
- if (!A20Line) Address &= ~(1 << 20);
-
- if (Address >= MAX_ADDRESS) return;
- Size = min(Size, MAX_ADDRESS - Address);
-
- /* Write while calling fast memory hooks */
- MemWrite(Address, Buffer, Size);
-}
-
-UCHAR WINAPI EmulatorIntAcknowledge(PFAST486_STATE State)
+UCHAR FASTCALL EmulatorIntAcknowledge(PFAST486_STATE State)
{
UNREFERENCED_PARAMETER(State);
return PicGetInterrupt();
}
-VOID WINAPI EmulatorFpu(PFAST486_STATE State)
+VOID FASTCALL EmulatorFpu(PFAST486_STATE State)
{
/* The FPU is wired to IRQ 13 */
PicInterruptRequest(13);
/* Stop the VDM */
EmulatorTerminate();
- return;
-}
-
-VOID EmulatorTerminate(VOID)
-{
- /* Stop the VDM */
- CpuUnsimulate(); // Halt the CPU
- VdmRunning = FALSE;
}
VOID EmulatorInterruptSignal(VOID)
Fast486InterruptSignal(&EmulatorContext);
}
-VOID EmulatorSetA20(BOOLEAN Enabled)
-{
- A20Line = Enabled;
-}
-
-BOOLEAN EmulatorGetA20(VOID)
-{
- return A20Line;
-}
-
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(Port61hState);
- SpeakerChange(Port61hState);
-}
-
static VOID WINAPI PitChan0Out(LPVOID Param, BOOLEAN State)
{
if (State)
static DWORD
WINAPI
-PumpConsoleInput(LPVOID Parameter)
+ConsoleEventThread(LPVOID Parameter)
{
HANDLE ConsoleInput = (HANDLE)Parameter;
- INPUT_RECORD InputRecord;
- DWORD Count;
+ HANDLE WaitHandles[2];
+ DWORD WaitResult;
+
+ /*
+ * For optimization purposes, Windows (and hence ReactOS, too, for
+ * compatibility reasons) uses a static buffer if no more than five
+ * input records are read. Otherwise a new buffer is used.
+ * The client-side expects that we know this behaviour.
+ * See consrv/coninput.c
+ *
+ * We exploit here this optimization by also using a buffer of 5 records.
+ */
+ INPUT_RECORD InputRecords[5];
+ ULONG NumRecords, i;
+
+ WaitHandles[0] = VdmTaskEvent;
+ WaitHandles[1] = GetConsoleInputWaitHandle();
while (VdmRunning)
{
/* Make sure the task event is signaled */
- WaitForSingleObject(VdmTaskEvent, INFINITE);
+ WaitResult = WaitForMultipleObjects(ARRAYSIZE(WaitHandles),
+ WaitHandles,
+ TRUE,
+ INFINITE);
+ switch (WaitResult)
+ {
+ case WAIT_OBJECT_0 + 0:
+ case WAIT_OBJECT_0 + 1:
+ break;
+ default:
+ return GetLastError();
+ }
/* Wait for an input record */
- if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
+ if (!ReadConsoleInputExW(ConsoleInput,
+ InputRecords,
+ ARRAYSIZE(InputRecords),
+ &NumRecords,
+ CONSOLE_READ_CONTINUE))
{
DWORD LastError = GetLastError();
- DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
+ DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, NumRecords, LastError);
return LastError;
}
- ASSERT(Count != 0);
-
- /* Check the event type */
- switch (InputRecord.EventType)
+ // ASSERT(NumRecords != 0);
+ if (NumRecords == 0)
{
- /*
- * 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;
+ DPRINT1("Got NumRecords == 0!\n");
+ continue;
+ }
- default:
- break;
+ /* Dispatch the events */
+ for (i = 0; i < NumRecords; i++)
+ {
+ /* Check the event type */
+ switch (InputRecords[i].EventType)
+ {
+ /*
+ * Hardware events
+ */
+ case KEY_EVENT:
+ KeyboardEventHandler(&InputRecords[i].Event.KeyEvent);
+ break;
+
+ case MOUSE_EVENT:
+ MouseEventHandler(&InputRecords[i].Event.MouseEvent);
+ break;
+
+ case WINDOW_BUFFER_SIZE_EVENT:
+ ScreenEventHandler(&InputRecords[i].Event.WindowBufferSizeEvent);
+ break;
+
+ /*
+ * Interface events
+ */
+ case MENU_EVENT:
+ MenuEventHandler(&InputRecords[i].Event.MenuEvent);
+ break;
+
+ case FOCUS_EVENT:
+ FocusEventHandler(&InputRecords[i].Event.FocusEvent);
+ break;
+
+ default:
+ DPRINT1("Unknown input event type 0x%04x\n", InputRecords[i].EventType);
+ break;
+ }
}
+
+ /* Let the console subsystem queue some new events */
+ Sleep(10);
}
return 0;
}
+static VOID PauseEventThread(VOID)
+{
+ ResetEvent(VdmTaskEvent);
+}
+
+static VOID ResumeEventThread(VOID)
+{
+ SetEvent(VdmTaskEvent);
+}
+
+
/* PUBLIC FUNCTIONS ***********************************************************/
static VOID
DPRINT1("Memory dump done\n");
}
+VOID MountFloppy(IN ULONG DiskNumber)
+{
+// FIXME: This should be present in PSDK commdlg.h
+//
+// FlagsEx Values
+#if (_WIN32_WINNT >= 0x0500)
+#define OFN_EX_NOPLACESBAR 0x00000001
+#endif // (_WIN32_WINNT >= 0x0500)
+
+ OPENFILENAMEA ofn;
+ CHAR szFile[MAX_PATH] = "";
+
+ RtlZeroMemory(&ofn, sizeof(ofn));
+ ofn.lStructSize = sizeof(ofn);
+ ofn.hwndOwner = hConsoleWnd;
+ ofn.lpstrTitle = "Select a virtual floppy image";
+ ofn.Flags = OFN_EXPLORER | OFN_ENABLESIZING | OFN_LONGNAMES | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
+// ofn.FlagsEx = OFN_EX_NOPLACESBAR;
+ ofn.lpstrFilter = "Virtual floppy images (*.vfd;*.img;*.ima;*.dsk)\0*.vfd\0All files (*.*)\0*.*\0";
+ ofn.lpstrDefExt = "vfd";
+ ofn.nFilterIndex = 0;
+ ofn.lpstrFile = szFile;
+ ofn.nMaxFile = ARRAYSIZE(szFile);
+
+ if (!GetOpenFileNameA(&ofn))
+ {
+ DPRINT1("CommDlgExtendedError = %d\n", CommDlgExtendedError());
+ return;
+ }
+
+ // TODO: Refresh the menu state
+
+ if (!MountDisk(FLOPPY_DISK, DiskNumber, szFile, !!(ofn.Flags & OFN_READONLY)))
+ DisplayMessage(L"An error happened when mounting disk %d", DiskNumber);
+}
+
+VOID EjectFloppy(IN ULONG DiskNumber)
+{
+ // TODO: Refresh the menu state
+
+ if (!UnmountDisk(FLOPPY_DISK, DiskNumber))
+ DisplayMessage(L"An error happened when ejecting disk %d", DiskNumber);
+}
+
+
+VOID EmulatorPause(VOID)
+{
+ /* Pause the VDM */
+ VDDBlockUserHook();
+ VgaRefreshDisplay();
+ PauseEventThread();
+}
+
+VOID EmulatorResume(VOID)
+{
+ /* Resume the VDM */
+ ResumeEventThread();
+ VgaRefreshDisplay();
+ VDDResumeUserHook();
+}
+
+VOID EmulatorTerminate(VOID)
+{
+ /* Stop the VDM */
+ CpuUnsimulate(); // Halt the CPU
+ VdmRunning = FALSE;
+}
+
BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
{
+ USHORT i;
+
/* Initialize memory */
if (!MemInitialize())
{
/* Initialize DMA */
DmaInitialize();
- /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
+ /* Initialize PIC, PIT, CMOS, PC Speaker and PS/2 */
PicInitialize();
- PitInitialize();
- CmosInitialize();
- SpeakerInitialize();
- /* Set output functions */
+ PitInitialize();
PitSetOutFunction(0, NULL, PitChan0Out);
PitSetOutFunction(1, NULL, PitChan1Out);
PitSetOutFunction(2, NULL, PitChan2Out);
- /* Register the I/O Ports */
- RegisterIoPort(CONTROL_SYSTEM_PORT61H, Port61hRead, Port61hWrite);
+ CmosInitialize();
+ SpeakerInitialize();
+ PpiInitialize();
- /* Initialize the PS/2 port */
PS2Initialize();
/* Initialize the keyboard and mouse and connect them to their PS/2 ports */
MouseInit(1);
/**************** ATTACH INPUT WITH CONSOLE *****************/
+ /* Create the task event */
+ VdmTaskEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
+ ASSERT(VdmTaskEvent != NULL);
+
/* Start the input thread */
- InputThread = CreateThread(NULL, 0, &PumpConsoleInput, ConsoleInput, 0, NULL);
+ InputThread = CreateThread(NULL, 0, &ConsoleEventThread, ConsoleInput, 0, NULL);
if (InputThread == NULL)
{
- DisplayMessage(L"Failed to create the console input thread.");
+ wprintf(L"FATAL: Failed to create the console input thread.\n");
EmulatorCleanup();
return FALSE;
}
+ ResumeEventThread();
/************************************************************/
/* Initialize the VGA */
if (!VgaInitialize(ConsoleOutput))
{
- DisplayMessage(L"Failed to initialize VGA support.");
+ wprintf(L"FATAL: Failed to initialize VGA support.\n");
+ EmulatorCleanup();
+ return FALSE;
+ }
+
+ /* Initialize the disk controller */
+ if (!DiskCtrlInitialize())
+ {
+ wprintf(L"FATAL: Failed to completely initialize the disk controller.\n");
EmulatorCleanup();
return FALSE;
}
+ /* Mount the available floppy disks */
+ for (i = 0; i < ARRAYSIZE(GlobalSettings.FloppyDisks); ++i)
+ {
+ if (GlobalSettings.FloppyDisks[i].Length != 0 &&
+ GlobalSettings.FloppyDisks[i].Buffer &&
+ GlobalSettings.FloppyDisks[i].Buffer != '\0')
+ {
+ MountDisk(FLOPPY_DISK, i, GlobalSettings.FloppyDisks[i].Buffer, FALSE);
+ }
+ }
+
+ /* Mount the available hard disks */
+ for (i = 0; i < ARRAYSIZE(GlobalSettings.HardDisks); ++i)
+ {
+ if (GlobalSettings.HardDisks[i].Length != 0 &&
+ GlobalSettings.HardDisks[i].Buffer &&
+ GlobalSettings.HardDisks[i].Buffer != '\0')
+ {
+ MountDisk(HARD_DISK, i, GlobalSettings.HardDisks[i].Buffer, FALSE);
+ }
+ }
+
/* Initialize the software callback system and register the emulator BOPs */
InitializeInt32();
RegisterBop(BOP_DEBUGGER , EmulatorDebugBreakBop);
VOID EmulatorCleanup(VOID)
{
+ DiskCtrlCleanup();
+
VgaCleanup();
/* Close the input thread handle */
if (InputThread != NULL) CloseHandle(InputThread);
InputThread = NULL;
+ /* Close the task event */
+ if (VdmTaskEvent != NULL) CloseHandle(VdmTaskEvent);
+ VdmTaskEvent = NULL;
+
PS2Cleanup();
SpeakerCleanup();