[NTVDM]
authorAleksandar Andrejevic <aandrejevic@reactos.org>
Sun, 3 Nov 2013 21:33:22 +0000 (21:33 +0000)
committerAleksandar Andrejevic <aandrejevic@reactos.org>
Sun, 3 Nov 2013 21:33:22 +0000 (21:33 +0000)
Implement the CMOS and Real Time Clock (RTC).
Improve the performance of the PIT and RTC (correctly this time).

svn path=/branches/ntvdm/; revision=60854

subsystems/ntvdm/CMakeLists.txt
subsystems/ntvdm/cmos.c [new file with mode: 0644]
subsystems/ntvdm/cmos.h [new file with mode: 0644]
subsystems/ntvdm/emulator.c
subsystems/ntvdm/ntvdm.c

index c812b03..9d1bc8b 100644 (file)
@@ -14,6 +14,7 @@ list(APPEND SOURCE
     ps2.c
     speaker.c
     vga.c
+    cmos.c
     ntvdm.c
     ntvdm.rc
     ${CMAKE_CURRENT_BINARY_DIR}/ntvdm.def)
diff --git a/subsystems/ntvdm/cmos.c b/subsystems/ntvdm/cmos.c
new file mode 100644 (file)
index 0000000..be67438
--- /dev/null
@@ -0,0 +1,419 @@
+/*
+ * COPYRIGHT:       GPL - See COPYING in the top level directory
+ * PROJECT:         ReactOS Virtual DOS Machine
+ * FILE:            cmos.c
+ * PURPOSE:         CMOS Real Time Clock emulation
+ * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
+ */
+
+/* INCLUDES *******************************************************************/
+
+#define NDEBUG
+
+#include "cmos.h"
+#include "pic.h"
+
+/* PRIVATE VARIABLES **********************************************************/
+
+static BOOLEAN NmiEnabled = TRUE;
+static BYTE StatusRegA = CMOS_DEFAULT_STA;
+static BYTE StatusRegB = CMOS_DEFAULT_STB;
+static BYTE StatusRegC = 0;
+static BYTE AlarmHour, AlarmMinute, AlarmSecond;
+static CMOS_REGISTERS SelectedRegister = CMOS_REG_STATUS_D;
+
+/* PUBLIC FUNCTIONS ***********************************************************/
+
+BOOLEAN IsNmiEnabled(VOID)
+{
+    return NmiEnabled;
+}
+
+VOID CmosWriteAddress(BYTE Value)
+{
+    /* Update the NMI enabled flag */
+    NmiEnabled = !(Value & CMOS_DISABLE_NMI);
+    
+    /* Get the register number */
+    Value &= ~CMOS_DISABLE_NMI;
+
+    if (Value < CMOS_REG_MAX)
+    {
+        /* Select the new register */
+        SelectedRegister = Value;
+    }
+    else
+    {
+        /* Default to Status Register D */
+        SelectedRegister = CMOS_REG_STATUS_D;
+    }
+}
+
+BYTE CmosReadData(VOID)
+{
+    SYSTEMTIME CurrentTime;
+
+    /* Get the current time */
+    GetLocalTime(&CurrentTime);
+
+    switch (SelectedRegister)
+    {
+        case CMOS_REG_SECONDS:
+        {
+            return (StatusRegB & CMOS_STB_BINARY)
+                   ? CurrentTime.wSecond
+                   : BINARY_TO_BCD(CurrentTime.wSecond);
+        }
+
+        case CMOS_REG_ALARM_SEC:
+        {
+            return (StatusRegB & CMOS_STB_BINARY)
+                   ? AlarmSecond
+                   : BINARY_TO_BCD(AlarmSecond);
+        }
+
+        case CMOS_REG_MINUTES:
+        {
+            return (StatusRegB & CMOS_STB_BINARY)
+                   ? CurrentTime.wMinute
+                   : BINARY_TO_BCD(CurrentTime.wMinute);
+        }
+
+        case CMOS_REG_ALARM_MIN:
+        {
+            return (StatusRegB & CMOS_STB_BINARY)
+                   ? AlarmMinute
+                   : BINARY_TO_BCD(AlarmMinute);
+        }
+
+        case CMOS_REG_HOURS:
+        {
+            BOOLEAN Afternoon = FALSE;
+            BYTE Value = CurrentTime.wHour;
+
+            if (!(StatusRegB & CMOS_STB_24HOUR) && (Value >= 12))
+            {
+                Value -= 12;
+                Afternoon = TRUE;
+            }
+
+            if (!(StatusRegB & CMOS_STB_BINARY)) Value = BINARY_TO_BCD(Value);
+
+            /* Convert to 12-hour */
+            if (Afternoon) Value |= 0x80;
+
+            return Value;
+        }
+
+        case CMOS_REG_ALARM_HRS:
+        {
+            BOOLEAN Afternoon = FALSE;
+            BYTE Value = AlarmHour;
+
+            if (!(StatusRegB & CMOS_STB_24HOUR) && (Value >= 12))
+            {
+                Value -= 12;
+                Afternoon = TRUE;
+            }
+
+            if (!(StatusRegB & CMOS_STB_BINARY)) Value = BINARY_TO_BCD(Value);
+
+            /* Convert to 12-hour */
+            if (Afternoon) Value |= 0x80;
+
+            return Value;
+        }
+
+        case CMOS_REG_DAY_OF_WEEK:
+        {
+            return (StatusRegB & CMOS_STB_BINARY)
+                   ? CurrentTime.wDayOfWeek
+                   : BINARY_TO_BCD(CurrentTime.wDayOfWeek);
+        }
+
+        case CMOS_REG_DAY:
+        {
+            return (StatusRegB & CMOS_STB_BINARY)
+                   ? CurrentTime.wDay
+                   :BINARY_TO_BCD(CurrentTime.wDay);
+        }
+
+        case CMOS_REG_MONTH:
+        {
+            return (StatusRegB & CMOS_STB_BINARY)
+                   ? CurrentTime.wMonth
+                   : BINARY_TO_BCD(CurrentTime.wMonth);
+        }
+
+        case CMOS_REG_YEAR:
+        {
+            return (StatusRegB & CMOS_STB_BINARY)
+                   ? (CurrentTime.wYear % 100)
+                   : BINARY_TO_BCD(CurrentTime.wYear % 100);
+        }
+
+        case CMOS_REG_STATUS_A:
+        {
+            return StatusRegA;
+        }
+
+        case CMOS_REG_STATUS_B:
+        {
+            return StatusRegB;
+        }
+
+        case CMOS_REG_STATUS_C:
+        {
+            BYTE Value = StatusRegC;
+
+            /* Clear status register C */
+            StatusRegC = 0;
+
+            /* Return the old value */
+            return Value;
+        }
+
+        case CMOS_REG_STATUS_D:
+        {
+            /* Our CMOS battery works perfectly forever */
+            return CMOS_BATTERY_OK;
+        }
+
+        case CMOS_REG_DIAGNOSTICS:
+        {
+            /* Diagnostics found no errors */
+            return 0;
+        }
+
+        default:
+        {
+            /* Read ignored */
+            return 0;
+        }
+    }
+
+    /* Return to Status Register D */
+    SelectedRegister = CMOS_REG_STATUS_D;
+}
+
+VOID CmosWriteData(BYTE Value)
+{
+    BOOLEAN ChangeTime = FALSE;
+    SYSTEMTIME CurrentTime;
+
+    /* Get the current time */
+    GetLocalTime(&CurrentTime);
+
+    switch (SelectedRegister)
+    {
+        case CMOS_REG_SECONDS:
+        {
+            ChangeTime = TRUE;
+            CurrentTime.wSecond = (StatusRegB & CMOS_STB_BINARY)
+                                  ? Value
+                                  : BCD_TO_BINARY(Value);
+
+            break;
+        }
+
+        case CMOS_REG_ALARM_SEC:
+        {
+            AlarmSecond = (StatusRegB & CMOS_STB_BINARY)
+                          ? Value
+                          : BCD_TO_BINARY(Value);
+
+            break;
+        }
+
+        case CMOS_REG_MINUTES:
+        {
+            ChangeTime = TRUE;
+            CurrentTime.wMinute = (StatusRegB & CMOS_STB_BINARY)
+                                  ? Value
+                                  : BCD_TO_BINARY(Value);
+
+            break;
+        }
+
+        case CMOS_REG_ALARM_MIN:
+        {
+            AlarmMinute = (StatusRegB & CMOS_STB_BINARY)
+                          ? Value
+                          : BCD_TO_BINARY(Value);
+
+            break;
+        }
+
+        case CMOS_REG_HOURS:
+        {
+            BOOLEAN Afternoon = FALSE;
+
+            ChangeTime = TRUE;
+
+            if (!(StatusRegB & CMOS_STB_24HOUR) && (Value & 0x80))
+            {
+                Value &= ~0x80;
+                Afternoon = TRUE;
+            }
+
+            CurrentTime.wHour = (StatusRegB & CMOS_STB_BINARY)
+                                ? Value
+                                : BCD_TO_BINARY(Value);
+
+            /* Convert to 24-hour format */
+            if (Afternoon) CurrentTime.wHour += 12;
+
+            break;
+        }
+
+        case CMOS_REG_ALARM_HRS:
+        {
+            BOOLEAN Afternoon = FALSE;
+
+            if (!(StatusRegB & CMOS_STB_24HOUR) && (Value & 0x80))
+            {
+                Value &= ~0x80;
+                Afternoon = TRUE;
+            }
+
+            AlarmHour = (StatusRegB & CMOS_STB_BINARY)
+                        ? Value
+                        : BCD_TO_BINARY(Value);
+
+            /* Convert to 24-hour format */
+            if (Afternoon) AlarmHour += 12;
+
+            break;
+        }
+
+        case CMOS_REG_DAY_OF_WEEK:
+        {
+            ChangeTime = TRUE;
+            CurrentTime.wDayOfWeek = (StatusRegB & CMOS_STB_BINARY)
+                                     ? Value
+                                     : BCD_TO_BINARY(Value);
+
+            break;
+        }
+
+        case CMOS_REG_DAY:
+        {
+            ChangeTime = TRUE;
+            CurrentTime.wDay = (StatusRegB & CMOS_STB_BINARY)
+                               ? Value
+                               : BCD_TO_BINARY(Value);
+
+            break;
+        }
+
+        case CMOS_REG_MONTH:
+        {
+            ChangeTime = TRUE;
+            CurrentTime.wMonth = (StatusRegB & CMOS_STB_BINARY)
+                                 ? Value
+                                 : BCD_TO_BINARY(Value);
+
+            break;
+        }
+
+        case CMOS_REG_YEAR:
+        {
+            ChangeTime = TRUE;
+
+            /* Clear everything except the century */
+            CurrentTime.wYear = (CurrentTime.wYear / 100) * 100;
+
+            CurrentTime.wYear += (StatusRegB & CMOS_STB_BINARY)
+                                 ? Value
+                                 : BCD_TO_BINARY(Value);
+
+            break;
+        }
+
+        case CMOS_REG_STATUS_A:
+        {
+            StatusRegA = Value;
+            break;
+        }
+
+        case CMOS_REG_STATUS_B:
+        {
+            StatusRegB = Value;
+            break;
+        }
+
+        default:
+        {
+            /* Write ignored */
+        }
+    }
+
+    if (ChangeTime) SetLocalTime(&CurrentTime);
+
+    /* Return to Status Register D */
+    SelectedRegister = CMOS_REG_STATUS_D;
+}
+
+DWORD RtcGetTicksPerSecond(VOID)
+{
+    BYTE RateSelect = StatusRegB & 0x0F;
+
+    if (RateSelect == 0)
+    {
+        /* No periodic interrupt */
+        return 0;
+    }
+
+    /* 1 and 2 act like 8 and 9 */
+    if (RateSelect <= 2) RateSelect += 7;
+
+    return 1 << (16 - RateSelect);
+}
+
+VOID RtcPeriodicTick(VOID)
+{
+    /* Set PF */
+    StatusRegC |= CMOS_STC_PF;
+
+    /* Check if there should be an interrupt on a periodic timer tick */
+    if (StatusRegB & CMOS_STB_INT_PERIODIC)
+    {
+        StatusRegC |= CMOS_STC_IRQF;
+
+        /* Interrupt! */
+        PicInterruptRequest(RTC_IRQ_NUMBER);
+    }
+}
+
+/* Should be called every second */
+VOID RtcTimeUpdate(VOID)
+{
+    SYSTEMTIME CurrentTime;
+
+    /* Get the current time */
+    GetLocalTime(&CurrentTime);
+
+    /* Set UF */
+    StatusRegC |= CMOS_STC_UF;
+
+    /* Check if the time matches the alarm time */
+    if ((CurrentTime.wHour == AlarmHour)
+        && (CurrentTime.wMinute == AlarmMinute)
+        && (CurrentTime.wSecond == AlarmSecond))
+    {
+        /* Set the alarm flag */
+        StatusRegC |= CMOS_STC_AF;
+
+        /* Set IRQF if there should be an interrupt */
+        if (StatusRegB & CMOS_STB_INT_ON_ALARM) StatusRegC |= CMOS_STC_IRQF;
+    }
+
+    /* Check if there should be an interrupt on update */
+    if (StatusRegB & CMOS_STB_INT_ON_UPDATE) StatusRegC |= CMOS_STC_IRQF;
+
+    if (StatusRegC & CMOS_STC_IRQF)
+    {
+        /* Interrupt! */
+        PicInterruptRequest(RTC_IRQ_NUMBER);
+    }
+}
diff --git a/subsystems/ntvdm/cmos.h b/subsystems/ntvdm/cmos.h
new file mode 100644 (file)
index 0000000..5f7f632
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * COPYRIGHT:       GPL - See COPYING in the top level directory
+ * PROJECT:         ReactOS Virtual DOS Machine
+ * FILE:            cmos.h
+ * PURPOSE:         Real Time Clock emulation (header file)
+ * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
+ */
+
+#ifndef _CMOS_H_
+#define _CMOS_H_
+
+/* INCLUDES *******************************************************************/
+
+#include "ntvdm.h"
+
+/* DEFINES ********************************************************************/
+
+#define RTC_IRQ_NUMBER      8
+#define CMOS_ADDRESS_PORT   0x70
+#define CMOS_DATA_PORT      0x71
+#define CMOS_DISABLE_NMI    (1 << 7)
+#define CMOS_BATTERY_OK     0x80
+
+/* Status Register B flags */
+#define CMOS_STB_24HOUR         (1 << 1)
+#define CMOS_STB_BINARY         (1 << 2)
+#define CMOS_STB_SQUARE_WAVE    (1 << 3)
+#define CMOS_STB_INT_ON_UPDATE  (1 << 4)
+#define CMOS_STB_INT_ON_ALARM   (1 << 5)
+#define CMOS_STB_INT_PERIODIC   (1 << 6)
+
+/* Status Register C flags */
+#define CMOS_STC_UF     CMOS_STB_INT_ON_UPDATE
+#define CMOS_STC_AF     CMOS_STB_INT_ON_ALARM
+#define CMOS_STC_PF     CMOS_STB_INT_PERIODIC
+#define CMOS_STC_IRQF   (1 << 7)
+
+/* Default register values */
+#define CMOS_DEFAULT_STA 0x26
+#define CMOS_DEFAULT_STB CMOS_STB_24HOUR
+
+/* BCD-Binary conversion */
+#define BINARY_TO_BCD(x) (((x / 10) << 4) | (x % 10))
+#define BCD_TO_BINARY(x) (((x >> 4) * 10) + (x & 0x0F))
+
+typedef enum _CMOS_REGISTERS
+{
+    CMOS_REG_SECONDS,
+    CMOS_REG_ALARM_SEC,
+    CMOS_REG_MINUTES,
+    CMOS_REG_ALARM_MIN,
+    CMOS_REG_HOURS,
+    CMOS_REG_ALARM_HRS,
+    CMOS_REG_DAY_OF_WEEK,
+    CMOS_REG_DAY,
+    CMOS_REG_MONTH,
+    CMOS_REG_YEAR,
+    CMOS_REG_STATUS_A,
+    CMOS_REG_STATUS_B,
+    CMOS_REG_STATUS_C,
+    CMOS_REG_STATUS_D,
+    CMOS_REG_DIAGNOSTICS,
+    CMOS_REG_MAX
+} CMOS_REGISTERS, *PCMOS_REGISTERS;
+
+BOOLEAN IsNmiEnabled(VOID);
+VOID CmosWriteAddress(BYTE Value);
+BYTE CmosReadData(VOID);
+VOID CmosWriteData(BYTE Value);
+DWORD RtcGetTicksPerSecond(VOID);
+VOID RtcPeriodicTick(VOID);
+VOID RtcTimeUpdate(VOID);
+    
+#endif // _CMOS_H_
+
+/* EOF */
index cd03403..d9a24f8 100644 (file)
@@ -19,6 +19,7 @@
 #include "pic.h"
 #include "ps2.h"
 #include "timer.h"
