Newer
Older
AMI-Aptio-BIOS-Reversed / BmcLanConfig / BmcLanConfig.c
@Ajax Dong Ajax Dong 2 days ago 28 KB Init
/** @file
  BmcLanConfig.c - BMC LAN Configuration UEFI Driver

  This DXE driver initializes BMC LAN channel settings at boot time.
  It enumerates all available LAN channels on the BMC and configures
  their IP address, subnet mask, default gateway IP, and gateway MAC address
  by sending IPMI commands via the IPMI transport protocol.

  The configuration parameters (IP addresses, subnet masks, gateway MAC)
  are read from a "ServerSetup" HII configuration data structure embedded
  in the firmware's NVRAM variables.

  Copyright (c) Lenovo. All rights reserved.
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Uefi.h>
#include <Protocol/IpmiTransportProtocol.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/BaseLib.h>
#include <Library/UefiLib.h>

//
// ---------------------------------------------------------------------------
// IPMI LAN Configuration Parameter Selectors
// (IPMI Specification v2.0, Section 18, LAN Configuration Parameters)
// ---------------------------------------------------------------------------
//
#define IPMI_LAN_PARAM_IP_ADDRESS       3   // Station IP address (4 bytes)
#define IPMI_LAN_PARAM_SUBNET_MASK      6   // Subnet mask (4 bytes)
#define IPMI_LAN_PARAM_GATEWAY_IP      12   // Default gateway IP (4 bytes)
#define IPMI_LAN_PARAM_GATEWAY_MAC     13   // Default gateway MAC (6 bytes)

#define IPMI_LAN_NETFN                  0x0C    // IPMI LAN network function
#define IPMI_APP_NETFN                  0x06    // IPMI Application network function
#define IPMI_CC_SUCCESS                 0x00
#define IPMI_LUN_APP                    0  // Application LUN
#define IPMI_LUN_BMC                    1  // BMC LUN
#define IPMI_LUN_OEM                    2  // OEM LUN

#define IPMI_RETRY_MAX                  10
#define IPMI_RETRY_DELAY_MS             1000

//
// ---------------------------------------------------------------------------
// Global protocol pointers (stored in .data section, runtime BSS)
// ---------------------------------------------------------------------------
//
// The IPMI transport protocol interface, located by LocateProtocol()
// using the GUID at unk_1F20 ({0x36232936, ...}).
//
EFI_IPMI_TRANSPORT_PROTOCOL  *mIpmiTransport = NULL;  // qword_23F0

// The HOB list pointer, located from the system table configuration table
VOID                          *mHobList = NULL;        // qword_1FA8

// The debug output protocol, located from HOB
VOID                          *mDebugProtocol = NULL;  // (internal)

// The PCD protocol interface
VOID                          *mPcdProtocol = NULL;    // qword_1FB0

//
// ---------------------------------------------------------------------------
// Forward declarations
// ---------------------------------------------------------------------------
//
EFI_STATUS
InitializeBmcLanConfig (
  VOID
  );


// ===========================================================================
// Internal helper functions (MdePkg library wrappers)
// ===========================================================================

/**
  Reads a UINT64 from an unaligned pointer, asserting the pointer is non-NULL.
**/
UINT64
EFIAPI
ReadUnaligned64 (
  CONST VOID  *Buffer
  )
{
  ASSERT (Buffer != NULL);
  return *(CONST UINT64 *)Buffer;
}

/**
  Compares two GUID structures by comparing their first and second QWORDs.
**/
BOOLEAN
EFIAPI
IsGuidMatch (
  CONST GUID  *Guid1,
  CONST GUID  *Guid2
  )
{
  return ReadUnaligned64 (Guid1) == ReadUnaligned64 (Guid2) &&
         ReadUnaligned64 ((UINT8 *)Guid1 + 8) == ReadUnaligned64 ((UINT8 *)Guid2 + 8);
}

