Newer
Older
AMI-Aptio-BIOS-Reversed / TpmNvmeSupport / TpmNvmeSupport.c
@Ajax Dong Ajax Dong 2 days ago 21 KB Init
/** @file
  TpmNvmeSupport.c -- TPM NVMe Support DXE Driver

  This DXE driver is responsible for detecting NVMe device presence on
  TPM-connected NVMe controllers and reporting the result through the
  AMI BIOS Setup variable.  It integrates with the AMI TCG PPI mechanism
  and provides a periodic retry mechanism via the UEFI timer services.

  Functional overview:
  1. On entry (TpmNvmeSupportEntry), the driver initializes all UEFI
     globals (ImageHandle, SystemTable, BootServices, RuntimeServices),
     locates the HOB list and PCD protocol, maps the PCI Express MMIO
     range, and performs a hardware-settling delay.
  2. TpmNvmeSupportDxeEntry checks the AMI Setup "TpmNvmeSupport" option.
     If enabled, it clears the AMITCGPPIVAR variable and calls
     CheckNvmeDevicePresence().
  3. CheckNvmeDevicePresence() enumerates all handles with the NVMe
     Storage protocol, probes each handle for a live device, and updates
     the Setup variable with the presence flag (offset 300).
  4. If no device is found immediately, a periodic timer (sub_800) is
     registered to retry the check.
  5. Debug output is provided through the DebugMode protocol, gated by
     a CMOS-based severity filter (CMOS register 0x4B).

  Copyright (C) 2025, American Megatrends International LLC.
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include "TpmNvmeSupport.h"

//
// ---------------------------------------------------------------------------
// GUID Definitions
// ---------------------------------------------------------------------------
//

EFI_GUID  gAmiTcgPpiVariableGuid    = AMI_TCG_PPI_VARIABLE_GUID;
EFI_GUID  gAmiNvmeStorageProtocolGuid = AMI_NVME_STORAGE_PROTOCOL_GUID;
EFI_GUID  gAmiSetupVariableGuid     = AMI_SETUP_VARIABLE_GUID;
EFI_GUID  gDebugModeProtocolGuid    = DEBUG_MODE_PROTOCOL_GUID;
EFI_GUID  gPcdProtocolGuid          = PCD_PROTOCOL_GUID;
EFI_GUID  gEfiHobListGuid           = {
  0x7739f24c, 0x93d7, 0x11d4, { 0x9a, 0x3a, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }
};

//
// ---------------------------------------------------------------------------
// Global Data
// ---------------------------------------------------------------------------
//

//
// EFI BIOS globals.
//
EFI_HANDLE              gImageHandle       = NULL;
EFI_SYSTEM_TABLE        *gSystemTable       = NULL;
EFI_BOOT_SERVICES       *gBootServices      = NULL;
EFI_RUNTIME_SERVICES    *gRuntimeServices   = NULL;

//
// Debug mode protocol interface (obtained via LocateProtocol).
//
VOID                    *gDebugModeProtocol = NULL;

//
// HOB list pointer cached from the system configuration table.
//
VOID                    *gHobList           = NULL;

//
// PCI Express base address from PCD token 5.
//
UINT64                  gPciExpressBaseAddress = 0;

//
// PCD protocol interface (obtained via LocateProtocol).
//
VOID                    *gPcdProtocol       = NULL;

//
// ---------------------------------------------------------------------------
// Helper / CPU Intrinsic Functions
// ---------------------------------------------------------------------------
//

/**
  Executes the CPU PAUSE instruction.

  PAUSE is a hint that the code is in a spin-loop, improving performance
  and power consumption on SMT-capable processors.
**/
VOID
CpuPause (
  VOID
  )
{
  _mm_pause ();
}

/**
  Reads the Time-Stamp Counter (TSC).

  @return  The current 64-bit TSC value.
**/
UINT64
ReadTsc (
  VOID
  )
{
  return __rdtsc ();
}

/**
  Enables maskable interrupts (STI).
**/
VOID
EnableInterrupts (
  VOID
  )
{
  _enable ();
}

