[NTVDM]
authorHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Thu, 1 Oct 2015 00:09:24 +0000 (00:09 +0000)
committerHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Thu, 1 Oct 2015 00:09:24 +0000 (00:09 +0000)
- Add a basic boot sequence functionality (read from CMOS); will be improved in the future.
- Print a "FATAL BOOT FAILURE" error message when INT 18h is called.
- Fail startup if we cannot mount the available hard disk images.
- Improve some diagnostic error messages.

svn path=/trunk/; revision=69421

reactos/subsystems/mvdm/ntvdm/bios/bios32/bios32.c
reactos/subsystems/mvdm/ntvdm/dos/dem.c
reactos/subsystems/mvdm/ntvdm/dos/dem.h
reactos/subsystems/mvdm/ntvdm/emulator.c
reactos/subsystems/mvdm/ntvdm/hardware/cmos.h
reactos/subsystems/mvdm/ntvdm/hardware/disk.c
reactos/subsystems/mvdm/ntvdm/ntvdm.c
reactos/subsystems/mvdm/ntvdm/ntvdm.h

index 89eee51..9e0d529 100644 (file)
@@ -161,6 +161,31 @@ static const BYTE PostCode[] =
 
 /* PRIVATE FUNCTIONS **********************************************************/
 
+static VOID BiosCharPrint(CHAR Character)
+{
+    /* Save AX and BX */
+    USHORT AX = getAX();
+    USHORT BX = getBX();
+
+    /*
+     * Set the parameters:
+     * AL contains the character to print,
+     * BL contains the character attribute,
+     * BH contains the video page to use.
+     */
+    setAL(Character);
+    setBL(DEFAULT_ATTRIBUTE);
+    setBH(Bda->VideoPage);
+
+    /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
+    setAH(0x0E);
+    Int32Call(&BiosContext, BIOS_VIDEO_INTERRUPT);
+
+    /* Restore AX and BX */
+    setBX(BX);
+    setAX(AX);
+}
+
 static VOID WINAPI BiosException(LPWORD Stack)
 {
     /* Get the exception number and call the emulator API */
@@ -505,6 +530,8 @@ static VOID WINAPI BiosMiscService(LPWORD Stack)
 
 static VOID WINAPI BiosRomBasic(LPWORD Stack)
 {
+    PrintMessageAnsi(BiosCharPrint, "FATAL: INT18: BOOT FAILURE.");
+
     /* ROM Basic is unsupported, display a message to the user */
     DisplayMessage(L"NTVDM doesn't support ROM Basic. The VDM is closing.");
 
@@ -518,79 +545,110 @@ extern VOID WINAPI BiosDiskService(LPWORD Stack);
 
 static VOID WINAPI BiosBootstrapLoader(LPWORD Stack)
 {
+    USHORT BootOrder;
+
+    USHORT AX, BX, CX, DX, ES;
+    AX = getAX();
+    BX = getBX();
+    CX = getCX();
+    DX = getDX();
+    ES = getES();
+
+    /*
+     * Read the boot sequence order from the CMOS, old behaviour AMI-style.
+     * 
+     * For more information, see:
+     * http://www.virtualbox.org/svn/vbox/trunk/src/VBox/Devices/PC/BIOS/orgs.asm
+     * http://www.virtualbox.org/svn/vbox/trunk/src/VBox/Devices/PC/BIOS/boot.c
+     * http://bochs.sourceforge.net/cgi-bin/lxr/source/iodev/cmos.cc
+     * https://web.archive.org/web/20111209041013/http://www-ivs.cs.uni-magdeburg.de/~zbrog/asm/cmos.html
+     * http://www.bioscentral.com/misc/cmosmap.htm
+     */
+    IOWriteB(CMOS_ADDRESS_PORT, CMOS_REG_SYSOP);
+    BootOrder = (IOReadB(CMOS_DATA_PORT) & 0x20) >> 5;
+
     /*
-     * In real BIOSes one loads the bootsector read from a diskette
-     * or from a disk, copy it to 0000:7C00 and then boot it.
-     * Since we are 32-bit VM and we hardcode our DOS at the moment,
-     * just call the DOS 32-bit initialization code.
+     * BootOrder =
+     * 0: Hard Disk, then Floppy Disk
+     * 1: Floppy Disk, then Hard Disk
+     * In all cases, if booting from those devices failed,
+     * ROM DOS-32 is started. If it fails, INT 18h is called.
      */
 
-    DPRINT("BiosBootstrapLoader -->\n");
+    DPRINT("BiosBootstrapLoader (BootOrder = 0x%02X) -->\n", BootOrder);
 
+    /*
+     * Format of the BootOrder command:
+     * 2 bytes. Each half-byte contains the ID of the drive to boot.
+     * Currently defined:
+     * 0x0: 1st Floppy disk
+     * 0x1: 1st Hard disk
+     * Other, or 0xF: Stop boot sequence.
+     */
+    BootOrder = 0xFF00 | ((1 << (4 * BootOrder)) & 0xFF);
+
+Retry:
+    switch (BootOrder & 0x0F)
     {
-        USHORT i;
-
-        USHORT AX, BX, CX, DX, ES;
-        AX = getAX();
-        BX = getBX();
-        CX = getCX();
-        DX = getDX();
-        ES = getES();
-
-        // i = 0;
-        i = 1;
-        do
+        /* Boot from 1st floppy drive */
+        case 0:
         {
-            if (i == 0)
-            {
-                /* Boot from 1st floppy drive */
-
-                setAH(0x02); // Read sectors
-                setAL(0x01); // Number of sectors
-                setDH(0x00); // First head
-                setCH(0x00); // First cylinder
-                setCL(0x01); // First sector
-                setDL(0x00); // First diskette drive (used by loader code, so should not be cleared)
-                setES(0x0000);  // Place data in 0000:7C00
-                setBX(0x7C00);
-                BiosDiskService(Stack);
-                if (!(Stack[STACK_FLAGS] & EMULATOR_FLAG_CF)) goto Quit;
-                DPRINT1("An error happened while loading the bootsector from floppy 0, error = %d\n", getAH());
-            }
-            else if (i == 1)
-            {
-                /* Boot from 1st HDD drive */
-
-                setAH(0x02); // Read sectors
-                setAL(0x01); // Number of sectors
-                setDH(0x00); // First head
-                setCH(0x00); // First cylinder
-                setCL(0x01); // First sector
-                setDL(0x80); // First HDD drive (used by loader code, so should not be cleared)
-                setES(0x0000);  // Place data in 0000:7C00
-                setBX(0x7C00);
-                BiosDiskService(Stack);
-                if (!(Stack[STACK_FLAGS] & EMULATOR_FLAG_CF)) goto Quit;
-                DPRINT1("An error happened while loading the bootsector from HDD 0, error = %d\n", getAH());
-            }
-        // } while (i++ < 1);
-        } while (i-- > 0);
-
-        /* Clear everything, we are going to load DOS32 */
-        setAX(AX);
-        setBX(BX);
-        setCX(CX);
-        setDX(DX);
-        setES(ES);
-        Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+            setAH(0x02); // Read sectors
+            setAL(0x01); // Number of sectors
+            setDH(0x00); // Head 0
+            setCH(0x00); // Cylinder 0
+            setCL(0x01); // Sector 1
+            setDL(0x00); // First diskette drive (used by loader code, so should not be cleared)
+            setES(0x0000);  // Write data in 0000:7C00
+            setBX(0x7C00);
+            BiosDiskService(Stack);
+            if (!(Stack[STACK_FLAGS] & EMULATOR_FLAG_CF)) goto Quit;
+            DPRINT1("An error happened while loading the bootsector from floppy 0, error = %d\n", getAH());
+
+            break;
+        }
+
+        /* Boot from 1st HDD drive */
+        case 1:
+        {
+            setAH(0x02); // Read sectors
+            setAL(0x01); // Number of sectors
+            setDH(0x00); // Head 0
+            setCH(0x00); // Cylinder 0
+            setCL(0x01); // Sector 1
+            setDL(0x80); // First HDD drive (used by loader code, so should not be cleared)
+            setES(0x0000);  // Write data in 0000:7C00
+            setBX(0x7C00);
+            BiosDiskService(Stack);
+            if (!(Stack[STACK_FLAGS] & EMULATOR_FLAG_CF)) goto Quit;
+            DPRINT1("An error happened while loading the bootsector from HDD 0, error = %d\n", getAH());
+
+            break;
+        }
+
+        default:
+            goto StartDos;
     }
 
+    /* Go to next drive and invalidate the last half-byte. */
+    BootOrder = (BootOrder >> 4) | 0xF000;
+    goto Retry;
+
+StartDos:
+    /* Clear everything, we are going to load DOS32 */
+    setAX(AX);
+    setBX(BX);
+    setCX(CX);
+    setDX(DX);
+    setES(ES);
+    Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+
     /* Load our DOS */
     DosBootsectorInitialize();
 
 Quit:
     /*
-     * Position CPU to 0000:7C00 to boot the OS.
+     * Jump to 0000:7C00 to boot the OS.
      *
      * Since we are called via the INT32 mechanism, we need to correctly set
      * CS:IP, not by changing the current one (otherwise the interrupt could
index b364221..1af1069 100644 (file)
@@ -82,83 +82,6 @@ VOID DosCharPrint(CHAR Character)
     DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
 }
 
-/*
- * This function, derived from ntvdm.c!DisplayMessage, is used by the BIOS and
- * the DOS to display messages to an output device. A printer function is given
- * for printing the characters.
- */
-static VOID
-DisplayMessageAnsiV(IN CHAR_PRINT CharPrint,
-                    IN LPCSTR Format,
-                    IN va_list args)
-{
-    static CHAR CurChar = 0;
-    LPSTR str;
-
-#ifndef WIN2K_COMPLIANT
-    CHAR  StaticBuffer[256];
-    LPSTR Buffer = StaticBuffer; // Use the static buffer by default.
-#else
-    CHAR  Buffer[2048]; // Large enough. If not, increase it by hand.
-#endif
-    size_t MsgLen;
-
-#ifndef WIN2K_COMPLIANT
-    /*
-     * Retrieve the message length and if it is too long, allocate
-     * an auxiliary buffer; otherwise use the static buffer.
-     * The string is built to be NULL-terminated.
-     */
-    MsgLen = _vscprintf(Format, args);
-    if (MsgLen >= ARRAYSIZE(StaticBuffer))
-    {
-        Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(CHAR));
-        if (Buffer == NULL)
-        {
-            /* Allocation failed, use the static buffer and display a suitable error message */
-            Buffer = StaticBuffer;
-            Format = "DisplayMessageAnsi()\nOriginal message is too long and allocating an auxiliary buffer failed.";
-            MsgLen = strlen(Format);
-        }
-    }
-#else
-    MsgLen = ARRAYSIZE(Buffer) - 1;
-#endif
-
-    RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(CHAR));
-    _vsnprintf(Buffer, MsgLen, Format, args);
-
-    /* Display the message */
-    DPRINT1("\n\nNTVDM DOS32\n%s\n\n", Buffer);
-
-    MsgLen = strlen(Buffer);
-    str = Buffer;
-    while (MsgLen--)
-    {
-        if (*str == '\n' && CurChar != '\r')
-            CharPrint('\r');
-
-        CurChar = *str++;
-        CharPrint(CurChar);
-    }
-
-#ifndef WIN2K_COMPLIANT
-    /* Free the buffer if needed */
-    if (Buffer != StaticBuffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
-#endif
-}
-
-VOID
-DemDisplayMessage(IN CHAR_PRINT CharPrint,
-                  IN LPCSTR Format, ...)
-{
-    va_list Parameters;
-
-    va_start(Parameters, Format);
-    DisplayMessageAnsiV(CharPrint, Format, Parameters);
-    va_end(Parameters);
-}
-
 
 static VOID DemLoadNTDOSKernel(VOID)
 {
index 9ee27a5..befbe80 100644 (file)
 
 /* FUNCTIONS ******************************************************************/
 
-typedef VOID (*CHAR_PRINT)(IN CHAR Character);
 VOID BiosCharPrint(CHAR Character);
-VOID DosCharPrint(CHAR Character);
-
-VOID DemDisplayMessage(IN CHAR_PRINT CharPrint,
-                       IN LPCSTR Format, ...);
-
 #define BiosDisplayMessage(Format, ...) \
-    DemDisplayMessage(BiosCharPrint, (Format), ##__VA_ARGS__)
+    PrintMessageAnsi(BiosCharPrint, (Format), ##__VA_ARGS__)
 
+VOID DosCharPrint(CHAR Character);
 #define DosDisplayMessage(Format, ...)  \
-    DemDisplayMessage(DosCharPrint, (Format), ##__VA_ARGS__)
+    PrintMessageAnsi(DosCharPrint, (Format), ##__VA_ARGS__)
 
 
 BOOLEAN DosShutdown(BOOLEAN Immediate);
index 344f64e..fa55abb 100644 (file)
@@ -564,14 +564,22 @@ BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
         }
     }
 
-    /* Mount the available hard disks */
+    /*
+     * Mount the available hard disks. Contrary to floppies, failing
+     * mounting a hard disk is considered as an unrecoverable error.
+     */
     for (i = 0; i < ARRAYSIZE(GlobalSettings.HardDisks); ++i)
     {
         if (GlobalSettings.HardDisks[i].Length != 0 &&
             GlobalSettings.HardDisks[i].Buffer      &&
             GlobalSettings.HardDisks[i].Buffer != '\0')
         {
-            MountDisk(HARD_DISK, i, GlobalSettings.HardDisks[i].Buffer, FALSE);
+            if (!MountDisk(HARD_DISK, i, GlobalSettings.HardDisks[i].Buffer, FALSE))
+            {
+                wprintf(L"FATAL: Failed to mount hard disk file '%Z'.\n", &GlobalSettings);
+                EmulatorCleanup();
+                return FALSE;
+            }
         }
     }
 
index 45a222f..3420f57 100644 (file)
@@ -70,6 +70,7 @@ typedef enum _CMOS_REGISTERS
     CMOS_REG_BASE_MEMORY_HIGH       = 0x16,
     CMOS_REG_EXT_MEMORY_LOW         = 0x17,
     CMOS_REG_EXT_MEMORY_HIGH        = 0x18,
+    CMOS_REG_SYSOP                  = 0x2D,
     CMOS_REG_ACTUAL_EXT_MEMORY_LOW  = 0x30,
     CMOS_REG_ACTUAL_EXT_MEMORY_HIGH = 0x31,
     CMOS_REG_CENTURY                = 0x32,
index 6c67312..581efae 100644 (file)
@@ -199,7 +199,7 @@ MountFDI(IN PDISK_IMAGE DiskImage,
     if (FileSize == INVALID_FILE_SIZE && GetLastError() != ERROR_SUCCESS)
     {
         /* We failed, bail out */
-        DisplayMessage(L"Error when retrieving file size, or size too large (%d).", FileSize);
+        DisplayMessage(L"MountFDI: Error when retrieving file size, or size too large (%d).", FileSize);
         return FALSE;
     }
 
@@ -547,7 +547,11 @@ MountDisk(IN DISK_TYPE DiskType,
             FileName, hFile != INVALID_HANDLE_VALUE ? "succeeded" : "failed", GetLastError());
 
     /* If we failed, bail out */
-    if (hFile == INVALID_HANDLE_VALUE) return FALSE;
+    if (hFile == INVALID_HANDLE_VALUE)
+    {
+        DisplayMessage(L"MountDisk: Error when opening disk file '%S' (Error: %u).", FileName, GetLastError());
+        return FALSE;
+    }
 
     /* OK, we have a handle to the file */
 
@@ -563,7 +567,7 @@ MountDisk(IN DISK_TYPE DiskType,
         GetLastError() == ERROR_INVALID_FUNCTION)
     {
         /* Objects other than real files are not supported */
-        DisplayMessage(L"'%S' is not a valid disk file.", FileName);
+        DisplayMessage(L"MountDisk: '%S' is not a valid disk file.", FileName);
         goto Quit;
     }
     SetLastError(0);
@@ -571,7 +575,7 @@ MountDisk(IN DISK_TYPE DiskType,
         GetLastError() == ERROR_INVALID_FUNCTION)
     {
         /* Objects other than real files are not supported */
-        DisplayMessage(L"'%S' is not a valid disk file.", FileName);
+        DisplayMessage(L"MountDisk: '%S' is not a valid disk file.", FileName);
         goto Quit;
     }
 
index 7094807..29ecf73 100644 (file)
@@ -562,9 +562,9 @@ DisplayMessage(IN LPCWSTR Format, ...)
     WCHAR  Buffer[2048]; // Large enough. If not, increase it by hand.
 #endif
     size_t MsgLen;
-    va_list Parameters;
+    va_list args;
 
-    va_start(Parameters, Format);
+    va_start(args, Format);
 
 #ifndef WIN2K_COMPLIANT
     /*
@@ -572,7 +572,7 @@ DisplayMessage(IN LPCWSTR Format, ...)
      * an auxiliary buffer; otherwise use the static buffer.
      * The string is built to be NULL-terminated.
      */
-    MsgLen = _vscwprintf(Format, Parameters);
+    MsgLen = _vscwprintf(Format, args);
     if (MsgLen >= ARRAYSIZE(StaticBuffer))
     {
         Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(WCHAR));
@@ -589,9 +589,9 @@ DisplayMessage(IN LPCWSTR Format, ...)
 #endif
 
     RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(WCHAR));
-    _vsnwprintf(Buffer, MsgLen, Format, Parameters);
+    _vsnwprintf(Buffer, MsgLen, Format, args);
 
-    va_end(Parameters);
+    va_end(args);
 
     /* Display the message */
     DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
@@ -603,6 +603,76 @@ DisplayMessage(IN LPCWSTR Format, ...)
 #endif
 }
 
+/*
+ * This function, derived from DisplayMessage, is used by the BIOS and
+ * the DOS to display messages to an output device. A printer function
+ * is given for printing the characters.
+ */
+VOID
+PrintMessageAnsi(IN CHAR_PRINT CharPrint,
+                 IN LPCSTR Format, ...)
+{
+    static CHAR CurChar = 0;
+    LPSTR str;
+
+#ifndef WIN2K_COMPLIANT
+    CHAR  StaticBuffer[256];
+    LPSTR Buffer = StaticBuffer; // Use the static buffer by default.
+#else
+    CHAR  Buffer[2048]; // Large enough. If not, increase it by hand.
+#endif
+    size_t MsgLen;
+    va_list args;
+
+    va_start(args, Format);
+
+#ifndef WIN2K_COMPLIANT
+    /*
+     * Retrieve the message length and if it is too long, allocate
+     * an auxiliary buffer; otherwise use the static buffer.
+     * The string is built to be NULL-terminated.
+     */
+    MsgLen = _vscprintf(Format, args);
+    if (MsgLen >= ARRAYSIZE(StaticBuffer))
+    {
+        Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(CHAR));
+        if (Buffer == NULL)
+        {
+            /* Allocation failed, use the static buffer and display a suitable error message */
+            Buffer = StaticBuffer;
+            Format = "DisplayMessageAnsi()\nOriginal message is too long and allocating an auxiliary buffer failed.";
+            MsgLen = strlen(Format);
+        }
+    }
+#else
+    MsgLen = ARRAYSIZE(Buffer) - 1;
+#endif
+
+    RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(CHAR));
+    _vsnprintf(Buffer, MsgLen, Format, args);
+
+    va_end(args);
+
+    /* Display the message */
+    // DPRINT1("\n\nNTVDM DOS32\n%s\n\n", Buffer);
+
+    MsgLen = strlen(Buffer);
+    str = Buffer;
+    while (MsgLen--)
+    {
+        if (*str == '\n' && CurChar != '\r')
+            CharPrint('\r');
+
+        CurChar = *str++;
+        CharPrint(CurChar);
+    }
+
+#ifndef WIN2K_COMPLIANT
+    /* Free the buffer if needed */
+    if (Buffer != StaticBuffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
+#endif
+}
+
 static VOID
 ConsoleCleanup(VOID);
 
@@ -877,7 +947,7 @@ wmain(INT argc, WCHAR *argv[])
     }
 #endif
 
-    /* Load global VDM settings */
+    /* Load the global VDM settings */
     LoadGlobalSettings(&GlobalSettings);
 
     DPRINT1("\n\n\nNTVDM - Starting...\n\n\n");
index d43a26e..e1d48e4 100644 (file)
@@ -94,7 +94,10 @@ extern HWND hConsoleWnd;
 /*
  * Interface functions
  */
+typedef VOID (*CHAR_PRINT)(IN CHAR Character);
 VOID DisplayMessage(IN LPCWSTR Format, ...);
+VOID PrintMessageAnsi(IN CHAR_PRINT CharPrint,
+                      IN LPCSTR Format, ...);
 
 /*static*/ VOID
 CreateVdmMenu(HANDLE ConOutHandle);