/*
* 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;
}