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