/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

    entry.c

Abstract:

    x86-specific startup for setupldr

Author:

    John Vert (jvert) 14-Oct-1993

Revision History:

--*/
#include "bootx86.h"
#include "stdio.h"
#include "flop.h"
#include <ramdisk.h>

#if 0
#define DBGOUT(x)   BlPrint x
#define DBGPAUSE    while(!GET_KEY());
#else
#define DBGOUT(x)
#define DBGPAUSE
#endif


//
// Prototypes for Internal Routines
//

VOID
DoGlobalInitialization(
    PBOOT_CONTEXT
    );

#if defined(ELTORITO)
BOOLEAN ElToritoCDBoot = FALSE;
#endif

extern CHAR NetBootPath[];

//
// Global context pointers. These are passed to us by the SU module or
// the bootstrap code.
//

PCONFIGURATION_COMPONENT_DATA FwConfigurationTree = NULL;
PEXTERNAL_SERVICES_TABLE ExternalServicesTable;
UCHAR BootPartitionName[80];
ULONG MachineType = 0;
ULONG OsLoaderBase;
ULONG OsLoaderExports;
extern PUCHAR BlpResourceDirectory;
extern PUCHAR BlpResourceFileOffset;
ULONG PcrBasePage;
ULONG TssBasePage;

ULONG BootFlags = 0;

ULONG NtDetectStart = 0;
ULONG NtDetectEnd = 0;

#ifdef FORCE_CD_BOOT

BOOLEAN
BlGetCdRomDrive(
  PUCHAR DriveId
  )
{
  BOOLEAN Result = FALSE;
  UCHAR Id = 0;

  do {
    if (BlIsElToritoCDBoot(Id)) {
      *DriveId = Id;
      Result = TRUE;

      break;
    }

    Id++;
  }
  while (Id != 0);

  return Result;
}

#endif

VOID
NtProcessStartup(
    IN PBOOT_CONTEXT BootContextRecord
    )
