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