/** @file
Ip6BmcLanConfig.c -- BMC LAN IPv6 Configuration UEFI Driver
This DXE driver configures BMC LAN1 and LAN2 interfaces with static
IPv6 addresses and router addresses via the IPMI transport protocol
at boot time.
The driver:
1. Locates the IPMI Transport Protocol
2. Reads the "ServerSetup" UEFI NV variable for BMC IPv6 configuration
3. Enumerates available LAN channels via IPMI
4. For each configured LAN channel (LAN1, LAN2):
a. Checks the IPMI configuration status
b. Sets the IP address source (static or DHCP)
c. Programs the static IPv6 address
d. Programs the static IPv6 router/gateway address
5. Writes any modified setup data back to the "ServerSetup" variable
Copyright (c) Lenovo. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "Ip6BmcLanConfig.h"
//
// Global data
//
/// IPMI Transport Protocol interface pointer (located via gBS->LocateProtocol)
VOID *gIpmiTransport = NULL;
/// Debug output protocol pointer (lazy-initialized)
VOID *gDebugOutputProtocol = NULL;
/// PCD protocol pointer (lazy-initialized)
VOID *gPcdProtocol = NULL;
/// HOB list pointer (lazy-initialized from System Table)
VOID *gHobList = NULL;
//
// Memory operations
//
/**
Reads an unaligned 64-bit value from memory.
@param[in] Buffer Pointer to the memory location.
@return The 64-bit value read.
**/
UINT64
EFIAPI
ReadUnaligned64 (
IN CONST VOID *Buffer
)
{
ASSERT (Buffer != NULL);
return *(UINT64 *)Buffer;
}
/**
Copies memory -- Internal implementation.
Handles overlapping regions by copying backwards if source < destination.
@param[out] Destination Pointer to the destination buffer.
@param[in] Source Pointer to the source buffer.
@param[in] Length Number of bytes to copy.
@return Destination pointer.
**/
VOID *
EFIAPI
InternalCopyMem (
OUT VOID *Destination,
IN CONST VOID *Source,
IN UINTN Length
)
{
UINT8 *Dst8;
UINT8 *Src8;
if (Source < Destination && Source + Length > Destination) {
//
// Overlap: copy backwards
//
Src8 = (UINT8 *)Source + Length - 1;
Dst8 = (UINT8 *)Destination + Length - 1;
while (Length--) {
*Dst8-- = *Src8--;
}
} else {
//
// No overlap -- copy forward
//
CopyMem (Destination, Source, Length);
}
return Destination;
}
/**
Zeroes a block of memory -- Internal implementation.
@param[out] Buffer Pointer to the buffer to zero.
@param[in] Length Number of bytes to zero.
@return Buffer pointer.
**/
VOID *
EFIAPI
InternalZeroMem (
OUT VOID *Buffer,
IN UINTN Length
)
{
SetMem (Buffer, Length, 0);
return Buffer;
}
/**
Zeroes a block of memory (wrapper with bounds check).
@param[out] Buffer Pointer to the buffer to zero.
@param[in] Length Number of bytes to zero.
@return Buffer pointer.
**/
VOID *
EFIAPI
ZeroMem (
OUT VOID *Buffer,
IN UINTN Length
)
{
if (Buffer == NULL) {
ASSERT (Buffer != NULL);
return NULL;
}
ASSERT (Length <= (MAX_ADDRESS - (UINTN)Buffer + 1));
return InternalZeroMem (Buffer, Length);
}
/**
Copies memory (wrapper with bounds check).
@param[out] Destination Pointer to the destination buffer.
@param[in] Source Pointer to the source buffer.
@param[in] Length Number of bytes to copy.
@return Destination pointer.
**/
VOID *
EFIAPI
CopyMem (
OUT VOID *Destination,
IN CONST VOID *Source,
IN UINTN Length
)
{
if (Length == 0) {
return Destination;
}
ASSERT (Length - 1 <= (MAX_ADDRESS - (UINTN)Destination));
ASSERT (Length - 1 <= (MAX_ADDRESS - (UINTN)Source));
if (Destination == Source) {
return Destination;
}
return InternalCopyMem (Destination, Source, Length);
}
//
// Library helpers
//
/**
Returns the length of a null-terminated Unicode string.
@param[in] String Pointer to the Unicode string.
@return The number of Unicode characters (not including the null terminator).
**/
UINTN
EFIAPI
StrLen (
IN CONST CHAR16 *String
)
{
UINTN Length;
ASSERT (String != NULL);
ASSERT (((UINTN)String & 1) == 0);
Length = 0;
while (String[Length] != 0) {
if (Length >= MAX_UNICODE_STRING_LENGTH) {
ASSERT (Length < _gPcd_FixedAtBuild_PcdMaximumUnicodeStringLength);
}
Length++;
}
return Length;
}
/**
Returns the length of a null-terminated ASCII string.
@param[in] String Pointer to the ASCII string.
@return The number of ASCII characters (not including the null terminator).
**/
UINTN
EFIAPI
AsciiStrLen (
IN CONST CHAR8 *String
)
{
UINTN Length;
ASSERT (String != NULL);
Length = 0;
while (String[Length] != 0) {
if (Length >= MAX_ASCII_STRING_LENGTH) {
ASSERT (Length < _gPcd_FixedAtBuild_PcdMaximumAsciiStringLength);
}
Length++;
}
return Length;
}
/**
Returns the safe (bounded) length of a null-terminated ASCII string.
@param[in] String Pointer to the ASCII string.
@return The number of ASCII characters, or 1000001 if max exceeded.
**/
UINTN
EFIAPI
AsciiStrnLenS (
IN CONST CHAR8 *String
)
{
UINTN Length;
Length = 0;
if (String != NULL && *String != '\0') {
while (Length < MAX_ASCII_STRING_LENGTH) {
if (String[++Length] == '\0') {
return Length;
}
}
return 1000001;
}
return Length;
}
/**
Converts a hex character to its integer value.
Supports '0'-'9', 'A'-'F', 'a'-'f'.
@param[in] Char The hex character.
@return The integer value (0-15).
**/
UINTN
EFIAPI
HexCharToInt (
IN CHAR8 Char
)
{
if (Char >= '0' && Char <= '9') {
return Char - '0';
}
if (Char >= 'a' && Char <= 'f') {
Char -= 0x20;
}
return Char - 55;
}
/**
Converts an ASCII hexadecimal string to a UINT64 value.
Handles "0x" prefix, leading zeros, and whitespace trimming.
@param[in] String Pointer to the ASCII hex string.
@param[out] Data Pointer to receive the converted value.
@return EFI_STATUS.
**/
EFI_STATUS
EFIAPI
AsciiStrHexToUint64S (
IN CONST CHAR8 *String,
OUT UINT64 *Data
)
{
CONST CHAR8 *Str;
CHAR8 Char;
UINT64 Accumulator;
UINT64 MaxVal;
ASSERT (String != NULL);
ASSERT (Data != NULL);
if (AsciiStrnLenS (String, NULL) > MAX_ASCII_STRING_LENGTH) {
return RETURN_UNSUPPORTED;
}
Str = String;
while (*Str == ' ' || *Str == '\t') {
Str++;
}
while (*Str == '0') {
Str++;
}
Char = *Str;
if (Char >= 'a' && Char <= 'f') {
Char -= 0x20;
}
if (Char == 'X' && *(Str - 1) == '0') {
Str++;
goto ParseHex;
}
*Data = 0;
if (*(Str - 1) != '0') {
return RETURN_SUCCESS;
}
return RETURN_SUCCESS;
ParseHex:
*Data = 0;
MaxVal = MAX_UINT64;
while ((*Str >= '0' && *Str <= '9') ||
(*Str >= 'A' && *Str <= 'F') ||
(*Str >= 'a' && *Str <= 'f'))
{
Char = *Str;
if (Char >= 'a' && Char <= 'f') {
Char -= 0x20;
}
if (*Data > (MaxVal - HexCharToInt (Char)) >> 4) {
*Data = MaxVal;
return RETURN_BUFFER_TOO_SMALL;
}
*Data = (*Data << 4) + HexCharToInt (Char);
Str++;
}
return RETURN_SUCCESS;
}
/**
Converts a Unicode string to an ASCII string.
@param[in] Source Pointer to the source Unicode string.
@param[out] Destination Pointer to the destination ASCII buffer.
@return Pointer to the Destination buffer.
**/
CHAR8 *
EFIAPI
UnicodeStrToAsciiStr (
IN CONST CHAR16 *Source,
OUT CHAR8 *Destination
)
{
CHAR8 *Dest;
UINTN StrLenChars;
ASSERT (Destination != NULL);
ASSERT (StrSize (Source) != 0);
ASSERT ((UINTN)(Destination - (CHAR8 *)Source) >= StrSize (Source));
ASSERT ((UINTN)((CHAR8 *)Source - Destination) > StrLen (Source));
Dest = Destination;
while (*Source != 0) {
ASSERT (*Source < 0x100);
*Dest++ = (CHAR8)*Source++;
}
*Dest = 0;
ASSERT (AsciiStrSize (Destination) != 0);
return Destination;
}
//
// Debug output
//
/**
Retrieves (or lazily initializes) the debug output protocol.
Looks up the debug output protocol via the Boot Services table.
@return Pointer to the debug output protocol, or NULL if unavailable.
**/
DEBUG_OUTPUT_PROTOCOL *
GetDebugOutputProtocol (
VOID
)
{
EFI_STATUS Status;
if (gDebugOutputProtocol == NULL) {
//
// Check if we are running in SMM (or a restricted context) by probing CMOS.
//
if ((IoRead8 (0x71) > 3) || (IoRead8 (0x71) == 0 &&
(MmioRead8 (0xFDAF0490) & 2) == 0)) {
return NULL;
}
Status = gBS->LocateProtocol (
&gEfiDebugPortProtocolGuid,
NULL,
&gDebugOutputProtocol
);
if (EFI_ERROR (Status)) {
gDebugOutputProtocol = NULL;
}
}
return gDebugOutputProtocol;
}
/**
Debug print with level filtering.
Writes a formatted debug message via the debug output protocol,
if the error level matches the configured debug level.
@param[in] ErrorLevel Debug error level mask.
@param[in] Format Print format string.
@param[in] ... Variable arguments for format.
**/
VOID
EFIAPI
DebugPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VA_LIST VaList;
DEBUG_OUTPUT_PROTOCOL *Protocol;
Protocol = GetDebugOutputProtocol ();
if (Protocol != NULL) {
if ((ErrorLevel & gDebugLevel) != 0) {
VA_START (VaList, Format);
Protocol->DebugPrint (ErrorLevel, Format, VaList);
VA_END (VaList);
}
}
}
/**
Assert condition and halt on failure.
@param[in] FileName Source file name.
@param[in] LineNumber Line number in source file.
@param[in] Description Assert condition description.
**/
VOID
EFIAPI
DebugAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Description
)
{
DEBUG_OUTPUT_PROTOCOL *Protocol;
Protocol = GetDebugOutputProtocol ();
if (Protocol != NULL) {
Protocol->DebugAssert (FileName, LineNumber, Description);
}
}
/**
Allocates a buffer from pool.
@param[in] AllocationSize Size in bytes to allocate.
@return Pointer to the allocated buffer, or NULL.
**/
VOID *
EFIAPI
AllocatePool (
IN UINTN AllocationSize
)
{
EFI_STATUS Status;
VOID *Buffer;
Status = gBS->AllocatePool (EfiBootServicesData, AllocationSize, &Buffer);
if (EFI_ERROR (Status)) {
return NULL;
}
return Buffer;
}
/**
Frees a buffer previously allocated by AllocatePool.
@param[in] Buffer Pointer to the buffer to free.
**/
VOID
EFIAPI
FreePool (
IN VOID *Buffer
)
{
EFI_STATUS Status;
Status = gBS->FreePool (Buffer);
ASSERT_EFI_ERROR (Status);
}
//
// HOB (Hand-Off Block) helpers
//
/**
Reads a GUID header from memory at the given offset within the HOB list.
@param[in] Entry Pointer to the HOB entry.
@retval TRUE The HOB entry GUID matches the expected HOB list GUID.
@retval FALSE Mismatch.
**/
BOOLEAN
EFIAPI
IsHobGuidMatch (
IN VOID *Entry
)
{
EFI_GUID *HobGuid;
EFI_GUID *EntryGuid;
HobGuid = (EFI_GUID *)&gHobListGuid;
EntryGuid = (EFI_GUID *)Entry;
return (ReadUnaligned64 (HobGuid) == ReadUnaligned64 (EntryGuid) &&
ReadUnaligned64 ((UINT8 *)HobGuid + 8) == ReadUnaligned64 ((UINT8 *)EntryGuid + 8));
}
/**
Retrieves the HOB (Hand-Off Block) list pointer.
Walks the System Table configuration table entries looking for the
HOB list GUID. On first call, caches the result.
@return Pointer to the HOB list, or NULL if not found.
**/
VOID *
GetHobList (
VOID
)
{
UINTN Index;
if (gHobList == NULL) {
gHobList = NULL;
if (gST->NumberOfTableEntries > 0) {
for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
if (IsHobGuidMatch ((VOID *)&gST->ConfigurationTable[Index])) {
gHobList = (VOID *)gST->ConfigurationTable[Index].VendorTable;
break;
}
}
if (gHobList == NULL) {
DEBUG ((DEBUG_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
ASSERT (FALSE);
}
}
if (gHobList == NULL) {
ASSERT (gHobList != NULL);
}
}
return gHobList;
}
//
// PCD Protocol
//
/**
Retrieves (or lazily initializes) the PCD protocol.
@return Pointer to the PCD protocol, or NULL if not found.
**/
VOID *
GetPcdProtocol (
VOID
)
{
EFI_STATUS Status;
if (gPcdProtocol == NULL) {
Status = gBS->LocateProtocol (
&gPcdProtocolGuid,
NULL,
&gPcdProtocol
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (Status);
}
if (gPcdProtocol == NULL) {
ASSERT (gPcdProtocol != NULL);
}
}
return gPcdProtocol;
}
//
// IPMI Helpers
//
/**
Wrapper to submit an IPMI command with retry logic.
The IPMI transport protocol's SendCommand is called. If the IPMI
response indicates the channel medium is not ready (bit 1 of
completion code), we stall 1 ms and retry up to 10 times.
@param[in] Channel The IPMI channel number (low nibble).
@param[in] NetFn IPMI network function (e.g., 0x0C for transport).
@param[in] Cmd IPMI command code.
@param[in] Data Command data buffer.
@param[in] DataSize Size of command data.
@param[out] Response Response data buffer.
@param[out] RespSize On input: max size; on output: actual size.
@retval EFI_SUCCESS Command completed.
@retval EFI_TIMEOUT Medium not ready after retries.
@retval other IPMI transport error.
**/
EFI_STATUS
IpmiSendCommand (
IN UINT8 Channel,
IN UINT8 NetFn,
IN UINT8 Cmd,
IN VOID *Data,
IN UINT8 DataSize,
OUT VOID *Response,
OUT UINT8 *RespSize
)
{
UINT8 Retries;
EFI_STATUS Status;
UINT8 ChannelData[2];
UINT8 RespBuf;
ChannelData[0] = 0;
ChannelData[1] = 0;
Retries = 10;
ChannelData[0] = Channel & 0x0F;
while (TRUE) {
*RespSize = 2;
Status = ((IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport)->SendCommand (
(IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport,
NetFn,
0,
Cmd,
&ChannelData,
DataSize,
Response,
RespSize
);
if (EFI_ERROR (Status)) {
break;
}
if ((RespBuf & 3) == 0) {
return EFI_SUCCESS;
}
gBS->Stall (1000);
if (--Retries == 0) {
return EFI_TIMEOUT;
}
}
return Status;
}
/**
Checks the IPMI configuration status for a given LAN channel.
Sends an IPMI "Get LAN Configuration Parameters" command with
parameter 51 (channel medium type / status) to verify the channel
is operational.
@param[in] Channel The channel to check (low nibble).
@retval EFI_SUCCESS Channel is operational.
@retval EFI_NOT_FOUND Channel configuration is invalid.
@retval other IPMI transport error.
**/
EFI_STATUS
IpmiCheckConfigurationStatus (
IN UINT8 Channel
)
{
EFI_STATUS Status;
UINT8 RespSize;
UINT8 ParameterData[4];
UINT8 ResponseData[3];
UINT8 RespBuf;
ParameterData[0] = (Channel & 0x0F);
ParameterData[1] = 51;
ParameterData[2] = 0;
ParameterData[3] = 0;
RespSize = 2;
Status = IpmiSendCommand (
Channel,
IPMI_NETFN_TRANSPORT,
IPMI_LAN_GET_CONFIG_PARAMS,
ParameterData,
4,
&RespBuf,
&RespSize
);
if (!EFI_ERROR (Status)) {
if (*(UINT8 *)((UINTN)gIpmiTransport + 8) == IPMI_CC_NODE_BUSY || RespBuf == 0) {
return EFI_NOT_FOUND;
}
}
return Status;
}
/**
Sets the LAN configuration parameter for IPv6 on a given BMC channel
(IPMI LAN param 0x38 = 56).
@param[in] Channel The BMC LAN channel.
@return EFI_STATUS code from the IPMI transport.
**/
EFI_STATUS
IpmiSetLanConfig (
IN UINT8 Channel
)
{
EFI_STATUS Status;
UINT8 RespSize;
UINT8 ParameterData[3];
UINT8 RespBuf;
RespBuf = 0;
ParameterData[0] = Channel & 0x0F;
ParameterData[1] = 0x40;
ParameterData[2] = 0x02;
Status = IpmiSendCommand (
Channel,
IPMI_NETFN_TRANSPORT,
IPMI_LAN_SET_CONFIG_PARAMS,
ParameterData,
3,
NULL,
&RespSize
);
return Status;
}
/**
Sets IPMI LAN configuration parameters (IP address source).
First retrieves the current LAN configuration for the channel,
then modifies the IP source byte and writes it back.
@param[in] Channel The BMC LAN channel.
@param[in] EnableStatic TRUE to set IP source to static.
FALSE for DHCP.
@return EFI_STATUS code.
**/
EFI_STATUS
IpmiSetLanConfigParams (
IN UINT8 Channel,
IN BOOLEAN EnableStatic
)
{
EFI_STATUS Status;
UINT8 RespSize;
UINT8 RequestData[4];
UINT8 GetResp[4];
UINT8 SetParamBuffer[3 + 19];
UINT8 RespBuf;
RespBuf = 0;
Status = IpmiSendCommand (
Channel,
IPMI_NETFN_TRANSPORT,
IPMI_LAN_SUSPEND_BMC_ARPS,
NULL,
0,
NULL,
&RespSize
);
if (EFI_ERROR (Status)) {
return Status;
}
RequestData[0] = Channel & 0x0F;
RequestData[1] = 56;
RequestData[2] = 0;
RequestData[3] = 0;
RespSize = 21;
Status = ((IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport)->SendCommand (
(IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport,
IPMI_NETFN_TRANSPORT,
0,
IPMI_LAN_GET_CONFIG_PARAMS,
RequestData,
4,
GetResp,
&RespSize
);
if (EFI_ERROR (Status)) {
return Status;
}
SetParamBuffer[0] = Channel & 0x0F;
SetParamBuffer[1] = 56;
SetParamBuffer[2] = GetResp[1];
if (EnableStatic) {
SetParamBuffer[3] = 1 << 7;
} else {
SetParamBuffer[3] = 0;
}
CopyMem (&SetParamBuffer[4], &GetResp[3], 16);
SetParamBuffer[20] = GetResp[19];
Status = IpmiSendCommand (
Channel,
IPMI_NETFN_TRANSPORT,
IPMI_LAN_SUSPEND_BMC_ARPS,
NULL,
0,
NULL,
&RespSize
);
if (EFI_ERROR (Status)) {
return Status;
}
RespSize = 22;
return ((IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport)->SendCommand (
(IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport,
IPMI_NETFN_TRANSPORT,
0,
IPMI_LAN_SET_CONFIG_PARAMS,
SetParamBuffer,
22,
NULL,
&RespBuf
);
}
/**
Sets the static IPv6 address on a BMC LAN channel via IPMI.
Sends an IPMI "Set LAN Configuration Parameters" command with
parameter 56 (IPv6 address) and the 16-byte IPv6 address plus
prefix length.
@param[in] Channel The BMC LAN channel (low nibble).
@param[in] Ipv6Addr 17-byte buffer: 16 bytes IPv6 address + 1 byte
prefix length.
@param[in] PrefixLen The IPv6 prefix length.
@return EFI_STATUS code.
**/
EFI_STATUS
IpmiSetStaticIpv6Address (
IN UINT8 Channel,
IN UINT8 *Ipv6Addr,
IN UINT8 PrefixLen
)
{
EFI_STATUS Status;
UINT8 RespBuf;
UINT8 SetParamBuffer[22];
UINT8 RespSize;
UINT8 GetResponseData[4];
RespBuf = 0;
SetParamBuffer[0] = Channel & 0x0F;
SetParamBuffer[1] = 56;
SetParamBuffer[2] = 0;
SetParamBuffer[3] = 0x80;
CopyMem (&SetParamBuffer[4], Ipv6Addr, 16);
SetParamBuffer[20] = PrefixLen;
Status = IpmiSendCommand (
Channel,
IPMI_NETFN_TRANSPORT,
IPMI_LAN_SUSPEND_BMC_ARPS,
NULL,
0,
NULL,
&RespSize
);
if (EFI_ERROR (Status)) {
return Status;
}
RespSize = 22;
return ((IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport)->SendCommand (
(IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport,
IPMI_NETFN_TRANSPORT,
0,
IPMI_LAN_SET_CONFIG_PARAMS,
SetParamBuffer,
22,
NULL,
&RespBuf
);
}
/**
Sets the static IPv6 router (gateway) address on a BMC LAN channel
via IPMI.
Sends an IPMI "Set LAN Configuration Parameters" command with
parameter 65 (0x41 = IPv6 router address).
@param[in] Channel The BMC LAN channel (low nibble).
@param[in] RouterAddr 16-byte IPv6 router address.
@return EFI_STATUS code.
**/
EFI_STATUS
IpmiSetStaticRouterIpv6Address (
IN UINT8 Channel,
IN UINT8 *RouterAddr
)
{
EFI_STATUS Status;
UINT8 RespBuf;
UINT8 SetParamBuffer[18];
UINT8 RespSize;
UINT8 ChannelInfo[3];
RespBuf = 0;
ChannelInfo[0] = Channel & 0x0F;
ChannelInfo[1] = 0x40;
ChannelInfo[2] = 0x01;
Status = IpmiSendCommand (
Channel,
IPMI_NETFN_TRANSPORT,
IPMI_LAN_SUSPEND_BMC_ARPS,
NULL,
0,
NULL,
&RespSize
);
if (EFI_ERROR (Status)) {
return Status;
}
SetParamBuffer[0] = Channel & 0x0F;
SetParamBuffer[1] = 65;
CopyMem (&SetParamBuffer[2], RouterAddr, 16);
Status = IpmiSendCommand (
Channel,
IPMI_NETFN_TRANSPORT,
IPMI_LAN_SUSPEND_BMC_ARPS,
NULL,
0,
NULL,
&RespSize
);
if (EFI_ERROR (Status)) {
return Status;
}
RespSize = 18;
return ((IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport)->SendCommand (
(IPMI_TRANSPORT_PROTOCOL *)gIpmiTransport,
IPMI_NETFN_TRANSPORT,
0,
IPMI_LAN_SET_CONFIG_PARAMS,
SetParamBuffer,
18,
NULL,
&RespBuf
);
}
/**
Converts an ASCII IPv6 address string to 16 bytes of binary data.
Supports standard IPv6 text representation, including "::" zero
compression, and hex group notation. Stores the result as 8
16-bit values in network byte order (16 bytes total).
Accepted formats:
- x:x:x:x:x:x:x:x (8 groups)
- x::x (zero compression)
- ::x (leading zero compression)
- x:: (trailing zero compression)
@param[in] Str Pointer to the null-terminated ASCII string.
@param[out] Addr Buffer to receive the 16-byte IPv6 address.
@retval EFI_SUCCESS Conversion succeeded.
@retval EFI_INVALID_PARAMETER Invalid IPv6 address format.
**/
EFI_STATUS
StrToIp6Addr (
IN CHAR8 *Str,
OUT UINT8 *Addr
)
{
CHAR8 *StrPtr;
CHAR8 *Start;
CHAR8 *End;
UINT8 HexGroupIdx;
BOOLEAN HasCompression;
BOOLEAN CompressionHandled;
UINT8 GroupSize;
BOOLEAN IsFirstGroup;
UINT8 CompressCount;
UINT8 FirstGroupCheck;
UINT8 GroupIdx;
UINT16 HexValue;
UINT16 HexValueLo;
UINT8 DigitCount;
UINT8 SavedFirstGroup;
if (Str == NULL || Addr == NULL) {
return EFI_INVALID_PARAMETER;
}
StrPtr = Str;
HexGroupIdx = 0;
HasCompression = FALSE;
CompressionHandled = FALSE;
IsFirstGroup = TRUE;
CompressCount = 0;
FirstGroupCheck = 0;
SavedFirstGroup = 0;
if (*Str == ':') {
if (Str[1] != ':') {
return EFI_INVALID_PARAMETER;
}
goto StartParse;
}
StartParse:
ZeroMem (Addr, 16);
do {
Start = StrPtr;
End = StrPtr;
if (*StrPtr != ':') {
do {
if (*StrPtr == ':') {
break;
}
StrPtr++;
} while (*StrPtr != '\0');
}
if (*StrPtr == ':') {
if (StrPtr[1] == ':') {
if (HexGroupIdx > 6) {
return EFI_INVALID_PARAMETER;
}
StrPtr += 2;
if (*StrPtr != '\0') {
if (AsciiStrHexToUint64S (StrPtr, &HexValue) != 0 || HexValue == 0) {
return EFI_INVALID_PARAMETER;
}
}
if (HexGroupIdx == 6) {
if (*StrPtr != '\0') {
if (AsciiStrHexToUint64S (StrPtr, &HexValue) != 0 || HexValue == 0) {
return EFI_INVALID_PARAMETER;
}
}
}
while (*StrPtr != '\0') {
if (*StrPtr == ':') {
if (StrPtr[1] == ':') {
return EFI_INVALID_PARAMETER;
}
if (CompressCount >= (7 - HexGroupIdx)) {
return EFI_INVALID_PARAMETER;
}
CompressCount++;
}
StrPtr++;
}
HasCompression = TRUE;
CompressionHandled = TRUE;
FirstGroupCheck = 1;
} else {
if (StrPtr[1] == '\0') {
return EFI_INVALID_PARAMETER;
}
StrPtr++;
HexGroupIdx++;
if (HasCompression) {
if (HexGroupIdx > 6) {
return EFI_INVALID_PARAMETER;
}
} else {
if (HexGroupIdx > 7) {
return EFI_INVALID_PARAMETER;
}
}
}
}
AsciiStrHexToUint64S (Start, &HexValue);
HexValueLo = (UINT16)HexValue;
if (HexValue > 0xFFFF) {
return EFI_INVALID_PARAMETER;
}
//
// Validate leading zeros
//
if (HexValue != 0) {
if (*Start == '0') {
if (Start[2] == ':' || Start[3] == ':') {
return EFI_INVALID_PARAMETER;
}
if (Start[2] == '\0' || Start[3] == '\0') {
return EFI_INVALID_PARAMETER;
}
if (Start[4] != '\0' && Start[4] != ':') {
return EFI_INVALID_PARAMETER;
}
}
} else if (*Start == '0' && Start[1] == '0') {
if (Start[2] == ':' || Start[2] == '\0') {
return EFI_INVALID_PARAMETER;
}
if (Start[1] == '0' && Start[2] == '0') {
if (Start[3] == ':' || Start[3] == '\0') {
return EFI_INVALID_PARAMETER;
}
}
}
DigitCount = 0;
if (*Start != ':') {
do {
if (Start[DigitCount] == '\0') {
break;
}
DigitCount++;
} while (Start[DigitCount] != ':');
}
if (IsFirstGroup) {
if (DigitCount == 4) {
if (*Start == '0') {
IsFirstGroup = FALSE;
SavedFirstGroup = 1;
}
}
if (DigitCount != 4 || (IsFirstGroup && DigitCount == 4)) {
IsFirstGroup = FALSE;
}
}
Addr[HexGroupIdx * 2] = (UINT8)(HexValue >> 8);
Addr[HexGroupIdx * 2 + 1] = (UINT8)(HexValueLo);
if (HasCompression && CompressionHandled) {
HexGroupIdx = 2 * (6 - CompressCount);
CompressionHandled = FALSE;
}
HexGroupIdx += 2;
if (HexGroupIdx >= 15) {
if ((HasCompression || HexGroupIdx == 16) && *StrPtr == '\0') {
return EFI_SUCCESS;
}
return EFI_INVALID_PARAMETER;
}
} while (*StrPtr != '\0');
return EFI_INVALID_PARAMETER;
}
/**
Converts a Unicode IPv6 address string to binary IPv6 address.
Allocates an ASCII buffer, converts the Unicode string to ASCII,
parses it via StrToIp6Addr, then frees the allocated buffer.
@param[in] UnicodeStr Pointer to a Unicode string containing an
IPv6 address.
@param[out] Ipv6Addr Buffer to receive the 16-byte IPv6 address.
@retval EFI_SUCCESS Conversion succeeded.
@retval EFI_OUT_OF_RESOURCES Memory allocation failed.
@retval EFI_INVALID_PARAMETER Null parameter.
**/
EFI_STATUS
ConfigIpv6Address (
IN CONST CHAR16 *UnicodeStr,
OUT UINT8 *Ipv6Addr
)
{
CHAR8 *AsciiStr;
UINTN StrLenChars;
EFI_STATUS Status;
if (Ipv6Addr == NULL) {
return EFI_INVALID_PARAMETER;
}
StrLenChars = StrLen (UnicodeStr);
AsciiStr = (CHAR8 *)AllocatePool (StrLenChars + 1);
if (AsciiStr == NULL) {
return EFI_OUT_OF_RESOURCES;
}
UnicodeStrToAsciiStr (UnicodeStr, AsciiStr);
Status = StrToIp6Addr (AsciiStr, Ipv6Addr);
FreePool (AsciiStr);
return Status;
}
/**
Enumerates all available LAN channels via IPMI.
Probes channels 1-12 by sending the "Get LAN Configuration Parameters"
command with parameter 66 (channel medium type). Channels with medium
type 4 (Ethernet/IPMB) are collected.
@param[in] IpmiTransport Pointer to IPMI transport protocol.
@param[out] ChannelBuf Buffer to receive channel numbers.
@param[in,out] ChannelCount Max channels on input, actual count.
@retval EFI_SUCCESS Channels enumerated.
@retval EFI_INVALID_PARAMETER Null pointer or ChannelCount=0.
@retval EFI_NOT_FOUND No LAN channels found.
**/
EFI_STATUS
GetLanChannelNumber (
IN VOID *IpmiTransport,
OUT UINT8 *ChannelBuf,
IN OUT UINT8 *ChannelCount
)
{
UINT8 Channel;
UINT8 MaxChannels;
UINT8 ChannelNo;
UINT8 RespSize;
UINT8 Response[2];
EFI_STATUS Status;
UINT32 CCode;
UINT32 ChannelIdx;
Channel = 0;
ChannelNo = 0;
if (ChannelBuf == NULL || ChannelCount == NULL || *ChannelCount == 0) {
return EFI_INVALID_PARAMETER;
}
MaxChannels = *(UINT8 *)((*(UINTN (**)(UINTN))((UINTN)GetPcdProtocol() + 56))(185));
ChannelBuf = *(UINT8 **)((*(UINTN (**)(UINTN))((UINTN)GetPcdProtocol() + 40))(185));
if (ChannelBuf == NULL || MaxChannels == 0) {
return EFI_NOT_FOUND;
}
if (*ChannelBuf != 0) {
goto CopyOut;
}
ChannelNo = 1;
do {
RespSize = 10;
Status = ((IPMI_TRANSPORT_PROTOCOL *)IpmiTransport)->SendCommand (
(IPMI_TRANSPORT_PROTOCOL *)IpmiTransport,
6,
0,
0x42,
&ChannelNo,
1,
Response,
&RespSize
);
CCode = Response[0] & 0x0F;
ChannelIdx = Response[0];
DEBUG ((
DEBUG_INFO,
" %a:%r ChannelNo: %d, CompletionCode: %x\n",
"GetLanChannelNumber",
Status,
ChannelNo,
CCode
));
if (!EFI_ERROR (Status) && *(UINT8 *)((UINTN)IpmiTransport + 8) == 0) {
if ((Response[1] & 0x7F) == 4) {
DEBUG ((DEBUG_INFO, "%a: Channel Medium Type: %x\n",
"GetLanChannelNumber", Response[1] & 0x7F));
if (Channel < MaxChannels) {
ChannelBuf[Channel] = ChannelNo;
Channel++;
}
}
}
ChannelNo++;
} while (ChannelNo <= 12);
CopyOut:
if (*ChannelCount > MaxChannels) {
*ChannelCount = MaxChannels;
}
if (*ChannelCount != 0) {
CopyMem (ChannelBuf, ChannelBuf, *ChannelCount);
}
return EFI_SUCCESS;
}
//
// Main driver logic
//
/**
Main driver entry point -- configures BMC LAN1 and LAN2 with IPv6
addresses read from the ServerSetup UEFI variable.
Flow:
1. Locate the IPMI Transport Protocol
2. Read the "ServerSetup" variable from UEFI NVRAM
3. Enumerate LAN channels via GetLanChannelNumber
4. For LAN1 channel:
a. Check IPMI configuration status
b. If setup says LAN1 is enabled:
- Set IP source (static)
- Set LAN config
- Set static IPv6 address from setup data
- Set static router IPv6 address from setup data
5. For LAN2 channel:
a. Check IPMI configuration status
b. If setup says LAN2 is enabled:
- Set IP source (static)
- Set LAN config
- Set static IPv6 address from setup data
- Set static router IPv6 address from setup data
6. Write back the ServerSetup variable
@return EFI_STATUS code.
**/
EFI_STATUS
DriverEntryPoint (
VOID
)
{
EFI_STATUS Status;
UINT8 ChannelCount;
UINT8 ChannelBuffer[2];
UINT8 Lan1Channel;
UINT8 Lan2Channel;
UINT8 SetupBuffer[1072];
UINTN SetupSize;
BOOLEAN Lan1Enabled;
BOOLEAN Lan2Enabled;
CHAR16 Lan1Ipv6Str[40];
CHAR16 Lan1RouterStr[40];
CHAR16 Lan2Ipv6Str[40];
CHAR16 Lan2RouterStr[40];
UINT8 Ipv6AddrBuf[16];
EFI_STATUS ConfigStatus;
UINT32 Attributes;
ChannelCount = 2;
Lan1Enabled = FALSE;
Lan2Enabled = FALSE;
//
// 1. Locate the IPMI Transport Protocol
//
Status = gBS->LocateProtocol (
&gIpmiTransportProtocolGuid,
NULL,
&gIpmiTransport
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// 2. Read the ServerSetup variable (size 1072 bytes)
//
SetupSize = 1072;
Status = gRT->GetVariable (
L"ServerSetup",
&gServerSetupGuid,
&Attributes,
&SetupSize,
SetupBuffer
);
if (!EFI_ERROR (Status)) {
Lan1Enabled = SetupBuffer[SETUP_BMC_LAN1_ENABLE];
Lan2Enabled = SetupBuffer[SETUP_BMC_LAN2_ENABLE];
} else {
Lan1Enabled = 0;
Lan2Enabled = 0;
}
//
// 3. Enumerate LAN channels
//
if (gIpmiTransport != NULL) {
Status = GetLanChannelNumber (
gIpmiTransport,
ChannelBuffer,
&ChannelCount
);
} else {
Status = EFI_INVALID_PARAMETER;
}
DEBUG ((
DEBUG_INFO,
"%a ChannelNumberBuffer[0]: %x ChannelNumberBuffer[1]: %x ChannelCount = %x\n",
"DriverEntryPoint",
ChannelBuffer[0],
ChannelBuffer[1],
ChannelCount
));
if (EFI_ERROR (Status)) {
return Status;
}
Lan1Channel = ChannelBuffer[0];
Lan2Channel = ChannelBuffer[1];
//
// 4. Configure LAN1 (BMC LAN1)
//
if (Lan1Channel != 0) {
ConfigStatus = IpmiCheckConfigurationStatus (Lan1Channel);
DEBUG ((DEBUG_INFO,
"IPMI : BMC LAN1 Ip6 configuration Status: %r\n",
ConfigStatus));
if (EFI_ERROR (ConfigStatus)) {
SetupBuffer[SETUP_BMC_LAN1_ENABLE] = 0;
}
if (SetupBuffer[SETUP_BMC_LAN1_ENABLE]) {
SetupBuffer[SETUP_BMC_LAN1_ENABLE] = 0;
if (Lan1Enabled) {
IpmiSetLanConfigParams (Lan1Channel,
(SetupBuffer[SETUP_BMC_LAN1_DHCP] == 0) ? TRUE : FALSE);
IpmiSetLanConfig (Lan1Channel);
if (Lan1Enabled == 1) {
CopyMem (Lan1Ipv6Str,
&SetupBuffer[SETUP_BMC_LAN1_IPV6_ADDR],
80);
DEBUG ((DEBUG_INFO,
"InitializeIp6BmcLanConfig: BMC LAN1 Static Ipv6 Address: %S\n",
Lan1Ipv6Str));
ConfigIpv6Address (Lan1Ipv6Str, Ipv6AddrBuf);
IpmiSetStaticIpv6Address (
Lan1Channel,
Ipv6AddrBuf,
SetupBuffer[SETUP_BMC_LAN1_DHCP]
);
CopyMem (Lan1RouterStr,
&SetupBuffer[SETUP_BMC_LAN1_IPV6_ROUTER],
80);
DEBUG ((DEBUG_INFO,
"InitializeIp6BmcLanConfig: BMC LAN1 Static Router Ipv6 Address: %S\n",
Lan1RouterStr));
ConfigIpv6Address (Lan1RouterStr, Ipv6AddrBuf);
IpmiSetStaticRouterIpv6Address (Lan1Channel, Ipv6AddrBuf);
}
}
}
}
//
// 5. Configure LAN2 (BMC LAN2)
//
if (Lan2Channel != 0) {
ConfigStatus = IpmiCheckConfigurationStatus (Lan2Channel);
DEBUG ((DEBUG_INFO,
"IPMI: BMC LAN2 Ip6 configuration Status: %r\n",
ConfigStatus));
if (EFI_ERROR (ConfigStatus)) {
SetupBuffer[SETUP_BMC_LAN2_ENABLE] = 0;
}
if (SetupBuffer[SETUP_BMC_LAN2_ENABLE]) {
SetupBuffer[SETUP_BMC_LAN2_ENABLE] = 0;
if (Lan2Enabled) {
IpmiSetLanConfigParams (Lan2Channel,
(SetupBuffer[SETUP_BMC_LAN2_DHCP] == 0) ? TRUE : FALSE);
IpmiSetLanConfig (Lan2Channel);
if (Lan2Enabled == 1) {
CopyMem (Lan2Ipv6Str,
&SetupBuffer[SETUP_BMC_LAN2_IPV6_ADDR],
80);
DEBUG ((DEBUG_INFO,
"InitializeIp6BmcLanConfig: BMC LAN2 Static Ipv6 Address: %S\n",
Lan2Ipv6Str));
ConfigIpv6Address (Lan2Ipv6Str, Ipv6AddrBuf);
IpmiSetStaticIpv6Address (
Lan2Channel,
Ipv6AddrBuf,
SetupBuffer[SETUP_BMC_LAN2_DHCP]
);
CopyMem (Lan2RouterStr,
&SetupBuffer[SETUP_BMC_LAN2_IPV6_ROUTER],
80);
DEBUG ((DEBUG_INFO,
"InitializeIp6BmcLanConfig: BMC LAN2 Static Router Ipv6 Address: %S\n",
Lan2RouterStr));
ConfigIpv6Address (Lan2RouterStr, Ipv6AddrBuf);
IpmiSetStaticRouterIpv6Address (Lan2Channel, Ipv6AddrBuf);
}
}
}
}
//
// 6. Write back the ServerSetup variable
//
gRT->SetVariable (
L"ServerSetup",
&gServerSetupGuid,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
1072,
SetupBuffer
);
return EFI_SUCCESS;
}
/**
UEFI DXE Driver Entry Point.
Initializes global state (gImageHandle, gSystemTable, gBS, gRT)
through the standard UEFI boot/runtime services library, then
calls the HOB library initialization and DriverEntryPoint.
@param[in] ImageHandle EFI image handle.
@param[in] SystemTable EFI system table.
@return EFI_STATUS from DriverEntryPoint.
**/
EFI_STATUS
EFIAPI
_ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// Standard UEFI prologue: save image handle and system table
//
gImageHandle = ImageHandle;
ASSERT (gImageHandle != NULL);
gST = SystemTable;
ASSERT (gST != NULL);
gBS = SystemTable->BootServices;
ASSERT (gBS != NULL);
gRT = SystemTable->RuntimeServices;
ASSERT (gRT != NULL);
//
// Initialize HOB list (must happen before any HOB accesses)
//
GetHobList ();
//
// Run the main driver logic
//
return DriverEntryPoint ();
}