/** @file
  Legacy BIOS Platform support

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

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

**/

#include "LegacyPlatform.h"

EFI_SETUP_BBS_MAP  mSetupBbsMap[] = {
  { 1, 2,    1, 1 },      // ATA HardDrive
  { 2, 3,    1, 1 },      // ATAPI CDROM
  { 3, 0x80, 2, 0 },      // PXE
  { 4, 1,    0, 6 },      // USB Floppy
  { 4, 2,    0, 6 },      // USB HDD
  { 4, 3,    0, 6 },      // USB CD
  { 4, 1,    0, 0 },      // USB ZIP Bugbug since Class/SubClass code is uninitialized
  { 4, 2,    0, 0 }       // USB ZIP Bugbug since Class/SubClass code is uninitialized
};

//
// Global variables for System ROMs
//
#define SYSTEM_ROM_FILE_GUID \
{ 0x1547B4F3, 0x3E8A, 0x4FEF, { 0x81, 0xC8, 0x32, 0x8E, 0xD6, 0x47, 0xAB, 0x1A } }

#define NULL_ROM_FILE_GUID \
{ 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }

SYSTEM_ROM_TABLE  mSystemRomTable[] = {
  { SYSTEM_ROM_FILE_GUID, 1 },
  { NULL_ROM_FILE_GUID,   0 }
};

EFI_HANDLE  mVgaHandles[0x20];
EFI_HANDLE  mDiskHandles[0x20];
EFI_HANDLE  mIsaHandles[0x20];

EFI_LEGACY_IRQ_PRIORITY_TABLE_ENTRY  IrqPriorityTable[MAX_IRQ_PRIORITY_ENTRIES] = {
  { 0x0B, 0 },
  { 0x09, 0 },
  { 0x0A, 0 },
  { 0x05, 0 },
  { 0x07, 0 },
  { 0x00, 0 },
  { 0x00, 0 }
};

//
// PIRQ Table
// - Slot numbering will be used to update the bus number and determine bridge
//   to check to get bus number.  The Slot number - 1 is an index into a decode
//   table to get the bridge information.
//
EFI_LEGACY_PIRQ_TABLE  PirqTableHead = {
  {
    EFI_LEGACY_PIRQ_TABLE_SIGNATURE, // UINT32  Signature
    0x00,                            // UINT8   MinorVersion
    0x01,                            // UINT8   MajorVersion
    0x0000,                          // UINT16  TableSize
    0x00,                            // UINT8   Bus
    0x08,                            // UINT8   DevFun
    0x0000,                          // UINT16  PciOnlyIrq
    0x8086,                          // UINT16  CompatibleVid
    0x122e,                          // UINT16  CompatibleDid
    0x00000000,                      // UINT32  Miniport
    {                 // UINT8   Reserved[11]
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00
    },
    0x00,             // UINT8   Checksum
  },
  {
    //           -- Pin 1 --   -- Pin 2 --   -- Pin 3 --   -- Pin 4 --
    // Bus  Dev   Reg   Map     Reg   Map     Reg   Map     Reg   Map
    //
    { 0x00, 0x08, {
            { 0x60, 0xDEB8 }, { 0x61, 0xDEB8 }, { 0x62, 0xDEB8 }, { 0x63, 0xDEB8 }
          }, 0x00, 0x00 },
    { 0x00, 0x10, {
            { 0x61, 0xDEB8 }, { 0x62, 0xDEB8 }, { 0x63, 0xDEB8 }, { 0x60, 0xDEB8 }
          }, 0x01, 0x00 },
    { 0x00, 0x18, {
            { 0x62, 0xDEB8 }, { 0x63, 0xDEB8 }, { 0x60, 0xDEB8 }, { 0x61, 0xDEB8 }
          }, 0x02, 0x00 },
    { 0x00, 0x20, {
            { 0x63, 0xDEB8 }, { 0x60, 0xDEB8 }, { 0x61, 0xDEB8 }, { 0x62, 0xDEB8 }
          }, 0x03, 0x00 },
    { 0x00, 0x28, {
            { 0x60, 0xDEB8 }, { 0x61, 0xDEB8 }, { 0x62, 0xDEB8 }, { 0x63, 0xDEB8 }
          }, 0x04, 0x00 },
    { 0x00, 0x30, {
            { 0x61, 0xDEB8 }, { 0x62, 0xDEB8 }, { 0x63, 0xDEB8 }, { 0x60, 0xDEB8 }
          }, 0x05, 0x00 },
  }
};

LEGACY_BIOS_PLATFORM_INSTANCE  mPrivateData;
EFI_HANDLE                     mImageHandle = NULL;