/*++

Routine Description:

    Main entry point for setup loader. Control is transferred here by the
    start-up (SU) module.

Arguments:

    BootContextRecord - Supplies the boot context, particularly the
        ExternalServicesTable.

Returns:

    Does not return. Control eventually passed to the kernel.


--*/
{
    ARC_STATUS Status;

#ifdef FORCE_CD_BOOT
    BOOLEAN CdFound;
    UCHAR CdId;
#endif

    ULONG_PTR pFirmwareHeapAddress;
    ULONG TssSize,TssPages;

    //
    // Initialize the boot loader's video
    //

    DoGlobalInitialization(BootContextRecord);

    BlFillInSystemParameters(BootContextRecord);

    //
    // Set the global bootflags
    //
    BootFlags = BootContextRecord->BootFlags;

#ifdef FORCE_CD_BOOT
    CdFound = BlGetCdRomDrive(&CdId);

    if (CdFound) {
      BlPrint("CD/DVD-Rom drive found with id:%u\n", CdId);
      BootContextRecord->FSContextPointer->BootDrive = CdId;
    } else {
      BlPrint("CD/DVD-Rom drive not found");
    }
#endif  // for FORCE_CD_BOOT

    if (BootContextRecord->FSContextPointer->BootDrive == 0) {

        //
        // Boot was from A:
        //

        strcpy(BootPartitionName,"multi(0)disk(0)fdisk(0)");

        //
        // To get around an apparent bug on the BIOS of some MCA machines
        // (specifically the NCR 386sx/MC20 w/ BIOS version 1.04.00 (3421),
        // Phoenix BIOS 1.02.07), whereby the first int13 to floppy results
        // in a garbage buffer, reset drive 0 here.
        //

        GET_SECTOR(0,0,0,0,0,0,NULL);

    } else if (BootContextRecord->FSContextPointer->BootDrive == 0x40) {

        //
        // Boot was from the net
        //

        strcpy(BootPartitionName,"net(0)");
        BlBootingFromNet = TRUE;

#if defined(REMOTE_BOOT)
        BlGetActivePartition(NetBootActivePartitionName);
        NetFindCSCPartitionName();
#endif

    } else if (BootContextRecord->FSContextPointer->BootDrive == 0x41) {

        //
        // Boot was from an SDI image
        //

        strcpy(BootPartitionName,"ramdisk(0)");

    } else if (BlIsElToritoCDBoot(BootContextRecord->FSContextPointer->BootDrive)) {

        //
        // Boot was from El Torito CD
        //

        sprintf(BootPartitionName, "multi(0)disk(0)cdrom(%u)", BootContextRecord->FSContextPointer->BootDrive);
        ElToritoCDBoot = TRUE;

    } else {

        //
        // Find the partition we have been booted from.  Note that this
        // is *NOT* necessarily the active partition.  If the system has
        // Boot Mangler installed, it will be the active partition, and
        // we have to go figure out what partition we are actually on.
        //
        BlGetActivePartition(BootPartitionName);

#if defined(REMOTE_BOOT)
        strcpy(NetBootActivePartitionName, BootPartitionName);
        NetFindCSCPartitionName();
#endif

    }


    //
    // We need to make sure that we've got a signature on disk 80.
    // If not, then write one.
    //
    {
    ULONG   DriveId;
    ULONG   NewSignature;
    UCHAR SectorBuffer[4096+256];
    PUCHAR Sector;
    LARGE_INTEGER SeekValue;
    ULONG Count;
    

        Status = ArcOpen("multi(0)disk(0)rdisk(0)partition(0)", ArcOpenReadWrite, &DriveId);

        if (Status == ESUCCESS) {

            //
            // Get a reasonably unique seed to start with.
            //
            NewSignature = ArcGetRelativeTime();
            NewSignature = (NewSignature & 0xFFFF) << 16;
            NewSignature += ArcGetRelativeTime();

            //
            // Now we have a valid new signature to put on the disk.
            // Read the sector off disk, put the new signature in,
            // write the sector back, and recompute the checksum.
            //
            Sector = ALIGN_BUFFER(SectorBuffer);
            SeekValue.QuadPart = 0;
            Status = ArcSeek(DriveId, &SeekValue, SeekAbsolute);
            if (Status == ESUCCESS) {
                Status = ArcRead(DriveId,Sector,512,&Count);

                if( Status == ESUCCESS ) {
                    if( ((PULONG)Sector)[PARTITION_TABLE_OFFSET/2-1] == 0 ) {
                        //
                        // He's 0.  Write a real signature in there.
                        //

                        ((PULONG)Sector)[PARTITION_TABLE_OFFSET/2-1] = NewSignature;

                        Status = ArcSeek(DriveId, &SeekValue, SeekAbsolute);
                        if (Status == ESUCCESS) {
                            Status = ArcWrite(DriveId,Sector,512,&Count);
                            if( Status != ESUCCESS ) {
#if DBG
                                BlPrint( "Falied to write the new signature on the boot partition.\n" );
#endif
                            }
                        } else {
#if DBG
                            BlPrint( "Failed second ArcSeek on the boot partition to check for a signature.\n" );
#endif
                        }
                    }
                } else {
#if DBG
                    BlPrint( "Failed to ArcRead the boot partition to check for a signature.\n" );
#endif
                }
            } else {
#if DBG
                BlPrint( "Failed to ArcSeek the boot partition to check for a signature.\n" );
#endif
            }

            ArcClose(DriveId);
        } else {
#if DBG
            BlPrint( "Couldn't Open the boot partition to check for a signature.\n" );
#endif
        }
    }

    //
    // squirrel away some memory for the PCR and TSS so that we get the 
    // preferred memory location (<16MB) for this data.
    //
    pFirmwareHeapAddress = (ULONG_PTR)FwAllocateHeapPermanent( 2 );
    if (!pFirmwareHeapAddress) {
        BlPrint("Couldn't allocate memory for PCR\n");
        goto BootFailed;
    }
    PcrBasePage = (ULONG)(pFirmwareHeapAddress>>PAGE_SHIFT);

    TssSize = (sizeof(KTSS) + PAGE_SIZE) & ~(PAGE_SIZE - 1);
    TssPages = TssSize / PAGE_SIZE;
    pFirmwareHeapAddress = (ULONG_PTR)FwAllocateHeapPermanent( TssPages );
    if (!pFirmwareHeapAddress) {
        BlPrint("Couldn't allocate memory for TSS\n");
        goto BootFailed;
    }
    TssBasePage = (ULONG)(pFirmwareHeapAddress>>PAGE_SHIFT);

    //
    // Initialize the memory descriptor list, the OS loader heap, and the
    // OS loader parameter block.
    //
    Status = BlMemoryInitialize();
    if (Status != ESUCCESS) {
        BlPrint("Couldn't initialize memory\n");
        goto BootFailed;
    }

    //
    // Initialize the OS loader I/O system.
    //

    AEInitializeStall();

    Status = BlIoInitialize();
    if (Status != ESUCCESS) {
        BlPrint("Couldn't initialize I/O\n");
        goto BootFailed;
    }

    //
    // Call off to regular startup code
    //
    BlStartup(BootPartitionName);

    //
    // we should never get here!
    //
BootFailed:
    if (BootFlags & BOOTFLAG_REBOOT_ON_FAILURE) {
        ULONG StartTime = ArcGetRelativeTime();
        BlPrint(TEXT("\nRebooting in 5 seconds...\n"));
        while ( ArcGetRelativeTime() - StartTime < 5) {}
        ArcRestart();      
    }

    do {
        GET_KEY();  // BOOT FAILED!
        if (BlTerminalHandleLoaderFailure()) {
           ArcRestart();
        }
    } while ( 1 );

}

