/** @file
  Source file to provide the platform Redfish Host Interface information
  of USB NIC Device exposed by BMC.

  Copyright (C) 2023 Advanced Micro Devices, Inc. All rights reserved.

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "PlatformHostInterfaceBmcUsbNicLib.h"

static EFI_GUID  mPlatformHostInterfaceBmcUsbNicReadinessGuid =
  BMC_USB_NIC_HOST_INTERFASCE_READINESS_GUID;
static EFI_EVENT  mPlatformHostInterfaceSnpEvent         = NULL;
static VOID       *mPlatformHostInterfaceSnpRegistration = NULL;

static LIST_ENTRY  mBmcUsbNic;
static LIST_ENTRY  mBmcIpmiLan;

/**
  Probe if the system supports Redfish Host Interface Credentail
  Bootstrapping.

  @retval TRUE   Yes, it is supported.
          FALSE  No, it is not supported.

**/
BOOLEAN
ProbeRedfishCredentialBootstrap (
  VOID
  )
{
  EDKII_REDFISH_AUTH_METHOD           AuthMethod;
  EDKII_REDFISH_CREDENTIAL2_PROTOCOL  *CredentialProtocol;
  CHAR8                               *UserName;
  CHAR8                               *Password;
  BOOLEAN                             ReturnBool;
  EFI_STATUS                          Status;

  DEBUG ((DEBUG_MANAGEABILITY, "%a: Entry\n", __func__));

  ReturnBool = FALSE;
  //
  // Locate HII credential protocol.
  //
  Status = gBS->LocateProtocol (
                  &gEdkIIRedfishCredential2ProtocolGuid,
                  NULL,
                  (VOID **)&CredentialProtocol
                  );
  if (EFI_ERROR (Status)) {
    ASSERT_EFI_ERROR (Status);
    return FALSE;
  }

  Status = CredentialProtocol->GetAuthInfo (
                                 CredentialProtocol,
                                 &AuthMethod,
                                 &UserName,
                                 &Password
                                 );
  if (!EFI_ERROR (Status)) {
    ZeroMem (Password, AsciiStrSize (Password));
    FreePool (Password);
    ZeroMem (UserName, AsciiStrSize (UserName));
    FreePool (UserName);
    ReturnBool = TRUE;
  } else {
    if (Status == EFI_ACCESS_DENIED) {
      // bootstrap credential support was disabled
      ReturnBool = TRUE;
    }
  }

  DEBUG ((
    DEBUG_REDFISH_HOST_INTERFACE,
    "    Redfish Credential Bootstrapping is %a\n",
    ReturnBool ? "supported" : "not supported"
    ));
  return ReturnBool;
}

/**
  Get platform Redfish host interface device descriptor.

  @param[in] DeviceType         Pointer to retrieve device type.
  @param[out] DeviceDescriptor  Pointer to retrieve REDFISH_INTERFACE_DATA, caller has to free
                                this memory using FreePool().

  @retval EFI_NOT_FOUND   No Redfish host interface descriptor provided on this platform.

**/
EFI_STATUS
RedfishPlatformHostInterfaceDeviceDescriptor (
  IN UINT8                    *DeviceType,
  OUT REDFISH_INTERFACE_DATA  **DeviceDescriptor
  )
{
  HOST_INTERFACE_BMC_USB_NIC_INFO  *ThisInstance;
  REDFISH_INTERFACE_DATA           *InterfaceData;

  DEBUG ((DEBUG_MANAGEABILITY, "%a: Entry\n", __func__));

  if (IsListEmpty (&mBmcUsbNic)) {
    return EFI_NOT_FOUND;
  }

  // Check if BMC exposed USB NIC is found and ready for using.
  ThisInstance = (HOST_INTERFACE_BMC_USB_NIC_INFO *)GetFirstNode (&mBmcUsbNic);
  while (TRUE) {
    if (ThisInstance->IsExposedByBmc && ThisInstance->IsSuppportedHostInterface) {
      *DeviceType = REDFISH_HOST_INTERFACE_DEVICE_TYPE_USB_V2;

      // Fill up REDFISH_INTERFACE_DATA defined in Redfish host interface spec v1.3
      InterfaceData = (REDFISH_INTERFACE_DATA *)AllocateZeroPool (USB_INTERFACE_DEVICE_DESCRIPTOR_V2_SIZE_1_3);
      if (InterfaceData == NULL) {
        DEBUG ((DEBUG_ERROR, "Failed to allocate memory for REDFISH_INTERFACE_DATA\n"));
        return EFI_OUT_OF_RESOURCES;
      }

      InterfaceData->DeviceType                                   = REDFISH_HOST_INTERFACE_DEVICE_TYPE_USB_V2;
      InterfaceData->DeviceDescriptor.UsbDeviceV2.Length          = USB_INTERFACE_DEVICE_DESCRIPTOR_V2_SIZE_1_3;
      InterfaceData->DeviceDescriptor.UsbDeviceV2.IdVendor        = ThisInstance->UsbVendorId;
      InterfaceData->DeviceDescriptor.UsbDeviceV2.IdProduct       = ThisInstance->UsbProductId;
      InterfaceData->DeviceDescriptor.UsbDeviceV2.SerialNumberStr = 0;
      CopyMem (
        (VOID *)&InterfaceData->DeviceDescriptor.UsbDeviceV2.MacAddress,
        (VOID *)ThisInstance->MacAddress,
        sizeof (InterfaceData->DeviceDescriptor.UsbDeviceV2.MacAddress)
        );
      InterfaceData->DeviceDescriptor.UsbDeviceV2.Characteristics              |= (UINT16)ThisInstance->CredentialBootstrapping;
      InterfaceData->DeviceDescriptor.UsbDeviceV2.CredentialBootstrappingHandle = 0;
      *DeviceDescriptor                                                         = InterfaceData;
      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    REDFISH_INTERFACE_DATA is returned successfully.\n"));
      return EFI_SUCCESS;
    }

    if (IsNodeAtEnd (&mBmcUsbNic, &ThisInstance->NextInstance)) {
      break;
    }

    ThisInstance = (HOST_INTERFACE_BMC_USB_NIC_INFO *)
                   GetNextNode (&mBmcUsbNic, &ThisInstance->NextInstance);
  }

  return EFI_NOT_FOUND;
}

