/** @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)) {
      //
      // Detemine 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 ((EFI_D_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
            // configureation.
            //
            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 interrpts, 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;
}

