/** @file
 *
 *  Static SMBIOS tables for the SbsaQemu platform.
 *
 *  Note: Some tables are provided by ArmPkg. The tables that are not provided by
 *  ArmPkg, but are required by SBBR, are as follows:
 *    Physical Memory Array (Type 16)
 *    Memory Device (Type 17) - For each socketed system-memory Device
 *    Memory Array Mapped Address (Type 19) - One per contiguous block per Physical Memory Array
 *
 *  Copyright (c) 2022, ARM Limited. All rights reserved.
 *
 *  SPDX-License-Identifier: BSD-2-Clause-Patent
 *
 **/

#include <Base.h>
#include <Uefi/UefiSpec.h>
#include <IndustryStandard/SmBios.h>
#include <Protocol/Smbios.h>
#include <Guid/SmBios.h>
#include <Library/ArmLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/PcdLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/PrintLib.h>

/**
  A global variable to store the SMBIOS handle for table Type 16. This variable should
  only be modified by calling PhyMemArrayInfoUpdateSmbiosType16.
**/
STATIC SMBIOS_HANDLE  mPhyMemArrayInfoType16Handle = SMBIOS_HANDLE_PI_RESERVED;

/**
  SMBIOS data definition, TYPE16, Physical Memory Array
**/
SMBIOS_TABLE_TYPE16  mPhyMemArrayInfoType16 = {
  { EFI_SMBIOS_TYPE_PHYSICAL_MEMORY_ARRAY, sizeof (SMBIOS_TABLE_TYPE16), 0 },
  MemoryArrayLocationSystemBoard,  // Location; (system board)
  MemoryArrayUseSystemMemory,      // Use; (system memory)
  MemoryErrorCorrectionUnknown,    // MemoryErrorCorrection; (unknown).
  0x80000000,                      // MaximumCapacity; (capacity is represented in the ExtendedMaximumCapacity field)
  0xFFFE,                          // MemoryErrorInformationHandle; (not provided)
  1,                               // NumberOfMemoryDevices
  0x0000080000000000ULL            // ExtendedMaximumCapacity; (fixed at 8 TiB for SbsaQemu)
};
CHAR8                *mPhyMemArrayInfoType16Strings[] = {
  NULL
};

/**
  SMBIOS data definition, TYPE17, Physical Memory Array
**/
SMBIOS_TABLE_TYPE17  mMemDevInfoType17 = {
  { EFI_SMBIOS_TYPE_MEMORY_DEVICE, sizeof (SMBIOS_TABLE_TYPE17), 0 },
  0,                        // MemoryArrayHandle; should match SMBIOS_TABLE_TYPE16.Handle,
                            // initialized at runtime, refer to MemDevInfoUpdateSmbiosType17
  0xFFFE,                   // MemoryErrorInformationHandle; (not provided)
  0xFFFF,                   // TotalWidth; (unknown)
  0xFFFF,                   // DataWidth; (unknown)
  0x0,                      // Size; initialized at runtime, refer to MemDevInfoUpdateSmbiosType17
  MemoryFormFactorUnknown,  // FormFactor; (unknown)
  0,                        // DeviceSet; (not part of a set)
  0,                        // DeviceLocator String
  0,                        // BankLocator String
  MemoryTypeUnknown,        // MemoryType; (unknown)
  {     // TypeDetail;
    0,  // Reserved        :1;
    0,  // Other           :1;
    1,  // Unknown         :1;
    0,  // FastPaged       :1;
    0,  // StaticColumn    :1;
    0,  // PseudoStatic    :1;
    0,  // Rambus          :1;
    0,  // Synchronous     :1;
    0,  // Cmos            :1;
    0,  // Edo             :1;
    0,  // WindowDram      :1;
    0,  // CacheDram       :1;
    0,  // Nonvolatile     :1;
    0,  // Registered      :1;
    0,  // Unbuffered      :1;
    0,  // Reserved1       :1;
  },
  0,                        // Speed; (unknown)
  0,                        // Manufacturer String
  0,                        // SerialNumber String
  0,                        // AssetTag String
  0,                        // PartNumber String
  0,                        // Attributes; (unknown rank)
  0,                        // ExtendedSize; potentially initialized at runtime if size is >= 32 GiB - 1 MiB,
                            // refer to MemDevInfoUpdateSmbiosType17
  0,                        // ConfiguredMemoryClockSpeed; (unknown)
  0,                        // MinimumVoltage; (unknown)
  0,                        // MaximumVoltage; (unknown)
  0,                        // ConfiguredVoltage; (unknown)
  MemoryTechnologyDram,     // MemoryTechnology; (DRAM)
  {                         // MemoryOperatingModeCapability
    {
      0, // Reserved                        :1;
      0, // Other                           :1;
      0, // Unknown                         :1;
      1, // VolatileMemory                  :1;
      0, // ByteAccessiblePersistentMemory  :1;
      0, // BlockAccessiblePersistentMemory :1;
      0  // Reserved                        :10;
    }
  },
  0,                        // FirwareVersion
  0,                        // ModuleManufacturerID (unknown)
  0,                        // ModuleProductID (unknown)
  0,                        // MemorySubsystemControllerManufacturerID (unknown)
  0,                        // MemorySubsystemControllerProductID (unknown)
  0,                        // NonVolatileSize
  0,                        // VolatileSize; initialized at runtime, refer to MemDevInfoUpdateSmbiosType17
  0,                        // CacheSize
  0,                        // LogicalSize (since MemoryType is not MemoryTypeLogicalNonVolatileDevice)
  0,                        // ExtendedSpeed
  0                         // ExtendedConfiguredMemorySpeed
};
CHAR8                *mMemDevInfoType17Strings[] = {
  NULL
};

