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