/** @file
  RISC-V generic SMBIOS DXE driver to build up SMBIOS type 4, type 7 and type 44 records.

  Copyright (c) 2019-2022, Hewlett Packard Enterprise Development LP. All rights reserved.<BR>

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

**/

#include "RiscVSmbiosDxe.h"

STATIC EFI_SMBIOS_PROTOCOL  *mSmbios;

/**
  This function builds SMBIOS type 7 record according to
  the given  RISC_V_PROCESSOR_TYPE7_HOB_DATA.

  @param Type4HobData       Pointer to RISC_V_PROCESSOR_TYPE4_HOB_DATA
  @param Type7DataHob       Pointer to RISC_V_PROCESSOR_TYPE7_HOB_DATA
  @param SmbiosHandle       Pointer to SMBIOS_HANDLE

  @retval EFI_STATUS

**/
STATIC
EFI_STATUS
BuildSmbiosType7 (
  IN RISC_V_PROCESSOR_TYPE4_HOB_DATA  *Type4HobData,
  IN RISC_V_PROCESSOR_TYPE7_HOB_DATA  *Type7DataHob,
  OUT SMBIOS_HANDLE                   *SmbiosHandle
  )
{
  EFI_STATUS     Status;
  SMBIOS_HANDLE  Handle;

  if (!CompareGuid (&Type4HobData->ProcessorGuid, &Type7DataHob->ProcessorGuid) ||
      (Type4HobData->ProcessorUid != Type7DataHob->ProcessorUid))
  {
    return EFI_INVALID_PARAMETER;
  }

  Handle                                    = SMBIOS_HANDLE_PI_RESERVED;
  Type7DataHob->SmbiosType7Cache.Hdr.Type   = SMBIOS_TYPE_CACHE_INFORMATION;
  Type7DataHob->SmbiosType7Cache.Hdr.Length = sizeof (SMBIOS_TABLE_TYPE7);
  Type7DataHob->SmbiosType7Cache.Hdr.Handle = 0;
  Type7DataHob->EndingZero                  = 0;
  Status                                    = mSmbios->Add (mSmbios, NULL, &Handle, &Type7DataHob->SmbiosType7Cache.Hdr);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a: Fail to add SMBIOS Type 7\n", __FUNCTION__));
    return Status;
  }

  DEBUG ((DEBUG_INFO, "SMBIOS Type 7 was added. SMBIOS Handle: 0x%x\n", Handle));
  DEBUG ((DEBUG_VERBOSE, "     Cache belone to processor GUID: %g\n", &Type7DataHob->ProcessorGuid));
  DEBUG ((DEBUG_VERBOSE, "     Cache belone processor  UID: %d\n", Type7DataHob->ProcessorUid));
  DEBUG ((DEBUG_VERBOSE, "     ==============================\n"));
  DEBUG ((DEBUG_VERBOSE, "     Socket Designation: %d\n", Type7DataHob->SmbiosType7Cache.SocketDesignation));
  DEBUG ((DEBUG_VERBOSE, "     Cache Configuration: 0x%x\n", Type7DataHob->SmbiosType7Cache.CacheConfiguration));
  DEBUG ((DEBUG_VERBOSE, "     Maximum Cache Size: 0x%x\n", Type7DataHob->SmbiosType7Cache.MaximumCacheSize));
  DEBUG ((DEBUG_VERBOSE, "     Installed Size: 0x%x\n", Type7DataHob->SmbiosType7Cache.InstalledSize));
  DEBUG ((DEBUG_VERBOSE, "     Supported SRAM Type: 0x%x\n", Type7DataHob->SmbiosType7Cache.SupportedSRAMType));
  DEBUG ((DEBUG_VERBOSE, "     Current SRAMT ype: 0x%x\n", Type7DataHob->SmbiosType7Cache.CurrentSRAMType));
  DEBUG ((DEBUG_VERBOSE, "     Cache Speed: 0x%x\n", Type7DataHob->SmbiosType7Cache.CacheSpeed));
  DEBUG ((DEBUG_VERBOSE, "     Error Correction Type: 0x%x\n", Type7DataHob->SmbiosType7Cache.ErrorCorrectionType));
  DEBUG ((DEBUG_VERBOSE, "     System Cache Type: 0x%x\n", Type7DataHob->SmbiosType7Cache.SystemCacheType));
  DEBUG ((DEBUG_VERBOSE, "     Associativity: 0x%x\n", Type7DataHob->SmbiosType7Cache.Associativity));

  *SmbiosHandle = Handle;
  return EFI_SUCCESS;
}

