[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 VdmRunning = FALSE;
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 VdmRunning = FALSE;
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 EmulatorInterrupt(BYTE Number)
223 {
224 /* Call the Fast486 API */
225 Fast486Interrupt(&EmulatorContext, Number);
226 }
227
228 VOID EmulatorInterruptSignal(VOID)
229 {
230 /* Call the Fast486 API */
231 Fast486InterruptSignal(&EmulatorContext);
232 }
233
234 VOID EmulatorSetA20(BOOLEAN Enabled)
235 {
236 A20Line = Enabled;
237 }
238
239 VOID WINAPI EmulatorDebugBreakBop(LPWORD Stack)
240 {
241 DPRINT1("NTVDM: BOP_DEBUGGER\n");
242 DebugBreak();
243 }
244
245 VOID WINAPI EmulatorUnsimulateBop(LPWORD Stack)
246 {
247 EmulatorUnsimulate();
248 }
249
250 static BYTE WINAPI Port61hRead(ULONG Port)
251 {
252 return Port61hState;
253 }
254
255 static VOID WINAPI Port61hWrite(ULONG Port, BYTE Data)
256 {
257 // BOOLEAN SpeakerChange = FALSE;
258 BYTE OldPort61hState = Port61hState;
259
260 /* Only the four lowest bytes can be written */
261 Port61hState = (Port61hState & 0xF0) | (Data & 0x0F);
262
263 if ((OldPort61hState ^ Port61hState) & 0x01)
264 {
265 DPRINT("PIT 2 Gate %s\n", Port61hState & 0x01 ? "on" : "off");
266 // SpeakerChange = TRUE;
267 }
268
269 PitSetGate(2, !!(Port61hState & 0x01));
270
271 if ((OldPort61hState ^ Port61hState) & 0x02)
272 {
273 /* There were some change for the speaker... */
274 DPRINT("Speaker %s\n", Port61hState & 0x02 ? "on" : "off");
275 // SpeakerChange = TRUE;
276 }
277 // if (SpeakerChange) SpeakerChange();
278 SpeakerChange();
279 }
280
281 static VOID WINAPI PitChan0Out(LPVOID Param, BOOLEAN State)
282 {
283 if (State)
284 {
285 DPRINT("PicInterruptRequest\n");
286 PicInterruptRequest(0); // Raise IRQ 0
287 }
288 // else < Lower IRQ 0 >
289 }
290
291 static VOID WINAPI PitChan1Out(LPVOID Param, BOOLEAN State)
292 {
293 #if 0
294 if (State)
295 {
296 /* Set bit 4 of Port 61h */
297 Port61hState |= 1 << 4;
298 }
299 else
300 {
301 /* Clear bit 4 of Port 61h */
302 Port61hState &= ~(1 << 4);
303 }
304 #else
305 Port61hState = (Port61hState & 0xEF) | (State << 4);
306 #endif
307 }
308
309 static VOID WINAPI PitChan2Out(LPVOID Param, BOOLEAN State)
310 {
311 // BYTE OldPort61hState = Port61hState;
312
313 #if 0
314 if (State)
315 {
316 /* Set bit 5 of Port 61h */
317 Port61hState |= 1 << 5;
318 }
319 else
320 {
321 /* Clear bit 5 of Port 61h */
322 Port61hState &= ~(1 << 5);
323 }
324 #else
325 Port61hState = (Port61hState & 0xDF) | (State << 5);
326 #endif
327 DPRINT("Speaker PIT out\n");
328 // if ((OldPort61hState ^ Port61hState) & 0x20)
329 // SpeakerChange();
330 }
331
332 /* PUBLIC FUNCTIONS ***********************************************************/
333
334 DWORD WINAPI PumpConsoleInput(LPVOID Parameter);
335
336 BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
337 {
338 /* Allocate memory for the 16-bit address space */
339 BaseAddress = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_ADDRESS);
340 if (BaseAddress == NULL)
341 {
342 wprintf(L"FATAL: Failed to allocate VDM memory.\n");
343 return FALSE;
344 }
345
346 /* Initialize I/O ports */
347 /* Initialize RAM */
348
349 /* Initialize the internal clock */
350 if (!ClockInitialize())
351 {
352 wprintf(L"FATAL: Failed to initialize the clock\n");
353 return FALSE;
354 }
355
356 /* Initialize the CPU */
357 Fast486Initialize(&EmulatorContext,
358 EmulatorReadMemory,
359 EmulatorWriteMemory,
360 EmulatorReadIo,
361 EmulatorWriteIo,
362 NULL,
363 EmulatorBiosOperation,
364 EmulatorIntAcknowledge,
365 NULL /* TODO: Use a TLB */);
366
367 /* Initialize DMA */
368
369 /* Initialize the PIC, the PIT, the CMOS and the PC Speaker */
370 PicInitialize();
371 PitInitialize();
372 CmosInitialize();
373 SpeakerInitialize();
374
375 /* Set output functions */
376 PitSetOutFunction(0, NULL, PitChan0Out);
377 PitSetOutFunction(1, NULL, PitChan1Out);
378 PitSetOutFunction(2, NULL, PitChan2Out);
379
380 /* Register the I/O Ports */
381 RegisterIoPort(CONTROL_SYSTEM_PORT61H, Port61hRead, Port61hWrite);
382
383 /* Initialize the PS2 port */
384 PS2Initialize(ConsoleInput);
385
386 /* Set the console input mode */
387 // SetConsoleMode(ConsoleInput, ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT);
388
389 /* Start the input thread */
390 InputThread = CreateThread(NULL, 0, &PumpConsoleInput, ConsoleInput, 0, NULL);
391 // if (InputThread == NULL) return FALSE;
392
393 /* Initialize the VGA */
394 // if (!VgaInitialize(ConsoleOutput)) return FALSE;
395 VgaInitialize(ConsoleOutput);
396
397 /* Initialize the software callback system and register the emulator BOPs */
398 InitializeCallbacks();
399 RegisterBop(BOP_DEBUGGER , EmulatorDebugBreakBop);
400 RegisterBop(BOP_UNSIMULATE, EmulatorUnsimulateBop);
401
402 /* Initialize VDD support */
403 VDDSupInitialize();
404
405 return TRUE;
406 }
407
408 VOID EmulatorCleanup(VOID)
409 {
410 // VgaCleanup();
411
412 /* Close the input thread handle */
413 if (InputThread != NULL) CloseHandle(InputThread);
414 InputThread = NULL;
415
416 PS2Cleanup();
417
418 SpeakerCleanup();
419 CmosCleanup();
420 // PitCleanup();
421 // PicCleanup();
422
423 // Fast486Cleanup();
424
425 /* Free the memory allocated for the 16-bit address space */
426 if (BaseAddress != NULL) HeapFree(GetProcessHeap(), 0, BaseAddress);
427 }
428
429
430
431 VOID
432 WINAPI
433 VDDSimulate16(VOID)
434 {
435 EmulatorSimulate();
436 }
437
438 VOID
439 WINAPI
440 VDDTerminateVDM(VOID)
441 {
442 /* Stop the VDM */
443 VdmRunning = FALSE;
444 }
445
446 PBYTE
447 WINAPI
448 Sim32pGetVDMPointer(IN ULONG Address,
449 IN BOOLEAN ProtectedMode)
450 {
451 // FIXME
452 UNREFERENCED_PARAMETER(ProtectedMode);
453
454 /*
455 * HIWORD(Address) == Segment (if ProtectedMode == FALSE)
456 * or Selector (if ProtectedMode == TRUE )
457 * LOWORD(Address) == Offset
458 */
459 return (PBYTE)FAR_POINTER(Address);
460 }
461
462 PBYTE
463 WINAPI
464 MGetVdmPointer(IN ULONG Address,
465 IN ULONG Size,
466 IN BOOLEAN ProtectedMode)
467 {
468 UNREFERENCED_PARAMETER(Size);
469 return Sim32pGetVDMPointer(Address, ProtectedMode);
470 }
471
472 PVOID
473 WINAPI
474 VdmMapFlat(IN USHORT Segment,
475 IN ULONG Offset,
476 IN VDM_MODE Mode)
477 {
478 // FIXME
479 UNREFERENCED_PARAMETER(Mode);
480
481 return SEG_OFF_TO_PTR(Segment, Offset);
482 }
483
484 BOOL
485 WINAPI
486 VdmFlushCache(IN USHORT Segment,
487 IN ULONG Offset,
488 IN ULONG Size,
489 IN VDM_MODE Mode)
490 {
491 // FIXME
492 UNIMPLEMENTED;
493 return TRUE;
494 }
495
496 BOOL
497 WINAPI
498 VdmUnmapFlat(IN USHORT Segment,
499 IN ULONG Offset,
500 IN PVOID Buffer,
501 IN VDM_MODE Mode)
502 {
503 // FIXME
504 UNIMPLEMENTED;
505 return TRUE;
506 }
507
508 /* EOF */