6a2a0ca27ad783a2f414afcfcd407703509fa8bc
[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 EmulatorBop(WORD Code)
169 {
170 WORD StackSegment, StackPointer, CodeSegment, InstructionPointer;
171 BYTE IntNum;
172 LPWORD Stack;
173
174 /* Get the SS:SP */
175 #ifndef NEW_EMULATOR
176 StackSegment = EmulatorContext.state->segment_reg[SX86_SREG_SS].val;
177 StackPointer = EmulatorContext.state->general_reg[SX86_REG_SP].val;
178 #else
179 StackSegment = EmulatorContext.Registers[EMULATOR_REG_SS].LowWord;
180 StackPointer = EmulatorContext.Registers[EMULATOR_REG_SP].LowWord;
181 #endif
182
183 /* Get the stack */
184 Stack = (LPWORD)((ULONG_PTR)BaseAddress + TO_LINEAR(StackSegment, StackPointer));
185
186 if (Code == EMULATOR_INT_BOP)
187 {
188 /* Get the interrupt number */
189 IntNum = LOBYTE(Stack[0]);
190
191 /* Get the CS:IP */
192 InstructionPointer = Stack[1];
193 CodeSegment = Stack[2];
194
195 /* Check if this was an exception */
196 if (IntNum < 8)
197 {
198 /* Display a message to the user */
199 DisplayMessage(L"Exception: %s occured at %04X:%04X",
200 ExceptionName[IntNum],
201 CodeSegment,
202 InstructionPointer);
203
204 /* Stop the VDM */
205 VdmRunning = FALSE;
206 return;
207 }
208
209 /* Check if this was an PIC IRQ */
210 if (IntNum >= BIOS_PIC_MASTER_INT && IntNum < BIOS_PIC_MASTER_INT + 8)
211 {
212 /* It was an IRQ from the master PIC */
213 BiosHandleIrq(IntNum - BIOS_PIC_MASTER_INT);
214 return;
215 }
216 else if (IntNum >= BIOS_PIC_SLAVE_INT && IntNum < BIOS_PIC_SLAVE_INT + 8)
217 {
218 /* It was an IRQ from the slave PIC */
219 BiosHandleIrq(IntNum - BIOS_PIC_SLAVE_INT + 8);
220 return;
221 }
222
223 switch (IntNum)
224 {
225 case BIOS_VIDEO_INTERRUPT:
226 {
227 /* This is the video BIOS interrupt, call the BIOS */
228 BiosVideoService();
229 break;
230 }
231 case BIOS_EQUIPMENT_INTERRUPT:
232 {
233 /* This is the BIOS "get equipment" command, call the BIOS */
234 BiosEquipmentService();
235 break;
236 }
237 case BIOS_KBD_INTERRUPT:
238 {
239 /* This is the keyboard BIOS interrupt, call the BIOS */
240 BiosKeyboardService();
241 break;
242 }
243 case BIOS_TIME_INTERRUPT:
244 {
245 /* This is the time BIOS interrupt, call the BIOS */
246 BiosTimeService();
247 break;
248 }
249 case BIOS_SYS_TIMER_INTERRUPT:
250 {
251 /* BIOS timer update */
252 BiosSystemTimerInterrupt();
253 break;
254 }
255 case 0x20:
256 {
257 DosInt20h(CodeSegment);
258 break;
259 }
260 case 0x21:
261 {
262 DosInt21h(CodeSegment);
263 break;
264 }
265 case 0x23:
266 {
267 DosBreakInterrupt();
268 break;
269 }
270 default:
271 {
272 DPRINT1("Unhandled interrupt: 0x%02X\n", IntNum);
273 break;
274 }
275 }
276
277 /* Update the flags on the stack */
278 #ifndef NEW_EMULATOR
279 Stack[3] = EmulatorContext.state->reg_flags.val;
280 #else
281 Stack[3] = EmulatorContext.Flags.LowWord;
282 #endif
283 }
284 }
285
286 static VOID EmulatorSoftwareInt(PVOID Context, BYTE Number)
287 {
288 /* Do nothing */
289 }
290
291 static VOID EmulatorHardwareInt(PVOID Context, BYTE Number)
292 {
293 /* Do nothing */
294 }
295
296 static VOID EmulatorHardwareIntAck(PVOID Context, BYTE Number)
297 {
298 /* Do nothing */
299 }
300
301 #endif
302
303 /* PUBLIC FUNCTIONS ***********************************************************/
304
305 BOOLEAN EmulatorInitialize()
306 {
307 /* Allocate memory for the 16-bit address space */
308 BaseAddress = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_ADDRESS);
309 if (BaseAddress == NULL) return FALSE;
310
311 #ifndef NEW_EMULATOR
312 /* Initialize the softx86 CPU emulator */
313 if (!softx86_init(&EmulatorContext, SX86_CPULEVEL_80286))
314 {
315 HeapFree(GetProcessHeap(), 0, BaseAddress);
316 return FALSE;
317 }
318
319 /* Initialize the softx87 FPU emulator*/
320 if(!softx87_init(&FpuEmulatorContext, SX87_FPULEVEL_8087))
321 {
322 softx86_free(&EmulatorContext);
323 HeapFree(GetProcessHeap(), 0, BaseAddress);
324 return FALSE;
325 }
326
327 /* Set memory read/write callbacks */
328 EmulatorContext.callbacks->on_read_memory = EmulatorReadMemory;
329 EmulatorContext.callbacks->on_write_memory = EmulatorWriteMemory;
330
331 /* Set MMIO read/write callbacks */
332 EmulatorContext.callbacks->on_read_io = EmulatorReadIo;
333 EmulatorContext.callbacks->on_write_io = EmulatorWriteIo;
334
335 /* Set interrupt callbacks */
336 EmulatorContext.callbacks->on_sw_int = EmulatorSoftwareInt;
337 EmulatorContext.callbacks->on_hw_int = EmulatorHardwareInt;
338 EmulatorContext.callbacks->on_hw_int_ack = EmulatorHardwareIntAck;
339
340 /* Connect the emulated FPU to the emulated CPU */
341 softx87_connect_to_CPU(&EmulatorContext, &FpuEmulatorContext);
342 #else
343 // TODO: NOT IMPLEMENTED
344 #endif
345
346 /* Enable interrupts */
347 EmulatorSetFlag(EMULATOR_FLAG_IF);
348
349 return TRUE;
350 }
351
352 VOID EmulatorSetStack(WORD Segment, WORD Offset)
353 {
354 #ifndef NEW_EMULATOR
355 /* Call the softx86 API */
356 softx86_set_stack_ptr(&EmulatorContext, Segment, Offset);
357 #else
358 // TODO: NOT IMPLEMENTED
359 #endif
360 }
361
362 VOID EmulatorExecute(WORD Segment, WORD Offset)
363 {
364 #ifndef NEW_EMULATOR
365 /* Call the softx86 API */
366 softx86_set_instruction_ptr(&EmulatorContext, Segment, Offset);
367 #else
368 // TODO: NOT IMPLEMENTED
369 #endif
370 }
371
372 VOID EmulatorInterrupt(BYTE Number)
373 {
374 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
375 UINT Segment, Offset;
376
377 /* Get the segment and offset */
378 Segment = HIWORD(IntVecTable[Number]);
379 Offset = LOWORD(IntVecTable[Number]);
380
381 #ifndef NEW_EMULATOR
382 /* Call the softx86 API */
383 softx86_make_simple_interrupt_call(&EmulatorContext, &Segment, &Offset);
384 #else
385 UNREFERENCED_PARAMETER(Segment);
386 UNREFERENCED_PARAMETER(Offset);
387 // TODO: NOT IMPLEMENTED
388 #endif
389 }
390
391 VOID EmulatorExternalInterrupt(BYTE Number)
392 {
393 #ifndef NEW_EMULATOR
394 /* Call the softx86 API */
395 softx86_ext_hw_signal(&EmulatorContext, Number);
396 #endif
397 }
398
399 ULONG EmulatorGetRegister(ULONG Register)
400 {
401 #ifndef NEW_EMULATOR
402 if (Register < EMULATOR_REG_ES)
403 {
404 return EmulatorContext.state->general_reg[Register].val;
405 }
406 else
407 {
408 return EmulatorContext.state->segment_reg[Register - EMULATOR_REG_ES].val;
409 }
410 #else
411 return EmulatorContext.Registers[Register].Long;
412 #endif
413 }
414
415 VOID EmulatorSetRegister(ULONG Register, ULONG Value)
416 {
417 #ifndef NEW_EMULATOR
418 if (Register < EMULATOR_REG_CS)
419 {
420 EmulatorContext.state->general_reg[Register].val = Value;
421 }
422 else
423 {
424 EmulatorContext.state->segment_reg[Register - EMULATOR_REG_ES].val = Value;
425 }
426 #else
427 // TODO: NOT IMPLEMENTED
428 #endif
429 }
430
431 BOOLEAN EmulatorGetFlag(ULONG Flag)
432 {
433 #ifndef NEW_EMULATOR
434 return (EmulatorContext.state->reg_flags.val & Flag) ? TRUE : FALSE;
435 #else
436 return (EmulatorContext.Flags.Long & Flag) ? TRUE : FALSE;
437 #endif
438 }
439
440 VOID EmulatorSetFlag(ULONG Flag)
441 {
442 #ifndef NEW_EMULATOR
443 EmulatorContext.state->reg_flags.val |= Flag;
444 #else
445 EmulatorContext.Flags.Long |= Flag;
446 #endif
447 }
448
449 VOID EmulatorClearFlag(ULONG Flag)
450 {
451 #ifndef NEW_EMULATOR
452 EmulatorContext.state->reg_flags.val &= ~Flag;
453 #else
454 EmulatorContext.Flags.Long &= ~Flag;
455 #endif
456 }
457
458 VOID EmulatorStep(VOID)
459 {
460 LPWORD Instruction;
461
462 #ifndef NEW_EMULATOR
463 /* Print the current position - useful for debugging */
464 DPRINT("Executing at CS:IP = %04X:%04X\n",
465 EmulatorGetRegister(EMULATOR_REG_CS),
466 EmulatorContext.state->reg_ip);
467
468 Instruction = (LPWORD)((ULONG_PTR)BaseAddress
469 + TO_LINEAR(EmulatorGetRegister(EMULATOR_REG_CS),
470 EmulatorContext.state->reg_ip));
471
472 /* Check for the BIOS operation (BOP) sequence */
473 if (Instruction[0] == EMULATOR_BOP)
474 {
475 /* Skip the opcodes */
476 EmulatorContext.state->reg_ip += 4;
477
478 /* Call the BOP handler */
479 EmulatorBop(Instruction[1]);
480 }
481
482 /* Call the softx86 API */
483 if (!softx86_step(&EmulatorContext))
484 {
485 /* Invalid opcode */
486 EmulatorInterrupt(EMULATOR_EXCEPTION_INVALID_OPCODE);
487 }
488 #else
489 // TODO: NOT IMPLEMENTED
490 #endif
491 }
492
493 VOID EmulatorCleanup(VOID)
494 {
495 /* Free the memory allocated for the 16-bit address space */
496 if (BaseAddress != NULL) HeapFree(GetProcessHeap(), 0, BaseAddress);
497
498 #ifndef NEW_EMULATOR
499 /* Free the softx86 CPU and FPU emulator */
500 softx86_free(&EmulatorContext);
501 softx87_free(&FpuEmulatorContext);
502 #endif
503 }
504
505 VOID EmulatorSetA20(BOOLEAN Enabled)
506 {
507 A20Line = Enabled;
508 }
509
510 /* EOF */