[NTVDM]: it is always interesting to dump the full CPU state when an exception occurs.
[reactos.git] / reactos / subsystems / 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
15 #include "cpu/callback.h"
16 #include "cpu/cpu.h"
17 #include "cpu/bop.h"
18 #include <isvbop.h>
19
20 #include "int32.h"
21
22 #include "clock.h"
23 #include "bios/rom.h"
24 #include "hardware/cmos.h"
25 #include "hardware/keyboard.h"
26 #include "hardware/mouse.h"
27 #include "hardware/pic.h"
28 #include "hardware/ps2.h"
29 #include "hardware/speaker.h"
30 #include "hardware/timer.h"
31 #include "hardware/vga.h"
32
33 #include "vddsup.h"
34 #include "io.h"
35
36 /* PRIVATE VARIABLES **********************************************************/
37
38 LPVOID BaseAddress = NULL;
39 BOOLEAN VdmRunning = TRUE;
40
41 static BOOLEAN A20Line = FALSE;
42 static BYTE Port61hState = 0x00;
43
44 static HANDLE InputThread = NULL;
45
46 LPCWSTR ExceptionName[] =
47 {
48 L"Division By Zero",
49 L"Debug",
50 L"Unexpected Error",
51 L"Breakpoint",
52 L"Integer Overflow",
53 L"Bound Range Exceeded",
54 L"Invalid Opcode",
55 L"FPU Not Available"
56 };
57
58 /* BOP Identifiers */
59 #define BOP_DEBUGGER 0x56 // Break into the debugger from a 16-bit app
60
61 /* PRIVATE FUNCTIONS **********************************************************/
62
63 VOID WINAPI EmulatorReadMemory(PFAST486_STATE State, ULONG Address, PVOID Buffer, ULONG Size)
64 {
65 UNREFERENCED_PARAMETER(State);
66
67 // BIG HACK!!!! To make BIOS images working correctly,
68 // until Aleksander rewrites memory management!!
69 if (Address >= 0xFFFFFFF0) Address -= 0xFFF00000;
70
71 /* If the A20 line is disabled, mask bit 20 */
72 if (!A20Line) Address &= ~(1 << 20);
73
74 /* Make sure the requested address is valid */
75 if ((Address + Size) >= MAX_ADDRESS) return;
76
77 /*
78 * Check if we are going to read the VGA memory and
79 * copy it into the virtual address space if needed.
80 */
81 if (((Address + Size) >= VgaGetVideoBaseAddress())
82 && (Address < VgaGetVideoLimitAddress()))
83 {
84 DWORD VgaAddress = max(Address, VgaGetVideoBaseAddress());
85 DWORD ActualSize = min(Address + Size - 1, VgaGetVideoLimitAddress())
86 - VgaAddress + 1;
87 LPBYTE DestBuffer = (LPBYTE)REAL_TO_PHYS(VgaAddress);
88
89 /* Read from the VGA memory */
90 VgaReadMemory(VgaAddress, DestBuffer, ActualSize);
91 }
92
93 /* Read the data from the virtual address space and store it in the buffer */
94 RtlCopyMemory(Buffer, REAL_TO_PHYS(Address), Size);
95 }
96
97 VOID WINAPI EmulatorWriteMemory(PFAST486_STATE State, ULONG Address, PVOID Buffer, ULONG Size)
98 {
99 UNREFERENCED_PARAMETER(State);
100
101 // BIG HACK!!!! To make BIOS images working correctly,
102 // until Aleksander rewrites memory management!!
103 if (Address >= 0xFFFFFFF0) Address -= 0xFFF00000;
104
105 /* If the A20 line is disabled, mask bit 20 */
106 if (!A20Line) Address &= ~(1 << 20);
107
108 /* Make sure the requested address is valid */
109 if ((Address + Size) >= MAX_ADDRESS) return;
110
111 /* Make sure we don't write to the ROM area */
112 if ((Address + Size) >= ROM_AREA_START && (Address < ROM_AREA_END)) return;
113
114 /* Read the data from the buffer and store it in the virtual address space */
115 RtlCopyMemory(REAL_TO_PHYS(Address), Buffer, Size);
116
117 /*
118 * Check if we modified the VGA memory.
119 */
120 if (((Address + Size) >= VgaGetVideoBaseAddress())
121 && (Address < VgaGetVideoLimitAddress()))
122 {
123 DWORD VgaAddress = max(Address, VgaGetVideoBaseAddress());
124 DWORD ActualSize = min(Address + Size - 1, VgaGetVideoLimitAddress())
125 - VgaAddress + 1;
126 LPBYTE SrcBuffer = (LPBYTE)REAL_TO_PHYS(VgaAddress);
127
128 /* Write to the VGA memory */
129 VgaWriteMemory(VgaAddress, SrcBuffer, ActualSize);
130 }
131 }
132
133 UCHAR WINAPI EmulatorIntAcknowledge(PFAST486_STATE State)
134 {
135 UNREFERENCED_PARAMETER(State);
136
137 /* Get the interrupt number from the PIC */
138 return PicGetInterrupt();
139 }
140
141 VOID EmulatorException(BYTE ExceptionNumber, LPWORD Stack)
142 {
143 WORD CodeSegment, InstructionPointer;
144 PBYTE Opcode;
145
146 ASSERT(ExceptionNumber < 8);
147
148 /* Get the CS:IP */
149 InstructionPointer = Stack[STACK_IP];
150 CodeSegment = Stack[STACK_CS];
151 Opcode = (PBYTE)SEG_OFF_TO_PTR(CodeSegment, InstructionPointer);
152
153 /* Display a message to the user */
154 DisplayMessage(L"Exception: %s occured at %04X:%04X\n"
155 L"Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
156 ExceptionName[ExceptionNumber],
157 CodeSegment,
158 InstructionPointer,
159 Opcode[0],
160 Opcode[1],
161 Opcode[2],
162 Opcode[3],
163 Opcode[4],
164 Opcode[5],
165 Opcode[6],
166 Opcode[7],
167 Opcode[8],
168 Opcode[9]);
169
170 Fast486DumpState(&EmulatorContext);
171
172 /* Stop the VDM */
173 EmulatorTerminate();
174 return;
175 }
176
177 VOID EmulatorTerminate(VOID)
178 {
179 /* Stop the VDM */
180 VdmRunning = FALSE;
181 }
182
183 VOID EmulatorInterrupt(BYTE Number)
184 {
185 /* Call the Fast486 API */
186 Fast486Interrupt(&EmulatorContext, Number);
187 }
188
189 VOID EmulatorInterruptSignal(VOID)
190 {
191 /* Call the Fast486 API */
192 Fast486InterruptSignal(&EmulatorContext);
193 }
194
195 VOID EmulatorSetA20(BOOLEAN Enabled)
196 {
197 A20Line = Enabled;
198 }
199
200 static VOID WINAPI EmulatorDebugBreakBop(LPWORD Stack)
201 {
202 DPRINT1("NTVDM: BOP_DEBUGGER\n");
203 DebugBreak();
204 }
205
206 static BYTE WINAPI Port61hRead(ULONG Port)
207 {
208 return Port61hState;
209 }
210
211 static VOID WINAPI Port61hWrite(ULONG Port, BYTE Data)
212 {
213 // BOOLEAN SpeakerStateChange = FALSE;
214 BYTE OldPort61hState = Port61hState;
215
216 /* Only the four lowest bytes can be written */
217 Port61hState = (Port61hState & 0xF0) | (Data & 0x0F);
218
219 if ((OldPort61hState ^ Port61hState) & 0x01)
220 {
221 DPRINT("PIT 2 Gate %s\n", Port61hState & 0x01 ? "on" : "off");
222 PitSetGate(2, !!(Port61hState & 0x01));
223 // SpeakerStateChange = TRUE;
224 }
225
226 if ((OldPort61hState ^ Port61hState) & 0x02)
227 {
228 /* There were some change for the speaker... */
229 DPRINT("Speaker %s\n", Port61hState & 0x02 ? "on" : "off");
230 // SpeakerStateChange = TRUE;
231 }
232 // if (SpeakerStateChange) SpeakerChange();
233 SpeakerChange();
234 }
235
236 static VOID WINAPI PitChan0Out(LPVOID Param, BOOLEAN State)
237 {
238 if (State)
239 {
240 DPRINT("PicInterruptRequest\n");
241 PicInterruptRequest(0); // Raise IRQ 0
242 }
243 // else < Lower IRQ 0 >
244 }
245
246 static VOID WINAPI PitChan1Out(LPVOID Param, BOOLEAN State)
247 {
248 #if 0
249 if (State)
250 {
251 /* Set bit 4 of Port 61h */
252 Port61hState |= 1 << 4;
253 }
254 else
255 {
256 /* Clear bit 4 of Port 61h */
257 Port61hState &= ~(1 << 4);
258 }
259 #else
260 Port61hState = (Port61hState & 0xEF) | (State << 4);
261 #endif
262 }
263
264 static VOID WINAPI PitChan2Out(LPVOID Param, BOOLEAN State)
265 {
266 BYTE OldPort61hState = Port61hState;
267
268 #if 0
269 if (State)
270 {
271 /* Set bit 5 of Port 61h */
272 Port61hState |= 1 << 5;
273 }
274 else
275 {
276 /* Clear bit 5 of Port 61h */
277 Port61hState &= ~(1 << 5);
278 }
279 #else
280 Port61hState = (Port61hState & 0xDF) | (State << 5);
281 #endif
282
283 if ((OldPort61hState ^ Port61hState) & 0x20)
284 {
285 DPRINT("PitChan2Out -- Port61hState changed\n");
286 SpeakerChange();
287 }
288 }
289
290
291 static DWORD
292 WINAPI
293 PumpConsoleInput(LPVOID Parameter)
294 {
295 HANDLE ConsoleInput = (HANDLE)Parameter;
296 INPUT_RECORD InputRecord;
297 DWORD Count;
298
299 while (VdmRunning)
300 {
301 /* Make sure the task event is signaled */
302 WaitForSingleObject(VdmTaskEvent, INFINITE);
303
304 /* Wait for an input record */
305 if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
306 {
307 DWORD LastError = GetLastError();
308 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
309 return LastError;
310 }
311
312 ASSERT(Count != 0);
313
314 /* Check the event type */
315 switch (InputRecord.EventType)
316 {
317 /*
318 * Hardware events
319 */
320 case KEY_EVENT:
321 KeyboardEventHandler(&InputRecord.Event.KeyEvent);
322 break;
323
324 case MOUSE_EVENT:
325 MouseEventHandler(&InputRecord.Event.MouseEvent);
326 break;
327
328 case WINDOW_BUFFER_SIZE_EVENT:
329 ScreenEventHandler(&InputRecord.Event.WindowBufferSizeEvent);
330 break;
331
332 /*
333 * Interface events
334 */
335 case MENU_EVENT:
336 MenuEventHandler(&InputRecord.Event.MenuEvent);
337 break;
338
339 case FOCUS_EVENT:
340 FocusEventHandler(&InputRecord.Event.FocusEvent);
341 break;
342
343 default:
344 break;
345 }
346 }
347
348 return 0;
349 }
350
351 static VOID EnableExtraHardware(HANDLE ConsoleInput)
352 {
353 DWORD ConInMode;
354
355 if (GetConsoleMode(ConsoleInput, &ConInMode))
356 {
357 #if 0
358 // GetNumberOfConsoleMouseButtons();
359 // GetSystemMetrics(SM_CMOUSEBUTTONS);
360 // GetSystemMetrics(SM_MOUSEPRESENT);
361 if (MousePresent)
362 {
363 #endif
364 /* Support mouse input events if there is a mouse on the system */
365 ConInMode |= ENABLE_MOUSE_INPUT;
366 #if 0
367 }
368 else
369 {
370 /* Do not support mouse input events if there is no mouse on the system */
371 ConInMode &= ~ENABLE_MOUSE_INPUT;
372 }
373 #endif
374
375 SetConsoleMode(ConsoleInput, ConInMode);
376 }
377 }
378
379 /* PUBLIC FUNCTIONS ***********************************************************/
380
381 static VOID
382 DumpMemoryRaw(HANDLE hFile)
383 {
384 PVOID Buffer;
385 SIZE_T Size;
386
387 /* Dump the VM memory */
388 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
389 Buffer = REAL_TO_PHYS(NULL);
390 Size = MAX_ADDRESS - (ULONG_PTR)(NULL);
391 WriteFile(hFile, Buffer, Size, &Size, NULL);
392 }
393
394 static VOID
395 DumpMemoryTxt(HANDLE hFile)
396 {
397 #define LINE_SIZE 75 + 2
398 ULONG i;
399 PBYTE Ptr1, Ptr2;
400 CHAR LineBuffer[LINE_SIZE];
401 PCHAR Line;
402 SIZE_T LineSize;
403
404 /* Dump the VM memory */
405 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
406 Ptr1 = Ptr2 = REAL_TO_PHYS(NULL);
407 while (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0)
408 {
409 Ptr1 = Ptr2;
410 Line = LineBuffer;
411
412 /* Print the address */
413 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, "%08x ", PHYS_TO_REAL(Ptr1));
414
415 /* Print up to 16 bytes... */
416
417 /* ... in hexadecimal form first... */
418 i = 0;
419 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0))
420 {
421 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, " %02x", *Ptr1);
422 ++Ptr1;
423 }
424
425 /* ... align with spaces if needed... */
426 RtlFillMemory(Line, 0x0F + 4 - i, ' ');
427 Line += 0x0F + 4 - i;
428
429 /* ... then in character form. */
430 i = 0;
431 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr2) > 0))
432 {
433 *Line++ = ((*Ptr2 >= 0x20 && *Ptr2 <= 0x7E) || (*Ptr2 >= 0x80 && *Ptr2 < 0xFF) ? *Ptr2 : '.');
434 ++Ptr2;
435 }
436
437 /* Newline */
438 *Line++ = '\r';
439 *Line++ = '\n';
440
441 /* Finally write the line to the file */
442 LineSize = Line - LineBuffer;
443 WriteFile(hFile, LineBuffer, LineSize, &LineSize, NULL);
444 }
445 }
446
447 VOID DumpMemory(BOOLEAN TextFormat)
448 {
449 static ULONG DumpNumber = 0;
450
451 HANDLE hFile;
452 WCHAR FileName[MAX_PATH];
453
454 /* Build a suitable file name */
455 _snwprintf(FileName, MAX_PATH,
456 L"memdump%lu.%s",
457 DumpNumber,
458 TextFormat ? L"txt" : L"dat");
459 ++DumpNumber;
460
461 DPRINT1("Creating memory dump file '%S'...\n", FileName);
462
463 /* Always create the dump file */
464 hFile = CreateFileW(FileName,
465 GENERIC_WRITE,
466 0,
467 NULL,
468 CREATE_ALWAYS,
469 FILE_ATTRIBUTE_NORMAL,
470 NULL);
471
472 if (hFile == INVALID_HANDLE_VALUE)
473 {
474 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
475 FileName, GetLastError());
476 return;
477 }
478
479 /* Dump the VM memory in the chosen format */
480 if (TextFormat)
481 DumpMemoryTxt(hFile);
482 else
483 DumpMemoryRaw(hFile);
484
485 /* Close the file */
486 CloseHandle(hFile);
487
488 DPRINT1("Memory dump done\n");
489 }
490
491 BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
492 {
493 /* Allocate memory for the 16-bit address space */
494 BaseAddress = HeapAlloc(GetProcessHeap(), /*HEAP_ZERO_MEMORY*/ 0, MAX_ADDRESS);
495 if (BaseAddress == NULL)
496 {
497 wprintf(L"FATAL: Failed to allocate VDM memory.\n");
498 return FALSE;
499 }
500 /*
501 * For diagnostics purposes, we fill the memory with INT 0x03 codes
502 * so that if a program wants to execute random code in memory, we can
503 * retrieve the exact CS:IP where the problem happens.
504 */
505 RtlFillMemory(BaseAddress, MAX_ADDRESS, 0xCC);
506
507 /* Initialize I/O ports */
508 /* Initialize RAM */
509
510 /* Initialize the CPU */
511
512 /* Initialize the internal clock */
513 if (!ClockInitialize())
514 {
515 wprintf(L"FATAL: Failed to initialize the clock\n");
516 return FALSE;
517 }
518
519 /* Initialize the CPU */
520 CpuInitialize();
521 // Fast486Initialize(&EmulatorContext,
522 // EmulatorReadMemory,
523 // EmulatorWriteMemory,
524 // EmulatorReadIo,
525 // EmulatorWriteIo,
526 // NULL,
527 // EmulatorBiosOperation,
528 // EmulatorIntAcknowledge,
529 // NULL /* TODO: Use a TLB */);
530
531 /* Initialize DMA */
532
533 /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
534 PicInitialize();
535 PitInitialize();
536 CmosInitialize();
537 SpeakerInitialize();
538
539 /* Set output functions */
540 PitSetOutFunction(0, NULL, PitChan0Out);
541 PitSetOutFunction(1, NULL, PitChan1Out);
542 PitSetOutFunction(2, NULL, PitChan2Out);
543
544 /* Register the I/O Ports */
545 RegisterIoPort(CONTROL_SYSTEM_PORT61H, Port61hRead, Port61hWrite);
546
547 /* Set the console input mode */
548 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
549 // upon console window events (screen buffer resize, ...).
550 SetConsoleMode(ConsoleInput, ENABLE_PROCESSED_INPUT /* | ENABLE_WINDOW_INPUT */);
551 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
552
553 /**/EnableExtraHardware(ConsoleInput);/**/
554
555 /* Initialize the PS/2 port */
556 PS2Initialize();
557
558 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
559 KeyboardInit(0);
560 MouseInit(1);
561
562 /**************** ATTACH INPUT WITH CONSOLE *****************/
563 /* Start the input thread */
564 InputThread = CreateThread(NULL, 0, &PumpConsoleInput, ConsoleInput, 0, NULL);
565 if (InputThread == NULL)
566 {
567 DisplayMessage(L"Failed to create the console input thread.");
568 return FALSE;
569 }
570 /************************************************************/
571
572 /* Initialize the VGA */
573 if (!VgaInitialize(ConsoleOutput))
574 {
575 DisplayMessage(L"Failed to initialize VGA support.");
576 return FALSE;
577 }
578
579 /* Initialize the software callback system and register the emulator BOPs */
580 InitializeInt32();
581 RegisterBop(BOP_DEBUGGER , EmulatorDebugBreakBop);
582 // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop);
583
584 /* Initialize VDD support */
585 VDDSupInitialize();
586
587 return TRUE;
588 }
589
590 VOID EmulatorCleanup(VOID)
591 {
592 VgaCleanup();
593
594 /* Close the input thread handle */
595 if (InputThread != NULL) CloseHandle(InputThread);
596 InputThread = NULL;
597
598 PS2Cleanup();
599
600 SpeakerCleanup();
601 CmosCleanup();
602 // PitCleanup();
603 // PicCleanup();
604
605 CpuCleanup();
606
607 /* Free the memory allocated for the 16-bit address space */
608 if (BaseAddress != NULL) HeapFree(GetProcessHeap(), 0, BaseAddress);
609 }
610
611
612
613 VOID
614 WINAPI
615 VDDSimulate16(VOID)
616 {
617 CpuSimulate();
618 }
619
620 VOID
621 WINAPI
622 VDDTerminateVDM(VOID)
623 {
624 /* Stop the VDM */
625 EmulatorTerminate();
626 }
627
628 PBYTE
629 WINAPI
630 Sim32pGetVDMPointer(IN ULONG Address,
631 IN BOOLEAN ProtectedMode)
632 {
633 // FIXME
634 UNREFERENCED_PARAMETER(ProtectedMode);
635
636 /*
637 * HIWORD(Address) == Segment (if ProtectedMode == FALSE)
638 * or Selector (if ProtectedMode == TRUE )
639 * LOWORD(Address) == Offset
640 */
641 return (PBYTE)FAR_POINTER(Address);
642 }
643
644 PBYTE
645 WINAPI
646 MGetVdmPointer(IN ULONG Address,
647 IN ULONG Size,
648 IN BOOLEAN ProtectedMode)
649 {
650 UNREFERENCED_PARAMETER(Size);
651 return Sim32pGetVDMPointer(Address, ProtectedMode);
652 }
653
654 PVOID
655 WINAPI
656 VdmMapFlat(IN USHORT Segment,
657 IN ULONG Offset,
658 IN VDM_MODE Mode)
659 {
660 // FIXME
661 UNREFERENCED_PARAMETER(Mode);
662
663 return SEG_OFF_TO_PTR(Segment, Offset);
664 }
665
666 BOOL
667 WINAPI
668 VdmFlushCache(IN USHORT Segment,
669 IN ULONG Offset,
670 IN ULONG Size,
671 IN VDM_MODE Mode)
672 {
673 // FIXME
674 UNIMPLEMENTED;
675 return TRUE;
676 }
677
678 BOOL
679 WINAPI
680 VdmUnmapFlat(IN USHORT Segment,
681 IN ULONG Offset,
682 IN PVOID Buffer,
683 IN VDM_MODE Mode)
684 {
685 // FIXME
686 UNIMPLEMENTED;
687 return TRUE;
688 }
689
690 /* EOF */