/**
  Return the handles and assorted information for the specified PCI Class code

  @param[in]     PciClasses    Array of PCI_CLASS_RECORD to find terminated with ClassCode 0xff
  @param[in,out] DeviceTable   Table to place handles etc in.
  @param[in,out] DeviceIndex   Number of devices found
  @param[in]     DeviceFlags   FALSE if a valid legacy ROM is required, TRUE otherwise.

  @retval EFI_SUCCESS     One or more devices found
  @retval EFI_NOT_FOUND   No device found

**/
EFI_STATUS
FindAllDeviceTypes (
  IN       PCI_CLASS_RECORD  *PciClasses,
  IN OUT   DEVICE_STRUCTURE  *DeviceTable,
  IN OUT   UINT16            *DeviceIndex,
  IN       BOOLEAN           DeviceFlags
  )
{
  UINTN                     HandleCount;
  EFI_HANDLE                *HandleBuffer;
  UINTN                     Index;
  UINTN                     StartIndex;
  PCI_TYPE00                PciConfigHeader;
  EFI_PCI_IO_PROTOCOL       *PciIo;
  EFI_LEGACY_BIOS_PROTOCOL  *LegacyBios;
  UINTN                     Flags;
  EFI_STATUS                Status;
  UINTN                     Index2;

  //
  // Get legacy BIOS protocol as it is required to deal with Option ROMs.
  //
  StartIndex = *DeviceIndex;
  Status     = gBS->LocateProtocol (
                      &gEfiLegacyBiosProtocolGuid,
                      NULL,
                      (VOID **)&LegacyBios
                      );
  ASSERT_EFI_ERROR (Status);

  //
  // Get all PCI handles and check them to generate a list of matching devices.
  //
  gBS->LocateHandleBuffer (
         ByProtocol,
         &gEfiPciIoProtocolGuid,
         NULL,
         &HandleCount,
         &HandleBuffer
         );
  for (Index = 0; Index < HandleCount; Index++) {
    gBS->HandleProtocol (
           HandleBuffer[Index],
           &gEfiPciIoProtocolGuid,
           (VOID **)&PciIo
           );
    PciIo->Pci.Read (
                 PciIo,
                 EfiPciIoWidthUint32,
                 0,
                 sizeof (PciConfigHeader) / sizeof (UINT32),
                 &PciConfigHeader
                 );
    for (Index2 = 0; PciClasses[Index2].Class != 0xff; Index2++) {
      if ((PciConfigHeader.Hdr.ClassCode[2] == PciClasses[Index2].Class) &&
          (PciConfigHeader.Hdr.ClassCode[1] == PciClasses[Index2].SubClass))
      {
        LegacyBios->CheckPciRom (
                      LegacyBios,
                      HandleBuffer[Index],
                      NULL,
                      NULL,
                      &Flags
                      );

        //
        // Verify that results of OPROM check match request.
        // The two valid requests are:
        //   DeviceFlags = 0 require a valid legacy ROM
        //   DeviceFlags = 1 require either no ROM or a valid legacy ROM
        //
        if (
            ((DeviceFlags != 0) && (Flags == NO_ROM)) ||
            ((Flags & (ROM_FOUND | VALID_LEGACY_ROM)) == (ROM_FOUND | VALID_LEGACY_ROM))
            )
        {
          DeviceTable->Handle = HandleBuffer[Index];
          DeviceTable->Vid    = PciConfigHeader.Hdr.VendorId;
          DeviceTable->Did    = PciConfigHeader.Hdr.DeviceId;
          DeviceTable->SvId   = PciConfigHeader.Device.SubsystemVendorID;
          DeviceTable->SysId  = PciConfigHeader.Device.SubsystemID;
          ++*DeviceIndex;
          DeviceTable++;
        }
      }
    }
  }

  //
  // Free any allocated buffers
  //
  gBS->FreePool (HandleBuffer);

  if (*DeviceIndex != StartIndex) {
    return EFI_SUCCESS;
  } else {
    return EFI_NOT_FOUND;
  }
}

/**
  Load and initialize the Legacy BIOS SMM handler.

  @param  This                   The protocol instance pointer.
  @param  EfiToLegacy16BootTable A pointer to Legacy16 boot table.

  @retval EFI_SUCCESS           SMM code loaded.
  @retval EFI_DEVICE_ERROR      SMM code failed to load

**/
EFI_STATUS
EFIAPI
SmmInit (
  IN  EFI_LEGACY_BIOS_PLATFORM_PROTOCOL  *This,
  IN  VOID                               *EfiToLegacy16BootTable
  )
{
  return EFI_SUCCESS;
}

