/** @file

Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>

SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "LegacyBiosInterface.h"

#define PHYSICAL_ADDRESS_TO_POINTER(Address)  ((VOID *) ((UINTN) Address))

//
// define maximum number of HDD system supports
//
#define MAX_HDD_ENTRIES 0x30

//
// Module Global:
//  Since this driver will only ever produce one instance of the Private Data
//  protocol you are not required to dynamically allocate the PrivateData.
//
LEGACY_BIOS_INSTANCE  mPrivateData;

//
// The SMBIOS table in EfiRuntimeServicesData memory
//
VOID                  *mRuntimeSmbiosEntryPoint = NULL;

//
// The SMBIOS table in EfiReservedMemoryType memory
//
EFI_PHYSICAL_ADDRESS  mReserveSmbiosEntryPoint = 0;
EFI_PHYSICAL_ADDRESS  mStructureTableAddress   = 0;
UINTN                 mStructureTablePages     = 0;
BOOLEAN               mEndOfDxe                = FALSE;

/**
  Allocate memory for legacy usage. The memory is executable.

  @param  AllocateType               The type of allocation to perform.
  @param  MemoryType                 The type of memory to allocate.
  @param  StartPageAddress           Start address of range
  @param  Pages                      Number of pages to allocate
  @param  Result                     Result of allocation

  @retval EFI_SUCCESS                Legacy memory is allocated successfully.
  @retval Other                      Legacy memory is not allocated.

**/
EFI_STATUS
AllocateLegacyMemory (
  IN  EFI_ALLOCATE_TYPE         AllocateType,
  IN  EFI_MEMORY_TYPE           MemoryType,
  IN  EFI_PHYSICAL_ADDRESS      StartPageAddress,
  IN  UINTN                     Pages,
  OUT EFI_PHYSICAL_ADDRESS      *Result
  )
{
  EFI_STATUS                      Status;
  EFI_PHYSICAL_ADDRESS            MemPage;
  EFI_GCD_MEMORY_SPACE_DESCRIPTOR MemDesc;

  //
  // Allocate Pages of memory less <= StartPageAddress
  //
  MemPage = (EFI_PHYSICAL_ADDRESS) (UINTN) StartPageAddress;
  Status = gBS->AllocatePages (
                  AllocateType,
                  MemoryType,
                  Pages,
                  &MemPage
                  );
  //
  // Do not ASSERT on Status error but let caller decide since some cases
  // memory is already taken but that is ok.
  //
  if (!EFI_ERROR (Status)) {
    if (MemoryType != EfiBootServicesCode) {
      //
      // Make sure that the buffer can be used to store code.
      //
      Status = gDS->GetMemorySpaceDescriptor (MemPage, &MemDesc);
      if (!EFI_ERROR (Status) && (MemDesc.Attributes & EFI_MEMORY_XP) != 0) {
        Status = gDS->SetMemorySpaceAttributes (
                        MemPage,
                        EFI_PAGES_TO_SIZE (Pages),
                        MemDesc.Attributes & (~EFI_MEMORY_XP)
                        );
      }
      if (EFI_ERROR (Status)) {
        gBS->FreePages (MemPage, Pages);
      }
    }
  }

  if (!EFI_ERROR (Status)) {
    *Result = (EFI_PHYSICAL_ADDRESS) (UINTN) MemPage;
  }

  return Status;
}


/**
  This function is called when EFI needs to reserve an area in the 0xE0000 or 0xF0000
  64 KB blocks.

  Note: inconsistency with the Framework CSM spec. Per the spec, this function may be
  invoked only once. This limitation is relaxed to allow multiple calls in this implemenation.

  @param  This                       Protocol instance pointer.
  @param  LegacyMemorySize           Size of required region
  @param  Region                     Region to use. 00 = Either 0xE0000 or 0xF0000
                                     block Bit0 = 1 0xF0000 block Bit1 = 1 0xE0000
                                     block
  @param  Alignment                  Address alignment. Bit mapped. First non-zero
                                     bit from right is alignment.
  @param  LegacyMemoryAddress        Region Assigned

  @retval EFI_SUCCESS                Region assigned
  @retval EFI_ACCESS_DENIED          Procedure previously invoked
  @retval Other                      Region not assigned

**/
EFI_STATUS
EFIAPI
LegacyBiosGetLegacyRegion (
  IN    EFI_LEGACY_BIOS_PROTOCOL *This,
  IN    UINTN                    LegacyMemorySize,
  IN    UINTN                    Region,
  IN    UINTN                    Alignment,
  OUT   VOID                     **LegacyMemoryAddress
  )
{

  LEGACY_BIOS_INSTANCE  *Private;
  EFI_IA32_REGISTER_SET Regs;
  EFI_STATUS            Status;
  UINT32                Granularity;

  Private = LEGACY_BIOS_INSTANCE_FROM_THIS (This);
  Private->LegacyRegion->UnLock (Private->LegacyRegion, 0xE0000, 0x20000, &Granularity);

  ZeroMem (&Regs, sizeof (EFI_IA32_REGISTER_SET));
  Regs.X.AX = Legacy16GetTableAddress;
  Regs.X.BX = (UINT16) Region;
  Regs.X.CX = (UINT16) LegacyMemorySize;
  Regs.X.DX = (UINT16) Alignment;
  Private->LegacyBios.FarCall86 (
     &Private->LegacyBios,
     Private->Legacy16CallSegment,
     Private->Legacy16CallOffset,
     &Regs,
     NULL,
     0
     );

  if (Regs.X.AX == 0) {
    *LegacyMemoryAddress  = (VOID *) (((UINTN) Regs.X.DS << 4) + Regs.X.BX);
    Status = EFI_SUCCESS;
  } else {
    Status = EFI_OUT_OF_RESOURCES;
  }

  Private->Cpu->FlushDataCache (Private->Cpu, 0xE0000, 0x20000, EfiCpuFlushTypeWriteBackInvalidate);
  Private->LegacyRegion->Lock (Private->LegacyRegion, 0xE0000, 0x20000, &Granularity);

  return Status;
}