BOOLEAN
BlDetectHardware(
    IN ULONG DriveId,
    IN PCHAR LoadOptions
    )

/*++

Routine Description:

    Loads and runs NTDETECT.COM to populate the ARC configuration tree.

Arguments:

    DriveId - Supplies drive id where NTDETECT is located.

    LoadOptions - Supplies Load Options string to ntdetect.

Return Value:

    TRUE - NTDETECT successfully run.

    FALSE - Error

--*/

{

// Current Loader stack size is 8K, so make sure you do not
// blow that space. Make sure this is not smaller than 140.
#define LOAD_OPTIONS_BUFFER_SIZE 512

    ARC_STATUS Status;
    PCONFIGURATION_COMPONENT_DATA TempFwTree;
    ULONG TempFwHeapUsed;
    ULONG FileSize;
    ULONG DetectFileId;
    FILE_INFORMATION FileInformation;
    PUCHAR DetectionBuffer = (PUCHAR)DETECTION_LOADED_ADDRESS;
    PUCHAR Options = NULL;
    UCHAR Buffer[LOAD_OPTIONS_BUFFER_SIZE];
    LARGE_INTEGER SeekPosition;
    ULONG Read;
    BOOLEAN SkipLegacyDetection;
    BOOLEAN Success = FALSE;
    ULONG HeapStart;
    ULONG HeapSize;
    ULONG RequiredLength = 0;

    //
    // Check if the ntdetect.com was bundled as a data section
    // in the loader executable.
    //
    if (NtDetectStart == 0) {

        //
        // Now check if we have ntdetect.com in the root directory, if yes,
        // we will load it to predefined location and transfer control to
        // it.
        //

#if defined(ELTORITO)
        if (ElToritoCDBoot) {
            // we assume ntdetect.com is in the i386 directory
            Status = BlOpen( DriveId,
                             "\\i386\\ntdetect.com",
                             ArcOpenReadOnly,
                             &DetectFileId );
        } else {
#endif

        if (BlBootingFromNet
#if defined(REMOTE_BOOT)
            && NetworkBootRom
#endif // defined(REMOTE_BOOT)
            ) {
    
            strcpy(Buffer, NetBootPath);
    
#if defined(REMOTE_BOOT)
            //
            // This is the way it was done for remote BOOT, where we were
            // booting out of a client's machine directory.
            //
            strcat(Buffer, "BootDrive\\ntdetect.com");
#else
            //
            // This is how it is done for remote INSTALL, where we are
            // booting out of the templates directory under a setup directory.
            //
            strcat(Buffer, "ntdetect.com");
#endif // defined(REMOTE_BOOT)
    
            Status = BlOpen( DriveId,
                             Buffer,
                             ArcOpenReadOnly,
                             &DetectFileId );
        } else {
            Status = BlOpen( DriveId,
                             "\\ntdetect.com",
                             ArcOpenReadOnly,
                             &DetectFileId );
        }
#if defined(ELTORITO)
        }
#endif

        if (Status != ESUCCESS) {
#if DBG
            BlPrint("Error opening NTDETECT.COM, status = %x\n", Status);
            BlPrint("Press any key to continue\n");
#endif
            goto Exit;
        }
    
        //
        // Determine the length of the ntdetect.com file
        //
    
        Status = BlGetFileInformation(DetectFileId, &FileInformation);
        if (Status != ESUCCESS) {
            BlClose(DetectFileId);
#if DBG
            BlPrint("Error getting NTDETECT.COM file information, status = %x\n", Status);
            BlPrint("Press any key to continue\n");
#endif
            goto Exit;
        }
    
        FileSize = FileInformation.EndingAddress.LowPart;
        if (FileSize == 0) {
            BlClose(DetectFileId);
#if DBG
            BlPrint("Error: size of NTDETECT.COM is zero.\n");
            BlPrint("Press any key to continue\n");
#endif
            goto Exit;
        }
    
        SeekPosition.QuadPart = 0;
        Status = BlSeek(DetectFileId,
                        &SeekPosition,
                        SeekAbsolute);
        if (Status != ESUCCESS) {
            BlClose(DetectFileId);
#if DBG
            BlPrint("Error seeking to start of NTDETECT.COM file\n");
            BlPrint("Press any key to continue\n");
#endif
            goto Exit;
        }
        
        Status = BlRead( DetectFileId,
                         DetectionBuffer,
                         FileSize,
                         &Read );
    
        BlClose(DetectFileId);
        if (Status != ESUCCESS) {
#if DBG
            BlPrint("Error reading from NTDETECT.COM\n");
            BlPrint("Read %lx bytes\n",Read);
            BlPrint("Press any key to continue\n");
#endif
            goto Exit;
        }
    } else {

        // ntdetect.com was bundled in the loader image
        // as a data section. We will use it contents
        // instead of opening the file.
        RtlCopyMemory( DetectionBuffer, (PVOID)NtDetectStart, NtDetectEnd - NtDetectStart );
    }
    
    //
    // Set the heap start and size used by ntdetect
    //
    HeapStart = (TEMPORARY_HEAP_START - 0x10) * PAGE_SIZE;
    HeapSize = 0x10000; // 64K

    //
    // We need to pass NTDETECT pointers < 1Mb, so
    // use local storage off the stack if possible.  (which is
    // always < 1Mb.) If not possible (boot.ini is too big)
    // and we will add it to the heap used by ntdetect.com, thereby
    // reducing the heap space used by ntdetect.com
    //
    if ( LoadOptions ) {
        // count the characters in LoadOptions + null terminator + 
        // room for " NOLEGACY" that might be appended later
        RequiredLength = strlen(LoadOptions) + strlen(" NOLEGACY") + 1;

        // check if the buffer on the stack is big enough
        if ( RequiredLength > LOAD_OPTIONS_BUFFER_SIZE ) {
            //
            // Buffer is too small. let move it to the 
            // end of the ntdetect heap
            //
            Options = (PCHAR)( HeapStart + HeapSize - RequiredLength );
            HeapSize -= RequiredLength;

            strcpy( Options, LoadOptions );
            
        } else {
            //
            // Load options will fit on the stack. copy them there
            //
            strcpy( Buffer, LoadOptions );
            Options = Buffer;

        }
    } else {
        //
        // No load options
        //
        Options = NULL;
    }

    //
    // Check whether we need to add the NOLEGACY option
    //
    if (BlDetectLegacyFreeBios()) {
        if (Options != NULL) {
            strcat(Options, " NOLEGACY");
        } else {
            strcpy(Buffer, " NOLEGACY");
            Options = Buffer;
        }
    }
    
    DETECT_HARDWARE((ULONG)HeapStart,
                    (ULONG)HeapSize,
                    (PVOID)&TempFwTree,
                    (PULONG)&TempFwHeapUsed,
                    (PCHAR)Options,
                    (Options != NULL) ? strlen(Options) : 0
                    );

    FwConfigurationTree = TempFwTree;

    Status = BlpMarkExtendedVideoRegionOffLimits();
    
    Success = (BOOLEAN)(Status == ESUCCESS);

Exit:

    //
    // Reinitialize the headless port - detect wipes it out.
    //
    BlInitializeHeadlessPort();

    return(Success);
}