/**
  Finds the device path that should be used as the primary display adapter.

  @param  VgaHandle - The handle of the video device

**/
VOID
GetSelectedVgaDeviceInfo (
  OUT EFI_HANDLE  *VgaHandle
  )
{
  EFI_STATUS           Status;
  UINTN                HandleCount;
  EFI_HANDLE           *HandleBuffer;
  UINTN                Index;
  EFI_PCI_IO_PROTOCOL  *PciIo;
  PCI_TYPE00           Pci;
  UINT8                MinBus;
  UINT8                MaxBus;
  UINTN                Segment;
  UINTN                Bus;
  UINTN                Device;
  UINTN                Function;
  UINTN                SelectedAddress;
  UINTN                CurrentAddress;

  //
  // Initialize return to 'not found' state
  //
  *VgaHandle = NULL;

  //
  // Initialize variable states.  This is important for selecting the VGA
  // device if multiple devices exist behind a single bridge.
  //
  HandleCount     = 0;
  HandleBuffer    = NULL;
  SelectedAddress = PCI_LIB_ADDRESS (0xff, 0x1f, 0x7, 0);

  //
  // The bus range to search for a VGA device in.
  //
  MinBus = MaxBus = 0;

  //
  // Start to check all the pci io to find all possible VGA device
  //
  HandleCount  = 0;
  HandleBuffer = NULL;
  Status       = gBS->LocateHandleBuffer (
                        ByProtocol,
                        &gEfiPciIoProtocolGuid,
                        NULL,
                        &HandleCount,
                        &HandleBuffer
                        );
  if (EFI_ERROR (Status)) {
    return;
  }

  for (Index = 0; Index < HandleCount; Index++) {
    Status = gBS->HandleProtocol (HandleBuffer[Index], &gEfiPciIoProtocolGuid, (VOID **)&PciIo);
    if (!EFI_ERROR (Status)) {
      //
      // Determine if this is in the correct bus range.
      //
      Status = PciIo->GetLocation (PciIo, &Segment, &Bus, &Device, &Function);
      if (EFI_ERROR (Status) || ((Bus < MinBus) || (Bus > MaxBus))) {
        continue;
      }

      //
      // Read device information.
      //
      Status = PciIo->Pci.Read (
                            PciIo,
                            EfiPciIoWidthUint32,
                            0,
                            sizeof (Pci) / sizeof (UINT32),
                            &Pci
                            );
      if (EFI_ERROR (Status)) {
        continue;
      }

      //
      // Make sure the device is a VGA device.
      //
      if (!IS_PCI_VGA (&Pci)) {
        continue;
      }

      DEBUG ((
        DEBUG_INFO,
        "PCI VGA: 0x%04x:0x%04x\n",
        Pci.Hdr.VendorId,
        Pci.Hdr.DeviceId
        ));

      //
      // Currently we use the lowest numbered bus/device/function if multiple
      // devices are found in the target bus range.
      //
      CurrentAddress = PCI_LIB_ADDRESS (Bus, Device, Function, 0);
      if (CurrentAddress < SelectedAddress) {
        SelectedAddress = CurrentAddress;
        *VgaHandle      = HandleBuffer[Index];
      }
    }
  }

  FreePool (HandleBuffer);
}