/**
  Disables maskable interrupts (CLI).
**/
VOID
DisableInterrupts (
  VOID
  )
{
  _disable ();
}

/**
  Reads the current processor EFLAGS register.

  @return  EFLAGS as a UINTN value.
**/
UINTN
ReadEflags (
  VOID
  )
{
  return __getcallerseflags ();
}

//
// ---------------------------------------------------------------------------
// Protocol Access Helpers
// ---------------------------------------------------------------------------
//

/**
  Locates and returns the debug mode protocol interface.

  The protocol is resolved once and cached in gDebugModeProtocol.
  A size check on the firmware memory allocation (must be >= 16) is
  used as a heuristic to detect pre-DXE phases where the protocol
  is unavailable.

  @return  Pointer to the debug protocol interface, or NULL if not found.
**/
DEBUG_MODE_PROTOCOL *
GetDebugProtocol (
  VOID
  )
{
  DEBUG_MODE_PROTOCOL  *Protocol;
  UINT64               MemorySize;

  if (gDebugModeProtocol != NULL) {
    return gDebugModeProtocol;
  }

  //
  // Check available memory -- in very early phases this will be small.
  //
  MemorySize = gBootServices->AllocatePages (AllocateAnyPages, EfiBootServicesData, 31);
  gBootServices->FreePages (MemorySize, 31);

  if (MemorySize > 0x10) {
    //
    // DXE phase -- attempt to locate the debug protocol.
    //
    if (gBootServices->LocateProtocol (
                         &gDebugModeProtocolGuid,
                         NULL,
                         &gDebugModeProtocol
                         ) >= 0) {
      return gDebugModeProtocol;
    }
  }

  gDebugModeProtocol = NULL;
  return NULL;
}

/**
  Locates and returns the PCD protocol interface.

  The protocol is resolved once and cached in gPcdProtocol.
  ASSERTs if the protocol cannot be located.

  @return  Pointer to the PCD protocol interface.
**/
PCD_PROTOCOL *
GetPcdProtocol (
  VOID
  )
{
  if (gPcdProtocol == NULL) {
    if (gBootServices->LocateProtocol (
                         &gPcdProtocolGuid,
                         NULL,
                         &gPcdProtocol
                         ) < 0) {
      DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
      ASSERT_EFI_ERROR (EFI_NOT_FOUND);
      ASSERT (gPcdProtocol != NULL);
    }
  }

  return gPcdProtocol;
}

//
// ---------------------------------------------------------------------------
// Debug Print and Assert Support
// ---------------------------------------------------------------------------
//

/**
  Conditionally prints a debug message based on CMOS debug-level filtering.

  The function reads the CMOS debug level register (port 0x70 register 0x4B)
  and only invokes the debug protocol print function if the requested
  ErrorLevel passes the filter mask.

  @param[in] ErrorLevel  DEBUG severity level for the message.
  @param[in] Format      Print format string.
  @param[in] ...         Variable argument list.
**/
VOID
EFIAPI
DebugPrint (
  IN UINTN        ErrorLevel,
  IN CONST CHAR8  *Format,
  ...
  )
{
  DEBUG_MODE_PROTOCOL  *Protocol;
  UINTN                 DebugLevel;
  UINTN                 FilterMask;
  VA_LIST              Va;
  UINT8                 CmosIndex;
  UINT8                 CmosValue;

  Protocol = GetDebugProtocol ();
  if (Protocol == NULL) {
    return;
  }

  //
  // Read the CMOS debug level register.
  //
  CmosIndex = IoRead8 (0x70) & 0x80;
  IoWrite8 (0x70, CmosIndex | 0x4B);
  CmosValue = IoRead8 (0x71);

  //
  // Decode the CMOS value to get the debug filter mask.
  //
  if (CmosValue > 3) {
    DebugLevel = CmosValue;
    if (CmosValue == 0) {
      //
      // Read hardware debug pin status.
      //
      DebugLevel = (MmioRead8 (0xFEDAF0490) & 2) | 1;
    }
  } else {
    DebugLevel = CmosValue;
  }

  DebugLevel--;

  if (DebugLevel > 0xFD) {
    FilterMask = 0;
  } else {
    FilterMask = (DebugLevel == 1) ? DEBUG_INFO : DEBUG_ERROR;
  }

  //
  // If the message passes the filter, call the debug protocol print function.
  //
  if ((FilterMask & ErrorLevel) != 0) {
    VA_START (Va, Format);
    Protocol->Print (ErrorLevel, Format, Va);
    VA_END (Va);
  }
}

