/** @file

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

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

**/

#include "LegacyBiosInterface.h"
#include <IndustryStandard/Pci.h>

// Give floppy 3 states
// FLOPPY_PRESENT_WITH_MEDIA  = Floppy controller present and media is inserted
// FLOPPY_NOT_PRESENT = No floppy controller present
// FLOPPY_PRESENT_NO_MEDIA = Floppy controller present but no media inserted
//
#define FLOPPY_NOT_PRESENT         0
#define FLOPPY_PRESENT_WITH_MEDIA  1
#define FLOPPY_PRESENT_NO_MEDIA    2

BBS_TABLE  *mBbsTable;
BOOLEAN    mBbsTableDoneFlag   = FALSE;
BOOLEAN    IsHaveMediaInFloppy = TRUE;

/**
  Checks the state of the floppy and if media is inserted.

  This routine checks the state of the floppy and if media is inserted.
  There are 3 cases:
  No floppy present         - Set BBS entry to ignore
  Floppy present & no media - Set BBS entry to lowest priority. We cannot
  set it to ignore since 16-bit CSM will
  indicate no floppy and thus drive A: is
  unusable. CSM-16 will not try floppy since
  lowest priority and thus not incur boot
  time penality.
  Floppy present & media    - Set BBS entry to some priority.

  @return  State of floppy media

**/
UINT8
HasMediaInFloppy (
  VOID
  )
{
  EFI_STATUS             Status;
  UINTN                  HandleCount;
  EFI_HANDLE             *HandleBuffer;
  UINTN                  Index;
  EFI_ISA_IO_PROTOCOL    *IsaIo;
  EFI_BLOCK_IO_PROTOCOL  *BlkIo;

  HandleBuffer = NULL;
  HandleCount  = 0;

  gBS->LocateHandleBuffer (
         ByProtocol,
         &gEfiIsaIoProtocolGuid,
         NULL,
         &HandleCount,
         &HandleBuffer
         );

  //
  // If don't find any ISA/IO protocol assume no floppy. Need for floppy
  // free system
  //
  if (HandleCount == 0) {
    return FLOPPY_NOT_PRESENT;
  }

  ASSERT (HandleBuffer != NULL);

  for (Index = 0; Index < HandleCount; Index++) {
    Status = gBS->HandleProtocol (
                    HandleBuffer[Index],
                    &gEfiIsaIoProtocolGuid,
                    (VOID **)&IsaIo
                    );
    if (EFI_ERROR (Status)) {
      continue;
    }

    if (IsaIo->ResourceList->Device.HID != EISA_PNP_ID (0x604)) {
      continue;
    }

    //
    // Update blockio in case the floppy is inserted in during BdsTimeout
    //
    Status = gBS->DisconnectController (HandleBuffer[Index], NULL, NULL);

    if (EFI_ERROR (Status)) {
      continue;
    }

    Status = gBS->ConnectController (HandleBuffer[Index], NULL, NULL, TRUE);

    if (EFI_ERROR (Status)) {
      continue;
    }

    Status = gBS->HandleProtocol (
                    HandleBuffer[Index],
                    &gEfiBlockIoProtocolGuid,
                    (VOID **)&BlkIo
                    );
    if (EFI_ERROR (Status)) {
      continue;
    }

    if (BlkIo->Media->MediaPresent) {
      FreePool (HandleBuffer);
      return FLOPPY_PRESENT_WITH_MEDIA;
    } else {
      FreePool (HandleBuffer);
      return FLOPPY_PRESENT_NO_MEDIA;
    }
  }

  FreePool (HandleBuffer);

  return FLOPPY_NOT_PRESENT;
}