/**
  Get platform Redfish host interface protocol data.
  Caller should pass NULL in ProtocolRecord to retrive the first protocol record.
  Then continuously pass previous ProtocolRecord for retrieving the next ProtocolRecord.

  @param[in, out] ProtocolRecord  Pointer to retrieve the first or the next protocol record.
                                  caller has to free the new protocol record returned from
                                  this function using FreePool().
  @param[in] IndexOfProtocolData  The index of protocol data.

  @retval EFI_NOT_FOUND   No more protocol records.

**/
EFI_STATUS
RedfishPlatformHostInterfaceProtocolData (
  IN OUT MC_HOST_INTERFACE_PROTOCOL_RECORD  **ProtocolRecord,
  IN UINT8                                  IndexOfProtocolData
  )
{
  HOST_INTERFACE_BMC_USB_NIC_INFO    *ThisInstance;
  MC_HOST_INTERFACE_PROTOCOL_RECORD  *ThisProtocolRecord;
  REDFISH_OVER_IP_PROTOCOL_DATA      *RedfishOverIpData;
  UINT8                              HostNameLength;
  CHAR8                              *HostNameString;

  DEBUG ((DEBUG_MANAGEABILITY, "%a: Entry\n", __func__));

  if (IsListEmpty (&mBmcUsbNic) || (IndexOfProtocolData > 0)) {
    return EFI_NOT_FOUND;
  }

  ThisInstance = (HOST_INTERFACE_BMC_USB_NIC_INFO *)GetFirstNode (&mBmcUsbNic);
  while (TRUE) {
    if (ThisInstance->IsExposedByBmc  && ThisInstance->IsSuppportedHostInterface) {
      // Get the host name before allocating memory.
      HostNameString     = (CHAR8 *)PcdGetPtr (PcdRedfishHostName);
      HostNameLength     = (UINT8)AsciiStrSize (HostNameString);
      ThisProtocolRecord = (MC_HOST_INTERFACE_PROTOCOL_RECORD *)AllocateZeroPool (
                                                                  sizeof (MC_HOST_INTERFACE_PROTOCOL_RECORD) - 1 +
                                                                  sizeof (REDFISH_OVER_IP_PROTOCOL_DATA) - 1 +
                                                                  HostNameLength
                                                                  );
      if (ThisProtocolRecord == NULL) {
        DEBUG ((DEBUG_ERROR, "    Allocate memory fail for MC_HOST_INTERFACE_PROTOCOL_RECORD.\n"));
        return EFI_OUT_OF_RESOURCES;
      }

      ThisProtocolRecord->ProtocolType        = MCHostInterfaceProtocolTypeRedfishOverIP;
      ThisProtocolRecord->ProtocolTypeDataLen = sizeof (REDFISH_OVER_IP_PROTOCOL_DATA) -1 + HostNameLength;
      RedfishOverIpData                       = (REDFISH_OVER_IP_PROTOCOL_DATA *)&ThisProtocolRecord->ProtocolTypeData[0];
      //
      // Fill up REDFISH_OVER_IP_PROTOCOL_DATA
      //

      // Service UUID
      ZeroMem ((VOID *)&RedfishOverIpData->ServiceUuid, sizeof (EFI_GUID));
      if (StrLen ((CONST CHAR16 *)PcdGetPtr (PcdRedfishServiceUuid)) != 0) {
        StrToGuid ((CONST CHAR16 *)PcdGetPtr (PcdRedfishServiceUuid), &RedfishOverIpData->ServiceUuid);
        DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, " Service UUID: %g", &RedfishOverIpData->ServiceUuid));
      }

      // HostIpAddressFormat and RedfishServiceIpDiscoveryType
      RedfishOverIpData->HostIpAssignmentType          = RedfishHostIpAssignmentUnknown;
      RedfishOverIpData->RedfishServiceIpDiscoveryType = RedfishHostIpAssignmentUnknown;
      if (ThisInstance->IpAssignedType == IpmiStaticAddrsss) {
        RedfishOverIpData->HostIpAssignmentType          = RedfishHostIpAssignmentStatic;
        RedfishOverIpData->RedfishServiceIpDiscoveryType = RedfishHostIpAssignmentStatic;
      } else if (ThisInstance->IpAssignedType == IpmiDynamicAddressBmcDhcp) {
        RedfishOverIpData->HostIpAssignmentType          = RedfishHostIpAssignmentDhcp;
        RedfishOverIpData->RedfishServiceIpDiscoveryType = RedfishHostIpAssignmentDhcp;
      }

      // HostIpAddressFormat and RedfishServiceIpAddressFormat, only support IPv4 for now.
      RedfishOverIpData->HostIpAddressFormat           = REDFISH_HOST_INTERFACE_HOST_IP_ADDRESS_FORMAT_IP4;
      RedfishOverIpData->RedfishServiceIpAddressFormat = REDFISH_HOST_INTERFACE_HOST_IP_ADDRESS_FORMAT_IP4;

      // HostIpAddress
      CopyMem (
        (VOID *)RedfishOverIpData->HostIpAddress,
        (VOID *)ThisInstance->HostIpAddressIpv4,
        sizeof (ThisInstance->HostIpAddressIpv4)
        );

      // HostIpMask and RedfishServiceIpMask
      CopyMem (
        (VOID *)RedfishOverIpData->HostIpMask,
        (VOID *)ThisInstance->SubnetMaskIpv4,
        sizeof (ThisInstance->SubnetMaskIpv4)
        );
      CopyMem (
        (VOID *)RedfishOverIpData->RedfishServiceIpMask,
        (VOID *)ThisInstance->SubnetMaskIpv4,
        sizeof (ThisInstance->SubnetMaskIpv4)
        );

      // RedfishServiceIpAddress
      CopyMem (
        (VOID *)RedfishOverIpData->RedfishServiceIpAddress,
        (VOID *)ThisInstance->RedfishIpAddressIpv4,
        sizeof (ThisInstance->RedfishIpAddressIpv4)
        );

      // RedfishServiceIpPort
      RedfishOverIpData->RedfishServiceIpPort = PcdGet16 (PcdRedfishServicePort);

      // RedfishServiceVlanId
      RedfishOverIpData->RedfishServiceVlanId = ThisInstance->VLanId;

      // RedfishServiceHostnameLength
      RedfishOverIpData->RedfishServiceHostnameLength = HostNameLength;

      // Redfish host name.
      CopyMem (
        (VOID *)&RedfishOverIpData->RedfishServiceHostname,
        (VOID *)HostNameString,
        HostNameLength
        );

      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    MC_HOST_INTERFACE_PROTOCOL_RECORD is returned successfully.\n"));
      *ProtocolRecord = ThisProtocolRecord;
      return EFI_SUCCESS;
    }

    if (IsNodeAtEnd (&mBmcUsbNic, &ThisInstance->NextInstance)) {
      break;
    }

    ThisInstance = (HOST_INTERFACE_BMC_USB_NIC_INFO *)
                   GetNextNode (&mBmcUsbNic, &ThisInstance->NextInstance);
  }

  return EFI_NOT_FOUND;
}

