xref: /reactos/subsystems/mvdm/ntvdm/clock.c (revision 4561998a)
1 /*
2  * COPYRIGHT:       GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/clock.c
5  * PURPOSE:         Clock for VDM
6  * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7  *                  Hermes Belusca-Maito (hermes.belusca@sfr.fr)
8  */
9 
10 /* INCLUDES *******************************************************************/
11 
12 #include "ntvdm.h"
13 
14 #define NDEBUG
15 #include <debug.h>
16 
17 #include "emulator.h"
18 #include "clock.h"
19 
20 #include "cpu/cpu.h"
21 #include "hardware/cmos.h"
22 #include "hardware/ps2.h"
23 #include "hardware/pit.h"
24 #include "hardware/video/svga.h"
25 #include "hardware/mouse.h"
26 
27 /* DEFINES ********************************************************************/
28 
29 /*
30  * Activate IPS_DISPLAY if you want to display the
31  * number of instructions per second.
32  */
33 // #define IPS_DISPLAY
34 
35 /* Processor speed */
36 #define STEPS_PER_CYCLE 1024
37 
38 /* VARIABLES ******************************************************************/
39 
40 static LIST_ENTRY Timers;
41 static LARGE_INTEGER StartPerfCount, Frequency;
42 // static ULONG StartTickCount;
43 static LARGE_INTEGER Counter;
44 static ULONG CurrentTickCount;
45 static ULONGLONG LastCycles = 0ULL;
46 static PHARDWARE_TIMER IpsTimer;
47 
48 ULONGLONG CurrentCycleCount = 0ULL;
49 ULONGLONG CurrentIps = 20000000ULL; // 20 MIPS is a good estimate
50 
51 /* PRIVATE FUNCTIONS **********************************************************/
52 
53 static VOID FASTCALL IpsCallback(ULONGLONG ElapsedTime)
54 {
55 #ifdef IPS_DISPLAY
56     static INT NumCalls = 0;
57 #endif
58 
59     ULONGLONG NewIps = 10ULL * (CurrentCycleCount - LastCycles) / ElapsedTime;
60     CurrentIps = (CurrentIps + NewIps) >> 1;
61 
62 #ifdef IPS_DISPLAY
63     NumCalls++;
64     if (NumCalls == 10)
65     {
66         DPRINT1("NTVDM: %I64u Instructions Per Second\n", CurrentIps);
67         NumCalls = 0;
68     }
69 #endif
70 
71     LastCycles = CurrentCycleCount;
72 }
73 
74 /* PUBLIC FUNCTIONS ***********************************************************/
75 
76 VOID ClockUpdate(VOID)
77 {
78     extern BOOLEAN CpuRunning;
79     UINT i;
80     PLIST_ENTRY Entry;
81     PHARDWARE_TIMER Timer;
82 
83     while (VdmRunning && CpuRunning)
84     {
85         /* Get the current counters */
86         /// DWORD_PTR oldmask = SetThreadAffinityMask(GetCurrentThread(), 0);
87         CurrentTickCount = GetTickCount();
88         NtQueryPerformanceCounter(&Counter, NULL);
89         /// SetThreadAffinityMask(GetCurrentThread(), oldmask);
90 
91         /* Continue CPU emulation */
92         for (i = 0; VdmRunning && CpuRunning && (i < STEPS_PER_CYCLE); i++)
93         {
94             CpuStep();
95             ++CurrentCycleCount;
96         }
97 
98         Entry = Timers.Flink;
99         while (Entry != &Timers)
100         {
101             ULONGLONG Ticks = (ULONGLONG)-1;
102 
103             Timer = CONTAINING_RECORD(Entry, HARDWARE_TIMER, Link);
104             Entry = Entry->Flink;
105 
106             ASSERT((Timer->EnableCount > 0) && (Timer->Flags & HARDWARE_TIMER_ENABLED));
107 
108             if (Timer->Delay)
109             {
110                 if (Timer->Flags & HARDWARE_TIMER_PRECISE)
111                 {
112                     /* Use the performance counter for precise timers */
113                     if (Counter.QuadPart <= Timer->LastTick.QuadPart) continue;
114                     Ticks = (Counter.QuadPart - Timer->LastTick.QuadPart) / Timer->Delay;
115                 }
116                 else
117                 {
118                     /* Use the regular tick count for normal timers */
119                     if (CurrentTickCount <= Timer->LastTick.LowPart) continue;
120                     Ticks = (CurrentTickCount - Timer->LastTick.LowPart) / (ULONG)Timer->Delay;
121                 }
122 
123                 if (Ticks == 0) continue;
124             }
125 
126             Timer->Callback(Ticks);
127 
128             if (Timer->Flags & HARDWARE_TIMER_ONESHOT)
129             {
130                 /* Disable this timer */
131                 DisableHardwareTimer(Timer);
132             }
133 
134             /* Update the time of the last timer tick */
135             Timer->LastTick.QuadPart += Ticks * Timer->Delay;
136         }
137 
138         /* Yield execution to other threads */
139         // FIXME: Disabled because it causes timing issues (slowdowns).
140         // NtYieldExecution();
141     }
142 }
143 
144 PHARDWARE_TIMER CreateHardwareTimer(ULONG Flags, ULONGLONG Delay, PHARDWARE_TIMER_PROC Callback)
145 {
146     PHARDWARE_TIMER Timer;
147 
148     Timer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*Timer));
149     if (Timer == NULL) return NULL;
150 
151     Timer->Flags = Flags & ~HARDWARE_TIMER_ENABLED;
152     Timer->EnableCount = 0;
153     Timer->Callback = Callback;
154     Timer->LastTick.QuadPart = 0;
155     SetHardwareTimerDelay(Timer, Delay);
156 
157     if (Flags & HARDWARE_TIMER_ENABLED) EnableHardwareTimer(Timer);
158     return Timer;
159 }
160 
161 VOID EnableHardwareTimer(PHARDWARE_TIMER Timer)
162 {
163     /* Increment the count */
164     Timer->EnableCount++;
165 
166     /* Check if the count is above 0 but the timer isn't enabled */
167     if ((Timer->EnableCount > 0) && !(Timer->Flags & HARDWARE_TIMER_ENABLED))
168     {
169         if (Timer->Flags & HARDWARE_TIMER_PRECISE)
170         {
171             NtQueryPerformanceCounter(&Timer->LastTick, NULL);
172         }
173         else
174         {
175             Timer->LastTick.LowPart = GetTickCount();
176         }
177 
178         Timer->Flags |= HARDWARE_TIMER_ENABLED;
179         InsertTailList(&Timers, &Timer->Link);
180     }
181 }
182 
183 VOID DisableHardwareTimer(PHARDWARE_TIMER Timer)
184 {
185     /* Decrement the count */
186     Timer->EnableCount--;
187 
188     /* Check if the count is 0 or less but the timer is enabled */
189     if ((Timer->EnableCount <= 0) && (Timer->Flags & HARDWARE_TIMER_ENABLED))
190     {
191         /* Disable the timer */
192         Timer->Flags &= ~HARDWARE_TIMER_ENABLED;
193         RemoveEntryList(&Timer->Link);
194     }
195 }
196 
197 VOID SetHardwareTimerDelay(PHARDWARE_TIMER Timer, ULONGLONG NewDelay)
198 {
199     if (Timer->Flags & HARDWARE_TIMER_PRECISE)
200     {
201         /* Convert the delay from nanoseconds to performance counter ticks */
202         Timer->Delay = (NewDelay * Frequency.QuadPart + 500000000ULL) / 1000000000ULL;
203     }
204     else
205     {
206         Timer->Delay = NewDelay / 1000000ULL;
207     }
208 }
209 
210 VOID DestroyHardwareTimer(PHARDWARE_TIMER Timer)
211 {
212     if (Timer)
213     {
214         if (Timer->Flags & HARDWARE_TIMER_ENABLED) RemoveEntryList(&Timer->Link);
215         RtlFreeHeap(RtlGetProcessHeap(), 0, Timer);
216     }
217 }
218 
219 BOOLEAN ClockInitialize(VOID)
220 {
221     InitializeListHead(&Timers);
222 
223     /* Initialize the performance counter (needed for hardware timers) */
224     /* Find the starting performance */
225     NtQueryPerformanceCounter(&StartPerfCount, &Frequency);
226     if (Frequency.QuadPart == 0)
227     {
228         wprintf(L"FATAL: Performance counter not available\n");
229         return FALSE;
230     }
231 
232     /* Find the starting tick count */
233     // StartTickCount = GetTickCount();
234 
235     IpsTimer = CreateHardwareTimer(HARDWARE_TIMER_ENABLED, HZ_TO_NS(10), IpsCallback);
236     if (IpsTimer == NULL)
237     {
238         wprintf(L"FATAL: Cannot create IPS timer.\n");
239         return FALSE;
240     }
241 
242     return TRUE;
243 }
244