Newer
Older
AMI-Aptio-BIOS-Reversed / CpuIo2Smm / CpuIo2Smm.c
@Ajax Dong Ajax Dong 2 days ago 18 KB Init
/*
 * CpuIo2Smm.c - EFI_SMM_CPU_IO2 Protocol SMM Driver
 *
 * Source tree: UefiCpuPkg/CpuIo2Smm/CpuIo2Smm.c
 * Binary: CpuIo2Smm.efi (X64, PE32+) - HR650X index 0169
 * Build: VS2015, DEBUG, X64, HR6N0XMLK platform
 *
 * Provides EFI_SMM_CPU_IO2_PROTOCOL for MMIO and port I/O in SMM context.
 *
 * Function map:
 *   0x2C0  InternalCopyMem      - aligned+overlapped-aware memcpy
 *   0x310  SmmSetJump           - save context via SetJump
 *   0x3B0  SmmLongJump          - restore context via LongJump
 *   0x484  _ModuleEntryPoint    - AutoGen module entry point
 *   0x524  CpuIo2SmmInitServices - init gBS/gRT/gSmst (AutoGen)
 *   0x664  CpuIo2SmmCheckParameter - validate I/O parameters
 *   0x76C  CpuIo2SmmMemRead     - MMIO read via pointer deref
 *   0x850  CpuIo2SmmMemWrite    - MMIO write via pointer deref
 *   0x934  CpuIo2SmmIoRead      - I/O port read (inb/inw/ind)
 *   0xA10  CpuIo2SmmIoWrite     - I/O port write (outb/outw/outd)
 *   0xAF0  CpuIo2SmmInstallProtocol - SmmInstallProtocolInterface
 *   0xBD0  CpuIo2SmmGetDebugProtocol - locate debug protocol
 *   0xC20  CpuIo2SmmDebugVPrint - debug print wrapper
 *   0xCA8  CpuIo2SmmDebugAssert - debug assert wrapper
 *   0xCE8  CpuIo2SmmValidateJumpBuf - validate SetJump buffer alignment
 */

#include "CpuIo2Smm.h"

//
// Stride per width index:  mSmmIoStride[Width] = { 1, 2, 4, 8 }
// Stored at 0xE18.
//
CONST UINT8 mSmmIoStride[] = { 1, 2, 4, 8 };

//
// Globals initialized by CpuIo2SmmInitServices
//
EFI_HANDLE           gImageHandle      = NULL;   // 0x13E8
EFI_SYSTEM_TABLE    *gST               = NULL;   // 0x13D8
EFI_BOOT_SERVICES   *gBS               = NULL;   // 0x13E0
EFI_RUNTIME_SERVICES *gRT              = NULL;   // 0x13F0
VOID                 *gSmst            = NULL;   // 0x13F8 (SMM System Table 2)
VOID                 *gDebugProtocol   = NULL;   // 0x1400 (DebugLib protocol)

//
// SetJump buffer for SmmBase2->GetSmstLocation communication
// Stored at 0x1410.
//
BASE_LIBRARY_JUMP_BUFFER gJumpBuffer;

//
// EFI_SMM_CPU_IO2_PROTOCOL function table template at 0x13B0.
// 32 bytes: 4 x 8-byte function pointers:
//   +0x00: Mem.Read  (MMIO read,  0x76C)
//   +0x08: Mem.Write (MMIO write, 0x850)
//   +0x10: Io.Read   (port I/O read,  0x934)
//   +0x18: Io.Write  (port I/O write, 0xA10)
//
EFI_SMM_CPU_IO2_PROTOCOL gCpuIoTemplate = {
  {  // Mem
    CpuIo2SmmMemRead,   // .Read
    CpuIo2SmmMemWrite   // .Write
  },
  {  // Io
    CpuIo2SmmIoRead,    // .Read
    CpuIo2SmmIoWrite    // .Write
  }
};

//
// Protocol handle (initialized to NULL before installation)
// Stored at 0x13D0.
//
EFI_HANDLE mCpuIo2Handle = NULL;

