/** @file Driver Binding functions and Service Binding functions implementation for Dhcp6 Driver. Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.
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; }