[NTVDM]
[reactos.git] / reactos / subsystems / mvdm / ntvdm / emulator.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: emulator.c
5 * PURPOSE: Minimal x86 machine emulator for the VDM
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #define NDEBUG
12
13 #include "emulator.h"
14 #include "memory.h"
15
16 #include "cpu/callback.h"
17 #include "cpu/cpu.h"
18 #include "cpu/bop.h"
19 #include <isvbop.h>
20
21 #include "int32.h"
22
23 #include "clock.h"
24 #include "bios/rom.h"
25 #include "hardware/cmos.h"
26 #include "hardware/dma.h"
27 #include "hardware/keyboard.h"
28 #include "hardware/mouse.h"
29 #include "hardware/pic.h"
30 #include "hardware/ps2.h"
31 #include "hardware/sound/speaker.h"
32 #include "hardware/pit.h"
33 #include "hardware/video/vga.h"
34 #include "ems.h"
35
36 #include "vddsup.h"
37 #include "io.h"
38
39 /* Extra PSDK/NDK Headers */
40 #include <ndk/psfuncs.h>
41 #include <ndk/mmfuncs.h>
42
43 /* PRIVATE VARIABLES **********************************************************/
44
45 LPVOID BaseAddress = NULL;
46 BOOLEAN VdmRunning = TRUE;
47
48 static BOOLEAN A20Line = FALSE;
49 static BYTE Port61hState = 0x00;
50
51 static HANDLE InputThread = NULL;
52
53 LPCWSTR ExceptionName[] =
54 {
55 L"Division By Zero",
56 L"Debug",
57 L"Unexpected Error",
58 L"Breakpoint",
59 L"Integer Overflow",
60 L"Bound Range Exceeded",
61 L"Invalid Opcode",
62 L"FPU Not Available"
63 };
64
65 /* BOP Identifiers */
66 #define BOP_DEBUGGER 0x56 // Break into the debugger from a 16-bit app
67
68 /* PRIVATE FUNCTIONS **********************************************************/
69
70 VOID WINAPI EmulatorReadMemory(PFAST486_STATE State, ULONG Address, PVOID Buffer, ULONG Size)
71 {
72 UNREFERENCED_PARAMETER(State);
73
74 /* Mirror 0x000FFFF0 at 0xFFFFFFF0 */
75 if (Address >= 0xFFFFFFF0) Address -= 0xFFF00000;
76
77 /* If the A20 line is disabled, mask bit 20 */
78 if (!A20Line) Address &= ~(1 << 20);
79
80 if (Address >= MAX_ADDRESS) return;
81 Size = min(Size, MAX_ADDRESS - Address);
82
83 /* Read while calling fast memory hooks */
84 MemRead(Address, Buffer, Size);
85 }
86
87 VOID WINAPI EmulatorWriteMemory(PFAST486_STATE State, ULONG Address, PVOID Buffer, ULONG Size)
88 {
89 UNREFERENCED_PARAMETER(State);
90
91 /* If the A20 line is disabled, mask bit 20 */
92 if (!A20Line) Address &= ~(1 << 20);
93
94 if (Address >= MAX_ADDRESS) return;
95 Size = min(Size, MAX_ADDRESS - Address);
96
97 /* Write while calling fast memory hooks */
98 MemWrite(Address, Buffer, Size);
99 }
100
101 UCHAR WINAPI EmulatorIntAcknowledge(PFAST486_STATE State)
102 {
103 UNREFERENCED_PARAMETER(State);
104
105 /* Get the interrupt number from the PIC */
106 return PicGetInterrupt();
107 }
108
109 VOID WINAPI EmulatorFpu(PFAST486_STATE State)
110 {
111 /* The FPU is wired to IRQ 13 */
112 PicInterruptRequest(13);
113 }
114
115 VOID EmulatorException(BYTE ExceptionNumber, LPWORD Stack)
116 {
117 WORD CodeSegment, InstructionPointer;
118 PBYTE Opcode;
119
120 ASSERT(ExceptionNumber < 8);
121
122 /* Get the CS:IP */
123 InstructionPointer = Stack[STACK_IP];
124 CodeSegment = Stack[STACK_CS];
125 Opcode = (PBYTE)SEG_OFF_TO_PTR(CodeSegment, InstructionPointer);
126
127 /* Display a message to the user */
128 DisplayMessage(L"Exception: %s occured at %04X:%04X\n"
129 L"Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
130 ExceptionName[ExceptionNumber],
131 CodeSegment,
132 InstructionPointer,
133 Opcode[0],
134 Opcode[1],
135 Opcode[2],
136 Opcode[3],
137 Opcode[4],
138 Opcode[5],
139 Opcode[6],
140 Opcode[7],
141 Opcode[8],
142 Opcode[9]);
143
144 Fast486DumpState(&EmulatorContext);
145
146 /* Stop the VDM */
147 EmulatorTerminate();
148 return;
149 }
150
151 VOID EmulatorTerminate(VOID)
152 {
153 /* Stop the VDM */
154 CpuUnsimulate(); // Halt the CPU
155 VdmRunning = FALSE;
156 }
157
158 VOID EmulatorInterruptSignal(VOID)
159 {
160 /* Call the Fast486 API */
161 Fast486InterruptSignal(&EmulatorContext);
162 }
163
164 VOID EmulatorSetA20(BOOLEAN Enabled)
165 {
166 A20Line = Enabled;
167 }
168
169 static VOID WINAPI EmulatorDebugBreakBop(LPWORD Stack)
170 {
171 DPRINT1("NTVDM: BOP_DEBUGGER\n");
172 DebugBreak();
173 }
174
175 static BYTE WINAPI Port61hRead(USHORT Port)
176 {
177 return Port61hState;
178 }
179
180 static VOID WINAPI Port61hWrite(USHORT Port, BYTE Data)
181 {
182 // BOOLEAN SpeakerStateChange = FALSE;
183 BYTE OldPort61hState = Port61hState;
184
185 /* Only the four lowest bytes can be written */
186 Port61hState = (Port61hState & 0xF0) | (Data & 0x0F);
187
188 if ((OldPort61hState ^ Port61hState) & 0x01)
189 {
190 DPRINT("PIT 2 Gate %s\n", Port61hState & 0x01 ? "on" : "off");
191 PitSetGate(2, !!(Port61hState & 0x01));
192 // SpeakerStateChange = TRUE;
193 }
194
195 if ((OldPort61hState ^ Port61hState) & 0x02)
196 {
197 /* There were some change for the speaker... */
198 DPRINT("Speaker %s\n", Port61hState & 0x02 ? "on" : "off");
199 // SpeakerStateChange = TRUE;
200 }
201 // if (SpeakerStateChange) SpeakerChange(Port61hState);
202 SpeakerChange(Port61hState);
203 }
204
205 static VOID WINAPI PitChan0Out(LPVOID Param, BOOLEAN State)
206 {
207 if (State)
208 {
209 DPRINT("PicInterruptRequest\n");
210 PicInterruptRequest(0); // Raise IRQ 0
211 }
212 // else < Lower IRQ 0 >
213 }
214
215 static VOID WINAPI PitChan1Out(LPVOID Param, BOOLEAN State)
216 {
217 #if 0
218 if (State)
219 {
220 /* Set bit 4 of Port 61h */
221 Port61hState |= 1 << 4;
222 }
223 else
224 {
225 /* Clear bit 4 of Port 61h */
226 Port61hState &= ~(1 << 4);
227 }
228 #else
229 Port61hState = (Port61hState & 0xEF) | (State << 4);
230 #endif
231 }
232
233 static VOID WINAPI PitChan2Out(LPVOID Param, BOOLEAN State)
234 {
235 BYTE OldPort61hState = Port61hState;
236
237 #if 0
238 if (State)
239 {
240 /* Set bit 5 of Port 61h */
241 Port61hState |= 1 << 5;
242 }
243 else
244 {
245 /* Clear bit 5 of Port 61h */
246 Port61hState &= ~(1 << 5);
247 }
248 #else
249 Port61hState = (Port61hState & 0xDF) | (State << 5);
250 #endif
251
252 if ((OldPort61hState ^ Port61hState) & 0x20)
253 {
254 DPRINT("PitChan2Out -- Port61hState changed\n");
255 SpeakerChange(Port61hState);
256 }
257 }
258
259
260 static DWORD
261 WINAPI
262 PumpConsoleInput(LPVOID Parameter)
263 {
264 HANDLE ConsoleInput = (HANDLE)Parameter;
265 INPUT_RECORD InputRecord;
266 DWORD Count;
267
268 while (VdmRunning)
269 {
270 /* Make sure the task event is signaled */
271 WaitForSingleObject(VdmTaskEvent, INFINITE);
272
273 /* Wait for an input record */
274 if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
275 {
276 DWORD LastError = GetLastError();
277 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
278 return LastError;
279 }
280
281 ASSERT(Count != 0);
282
283 /* Check the event type */
284 switch (InputRecord.EventType)
285 {
286 /*
287 * Hardware events
288 */
289 case KEY_EVENT:
290 KeyboardEventHandler(&InputRecord.Event.KeyEvent);
291 break;
292
293 case MOUSE_EVENT:
294 MouseEventHandler(&InputRecord.Event.MouseEvent);
295 break;
296
297 case WINDOW_BUFFER_SIZE_EVENT:
298 ScreenEventHandler(&InputRecord.Event.WindowBufferSizeEvent);
299 break;
300
301 /*
302 * Interface events
303 */
304 case MENU_EVENT:
305 MenuEventHandler(&InputRecord.Event.MenuEvent);
306 break;
307
308 case FOCUS_EVENT:
309 FocusEventHandler(&InputRecord.Event.FocusEvent);
310 break;
311
312 default:
313 break;
314 }
315 }
316
317 return 0;
318 }
319
320 static VOID EnableExtraHardware(HANDLE ConsoleInput)
321 {
322 DWORD ConInMode;
323
324 if (GetConsoleMode(ConsoleInput, &ConInMode))
325 {
326 #if 0
327 // GetNumberOfConsoleMouseButtons();
328 // GetSystemMetrics(SM_CMOUSEBUTTONS);
329 // GetSystemMetrics(SM_MOUSEPRESENT);
330 if (MousePresent)
331 {
332 #endif
333 /* Support mouse input events if there is a mouse on the system */
334 ConInMode |= ENABLE_MOUSE_INPUT;
335 #if 0
336 }
337 else
338 {
339 /* Do not support mouse input events if there is no mouse on the system */
340 ConInMode &= ~ENABLE_MOUSE_INPUT;
341 }
342 #endif
343
344 SetConsoleMode(ConsoleInput, ConInMode);
345 }
346 }
347
348 /* PUBLIC FUNCTIONS ***********************************************************/
349
350 static VOID
351 DumpMemoryRaw(HANDLE hFile)
352 {
353 PVOID Buffer;
354 SIZE_T Size;
355
356 /* Dump the VM memory */
357 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
358 Buffer = REAL_TO_PHYS(NULL);
359 Size = MAX_ADDRESS - (ULONG_PTR)(NULL);
360 WriteFile(hFile, Buffer, Size, &Size, NULL);
361 }
362
363 static VOID
364 DumpMemoryTxt(HANDLE hFile)
365 {
366 #define LINE_SIZE 75 + 2
367 ULONG i;
368 PBYTE Ptr1, Ptr2;
369 CHAR LineBuffer[LINE_SIZE];
370 PCHAR Line;
371 SIZE_T LineSize;
372
373 /* Dump the VM memory */
374 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
375 Ptr1 = Ptr2 = REAL_TO_PHYS(NULL);
376 while (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0)
377 {
378 Ptr1 = Ptr2;
379 Line = LineBuffer;
380
381 /* Print the address */
382 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, "%08x ", PHYS_TO_REAL(Ptr1));
383
384 /* Print up to 16 bytes... */
385
386 /* ... in hexadecimal form first... */
387 i = 0;
388 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0))
389 {
390 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, " %02x", *Ptr1);
391 ++Ptr1;
392 }
393
394 /* ... align with spaces if needed... */
395 RtlFillMemory(Line, 0x0F + 4 - i, ' ');
396 Line += 0x0F + 4 - i;
397
398 /* ... then in character form. */
399 i = 0;
400 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr2) > 0))
401 {
402 *Line++ = ((*Ptr2 >= 0x20 && *Ptr2 <= 0x7E) || (*Ptr2 >= 0x80 && *Ptr2 < 0xFF) ? *Ptr2 : '.');
403 ++Ptr2;
404 }
405
406 /* Newline */
407 *Line++ = '\r';
408 *Line++ = '\n';
409
410 /* Finally write the line to the file */
411 LineSize = Line - LineBuffer;
412 WriteFile(hFile, LineBuffer, LineSize, &LineSize, NULL);
413 }
414 }
415
416 VOID DumpMemory(BOOLEAN TextFormat)
417 {
418 static ULONG DumpNumber = 0;
419
420 HANDLE hFile;
421 WCHAR FileName[MAX_PATH];
422
423 /* Build a suitable file name */
424 _snwprintf(FileName, MAX_PATH,
425 L"memdump%lu.%s",
426 DumpNumber,
427 TextFormat ? L"txt" : L"dat");
428 ++DumpNumber;
429
430 DPRINT1("Creating memory dump file '%S'...\n", FileName);
431
432 /* Always create the dump file */
433 hFile = CreateFileW(FileName,
434 GENERIC_WRITE,
435 0,
436 NULL,
437 CREATE_ALWAYS,
438 FILE_ATTRIBUTE_NORMAL,
439 NULL);
440
441 if (hFile == INVALID_HANDLE_VALUE)
442 {
443 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
444 FileName, GetLastError());
445 return;
446 }
447
448 /* Dump the VM memory in the chosen format */
449 if (TextFormat)
450 DumpMemoryTxt(hFile);
451 else
452 DumpMemoryRaw(hFile);
453
454 /* Close the file */
455 CloseHandle(hFile);
456
457 DPRINT1("Memory dump done\n");
458 }
459
460 BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
461 {
462 /* Initialize memory */
463 if (!MemInitialize())
464 {
465 wprintf(L"Memory initialization failed.\n");
466 return FALSE;
467 }
468
469 /* Initialize I/O ports */
470 /* Initialize RAM */
471
472 /* Initialize EMS */
473 EmsInitialize();
474
475 /* Initialize the CPU */
476
477 /* Initialize the internal clock */
478 if (!ClockInitialize())
479 {
480 wprintf(L"FATAL: Failed to initialize the clock\n");
481 EmulatorCleanup();
482 return FALSE;
483 }
484
485 /* Initialize the CPU */
486 CpuInitialize();
487
488 /* Initialize DMA */
489 DmaInitialize();
490
491 /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
492 PicInitialize();
493 PitInitialize();
494 CmosInitialize();
495 SpeakerInitialize();
496
497 /* Set output functions */
498 PitSetOutFunction(0, NULL, PitChan0Out);
499 PitSetOutFunction(1, NULL, PitChan1Out);
500 PitSetOutFunction(2, NULL, PitChan2Out);
501
502 /* Register the I/O Ports */
503 RegisterIoPort(CONTROL_SYSTEM_PORT61H, Port61hRead, Port61hWrite);
504
505 /* Set the console input mode */
506 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
507 // upon console window events (screen buffer resize, ...).
508 SetConsoleMode(ConsoleInput, ENABLE_PROCESSED_INPUT /* | ENABLE_WINDOW_INPUT */);
509 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
510
511 /**/EnableExtraHardware(ConsoleInput);/**/
512
513 /* Initialize the PS/2 port */
514 PS2Initialize();
515
516 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
517 KeyboardInit(0);
518 MouseInit(1);
519
520 /**************** ATTACH INPUT WITH CONSOLE *****************/
521 /* Start the input thread */
522 InputThread = CreateThread(NULL, 0, &PumpConsoleInput, ConsoleInput, 0, NULL);
523 if (InputThread == NULL)
524 {
525 DisplayMessage(L"Failed to create the console input thread.");
526 EmulatorCleanup();
527 return FALSE;
528 }
529 /************************************************************/
530
531 /* Initialize the VGA */
532 if (!VgaInitialize(ConsoleOutput))
533 {
534 DisplayMessage(L"Failed to initialize VGA support.");
535 EmulatorCleanup();
536 return FALSE;
537 }
538
539 /* Initialize the software callback system and register the emulator BOPs */
540 InitializeInt32();
541 RegisterBop(BOP_DEBUGGER , EmulatorDebugBreakBop);
542 // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop);
543
544 /* Initialize VDD support */
545 VDDSupInitialize();
546
547 return TRUE;
548 }
549
550 VOID EmulatorCleanup(VOID)
551 {
552 VgaCleanup();
553
554 /* Close the input thread handle */
555 if (InputThread != NULL) CloseHandle(InputThread);
556 InputThread = NULL;
557
558 PS2Cleanup();
559
560 EmsCleanup();
561 SpeakerCleanup();
562 CmosCleanup();
563 // PitCleanup();
564 // PicCleanup();
565
566 // DmaCleanup();
567
568 CpuCleanup();
569 MemCleanup();
570 }
571
572
573
574 VOID
575 WINAPI
576 VDDSimulate16(VOID)
577 {
578 CpuSimulate();
579 }
580
581 VOID
582 WINAPI
583 VDDTerminateVDM(VOID)
584 {
585 /* Stop the VDM */
586 EmulatorTerminate();
587 }
588
589 /* EOF */