/**
  This function builds SMBIOS type 4 record according to
  the given  RISC_V_PROCESSOR_TYPE4_HOB_DATA.

  @param Type4HobData       Pointer to RISC_V_PROCESSOR_TYPE4_HOB_DATA
  @param SmbiosHandle       Pointer to SMBIOS_HANDLE

  @retval EFI_STATUS

**/
STATIC
EFI_STATUS
BuildSmbiosType4 (
  IN RISC_V_PROCESSOR_TYPE4_HOB_DATA  *Type4HobData,
  OUT SMBIOS_HANDLE                   *SmbiosHandle
  )
{
  EFI_HOB_GUID_TYPE                *GuidHob;
  RISC_V_PROCESSOR_TYPE7_HOB_DATA  *Type7HobData;
  SMBIOS_HANDLE                    Cache;
  SMBIOS_HANDLE                    Processor;
  EFI_STATUS                       Status;

  DEBUG ((DEBUG_INFO, "Building Type 4.\n"));
  DEBUG ((DEBUG_INFO, "    Processor GUID: %g\n", &Type4HobData->ProcessorGuid));
  DEBUG ((DEBUG_INFO, "    Processor UUID: %d\n", Type4HobData->ProcessorUid));

  Type4HobData->SmbiosType4Processor.L1CacheHandle = RISC_V_CACHE_INFO_NOT_PROVIDED;
  Type4HobData->SmbiosType4Processor.L2CacheHandle = RISC_V_CACHE_INFO_NOT_PROVIDED;
  Type4HobData->SmbiosType4Processor.L3CacheHandle = RISC_V_CACHE_INFO_NOT_PROVIDED;
  GuidHob                                          = (EFI_HOB_GUID_TYPE *)GetFirstGuidHob ((EFI_GUID *)PcdGetPtr (PcdProcessorSmbiosType7GuidHobGuid));
  if (GuidHob == NULL) {
    DEBUG ((DEBUG_ERROR, "No RISC-V SMBIOS Type7 data HOB found.\n"));
    return EFI_NOT_FOUND;
  }

  //
  // Go through each RISC_V_PROCESSOR_TYPE4_HOB_DATA for multiple processors.
  //
  do {
    Type7HobData = (RISC_V_PROCESSOR_TYPE7_HOB_DATA *)GET_GUID_HOB_DATA (GuidHob);
    Status       = BuildSmbiosType7 (Type4HobData, Type7HobData, &Cache);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    if ((Type7HobData->SmbiosType7Cache.SystemCacheType & RISC_V_CACHE_CONFIGURATION_CACHE_LEVEL_MASK) ==
        RISC_V_CACHE_CONFIGURATION_CACHE_LEVEL_1)
    {
      Type4HobData->SmbiosType4Processor.L1CacheHandle = Cache;
    } else if ((Type7HobData->SmbiosType7Cache.SystemCacheType & RISC_V_CACHE_CONFIGURATION_CACHE_LEVEL_MASK) ==
               RISC_V_CACHE_CONFIGURATION_CACHE_LEVEL_2)
    {
      Type4HobData->SmbiosType4Processor.L2CacheHandle = Cache;
    } else if ((Type7HobData->SmbiosType7Cache.SystemCacheType & RISC_V_CACHE_CONFIGURATION_CACHE_LEVEL_MASK) ==
               RISC_V_CACHE_CONFIGURATION_CACHE_LEVEL_3)
    {
      Type4HobData->SmbiosType4Processor.L3CacheHandle = Cache;
    } else {
      DEBUG ((DEBUG_ERROR, "Improper cache level of SMBIOS handle %d\n", Cache));
    }

    GuidHob = GetNextGuidHob ((EFI_GUID *)PcdGetPtr (PcdProcessorSmbiosType7GuidHobGuid), GET_NEXT_HOB (GuidHob));
  } while (GuidHob != NULL);

  //
  // Build SMBIOS Type 4 record
  //
  Processor                                     = SMBIOS_HANDLE_PI_RESERVED;
  Type4HobData->SmbiosType4Processor.Hdr.Type   = SMBIOS_TYPE_PROCESSOR_INFORMATION;
  Type4HobData->SmbiosType4Processor.Hdr.Length = sizeof (SMBIOS_TABLE_TYPE4);
  Type4HobData->SmbiosType4Processor.Hdr.Handle = 0;
  Type4HobData->EndingZero                      = 0;
  Status                                        = mSmbios->Add (mSmbios, NULL, &Processor, &Type4HobData->SmbiosType4Processor.Hdr);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Fail to add SMBIOS Type 4\n"));
    return Status;
  }

  DEBUG ((DEBUG_INFO, "SMBIOS Type 4 was added. SMBIOS Handle: 0x%x\n", Processor));
  DEBUG ((DEBUG_VERBOSE, "     Socket StringID: %d\n", Type4HobData->SmbiosType4Processor.Socket));
  DEBUG ((DEBUG_VERBOSE, "     Processor Type: 0x%x\n", Type4HobData->SmbiosType4Processor.ProcessorType));
  DEBUG ((DEBUG_VERBOSE, "     Processor Family: 0x%x\n", Type4HobData->SmbiosType4Processor.ProcessorFamily));
  DEBUG ((DEBUG_VERBOSE, "     Processor Manufacture StringID: %d\n", Type4HobData->SmbiosType4Processor.ProcessorManufacturer));
  DEBUG ((
    DEBUG_VERBOSE,
    "     Processor Id: 0x%x:0x%x\n", \
    Type4HobData->SmbiosType4Processor.ProcessorId.Signature,
    Type4HobData->SmbiosType4Processor.ProcessorId.FeatureFlags
    ));
  DEBUG ((DEBUG_VERBOSE, "     Processor Version StringID: %d\n", Type4HobData->SmbiosType4Processor.ProcessorVersion));
  DEBUG ((DEBUG_VERBOSE, "     Voltage: 0x%x\n", Type4HobData->SmbiosType4Processor.Voltage));
  DEBUG ((DEBUG_VERBOSE, "     External Clock: 0x%x\n", Type4HobData->SmbiosType4Processor.ExternalClock));
  DEBUG ((DEBUG_VERBOSE, "     Max Speed: 0x%x\n", Type4HobData->SmbiosType4Processor.MaxSpeed));
  DEBUG ((DEBUG_VERBOSE, "     Current Speed: 0x%x\n", Type4HobData->SmbiosType4Processor.CurrentSpeed));
  DEBUG ((DEBUG_VERBOSE, "     Status: 0x%x\n", Type4HobData->SmbiosType4Processor.Status));
  DEBUG ((DEBUG_VERBOSE, "     ProcessorUpgrade: 0x%x\n", Type4HobData->SmbiosType4Processor.ProcessorUpgrade));
  DEBUG ((DEBUG_VERBOSE, "     L1 Cache Handle: 0x%x\n", Type4HobData->SmbiosType4Processor.L1CacheHandle));
  DEBUG ((DEBUG_VERBOSE, "     L2 Cache Handle: 0x%x\n", Type4HobData->SmbiosType4Processor.L2CacheHandle));
  DEBUG ((DEBUG_VERBOSE, "     L3 Cache Handle: 0x%x\n", Type4HobData->SmbiosType4Processor.L3CacheHandle));
  DEBUG ((DEBUG_VERBOSE, "     Serial Number StringID: %d\n", Type4HobData->SmbiosType4Processor.SerialNumber));
  DEBUG ((DEBUG_VERBOSE, "     Asset Tag StringID: %d\n", Type4HobData->SmbiosType4Processor.AssetTag));
  DEBUG ((DEBUG_VERBOSE, "     Part Number StringID: %d\n", Type4HobData->SmbiosType4Processor.PartNumber));
  DEBUG ((DEBUG_VERBOSE, "     Core Count: %d\n", Type4HobData->SmbiosType4Processor.CoreCount));
  DEBUG ((DEBUG_VERBOSE, "     Enabled CoreCount: %d\n", Type4HobData->SmbiosType4Processor.EnabledCoreCount));
  DEBUG ((DEBUG_VERBOSE, "     Thread Count: %d\n", Type4HobData->SmbiosType4Processor.ThreadCount));
  DEBUG ((DEBUG_VERBOSE, "     Processor Characteristics: 0x%x\n", Type4HobData->SmbiosType4Processor.ProcessorCharacteristics));
  DEBUG ((DEBUG_VERBOSE, "     Processor Family2: 0x%x\n", Type4HobData->SmbiosType4Processor.ProcessorFamily2));
  DEBUG ((DEBUG_VERBOSE, "     Core Count 2: %d\n", Type4HobData->SmbiosType4Processor.CoreCount2));
  DEBUG ((DEBUG_VERBOSE, "     Enabled CoreCount : %d\n", Type4HobData->SmbiosType4Processor.EnabledCoreCount2));
  DEBUG ((DEBUG_VERBOSE, "     Thread Count 2: %d\n", Type4HobData->SmbiosType4Processor.ThreadCount2));

  *SmbiosHandle = Processor;
  return EFI_SUCCESS;
}