VOID
DoGlobalInitialization(
    IN PBOOT_CONTEXT BootContextRecord
    )

/*++

Routine Description

    This routine calls all of the subsytem initialization routines.


Arguments:

    None

Returns:

    Nothing

--*/

{
    ARC_STATUS Status;

    //
    // Set base address of OS Loader image for the debugger.
    //

    OsLoaderBase = BootContextRecord->OsLoaderBase;
    OsLoaderExports = BootContextRecord->OsLoaderExports;

    //
    // Initialize memory.
    //

    Status = InitializeMemorySubsystem(BootContextRecord);
    if (Status != ESUCCESS) {
        BlPrint("InitializeMemory failed %lx\n",Status);
        while (1) {
        }
    }
    ExternalServicesTable=BootContextRecord->ExternalServicesTable;
    MachineType = BootContextRecord->MachineType;

    //
    // Turn the cursor off
    //

    HW_CURSOR(0,127);

    BlpResourceDirectory = (PUCHAR)(BootContextRecord->ResourceDirectory);
    BlpResourceFileOffset = (PUCHAR)(BootContextRecord->ResourceOffset);

    NtDetectStart = BootContextRecord->NtDetectStart;
    NtDetectEnd = BootContextRecord->NtDetectEnd;

    //
    // If this is an SDI boot, copy the address of the SDI image out of the
    // boot context record. SdiAddress is declared in boot\inc\ramdisk.h and
    // initialized to 0 in boot\lib\ramdisk.c.
    //

    if (BootContextRecord->FSContextPointer->BootDrive == 0x41) {
        SdiAddress = BootContextRecord->SdiAddress;
    }

    InitializeMemoryDescriptors ();
}