/**
  ASSERT implementation that prints a message and breaks.

  @param[in] FileName     Source file where the assertion fired.
  @param[in] LineNumber   Line number of the assertion.
  @param[in] Description  Descriptive string for the failed assertion.
**/
VOID
EFIAPI
AssertBreak (
  IN CONST CHAR8  *FileName,
  IN UINTN        LineNumber,
  IN CONST CHAR8  *Description
  )
{
  DEBUG_MODE_PROTOCOL  *Protocol;

  Protocol = GetDebugProtocol ();
  if (Protocol != NULL) {
    Protocol->AssertBreak (FileName, LineNumber, Description);
  }
}

//
// ---------------------------------------------------------------------------
// HOB and PCD Access
// ---------------------------------------------------------------------------
//

/**
  Locates and returns the HOB list pointer.

  Scans the system configuration table for the gEfiHobListGuid entry
  and caches the result in gHobList.  ASSERTs on failure.

  @return  Pointer to the start of the HOB list.
**/
VOID *
GetHobList (
  VOID
  )
{
  UINTN  Index;

  if (gHobList != NULL) {
    return gHobList;
  }

  gHobList = NULL;

  if (gSystemTable->NumberOfTableEntries > 0) {
    for (Index = 0; Index < gSystemTable->NumberOfTableEntries; Index++) {
      if (CompareGuid (
            &gSystemTable->ConfigurationTable[Index].VendorGuid,
            &gEfiHobListGuid
            )) {
        gHobList = gSystemTable->ConfigurationTable[Index].VendorTable;
        break;
      }
    }

    if (gHobList == NULL) {
      DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
      ASSERT_EFI_ERROR (EFI_NOT_FOUND);
      ASSERT (gHobList != NULL);
    }
  } else {
    DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
    ASSERT_EFI_ERROR (EFI_NOT_FOUND);
    ASSERT (gHobList != NULL);
  }

  return gHobList;
}

/**
  Returns the PCI Express base address from PCD.

  Reads PCD token 5 via the PCD protocol (GetPcdProtocol()->Get5())
  and caches the result in gPciExpressBaseAddress.

  @return  The PCI Express MMIO base address.
**/
UINT64
GetPciExpressBaseAddress (
  VOID
  )
{
  PCD_PROTOCOL  *PcdProtocol;

  if (gPciExpressBaseAddress != 0) {
    return gPciExpressBaseAddress;
  }

  PcdProtocol = GetPcdProtocol ();

  //
  // Token 5 in the PCD database corresponds to PcdPciExpressBaseAddress.
  //
  gPciExpressBaseAddress = PcdProtocol->Get5 (PcdToken (PcdPciExpressBaseAddress));

  return gPciExpressBaseAddress;
}

//
// ---------------------------------------------------------------------------
// I/O and Memory Access Wrappers
// ---------------------------------------------------------------------------
//

/**
  Translates a PCI library address into a full MMIO address for PCI ECAM.

  @param[in] Address  PCI CF8-style address (only lower 28 bits valid).

  @return  Full MMIO address in the PCI Express ECAM space.
**/
UINTN
PciExpressLibAddress (
  IN UINTN  Address
  )
{
  ASSERT ((Address & ~0xFFFFFFF) == 0);
  return Address + gPciExpressBaseAddress;
}

/**
  Writes a 16-bit value to an I/O port.

  @param[in] Port   The I/O port address (must be word-aligned).
  @param[in] Value  The 16-bit value to write.
**/
VOID
IoWrite16 (
  IN UINT16  Port,
  IN UINT16  Value
  )
{
  ASSERT ((Port & 1) == 0);
  __outword (Port, Value);
}