/**
  This function is called when copying data to the region assigned by
  EFI_LEGACY_BIOS_PROTOCOL.GetLegacyRegion().

  @param  This                       Protocol instance pointer.
  @param  LegacyMemorySize           Size of data to copy
  @param  LegacyMemoryAddress        Legacy Region destination address Note: must
                                     be in region assigned by
                                     LegacyBiosGetLegacyRegion
  @param  LegacyMemorySourceAddress  Source of data

  @retval EFI_SUCCESS                The data was copied successfully.
  @retval EFI_ACCESS_DENIED          Either the starting or ending address is out of bounds.
**/
EFI_STATUS
EFIAPI
LegacyBiosCopyLegacyRegion (
  IN EFI_LEGACY_BIOS_PROTOCOL *This,
  IN    UINTN                 LegacyMemorySize,
  IN    VOID                  *LegacyMemoryAddress,
  IN    VOID                  *LegacyMemorySourceAddress
  )
{

  LEGACY_BIOS_INSTANCE  *Private;
  UINT32                Granularity;

  if ((LegacyMemoryAddress < (VOID *)(UINTN)0xE0000 ) ||
      ((UINTN) LegacyMemoryAddress + LegacyMemorySize > (UINTN) 0x100000)
        ) {
    return EFI_ACCESS_DENIED;
  }
  //
  // There is no protection from writes over lapping if this function is
  // called multiple times.
  //
  Private = LEGACY_BIOS_INSTANCE_FROM_THIS (This);
  Private->LegacyRegion->UnLock (Private->LegacyRegion, 0xE0000, 0x20000, &Granularity);
  CopyMem (LegacyMemoryAddress, LegacyMemorySourceAddress, LegacyMemorySize);

  Private->Cpu->FlushDataCache (Private->Cpu, 0xE0000, 0x20000, EfiCpuFlushTypeWriteBackInvalidate);
  Private->LegacyRegion->Lock (Private->LegacyRegion, 0xE0000, 0x20000, &Granularity);

  return EFI_SUCCESS;
}


