text: /** @file
  *
  *  Copyright (c) 2013-2020, ARM Limited. All rights reserved.
  *
  *  SPDX-License-Identifier: BSD-2-Clause-Patent
  *
  **/
  
  #include "ArmJunoDxeInternal.h"
  #include <ArmPlatform.h>
  
  #include <IndustryStandard/Pci.h>
  #include <Protocol/DevicePathFromText.h>
  #include <Protocol/PciIo.h>
  #include <Protocol/PciRootBridgeIo.h>
  
  #include <Guid/EventGroup.h>
  #include <Guid/GlobalVariable.h>
  
  #include <Library/ArmShellCmdLib.h>
  #include <Library/AcpiLib.h>
  #include <Library/BaseMemoryLib.h>
  #include <Library/DevicePathLib.h>
  #include <Library/MemoryAllocationLib.h>
  #include <Library/NonDiscoverableDeviceRegistrationLib.h>
  #include <Library/UefiRuntimeServicesTableLib.h>
  #include <Library/IoLib.h>
  #include <Library/PrintLib.h>
  
  #ifndef DYNAMIC_TABLES_FRAMEWORK
  // This GUID must match the FILE_GUID in ArmPlatformPkg/ArmJunoPkg/AcpiTables/AcpiTables.inf
  STATIC CONST EFI_GUID mJunoAcpiTableFile = { 0xa1dd808e, 0x1e95, 0x4399, { 0xab, 0xc0, 0x65, 0x3c, 0x82, 0xe8, 0x53, 0x0c } };
  STATIC VOID *mAcpiRegistration = NULL;
  #endif
  
  STATIC VOID *mPciIoNotificationRegistration = NULL;
  
  /**
    This function reads PCI ID of the controller.
  
    @param[in]  PciIo   PCI IO protocol handle
    @param[in]  PciId   Looking for specified PCI ID Vendor/Device
  **/
  STATIC
  EFI_STATUS
  ReadMarvellYoukonPciId (
    IN EFI_PCI_IO_PROTOCOL  *PciIo,
    IN UINT32               PciId
    )
  {
    UINT32      DevicePciId;
    EFI_STATUS  Status;
  
    Status = PciIo->Pci.Read (
                          PciIo,
                          EfiPciIoWidthUint32,
                          PCI_VENDOR_ID_OFFSET,
                          1,
                          &DevicePciId);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    if (DevicePciId != PciId) {
      return EFI_NOT_FOUND;
    }
  
    return EFI_SUCCESS;
  }
  
  /**
   This function restore the original controller attributes
  
     @param[in]   PciIo               PCI IO protocol handle
     @param[in]   PciAttr             PCI controller attributes.
     @param[in]   AcpiResDescriptor   ACPI 2.0 resource descriptors for the BAR
  **/
  STATIC
  VOID
  RestorePciDev (
    IN EFI_PCI_IO_PROTOCOL                *PciIo,
    IN UINT64                             PciAttr
    )
  {
    PciIo->Attributes (
             PciIo,
             EfiPciIoAttributeOperationSet,
             PciAttr,
             NULL
             );
  }
  
  /**
   This function returns PCI MMIO base address for a controller
  
     @param[in]   PciIo               PCI IO protocol handle
     @param[out]  PciRegBase          PCI base MMIO address
  **/
  STATIC
  EFI_STATUS
  BarIsDeviceMemory (
    IN   EFI_PCI_IO_PROTOCOL *PciIo,
    OUT  UINT32              *PciRegBase
    )
  {
    EFI_STATUS                         Status;
    EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR  *AcpiResDescriptor;
    EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR  *AcpiCurrentDescriptor;
  
    // Marvell Yukon's Bar0 provides base memory address for control registers
    Status = PciIo->GetBarAttributes (PciIo, PCI_BAR_IDX0, NULL, (VOID**)&AcpiResDescriptor);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    AcpiCurrentDescriptor = AcpiResDescriptor;
  
    // Search for a memory type descriptor
    while (AcpiCurrentDescriptor->Desc != ACPI_END_TAG_DESCRIPTOR) {
  
      // Check if Bar is memory type one and fetch a base address
      if (AcpiCurrentDescriptor->Desc == ACPI_ADDRESS_SPACE_DESCRIPTOR &&
          AcpiCurrentDescriptor->ResType == ACPI_ADDRESS_SPACE_TYPE_MEM &&
          !(AcpiCurrentDescriptor->SpecificFlag & ACPI_SPECFLAG_PREFETCHABLE)) {
        *PciRegBase = AcpiCurrentDescriptor->AddrRangeMin;
        break;
      } else {
        Status = EFI_UNSUPPORTED;
      }
  
      AcpiCurrentDescriptor = (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *) (AcpiCurrentDescriptor + 1);
    }
  
    gBS->FreePool (AcpiResDescriptor);
  
    return Status;
  }
  
  /**
   This function provides PCI MMIO base address, old PCI controller attributes.
  
     @param[in]   PciIo               PCI IO protocol handle
     @param[out]  PciRegBase          PCI base MMIO address
     @param[out]  OldPciAttr          Old PCI controller attributes.
  **/
  STATIC
  EFI_STATUS
  InitPciDev (
    IN EFI_PCI_IO_PROTOCOL                 *PciIo,
    OUT UINT32                             *PciRegBase,
    OUT UINT64                             *OldPciAttr
    )
  {
    UINT64      AttrSupports;
    EFI_STATUS  Status;
  
    // Get controller's current attributes
    Status = PciIo->Attributes (
                      PciIo,
                      EfiPciIoAttributeOperationGet,
                      0,
                      OldPciAttr);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    // Fetch supported attributes
    Status = PciIo->Attributes (
                      PciIo,
                      EfiPciIoAttributeOperationSupported,
                      0,
                      &AttrSupports);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    // Enable EFI_PCI_IO_ATTRIBUTE_IO, EFI_PCI_IO_ATTRIBUTE_MEMORY and
    // EFI_PCI_IO_ATTRIBUTE_BUS_MASTER bits in the PCI Config Header
    AttrSupports &= EFI_PCI_DEVICE_ENABLE;
    Status = PciIo->Attributes (
                      PciIo,
                      EfiPciIoAttributeOperationEnable,
                      AttrSupports,
                      NULL);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    Status = BarIsDeviceMemory (PciIo, PciRegBase);
    if (EFI_ERROR (Status)) {
      RestorePciDev (PciIo, *OldPciAttr);
    }
  
    return Status;
  }
  
  /**
   This function reads MAC address from IOFPGA and writes it to Marvell Yukon NIC
  
     @param[in]   PciRegBase   PCI base MMIO address
  **/
  STATIC
  EFI_STATUS
  WriteMacAddress (
    IN UINT32  PciRegBase
    )
  {
    UINT32  MacHigh;
    UINT32  MacLow;
  
    // Read MAC address from IOFPGA
    MacHigh= MmioRead32 (ARM_JUNO_SYS_PCIGBE_H);
    MacLow = MmioRead32 (ARM_JUNO_SYS_PCIGBE_L);
  
    // Set software reset control register to protect from deactivation
    // the config write state
    MmioWrite16 (PciRegBase + R_CONTROL_STATUS, CS_RESET_CLR);
  
    // Convert to Marvell MAC Address register format
    MacHigh = SwapBytes32 ((MacHigh & 0xFFFF) << 16 |
                           (MacLow & 0xFFFF0000) >> 16);
    MacLow = SwapBytes32 (MacLow) >> 16;
  
    // Set MAC Address
    MmioWrite8 (PciRegBase + R_TST_CTRL_1, TST_CFG_WRITE_ENABLE);
    MmioWrite32 (PciRegBase + R_MAC, MacHigh);
    MmioWrite32 (PciRegBase + R_MAC_MAINT, MacHigh);
    MmioWrite32 (PciRegBase + R_MAC + R_MAC_LOW, MacLow);
    MmioWrite32 (PciRegBase + R_MAC_MAINT + R_MAC_LOW, MacLow);
    MmioWrite8 (PciRegBase + R_TST_CTRL_1, TST_CFG_WRITE_DISABLE);
  
    // Initiate device reset
    MmioWrite16 (PciRegBase + R_CONTROL_STATUS, CS_RESET_SET);
    MmioWrite16 (PciRegBase + R_CONTROL_STATUS, CS_RESET_CLR);
  
    return EFI_SUCCESS;
  }
  
  /**
    The function reads MAC address from Juno IOFPGA registers and writes it
    into Marvell Yukon NIC.
  **/
  STATIC
  EFI_STATUS
  ArmJunoSetNicMacAddress (
    IN  EFI_PCI_IO_PROTOCOL   *PciIo
    )
  {
    UINT64                              OldPciAttr;
    UINT32                              PciRegBase;
    EFI_STATUS                          Status;
  
    PciRegBase = 0;
    Status = InitPciDev (PciIo, &PciRegBase, &OldPciAttr);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    Status = WriteMacAddress (PciRegBase);
  
    RestorePciDev (PciIo, OldPciAttr);
  
    return EFI_SUCCESS;
  }
  
  /**
    This function is called when a gEfiPciIoProtocolGuid protocol instance is
    registered in the protocol database.
  
    @param[in]  Event    Event declared in the entry point of the driver whose
                         notification function is being invoked.
    @param[in]  Context  NULL
  **/
  STATIC
  VOID
  PciIoNotificationEvent (
    IN EFI_EVENT  Event,
    IN VOID       *Context
    )
  {
    EFI_STATUS            Status;
    EFI_PCI_IO_PROTOCOL   *PciIo;
  
    Status = gBS->LocateProtocol (&gEfiPciIoProtocolGuid,
                    mPciIoNotificationRegistration, (VOID **)&PciIo);
    if (EFI_ERROR (Status)) {
      return;
    }
  
    Status = ReadMarvellYoukonPciId (PciIo, JUNO_MARVELL_YUKON_ID);
    if (EFI_ERROR (Status)) {
      return;
    }
  
    Status = ArmJunoSetNicMacAddress (PciIo);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "ArmJunoDxe: Failed to set Marvell Yukon NIC MAC address\n"));
    }
    gBS->CloseEvent (Event);
  }
  
  EFI_STATUS
  EFIAPI
  ArmJunoEntryPoint (
    IN EFI_HANDLE         ImageHandle,
    IN EFI_SYSTEM_TABLE   *SystemTable
    )
  {
    EFI_STATUS            Status;
    EFI_PHYSICAL_ADDRESS  HypBase;
    CHAR16                *TextDevicePath;
    UINTN                 TextDevicePathSize;
    UINT32                JunoRevision;
  
    //
    // Register the OHCI and EHCI controllers as non-coherent
    // non-discoverable devices.
    //
    Status = RegisterNonDiscoverableMmioDevice (
               NonDiscoverableDeviceTypeOhci,
               NonDiscoverableDeviceDmaTypeNonCoherent,
               NULL,
               NULL,
               1,
               FixedPcdGet32 (PcdSynopsysUsbOhciBaseAddress),
               SIZE_64KB
               );
    ASSERT_EFI_ERROR (Status);
  
    Status = RegisterNonDiscoverableMmioDevice (
               NonDiscoverableDeviceTypeEhci,
               NonDiscoverableDeviceDmaTypeNonCoherent,
               NULL,
               NULL,
               1,
               FixedPcdGet32 (PcdSynopsysUsbEhciBaseAddress),
               SIZE_64KB
               );
    ASSERT_EFI_ERROR (Status);
  
    //
    // If a hypervisor has been declared then we need to make sure its region is protected at runtime
    //
    // Note: This code is only a workaround for our dummy hypervisor (ArmPkg/Extra/AArch64ToAArch32Shim/)
    //       that does not set up (yet) the stage 2 translation table to hide its own memory to EL1.
    //
    if (FixedPcdGet32 (PcdHypFvSize) != 0) {
      // Ensure the hypervisor region is strictly contained into a EFI_PAGE_SIZE-aligned region.
      // The memory must be a multiple of EFI_PAGE_SIZE to ensure we do not reserve more memory than the hypervisor itself.
      // A UEFI Runtime region size granularity cannot be smaller than EFI_PAGE_SIZE. If the hypervisor size is not rounded
      // to this size then there is a risk some non-runtime memory could be visible to the OS view.
      if (((FixedPcdGet32 (PcdHypFvSize) & EFI_PAGE_MASK) == 0) && ((FixedPcdGet32 (PcdHypFvBaseAddress) & EFI_PAGE_MASK) == 0)) {
        // The memory needs to be declared because the DXE core marked it as reserved and removed it from the memory space
        // as it contains the Firmware.
        Status = gDS->AddMemorySpace (
            EfiGcdMemoryTypeSystemMemory,
            FixedPcdGet32 (PcdHypFvBaseAddress), FixedPcdGet32 (PcdHypFvSize),
            EFI_MEMORY_WB | EFI_MEMORY_RUNTIME
            );
        if (!EFI_ERROR (Status)) {
          // We allocate the memory to ensure it is marked as runtime memory
          HypBase = FixedPcdGet32 (PcdHypFvBaseAddress);
          Status = gBS->AllocatePages (AllocateAddress, EfiRuntimeServicesCode,
                                       EFI_SIZE_TO_PAGES (FixedPcdGet32 (PcdHypFvSize)), &HypBase);
        }
      } else {
        // The hypervisor must be contained into a EFI_PAGE_SIZE-aligned region and its size must also be aligned
        // on a EFI_PAGE_SIZE boundary (ie: 4KB).
        Status = EFI_UNSUPPORTED;
        ASSERT_EFI_ERROR (Status);
      }
  
      if (EFI_ERROR (Status)) {
        return Status;
      }
    }
  
    // Install dynamic Shell command to run baremetal binaries.
    Status = ShellDynCmdRunAxfInstall (ImageHandle);
    if (EFI_ERROR (Status)) {
      DEBUG ((EFI_D_ERROR, "ArmJunoDxe: Failed to install ShellDynCmdRunAxf\n"));
    }
  
    GetJunoRevision(JunoRevision);
  
  #ifndef DYNAMIC_TABLES_FRAMEWORK
    //
    // Try to install the ACPI Tables
    //
    Status = LocateAndInstallAcpiFromFv (&mJunoAcpiTableFile);
    ASSERT_EFI_ERROR (Status);
  #endif
  
    //
    // Setup R1/R2 options if not already done.
    //
    if (JunoRevision != JUNO_REVISION_R0) {
      // Enable PCI enumeration
      PcdSetBoolS (PcdPciDisableBusEnumeration, FALSE);
  
      //
      // Create a protocol notification event handler on the PciIo protocol
      // so we can set the MAC address on the Marvell Yukon as soon as it
      // appears.
      //
      EfiCreateProtocolNotifyEvent (
          &gEfiPciIoProtocolGuid,
          TPL_NOTIFY,
          PciIoNotificationEvent,
          NULL,
          &mPciIoNotificationRegistration
          );
  
  #ifndef DYNAMIC_TABLES_FRAMEWORK
      // Declare the related ACPI Tables
      EfiCreateProtocolNotifyEvent (
          &gEfiAcpiTableProtocolGuid,
          TPL_CALLBACK,
          AcpiPciNotificationEvent,
          NULL,
          &mAcpiRegistration
          );
  #endif
    }
  
    //
    // Set up the device path to the FDT.
    //
    TextDevicePath = (CHAR16*)FixedPcdGetPtr (PcdJunoFdtDevicePath);
    if (TextDevicePath != NULL) {
      TextDevicePathSize = StrSize (TextDevicePath);
      Status = PcdSetPtrS (
                 PcdFdtDevicePaths,
                 &TextDevicePathSize,
                 TextDevicePath
                 );
    } else {
      Status = EFI_NOT_FOUND;
    }
  
    if (EFI_ERROR (Status)) {
      DEBUG (
        (EFI_D_ERROR,
        "ArmJunoDxe: Setting of FDT device path in PcdFdtDevicePaths failed - %r\n", Status)
        );
      return Status;
    }
  
    return Status;
  }
  