[NTVDM]: Add debug print for timer ticks to try to find why the emulator sometimes...
[reactos.git] / subsystems / ntvdm / ntvdm.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: ntvdm.c
5 * PURPOSE: Virtual DOS Machine
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #define NDEBUG
12
13 #include "ntvdm.h"
14 #include "emulator.h"
15
16 #include "bios/bios.h"
17 #include "dos/dem.h"
18 #include "hardware/cmos.h"
19 #include "hardware/ps2.h"
20 #include "hardware/timer.h"
21 #include "hardware/vga.h"
22
23 /*
24 * Activate this line if you want to be able to test NTVDM with:
25 * ntvdm.exe <program>
26 */
27 #define TESTING
28
29 #define IPS_DISPLAY
30
31 /* PUBLIC VARIABLES ***********************************************************/
32
33 static HANDLE ConsoleInput = INVALID_HANDLE_VALUE;
34 static HANDLE ConsoleOutput = INVALID_HANDLE_VALUE;
35 static DWORD OrgConsoleInputMode, OrgConsoleOutputMode;
36 static CONSOLE_CURSOR_INFO OrgConsoleCursorInfo;
37 static CONSOLE_SCREEN_BUFFER_INFO OrgConsoleBufferInfo;
38
39 /* PUBLIC FUNCTIONS ***********************************************************/
40
41 VOID DisplayMessage(LPCWSTR Format, ...)
42 {
43 WCHAR Buffer[256];
44 va_list Parameters;
45
46 va_start(Parameters, Format);
47 _vsnwprintf(Buffer, 256, Format, Parameters);
48 DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
49 MessageBoxW(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
50 va_end(Parameters);
51 }
52
53 BOOL WINAPI ConsoleCtrlHandler(DWORD ControlType)
54 {
55 switch (ControlType)
56 {
57 case CTRL_C_EVENT:
58 case CTRL_BREAK_EVENT:
59 {
60 /* Call INT 23h */
61 EmulatorInterrupt(0x23);
62 break;
63 }
64 default:
65 {
66 /* Stop the VDM if the user logs out or closes the console */
67 VdmRunning = FALSE;
68 }
69 }
70 return TRUE;
71 }
72
73 BOOL ConsoleInit(VOID)
74 {
75 /* Set the handler routine */
76 SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
77
78 /* Get the input handle to the real console, and check for success */
79 ConsoleInput = CreateFileW(L"CONIN$",
80 GENERIC_READ | GENERIC_WRITE,
81 FILE_SHARE_READ | FILE_SHARE_WRITE,
82 NULL,
83 OPEN_EXISTING,
84 0,
85 NULL);
86 if (ConsoleInput == INVALID_HANDLE_VALUE)
87 {
88 wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
89 return FALSE;
90 }
91
92 /* Get the output handle to the real console, and check for success */
93 ConsoleOutput = CreateFileW(L"CONOUT$",
94 GENERIC_READ | GENERIC_WRITE,
95 FILE_SHARE_READ | FILE_SHARE_WRITE,
96 NULL,
97 OPEN_EXISTING,
98 0,
99 NULL);
100 if (ConsoleOutput == INVALID_HANDLE_VALUE)
101 {
102 CloseHandle(ConsoleInput);
103 wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
104 return FALSE;
105 }
106
107 /* Save the original input and output console modes */
108 if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
109 !GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
110 {
111 CloseHandle(ConsoleOutput);
112 CloseHandle(ConsoleInput);
113 wprintf(L"FATAL: Cannot save console in/out modes\n");
114 return FALSE;
115 }
116
117 /* Save the original cursor and console screen buffer information */
118 if (!GetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo) ||
119 !GetConsoleScreenBufferInfo(ConsoleOutput, &OrgConsoleBufferInfo))
120 {
121 CloseHandle(ConsoleOutput);
122 CloseHandle(ConsoleInput);
123 wprintf(L"FATAL: Cannot save console cursor/screen-buffer info\n");
124 return FALSE;
125 }
126
127 return TRUE;
128 }
129
130 VOID ConsoleCleanup(VOID)
131 {
132 SMALL_RECT ConRect;
133 CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
134
135 /* Restore the old screen buffer */
136 SetConsoleActiveScreenBuffer(ConsoleOutput);
137
138 /* Restore the original console size */
139 GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo);
140 ConRect.Left = 0; // OrgConsoleBufferInfo.srWindow.Left;
141 // ConRect.Top = ConsoleInfo.dwCursorPosition.Y / (OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top + 1);
142 // ConRect.Top *= (OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top + 1);
143 ConRect.Top = ConsoleInfo.dwCursorPosition.Y;
144 ConRect.Right = ConRect.Left + OrgConsoleBufferInfo.srWindow.Right - OrgConsoleBufferInfo.srWindow.Left;
145 ConRect.Bottom = ConRect.Top + OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top ;
146 /* See the following trick explanation in vga.c:VgaEnterTextMode() */
147 SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
148 SetConsoleWindowInfo(ConsoleOutput, TRUE, &ConRect);
149 // SetConsoleWindowInfo(ConsoleOutput, TRUE, &OrgConsoleBufferInfo.srWindow);
150 SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
151
152 /* Restore the original cursor shape */
153 SetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo);
154
155 /* Restore the original input and output console modes */
156 SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
157 SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
158
159 /* Close the console handles */
160 if (ConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleOutput);
161 if (ConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleInput);
162 }
163
164 INT wmain(INT argc, WCHAR *argv[])
165 {
166 INT i;
167 CHAR CommandLine[DOS_CMDLINE_LENGTH];
168 LARGE_INTEGER StartPerfCount;
169 LARGE_INTEGER Frequency, LastTimerTick, LastRtcTick, Counter;
170 LONGLONG TimerTicks;
171 DWORD StartTickCount, CurrentTickCount;
172 DWORD LastClockUpdate;
173 DWORD LastVerticalRefresh;
174 #ifdef IPS_DISPLAY
175 DWORD LastCyclePrintout;
176 DWORD Cycles = 0;
177 #endif
178 INT KeyboardIntCounter = 0;
179
180 #ifndef TESTING
181 UNREFERENCED_PARAMETER(argc);
182 UNREFERENCED_PARAMETER(argv);
183
184 /* The DOS command line must be ASCII */
185 WideCharToMultiByte(CP_ACP, 0, GetCommandLine(), -1, CommandLine, sizeof(CommandLine), NULL, NULL);
186 #else
187 if (argc == 2 && argv[1] != NULL)
188 {
189 WideCharToMultiByte(CP_ACP, 0, argv[1], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
190 }
191 else
192 {
193 wprintf(L"\nReactOS Virtual DOS Machine\n\n"
194 L"Usage: NTVDM <executable>\n");
195 return 0;
196 }
197 #endif
198
199 DPRINT1("\n\n\nNTVDM - Starting '%s'...\n\n\n", CommandLine);
200
201 /* Initialize the console */
202 if (!ConsoleInit())
203 {
204 wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
205 goto Cleanup;
206 }
207
208 /* Initialize the emulator */
209 if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
210 {
211 wprintf(L"FATAL: Failed to initialize the emulator\n");
212 goto Cleanup;
213 }
214
215 /* Initialize the performance counter (needed for hardware timers) */
216 if (!QueryPerformanceFrequency(&Frequency))
217 {
218 wprintf(L"FATAL: Performance counter not available\n");
219 goto Cleanup;
220 }
221
222 /* Initialize the system BIOS */
223 if (!BiosInitialize(ConsoleInput, ConsoleOutput))
224 {
225 wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
226 goto Cleanup;
227 }
228
229 /* Initialize the VDM DOS kernel */
230 if (!DosInitialize(NULL))
231 {
232 wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
233 goto Cleanup;
234 }
235
236 /* Start the process from the command line */
237 if (!DosCreateProcess(CommandLine, 0))
238 {
239 DisplayMessage(L"Could not start program: %S", CommandLine);
240 goto Cleanup;
241 }
242
243 /* Find the starting performance and tick count */
244 StartTickCount = GetTickCount();
245 QueryPerformanceCounter(&StartPerfCount);
246
247 /* Set the different last counts to the starting count */
248 LastClockUpdate = LastVerticalRefresh =
249 #ifdef IPS_DISPLAY
250 LastCyclePrintout =
251 #endif
252 StartTickCount;
253
254 /* Set the last timer ticks to the current time */
255 LastTimerTick = LastRtcTick = StartPerfCount;
256
257 /* Main loop */
258 while (VdmRunning)
259 {
260 DWORD PitResolution = PitGetResolution();
261 DWORD RtcFrequency = RtcGetTicksPerSecond();
262
263 /* Get the current number of ticks */
264 CurrentTickCount = GetTickCount();
265
266 if ((PitResolution <= 1000) && (RtcFrequency <= 1000))
267 {
268 /* Calculate the approximate performance counter value instead */
269 Counter.QuadPart = StartPerfCount.QuadPart
270 + (CurrentTickCount - StartTickCount)
271 * (Frequency.QuadPart / 1000);
272 }
273 else
274 {
275 /* Get the current performance counter value */
276 QueryPerformanceCounter(&Counter);
277 }
278
279 /* Get the number of PIT ticks that have passed */
280 TimerTicks = ((Counter.QuadPart - LastTimerTick.QuadPart)
281 * PIT_BASE_FREQUENCY) / Frequency.QuadPart;
282
283 /* Update the PIT */
284 if (TimerTicks > 0)
285 {
286 PitClock(TimerTicks);
287 LastTimerTick = Counter;
288 }
289
290 /* Check for RTC update */
291 if ((CurrentTickCount - LastClockUpdate) >= 1000)
292 {
293 RtcTimeUpdate();
294 LastClockUpdate = CurrentTickCount;
295 }
296
297 /* Check for RTC periodic tick */
298 if ((Counter.QuadPart - LastRtcTick.QuadPart)
299 >= (Frequency.QuadPart / (LONGLONG)RtcFrequency))
300 {
301 RtcPeriodicTick();
302 LastRtcTick = Counter;
303 }
304
305 /* Check for vertical retrace */
306 if ((CurrentTickCount - LastVerticalRefresh) >= 15)
307 {
308 VgaRefreshDisplay();
309 LastVerticalRefresh = CurrentTickCount;
310 }
311
312 KeyboardIntCounter++;
313 if (KeyboardIntCounter == KBD_INT_CYCLES)
314 {
315 GenerateKeyboardInterrupts();
316 KeyboardIntCounter = 0;
317 }
318
319 /* Horizontal retrace occurs as fast as possible */
320 VgaHorizontalRetrace();
321
322 /* Continue CPU emulation */
323 for (i = 0; (i < STEPS_PER_CYCLE) && VdmRunning; i++)
324 {
325 EmulatorStep();
326 #ifdef IPS_DISPLAY
327 Cycles++;
328 #endif
329 }
330
331 #ifdef IPS_DISPLAY
332 if ((CurrentTickCount - LastCyclePrintout) >= 1000)
333 {
334 DPRINT1("NTVDM: %lu Instructions Per Second; TimerTicks = %lu\n", Cycles, TimerTicks);
335 LastCyclePrintout = CurrentTickCount;
336 Cycles = 0;
337 }
338 #endif
339 }
340
341 /* Perform another screen refresh */
342 VgaRefreshDisplay();
343
344 Cleanup:
345 BiosCleanup();
346 EmulatorCleanup();
347 ConsoleCleanup();
348
349 DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
350
351 return 0;
352 }
353
354 /* EOF */