SMBIOS_TABLE_TYPE19  mMemArrMapInfoType19 = {
  { EFI_SMBIOS_TYPE_MEMORY_ARRAY_MAPPED_ADDRESS, sizeof (SMBIOS_TABLE_TYPE19), 0 },
  0,  // StartingAddress; initialized at runtime, refer to MemArrMapInfoUpdateSmbiosType19
  0,  // EndingAddress; initialized at runtime, refer to MemArrMapInfoUpdateSmbiosType19
  0,  // MemoryArrayHandle; should match SMBIOS_TABLE_TYPE16.Handle,
      // initialized at runtime, refer to MemDevInfoUpdateSmbiosType17
  1,  // PartitionWidth
  0,  // ExtendedStartingAddress; potentially initialized at runtime,
      // refer to MemDevInfoUpdateSmbiosType17
  0,  // ExtendedEndingAddress; potentially initialized at runtime,
      // refer to MemDevInfoUpdateSmbiosType17
};
CHAR8                *mMemArrMapInfoType19Strings[] = {
  NULL
};

/**
  Create an SMBIOS record.

  Converts a fixed SMBIOS structure and an array of pointers to strings into
  an SMBIOS record where the strings are cat'ed on the end of the fixed record
  and terminated via a double NULL and add to SMBIOS table.

  @param  Template          Fixed SMBIOS structure, required.
  @param  StringPack        Array of strings to convert to an SMBIOS string pack.
                            NULL is OK.
  @param  DataSmbiosHandle  The new SMBIOS record handle.
                            NULL is OK.

  @return  EFI_SUCCESS on success, other values on error.
**/
STATIC
EFI_STATUS
EFIAPI
LogSmbiosData (
  IN  EFI_SMBIOS_TABLE_HEADER  *Template,
  IN  CHAR8                    **StringPack,
  OUT EFI_SMBIOS_HANDLE        *DataSmbiosHandle
  )
{
  EFI_STATUS               Status;
  EFI_SMBIOS_PROTOCOL      *Smbios;
  EFI_SMBIOS_HANDLE        SmbiosHandle;
  EFI_SMBIOS_TABLE_HEADER  *Record;
  UINTN                    Index;
  UINTN                    StringSize;
  UINTN                    Size;
  CHAR8                    *Str;

  Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID **)&Smbios);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Failed to locate SMBIOS protocol: %r\n", Status));
    return Status;
  }

  //
  // Calculate the size of the fixed record and optional string pack
  //
  Size = Template->Length;
  if (StringPack == NULL) {
    //
    // If there are no strings, allow for a double-NULL
    //
    Size += 2;
  } else {
    for (Index = 0; StringPack[Index] != NULL; Index++) {
      StringSize = AsciiStrSize (StringPack[Index]);
      Size      += StringSize;
    }

    if (StringPack[0] == NULL) {
      //
      // If the only string is NULL, include it in size calculation
      //
      Size += 1;
    }

    //
    // Add an additional NULL for a terminating double NULL
    //
    Size += 1;
  }

  //
  // Copy over the template
  //
  Record = (EFI_SMBIOS_TABLE_HEADER *)AllocateZeroPool (Size);
  if (Record == NULL) {
    DEBUG ((DEBUG_ERROR, "Failed to allocate memory for SMBIOS table\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  CopyMem (Record, Template, Template->Length);

  //
  // Append the string pack
  //
  Str = ((CHAR8 *)Record) + Record->Length;
  for (Index = 0; StringPack[Index] != NULL; Index++) {
    StringSize = AsciiStrSize (StringPack[Index]);
    CopyMem (Str, StringPack[Index], StringSize);
    Str += StringSize;
  }

  //
  // Add an additional NULL for a terminating double NULL
  //
  *Str = 0;

  //
  // Add the table to SMBIOS
  //
  SmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
  Status       = Smbios->Add (
                           Smbios,
                           gImageHandle,
                           &SmbiosHandle,
                           Record
                           );

  if ((Status == EFI_SUCCESS) && (DataSmbiosHandle != NULL)) {
    *DataSmbiosHandle = SmbiosHandle;
  }

  ASSERT_EFI_ERROR (Status);
  FreePool (Record);
  return Status;
}

/**
  Updates SMBIOS table Type 16 and creates an SMBIOS record for it.

  Stores the SMBIOS handle for table Type 16 in mPhyMemArrayInfoType16Handle.
**/
STATIC
VOID
PhyMemArrayInfoUpdateSmbiosType16 (
  VOID
  )
{
  LogSmbiosData (
    (EFI_SMBIOS_TABLE_HEADER *)&mPhyMemArrayInfoType16,
    mPhyMemArrayInfoType16Strings,
    &mPhyMemArrayInfoType16Handle
    );
}

/**
  Updates SMBIOS table Type 17 and creates an SMBIOS record for it.

  Must be called after PhyMemArrayInfoUpdateSmbiosType16.
**/
STATIC
VOID
MemDevInfoUpdateSmbiosType17 (
  VOID
  )
{
  UINT64  MemorySize;

  //
  // PhyMemArrayInfoUpdateSmbiosType16 must be called before MemDevInfoUpdateSmbiosType17
  //
  if (mPhyMemArrayInfoType16Handle == SMBIOS_HANDLE_PI_RESERVED) {
    DEBUG ((DEBUG_ERROR, "%a: mPhyMemArrayInfoType16Handle is not initialized\n", __FUNCTION__));
    return;
  }

  mMemDevInfoType17.MemoryArrayHandle = mPhyMemArrayInfoType16Handle;

  MemorySize = PcdGet64 (PcdSystemMemorySize);
  if (MemorySize >= SIZE_32GB - SIZE_1MB) {
    //
    // If memory size is >= 32 GiB - 1 MiB, we need to use ExtendedSize to represent it.
    // In this case, Size is set to 0x7FFF.
    //
    mMemDevInfoType17.Size         = 0x7FFF;
    mMemDevInfoType17.ExtendedSize = MemorySize / SIZE_1MB;
  } else {
    //
    // If memory size is < 32 GiB - 1 MiB, we use just Size.
    //
    mMemDevInfoType17.Size         = MemorySize / SIZE_1MB;
    mMemDevInfoType17.ExtendedSize = 0;
  }

  //
  // All memory on SbsaQemu is volatile
  //
  mMemDevInfoType17.VolatileSize = MemorySize;

  LogSmbiosData (
    (EFI_SMBIOS_TABLE_HEADER *)&mMemDevInfoType17,
    mMemDevInfoType17Strings,
    NULL
    );
}

/**
  Updates SMBIOS table Type 19 and creates an SMBIOS record for it.

  Must be called after PhyMemArrayInfoUpdateSmbiosType16.
**/
VOID
MemArrMapInfoUpdateSmbiosType19 (
  VOID
  )
{
  UINT64  MemorySize;
  UINT64  MemoryBase;
  UINT64  StartingAddressKiB;
  UINT64  EndingAddressKiB;

  //
  // PhyMemArrayInfoUpdateSmbiosType16 must be called before MemDevInfoUpdateSmbiosType17
  //
  if (mPhyMemArrayInfoType16Handle == SMBIOS_HANDLE_PI_RESERVED) {
    DEBUG ((DEBUG_ERROR, "%a: mPhyMemArrayInfoType16Handle is not initialized\n", __FUNCTION__));
    return;
  }

  mMemArrMapInfoType19.MemoryArrayHandle = mPhyMemArrayInfoType16Handle;

  MemorySize         = PcdGet64 (PcdSystemMemorySize);
  MemoryBase         = PcdGet64 (PcdSystemMemoryBase);
  StartingAddressKiB = MemoryBase / SIZE_1KB;
  EndingAddressKiB   = StartingAddressKiB + (MemorySize / SIZE_1KB) - 1;
  if (EndingAddressKiB > MAX_UINT32) {
    //
    // If the ending address cannot fit in a 32-bit unsigned integer, we use
    // ExtendedStartingAddress and ExtendedEndingAddress fields, which are 64 bits.
    // In this case, StartingAddress and EndingAddress fields are set to 0xFFFFFFFF.
    // Note that, since always starting address < ending address, if the ending address
    // can fit into 32 bits, then so can the starting address.
    //
    mMemArrMapInfoType19.StartingAddress         = 0xFFFFFFFF;
    mMemArrMapInfoType19.EndingAddress           = 0xFFFFFFFF;
    mMemArrMapInfoType19.ExtendedStartingAddress = StartingAddressKiB;
    mMemArrMapInfoType19.ExtendedEndingAddress   = EndingAddressKiB;
  } else {
    //
    // If the starting address and the ending address can fit into 32 bits, we just use
    // StartingAddress and EndingAddress fields.
    //
    mMemArrMapInfoType19.StartingAddress         = StartingAddressKiB;
    mMemArrMapInfoType19.EndingAddress           = EndingAddressKiB;
    mMemArrMapInfoType19.ExtendedStartingAddress = 0;
    mMemArrMapInfoType19.ExtendedEndingAddress   = 0;
  }

  LogSmbiosData (
    (EFI_SMBIOS_TABLE_HEADER *)&mMemArrMapInfoType19,
    mMemArrMapInfoType19Strings,
    NULL
    );
}

/**
  Driver entry point.
**/
EFI_STATUS
EFIAPI
SbsaQemuSmbiosDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  PhyMemArrayInfoUpdateSmbiosType16 ();
  MemDevInfoUpdateSmbiosType17 ();
  MemArrMapInfoUpdateSmbiosType19 ();

  return EFI_SUCCESS;
}