//
// Module status - starts as EFI_UNSUPPORTED (0x8000000000000001)
// Stored at 0x1508. Updated with result from protocol installation.
//
EFI_STATUS gStatus = EFI_UNSUPPORTED;


// ============================================================================
// Internal Helpers (library bindings)
// ============================================================================

//
// InternalCopyMem (0x2C0) - aligned copy with overlap handling
//
STATIC
VOID *
EFIAPI
InternalCopyMem (
  OUT VOID       *Destination,
  IN  CONST VOID *Source,
  IN  UINTN      Length
  )
{
  CHAR8       *Dst8;
  CONST CHAR8 *Src8;

  Dst8 = (CHAR8 *)Destination;
  Src8 = (CONST CHAR8 *)Source;

  if ((Src8 < Dst8) && (&Src8[Length - 1] >= Dst8)) {
    // Overlapping: copy backwards from end
    Src8 = &Src8[Length - 1];
    Dst8 = &Dst8[Length - 1];
  } else {
    // Non-overlapping: copy aligned 8-byte chunks, then remainder
    CopyMem (Dst8, Src8, Length & ~7);
    Src8 += Length & ~7;
    Dst8 += Length & ~7;
  }

  CopyMem (Dst8, Src8, Length & 7);
  return Destination;
}


//
// SmmSetJump (0x310) - save execution context
//
STATIC
UINTN
EFIAPI
SmmSetJump (
  OUT BASE_LIBRARY_JUMP_BUFFER *JumpBuffer
  )
{
  return SetJump (JumpBuffer);
}


//
// SmmLongJump (0x3B0) - restore execution context
//
STATIC
VOID
EFIAPI
SmmLongJump (
  IN  BASE_LIBRARY_JUMP_BUFFER *JumpBuffer,
  IN  UINTN                    Value
  )
{
  LongJump (JumpBuffer, Value);
}


//
// CpuIo2SmmValidateJumpBuffer (0xCE8)
// Validates alignment of JumpBuffer for SetJump.
//
STATIC
VOID
CpuIo2SmmValidateJumpBuffer (
  IN BASE_LIBRARY_JUMP_BUFFER *JumpBuffer
  )
{
  ASSERT (JumpBuffer != NULL);
  ASSERT (((UINTN)JumpBuffer & (sizeof (UINT64) - 1)) == 0);
}


// ============================================================================
// CpuIo2SmmInitServices (0x524)
// AutoGen function that initializes gBS, gRT, gSmst from SystemTable.
// Called once during module entry.
//
// Also locates EFI_SMM_BASE2_PROTOCOL (F4CCBFB7-F6E0-47FD-9DD4-10A8F150C191)
// and retrieves the SMM System Table 2 via GetSmstLocation.
// ============================================================================

EFI_STATUS
EFIAPI
CpuIo2SmmInitServices (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;
  VOID        *SmmBase2;

  gImageHandle = ImageHandle;
  ASSERT (gImageHandle != NULL);

  gST = SystemTable;
  ASSERT (gST != NULL);

  gBS = SystemTable->BootServices;
  ASSERT (gBS != NULL);

  gRT = SystemTable->RuntimeServices;
  ASSERT (gRT != NULL);

  //
  // Locate EFI_SMM_BASE2_PROTOCOL
  //
  SmmBase2 = NULL;
  Status = gBS->LocateProtocol (
                  &gEfiSmmBase2ProtocolGuid,
                  NULL,
                  &SmmBase2
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
    ASSERT_EFI_ERROR (FALSE);
  }

  ASSERT (SmmBase2 != NULL);

  //
  // Retrieve gSmst via SmmBase2->GetSmstLocation()
  // Uses SetJump/LongJump for context switching inside SMM
  //
  CpuIo2SmmValidateJumpBuffer (&gJumpBuffer);
  SmmSetJump (&gJumpBuffer);
  Status = ((EFI_SMM_BASE2_PROTOCOL *)SmmBase2)->GetSmstLocation (&gSmst);
  SmmLongJump (&gJumpBuffer, 1);

  if (gSmst == NULL) {
    Status = EFI_NOT_FOUND;
    ASSERT (gSmst != NULL);
  }

  return Status;
}