/**
  Copies a memory buffer, handling overlapping source and destination.
**/
VOID *
EFIAPI
InternalCopyMem (
  VOID        *Destination,
  CONST VOID  *Source,
  UINTN       Length
  )
{
  UINT8 *Dst8;
  UINT8 *Src8;
  UINTN  AlignedCount;
  UINTN  Remaining;

  if (Source < Destination && (UINT8 *)Source + Length > (UINT8 *)Destination) {
    //
    // Overlap: Source starts before Destination and extends into it.
    // Copy from end to beginning.
    //
    Src8 = (UINT8 *)Source + Length - 1;
    Dst8 = (UINT8 *)Destination + Length - 1;
    while (Length--) {
      *Dst8-- = *Src8--;
    }
  } else {
    //
    // No overlap or Destination starts before Source.
    // Copy from beginning, using qmemcpy for aligned 8-byte chunks.
    //
    AlignedCount = Length >> 3;
    Remaining    = Length & 7;
    qmemcpy (Destination, Source, AlignedCount * 8);
    Src8 = (UINT8 *)Source + AlignedCount * 8;
    Dst8 = (UINT8 *)Destination + AlignedCount * 8;
    while (Remaining--) {
      *Dst8++ = *Src8++;
    }
  }

  return Destination;
}

/**
  Copies memory with bounds-check assertions.

  @param[out] DestinationBuffer  Pointer to the destination.
  @param[in]  SourceBuffer       Pointer to the source.
  @param[in]  Length             Number of bytes to copy.

  @return DestinationBuffer.
**/
VOID *
EFIAPI
CopyMemWithAssert (
  VOID        *DestinationBuffer,
  CONST VOID  *SourceBuffer,
  UINTN       Length
  )
{
  UINTN Index;

  Index = Length - 1;
  ASSERT ((Length - 1) <= (MAX_ADDRESS - (UINTN)DestinationBuffer));
  ASSERT ((Length - 1) <= (MAX_ADDRESS - (UINTN)SourceBuffer));

  if (DestinationBuffer == SourceBuffer) {
    return DestinationBuffer;
  }

  return InternalCopyMem (DestinationBuffer, SourceBuffer, Length);
}


// ===========================================================================
// HOB list and protocol locator functions
// ===========================================================================

/**
  Locates the HOB (Hand-Off Block) list pointer from the UEFI system table
  configuration table, using gEfiHobListGuid.

  @return  Pointer to the HOB list, or NULL if not found.
**/
VOID *
GetHobList (
  VOID
  )
{
  UINTN      Index;

  if (mHobList != NULL) {
    return mHobList;
  }

  mHobList = NULL;

  for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
    if (IsGuidMatch (&gEfiHobListGuid, gST->ConfigurationTable[Index].VendorGuid)) {
      mHobList = gST->ConfigurationTable[Index].VendorTable;
      break;
    }
  }

  if (mHobList == NULL) {
    DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
    ASSERT_EFI_ERROR (EFI_NOT_FOUND);
  }

  ASSERT (mHobList != NULL);
  return mHobList;
}

/**
  Locates and returns the debug output protocol interface.

  @return  Pointer to the debug output protocol, or NULL if not found.
**/
VOID *
GetDebugOutputProtocol (
  VOID
  )
{
  EFI_STATUS  Status;

  if (mDebugProtocol != NULL) {
    return mDebugProtocol;
  }

  //
  // Allocate pool for the HOB protocol lookup, using BootServices.
  // If the HOB list size is 16 bytes or less (platform-specific check),
  // use LocateProtocol for the debug output protocol instead.
  //
  if (gBS->CalculateEfiHobListSize (mHobList) <= sizeof (UINT64) + sizeof (UINT64)) {
    Status = gBS->LocateProtocol (
                    &gEfiDebugSupportProtocolGuid,
                    NULL,
                    &mDebugProtocol
                    );
    if (EFI_ERROR (Status)) {
      mDebugProtocol = NULL;
    }
  }

  return mDebugProtocol;
}

/**
  Locates and returns the PCD protocol interface.

  @return  Pointer to the PCD protocol, or NULL if not found.
**/
VOID *
GetPcdProtocol (
  VOID
  )
{
  EFI_STATUS  Status;

  if (mPcdProtocol != NULL) {
    return mPcdProtocol;
  }

  Status = gBS->LocateProtocol (
                  &gEfiPcdProtocolGuid,
                  NULL,
                  &mPcdProtocol
                  );

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
    ASSERT_EFI_ERROR (Status);
  }

  ASSERT (mPcdProtocol != NULL);
  return mPcdProtocol;
}