/**
  Returns a buffer of handles for the requested subfunction.

  @param  This                  The protocol instance pointer.
  @param  Mode                  Specifies what handle to return. See EFI_GET_PLATFORM_HANDLE_MODE enum.
  @param  Type                  Mode specific. See EFI_GET_PLATFORM_HANDLE_MODE enum.
  @param  HandleBuffer          Mode specific. See EFI_GET_PLATFORM_HANDLE_MODE enum.
  @param  HandleCount           Mode specific. See EFI_GET_PLATFORM_HANDLE_MODE enum.
  @param  AdditionalData        Mode specific. See EFI_GET_PLATFORM_HANDLE_MODE enum.

  @retval EFI_SUCCESS           Handle is valid.
  @retval EFI_UNSUPPORTED       Mode is not supported on the platform.
  @retval EFI_NOT_FOUND         Handle is not known.

**/
EFI_STATUS
EFIAPI
GetPlatformHandle (
  IN  EFI_LEGACY_BIOS_PLATFORM_PROTOCOL  *This,
  IN  EFI_GET_PLATFORM_HANDLE_MODE       Mode,
  IN  UINT16                             Type,
  OUT EFI_HANDLE                         **HandleBuffer,
  OUT UINTN                              *HandleCount,
  OUT VOID                               **AdditionalData OPTIONAL
  )
{
  DEVICE_STRUCTURE          LocalDevice[0x40];
  UINT32                    LocalIndex;
  UINT32                    Index;
  DEVICE_STRUCTURE          TempDevice;
  EFI_STATUS                Status;
  EFI_PCI_IO_PROTOCOL       *PciIo;
  UINTN                     Segment;
  UINTN                     Bus;
  UINTN                     Device;
  UINTN                     Function;
  HDD_INFO                  *HddInfo;
  PCI_TYPE00                PciConfigHeader;
  UINT32                    HddIndex;
  EFI_HANDLE                IdeHandle;
  EFI_LEGACY_BIOS_PROTOCOL  *LegacyBios;
  PCI_CLASS_RECORD          ClassLists[10];
  UINTN                     PriorityIndex;

  static BOOLEAN  bConnected = FALSE;

  LocalIndex = 0x00;
  HddInfo    = NULL;
  HddIndex   = 0;

  Status = gBS->LocateProtocol (
                  &gEfiLegacyBiosProtocolGuid,
                  NULL,
                  (VOID **)&LegacyBios
                  );

  //
  // Process mode specific operations
  //
  switch (Mode) {
    case EfiGetPlatformVgaHandle:
      //
      // Get the handle for the currently selected VGA device.
      //
      GetSelectedVgaDeviceInfo (&mVgaHandles[0]);
      *HandleBuffer = &mVgaHandles[0];
      *HandleCount  = (mVgaHandles[0] != NULL) ? 1 : 0;
      return EFI_SUCCESS;
    case EfiGetPlatformIdeHandle:
      IdeHandle = NULL;
      if (AdditionalData != NULL) {
        HddInfo = (HDD_INFO *)*AdditionalData;
      }

      //
      // Locate all found block io devices
      //
      ClassLists[0].Class    = PCI_CLASS_MASS_STORAGE;
      ClassLists[0].SubClass = PCI_CLASS_MASS_STORAGE_SCSI;
      ClassLists[1].Class    = PCI_CLASS_MASS_STORAGE;
      ClassLists[1].SubClass = PCI_CLASS_MASS_STORAGE_IDE;
      ClassLists[2].Class    = PCI_CLASS_MASS_STORAGE;
      ClassLists[2].SubClass = PCI_CLASS_MASS_STORAGE_RAID;
      ClassLists[3].Class    = PCI_CLASS_MASS_STORAGE;
      ClassLists[3].SubClass = PCI_CLASS_MASS_STORAGE_SATADPA;
      ClassLists[4].Class    = 0xff;
      FindAllDeviceTypes (ClassLists, LocalDevice, (UINT16 *)&LocalIndex, TRUE);
      if (LocalIndex == 0) {
        return EFI_NOT_FOUND;
      }

      //
      // Make sure all IDE controllers are connected. This is necessary
      // in NO_CONFIG_CHANGE boot path to ensure IDE controller is correctly
      // initialized and all IDE drives are enumerated
      //
      if (!bConnected) {
        for (Index = 0; Index < LocalIndex; Index++) {
          gBS->ConnectController (LocalDevice[Index].Handle, NULL, NULL, TRUE);
        }
      }

      //
      // Locate onboard controllers.
      //
      for (Index = 0; Index < LocalIndex; Index++) {
        if (LocalDevice[Index].Vid == V_INTEL_VENDOR_ID) {
          if (LocalDevice[Index].Did == V_PIIX4_IDE_DEVICE_ID) {
            IdeHandle = LocalDevice[Index].Handle;
          }
        }
      }

      //
      // Set the IDE contorller as primary devices.
      //
      PriorityIndex = 0;
      for (Index = 0; Index < LocalIndex; Index++) {
        if ((LocalDevice[Index].Handle == IdeHandle) && (PriorityIndex == 0)) {
          TempDevice                 = LocalDevice[PriorityIndex];
          LocalDevice[PriorityIndex] = LocalDevice[Index];
          LocalDevice[Index]         = TempDevice;
          PriorityIndex++;
          break;
        }
      }

      //
      // Copy over handles and update return values.
      //
      for (Index = 0; Index < LocalIndex; Index++) {
        mDiskHandles[Index] = LocalDevice[Index].Handle;
      }

      *HandleBuffer = &mDiskHandles[0];
      *HandleCount  = LocalIndex;

      //
      // We have connected all IDE controllers once. No more needed
      //
      bConnected = TRUE;

      //
      // Log all onboard controllers.
      //
      for (Index = 0; (Index < LocalIndex) && (AdditionalData != NULL); Index++) {
        if ((LocalDevice[Index].Handle != NULL) &&
            (LocalDevice[Index].Handle == IdeHandle))
        {
          Status = gBS->HandleProtocol (
                          LocalDevice[Index].Handle,
                          &gEfiPciIoProtocolGuid,
                          (VOID **)&PciIo
                          );
          PciIo->Pci.Read (
                       PciIo,
                       EfiPciIoWidthUint32,
                       0,
                       sizeof (PciConfigHeader) / sizeof (UINT32),
                       &PciConfigHeader
                       );
          if (!EFI_ERROR (Status)) {
            PciIo->GetLocation (
                     PciIo,
                     &Segment,
                     &Bus,
                     &Device,
                     &Function
                     );

            //
            // Be sure to only fill out correct information based on platform
            // configuration.
            //
            HddInfo[HddIndex].Status      |= HDD_PRIMARY;
            HddInfo[HddIndex].Bus          = (UINT32)Bus;
            HddInfo[HddIndex].Device       = (UINT32)Device;
            HddInfo[HddIndex].Function     = (UINT32)Function;
            HddInfo[HddIndex + 1].Status  |= HDD_SECONDARY;
            HddInfo[HddIndex + 1].Bus      = (UINT32)Bus;
            HddInfo[HddIndex + 1].Device   = (UINT32)Device;
            HddInfo[HddIndex + 1].Function = (UINT32)Function;

            //
            // Primary controller data
            //
            if ((PciConfigHeader.Hdr.ClassCode[0] & 0x01) != 0) {
              HddInfo[HddIndex].CommandBaseAddress =
                (UINT16)(PciConfigHeader.Device.Bar[0] & 0xfffc);
              HddInfo[HddIndex].ControlBaseAddress =
                (UINT16)((PciConfigHeader.Device.Bar[1] & 0xfffc)+2);
              HddInfo[HddIndex].BusMasterAddress =
                (UINT16)(PciConfigHeader.Device.Bar[4] & 0xfffc);
              HddInfo[HddIndex].HddIrq = PciConfigHeader.Device.InterruptLine;
            } else {
              HddInfo[HddIndex].HddIrq             = 14;
              HddInfo[HddIndex].CommandBaseAddress = 0x1f0;
              HddInfo[HddIndex].ControlBaseAddress = 0x3f6;
              HddInfo[HddIndex].BusMasterAddress   = 0;
            }

            HddIndex++;

            //
            // Secondary controller data
            //
            if ((PciConfigHeader.Hdr.ClassCode[0] & 0x04) != 0) {
              HddInfo[HddIndex].CommandBaseAddress =
                (UINT16)(PciConfigHeader.Device.Bar[2] & 0xfffc);
              HddInfo[HddIndex].ControlBaseAddress =
                (UINT16)((PciConfigHeader.Device.Bar[3] & 0xfffc)+2);
              HddInfo[HddIndex].BusMasterAddress =
                (UINT16)(HddInfo[HddIndex].BusMasterAddress + 8);
              HddInfo[HddIndex].HddIrq = PciConfigHeader.Device.InterruptLine;
            } else {
              HddInfo[HddIndex].HddIrq             = 15;
              HddInfo[HddIndex].CommandBaseAddress = 0x170;
              HddInfo[HddIndex].ControlBaseAddress = 0x376;
              HddInfo[HddIndex].BusMasterAddress   = 0;
            }

            HddIndex++;
          }
        }
      }

      return EFI_SUCCESS;
    case EfiGetPlatformIsaBusHandle:
      ClassLists[0].Class    = (UINT8)PCI_CLASS_BRIDGE;
      ClassLists[0].SubClass = (UINT8)PCI_CLASS_BRIDGE_ISA_PDECODE;
      ClassLists[1].Class    = (UINT8)PCI_CLASS_BRIDGE;
      ClassLists[1].SubClass = (UINT8)PCI_CLASS_BRIDGE_ISA;
      ClassLists[2].Class    = 0xff;

      //
      // Locate all found block io devices
      //
      FindAllDeviceTypes (ClassLists, LocalDevice, (UINT16 *)(&LocalIndex), TRUE);
      if (LocalIndex == 0) {
        return EFI_NOT_FOUND;
      }

      //
      // Find our ISA bridge.
      //
      for (Index = 0; Index < LocalIndex; Index++) {
        if (LocalDevice[Index].Vid == V_INTEL_VENDOR_ID) {
          TempDevice         = LocalDevice[0];
          LocalDevice[0]     = LocalDevice[Index];
          LocalDevice[Index] = TempDevice;
        }
      }

      //
      // Perform copy and update return values.
      //
      for (Index = 0; Index < LocalIndex; Index++) {
        mIsaHandles[Index] = LocalDevice[Index].Handle;
      }

      *HandleBuffer = &mIsaHandles[0];
      *HandleCount  = LocalIndex;
      return EFI_SUCCESS;
    case EfiGetPlatformUsbHandle:
    default:
      return EFI_UNSUPPORTED;
  }
}

