/** @file
SBDXE -- South Bridge DXE Driver.
Provides South Bridge initialization for Purley/Lewisburg PCH:
- Installs driver binding and protocol interfaces
- Manages XHCI USB port-per-port disable based on HII configuration
- Configures PCIe segment/bus routing via PCIE_SEG_BUS_TABLE
- Integrates with S3 Boot Script save/restore and SMM LockBox
- Detects PCH stepping and SKU for runtime policy decisions
Copyright (c) 2017-2019, American Megatrends Inc. (AMI)
Copyright (c) 2017-2019, Intel Corporation.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Uefi.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/IoLib.h>
#include <Library/PcdLib.h>
#include <Library/UefiLib.h>
#include <Library/DxeServicesTableLib.h>
#include <Library/DxeHobLib.h>
#include <Library/PciExpressLib.h>
#include <Library/PrintLib.h>
#include <Protocol/PciIo.h>
#include <Protocol/SmmCommunication.h>
#include <Protocol/PiPcd.h>
#include <Guid/Acpi.h>
#include <Guid/EventGroup.h>
#include "SBDXE.h"
//
// ============================================================================
// Global Data
// ============================================================================
//
EFI_HANDLE gImageHandle = NULL;
EFI_SYSTEM_TABLE *gST = NULL;
EFI_BOOT_SERVICES *gBS = NULL;
EFI_RUNTIME_SERVICES *gRT = NULL;
//
// HOB list pointer (from DXE HOB library)
//
VOID *gHobList = NULL;
//
// PciUsra (MM PCI base) from DxeMmPciBaseLib
//
VOID *gPciUsra = NULL;
//
// PciExpress Base Address (from PCD)
//
UINT64 gPciExpressBaseAddr = 0;
//
// PCH stepping / SKU global state
//
UINT32 gPchStepping = 3; ///< Initial state; determined at first call
UINT32 gPchSku = 3;
//
// Boot Script / S3 state
//
UINT64 gBootScriptTable = 0;
UINT64 gBootScriptTable2 = 0;
UINT8 gBootScriptLocked = 0;
BOOLEAN gS3BootScriptInitialized = FALSE;
BOOLEAN gSmmReadyToLockOccurred = FALSE;
//
// XHCI USB port disable state: set to TRUE after first callback
//
BOOLEAN gXhciPortDisableDone = FALSE;
//
// PCD protocol, SMM Communication protocol
//
VOID *gPcdProtocol = NULL;
VOID *gSmmCommunicationProtocol = NULL;
//
// PciUsra via SMM PCI base protocol
//
STATIC VOID *mPciUsra = NULL;
//
// ============================================================================
// GUID Definitions
// ============================================================================
//
EFI_GUID gPchRcConfigGuid = { 0x6350F689, 0x6EF9, 0x4B9A, { 0x87, 0xA0, 0xBB, 0xF6, 0x4D, 0x35, 0x08, 0xED }};
EFI_GUID gBootScriptContextGuid = { 0x5C6590C9, 0x11D5, 0x4A1B, { 0xA8, 0xF5, 0x4B, 0x62, 0x3B, 0x4D, 0x3C, 0x42 }};
EFI_GUID gBootScriptExecutorGuid = { 0x9BDBB6F3, 0x600A, 0x42FF, { 0xBD, 0x15, 0x92, 0x05, 0x1B, 0x0A, 0xDD, 0x8E }};
EFI_GUID gEfiPciRootBridgeIoProtocolGuid = EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_GUID;
EFI_GUID gEfiSmmCommunicationProtocolGuid = EFI_SMM_COMMUNICATION_PROTOCOL_GUID;
EFI_GUID gEfiPcdProtocolGuid = { 0x11B0836B, 0x1D4A, 0x4BDE, { 0x88, 0x87, 0x7F, 0x06, 0xE2, 0x13, 0x22, 0xC4 }};
//
// GUID for PiSmmCommunicationRegionTable (for SMM LockBox)
//
EFI_GUID gSmmCommRegionGuid = { 0x8E7BE0F9, 0x3D41, 0x42D9, { 0x9F, 0xFF, 0xAC, 0xC0, 0x88, 0xC2, 0xC8, 0x32 }};
//
// ============================================================================
// Function Prototypes (local)
// ============================================================================
//
STATIC
EFI_STATUS
GetPcdProtocol (
VOID
);
STATIC
VOID
SbdInstallPcieSegBusTable (
VOID
);
// ============================================================================
// Internal helper: ASSERT macro with message
// ============================================================================
/**
Internal assertion helper. Called by ASSERT macros from UEFI libraries.
Logs the assertion to the debug console and then breaks.
**/
VOID
SbdAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Description
);
/**
Debug print helper. Routes to the UEFI debug output if the debug level
is enabled.
**/
VOID
SbdDebugPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
);
// ============================================================================
// Internal: PciExpress MMIO address computation
// ============================================================================
/**
Get the PCI Express MMIO base address from the PCD protocol.
@return PCI Express MMIO base address, or 0 if unavailable.
**/
UINT64
GetPciExpressBaseAddress (
VOID
)
{
return gPciExpressBaseAddr;
}
// ============================================================================
// PCH SKU Detection
// ============================================================================
/**
Detect PCH SKU (PCH-H vs PCH-LP) based on LPC/eSPI device ID.
Reads the LPC device ID via PCI config space and categorizes it:
- Some device IDs indicate PCH-H (SKU 1)
- Others indicate PCH-LP (SKU 2)
@return 1 for PCH-H, 2 for PCH-LP, 3 if unrecognized.
**/
UINT32
GetPchSku (
VOID
)
{
UINTN LpcDevId;
UINT32 Sku = 3;
if (gPchSku == 3) {
//
// Read LPC/eSPI device ID from PCI config space (Bus 0, Dev 31, Func 0, offset 2)
//
LpcDevId = IoRead16 (PCI_EXPRESS_LIB_ADDRESS (0, 31, 0, 0x02));
//
// Check if device ID falls in the PCH-H range
// PCH-H device IDs typically include A1xx-A14x, A150-A1FF range
//
if (((LpcDevId + 24128) & 0xFF70) == 0) {
//
// This is PCH-H (SKU 1)
//
Sku = 1;
} else if ((UINT16)(LpcDevId + 25280) <= 8) {
//
// This is PCH-LP (SKU 2)
//
Sku = 2;
} else {
DEBUG ((DEBUG_ERROR, "Unsupported PCH SKU, LpcDeviceId: 0x%04x!\n", (UINT16)LpcDevId));
ASSERT (!"Unsupported PCH SKU");
}
gPchSku = Sku;
}
return gPchSku;
}
// ============================================================================
// PCH Stepping Detection
// ============================================================================
/**
Determine the PCH stepping (silicon revision) by reading the PCI config
space of the LPC/eSPI controller and the RTC CMOS register.
Stepping values:
0 = A0 step
1 = A1 step
2 = A2 step
3 = B0 step
4 = B1 step
35 = C1 (PCH-LP D0)
36 = C2 (PCH-LP D1)
37 = C3 (PCH-LP D2)
38 = C4 (PCH-LP D3)
48 = D0
49 = D1
50 = D2
51 = D3
52 = E0
53 = E1
54 = Unknown/unsupported
@return PCH stepping identifier.
**/
UINT32
GetPchStepping (
VOID
)
{
UINT32 Stepping;
UINT8 RevId;
UINT32 LpcDeviceId;
UINTN PchInfoBase;
if (gPchStepping != 54) {
return gPchStepping;
}
//
// Read the PCH RID (Revision ID) from PCI config
// Get the base address for PCH info from the PchInfoLib
//
PchInfoBase = PciExpressRead32 (
PCI_EXPRESS_LIB_ADDRESS (0, 31, 0, 0x00)
);
RevId = (UINT8)(PchInfoBase >> 8);
//
// Read the LPC device ID for stepping decode
//
LpcDeviceId = IoRead16 (PCI_EXPRESS_LIB_ADDRESS (0, 31, 0, 0x02));
//
// Decode stepping based on Device ID + Rev ID
//
//
// A-stepping devices (LbgA0/A1/A2)
//
if (((LpcDeviceId + 24128) & 0xFF70) == 0) {
//
// PCH-H with known A-step: map rev ID to stepping
//
switch (RevId) {
case 0x00:
return 0; // A0
case 0x10:
return 1; // A1
case 0x20:
return 2; // A2
case 0x30:
return 3; // B0
case 0x31:
return 4; // B1
default:
DEBUG ((DEBUG_ERROR,
"Unsupported PCH Stepping. Supporting PCH stepping starting from LbgA0 and above\n"));
ASSERT (!"Unsupported PCH Stepping");
return 54;
}
}
//
// C-stepping or later (PCH-LP or newer PCH-H)
//
if ((UINT16)(LpcDeviceId + 25280) <= 8) {
switch (RevId) {
case 0x10:
return 35; // C1 / LP D0
case 0x11:
return 36; // C2 / LP D1
case 0x20:
return 37; // C3 / LP D2
case 0x21:
return 38; // C4 / LP D3
default:
goto Unsupported;
}
}
//
// If none of the above, check for D/E stepping
//
if (((LpcDeviceId + 24128) & 0xFF70) != 0) {
return 54; // Unknown, keep default
}
//
// Check the RTC CMOS register for stepping hints
//
{
UINT8 CmosVal;
CmosVal = IoRead8 (RTC_INDEX_PORT);
IoWrite8 (RTC_INDEX_PORT, CmosVal & 0x80 | RTC_REGISTER_B);
RevId = IoRead8 (RTC_DATA_PORT);
if (RevId > 3) {
if (gPchStepping == 0) {
RevId = MmioRead8 (0xFDAF0490) & 2 | 1;
} else {
RevId = 0;
}
}
if (RevId == 1) {
return 48; // D0
} else if (RevId == 2) {
return 49; // D1
} else if (RevId == 3) {
return 50; // D2
} else if (RevId == 4) {
return 51; // D3
} else if (RevId == 5) {
return 52; // E0
} else if (RevId == 6) {
return 53; // E1
} else {
return 54; // Unknown
}
}
Unsupported:
DEBUG ((DEBUG_ERROR,
"Unsupported PCH Stepping. Supporting PCH stepping starting from LbgA0 and above\n"));
ASSERT (!"((BOOLEAN)(0==1))");
return 54;
}
// ============================================================================
// XHCI Per-Port Disable Logic
// ============================================================================
/**
Internal helper: write a DWORD to an XHCI MMIO register.
@param XhciBar XHCI controller MMIO base address.
@param Offset Register offset from XhciBar.
@param Value 32-bit value to write.
**/
STATIC
VOID
XhciWrite32 (
IN UINTN XhciBar,
IN UINTN Offset,
IN UINT32 Value
)
{
MmioWrite32 (XhciBar + Offset, Value);
}
/**
Internal helper: read a DWORD from an XHCI MMIO register.
@param XhciBar XHCI controller MMIO base address.
@param Offset Register offset from XhciBar.
@return 32-bit value read.
**/
STATIC
UINT32
XhciRead32 (
IN UINTN XhciBar,
IN UINTN Offset
)
{
return MmioRead32 (XhciBar + Offset);
}
/**
Disable a single USB port on the XHCI controller.
For HS (USB 2.0) ports: sets PORTSC = 0x210 (Port Reset + Port Disable)
and waits for the port to become disabled.
For SS (USB 3.0) ports: sets PORTSC = 0x8000FE00 (Port Disable + Link Error)
and waits for the port to become disabled.
@param XhciBar XHCI MMIO base address.
@param PortBase PORTSC register offset for this port.
@param PortNumber Physical port number (0-based).
@param IsSSPort TRUE if SuperSpeed (USB 3.0) port.
**/
VOID
XhciDisablePort (
IN UINTN XhciBar,
IN UINTN PortBase,
IN UINTN PortNumber,
IN BOOLEAN IsSSPort
)
{
UINT32 PortSc;
UINT32 Timeout;
PortSc = XhciRead32 (XhciBar, PortBase);
if ((PortSc & BIT1) == 0) {
//
// Port is already disabled, nothing to do.
//
return;
}
DEBUG ((DEBUG_INFO, "Disable port %d under Xhci controller\n", PortNumber + 1));
//
// Issue port disable
//
if (IsSSPort) {
//
// For SS ports, set Port Disable + Reset
//
XhciWrite32 (XhciBar, PortBase, 0x8000FE00);
//
// Wait up to 3 seconds (3000 * 1ms)
//
for (Timeout = 0; Timeout < 3000; Timeout++) {
PortSc = XhciRead32 (XhciBar, PortBase);
if ((PortSc & 0x280001) != BIT0) {
break;
}
gBS->Stall (1000);
}
} else {
//
// For HS ports, set Port Reset + Port Disable
//
XhciWrite32 (XhciBar, PortBase, 0x210);
//
// Wait up to 200ms (200 * 1ms)
//
for (Timeout = 0; Timeout < 200; Timeout++) {
PortSc = XhciRead32 (XhciBar, PortBase);
if ((PortSc & 0x200001) != BIT0) {
break;
}
gBS->Stall (1000);
}
}
}
/**
USB Per-Port Disable Callback.
Invoked by the UEFI event mechanism (notify function) after the
PchRcConfiguration variable is ready. Reads per-port enable/disable
flags from the HII configuration and programs the XHCI controller's
Port Disable Override (PDO) and SuperSpeed Port Enable (SSPE) registers
accordingly.
@param[in] Event Event whose notification function is being invoked.
@param[in] Context Pointer to the notification function's context.
@return Status code.
**/
EFI_STATUS
EFIAPI
UsbPerPortDisableCallback (
IN EFI_EVENT Event,
IN VOID *Context
)
{
UINTN XhciBar;
UINT32 HsPdo;
UINT32 SsPdo;
UINT32 SsPe;
UINT32 Sku;
UINT32 MaxHsPorts;
UINT32 MaxSsPorts;
UINT32 PortIndex;
UINT32 PortScOffset;
UINTN PdoPortOffset;
UINT8 PortConfig[1552];
UINT16 ConfigDataSize;
UINT16 ConfigRequest;
UINT32 *PciAddr;
EFI_STATUS Status;
UINT8 PortEnabled;
UINT8 DisableMask;
if (gXhciPortDisableDone) {
goto Done;
}
DEBUG ((DEBUG_INFO, "USBPerPortDisableCallback - Start\n"));
//
// Determine PCH SKU for port count and register layout
//
Sku = GetPchSku ();
if (Sku == 2) {
MaxHsPorts = 10;
MaxSsPorts = 6;
} else {
MaxHsPorts = 14;
MaxSsPorts = 10;
}
//
// Read PchRcConfiguration variable from runtime services
//
ConfigDataSize = sizeof (PortConfig);
ZeroMem (PortConfig, sizeof (PortConfig));
Status = gRT->GetVariable (
L"PchRcConfiguration",
&gPchRcConfigGuid,
NULL,
&ConfigDataSize,
PortConfig
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "Failed to read PchRcConfiguration: %r\n", Status));
goto Done;
}
//
// Read XHCI BAR
//
ConfigRequest = 0xA0010; // EFI_PCI_IO_ATTRIBUTE_MEMORY
PciAddr = (UINT32 *)gBS->LocateProtocol (&gEfiPciRootBridgeIoProtocolGuid, NULL, NULL);
//
// Simplified: Read XHCI MMIO BAR from PCI config space (Dev 20, Func 0)
//
XhciBar = PciExpressRead32 (PCI_EXPRESS_LIB_ADDRESS (0, 20, 0, 0x10));
if ((XhciBar & 0xFFFFFFF0) == 0) {
DEBUG ((DEBUG_ERROR, "XHCI BAR is 0!\n"));
goto Done;
}
XhciBar &= 0xFFFFFFF0;
//
// Handle 64-bit BAR if needed
//
if ((PciExpressRead32 (PCI_EXPRESS_LIB_ADDRESS (0, 20, 0, 0x10)) & 0x06) == 0x04) {
XhciBar |= (UINTN)PciExpressRead32 (PCI_EXPRESS_LIB_ADDRESS (0, 20, 0, 0x14)) << 32;
}
DEBUG ((DEBUG_INFO, "Xhci Bar = %lx\n", XhciBar));
//
// Read current HS PDO, SS PDO, and SSPE values
//
HsPdo = XhciRead32 (XhciBar, XHCI_HSPDO_OFS);
if (Sku == 2) {
SsPdo = HsPdo & 0x3FF;
SsPe = 0;
} else {
SsPdo = XhciRead32 (XhciBar, XHCI_SSPDO_OFS) & 0x7FFF;
SsPe = 0;
}
//
// Process HS (USB 2.0) ports
//
for (PortIndex = 0; PortIndex < MaxHsPorts; PortIndex++) {
PortEnabled = PortConfig[38 + PortIndex];
DisableMask = (UINT32)(1 << PortIndex);
if (PortEnabled == 1) {
//
// User wants port disabled
//
HsPdo &= ~DisableMask;
if (PortIndex < 12) {
PortScOffset = XHCI_PORTSC_OFS + PortIndex * XHCI_PORTSC_STRIDE;
} else {
PortScOffset = (PortIndex - 12) * XHCI_PORTSC_STRIDE + 0x0540;
}
} else {
//
// User wants port enabled
//
HsPdo |= DisableMask;
}
}
//
// Process SS (USB 3.0) ports
//
for (PortIndex = 0; PortIndex < MaxSsPorts; PortIndex++) {
PortEnabled = PortConfig[54 + PortIndex];
DisableMask = (UINT32)(1 << PortIndex);
if (PortEnabled == 1) {
SsPdo &= ~DisableMask;
} else {
SsPdo |= DisableMask;
}
}
//
// Compute SSPE value
//
if (PortConfig[1254] != 0) {
//
// All SS ports explicitly managed
//
if (Sku == 2) {
SsPe = SsPdo & 0x3C0;
} else {
SsPe = 0;
}
} else {
//
// Derive SSPE from SS PDO
//
SsPe = SsPdo;
}
//
// Write back the PDO and SSPE registers
//
DEBUG ((DEBUG_INFO, "Write back Xhci HS PDO value: %x to HS PDO register\n", HsPdo));
XhciWrite32 (XhciBar, XHCI_HSPDO_OFS, HsPdo);
DEBUG ((DEBUG_INFO, "Write back Xhci SS PDO value: %x to SS PDO register\n", SsPdo));
XhciWrite32 (XhciBar, XHCI_SSPDO_OFS, SsPdo);
DEBUG ((DEBUG_INFO, "Write back Xhci SSPE value: %x to SSPE register\n", SsPe));
XhciWrite32 (XhciBar, XHCI_SSPE_OFS, SsPe);
//
// Now physically disable each port that was marked for disable
//
{
UINT32 HsDisableCount;
UINT32 SsDisableCount;
HsDisableCount = 0;
SsDisableCount = 0;
for (PortIndex = 0; PortIndex < MaxHsPorts; PortIndex++) {
if (PortConfig[38 + PortIndex] == 1) {
//
// This HS port needs to be disabled
//
if (PortIndex < 12) {
PortScOffset = XHCI_PORTSC_OFS + PortIndex * XHCI_PORTSC_STRIDE;
} else {
PortScOffset = (PortIndex - 12) * XHCI_PORTSC_STRIDE + 0x0540;
}
XhciDisablePort (XhciBar, PortScOffset, PortIndex, FALSE);
}
}
for (PortIndex = 0; PortIndex < MaxSsPorts; PortIndex++) {
if (PortConfig[54 + PortIndex] == 1) {
//
// This SS port needs to be disabled
//
if (Sku == 2) {
if (PortIndex >= 6) {
PortScOffset = (PortIndex - 6) * XHCI_PORTSC_STRIDE + 0x0540;
} else {
PortScOffset = (PortIndex + 12) * XHCI_PORTSC_STRIDE + 0x0480;
}
} else {
if (PortIndex >= 10) {
PortScOffset = (PortIndex - 10) * XHCI_PORTSC_STRIDE + 0x0580;
} else {
PortScOffset = (PortIndex + 16) * XHCI_PORTSC_STRIDE + 0x0480;
}
}
XhciDisablePort (XhciBar, PortScOffset, PortIndex, TRUE);
}
}
}
gXhciPortDisableDone = TRUE;
DEBUG ((DEBUG_INFO, "USBPerPortDisableCallback - End\n"));
Done:
if (Event != NULL) {
gBS->CloseEvent (Event);
}
return EFI_SUCCESS;
}
// ============================================================================
// Boot Script Support
// ============================================================================
/**
Save a data buffer to the SMM LockBox as a boot script entry.
This function communicates with the SMM LockBox handler via the
SMM Communication protocol. The lockbox entry is identified by
the boot script context GUID.
@param[in] Buffer Pointer to the data to save.
@param[in] BufferSize Size of the data in bytes.
@return EFI_SUCCESS on success, or error code from LockBox protocol.
**/
EFI_STATUS
SaveBootScriptToLockBox (
IN VOID *Buffer,
IN UINTN BufferSize
)
{
EFI_SMM_COMMUNICATION_PROTOCOL *SmmComm;
EFI_SMM_COMMUNICATE_HEADER CommHeader;
UINT64 CommSize;
UINT8 LocalBuffer[EFI_PAGE_SIZE];
UINT8 *BufferPtr;
EFI_STATUS Status;
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib SaveLockBox - Enter\n"));
if ((Buffer == NULL) || (BufferSize == 0)) {
return EFI_INVALID_PARAMETER;
}
SmmComm = (EFI_SMM_COMMUNICATION_PROTOCOL *)gSmmCommunicationProtocol;
if (SmmComm == NULL) {
return EFI_NOT_FOUND;
}
//
// Prepare SMM communication buffer for LockBox save
//
BufferPtr = &LocalBuffer[0];
ZeroMem (BufferPtr, sizeof (LocalBuffer));
//
// Build the LockBox communication buffer:
// [0..15] = Guid (boot script context GUID)
// [16..23] = Command (48 = Save LockBox)
// [24..31] = ReturnStatus (~0UL = no status)
// [32..35] = Signature (1)
// [36..39] = Length (48 bytes header)
// [40..55] = LockBox GUID (buffer GUID)
// [56..63] = Buffer address
// [64..71] = Buffer size
//
CopyMem (BufferPtr, &gBootScriptContextGuid, sizeof (EFI_GUID));
*((UINT64 *)&BufferPtr[16]) = 48; // EFI_SMM_LOCK_BOX_COMMAND_SAVE
*((UINT64 *)&BufferPtr[24]) = (UINT64)-1;
*((UINT32 *)&BufferPtr[32]) = 1; // EFI_SMM_LOCK_BOX_SIGNATURE
*((UINT32 *)&BufferPtr[36]) = 48; // Header size
CopyMem (&BufferPtr[40], Buffer, 16);
*((UINT64 *)&BufferPtr[56]) = (UINT64)Buffer;
*((UINT64 *)&BufferPtr[64]) = BufferSize;
CommSize = 72;
Status = SmmComm->Communicate (SmmComm, &LocalBuffer[0], &CommSize);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "SaveLockBox communicate failed: %r\n", Status));
return Status;
}
Status = *((UINT64 *)&LocalBuffer[24]);
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib SaveLockBox - Exit (%r)\n", Status));
return Status;
}
/**
Set attributes on the boot script LockBox entry.
Currently sets the attribute to 1 (LOCK_BOX_ATTRIBUTE_RESTORE_IN_PLACE).
This must be called after SaveBootScriptToLockBox() to prevent the
boot script from being overwritten in memory during S3 resume.
@return EFI_SUCCESS on success, or error code from LockBox protocol.
**/
EFI_STATUS
SetBootScriptLockBoxAttributes (
VOID
)
{
EFI_SMM_COMMUNICATION_PROTOCOL *SmmComm;
UINT8 LocalBuffer[EFI_PAGE_SIZE];
UINT8 *BufferPtr;
UINT64 CommSize;
EFI_STATUS Status;
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib SetLockBoxAttributes - Enter\n"));
SmmComm = (EFI_SMM_COMMUNICATION_PROTOCOL *)gSmmCommunicationProtocol;
if (SmmComm == NULL) {
return EFI_NOT_FOUND;
}
BufferPtr = &LocalBuffer[0];
ZeroMem (BufferPtr, sizeof (LocalBuffer));
//
// Build LockBox communication buffer for SetAttributes command
//
CopyMem (BufferPtr, &gBootScriptContextGuid, sizeof (EFI_GUID));
*((UINT64 *)&BufferPtr[16]) = 40; // EFI_SMM_LOCK_BOX_COMMAND_SET_ATTRIBUTES
*((UINT64 *)&BufferPtr[24]) = (UINT64)-1;
*((UINT32 *)&BufferPtr[32]) = 4; // Header version
*((UINT32 *)&BufferPtr[36]) = 40; // Header size
CopyMem (&BufferPtr[40], &gBootScriptContextGuid, sizeof (EFI_GUID));
*((UINT64 *)&BufferPtr[56]) = 1; // LOCK_BOX_ATTRIBUTE_RESTORE_IN_PLACE
CommSize = 64;
Status = SmmComm->Communicate (SmmComm, &LocalBuffer[0], &CommSize);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "SetLockBoxAttributes communicate failed: %r\n", Status));
return Status;
}
Status = *((UINT64 *)&LocalBuffer[24]);
DEBUG ((DEBUG_INFO, "SmmLockBoxDxeLib SetLockBoxAttributes - Exit (%r)\n", Status));
return Status;
}
// ============================================================================
// S3 Boot Script Initialization
// ============================================================================
/**
Allocate and prepare memory for the S3 boot script table.
This function is called on first usage of the boot script library
if no pre-allocated table exists.
@param[out] Table Receives the pointer to the boot script table.
@retval EFI_SUCCESS Table was allocated and initialized.
**/
STATIC
EFI_STATUS
InitBootScriptTable (
OUT UINT64 *Table
)
{
EFI_STATUS Status;
UINT64 PcdValue;
PcdValue = 0xFFFFFFFFULL;
//
// Allocate runtime memory page for the boot script table
//
Status = gBS->AllocatePages (AllocateAnyPages, EfiRuntimeServicesData, 1, &PcdValue);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "Failed to allocate boot script table: %r\n", Status));
return Status;
}
gS3BootScriptInitialized = TRUE;
//
// Store the PCD value for the boot script table address
//
PcdSet64S (PcdBootScriptTableAddress, PcdValue);
//
// Signal that the boot script table is ready
//
gBootScriptTable = PcdValue;
gBootScriptTable2 = PcdValue;
//
// Register notification for DxeSmmReadyToLock
//
EfiCreateEventReadyToBoot (
TPL_CALLBACK,
BootScriptReadyToLockNotify,
NULL,
&gEventDxeSmmReadyToLock
);
return EFI_SUCCESS;
}
/**
Boot Script Ready-to-Lock notification callback.
Invoked when the DxeSmmReadyToLock group signal is fired.
Saves the current boot script entries to the SMM LockBox,
then registers additional callbacks for boot script close.
@param[in] Event Event that triggered this notification.
@param[in] Context Not used.
**/
VOID
EFIAPI
BootScriptReadyToLockNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
if (!gSmmReadyToLockOccurred) {
gSmmReadyToLockOccurred = TRUE;
DEBUG ((DEBUG_INFO, "%a() in SBDxe module\n", __FUNCTION__));
if (gS3BootScriptInitialized) {
//
// Close event notification
//
gBS->CloseEvent (gEventDxeSmmReadyToLock);
//
// Lock the boot script table
//
gBootScriptLocked = 1;
//
// Save the current boot script data to LockBox
//
SaveBootScriptToLockBox (
(VOID *)&gBootScriptContextGuid,
gBootScriptTable,
(UINT32)(*(UINT32 *)(UINTN)gBootScriptTable + 3)
);
//
// Set LockBox attributes
//
SetBootScriptLockBoxAttributes ();
//
// Register for subsequent boot script close events
//
if (gSmmCommunicationProtocol != NULL) {
//
// Register close event callbacks for S3 resume paths
//
((EFI_SMM_COMMUNICATION_PROTOCOL *)gSmmCommunicationProtocol)->RegisterProtocolNotify (
&gBootScriptExecutorGuid,
BootScriptCloseNotify,
&gEventBootScriptClose
);
((EFI_SMM_COMMUNICATION_PROTOCOL *)gSmmCommunicationProtocol)->RegisterProtocolNotify (
&gBootScriptContextGuid,
BootScriptCloseNotify,
&gEventBootScriptContext
);
}
//
// Additional cleanup callbacks
//
((EFI_SMM_COMMUNICATION_PROTOCOL *)gSmmCommunicationProtocol)->RegisterProtocolNotify (
&gSmmCommRegionGuid,
BootScriptContextCloseNotify,
&gEventBootScriptContextClose
);
}
}
}
/**
Boot script close notification callback.
Handles the case where the boot script context is being updated
after the initial save. This callback is registered to run at
EndOfDxe to finalize the boot script data.
@param[in] Event Event that triggered this notification.
@param[in] Context Not used.
**/
VOID
EFIAPI
BootScriptCloseNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
if (gBootScriptTable != gBootScriptTable2) {
//
// Copy the new table to the LockBox
//
CloseBootScriptTable ();
if (*(UINT64 *)(UINTN)gBootScriptTable2 == 0) {
CopyMem (
(VOID *)(UINTN)gBootScriptTable2,
(VOID *)(UINTN)gBootScriptTable,
32
);
*(UINT8 *)(UINTN)(gBootScriptTable2 + 14) = 1;
}
gBootScriptTable = gBootScriptTable2;
}
}
/**
Close and save the current boot script table to the LockBox.
Calculates the final size of the boot script table entry and
invokes SaveBootScriptToLockBox, then calls
SetBootScriptLockBoxAttributes for the new copy.
@return EFI_SUCCESS on completion.
**/
UINT64
CloseBootScriptTable (
VOID
)
{
UINT32 Size;
if (gBootScriptTable != gBootScriptTable2) {
CloseBootScriptTableWorker (0, 0);
if (*(UINT64 *)(UINTN)gBootScriptTable2 == 0) {
CopyMem (
(VOID *)(UINTN)gBootScriptTable2,
(VOID *)(UINTN)gBootScriptTable,
32
);
*(UINT8 *)(UINTN)(gBootScriptTable2 + 14) = 1;
}
gBootScriptTable = gBootScriptTable2;
}
return 0;
}
/**
Boot script context close notification.
Registered on the PiSmmCommunicationRegionTable protocol.
Saves the boot script context and updates the boot script
table pointer.
@param[in] Event Event that triggered this notification.
@param[in] Context Not used.
**/
VOID
EFIAPI
BootScriptContextCloseNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_STATUS Status;
UINT64 BootScriptPcd;
if (gSmmCommunicationProtocol != NULL && gBootScriptLocked) {
Status = ((EFI_SMM_COMMUNICATION_PROTOCOL *)gSmmCommunicationProtocol)->Communicate (
gSmmCommunicationProtocol,
(VOID *)(UINTN)gBootScriptTable,
&BootScriptPcd
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "BootScript context close communicate failed: %r\n", Status));
}
//
// Restore the PCD to the locked value
//
PcdSet64S (PcdBootScriptTableAddress, gBootScriptTable);
gBootScriptLocked = 0;
}
}
// ============================================================================
// PCIe Segment Bus Table Setup
// ============================================================================
/**
Initialize and install the PCIe Segment/Bus routing table.
Reads the number of PCIe segments from the PCD and allocates
the bus-to-bus routing table. This allows the PCI enumerator
to correctly assign bus numbers across multiple PCIe root ports.
**/
STATIC
VOID
SbdInstallPcieSegBusTable (
VOID
)
{
UINTN NumSegments;
UINTN TableSize;
VOID *PcdProtocol;
VOID *PcieSegBusTable;
//
// Get the number of PCIe segments from PCD
//
PcdProtocol = GetPcdProtocolPtr ();
NumSegments = PcdGetSize (PcdPcieSegBusConfig);
if (NumSegments > sizeof (PCIE_SEG_BUS_TABLE)) {
DEBUG ((DEBUG_ERROR, "PCIE_SEG_BUS_TABLE too small!\n"));
ASSERT (FALSE);
}
//
// Allocate and initialize the bus table
//
PcieSegBusTable = AllocateZeroPool (NumSegments);
//
// Call the PCH-specific installation routine
//
PchInstallPcieSegBusTable (
PcieSegBusTable,
PciExpressRead32 (
PCI_EXPRESS_LIB_ADDRESS (0, 31, 0, 0x54) // PCIe config base
),
NumSegments
);
}
// ============================================================================
// PCI Express Base Address Initialization
// ============================================================================
/**
Locate and cache the PCD protocol pointer.
@return EFI_SUCCESS if found, EFI_NOT_FOUND otherwise.
**/
STATIC
EFI_STATUS
GetPcdProtocol (
VOID
)
{
EFI_STATUS Status;
if (gPcdProtocol == NULL) {
Status = gBS->LocateProtocol (
&gEfiPcdProtocolGuid,
NULL,
&gPcdProtocol
);
if (EFI_ERROR (Status)) {
gPcdProtocol = NULL;
}
gPciExpressBaseAddr = PcdGet64 (PcdPciExpressBaseAddress);
}
return (gPcdProtocol != NULL) ? EFI_SUCCESS : EFI_NOT_FOUND;
}
// ============================================================================
// DXE Service Table and HOB List Discovery
// ============================================================================
/**
Locate the DXE Services Table (gDS) and HOB list via the configuration table.
**/
STATIC
VOID
InitializeDxeServices (
VOID
)
{
EFI_STATUS Status;
VOID *HobList;
Status = EfiGetSystemConfigurationTable (&gEfiDxeServicesTableGuid, (VOID **)&gDS);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "Failed to locate DXE Services Table\n"));
}
Status = EfiGetSystemConfigurationTable (&gEfiHobListGuid, &HobList);
if (!EFI_ERROR (Status)) {
gHobList = HobList;
}
}
// ============================================================================
// SMM Communication Protocol Discovery
// ============================================================================
/**
Locate the SMM Communication protocol (DXE phase).
This is needed for LockBox operations and PCIe base access.
**/
STATIC
EFI_STATUS
LocateSmmCommunicationProtocol (
VOID
)
{
EFI_STATUS Status;
if (gSmmCommunicationProtocol == NULL) {
Status = gBS->LocateProtocol (
&gEfiSmmCommunicationProtocolGuid,
NULL,
&gSmmCommunicationProtocol
);
if (EFI_ERROR (Status)) {
return Status;
}
}
return EFI_SUCCESS;
}
// ============================================================================
// Library Constructor-Style Init
// ============================================================================
/**
Initialize the library globals (gImageHandle, gST, gBS, gRT, gDS) from
the EFI System Table.
This is called early in SBDxeInitialize before any protocol lookups.
**/
STATIC
VOID
InitializeLibGlobals (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// Cache UEFI core services
//
gImageHandle = ImageHandle;
gST = SystemTable;
gBS = SystemTable->BootServices;
gRT = SystemTable->RuntimeServices;
//
// Initialize DXE services table and HOB list from configuration table
//
InitializeDxeServices ();
}
// ============================================================================
// Main Driver Initialization
// ============================================================================
/**
Main driver initialization for the SB DXE driver.
Called from _ModuleEntryPoint after library dependencies are resolved.
Performs:
- Caching of global service pointers
- DXE Services Table and HOB list discovery
- SMM PCI base protocol retrieval
- PCH stepping detection and PCIe segment bus table setup
- I/O port timing delays for chipset workarounds
- XHCI USB port disable event registration
- S3 boot script initialization
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@return EFI_SUCCESS on success, or an error code on failure.
**/
EFI_STATUS
SBDxeInitialize (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UINT16 AcpiBase;
UINTN SegCount;
UINT8 PcieSegBase;
BOOLEAN IsPchLp;
UINT32 Stepping;
UINT32 PdoValue;
UINT32 TimeoutValue;
//
// 1. Initialize library globals
//
InitializeLibGlobals (ImageHandle, SystemTable);
//
// 2. Register Driver Binding Protocol (simplified: install protocols)
// This driver uses the simpler callback registration model.
//
//
// 3. Locate SMM PCI base protocol (PciUsra) for MM PCI config access
//
Status = gBS->LocateProtocol (
&gEfiSmmCommunicationProtocolGuid,
NULL,
&gPciUsra
);
if (EFI_ERROR (Status)) {
//
// SMM protocol may not be available yet; we try again in callbacks.
//
gPciUsra = NULL;
}
//
// 4. Initialize PCH stepping detection
//
Stepping = GetPchStepping ();
PcieSegBase = PciExpressRead32 (
PCI_EXPRESS_LIB_ADDRESS (0, 31, 0, 0x54)
);
SegCount = PcdGetSize (PcdPcieSegBusConfig);
PchInstallPcieSegBusTable (&gPcieSegBusTable, PcieSegBase, SegCount);
//
// 5. PCI express memory-mapped I/O base setup
// Also used to enable write-combining for PCIe MMIO range
//
{
UINT8 *PciMmCfg;
PciMmCfg = (UINT8 *)(UINTN)PcdGet64 (PcdPciExpressBaseAddress);
if (*PciMmCfg >= 0) {
//
// Enable PCIe ECAM (enhanced configuration access mechanism)
//
IoWrite16 (PcdGet16 (PcdPciExpressBaseAddress), 0x500);
PciMmCfg = (UINT8 *)(UINTN)PcdGet16 (PcdPciExpressBaseAddress + 4);
*PciMmCfg |= 0x80;
}
}
//
// 6. Chipset timing workaround: read ACPI base, compute delay
//
IsPchLp = (GetPchSku () == 2);
AcpiBase = IoRead16 (PcdGet16 (PcdAcpiBaseAddress));
TimeoutValue = IoRead32 (PcdGet16 (PcdAcpiBaseAddress) + 4) & 0x00FFFFFF;
IoRead32 (PcdGet16 (PcdAcpiBaseAddress));
//
// Busy-wait for chipset stabilization
//
while (((TimeoutValue + 357 - IoRead32 (PcdGet16 (PcdAcpiBaseAddress))) & 0x800000) == 0) {
IoRead32 (PcdGet16 (PcdAcpiBaseAddress));
}
//
// 7. Enable/disable SMI based on PCH LP vs H
//
if (IsPchLp) {
IoWrite8 (RTC_INDEX_PORT, 0x00);
} else {
IoRead8 (RTC_DATA_PORT);
}
//
// 8. Initialize S3 boot script support
//
Status = InitBootScriptTable (&gBootScriptTable);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "Failed to initialize boot script table: %r\n", Status));
return Status;
}
//
// 9. Register USB per-port disable callback via event
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
UsbPerPortDisableCallback,
NULL,
&gXhciPortDisableEvent
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_WARN, "Failed to create XHCI port disable event: %r\n", Status));
}
return EFI_SUCCESS;
}
// ============================================================================
// Module Entry Point
// ============================================================================
/**
Entry point for the SBDXE driver. Called by the DXE dispatcher
after all dependencies are resolved.
Calls SBDxeInitialize() to perform all initialization. If the
initialization succeeds, registers the driver unload handler and
installs the driver binding protocol.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@return EFI_SUCCESS The driver was initialized successfully.
@return EFI_UNSUPPORTED The driver initialization failed.
@return EFI_INVALID_PARAMETER SystemTable is NULL.
**/
EFI_STATUS
EFIAPI
SBDxeEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
//
// Invoke the main initialization routine
//
Status = SBDxeInitialize (ImageHandle, SystemTable);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "SBDxe initialization failed: %r\n", Status));
}
return Status;
}
// ============================================================================
// Assertion / Debug Helpers
// ============================================================================
/**
Internal assertion helper. Logs an assertion failure to the debug console.
In DEBUG builds this will trigger a breakpoint. In RELEASE builds
the assertion is compiled out.
@param[in] FileName Source file name of the assertion.
@param[in] LineNumber Line number of the assertion.
@param[in] Description Null-terminated ASCII description string.
**/
#pragma optimize("", off)
VOID
SbdAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Description
)
{
DEBUG ((DEBUG_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
DEBUG ((
DEBUG_ERROR,
"%a(%d): %a\n",
FileName,
LineNumber,
Description
));
CpuDeadLoop ();
}
#pragma optimize("", on)
/**
Debug print helper. Wrapper around the UEFI DebugLib DEBUG macro.
@param[in] ErrorLevel Debug error level (DEBUG_INFO, DEBUG_ERROR, etc.).
@param[in] Format Print format string.
@param[in] ... Variable arguments for the format string.
**/
VOID
SbdDebugPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VA_LIST Marker;
VA_START (Marker, Format);
DebugVPrint (ErrorLevel, Format, Marker);
VA_END (Marker);
}
// ============================================================================
// PCI Express MMIO Read/Write Helpers (used for chipset access)
// ============================================================================
/**
Read a 32-bit value from PCI Express config space.
@param[in] Address PCI Express address (format per PCI_EXPRESS_LIB_ADDRESS).
@return 32-bit configuration value.
**/
UINT32
PciExpressRead32 (
IN UINT64 Address
)
{
ASSERT (((Address) & ~0xFFFFFFF) == 0);
return MmioRead32 ((UINTN)(gPciExpressBaseAddr + Address));
}
/**
Write a 32-bit value to PCI Express config space.
@param[in] Address PCI Express address (format per PCI_EXPRESS_LIB_ADDRESS).
@param[in] Data 32-bit value to write.
**/
VOID
PciExpressWrite32 (
IN UINT64 Address,
IN UINT32 Data
)
{
ASSERT (((Address) & ~0xFFFFFFF) == 0);
MmioWrite32 ((UINTN)(gPciExpressBaseAddr + Address), Data);
}
// ============================================================================
// IO Port Helpers
// ============================================================================
/**
Read a 16-bit value from an I/O port.
@param[in] Port I/O port address. Must be 16-bit aligned.
@return 16-bit value read from the port.
**/
UINT16
IoRead16 (
IN UINT16 Port
)
{
ASSERT ((Port & 1) == 0);
return __inword (Port);
}
/**
Write a 16-bit value to an I/O port.
@param[in] Port I/O port address. Must be 16-bit aligned.
@param[in] Value 16-bit value to write.
**/
VOID
IoWrite16 (
IN UINT16 Port,
IN UINT16 Value
)
{
ASSERT ((Port & 1) == 0);
__outword (Port, Value);
}
/**
Read a 32-bit value from an I/O port.
@param[in] Port I/O port address. Must be 32-bit aligned.
@return 32-bit value read from the port.
**/
UINT32
IoRead32 (
IN UINT16 Port
)
{
ASSERT ((Port & 3) == 0);
__indword (Port);
}
/**
Write a 32-bit value to an I/O port.
@param[in] Port I/O port address. Must be 32-bit aligned.
@param[in] Value 32-bit value to write.
**/
VOID
IoWrite32 (
IN UINT16 Port,
IN UINT32 Value
)
{
ASSERT ((Port & 3) == 0);
__outdword (Port, Value);
}
/**
Read an 8-bit value from an I/O port.
@param[in] Port I/O port address.
@return 8-bit value read from the port.
**/
UINT8
IoRead8 (
IN UINT16 Port
)
{
return __inbyte (Port);
}
/**
Write an 8-bit value to an I/O port.
@param[in] Port I/O port address.
@param[in] Value 8-bit value to write.
**/
VOID
IoWrite8 (
IN UINT16 Port,
IN UINT8 Value
)
{
__outbyte (Port, Value);
}
/**
Copy a block of memory. Handles overlapping buffers correctly.
@param[out] Dest Pointer to the destination buffer.
@param[in] Src Pointer to the source buffer.
@param[in] Count Number of bytes to copy.
@return Pointer to the destination buffer (Dest).
**/
VOID *
MemCopy (
OUT VOID *Dest,
IN CONST VOID *Src,
IN UINTN Count
)
{
//
// Use aligned copy for speed when possible.
// Fall back to byte copy for tail bytes.
//
if ((Src < Dest) && ((UINT8 *)Src + Count - 1 >= (UINT8 *)Dest)) {
//
// Overlapping: copy from end to start
//
CopyMemBackwards (Dest, Src, Count);
} else {
//
// Non-overlapping or forward overlap: copy from start to end
//
CopyMemForward (Dest, Src, Count);
}
return Dest;
}
/**
Zero a block of memory.
@param[out] Buf Pointer to the buffer to zero.
@param[in] Len Number of bytes to zero.
**/
VOID
MemZero (
OUT VOID *Buf,
IN UINTN Len
)
{
SetMem (Buf, Len, 0);
}