/**
  This function retrieve the information of BMC USB NIC.

  @retval EFI_SUCCESS      All necessary information is retrieved.
  @retval EFI_NOT_FOUND    There is no BMC exposed USB NIC.
  @retval Others           Other errors.

**/
EFI_STATUS
RetrievedBmcUsbNicInfo (
  VOID
  )
{
  EFI_STATUS                                      Status;
  UINT32                                          ResponseDataSize;
  HOST_INTERFACE_BMC_USB_NIC_INFO                 *ThisInstance;
  IPMI_GET_LAN_CONFIGURATION_PARAMETERS_REQUEST   GetLanConfigReq;
  IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE  *GetLanConfigReps;
  IPMI_LAN_IP_ADDRESS_SRC                         *IpAddressSrc;
  IPMI_LAN_IP_ADDRESS                             *DestIpAddress;
  IPMI_LAN_SUBNET_MASK                            *SubnetMask;
  IPMI_LAN_DEFAULT_GATEWAY                        *DefaultGateway;
  IPMI_LAN_VLAN_ID                                *LanVlanId;
  EFI_USB_DEVICE_DESCRIPTOR                       UsbDeviceDescriptor;

  DEBUG ((DEBUG_MANAGEABILITY, "%a: Entry\n", __func__));

  if (IsListEmpty (&mBmcUsbNic)) {
    return EFI_NOT_FOUND;
  }

  ThisInstance = (HOST_INTERFACE_BMC_USB_NIC_INFO *)GetFirstNode (&mBmcUsbNic);
  while (TRUE) {
    if (ThisInstance->IsExposedByBmc) {
      ThisInstance->IsSuppportedHostInterface = FALSE;

      // Probe if Redfish Host Interface Credential Bootstrapping is supported.
      ThisInstance->CredentialBootstrapping = ProbeRedfishCredentialBootstrap ();

      // Get IP address source
      GetLanConfigReq.SetSelector                     = 0;
      GetLanConfigReq.BlockSelector                   = 0;
      GetLanConfigReq.ChannelNumber.Bits.ChannelNo    = ThisInstance->IpmiLanChannelNumber;
      GetLanConfigReq.ChannelNumber.Bits.GetParameter = 0;
      GetLanConfigReq.ChannelNumber.Bits.Reserved     = 0;
      GetLanConfigReq.ParameterSelector               = IpmiLanIpAddressSource;
      ResponseDataSize                                = sizeof (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE) + sizeof (IPMI_LAN_IP_ADDRESS_SRC);
      GetLanConfigReps                                = (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE *)AllocateZeroPool (ResponseDataSize);
      GetLanConfigReps->CompletionCode                = IPMI_COMP_CODE_UNSPECIFIED;
      Status                                          = IpmiGetLanConfigurationParameters (
                                                          &GetLanConfigReq,
                                                          GetLanConfigReps,
                                                          &ResponseDataSize
                                                          );
      if (EFI_ERROR (Status) || (GetLanConfigReps->CompletionCode != IPMI_COMP_CODE_NORMAL)) {
        DEBUG ((DEBUG_ERROR, "    Failed to get IP address source at channel %d: %r, 0x%02x.\n", ThisInstance->IpmiLanChannelNumber, Status, GetLanConfigReps->CompletionCode));
        FreePool (GetLanConfigReps);
        return Status;
      }

      IpAddressSrc = (IPMI_LAN_IP_ADDRESS_SRC *)(GetLanConfigReps + 1);
      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    IP address source at channel %d: %x\n", ThisInstance->IpmiLanChannelNumber, IpAddressSrc->Bits.AddressSrc));
      ThisInstance->IpAssignedType = IpAddressSrc->Bits.AddressSrc;
      FreePool (GetLanConfigReps);

      // Get LAN IPv4 IP address
      GetLanConfigReq.ParameterSelector = IpmiLanIpAddress;
      ResponseDataSize                  = sizeof (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE) + sizeof (IPMI_LAN_IP_ADDRESS);
      GetLanConfigReps                  = (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE *)AllocateZeroPool (ResponseDataSize);
      GetLanConfigReps->CompletionCode  = IPMI_COMP_CODE_UNSPECIFIED;
      Status                            = IpmiGetLanConfigurationParameters (
                                            &GetLanConfigReq,
                                            GetLanConfigReps,
                                            &ResponseDataSize
                                            );
      if (EFI_ERROR (Status) || (GetLanConfigReps->CompletionCode != IPMI_COMP_CODE_NORMAL)) {
        DEBUG ((DEBUG_ERROR, "    Failed to get Dest IP address at channel %d: %r, 0x%02x.\n", ThisInstance->IpmiLanChannelNumber, Status, GetLanConfigReps->CompletionCode));
        FreePool (GetLanConfigReps);
        return Status;
      }

      DestIpAddress = (IPMI_LAN_IP_ADDRESS *)(GetLanConfigReps + 1);
      DEBUG ((
        DEBUG_REDFISH_HOST_INTERFACE,
        "    Dest IP address at channel %d: %d.%d.%d.%d\n",
        ThisInstance->IpmiLanChannelNumber,
        DestIpAddress->IpAddress[0],
        DestIpAddress->IpAddress[1],
        DestIpAddress->IpAddress[2],
        DestIpAddress->IpAddress[3]
        ));
      CopyMem ((VOID *)&ThisInstance->RedfishIpAddressIpv4, (VOID *)&DestIpAddress->IpAddress, sizeof (DestIpAddress->IpAddress));
      //
      // According to the design spec:
      // https://github.com/tianocore/edk2/tree/master/RedfishPkg#platform-with-bmc-and-the-bmc-exposed-usb-network-device
      // The IP address at BMC USB NIC host end is the IP address at BMC end minus 1.
      //
      CopyMem ((VOID *)&ThisInstance->HostIpAddressIpv4, (VOID *)&DestIpAddress->IpAddress, sizeof (DestIpAddress->IpAddress));
      ThisInstance->HostIpAddressIpv4[sizeof (ThisInstance->HostIpAddressIpv4) - 1] -= 1;
      FreePool (GetLanConfigReps);
      DEBUG ((
        DEBUG_REDFISH_HOST_INTERFACE,
        "    Host IP address at channel %d: %d.%d.%d.%d\n",
        ThisInstance->IpmiLanChannelNumber,
        ThisInstance->HostIpAddressIpv4[0],
        ThisInstance->HostIpAddressIpv4[1],
        ThisInstance->HostIpAddressIpv4[2],
        ThisInstance->HostIpAddressIpv4[3]
        ));

      // Get IPv4 subnet mask
      GetLanConfigReq.ParameterSelector = IpmiLanSubnetMask;
      ResponseDataSize                  = sizeof (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE) + sizeof (IPMI_LAN_SUBNET_MASK);
      GetLanConfigReps                  = (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE *)AllocateZeroPool (ResponseDataSize);
      GetLanConfigReps->CompletionCode  = IPMI_COMP_CODE_UNSPECIFIED;
      Status                            = IpmiGetLanConfigurationParameters (
                                            &GetLanConfigReq,
                                            GetLanConfigReps,
                                            &ResponseDataSize
                                            );
      if ((EFI_ERROR (Status)) || (GetLanConfigReps->CompletionCode != IPMI_COMP_CODE_NORMAL)) {
        DEBUG ((DEBUG_ERROR, "    Failed to get subnet mask at channel %d: %r, 0x%02x.\n", ThisInstance->IpmiLanChannelNumber, Status, GetLanConfigReps->CompletionCode));
        FreePool (GetLanConfigReps);
        return Status;
      }

      SubnetMask = (IPMI_LAN_SUBNET_MASK *)(GetLanConfigReps + 1);
      DEBUG ((
        DEBUG_REDFISH_HOST_INTERFACE,
        "    Subnet mask at channel %d: %d.%d.%d.%d\n",
        ThisInstance->IpmiLanChannelNumber,
        SubnetMask->IpAddress[0],
        SubnetMask->IpAddress[1],
        SubnetMask->IpAddress[2],
        SubnetMask->IpAddress[3]
        ));
      CopyMem ((VOID *)&ThisInstance->SubnetMaskIpv4, (VOID *)&SubnetMask->IpAddress, sizeof (SubnetMask->IpAddress));
      FreePool (GetLanConfigReps);

      // Get Gateway IP address.
      GetLanConfigReq.ParameterSelector = IpmiLanDefaultGateway;
      ResponseDataSize                  = sizeof (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE) + sizeof (IPMI_LAN_DEFAULT_GATEWAY);
      GetLanConfigReps                  = (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE *)AllocateZeroPool (ResponseDataSize);
      GetLanConfigReps->CompletionCode  = IPMI_COMP_CODE_UNSPECIFIED;
      Status                            = IpmiGetLanConfigurationParameters (
                                            &GetLanConfigReq,
                                            GetLanConfigReps,
                                            &ResponseDataSize
                                            );
      if ((EFI_ERROR (Status)) || (GetLanConfigReps->CompletionCode != IPMI_COMP_CODE_NORMAL)) {
        DEBUG ((DEBUG_ERROR, "    Failed to get default gateway at channel %d: %r, 0x%02x.\n", ThisInstance->IpmiLanChannelNumber, Status, GetLanConfigReps->CompletionCode));
        FreePool (GetLanConfigReps);
        return Status;
      }

      DefaultGateway = (IPMI_LAN_DEFAULT_GATEWAY *)(GetLanConfigReps + 1);
      DEBUG ((
        DEBUG_REDFISH_HOST_INTERFACE,
        "    Gateway at channel %d: %d.%d.%d.%d\n",
        ThisInstance->IpmiLanChannelNumber,
        DefaultGateway->IpAddress[0],
        DefaultGateway->IpAddress[1],
        DefaultGateway->IpAddress[2],
        DefaultGateway->IpAddress[3]
        ));
      CopyMem ((VOID *)&ThisInstance->GatewayIpv4, (VOID *)&DefaultGateway->IpAddress, sizeof (DefaultGateway->IpAddress));
      FreePool (GetLanConfigReps);

      // Get VLAN ID
      GetLanConfigReq.ParameterSelector = IpmiLanVlanId;
      ResponseDataSize                  = sizeof (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE) + sizeof (IPMI_LAN_VLAN_ID);
      GetLanConfigReps                  = (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE *)AllocateZeroPool (ResponseDataSize);
      GetLanConfigReps->CompletionCode  = IPMI_COMP_CODE_UNSPECIFIED;
      Status                            = IpmiGetLanConfigurationParameters (
                                            &GetLanConfigReq,
                                            GetLanConfigReps,
                                            &ResponseDataSize
                                            );
      if ((EFI_ERROR (Status)) || (GetLanConfigReps->CompletionCode != IPMI_COMP_CODE_NORMAL)) {
        DEBUG ((DEBUG_ERROR, "    Failed to get VLAN ID at channel %d: %r, 0x%02x.\n", ThisInstance->IpmiLanChannelNumber, Status, GetLanConfigReps->CompletionCode));
        FreePool (GetLanConfigReps);
        return Status;
      }

      LanVlanId            = (IPMI_LAN_VLAN_ID *)(GetLanConfigReps + 1);
      ThisInstance->VLanId = 0;
      if (LanVlanId->Data2.Bits.Enabled == 1) {
        ThisInstance->VLanId = LanVlanId->Data1.VanIdLowByte | (LanVlanId->Data2.Bits.VanIdHighByte << 8);
      }

      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    VLAN ID %x\n", ThisInstance->VLanId));

      FreePool (GetLanConfigReps);

      //
      // Read USB device information.
      //
      if (ThisInstance->ThisUsbIo != NULL) {
        Status = ThisInstance->ThisUsbIo->UsbGetDeviceDescriptor (ThisInstance->ThisUsbIo, &UsbDeviceDescriptor);
        if (!EFI_ERROR (Status)) {
          DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    USB NIC Vendor ID: 0x%04x, Device ID: 0x%04x\n", UsbDeviceDescriptor.IdVendor, UsbDeviceDescriptor.IdProduct));
          ThisInstance->UsbVendorId  = UsbDeviceDescriptor.IdVendor;
          ThisInstance->UsbProductId = UsbDeviceDescriptor.IdProduct;
        } else {
          DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    Fail to get USB device descriptor.\n"));
        }
      }

      // All information is retrieved.
      ThisInstance->IsSuppportedHostInterface = TRUE;
      return EFI_SUCCESS;
    }

    if (IsNodeAtEnd (&mBmcUsbNic, &ThisInstance->NextInstance)) {
      break;
    }

    ThisInstance = (HOST_INTERFACE_BMC_USB_NIC_INFO *)
                   GetNextNode (&mBmcUsbNic, &ThisInstance->NextInstance);
  }

  return EFI_NOT_FOUND;
}

