/** @file
  Driver Binding functions implementation for UefiPxeBc Driver.

  (C) Copyright 2014 Hewlett-Packard Development Company, L.P.<BR>
  Copyright (c) 2007 - 2019, Intel Corporation. All rights reserved.<BR>
  Copyright (c) Microsoft Corporation

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

**/

#include "PxeBcImpl.h"

EFI_DRIVER_BINDING_PROTOCOL  gPxeBcIp4DriverBinding = {
  PxeBcIp4DriverBindingSupported,
  PxeBcIp4DriverBindingStart,
  PxeBcIp4DriverBindingStop,
  0xa,
  NULL,
  NULL
};

EFI_DRIVER_BINDING_PROTOCOL  gPxeBcIp6DriverBinding = {
  PxeBcIp6DriverBindingSupported,
  PxeBcIp6DriverBindingStart,
  PxeBcIp6DriverBindingStop,
  0xa,
  NULL,
  NULL
};

/**
  Get the Nic handle using any child handle in the IPv4 stack.

  @param[in]  ControllerHandle    Pointer to child handle over IPv4.

  @return NicHandle               The pointer to the Nic handle.

**/
EFI_HANDLE
PxeBcGetNicByIp4Children (
  IN EFI_HANDLE  ControllerHandle
  )
{
  EFI_HANDLE  NicHandle;

  NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiArpProtocolGuid);
  if (NicHandle == NULL) {
    NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiIp4ProtocolGuid);
    if (NicHandle == NULL) {
      NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiUdp4ProtocolGuid);
      if (NicHandle == NULL) {
        NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiDhcp4ProtocolGuid);
        if (NicHandle == NULL) {
          NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiMtftp4ProtocolGuid);
          if (NicHandle == NULL) {
            return NULL;
          }
        }
      }
    }
  }

  return NicHandle;
}

/**
  Get the Nic handle using any child handle in the IPv6 stack.

  @param[in]  ControllerHandle    Pointer to child handle over IPv6.

  @return NicHandle               The pointer to the Nic handle.

**/
EFI_HANDLE
PxeBcGetNicByIp6Children (
  IN EFI_HANDLE  ControllerHandle
  )
{
  EFI_HANDLE  NicHandle;

  NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiIp6ProtocolGuid);
  if (NicHandle == NULL) {
    NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiUdp6ProtocolGuid);
    if (NicHandle == NULL) {
      NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiDhcp6ProtocolGuid);
      if (NicHandle == NULL) {
        NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiMtftp6ProtocolGuid);
        if (NicHandle == NULL) {
          return NULL;
        }
      }
    }
  }

  return NicHandle;
}

/**
  Destroy the opened instances based on IPv4.

  @param[in]  This              Pointer to the EFI_DRIVER_BINDING_PROTOCOL.
  @param[in]  Private           Pointer to PXEBC_PRIVATE_DATA.

**/
VOID
PxeBcDestroyIp4Children (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN PXEBC_PRIVATE_DATA           *Private
  )
{
  ASSERT (Private != NULL);

  if (Private->ArpChild != NULL) {
    //
    // Close Arp for PxeBc->Arp and destroy the instance.
    //
    gBS->CloseProtocol (
           Private->ArpChild,
           &gEfiArpProtocolGuid,
           This->DriverBindingHandle,
           Private->Controller
           );

    NetLibDestroyServiceChild (
      Private->Controller,
      This->DriverBindingHandle,
      &gEfiArpServiceBindingProtocolGuid,
      Private->ArpChild
      );
  }

  if (Private->Ip4Child != NULL) {
    //
    // Close Ip4 for background ICMP error message and destroy the instance.
    //
    gBS->CloseProtocol (
           Private->Ip4Child,
           &gEfiIp4ProtocolGuid,
           This->DriverBindingHandle,
           Private->Controller
           );

    NetLibDestroyServiceChild (
      Private->Controller,
      This->DriverBindingHandle,
      &gEfiIp4ServiceBindingProtocolGuid,
      Private->Ip4Child
      );
  }

  if (Private->Udp4WriteChild != NULL) {
    //
    // Close Udp4 for PxeBc->UdpWrite and destroy the instance.
    //
    gBS->CloseProtocol (
           Private->Udp4WriteChild,
           &gEfiUdp4ProtocolGuid,
           This->DriverBindingHandle,
           Private->Controller
           );

    NetLibDestroyServiceChild (
      Private->Controller,
      This->DriverBindingHandle,
      &gEfiUdp4ServiceBindingProtocolGuid,
      Private->Udp4WriteChild
      );
  }

  if (Private->Udp4ReadChild != NULL) {
    //
    // Close Udp4 for PxeBc->UdpRead and destroy the instance.
    //
    gBS->CloseProtocol (
           Private->Udp4ReadChild,
           &gEfiUdp4ProtocolGuid,
           This->DriverBindingHandle,
           Private->Controller
           );

    NetLibDestroyServiceChild (
      Private->Controller,
      This->DriverBindingHandle,
      &gEfiUdp4ServiceBindingProtocolGuid,
      Private->Udp4ReadChild
      );
  }

  if (Private->Mtftp4Child != NULL) {
    //
    // Close Mtftp4 for PxeBc->Mtftp4 and destroy the instance.
    //
    gBS->CloseProtocol (
           Private->Mtftp4Child,
           &gEfiMtftp4ProtocolGuid,
           This->DriverBindingHandle,
           Private->Controller
           );

    NetLibDestroyServiceChild (
      Private->Controller,
      This->DriverBindingHandle,
      &gEfiMtftp4ServiceBindingProtocolGuid,
      Private->Mtftp4Child
      );
  }

  if (Private->Dhcp4Child != NULL) {
    //
    // Close Dhcp4 for PxeBc->Dhcp4 and destroy the instance.
    //
    gBS->CloseProtocol (
           Private->Dhcp4Child,
           &gEfiDhcp4ProtocolGuid,
           This->DriverBindingHandle,
           Private->Controller
           );

    NetLibDestroyServiceChild (
      Private->Controller,
      This->DriverBindingHandle,
      &gEfiDhcp4ServiceBindingProtocolGuid,
      Private->Dhcp4Child
      );
  }

  if (Private->Ip4Nic != NULL) {
    //
    // Close PxeBcPrivate from the parent Nic handle and destroy the virtual handle.
    //
    gBS->CloseProtocol (
           Private->Controller,
           &gEfiCallerIdGuid,
           This->DriverBindingHandle,
           Private->Ip4Nic->Controller
           );

    gBS->UninstallMultipleProtocolInterfaces (
           Private->Ip4Nic->Controller,
           &gEfiDevicePathProtocolGuid,
           Private->Ip4Nic->DevicePath,
           &gEfiLoadFileProtocolGuid,
           &Private->Ip4Nic->LoadFile,
           &gEfiPxeBaseCodeProtocolGuid,
           &Private->PxeBc,
           NULL
           );
    if (Private->Ip4Nic->DevicePath != NULL) {
      FreePool (Private->Ip4Nic->DevicePath);
    }

    if (Private->Snp != NULL) {
      //
      // Close SNP from the child virtual handle
      //
      gBS->CloseProtocol (
             Private->Ip4Nic->Controller,
             &gEfiSimpleNetworkProtocolGuid,
             This->DriverBindingHandle,
             Private->Ip4Nic->Controller
             );

      gBS->UninstallProtocolInterface (
             Private->Ip4Nic->Controller,
             &gEfiSimpleNetworkProtocolGuid,
             Private->Snp
             );
    }

    FreePool (Private->Ip4Nic);
  }

  Private->ArpChild       = NULL;
  Private->Ip4Child       = NULL;
  Private->Udp4WriteChild = NULL;
  Private->Udp4ReadChild  = NULL;
  Private->Mtftp4Child    = NULL;
  Private->Dhcp4Child     = NULL;
  Private->Ip4Nic         = NULL;
}

