/** @file
  Collect IDE information from Native EFI Driver

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

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

**/

#include "LegacyBiosInterface.h"

BOOLEAN mIdeDataBuiltFlag = FALSE;

/**
  Collect IDE Inquiry data from the IDE disks

  @param  Private        Legacy BIOS Instance data
  @param  HddInfo        Hdd Information
  @param  Flag           Reconnect IdeController or not

  @retval EFI_SUCCESS    It should always work.

**/
EFI_STATUS
LegacyBiosBuildIdeData (
  IN  LEGACY_BIOS_INSTANCE      *Private,
  IN  HDD_INFO                  **HddInfo,
  IN  UINT16                    Flag
  )
{
  EFI_STATUS                Status;
  EFI_HANDLE                IdeController;
  UINTN                     HandleCount;
  EFI_HANDLE                *HandleBuffer;
  UINTN                     Index;
  EFI_DISK_INFO_PROTOCOL    *DiskInfo;
  UINT32                    IdeChannel;
  UINT32                    IdeDevice;
  UINT32                    Size;
  UINT8                     *InquiryData;
  UINT32                    InquiryDataSize;
  HDD_INFO                  *LocalHddInfo;
  UINT32                    PciIndex;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePathNode;
  EFI_DEVICE_PATH_PROTOCOL  *TempDevicePathNode;
  PCI_DEVICE_PATH           *PciDevicePath;

  //
  // Only build data once
  // We have a problem with GetBbsInfo in that it can be invoked two
  // places. Once in BDS, when all EFI drivers are connected and once in
  // LegacyBoot after all EFI drivers are disconnected causing this routine
  // to hang. In LegacyBoot this function is also called before EFI drivers
  // are disconnected.
  // Cases covered
  //    GetBbsInfo invoked in BDS. Both invocations in LegacyBoot ignored.
  //    GetBbsInfo not invoked in BDS. First invocation of this function
  //       proceeds normally and second via GetBbsInfo ignored.
  //
  PciDevicePath = NULL;
  LocalHddInfo  = *HddInfo;
  Status = Private->LegacyBiosPlatform->GetPlatformHandle (
                                          Private->LegacyBiosPlatform,
                                          EfiGetPlatformIdeHandle,
                                          0,
                                          &HandleBuffer,
                                          &HandleCount,
                                          (VOID *) &LocalHddInfo
                                          );
  if (!EFI_ERROR (Status)) {
    IdeController = HandleBuffer[0];
    //
    // Force IDE drive spin up!
    //
    if (Flag != 0) {
      gBS->DisconnectController (
            IdeController,
            NULL,
            NULL
            );
    }

    gBS->ConnectController (IdeController, NULL, NULL, FALSE);

    //
    // Do GetIdeHandle twice since disconnect/reconnect will switch to native mode
    // And GetIdeHandle will switch to Legacy mode, if required.
    //
    Private->LegacyBiosPlatform->GetPlatformHandle (
                                  Private->LegacyBiosPlatform,
                                  EfiGetPlatformIdeHandle,
                                  0,
                                  &HandleBuffer,
                                  &HandleCount,
                                  (VOID *) &LocalHddInfo
                                  );
  }

  mIdeDataBuiltFlag = TRUE;

  //
  // Get Identity command from all drives
  //
  gBS->LocateHandleBuffer (
        ByProtocol,
        &gEfiDiskInfoProtocolGuid,
        NULL,
        &HandleCount,
        &HandleBuffer
        );

  Private->IdeDriveCount = (UINT8) HandleCount;
  for (Index = 0; Index < HandleCount; Index++) {
    Status = gBS->HandleProtocol (
                    HandleBuffer[Index],
                    &gEfiDiskInfoProtocolGuid,
                    (VOID **) &DiskInfo
                    );
    ASSERT_EFI_ERROR (Status);

    if (CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoIdeInterfaceGuid)) {
      //
      //  Locate which PCI device
      //
      Status = gBS->HandleProtocol (
                      HandleBuffer[Index],
                      &gEfiDevicePathProtocolGuid,
                      (VOID *) &DevicePath
                      );
      ASSERT_EFI_ERROR (Status);

      DevicePathNode = DevicePath;
      while (!IsDevicePathEnd (DevicePathNode)) {
        TempDevicePathNode = NextDevicePathNode (DevicePathNode);
        if ((DevicePathType (DevicePathNode) == HARDWARE_DEVICE_PATH) &&
              ( DevicePathSubType (DevicePathNode) == HW_PCI_DP) &&
              ( DevicePathType(TempDevicePathNode) == MESSAGING_DEVICE_PATH) &&
              ( DevicePathSubType(TempDevicePathNode) == MSG_ATAPI_DP) ) {
          PciDevicePath = (PCI_DEVICE_PATH *) DevicePathNode;
          break;
        }
        DevicePathNode = NextDevicePathNode (DevicePathNode);
      }

      if (PciDevicePath == NULL) {
        continue;
      }

      //
      // Find start of PCI device in HddInfo. The assumption of the data
      // structure is 2 controllers(channels) per PCI device and each
      // controller can have 2 drives(devices).
      // HddInfo[PciIndex+0].[0] = Channel[0].Device[0] Primary Master
      // HddInfo[PciIndex+0].[1] = Channel[0].Device[1] Primary Slave
      // HddInfo[PciIndex+1].[0] = Channel[1].Device[0] Secondary Master
      // HddInfo[PciIndex+1].[1] = Channel[1].Device[1] Secondary Slave
      // @bug eventually need to pass in max number of entries
      // for end of for loop
      //
      for (PciIndex = 0; PciIndex < 8; PciIndex++) {
        if ((PciDevicePath->Device == LocalHddInfo[PciIndex].Device) &&
            (PciDevicePath->Function == LocalHddInfo[PciIndex].Function)
            ) {
          break;
        }
      }

      if (PciIndex == 8) {
        continue;
      }

      Status = DiskInfo->WhichIde (DiskInfo, &IdeChannel, &IdeDevice);
      if (!EFI_ERROR (Status)) {
        Size = sizeof (ATAPI_IDENTIFY);
        DiskInfo->Identify (
                    DiskInfo,
                    &LocalHddInfo[PciIndex + IdeChannel].IdentifyDrive[IdeDevice],
                    &Size
                    );
        if (IdeChannel == 0) {
          LocalHddInfo[PciIndex + IdeChannel].Status |= HDD_PRIMARY;
        } else if (IdeChannel == 1) {
          LocalHddInfo[PciIndex + IdeChannel].Status |= HDD_SECONDARY;
        }

        InquiryData     = NULL;
        InquiryDataSize = 0;
        Status = DiskInfo->Inquiry (
                             DiskInfo,
                             NULL,
                             &InquiryDataSize
                             );
        if (Status == EFI_BUFFER_TOO_SMALL) {
          InquiryData = (UINT8 *) AllocatePool (
                                  InquiryDataSize
                                  );
          if (InquiryData != NULL) {
            Status = DiskInfo->Inquiry (
                                 DiskInfo,
                                 InquiryData,
                                 &InquiryDataSize
                                 );
          }
        } else {
          Status = EFI_DEVICE_ERROR;
        }

        //
        // If ATAPI device then Inquiry will pass and ATA fail.
        //
        if (!EFI_ERROR (Status)) {
          ASSERT (InquiryData != NULL);
          //
          // If IdeDevice = 0 then set master bit, else slave bit
          //
          if (IdeDevice == 0) {
            if ((InquiryData[0] & 0x1f) == 0x05) {
              LocalHddInfo[PciIndex + IdeChannel].Status |= HDD_MASTER_ATAPI_CDROM;
            } else if ((InquiryData[0] & 0x1f) == 0x00) {
              LocalHddInfo[PciIndex + IdeChannel].Status |= HDD_MASTER_ATAPI_ZIPDISK;
            }
          } else {
            if ((InquiryData[0] & 0x1f) == 0x05) {
              LocalHddInfo[PciIndex + IdeChannel].Status |= HDD_SLAVE_ATAPI_CDROM;
            } else if ((InquiryData[0] & 0x1f) == 0x00) {
              LocalHddInfo[PciIndex + IdeChannel].Status |= HDD_SLAVE_ATAPI_ZIPDISK;
            }
          }
          FreePool (InquiryData);
        } else {
          if (IdeDevice == 0) {
            LocalHddInfo[PciIndex + IdeChannel].Status |= HDD_MASTER_IDE;
          } else {
            LocalHddInfo[PciIndex + IdeChannel].Status |= HDD_SLAVE_IDE;
          }
        }
      }
    }
  }

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

  return EFI_SUCCESS;
}