/**
  Find Legacy16 BIOS image in the FLASH device and shadow it into memory. Find
  the $EFI table in the shadow area. Thunk into the Legacy16 code after it had
  been shadowed.

  @param  Private                    Legacy BIOS context data

  @retval EFI_SUCCESS                Legacy16 code loaded
  @retval Other                      No protocol installed, unload driver.

**/
EFI_STATUS
ShadowAndStartLegacy16 (
  IN  LEGACY_BIOS_INSTANCE  *Private
  )
{
  EFI_STATUS                        Status;
  UINT8                             *Ptr;
  UINT8                             *PtrEnd;
  BOOLEAN                           Done;
  EFI_COMPATIBILITY16_TABLE         *Table;
  UINT8                             CheckSum;
  EFI_IA32_REGISTER_SET             Regs;
  EFI_TO_COMPATIBILITY16_INIT_TABLE *EfiToLegacy16InitTable;
  EFI_TO_COMPATIBILITY16_BOOT_TABLE *EfiToLegacy16BootTable;
  VOID                              *LegacyBiosImage;
  UINTN                             LegacyBiosImageSize;
  UINTN                             E820Size;
  UINT32                            *ClearPtr;
  BBS_TABLE                         *BbsTable;
  LEGACY_EFI_HDD_TABLE              *LegacyEfiHddTable;
  UINTN                             Index;
  UINT32                            TpmPointer;
  VOID                              *TpmBinaryImage;
  UINTN                             TpmBinaryImageSize;
  UINTN                             Location;
  UINTN                             Alignment;
  UINTN                             TempData;
  EFI_PHYSICAL_ADDRESS              Address;
  UINT16                            OldMask;
  UINT16                            NewMask;
  UINT32                            Granularity;
  EFI_GCD_MEMORY_SPACE_DESCRIPTOR   Descriptor;

  Location  = 0;
  Alignment = 0;

  //
  // we allocate the C/D/E/F segment as RT code so no one will use it any more.
  //
  Address = 0xC0000;
  gDS->GetMemorySpaceDescriptor (Address, &Descriptor);
  if (Descriptor.GcdMemoryType == EfiGcdMemoryTypeSystemMemory) {
    //
    // If it is already reserved, we should be safe, or else we allocate it.
    //
    Status = gBS->AllocatePages (
                    AllocateAddress,
                    EfiRuntimeServicesCode,
                    0x40000/EFI_PAGE_SIZE,
                    &Address
                    );
    if (EFI_ERROR (Status)) {
      //
      // Bugbug: need to figure out whether C/D/E/F segment should be marked as reserved memory.
      //
      DEBUG ((DEBUG_ERROR, "Failed to allocate the C/D/E/F segment Status = %r", Status));
    }
  }

  //
  // start testtest
  //    GetTimerValue (&Ticker);
  //
  //  gRT->SetVariable (L"StartLegacy",
  //                    &gEfiGlobalVariableGuid,
  //                    EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
  //                    sizeof (UINT64),
  //                    (VOID *)&Ticker
  //                    );
  // end testtest
  //
  EfiToLegacy16BootTable = &Private->IntThunk->EfiToLegacy16BootTable;
  Status = Private->LegacyBiosPlatform->GetPlatformInfo (
                                          Private->LegacyBiosPlatform,
                                          EfiGetPlatformBinarySystemRom,
                                          &LegacyBiosImage,
                                          &LegacyBiosImageSize,
                                          &Location,
                                          &Alignment,
                                          0,
                                          0
                                          );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Private->BiosStart            = (UINT32) (0x100000 - LegacyBiosImageSize);
  Private->OptionRom            = 0xc0000;
  Private->LegacyBiosImageSize  = (UINT32) LegacyBiosImageSize;

  //
  // Can only shadow into memory allocated for legacy useage.
  //
  ASSERT (Private->BiosStart > Private->OptionRom);

  //
  // Shadow Legacy BIOS. Turn on memory and copy image
  //
  Private->LegacyRegion->UnLock (Private->LegacyRegion, 0xc0000, 0x40000, &Granularity);

  ClearPtr = (VOID *) ((UINTN) 0xc0000);

  //
  // Initialize region from 0xc0000 to start of BIOS to all ffs. This allows unused
  // regions to be used by EMM386 etc.
  //
  SetMem ((VOID *) ClearPtr, (UINTN) (0x40000 - LegacyBiosImageSize), 0xff);

  TempData = Private->BiosStart;

  CopyMem (
    (VOID *) TempData,
    LegacyBiosImage,
    (UINTN) LegacyBiosImageSize
    );

  Private->Cpu->FlushDataCache (Private->Cpu, 0xc0000, 0x40000, EfiCpuFlushTypeWriteBackInvalidate);

  //
  // Search for Legacy16 table in Shadowed ROM
  //
  Done  = FALSE;
  Table = NULL;
  for (Ptr = (UINT8 *) TempData; Ptr < (UINT8 *) ((UINTN) 0x100000) && !Done; Ptr += 0x10) {
    if (*(UINT32 *) Ptr == SIGNATURE_32 ('I', 'F', 'E', '$')) {
      Table   = (EFI_COMPATIBILITY16_TABLE *) Ptr;
      PtrEnd  = Ptr + Table->TableLength;
      for (CheckSum = 0; Ptr < PtrEnd; Ptr++) {
        CheckSum = (UINT8) (CheckSum +*Ptr);
      }

      Done = TRUE;
    }
  }

  if (Table == NULL) {
    DEBUG ((EFI_D_ERROR, "No Legacy16 table found\n"));
    return EFI_NOT_FOUND;
  }

  if (!Done) {
    //
    // Legacy16 table header checksum error.
    //
    DEBUG ((EFI_D_ERROR, "Legacy16 table found with bad talbe header checksum\n"));
  }

  //
  // Remember location of the Legacy16 table
  //
  Private->Legacy16Table            = Table;
  Private->Legacy16CallSegment      = Table->Compatibility16CallSegment;
  Private->Legacy16CallOffset       = Table->Compatibility16CallOffset;
  EfiToLegacy16InitTable            = &Private->IntThunk->EfiToLegacy16InitTable;
  Private->Legacy16InitPtr          = EfiToLegacy16InitTable;
  Private->Legacy16BootPtr          = &Private->IntThunk->EfiToLegacy16BootTable;
  Private->InternalIrqRoutingTable  = NULL;
  Private->NumberIrqRoutingEntries  = 0;
  Private->BbsTablePtr              = NULL;
  Private->LegacyEfiHddTable        = NULL;
  Private->DiskEnd                  = 0;
  Private->Disk4075                 = 0;
  Private->HddTablePtr              = &Private->IntThunk->EfiToLegacy16BootTable.HddInfo;
  Private->NumberHddControllers     = MAX_IDE_CONTROLLER;
  Private->Dump[0]                  = 'D';
  Private->Dump[1]                  = 'U';
  Private->Dump[2]                  = 'M';
  Private->Dump[3]                  = 'P';

  ZeroMem (
    Private->Legacy16BootPtr,
    sizeof (EFI_TO_COMPATIBILITY16_BOOT_TABLE)
    );

  //
  // Store away a copy of the EFI System Table
  //
  Table->EfiSystemTable = (UINT32) (UINTN) gST;

  //
  // IPF CSM integration -Bug
  //
  // Construct the Legacy16 boot memory map. This sets up number of
  // E820 entries.
  //
  LegacyBiosBuildE820 (Private, &E820Size);
  //
  // Initialize BDA and EBDA standard values needed to load Legacy16 code
  //
  LegacyBiosInitBda (Private);
  LegacyBiosInitCmos (Private);

  //
  // All legacy interrupt should be masked when do initialization work from legacy 16 code.
  //
  Private->Legacy8259->GetMask(Private->Legacy8259, &OldMask, NULL, NULL, NULL);
  NewMask = 0xFFFF;
  Private->Legacy8259->SetMask(Private->Legacy8259, &NewMask, NULL, NULL, NULL);

  //
  // Call into Legacy16 code to do an INIT
  //
  ZeroMem (&Regs, sizeof (EFI_IA32_REGISTER_SET));
  Regs.X.AX = Legacy16InitializeYourself;
  Regs.X.ES = EFI_SEGMENT (*((UINT32 *) &EfiToLegacy16InitTable));
  Regs.X.BX = EFI_OFFSET (*((UINT32 *) &EfiToLegacy16InitTable));

  Private->LegacyBios.FarCall86 (
    &Private->LegacyBios,
    Table->Compatibility16CallSegment,
    Table->Compatibility16CallOffset,
    &Regs,
    NULL,
    0
    );

  //
  // Restore original legacy interrupt mask value
  //
  Private->Legacy8259->SetMask(Private->Legacy8259, &OldMask, NULL, NULL, NULL);

  if (Regs.X.AX != 0) {
    return EFI_DEVICE_ERROR;
  }

  //
  // start testtest
  //  GetTimerValue (&Ticker);
  //
  //  gRT->SetVariable (L"BackFromInitYourself",
  //                    &gEfiGlobalVariableGuid,
  //                    EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
  //                    sizeof (UINT64),
  //                    (VOID *)&Ticker
  //                    );
  // end testtest
  //
  // Copy E820 table after InitializeYourself is completed
  //
  ZeroMem (&Regs, sizeof (EFI_IA32_REGISTER_SET));
  Regs.X.AX = Legacy16GetTableAddress;
  Regs.X.CX = (UINT16) E820Size;
  Regs.X.DX = 1;
  Private->LegacyBios.FarCall86 (
    &Private->LegacyBios,
    Table->Compatibility16CallSegment,
    Table->Compatibility16CallOffset,
    &Regs,
    NULL,
    0
    );

  Table->E820Pointer  = (UINT32) (Regs.X.DS * 16 + Regs.X.BX);
  Table->E820Length   = (UINT32) E820Size;
  if (Regs.X.AX != 0) {
    DEBUG ((EFI_D_ERROR, "Legacy16 E820 length insufficient\n"));
  } else {
    TempData = Table->E820Pointer;
    CopyMem ((VOID *) TempData, Private->E820Table, E820Size);
  }
  //
  // Get PnPInstallationCheck Info.
  //
  Private->PnPInstallationCheckSegment  = Table->PnPInstallationCheckSegment;
  Private->PnPInstallationCheckOffset   = Table->PnPInstallationCheckOffset;

  //
  // Check if PCI Express is supported. If yes, Save base address.
  //
  Status = Private->LegacyBiosPlatform->GetPlatformInfo (
                                          Private->LegacyBiosPlatform,
                                          EfiGetPlatformPciExpressBase,
                                          NULL,
                                          NULL,
                                          &Location,
                                          &Alignment,
                                          0,
                                          0
                                          );
  if (!EFI_ERROR (Status)) {
    Private->Legacy16Table->PciExpressBase  = (UINT32)Location;
    Location = 0;
  }
  //
  // Check if TPM is supported. If yes get a region in E0000,F0000 to copy it
  // into, copy it and update pointer to binary image. This needs to be
  // done prior to any OPROM for security purposes.
  //
  Status = Private->LegacyBiosPlatform->GetPlatformInfo (
                                          Private->LegacyBiosPlatform,
                                          EfiGetPlatformBinaryTpmBinary,
                                          &TpmBinaryImage,
                                          &TpmBinaryImageSize,
                                          &Location,
                                          &Alignment,
                                          0,
                                          0
                                          );
  if (!EFI_ERROR (Status)) {

    ZeroMem (&Regs, sizeof (EFI_IA32_REGISTER_SET));
    Regs.X.AX = Legacy16GetTableAddress;
    Regs.X.CX = (UINT16) TpmBinaryImageSize;
    Regs.X.DX = 1;
    Private->LegacyBios.FarCall86 (
      &Private->LegacyBios,
      Table->Compatibility16CallSegment,
      Table->Compatibility16CallOffset,
      &Regs,
      NULL,
      0
      );

    TpmPointer = (UINT32) (Regs.X.DS * 16 + Regs.X.BX);
    if (Regs.X.AX != 0) {
      DEBUG ((EFI_D_ERROR, "TPM cannot be loaded\n"));
    } else {
      CopyMem ((VOID *) (UINTN)TpmPointer, TpmBinaryImage, TpmBinaryImageSize);
      Table->TpmSegment = Regs.X.DS;
      Table->TpmOffset  = Regs.X.BX;

    }
  }
  //
  // Lock the Legacy BIOS region
  //
  Private->Cpu->FlushDataCache (Private->Cpu, Private->BiosStart, (UINT32) LegacyBiosImageSize, EfiCpuFlushTypeWriteBackInvalidate);
  Private->LegacyRegion->Lock (Private->LegacyRegion, Private->BiosStart, (UINT32) LegacyBiosImageSize, &Granularity);

  //
  // Get the BbsTable from LOW_MEMORY_THUNK
  //
  BbsTable = (BBS_TABLE *)(UINTN)Private->IntThunk->BbsTable;
  ZeroMem ((VOID *)BbsTable, sizeof (Private->IntThunk->BbsTable));

  EfiToLegacy16BootTable->BbsTable  = (UINT32)(UINTN)BbsTable;
  Private->BbsTablePtr              = (VOID *) BbsTable;

  //
  // Populate entire table with BBS_IGNORE_ENTRY
  //
  EfiToLegacy16BootTable->NumberBbsEntries = MAX_BBS_ENTRIES;

  for (Index = 0; Index < MAX_BBS_ENTRIES; Index++) {
    BbsTable[Index].BootPriority = BBS_IGNORE_ENTRY;
  }
  //
  // Allocate space for Legacy HDD table
  //
  LegacyEfiHddTable = (LEGACY_EFI_HDD_TABLE *) AllocateZeroPool ((UINTN) MAX_HDD_ENTRIES * sizeof (LEGACY_EFI_HDD_TABLE));
  ASSERT (LegacyEfiHddTable);

  Private->LegacyEfiHddTable      = LegacyEfiHddTable;
  Private->LegacyEfiHddTableIndex = 0x00;

  //
  // start testtest
  //  GetTimerValue (&Ticker);
  //
  //  gRT->SetVariable (L"EndOfLoadFv",
  //                    &gEfiGlobalVariableGuid,
  //                    EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
  //                    sizeof (UINT64),
  //                    (VOID *)&Ticker
  //                    );
  // end testtest
  //
  return EFI_SUCCESS;
}