/**
  Destroy the opened instances based on IPv6.

  @param[in]  This              Pointer to the EFI_DRIVER_BINDING_PROTOCOL.
  @param[in]  Private           Pointer to PXEBC_PRIVATE_DATA.

**/
VOID
PxeBcDestroyIp6Children (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN PXEBC_PRIVATE_DATA           *Private
  )
{
  ASSERT (Private != NULL);

  if (Private->Ip6Child != NULL) {
    //
    // Close Ip6 for Ip6->Ip6Config and destroy the instance.
    //
    gBS->CloseProtocol (
           Private->Ip6Child,
           &gEfiIp6ProtocolGuid,
           This->DriverBindingHandle,
           Private->Controller
           );

    NetLibDestroyServiceChild (
      Private->Controller,
      This->DriverBindingHandle,
      &gEfiIp6ServiceBindingProtocolGuid,
      Private->Ip6Child
      );
  }

  if (Private->Udp6WriteChild != NULL) {
    //
    // Close Udp6 for PxeBc->UdpWrite and destroy the instance.
    //
    gBS->CloseProtocol (
           Private->Udp6WriteChild,
           &gEfiUdp6ProtocolGuid,
           This->DriverBindingHandle,
           Private->Controller
           );
    NetLibDestroyServiceChild (
      Private->Controller,
      This->DriverBindingHandle,
      &gEfiUdp6ServiceBindingProtocolGuid,
      Private->Udp6WriteChild
      );
  }

  if (Private->Udp6ReadChild != NULL) {
    //
    // Close Udp6 for PxeBc->UdpRead and destroy the instance.
    //
    gBS->CloseProtocol (
           Private->Udp6ReadChild,
           &gEfiUdp6ProtocolGuid,
           This->DriverBindingHandle,
           Private->Controller
           );
    NetLibDestroyServiceChild (
      Private->Controller,
      This->DriverBindingHandle,
      &gEfiUdp6ServiceBindingProtocolGuid,
      Private->Udp6ReadChild
      );
  }

  if (Private->Mtftp6Child != NULL) {
    //
    // Close Mtftp6 for PxeBc->Mtftp and destroy the instance.
    //
    gBS->CloseProtocol (
           Private->Mtftp6Child,
           &gEfiMtftp6ProtocolGuid,
           This->DriverBindingHandle,
           Private->Controller
           );

    NetLibDestroyServiceChild (
      Private->Controller,
      This->DriverBindingHandle,
      &gEfiMtftp6ServiceBindingProtocolGuid,
      Private->Mtftp6Child
      );
  }

  if (Private->Dhcp6Child != NULL) {
    //
    // Close Dhcp6 for PxeBc->Dhcp and destroy the instance.
    //
    gBS->CloseProtocol (
           Private->Dhcp6Child,
           &gEfiDhcp6ProtocolGuid,
           This->DriverBindingHandle,
           Private->Controller
           );

    NetLibDestroyServiceChild (
      Private->Controller,
      This->DriverBindingHandle,
      &gEfiDhcp6ServiceBindingProtocolGuid,
      Private->Dhcp6Child
      );
  }

  if (Private->Ip6Nic != NULL) {
    //
    // Close PxeBcPrivate from the parent Nic handle and destroy the virtual handle.
    //
    gBS->CloseProtocol (
           Private->Controller,
           &gEfiCallerIdGuid,
           This->DriverBindingHandle,
           Private->Ip6Nic->Controller
           );

    gBS->UninstallMultipleProtocolInterfaces (
           Private->Ip6Nic->Controller,
           &gEfiDevicePathProtocolGuid,
           Private->Ip6Nic->DevicePath,
           &gEfiLoadFileProtocolGuid,
           &Private->Ip6Nic->LoadFile,
           &gEfiPxeBaseCodeProtocolGuid,
           &Private->PxeBc,
           NULL
           );
    if (Private->Ip6Nic->DevicePath != NULL) {
      FreePool (Private->Ip6Nic->DevicePath);
    }

    if (Private->Snp != NULL) {
      //
      // Close SNP from the child virtual handle
      //
      gBS->CloseProtocol (
             Private->Ip6Nic->Controller,
             &gEfiSimpleNetworkProtocolGuid,
             This->DriverBindingHandle,
             Private->Ip6Nic->Controller
             );
      gBS->UninstallProtocolInterface (
             Private->Ip6Nic->Controller,
             &gEfiSimpleNetworkProtocolGuid,
             Private->Snp
             );
    }

    FreePool (Private->Ip6Nic);
  }

  Private->Ip6Child           = NULL;
  Private->Udp6WriteChild     = NULL;
  Private->Udp6ReadChild      = NULL;
  Private->Mtftp6Child        = NULL;
  Private->Dhcp6Child         = NULL;
  Private->Ip6Nic             = NULL;
  Private->Mode.Ipv6Available = FALSE;
}