// ===========================================================================
// IPMI Transport Protocol Command Functions
// ===========================================================================

/**
  Sends an IPMI command to the BMC, retrying on transient failures.

  The command uses the LAN network function (0x0C) and the given LUN.
  If the BMC response indicates a "busy" condition (lower 2 bits != 0),
  the function waits 1 second and retries, up to 10 retries.

  @param[in]  ChannelNumber   The IPMB channel number.
  @param[in]  CommandData     Pointer to the IPMI command data (after NetFn/LUN byte).
  @param[in]  DataSize        Size of CommandData in bytes.
  @param[in]  Lun             The logical unit number (typically 2 for OEM).

  @retval EFI_SUCCESS       The command completed and the BMC responded.
  @retval EFI_TIMEOUT       The BMC was busy after 10 retries.
  @retval Others            Error propagated from the IPMI transport protocol.
**/
EFI_STATUS
IpmiSendCommandWithRetry (
  IN UINT8   ChannelNumber,
  IN UINT8   *CommandData,
  IN UINT8   DataSize,
  IN UINT8   Lun
  )
{
  UINT8        Retries;
  EFI_STATUS   Status;
  UINT8        ResponseBuffer[4];
  UINTN        ResponseSize;

  Retries = IPMI_RETRY_MAX;
  ResponseSize = sizeof (ResponseBuffer);

  do {
    Status = mIpmiTransport->SendCommand (
                               mIpmiTransport,
                               IPMI_LAN_NETFN,
                               0,
                               Lun,
                               CommandData,
                               DataSize,
                               ResponseBuffer,
                               &ResponseSize
                               );

    if (EFI_ERROR (Status)) {
      break;
    }

    if ((ResponseBuffer[1] & 0x03) != 0) {
      //
      // bits [1:0] of second byte = busy flag from BMC
      //
      gBS->Stall (IPMI_RETRY_DELAY_MS * 1000);
      Retries--;
    } else {
      break;
    }
  } while (Retries > 0);

  if (Retries == 0) {
    return EFI_TIMEOUT;
  }

  return Status;
}

/**
  Sets a LAN configuration parameter on the BMC.

  The parameter is identified by a selector byte (e.g., 3 = Station IP,
  6 = Subnet Mask, 12 = Gateway IP, 13 = Gateway MAC).

  @param[in]  ChannelNumber   The LAN channel to configure.
  @param[in]  ParameterData   Pointer to the parameter value bytes.
  @param[in]  ParameterSize   Size of the parameter value in bytes.
  @param[in]  ParameterSel    The IPMI LAN parameter selector.

  @retval EFI_STATUS from the IPMI transport.
**/
EFI_STATUS
IpmiSetLanConfigParamBytes (
  IN UINT8   ChannelNumber,
  IN UINT8   *ParameterData,
  IN UINT8   ParameterSize,
  IN UINT8   ParameterSel
  )
{
  UINT8   RequestBuffer[20];
  UINT8   ResponseData[1];
  UINTN   ResponseSize;

  RequestBuffer[0] = ChannelNumber;
  RequestBuffer[1] = ParameterSel;

  if (ParameterSize > 0) {
    CopyMemWithAssert (&RequestBuffer[2], ParameterData, ParameterSize);
  }

  //
  // Set LAN Configuration Parameters command:
  //   NetFn: 0x0C (LAN)
  //   Cmd:   0x01 (Set LAN Configuration Parameters)
  //   LUN:   1 (BMC)
  //
  ResponseSize = 1;
  return mIpmiTransport->SendCommand (
                           mIpmiTransport,
                           IPMI_LAN_NETFN,
                           0x01,
                           IPMI_LUN_BMC,
                           RequestBuffer,
                           2 + ParameterSize,
                           ResponseData,
                           &ResponseSize
                           );
}