/**
  Complete build of BBS TABLE.

  @param  Private                 Legacy BIOS Instance data
  @param  BbsTable                BBS Table passed to 16-bit code

  @retval EFI_SUCCESS             Removable media not present

**/
EFI_STATUS
LegacyBiosBuildBbs (
  IN  LEGACY_BIOS_INSTANCE  *Private,
  IN  BBS_TABLE             *BbsTable
  )
{
  UINTN       BbsIndex;
  HDD_INFO    *HddInfo;
  UINTN       HddIndex;
  UINTN       Index;
  EFI_HANDLE  *BlockIoHandles;
  UINTN       NumberBlockIoHandles;
  UINTN       BlockIndex;
  EFI_STATUS  Status;

  //
  // First entry is floppy.
  // Next 2*MAX_IDE_CONTROLLER entries are for onboard IDE.
  // Next n entries are filled in after each ROM is dispatched.
  //   Entry filled in if follow BBS spec. See LegacyPci.c
  // Next entries are for non-BBS compliant ROMS. They are filled in by
  //   16-bit code during Legacy16UpdateBbs invocation. Final BootPriority
  //   occurs after that invocation.
  //
  // Floppy
  // Set default state.
  //
  IsHaveMediaInFloppy = HasMediaInFloppy ();
  if (IsHaveMediaInFloppy == FLOPPY_PRESENT_WITH_MEDIA) {
    BbsTable[0].BootPriority = BBS_UNPRIORITIZED_ENTRY;
  } else {
    if (IsHaveMediaInFloppy == FLOPPY_PRESENT_NO_MEDIA) {
      BbsTable[0].BootPriority = BBS_LOWEST_PRIORITY;
    } else {
      BbsTable[0].BootPriority = BBS_IGNORE_ENTRY;
    }
  }

  BbsTable[0].Bus                      = 0xff;
  BbsTable[0].Device                   = 0xff;
  BbsTable[0].Function                 = 0xff;
  BbsTable[0].DeviceType               = BBS_FLOPPY;
  BbsTable[0].Class                    = 01;
  BbsTable[0].SubClass                 = 02;
  BbsTable[0].StatusFlags.OldPosition  = 0;
  BbsTable[0].StatusFlags.Reserved1    = 0;
  BbsTable[0].StatusFlags.Enabled      = 0;
  BbsTable[0].StatusFlags.Failed       = 0;
  BbsTable[0].StatusFlags.MediaPresent = 0;
  BbsTable[0].StatusFlags.Reserved2    = 0;

  //
  // Onboard HDD - Note Each HDD controller controls 2 drives
  //               Master & Slave
  //
  HddInfo = &Private->IntThunk->EfiToLegacy16BootTable.HddInfo[0];
  //
  // Get IDE Drive Info
  //
  LegacyBiosBuildIdeData (Private, &HddInfo, 0);

  for (HddIndex = 0; HddIndex < MAX_IDE_CONTROLLER; HddIndex++) {
    BbsIndex = HddIndex * 2 + 1;
    for (Index = 0; Index < 2; ++Index) {
      BbsTable[BbsIndex + Index].Bus                      = HddInfo[HddIndex].Bus;
      BbsTable[BbsIndex + Index].Device                   = HddInfo[HddIndex].Device;
      BbsTable[BbsIndex + Index].Function                 = HddInfo[HddIndex].Function;
      BbsTable[BbsIndex + Index].Class                    = 01;
      BbsTable[BbsIndex + Index].SubClass                 = 01;
      BbsTable[BbsIndex + Index].StatusFlags.OldPosition  = 0;
      BbsTable[BbsIndex + Index].StatusFlags.Reserved1    = 0;
      BbsTable[BbsIndex + Index].StatusFlags.Enabled      = 0;
      BbsTable[BbsIndex + Index].StatusFlags.Failed       = 0;
      BbsTable[BbsIndex + Index].StatusFlags.MediaPresent = 0;
      BbsTable[BbsIndex + Index].StatusFlags.Reserved2    = 0;

      //
      // If no controller found or no device found set to ignore
      // else set to unprioritized and set device type
      //
      if (HddInfo[HddIndex].CommandBaseAddress == 0) {
        BbsTable[BbsIndex + Index].BootPriority = BBS_IGNORE_ENTRY;
      } else {
        if (Index == 0) {
          if ((HddInfo[HddIndex].Status & (HDD_MASTER_IDE | HDD_MASTER_ATAPI_CDROM | HDD_MASTER_ATAPI_ZIPDISK)) != 0) {
            BbsTable[BbsIndex + Index].BootPriority = BBS_UNPRIORITIZED_ENTRY;
            if ((HddInfo[HddIndex].Status & HDD_MASTER_IDE) != 0) {
              BbsTable[BbsIndex + Index].DeviceType = BBS_HARDDISK;
            } else if ((HddInfo[HddIndex].Status & HDD_MASTER_ATAPI_CDROM) != 0) {
              BbsTable[BbsIndex + Index].DeviceType = BBS_CDROM;
            } else {
              //
              // for ZIPDISK
              //
              BbsTable[BbsIndex + Index].DeviceType = BBS_HARDDISK;
            }
          } else {
            BbsTable[BbsIndex + Index].BootPriority = BBS_IGNORE_ENTRY;
          }
        } else {
          if ((HddInfo[HddIndex].Status & (HDD_SLAVE_IDE | HDD_SLAVE_ATAPI_CDROM | HDD_SLAVE_ATAPI_ZIPDISK)) != 0) {
            BbsTable[BbsIndex + Index].BootPriority = BBS_UNPRIORITIZED_ENTRY;
            if ((HddInfo[HddIndex].Status & HDD_SLAVE_IDE) != 0) {
              BbsTable[BbsIndex + Index].DeviceType = BBS_HARDDISK;
            } else if ((HddInfo[HddIndex].Status & HDD_SLAVE_ATAPI_CDROM) != 0) {
              BbsTable[BbsIndex + Index].DeviceType = BBS_CDROM;
            } else {
              //
              // for ZIPDISK
              //
              BbsTable[BbsIndex + Index].DeviceType = BBS_HARDDISK;
            }
          } else {
            BbsTable[BbsIndex + Index].BootPriority = BBS_IGNORE_ENTRY;
          }
        }
      }
    }
  }

  //
  // Add non-IDE block devices
  //
  BbsIndex = HddIndex * 2 + 1;

  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiBlockIoProtocolGuid,
                  NULL,
                  &NumberBlockIoHandles,
                  &BlockIoHandles
                  );

  if (!EFI_ERROR (Status)) {
    UINTN                     Removable;
    EFI_BLOCK_IO_PROTOCOL     *BlkIo;
    EFI_PCI_IO_PROTOCOL       *PciIo;
    EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
    EFI_DEVICE_PATH_PROTOCOL  *DevicePathNode;
    EFI_HANDLE                PciHandle;
    UINTN                     SegNum;
    UINTN                     BusNum;
    UINTN                     DevNum;
    UINTN                     FuncNum;

    for (Removable = 0; Removable < 2; Removable++) {
      for (BlockIndex = 0; BlockIndex < NumberBlockIoHandles; BlockIndex++) {
        Status = gBS->HandleProtocol (
                        BlockIoHandles[BlockIndex],
                        &gEfiBlockIoProtocolGuid,
                        (VOID **)&BlkIo
                        );

        if (EFI_ERROR (Status)) {
          continue;
        }

        //
        // Skip the logical partitions
        //
        if (BlkIo->Media->LogicalPartition) {
          continue;
        }

        //
        // Skip the fixed block io then the removable block io
        //
        if (BlkIo->Media->RemovableMedia == ((Removable == 0) ? FALSE : TRUE)) {
          continue;
        }

        //
        // Get Device Path
        //
        Status = gBS->HandleProtocol (
                        BlockIoHandles[BlockIndex],
                        &gEfiDevicePathProtocolGuid,
                        (VOID **)&DevicePath
                        );
        if (EFI_ERROR (Status)) {
          continue;
        }

        //
        // Skip ATA devices as they have already been handled
        //
        DevicePathNode = DevicePath;
        while (!IsDevicePathEnd (DevicePathNode)) {
          if ((DevicePathType (DevicePathNode) == MESSAGING_DEVICE_PATH) &&
              (DevicePathSubType (DevicePathNode) == MSG_ATAPI_DP))
          {
            break;
          }

          DevicePathNode = NextDevicePathNode (DevicePathNode);
        }

        if (!IsDevicePathEnd (DevicePathNode)) {
          continue;
        }

        //
        //  Locate which PCI device
        //
        Status = gBS->LocateDevicePath (
                        &gEfiPciIoProtocolGuid,
                        &DevicePath,
                        &PciHandle
                        );
        if (EFI_ERROR (Status)) {
          continue;
        }

        Status = gBS->HandleProtocol (
                        PciHandle,
                        &gEfiPciIoProtocolGuid,
                        (VOID **)&PciIo
                        );
        if (EFI_ERROR (Status)) {
          continue;
        }

        Status = PciIo->GetLocation (
                          PciIo,
                          &SegNum,
                          &BusNum,
                          &DevNum,
                          &FuncNum
                          );
        if (EFI_ERROR (Status)) {
          continue;
        }

        if (SegNum != 0) {
          DEBUG ((
            DEBUG_WARN,
            "CSM cannot use PCI devices in segment %Lu\n",
            (UINT64)SegNum
            ));
          continue;
        }

        DEBUG ((
          DEBUG_INFO,
          "Add Legacy Bbs entry for PCI %d/%d/%d\n",
          BusNum,
          DevNum,
          FuncNum
          ));

        BbsTable[BbsIndex].Bus                      = BusNum;
        BbsTable[BbsIndex].Device                   = DevNum;
        BbsTable[BbsIndex].Function                 = FuncNum;
        BbsTable[BbsIndex].Class                    = 1;
        BbsTable[BbsIndex].SubClass                 = 0x80;
        BbsTable[BbsIndex].StatusFlags.OldPosition  = 0;
        BbsTable[BbsIndex].StatusFlags.Reserved1    = 0;
        BbsTable[BbsIndex].StatusFlags.Enabled      = 0;
        BbsTable[BbsIndex].StatusFlags.Failed       = 0;
        BbsTable[BbsIndex].StatusFlags.MediaPresent = 0;
        BbsTable[BbsIndex].StatusFlags.Reserved2    = 0;
        BbsTable[BbsIndex].DeviceType               = BBS_HARDDISK;
        BbsTable[BbsIndex].BootPriority             = BBS_UNPRIORITIZED_ENTRY;
        BbsIndex++;

        if (BbsIndex == MAX_BBS_ENTRIES) {
          Removable = 2;
          break;
        }
      }
    }

    FreePool (BlockIoHandles);
  }

  return EFI_SUCCESS;
}