/**
  Shadow all legacy16 OPROMs that haven't been shadowed.
  Warning: Use this with caution. This routine disconnects all EFI
  drivers. If used externally then caller must re-connect EFI
  drivers.

  @param  This                    Protocol instance pointer.

  @retval EFI_SUCCESS             OPROMs shadowed

**/
EFI_STATUS
EFIAPI
LegacyBiosShadowAllLegacyOproms (
  IN EFI_LEGACY_BIOS_PROTOCOL *This
  )
{
  LEGACY_BIOS_INSTANCE  *Private;

  //
  //  EFI_LEGACY_BIOS_PLATFORM_PROTOCOL    *LegacyBiosPlatform;
  //  EFI_LEGACY16_TABLE                   *Legacy16Table;
  //
  Private = LEGACY_BIOS_INSTANCE_FROM_THIS (This);

  //
  //  LegacyBiosPlatform       = Private->LegacyBiosPlatform;
  //  Legacy16Table            = Private->Legacy16Table;
  //
  // Shadow PCI ROMs. We must do this near the end since this will kick
  // of Native EFI drivers that may be needed to collect info for Legacy16
  //
  //  WARNING: PciIo is gone after this call.
  //
  PciProgramAllInterruptLineRegisters (Private);

  PciShadowRoms (Private);

  //
  // Shadow PXE base code, BIS etc.
  //
  //  LegacyBiosPlatform->ShadowServiceRoms (LegacyBiosPlatform,
  //                       &Private->OptionRom,
  //                       Legacy16Table);
  //
  return EFI_SUCCESS;
}

