[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 #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 DPRINT1("Creating memory dump file '%S'...\n", FileName);
359
360 /* Always create the dump file */
361 hFile = CreateFileW(FileName,
362 GENERIC_WRITE,
363 0,
364 NULL,
365 CREATE_ALWAYS,
366 FILE_ATTRIBUTE_NORMAL,
367 NULL);
368
369 if (hFile == INVALID_HANDLE_VALUE)
370 {
371 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n",
372 FileName, GetLastError());
373 return;
374 }
375
376 /* Dump the VM memory */
377 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
378 Ptr1 = Ptr2 = REAL_TO_PHYS(NULL);
379 while (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0)
380 {
381 Ptr1 = Ptr2;
382 Line = LineBuffer;
383
384 /* Print the address */
385 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, "%08x ", PHYS_TO_REAL(Ptr1));
386
387 /* Print up to 16 bytes... */
388
389 /* ... in hexadecimal form first... */
390 i = 0;
391 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0))
392 {
393 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, " %02x", *Ptr1);
394 ++Ptr1;
395 }
396
397 /* ... align with spaces if needed... */
398 RtlFillMemory(Line, 0x0F + 4 - i, ' ');
399 Line += 0x0F + 4 - i;
400
401 /* ... then in character form. */
402 i = 0;
403 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr2) > 0))
404 {
405 *Line++ = ((*Ptr2 >= 0x20 && *Ptr2 <= 0x7E) || (*Ptr2 >= 0x80 && *Ptr2 < 0xFF) ? *Ptr2 : '.');
406 ++Ptr2;
407 }
408
409 /* Newline */
410 *Line++ = '\r';
411 *Line++ = '\n';
412
413 /* Finally write the line to the file */
414 LineSize = Line - LineBuffer;
415 WriteFile(hFile, LineBuffer, LineSize, &LineSize, NULL);
416 }
417
418 /* Close the file */
419 CloseHandle(hFile);
420
421 DPRINT1("Memory dump done\n");
422 }
423
424 DWORD WINAPI PumpConsoleInput(LPVOID Parameter);
425
426 BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
427 {
428 /* Allocate memory for the 16-bit address space */
429 BaseAddress = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_ADDRESS);
430 if (BaseAddress == NULL)
431 {
432 wprintf(L"FATAL: Failed to allocate VDM memory.\n");
433 return FALSE;
434 }
435
436 /* Initialize I/O ports */
437 /* Initialize RAM */
438
439 /* Initialize the internal clock */
440 if (!ClockInitialize())
441 {
442 wprintf(L"FATAL: Failed to initialize the clock\n");
443 return FALSE;
444 }
445
446 /* Initialize the CPU */
447 Fast486Initialize(&EmulatorContext,
448 EmulatorReadMemory,
449 EmulatorWriteMemory,
450 EmulatorReadIo,
451 EmulatorWriteIo,
452 NULL,
453 EmulatorBiosOperation,
454 EmulatorIntAcknowledge,
455 NULL /* TODO: Use a TLB */);
456
457 /* Initialize DMA */
458
459 /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
460 PicInitialize();
461 PitInitialize();
462 CmosInitialize();
463 SpeakerInitialize();
464
465 /* Set output functions */
466 PitSetOutFunction(0, NULL, PitChan0Out);
467 PitSetOutFunction(1, NULL, PitChan1Out);
468 PitSetOutFunction(2, NULL, PitChan2Out);
469
470 /* Register the I/O Ports */
471 RegisterIoPort(CONTROL_SYSTEM_PORT61H, Port61hRead, Port61hWrite);
472
473 /* Set the console input mode */
474 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
475 // upon console window events (screen buffer resize, ...).
476 SetConsoleMode(ConsoleInput, ENABLE_PROCESSED_INPUT /* | ENABLE_WINDOW_INPUT */);
477 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
478
479 /* Initialize the PS2 port */
480 PS2Initialize(ConsoleInput);
481
482 /**************** ATTACH INPUT WITH CONSOLE *****************/
483 /* Start the input thread */
484 InputThread = CreateThread(NULL, 0, &PumpConsoleInput, ConsoleInput, 0, NULL);
485 if (InputThread == NULL)
486 {
487 DisplayMessage(L"Failed to create the console input thread.");
488 return FALSE;
489 }
490 /************************************************************/
491
492 /* Initialize the VGA */
493 if (!VgaInitialize(ConsoleOutput))
494 {
495 DisplayMessage(L"Failed to initialize VGA support.");
496 return FALSE;
497 }
498
499 /* Initialize the software callback system and register the emulator BOPs */
500 InitializeCallbacks();
501 RegisterBop(BOP_DEBUGGER , EmulatorDebugBreakBop);
502 RegisterBop(BOP_UNSIMULATE, EmulatorUnsimulateBop);
503
504 /* Initialize VDD support */
505 VDDSupInitialize();
506
507 return TRUE;
508 }
509
510 VOID EmulatorCleanup(VOID)
511 {
512 VgaCleanup();
513
514 /* Close the input thread handle */
515 if (InputThread != NULL) CloseHandle(InputThread);
516 InputThread = NULL;
517
518 PS2Cleanup();
519
520 SpeakerCleanup();
521 CmosCleanup();
522 // PitCleanup();
523 // PicCleanup();
524
525 // Fast486Cleanup();
526
527 /* Free the memory allocated for the 16-bit address space */
528 if (BaseAddress != NULL) HeapFree(GetProcessHeap(), 0, BaseAddress);
529 }
530
531
532
533 VOID
534 WINAPI
535 VDDSimulate16(VOID)
536 {
537 EmulatorSimulate();
538 }
539
540 VOID
541 WINAPI
542 VDDTerminateVDM(VOID)
543 {
544 /* Stop the VDM */
545 EmulatorTerminate();
546 }
547
548 PBYTE
549 WINAPI
550 Sim32pGetVDMPointer(IN ULONG Address,
551 IN BOOLEAN ProtectedMode)
552 {
553 // FIXME
554 UNREFERENCED_PARAMETER(ProtectedMode);
555
556 /*
557 * HIWORD(Address) == Segment (if ProtectedMode == FALSE)
558 * or Selector (if ProtectedMode == TRUE )
559 * LOWORD(Address) == Offset
560 */
561 return (PBYTE)FAR_POINTER(Address);
562 }
563
564 PBYTE
565 WINAPI
566 MGetVdmPointer(IN ULONG Address,
567 IN ULONG Size,
568 IN BOOLEAN ProtectedMode)
569 {
570 UNREFERENCED_PARAMETER(Size);
571 return Sim32pGetVDMPointer(Address, ProtectedMode);
572 }
573
574 PVOID
575 WINAPI
576 VdmMapFlat(IN USHORT Segment,
577 IN ULONG Offset,
578 IN VDM_MODE Mode)
579 {
580 // FIXME
581 UNREFERENCED_PARAMETER(Mode);
582
583 return SEG_OFF_TO_PTR(Segment, Offset);
584 }
585
586 BOOL
587 WINAPI
588 VdmFlushCache(IN USHORT Segment,
589 IN ULONG Offset,
590 IN ULONG Size,
591 IN VDM_MODE Mode)
592 {
593 // FIXME
594 UNIMPLEMENTED;
595 return TRUE;
596 }
597
598 BOOL
599 WINAPI
600 VdmUnmapFlat(IN USHORT Segment,
601 IN ULONG Offset,
602 IN PVOID Buffer,
603 IN VDM_MODE Mode)
604 {
605 // FIXME
606 UNIMPLEMENTED;
607 return TRUE;
608 }
609
610 /* EOF */