[NTVDM]: Add debug print for timer ticks to try to find why the emulator sometimes...
[reactos.git] / subsystems / ntvdm / ntvdm.c
index f3ea8b0..004c42e 100644 (file)
@@ -8,29 +8,33 @@
 
 /* INCLUDES *******************************************************************/
 
+#define NDEBUG
+
 #include "ntvdm.h"
 #include "emulator.h"
-#include "bios.h"
-#include "dos.h"
-#include "timer.h"
-#include "pic.h"
-#include "ps2.h"
+
+#include "bios/bios.h"
+#include "dos/dem.h"
+#include "hardware/cmos.h"
+#include "hardware/ps2.h"
+#include "hardware/timer.h"
+#include "hardware/vga.h"
+
+/*
+ * Activate this line if you want to be able to test NTVDM with:
+ * ntvdm.exe <program>
+ */
+#define TESTING
+
+#define IPS_DISPLAY
 
 /* PUBLIC VARIABLES ***********************************************************/
 
-BOOLEAN VdmRunning = TRUE;
-LPVOID BaseAddress = 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"
-};
+static HANDLE ConsoleInput  = INVALID_HANDLE_VALUE;
+static HANDLE ConsoleOutput = INVALID_HANDLE_VALUE;
+static DWORD  OrgConsoleInputMode, OrgConsoleOutputMode;
+static CONSOLE_CURSOR_INFO         OrgConsoleCursorInfo;
+static CONSOLE_SCREEN_BUFFER_INFO  OrgConsoleBufferInfo;
 
 /* PUBLIC FUNCTIONS ***********************************************************/
 
@@ -41,7 +45,8 @@ VOID DisplayMessage(LPCWSTR Format, ...)
 
     va_start(Parameters, Format);
     _vsnwprintf(Buffer, 256, Format, Parameters);
-    MessageBox(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
+    DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
+    MessageBoxW(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
     va_end(Parameters);
 }
 
@@ -52,7 +57,7 @@ BOOL WINAPI ConsoleCtrlHandler(DWORD ControlType)
         case CTRL_C_EVENT:
         case CTRL_BREAK_EVENT:
         {
-            /* Perform interrupt 0x23 */
+            /* Call INT 23h */
             EmulatorInterrupt(0x23);
             break;
         }
@@ -65,29 +70,148 @@ BOOL WINAPI ConsoleCtrlHandler(DWORD ControlType)
     return TRUE;
 }
 
