audk/NetworkPkg/Dhcp6Dxe/Dhcp6Driver.c

810 lines
22 KiB
C

/** @file
Driver Binding functions and Service Binding functions
implementation for Dhcp6 Driver.
Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>
Copyright (c) Microsoft Corporation
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "Dhcp6Impl.h"
EFI_DRIVER_BINDING_PROTOCOL gDhcp6DriverBinding = {
Dhcp6DriverBindingSupported,
Dhcp6DriverBindingStart,
Dhcp6DriverBindingStop,
0xa,
NULL,
NULL
};
EFI_SERVICE_BINDING_PROTOCOL gDhcp6ServiceBindingTemplate = {
Dhcp6ServiceBindingCreateChild,
Dhcp6ServiceBindingDestroyChild
};
/**
Configure the default Udp6Io to receive all the DHCP6 traffic
on this network interface.
@param[in] UdpIo The pointer to Udp6Io to be configured.
@param[in] Context The pointer to the context.
@retval EFI_SUCCESS The Udp6Io is successfully configured.
@retval Others Failed to configure the Udp6Io.
**/
EFI_STATUS
EFIAPI
Dhcp6ConfigureUdpIo (
IN UDP_IO *UdpIo,
IN VOID *Context
)
{
EFI_UDP6_PROTOCOL *Udp6;
EFI_UDP6_CONFIG_DATA *Config;
Udp6 = UdpIo->Protocol.Udp6;
Config = &(UdpIo->Config.Udp6);
ZeroMem (Config, sizeof (EFI_UDP6_CONFIG_DATA));
//
// Set Udp6 configure data for the Dhcp6 instance.
//
Config->AcceptPromiscuous = FALSE;
Config->AcceptAnyPort = FALSE;
Config->AllowDuplicatePort = FALSE;
Config->TrafficClass = 0;
Config->HopLimit = 128;
Config->ReceiveTimeout = 0;
Config->TransmitTimeout = 0;
//
// Configure an endpoint of client(0, 546), server(0, 0), the addresses
// will be overridden later. Note that we MUST not limit RemotePort.
// More details, refer to RFC 3315 section 5.2.
//
Config->StationPort = DHCP6_PORT_CLIENT;
Config->RemotePort = 0;
return Udp6->Configure (Udp6, Config);
}
/**
Destroy the Dhcp6 service. The Dhcp6 service may be partly initialized,
or partly destroyed. If a resource is destroyed, it is marked as such in
case the destroy failed and being called again later.
@param[in, out] Service The pointer to Dhcp6 service to be destroyed.
**/
VOID
Dhcp6DestroyService (
IN OUT DHCP6_SERVICE *Service
)
{
//
// All children instances should have been already destroyed here.
//
ASSERT (Service->NumOfChild == 0);
if (Service->ClientId != NULL) {
FreePool (Service->ClientId);
}
if (Service->UdpIo != NULL) {
UdpIoFreeIo (Service->UdpIo);
}
FreePool (Service);
}
/**
Create a new Dhcp6 service for the Nic controller.
@param[in] Controller The controller to be installed DHCP6 service
binding protocol.
@param[in] ImageHandle The image handle of the Dhcp6 driver.
@param[out] Service The return pointer of the new Dhcp6 service.
@retval EFI_SUCCESS The Dhcp6 service is created successfully.
@retval EFI_DEVICE_ERROR An unexpected system or network error occurred.
@retval EFI_OUT_OF_RESOURCES Failed to allocate resource.
**/
EFI_STATUS
Dhcp6CreateService (
IN EFI_HANDLE Controller,
IN EFI_HANDLE ImageHandle,
OUT DHCP6_SERVICE **Service
)
{
DHCP6_SERVICE *Dhcp6Srv;
EFI_STATUS Status;
UINT32 Random;
Status = PseudoRandomU32 (&Random);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "%a failed to generate random number: %r\n", __func__, Status));
return Status;
}
*Service = NULL;
Dhcp6Srv = AllocateZeroPool (sizeof (DHCP6_SERVICE));
if (Dhcp6Srv == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Open the SNP protocol to get mode data later.
//
Dhcp6Srv->Snp = NULL;
NetLibGetSnpHandle (Controller, &Dhcp6Srv->Snp);
if (Dhcp6Srv->Snp == NULL) {
FreePool (Dhcp6Srv);
return EFI_DEVICE_ERROR;
}
//
// Initialize the fields of the new Dhcp6 service.
//
Dhcp6Srv->Signature = DHCP6_SERVICE_SIGNATURE;
Dhcp6Srv->Controller = Controller;
Dhcp6Srv->Image = ImageHandle;
Dhcp6Srv->Xid = (0xffffff & Random);
CopyMem (
&Dhcp6Srv->ServiceBinding,
&gDhcp6ServiceBindingTemplate,
sizeof (EFI_SERVICE_BINDING_PROTOCOL)
);
//
// Locate Ip6->Ip6Config and store it for get IP6 Duplicate Address Detection transmits.
//
Status = gBS->HandleProtocol (
Controller,
&gEfiIp6ConfigProtocolGuid,
(VOID **)&Dhcp6Srv->Ip6Cfg
);
if (EFI_ERROR (Status)) {
FreePool (Dhcp6Srv);
return Status;
}
//
// Generate client Duid: If SMBIOS system UUID is located, generate DUID in DUID-UUID format.
// Otherwise, in DUID-LLT format.
//
Dhcp6Srv->ClientId = Dhcp6GenerateClientId (Dhcp6Srv->Snp->Mode);
if (Dhcp6Srv->ClientId == NULL) {
FreePool (Dhcp6Srv);
return EFI_DEVICE_ERROR;
}
//
// Create an Udp6Io for stateful transmit/receive of each Dhcp6 instance.
//
Dhcp6Srv->UdpIo = UdpIoCreateIo (
Controller,
ImageHandle,
Dhcp6ConfigureUdpIo,
UDP_IO_UDP6_VERSION,
NULL
);
if (Dhcp6Srv->UdpIo == NULL) {
FreePool (Dhcp6Srv->ClientId);
FreePool (Dhcp6Srv);
return EFI_DEVICE_ERROR;
}
InitializeListHead (&Dhcp6Srv->Child);
*Service = Dhcp6Srv;
return EFI_SUCCESS;
}
/**
Destroy the Dhcp6 instance and recycle the resources.
@param[in, out] Instance The pointer to the Dhcp6 instance.
**/
VOID
Dhcp6DestroyInstance (
IN OUT DHCP6_INSTANCE *Instance
)
{
//
// Clean up the retry list first.
//
Dhcp6CleanupRetry (Instance, DHCP6_PACKET_ALL);
gBS->CloseEvent (Instance->Timer);
//
// Clean up the current configure data.
//
if (Instance->Config != NULL) {
Dhcp6CleanupConfigData (Instance->Config);
FreePool (Instance->Config);
}
//
// Clean up the current Ia.
//
if (Instance->IaCb.Ia != NULL) {
if (Instance->IaCb.Ia->ReplyPacket != NULL) {
FreePool (Instance->IaCb.Ia->ReplyPacket);
}
FreePool (Instance->IaCb.Ia);
}
if (Instance->Unicast != NULL) {
FreePool (Instance->Unicast);
}
if (Instance->AdSelect != NULL) {
FreePool (Instance->AdSelect);
}
FreePool (Instance);
}
/**
Create the Dhcp6 instance and initialize it.
@param[in] Service The pointer to the Dhcp6 service.
@param[out] Instance The pointer to the Dhcp6 instance.
@retval EFI_SUCCESS The Dhcp6 instance is created.
@retval EFI_OUT_OF_RESOURCES Failed to allocate resources.
**/
EFI_STATUS
Dhcp6CreateInstance (
IN DHCP6_SERVICE *Service,
OUT DHCP6_INSTANCE **Instance
)
{
EFI_STATUS Status;
DHCP6_INSTANCE *Dhcp6Ins;
*Instance = NULL;
Dhcp6Ins = AllocateZeroPool (sizeof (DHCP6_INSTANCE));
if (Dhcp6Ins == NULL) {
return EFI_OUT_OF_RESOURCES;
}
//
// Initialize the fields of the new Dhcp6 instance.
//
Dhcp6Ins->Signature = DHCP6_INSTANCE_SIGNATURE;
Dhcp6Ins->UdpSts = EFI_ALREADY_STARTED;
Dhcp6Ins->Service = Service;
Dhcp6Ins->InDestroy = FALSE;
Dhcp6Ins->MediaPresent = TRUE;
CopyMem (
&Dhcp6Ins->Dhcp6,
&gDhcp6ProtocolTemplate,
sizeof (EFI_DHCP6_PROTOCOL)
);
InitializeListHead (&Dhcp6Ins->TxList);
InitializeListHead (&Dhcp6Ins->InfList);
//
// There is a timer for each Dhcp6 instance, which is used to track the
// lease time of Ia and the retransmission time of all sent packets.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL | EVT_TIMER,
TPL_CALLBACK,
Dhcp6OnTimerTick,
Dhcp6Ins,
&Dhcp6Ins->Timer
);
if (EFI_ERROR (Status)) {
FreePool (Dhcp6Ins);
return Status;
}
*Instance = Dhcp6Ins;
return EFI_SUCCESS;
}
/**
Callback function which provided by user to remove one node in NetDestroyLinkList process.
@param[in] Entry The entry to be removed.
@param[in] Context Pointer to the callback context corresponds to the Context in NetDestroyLinkList.
@retval EFI_SUCCESS The entry has been removed successfully.
@retval Others Fail to remove the entry.
**/
EFI_STATUS
EFIAPI
Dhcp6DestroyChildEntry (
IN LIST_ENTRY *Entry,
IN VOID *Context
)
{
DHCP6_INSTANCE *Instance;
EFI_SERVICE_BINDING_PROTOCOL *ServiceBinding;
if ((Entry == NULL) || (Context == NULL)) {
return EFI_INVALID_PARAMETER;
}
Instance = NET_LIST_USER_STRUCT_S (Entry, DHCP6_INSTANCE, Link, DHCP6_INSTANCE_SIGNATURE);
ServiceBinding = (EFI_SERVICE_BINDING_PROTOCOL *)Context;
return ServiceBinding->DestroyChild (ServiceBinding, Instance->Handle);
}
/**
Entry point of the DHCP6 driver to install various protocols.
@param[in] ImageHandle The handle of the UEFI image file.
@param[in] SystemTable The pointer to the EFI System Table.
@retval EFI_SUCCESS The operation completed successfully.
@retval Others Unexpected error occurs.
**/
EFI_STATUS
EFIAPI
Dhcp6DriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
return EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gDhcp6DriverBinding,
ImageHandle,
&gDhcp6ComponentName,
&gDhcp6ComponentName2
);
}
/**
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 Others This driver does not support this device.
**/
EFI_STATUS
EFIAPI
Dhcp6DriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
return gBS->OpenProtocol (
ControllerHandle,
&gEfiUdp6ServiceBindingProtocolGuid,
NULL,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_TEST_PROTOCOL
);
}
/**
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 use 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
Dhcp6DriverBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
EFI_STATUS Status;
DHCP6_SERVICE *Service;
//
// Check the Dhcp6 service whether already started.
//
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiDhcp6ServiceBindingProtocolGuid,
NULL,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_TEST_PROTOCOL
);
if (!EFI_ERROR (Status)) {
return EFI_ALREADY_STARTED;
}
//
// Create and initialize the Dhcp6 service.
//
Status = Dhcp6CreateService (
ControllerHandle,
This->DriverBindingHandle,
&Service
);
if (EFI_ERROR (Status)) {
return Status;
}
ASSERT (Service != NULL);
Status = gBS->InstallMultipleProtocolInterfaces (
&ControllerHandle,
&gEfiDhcp6ServiceBindingProtocolGuid,
&Service->ServiceBinding,
NULL
);
if (EFI_ERROR (Status)) {
Dhcp6DestroyService (Service);
return Status;
}
return EFI_SUCCESS;
}
/**
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 other This driver was not removed from this device
**/
EFI_STATUS
EFIAPI
Dhcp6DriverBindingStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
)
{
EFI_STATUS Status;
EFI_HANDLE NicHandle;
EFI_SERVICE_BINDING_PROTOCOL *ServiceBinding;
DHCP6_SERVICE *Service;
LIST_ENTRY *List;
UINTN ListLength;
//
// Find and check the Nic handle by the controller handle.
//
NicHandle = NetLibGetNicHandle (ControllerHandle, &gEfiUdp6ProtocolGuid);
if (NicHandle == NULL) {
return EFI_SUCCESS;
}
Status = gBS->OpenProtocol (
NicHandle,
&gEfiDhcp6ServiceBindingProtocolGuid,
(VOID **)&ServiceBinding,
This->DriverBindingHandle,
NicHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
return Status;
}
Service = DHCP6_SERVICE_FROM_THIS (ServiceBinding);
if (!IsListEmpty (&Service->Child)) {
//
// Destroy all the children instances before destroy the service.
//
List = &Service->Child;
Status = NetDestroyLinkList (
List,
Dhcp6DestroyChildEntry,
ServiceBinding,
&ListLength
);
if (EFI_ERROR (Status) || (ListLength != 0)) {
Status = EFI_DEVICE_ERROR;
}
}
if ((NumberOfChildren == 0) && !IsListEmpty (&Service->Child)) {
Status = EFI_DEVICE_ERROR;
}
if ((NumberOfChildren == 0) && IsListEmpty (&Service->Child)) {
//
// Destroy the service itself if no child instance left.
//
Status = gBS->UninstallProtocolInterface (
NicHandle,
&gEfiDhcp6ServiceBindingProtocolGuid,
ServiceBinding
);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
Dhcp6DestroyService (Service);
Status = EFI_SUCCESS;
}
ON_EXIT:
return Status;
}
/**
Creates a child handle and installs a protocol.
The CreateChild() function installs a protocol on ChildHandle.
If ChildHandle is a pointer to NULL, then a new handle is created and returned in ChildHandle.
If ChildHandle is not a pointer to NULL, then the protocol installs on the existing ChildHandle.
@param[in] This Pointer to the EFI_SERVICE_BINDING_PROTOCOL instance.
@param[in, out] ChildHandle Pointer to the handle of the child to create. If it is NULL,
then a new handle is created. If it is a pointer to an existing
UEFI handle, then the protocol is added to the existing UEFI handle.
@retval EFI_SUCCESS The protocol was added to ChildHandle.
@retval EFI_INVALID_PARAMETER ChildHandle is NULL.
@retval other The child handle was not created.
**/
EFI_STATUS
EFIAPI
Dhcp6ServiceBindingCreateChild (
IN EFI_SERVICE_BINDING_PROTOCOL *This,
IN OUT EFI_HANDLE *ChildHandle
)
{
EFI_STATUS Status;
EFI_TPL OldTpl;
DHCP6_SERVICE *Service;
DHCP6_INSTANCE *Instance;
VOID *Udp6;
if ((This == NULL) || (ChildHandle == NULL)) {
return EFI_INVALID_PARAMETER;
}
Service = DHCP6_SERVICE_FROM_THIS (This);
Status = Dhcp6CreateInstance (Service, &Instance);
if (EFI_ERROR (Status)) {
return Status;
}
ASSERT (Instance != NULL);
//
// Start the timer when the instance is ready to use.
//
Status = gBS->SetTimer (
Instance->Timer,
TimerPeriodic,
TICKS_PER_SECOND
);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
//
// Install the DHCP6 protocol onto ChildHandle.
//
Status = gBS->InstallMultipleProtocolInterfaces (
ChildHandle,
&gEfiDhcp6ProtocolGuid,
&Instance->Dhcp6,
NULL
);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
Instance->Handle = *ChildHandle;
//
// Open the UDP6 protocol BY_CHILD.
//
Status = gBS->OpenProtocol (
Service->UdpIo->UdpHandle,
&gEfiUdp6ProtocolGuid,
(VOID **)&Udp6,
gDhcp6DriverBinding.DriverBindingHandle,
Instance->Handle,
EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
);
if (EFI_ERROR (Status)) {
gBS->UninstallMultipleProtocolInterfaces (
Instance->Handle,
&gEfiDhcp6ProtocolGuid,
&Instance->Dhcp6,
NULL
);
goto ON_ERROR;
}
//
// Add into the children list of its parent service.
//
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
InsertTailList (&Service->Child, &Instance->Link);
Service->NumOfChild++;
gBS->RestoreTPL (OldTpl);
return EFI_SUCCESS;
ON_ERROR:
Dhcp6DestroyInstance (Instance);
return Status;
}
/**
Destroys a child handle with a protocol installed on it.
The DestroyChild() function does the opposite of CreateChild(). It removes a protocol
that was installed by CreateChild() from ChildHandle. If the removed protocol is the
last protocol on ChildHandle, then ChildHandle is destroyed.
@param[in] This Pointer to the EFI_SERVICE_BINDING_PROTOCOL instance.
@param[in] ChildHandle Handle of the child to destroy
@retval EFI_SUCCESS The protocol was removed from ChildHandle.
@retval EFI_UNSUPPORTED ChildHandle does not support the protocol that is being removed.
@retval EFI_INVALID_PARAMETER Child handle is NULL.
@retval EFI_ACCESS_DENIED The protocol could not be removed from the ChildHandle
because its services are being used.
@retval other The child handle was not destroyed
**/
EFI_STATUS
EFIAPI
Dhcp6ServiceBindingDestroyChild (
IN EFI_SERVICE_BINDING_PROTOCOL *This,
IN EFI_HANDLE ChildHandle
)
{
EFI_STATUS Status;
EFI_TPL OldTpl;
EFI_DHCP6_PROTOCOL *Dhcp6;
DHCP6_SERVICE *Service;
DHCP6_INSTANCE *Instance;
if ((This == NULL) || (ChildHandle == NULL)) {
return EFI_INVALID_PARAMETER;
}
//
// Retrieve the private context data structures
//
Status = gBS->OpenProtocol (
ChildHandle,
&gEfiDhcp6ProtocolGuid,
(VOID **)&Dhcp6,
gDhcp6DriverBinding.DriverBindingHandle,
ChildHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
return EFI_UNSUPPORTED;
}
Instance = DHCP6_INSTANCE_FROM_THIS (Dhcp6);
Service = DHCP6_SERVICE_FROM_THIS (This);
if (Instance->Service != Service) {
return EFI_INVALID_PARAMETER;
}
if (Instance->InDestroy) {
return EFI_SUCCESS;
}
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
Instance->InDestroy = TRUE;
Status = gBS->CloseProtocol (
Service->UdpIo->UdpHandle,
&gEfiUdp6ProtocolGuid,
gDhcp6DriverBinding.DriverBindingHandle,
ChildHandle
);
if (EFI_ERROR (Status)) {
Instance->InDestroy = FALSE;
gBS->RestoreTPL (OldTpl);
return Status;
}
//
// Uninstall the MTFTP6 protocol first to enable a top down destruction.
//
gBS->RestoreTPL (OldTpl);
Status = gBS->UninstallProtocolInterface (
ChildHandle,
&gEfiDhcp6ProtocolGuid,
Dhcp6
);
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
if (EFI_ERROR (Status)) {
Instance->InDestroy = FALSE;
gBS->RestoreTPL (OldTpl);
return Status;
}
//
// Remove it from the children list of its parent service.
//
RemoveEntryList (&Instance->Link);
Service->NumOfChild--;
gBS->RestoreTPL (OldTpl);
Dhcp6DestroyInstance (Instance);
return EFI_SUCCESS;
}