/**
  Get all BBS info

  @param  This                    Protocol instance pointer.
  @param  HddCount                Number of HDD_INFO structures
  @param  HddInfo                 Onboard IDE controller information
  @param  BbsCount                Number of BBS_TABLE structures
  @param  BbsTable                List BBS entries

  @retval EFI_SUCCESS             Tables returned
  @retval EFI_NOT_FOUND           resource not found
  @retval EFI_DEVICE_ERROR        can not get BBS table

**/
EFI_STATUS
EFIAPI
LegacyBiosGetBbsInfo (
  IN EFI_LEGACY_BIOS_PROTOCOL  *This,
  OUT UINT16                   *HddCount,
  OUT HDD_INFO                 **HddInfo,
  OUT UINT16                   *BbsCount,
  OUT BBS_TABLE                **BbsTable
  )
{
  LEGACY_BIOS_INSTANCE               *Private;
  EFI_IA32_REGISTER_SET              Regs;
  EFI_TO_COMPATIBILITY16_BOOT_TABLE  *EfiToLegacy16BootTable;
  //  HDD_INFO                          *LocalHddInfo;
  //  IN BBS_TABLE                      *LocalBbsTable;
  UINTN       NumHandles;
  EFI_HANDLE  *HandleBuffer;
  UINTN       Index;
  UINTN       TempData;
  UINT32      Granularity;

  HandleBuffer = NULL;

  Private                = LEGACY_BIOS_INSTANCE_FROM_THIS (This);
  EfiToLegacy16BootTable = &Private->IntThunk->EfiToLegacy16BootTable;
  //  LocalHddInfo            = EfiToLegacy16BootTable->HddInfo;
  //  LocalBbsTable           = (BBS_TABLE*)(UINTN)EfiToLegacy16BootTable->BbsTable;

  if (!mBbsTableDoneFlag) {
    mBbsTable = Private->BbsTablePtr;

    //
    // Always enable disk controllers so 16-bit CSM code has valid information for all
    // drives.
    //
    //
    // Get PciRootBridgeIO protocol
    //
    gBS->LocateHandleBuffer (
           ByProtocol,
           &gEfiPciRootBridgeIoProtocolGuid,
           NULL,
           &NumHandles,
           &HandleBuffer
           );

    if (NumHandles == 0) {
      return EFI_NOT_FOUND;
    }

    mBbsTableDoneFlag = TRUE;
    for (Index = 0; Index < NumHandles; Index++) {
      //
      // Connect PciRootBridgeIO protocol handle with FALSE parameter to let
      // PCI bus driver enumerate all subsequent handles
      //
      gBS->ConnectController (HandleBuffer[Index], NULL, NULL, FALSE);
    }

    LegacyBiosBuildBbs (Private, mBbsTable);

    Private->LegacyRegion->UnLock (Private->LegacyRegion, 0xe0000, 0x20000, &Granularity);

    //
    // Call into Legacy16 code to add to BBS table for non BBS compliant OPROMs.
    //
    ZeroMem (&Regs, sizeof (EFI_IA32_REGISTER_SET));
    Regs.X.AX = Legacy16UpdateBbs;

    //
    // Pass in handoff data
    //
    TempData  = (UINTN)EfiToLegacy16BootTable;
    Regs.X.ES = NORMALIZE_EFI_SEGMENT ((UINT32)TempData);
    Regs.X.BX = NORMALIZE_EFI_OFFSET ((UINT32)TempData);

    Private->LegacyBios.FarCall86 (
                          This,
                          Private->Legacy16CallSegment,
                          Private->Legacy16CallOffset,
                          &Regs,
                          NULL,
                          0
                          );

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

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

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

  *HddCount = MAX_IDE_CONTROLLER;
  *HddInfo  = EfiToLegacy16BootTable->HddInfo;
  *BbsTable = (BBS_TABLE *)(UINTN)EfiToLegacy16BootTable->BbsTable;
  *BbsCount = (UINT16)(sizeof (Private->IntThunk->BbsTable) / sizeof (BBS_TABLE));
  return EFI_SUCCESS;
}