/**
  This function caches the found IPMI LAN channel. So we
  don't have to sedn IPMI commands again if the USB NIC is
  connected later.

  @param[in] ChannelNum                The IPMI channel number.
  @param[in] IpmiLanChannelMacAddress  Pointer to EFI_MAC_ADDRESS.
  @param[in] IpmiLanMacAddressSize     The MAC address size.

  @retval EFI_SUCCESS          IPMI LAN channel is cached.
  @retval EFI_OUT_OF_RESOURCE  Memory allocated failed.
  @retval Others               Other errors.

**/
EFI_STATUS
CacheIpmiLanMac (
  IN UINT8            ChannelNum,
  IN EFI_MAC_ADDRESS  *IpmiLanChannelMacAddress,
  IN UINT8            IpmiLanMacAddressSize
  )
{
  BMC_IPMI_LAN_CHANNEL_INFO  *ChannelInfo;

  ChannelInfo = (BMC_IPMI_LAN_CHANNEL_INFO *)AllocateZeroPool (sizeof (BMC_IPMI_LAN_CHANNEL_INFO));
  if (ChannelInfo == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  ChannelInfo->Channel = ChannelNum;
  CopyMem ((VOID *)&ChannelInfo->MacAddress.Addr, (VOID *)IpmiLanChannelMacAddress->Addr, IpmiLanMacAddressSize);
  ChannelInfo->MacAddressSize = IpmiLanMacAddressSize;
  InitializeListHead (&ChannelInfo->NextInstance);
  InsertTailList (&mBmcIpmiLan, &ChannelInfo->NextInstance);
  return EFI_SUCCESS;
}

/**
  This function checks if the IPMI channel already identified
  previously.

  @param[in]  ChannelNum            The IPMI channel number.
  @param[out] CachedIpmiLanChannel  Pointer to retrieve the cached
                                    BMC_IPMI_LAN_CHANNEL_INFO.

  @retval EFI_SUCCESS          IPMI LAN channel is found.
  @retval Others               Other errors.

**/
EFI_STATUS
CheckCachedIpmiLanMac (
  IN UINT8                       ChannelNum,
  OUT BMC_IPMI_LAN_CHANNEL_INFO  **CachedIpmiLanChannel
  )
{
  BMC_IPMI_LAN_CHANNEL_INFO  *ThisInstance;

  if (IsListEmpty (&mBmcIpmiLan)) {
    return EFI_NOT_FOUND;
  }

  ThisInstance = (BMC_IPMI_LAN_CHANNEL_INFO *)GetFirstNode (&mBmcIpmiLan);
  while (TRUE) {
    if (ThisInstance->Channel == ChannelNum) {
      *CachedIpmiLanChannel = ThisInstance;
      return EFI_SUCCESS;
    }

    if (IsNodeAtEnd (&mBmcIpmiLan, &ThisInstance->NextInstance)) {
      break;
    }

    ThisInstance = (BMC_IPMI_LAN_CHANNEL_INFO *)
                   GetNextNode (&mBmcIpmiLan, &ThisInstance->NextInstance);
  }

  return EFI_NOT_FOUND;
}

/**
  This function goes through IPMI channels to find the
  mactched MAC addrss of BMC USB NIC endpoint.

  @param[in] UsbNicInfo  The instance of HOST_INTERFACE_BMC_USB_NIC_INFO.

  @retval EFI_SUCCESS          Yes, USB NIC exposed by BMC is found.
  @retval EFI_NOT_FOUND        No, USB NIC exposed by BMC is not found
                               on the existing SNP handle.
  @retval Others               Other errors.

**/
EFI_STATUS
HostInterfaceIpmiCheckMacAddress (
  IN HOST_INTERFACE_BMC_USB_NIC_INFO  *UsbNicInfo
  )
{
  EFI_STATUS                                      Status;
  EFI_STATUS                                      ExitStatus;
  UINTN                                           ChannelNum;
  UINT32                                          ResponseDataSize;
  IPMI_GET_CHANNEL_INFO_REQUEST                   GetChanelInfoRequest;
  IPMI_GET_CHANNEL_INFO_RESPONSE                  GetChanelInfoResponse;
  IPMI_GET_LAN_CONFIGURATION_PARAMETERS_REQUEST   GetLanConfigReq;
  IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE  *GetLanConfigReps;
  BMC_IPMI_LAN_CHANNEL_INFO                       *CachedIpmiLanChannel;
  UINT8                                           IpmiLanMacAddressSize;
  EFI_MAC_ADDRESS                                 IpmiLanChannelMacAddress;
  BOOLEAN                                         AlreadyCached;

  DEBUG ((DEBUG_MANAGEABILITY, "%a: Entry.\n", __func__));

  GetLanConfigReps = NULL;
  AlreadyCached    = FALSE;
  if (!IsListEmpty (&mBmcIpmiLan)) {
    AlreadyCached = TRUE;
  }

  // Initial the get MAC address request.
  GetLanConfigReq.ChannelNumber.Uint8 = 0;
  GetLanConfigReq.SetSelector         = 0;
  GetLanConfigReq.BlockSelector       = 0;
  GetLanConfigReq.ParameterSelector   = IpmiLanMacAddress;

  ExitStatus = EFI_NOT_FOUND;
  for (ChannelNum = IPMI_CHANNEL_NUMBER_IMPLEMENTATION_SPECIFIC_1;
       ChannelNum <= IPMI_CHANNEL_NUMBER_IMPLEMENTATION_SPECIFIC_11;
       ChannelNum++)
  {
    IpmiLanMacAddressSize = 0;

    // Check if the IPMI channel information is already cached.
    Status = EFI_NOT_FOUND;
    if (AlreadyCached) {
      Status = CheckCachedIpmiLanMac ((UINT8)ChannelNum, &CachedIpmiLanChannel);
    }

    if (Status == EFI_SUCCESS) {
      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "  Got cached IPMI LAN info.\n"));
      IpmiLanMacAddressSize = sizeof (IPMI_LAN_MAC_ADDRESS);
      CopyMem ((VOID *)&IpmiLanChannelMacAddress.Addr, (VOID *)&CachedIpmiLanChannel->MacAddress.Addr, IpmiLanMacAddressSize);
    } else {
      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "  No cached IPMI LAN info\n"));
      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "  Send NetFn = App, Command = 0x42 to channel %d\n", ChannelNum));
      GetChanelInfoRequest.ChannelNumber.Uint8          = 0;
      GetChanelInfoRequest.ChannelNumber.Bits.ChannelNo = (UINT8)ChannelNum;
      Status                                            = IpmiGetChannelInfo (
                                                            &GetChanelInfoRequest,
                                                            &GetChanelInfoResponse,
                                                            &ResponseDataSize
                                                            );
      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_ERROR, "    - Channel %d fails to send command.\n", ChannelNum));
        continue;
      }

      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    - Response data size = 0x%x\n", ResponseDataSize));
      if ((GetChanelInfoResponse.CompletionCode != IPMI_COMP_CODE_NORMAL) || (ResponseDataSize == 0)) {
        DEBUG ((DEBUG_ERROR, "    - Command returned fail: 0x%x.\n", GetChanelInfoResponse.CompletionCode));
        continue;
      }

      DEBUG ((
        DEBUG_REDFISH_HOST_INTERFACE,
        "    - Channel protocol = 0x%x, Media = 0x%x\n",
        GetChanelInfoResponse.ProtocolType.Bits.ChannelProtocolType,
        GetChanelInfoResponse.MediumType.Bits.ChannelMediumType
        ));

      if (GetChanelInfoResponse.ChannelNumber.Bits.ChannelNo != ChannelNum) {
        DEBUG ((
          DEBUG_ERROR,
          "    - ChannelNumber = %d in the response which is not macthed to the request.\n",
          GetChanelInfoResponse.ChannelNumber.Bits.ChannelNo
          ));
        continue;
      }

      if ((GetChanelInfoResponse.MediumType.Bits.ChannelMediumType == IPMI_CHANNEL_MEDIA_TYPE_802_3_LAN) &&
          (GetChanelInfoResponse.ProtocolType.Bits.ChannelProtocolType == IPMI_CHANNEL_PROTOCOL_TYPE_IPMB_1_0))
      {
        DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    - Channel %d is a LAN device!\n", ChannelNum));

        ResponseDataSize = sizeof (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE) +
                           sizeof (IPMI_LAN_MAC_ADDRESS);
        if (GetLanConfigReps == NULL) {
          GetLanConfigReps =
            (IPMI_GET_LAN_CONFIGURATION_PARAMETERS_RESPONSE *)AllocateZeroPool (ResponseDataSize);
          if (GetLanConfigReps == NULL) {
            DEBUG ((DEBUG_ERROR, "    Allocate memory failed for getting MAC address.\n"));
            continue;
          }
        }

        GetLanConfigReq.ChannelNumber.Bits.ChannelNo = (UINT8)ChannelNum;
        GetLanConfigReps->CompletionCode             = IPMI_COMP_CODE_UNSPECIFIED;
        Status                                       = IpmiGetLanConfigurationParameters (
                                                         &GetLanConfigReq,
                                                         GetLanConfigReps,
                                                         &ResponseDataSize
                                                         );
        if (EFI_ERROR (Status) || (GetLanConfigReps->CompletionCode != IPMI_COMP_CODE_NORMAL)) {
          DEBUG ((
            DEBUG_ERROR,
            "    Fails to get MAC address of channel %d, CompletionCode = %02x.\n",
            ChannelNum,
            GetLanConfigReps->CompletionCode
            ));
          continue;
        } else {
          DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    The MAC address of channel %d.\n", ChannelNum));
          DEBUG ((
            DEBUG_REDFISH_HOST_INTERFACE,
            "      %02x:%02x:%02x:%02x:%02x:%02x\n",
            *((UINT8 *)(GetLanConfigReps + 1) + 0),
            *((UINT8 *)(GetLanConfigReps + 1) + 1),
            *((UINT8 *)(GetLanConfigReps + 1) + 2),
            *((UINT8 *)(GetLanConfigReps + 1) + 3),
            *((UINT8 *)(GetLanConfigReps + 1) + 4),
            *((UINT8 *)(GetLanConfigReps + 1) + 5)
            ));
          IpmiLanMacAddressSize = sizeof (IPMI_LAN_MAC_ADDRESS);
          CopyMem ((VOID *)&IpmiLanChannelMacAddress.Addr, (VOID *)(GetLanConfigReps + 1), IpmiLanMacAddressSize);
        }
      }
    }

    if (IpmiLanMacAddressSize != 0) {
      if (!AlreadyCached) {
        // Cache this IPMI LAN channel.
        DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    Cache this IPMI LAN channel.\n"));
        CacheIpmiLanMac ((UINT8)ChannelNum, &IpmiLanChannelMacAddress, IpmiLanMacAddressSize);
      }

      //
      // According to design spec in Readme file under RedfishPkg.
      // https://github.com/tianocore/edk2/tree/master/RedfishPkg#platform-with-bmc-and-the-bmc-exposed-usb-network-device
      // Compare the first five elements of MAC address and the 6th element of MAC address.
      // The 6th element of MAC address must be the 6th element of
      // IPMI channel MAC address minus 1.
      //
      if ((IpmiLanMacAddressSize != UsbNicInfo->MacAddressSize) ||
          (CompareMem (
             (VOID *)UsbNicInfo->MacAddress,
             (VOID *)&IpmiLanChannelMacAddress.Addr,
             IpmiLanMacAddressSize - 1
             ) != 0) ||
          ((IpmiLanChannelMacAddress.Addr[IpmiLanMacAddressSize - 1] - 1) !=
           *(UsbNicInfo->MacAddress + IpmiLanMacAddressSize - 1))
          )
      {
        DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    MAC address is not matched.\n"));
        continue;
      }

      // This is the NIC exposed by BMC.
      UsbNicInfo->IpmiLanChannelNumber = (UINT8)ChannelNum;
      UsbNicInfo->IsExposedByBmc       = TRUE;
      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    MAC address is matched.\n"));
      ExitStatus = EFI_SUCCESS;
      break;
    }
  }

  if (GetLanConfigReps != NULL) {
    FreePool (GetLanConfigReps);
  }

  return ExitStatus;
}