/**
  Allows platform to perform any required action after a LegacyBios operation.
  Invokes the specific sub function specified by Mode.

  @param  This                  The protocol instance pointer.
  @param  Mode                  Specifies what handle to return. See EFI_GET_PLATFORM_HOOK_MODE enum.
  @param  Type                  Mode specific.  See EFI_GET_PLATFORM_HOOK_MODE enum.
  @param  DeviceHandle          Mode specific.  See EFI_GET_PLATFORM_HOOK_MODE enum.
  @param  ShadowAddress         Mode specific.  See EFI_GET_PLATFORM_HOOK_MODE enum.
  @param  Compatibility16Table  Mode specific.  See EFI_GET_PLATFORM_HOOK_MODE enum.
  @param  AdditionalData        Mode specific.  See EFI_GET_PLATFORM_HOOK_MODE enum.

  @retval EFI_SUCCESS           The operation performed successfully. Mode specific.
  @retval EFI_UNSUPPORTED       Mode is not supported on the platform.

**/
EFI_STATUS
EFIAPI
PlatformHooks (
  IN       EFI_LEGACY_BIOS_PLATFORM_PROTOCOL  *This,
  IN       EFI_GET_PLATFORM_HOOK_MODE         Mode,
  IN       UINT16                             Type,
  OUT   EFI_HANDLE                            DeviceHandle  OPTIONAL,
  IN OUT   UINTN                              *Shadowaddress  OPTIONAL,
  IN       EFI_COMPATIBILITY16_TABLE          *Compatibility16Table  OPTIONAL,
  OUT   VOID                                  **AdditionalData OPTIONAL
  )
{
  EFI_IA32_REGISTER_SET     Regs;
  EFI_LEGACY_BIOS_PROTOCOL  *LegacyBios;
  EFI_STATUS                Status;

  switch (Mode) {
    case EfiPlatformHookPrepareToScanRom:
      Status = gBS->LocateProtocol (
                      &gEfiLegacyBiosProtocolGuid,
                      NULL,
                      (VOID **)&LegacyBios
                      );

      //
      // Set the 80x25 Text VGA Mode
      //
      Regs.H.AH = 0x00;
      Regs.H.AL = 0x03;
      Status    = LegacyBios->Int86 (LegacyBios, 0x10, &Regs);
      return Status;
    case EfiPlatformHookShadowServiceRoms:
      return EFI_SUCCESS;
    case EfiPlatformHookAfterRomInit:
    default:
      return EFI_UNSUPPORTED;
  }
}