/**
  Check whether UNDI protocol supports IPv6.

  @param[in]   ControllerHandle  Controller handle.
  @param[in]   Private           Pointer to PXEBC_PRIVATE_DATA.
  @param[out]  Ipv6Support       TRUE if UNDI supports IPv6.

  @retval EFI_SUCCESS            Get the result whether UNDI supports IPv6 by NII or AIP protocol successfully.
  @retval EFI_NOT_FOUND          Don't know whether UNDI supports IPv6 since NII or AIP is not available.

**/
EFI_STATUS
PxeBcCheckIpv6Support (
  IN  EFI_HANDLE          ControllerHandle,
  IN  PXEBC_PRIVATE_DATA  *Private,
  OUT BOOLEAN             *Ipv6Support
  )
{
  EFI_HANDLE                        Handle;
  EFI_ADAPTER_INFORMATION_PROTOCOL  *Aip;
  EFI_STATUS                        Status;
  EFI_GUID                          *InfoTypesBuffer;
  UINTN                             InfoTypeBufferCount;
  UINTN                             TypeIndex;
  BOOLEAN                           Supported;
  VOID                              *InfoBlock;
  UINTN                             InfoBlockSize;

  ASSERT (Private != NULL && Ipv6Support != NULL);

  //
  // Check whether the UNDI supports IPv6 by NII protocol.
  //
  if (Private->Nii != NULL) {
    *Ipv6Support = Private->Nii->Ipv6Supported;
    return EFI_SUCCESS;
  }

  //
  // Check whether the UNDI supports IPv6 by AIP protocol.
  //

  //
  // Get the NIC handle by SNP protocol.
  //
  Handle = NetLibGetSnpHandle (ControllerHandle, NULL);
  if (Handle == NULL) {
    return EFI_NOT_FOUND;
  }

  Aip    = NULL;
  Status = gBS->HandleProtocol (
                  Handle,
                  &gEfiAdapterInformationProtocolGuid,
                  (VOID *)&Aip
                  );
  if (EFI_ERROR (Status) || (Aip == NULL)) {
    return EFI_NOT_FOUND;
  }

  InfoTypesBuffer     = NULL;
  InfoTypeBufferCount = 0;
  Status              = Aip->GetSupportedTypes (Aip, &InfoTypesBuffer, &InfoTypeBufferCount);
  if (EFI_ERROR (Status) || (InfoTypesBuffer == NULL)) {
    FreePool (InfoTypesBuffer);
    return EFI_NOT_FOUND;
  }

  Supported = FALSE;
  for (TypeIndex = 0; TypeIndex < InfoTypeBufferCount; TypeIndex++) {
    if (CompareGuid (&InfoTypesBuffer[TypeIndex], &gEfiAdapterInfoUndiIpv6SupportGuid)) {
      Supported = TRUE;
      break;
    }
  }

  FreePool (InfoTypesBuffer);
  if (!Supported) {
    return EFI_NOT_FOUND;
  }

  //
  // We now have adapter information block.
  //
  InfoBlock     = NULL;
  InfoBlockSize = 0;
  Status        = Aip->GetInformation (Aip, &gEfiAdapterInfoUndiIpv6SupportGuid, &InfoBlock, &InfoBlockSize);
  if (EFI_ERROR (Status) || (InfoBlock == NULL)) {
    FreePool (InfoBlock);
    return EFI_NOT_FOUND;
  }

  *Ipv6Support = ((EFI_ADAPTER_INFO_UNDI_IPV6_SUPPORT *)InfoBlock)->Ipv6Support;
  FreePool (InfoBlock);
  return EFI_SUCCESS;
}