/**
  This function searches the next MSG_USB_DP device path node.

  @param[in]  ThisDevicePath    Device path to search.

  @retval NULL          MSG_USB_DP is not found.
          Otherwise     MSG_USB_DP is found.

**/
EFI_DEVICE_PATH_PROTOCOL *
UsbNicGetNextMsgUsbDp (
  IN EFI_DEVICE_PATH_PROTOCOL  *ThisDevicePath
  )
{
  if (ThisDevicePath == NULL) {
    return NULL;
  }

  while (TRUE) {
    ThisDevicePath = NextDevicePathNode (ThisDevicePath);
    if (IsDevicePathEnd (ThisDevicePath)) {
      return NULL;
    }

    if ((ThisDevicePath->Type == MESSAGING_DEVICE_PATH) && (ThisDevicePath->SubType == MSG_USB_DP)) {
      return ThisDevicePath;
    }
  }

  return NULL;
}

/**
  This function search the UsbIo handle that matches the UsbDevicePath.

  @param[in]  UsbDevicePath    Device path of this SNP handle.
  @param[out] UsbIo            Return the UsbIo protocol.

  @retval EFI_SUCCESS          Yes, UsbIo protocl is found.
  @retval EFI_NOT_FOUND        No, UsbIo protocl is not found
  @retval Others               Other errors.

**/
EFI_STATUS
UsbNicSearchUsbIo (
  IN   EFI_DEVICE_PATH_PROTOCOL  *UsbDevicePath,
  OUT  EFI_USB_IO_PROTOCOL       **UsbIo
  )
{
  EFI_STATUS                Status;
  UINTN                     BufferSize;
  EFI_HANDLE                *HandleBuffer;
  UINT16                    Length;
  UINTN                     Index;
  CHAR16                    *DevicePathStr;
  EFI_DEVICE_PATH_PROTOCOL  *TempDevicePath;
  EFI_DEVICE_PATH_PROTOCOL  *ThisDevicePath;
  EFI_DEVICE_PATH_PROTOCOL  *ThisDevicePathEnd;
  EFI_DEVICE_PATH_PROTOCOL  *ThisUsbDevicePath;
  EFI_DEVICE_PATH_PROTOCOL  *ThisUsbDevicePathEnd;

  DEBUG ((DEBUG_MANAGEABILITY, "%a: Entry.\n", __func__));
  DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "Device path on the EFI handle which has UsbIo and SNP instaleld on it.\n"));
  DevicePathStr = ConvertDevicePathToText (UsbDevicePath, FALSE, FALSE);
  if (DevicePathStr != NULL) {
    DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "%s\n", DevicePathStr));
    FreePool (DevicePathStr);
  } else {
    DEBUG ((DEBUG_ERROR, "Failed to convert device path.\n"));
    return EFI_INVALID_PARAMETER;
  }

  BufferSize   = 0;
  HandleBuffer = NULL;
  *UsbIo       = NULL;
  Status       = gBS->LocateHandle (
                        ByProtocol,
                        &gEfiUsbIoProtocolGuid,
                        NULL,
                        &BufferSize,
                        NULL
                        );
  if (Status == EFI_BUFFER_TOO_SMALL) {
    DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "  %d UsbIo protocol instances.\n", BufferSize/sizeof (EFI_HANDLE)));
    HandleBuffer = AllocateZeroPool (BufferSize);
    if (HandleBuffer == NULL) {
      DEBUG ((DEBUG_ERROR, "    Falied to allocate buffer for the handles.\n"));
      return EFI_OUT_OF_RESOURCES;
    }

    Status = gBS->LocateHandle (
                    ByProtocol,
                    &gEfiUsbIoProtocolGuid,
                    NULL,
                    &BufferSize,
                    HandleBuffer
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "  Falied to locate UsbIo protocol handles.\n"));
      FreePool (HandleBuffer);
      return Status;
    }
  } else {
    return Status;
  }

  for (Index = 0; Index < (BufferSize/sizeof (EFI_HANDLE)); Index++) {
    Status = gBS->HandleProtocol (
                    *(HandleBuffer + Index),
                    &gEfiDevicePathProtocolGuid,
                    (VOID **)&ThisDevicePath
                    );
    if (EFI_ERROR (Status)) {
      continue;
    }

    DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "Device path on #%d instance of UsbIo.\n", Index));
    DevicePathStr = ConvertDevicePathToText (ThisDevicePath, FALSE, FALSE);
    if (DevicePathStr != NULL) {
      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "%s\n", DevicePathStr));
      FreePool (DevicePathStr);
    } else {
      DEBUG ((DEBUG_ERROR, "Failed to convert device path on #%d instance of UsbIo.\n", Index));
      continue;
    }

    Status = EFI_NOT_FOUND;

    // Search for the starting MSG_USB_DP node.
    ThisUsbDevicePath = UsbDevicePath;
    if ((DevicePathType (ThisUsbDevicePath) != MESSAGING_DEVICE_PATH) ||
        (DevicePathSubType (ThisUsbDevicePath) != MSG_USB_DP))
    {
      ThisUsbDevicePath = UsbNicGetNextMsgUsbDp (ThisUsbDevicePath);
      if (ThisUsbDevicePath == NULL) {
        continue;
      }
    }

    if ((DevicePathType (ThisDevicePath) != MESSAGING_DEVICE_PATH) ||
        (DevicePathSubType (ThisDevicePath) != MSG_USB_DP))
    {
      ThisDevicePath = UsbNicGetNextMsgUsbDp (ThisDevicePath);
      if (ThisDevicePath == NULL) {
        continue;
      }
    }

    // Search for the ending MSG_USB_DP node.
    ThisDevicePathEnd    = ThisDevicePath;
    ThisUsbDevicePathEnd = ThisUsbDevicePath;
    while (TRUE) {
      TempDevicePath = UsbNicGetNextMsgUsbDp (ThisDevicePathEnd);
      if (TempDevicePath == NULL) {
        break;
      }

      ThisDevicePathEnd = TempDevicePath;
    }

    while (TRUE) {
      TempDevicePath = UsbNicGetNextMsgUsbDp (ThisUsbDevicePathEnd);
      if (TempDevicePath == NULL) {
        break;
      }

      ThisUsbDevicePathEnd = TempDevicePath;
    }

    // Compare these two device paths
    Length = (UINT16)((UINTN)(UINT8 *)ThisDevicePathEnd + DevicePathNodeLength (ThisDevicePathEnd) - (UINTN)(UINT8 *)ThisDevicePath);
    if (Length != ((UINTN)(UINT8 *)ThisUsbDevicePathEnd + DevicePathNodeLength (ThisUsbDevicePathEnd) - (UINTN)(UINT8 *)ThisUsbDevicePath)) {
      continue;
    }

    if (CompareMem (
          (VOID *)ThisDevicePath,
          (VOID *)ThisUsbDevicePath,
          Length
          ) == 0)
    {
      Status = EFI_SUCCESS;
      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "EFI handle with the correct UsbIo is found at #%d instance of UsbIo.\n", Index));
      break;
    }
  }

  if (Status == EFI_SUCCESS) {
    // Locate UsbIo from this handle.
    Status = gBS->HandleProtocol (
                    *(HandleBuffer + Index),
                    &gEfiUsbIoProtocolGuid,
                    (VOID **)UsbIo
                    );
    return Status;
  }

  return EFI_NOT_FOUND;
}