/**
  Returns information associated with PCI IRQ routing.
  This function returns the following information associated with PCI IRQ routing:
    * An IRQ routing table and number of entries in the table.
    * The $PIR table and its size.
    * A list of PCI IRQs and the priority order to assign them.

  @param  This                    The protocol instance pointer.
  @param  RoutingTable            The pointer to PCI IRQ Routing table.
                                  This location is the $PIR table minus the header.
  @param  RoutingTableEntries     The number of entries in table.
  @param  LocalPirqTable          $PIR table.
  @param  PirqTableSize           $PIR table size.
  @param  LocalIrqPriorityTable   A list of interrupts in priority order to assign.
  @param  IrqPriorityTableEntries The number of entries in the priority table.

  @retval EFI_SUCCESS           Data was successfully returned.

**/
EFI_STATUS
EFIAPI
GetRoutingTable (
  IN  EFI_LEGACY_BIOS_PLATFORM_PROTOCOL  *This,
  OUT VOID                               **RoutingTable,
  OUT UINTN                              *RoutingTableEntries,
  OUT VOID                               **LocalPirqTable  OPTIONAL,
  OUT UINTN                              *PirqTableSize  OPTIONAL,
  OUT VOID                               **LocalIrqPriorityTable  OPTIONAL,
  OUT UINTN                              *IrqPriorityTableEntries OPTIONAL
  )
{
  UINT16                         PTableSize;
  UINT32                         Index;
  UINT8                          Bus;
  UINT8                          Device;
  UINT8                          Function;
  UINT8                          Checksum;
  UINT8                          *Ptr;
  EFI_STATUS                     Status;
  EFI_LEGACY_INTERRUPT_PROTOCOL  *LegacyInterrupt;

  Checksum = 0;

  if (LocalPirqTable != NULL) {
    PTableSize = sizeof (EFI_LEGACY_PIRQ_TABLE_HEADER) +
                 sizeof (EFI_LEGACY_IRQ_ROUTING_ENTRY) * MAX_IRQ_ROUTING_ENTRIES;

    Status = gBS->LocateProtocol (
                    &gEfiLegacyInterruptProtocolGuid,
                    NULL,
                    (VOID **)&LegacyInterrupt
                    );
    ASSERT_EFI_ERROR (Status);
    LegacyInterrupt->GetLocation (
                       LegacyInterrupt,
                       &Bus,
                       &Device,
                       &Function
                       );

    //
    // Update fields in $PIR table header
    //
    PirqTableHead.PirqTable.TableSize = PTableSize;
    PirqTableHead.PirqTable.Bus       = Bus;
    PirqTableHead.PirqTable.DevFun    = (UINT8)((Device << 3) + Function);
    Ptr                               = (UINT8 *)(&PirqTableHead);

    //
    // Calculate checksum.
    //
    for (Index = 0; Index < PTableSize; Index++) {
      Checksum = (UINT8)(Checksum + (UINT8)*Ptr);
      Ptr     += 1;
    }

    Checksum                         = (UINT8)(0x00 - Checksum);
    PirqTableHead.PirqTable.Checksum = Checksum;

    //
    // Update return values.
    //
    *LocalPirqTable = (VOID *)(&PirqTableHead);
    *PirqTableSize  = PTableSize;
  }

  //
  // More items to return.
  //
  *RoutingTable        = PirqTableHead.IrqRoutingEntry;
  *RoutingTableEntries = MAX_IRQ_ROUTING_ENTRIES;
  if (LocalIrqPriorityTable != NULL) {
    *LocalIrqPriorityTable   = IrqPriorityTable;
    *IrqPriorityTableEntries = MAX_IRQ_PRIORITY_ENTRIES;
  }

  return EFI_SUCCESS;
}

/**
  Finds the binary data or other platform information.

  @param  This                  The protocol instance pointer.
  @param  Mode                  Specifies what data to return. See See EFI_GET_PLATFORM_INFO_MODE enum.
  @param  Table                 Mode specific.  See EFI_GET_PLATFORM_INFO_MODE enum.
  @param  TableSize             Mode specific.  See EFI_GET_PLATFORM_INFO_MODE enum.
  @param  Location              Mode specific.  See EFI_GET_PLATFORM_INFO_MODE enum.
  @param  Alignment             Mode specific.  See EFI_GET_PLATFORM_INFO_MODE enum.
  @param  LegacySegment         Mode specific.  See EFI_GET_PLATFORM_INFO_MODE enum.
  @param  LegacyOffset          Mode specific.  See EFI_GET_PLATFORM_INFO_MODE enum.

  @retval EFI_SUCCESS           Data returned successfully.
  @retval EFI_UNSUPPORTED       Mode is not supported on the platform.
  @retval EFI_NOT_FOUND         Binary image or table not found.

**/
EFI_STATUS
EFIAPI
GetPlatformInfo (
  IN  EFI_LEGACY_BIOS_PLATFORM_PROTOCOL  *This,
  IN  EFI_GET_PLATFORM_INFO_MODE         Mode,
  OUT VOID                               **Table,
  OUT UINTN                              *TableSize,
  OUT UINTN                              *Location,
  OUT UINTN                              *Alignment,
  IN  UINT16                             LegacySegment,
  IN  UINT16                             LegacyOffset
  )
{
  EFI_STATUS  Status;
  UINTN       Index;

  switch (Mode) {
    case EfiGetPlatformBinarySystemRom:
      //
      // Loop through table of System rom descriptions
      //
      for (Index = 0; mSystemRomTable[Index].Valid != 0; Index++) {
        Status = GetSectionFromFv (
                   &mSystemRomTable[Index].FileName,
                   EFI_SECTION_RAW,
                   0,
                   Table,
                   (UINTN *)TableSize
                   );
        if (EFI_ERROR (Status)) {
          continue;
        }

        return EFI_SUCCESS;
      }

      return EFI_NOT_FOUND;
    case EfiGetPlatformBinaryOem16Data:
    case EfiGetPlatformBinaryMpTable:
    case EfiGetPlatformBinaryOemIntData:
    case EfiGetPlatformBinaryOem32Data:
    case EfiGetPlatformBinaryTpmBinary:
    case EfiGetPlatformPciExpressBase:
    default:
      return EFI_UNSUPPORTED;
  }
}