/**
  Retrieves a LAN configuration parameter from the BMC.

  @param[in]  ChannelNumber      The LAN channel to query.
  @param[out] ParameterValue     Pointer to receive the parameter value.
  @param[in]  ParameterSelector  The LAN parameter selector.
  @param[in]  SetSelector        The set number (0 = current, 20 = set #0 revision).

  @retval EFI_STATUS from the IPMI transport.
**/
EFI_STATUS
IpmiGetLanConfigParamBytes (
  IN  UINT8   ChannelNumber,
  OUT UINT16  *ParameterValue,
  IN  UINT8   ParameterSelector,
  IN  UINT8   SetSelector
  )
{
  UINT8       RequestBuffer[3];
  UINT8       ResponseBuffer[4];
  UINTN       ResponseSize;
  EFI_STATUS  Status;

  RequestBuffer[0] = ChannelNumber;
  RequestBuffer[1] = ParameterSelector;
  RequestBuffer[2] = SetSelector;

  ResponseSize = sizeof (ResponseBuffer);
  Status = mIpmiTransport->SendCommand (
                             mIpmiTransport,
                             IPMI_LAN_NETFN,
                             0x02,         // Get LAN Configuration Parameters
                             IPMI_LUN_OEM,
                             RequestBuffer,
                             sizeof (RequestBuffer),
                             ResponseBuffer,
                             &ResponseSize
                             );

  if (!EFI_ERROR (Status)) {
    //
    // Response format: bit 7 = parameter revision flag
    // If bit 7 set: value is byte[1] | ((byte[2] & 0x3F) << 8)
    // If bit 7 clear: parameter is not supported / invalid
    //
    if (ResponseBuffer[0] & 0x80) {
      *ParameterValue = ResponseBuffer[1] | ((ResponseBuffer[2] & 0x3F) << 8);
    } else {
      *ParameterValue = 0;
    }
  }

  return Status;
}

/**
  Queries the BMC for the current channel number.

  @param[in]  ChannelNumber   IPMB channel to query.
  @param[in]  Lun             Logical unit number.
  @param[in]  RetryCount      (Unused in current implementation).
  @param[in]  RetryInterval   (Unused in current implementation).

  @return The channel number from the BMC, or 0 on error.
**/
UINT8
IpmiGetChannelNumber (
  IN UINT8   ChannelNumber,
  IN UINT16  Lun,
  IN UINT8   RetryCount,
  IN UINT8   RetryInterval
  )
{
  UINT8   RequestBuffer[2];
  UINT8   ResponseBuffer[3];
  UINTN   ResponseSize;

  RequestBuffer[0] = ChannelNumber;
  if (Lun != 0) {
    RequestBuffer[1] = (UINT8)Lun;
    RequestBuffer[1] = (UINT8)(Lun >> 8) | 0x80;
  } else {
    RequestBuffer[1] = 0;
  }

  IpmiSendCommandWithRetry (ChannelNumber, RequestBuffer, sizeof (RequestBuffer), 0);

  ResponseSize = sizeof (ResponseBuffer);
  mIpmiTransport->SendCommand (
    mIpmiTransport,
    IPMI_LAN_NETFN,
    0,
    IPMI_LUN_BMC,
    RequestBuffer,
    sizeof (RequestBuffer),
    ResponseBuffer,
    &ResponseSize
    );

  return 0;
}


// ===========================================================================
// LAN Channel Discovery
// ===========================================================================

