/** @file
OpromUpdateDxeNeonCityEPECB - Option ROM Update DXE Driver for NeonCity EP EC B
This DXE driver implements the UBA (Universal BIOS Adapter) OpromUpdate
protocol for the NeonCity EP EC B platform. It provides:
- PCIe slot number validation against platform-specific slot ranges using the
DXE Services Protocol to read PCIe Slot Capabilities registers.
- Platform-specific PCIe slot configuration data tables (default, platform,
and slot number mappings).
- PCIe slot number assignment (fixed to slot 2 for this platform).
- UBA backchannel debug logging with platform type detection via CMOS/RTC.
The driver entry point locates the UBA OpromUpdate config protocol and
registers its platform-specific configuration data and callbacks.
Copyright (c) 2024, Inspur Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "OpromUpdateDxeNeonCityEPECB.h"
//
// Global UEFI table pointers (initialized at entry point)
//
EFI_HANDLE gImageHandle = NULL;
EFI_SYSTEM_TABLE *gSystemTable = NULL;
EFI_BOOT_SERVICES *gBootServices = NULL;
EFI_RUNTIME_SERVICES *gRuntimeServices = NULL;
//
// Cached UBA protocol interface pointer (for debug print and assert)
//
STATIC VOID *mUbaProtocol = NULL;
//
// Cached HOB list pointer
//
STATIC VOID *mHobList = NULL;
//
// GUID definitions (located in .data section at runtime)
//
STATIC CONST EFI_GUID mDxeServicesProtocolGuid =
{ 0x2F707EBB, 0x4A1A, 0x11D4, { 0x9A, 0x38, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } };
STATIC CONST EFI_GUID mUbaProtocolGuid =
{ 0x36232936, 0x0E76, 0x31C8, { 0xA1, 0x3A, 0x3A, 0xF2, 0xFC, 0x1C, 0x39, 0x32 } };
STATIC CONST EFI_GUID mUbaConfigProtocolGuid =
{ 0xE03E0D46, 0x5263, 0x4845, { 0xB0, 0xA4, 0x58, 0xD5, 0x7B, 0x31, 0x77, 0xE2 } };
STATIC CONST EFI_GUID mHobListGuid =
{ 0x7739F24C, 0x93D7, 0x11D4, { 0x9A, 0x3A, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } };
//
// OPRM (Option ROM Policy) Table
//
// 8 entries, each spanning 3 bytes across a 4-byte stride.
// The table encodes the PCI bus:device:function range for each of the 8 slots.
// Entries are at offsets 0xEF1, 0xEF5, 0xEF9, 0xEFD, 0xF01, 0xF05, 0xF09, 0xF0D.
// At runtime the data is populated by the platform initialization code.
//
// Byte layout per entry (bytes v4-1, v4, v4+1):
// byte[0] = Bus number (or upper byte of BDF encoding)
// byte[1] = Device number
// byte[2] = Function number (or lower byte of BDF encoding)
//
// Combined key: ((Bus << 8) | (Device << 16) | (Function << 24)) & 0xFFFFFF00
//
STATIC UINT8 mOprmTable[OPRM_TABLE_ENTRY_COUNT * OPRM_TABLE_STRIDE];
//
// Default slot configuration table (all 0xFFFF = empty)
//
STATIC UINT8 mDefaultSlotConfig[64] = { 0 };
//
// NeonCity EP EC B slot configuration table
// Format: array of 6 { DeviceId, SubVendorId, Revision, ... } entries
//
#pragma pack(1)
typedef struct {
UINT32 SlotInfo;
UINT32 VendorDeviceId;
UINT16 SubSystemId;
UINT8 Revision;
UINT8 ProgInterface;
UINT8 SubClass;
UINT8 BaseClass;
UINT8 Reserved0[4];
UINT8 EepromOffset;
UINT8 Reserved1[3];
} NEONCITY_SLOT_CONFIG_ENTRY;
typedef struct {
UINT8 EntryCount;
UINT8 Reserved[3];
NEONCITY_SLOT_CONFIG_ENTRY Entries[6];
} NEONCITY_SLOT_CONFIG_TABLE;
#pragma pack()
STATIC CONST NEONCITY_SLOT_CONFIG_ENTRY mNeonCitySlotConfig[6] = {
// Placeholder entries matching the .rdata structure at 0xD40
{ 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0} },
{ 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0} },
{ 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0} },
{ 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0} },
{ 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0} },
{ 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0} },
};
//
// PciBdfSlot number table (10 entries for NeonCity EP EC B)
//
#pragma pack(1)
typedef struct {
UINT16 SlotFlags; // Flags/attributes for this slot
UINT16 DeviceId; // PCI Device ID
UINT16 VendorId; // PCI Vendor ID
UINT32 Reserved;
UINT32 SlotCapabilities; // PCIe Slot Capabilities register value
} PCIE_BDF_SLOT_ENTRY;
#pragma pack()
STATIC CONST PCIE_BDF_SLOT_ENTRY mPcieBdfSlotTable[10] = {
// Data at 0xE40 - platform-specific PCIe BDF slot mappings
// These entries define the PCIe slot numbering for the platform
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
};
/**
Safely reads a UINT64 from memory, asserting if the pointer is NULL.
@param[in] Buffer Pointer to the UINT64 value to read.
@return The UINT64 value at the specified address.
**/
UINT64
ReadUnaligned64 (
IN CONST UINT64 *Buffer
)
{
if (Buffer == NULL) {
UbaAssert (
"e:\\hs\\MdePkg\\Library\\BaseLib\\Unaligned.c",
0xC0,
"Buffer != ((void *) 0)"
);
}
return *Buffer;
}
/**
Compares two GUIDs by comparing their first and last QWORD components.
This avoids pulling in the full CompareGuid library routine.
@param[in] GuidPtr Pointer to the reference GUID.
@param[in] HobEntry Pointer to the GUID in the HOB entry.
@retval TRUE Both QWORD components match.
@retval FALSE The GUIDs do not match.
**/
BOOLEAN
CompareGuidQword (
IN EFI_GUID *GuidPtr,
IN VOID *HobEntry
)
{
UINT64 GuidFirst;
UINT64 GuidLast;
UINT64 EntryFirst;
UINT64 EntryLast;
GuidFirst = ReadUnaligned64 ((UINT64 *)GuidPtr);
GuidLast = ReadUnaligned64 ((UINT64 *)((UINTN)GuidPtr + sizeof (UINT64)));
EntryFirst = ReadUnaligned64 ((UINT64 *)HobEntry);
EntryLast = ReadUnaligned64 ((UINT64 *)((UINTN)HobEntry + sizeof (UINT64)));
return (BOOLEAN)(GuidFirst == EntryFirst && GuidLast == EntryLast);
}
/**
Retrieves the HOB list pointer from the UEFI SystemTable.
Scans the SystemTable configuration table entries to find the HOB list
GUID match, then caches and returns the HOB list pointer.
@param[in] ImageHandle The EFI image handle (currently unused).
@return Pointer to the HOB list, or NULL if not found.
**/
VOID *
GetHobList (
IN EFI_HANDLE ImageHandle
)
{
UINTN Index;
UINTN EntryCount;
VOID *ConfigTable;
VOID *HobListResult;
if (mHobList != NULL) {
return mHobList;
}
mHobList = NULL;
HobListResult = NULL;
//
// Scan system configuration table for EFI_HOB_LIST_GUID entry
// gSystemTable->ConfigurationTable is an array of {EFI_GUID, VOID*} entries
//
if (gSystemTable->NumberOfTableEntries > 0) {
for (Index = 0; Index < gSystemTable->NumberOfTableEntries; Index++) {
ConfigTable = (VOID *)((UINTN)gSystemTable->ConfigurationTable + (Index * 24));
if (CompareGuidQword (&mHobListGuid, ConfigTable)) {
HobListResult = *(VOID **)((UINTN)ConfigTable + 16);
mHobList = HobListResult;
return HobListResult;
}
}
}
//
// HOB list not found - trigger assertion (matching original behavior)
//
UbaDebugPrint (
UBA_DEBUG_ERROR,
"\nASSERT_EFI_ERROR (Status = %r)\n",
0x800000000000000EULL
);
UbaAssert (
"e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
54,
"!EFI_ERROR (Status)"
);
if (mHobList == NULL) {
UbaAssert (
"e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
55,
"mHobList != ((void *) 0)"
);
}
return mHobList;
}
/**
Locates and returns the UBA backchannel protocol interface pointer.
Uses the DXE Services Table to locate the UBA protocol. First checks
the HOB size via GetBootMode (index 3), and if HOB size <= 0x10,
locates the protocol via the DXE Services Table's LocateProtocol
(function at vtable index 40 = offset 320).
@return Pointer to the UBA protocol interface, or NULL if not found.
**/
VOID *
GetUbaProtocol (
VOID
)
{
UINT64 HobSize;
EFI_DXE_SERVICES *DxeServices;
EFI_STATUS Status;
if (mUbaProtocol != NULL) {
return mUbaProtocol;
}
//
// Get HOB size via DXE Services GetBootMode (function at vtable+24 = index 3)
// BootServices[24] = CalculateE820CompatibleSize or similar size-retrieval
//
HobSize = gBootServices->CalculateE820CompatibleSize (31);
gBootServices->FreePool ((VOID *)(UINTN)HobSize);
if (HobSize <= 0x10) {
//
// Locate UBA protocol via DXE Services Table
// gBootServices->LocateProtocol is at offset 320 (index 40) in BootServices vtable
//
Status = gBootServices->LocateProtocol (
&mUbaProtocolGuid,
NULL,
&mUbaProtocol
);
if (EFI_ERROR (Status)) {
mUbaProtocol = NULL;
}
}
return mUbaProtocol;
}
/**
Debug print via UBA backchannel protocol.
Reads the platform type from CMOS/RTC register 0x4B to determine
the debug filter level. If the platform matches NeonCity EP and
the error level matches the filter, calls the UBA debug print function.
@param[in] ErrorLevel Debug severity level.
@param[in] Format Format string.
@param[in] ... Variable arguments.
@retval TRUE The message was printed.
@retval FALSE The UBA backchannel was not available.
**/
BOOLEAN
UbaDebugPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VA_LIST Args;
VOID *UbaProtocol;
EFI_STATUS (EFIAPI *DebugPrintFunc)(UINTN, CONST CHAR8 *, VA_LIST);
UINT8 RtcReg;
UINT8 PlatformType;
UINTN FilterLevel;
BOOLEAN Result;
UbaProtocol = GetUbaProtocol ();
FilterLevel = 0;
DebugPrintFunc = NULL;
Result = FALSE;
if (UbaProtocol != NULL) {
//
// Detect platform type from CMOS/RTC register
//
RtcReg = IoRead8 (RTC_ADDRESS_PORT);
IoWrite8 (RTC_ADDRESS_PORT, (RtcReg & 0x80) | RTC_INDEX_PLATFORM_TYPE);
PlatformType = IoRead8 (RTC_DATA_PORT);
//
// Validate platform type
//
if (PlatformType > PLATFORM_TYPE_MAX) {
if (PlatformType == 0) {
//
// Fallback: read platform type from MMIO register
//
PlatformType = (MmioRead32 (PLATFORM_TYPE_MMIO_REG) & 0x2) | 0x1;
}
}
//
// Set filter level based on platform type
//
FilterLevel = UBA_DEBUG_ERROR;
if (PlatformType == PLATFORM_TYPE_NEONCITY_EP) {
FilterLevel = UBA_DEBUG_INFO;
}
//
// Call UBA debug print function if severity matches filter
// Debug print function is at vtable offset 1 (index 1, offset 8)
//
if ((FilterLevel & ErrorLevel) != 0) {
DebugPrintFunc = (EFI_STATUS (EFIAPI *)(UINTN, CONST CHAR8 *, VA_LIST))
*((UINT64 *)UbaProtocol + 1);
VA_START (Args, Format);
DebugPrintFunc (ErrorLevel, Format, Args);
VA_END (Args);
Result = TRUE;
}
}
return Result;
}
/**
Assert handler via UBA backchannel protocol.
@param[in] FileName Source file name where the assertion occurred.
@param[in] LineNumber Line number of the assertion.
@param[in] AssertString The assertion expression string.
**/
VOID
UbaAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *AssertString
)
{
VOID *UbaProtocol;
EFI_STATUS (EFIAPI *AssertFunc)(CONST CHAR8 *, UINTN, CONST CHAR8 *);
UbaProtocol = GetUbaProtocol ();
if (UbaProtocol != NULL) {
//
// Assert function is at vtable offset 2 (index 2, offset 16)
//
AssertFunc = (EFI_STATUS (EFIAPI *)(CONST CHAR8 *, UINTN, CONST CHAR8 *))
*((UINT64 *)UbaProtocol + 2);
AssertFunc (FileName, LineNumber, AssertString);
}
}
/**
Validates a PCI function address against the slot bitmask.
For each bit set in SlotBitmask (bits 0-7), reads the slot BDF range
from the OPRM table and queries the DXE Services protocol to determine
whether the given PCI address falls within the slot's valid range.
The DXE Services protocol function at vtable index 7 (offset 56) is
called to read configuration space bytes at the computed BDF address.
@param[in] PciAddress PCI bus:device:function address to validate.
@param[in] SlotBitmask Bitmask of slot positions to check (bits 0-7).
@retval TRUE The address falls within an enabled slot's range.
@retval FALSE The address is not within any enabled slot's range.
**/
BOOLEAN
IsPcieSlotNumberValid (
IN UINT64 PciAddress,
IN UINT32 SlotBitmask
)
{
UINT32 Bitmask;
UINT8 *SlotBase;
UINT8 *OprmEntry;
BOOLEAN Result;
UINTN PortIndex;
UINT64 Bus;
UINT64 Device;
UINT64 Function;
EFI_STATUS Status;
VOID *DxeServices;
UINT64 BdfSlotKey;
UINT8 SlotRangeLow;
UINT8 SlotRangeHigh;
Bitmask = SlotBitmask;
SlotBase = (UINT8 *)(UINTN)mOprmTable - 1; // Adjust for v4-1 offset
Result = FALSE;
for (PortIndex = 0; PortIndex < OPRM_TABLE_ENTRY_COUNT; PortIndex++) {
if (((Bitmask >> PortIndex) & 1) == 0) {
continue;
}
//
// Each OPRM entry is 3 bytes at 4-byte stride
// OprmEntry points to entry base = mOprmTable + (PortIndex * 4)
//
OprmEntry = (UINT8 *)(UINTN)mOprmTable + (PortIndex * 4);
//
// Decode BDF from 3 bytes at offset v4-1, v4, v4+1
// Combined into a slot key: ((byte[-1] << 8) | (byte[0] << 16) | (byte[1] << 24)) & 0xFFFFFF00
//
Bus = OprmEntry[-1];
Device = OprmEntry[0];
Function = OprmEntry[1];
//
// Build the BDF slot key: ((Bus) | (Device << 8) | (Function << 16)) << 8
// Then OR with 0x19 (Slot Capabilities Register low byte offset) and 0x1A (high byte)
//
BdfSlotKey = (Bus | (Device << 8) | (Function << 16)) << 8;
Result = FALSE;
//
// Locate DXE Services Protocol
// (GUID = EFI_DXE_SERVICES_TABLE_GUID)
//
Status = gBootServices->LocateProtocol (
&mDxeServicesProtocolGuid,
NULL,
&DxeServices
);
if (EFI_ERROR (Status)) {
continue;
}
//
// Read slot range low byte via DXE Services function at vtable+56 (index 7)
// BdfSlotKey | 0x19 = PCIe Slot Capabilities register low byte
//
Status = ((EFI_DXE_SERVICES *)DxeServices)->GetFunctionTableEntry (
DxeServices,
0,
BdfSlotKey | 0x19,
1,
&SlotRangeLow
);
if (EFI_ERROR (Status)) {
continue;
}
//
// Read slot range high byte via DXE Services function at vtable+56 (index 7)
// BdfSlotKey | 0x1A = PCIe Slot Capabilities register high byte
//
Status = ((EFI_DXE_SERVICES *)DxeServices)->GetFunctionTableEntry (
DxeServices,
0,
BdfSlotKey | 0x1A,
1,
&SlotRangeHigh
);
if (EFI_ERROR (Status)) {
continue;
}
//
// Check if PciAddress falls within this slot's valid range
//
if (PciAddress >= SlotRangeLow && PciAddress <= SlotRangeHigh) {
Result = TRUE;
}
}
return Result;
}
/**
Returns the default (empty) slot configuration table.
@param[out] ConfigTable Pointer to receive the table address.
@retval EFI_SUCCESS The table pointer was returned.
**/
EFI_STATUS
GetDefaultSlotConfig (
OUT VOID **ConfigTable
)
{
*ConfigTable = (VOID *)mDefaultSlotConfig;
return EFI_SUCCESS;
}
/**
Returns the NeonCity EP EC B slot configuration table.
@param[out] ConfigTable Pointer to receive the table address.
@param[out] EntryCount Pointer to receive the number of entries (6).
@retval EFI_SUCCESS The table pointer and count were returned.
**/
EFI_STATUS
GetNeonCitySlotConfig (
OUT VOID **ConfigTable,
OUT UINTN *EntryCount
)
{
*ConfigTable = (VOID *)mNeonCitySlotConfig;
*EntryCount = 6;
return EFI_SUCCESS;
}
/**
Returns the PCIe BDF slot number table for NeonCity EP EC B.
@param[out] SlotTable Pointer to receive the table address.
@param[out] EntryCount Pointer to receive the number of entries (10).
@retval EFI_SUCCESS The table pointer and count were returned.
**/
EFI_STATUS
GetPcieSlotNumberTable (
OUT VOID **SlotTable,
OUT UINTN *EntryCount
)
{
*SlotTable = (VOID *)mPcieBdfSlotTable;
*EntryCount = 10;
return EFI_SUCCESS;
}
/**
Set PCIe slot number callback.
Assigns slot number 2 and emits a debug trace.
@param[out] SlotNumber Pointer to receive the assigned slot number.
@retval EFI_SUCCESS The slot number was set successfully.
**/
EFI_STATUS
SetPcieSlotNumber (
OUT UINT8 *SlotNumber
)
{
*SlotNumber = 2;
UbaDebugPrint (
UBA_DEBUG_INFO,
"[UBA]:SetPcieSlotNumber callback - %d\n",
*SlotNumber
);
return EFI_SUCCESS;
}
/**
Entry point for the OpromUpdate DXE driver.
Initializes global UEFI table pointers, retrieves the HOB list, and
registers the platform's OpromUpdate configuration with the UBA
framework.
The registration provides:
- A GUID identifying the platform
- A data table containing function pointers for config callbacks
- The data size (48 bytes)
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS Protocol installed successfully.
@retval EFI_NOT_FOUND UBA protocol not found.
@retval Others Error from gBS->InstallProtocolInterface.
**/
EFI_STATUS
EFIAPI
OpromUpdateEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
VOID *UbaConfigProtocol;
//
// UBA configuration data structure (48 bytes total)
// Contains GUID + version + callback function pointers
//
typedef struct {
EFI_GUID ConfigGuid; // Platform config GUID (16 bytes)
UINT32 Version; // Configuration data version
UINT8 Padding[4]; // Alignment padding
//
// Callback function pointers (the "PSET" table at 0xEC0 in .data)
// Index 0: GetDefaultSlotConfig
// Index 1: GetNeonCitySlotConfig
// Index 2: GetPcieSlotNumberTable
// Index 3: SetPcieSlotNumber
// Index 4,5: Reserved (set to 0)
//
UINT64 Callbacks[6]; // 6 * 8 = 48 bytes
} UBA_OPROM_CONFIG_DATA;
UBA_OPROM_CONFIG_DATA ConfigData;
//
// Initialize global UEFI table pointers to match UEFI standard library behavior
//
gImageHandle = ImageHandle;
if (ImageHandle == NULL) {
UbaAssert (
"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
51,
"gImageHandle != ((void *) 0)"
);
}
gSystemTable = SystemTable;
if (SystemTable == NULL) {
UbaAssert (
"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
57,
"gST != ((void *) 0)"
);
}
gBootServices = SystemTable->BootServices;
if (gBootServices == NULL) {
UbaAssert (
"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
63,
"gBS != ((void *) 0)"
);
}
gRuntimeServices = SystemTable->RuntimeServices;
if (gRuntimeServices == NULL) {
UbaAssert (
"e:\\hs\\MdePkg\\Library\\UefiRuntimeServicesTableLib\\UefiRuntimeServicesTableLib.c",
47,
"gRT != ((void *) 0)"
);
}
//
// Initialize HOB list (cached for later use)
//
GetHobList (ImageHandle);
//
// Log driver entry
//
UbaDebugPrint (
UBA_DEBUG_INFO,
"UBA:OpromUpdate-TypeNeonCityEPECB\n"
);
//
// Locate the UBA OpromUpdate config protocol
//
Status = gBootServices->LocateProtocol (
&mUbaConfigProtocolGuid,
NULL,
&UbaConfigProtocol
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Build configuration data structure
//
CopyMem (&ConfigData.ConfigGuid, &mUbaProtocolGuid, sizeof (EFI_GUID));
ConfigData.Version = 1;
ZeroMem (ConfigData.Padding, sizeof (ConfigData.Padding));
ConfigData.Callbacks[0] = (UINT64)(UINTN)IsPcieSlotNumberValid;
ConfigData.Callbacks[1] = (UINT64)(UINTN)GetDefaultSlotConfig;
ConfigData.Callbacks[2] = (UINT64)(UINTN)GetNeonCitySlotConfig;
ConfigData.Callbacks[3] = (UINT64)(UINTN)GetPcieSlotNumberTable;
ConfigData.Callbacks[4] = (UINT64)(UINTN)SetPcieSlotNumber;
ConfigData.Callbacks[5] = 0;
//
// Register configuration with UBA framework
// Call function at vtable index 2 (offset 16) in UbaConfigProtocol
//
return ((EFI_STATUS (EFIAPI *)(VOID *, VOID *, VOID *, UINTN))UbaConfigProtocol)(
UbaConfigProtocol,
&ConfigData,
&ConfigData.Callbacks,
sizeof (UBA_OPROM_CONFIG_DATA)
);
}