text: /** @file
  
    Copyright (c) 2014, Applied Micro Curcuit Corporation. All rights reserved.<BR>
    Copyright (c) 2015, Hisilicon Limited. All rights reserved.<BR>
    Copyright (c) 2015, Linaro Limited. All rights reserved.<BR>
    SPDX-License-Identifier: BSD-2-Clause-Patent
  
    This driver is called to initialize the FW part of the PHY in preparation
    for the OS.
  
  **/
  
  #include <Guid/ShellVariableGuid.h>
  #include <Library/UefiRuntimeServicesTableLib.h>
  #include <Library/DebugLib.h>
  #include <Library/TimerLib.h>
  
  #include <PiDxe.h>
  #include <Guid/EventGroup.h>
  #include <Protocol/AcpiTable.h>
  #include <Protocol/FirmwareVolume2.h>
  #include <Library/BaseLib.h>
  #include <Library/UefiBootServicesTableLib.h>
  #include <Protocol/AcpiSystemDescriptionTable.h>
  #include <Library/DebugLib.h>
  #include <Library/PcdLib.h>
  #include <Library/PrintLib.h>
  #include <Library/DebugLib.h>
  #include <Library/BaseMemoryLib.h>
  #include <Library/UefiRuntimeServicesTableLib.h>
  #include <IndustryStandard/Acpi.h>
  #include <IndustryStandard/AcpiAml.h>
  
  #include <Protocol/HisiBoardNicProtocol.h>
  
  // Turn on debug message by enabling below define
  //#define ACPI_DEBUG
  
  #ifdef ACPI_DEBUG
  #define DBG(arg...) DEBUG((EFI_D_ERROR,## arg))
  #else
  #define DBG(arg...)
  #endif
  
  #define EFI_ACPI_MAX_NUM_TABLES         20
  #define DSDT_SIGNATURE                  0x54445344
  
  #define D03_ACPI_ETH_ID                     "HISI00C2"
  
  #define ACPI_ETH_MAC_KEY                "local-mac-address"
  
  #define PREFIX_VARIABLE_NAME            L"MAC"
  #define PREFIX_VARIABLE_NAME_COMPAT     L"RGMII_MAC"
  #define MAC_MAX_LEN                     30
  
  EFI_STATUS GetEnvMac(
    IN          UINTN    MacNextID,
    IN OUT      UINT8    *MacBuffer)
  {
    EFI_MAC_ADDRESS Mac;
    EFI_STATUS Status;
    HISI_BOARD_NIC_PROTOCOL *OemNic = NULL;
  
    Status = gBS->LocateProtocol(&gHisiBoardNicProtocolGuid, NULL, (VOID **)&OemNic);
    if(EFI_ERROR(Status))
    {
      DEBUG((EFI_D_ERROR, "[%a]:[%dL] LocateProtocol failed %r\n", __FUNCTION__, __LINE__, Status));
      return Status;
    }
  
    Status = OemNic->GetMac(&Mac, MacNextID);
    if(EFI_ERROR(Status))
    {
      DEBUG((EFI_D_ERROR, "[%a]:[%dL] GetMac failed %r\n", __FUNCTION__, __LINE__, Status));
      return Status;
    }
  
    CopyMem (MacBuffer, &Mac, 6);
    DEBUG((EFI_D_ERROR, "Port %d MAC %02x:%02x:%02x:%02x:%02x:%02x\n",
          MacNextID,
          MacBuffer[0],
          MacBuffer[1],
          MacBuffer[2],
          MacBuffer[3],
          MacBuffer[4],
          MacBuffer[5]
          ));
  
    return EFI_SUCCESS;
  }
  
  EFI_STATUS _SearchReplacePackageMACAddress(
    IN EFI_ACPI_SDT_PROTOCOL  *AcpiTableProtocol,
    IN EFI_ACPI_HANDLE        ChildHandle,
    IN UINTN                  Level,
    IN OUT BOOLEAN            *Found,
    IN UINTN                  MacNextID)
  {
    // ASL template for ethernet driver:
  /*
   *   Name (_DSD, Package () {
   *   ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
   *   Package () {
   *     Package (2) {"mac-address", Package (6) { 00, 11, 22, 33, 44, 55 }}
   *     Package (2) {"phy-channel", 0},
   *     Package (2) {"phy-mode", "rgmii"},
   *     Package (2) {"max-transfer-unit", 0x5dc},   // MTU of 1500
   *     Package (2) {"max-speed", 0x3e8},            // 1000 Mbps
   *   }
   * })
   */
    EFI_STATUS          Status;
    EFI_ACPI_DATA_TYPE  DataType;
    CONST UINT8         *Data;
    CONST VOID          *Buffer;
    UINTN               DataSize;
    UINTN               Count;
    EFI_ACPI_HANDLE     CurrentHandle;
    EFI_ACPI_HANDLE     NextHandle;
    UINT8               MACBuffer[MAC_MAX_LEN];
  
    DBG("In Level:%d\n", Level);
    Status = EFI_SUCCESS;
    for (CurrentHandle = NULL; ;) {
      Status = AcpiTableProtocol->GetChild(ChildHandle, &CurrentHandle);
      if (Level != 3 && (EFI_ERROR(Status) || CurrentHandle == NULL))
         break;
  
      Status = AcpiTableProtocol->GetOption(CurrentHandle, 0, &DataType, &Buffer, &DataSize);
      Data = Buffer;
      DBG("_DSD Child Subnode Store Op Code 0x%02X 0x%02X %02X\n",
          DataSize, Data[0], DataSize > 1 ? Data[1] : 0);
  
      if (Level < 2 && Data[0] != AML_PACKAGE_OP)
        continue;
  
      if (Level == 2 && Data[0] == AML_STRING_PREFIX) {
        Status = AcpiTableProtocol->GetOption(CurrentHandle, 1, &DataType, &Buffer, &DataSize);
        if (EFI_ERROR(Status))
          break;
  
        DBG("  _DSD Child Subnode Store Op Code 0x%02X 0x%02X %02X\n",
                DataSize, Data[0], DataSize > 1 ? Data[1] : 0);
  
        Data = Buffer;
        if (DataType != EFI_ACPI_DATA_TYPE_STRING
                || AsciiStrCmp((CHAR8 *) Data, ACPI_ETH_MAC_KEY) != 0)
          continue;
  
        DBG("_DSD Key Type %d. Found MAC address key\n", DataType);
  
        //
        // We found the node.
        //
        *Found = TRUE;
        continue;
      }
  
      if (Level == 3 && *Found) {
  
        //Update the MAC
        Status = GetEnvMac(MacNextID, MACBuffer);
        if (EFI_ERROR(Status))
          break;
  
        for (Count = 0; Count < 6; Count++) {
          Status = AcpiTableProtocol->GetOption(CurrentHandle, 1, &DataType, &Buffer, &DataSize);
          if (EFI_ERROR(Status))
            break;
  
          Data = Buffer;
          DBG("    _DSD Child Subnode Store Op Code 0x%02X 0x%02X %02X DataType 0x%X\n",
              DataSize, Data[0], DataSize > 1 ? Data[1] : 0, DataType);
  
          if (DataType != EFI_ACPI_DATA_TYPE_UINT)
            break;
  
          // only need one byte.
          // FIXME: Assume the CPU is little endian
          Status = AcpiTableProtocol->SetOption(CurrentHandle, 1, (VOID *)&MACBuffer[Count], sizeof(UINT8));
          if (EFI_ERROR(Status))
            break;
          Status = AcpiTableProtocol->GetChild(ChildHandle, &CurrentHandle);
          if (EFI_ERROR(Status) || CurrentHandle == NULL)
            break;
        }
        break;
      }
  
      if (Level > 3)
        break;
  
      //Search next package
      AcpiTableProtocol->Open((VOID *) Buffer, &NextHandle);
      Status = _SearchReplacePackageMACAddress(AcpiTableProtocol, NextHandle, Level + 1, Found, MacNextID);
      AcpiTableProtocol->Close(NextHandle);
      if (!EFI_ERROR(Status))
        break;
    }
  
    return Status;
  }
  
  EFI_STATUS SearchReplacePackageMACAddress(
    IN EFI_ACPI_SDT_PROTOCOL  *AcpiTableProtocol,
    IN EFI_ACPI_HANDLE        ChildHandle,
    IN UINTN                  MacNextID)
  {
    BOOLEAN Found = FALSE;
    UINTN Level = 0;
  
    return _SearchReplacePackageMACAddress(AcpiTableProtocol, ChildHandle, Level, &Found, MacNextID);
  }
  
  EFI_STATUS
  GetEthID (
    EFI_ACPI_SDT_PROTOCOL   *AcpiTableProtocol,
    EFI_ACPI_HANDLE         ChildHandle,
    UINTN                   *EthID
    )
  {
    EFI_STATUS Status;
    EFI_ACPI_DATA_TYPE  DataType;
    CHAR8               Data[5];
    CONST VOID          *Buffer;
    UINTN               DataSize;
  
    // Get NameString ETHx
    Status = AcpiTableProtocol->GetOption (ChildHandle, 1, &DataType, &Buffer, &DataSize);
    if (EFI_ERROR (Status)) {
      DEBUG ((EFI_D_ERROR, "[%a:%d] Get NameString failed: %r\n", __FUNCTION__, __LINE__, Status));
      return Status;
    }
  
    CopyMem (Data, Buffer, 4);
    DBG("Size %p Data %02x %02x %02x %02x\n", DataSize, Data[0], Data[1], Data[2], Data[3]);
  
    Data[4] = '\0';
    if (DataSize != 4 ||
        AsciiStrnCmp ("ETH", Data, 3) != 0 ||
        Data[3] > '9' || Data[3] < '0') {
      DEBUG ((EFI_D_ERROR, "[%a:%d] The NameString %a is not ETHn\n", __FUNCTION__, __LINE__, Data));
      return EFI_INVALID_PARAMETER;
    }
  
    *EthID = Data[3] - '0';
    return EFI_SUCCESS;
  }
  
  EFI_STATUS ProcessDSDTDevice (
    EFI_ACPI_SDT_PROTOCOL *AcpiTableProtocol,
    EFI_ACPI_HANDLE ChildHandle)
  {
    EFI_STATUS          Status;
    EFI_ACPI_DATA_TYPE  DataType;
    CONST UINT8         *Data;
    CONST VOID          *Buffer;
    UINTN               DataSize;
    EFI_ACPI_HANDLE     DevHandle;
    INTN                Found = 0;
    UINTN               MacNextID;
  
    Status = AcpiTableProtocol->GetOption(ChildHandle, 0, &DataType, &Buffer, &DataSize);
    if (EFI_ERROR(Status))
      return EFI_SUCCESS;
  
    Data = Buffer;
    //
    // Skip all non-device type
    //
    if (DataSize != 2 || Data[0] != AML_EXT_OP || Data[1] != AML_EXT_DEVICE_OP)
      return EFI_SUCCESS;
  
    //
    // Walk the device type node
    //
    for (DevHandle = NULL; ; ) {
      Status = AcpiTableProtocol->GetChild(ChildHandle, &DevHandle);
      if (EFI_ERROR(Status) || DevHandle == NULL)
        break;
  
      //
      // Search for _HID with Ethernet ID
      //
      Status = AcpiTableProtocol->GetOption(DevHandle, 0, &DataType, &Buffer, &DataSize);
      if (EFI_ERROR(Status))
        break;
  
      Data = Buffer;
      DBG("Data Type 0x%02X %02X\n", Data[0], DataSize > 1 ? Data[1] : 0);
      if (DataSize == 1 && Data[0] == AML_NAME_OP) {
        Status = AcpiTableProtocol->GetOption(DevHandle, 1, &DataType, &Buffer, &DataSize);
        if (EFI_ERROR(Status))
          break;
  
        Data = Buffer;
        if (DataType == EFI_ACPI_DATA_TYPE_NAME_STRING) {
          if (AsciiStrnCmp((CHAR8 *) Data, "_HID", 4) == 0) {
            EFI_ACPI_HANDLE ValueHandle;
  
            Status = AcpiTableProtocol->GetOption(DevHandle, 2, &DataType, &Buffer, &DataSize);
            if (EFI_ERROR(Status))
              break;
  
            if (DataType != EFI_ACPI_DATA_TYPE_CHILD)
              continue;
  
            AcpiTableProtocol->Open((VOID *) Buffer, &ValueHandle);
            Status = AcpiTableProtocol->GetOption(ValueHandle, 1, &DataType, &Buffer, &DataSize);
  
            Data = Buffer;
            DBG("[%a:%d] - _HID = %a\n", __FUNCTION__, __LINE__, Data);
  
            if (EFI_ERROR(Status) ||
                DataType != EFI_ACPI_DATA_TYPE_STRING ||
                (AsciiStrCmp((CHAR8 *) Data, D03_ACPI_ETH_ID) != 0)) {
              AcpiTableProtocol->Close(ValueHandle);
              Found = 0;
              continue;
            }
  
            DBG("Found Ethernet device\n");
            AcpiTableProtocol->Close(ValueHandle);
            Status = GetEthID (AcpiTableProtocol, ChildHandle, &MacNextID);
            if (EFI_ERROR (Status)) {
              continue;
            }
            Found = 1;
          } else if (Found == 1 && AsciiStrnCmp((CHAR8 *) Data, "_DSD", 4) == 0) {
            //
            // Patch MAC address for open source kernel
            //
            EFI_ACPI_HANDLE    PkgHandle;
            Status = AcpiTableProtocol->GetOption(DevHandle, 2, &DataType, &Buffer, &DataSize);
            if (EFI_ERROR(Status))
              break;
  
            if (DataType != EFI_ACPI_DATA_TYPE_CHILD)
              continue;
  
            //
            // Open package data
            //
            AcpiTableProtocol->Open((VOID *) Buffer, &PkgHandle);
            Status = AcpiTableProtocol->GetOption(PkgHandle, 0, &DataType, &Buffer, &DataSize);
  
            Data = Buffer;
            DBG("_DSD Subnode Store Op Code 0x%02X %02X\n",
                  Data[0], DataSize > 1 ? Data[1] : 0);
  
            //
            // Walk the _DSD node
            //
            if (DataSize == 1 && Data[0] == AML_PACKAGE_OP)
              Status = SearchReplacePackageMACAddress(AcpiTableProtocol, PkgHandle, MacNextID);
  
            AcpiTableProtocol->Close(PkgHandle);
          }
        }
      }
    }
  
    return EFI_SUCCESS;
  }
  
  
  BOOLEAN
  IsSbScope (
    EFI_ACPI_SDT_PROTOCOL   *AcpiTableProtocol,
    EFI_ACPI_HANDLE         ChildHandle
    )
  {
    EFI_STATUS          Status;
    EFI_ACPI_DATA_TYPE  DataType;
    CONST UINT8         *Data;
    CONST VOID          *Buffer;
    UINTN               DataSize;
  
    Status = AcpiTableProtocol->GetOption (ChildHandle, 0, &DataType, &Buffer, &DataSize);
    if (EFI_ERROR(Status)) return FALSE;
  
    Data = Buffer;
    if (DataSize != 1 || Data[0] != AML_SCOPE_OP) {
      return FALSE;
    }
  
    return TRUE;
  }
  
  EFI_STATUS ProcessDSDTChild(
    EFI_ACPI_SDT_PROTOCOL *AcpiTableProtocol,
    EFI_ACPI_HANDLE ChildHandle)
  {
    EFI_STATUS          Status;
    EFI_ACPI_HANDLE     DevHandle;
  
    // Check Scope(_SB) at first
    if (!IsSbScope (AcpiTableProtocol, ChildHandle)) {
      return ProcessDSDTDevice (AcpiTableProtocol, ChildHandle);
    }
  
    for (DevHandle = NULL; ; ) {
      Status = AcpiTableProtocol->GetChild (ChildHandle, &DevHandle);
      if (EFI_ERROR(Status) || DevHandle == NULL) {
        break;
      }
  
      ProcessDSDTDevice (AcpiTableProtocol, DevHandle);
    }
  
    return EFI_SUCCESS;
  }
  
  static EFI_STATUS ProcessDSDT(
    EFI_ACPI_SDT_PROTOCOL *AcpiTableProtocol,
    EFI_ACPI_HANDLE TableHandle)
  {
    EFI_STATUS              Status;
    EFI_ACPI_HANDLE         ChildHandle;
    //
    // Parse table for device type
    DBG ("[%a:%d] - TableHandle=%p\n", __FUNCTION__, __LINE__, TableHandle);
    for (ChildHandle = NULL; ; ) {
      Status = AcpiTableProtocol->GetChild(TableHandle, &ChildHandle);
      DBG ("[%a:%d] - Child=%p, %r\n", __FUNCTION__, __LINE__, ChildHandle, Status);
      if (EFI_ERROR(Status))
        break;
      if (ChildHandle == NULL)
        break;
  
      ProcessDSDTChild(AcpiTableProtocol, ChildHandle);
    }
  
    return EFI_SUCCESS;
  }
  
  STATIC
  VOID
  AcpiCheckSum (
    IN OUT  EFI_ACPI_SDT_HEADER *Table
    )
  {
    UINTN ChecksumOffset;
    UINT8 *Buffer;
  
    ChecksumOffset = OFFSET_OF (EFI_ACPI_DESCRIPTION_HEADER, Checksum);
    Buffer = (UINT8 *)Table;
  
    //
    // set checksum to 0 first
    //
    Buffer[ChecksumOffset] = 0;
  
    //
    // Update checksum value
    //
    Buffer[ChecksumOffset] = CalculateCheckSum8 (Buffer, Table->Length);
  }
  
  EFI_STATUS EthMacInit(void)
  {
    EFI_STATUS              Status;
    EFI_ACPI_SDT_PROTOCOL   *AcpiTableProtocol;
    EFI_ACPI_SDT_HEADER     *Table;
    EFI_ACPI_TABLE_VERSION  TableVersion;
    UINTN                   TableKey;
    EFI_ACPI_HANDLE         TableHandle;
    UINTN                   i;
  
    DEBUG ((EFI_D_ERROR, "Updating Ethernet MAC in ACPI DSDT...\n"));
  
    //
    // Find the AcpiTable protocol
    Status = gBS->LocateProtocol(&gEfiAcpiSdtProtocolGuid, NULL, (VOID**) &AcpiTableProtocol);
    if (EFI_ERROR(Status)) {
      DBG("Unable to locate ACPI table protocol\n");
      return EFI_SUCCESS;
    }
  
    //
    // Search for DSDT Table
    for (i = 0; i < EFI_ACPI_MAX_NUM_TABLES; i++) {
      Status = AcpiTableProtocol->GetAcpiTable(i, &Table, &TableVersion, &TableKey);
      if (EFI_ERROR(Status))
        break;
      if (Table->Signature != DSDT_SIGNATURE)
        continue;
  
      Status = AcpiTableProtocol->OpenSdt(TableKey, &TableHandle);
      if (EFI_ERROR(Status))
        break;
  
      ProcessDSDT(AcpiTableProtocol, TableHandle);
  
      AcpiTableProtocol->Close(TableHandle);
      AcpiCheckSum (Table);
    }
  
    return EFI_SUCCESS;
  }
  