[NTVDM]
[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 /* Stop the VDM */
171 EmulatorTerminate();
172 return;
173 }
174
175 VOID EmulatorTerminate(VOID)
176 {
177 /* Stop the VDM */
178 VdmRunning = FALSE;
179 }
180
181 VOID EmulatorInterrupt(BYTE Number)
182 {
183 /* Call the Fast486 API */
184 Fast486Interrupt(&EmulatorContext, Number);
185 }
186
187 VOID EmulatorInterruptSignal(VOID)
188 {
189 /* Call the Fast486 API */
190 Fast486InterruptSignal(&EmulatorContext);
191 }
192
193 VOID EmulatorSetA20(BOOLEAN Enabled)
194 {
195 A20Line = Enabled;
196 }
197
198 static VOID WINAPI EmulatorDebugBreakBop(LPWORD Stack)
199 {
200 DPRINT1("NTVDM: BOP_DEBUGGER\n");
201 DebugBreak();
202 }
203
204 static BYTE WINAPI Port61hRead(ULONG Port)
205 {
206 return Port61hState;
207 }
208
209 static VOID WINAPI Port61hWrite(ULONG Port, BYTE Data)
210 {
211 // BOOLEAN SpeakerStateChange = FALSE;
212 BYTE OldPort61hState = Port61hState;
213
214 /* Only the four lowest bytes can be written */
215 Port61hState = (Port61hState & 0xF0) | (Data & 0x0F);
216
217 if ((OldPort61hState ^ Port61hState) & 0x01)
218 {
219 DPRINT("PIT 2 Gate %s\n", Port61hState & 0x01 ? "on" : "off");
220 PitSetGate(2, !!(Port61hState & 0x01));
221 // SpeakerStateChange = TRUE;
222 }
223
224 if ((OldPort61hState ^ Port61hState) & 0x02)
225 {
226 /* There were some change for the speaker... */
227 DPRINT("Speaker %s\n", Port61hState & 0x02 ? "on" : "off");
228 // SpeakerStateChange = TRUE;
229 }
230 // if (SpeakerStateChange) SpeakerChange();
231 SpeakerChange();
232 }
233
234 static VOID WINAPI PitChan0Out(LPVOID Param, BOOLEAN State)
235 {
236 if (State)
237 {
238 DPRINT("PicInterruptRequest\n");
239 PicInterruptRequest(0); // Raise IRQ 0
240 }
241 // else < Lower IRQ 0 >
242 }
243
244 static VOID WINAPI PitChan1Out(LPVOID Param, BOOLEAN State)
245 {
246 #if 0
247 if (State)
248 {
249 /* Set bit 4 of Port 61h */
250 Port61hState |= 1 << 4;
251 }
252 else
253 {
254 /* Clear bit 4 of Port 61h */
255 Port61hState &= ~(1 << 4);
256 }
257 #else
258 Port61hState = (Port61hState & 0xEF) | (State << 4);
259 #endif
260 }
261
262 static VOID WINAPI PitChan2Out(LPVOID Param, BOOLEAN State)
263 {
264 BYTE OldPort61hState = Port61hState;
265
266 #if 0
267 if (State)
268 {
269 /* Set bit 5 of Port 61h */
270 Port61hState |= 1 << 5;
271 }
272 else
273 {
274 /* Clear bit 5 of Port 61h */
275 Port61hState &= ~(1 << 5);
276 }
277 #else
278 Port61hState = (Port61hState & 0xDF) | (State << 5);
279 #endif
280
281 if ((OldPort61hState ^ Port61hState) & 0x20)
282 {
283 DPRINT("PitChan2Out -- Port61hState changed\n");
284 SpeakerChange();
285 }
286 }
287
288
289 static DWORD
290 WINAPI
291 PumpConsoleInput(LPVOID Parameter)
292 {
293 HANDLE ConsoleInput = (HANDLE)Parameter;
294 INPUT_RECORD InputRecord;
295 DWORD Count;
296
297 while (VdmRunning)
298 {
299 /* Make sure the task event is signaled */
300 WaitForSingleObject(VdmTaskEvent, INFINITE);
301
302 /* Wait for an input record */
303 if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
304 {
305 DWORD LastError = GetLastError();
306 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
307 return LastError;
308 }
309
310 ASSERT(Count != 0);
311
312 /* Check the event type */
313 switch (InputRecord.EventType)
314 {
315 /*
316 * Hardware events
317 */
318 case KEY_EVENT:
319 KeyboardEventHandler(&InputRecord.Event.KeyEvent);
320 break;
321
322 case MOUSE_EVENT:
323 MouseEventHandler(&InputRecord.Event.MouseEvent);
324 break;
325
326 case WINDOW_BUFFER_SIZE_EVENT:
327 ScreenEventHandler(&InputRecord.Event.WindowBufferSizeEvent);
328 break;
329
330 /*
331 * Interface events
332 */
333 case MENU_EVENT:
334 MenuEventHandler(&InputRecord.Event.MenuEvent);
335 break;
336
337 case FOCUS_EVENT:
338 FocusEventHandler(&InputRecord.Event.FocusEvent);
339 break;
340
341 default:
342 break;
343 }
344 }
345
346 return 0;
347 }
348
349 static VOID EnableExtraHardware(HANDLE ConsoleInput)
350 {
351 DWORD ConInMode;
352
353 if (GetConsoleMode(ConsoleInput, &ConInMode))
354 {
355 #if 0
356 // GetNumberOfConsoleMouseButtons();
357 // GetSystemMetrics(SM_CMOUSEBUTTONS);
358 // GetSystemMetrics(SM_MOUSEPRESENT);
359 if (MousePresent)
360 {
361 #endif
362 /* Support mouse input events if there is a mouse on the system */
363 ConInMode |= ENABLE_MOUSE_INPUT;
364 #if 0
365 }
366 else
367 {
368 /* Do not support mouse input events if there is no mouse on the system */
369 ConInMode &= ~ENABLE_MOUSE_INPUT;
370 }
371 #endif
372
373 SetConsoleMode(ConsoleInput, ConInMode);
374 }
375 }
376
377 /* PUBLIC FUNCTIONS ***********************************************************/
378
379 static VOID
380 DumpMemoryRaw(HANDLE hFile)
381 {
382 PVOID Buffer;
383 SIZE_T Size;
384
385 /* Dump the VM memory */
386 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
387 Buffer = REAL_TO_PHYS(NULL);
388 Size = MAX_ADDRESS - (ULONG_PTR)(NULL);
389 WriteFile(hFile, Buffer, Size, &Size, NULL);
390 }
391
392 static VOID
393 DumpMemoryTxt(HANDLE hFile)
394 {
395 #define LINE_SIZE 75 + 2
396 ULONG i;
397 PBYTE Ptr1, Ptr2;
398 CHAR LineBuffer[LINE_SIZE];
399 PCHAR Line;
400 SIZE_T LineSize;
401
402 /* Dump the VM memory */
403 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
404 Ptr1 = Ptr2 = REAL_TO_PHYS(NULL);
405 while (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0)
406 {
407 Ptr1 = Ptr2;
408 Line = LineBuffer;
409
410 /* Print the address */
411 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, "%08x ", PHYS_TO_REAL(Ptr1));
412
413 /* Print up to 16 bytes... */
414
415 /* ... in hexadecimal form first... */
416 i = 0;
417 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0))
418 {
419 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, " %02x", *Ptr1);
420 ++Ptr1;
421 }
422
423 /* ... align with spaces if needed... */
424 RtlFillMemory(Line, 0x0F + 4 - i, ' ');
425 Line += 0x0F + 4 - i;
426
427 /* ... then in character form. */
428 i = 0;
429 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr2) > 0))
430 {
431 *Line++ = ((*Ptr2 >= 0x20 && *Ptr2 <= 0x7E) || (*Ptr2 >= 0x80 && *Ptr2 < 0xFF) ? *Ptr2 : '.');
432 ++Ptr2;
433 }
434
435 /* Newline */
436 *Line++ = '\r';
437 *Line++ = '\n';
438
439 /* Finally write the line to the file */
440 LineSize = Line - LineBuffer;
441 WriteFile(hFile, LineBuffer, LineSize, &LineSize, NULL);
442 }
443 }
444
445 VOID DumpMemory(BOOLEAN TextFormat)
446 {
447 static ULONG DumpNumber = 0;
448
449 HANDLE hFile;
450 WCHAR FileName[MAX_PATH];
451
452 /* Build a suitable file name */
453 _snwprintf(FileName, MAX_PATH,
454 L"memdump%lu.%s",
455 DumpNumber,
456 TextFormat ? L"txt" : L"dat");
457 ++DumpNumber;
458
459 DPRINT1("Creating memory dump file '%S'...\n", FileName);
460
461 /* Always create the dump file */
462 hFile = CreateFileW(FileName,
463 GENERIC_WRITE,
464 0,
465 NULL,
466 CREATE_ALWAYS,
467 FILE_ATTRIBUTE_NORMAL,
468 NULL);
469
470 if (hFile == INVALID_HANDLE_VALUE)
471 {
472 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
473 FileName, GetLastError());
474 return;
475 }
476
477 /* Dump the VM memory in the chosen format */
478 if (TextFormat)
479 DumpMemoryTxt(hFile);
480 else
481 DumpMemoryRaw(hFile);
482
483 /* Close the file */
484 CloseHandle(hFile);
485
486 DPRINT1("Memory dump done\n");
487 }
488
489 BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
490 {
491 /* Allocate memory for the 16-bit address space */
492 BaseAddress = HeapAlloc(GetProcessHeap(), /*HEAP_ZERO_MEMORY*/ 0, MAX_ADDRESS);
493 if (BaseAddress == NULL)
494 {
495 wprintf(L"FATAL: Failed to allocate VDM memory.\n");
496 return FALSE;
497 }
498 /*
499 * For diagnostics purposes, we fill the memory with INT 0x03 codes
500 * so that if a program wants to execute random code in memory, we can
501 * retrieve the exact CS:IP where the problem happens.
502 */
503 RtlFillMemory(BaseAddress, MAX_ADDRESS, 0xCC);
504
505 /* Initialize I/O ports */
506 /* Initialize RAM */
507
508 /* Initialize the CPU */
509
510 /* Initialize the internal clock */
511 if (!ClockInitialize())
512 {
513 wprintf(L"FATAL: Failed to initialize the clock\n");
514 return FALSE;
515 }
516
517 /* Initialize the CPU */
518 CpuInitialize();
519 // Fast486Initialize(&EmulatorContext,
520 // EmulatorReadMemory,
521 // EmulatorWriteMemory,
522 // EmulatorReadIo,
523 // EmulatorWriteIo,
524 // NULL,
525 // EmulatorBiosOperation,
526 // EmulatorIntAcknowledge,
527 // NULL /* TODO: Use a TLB */);
528
529 /* Initialize DMA */
530
531 /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
532 PicInitialize();
533 PitInitialize();
534 CmosInitialize();
535 SpeakerInitialize();
536
537 /* Set output functions */
538 PitSetOutFunction(0, NULL, PitChan0Out);
539 PitSetOutFunction(1, NULL, PitChan1Out);
540 PitSetOutFunction(2, NULL, PitChan2Out);
541
542 /* Register the I/O Ports */
543 RegisterIoPort(CONTROL_SYSTEM_PORT61H, Port61hRead, Port61hWrite);
544
545 /* Set the console input mode */
546 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
547 // upon console window events (screen buffer resize, ...).
548 SetConsoleMode(ConsoleInput, ENABLE_PROCESSED_INPUT /* | ENABLE_WINDOW_INPUT */);
549 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
550
551 /**/EnableExtraHardware(ConsoleInput);/**/
552
553 /* Initialize the PS/2 port */
554 PS2Initialize();
555
556 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */
557 KeyboardInit(0);
558 MouseInit(1);
559
560 /**************** ATTACH INPUT WITH CONSOLE *****************/
561 /* Start the input thread */
562 InputThread = CreateThread(NULL, 0, &PumpConsoleInput, ConsoleInput, 0, NULL);
563 if (InputThread == NULL)
564 {
565 DisplayMessage(L"Failed to create the console input thread.");
566 return FALSE;
567 }
568 /************************************************************/
569
570 /* Initialize the VGA */
571 if (!VgaInitialize(ConsoleOutput))
572 {
573 DisplayMessage(L"Failed to initialize VGA support.");
574 return FALSE;
575 }
576
577 /* Initialize the software callback system and register the emulator BOPs */
578 InitializeInt32();
579 RegisterBop(BOP_DEBUGGER , EmulatorDebugBreakBop);
580 // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop);
581
582 /* Initialize VDD support */
583 VDDSupInitialize();
584
585 return TRUE;
586 }
587
588 VOID EmulatorCleanup(VOID)
589 {
590 VgaCleanup();
591
592 /* Close the input thread handle */
593 if (InputThread != NULL) CloseHandle(InputThread);
594 InputThread = NULL;
595
596 PS2Cleanup();
597
598 SpeakerCleanup();
599 CmosCleanup();
600 // PitCleanup();
601 // PicCleanup();
602
603 CpuCleanup();
604
605 /* Free the memory allocated for the 16-bit address space */
606 if (BaseAddress != NULL) HeapFree(GetProcessHeap(), 0, BaseAddress);
607 }
608
609
610
611 VOID
612 WINAPI
613 VDDSimulate16(VOID)
614 {
615 CpuSimulate();
616 }
617
618 VOID
619 WINAPI
620 VDDTerminateVDM(VOID)
621 {
622 /* Stop the VDM */
623 EmulatorTerminate();
624 }
625
626 PBYTE
627 WINAPI
628 Sim32pGetVDMPointer(IN ULONG Address,
629 IN BOOLEAN ProtectedMode)
630 {
631 // FIXME
632 UNREFERENCED_PARAMETER(ProtectedMode);
633
634 /*
635 * HIWORD(Address) == Segment (if ProtectedMode == FALSE)
636 * or Selector (if ProtectedMode == TRUE )
637 * LOWORD(Address) == Offset
638 */
639 return (PBYTE)FAR_POINTER(Address);
640 }
641
642 PBYTE
643 WINAPI
644 MGetVdmPointer(IN ULONG Address,
645 IN ULONG Size,
646 IN BOOLEAN ProtectedMode)
647 {
648 UNREFERENCED_PARAMETER(Size);
649 return Sim32pGetVDMPointer(Address, ProtectedMode);
650 }
651
652 PVOID
653 WINAPI
654 VdmMapFlat(IN USHORT Segment,
655 IN ULONG Offset,
656 IN VDM_MODE Mode)
657 {
658 // FIXME
659 UNREFERENCED_PARAMETER(Mode);
660
661 return SEG_OFF_TO_PTR(Segment, Offset);
662 }
663
664 BOOL
665 WINAPI
666 VdmFlushCache(IN USHORT Segment,
667 IN ULONG Offset,
668 IN ULONG Size,
669 IN VDM_MODE Mode)
670 {
671 // FIXME
672 UNIMPLEMENTED;
673 return TRUE;
674 }
675
676 BOOL
677 WINAPI
678 VdmUnmapFlat(IN USHORT Segment,
679 IN ULONG Offset,
680 IN PVOID Buffer,
681 IN VDM_MODE Mode)
682 {
683 // FIXME
684 UNIMPLEMENTED;
685 return TRUE;
686 }
687
688 /* EOF */