/**
  Get the PCI BIOS interface version.

  @param  Private  Driver private data.

  @return The PCI interface version number in Binary Coded Decimal (BCD) format.
          E.g.: 0x0210 indicates 2.10, 0x0300 indicates 3.00

**/
UINT16
GetPciInterfaceVersion (
  IN LEGACY_BIOS_INSTANCE *Private
  )
{
  EFI_IA32_REGISTER_SET Reg;
  BOOLEAN               ThunkFailed;
  UINT16                PciInterfaceVersion;

  PciInterfaceVersion = 0;

  Reg.X.AX = 0xB101;
  Reg.E.EDI = 0;

  ThunkFailed = Private->LegacyBios.Int86 (&Private->LegacyBios, 0x1A, &Reg);
  if (!ThunkFailed) {
    //
    // From PCI Firmware 3.0 Specification:
    //   If the CARRY FLAG [CF] is cleared and AH is set to 00h, it is still necessary to examine the
    //   contents of [EDX] for the presence of the string "PCI" + (trailing space) to fully validate the
    //   presence of the PCI function set. [BX] will further indicate the version level, with enough
    //   granularity to allow for incremental changes in the code that don't affect the function interface.
    //   Version numbers are stored as Binary Coded Decimal (BCD) values. For example, Version 2.10
    //   would be returned as a 02h in the [BH] registers and 10h in the [BL] registers.
    //
    if ((Reg.X.Flags.CF == 0) && (Reg.H.AH == 0) && (Reg.E.EDX == SIGNATURE_32 ('P', 'C', 'I', ' '))) {
      PciInterfaceVersion = Reg.X.BX;
    }
  }
  return PciInterfaceVersion;
}

/**
  Callback function to calculate SMBIOS table size, and allocate memory for SMBIOS table.
  SMBIOS table will be copied into EfiReservedMemoryType memory in legacy boot path.

  @param  Event                 Event whose notification function is being invoked.
  @param  Context               The pointer to the notification function's context,
                                which is implementation-dependent.

**/
VOID
EFIAPI
InstallSmbiosEventCallback (
  IN EFI_EVENT                Event,
  IN VOID                     *Context
  )
{
  EFI_STATUS                  Status;
  SMBIOS_TABLE_ENTRY_POINT    *EntryPointStructure;

  //
  // Get SMBIOS table from EFI configuration table
  //
  Status = EfiGetSystemConfigurationTable (
            &gEfiSmbiosTableGuid,
            &mRuntimeSmbiosEntryPoint
            );
  if ((EFI_ERROR (Status)) || (mRuntimeSmbiosEntryPoint == NULL)) {
    return;
  }

  EntryPointStructure = (SMBIOS_TABLE_ENTRY_POINT *) mRuntimeSmbiosEntryPoint;

  //
  // Allocate memory for SMBIOS Entry Point Structure.
  // CSM framework spec requires SMBIOS table below 4GB in EFI_TO_COMPATIBILITY16_BOOT_TABLE.
  //
  if (mReserveSmbiosEntryPoint == 0) {
    //
    // Entrypoint structure with fixed size is allocated only once.
    //
    mReserveSmbiosEntryPoint = SIZE_4GB - 1;
    Status = gBS->AllocatePages (
                    AllocateMaxAddress,
                    EfiReservedMemoryType,
                    EFI_SIZE_TO_PAGES ((UINTN) (EntryPointStructure->EntryPointLength)),
                    &mReserveSmbiosEntryPoint
                    );
    if (EFI_ERROR (Status)) {
      mReserveSmbiosEntryPoint = 0;
      return;
    }
    DEBUG ((EFI_D_INFO, "Allocate memory for Smbios Entry Point Structure\n"));
  }

  if ((mStructureTableAddress != 0) &&
      (mStructureTablePages < EFI_SIZE_TO_PAGES ((UINT32)EntryPointStructure->TableLength))) {
    //
    // If original buffer is not enough for the new SMBIOS table, free original buffer and re-allocate
    //
    gBS->FreePages (mStructureTableAddress, mStructureTablePages);
    mStructureTableAddress = 0;
    mStructureTablePages   = 0;
    DEBUG ((EFI_D_INFO, "Original size is not enough. Re-allocate the memory.\n"));
  }

  if (mStructureTableAddress == 0) {
    //
    // Allocate reserved memory below 4GB.
    // Smbios spec requires the structure table is below 4GB.
    //
    mStructureTableAddress = SIZE_4GB - 1;
    mStructureTablePages   = EFI_SIZE_TO_PAGES (EntryPointStructure->TableLength);
    Status = gBS->AllocatePages (
                    AllocateMaxAddress,
                    EfiReservedMemoryType,
                    mStructureTablePages,
                    &mStructureTableAddress
                    );
    if (EFI_ERROR (Status)) {
      gBS->FreePages (
        mReserveSmbiosEntryPoint,
        EFI_SIZE_TO_PAGES ((UINTN) (EntryPointStructure->EntryPointLength))
        );
      mReserveSmbiosEntryPoint = 0;
      mStructureTableAddress   = 0;
      mStructureTablePages     = 0;
      return;
    }
    DEBUG ((EFI_D_INFO, "Allocate memory for Smbios Structure Table\n"));
  }
}