/**
  Reads a 32-bit value from an I/O port.

  @param[in] Port  The I/O port address (must be dword-aligned).

  @return  The 32-bit value read from the port.
**/
UINT32
IoRead32 (
  IN UINT16  Port
  )
{
  ASSERT ((Port & 3) == 0);
  return __indword (Port);
}

/**
  Reads a 64-bit value from an unaligned buffer.

  @param[in] Buffer  Pointer to the buffer (must be non-NULL).

  @return  The 64-bit value at Buffer.
**/
UINT64
ReadUnaligned64 (
  IN CONST VOID  *Buffer
  )
{
  ASSERT (Buffer != NULL);
  return *(volatile UINT64 *)Buffer;
}

/**
  Compares two EFI GUIDs for equality.

  @param[in] Guid1  Pointer to the first GUID.
  @param[in] Guid2  Pointer to the second GUID.

  @retval TRUE   The two GUIDs are identical.
  @retval FALSE  The GUIDs differ.
**/
BOOLEAN
CompareGuid (
  IN CONST EFI_GUID  *Guid1,
  IN CONST EFI_GUID  *Guid2
  )
{
  UINT64  Low1;
  UINT64  High1;
  UINT64  Low2;
  UINT64  High2;

  if (Guid1 == NULL || Guid2 == NULL) {
    return FALSE;
  }

  Low1  = ReadUnaligned64 (Guid1);
  High1 = ReadUnaligned64 ((UINT8 *)Guid1 + 8);
  Low2  = ReadUnaligned64 (Guid2);
  High2 = ReadUnaligned64 ((UINT8 *)Guid2 + 8);

  return (BOOLEAN)(Low1 == Low2 && High1 == High2);
}

//
// ---------------------------------------------------------------------------
// Core Driver Logic
// ---------------------------------------------------------------------------
//

/**
  Driver entry point.

  Initializes all UEFI global state:
  1. Caches ImageHandle, SystemTable, BootServices, RuntimeServices.
  2. Locates the HOB list via the system configuration table.
  3. Reads the PCI Express base address from the PCD database.
  4. Performs short I/O delay to allow hardware to settle.
  5. Enables or disables interrupts based on the prior interrupt flag.

  @param[in] ImageHandle  The firmware-allocated handle for the driver image.
  @param[in] SystemTable  A pointer to the UEFI system table.

  @return  EFI_SUCCESS        Global state initialized successfully.
  @return  EFI_INVALID_PARAMETER  One of the required pointers is NULL.
**/
EFI_STATUS
EFIAPI
TpmNvmeSupportEntry (
  IN EFI_HANDLE          ImageHandle,
  IN EFI_SYSTEM_TABLE    *SystemTable
  )
{
  UINTN   PciBaseAddress;
  UINTN   DelayEnd;
  UINTN   StartTsc;
  BOOLEAN InterruptsOn;

  //
  // Save the driver image handle.
  //
  gImageHandle = ImageHandle;
  if (ImageHandle == NULL) {
    ASSERT (gImageHandle != NULL);
  }

  //
  // Save the system table.
  //
  gSystemTable = SystemTable;
  if (SystemTable == NULL) {
    ASSERT (gSystemTable != NULL);
  }

  //
  // Save the boot services table.
  //
  gBootServices = SystemTable->BootServices;
  if (gBootServices == NULL) {
    ASSERT (gBootServices != NULL);
  }

  //
  // Save the runtime services table.
  //
  gRuntimeServices = SystemTable->RuntimeServices;
  if (gRuntimeServices == NULL) {
    ASSERT (gRuntimeServices != NULL);
  }

  //
  // Locate the HOB list.
  //
  GetHobList ();

  //
  // Obtain the PCI Express base address from PCD.
  //
  GetPciExpressBaseAddress ();

  //
  // If the PcdPciExpressBaseAddress token is non-negative, enable
  // PCI Express MMIO access by writing the port 0xCF8 command.
  //
  if ((INT8)PciExpressLibAddress (0xF0004) >= 0) {
    PciExpressLibAddress (0xF0000);
    IoWrite16 (1280, 0x500);
  }

  //
  // Save the current interrupt state and disable interrupts.
  //
  InterruptsOn = (ReadEflags () & EFI_FLAGS_IF) != 0;
  DisableInterrupts ();

  //
  // Spin-wait for ~357 TSC ticks (approximately 100 ns on a 3.6 GHz CPU,
  // sufficient for I/O posting to complete).
  //
  StartTsc  = (UINTN)IoRead32 (1288);
  DelayEnd  = (StartTsc + 357) & 0x7FFFFF;
  do {
    CpuPause ();
  } while ((((UINTN)IoRead32 (1288) - DelayEnd) & 0x800000) == 0);

  ReadTsc ();

  //
  // Restore the interrupt state.
  //
  if (InterruptsOn) {
    EnableInterrupts ();
  } else {
    DisableInterrupts ();
  }

  return EFI_SUCCESS;
}