VOID
BlGetActivePartition(
    OUT PUCHAR BootPartitionName
    )

/*++

Routine Description:

    Determine the ARC name for the partition NTLDR was started from

Arguments:

    BootPartitionName - Supplies a buffer where the ARC name of the
        partition will be returned.

Return Value:

    Name of the partition is in BootPartitionName.

    Must always succeed.
--*/

{
    UCHAR SectorBuffer[512];
    ARC_STATUS Status;
    ULONG FileId;
    ULONG Count;
    int i;

    //
    // The boot sector used to boot us is still in memory at 0x7c00.
    // The hidden sectors field in the BPB is pretty much guaranteed
    // to be intact, since all boot codes use that field and thus
    // are unlikely to have overwritten it.
    // We open each partition and compare the in-memory hidden sector count
    // at 0x7c1c to the hidden sector value in the BPB.
    //
    i = 1;
    do {

        sprintf(BootPartitionName,"multi(0)disk(0)rdisk(0)partition(%u)",i);

        Status = ArcOpen(BootPartitionName,ArcOpenReadOnly,&FileId);
        if(Status == ESUCCESS) {

            //
            // Read the first part of the partition.
            //
            Status = ArcRead(FileId,SectorBuffer,512,&Count);
            ArcClose(FileId);
            if((Status == ESUCCESS) && !memcmp(SectorBuffer+0x1c,(PVOID)0x7c1c,4)) {
                //
                // Found it, BootPartitionName is already set for return.
                //
                return;
            }

            Status = ESUCCESS;
        }

        i++;

    } while (Status == ESUCCESS);

    //
    // Run out of partitions without finding match. Fall back on partition 1.
    //
    strcpy(BootPartitionName,"multi(0)disk(0)rdisk(0)partition(1)");
}


BOOLEAN
BlIsElToritoCDBoot(
    UCHAR DriveNum
    )
{

    //
    // Note, even though args are short, they are pushed on the stack with
    // 32bit alignment so the effect on the stack seen by the 16bit real
    // mode code is the same as if we were pushing longs here.
    //
    // GET_ELTORITO_STATUS is 0 if we are in emulation mode

    if (DriveNum > 0x81) {
        if (!GET_ELTORITO_STATUS(FwDiskCache,DriveNum)) {
            return(TRUE);
        } else {
            return(FALSE);
        }
    } else {
        return(FALSE);
    }
}

#if defined(REMOTE_BOOT)
BOOLEAN
NetFindCSCPartitionName(
    )
{
    UCHAR FileName[80];
    UCHAR DiskName[80];
    UCHAR PartitionName[80];
    PUCHAR p;
    ULONG Part;
    ULONG FileId;
    ULONG DeviceId;

    if (NetBootSearchedForCSC) {
        return((BOOLEAN)strlen(NetBootCSCPartitionName));
    }

    if (!strlen(NetBootActivePartitionName)) {
        BlGetActivePartition(NetBootActivePartitionName);
    }

    strcpy(DiskName, NetBootActivePartitionName);
    p = strstr(DiskName, "partition");
    ASSERT( p != NULL );
    *p = '\0';

    Part = 1;
    while (TRUE) {

        sprintf(PartitionName, "%spartition(%u)", DiskName, Part);
        if (ArcOpen(PartitionName, ArcOpenReadOnly, &DeviceId) != ESUCCESS) {
            break;
        }
        ArcClose(DeviceId);

        sprintf(FileName,
                "%s%s",
                PartitionName,
                REMOTE_BOOT_IMIRROR_PATH_A REMOTE_BOOT_CSC_SUBDIR_A);

        if (ArcOpen(FileName, ArcOpenReadOnly, &FileId) == ESUCCESS) {
            ArcClose(FileId);
            NetBootSearchedForCSC = TRUE;
            strcpy(NetBootCSCPartitionName, PartitionName);
            return TRUE;
        }
        ArcClose(FileId);
        Part++;
    }

    strcpy(NetBootCSCPartitionName, NetBootActivePartitionName);
    return FALSE;
}
#endif // defined(REMOTE_BOOT)