/**
  This function identifies if the USB NIC has MAC address and internet
  protocol device path installed. (Only support IPv4)

  @param[in] UsbDevicePath     USB device path.

  @retval EFI_SUCCESS          Yes, this is IPv4 SNP handle
  @retval EFI_NOT_FOUND        No, this is not IPv4 SNP handle

**/
EFI_STATUS
IdentifyNetworkMessageDevicePath (
  IN EFI_DEVICE_PATH_PROTOCOL  *UsbDevicePath
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;

  DevicePath = UsbDevicePath;
  while (TRUE) {
    DevicePath = NextDevicePathNode (DevicePath);
    if (IsDevicePathEnd (DevicePath)) {
      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "MAC address device path is not found on this handle.\n"));
      break;
    }

    if ((DevicePath->Type == MESSAGING_DEVICE_PATH) && (DevicePath->SubType == MSG_MAC_ADDR_DP)) {
      DevicePath = NextDevicePathNode (DevicePath); // Advance to next device path protocol.
      if (IsDevicePathEnd (DevicePath)) {
        DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "IPv4 device path is not found on this handle.\n"));
        break;
      }

      if ((DevicePath->Type == MESSAGING_DEVICE_PATH) && (DevicePath->SubType == MSG_IPv4_DP)) {
        return EFI_SUCCESS;
      }

      break;
    }
  }

  return EFI_NOT_FOUND;
}