// ============================================================================
// CpuIo2SmmCheckParameter (0x664)
// Validates I/O parameters for all four protocol interface functions.
//
// Returns:
//   EFI_SUCCESS           - parameters are valid
//   EFI_INVALID_PARAMETER - invalid width, alignment, or out of range
// ============================================================================

EFI_STATUS
CpuIo2SmmCheckParameter (
  IN  BOOLEAN                   MmioOperation,   // TRUE=MMIO, FALSE=I/O port
  IN  EFI_SMM_CPU_IO_WIDTH      Width,
  IN  UINTN                     Address,
  IN  UINTN                     Count,
  IN  VOID                      *Buffer
  )
{
  UINTN  MaxAddress;
  UINTN  Stride;

  //
  // Basic validation: Buffer must not be NULL, Width must be valid,
  // and QWord I/O (Width=3) is not supported for port I/O.
  //
  if ((Buffer == NULL) || (Width > SmmIoWidthUint64) ||
      (!MmioOperation && (Width == SmmIoWidthUint64))) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Maximum address:
  //   MMIO: full 64-bit address space
  //   I/O:  16-bit I/O port space (0x0000 - 0xFFFF)
  //
  if (MmioOperation) {
    MaxAddress = MAX_UINTN;
  } else {
    MaxAddress = MAX_UINT16;
  }

  //
  // If Count > 0, ensure the access range does not exceed MaxAddress.
  // Stride is mSmmIoStride[Width]: 1, 2, 4, or 8 bytes.
  //
  if (Count > 0) {
    Stride = mSmmIoStride[Width];
    if (Stride >= Count - 1) {
      return EFI_INVALID_PARAMETER;
    }
    MaxAddress = (Stride - Count + 1) << Width;
  }

  //
  // Check start address is within bounds
  //
  if (Address > MaxAddress) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Alignment: Address must be aligned to its access width
  //
  if ((Address & (mSmmIoStride[Width] - 1)) != 0) {
    return EFI_INVALID_PARAMETER;
  }

  return EFI_SUCCESS;
}


// ============================================================================
// EFI_SMM_CPU_IO2_PROTOCOL Interface Functions
// ============================================================================

//
// CpuIo2SmmMemRead (0x76C)
// MMIO read via direct memory pointer dereference.
// Reads Count elements of Width from Address into Buffer.
//
EFI_STATUS
EFIAPI
CpuIo2SmmMemRead (
  IN  CONST EFI_SMM_CPU_IO2_PROTOCOL  *This,
  IN  EFI_SMM_CPU_IO_WIDTH            Width,
  IN  UINTN                           Address,
  IN  UINTN                           Count,
  IN  OUT VOID                        *Buffer
  )
{
  EFI_STATUS  Status;
  UINT8       Stride;
  UINTN       Index;
  UINT8       *Src;
  UINT8       *Dst;
  INTN        Delta;

  Status = CpuIo2SmmCheckParameter (TRUE, Width, Address, Count, Buffer);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (Count == 0) {
    return EFI_SUCCESS;
  }

  Stride = mSmmIoStride[Width];
  Src = (UINT8 *)(UINTN)Address;
  Dst = (UINT8 *)Buffer;
  Delta = Dst - Src;

  switch (Width) {
    case SmmIoWidthUint8:
      for (Index = 0; Index < Count; Index++) {
        *(Dst + Index * Stride) = *(Src + Index * Stride);
      }
      break;
    case SmmIoWidthUint16:
      for (Index = 0; Index < Count; Index++) {
        ASSERT (((UINTN)(Src + Index * Stride) & 1) == 0);
        *(UINT16 *)((UINT8 *)Dst + Index * Stride) =
          *(UINT16 *)((UINT8 *)Src + Index * Stride);
      }
      break;
    case SmmIoWidthUint32:
      for (Index = 0; Index < Count; Index++) {
        *(UINT32 *)((UINT8 *)Dst + Index * Stride) =
          *(UINT32 *)((UINT8 *)Src + Index * Stride);
      }
      break;
    case SmmIoWidthUint64:
      for (Index = 0; Index < Count; Index++) {
        ASSERT (((UINTN)(Src + Index * Stride) & 7) == 0);
        *(UINT64 *)((UINT8 *)Dst + Index * Stride) =
          *(UINT64 *)((UINT8 *)Src + Index * Stride);
      }
      break;
  }

  return EFI_SUCCESS;
}