+BOOL ConsoleInit(VOID)
+{
+    /* Set the handler routine */
+    SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
+
+    /* Get the input handle to the real console, and check for success */
+    ConsoleInput = CreateFileW(L"CONIN$",
+                               GENERIC_READ | GENERIC_WRITE,
+                               FILE_SHARE_READ | FILE_SHARE_WRITE,
+                               NULL,
+                               OPEN_EXISTING,
+                               0,
+                               NULL);
+    if (ConsoleInput == INVALID_HANDLE_VALUE)
+    {
+        wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
+        return FALSE;
+    }
+
+    /* Get the output handle to the real console, and check for success */
+    ConsoleOutput = CreateFileW(L"CONOUT$",
+                                GENERIC_READ | GENERIC_WRITE,
+                                FILE_SHARE_READ | FILE_SHARE_WRITE,
+                                NULL,
+                                OPEN_EXISTING,
+                                0,
+                                NULL);
+    if (ConsoleOutput == INVALID_HANDLE_VALUE)
+    {
+        CloseHandle(ConsoleInput);
+        wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
+        return FALSE;
+    }
+
+    /* Save the original input and output console modes */
+    if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
+        !GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
+    {
+        CloseHandle(ConsoleOutput);
+        CloseHandle(ConsoleInput);
+        wprintf(L"FATAL: Cannot save console in/out modes\n");
+        return FALSE;
+    }
+
+    /* Save the original cursor and console screen buffer information */
+    if (!GetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo) ||
+        !GetConsoleScreenBufferInfo(ConsoleOutput, &OrgConsoleBufferInfo))
+    {
+        CloseHandle(ConsoleOutput);
+        CloseHandle(ConsoleInput);
+        wprintf(L"FATAL: Cannot save console cursor/screen-buffer info\n");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+VOID ConsoleCleanup(VOID)
+{
+    SMALL_RECT ConRect;
+    CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
+
+    /* Restore the old screen buffer */
+    SetConsoleActiveScreenBuffer(ConsoleOutput);
+
+    /* Restore the original console size */
+    GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo);
+    ConRect.Left = 0; // OrgConsoleBufferInfo.srWindow.Left;
+    // ConRect.Top  = ConsoleInfo.dwCursorPosition.Y / (OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top + 1);
+    // ConRect.Top *= (OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top + 1);
+    ConRect.Top    = ConsoleInfo.dwCursorPosition.Y;
+    ConRect.Right  = ConRect.Left + OrgConsoleBufferInfo.srWindow.Right  - OrgConsoleBufferInfo.srWindow.Left;
+    ConRect.Bottom = ConRect.Top  + OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top ;
+    /* See the following trick explanation in vga.c:VgaEnterTextMode() */
+    SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
+    SetConsoleWindowInfo(ConsoleOutput, TRUE, &ConRect);
+    // SetConsoleWindowInfo(ConsoleOutput, TRUE, &OrgConsoleBufferInfo.srWindow);
+    SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
+
+    /* Restore the original cursor shape */
+    SetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo);
+
+    /* Restore the original input and output console modes */
+    SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
+    SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
+
+    /* Close the console handles */
+    if (ConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleOutput);
+    if (ConsoleInput  != INVALID_HANDLE_VALUE) CloseHandle(ConsoleInput);
+}
+
 INT wmain(INT argc, WCHAR *argv[])
 {
     INT i;
-    CHAR CommandLine[128];
-    DWORD CurrentTickCount;
-    DWORD LastTickCount = GetTickCount();
-    DWORD Cycles = 0;
-    DWORD LastCyclePrintout = GetTickCount();
-    LARGE_INTEGER Frequency, LastTimerTick, Counter;
+    CHAR CommandLine[DOS_CMDLINE_LENGTH];
+    LARGE_INTEGER StartPerfCount;
+    LARGE_INTEGER Frequency, LastTimerTick, LastRtcTick, Counter;
     LONGLONG TimerTicks;
+    DWORD StartTickCount, CurrentTickCount;
+    DWORD LastClockUpdate;
+    DWORD LastVerticalRefresh;
+#ifdef IPS_DISPLAY
+    DWORD LastCyclePrintout;
+    DWORD Cycles = 0;
+#endif
+    INT KeyboardIntCounter = 0;
 
-    /* Set the handler routine */
-    SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
+#ifndef TESTING
+    UNREFERENCED_PARAMETER(argc);
+    UNREFERENCED_PARAMETER(argv);
 
     /* The DOS command line must be ASCII */
-    WideCharToMultiByte(CP_ACP, 0, GetCommandLine(), -1, CommandLine, 128, NULL, NULL);
+    WideCharToMultiByte(CP_ACP, 0, GetCommandLine(), -1, CommandLine, sizeof(CommandLine), NULL, NULL);
+#else
+    if (argc == 2 && argv[1] != NULL)
+    {
+        WideCharToMultiByte(CP_ACP, 0, argv[1], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
+    }
+    else
+    {
+        wprintf(L"\nReactOS Virtual DOS Machine\n\n"
+                L"Usage: NTVDM <executable>\n");
+        return 0;
+    }
+#endif
+
+    DPRINT1("\n\n\nNTVDM - Starting '%s'...\n\n\n", CommandLine);
+
+    /* Initialize the console */
+    if (!ConsoleInit())
+    {
+        wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
+        goto Cleanup;
+    }
 
-    if (!EmulatorInitialize())
+    /* Initialize the emulator */
+    if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
     {
-        wprintf(L"FATAL: Failed to initialize the CPU emulator\n");
-        return 1;
+        wprintf(L"FATAL: Failed to initialize the emulator\n");
+        goto Cleanup;
     }
-    
+
     /* Initialize the performance counter (needed for hardware timers) */
     if (!QueryPerformanceFrequency(&Frequency))
     {
@@ -96,14 +220,14 @@ INT wmain(INT argc, WCHAR *argv[])
     }
 
     /* Initialize the system BIOS */
-    if (!BiosInitialize())
+    if (!BiosInitialize(ConsoleInput, ConsoleOutput))
     {
         wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
         goto Cleanup;
     }
 
     /* Initialize the VDM DOS kernel */
-    if (!DosInitialize())
+    if (!DosInitialize(NULL))
     {
         wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
         goto Cleanup;
@@ -113,53 +237,116 @@ INT wmain(INT argc, WCHAR *argv[])
     if (!DosCreateProcess(CommandLine, 0))
     {
         DisplayMessage(L"Could not start program: %S", CommandLine);
-        return -1;
+        goto Cleanup;
     }
-    
-    /* Set the last timer tick to the current time */
-    QueryPerformanceCounter(&LastTimerTick);
+
+    /* Find the starting performance and tick count */
+    StartTickCount = GetTickCount();
+    QueryPerformanceCounter(&StartPerfCount);
+
+    /* Set the different last counts to the starting count */
+    LastClockUpdate = LastVerticalRefresh =
+#ifdef IPS_DISPLAY
+    LastCyclePrintout =
+#endif
+    StartTickCount;
+
+    /* Set the last timer ticks to the current time */
+    LastTimerTick = LastRtcTick = StartPerfCount;
 
     /* Main loop */
     while (VdmRunning)
     {
+        DWORD PitResolution = PitGetResolution();
+        DWORD RtcFrequency = RtcGetTicksPerSecond();
+
         /* Get the current number of ticks */
         CurrentTickCount = GetTickCount();
-        
-        /* Get the current performance counter value */
-        QueryPerformanceCounter(&Counter);
-        
+
+        if ((PitResolution <= 1000) && (RtcFrequency <= 1000))
+        {
+            /* Calculate the approximate performance counter value instead */
+            Counter.QuadPart = StartPerfCount.QuadPart
+                               + (CurrentTickCount - StartTickCount)
+                               * (Frequency.QuadPart / 1000);
+        }
+        else
+        {
+            /* Get the current performance counter value */
+            QueryPerformanceCounter(&Counter);
+        }
+
         /* Get the number of PIT ticks that have passed */
         TimerTicks = ((Counter.QuadPart - LastTimerTick.QuadPart)
                      * PIT_BASE_FREQUENCY) / Frequency.QuadPart;
-        
+
         /* Update the PIT */
-        for (i = 0; i < TimerTicks; i++) PitDecrementCount();
-        LastTimerTick = Counter;
-        
-        /* Check for console input events every millisecond */
-        if (CurrentTickCount != LastTickCount)
+        if (TimerTicks > 0)
+        {
+            PitClock(TimerTicks);
+            LastTimerTick = Counter;
+        }
+
+        /* Check for RTC update */
+        if ((CurrentTickCount - LastClockUpdate) >= 1000)
         {
-            CheckForInputEvents();
-            LastTickCount = CurrentTickCount;
+            RtcTimeUpdate();
+            LastClockUpdate = CurrentTickCount;
         }
-        
+
+        /* Check for RTC periodic tick */
+        if ((Counter.QuadPart - LastRtcTick.QuadPart)
+            >= (Frequency.QuadPart / (LONGLONG)RtcFrequency))
+        {
+            RtcPeriodicTick();
+            LastRtcTick = Counter;
+        }
+
+        /* Check for vertical retrace */
+        if ((CurrentTickCount - LastVerticalRefresh) >= 15)
+        {
+            VgaRefreshDisplay();
+            LastVerticalRefresh = CurrentTickCount;
+        }
+
+        KeyboardIntCounter++;
+        if (KeyboardIntCounter == KBD_INT_CYCLES)
+        {
+            GenerateKeyboardInterrupts();
+            KeyboardIntCounter = 0;
+        }
+
+        /* Horizontal retrace occurs as fast as possible */
+        VgaHorizontalRetrace();
+
         /* Continue CPU emulation */
         for (i = 0; (i < STEPS_PER_CYCLE) && VdmRunning; i++)
         {
             EmulatorStep();
+#ifdef IPS_DISPLAY
             Cycles++;
+#endif
         }
-        
+
+#ifdef IPS_DISPLAY
         if ((CurrentTickCount - LastCyclePrintout) >= 1000)
         {
-            DPRINT1("NTVDM: %d Instructions Per Second\n", Cycles);
+            DPRINT1("NTVDM: %lu Instructions Per Second; TimerTicks = %lu\n", Cycles, TimerTicks);
             LastCyclePrintout = CurrentTickCount;
             Cycles = 0;
         }
+#endif
     }
 
+    /* Perform another screen refresh */
+    VgaRefreshDisplay();
+
 Cleanup:
+    BiosCleanup();
     EmulatorCleanup();
+    ConsoleCleanup();
+
+    DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
 
     return 0;
 }