/**
  Translates the given PIRQ accounting for bridge.
  This function translates the given PIRQ back through all buses, if required,
  and returns the true PIRQ and associated IRQ.

  @param  This                  The protocol instance pointer.
  @param  PciBus                The PCI bus number for this device.
  @param  PciDevice             The PCI device number for this device.
  @param  PciFunction           The PCI function number for this device.
  @param  Pirq                  Input is PIRQ reported by device, and output is true PIRQ.
  @param  PciIrq                The IRQ already assigned to the PIRQ, or the IRQ to be
                                assigned to the PIRQ.

  @retval EFI_SUCCESS           The PIRQ was translated.

**/
EFI_STATUS
EFIAPI
TranslatePirq (
  IN        EFI_LEGACY_BIOS_PLATFORM_PROTOCOL  *This,
  IN        UINTN                              PciBus,
  IN        UINTN                              PciDevice,
  IN        UINTN                              PciFunction,
  IN  OUT   UINT8                              *Pirq,
  OUT   UINT8                                  *PciIrq
  )
{
  EFI_LEGACY_INTERRUPT_PROTOCOL  *LegacyInterrupt;
  EFI_STATUS                     Status;
  UINTN                          Index;
  UINTN                          Index1;
  UINT8                          LocalPirq;
  UINT8                          PirqData;
  UINT8                          MatchData;

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

  for (Index = 0; Index < MAX_IRQ_ROUTING_ENTRIES; Index++) {
    if ((PirqTableHead.IrqRoutingEntry[Index].Bus == PciBus) &&
        (PirqTableHead.IrqRoutingEntry[Index].Device == PciDevice))
    {
      LocalPirq = (UINT8)(PirqTableHead.IrqRoutingEntry[Index].PirqEntry[LocalPirq].Pirq & 0x0f);
      if (LocalPirq > 4) {
        LocalPirq -= 4;
      }

      LegacyInterrupt->ReadPirq (LegacyInterrupt, LocalPirq, &PirqData);
      MatchData = PCI_UNUSED;
      while (PirqData == 0) {
        for (Index1 = 0; Index1 < MAX_IRQ_PRIORITY_ENTRIES; Index1++) {
          if ((IrqPriorityTable[Index1].Used == MatchData) &&
              (IrqPriorityTable[Index1].Irq != 0))
          {
            PirqData                      = IrqPriorityTable[Index1].Irq;
            IrqPriorityTable[Index1].Used = 0xff;
            LegacyInterrupt->WritePirq (
                               LegacyInterrupt,
                               LocalPirq,
                               PirqData
                               );
            break;
          }
        }

        if (PirqData == 0) {
          //
          // No unused interrupts, so start reusing them.
          //
          MatchData = (UINT8)(~MatchData);
        }
      }

      *PciIrq = PirqData;
      *Pirq   = LocalPirq;
    }
  }

  return EFI_SUCCESS;
}