//
// CpuIo2SmmMemWrite (0x850)
// MMIO write via direct memory pointer dereference.
// Writes Count elements of Width from Buffer into Address.
//
EFI_STATUS
EFIAPI
CpuIo2SmmMemWrite (
  IN  CONST EFI_SMM_CPU_IO2_PROTOCOL  *This,
  IN  EFI_SMM_CPU_IO_WIDTH            Width,
  IN  UINTN                           Address,
  IN  UINTN                           Count,
  IN  OUT VOID                        *Buffer
  )
{
  EFI_STATUS  Status;
  UINT8       Stride;
  UINTN       Index;
  UINT8       *Dst;
  UINT8       *Src;
  INTN        Delta;

  Status = CpuIo2SmmCheckParameter (TRUE, Width, Address, Count, Buffer);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (Count == 0) {
    return EFI_SUCCESS;
  }

  Stride = mSmmIoStride[Width];
  Src = (UINT8 *)Buffer;
  Dst = (UINT8 *)(UINTN)Address;
  Delta = Dst - Src;

  switch (Width) {
    case SmmIoWidthUint8:
      for (Index = 0; Index < Count; Index++) {
        *(Dst + Index * Stride) = *(Src + Index * Stride);
      }
      break;
    case SmmIoWidthUint16:
      for (Index = 0; Index < Count; Index++) {
        ASSERT (((UINTN)(Src + Index * Stride) & 1) == 0);
        *(UINT16 *)((UINT8 *)Dst + Index * Stride) =
          *(UINT16 *)((UINT8 *)Src + Index * Stride);
      }
      break;
    case SmmIoWidthUint32:
      for (Index = 0; Index < Count; Index++) {
        *(UINT32 *)((UINT8 *)Dst + Index * Stride) =
          *(UINT32 *)((UINT8 *)Src + Index * Stride);
      }
      break;
    case SmmIoWidthUint64:
      for (Index = 0; Index < Count; Index++) {
        ASSERT (((UINTN)(Src + Index * Stride) & 7) == 0);
        *(UINT64 *)((UINT8 *)Dst + Index * Stride) =
          *(UINT64 *)((UINT8 *)Src + Index * Stride);
      }
      break;
  }

  return EFI_SUCCESS;
}


//
// CpuIo2SmmIoRead (0x934)
// I/O port read via inb/inw/ind instructions.
// Reads Count elements of Width from I/O Port Address into Buffer.
//
EFI_STATUS
EFIAPI
CpuIo2SmmIoRead (
  IN  CONST EFI_SMM_CPU_IO2_PROTOCOL  *This,
  IN  EFI_SMM_CPU_IO_WIDTH            Width,
  IN  UINTN                           Address,
  IN  UINTN                           Count,
  IN  OUT VOID                        *Buffer
  )
{
  EFI_STATUS  Status;
  UINT8       Stride;
  UINTN       Index;
  UINTN       Port;

  Status = CpuIo2SmmCheckParameter (FALSE, Width, Address, Count, Buffer);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (Count == 0) {
    return EFI_SUCCESS;
  }

  Stride = mSmmIoStride[Width];
  Port = Address;

  switch (Width) {
    case SmmIoWidthUint8:
      for (Index = 0; Index < Count; Index++) {
        *(UINT8 *)((UINT8 *)Buffer + Index * Stride) =
          IoRead8 ((UINT16)Port);
        Port += Stride;
      }
      break;
    case SmmIoWidthUint16:
      for (Index = 0; Index < Count; Index++) {
        ASSERT ((Port & 1) == 0);
        *(UINT16 *)((UINT8 *)Buffer + Index * Stride) =
          IoRead16 ((UINT16)Port);
        Port += Stride;
      }
      break;
    case SmmIoWidthUint32:
      for (Index = 0; Index < Count; Index++) {
        ASSERT ((Port & 3) == 0);
        *(UINT32 *)((UINT8 *)Buffer + Index * Stride) =
          IoRead32 ((UINT16)Port);
        Port += Stride;
      }
      break;
    case SmmIoWidthUint64:
      // QWord I/O port access is not supported by x86 ISA
      break;
  }

  return EFI_SUCCESS;
}


