1 /* 2 * COPYRIGHT: GPL - See COPYING in the top level directory 3 * PROJECT: ReactOS Virtual DOS Machine 4 * FILE: subsystems/mvdm/ntvdm/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 "ntvdm.h" 12 13 #define NDEBUG 14 #include <debug.h> 15 16 #include "emulator.h" 17 #include "memory.h" 18 19 #include "cpu/callback.h" 20 #include "cpu/cpu.h" 21 #include "cpu/bop.h" 22 #include <isvbop.h> 23 24 #include "int32.h" 25 26 #include "clock.h" 27 #include "bios/rom.h" 28 #include "hardware/cmos.h" 29 #include "hardware/disk.h" 30 #include "hardware/dma.h" 31 #include "hardware/keyboard.h" 32 #include "hardware/mouse.h" 33 #include "hardware/pic.h" 34 #include "hardware/pit.h" 35 #include "hardware/ppi.h" 36 #include "hardware/ps2.h" 37 #include "hardware/sound/speaker.h" 38 #include "hardware/video/svga.h" 39 /**/ 40 #include "./console/video.h" 41 /**/ 42 43 #include "vddsup.h" 44 #include "io.h" 45 46 /* PRIVATE VARIABLES **********************************************************/ 47 48 LPVOID BaseAddress = NULL; 49 BOOLEAN VdmRunning = TRUE; 50 51 HANDLE VdmTaskEvent = NULL; 52 static HANDLE InputThread = NULL; 53 54 LPCWSTR ExceptionName[] = 55 { 56 L"Division By Zero", 57 L"Debug", 58 L"Unexpected Error", 59 L"Breakpoint", 60 L"Integer Overflow", 61 L"Bound Range Exceeded", 62 L"Invalid Opcode", 63 L"FPU Not Available" 64 }; 65 66 /* BOP Identifiers */ 67 #define BOP_DEBUGGER 0x56 // Break into the debugger from a 16-bit app 68 69 /* PRIVATE FUNCTIONS **********************************************************/ 70 71 UCHAR FASTCALL EmulatorIntAcknowledge(PFAST486_STATE State) 72 { 73 UNREFERENCED_PARAMETER(State); 74 75 /* Get the interrupt number from the PIC */ 76 return PicGetInterrupt(); 77 } 78 79 VOID FASTCALL EmulatorFpu(PFAST486_STATE State) 80 { 81 /* The FPU is wired to IRQ 13 */ 82 PicInterruptRequest(13); 83 } 84 85 VOID EmulatorException(BYTE ExceptionNumber, LPWORD Stack) 86 { 87 WORD CodeSegment, InstructionPointer; 88 PBYTE Opcode; 89 90 ASSERT(ExceptionNumber < 8); 91 92 /* Get the CS:IP */ 93 InstructionPointer = Stack[STACK_IP]; 94 CodeSegment = Stack[STACK_CS]; 95 Opcode = (PBYTE)SEG_OFF_TO_PTR(CodeSegment, InstructionPointer); 96 97 /* Display a message to the user */ 98 DisplayMessage(L"Exception: %s occurred at %04X:%04X\n" 99 L"Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", 100 ExceptionName[ExceptionNumber], 101 CodeSegment, 102 InstructionPointer, 103 Opcode[0], 104 Opcode[1], 105 Opcode[2], 106 Opcode[3], 107 Opcode[4], 108 Opcode[5], 109 Opcode[6], 110 Opcode[7], 111 Opcode[8], 112 Opcode[9]); 113 114 Fast486DumpState(&EmulatorContext); 115 116 /* Stop the VDM */ 117 EmulatorTerminate(); 118 } 119 120 VOID EmulatorInterruptSignal(VOID) 121 { 122 /* Call the Fast486 API */ 123 Fast486InterruptSignal(&EmulatorContext); 124 } 125 126 static VOID WINAPI EmulatorDebugBreakBop(LPWORD Stack) 127 { 128 DPRINT1("NTVDM: BOP_DEBUGGER\n"); 129 DebugBreak(); 130 } 131 132 static VOID WINAPI PitChan0Out(LPVOID Param, BOOLEAN State) 133 { 134 if (State) 135 { 136 DPRINT("PicInterruptRequest\n"); 137 PicInterruptRequest(0); // Raise IRQ 0 138 } 139 // else < Lower IRQ 0 > 140 } 141 142 static VOID WINAPI PitChan1Out(LPVOID Param, BOOLEAN State) 143 { 144 #if 0 145 if (State) 146 { 147 /* Set bit 4 of Port 61h */ 148 Port61hState |= 1 << 4; 149 } 150 else 151 { 152 /* Clear bit 4 of Port 61h */ 153 Port61hState &= ~(1 << 4); 154 } 155 #else 156 Port61hState = (Port61hState & 0xEF) | (State << 4); 157 #endif 158 } 159 160 static VOID WINAPI PitChan2Out(LPVOID Param, BOOLEAN State) 161 { 162 BYTE OldPort61hState = Port61hState; 163 164 #if 0 165 if (State) 166 { 167 /* Set bit 5 of Port 61h */ 168 Port61hState |= 1 << 5; 169 } 170 else 171 { 172 /* Clear bit 5 of Port 61h */ 173 Port61hState &= ~(1 << 5); 174 } 175 #else 176 Port61hState = (Port61hState & 0xDF) | (State << 5); 177 #endif 178 179 if ((OldPort61hState ^ Port61hState) & 0x20) 180 { 181 DPRINT("PitChan2Out -- Port61hState changed\n"); 182 SpeakerChange(Port61hState); 183 } 184 } 185 186 187 static DWORD 188 WINAPI 189 ConsoleEventThread(LPVOID Parameter) 190 { 191 HANDLE ConsoleInput = (HANDLE)Parameter; 192 HANDLE WaitHandles[2]; 193 DWORD WaitResult; 194 195 /* 196 * For optimization purposes, Windows (and hence ReactOS, too, for 197 * compatibility reasons) uses a static buffer if no more than five 198 * input records are read. Otherwise a new buffer is used. 199 * The client-side expects that we know this behaviour. 200 * See consrv/coninput.c 201 * 202 * We exploit here this optimization by also using a buffer of 5 records. 203 */ 204 INPUT_RECORD InputRecords[5]; 205 ULONG NumRecords, i; 206 207 WaitHandles[0] = VdmTaskEvent; 208 WaitHandles[1] = GetConsoleInputWaitHandle(); 209 210 while (VdmRunning) 211 { 212 /* Make sure the task event is signaled */ 213 WaitResult = WaitForMultipleObjects(ARRAYSIZE(WaitHandles), 214 WaitHandles, 215 TRUE, 216 INFINITE); 217 switch (WaitResult) 218 { 219 case WAIT_OBJECT_0 + 0: 220 case WAIT_OBJECT_0 + 1: 221 break; 222 default: 223 return GetLastError(); 224 } 225 226 /* Wait for an input record */ 227 if (!ReadConsoleInputExW(ConsoleInput, 228 InputRecords, 229 ARRAYSIZE(InputRecords), 230 &NumRecords, 231 CONSOLE_READ_CONTINUE)) 232 { 233 DWORD LastError = GetLastError(); 234 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, NumRecords, LastError); 235 return LastError; 236 } 237 238 // ASSERT(NumRecords != 0); 239 if (NumRecords == 0) 240 { 241 DPRINT1("Got NumRecords == 0!\n"); 242 continue; 243 } 244 245 /* Dispatch the events */ 246 for (i = 0; i < NumRecords; i++) 247 { 248 /* Check the event type */ 249 switch (InputRecords[i].EventType) 250 { 251 /* 252 * Hardware events 253 */ 254 case KEY_EVENT: 255 KeyboardEventHandler(&InputRecords[i].Event.KeyEvent); 256 break; 257 258 case MOUSE_EVENT: 259 MouseEventHandler(&InputRecords[i].Event.MouseEvent); 260 break; 261 262 case WINDOW_BUFFER_SIZE_EVENT: 263 ScreenEventHandler(&InputRecords[i].Event.WindowBufferSizeEvent); 264 break; 265 266 /* 267 * Interface events 268 */ 269 case MENU_EVENT: 270 MenuEventHandler(&InputRecords[i].Event.MenuEvent); 271 break; 272 273 case FOCUS_EVENT: 274 FocusEventHandler(&InputRecords[i].Event.FocusEvent); 275 break; 276 277 default: 278 DPRINT1("Unknown input event type 0x%04x\n", InputRecords[i].EventType); 279 break; 280 } 281 } 282 283 /* Let the console subsystem queue some new events */ 284 Sleep(10); 285 } 286 287 return 0; 288 } 289 290 static VOID PauseEventThread(VOID) 291 { 292 ResetEvent(VdmTaskEvent); 293 } 294 295 static VOID ResumeEventThread(VOID) 296 { 297 SetEvent(VdmTaskEvent); 298 } 299 300 301 /* PUBLIC FUNCTIONS ***********************************************************/ 302 303 static VOID 304 DumpMemoryRaw(HANDLE hFile) 305 { 306 PVOID Buffer; 307 DWORD Size; 308 309 /* Dump the VM memory */ 310 SetFilePointer(hFile, 0, NULL, FILE_BEGIN); 311 Buffer = REAL_TO_PHYS(NULL); 312 Size = MAX_ADDRESS - (ULONG_PTR)(NULL); 313 WriteFile(hFile, Buffer, Size, &Size, NULL); 314 } 315 316 static VOID 317 DumpMemoryTxt(HANDLE hFile) 318 { 319 #define LINE_SIZE 75 + 2 320 ULONG i; 321 PBYTE Ptr1, Ptr2; 322 CHAR LineBuffer[LINE_SIZE]; 323 PCHAR Line; 324 DWORD LineSize; 325 326 /* Dump the VM memory */ 327 SetFilePointer(hFile, 0, NULL, FILE_BEGIN); 328 Ptr1 = Ptr2 = REAL_TO_PHYS(NULL); 329 while (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0) 330 { 331 Ptr1 = Ptr2; 332 Line = LineBuffer; 333 334 /* Print the address */ 335 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, "%08Ix ", (ULONG_PTR)PHYS_TO_REAL(Ptr1)); 336 337 /* Print up to 16 bytes... */ 338 339 /* ... in hexadecimal form first... */ 340 i = 0; 341 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr1) > 0)) 342 { 343 Line += snprintf(Line, LINE_SIZE + LineBuffer - Line, " %02x", *Ptr1); 344 ++Ptr1; 345 } 346 347 /* ... align with spaces if needed... */ 348 RtlFillMemory(Line, (0x0F + 2 - i) * 3 + 2, ' '); 349 Line += (0x0F + 2 - i) * 3 + 2; 350 351 /* ... then in character form. */ 352 i = 0; 353 while (i++ <= 0x0F && (MAX_ADDRESS - (ULONG_PTR)PHYS_TO_REAL(Ptr2) > 0)) 354 { 355 *Line++ = ((*Ptr2 >= 0x20 && *Ptr2 <= 0x7E) || (*Ptr2 >= 0x80 && *Ptr2 < 0xFF) ? *Ptr2 : '.'); 356 ++Ptr2; 357 } 358 359 /* Newline */ 360 *Line++ = '\r'; 361 *Line++ = '\n'; 362 363 /* Finally write the line to the file */ 364 LineSize = Line - LineBuffer; 365 WriteFile(hFile, LineBuffer, LineSize, &LineSize, NULL); 366 } 367 } 368 369 VOID DumpMemory(BOOLEAN TextFormat) 370 { 371 static ULONG DumpNumber = 0; 372 373 HANDLE hFile; 374 WCHAR FileName[MAX_PATH]; 375 376 /* Build a suitable file name */ 377 _snwprintf(FileName, MAX_PATH, 378 L"memdump%lu.%s", 379 DumpNumber, 380 TextFormat ? L"txt" : L"dat"); 381 ++DumpNumber; 382 383 DPRINT1("Creating memory dump file '%S'...\n", FileName); 384 385 /* Always create the dump file */ 386 hFile = CreateFileW(FileName, 387 GENERIC_WRITE, 388 0, 389 NULL, 390 CREATE_ALWAYS, 391 FILE_ATTRIBUTE_NORMAL, 392 NULL); 393 394 if (hFile == INVALID_HANDLE_VALUE) 395 { 396 DPRINT1("Error when creating '%S' for memory dumping, GetLastError() = %u\n", 397 FileName, GetLastError()); 398 return; 399 } 400 401 /* Dump the VM memory in the chosen format */ 402 if (TextFormat) 403 DumpMemoryTxt(hFile); 404 else 405 DumpMemoryRaw(hFile); 406 407 /* Close the file */ 408 CloseHandle(hFile); 409 410 DPRINT1("Memory dump done\n"); 411 } 412 413 VOID MountFloppy(IN ULONG DiskNumber) 414 { 415 // FIXME: This should be present in PSDK commdlg.h 416 // 417 // FlagsEx Values 418 #if (_WIN32_WINNT >= 0x0500) 419 #define OFN_EX_NOPLACESBAR 0x00000001 420 #endif // (_WIN32_WINNT >= 0x0500) 421 422 BOOLEAN Success; 423 OPENFILENAMEW ofn; 424 WCHAR szFile[MAX_PATH] = L""; 425 426 ASSERT(DiskNumber < ARRAYSIZE(GlobalSettings.FloppyDisks)); 427 428 RtlZeroMemory(&ofn, sizeof(ofn)); 429 ofn.lStructSize = sizeof(ofn); 430 ofn.hwndOwner = hConsoleWnd; 431 ofn.lpstrTitle = L"Select a virtual floppy image"; 432 ofn.Flags = OFN_EXPLORER | OFN_ENABLESIZING | OFN_LONGNAMES | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; 433 // ofn.FlagsEx = OFN_EX_NOPLACESBAR; 434 ofn.lpstrFilter = L"Virtual floppy images (*.vfd;*.img;*.ima;*.dsk)\0*.vfd;*.img;*.ima;*.dsk\0All files (*.*)\0*.*\0\0"; 435 ofn.lpstrDefExt = L"vfd"; 436 ofn.nFilterIndex = 0; 437 ofn.lpstrFile = szFile; 438 ofn.nMaxFile = ARRAYSIZE(szFile); 439 440 if (!GetOpenFileNameW(&ofn)) 441 { 442 DPRINT1("CommDlgExtendedError = %d\n", CommDlgExtendedError()); 443 return; 444 } 445 446 /* Free the old string */ 447 if (GlobalSettings.FloppyDisks[DiskNumber].Buffer) 448 RtlFreeUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber]); 449 450 /* Reinitialize the string */ 451 Success = RtlCreateUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber], szFile); 452 ASSERT(Success); 453 454 /* Mount the disk */ 455 if (!MountDisk(FLOPPY_DISK, DiskNumber, GlobalSettings.FloppyDisks[DiskNumber].Buffer, !!(ofn.Flags & OFN_READONLY))) 456 { 457 DisplayMessage(L"An error happened when mounting disk %d", DiskNumber); 458 RtlFreeUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber]); 459 RtlInitEmptyUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber], NULL, 0); 460 return; 461 } 462 463 /* Refresh the menu state */ 464 UpdateVdmMenuDisks(); 465 } 466 467 VOID EjectFloppy(IN ULONG DiskNumber) 468 { 469 ASSERT(DiskNumber < ARRAYSIZE(GlobalSettings.FloppyDisks)); 470 471 /* Unmount the disk */ 472 if (!UnmountDisk(FLOPPY_DISK, DiskNumber)) 473 DisplayMessage(L"An error happened when ejecting disk %d", DiskNumber); 474 475 /* Free the old string */ 476 if (GlobalSettings.FloppyDisks[DiskNumber].Buffer) 477 { 478 RtlFreeUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber]); 479 RtlInitEmptyUnicodeString(&GlobalSettings.FloppyDisks[DiskNumber], NULL, 0); 480 } 481 482 /* Refresh the menu state */ 483 UpdateVdmMenuDisks(); 484 } 485 486 487 VOID EmulatorPause(VOID) 488 { 489 /* Pause the VDM */ 490 VDDBlockUserHook(); 491 VgaRefreshDisplay(); 492 PauseEventThread(); 493 } 494 495 VOID EmulatorResume(VOID) 496 { 497 /* Resume the VDM */ 498 ResumeEventThread(); 499 VgaRefreshDisplay(); 500 VDDResumeUserHook(); 501 } 502 503 VOID EmulatorTerminate(VOID) 504 { 505 /* Stop the VDM */ 506 CpuUnsimulate(); // Halt the CPU 507 VdmRunning = FALSE; 508 } 509 510 BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput) 511 { 512 USHORT i; 513 514 /* Initialize memory */ 515 if (!MemInitialize()) 516 { 517 wprintf(L"Memory initialization failed.\n"); 518 return FALSE; 519 } 520 521 /* Initialize I/O ports */ 522 /* Initialize RAM */ 523 524 /* Initialize the CPU */ 525 526 /* Initialize the internal clock */ 527 if (!ClockInitialize()) 528 { 529 wprintf(L"FATAL: Failed to initialize the clock\n"); 530 EmulatorCleanup(); 531 return FALSE; 532 } 533 534 /* Initialize the CPU */ 535 CpuInitialize(); 536 537 /* Initialize DMA */ 538 DmaInitialize(); 539 540 /* Initialize PIC, PIT, CMOS, PC Speaker and PS/2 */ 541 PicInitialize(); 542 543 PitInitialize(); 544 PitSetOutFunction(0, NULL, PitChan0Out); 545 PitSetOutFunction(1, NULL, PitChan1Out); 546 PitSetOutFunction(2, NULL, PitChan2Out); 547 548 CmosInitialize(); 549 SpeakerInitialize(); 550 PpiInitialize(); 551 552 PS2Initialize(); 553 554 /* Initialize the keyboard and mouse and connect them to their PS/2 ports */ 555 KeyboardInit(0); 556 MouseInit(1); 557 558 /**************** ATTACH INPUT WITH CONSOLE *****************/ 559 /* Create the task event */ 560 VdmTaskEvent = CreateEventW(NULL, TRUE, FALSE, NULL); 561 ASSERT(VdmTaskEvent != NULL); 562 563 /* Start the input thread */ 564 InputThread = CreateThread(NULL, 0, &ConsoleEventThread, ConsoleInput, 0, NULL); 565 if (InputThread == NULL) 566 { 567 wprintf(L"FATAL: Failed to create the console input thread.\n"); 568 EmulatorCleanup(); 569 return FALSE; 570 } 571 ResumeEventThread(); 572 /************************************************************/ 573 574 /* Initialize the VGA */ 575 if (!VgaInitialize(ConsoleOutput)) 576 { 577 wprintf(L"FATAL: Failed to initialize VGA support.\n"); 578 EmulatorCleanup(); 579 return FALSE; 580 } 581 582 /* Initialize the disk controller */ 583 if (!DiskCtrlInitialize()) 584 { 585 wprintf(L"FATAL: Failed to completely initialize the disk controller.\n"); 586 EmulatorCleanup(); 587 return FALSE; 588 } 589 590 /* Mount the available floppy disks */ 591 for (i = 0; i < ARRAYSIZE(GlobalSettings.FloppyDisks); ++i) 592 { 593 if (GlobalSettings.FloppyDisks[i].Length != 0 && 594 GlobalSettings.FloppyDisks[i].Buffer && 595 *GlobalSettings.FloppyDisks[i].Buffer != L'\0') 596 { 597 if (!MountDisk(FLOPPY_DISK, i, GlobalSettings.FloppyDisks[i].Buffer, FALSE)) 598 { 599 DPRINT1("Failed to mount floppy disk file '%wZ'.\n", &GlobalSettings.FloppyDisks[i]); 600 RtlFreeUnicodeString(&GlobalSettings.FloppyDisks[i]); 601 RtlInitEmptyUnicodeString(&GlobalSettings.FloppyDisks[i], NULL, 0); 602 } 603 } 604 } 605 606 /* 607 * Mount the available hard disks. Contrary to floppies, failing 608 * mounting a hard disk is considered as an unrecoverable error. 609 */ 610 for (i = 0; i < ARRAYSIZE(GlobalSettings.HardDisks); ++i) 611 { 612 if (GlobalSettings.HardDisks[i].Length != 0 && 613 GlobalSettings.HardDisks[i].Buffer && 614 *GlobalSettings.HardDisks[i].Buffer != L'\0') 615 { 616 if (!MountDisk(HARD_DISK, i, GlobalSettings.HardDisks[i].Buffer, FALSE)) 617 { 618 wprintf(L"FATAL: Failed to mount hard disk file '%wZ'.\n", &GlobalSettings.HardDisks[i]); 619 EmulatorCleanup(); 620 return FALSE; 621 } 622 } 623 } 624 625 /* Refresh the menu state */ 626 UpdateVdmMenuDisks(); 627 628 /* Initialize the software callback system and register the emulator BOPs */ 629 InitializeInt32(); 630 RegisterBop(BOP_DEBUGGER , EmulatorDebugBreakBop); 631 // RegisterBop(BOP_UNSIMULATE, CpuUnsimulateBop); 632 633 /* Initialize VDD support */ 634 VDDSupInitialize(); 635 636 return TRUE; 637 } 638 639 VOID EmulatorCleanup(VOID) 640 { 641 DiskCtrlCleanup(); 642 643 VgaCleanup(); 644 645 /* Close the input thread handle */ 646 if (InputThread != NULL) CloseHandle(InputThread); 647 InputThread = NULL; 648 649 /* Close the task event */ 650 if (VdmTaskEvent != NULL) CloseHandle(VdmTaskEvent); 651 VdmTaskEvent = NULL; 652 653 PS2Cleanup(); 654 655 SpeakerCleanup(); 656 CmosCleanup(); 657 // PitCleanup(); 658 // PicCleanup(); 659 660 // DmaCleanup(); 661 662 CpuCleanup(); 663 MemCleanup(); 664 } 665 666 667 668 VOID 669 WINAPI 670 VDDSimulate16(VOID) 671 { 672 CpuSimulate(); 673 } 674 675 VOID 676 WINAPI 677 VDDTerminateVDM(VOID) 678 { 679 /* Stop the VDM */ 680 EmulatorTerminate(); 681 } 682 683 /* EOF */ 684