/**
  This function identifies if the USB NIC is exposed by BMC as
  the host-BMC channel.

  @param[in] Handle          This is the EFI handle with SNP installed.
  @param[in] UsbDevicePath   USB device path.

  @retval EFI_SUCCESS          Yes, USB NIC exposed by BMC is found.
  @retval EFI_NOT_FOUND        No, USB NIC exposed by BMC is not found
                               on the existing SNP handle.
  @retval Others               Other errors.

**/
EFI_STATUS
IdentifyUsbNicBmcChannel (
  IN EFI_HANDLE                Handle,
  IN EFI_DEVICE_PATH_PROTOCOL  *UsbDevicePath
  )
{
  UINTN                            Index;
  EFI_STATUS                       Status;
  EFI_SIMPLE_NETWORK_PROTOCOL      *Snp;
  EFI_USB_IO_PROTOCOL              *UsbIo;
  HOST_INTERFACE_BMC_USB_NIC_INFO  *BmcUsbNic;

  DEBUG ((DEBUG_MANAGEABILITY, "%a: Entry.\n", __func__));
  Status = gBS->HandleProtocol (
                  Handle,
                  &gEfiSimpleNetworkProtocolGuid,
                  (VOID **)&Snp
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "    Failed to locate SNP.\n"));
    return Status;
  }

  Status = UsbNicSearchUsbIo (UsbDevicePath, &UsbIo);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "    Failed to find USBIO.\n"));
    return Status;
  }

  // Get the MAC address of this SNP instance.
  BmcUsbNic = AllocateZeroPool (sizeof (HOST_INTERFACE_BMC_USB_NIC_INFO));
  if (BmcUsbNic == NULL) {
    DEBUG ((DEBUG_ERROR, "    Failed to allocate memory for HOST_INTERFACE_BMC_USB_NIC_INFO.\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  InitializeListHead (&BmcUsbNic->NextInstance);
  BmcUsbNic->MacAddressSize = Snp->Mode->HwAddressSize;
  BmcUsbNic->MacAddress     = AllocatePool (BmcUsbNic->MacAddressSize);
  if (BmcUsbNic->MacAddress == NULL) {
    DEBUG ((DEBUG_ERROR, "    Failed to allocate memory for HW MAC addresss.\n"));
    FreePool (BmcUsbNic);
    return EFI_OUT_OF_RESOURCES;
  }

  CopyMem (
    (VOID *)BmcUsbNic->MacAddress,
    (VOID *)&Snp->Mode->CurrentAddress,
    BmcUsbNic->MacAddressSize
    );
  DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    MAC address (in size %d) for this SNP instance:\n", BmcUsbNic->MacAddressSize));
  for (Index = 0; Index < BmcUsbNic->MacAddressSize; Index++) {
    DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "%02x ", *(BmcUsbNic->MacAddress + Index)));
  }

  DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "\n"));
  BmcUsbNic->ThisSnp   = Snp;
  BmcUsbNic->ThisUsbIo = UsbIo;

  Status = HostInterfaceIpmiCheckMacAddress (BmcUsbNic);
  if (Status == EFI_SUCCESS) {
    BmcUsbNic->IsExposedByBmc = TRUE;
    DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    BMC exposed USB NIC is found.\n"));
  } else {
    DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    BMC exposed USB NIC is not found.\n"));
  }

  InsertTailList (&mBmcUsbNic, &BmcUsbNic->NextInstance);
  return Status;
}

/**
  This function checks if the USB NIC exposed by BMC
  on each handle has SNP protocol installed on it.

  @param[in] HandleNumer    Number of handles to check.
  @param[in] HandleBuffer   Handles buffer.

  @retval EFI_SUCCESS          Yes, USB NIC exposed by BMC is found.
  @retval EFI_NOT_FOUND        No, USB NIC exposed by BMC is not found
                               on the existing SNP handle.
  @retval Others               Other errors.

**/
EFI_STATUS
CheckBmcUsbNicOnHandles (
  IN  UINTN       HandleNumer,
  IN  EFI_HANDLE  *HandleBuffer
  )
{
  UINTN                     Index;
  EFI_STATUS                Status;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
  BOOLEAN                   GotBmcUsbNic;
  CHAR16                    *DevicePathStr;

  if ((HandleNumer == 0) || (HandleBuffer == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  DEBUG ((DEBUG_MANAGEABILITY, "%a: Entry, #%d SNP handle\n", __func__, HandleNumer));

  GotBmcUsbNic = FALSE;
  for (Index = 0; Index < HandleNumer; Index++) {
    DEBUG ((DEBUG_MANAGEABILITY, "    Locate device path on handle 0x%08x\n", *(HandleBuffer + Index)));
    Status = gBS->HandleProtocol (
                    *(HandleBuffer + Index),
                    &gEfiDevicePathProtocolGuid,
                    (VOID **)&DevicePath
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "    Failed to locate device path on %d handle.\n", Index));
      continue;
    }

    DevicePathStr = ConvertDevicePathToText (DevicePath, FALSE, FALSE);
    if (DevicePathStr != NULL) {
      DEBUG ((DEBUG_MANAGEABILITY, "    Device path: %s\n", DevicePathStr));
      FreePool (DevicePathStr);
    }

    // Check if this is an BMC exposed USB NIC device.
    while (TRUE) {
      if ((DevicePath->Type == MESSAGING_DEVICE_PATH) && (DevicePath->SubType == MSG_USB_DP)) {
        Status = IdentifyNetworkMessageDevicePath (DevicePath);
        if (!EFI_ERROR (Status)) {
          Status = IdentifyUsbNicBmcChannel (*(HandleBuffer + Index), DevicePath);
          if (!EFI_ERROR (Status)) {
            GotBmcUsbNic = TRUE;
          }
        }

        break; // Advance to next SNP handle.
      }

      DevicePath = NextDevicePathNode (DevicePath);
      if (IsDevicePathEnd (DevicePath)) {
        break;
      }
    }
  }

  if (GotBmcUsbNic) {
    return EFI_SUCCESS;
  }

  DEBUG ((DEBUG_MANAGEABILITY, "No BMC USB NIC found on SNP handles\n"));
  return EFI_NOT_FOUND;
}