/**
  Enumerates all available LAN channels on the BMC.

  The function scans IPMB channels 1 through 11 and identifies those
  with medium type 4 (e.g., dedicated LAN-over-USB / NC-SI / sideband).
  It calls GetLanChannelNumber (application command) on each candidate
  channel and checks the response.

  @param[in]      IpmiProtocol     Pointer to the IPMI transport protocol.
  @param[out]     ChannelBuffer    Buffer to receive discovered channel numbers.
  @param[in,out]  ChannelCount     On input: max channels the buffer can hold.
                                   On output: number of channels actually found.

  @retval EFI_SUCCESS           One or more channels were found.
  @retval EFI_INVALID_PARAMETER IpmiProtocol or ChannelBuffer was NULL.
  @retval EFI_NOT_FOUND         No channels with medium type 4 were found.
**/
EFI_STATUS
GetLanChannelNumbers (
  IN     VOID    *IpmiProtocol,
  OUT    UINT8   *ChannelBuffer,
  IN OUT UINT8   *ChannelCount
  )
{
  UINT8       CandidateChannel;
  UINT8       FoundCount;
  UINT8       MaxChannels;
  UINT8       MediumType;
  UINT8       CompletionCode;
  UINT8       ResponseByte;
  UINTN       ResponseSize;
  EFI_STATUS  Status;

  FoundCount = 0;
  CandidateChannel = 1;

  if (IpmiProtocol == NULL || ChannelBuffer == NULL || *ChannelCount == 0) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Query the IPMI transport's capabilities for max channels and medium type.
  // (The exact IPMI commands vary by platform.)
  //
  MaxChannels = ((EFI_IPMI_TRANSPORT_PROTOCOL *)IpmiProtocol)->GetMaxChannels (185);
  MediumType  = ((EFI_IPMI_TRANSPORT_PROTOCOL *)IpmiProtocol)->GetChannelMediumType (185);

  if (MaxChannels == 0 || MediumType == 0) {
    return EFI_NOT_FOUND;
  }

  //
  // Scan candidate channels 1 through 11
  //
  do {
    //
    // Application command: Get Channel Number
    // NetFn: 0x06 (Application), Cmd: 0x42 (Get Channel Number)
    //
    CompletionCode = IPMI_CC_SUCCESS;
    ResponseSize = 1;
    Status = ((EFI_IPMI_TRANSPORT_PROTOCOL *)IpmiProtocol)->SendCommand (
        IpmiProtocol,
        IPMI_APP_NETFN,
        0x42,
        66,                          // OEM-specific LUN/target
        &CandidateChannel,
        1,
        &CompletionCode,             // completion code
        &ResponseByte
        );

    DEBUG ((
      EFI_D_INFO,
      " %a:%r ChannelNo: %d, CompletionCode: %x\n",
      "GetLanChannelNumber",
      Status,
      CandidateChannel,
      CompletionCode
      ));

    if (!EFI_ERROR (Status) && CompletionCode == 0 && (ResponseByte & 0x7F) == 4) {
      //
      // Medium type bits [6:0] = 4 means this channel supports the
      // dedicated LAN (NC-SI or USB-over-LAN) medium.
      //
      DEBUG ((EFI_D_INFO, "%a: Channel Medium Type: %x\n", "GetLanChannelNumber", ResponseByte & 0x7F));

      if (FoundCount < MediumType) {
        ChannelBuffer[FoundCount++] = CandidateChannel;
      }
    }

    CandidateChannel++;
  } while (CandidateChannel <= 11);

  if (CandidateChannel > 11 && FoundCount == 0) {
    return EFI_NOT_FOUND;
  }

  if (*ChannelCount > MaxChannels) {
    *ChannelCount = (UINT8)MaxChannels;
  }

  if (*ChannelCount > 0) {
    //
    // The channel buffer already has the correct values from the scan,
    // so this is effectively a length adjustment.
    //
    CopyMemWithAssert (ChannelBuffer, ChannelBuffer, *ChannelCount);
  }

  return EFI_SUCCESS;
}


// ===========================================================================
// ASCII String Parsers for IPMI Configuration
// ===========================================================================