/**
  Callback function to toggle EndOfDxe status. NULL pointer detection needs
  this status to decide if it's necessary to change attributes of page 0.

  @param  Event            Event whose notification function is being invoked.
  @param  Context          The pointer to the notification function's context,
                           which is implementation-dependent.

**/
VOID
EFIAPI
ToggleEndOfDxeStatus (
  IN EFI_EVENT                Event,
  IN VOID                     *Context
  )
{
  mEndOfDxe = TRUE;
  return;
}

/**
  Install Driver to produce Legacy BIOS protocol.

  @param  ImageHandle  Handle of driver image.
  @param  SystemTable  Pointer to system table.

  @retval EFI_SUCCESS  Legacy BIOS protocol installed
  @retval No protocol installed, unload driver.

**/
EFI_STATUS
EFIAPI
LegacyBiosInstall (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS                         Status;
  LEGACY_BIOS_INSTANCE               *Private;
  EFI_TO_COMPATIBILITY16_INIT_TABLE  *EfiToLegacy16InitTable;
  EFI_PHYSICAL_ADDRESS               MemoryAddress;
  EFI_PHYSICAL_ADDRESS               EbdaReservedBaseAddress;
  VOID                               *MemoryPtr;
  EFI_PHYSICAL_ADDRESS               MemoryAddressUnder1MB;
  UINTN                              Index;
  UINT32                             *BaseVectorMaster;
  EFI_PHYSICAL_ADDRESS               StartAddress;
  UINT32                             *ClearPtr;
  EFI_PHYSICAL_ADDRESS               MemStart;
  UINT32                             IntRedirCode;
  UINT32                             Granularity;
  BOOLEAN                            DecodeOn;
  UINT32                             MemorySize;
  EFI_GCD_MEMORY_SPACE_DESCRIPTOR    Descriptor;
  UINT64                             Length;
  UINT8                              *SecureBoot;
  EFI_EVENT                          InstallSmbiosEvent;
  EFI_EVENT                          EndOfDxeEvent;

  //
  // Load this driver's image to memory
  //
  Status = RelocateImageUnder4GIfNeeded (ImageHandle, SystemTable);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // When UEFI Secure Boot is enabled, CSM module will not start any more.
  //
  SecureBoot = NULL;
  GetEfiGlobalVariable2 (EFI_SECURE_BOOT_MODE_NAME, (VOID**)&SecureBoot, NULL);
  if ((SecureBoot != NULL) && (*SecureBoot == SECURE_BOOT_MODE_ENABLE)) {
    FreePool (SecureBoot);
    return EFI_SECURITY_VIOLATION;
  }

  if (SecureBoot != NULL) {
    FreePool (SecureBoot);
  }

  Private = &mPrivateData;
  ZeroMem (Private, sizeof (LEGACY_BIOS_INSTANCE));

  //
  // Grab a copy of all the protocols we depend on. Any error would
  // be a dispatcher bug!.
  //
  Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **) &Private->Cpu);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (&gEfiTimerArchProtocolGuid, NULL, (VOID **) &Private->Timer);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (&gEfiLegacyRegion2ProtocolGuid, NULL, (VOID **) &Private->LegacyRegion);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (&gEfiLegacyBiosPlatformProtocolGuid, NULL, (VOID **) &Private->LegacyBiosPlatform);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (&gEfiLegacy8259ProtocolGuid, NULL, (VOID **) &Private->Legacy8259);
  ASSERT_EFI_ERROR (Status);

  Status = gBS->LocateProtocol (&gEfiLegacyInterruptProtocolGuid, NULL, (VOID **) &Private->LegacyInterrupt);
  ASSERT_EFI_ERROR (Status);

  //
  // Locate Memory Test Protocol if exists
  //
  Status = gBS->LocateProtocol (
                  &gEfiGenericMemTestProtocolGuid,
                  NULL,
                  (VOID **) &Private->GenericMemoryTest
                  );
  ASSERT_EFI_ERROR (Status);

  //
  // Make sure all memory from 0-640K is tested
  //
  for (StartAddress = 0; StartAddress < 0xa0000; ) {
    gDS->GetMemorySpaceDescriptor (StartAddress, &Descriptor);
    if (Descriptor.GcdMemoryType != EfiGcdMemoryTypeReserved) {
      StartAddress = Descriptor.BaseAddress + Descriptor.Length;
      continue;
    }
    Length = MIN (Descriptor.Length, 0xa0000 - StartAddress);
    Private->GenericMemoryTest->CompatibleRangeTest (
                                  Private->GenericMemoryTest,
                                  StartAddress,
                                  Length
                                  );
    StartAddress = StartAddress + Length;
  }
  //
  // Make sure all memory from 1MB to 16MB is tested and added to memory map
  //
  for (StartAddress = BASE_1MB; StartAddress < BASE_16MB; ) {
    gDS->GetMemorySpaceDescriptor (StartAddress, &Descriptor);
    if (Descriptor.GcdMemoryType != EfiGcdMemoryTypeReserved) {
      StartAddress = Descriptor.BaseAddress + Descriptor.Length;
      continue;
    }
    Length = MIN (Descriptor.Length, BASE_16MB - StartAddress);
    Private->GenericMemoryTest->CompatibleRangeTest (
                                  Private->GenericMemoryTest,
                                  StartAddress,
                                  Length
                                  );
    StartAddress = StartAddress + Length;
  }

  Private->Signature = LEGACY_BIOS_INSTANCE_SIGNATURE;

  Private->LegacyBios.Int86 = LegacyBiosInt86;
  Private->LegacyBios.FarCall86 = LegacyBiosFarCall86;
  Private->LegacyBios.CheckPciRom = LegacyBiosCheckPciRom;
  Private->LegacyBios.InstallPciRom = LegacyBiosInstallPciRom;
  Private->LegacyBios.LegacyBoot = LegacyBiosLegacyBoot;
  Private->LegacyBios.UpdateKeyboardLedStatus = LegacyBiosUpdateKeyboardLedStatus;
  Private->LegacyBios.GetBbsInfo = LegacyBiosGetBbsInfo;
  Private->LegacyBios.ShadowAllLegacyOproms = LegacyBiosShadowAllLegacyOproms;
  Private->LegacyBios.PrepareToBootEfi = LegacyBiosPrepareToBootEfi;
  Private->LegacyBios.GetLegacyRegion = LegacyBiosGetLegacyRegion;
  Private->LegacyBios.CopyLegacyRegion = LegacyBiosCopyLegacyRegion;
  Private->LegacyBios.BootUnconventionalDevice = LegacyBiosBootUnconventionalDevice;

  Private->ImageHandle = ImageHandle;

  //
  // Enable read attribute of legacy region.
  //
  DecodeOn = TRUE;
  Private->LegacyRegion->Decode (
                           Private->LegacyRegion,
                           0xc0000,
                           0x40000,
                           &Granularity,
                           &DecodeOn
                           );
  //
  // Set Cachebility for legacy region
  // BUGBUG: Comments about this legacy region cacheability setting
  //         This setting will make D865GCHProduction CSM Unhappy
  //
  if (PcdGetBool (PcdLegacyBiosCacheLegacyRegion)) {
    gDS->SetMemorySpaceAttributes (
           0x0,
           0xA0000,
           EFI_MEMORY_WB
           );
    gDS->SetMemorySpaceAttributes (
           0xc0000,
           0x40000,
           EFI_MEMORY_WB
           );
  }

  gDS->SetMemorySpaceAttributes (
         0xA0000,
         0x20000,
         EFI_MEMORY_UC
         );

  //
  // Allocate 0 - 4K for real mode interupt vectors and BDA.
  //
  AllocateLegacyMemory (
    AllocateAddress,
    EfiReservedMemoryType,
    0,
    1,
    &MemoryAddress
    );
  ASSERT (MemoryAddress == 0x000000000);

  ClearPtr = (VOID *) ((UINTN) 0x0000);

  //
  // Initialize region from 0x0000 to 4k. This initializes interrupt vector
  // range.
  //
  ACCESS_PAGE0_CODE (
    gBS->SetMem ((VOID *) ClearPtr, 0x400, INITIAL_VALUE_BELOW_1K);
    ZeroMem ((VOID *) ((UINTN)ClearPtr + 0x400), 0xC00);
  );

  //
  // Allocate pages for OPROM usage
  //
  MemorySize = PcdGet32 (PcdEbdaReservedMemorySize);
  ASSERT ((MemorySize & 0xFFF) == 0);

  Status = AllocateLegacyMemory (
             AllocateAddress,
             EfiReservedMemoryType,
             CONVENTIONAL_MEMORY_TOP - MemorySize,
             EFI_SIZE_TO_PAGES (MemorySize),
             &MemoryAddress
             );
  ASSERT_EFI_ERROR (Status);

  ZeroMem ((VOID *) ((UINTN) MemoryAddress), MemorySize);

  //
  // Allocate all 32k chunks from 0x60000 ~ 0x88000 for Legacy OPROMs that
  // don't use PMM but look for zeroed memory. Note that various non-BBS
  // OpROMs expect different areas to be free
  //
  EbdaReservedBaseAddress = MemoryAddress;
  MemoryAddress = PcdGet32 (PcdOpromReservedMemoryBase);
  MemorySize    = PcdGet32 (PcdOpromReservedMemorySize);
  //
  // Check if base address and size for reserved memory are 4KB aligned.
  //
  ASSERT ((MemoryAddress & 0xFFF) == 0);
  ASSERT ((MemorySize & 0xFFF) == 0);
  //
  // Check if the reserved memory is below EBDA reserved range.
  //
  ASSERT ((MemoryAddress < EbdaReservedBaseAddress) && ((MemoryAddress + MemorySize - 1) < EbdaReservedBaseAddress));
  for (MemStart = MemoryAddress; MemStart < MemoryAddress + MemorySize; MemStart += 0x1000) {
    Status = AllocateLegacyMemory (
               AllocateAddress,
               EfiBootServicesCode,
               MemStart,
               1,
               &StartAddress
               );
    if (!EFI_ERROR (Status)) {
      MemoryPtr = (VOID *) ((UINTN) StartAddress);
      ZeroMem (MemoryPtr, 0x1000);
    } else {
      DEBUG ((EFI_D_ERROR, "WARNING: Allocate legacy memory fail for SCSI card - %x\n", MemStart));
    }
  }

  //
  // Allocate low PMM memory and zero it out
  //
  MemorySize = PcdGet32 (PcdLowPmmMemorySize);
  ASSERT ((MemorySize & 0xFFF) == 0);
  Status = AllocateLegacyMemory (
             AllocateMaxAddress,
             EfiBootServicesCode,
             CONVENTIONAL_MEMORY_TOP,
             EFI_SIZE_TO_PAGES (MemorySize),
             &MemoryAddressUnder1MB
             );
  ASSERT_EFI_ERROR (Status);

  ZeroMem ((VOID *) ((UINTN) MemoryAddressUnder1MB), MemorySize);

  //
  // Allocate space for thunker and Init Thunker
  //
  Status = AllocateLegacyMemory (
             AllocateMaxAddress,
             EfiReservedMemoryType,
             CONVENTIONAL_MEMORY_TOP,
             (sizeof (LOW_MEMORY_THUNK) / EFI_PAGE_SIZE) + 2,
             &MemoryAddress
             );
  ASSERT_EFI_ERROR (Status);
  Private->IntThunk                   = (LOW_MEMORY_THUNK *) (UINTN) MemoryAddress;
  EfiToLegacy16InitTable                   = &Private->IntThunk->EfiToLegacy16InitTable;
  EfiToLegacy16InitTable->ThunkStart       = (UINT32) (EFI_PHYSICAL_ADDRESS) (UINTN) MemoryAddress;
  EfiToLegacy16InitTable->ThunkSizeInBytes = (UINT32) (sizeof (LOW_MEMORY_THUNK));

  Status = LegacyBiosInitializeThunk (Private);
  ASSERT_EFI_ERROR (Status);

  //
  // Init the legacy memory map in memory < 1 MB.
  //
  EfiToLegacy16InitTable->BiosLessThan1MB         = (UINT32) MemoryAddressUnder1MB;
  EfiToLegacy16InitTable->LowPmmMemory            = (UINT32) MemoryAddressUnder1MB;
  EfiToLegacy16InitTable->LowPmmMemorySizeInBytes = MemorySize;

  MemorySize = PcdGet32 (PcdHighPmmMemorySize);
  ASSERT ((MemorySize & 0xFFF) == 0);
  //
  // Allocate high PMM Memory under 16 MB
  //
  Status = AllocateLegacyMemory (
             AllocateMaxAddress,
             EfiBootServicesCode,
             0x1000000,
             EFI_SIZE_TO_PAGES (MemorySize),
             &MemoryAddress
             );
  if (EFI_ERROR (Status)) {
    //
    // If it fails, allocate high PMM Memory under 4GB
    //
    Status = AllocateLegacyMemory (
               AllocateMaxAddress,
               EfiBootServicesCode,
               0xFFFFFFFF,
               EFI_SIZE_TO_PAGES (MemorySize),
               &MemoryAddress
               );
  }
  if (!EFI_ERROR (Status)) {
    EfiToLegacy16InitTable->HiPmmMemory            = (UINT32) (EFI_PHYSICAL_ADDRESS) (UINTN) MemoryAddress;
    EfiToLegacy16InitTable->HiPmmMemorySizeInBytes = MemorySize;
  }

  //
  //  ShutdownAPs();
  //
  // Start the Legacy BIOS;
  //
  Status = ShadowAndStartLegacy16 (Private);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Initialize interrupt redirection code and entries;
  // IDT Vectors 0x68-0x6f must be redirected to IDT Vectors 0x08-0x0f.
  //
  CopyMem (
         Private->IntThunk->InterruptRedirectionCode,
         (VOID *) (UINTN) InterruptRedirectionTemplate,
         sizeof (Private->IntThunk->InterruptRedirectionCode)
         );

  //
  // Save Unexpected interrupt vector so can restore it just prior to boot
  //
  ACCESS_PAGE0_CODE (
    BaseVectorMaster = (UINT32 *) (sizeof (UINT32) * PROTECTED_MODE_BASE_VECTOR_MASTER);
    Private->BiosUnexpectedInt = BaseVectorMaster[0];
    IntRedirCode = (UINT32) (UINTN) Private->IntThunk->InterruptRedirectionCode;
    for (Index = 0; Index < 8; Index++) {
      BaseVectorMaster[Index] = (EFI_SEGMENT (IntRedirCode + Index * 4) << 16) | EFI_OFFSET (IntRedirCode + Index * 4);
    }
  );

  //
  // Save EFI value
  //
  Private->ThunkSeg = (UINT16) (EFI_SEGMENT (IntRedirCode));

  //
  // Allocate reserved memory for SMBIOS table used in legacy boot if SMBIOS table exists
  //
  InstallSmbiosEventCallback (NULL, NULL);

  //
  // Create callback function to update the size of reserved memory after LegacyBiosDxe starts
  //
  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  InstallSmbiosEventCallback,
                  NULL,
                  &gEfiSmbiosTableGuid,
                  &InstallSmbiosEvent
                  );
  ASSERT_EFI_ERROR (Status);

  //
  // Create callback to update status of EndOfDxe, which is needed by NULL
  // pointer detection
  //
  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  ToggleEndOfDxeStatus,
                  NULL,
                  &gEfiEndOfDxeEventGroupGuid,
                  &EndOfDxeEvent
                  );
  ASSERT_EFI_ERROR (Status);

  //
  // Make a new handle and install the protocol
  //
  Private->Handle = NULL;
  Status = gBS->InstallProtocolInterface (
                  &Private->Handle,
                  &gEfiLegacyBiosProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &Private->LegacyBios
                  );
  Private->Csm16PciInterfaceVersion = GetPciInterfaceVersion (Private);

  DEBUG ((EFI_D_INFO, "CSM16 PCI BIOS Interface Version: %02x.%02x\n",
          (UINT8) (Private->Csm16PciInterfaceVersion >> 8),
          (UINT8) Private->Csm16PciInterfaceVersion
        ));
  ASSERT (Private->Csm16PciInterfaceVersion != 0);
  return Status;
}
