diff --git a/subsystems/ntvdm/CMakeLists.txt b/subsystems/ntvdm/CMakeLists.txt index c812b03c5ac..9d1bc8b31ef 100644 --- a/subsystems/ntvdm/CMakeLists.txt +++ b/subsystems/ntvdm/CMakeLists.txt @@ -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 index 00000000000..be67438018d --- /dev/null +++ b/subsystems/ntvdm/cmos.c @@ -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 + */ + +/* 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 index 00000000000..5f7f63236ae --- /dev/null +++ b/subsystems/ntvdm/cmos.h @@ -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 + */ + +#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 */ diff --git a/subsystems/ntvdm/emulator.c b/subsystems/ntvdm/emulator.c index cd03403144f..d9a24f8e872 100644 --- a/subsystems/ntvdm/emulator.c +++ b/subsystems/ntvdm/emulator.c @@ -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++)); diff --git a/subsystems/ntvdm/ntvdm.c b/subsystems/ntvdm/ntvdm.c index 2fec5624057..9c91778ed1e 100644 --- a/subsystems/ntvdm/ntvdm.c +++ b/subsystems/ntvdm/ntvdm.c @@ -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) {