//
// CpuIo2SmmIoWrite (0xA10)
// I/O port write via outb/outw/outd instructions.
// Writes Count elements of Width from Buffer to I/O Port Address.
//
EFI_STATUS
EFIAPI
CpuIo2SmmIoWrite (
  IN  CONST EFI_SMM_CPU_IO2_PROTOCOL  *This,
  IN  EFI_SMM_CPU_IO_WIDTH            Width,
  IN  UINTN                           Address,
  IN  UINTN                           Count,
  IN  OUT VOID                        *Buffer
  )
{
  EFI_STATUS  Status;
  UINT8       Stride;
  UINTN       Index;
  UINTN       Port;

  Status = CpuIo2SmmCheckParameter (FALSE, Width, Address, Count, Buffer);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (Count == 0) {
    return EFI_SUCCESS;
  }

  Stride = mSmmIoStride[Width];
  Port = Address;

  switch (Width) {
    case SmmIoWidthUint8:
      for (Index = 0; Index < Count; Index++) {
        IoWrite8 ((UINT16)Port,
                  *(UINT8 *)((UINT8 *)Buffer + Index * Stride));
        Port += Stride;
      }
      break;
    case SmmIoWidthUint16:
      for (Index = 0; Index < Count; Index++) {
        ASSERT ((Port & 1) == 0);
        IoWrite16 ((UINT16)Port,
                   *(UINT16 *)((UINT8 *)Buffer + Index * Stride));
        Port += Stride;
      }
      break;
    case SmmIoWidthUint32:
      for (Index = 0; Index < Count; Index++) {
        ASSERT ((Port & 3) == 0);
        IoWrite32 ((UINT16)Port,
                   *(UINT32 *)((UINT8 *)Buffer + Index * Stride));
        Port += Stride;
      }
      break;
    case SmmIoWidthUint64:
      // QWord I/O port access is not supported by x86 ISA
      break;
  }

  return EFI_SUCCESS;
}


// ============================================================================
// CpuIo2SmmInstallProtocol (0xAF0)
// Installs EFI_SMM_CPU_IO2_PROTOCOL via gSmst->SmmInstallProtocolInterface.
// Also copies the function table to SmmIoMemAbstraction area of Smst.
// ============================================================================

EFI_STATUS
CpuIo2SmmInstallProtocol (
  VOID
  )
{
  EFI_STATUS  Status;
  EFI_SMM_SYSTEM_TABLE2 *Smst;

  Smst = (EFI_SMM_SYSTEM_TABLE2 *)gSmst;

  //
  // Copy protocol function table to Smst+0x30
  // (SmmIoMemAbstraction / platform reserved area)
  //
  CopyMem (
    (UINT8 *)Smst + 0x30,
    &gCpuIoTemplate,
    sizeof (gCpuIoTemplate)
    );

  //
  // Install protocol into SMM protocol database
  //   ProtocolGuid: 3242A9D8-CE70-4AA0-955D-5E7B140DE4D2
  //   Handle:       &mCpuIo2Handle
  //   Type:         EFI_NATIVE_INTERFACE
  //   Interface:    &gCpuIoTemplate
  //
  Status = Smst->SmmInstallProtocolInterface (
                   &mCpuIo2Handle,
                   &gEfiSmmCpuIo2ProtocolGuid,
                   EFI_NATIVE_INTERFACE,
                   &gCpuIoTemplate
                   );

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
    ASSERT_EFI_ERROR (FALSE);
  }

  return Status;
}