/**
  DXE entry routine that drives NVMe presence detection.

  1. Prints a startup banner.
  2. Checks whether the AMI Setup option for TPM NVMe support is enabled.
  3. If enabled, clears the AMI TCG PPI variable.
  4. Calls CheckNvmeDevicePresence().
  5. If presence detection fails, registers a periodic timer event that
     retries the check via gBS->SetTimer / gBS->RegisterProtocolNotify.

  @return  EFI_SUCCESS           Detection completed or retry scheduled.
  @return  EFI_INVALID_PARAMETER Setup variable read failed.
  @return  others                Error from UEFI services.
**/
EFI_STATUS
TpmNvmeSupportDxeEntry (
  VOID
  )
{
  EFI_STATUS  Status;
  UINTN       SetupVarSize;
  UINT8       SetupData[299];
  UINT8       TpmNvmeSupportEnabled;
  UINTN       NvmeVarSize;
  UINTN       VarSize;

  //
  // Print the module banner.
  //
  DEBUG ((DEBUG_INFO, " \n TpmNvmeSupportDxeEntry  \n "));

  //
  // Read the AMI Setup variable to check if TPM NVMe support is enabled.
  //
  SetupVarSize = sizeof (SetupData);
  Status = gRuntimeServices->GetVariable (
                               L"Setup",
                               &gAmiSetupVariableGuid,
                               NULL,
                               &SetupVarSize,
                               SetupData
                               );

  if (Status == EFI_SUCCESS) {
    //
    // Field at offset 0x12C (300) in the Setup structure holds the
    // TpmNvmeSupport option -- a single byte: 0x01 = enabled.
    //
    TpmNvmeSupportEnabled = SetupData[300];
    if (TpmNvmeSupportEnabled) {
      //
      // Clear the AMI TCG PPI variable.
      //
      SetupData[300] = 0;
      Status = gRuntimeServices->SetVariable (
                                   L"Setup",
                                   &gAmiSetupVariableGuid,
                                   EFI_VARIABLE_NON_VOLATILE |
                                     EFI_VARIABLE_BOOTSERVICE_ACCESS |
                                     EFI_VARIABLE_RUNTIME_ACCESS,
                                   SetupVarSize,
                                   SetupData
                                   );

      //
      // Delete the AMITCGPPIVAR variable.
      //
      VarSize = 0;
      gRuntimeServices->SetVariable (
                          L"AMITCGPPIVAR",
                          &gAmiTcgPpiVariableGuid,
                          0,
                          7,
                          NULL
                          );
    }
  }

  //
  // Perform the initial NVMe device presence check.
  //
  Status = CheckNvmeDevicePresence ();

  if (EFI_ERROR (Status)) {
    //
    // Device not found yet -- register a periodic timer to retry.
    //
    Status = gBootServices->SetTimer (
                              TimerPeriodic,
                              TimerRelative,
                              EFI_TIMER_PERIOD_SECONDS (8)
                              );
    if (!EFI_ERROR (Status)) {
      Status = gBootServices->RegisterProtocolNotify (
                                 &gAmiNvmeStorageProtocolGuid,
                                 gDriverBindingProtocol,
                                 &gImageHandle
                                 );
    }
  }

  DEBUG ((DEBUG_INFO, " \n CheckNvmeDevicePresence results = %r  \n ", Status));

  return Status;
}

