Newer
Older
AMI-Aptio-BIOS-Reversed / Ip6BmcLanConfig / Ip6BmcLanConfig.c
@Ajax Dong Ajax Dong 2 days ago 37 KB Init
/** @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 ();
}