/**
  This function checks if the USB NIC exposed by BMC
  is already connected.

  @param[in] Registration      Locate SNP protocol from the notification
                               registeration key.
                               NULL means locate SNP protocol from the existing
                               handles.

  @retval EFI_SUCCESS          Yes, USB NIC exposed by BMC is found.
  @retval EFI_NOT_FOUND        No, USB NIC exposed by BMC is not found
                               on the existing SNP handle.
  @retval Others               Other errors.

**/
EFI_STATUS
CheckBmcUsbNic (
  VOID  *Registration
  )
{
  EFI_STATUS  Status;
  EFI_HANDLE  Handle;
  UINTN       BufferSize;
  EFI_HANDLE  *HandleBuffer;

  DEBUG ((DEBUG_MANAGEABILITY, "%a: Entry, the registration key - 0x%08x.\n", __func__, Registration));

  Handle       = NULL;
  HandleBuffer = NULL;
  Status       = EFI_SUCCESS;

  do {
    BufferSize = 0;
    Status     = gBS->LocateHandle (
                        Registration == NULL ? ByProtocol : ByRegisterNotify,
                        &gEfiSimpleNetworkProtocolGuid,
                        Registration,
                        &BufferSize,
                        NULL
                        );
    if (Status == EFI_BUFFER_TOO_SMALL) {
      DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    %d SNP protocol instance(s).\n", BufferSize/sizeof (EFI_HANDLE)));
      HandleBuffer = AllocateZeroPool (BufferSize);
      if (HandleBuffer == NULL) {
        DEBUG ((DEBUG_ERROR, "    Falied to allocate buffer for the handles.\n"));
        return EFI_OUT_OF_RESOURCES;
      }

      Status = gBS->LocateHandle (
                      Registration == NULL ? ByProtocol : ByRegisterNotify,
                      &gEfiSimpleNetworkProtocolGuid,
                      Registration,
                      &BufferSize,
                      HandleBuffer
                      );
      if (EFI_ERROR (Status)) {
        DEBUG ((DEBUG_ERROR, "    Falied to locate SNP protocol handles.\n"));
        FreePool (HandleBuffer);
        return Status;
      }
    } else if (EFI_ERROR (Status)) {
      if (Registration != NULL) {
        DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    No more newly installed SNP protocol for this registration - %r.\n", Status));
        return EFI_SUCCESS;
      }

      return Status;
    }

    // Check USB NIC on handles.
    Status = CheckBmcUsbNicOnHandles (BufferSize/sizeof (EFI_HANDLE), HandleBuffer);
    if (!EFI_ERROR (Status)) {
      // Retrieve the rest of BMC USB NIC information for Redfish over IP information
      // and USB Network Interface V2.
      Status = RetrievedBmcUsbNicInfo ();
      if (!EFI_ERROR (Status)) {
        DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "    Install protocol to notify the platform Redfish Host Interface information is ready.\n"));
        Status = gBS->InstallProtocolInterface (
                        &Handle,
                        &mPlatformHostInterfaceBmcUsbNicReadinessGuid,
                        EFI_NATIVE_INTERFACE,
                        NULL
                        );
        if (EFI_ERROR (Status)) {
          DEBUG ((DEBUG_ERROR, "    Install protocol fail %r.\n", Status));
        }
      }
    }

    FreePool (HandleBuffer);
  } while (Registration != NULL);

  return Status;
}

/**
  Notification event of SNP readiness.

  @param[in]  Event                 Event whose notification function is being invoked.
  @param[in]  Context               The pointer to the notification function's context,
                                    which is implementation-dependent.

**/
VOID
EFIAPI
PlatformHostInterfaceSnpCallback (
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
  DEBUG ((DEBUG_MANAGEABILITY, "%a: Entry.\n", __func__));

  CheckBmcUsbNic (mPlatformHostInterfaceSnpRegistration);
  return;
}

/**
  Get the EFI protocol GUID installed by platform library which
  indicates the necessary information is ready for building
  SMBIOS 42h record.

  @param[out] InformationReadinessGuid  Pointer to retrive the protocol
                                        GUID.

  @retval EFI_SUCCESS          Notification is required for building up
                               SMBIOS type 42h record.
  @retval EFI_UNSUPPORTED      Notification is not required for building up
                               SMBIOS type 42h record.
  @retval EFI_ALREADY_STARTED  Platform host information is already ready.
  @retval Others               Other errors.
**/
EFI_STATUS
RedfishPlatformHostInterfaceNotification (
  OUT EFI_GUID  **InformationReadinessGuid
  )
{
  EFI_STATUS  Status;

  DEBUG ((DEBUG_MANAGEABILITY, "%a: Entry\n", __func__));

  *InformationReadinessGuid = NULL;
  InitializeListHead (&mBmcUsbNic);
  InitializeListHead (&mBmcIpmiLan);

  //
  // Check if USB NIC exposed by BMC is already
  // connected.
  //
  Status = CheckBmcUsbNic (NULL);
  if (!EFI_ERROR (Status)) {
    return EFI_ALREADY_STARTED;
  }

  if (Status == EFI_NOT_FOUND) {
    DEBUG ((DEBUG_REDFISH_HOST_INTERFACE, "%a: BMC USB NIC is not found. Register the notification.\n", __func__));

    // Register the notification of SNP installation.
    Status = gBS->CreateEvent (
                    EVT_NOTIFY_SIGNAL,
                    TPL_CALLBACK,
                    PlatformHostInterfaceSnpCallback,
                    NULL,
                    &mPlatformHostInterfaceSnpEvent
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "%a: Fail to create event for the installation of SNP protocol.", __func__));
      return Status;
    }

    Status = gBS->RegisterProtocolNotify (
                    &gEfiSimpleNetworkProtocolGuid,
                    mPlatformHostInterfaceSnpEvent,
                    &mPlatformHostInterfaceSnpRegistration
                    );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_ERROR, "%a: Fail to register event for the installation of SNP protocol.", __func__));
      return Status;
    }

    *InformationReadinessGuid = &mPlatformHostInterfaceBmcUsbNicReadinessGuid;
    return EFI_SUCCESS;
  }

  DEBUG ((DEBUG_ERROR, "%a: Something wrong when look for BMC USB NIC.\n", __func__));
  return Status;
}
