/** @file
UsbOcUpdateDxeCLX64L.c -- USB Overcurrent Mapping DXE Driver for CLX64L
Determines the PCH stepping and board revision for the Cooper Lake
(CLX64L) platform, then publishes the appropriate USB overcurrent
pin mapping via the UBA (Unified Board Architecture) protocol.
The driver's main flow:
1. Standard UEFI driver entry point (process library constructors,
initialize global services).
2. Locate the UBA USB OC Update protocol.
3. Call the protocol with a callback that selects the OC table
based on PCH stepping.
4. The OC table selection (UsbOcUpdateGetPchStepping) reads the
PCH VID/DID register via MMIO PCI config space, decodes the
stepping, and returns the appropriate pin count.
Build path: PurleyRpPkg/Uba/UbaMain/Dxe/TypeCLX64L/UsbOcUpdateDxe/
Copyright (c) Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/DxeServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/DxeHobLib.h>
#include <Library/DxePcdLib.h>
#include <Library/DxeMmPciBaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseLib.h>
#include <Library/IoLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PcdLib.h>
#include <Protocol/PciIo.h>
#include "UsbOcUpdateDxeCLX64L.h"
//
// Global Variables
//
EFI_HANDLE gImageHandle = NULL;
EFI_SYSTEM_TABLE *gSystemTable = NULL;
EFI_BOOT_SERVICES *gBootServices = NULL;
EFI_RUNTIME_SERVICES *gRuntimeServices = NULL;
VOID *gDs = NULL;
UINTN gPchStepping = PCH_STEPPING_UNKNOWN; ///< Cached PCH stepping value
VOID *gMpciUsra = NULL; ///< MM PCI User Space Register Area
VOID *gMpcd = NULL; ///< PCD protocol pointer
VOID *gHobList = NULL; ///< HOB list pointer
//
// USB OC Protocol GUIDs
//
EFI_GUID gEfiUsbOcUpdateProtocolGuid = USB_OC_UPDATE_PROTOCOL_GUID;
//
// Forward function declarations
//
EFI_STATUS
UsbOcUpdateCallback (
IN VOID *This,
OUT VOID *OcTableA,
OUT VOID *OcTableB,
IN UINTN TableLength
);
UINTN
UsbOcUpdateGetPchStepping (
VOID
);
/**
Retrieves the MM PCI user-space register area base address.
Reads the PCH's MM PCI configuration register (B:D:F 0:0:0,
offset 0x48h) to obtain the base address for further PCI config
space access.
@return A pointer to the MM PCI User Space Register Area, or
NULL if the protocol could not be located.
**/
VOID *
GetMpciUsra (
VOID
)
{
UINT64 UsraAddress;
EFI_STATUS Status;
//
// Check if already retrieved
//
if (gMpciUsra != NULL) {
return gMpciUsra;
}
//
// Locate the MM PCI Base protocol
//
Status = gBootServices->LocateProtocol (
&gEfiMmPciBaseProtocolGuid,
NULL,
&gMpciUsra
);
if (EFI_ERROR (Status)) {
gMpciUsra = NULL;
}
return gMpciUsra;
}
/**
Reads a 16-bit value from the PCH's MM PCI configuration space.
@param[in] Address The MM PCI register offset to read.
@return The 16-bit value read from the specified register.
**/
UINT16
MmPciRead16 (
IN UINT16 Address
)
{
ASSERT ((Address & 1) == 0);
return *(volatile UINT16 *)((UINTN)gMpciUsra + Address);
}
/**
Allocates a buffer from EfiBootServicesData pool.
@param[in] Size The number of bytes to allocate.
@return A pointer to the allocated buffer, or NULL on failure.
**/
VOID *
AllocatePool (
IN UINTN Size
)
{
return gBootServices->AllocatePool (EfiBootServicesData, Size, &Size);
}
/**
Reads a 64-bit unaligned value from a buffer.
@param[in] Buffer Pointer to the buffer to read from.
@return The 64-bit value at the given address.
**/
UINT64
ReadUnaligned64 (
IN CONST VOID *Buffer
)
{
ASSERT (Buffer != NULL);
return *(UINT64 *)Buffer;
}
/**
Compares two EFI_GUIDs.
@param[in] Guid1 Pointer to the first GUID.
@param[in] Guid2 Pointer to the second GUID.
@return TRUE if the GUIDs are identical, FALSE otherwise.
**/
BOOLEAN
EFIAPI
CompareGuid (
IN CONST EFI_GUID *Guid1,
IN CONST EFI_GUID *Guid2
)
{
UINT64 Guid1Low;
UINT64 Guid1High;
UINT64 Guid2Low;
UINT64 Guid2High;
Guid1Low = ReadUnaligned64 (Guid1);
Guid2Low = ReadUnaligned64 (Guid2);
Guid1High = ReadUnaligned64 ((CONST VOID *)((CONST UINT8 *)Guid1 + 8));
Guid2High = ReadUnaligned64 ((CONST VOID *)((CONST UINT8 *)Guid2 + 8));
return (BOOLEAN)(Guid1Low == Guid2Low && Guid1High == Guid2High);
}
/**
Locates a configuration table entry by GUID in the system table.
@param[in] TableGuid Pointer to the GUID to search for.
@param[out] Table Pointer to receive the located table pointer.
@return EFI_SUCCESS The table was found.
@return EFI_NOT_FOUND No matching table was found.
@return EFI_INVALID_PARAMETER TableGuid or Table is NULL.
**/
EFI_STATUS
EFIAPI
EfiGetSystemConfigurationTable (
IN EFI_GUID *TableGuid,
OUT VOID **Table
)
{
EFI_CONFIGURATION_TABLE *ConfigTable;
UINTN Index;
ASSERT (TableGuid != NULL);
ASSERT (Table != NULL);
*Table = NULL;
ConfigTable = (EFI_CONFIGURATION_TABLE *)gSystemTable->ConfigurationTable;
for (Index = 0; Index < gSystemTable->NumberOfTableEntries; Index++) {
if (CompareGuid (TableGuid, &ConfigTable->Guid)) {
*Table = ConfigTable->Table;
return EFI_SUCCESS;
}
ConfigTable++;
}
return EFI_NOT_FOUND;
}
/**
Retrieves the PCH stepping identifier from the LPC device's VID/DID
register.
Uses MMIO PCI config access to read the PCH's device ID from
B:D:F 0:31:0, offset 0x2 (DID register). The device ID encodes
the PCH stepping, which is decoded as follows:
DID range | Stepping
------------------+---------
0xA13F | LPC Device (no stepping encoded) -> decode via SUBVID
0xA14C | LPC Device (alternate DID)
0xA150-0xA151 | Future stepping range
(A14C+A14F based) | A0/A1/B0/B1 based on SUBVID
Other specific | D0-D3, E0-E1 based on PCH revision
@return A PCH_STEPPING_* value identifying the current stepping.
**/
UINTN
UsbOcUpdateGetPchStepping (
VOID
)
{
EFI_STATUS Status;
VOID *Usra;
UINT16 DidValue;
UINT8 Revision;
UINT16 SubVid;
//
// Use cached value if already computed
//
if (gPchStepping != PCH_STEPPING_UNKNOWN) {
return gPchStepping;
}
//
// Get the MM PCI User Space Register Area
//
Usra = GetMpciUsra ();
//
// Read the LPC device DID (Device ID) register at offset 0x2
//
DidValue = MmPciRead16 ((UINTN)Usra + 2);
//
// Decode the PCH stepping from the DID
//
if ((DidValue == 0xA13F) ||
(DidValue >= 0xA150 && DidValue <= 0xA151))
{
//
// Mainstream stepping set (0xA13F base):
// Read SUBVID from offset 0x2C to distinguish A0/A1/B0/B1
//
SubVid = 0;
Revision = *(UINT8 *)((UINTN)Usra + Usra + 8); ///< Read PCH revision at +8
//
// Read SUBVID at MM PCI offset 0x2C
//
SubVid = MmPciRead16 ((UINTN)Usra + 0x2C);
switch (Revision) {
case 0x00:
//
// DMI Virtual Channel table entry 0 => A0 stepping
//
gPchStepping = PCH_STEPPING_A0;
break;
case 0x10:
//
// DMI Virtual Channel table entry 1 => A1 stepping
//
gPchStepping = PCH_STEPPING_A1;
break;
case 0x20:
//
// DMI Virtual Channel table entry 2 => B0 stepping
//
gPchStepping = PCH_STEPPING_B0;
break;
case 0x30:
case 0x31:
//
// DMI Virtual Channel table entry 3 => B1 stepping
//
gPchStepping = PCH_STEPPING_B1;
break;
default:
//
// Fall through to extended stepping decode
//
break;
}
//
// If we resolved to a known stepping via SUBVID, cache and return
//
if (gPchStepping != PCH_STEPPING_UNKNOWN) {
return gPchStepping;
}
//
// Extended stepping range for B2/C0/C1:
// DID plus 0x62A0 -> 0x62A8 range indicates B2/C stepping
//
if ((DidValue + 0x62A0) <= 8) {
UINTN ExtendedStep;
ExtendedStep = 0;
//
// B2 = 35, B3 = 36, C0 = 37, C1 = 38
//
if (Revision == 0x10) {
gPchStepping = PCH_STEPPING_B2;
} else if (Revision == 0x11) {
gPchStepping = PCH_STEPPING_B3;
} else if (Revision == 0x20) {
gPchStepping = PCH_STEPPING_C0;
} else if (Revision == 0x21) {
gPchStepping = PCH_STEPPING_C1;
} else {
gPchStepping = PCH_STEPPING_UNKNOWN;
}
} else {
gPchStepping = PCH_STEPPING_UNKNOWN;
}
}
else if (DidValue == 0xA14C)
{
//
// Alternate DID (0xA14C) — check revision for stepping decode
//
Revision = *(UINT8 *)((UINTN)Usra + 8);
if (*(UINT8 *)((UINTN)gMpcd + 252) == 4) {
//
// PCD indicates Server platform: stepping = D0
// No further decode needed
//
gPchStepping = PCH_STEPPING_D0;
} else {
//
// Not Server platform: stepping depends on revision
//
switch (Revision) {
case 2:
gPchStepping = PCH_STEPPING_D1;
break;
case 3:
gPchStepping = PCH_STEPPING_D2;
break;
case 4:
gPchStepping = PCH_STEPPING_D3;
break;
case 8:
gPchStepping = PCH_STEPPING_E0;
break;
case 9:
gPchStepping = PCH_STEPPING_E1;
break;
default:
DEBUG ((
DEBUG_ERROR,
"Unsupported PCH Stepping. Supporting PCH stepping starting from %s and above\n",
"LbgA0"
));
ASSERT (FALSE);
gPchStepping = PCH_STEPPING_UNKNOWN;
break;
}
}
}
else
{
//
// Unknown DID — stepping not recognized
//
gPchStepping = PCH_STEPPING_UNKNOWN;
}
return gPchStepping;
}
/**
USB OC callback — selects the appropriate OC table based on
PCH stepping.
This function is called by the UBA infrastructure. It determines
the correct number of USB OC pins for the current PCH stepping
and returns the appropriate OC mapping table.
@param[in] This Pointer to the USB OC Update protocol.
@param[out] OcTableA Pointer to receive OC table A.
@param[out] OcTableB Pointer to receive OC table B.
@param[in] TableLength Length of table entries.
@return EFI_SUCCESS The OC tables were populated.
**/
EFI_STATUS
UsbOcUpdateCallback (
IN VOID *This,
OUT VOID *OcTableA,
OUT VOID *OcTableB,
IN UINTN TableLength
)
{
UINTN Stepping;
UINTN OcPinCount;
Stepping = UsbOcUpdateGetPchStepping ();
//
// Choose OC table based on stepping
// Stepping >= 48 (D0 and above) uses 48-pin OC mapping
// All others use default 54-pin mapping
//
if (Stepping >= PCH_STEPPING_D0) {
OcPinCount = USB_OC_PIN_COUNT_48;
} else {
OcPinCount = USB_OC_PIN_COUNT_54;
}
//
// Populate output structures
// The actual OC table data is provided by the UBA base protocol
// (not defined in this driver) and filled in via the protocol
// dispatch layer.
//
*OcTableA = NULL;
*OcTableB = NULL;
return EFI_SUCCESS;
}
/**
Driver entry point.
Initializes UEFI boot/runtime services, locates the UBA USB OC
Update protocol, and registers the OC mapping callback.
@param[in] ImageHandle The firmware-allocated handle for this image.
@param[in] SystemTable Pointer to the UEFI system table.
@return EFI_SUCCESS The driver initialized successfully.
@return Others An error occurred during initialization.
**/
EFI_STATUS
EFIAPI
ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
USB_OC_UPDATE_PROTOCOL *UsbOcProtocol;
//
// Initialize library services
//
ProcessLibraryConstructorList (ImageHandle, SystemTable);
//
// Log the driver entry
//
DEBUG ((DEBUG_INFO, "UBA:UsbOcUpdate-TypeClx64L\n"));
//
// Locate the UBA USB OC Update protocol
//
Status = gBootServices->LocateProtocol (
&gEfiUsbOcUpdateProtocolGuid,
NULL,
(VOID **)&UsbOcProtocol
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Register the USB OC mapping callback
//
Status = UsbOcProtocol->GetUsbOcMapping (
UsbOcProtocol,
NULL,
NULL,
16
);
return Status;
}