// ============================================================================
// Debug Protocol Support
// The GUID at 0x1390 (441FFA18-8714-421E-8C95-587080796FEE) is used to
// locate a debug print protocol via gSmst->SmmLocateProtocol.
// ============================================================================

//
// CpuIo2SmmGetDebugProtocol (0xBD0)
//
STATIC
VOID *
CpuIo2SmmGetDebugProtocol (
  VOID
  )
{
  EFI_STATUS  Status;

  if (gDebugProtocol == NULL) {
    Status = ((EFI_SMM_SYSTEM_TABLE2 *)gSmst)->SmmLocateProtocol (
               &gEfiSmmCpuIo2ProtocolGuid,
               NULL,
               &gDebugProtocol
               );
    if (EFI_ERROR (Status)) {
      gDebugProtocol = NULL;
    }
  }

  return gDebugProtocol;
}


//
// CpuIo2SmmDebugVPrint (0xC20)
// Debug print using the located debug protocol.
//
STATIC
VOID
CpuIo2SmmDebugVPrint (
  IN  UINTN       ErrorLevel,
  IN  CONST CHAR8  *Format,
  ...
  )
{
  VOID    *DebugProtocol;
  UINTN   DebugLevel;
  UINT8   CmosIndex;
  UINT8   CmosData;
  VA_LIST Marker;

  VA_START (Marker, Format);

  DebugProtocol = CpuIo2SmmGetDebugProtocol ();
  if (DebugProtocol != NULL) {
    //
    // Read debug level from CMOS register 0x4C
    //
    CmosIndex = IoRead8 (0x70) & 0x80 | 0x4C;
    IoWrite8 (0x70, CmosIndex);
    CmosData = IoRead8 (0x71);

    //
    // Determine debug print level from CMOS byte
    //
    DebugLevel = 0;
    if (CmosData <= 3) {
      if (CmosData == 0) {
        DebugLevel = MEMORY[0xFDAF0490] & 2 | 1;
      } else {
        DebugLevel = CmosData & 0xFF;
      }
    }

    if ((DebugLevel & ErrorLevel) != 0) {
      ((EFI_DEBUG_PRINT_PROTOCOL *)DebugProtocol)->DebugVPrint (
        ErrorLevel,
        Format,
        Marker
        );
    }
  }

  VA_END (Marker);
}


//
// CpuIo2SmmDebugAssert (0xCA8)
// Debug assert call. Invokes the debug protocol's assert handler.
//
STATIC
VOID
CpuIo2SmmDebugAssert (
  IN CONST CHAR8  *FileName,
  IN UINTN         LineNumber,
  IN CONST CHAR8  *Description
  )
{
  VOID  *DebugProtocol;

  DebugProtocol = CpuIo2SmmGetDebugProtocol ();
  if (DebugProtocol != NULL) {
    ((EFI_DEBUG_ASSERT_PROTOCOL *)DebugProtocol)->DebugAssert (
      FileName,
      LineNumber,
      Description
      );
  }
}


// ============================================================================
// _ModuleEntryPoint (0x484)
// ============================================================================

EFI_STATUS
EFIAPI
CpuIo2SmmDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;

  //
  // Phase 1: Initialize global service table pointers
  //          (gBS, gRT, gSmst)
  //
  Status = CpuIo2SmmInitServices (ImageHandle, SystemTable);
  gStatus = Status;

  //
  // Phase 2: Install EFI_SMM_CPU_IO2_PROTOCOL
  //          (if Phase 1 succeeded)
  //
  if (!EFI_ERROR (Status)) {
    //
    // Note: we call into SmmBase2 via SetJump context.
    // The jump buffer at gJumpBuffer (0x1410) is used by AutoGen.
    //
    CpuIo2SmmValidateJumpBuffer (&gJumpBuffer);
    SmmSetJump (&gJumpBuffer);
    Status = CpuIo2SmmInstallProtocol ();
    SmmLongJump (&gJumpBuffer, 1);

    //
    // Preserve install error if init was OK but install failed
    //
    gStatus = Status;
  }

  return gStatus;
}