/**
  Create the opened instances based on IPv4.

  @param[in]  This              Pointer to EFI_DRIVER_BINDING_PROTOCOL.
  @param[in]  ControllerHandle  Handle of the child to destroy.
  @param[in]  Private Handle    Pointer to PXEBC_PRIVATE_DATA.

  @retval EFI_SUCCESS           The instances based on IPv4 were all created successfully.
  @retval Others                An unexpected error occurred.

**/
EFI_STATUS
PxeBcCreateIp4Children (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN PXEBC_PRIVATE_DATA           *Private
  )
{
  EFI_STATUS                   Status;
  IPv4_DEVICE_PATH             Ip4Node;
  EFI_PXE_BASE_CODE_MODE       *Mode;
  EFI_UDP4_CONFIG_DATA         *Udp4CfgData;
  EFI_IP4_CONFIG_DATA          *Ip4CfgData;
  EFI_IP4_MODE_DATA            Ip4ModeData;
  PXEBC_PRIVATE_PROTOCOL       *Id;
  EFI_SIMPLE_NETWORK_PROTOCOL  *Snp;

  if (Private->Ip4Nic != NULL) {
    //
    // Already created before.
    //
    return EFI_SUCCESS;
  }

  //
  // Create Dhcp4 child and open Dhcp4 protocol for PxeBc->Dhcp.
  //
  Status = NetLibCreateServiceChild (
             ControllerHandle,
             This->DriverBindingHandle,
             &gEfiDhcp4ServiceBindingProtocolGuid,
             &Private->Dhcp4Child
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = gBS->OpenProtocol (
                  Private->Dhcp4Child,
                  &gEfiDhcp4ProtocolGuid,
                  (VOID **)&Private->Dhcp4,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Create Mtftp4 child and open Mtftp4 protocol for PxeBc->Mtftp.
  //
  Status = NetLibCreateServiceChild (
             ControllerHandle,
             This->DriverBindingHandle,
             &gEfiMtftp4ServiceBindingProtocolGuid,
             &Private->Mtftp4Child
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = gBS->OpenProtocol (
                  Private->Mtftp4Child,
                  &gEfiMtftp4ProtocolGuid,
                  (VOID **)&Private->Mtftp4,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Create Udp4 child and open Udp4 protocol for PxeBc->UdpRead.
  //
  Status = NetLibCreateServiceChild (
             ControllerHandle,
             This->DriverBindingHandle,
             &gEfiUdp4ServiceBindingProtocolGuid,
             &Private->Udp4ReadChild
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = gBS->OpenProtocol (
                  Private->Udp4ReadChild,
                  &gEfiUdp4ProtocolGuid,
                  (VOID **)&Private->Udp4Read,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Create Udp4 child and open Udp4 protocol for PxeBc->UdpWrite.
  //
  Status = NetLibCreateServiceChild (
             ControllerHandle,
             This->DriverBindingHandle,
             &gEfiUdp4ServiceBindingProtocolGuid,
             &Private->Udp4WriteChild
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = gBS->OpenProtocol (
                  Private->Udp4WriteChild,
                  &gEfiUdp4ProtocolGuid,
                  (VOID **)&Private->Udp4Write,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Create Arp child and open Arp protocol for PxeBc->Arp.
  //
  Status = NetLibCreateServiceChild (
             ControllerHandle,
             This->DriverBindingHandle,
             &gEfiArpServiceBindingProtocolGuid,
             &Private->ArpChild
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = gBS->OpenProtocol (
                  Private->ArpChild,
                  &gEfiArpProtocolGuid,
                  (VOID **)&Private->Arp,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Create Ip4 child and open Ip4 protocol for background ICMP packets.
  //
  Status = NetLibCreateServiceChild (
             ControllerHandle,
             This->DriverBindingHandle,
             &gEfiIp4ServiceBindingProtocolGuid,
             &Private->Ip4Child
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = gBS->OpenProtocol (
                  Private->Ip4Child,
                  &gEfiIp4ProtocolGuid,
                  (VOID **)&Private->Ip4,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Get max packet size from Ip4 to calculate block size for Tftp later.
  //
  Status = Private->Ip4->GetModeData (Private->Ip4, &Ip4ModeData, NULL, NULL);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Private->Ip4MaxPacketSize = Ip4ModeData.MaxPacketSize;

  Private->Ip4Nic = AllocateZeroPool (sizeof (PXEBC_VIRTUAL_NIC));
  if (Private->Ip4Nic == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Private->Ip4Nic->Private   = Private;
  Private->Ip4Nic->Signature = PXEBC_VIRTUAL_NIC_SIGNATURE;

  //
  // Locate Ip4->Ip4Config2 and store it for set IPv4 Policy.
  //
  Status = gBS->HandleProtocol (
                  ControllerHandle,
                  &gEfiIp4Config2ProtocolGuid,
                  (VOID **)&Private->Ip4Config2
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Create a device path node for Ipv4 virtual nic, and append it.
  //
  ZeroMem (&Ip4Node, sizeof (IPv4_DEVICE_PATH));
  Ip4Node.Header.Type     = MESSAGING_DEVICE_PATH;
  Ip4Node.Header.SubType  = MSG_IPv4_DP;
  Ip4Node.StaticIpAddress = FALSE;

  SetDevicePathNodeLength (&Ip4Node.Header, sizeof (Ip4Node));

  Private->Ip4Nic->DevicePath = AppendDevicePathNode (Private->DevicePath, &Ip4Node.Header);

  if (Private->Ip4Nic->DevicePath == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_ERROR;
  }

  CopyMem (
    &Private->Ip4Nic->LoadFile,
    &gLoadFileProtocolTemplate,
    sizeof (EFI_LOAD_FILE_PROTOCOL)
    );

  //
  // Create a new handle for IPv4 virtual nic,
  // and install PxeBaseCode, LoadFile and DevicePath protocols.
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &Private->Ip4Nic->Controller,
                  &gEfiDevicePathProtocolGuid,
                  Private->Ip4Nic->DevicePath,
                  &gEfiLoadFileProtocolGuid,
                  &Private->Ip4Nic->LoadFile,
                  &gEfiPxeBaseCodeProtocolGuid,
                  &Private->PxeBc,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  if (Private->Snp != NULL) {
    //
    // Install SNP protocol on purpose is for some OS loader backward
    // compatibility consideration.
    //
    Status = gBS->InstallProtocolInterface (
                    &Private->Ip4Nic->Controller,
                    &gEfiSimpleNetworkProtocolGuid,
                    EFI_NATIVE_INTERFACE,
                    Private->Snp
                    );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }

    //
    // Open SNP on the child handle BY_DRIVER|EXCLUSIVE. It will prevent any additionally
    // layering to perform the experiment.
    //
    Status = gBS->OpenProtocol (
                    Private->Ip4Nic->Controller,
                    &gEfiSimpleNetworkProtocolGuid,
                    (VOID **)&Snp,
                    This->DriverBindingHandle,
                    Private->Ip4Nic->Controller,
                    EFI_OPEN_PROTOCOL_BY_DRIVER|EFI_OPEN_PROTOCOL_EXCLUSIVE
                    );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }
  }

  //
  // Open PxeBaseCodePrivate protocol by child to setup a parent-child relationship between
  // real NIC handle and the virtual IPv4 NIC handle.
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiCallerIdGuid,
                  (VOID **)&Id,
                  This->DriverBindingHandle,
                  Private->Ip4Nic->Controller,
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Set default configure data for Udp4Read and Ip4 instance.
  //
  Mode        = Private->PxeBc.Mode;
  Udp4CfgData = &Private->Udp4CfgData;
  Ip4CfgData  = &Private->Ip4CfgData;

  Udp4CfgData->AcceptBroadcast    = FALSE;
  Udp4CfgData->AcceptAnyPort      = TRUE;
  Udp4CfgData->AllowDuplicatePort = TRUE;
  Udp4CfgData->TypeOfService      = Mode->ToS;
  Udp4CfgData->TimeToLive         = Mode->TTL;
  Udp4CfgData->ReceiveTimeout     = PXEBC_DEFAULT_LIFETIME;
  Udp4CfgData->TransmitTimeout    = PXEBC_DEFAULT_LIFETIME;

  Ip4CfgData->AcceptIcmpErrors = TRUE;
  Ip4CfgData->DefaultProtocol  = EFI_IP_PROTO_ICMP;
  Ip4CfgData->TypeOfService    = Mode->ToS;
  Ip4CfgData->TimeToLive       = Mode->TTL;
  Ip4CfgData->ReceiveTimeout   = PXEBC_DEFAULT_LIFETIME;
  Ip4CfgData->TransmitTimeout  = PXEBC_DEFAULT_LIFETIME;

  return EFI_SUCCESS;

ON_ERROR:
  PxeBcDestroyIp4Children (This, Private);
  return Status;
}

/**
  Create the opened instances based on IPv6.

  @param[in]  This              Pointer to EFI_DRIVER_BINDING_PROTOCOL.
  @param[in]  ControllerHandle  Handle of the child to destroy.
  @param[in]  Private Handle    Pointer to PXEBC_PRIVATE_DATA.

  @retval EFI_SUCCESS           The instances based on IPv6 were all created successfully.
  @retval Others                An unexpected error occurred.

**/
EFI_STATUS
PxeBcCreateIp6Children (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN PXEBC_PRIVATE_DATA           *Private
  )
{
  EFI_STATUS                   Status;
  IPv6_DEVICE_PATH             Ip6Node;
  EFI_UDP6_CONFIG_DATA         *Udp6CfgData;
  EFI_IP6_CONFIG_DATA          *Ip6CfgData;
  EFI_IP6_MODE_DATA            Ip6ModeData;
  PXEBC_PRIVATE_PROTOCOL       *Id;
  EFI_SIMPLE_NETWORK_PROTOCOL  *Snp;
  UINTN                        Index;
  UINT32                       Random;

  Status = PseudoRandomU32 (&Random);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Failed to generate random number using EFI_RNG_PROTOCOL: %r\n", Status));
    return Status;
  }

  if (Private->Ip6Nic != NULL) {
    //
    // Already created before.
    //
    return EFI_SUCCESS;
  }

  Private->Ip6Nic = AllocateZeroPool (sizeof (PXEBC_VIRTUAL_NIC));

  if (Private->Ip6Nic == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Private->Ip6Nic->Private   = Private;
  Private->Ip6Nic->Signature = PXEBC_VIRTUAL_NIC_SIGNATURE;

  //
  // Create Dhcp6 child and open Dhcp6 protocol for PxeBc->Dhcp.
  //
  Status = NetLibCreateServiceChild (
             ControllerHandle,
             This->DriverBindingHandle,
             &gEfiDhcp6ServiceBindingProtocolGuid,
             &Private->Dhcp6Child
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = gBS->OpenProtocol (
                  Private->Dhcp6Child,
                  &gEfiDhcp6ProtocolGuid,
                  (VOID **)&Private->Dhcp6,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Set a random IAID for the Dhcp6 assigned address.
  //
  Private->IaId = Random;
  if (Private->Snp != NULL) {
    for (Index = 0; Index < Private->Snp->Mode->HwAddressSize; Index++) {
      Private->IaId |= (Private->Snp->Mode->CurrentAddress.Addr[Index] << ((Index << 3) & 31));
    }
  }

  //
  // Create Mtftp6 child and open Mtftp6 protocol for PxeBc->Mtftp.
  //
  Status = NetLibCreateServiceChild (
             ControllerHandle,
             This->DriverBindingHandle,
             &gEfiMtftp6ServiceBindingProtocolGuid,
             &Private->Mtftp6Child
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = gBS->OpenProtocol (
                  Private->Mtftp6Child,
                  &gEfiMtftp6ProtocolGuid,
                  (VOID **)&Private->Mtftp6,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Create Udp6 child and open Udp6 protocol for PxeBc->UdpRead.
  //
  Status = NetLibCreateServiceChild (
             ControllerHandle,
             This->DriverBindingHandle,
             &gEfiUdp6ServiceBindingProtocolGuid,
             &Private->Udp6ReadChild
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = gBS->OpenProtocol (
                  Private->Udp6ReadChild,
                  &gEfiUdp6ProtocolGuid,
                  (VOID **)&Private->Udp6Read,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Create Udp6 child and open Udp6 protocol for PxeBc->UdpWrite.
  //
  Status = NetLibCreateServiceChild (
             ControllerHandle,
             This->DriverBindingHandle,
             &gEfiUdp6ServiceBindingProtocolGuid,
             &Private->Udp6WriteChild
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = gBS->OpenProtocol (
                  Private->Udp6WriteChild,
                  &gEfiUdp6ProtocolGuid,
                  (VOID **)&Private->Udp6Write,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Create Ip6 child and open Ip6 protocol for background ICMP6 packets.
  //
  Status = NetLibCreateServiceChild (
             ControllerHandle,
             This->DriverBindingHandle,
             &gEfiIp6ServiceBindingProtocolGuid,
             &Private->Ip6Child
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = gBS->OpenProtocol (
                  Private->Ip6Child,
                  &gEfiIp6ProtocolGuid,
                  (VOID **)&Private->Ip6,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Get max packet size from Ip6 to calculate block size for Tftp later.
  //
  Status = Private->Ip6->GetModeData (Private->Ip6, &Ip6ModeData, NULL, NULL);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Private->Ip6MaxPacketSize = Ip6ModeData.MaxPacketSize;

  if (Ip6ModeData.AddressList != NULL) {
    FreePool (Ip6ModeData.AddressList);
  }

  if (Ip6ModeData.GroupTable != NULL) {
    FreePool (Ip6ModeData.GroupTable);
  }

  if (Ip6ModeData.RouteTable != NULL) {
    FreePool (Ip6ModeData.RouteTable);
  }

  if (Ip6ModeData.NeighborCache != NULL) {
    FreePool (Ip6ModeData.NeighborCache);
  }

  if (Ip6ModeData.PrefixTable != NULL) {
    FreePool (Ip6ModeData.PrefixTable);
  }

  if (Ip6ModeData.IcmpTypeList != NULL) {
    FreePool (Ip6ModeData.IcmpTypeList);
  }

  //
  // Locate Ip6->Ip6Config and store it for set IPv6 address.
  //
  Status = gBS->HandleProtocol (
                  ControllerHandle,
                  &gEfiIp6ConfigProtocolGuid,
                  (VOID **)&Private->Ip6Cfg
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Create a device path node for Ipv6 virtual nic, and append it.
  //
  ZeroMem (&Ip6Node, sizeof (IPv6_DEVICE_PATH));
  Ip6Node.Header.Type    = MESSAGING_DEVICE_PATH;
  Ip6Node.Header.SubType = MSG_IPv6_DP;
  Ip6Node.PrefixLength   = IP6_PREFIX_LENGTH;

  SetDevicePathNodeLength (&Ip6Node.Header, sizeof (Ip6Node));

  Private->Ip6Nic->DevicePath = AppendDevicePathNode (Private->DevicePath, &Ip6Node.Header);

  if (Private->Ip6Nic->DevicePath == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_ERROR;
  }

  CopyMem (
    &Private->Ip6Nic->LoadFile,
    &gLoadFileProtocolTemplate,
    sizeof (EFI_LOAD_FILE_PROTOCOL)
    );

  //
  // Create a new handle for IPv6 virtual nic,
  // and install PxeBaseCode, LoadFile and DevicePath protocols.
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &Private->Ip6Nic->Controller,
                  &gEfiDevicePathProtocolGuid,
                  Private->Ip6Nic->DevicePath,
                  &gEfiLoadFileProtocolGuid,
                  &Private->Ip6Nic->LoadFile,
                  &gEfiPxeBaseCodeProtocolGuid,
                  &Private->PxeBc,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  if (Private->Snp != NULL) {
    //
    // Install SNP protocol on purpose is for some OS loader backward
    // compatibility consideration.
    //
    Status = gBS->InstallProtocolInterface (
                    &Private->Ip6Nic->Controller,
                    &gEfiSimpleNetworkProtocolGuid,
                    EFI_NATIVE_INTERFACE,
                    Private->Snp
                    );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }

    //
    // Open SNP on the child handle BY_DRIVER|EXCLUSIVE. It will prevent any additionally
    // layering to perform the experiment.
    //
    Status = gBS->OpenProtocol (
                    Private->Ip6Nic->Controller,
                    &gEfiSimpleNetworkProtocolGuid,
                    (VOID **)&Snp,
                    This->DriverBindingHandle,
                    Private->Ip6Nic->Controller,
                    EFI_OPEN_PROTOCOL_BY_DRIVER|EFI_OPEN_PROTOCOL_EXCLUSIVE
                    );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }
  }

  //
  // Open PxeBaseCodePrivate protocol by child to setup a parent-child relationship between
  // real NIC handle and the virtual IPv6 NIC handle.
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiCallerIdGuid,
                  (VOID **)&Id,
                  This->DriverBindingHandle,
                  Private->Ip6Nic->Controller,
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Set IPv6 available flag and set default configure data for
  // Udp6Read and Ip6 instance.
  //
  Status = PxeBcCheckIpv6Support (ControllerHandle, Private, &Private->Mode.Ipv6Available);
  if (EFI_ERROR (Status)) {
    //
    // Fail to get the data whether UNDI supports IPv6. Set default value.
    //
    Private->Mode.Ipv6Available = TRUE;
  }

  if (!Private->Mode.Ipv6Available) {
    goto ON_ERROR;
  }

  Udp6CfgData = &Private->Udp6CfgData;
  Ip6CfgData  = &Private->Ip6CfgData;

  Udp6CfgData->AcceptAnyPort      = TRUE;
  Udp6CfgData->AllowDuplicatePort = TRUE;
  Udp6CfgData->HopLimit           = PXEBC_DEFAULT_HOPLIMIT;
  Udp6CfgData->ReceiveTimeout     = PXEBC_DEFAULT_LIFETIME;
  Udp6CfgData->TransmitTimeout    = PXEBC_DEFAULT_LIFETIME;

  Ip6CfgData->AcceptIcmpErrors = TRUE;
  Ip6CfgData->DefaultProtocol  = IP6_ICMP;
  Ip6CfgData->HopLimit         = PXEBC_DEFAULT_HOPLIMIT;
  Ip6CfgData->ReceiveTimeout   = PXEBC_DEFAULT_LIFETIME;
  Ip6CfgData->TransmitTimeout  = PXEBC_DEFAULT_LIFETIME;

  return EFI_SUCCESS;

ON_ERROR:
  PxeBcDestroyIp6Children (This, Private);
  return Status;
}

/**
  The entry point for UefiPxeBc driver that installs the driver
  binding and component name protocol on its image.

  @param[in]  ImageHandle          The Image handle of the driver.
  @param[in]  SystemTable          The system table.

  @return EFI_SUCCESS
  @return Others

**/
EFI_STATUS
EFIAPI
PxeBcDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;

  if ((PcdGet8 (PcdIPv4PXESupport) == PXE_DISABLED) && (PcdGet8 (PcdIPv6PXESupport) == PXE_DISABLED)) {
    return EFI_UNSUPPORTED;
  }

  Status = EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             SystemTable,
             &gPxeBcIp4DriverBinding,
             ImageHandle,
             &gPxeBcComponentName,
             &gPxeBcComponentName2
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             SystemTable,
             &gPxeBcIp6DriverBinding,
             NULL,
             &gPxeBcComponentName,
             &gPxeBcComponentName2
             );
  if (EFI_ERROR (Status)) {
    EfiLibUninstallDriverBindingComponentName2 (
      &gPxeBcIp4DriverBinding,
      &gPxeBcComponentName,
      &gPxeBcComponentName2
      );
  }

  return Status;
}

/**
  Test to see if this driver supports ControllerHandle. This is the worker function for
  PxeBcIp4(6)DriverBindingSupported.

  @param[in]  This                The pointer to the driver binding protocol.
  @param[in]  ControllerHandle    The handle of device to be tested.
  @param[in]  RemainingDevicePath Optional parameter used to pick a specific child
                                  device to be started.
  @param[in]  IpVersion           IP_VERSION_4 or IP_VERSION_6.

  @retval EFI_SUCCESS         This driver supports this device.
  @retval EFI_UNSUPPORTED     This driver does not support this device.

**/
EFI_STATUS
EFIAPI
PxeBcSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL,
  IN UINT8                        IpVersion
  )
{
  EFI_STATUS  Status;
  EFI_GUID    *DhcpServiceBindingGuid;
  EFI_GUID    *MtftpServiceBindingGuid;

  if (IpVersion == IP_VERSION_4) {
    if (PcdGet8 (PcdIPv4PXESupport) == PXE_DISABLED) {
      return EFI_UNSUPPORTED;
    }

    DhcpServiceBindingGuid  = &gEfiDhcp4ServiceBindingProtocolGuid;
    MtftpServiceBindingGuid = &gEfiMtftp4ServiceBindingProtocolGuid;
  } else {
    if (PcdGet8 (PcdIPv6PXESupport) == PXE_DISABLED) {
      return EFI_UNSUPPORTED;
    }

    DhcpServiceBindingGuid  = &gEfiDhcp6ServiceBindingProtocolGuid;
    MtftpServiceBindingGuid = &gEfiMtftp6ServiceBindingProtocolGuid;
  }

  //
  // Try to open the Mtftp and Dhcp protocol to test whether IP stack is ready.
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  DhcpServiceBindingGuid,
                  NULL,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                  );
  if (!EFI_ERROR (Status)) {
    Status = gBS->OpenProtocol (
                    ControllerHandle,
                    MtftpServiceBindingGuid,
                    NULL,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                    );
  }

  //
  // It's unsupported case if IP stack are not ready.
  //
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  return EFI_SUCCESS;
}

/**
  Start this driver on ControllerHandle. This is the worker function for
  PxeBcIp4(6)DriverBindingStart.

  @param[in]  This                 The pointer to the driver binding protocol.
  @param[in]  ControllerHandle     The handle of device to be started.
  @param[in]  RemainingDevicePath  Optional parameter used to pick a specific child
                                   device to be started.
  @param[in]  IpVersion            IP_VERSION_4 or IP_VERSION_6.


  @retval EFI_SUCCESS          This driver is installed to ControllerHandle.
  @retval EFI_ALREADY_STARTED  This driver is already running on ControllerHandle.
  @retval other                This driver does not support this device.

**/
EFI_STATUS
EFIAPI
PxeBcStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL,
  IN UINT8                        IpVersion
  )
{
  PXEBC_PRIVATE_DATA      *Private;
  EFI_STATUS              Status;
  PXEBC_PRIVATE_PROTOCOL  *Id;
  BOOLEAN                 FirstStart;

  FirstStart = FALSE;
  Status     = gBS->OpenProtocol (
                      ControllerHandle,
                      &gEfiCallerIdGuid,
                      (VOID **)&Id,
                      This->DriverBindingHandle,
                      ControllerHandle,
                      EFI_OPEN_PROTOCOL_GET_PROTOCOL
                      );
  if (!EFI_ERROR (Status)) {
    //
    // Skip the initialization if the driver has been started already.
    //
    Private = PXEBC_PRIVATE_DATA_FROM_ID (Id);
  } else {
    FirstStart = TRUE;
    //
    // If the driver has not been started yet, it should do initialization.
    //
    Private = AllocateZeroPool (sizeof (PXEBC_PRIVATE_DATA));
    if (Private == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    CopyMem (
      &Private->PxeBc,
      &gPxeBcProtocolTemplate,
      sizeof (EFI_PXE_BASE_CODE_PROTOCOL)
      );

    Private->Signature          = PXEBC_PRIVATE_DATA_SIGNATURE;
    Private->Controller         = ControllerHandle;
    Private->Image              = This->ImageHandle;
    Private->PxeBc.Mode         = &Private->Mode;
    Private->Mode.Ipv6Supported = TRUE;
    Private->Mode.AutoArp       = TRUE;
    Private->Mode.TTL           = DEFAULT_TTL;
    Private->Mode.ToS           = DEFAULT_ToS;

    //
    // Open device path to prepare for appending virtual NIC node.
    //
    Status = gBS->OpenProtocol (
                    ControllerHandle,
                    &gEfiDevicePathProtocolGuid,
                    (VOID **)&Private->DevicePath,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );

    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }

    //
    // Get the NII interface if it exists, it's not required.
    //
    Status = gBS->OpenProtocol (
                    ControllerHandle,
                    &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                    (VOID **)&Private->Nii,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (EFI_ERROR (Status)) {
      Private->Nii = NULL;
    }

    //
    // Install PxeBaseCodePrivate protocol onto the real NIC handler.
    // PxeBaseCodePrivate protocol is only used to keep the relationship between
    // NIC handle and virtual child handles.
    // gEfiCallerIdGuid will be used as its protocol guid.
    //
    Status = gBS->InstallProtocolInterface (
                    &ControllerHandle,
                    &gEfiCallerIdGuid,
                    EFI_NATIVE_INTERFACE,
                    &Private->Id
                    );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }

    //
    // Try to locate SNP protocol.
    //
    NetLibGetSnpHandle (ControllerHandle, &Private->Snp);
  }

  if (IpVersion == IP_VERSION_4) {
    //
    // Try to create virtual NIC handle for IPv4.
    //
    Status = PxeBcCreateIp4Children (This, ControllerHandle, Private);
  } else {
    //
    // Try to create virtual NIC handle for IPv6.
    //
    Status = PxeBcCreateIp6Children (This, ControllerHandle, Private);
  }

  if (EFI_ERROR (Status)) {
    //
    // Failed to start PXE driver if IPv4 and IPv6 stack are both not available.
    //
    Status = EFI_DEVICE_ERROR;
    goto ON_ERROR;
  }

  return EFI_SUCCESS;

ON_ERROR:
  if (FirstStart) {
    gBS->UninstallProtocolInterface (
           ControllerHandle,
           &gEfiCallerIdGuid,
           &Private->Id
           );
  }

  if (IpVersion == IP_VERSION_4) {
    PxeBcDestroyIp4Children (This, Private);
  } else {
    PxeBcDestroyIp6Children (This, Private);
  }

  if (FirstStart && (Private != NULL)) {
    FreePool (Private);
  }

  return Status;
}

/**
  Stop this driver on ControllerHandle. This is the worker function for
  PxeBcIp4(6)DriverBindingStop.

  @param[in]  This              Protocol instance pointer.
  @param[in]  ControllerHandle  Handle of device to stop driver on.
  @param[in]  NumberOfChildren  Number of Handles in ChildHandleBuffer. If number of
                                children is zero stop the entire bus driver.
  @param[in]  ChildHandleBuffer List of Child Handles to Stop.
  @param[in]  IpVersion         IP_VERSION_4 or IP_VERSION_6.

  @retval EFI_SUCCESS           This driver was removed ControllerHandle.
  @retval EFI_DEVICE_ERROR      An unexpected system or network error occurred.
  @retval Others                This driver was not removed from this device

**/
EFI_STATUS
EFIAPI
PxeBcStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN UINTN                        NumberOfChildren,
  IN EFI_HANDLE                   *ChildHandleBuffer,
  IN UINT8                        IpVersion
  )
{
  PXEBC_PRIVATE_DATA      *Private;
  PXEBC_VIRTUAL_NIC       *VirtualNic;
  EFI_LOAD_FILE_PROTOCOL  *LoadFile;
  EFI_STATUS              Status;
  EFI_HANDLE              NicHandle;
  PXEBC_PRIVATE_PROTOCOL  *Id;

  Private    = NULL;
  NicHandle  = NULL;
  VirtualNic = NULL;
  LoadFile   = NULL;
  Id         = NULL;

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiLoadFileProtocolGuid,
                  (VOID **)&LoadFile,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    //
    // Get the Nic handle by any pass-over service child handle.
    //
    if (IpVersion == IP_VERSION_4) {
      NicHandle = PxeBcGetNicByIp4Children (ControllerHandle);
    } else {
      NicHandle = PxeBcGetNicByIp6Children (ControllerHandle);
    }

    if (NicHandle == NULL) {
      return EFI_SUCCESS;
    }

    //
    // Try to retrieve the private data by PxeBcPrivate protocol.
    //
    Status = gBS->OpenProtocol (
                    NicHandle,
                    &gEfiCallerIdGuid,
                    (VOID **)&Id,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    Private = PXEBC_PRIVATE_DATA_FROM_ID (Id);
  } else {
    //
    // It's a virtual handle with LoadFileProtocol.
    //
    Status = gBS->OpenProtocol (
                    ControllerHandle,
                    &gEfiLoadFileProtocolGuid,
                    (VOID **)&LoadFile,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    VirtualNic = PXEBC_VIRTUAL_NIC_FROM_LOADFILE (LoadFile);
    Private    = VirtualNic->Private;
    NicHandle  = Private->Controller;
  }

  //
  // Stop functionality of PXE Base Code protocol
  //
  Status = Private->PxeBc.Stop (&Private->PxeBc);
  if ((Status != EFI_SUCCESS) && (Status != EFI_NOT_STARTED)) {
    return Status;
  }

  if ((Private->Ip4Nic != NULL) && (IpVersion == IP_VERSION_4)) {
    PxeBcDestroyIp4Children (This, Private);
  }

  if ((Private->Ip6Nic != NULL) && (IpVersion == IP_VERSION_6)) {
    PxeBcDestroyIp6Children (This, Private);
  }

  if ((Private->Ip4Nic == NULL) && (Private->Ip6Nic == NULL)) {
    gBS->UninstallProtocolInterface (
           NicHandle,
           &gEfiCallerIdGuid,
           &Private->Id
           );
    FreePool (Private);
  }

  return EFI_SUCCESS;
}

/**
  Test to see if this driver supports ControllerHandle. This service
  is called by the EFI boot service ConnectController(). In
  order to make drivers as small as possible, there are a few calling
  restrictions for this service. ConnectController() must
  follow these calling restrictions. If any other agent wishes to call
  Supported() it must also follow these calling restrictions.

  @param[in]  This                The pointer to the driver binding protocol.
  @param[in]  ControllerHandle    The handle of device to be tested.
  @param[in]  RemainingDevicePath Optional parameter used to pick a specific child
                                  device to be started.

  @retval EFI_SUCCESS         This driver supports this device.
  @retval EFI_UNSUPPORTED     This driver does not support this device.

**/
EFI_STATUS
EFIAPI
PxeBcIp4DriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  return PxeBcSupported (
           This,
           ControllerHandle,
           RemainingDevicePath,
           IP_VERSION_4
           );
}

/**
  Start this driver on ControllerHandle. This service is called by the
  EFI boot service ConnectController(). In order to make
  drivers as small as possible, there are a few calling restrictions for
  this service. ConnectController() must follow these
  calling restrictions. If any other agent wishes to call Start() it
  must also follow these calling restrictions.

  @param[in]  This                 The pointer to the driver binding protocol.
  @param[in]  ControllerHandle     The handle of device to be started.
  @param[in]  RemainingDevicePath  Optional parameter used to pick a specific child
                                   device to be started.

  @retval EFI_SUCCESS          This driver is installed to ControllerHandle.
  @retval EFI_ALREADY_STARTED  This driver is already running on ControllerHandle.
  @retval other                This driver does not support this device.

**/
EFI_STATUS
EFIAPI
PxeBcIp4DriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  return PxeBcStart (
           This,
           ControllerHandle,
           RemainingDevicePath,
           IP_VERSION_4
           );
}

/**
  Stop this driver on ControllerHandle. This service is called by the
  EFI boot service DisconnectController(). In order to
  make drivers as small as possible, there are a few calling
  restrictions for this service. DisconnectController()
  must follow these calling restrictions. If any other agent wishes
  to call Stop() it must also follow these calling restrictions.

  @param[in]  This              Protocol instance pointer.
  @param[in]  ControllerHandle  Handle of device to stop driver on
  @param[in]  NumberOfChildren  Number of Handles in ChildHandleBuffer. If number of
                                children is zero stop the entire bus driver.
  @param[in]  ChildHandleBuffer List of Child Handles to Stop.

  @retval EFI_SUCCESS           This driver is removed ControllerHandle
  @retval EFI_DEVICE_ERROR      An unexpected system or network error occurred.
  @retval Others                This driver was not removed from this device.

**/
EFI_STATUS
EFIAPI
PxeBcIp4DriverBindingStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN UINTN                        NumberOfChildren,
  IN EFI_HANDLE                   *ChildHandleBuffer
  )
{
  return PxeBcStop (
           This,
           ControllerHandle,
           NumberOfChildren,
           ChildHandleBuffer,
           IP_VERSION_4
           );
}

/**
  Test to see if this driver supports ControllerHandle. This service
  is called by the EFI boot service ConnectController(). In
  order to make drivers as small as possible, there are a few calling
  restrictions for this service. ConnectController() must
  follow these calling restrictions. If any other agent wishes to call
  Supported() it must also follow these calling restrictions.

  @param[in]  This                The pointer to the driver binding protocol.
  @param[in]  ControllerHandle    The handle of device to be tested.
  @param[in]  RemainingDevicePath Optional parameter use to pick a specific child
                                  device to be started.

  @retval EFI_SUCCESS         This driver supports this device.
  @retval EFI_UNSUPPORTED     This driver does not support this device.

**/
EFI_STATUS
EFIAPI
PxeBcIp6DriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  return PxeBcSupported (
           This,
           ControllerHandle,
           RemainingDevicePath,
           IP_VERSION_6
           );
}

/**
  Start this driver on ControllerHandle. This service is called by the
  EFI boot service ConnectController(). In order to make
  drivers as small as possible, there are a few calling restrictions for
  this service. ConnectController() must follow these
  calling restrictions. If any other agent wishes to call Start() it
  must also follow these calling restrictions.

  @param[in]  This                 The pointer to the driver binding protocol.
  @param[in]  ControllerHandle     The handle of device to be started.
  @param[in]  RemainingDevicePath  Optional parameter used to pick a specific child
                                   device to be started.

  @retval EFI_SUCCESS          This driver is installed to ControllerHandle.
  @retval EFI_ALREADY_STARTED  This driver is already running on ControllerHandle.
  @retval other                This driver does not support this device.

**/
EFI_STATUS
EFIAPI
PxeBcIp6DriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath OPTIONAL
  )
{
  return PxeBcStart (
           This,
           ControllerHandle,
           RemainingDevicePath,
           IP_VERSION_6
           );
}

/**
  Stop this driver on ControllerHandle. This service is called by the
  EFI boot service DisconnectController(). In order to
  make drivers as small as possible, there are a few calling
  restrictions for this service. DisconnectController()
  must follow these calling restrictions. If any other agent wishes
  to call Stop() it must also follow these calling restrictions.

  @param[in]  This              Protocol instance pointer.
  @param[in]  ControllerHandle  Handle of device to stop driver on
  @param[in]  NumberOfChildren  Number of Handles in ChildHandleBuffer. If number of
                                children is zero stop the entire bus driver.
  @param[in]  ChildHandleBuffer List of Child Handles to Stop.

  @retval EFI_SUCCESS           This driver is removed ControllerHandle
  @retval EFI_DEVICE_ERROR      An unexpected system or network error occurred.
  @retval Others                This driver was not removed from this device.

**/
EFI_STATUS
EFIAPI
PxeBcIp6DriverBindingStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN UINTN                        NumberOfChildren,
  IN EFI_HANDLE                   *ChildHandleBuffer
  )
{
  return PxeBcStop (
           This,
           ControllerHandle,
           NumberOfChildren,
           ChildHandleBuffer,
           IP_VERSION_6
           );
}