/**
  This function builds SMBIOS type 44 record according..

  @param Type4HobData      Pointer to RISC_V_PROCESSOR_TYPE4_HOB_DATA
  @param Type4Handle       SMBIOS handle of type 4

  @retval EFI_STATUS

**/
EFI_STATUS
BuildSmbiosType44 (
  IN RISC_V_PROCESSOR_TYPE4_HOB_DATA  *Type4HobData,
  IN SMBIOS_HANDLE                    Type4Handle
  )
{
  EFI_HOB_GUID_TYPE                   *GuidHob;
  RISC_V_PROCESSOR_SPECIFIC_HOB_DATA  *ProcessorSpecificData;
  SMBIOS_HANDLE                       RiscVType44;
  SMBIOS_TABLE_TYPE44                 *Type44Ptr;
  EFI_STATUS                          Status;

  DEBUG ((DEBUG_INFO, "Building Type 44 for...\n"));
  DEBUG ((DEBUG_VERBOSE, "     Processor GUID: %g\n", &Type4HobData->ProcessorGuid));
  DEBUG ((DEBUG_VERBOSE, "     Processor UUID: %d\n", Type4HobData->ProcessorUid));

  GuidHob = (EFI_HOB_GUID_TYPE *)GetFirstGuidHob ((EFI_GUID *)PcdGetPtr (PcdProcessorSpecificDataGuidHobGuid));
  if (GuidHob == NULL) {
    DEBUG ((DEBUG_ERROR, "No RISC_V_PROCESSOR_SPECIFIC_HOB_DATA found.\n"));
    return EFI_NOT_FOUND;
  }

  //
  // Go through each RISC_V_PROCESSOR_SPECIFIC_HOB_DATA for multiple cores.
  //
  do {
    ProcessorSpecificData = (RISC_V_PROCESSOR_SPECIFIC_HOB_DATA *)GET_GUID_HOB_DATA (GuidHob);
    if (!CompareGuid (&ProcessorSpecificData->ParentProcessorGuid, &Type4HobData->ProcessorGuid) ||
        (ProcessorSpecificData->ParentProcessorUid != Type4HobData->ProcessorUid))
    {
      GuidHob = GetNextGuidHob ((EFI_GUID *)PcdGetPtr (PcdProcessorSpecificDataGuidHobGuid), GET_NEXT_HOB (GuidHob));
      if (GuidHob == NULL) {
        break;
      }

      continue;
    }

    DEBUG ((DEBUG_VERBOSE, "================================\n"));
    DEBUG ((DEBUG_VERBOSE, "Core GUID: %g\n", &ProcessorSpecificData->CoreGuid));

    Type44Ptr = AllocateZeroPool (sizeof (SMBIOS_TABLE_TYPE44) + sizeof (SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA) + 2); // Two ending zero.
    if (Type44Ptr == NULL) {
      return EFI_NOT_FOUND;
    }

    Type44Ptr->Hdr.Type                                 = SMBIOS_TYPE_PROCESSOR_ADDITIONAL_INFORMATION;
    Type44Ptr->Hdr.Handle                               = 0;
    Type44Ptr->Hdr.Length                               = sizeof (SMBIOS_TABLE_TYPE44) + sizeof (SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA);
    Type44Ptr->RefHandle                                = Type4Handle;
    Type44Ptr->ProcessorSpecificBlock.Length            = sizeof (SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA);
    Type44Ptr->ProcessorSpecificBlock.ProcessorArchType = Type4HobData->SmbiosType4Processor.ProcessorFamily2 -
                                                          ProcessorFamilyRiscvRV32 + \
                                                          ProcessorSpecificBlockArchTypeRiscVRV32;
    CopyMem ((VOID *)(Type44Ptr + 1), (VOID *)&ProcessorSpecificData->ProcessorSpecificData, sizeof (SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA));

    DEBUG ((DEBUG_VERBOSE, "Core type: %d\n", Type44Ptr->ProcessorSpecificBlock.ProcessorArchType));
    DEBUG ((DEBUG_VERBOSE, "     HartId = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->HartId.Value64_L));
    DEBUG ((DEBUG_VERBOSE, "     Is Boot Hart? = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->BootHartId));
    DEBUG ((DEBUG_VERBOSE, "     PrivilegeModeSupported = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->PrivilegeModeSupported));
    DEBUG ((DEBUG_VERBOSE, "     MModeExcepDelegation = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->MModeExcepDelegation.Value64_L));
    DEBUG ((DEBUG_VERBOSE, "     MModeInterruptDelegation = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->MModeInterruptDelegation.Value64_L));
    DEBUG ((DEBUG_VERBOSE, "     HartXlen = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->HartXlen));
    DEBUG ((DEBUG_VERBOSE, "     MachineModeXlen = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->MachineModeXlen));
    DEBUG ((DEBUG_VERBOSE, "     SupervisorModeXlen = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->SupervisorModeXlen));
    DEBUG ((DEBUG_VERBOSE, "     UserModeXlen = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->UserModeXlen));
    DEBUG ((DEBUG_VERBOSE, "     InstSetSupported = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->InstSetSupported));
    DEBUG ((DEBUG_VERBOSE, "     MachineVendorId = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->MachineVendorId.Value64_L));
    DEBUG ((DEBUG_VERBOSE, "     MachineArchId = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->MachineArchId.Value64_L));
    DEBUG ((DEBUG_VERBOSE, "     MachineImplId = 0x%x\n", ((SMBIOS_RISC_V_PROCESSOR_SPECIFIC_DATA *)(Type44Ptr + 1))->MachineImplId.Value64_L));

    //
    // Add to SMBIOS table.
    //
    RiscVType44 = SMBIOS_HANDLE_PI_RESERVED;
    Status      = mSmbios->Add (mSmbios, NULL, &RiscVType44, &Type44Ptr->Hdr);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "Fail to add SMBIOS Type 44\n"));
      return Status;
    }

    DEBUG ((DEBUG_INFO, "SMBIOS Type 44 was added. SMBIOS Handle: 0x%x\n", RiscVType44));

    GuidHob = GetNextGuidHob ((EFI_GUID *)PcdGetPtr (PcdProcessorSpecificDataGuidHobGuid), GET_NEXT_HOB (GuidHob));
  } while (GuidHob != NULL);

  return EFI_SUCCESS;
}

/**
  Entry point of RISC-V SMBIOS builder.

  @param ImageHandle     Image handle this driver.
  @param SystemTable     Pointer to the System Table.

  @retval EFI_SUCCESS           Thread can be successfully created
  @retval EFI_OUT_OF_RESOURCES  Cannot allocate protocol data structure
  @retval EFI_DEVICE_ERROR      Cannot create the thread

**/
EFI_STATUS
EFIAPI
RiscVSmbiosBuilderEntry (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                       Status;
  EFI_HOB_GUID_TYPE                *GuidHob;
  RISC_V_PROCESSOR_TYPE4_HOB_DATA  *Type4HobData;
  SMBIOS_HANDLE                    Processor;

  DEBUG ((DEBUG_INFO, "%a: entry\n", __FUNCTION__));

  Status = gBS->LocateProtocol (
                  &gEfiSmbiosProtocolGuid,
                  NULL,
                  (VOID **)&mSmbios
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Locate SMBIOS Protocol fail\n"));
    return Status;
  }

  GuidHob = (EFI_HOB_GUID_TYPE *)GetFirstGuidHob ((EFI_GUID *)PcdGetPtr (PcdProcessorSmbiosType4GuidHobGuid));
  if (GuidHob == NULL) {
    DEBUG ((DEBUG_ERROR, "No RISC-V SMBIOS information found.\n"));
    return EFI_NOT_FOUND;
  }

  Type4HobData = (RISC_V_PROCESSOR_TYPE4_HOB_DATA *)GET_GUID_HOB_DATA (GuidHob);
  Status       = EFI_NOT_FOUND;
  //
  // Go through each RISC_V_PROCESSOR_TYPE4_HOB_DATA for multiple processors.
  //
  do {
    Status = BuildSmbiosType4 (Type4HobData, &Processor);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "No RISC-V SMBIOS type 4 created.\n"));
      ASSERT (FALSE);
    }

    Status = BuildSmbiosType44 (Type4HobData, Processor);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "No RISC-V SMBIOS type 44 found.\n"));
      ASSERT (FALSE);
    }

    GuidHob = GetNextGuidHob ((EFI_GUID *)PcdGetPtr (PcdProcessorSmbiosType4GuidHobGuid), GET_NEXT_HOB (GuidHob));
  } while (GuidHob != NULL);

  DEBUG ((DEBUG_INFO, "%a: exit\n", __FUNCTION__));
  return Status;
}