/**
  Checks for the presence of an NVMe device.

  This function:
  1. Locates all handles supporting the NVMe storage protocol.
  2. Attempts to open the protocol on each handle.
  3. If any handle returns success, an NVMe device is considered present.
  4. Writes the presence flag into the AMI Setup variable (offset 300).
  5. Frees the handle buffer.

  @return  EFI_SUCCESS           Presence detection completed.
  @return  EFI_NOT_FOUND         No NVMe handles located.
  @return  EFI_INVALID_PARAMETER Invalid argument.
  @return  others                Error from UEFI protocol or variable services.
**/
EFI_STATUS
CheckNvmeDevicePresence (
  VOID
  )
{
  EFI_STATUS           Status;
  UINTN                HandleCount;
  EFI_HANDLE           *HandleBuffer;
  UINTN                Index;
  UINT8                NvmePresent;
  UINTN                SetupVarSize;
  UINT8                SetupData[848];
  UINT32               Attributes;
  UINTN                VarSize;

  HandleCount = 0;
  HandleBuffer = NULL;
  VarSize     = 814;

  //
  // Locate all handles that support the NVMe storage protocol.
  //
  Status = gBootServices->LocateHandleBuffer (
                            ByProtocol,
                            &gAmiNvmeStorageProtocolGuid,
                            NULL,
                            &HandleCount,
                            &HandleBuffer
                            );

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "Locate NvmePassThruhandles results = %r\n", Status));
    goto Done;
  }

  //
  // Scan all NVMe protocol handles for a live device.
  //
  NvmePresent = 0;
  for (Index = 0; Index < HandleCount; Index++) {
    VOID  *Interface;

    Interface = NULL;
    Status = gBootServices->HandleProtocol (
                              HandleBuffer[Index],
                              &gAmiNvmeStorageProtocolGuid,
                              &Interface
                              );

    if (!EFI_ERROR (Status)) {
      //
      // Found a responsive NVMe device.
      //
      NvmePresent = 1;
      break;
    }

    DEBUG ((DEBUG_INFO, "gBS->HandleProtocol: Status=%r\n", Status));
  }

  DEBUG ((
    DEBUG_INFO,
    " \n CheckNvmeDevicePresence Found = %x  \n ",
    NvmePresent
    ));

  //
  // Write the presence flag into the AMI Setup variable.
  //
  Attributes = 0;
  SetupVarSize = sizeof (SetupData);
  Status = gRuntimeServices->GetVariable (
                               L"Setup",
                               &gAmiSetupVariableGuid,
                               &Attributes,
                               &SetupVarSize,
                               SetupData
                               );

  if (!EFI_ERROR (Status)) {
    //
    // Update byte offset 300 in the Setup data (TpmNvmeSupport presence flag).
    //
    SetupData[300] = NvmePresent;
    Status = gRuntimeServices->SetVariable (
                                 L"Setup",
                                 &gAmiSetupVariableGuid,
                                 Attributes,
                                 VarSize,
                                 SetupData
                                 );
  }

Done:
  if (HandleBuffer != NULL) {
    gBootServices->FreePool (HandleBuffer);
  }

  return Status;
}

/**
  Timer notification callback that retries the NVMe device presence check.

  Registered as a periodic timer callback when the initial presence check
  fails.  It calls CheckNvmeDevicePresence() to try again.

  @param[in] Event    The timer event that triggered this callback.
  @param[in] Context  Not used (can be NULL).

  @return  EFI_SUCCESS from CheckNvmeDevicePresence().
**/
EFI_STATUS
EFIAPI
NvmeCheckTimerNotify (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  //
  // Print the module banner.
  //
  DEBUG ((DEBUG_INFO, " \n TpmNvmeSupportDxeEntry  \n "));

  //
  // Retry the NVMe device presence check.
  //
  CheckNvmeDevicePresence ();

  //
  // Signal the event to indicate completion.
  //
  gBootServices->SignalEvent (Event);

  return EFI_SUCCESS;
}