a9814e1503888a7cc6676880529cc9ace3d39d08
[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 "vga.h"
17 #include "pic.h"
18 #include "ps2.h"
19 #include "timer.h"
20
21 /* PRIVATE VARIABLES **********************************************************/
22
23 #ifndef NEW_EMULATOR
24 softx86_ctx EmulatorContext;
25 softx87_ctx FpuEmulatorContext;
26 #else
27 SOFT386_STATE EmulatorContext;
28 #endif
29
30 static BOOLEAN A20Line = FALSE;
31
32 /* PRIVATE FUNCTIONS **********************************************************/
33
34 static VOID NTVDMCALL EmulatorReadMemory(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
35 {
36 UNREFERENCED_PARAMETER(Context);
37
38 /* If the A20 line is disabled, mask bit 20 */
39 if (!A20Line) Address &= ~(1 << 20);
40
41 /* Make sure the requested address is valid */
42 if ((Address + Size) >= MAX_ADDRESS) return;
43
44 /* Read the data from the virtual address space and store it in the buffer */
45 RtlCopyMemory(Buffer, (LPVOID)((ULONG_PTR)BaseAddress + Address), Size);
46
47 /* Check if we modified the console video memory */
48 if (((Address + Size) >= VgaGetVideoBaseAddress())
49 && (Address < VgaGetVideoLimitAddress()))
50 {
51 DWORD VgaAddress = max(Address, VgaGetVideoBaseAddress());
52 LPBYTE VgaBuffer = &Buffer[VgaAddress - Address];
53
54 /* Read from the VGA memory */
55 VgaReadMemory(VgaAddress, VgaBuffer, Size);
56 }
57 }
58
59 static VOID NTVDMCALL EmulatorWriteMemory(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
60 {
61 UNREFERENCED_PARAMETER(Context);
62
63 /* If the A20 line is disabled, mask bit 20 */
64 if (!A20Line) Address &= ~(1 << 20);
65
66 /* Make sure the requested address is valid */
67 if ((Address + Size) >= MAX_ADDRESS) return;
68
69 /* Make sure we don't write to the ROM area */
70 if ((Address + Size) >= ROM_AREA_START && (Address < ROM_AREA_END)) return;
71
72 /* Read the data from the buffer and store it in the virtual address space */
73 RtlCopyMemory((LPVOID)((ULONG_PTR)BaseAddress + Address), Buffer, Size);
74
75 /* Check if we modified the console video memory */
76 if (((Address + Size) >= VgaGetVideoBaseAddress())
77 && (Address < VgaGetVideoLimitAddress()))
78 {
79 DWORD VgaAddress = max(Address, VgaGetVideoBaseAddress());
80 LPBYTE VgaBuffer = &Buffer[VgaAddress - Address];
81
82 /* Write to the VGA memory */
83 VgaWriteMemory(VgaAddress, VgaBuffer, Size);
84 }
85 }
86
87 static VOID NTVDMCALL EmulatorReadIo(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
88 {
89 UNREFERENCED_PARAMETER(Context);
90 UNREFERENCED_PARAMETER(Size);
91
92 switch (Address)
93 {
94 case PIC_MASTER_CMD:
95 case PIC_SLAVE_CMD:
96 {
97 *Buffer = PicReadCommand(Address);
98 break;
99 }
100
101 case PIC_MASTER_DATA:
102 case PIC_SLAVE_DATA:
103 {
104 *Buffer = PicReadData(Address);
105 break;
106 }
107
108 case PIT_DATA_PORT(0):
109 case PIT_DATA_PORT(1):
110 case PIT_DATA_PORT(2):
111 {
112 *Buffer = PitReadData(Address - PIT_DATA_PORT(0));
113 break;
114 }
115
116 case PS2_CONTROL_PORT:
117 {
118 *Buffer = KeyboardReadStatus();
119 break;
120 }
121
122 case PS2_DATA_PORT:
123 {
124 *Buffer = KeyboardReadData();
125 break;
126 }
127
128 case VGA_AC_WRITE:
129 case VGA_AC_READ:
130 case VGA_SEQ_INDEX:
131 case VGA_SEQ_DATA:
132 case VGA_DAC_READ_INDEX:
133 case VGA_DAC_WRITE_INDEX:
134 case VGA_DAC_DATA:
135 case VGA_MISC_READ:
136 case VGA_MISC_WRITE:
137 case VGA_CRTC_INDEX:
138 case VGA_CRTC_DATA:
139 case VGA_GC_INDEX:
140 case VGA_GC_DATA:
141 case VGA_STAT_MONO:
142 case VGA_STAT_COLOR:
143 {
144 *Buffer = VgaReadPort(Address);
145 break;
146 }
147
148 default:
149 {
150 DPRINT1("Read from unknown port: 0x%X\n", Address);
151 }
152 }
153 }
154
155 static VOID NTVDMCALL EmulatorWriteIo(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
156 {
157 BYTE Byte = *Buffer;
158
159 UNREFERENCED_PARAMETER(Context);
160 UNREFERENCED_PARAMETER(Size);
161
162 switch (Address)
163 {
164 case PIT_COMMAND_PORT:
165 {
166 PitWriteCommand(Byte);
167 break;
168 }
169
170 case PIT_DATA_PORT(0):
171 case PIT_DATA_PORT(1):
172 case PIT_DATA_PORT(2):
173 {
174 PitWriteData(Address - PIT_DATA_PORT(0), Byte);
175 break;
176 }
177
178 case PIC_MASTER_CMD:
179 case PIC_SLAVE_CMD:
180 {
181 PicWriteCommand(Address, Byte);
182 break;
183 }
184
185 case PIC_MASTER_DATA:
186 case PIC_SLAVE_DATA:
187 {
188 PicWriteData(Address, Byte);
189 break;
190 }
191
192 case PS2_CONTROL_PORT:
193 {
194 KeyboardWriteCommand(Byte);
195 break;
196 }
197
198 case PS2_DATA_PORT:
199 {
200 KeyboardWriteData(Byte);
201 break;
202 }
203
204 case VGA_AC_WRITE:
205 case VGA_AC_READ:
206 case VGA_SEQ_INDEX:
207 case VGA_SEQ_DATA:
208 case VGA_DAC_READ_INDEX:
209 case VGA_DAC_WRITE_INDEX:
210 case VGA_DAC_DATA:
211 case VGA_MISC_READ:
212 case VGA_MISC_WRITE:
213 case VGA_CRTC_INDEX:
214 case VGA_CRTC_DATA:
215 case VGA_GC_INDEX:
216 case VGA_GC_DATA:
217 case VGA_STAT_MONO:
218 case VGA_STAT_COLOR:
219 {
220 VgaWritePort(Address, Byte);
221 break;
222 }
223
224 default:
225 {
226 DPRINT1("Write to unknown port: 0x%X\n", Address);
227 }
228 }
229 }
230
231 static VOID EmulatorBop(WORD Code)
232 {
233 WORD StackSegment, StackPointer, CodeSegment, InstructionPointer;
234 BYTE IntNum;
235 LPWORD Stack;
236
237 /* Get the SS:SP */
238 #ifndef NEW_EMULATOR
239 StackSegment = EmulatorContext.state->segment_reg[SX86_SREG_SS].val;
240 StackPointer = EmulatorContext.state->general_reg[SX86_REG_SP].val;
241 #else
242 StackSegment = EmulatorContext.SegmentRegs[SOFT386_REG_SS].Selector;
243 StackPointer = EmulatorContext.GeneralRegs[SOFT386_REG_ESP].LowWord;
244 #endif
245
246 /* Get the stack */
247 Stack = (LPWORD)((ULONG_PTR)BaseAddress + TO_LINEAR(StackSegment, StackPointer));
248
249 if (Code == EMULATOR_INT_BOP)
250 {
251 /* Get the interrupt number */
252 IntNum = LOBYTE(Stack[STACK_INT_NUM]);
253
254 /* Get the CS:IP */
255 InstructionPointer = Stack[STACK_IP];
256 CodeSegment = Stack[STACK_CS];
257
258 /* Check if this was an exception */
259 if (IntNum < 8)
260 {
261 /* Display a message to the user */
262 DisplayMessage(L"Exception: %s occured at %04X:%04X",
263 ExceptionName[IntNum],
264 CodeSegment,
265 InstructionPointer);
266
267 /* Stop the VDM */
268 VdmRunning = FALSE;
269 return;
270 }
271
272 /* Check if this was an PIC IRQ */
273 if (IntNum >= BIOS_PIC_MASTER_INT && IntNum < BIOS_PIC_MASTER_INT + 8)
274 {
275 /* It was an IRQ from the master PIC */
276 BiosHandleIrq(IntNum - BIOS_PIC_MASTER_INT, Stack);
277 return;
278 }
279 else if (IntNum >= BIOS_PIC_SLAVE_INT && IntNum < BIOS_PIC_SLAVE_INT + 8)
280 {
281 /* It was an IRQ from the slave PIC */
282 BiosHandleIrq(IntNum - BIOS_PIC_SLAVE_INT + 8, Stack);
283 return;
284 }
285
286 switch (IntNum)
287 {
288 case BIOS_VIDEO_INTERRUPT:
289 {
290 /* This is the video BIOS interrupt, call the BIOS */
291 BiosVideoService(Stack);
292 break;
293 }
294 case BIOS_EQUIPMENT_INTERRUPT:
295 {
296 /* This is the BIOS "get equipment" command, call the BIOS */
297 BiosEquipmentService(Stack);
298 break;
299 }
300 case BIOS_KBD_INTERRUPT:
301 {
302 /* This is the keyboard BIOS interrupt, call the BIOS */
303 BiosKeyboardService(Stack);
304 break;
305 }
306 case BIOS_TIME_INTERRUPT:
307 {
308 /* This is the time BIOS interrupt, call the BIOS */
309 BiosTimeService(Stack);
310 break;
311 }
312 case BIOS_SYS_TIMER_INTERRUPT:
313 {
314 /* BIOS timer update */
315 BiosSystemTimerInterrupt(Stack);
316 break;
317 }
318 case 0x20:
319 {
320 DosInt20h(Stack);
321 break;
322 }
323 case 0x21:
324 {
325 DosInt21h(Stack);
326 break;
327 }
328 case 0x23:
329 {
330 DosBreakInterrupt(Stack);
331 break;
332 }
333 default:
334 {
335 DPRINT1("Unhandled interrupt: 0x%02X\n", IntNum);
336 break;
337 }
338 }
339 }
340 }
341
342 #ifdef NEW_EMULATOR
343 static VOID WINAPI EmulatorBiosOperation(PSOFT386_STATE State, WORD Code)
344 {
345 /*
346 * HACK: To maintain softx86 compatbility, just call the old EmulatorBop here.
347 * Later on, when softx86 is no longer needed, the code from EmulatorBop should
348 * be moved here and should use the "State" variable.
349 */
350 EmulatorBop(Code);
351 }
352
353 #endif
354
355 #ifndef NEW_EMULATOR
356
357 static VOID EmulatorSoftwareInt(PVOID Context, BYTE Number)
358 {
359 UNREFERENCED_PARAMETER(Context);
360 UNREFERENCED_PARAMETER(Number);
361
362 /* Do nothing */
363 }
364
365 static VOID EmulatorHardwareInt(PVOID Context, BYTE Number)
366 {
367 UNREFERENCED_PARAMETER(Context);
368 UNREFERENCED_PARAMETER(Number);
369
370 /* Do nothing */
371 }
372
373 static VOID EmulatorHardwareIntAck(PVOID Context, BYTE Number)
374 {
375 UNREFERENCED_PARAMETER(Context);
376 UNREFERENCED_PARAMETER(Number);
377
378 /* Do nothing */
379 }
380
381 #endif
382
383 /* PUBLIC FUNCTIONS ***********************************************************/
384
385 BOOLEAN EmulatorInitialize()
386 {
387 /* Allocate memory for the 16-bit address space */
388 BaseAddress = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_ADDRESS);
389 if (BaseAddress == NULL) return FALSE;
390
391 #ifndef NEW_EMULATOR
392 /* Initialize the softx86 CPU emulator */
393 if (!softx86_init(&EmulatorContext, SX86_CPULEVEL_80286))
394 {
395 HeapFree(GetProcessHeap(), 0, BaseAddress);
396 return FALSE;
397 }
398
399 /* Initialize the softx87 FPU emulator*/
400 if(!softx87_init(&FpuEmulatorContext, SX87_FPULEVEL_8087))
401 {
402 softx86_free(&EmulatorContext);
403 HeapFree(GetProcessHeap(), 0, BaseAddress);
404 return FALSE;
405 }
406
407 /* Set memory read/write callbacks */
408 EmulatorContext.callbacks->on_read_memory = EmulatorReadMemory;
409 EmulatorContext.callbacks->on_write_memory = EmulatorWriteMemory;
410
411 /* Set MMIO read/write callbacks */
412 EmulatorContext.callbacks->on_read_io = EmulatorReadIo;
413 EmulatorContext.callbacks->on_write_io = EmulatorWriteIo;
414
415 /* Set interrupt callbacks */
416 EmulatorContext.callbacks->on_sw_int = EmulatorSoftwareInt;
417 EmulatorContext.callbacks->on_hw_int = EmulatorHardwareInt;
418 EmulatorContext.callbacks->on_hw_int_ack = EmulatorHardwareIntAck;
419
420 /* Connect the emulated FPU to the emulated CPU */
421 softx87_connect_to_CPU(&EmulatorContext, &FpuEmulatorContext);
422 #else
423 /* Set the callbacks */
424 EmulatorContext.MemReadCallback = (SOFT386_MEM_READ_PROC)EmulatorReadMemory;
425 EmulatorContext.MemWriteCallback = (SOFT386_MEM_WRITE_PROC)EmulatorWriteMemory;
426 EmulatorContext.IoReadCallback = (SOFT386_IO_READ_PROC)EmulatorReadIo;
427 EmulatorContext.IoWriteCallback = (SOFT386_IO_WRITE_PROC)EmulatorWriteIo;
428 EmulatorContext.BopCallback = (SOFT386_BOP_PROC)EmulatorBiosOperation;
429
430 /* Reset the CPU */
431 Soft386Reset(&EmulatorContext);
432 #endif
433
434 /* Enable interrupts */
435 EmulatorSetFlag(EMULATOR_FLAG_IF);
436
437 return TRUE;
438 }
439
440 VOID EmulatorSetStack(WORD Segment, DWORD Offset)
441 {
442 #ifndef NEW_EMULATOR
443 /* Call the softx86 API */
444 softx86_set_stack_ptr(&EmulatorContext, Segment, Offset);
445 #else
446 Soft386SetStack(&EmulatorContext, Segment, Offset);
447 #endif
448 }
449
450 // FIXME: This function assumes 16-bit mode!!!
451 VOID EmulatorExecute(WORD Segment, WORD Offset)
452 {
453 #ifndef NEW_EMULATOR
454 /* Call the softx86 API */
455 softx86_set_instruction_ptr(&EmulatorContext, Segment, Offset);
456 #else
457 /* Tell Soft386 to move the instruction pointer */
458 Soft386ExecuteAt(&EmulatorContext, Segment, Offset);
459 #endif
460 }
461
462 VOID EmulatorInterrupt(BYTE Number)
463 {
464 #ifndef NEW_EMULATOR
465 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
466 UINT Segment, Offset;
467
468 /* Get the segment and offset */
469 Segment = HIWORD(IntVecTable[Number]);
470 Offset = LOWORD(IntVecTable[Number]);
471
472 /* Call the softx86 API */
473 softx86_make_simple_interrupt_call(&EmulatorContext, &Segment, &Offset);
474 #else
475 /* Call the Soft386 API */
476 Soft386Interrupt(&EmulatorContext, Number, FALSE);
477 #endif
478 }
479
480 VOID EmulatorExternalInterrupt(BYTE Number)
481 {
482 #ifndef NEW_EMULATOR
483 /* Call the softx86 API */
484 softx86_ext_hw_signal(&EmulatorContext, Number);
485 #else
486 /* Call the Soft386 API */
487 Soft386Interrupt(&EmulatorContext, Number, TRUE);
488 #endif
489 }
490
491 ULONG EmulatorGetRegister(ULONG Register)
492 {
493 #ifndef NEW_EMULATOR
494 if (Register < EMULATOR_REG_ES)
495 {
496 return EmulatorContext.state->general_reg[Register].val;
497 }
498 else
499 {
500 return EmulatorContext.state->segment_reg[Register - EMULATOR_REG_ES].val;
501 }
502 #else
503 if (Register < EMULATOR_REG_ES)
504 {
505 return EmulatorContext.GeneralRegs[Register].Long;
506 }
507 else
508 {
509 return EmulatorContext.SegmentRegs[Register - EMULATOR_REG_ES].Selector;
510 }
511 #endif
512 }
513
514 ULONG EmulatorGetProgramCounter(VOID)
515 {
516 #ifndef NEW_EMULATOR
517 return EmulatorContext.state->reg_ip;
518 #else
519 return EmulatorContext.InstPtr.Long;
520 #endif
521 }
522
523 VOID EmulatorSetRegister(ULONG Register, ULONG Value)
524 {
525 #ifndef NEW_EMULATOR
526 if (Register < EMULATOR_REG_ES)
527 {
528 EmulatorContext.state->general_reg[Register].val = Value;
529 }
530 else
531 {
532 EmulatorContext.state->segment_reg[Register - EMULATOR_REG_ES].val = (USHORT)Value;
533 }
534 #else
535 if (Register < EMULATOR_REG_ES)
536 {
537 EmulatorContext.GeneralRegs[Register].Long = Value;
538 }
539 else
540 {
541 Soft386SetSegment(&EmulatorContext, Register - EMULATOR_REG_ES, (USHORT)Value);
542 }
543 #endif
544 }
545
546 BOOLEAN EmulatorGetFlag(ULONG Flag)
547 {
548 #ifndef NEW_EMULATOR
549 return (EmulatorContext.state->reg_flags.val & Flag) ? TRUE : FALSE;
550 #else
551 return (EmulatorContext.Flags.Long & Flag) ? TRUE : FALSE;
552 #endif
553 }
554
555 VOID EmulatorSetFlag(ULONG Flag)
556 {
557 #ifndef NEW_EMULATOR
558 EmulatorContext.state->reg_flags.val |= Flag;
559 #else
560 EmulatorContext.Flags.Long |= Flag;
561 #endif
562 }
563
564 VOID EmulatorClearFlag(ULONG Flag)
565 {
566 #ifndef NEW_EMULATOR
567 EmulatorContext.state->reg_flags.val &= ~Flag;
568 #else
569 EmulatorContext.Flags.Long &= ~Flag;
570 #endif
571 }
572
573 VOID EmulatorStep(VOID)
574 {
575 #ifndef NEW_EMULATOR
576 LPWORD Instruction;
577
578 /* Print the current position - useful for debugging */
579 DPRINT("Executing at CS:IP = %04X:%04X\n",
580 EmulatorGetRegister(EMULATOR_REG_CS),
581 EmulatorContext.state->reg_ip);
582
583 Instruction = (LPWORD)((ULONG_PTR)BaseAddress
584 + TO_LINEAR(EmulatorGetRegister(EMULATOR_REG_CS),
585 EmulatorContext.state->reg_ip));
586
587 /* Check for the BIOS operation (BOP) sequence */
588 if (Instruction[0] == EMULATOR_BOP)
589 {
590 /* Skip the opcodes */
591 EmulatorContext.state->reg_ip += 4;
592
593 // HACK: Refresh the display because the called function may wait.
594 VgaRefreshDisplay();
595
596 /* Call the BOP handler */
597 EmulatorBop(Instruction[1]);
598 }
599
600 /* Call the softx86 API */
601 if (!softx86_step(&EmulatorContext))
602 {
603 /* Invalid opcode */
604 EmulatorInterrupt(EMULATOR_EXCEPTION_INVALID_OPCODE);
605 }
606 #else
607 /* Dump the state for debugging purposes */
608 // Soft386DumpState(&EmulatorContext);
609
610 /* Execute the next instruction */
611 Soft386StepInto(&EmulatorContext);
612 #endif
613 }
614
615 VOID EmulatorCleanup(VOID)
616 {
617 #ifndef NEW_EMULATOR
618 /* Free the softx86 CPU and FPU emulator */
619 softx87_free(&FpuEmulatorContext);
620 softx86_free(&EmulatorContext);
621 #endif
622
623 /* Free the memory allocated for the 16-bit address space */
624 if (BaseAddress != NULL) HeapFree(GetProcessHeap(), 0, BaseAddress);
625 }
626
627 VOID EmulatorSetA20(BOOLEAN Enabled)
628 {
629 A20Line = Enabled;
630 }
631
632 /* EOF */