/**
  Parses an ASCII dotted-decimal IPv4 address string into 4 octets.

  The string is in UTF-16 format (as stored in UEFI HII configuration).
  Example input: L"192.168.1.100"

  @param[out] Octets       Buffer to receive the 4 IP octets.
  @param[in]  AddrString   Pointer to the null-terminated UTF-16 string.

  @retval EFI_SUCCESS           Parsing succeeded.
  @retval EFI_INVALID_PARAMETER The string was malformed or value out of range.
**/
EFI_STATUS
AsciiIpv4AddrToBytes (
  OUT UINT8   *Octets,
  IN  UINT16  *AddrString
  )
{
  UINT8   SegmentIndex;
  UINT8   Accumulator;
  UINT8   DigitCount;
  BOOLEAN HasDigits;
  UINT16  CheckSum;
  UINT8   Multiplier;
  CHAR16  Ch;
  INTN    StringLen;
  INTN    Index;

  HasDigits    = FALSE;
  SegmentIndex = 3;       // start from last octet (parse backwards)
  Accumulator  = 0;
  CheckSum     = 0;
  Multiplier   = 1;
  DigitCount   = 0;

  //
  // Determine string length (cap at 15 chars to avoid pathological inputs)
  //
  StringLen = 0;
  while (AddrString[StringLen] != 0) {
    StringLen++;
  }
  if (StringLen > 15) {
    StringLen = 15;
  }

  Index = StringLen - 1;

  while (Index >= 0) {
    Ch = AddrString[Index];

    if (Ch >= L'0' && Ch <= L'9') {
      HasDigits = TRUE;

      //
      // Validate value ranges: max 3 digits, max value 255
      //
      if (DigitCount > 2) {
        goto ParseError;
      }

      if (DigitCount == 2) {
        if ((Ch - L'0') > 2) {
          goto ParseError;
        }
        if (Ch == L'2') {
          if ((AddrString[Index + 1] - L'0') > 5) {
            goto ParseError;
          }
          if (AddrString[Index + 1] == L'5' && AddrString[Index + 2] > L'5') {
            goto ParseError;
          }
        }
      }

      Accumulator += Multiplier * (Ch - L'0');
      Multiplier  *= 10;
      DigitCount++;
    } else if (Ch == L'.') {
      if (!HasDigits || AddrString[Index + 1] == L'.') {
        goto ParseError;
      }
      Octets[SegmentIndex--] = Accumulator;
      CheckSum += Accumulator;
      if (SegmentIndex > 3) {
        goto ParseError;
      }
      Accumulator = 0;
      Multiplier  = 1;
      DigitCount  = 0;
    } else if (Ch == 0 && !HasDigits) {
      //
      // Skip leading null characters (padding)
      //
      goto NextChar;
    } else {
      goto ParseError;
    }

NextChar:
    Index--;
  }

  if (SegmentIndex == 0) {
    Octets[0] = Accumulator;
    CheckSum += Accumulator;
    if (HasDigits && CheckSum != 0) {
      return EFI_SUCCESS;
    }
  }

ParseError:
  //
  // Zero out output on parse failure
  //
  *(UINT32 *)Octets = 0;
  return EFI_INVALID_PARAMETER;
}

/**
  Parses an ASCII MAC address string into 6 bytes.

  The string is in UTF-16 format. Accepted format uses dashes as
  separators, e.g., L"00-11-22-33-44-55".

  @param[out] MacBytes    Buffer to receive the 6 MAC bytes.
  @param[in]  MacString   Pointer to the null-terminated UTF-16 string.

  @retval EFI_SUCCESS           Parsing succeeded.
  @retval EFI_INVALID_PARAMETER The string was malformed.
**/
EFI_STATUS
AsciiMacAddrToBytes (
  OUT UINT8   *MacBytes,
  IN  UINT16  *MacString
  )
{
  UINT8   SegmentIndex;
  UINT8   Accumulator;
  UINT8   DigitCount;
  BOOLEAN HasDigits;
  UINT16  CheckSum;
  UINT8   Multiplier;
  CHAR16  Ch;
  INTN    Index;

  HasDigits    = FALSE;
  SegmentIndex = 5;         // start from last byte (parse backwards)
  Accumulator  = 0;
  CheckSum     = 0;
  Multiplier   = 1;
  DigitCount   = 0;
  Index        = 16;

  while (TRUE) {
    Ch = MacString[Index];

    if (Ch >= L'0' && Ch <= L'9') {
      Ch = Ch - L'0';
    } else if (Ch >= L'A' && Ch <= L'F') {
      Ch = Ch - L'A' + 10;
    } else if (Ch >= L'a' && Ch <= L'f') {
      Ch = Ch - L'a' + 10;
    } else if (Ch == L'-') {
      //
      // Dash separator: commit the current byte
      //
      if (!HasDigits || MacString[Index + 1] == L'-') {
        goto ParseError;
      }
      MacBytes[SegmentIndex] = Accumulator;
      CheckSum += Accumulator;
      if (SegmentIndex == 0) {
        goto ParseError;
      }
      SegmentIndex--;
      Accumulator = 0;
      Multiplier  = 1;
      DigitCount  = 0;
      Index--;
      continue;
    } else if (Ch == 0) {
      //
      // End of string: commit final byte
      //
      if (HasDigits) {
        MacBytes[SegmentIndex] = Accumulator;
        CheckSum += Accumulator;
      }
      break;
    } else {
      goto ParseError;
    }

    //
    // Process hex nibble
    //
    HasDigits = TRUE;
    if (DigitCount > 1) {
      goto ParseError;
    }
    Accumulator += Multiplier * Ch;
    Multiplier *= 16;
    DigitCount++;
    Index--;
  }

  if (SegmentIndex == 0 && HasDigits && CheckSum != 0) {
    return EFI_SUCCESS;
  }

ParseError:
  *(UINT32 *)MacBytes = 0;
  *(UINT16 *)(MacBytes + 4) = 0;
  return EFI_INVALID_PARAMETER;
}


