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