c328f16c83ec48de38aac873311c5cb363a3877c
[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 "bios.h"
15 #include "dos.h"
16 #include "pic.h"
17 #include "ps2.h"
18 #include "timer.h"
19
20 /* PRIVATE VARIABLES **********************************************************/
21
22 #ifndef NEW_EMULATOR
23 softx86_ctx EmulatorContext;
24 softx87_ctx FpuEmulatorContext;
25 #else
26 EMULATOR_CONTEXT EmulatorContext;
27 #endif
28
29 static BOOLEAN A20Line = FALSE;
30
31 /* PRIVATE FUNCTIONS **********************************************************/
32
33 #ifndef NEW_EMULATOR
34
35 static VOID EmulatorReadMemory(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
36 {
37 /* If the A20 line is disabled, mask bit 20 */
38 if (!A20Line) Address &= ~(1 << 20);
39
40 /* Make sure the requested address is valid */
41 if ((Address + Size) >= MAX_ADDRESS) return;
42
43 /* Are we reading some of the console video memory? */
44 if (((Address + Size) >= BiosGetVideoMemoryStart())
45 && (Address < CONSOLE_VIDEO_MEM_END))
46 {
47 /* Call the VDM BIOS to update the video memory */
48 BiosUpdateVideoMemory(max(Address, BiosGetVideoMemoryStart()),
49 min(Address + Size, CONSOLE_VIDEO_MEM_END));
50 }
51
52 /* Read the data from the virtual address space and store it in the buffer */
53 RtlCopyMemory(Buffer, (LPVOID)((ULONG_PTR)BaseAddress + Address), Size);
54 }
55
56 static VOID EmulatorWriteMemory(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
57 {
58 /* If the A20 line is disabled, mask bit 20 */
59 if (!A20Line) Address &= ~(1 << 20);
60
61 /* Make sure the requested address is valid */
62 if ((Address + Size) >= MAX_ADDRESS) return;
63
64 /* Make sure we don't write to the ROM area */
65 if ((Address + Size) >= ROM_AREA_START && (Address < ROM_AREA_END)) return;
66
67 /* Read the data from the buffer and store it in the virtual address space */
68 RtlCopyMemory((LPVOID)((ULONG_PTR)BaseAddress + Address), Buffer, Size);
69
70 /* Check if we modified the console video memory */
71 if (((Address + Size) >= BiosGetVideoMemoryStart())
72 && (Address < CONSOLE_VIDEO_MEM_END))
73 {
74 /* Call the VDM BIOS to update the screen */
75 BiosUpdateConsole(max(Address, BiosGetVideoMemoryStart()),
76 min(Address + Size, CONSOLE_VIDEO_MEM_END));
77 }
78 }
79
80 static VOID EmulatorReadIo(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
81 {
82 switch (Address)
83 {
84 case PIC_MASTER_CMD:
85 case PIC_SLAVE_CMD:
86 {
87 *Buffer = PicReadCommand(Address);
88 break;
89 }
90
91 case PIC_MASTER_DATA:
92 case PIC_SLAVE_DATA:
93 {
94 *Buffer = PicReadData(Address);
95 break;
96 }
97
98 case PIT_DATA_PORT(0):
99 case PIT_DATA_PORT(1):
100 case PIT_DATA_PORT(2):
101 {
102 *Buffer = PitReadData(Address - PIT_DATA_PORT(0));
103 break;
104 }
105
106 case PS2_CONTROL_PORT:
107 {
108 *Buffer = KeyboardReadStatus();
109 break;
110 }
111
112 case PS2_DATA_PORT:
113 {
114 *Buffer = KeyboardReadData();
115 break;
116 }
117 }
118 }
119
120 static VOID EmulatorWriteIo(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
121 {
122 BYTE Byte = *Buffer;
123
124 switch (Address)
125 {
126 case PIT_COMMAND_PORT:
127 {
128 PitWriteCommand(Byte);
129 break;
130 }
131
132 case PIT_DATA_PORT(0):
133 case PIT_DATA_PORT(1):
134 case PIT_DATA_PORT(2):
135 {
136 PitWriteData(Address - PIT_DATA_PORT(0), Byte);
137 break;
138 }
139
140 case PIC_MASTER_CMD:
141 case PIC_SLAVE_CMD:
142 {
143 PicWriteCommand(Address, Byte);
144 break;
145 }
146
147 case PIC_MASTER_DATA:
148 case PIC_SLAVE_DATA:
149 {
150 PicWriteData(Address, Byte);
151 break;
152 }
153
154 case PS2_CONTROL_PORT:
155 {
156 KeyboardWriteCommand(Byte);
157 break;
158 }
159
160 case PS2_DATA_PORT:
161 {
162 KeyboardWriteData(Byte);
163 break;
164 }
165 }
166 }
167
168 static VOID EmulatorSoftwareInt(PVOID Context, BYTE Number)
169 {
170 WORD StackSegment, StackPointer, CodeSegment, InstructionPointer;
171 BYTE IntNum;
172
173 /* Check if this is the special interrupt */
174 if (Number == SPECIAL_INT_NUM)
175 {
176 /* Get the SS:SP */
177 #ifndef NEW_EMULATOR
178 StackSegment = EmulatorContext.state->segment_reg[SX86_SREG_SS].val;
179 StackPointer = EmulatorContext.state->general_reg[SX86_REG_SP].val;
180 #else
181 StackSegment = EmulatorContext.Registers[EMULATOR_REG_SS].LowWord;
182 StackPointer = EmulatorContext.Registers[EMULATOR_REG_SP].LowWord;
183 #endif
184
185 /* Get the interrupt number */
186 IntNum = *(LPBYTE)((ULONG_PTR)BaseAddress + TO_LINEAR(StackSegment, StackPointer));
187
188 /* Move the stack pointer forward one word to skip the interrupt number */
189 StackPointer += sizeof(WORD);
190
191 /* Get the CS:IP */
192 InstructionPointer = *(LPWORD)((ULONG_PTR)BaseAddress
193 + TO_LINEAR(StackSegment, StackPointer));
194 CodeSegment = *(LPWORD)((ULONG_PTR)BaseAddress
195 + TO_LINEAR(StackSegment, StackPointer + sizeof(WORD)));
196
197 /* Check if this was an exception */
198 if (IntNum < 8)
199 {
200 /* Display a message to the user */
201 DisplayMessage(L"Exception: %s occured at %04X:%04X",
202 ExceptionName[IntNum],
203 CodeSegment,
204 InstructionPointer);
205
206 /* Stop the VDM */
207 VdmRunning = FALSE;
208 return;
209 }
210
211 /* Check if this was an PIC IRQ */
212 if (IntNum >= BIOS_PIC_MASTER_INT && IntNum < BIOS_PIC_MASTER_INT + 8)
213 {
214 /* It was an IRQ from the master PIC */
215 BiosHandleIrq(IntNum - BIOS_PIC_MASTER_INT);
216 return;
217 }
218 else if (IntNum >= BIOS_PIC_SLAVE_INT && IntNum < BIOS_PIC_SLAVE_INT + 8)
219 {
220 /* It was an IRQ from the slave PIC */
221 BiosHandleIrq(IntNum - BIOS_PIC_SLAVE_INT + 8);
222 return;
223 }
224
225 switch (IntNum)
226 {
227 case BIOS_VIDEO_INTERRUPT:
228 {
229 /* This is the video BIOS interrupt, call the BIOS */
230 BiosVideoService();
231 break;
232 }
233 case BIOS_EQUIPMENT_INTERRUPT:
234 {
235 /* This is the BIOS "get equipment" command, call the BIOS */
236 BiosEquipmentService();
237 break;
238 }
239 case BIOS_KBD_INTERRUPT:
240 {
241 /* This is the keyboard BIOS interrupt, call the BIOS */
242 BiosKeyboardService();
243 break;
244 }
245 case BIOS_TIME_INTERRUPT:
246 {
247 /* This is the time BIOS interrupt, call the BIOS */
248 BiosTimeService();
249 break;
250 }
251 case BIOS_SYS_TIMER_INTERRUPT:
252 {
253 /* BIOS timer update */
254 BiosSystemTimerInterrupt();
255 break;
256 }
257 case 0x20:
258 {
259 DosInt20h(CodeSegment);
260 break;
261 }
262 case 0x21:
263 {
264 DosInt21h(CodeSegment);
265 break;
266 }
267 case 0x23:
268 {
269 DosBreakInterrupt();
270 break;
271 }
272 default:
273 {
274 DPRINT1("Unhandled interrupt: 0x%02X\n", IntNum);
275 break;
276 }
277 }
278 }
279 }
280
281 static VOID EmulatorHardwareInt(PVOID Context, BYTE Number)
282 {
283 /* Do nothing */
284 }
285
286 static VOID EmulatorHardwareIntAck(PVOID Context, BYTE Number)
287 {
288 /* Do nothing */
289 }
290
291 #endif
292
293 /* PUBLIC FUNCTIONS ***********************************************************/
294
295 BOOLEAN EmulatorInitialize()
296 {
297 /* Allocate memory for the 16-bit address space */
298 BaseAddress = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_ADDRESS);
299 if (BaseAddress == NULL) return FALSE;
300
301 #ifndef NEW_EMULATOR
302 /* Initialize the softx86 CPU emulator */
303 if (!softx86_init(&EmulatorContext, SX86_CPULEVEL_80286))
304 {
305 HeapFree(GetProcessHeap(), 0, BaseAddress);
306 return FALSE;
307 }
308
309 /* Initialize the softx87 FPU emulator*/
310 if(!softx87_init(&FpuEmulatorContext, SX87_FPULEVEL_8087))
311 {
312 softx86_free(&EmulatorContext);
313 HeapFree(GetProcessHeap(), 0, BaseAddress);
314 return FALSE;
315 }
316
317 /* Set memory read/write callbacks */
318 EmulatorContext.callbacks->on_read_memory = EmulatorReadMemory;
319 EmulatorContext.callbacks->on_write_memory = EmulatorWriteMemory;
320
321 /* Set MMIO read/write callbacks */
322 EmulatorContext.callbacks->on_read_io = EmulatorReadIo;
323 EmulatorContext.callbacks->on_write_io = EmulatorWriteIo;
324
325 /* Set interrupt callbacks */
326 EmulatorContext.callbacks->on_sw_int = EmulatorSoftwareInt;
327 EmulatorContext.callbacks->on_hw_int = EmulatorHardwareInt;
328 EmulatorContext.callbacks->on_hw_int_ack = EmulatorHardwareIntAck;
329
330 /* Connect the emulated FPU to the emulated CPU */
331 softx87_connect_to_CPU(&EmulatorContext, &FpuEmulatorContext);
332 #else
333 // TODO: NOT IMPLEMENTED
334 #endif
335
336 /* Enable interrupts */
337 EmulatorSetFlag(EMULATOR_FLAG_IF);
338
339 return TRUE;
340 }
341
342 VOID EmulatorSetStack(WORD Segment, WORD Offset)
343 {
344 #ifndef NEW_EMULATOR
345 /* Call the softx86 API */
346 softx86_set_stack_ptr(&EmulatorContext, Segment, Offset);
347 #else
348 // TODO: NOT IMPLEMENTED
349 #endif
350 }
351
352 VOID EmulatorExecute(WORD Segment, WORD Offset)
353 {
354 #ifndef NEW_EMULATOR
355 /* Call the softx86 API */
356 softx86_set_instruction_ptr(&EmulatorContext, Segment, Offset);
357 #else
358 // TODO: NOT IMPLEMENTED
359 #endif
360 }
361
362 VOID EmulatorInterrupt(BYTE Number)
363 {
364 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
365 UINT Segment, Offset;
366
367 /* Get the segment and offset */
368 Segment = HIWORD(IntVecTable[Number]);
369 Offset = LOWORD(IntVecTable[Number]);
370
371 #ifndef NEW_EMULATOR
372 /* Call the softx86 API */
373 softx86_make_simple_interrupt_call(&EmulatorContext, &Segment, &Offset);
374 #else
375 UNREFERENCED_PARAMETER(Segment);
376 UNREFERENCED_PARAMETER(Offset);
377 // TODO: NOT IMPLEMENTED
378 #endif
379 }
380
381 VOID EmulatorExternalInterrupt(BYTE Number)
382 {
383 #ifndef NEW_EMULATOR
384 /* Call the softx86 API */
385 softx86_ext_hw_signal(&EmulatorContext, Number);
386 #endif
387 }
388
389 ULONG EmulatorGetRegister(ULONG Register)
390 {
391 #ifndef NEW_EMULATOR
392 if (Register < EMULATOR_REG_ES)
393 {
394 return EmulatorContext.state->general_reg[Register].val;
395 }
396 else
397 {
398 return EmulatorContext.state->segment_reg[Register - EMULATOR_REG_ES].val;
399 }
400 #else
401 return EmulatorContext.Registers[Register].Long;
402 #endif
403 }
404
405 VOID EmulatorSetRegister(ULONG Register, ULONG Value)
406 {
407 #ifndef NEW_EMULATOR
408 if (Register < EMULATOR_REG_CS)
409 {
410 EmulatorContext.state->general_reg[Register].val = Value;
411 }
412 else
413 {
414 EmulatorContext.state->segment_reg[Register - EMULATOR_REG_ES].val = Value;
415 }
416 #else
417 // TODO: NOT IMPLEMENTED
418 #endif
419 }
420
421 BOOLEAN EmulatorGetFlag(ULONG Flag)
422 {
423 #ifndef NEW_EMULATOR
424 return (EmulatorContext.state->reg_flags.val & Flag) ? TRUE : FALSE;
425 #else
426 return (EmulatorContext.Flags.Long & Flag) ? TRUE : FALSE;
427 #endif
428 }
429
430 VOID EmulatorSetFlag(ULONG Flag)
431 {
432 #ifndef NEW_EMULATOR
433 EmulatorContext.state->reg_flags.val |= Flag;
434 #else
435 EmulatorContext.Flags.Long |= Flag;
436 #endif
437 }
438
439 VOID EmulatorClearFlag(ULONG Flag)
440 {
441 #ifndef NEW_EMULATOR
442 EmulatorContext.state->reg_flags.val &= ~Flag;
443 #else
444 EmulatorContext.Flags.Long &= ~Flag;
445 #endif
446 }
447
448 VOID EmulatorStep()
449 {
450 #ifndef NEW_EMULATOR
451 /* Print the current position - useful for debugging */
452 DPRINT("Executing at CS:IP = %04X:%04X\n",
453 EmulatorGetRegister(EMULATOR_REG_CS),
454 EmulatorContext.state->reg_ip);
455
456 /* Call the softx86 API */
457 if (!softx86_step(&EmulatorContext))
458 {
459 /* Invalid opcode */
460 EmulatorInterrupt(EMULATOR_EXCEPTION_INVALID_OPCODE);
461 }
462 #else
463 // TODO: NOT IMPLEMENTED
464 #endif
465 }
466
467 VOID EmulatorCleanup()
468 {
469 /* Free the memory allocated for the 16-bit address space */
470 if (BaseAddress != NULL) HeapFree(GetProcessHeap(), 0, BaseAddress);
471
472 #ifndef NEW_EMULATOR
473 /* Free the softx86 CPU and FPU emulator */
474 softx86_free(&EmulatorContext);
475 softx87_free(&FpuEmulatorContext);
476 #endif
477 }
478
479 VOID EmulatorSetA20(BOOLEAN Enabled)
480 {
481 A20Line = Enabled;
482 }
483
484 /* EOF */