/**
  Attempt to legacy boot the BootOption. If the EFI contexted has been
  compromised this function will not return.

  @param  This                   The protocol instance pointer.
  @param  BbsDevicePath          The EFI Device Path from BootXXXX variable.
  @param  BbsTable               The Internal BBS table.
  @param  LoadOptionSize         The size of LoadOption in size.
  @param  LoadOption             The LoadOption from BootXXXX variable
  @param  EfiToLegacy16BootTable A pointer to BootTable structure

  @retval EFI_SUCCESS           Ready to boot.

**/
EFI_STATUS
EFIAPI
PrepareToBoot (
  IN  EFI_LEGACY_BIOS_PLATFORM_PROTOCOL  *This,
  IN  BBS_BBS_DEVICE_PATH                *BbsDevicePath,
  IN  VOID                               *BbsTable,
  IN  UINT32                             LoadOptionsSize,
  IN  VOID                               *LoadOptions,
  IN  VOID                               *EfiToLegacy16BootTable
  )
{
  BBS_TABLE                          *LocalBbsTable;
  EFI_TO_COMPATIBILITY16_BOOT_TABLE  *Legacy16BootTable;
  DEVICE_PRODUCER_DATA_HEADER        *SioPtr;
  UINT16                             DevicePathType;
  UINT16                             Index;
  UINT16                             Priority;

  //
  // Initialize values
  //
  Priority          = 0;
  Legacy16BootTable = (EFI_TO_COMPATIBILITY16_BOOT_TABLE *)EfiToLegacy16BootTable;

  //
  // Set how Gate A20 is gated by hardware
  //
  SioPtr                  = &Legacy16BootTable->SioData;
  SioPtr->Flags.A20Kybd   = 1;
  SioPtr->Flags.A20Port90 = 1;
  SioPtr->MousePresent    = 1;

  LocalBbsTable = BbsTable;

  //
  // There are 2 cases that must be covered.
  // Case 1: Booting to a legacy OS - BbsDevicePath is non-NULL.
  // Case 2: Booting to an EFI aware OS - BbsDevicePath is NULL.
  //         We need to perform the PrepareToBoot function to assign
  //         drive numbers to HDD devices to allow the shell or EFI
  //         to access them.
  //
  if (BbsDevicePath != NULL) {
    DevicePathType = BbsDevicePath->DeviceType;
  } else {
    DevicePathType = BBS_HARDDISK;
  }

  //
  // Skip the boot devices where priority is set by BDS and set the next one
  //
  for (Index = 0; Index < Legacy16BootTable->NumberBbsEntries; Index++) {
    if ((LocalBbsTable[Index].BootPriority != BBS_UNPRIORITIZED_ENTRY) &&
        (LocalBbsTable[Index].BootPriority != BBS_IGNORE_ENTRY) &&
        (LocalBbsTable[Index].BootPriority != BBS_LOWEST_PRIORITY) &&
        (Priority <= LocalBbsTable[Index].BootPriority))
    {
      Priority = (UINT16)(LocalBbsTable[Index].BootPriority + 1);
    }
  }

  switch (DevicePathType) {
    case BBS_FLOPPY:
    case BBS_HARDDISK:
    case BBS_CDROM:
    case BBS_EMBED_NETWORK:
      for (Index = 0; Index < Legacy16BootTable->NumberBbsEntries; Index++) {
        if ((LocalBbsTable[Index].BootPriority == BBS_UNPRIORITIZED_ENTRY) &&
            (LocalBbsTable[Index].DeviceType == DevicePathType))
        {
          LocalBbsTable[Index].BootPriority = Priority;
          ++Priority;
        }
      }

      break;
    case BBS_BEV_DEVICE:
      for (Index = 0; Index < Legacy16BootTable->NumberBbsEntries; Index++) {
        if ((LocalBbsTable[Index].BootPriority == BBS_UNPRIORITIZED_ENTRY) &&
            (LocalBbsTable[Index].Class == 01) &&
            (LocalBbsTable[Index].SubClass == 01))
        {
          LocalBbsTable[Index].BootPriority = Priority;
          ++Priority;
        }
      }

      break;
    case BBS_USB:
    case BBS_PCMCIA:
    case BBS_UNKNOWN:
    default:
      break;
  }

  //
  // Set priority for rest of devices
  //
  for (Index = 0; Index < Legacy16BootTable->NumberBbsEntries; Index++) {
    if (LocalBbsTable[Index].BootPriority == BBS_UNPRIORITIZED_ENTRY) {
      LocalBbsTable[Index].BootPriority = Priority;
      ++Priority;
    }
  }

  return EFI_SUCCESS;
}

/**
  Initialize Legacy Platform support

  @retval EFI_SUCCESS   Successfully initialized

**/
EFI_STATUS
LegacyBiosPlatformInstall (
  VOID
  )
{
  EFI_STATUS                     Status;
  LEGACY_BIOS_PLATFORM_INSTANCE  *Private;

  mImageHandle = gImageHandle;
  Private      = &mPrivateData;

  //
  // Grab a copy of all the protocols we depend on.
  //
  Private->Signature                            = LEGACY_BIOS_PLATFORM_INSTANCE_SIGNATURE;
  Private->LegacyBiosPlatform.GetPlatformInfo   = GetPlatformInfo;
  Private->LegacyBiosPlatform.GetPlatformHandle = GetPlatformHandle;
  Private->LegacyBiosPlatform.SmmInit           = SmmInit;
  Private->LegacyBiosPlatform.PlatformHooks     = PlatformHooks;
  Private->LegacyBiosPlatform.GetRoutingTable   = GetRoutingTable;
  Private->LegacyBiosPlatform.TranslatePirq     = TranslatePirq;
  Private->LegacyBiosPlatform.PrepareToBoot     = PrepareToBoot;
  Private->ImageHandle                          = gImageHandle;

  //
  // Make a new handle and install the protocol
  //
  Private->Handle = NULL;
  Status          = gBS->InstallProtocolInterface (
                           &Private->Handle,
                           &gEfiLegacyBiosPlatformProtocolGuid,
                           EFI_NATIVE_INTERFACE,
                           &Private->LegacyBiosPlatform
                           );
  return Status;
}
