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