[NTVDM]
[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 const static 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 DWORD WINAPI PumpConsoleInput(LPVOID Parameter);
341
342 BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
343 {
344 /* Allocate memory for the 16-bit address space */
345 BaseAddress = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_ADDRESS);
346 if (BaseAddress == NULL)
347 {
348 wprintf(L"FATAL: Failed to allocate VDM memory.\n");
349 return FALSE;
350 }
351
352 /* Initialize I/O ports */
353 /* Initialize RAM */
354
355 /* Initialize the internal clock */
356 if (!ClockInitialize())
357 {
358 wprintf(L"FATAL: Failed to initialize the clock\n");
359 return FALSE;
360 }
361
362 /* Initialize the CPU */
363 Fast486Initialize(&EmulatorContext,
364 EmulatorReadMemory,
365 EmulatorWriteMemory,
366 EmulatorReadIo,
367 EmulatorWriteIo,
368 NULL,
369 EmulatorBiosOperation,
370 EmulatorIntAcknowledge,
371 NULL /* TODO: Use a TLB */);
372
373 /* Initialize DMA */
374
375 /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
376 PicInitialize();
377 PitInitialize();
378 CmosInitialize();
379 SpeakerInitialize();
380
381 /* Set output functions */
382 PitSetOutFunction(0, NULL, PitChan0Out);
383 PitSetOutFunction(1, NULL, PitChan1Out);
384 PitSetOutFunction(2, NULL, PitChan2Out);
385
386 /* Register the I/O Ports */
387 RegisterIoPort(CONTROL_SYSTEM_PORT61H, Port61hRead, Port61hWrite);
388
389 /* Set the console input mode */
390 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
391 // upon console window events (screen buffer resize, ...).
392 SetConsoleMode(ConsoleInput, ENABLE_PROCESSED_INPUT /* | ENABLE_WINDOW_INPUT */);
393 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
394
395 /* Initialize the PS2 port */
396 PS2Initialize(ConsoleInput);
397
398 /* Start the input thread */
399 InputThread = CreateThread(NULL, 0, &PumpConsoleInput, ConsoleInput, 0, NULL);
400 // if (InputThread == NULL) return FALSE;
401
402 /* Initialize the VGA */
403 // if (!VgaInitialize(ConsoleOutput)) return FALSE;
404 VgaInitialize(ConsoleOutput);
405
406 /* Initialize the software callback system and register the emulator BOPs */
407 InitializeCallbacks();
408 RegisterBop(BOP_DEBUGGER , EmulatorDebugBreakBop);
409 RegisterBop(BOP_UNSIMULATE, EmulatorUnsimulateBop);
410
411 /* Initialize VDD support */
412 VDDSupInitialize();
413
414 return TRUE;
415 }
416
417 VOID EmulatorCleanup(VOID)
418 {
419 VgaCleanup();
420
421 /* Close the input thread handle */
422 if (InputThread != NULL) CloseHandle(InputThread);
423 InputThread = NULL;
424
425 PS2Cleanup();
426
427 SpeakerCleanup();
428 CmosCleanup();
429 // PitCleanup();
430 // PicCleanup();
431
432 // Fast486Cleanup();
433
434 /* Free the memory allocated for the 16-bit address space */
435 if (BaseAddress != NULL) HeapFree(GetProcessHeap(), 0, BaseAddress);
436 }
437
438
439
440 VOID
441 WINAPI
442 VDDSimulate16(VOID)
443 {
444 EmulatorSimulate();
445 }
446
447 VOID
448 WINAPI
449 VDDTerminateVDM(VOID)
450 {
451 /* Stop the VDM */
452 EmulatorTerminate();
453 }
454
455 PBYTE
456 WINAPI
457 Sim32pGetVDMPointer(IN ULONG Address,
458 IN BOOLEAN ProtectedMode)
459 {
460 // FIXME
461 UNREFERENCED_PARAMETER(ProtectedMode);
462
463 /*
464 * HIWORD(Address) == Segment (if ProtectedMode == FALSE)
465 * or Selector (if ProtectedMode == TRUE )
466 * LOWORD(Address) == Offset
467 */
468 return (PBYTE)FAR_POINTER(Address);
469 }
470
471 PBYTE
472 WINAPI
473 MGetVdmPointer(IN ULONG Address,
474 IN ULONG Size,
475 IN BOOLEAN ProtectedMode)
476 {
477 UNREFERENCED_PARAMETER(Size);
478 return Sim32pGetVDMPointer(Address, ProtectedMode);
479 }
480
481 PVOID
482 WINAPI
483 VdmMapFlat(IN USHORT Segment,
484 IN ULONG Offset,
485 IN VDM_MODE Mode)
486 {
487 // FIXME
488 UNREFERENCED_PARAMETER(Mode);
489
490 return SEG_OFF_TO_PTR(Segment, Offset);
491 }
492
493 BOOL
494 WINAPI
495 VdmFlushCache(IN USHORT Segment,
496 IN ULONG Offset,
497 IN ULONG Size,
498 IN VDM_MODE Mode)
499 {
500 // FIXME
501 UNIMPLEMENTED;
502 return TRUE;
503 }
504
505 BOOL
506 WINAPI
507 VdmUnmapFlat(IN USHORT Segment,
508 IN ULONG Offset,
509 IN PVOID Buffer,
510 IN VDM_MODE Mode)
511 {
512 // FIXME
513 UNIMPLEMENTED;
514 return TRUE;
515 }
516
517 /* EOF */