// ===========================================================================
// IPMI LAN Configuration (Parameter Block Programming)
// ===========================================================================

/**
  Programs a complete set of LAN parameters for a given channel.

  The configuration data is organized as a 19-byte block per channel:
    byte[0]  = Channel number
    byte[1..4]   = Station IP address (4 bytes)
    byte[5..8]   = Subnet mask (4 bytes)
    byte[9..12]  = Gateway IP address (4 bytes)
    byte[13..18] = Gateway MAC address (6 bytes)

  @param[in]  ChannelNumber  The LAN channel to configure.
  @param[in]  ConfigBlock    Pointer to the 19-byte configuration block.

  @retval EFI_SUCCESS       All parameters were programmed.
  @retval Others            Error from IPMI transport.
**/
EFI_STATUS
IpmiSetLanConfigParams (
  IN UINT8   ChannelNumber,
  IN UINT8   *ConfigBlock
  )
{
  //
  // Parameter 3: Station IP Address (4 bytes at offset 1)
  //
  IpmiSendCommandWithRetry (
    ChannelNumber,
    ConfigBlock + 1,
    4,
    IPMI_LUN_BMC
    );
  IpmiSetLanConfigParamBytes (
    ChannelNumber,
    ConfigBlock + 1,
    4,
    IPMI_LAN_PARAM_IP_ADDRESS
    );

  //
  // Parameter 6: Subnet Mask (4 bytes at offset 5)
  //
  IpmiSendCommandWithRetry (
    ChannelNumber,
    ConfigBlock + 5,
    4,
    IPMI_LUN_BMC
    );
  IpmiSetLanConfigParamBytes (
    ChannelNumber,
    ConfigBlock + 5,
    4,
    IPMI_LAN_PARAM_SUBNET_MASK
    );

  //
  // Parameter 12: Default Gateway IP (4 bytes at offset 9)
  //
  IpmiSendCommandWithRetry (
    ChannelNumber,
    ConfigBlock + 9,
    4,
    IPMI_LUN_BMC
    );
  IpmiSetLanConfigParamBytes (
    ChannelNumber,
    ConfigBlock + 9,
    4,
    IPMI_LAN_PARAM_GATEWAY_IP
    );

  //
  // Parameter 13: Default Gateway MAC (6 bytes at offset 13)
  //
  IpmiSendCommandWithRetry (
    ChannelNumber,
    ConfigBlock + 13,
    6,
    IPMI_LUN_BMC
    );
  IpmiSetLanConfigParamBytes (
    ChannelNumber,
    ConfigBlock + 13,
    6,
    IPMI_LAN_PARAM_GATEWAY_MAC
    );

  return EFI_SUCCESS;
}


// ===========================================================================
// Main Entry Point: InitializeBmcLanConfig
// ===========================================================================