+#include "cmos.h"
 
 /* PRIVATE VARIABLES **********************************************************/
 
@@ -126,6 +127,12 @@ static VOID WINAPI EmulatorReadIo(PFAST486_STATE State, ULONG Port, PVOID Buffer
                 break;
             }
 
+            case CMOS_DATA_PORT:
+            {
+                *(Address++) = CmosReadData();
+                break;
+            }
+
             case SPEAKER_CONTROL_PORT:
             {
                 *(Address++) = SpeakerReadStatus();
@@ -211,6 +218,18 @@ static VOID WINAPI EmulatorWriteIo(PFAST486_STATE State, ULONG Port, PVOID Buffe
                 break;
             }
 
+            case CMOS_ADDRESS_PORT:
+            {
+                CmosWriteAddress(*(Address++));
+                break;
+            }
+
+            case CMOS_DATA_PORT:
+            {
+                CmosWriteData(*(Address++));
+                break;
+            }
+
             case SPEAKER_CONTROL_PORT:
             {
                 SpeakerWriteCommand(*(Address++));
index 2fec562..9c91778 100644 (file)
@@ -19,6 +19,7 @@
 #include "timer.h"
 #include "pic.h"
 #include "ps2.h"
+#include "cmos.h"
 
 /*
  * Activate this line if you want to be able to test NTVDM with:
@@ -72,9 +73,12 @@ INT wmain(INT argc, WCHAR *argv[])
     DWORD Cycles = 0;
     DWORD LastCyclePrintout = GetTickCount();
     DWORD LastVerticalRefresh = GetTickCount();
-    LARGE_INTEGER Frequency, LastTimerTick, Counter;
+    DWORD LastClockUpdate = GetTickCount();
+    LARGE_INTEGER Frequency, LastTimerTick, LastRtcTick, Counter;
     LONGLONG TimerTicks;
     HANDLE InputThread = NULL;
+    LARGE_INTEGER StartPerfCount;
+    DWORD StartTickCount;
 
     /* Set the handler routine */
     SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
@@ -138,17 +142,34 @@ INT wmain(INT argc, WCHAR *argv[])
     /* Start the input thread */
     InputThread = CreateThread(NULL, 0, &InputThreadProc, NULL, 0, NULL);
 
-    /* Set the last timer tick to the current time */
-    QueryPerformanceCounter(&LastTimerTick);
+    /* Find the starting performance and tick count */
+    StartTickCount = GetTickCount();
+    QueryPerformanceCounter(&StartPerfCount);
+    /* 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)
@@ -161,6 +182,21 @@ INT wmain(INT argc, WCHAR *argv[])
             LastTimerTick = Counter;
         }
 
+        /* Check for RTC update */
+        if ((CurrentTickCount - LastClockUpdate) >= 1000)
+        {
+            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) >= 16)
         {