/** @file
SmbiosType39.efi
UEFI DXE driver that installs SMBIOS Type 39 (Power Supply) records.
This driver communicates with the Management Engine (ME) via the HECI
protocol to retrieve power supply information for up to two power supply
bays. For each detected bay, it queries the ME for the device name,
manufacturer, serial number, asset tag, model part number, revision level,
max power capacity, and presence/status. The collected data is assembled
into SMBIOS Type 39 (Power Supply) structures and installed via the
SMBIOS protocol.
Copyright (c) Lenovo Corporation.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "SmbiosType39.h"
//
// Global data
//
EFI_HANDLE gImageHandle = NULL;
EFI_SYSTEM_TABLE *gST = NULL;
EFI_BOOT_SERVICES *gBS = NULL;
EFI_RUNTIME_SERVICES *gRT = NULL;
EFI_SMBIOS_PROTOCOL *gSmbios = NULL;
EFI_HECI_PROTOCOL *gHeci = NULL;
VOID *gPcd = NULL;
VOID *gMmPciBase = NULL;
EFI_EVENT gRegistrationEvent = NULL;
//
// GUID definitions (referenced from global data section)
//
extern EFI_GUID gEfiSmbiosProtocolGuid;
extern EFI_GUID gEfiHeciProtocolGuid;
extern EFI_GUID gEfiDxeServicesTableGuid;
extern EFI_GUID gEfiPcdProtocolGuid;
extern EFI_GUID gEfiMmPciBaseGuid;
//
// HECI command message for power supply queries
//
#pragma pack(1)
typedef struct {
UINT16 GroupId; // 0x8A0A = HECI_GROUP_ID << 8 | 0x0A
UINT8 Command; // HECI command byte
UINT8 Reserved;
UINT32 Param; // Parameter data (varies per command)
} HECI_POWER_SUPPLY_REQUEST;
typedef struct {
UINT16 GroupId;
UINT8 Command;
UINT8 Reserved;
UINT32 Param1;
UINT16 Param2;
} HECI_POWER_SUPPLY_REQUEST_EXT;
#pragma pack()
//
// HECI command template: GroupId = 0x8A0A
//
#define HECI_POWER_SUPPLY_GROUP_ID 0x8A0A
#define HECI_CMD_DEVICE_NAME 0x9A
#define HECI_CMD_MANUFACTURER 0x99
#define HECI_CMD_SERIAL_NUMBER 0x62 // (0x9E -> -98 signed)
#define HECI_CMD_ASSET_TAG 0x61 // (0xF1 -> -15 signed, but command field is 0x61)
#define HECI_CMD_MODEL_PART_NUMBER 0x9B // (-101 signed)
#define HECI_CMD_REVISION_LEVEL 0xA7 // (-89 signed)
#define HECI_CMD_MAX_POWER_CAPACITY 0x78 // (120)
#define HECI_CMD_POWER_SUPPLY_STATUS 0x78 // (120, same as max capacity but different param)
//
// ME FWS (Firmware Status) register bits
//
#define ME_FWS_CURRENT_STATE_MASK 0x0F
/**
ZeroMem implementation - fills a buffer with zeros.
@param[out] Buffer Pointer to the buffer to zero.
@param[in] Length Number of bytes to zero.
@return Buffer.
**/
VOID *
EFIAPI
InternalZeroMem (
OUT VOID *Buffer,
IN UINTN Length
)
{
//
// Zero out 8 bytes at a time, then remaining bytes
//
ZeroMem (Buffer, Length);
return Buffer;
}
/**
Reads a 64-bit value from memory (unaligned-safe).
@param[in] Buffer Pointer to the memory location.
@return The 64-bit value read.
**/
UINT64
EFIAPI
InternalReadUnaligned64 (
IN CONST VOID *Buffer
)
{
ASSERT (Buffer != NULL);
return *(UINT64 *)Buffer;
}
/**
Returns the length of a null-terminated ASCII string.
@param[in] String A pointer to the null-terminated ASCII string.
@return The number of ASCII characters in String excluding the null terminator.
**/
UINTN
EFIAPI
AsciiStrLen (
IN CONST CHAR8 *String
)
{
UINTN Length;
ASSERT (String != NULL);
Length = 0;
while (*String != '\0') {
if (Length >= 0xF4240) {
ASSERT (FALSE); // PcdMaximumAsciiStringLength exceeded
}
String++;
Length++;
}
return Length;
}
/**
Returns the length of a null-terminated Unicode string.
@param[in] String A pointer to the null-terminated Unicode string.
@return The number of Unicode characters in String excluding the null terminator.
**/
UINTN
EFIAPI
StrLen (
IN CONST CHAR16 *String
)
{
UINTN Length;
ASSERT (String != NULL);
ASSERT (((UINTN)String & 0x00000001) == 0);
Length = 0;
while (*String != L'\0') {
if (Length >= 0xF4240) {
ASSERT (FALSE); // PcdMaximumUnicodeStringLength exceeded
}
String++;
Length++;
}
return Length;
}
/**
Converts a null-terminated ASCII string to a null-terminated Unicode string.
@param[in] Source A pointer to the ASCII string.
@param[out] Destination A pointer to the destination Unicode buffer.
@return Destination.
**/
CHAR16 *
EFIAPI
AsciiStrToUnicodeStr (
IN CONST CHAR8 *Source,
OUT CHAR16 *Destination
)
{
CHAR16 *Dest;
ASSERT (Destination != NULL);
ASSERT (AsciiStrSize (Source) != 0);
//
// Ensure buffers do not overlap in a way that would cause corruption
//
ASSERT ((UINTN)((CHAR8 *)Destination - Source) > AsciiStrLen (Source));
ASSERT ((UINTN)(Source - (CHAR8 *)Destination) >= (AsciiStrSize (Source) * sizeof (CHAR16)));
Dest = Destination;
while (*Source != '\0') {
*(Dest++) = (CHAR16)(UINT8)*(Source++);
}
*Dest = L'\0';
ASSERT (StrSize (Destination) != 0);
return Destination;
}
/**
Copies one ASCII string to another (with overlap safety checks).
@param[out] Destination A pointer to the destination buffer.
@param[in] Source A pointer to the source string.
@return Destination.
**/
CHAR8 *
EFIAPI
AsciiStrCpyS (
OUT CHAR8 *Destination,
IN CONST CHAR8 *Source
)
{
CHAR8 *Dest;
ASSERT (Destination != NULL);
ASSERT ((UINTN)(Destination - Source) > AsciiStrLen (Source));
ASSERT ((UINTN)(Source - Destination) > AsciiStrLen (Source));
Dest = Destination;
while (*Source != '\0') {
*(Dest++) = *(Source++);
}
*Dest = '\0';
return Destination;
}
/**
Copies an ASCII string from a source buffer to destination, stopping after
a specified number of characters and appending a null terminator.
@param[out] Destination Pointer to the destination buffer.
@param[in] Source Pointer to the source buffer.
@param[in] Length Maximum number of characters to copy.
@return Destination.
**/
CHAR8 *
EFIAPI
InternalStrnCpyAscii (
OUT CHAR8 *Destination,
IN CHAR8 *Source,
IN UINTN Length
)
{
UINTN Index;
Index = 0;
if (Length > 0) {
do {
Destination[Index] = Source[Index];
Index++;
} while (Index < Length);
}
Destination[Index] = '\0';
return Destination;
}
/**
Locates a configuration table by GUID in the UEFI System Table.
@param[in] TableGuid GUID of the table to locate.
@param[out] Table On successful return, points to the table.
@retval EFI_SUCCESS The table was found.
@retval EFI_NOT_FOUND The table was not found.
**/
EFI_STATUS
EFIAPI
EfiGetSystemConfigurationTable (
IN EFI_GUID *TableGuid,
OUT VOID **Table
)
{
UINTN Index;
UINTN NumberOfTableEntries;
ASSERT (TableGuid != NULL);
ASSERT (Table != NULL);
*Table = NULL;
NumberOfTableEntries = gST->NumberOfTableEntries;
if (NumberOfTableEntries == 0) {
return EFI_NOT_FOUND;
}
for (Index = 0; Index < NumberOfTableEntries; Index++) {
if (CompareGuid (TableGuid, &gST->ConfigurationTable[Index].VendorGuid)) {
*Table = (VOID *)gST->ConfigurationTable[Index].VendorTable;
return EFI_SUCCESS;
}
}
return EFI_NOT_FOUND;
}
/**
Sends a HECI power supply command and retrieves the response.
This function constructs a HECI command message with the appropriate
group ID and command byte, sends it via the HECI protocol, and returns
the response length and data.
@param[in] HeciProtocol Pointer to the HECI protocol instance.
@param[in] Command HECI command byte.
@param[in] SupplyBay Power supply bay index (0 or 1).
@param[out] DataBuffer Buffer to receive response data.
@param[in] BufferSize Size of the data buffer.
@param[out] DataLength On output, length of data returned by ME.
@retval EFI_SUCCESS Command succeeded and data was returned.
@retval Others HECI communication failed.
**/
EFI_STATUS
SendHeciPowerSupplyCommand (
IN EFI_HECI_PROTOCOL *HeciProtocol,
IN UINT8 Command,
IN UINT8 SupplyBay,
OUT VOID *DataBuffer,
IN UINTN BufferSize,
OUT UINTN *DataLength
)
{
HECI_POWER_SUPPLY_REQUEST Request;
UINT8 Response[64];
UINTN ResponseLength;
UINT8 *DataPtr;
UINTN CopyLength;
//
// Build the HECI request header
//
Request.GroupId = HECI_POWER_SUPPLY_GROUP_ID; // 0x8A0A
Request.Command = Command;
Request.Reserved = 0;
Request.Param = SupplyBay; // supply bay index in low byte
DEBUG ((
DEBUG_INFO,
"pHeciProtocol->SendwACK (%x)\n",
Command
));
//
// Send the command via HECI with the default buffer size
//
ResponseLength = sizeof (Response);
Status = HeciProtocol->SendwACK (
0,
&Request,
sizeof (Request),
&ResponseLength,
0,
32
);
DEBUG ((
DEBUG_INFO,
"pHeciProtocol->SendwACK Status: %r, HeciMsg[1]=%x, HeciMsg[2]=%x\n",
Status,
Request.Command,
SupplyBay
));
if (EFI_ERROR (Status)) {
return Status;
}
//
// Check response: if HeciMsg[1] is non-zero but not 0xA3, no data available
//
if (Response[1] != 0) {
if (Response[1] != 0xA3) {
return EFI_DEVICE_ERROR;
}
}
//
// Response[2] contains the data length (SupplyBay response)
//
*DataLength = Response[2];
if (*DataLength == 0) {
return EFI_NOT_FOUND;
}
//
// Copy the returned data to the caller's buffer
//
DataPtr = &Response[3];
CopyLength = MIN (*DataLength, BufferSize);
CopyMem (DataBuffer, DataPtr, CopyLength);
return EFI_SUCCESS;
}
/**
Retrieves the HOB list pointer from the UEFI System Table.
The HOB list is needed for DXE services initialization. This function
looks up the gEfiDxeServicesTableGuid in the system configuration table
and caches the pointer.
**/
VOID
GetHobList (
VOID
)
{
EFI_STATUS Status;
if (gMmPciBase == NULL) {
Status = EfiGetSystemConfigurationTable (
&gEfiDxeServicesTableGuid,
&gMmPciBase
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
));
ASSERT_EFI_ERROR (FALSE);
}
if (gMmPciBase == NULL) {
ASSERT (gMmPciBase != NULL);
}
}
}
/**
Retrieves the SMBIOS protocol from the UEFI protocol database.
@return Pointer to the EFI_SMBIOS_PROTOCOL instance, or NULL if not found.
**/
EFI_SMBIOS_PROTOCOL *
GetSmbiosProtocol (
VOID
)
{
EFI_STATUS Status;
if (gSmbios == NULL) {
Status = gBS->LocateProtocol (
&gEfiSmbiosProtocolGuid,
NULL,
(VOID **)&gSmbios
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
));
ASSERT_EFI_ERROR (FALSE);
}
}
return gSmbios;
}
/**
Retrieves the HECI protocol from the UEFI protocol database.
@return Pointer to the EFI_HECI_PROTOCOL instance, or NULL if not found.
**/
EFI_HECI_PROTOCOL *
GetHeciProtocol (
VOID
)
{
EFI_STATUS Status;
if (gHeci == NULL) {
Status = gBS->LocateProtocol (
&gEfiHeciProtocolGuid,
NULL,
(VOID **)&gHeci
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
));
ASSERT_EFI_ERROR (FALSE);
}
}
return gHeci;
}
/**
Retrieves the PCD protocol from the UEFI protocol database.
@return Pointer to the PCD protocol, or NULL if not found.
**/
VOID *
GetPcdProtocol (
VOID
)
{
EFI_STATUS Status;
UINTN Pages;
VOID *Pcd;
if (gPcd != NULL) {
return gPcd;
}
//
// Allocate 31 pages for PCD protocol usage
//
Pages = EFI_SIZE_TO_PAGES (31);
gBS->AllocatePages (AllocateAnyPages, EfiBootServicesData, Pages, &Pages);
if (Pages > 0x10) {
//
// Not enough pages - return NULL
//
return NULL;
}
//
// Locate the PCD protocol
//
Status = gBS->LocateProtocol (
&gEfiPcdProtocolGuid,
NULL,
(VOID **)&gPcd
);
if (EFI_ERROR (Status)) {
gPcd = NULL;
}
return gPcd;
}
/**
Returns a string description for a given power supply bay index.
@param[in] SupplyBay 0 for Left/Inner Supply Bay, 1 for Right/Outer Supply Bay.
@return Pointer to a static string describing the supply bay.
**/
CONST CHAR8 *
GetSupplyBayString (
IN UINT8 SupplyBay
)
{
if (SupplyBay == 0) {
return "Left/Inner Supply Bay";
} else if (SupplyBay == 1) {
return "Right/Outer Supply Bay";
} else {
return "INVALID";
}
}
/**
Frees a buffer allocated via gBS->AllocatePool.
@param[in] Buffer Pointer to the buffer to free.
**/
VOID
FreePoolOrBuffer (
IN VOID *Buffer
)
{
EFI_STATUS Status;
Status = gBS->FreePool (Buffer);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
));
ASSERT_EFI_ERROR (FALSE);
}
}
/**
Debug print routing function.
This is a wrapper around the UEFI debug print functionality that checks
the current debug level by reading CMOS/NVRAM and routes the output to
the appropriate debug stream.
@param[in] ErrorLevel Debug error level.
@param[in] Format Format string.
@param[in] ... Variable arguments for the format string.
**/
VOID
EFIAPI
DebugPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VOID *DebugInfo;
UINT8 CmosByte;
UINT8 DebugLevel;
UINT8 Port92Value;
UINTN Mask;
DebugInfo = GetPcdProtocol ();
if (DebugInfo == NULL) {
return;
}
//
// Read CMOS to determine current debug level
//
CmosByte = IoRead8 (0x70);
IoWrite8 (0x70, CmosByte & 0x80 | 0x4B);
DebugLevel = IoRead8 (0x71);
//
// Determine the error mask based on the debug level
//
if (DebugLevel > 3) {
if (DebugLevel == 0) {
Port92Value = *(volatile UINT8 *)0xFDAF0490;
DebugLevel = (Port92Value & 2) | 1;
}
}
switch (DebugLevel) {
case 1:
Mask = DEBUG_ERROR;
break;
default:
Mask = DEBUG_INIT | DEBUG_WARN | DEBUG_INFO;
break;
}
if ((Mask & ErrorLevel) != 0) {
DEBUG ((ErrorLevel, Format, ...));
}
}
/**
Installs SMBIOS Type 39 (Power Supply) structures.
This function is the main callback that enumerates up to two power supply
bays. For each bay, it communicates via HECI with the ME to retrieve:
- Device Name
- Manufacturer
- Serial Number
- Asset Tag
- Model Part Number
- Revision Level
- Max Power Capacity
- Power Supply Present/Status
The collected data is assembled into an SMBIOS Type 39 record and added
to the SMBIOS table.
@param[in] Event The event that triggered this callback.
@param[in] Context The driver image handle.
@retval EFI_SUCCESS The SMBIOS Type 39 structures were installed.
@retval Others An error occurred.
**/
EFI_STATUS
EFIAPI
InstallSmbiosType39Structure (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_STATUS Status;
UINTN HeciStatus;
UINTN MeFwStatus;
UINT8 CurrentMeState;
EFI_HECI_PROTOCOL *HeciProtocol;
UINT8 SupplyBay;
UINT8 NvRamVariable;
UINT8 SupplyBayIncrement;
UINTN DataLen;
CHAR8 DeviceName[64];
CHAR8 Manufacturer[64];
CHAR8 SerialNumber[64];
CHAR8 AssetTag[64];
CHAR8 ModelPartNumber[64];
CHAR8 RevisionLevel[64];
UINT16 MaxPowerCapacity;
UINT16 PowerSupplyPresent;
UINT16 PowerSupplyStatus;
UINT16 PowerSupplyFlags;
SMBIOS_TABLE_TYPE39 *SmbiosRecord;
UINTN StringBufferSize;
CHAR8 *StringBuffer;
CHAR8 *CurrentStringPtr;
UINTN TotalStringLength;
CHAR16 UnicodeBuffer[256];
UINTN Index;
UINTN V2;
//
// Initialize the INVALID string length for fallback
//
V2 = AsciiStrLen ("INVALID");
DEBUG ((DEBUG_INFO, "InstallSmbiosType39Structure entered\n"));
//
// Locate the SMBIOS protocol
//
SmbiosRecord = (SMBIOS_TABLE_TYPE39 *)(UINTN)GetSmbiosProtocol ();
if (SmbiosRecord == NULL) {
return EFI_UNSUPPORTED;
}
//
// Close the notification event
//
Status = gBS->CloseEvent (Event);
DEBUG ((DEBUG_INFO, "CloseEvent Status: %r\n", Status));
//
// Locate the HECI protocol
//
HeciProtocol = GetHeciProtocol ();
if (HeciProtocol == NULL) {
return EFI_UNSUPPORTED;
}
//
// Check ME firmware status to determine if ME is in normal mode
//
NvRamVariable = 512;
HeciStatus = gMmPciBase->Read ((UINTN)&NvRamVariable, sizeof (NvRamVariable));
if (HeciStatus != EFI_SUCCESS) {
return HeciStatus;
}
CurrentMeState = MmPciRead8 (0) & ME_FWS_CURRENT_STATE_MASK;
if (CurrentMeState != 5) {
//
// ME is not in S0/M0 normal operation state
//
DEBUG ((DEBUG_INFO, "MeFs1.Bits.CurrentState == %d\n", CurrentMeState));
return EFI_DEVICE_ERROR;
}
//
// Enumerate power supply bays (0 and 1)
//
SupplyBay = 0;
SupplyBayIncrement = 0;
NvRamVariable = 0;
while (NvRamVariable < MAX_POWER_SUPPLY_COUNT) {
///
/// Step 1: Get Device Name (command = 0x9A, -102 signed)
///
DataLen = 0;
Status = SendHeciPowerSupplyCommand (
HeciProtocol,
HECI_CMD_DEVICE_NAME,
SupplyBay,
DeviceName,
sizeof (DeviceName) - 1,
&DataLen
);
if (EFI_ERROR (Status)) {
//
// Fallback: no device name available, use INVALID
//
AsciiStrCpyS (DeviceName, "INVALID");
} else {
DeviceName[DataLen] = '\0';
}
DEBUG ((DEBUG_INFO, "DeviceNameStr = %s\n", DeviceName));
///
/// Step 2: Get Manufacturer (command = 0x99, -103 signed)
///
DataLen = 0;
Status = SendHeciPowerSupplyCommand (
HeciProtocol,
HECI_CMD_MANUFACTURER,
SupplyBay,
Manufacturer,
sizeof (Manufacturer) - 1,
&DataLen
);
if (EFI_ERROR (Status)) {
AsciiStrCpyS (Manufacturer, "INVALID");
} else {
Manufacturer[DataLen] = '\0';
}
DEBUG ((DEBUG_INFO, "ManufacturerStr = %s\n", Manufacturer));
///
/// Step 3: Get Serial Number (command = 0x62, -98 signed as 0x9E)
///
DataLen = 0;
Status = SendHeciPowerSupplyCommand (
HeciProtocol,
HECI_CMD_SERIAL_NUMBER,
SupplyBay,
SerialNumber,
sizeof (SerialNumber) - 1,
&DataLen
);
if (EFI_ERROR (Status)) {
AsciiStrCpyS (SerialNumber, "INVALID");
} else {
SerialNumber[DataLen] = '\0';
}
DEBUG ((DEBUG_INFO, "SerialNumberStr = %s\n", SerialNumber));
///
/// Step 4: Get Asset Tag (command = 0xF1, -15 signed)
///
DataLen = 0;
Status = SendHeciPowerSupplyCommand (
HeciProtocol,
0xF1, // Asset tag command
SupplyBay,
AssetTag,
sizeof (AssetTag) - 1,
&DataLen
);
if (EFI_ERROR (Status)) {
AsciiStrCpyS (AssetTag, "INVALID");
} else {
AssetTag[DataLen] = '\0';
}
DEBUG ((DEBUG_INFO, "AssetTagNumberStr = %s\n", AssetTag));
///
/// Step 5: Get Model Part Number (command = 0x9B, -101 signed)
///
DataLen = 0;
Status = SendHeciPowerSupplyCommand (
HeciProtocol,
HECI_CMD_MODEL_PART_NUMBER,
SupplyBay,
ModelPartNumber,
sizeof (ModelPartNumber) - 1,
&DataLen
);
if (EFI_ERROR (Status)) {
AsciiStrCpyS (ModelPartNumber, "INVALID");
} else {
ModelPartNumber[DataLen] = '\0';
}
DEBUG ((DEBUG_INFO, "ModelPartNumberStr = %s\n", ModelPartNumber));
///
/// Step 6: Get Revision Level (command = 0xA7, -89 signed)
///
DataLen = 0;
Status = SendHeciPowerSupplyCommand (
HeciProtocol,
HECI_CMD_REVISION_LEVEL,
SupplyBay,
RevisionLevel,
sizeof (RevisionLevel) - 1,
&DataLen
);
if (EFI_ERROR (Status)) {
AsciiStrCpyS (RevisionLevel, "INVALID");
} else {
RevisionLevel[DataLen] = '\0';
}
DEBUG ((DEBUG_INFO, "RevisionLevelStr = %s\n", RevisionLevel));
///
/// Step 7: Get Max Power Capacity
///
{
HECI_POWER_SUPPLY_REQUEST CapRequest;
UINT8 CapResponse[64];
UINTN CapResponseLen;
UINT16 RawCapacity;
CapRequest.GroupId = HECI_POWER_SUPPLY_GROUP_ID;
CapRequest.Command = HECI_CMD_MAX_POWER_CAPACITY;
CapRequest.Reserved = 0;
CapRequest.Param = 0x01000001; // Capacity request parameter
CapResponseLen = sizeof (CapResponse);
Status = HeciProtocol->SendwACK (
0,
&CapRequest,
sizeof (CapRequest),
&CapResponseLen,
0,
32
);
DEBUG ((
DEBUG_INFO,
"pHeciProtocol->SendwACK Status: %r, HeciMsg[1]=%x, HeciMsg[2]=%x, HeciMsg[3]=%x\n",
Status,
CapResponse[1],
SupplyBay,
CapResponse[3]
));
if (EFI_ERROR (Status) || CapResponse[1] != 0) {
MaxPowerCapacity = POWER_SUPPLY_MAX_CAPACITY_DEFAULT;
} else {
RawCapacity = (UINT16)(CapResponse[2] | ((UINT16)CapResponse[3] << 8));
switch (RawCapacity) {
case 2323:
MaxPowerCapacity = 550;
break;
case 2423:
MaxPowerCapacity = 750;
break;
case 2598:
MaxPowerCapacity = 1100;
break;
case 2848:
MaxPowerCapacity = 1600;
break;
case 3048:
MaxPowerCapacity = 2000;
break;
default:
MaxPowerCapacity = POWER_SUPPLY_MAX_CAPACITY_DEFAULT;
break;
}
}
}
DEBUG ((DEBUG_INFO, "MaxPowerCapacity = %d\n", MaxPowerCapacity));
///
/// Step 8: Get Power Supply Present / Status
///
{
HECI_POWER_SUPPLY_REQUEST PsuStatusRequest;
UINT8 PsuResponse[64];
UINTN PsuResponseLen;
PsuStatusRequest.GroupId = HECI_POWER_SUPPLY_GROUP_ID;
PsuStatusRequest.Command = 0x78; // Same as capacity command
PsuStatusRequest.Reserved = 0;
PsuStatusRequest.Param = 0x01010078; // Status request parameter
PsuResponseLen = sizeof (PsuResponse);
Status = HeciProtocol->SendwACK (
0,
&PsuStatusRequest,
sizeof (PsuStatusRequest),
&PsuResponseLen,
0,
32
);
DEBUG ((
DEBUG_INFO,
"pHeciProtocol->SendwACK Status: %r, HeciMsg[1]=%x, HeciMsg[2]=%x\n",
Status,
PsuResponse[1],
SupplyBay
));
if (EFI_ERROR (Status) || PsuResponse[1] != 0) {
PowerSupplyPresent = 0;
PowerSupplyStatus = 2; // Status = Unknown
} else {
PowerSupplyPresent = 1;
//
// Determine power supply status based on response byte
// If bit 1 of response[2] is clear -> status = 1 (Present, Unknown)
// If bit 1 of response[2] is set -> status = 3 (Present, Good)
//
PowerSupplyStatus = ((PsuResponse[2] & 0x02) != 0) ? 1 : 3;
}
}
DEBUG ((
DEBUG_INFO,
"PowerSupplyPresent = %x, PowerSupplyStatus = %x\n",
PowerSupplyPresent,
PowerSupplyStatus
));
///
/// Step 9: Allocate and build SMBIOS Type 39 record
///
TotalStringLength = AsciiStrLen (DeviceName)
+ AsciiStrLen (Manufacturer)
+ AsciiStrLen (SerialNumber)
+ AsciiStrLen (AssetTag)
+ AsciiStrLen (ModelPartNumber)
+ AsciiStrLen (RevisionLevel);
StringBufferSize = TotalStringLength + 31;
StringBuffer = AllocatePool (StringBufferSize);
if (StringBuffer == NULL) {
DEBUG ((DEBUG_ERROR, "Allocating Space for SmBios Type39Record fails...\n"));
continue;
}
SmbiosRecord = AllocateZeroPool (StringBufferSize);
if (SmbiosRecord == NULL) {
DEBUG ((DEBUG_ERROR, "Allocating Space for SmBios Type39Record fails...\n"));
FreePool (StringBuffer);
continue;
}
//
// Fill in the SMBIOS Type 39 record fields
//
SmbiosRecord->Hdr.Type = SMBIOS_TYPE39_TYPE;
SmbiosRecord->Hdr.Length = SMBIOS_TYPE39_LENGTH;
SmbiosRecord->Hdr.Handle = SMBIOS_TYPE39_POWER_SUPPLY_HANDLE;
//
// Set power unit group (2 = "From the Mapping to the Power Unit Group")
//
SmbiosRecord->PowerUnitGroup = 2;
SmbiosRecord->NominalVoltage = 7; // Bits: Voltage=7, Location=0
SmbiosRecord->MaxCapacity = MaxPowerCapacity;
//
// Build the power supply status flags
//
// Bit 0 = Power Supply Present
// Bits 6-7 = Power Supply Status (00=Unknown, 01=OK, 10=Non-critical, 11=Critical)
//
PowerSupplyFlags = (PowerSupplyPresent & 1) | ((PowerSupplyStatus & 7) << 6);
SmbiosRecord->PowerSupplyCharacteristics = (UINT16)(0x0810 | (PowerSupplyFlags << 1));
SmbiosRecord->HotReplaceSupported = 0xFFFF;
//
// Now fill the string area.
// Strings are packed as null-terminated ASCII after the fixed structure.
//
CurrentStringPtr = (CHAR8 *)(SmbiosRecord + 1);
AsciiStrCpyS (CurrentStringPtr, GetSupplyBayString (SupplyBay));
CurrentStringPtr += AsciiStrLen (GetSupplyBayString (SupplyBay)) + 1;
AsciiStrCpyS (CurrentStringPtr, DeviceName);
CurrentStringPtr += AsciiStrLen (DeviceName) + 1;
AsciiStrCpyS (CurrentStringPtr, Manufacturer);
CurrentStringPtr += AsciiStrLen (Manufacturer) + 1;
AsciiStrCpyS (CurrentStringPtr, SerialNumber);
CurrentStringPtr += AsciiStrLen (SerialNumber) + 1;
AsciiStrCpyS (CurrentStringPtr, AssetTag);
CurrentStringPtr += AsciiStrLen (AssetTag) + 1;
AsciiStrCpyS (CurrentStringPtr, ModelPartNumber);
CurrentStringPtr += AsciiStrLen (ModelPartNumber) + 1;
AsciiStrCpyS (CurrentStringPtr, RevisionLevel);
//
// Step 10: Add the Type 39 record via SMBIOS protocol
//
{
SMBIOS_HANDLE SmbiosHandle;
SmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
Status = gSmbios->Add (
gSmbios,
NULL,
&SmbiosHandle,
(EFI_SMBIOS_TABLE_HEADER *)SmbiosRecord
);
DEBUG ((
DEBUG_INFO,
"SmbiosProtocol->Add Type39Record (%x).... Status: %r\n",
SupplyBay,
Status
));
}
//
// Cleanup allocated buffers
//
FreePool (StringBuffer);
FreePool (SmbiosRecord);
//
// Move to the next supply bay
//
SupplyBayIncrement += 2;
if (SupplyBayIncrement > 3) {
DEBUG ((DEBUG_INFO, "InstallSmbiosType39Structure Exiting......\n"));
return EFI_SUCCESS;
}
}
DEBUG ((DEBUG_INFO, "InstallSmbiosType39Structure Exiting......\n"));
return EFI_SUCCESS;
}
/**
Entry point for the SmbiosType39 UEFI driver.
This function initializes global variables (ImageHandle, SystemTable,
BootServices, RuntimeServices), locates the DXE services table,
PCD protocol, and registers a callback to install SMBIOS Type 39
structures at the appropriate time.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS The entry point executed successfully.
@retval EFI_NOT_FOUND A required protocol was not found.
@retval EFI_INVALID_PARAMETER A required global was NULL.
**/
EFI_STATUS
EFIAPI
SmbiosType39EntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
//
// Initialize global variables
//
gImageHandle = ImageHandle;
if (gImageHandle == NULL) {
ASSERT (gImageHandle != NULL);
}
gST = SystemTable;
if (gST == NULL) {
ASSERT (gST != NULL);
}
gBS = SystemTable->BootServices;
if (gBS == NULL) {
ASSERT (gBS != NULL);
}
gRT = SystemTable->RuntimeServices;
if (gRT == NULL) {
ASSERT (gRT != NULL);
}
//
// Initialize HOB list and PCD protocol
//
GetHobList ();
GetPcdProtocol ();
//
// Look up the DXE Services Table via system configuration
//
Status = EfiGetSystemConfigurationTable (
&gEfiDxeServicesTableGuid,
&gMmPciBase
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
));
ASSERT_EFI_ERROR (FALSE);
}
if (gMmPciBase == NULL) {
DEBUG ((
DEBUG_ERROR,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
));
ASSERT_EFI_ERROR (FALSE);
}
//
// Look up the MM PCI base protocol (for MMIO PCI config access)
//
Status = gBS->LocateProtocol (
&gEfiMmPciBaseGuid,
NULL,
(VOID **)&gMmPciBase
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
));
ASSERT_EFI_ERROR (FALSE);
}
ASSERT (gMmPciBase != NULL);
//
// Register a callback on the SMBIOS protocol installation
// When SMBIOS protocol becomes available, InstallSmbiosType39Structure
// will be called.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
InstallSmbiosType39Structure,
ImageHandle,
&gRegistrationEvent
);
if (EFI_ERROR (Status)) {
Status = gBS->RegisterProtocolNotify (
&gEfiSmbiosProtocolGuid,
gRegistrationEvent,
&gRegistrationEvent
);
}
return EFI_SUCCESS;
}