/**
  If the IDE channel is in compatibility (legacy) mode, remove all
  PCI I/O BAR addresses from the controller.

  @param  IdeController  The handle of target IDE controller


**/
VOID
InitLegacyIdeController (
  IN EFI_HANDLE                        IdeController
  )
{
  EFI_PCI_IO_PROTOCOL               *PciIo;
  UINT32                            IOBarClear;
  EFI_STATUS                        Status;
  PCI_TYPE00                        PciData;

  //
  // If the IDE channel is in compatibility (legacy) mode, remove all
  // PCI I/O BAR addresses from the controller.  Some software gets
  // confused if an IDE controller is in compatibility (legacy) mode
  // and has PCI I/O resources allocated
  //
  Status = gBS->HandleProtocol (
                  IdeController,
                  &gEfiPciIoProtocolGuid,
                  (VOID **)&PciIo
                  );
  if (EFI_ERROR (Status)) {
    return ;
  }

  Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, 0, sizeof (PciData), &PciData);
  if (EFI_ERROR (Status)) {
    return ;
  }

  //
  // Check whether this is IDE
  //
  if ((PciData.Hdr.ClassCode[2] != PCI_CLASS_MASS_STORAGE) ||
      (PciData.Hdr.ClassCode[1] != PCI_CLASS_MASS_STORAGE_IDE)) {
    return ;
  }

  //
  // Clear bar for legacy IDE
  //
  IOBarClear = 0x00;
  if ((PciData.Hdr.ClassCode[0] & IDE_PI_REGISTER_PNE) == 0) {
    PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, 0x10, 1, &IOBarClear);
    PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, 0x14, 1, &IOBarClear);
  }
  if ((PciData.Hdr.ClassCode[0] & IDE_PI_REGISTER_SNE) == 0) {
    PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, 0x18, 1, &IOBarClear);
    PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, 0x1C, 1, &IOBarClear);
  }

  return ;
}