/**
  The main initialization function for the BMC LAN configuration.

  This function:
    1. Locates the IPMI Transport Protocol.
    2. Calls GetLanChannelNumbers() to discover all available LAN channels.
    3. For each discovered channel, calls IpmiSetLanConfigParams().
    4. Prints the configured parameters as debug output.

  @retval EFI_SUCCESS           Configuration completed.
  @retval EFI_NOT_FOUND         IPMI transport protocol not found.
  @retval EFI_INVALID_PARAMETER Channel discovery failed.
**/
EFI_STATUS
EFIAPI
InitializeBmcLanConfig (
  VOID
  )
{
  EFI_STATUS  Status;
  UINT8       ChannelCount;
  UINT8       ChannelNumberBuffer[16];
  UINT8       Index;

  //
  // Locate the IPMI Transport Protocol
  //
  Status = gBS->LocateProtocol (
                  &gEfiIpmiTransportProtocolGuid,
                  NULL,
                  (VOID **)&mIpmiTransport
                  );

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
    ASSERT_EFI_ERROR (Status);
    return Status;
  }

  //
  // Enumerate active LAN channels
  //
  ChannelCount = sizeof (ChannelNumberBuffer);
  Status = GetLanChannelNumbers (
             mIpmiTransport,
             ChannelNumberBuffer,
             &ChannelCount
             );

  DEBUG ((
    EFI_D_INFO,
    "%a ChannelNumberBuffer[0]: %x ChannelNumberBuffer[1]: %x ChannelCount = %x\n",
    "InitializeBmcLanConfig",
    ChannelNumberBuffer[0],
    ChannelNumberBuffer[1],
    ChannelCount
    ));

  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Configure each discovered LAN channel
  //
  for (Index = 0; Index < ChannelCount; Index++) {
    //
    // The configuration data is referenced from the "ServerSetup"
    // HII variable (string at 0x1940). Each channel has a 19-byte
    // configuration block programmed via IpmiSetLanConfigParams().
    //
    IpmiSetLanConfigParams (
      ChannelNumberBuffer[Index],
      &mBmcLanConfigData[Index * 19]
      );

    //
    // Debug dump of configured parameters
    //
    DEBUG ((EFI_D_INFO, "#########################\n"));
    DEBUG ((EFI_D_INFO,
            "###  StationIp:  %d %d %d %d\n",
            mBmcLanConfigData[Index * 19 + 1],
            mBmcLanConfigData[Index * 19 + 2],
            mBmcLanConfigData[Index * 19 + 3],
            mBmcLanConfigData[Index * 19 + 4]
            ));
    DEBUG ((EFI_D_INFO,
            "###  SubnetMask: %d %d %d %d\n",
            mBmcLanConfigData[Index * 19 + 5],
            mBmcLanConfigData[Index * 19 + 6],
            mBmcLanConfigData[Index * 19 + 7],
            mBmcLanConfigData[Index * 19 + 8]
            ));
    DEBUG ((EFI_D_INFO,
            "###  RouterIp: %d %d %d %d\n",
            mBmcLanConfigData[Index * 19 + 9],
            mBmcLanConfigData[Index * 19 + 10],
            mBmcLanConfigData[Index * 19 + 11],
            mBmcLanConfigData[Index * 19 + 12]
            ));
    DEBUG ((EFI_D_INFO,
            "###  Router MAC: %x %x %x %x %x %x\n",
            mBmcLanConfigData[Index * 19 + 13],
            mBmcLanConfigData[Index * 19 + 14],
            mBmcLanConfigData[Index * 19 + 15],
            mBmcLanConfigData[Index * 19 + 16],
            mBmcLanConfigData[Index * 19 + 17],
            mBmcLanConfigData[Index * 19 + 18]
            ));
  }

  return EFI_SUCCESS;
}


//
// ---------------------------------------------------------------------------
// Standard UEFI Module Entry Point
// ---------------------------------------------------------------------------
//
// _ModuleEntryPoint (0x350)
//   - Saves ImageHandle, SystemTable, BootServices, RuntimeServices
//     as global pointers.
//   - Calls GetHobList() to initialize the HOB list pointer for debug output.
//   - Calls InitializeBmcLanConfig() to perform the actual configuration.
//
EFI_STATUS
EFIAPI
_ModuleEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  //
  // Save global service table pointers with assertions
  //
  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 for debug protocol access
  //
  GetHobList ();

  //
  // Perform BMC LAN configuration
  //
  return InitializeBmcLanConfig ();
}