odin-abl/NetworkPkg/UefiPxeBcDxe/PxeBcSupport.c
2021-07-23 18:06:52 +00:00

1514 lines
44 KiB
C

/** @file
Support functions implementation for UefiPxeBc Driver.
Copyright (c) 2007 - 2015, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution. The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php.
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include "PxeBcImpl.h"
/**
Flush the previous configration using the new station Ip address.
@param[in] Private The pointer to the PxeBc private data.
@param[in] StationIp The pointer to the station Ip address.
@param[in] SubnetMask The pointer to the subnet mask address for v4.
@retval EFI_SUCCESS Successfully flushed the previous configuration.
@retval Others Failed to flush using the new station Ip.
**/
EFI_STATUS
PxeBcFlushStationIp (
PXEBC_PRIVATE_DATA *Private,
EFI_IP_ADDRESS *StationIp,
EFI_IP_ADDRESS *SubnetMask OPTIONAL
)
{
EFI_PXE_BASE_CODE_MODE *Mode;
EFI_STATUS Status;
ASSERT (StationIp != NULL);
Mode = Private->PxeBc.Mode;
Status = EFI_SUCCESS;
if (Mode->UsingIpv6) {
CopyMem (&Private->Udp6CfgData.StationAddress, StationIp, sizeof (EFI_IPv6_ADDRESS));
CopyMem (&Private->Ip6CfgData.StationAddress, StationIp, sizeof (EFI_IPv6_ADDRESS));
//
// Reconfigure the Ip6 instance to capture background ICMP6 packets with new station Ip address.
//
Private->Ip6->Cancel (Private->Ip6, &Private->Icmp6Token);
Private->Ip6->Configure (Private->Ip6, NULL);
Status = Private->Ip6->Configure (Private->Ip6, &Private->Ip6CfgData);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
Status = Private->Ip6->Receive (Private->Ip6, &Private->Icmp6Token);
} else {
ASSERT (SubnetMask != NULL);
CopyMem (&Private->Udp4CfgData.StationAddress, StationIp, sizeof (EFI_IPv4_ADDRESS));
CopyMem (&Private->Udp4CfgData.SubnetMask, SubnetMask, sizeof (EFI_IPv4_ADDRESS));
CopyMem (&Private->Ip4CfgData.StationAddress, StationIp, sizeof (EFI_IPv4_ADDRESS));
CopyMem (&Private->Ip4CfgData.SubnetMask, SubnetMask, sizeof (EFI_IPv4_ADDRESS));
//
// Reconfigure the Ip4 instance to capture background ICMP packets with new station Ip address.
//
Private->Ip4->Cancel (Private->Ip4, &Private->IcmpToken);
Private->Ip4->Configure (Private->Ip4, NULL);
Status = Private->Ip4->Configure (Private->Ip4, &Private->Ip4CfgData);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
Status = Private->Ip4->Receive (Private->Ip4, &Private->IcmpToken);
}
ON_EXIT:
return Status;
}
/**
Notify the callback function when an event is triggered.
@param[in] Event The triggered event.
@param[in] Context The opaque parameter to the function.
**/
VOID
EFIAPI
PxeBcCommonNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
*((BOOLEAN *) Context) = TRUE;
}
/**
Do arp resolution from arp cache in PxeBcMode.
@param Mode The pointer to EFI_PXE_BASE_CODE_MODE.
@param Ip4Addr The Ip4 address for resolution.
@param MacAddress The resoluted MAC address if the resolution is successful.
The value is undefined if the resolution fails.
@retval TRUE Found an matched entry.
@retval FALSE Did not find a matched entry.
**/
BOOLEAN
PxeBcCheckArpCache (
IN EFI_PXE_BASE_CODE_MODE *Mode,
IN EFI_IPv4_ADDRESS *Ip4Addr,
OUT EFI_MAC_ADDRESS *MacAddress
)
{
UINT32 Index;
ASSERT (!Mode->UsingIpv6);
//
// Check whether the current Arp cache in mode data contains this information or not.
//
for (Index = 0; Index < Mode->ArpCacheEntries; Index++) {
if (EFI_IP4_EQUAL (&Mode->ArpCache[Index].IpAddr.v4, Ip4Addr)) {
CopyMem (
MacAddress,
&Mode->ArpCache[Index].MacAddr,
sizeof (EFI_MAC_ADDRESS)
);
return TRUE;
}
}
return FALSE;
}
/**
Update the arp cache periodically.
@param Event The pointer to EFI_PXE_BC_PROTOCOL.
@param Context Context of the timer event.
**/
VOID
EFIAPI
PxeBcArpCacheUpdate (
IN EFI_EVENT Event,
IN VOID *Context
)
{
PXEBC_PRIVATE_DATA *Private;
EFI_PXE_BASE_CODE_MODE *Mode;
EFI_ARP_FIND_DATA *ArpEntry;
UINT32 EntryLength;
UINT32 EntryCount;
UINT32 Index;
EFI_STATUS Status;
Private = (PXEBC_PRIVATE_DATA *) Context;
Mode = Private->PxeBc.Mode;
ASSERT (!Mode->UsingIpv6);
//
// Get the current Arp cache from Arp driver.
//
Status = Private->Arp->Find (
Private->Arp,
TRUE,
NULL,
&EntryLength,
&EntryCount,
&ArpEntry,
TRUE
);
if (EFI_ERROR (Status)) {
return;
}
//
// Update the Arp cache in mode data.
//
Mode->ArpCacheEntries = MIN (EntryCount, EFI_PXE_BASE_CODE_MAX_ARP_ENTRIES);
for (Index = 0; Index < Mode->ArpCacheEntries; Index++) {
CopyMem (
&Mode->ArpCache[Index].IpAddr,
ArpEntry + 1,
ArpEntry->SwAddressLength
);
CopyMem (
&Mode->ArpCache[Index].MacAddr,
(UINT8 *) (ArpEntry + 1) + ArpEntry->SwAddressLength,
ArpEntry->HwAddressLength
);
ArpEntry = (EFI_ARP_FIND_DATA *) ((UINT8 *) ArpEntry + EntryLength);
}
}
/**
Notify function to handle the received ICMP message in DPC.
@param Context The PXEBC private data.
**/
VOID
EFIAPI
PxeBcIcmpErrorDpcHandle (
IN VOID *Context
)
{
EFI_STATUS Status;
EFI_IP4_RECEIVE_DATA *RxData;
EFI_IP4_PROTOCOL *Ip4;
PXEBC_PRIVATE_DATA *Private;
EFI_PXE_BASE_CODE_MODE *Mode;
UINT8 Type;
UINTN Index;
UINT32 CopiedLen;
UINT8 *IcmpError;
Private = (PXEBC_PRIVATE_DATA *) Context;
Mode = &Private->Mode;
Status = Private->IcmpToken.Status;
RxData = Private->IcmpToken.Packet.RxData;
Ip4 = Private->Ip4;
ASSERT (!Mode->UsingIpv6);
if (Status == EFI_ABORTED) {
//
// It's triggered by user cancellation.
//
return;
}
if (RxData == NULL) {
goto ON_EXIT;
}
if (Status != EFI_ICMP_ERROR) {
//
// The return status should be recognized as EFI_ICMP_ERROR.
//
gBS->SignalEvent (RxData->RecycleSignal);
goto ON_EXIT;
}
if (EFI_IP4 (RxData->Header->SourceAddress) != 0 &&
!NetIp4IsUnicast (EFI_NTOHL (RxData->Header->SourceAddress), 0)) {
//
// The source address of the received packet should be a valid unicast address.
//
gBS->SignalEvent (RxData->RecycleSignal);
goto ON_EXIT;
}
if (!EFI_IP4_EQUAL (&RxData->Header->DestinationAddress, &Mode->StationIp.v4)) {
//
// The destination address of the received packet should be equal to the host address.
//
gBS->SignalEvent (RxData->RecycleSignal);
goto ON_EXIT;
}
if (RxData->Header->Protocol != EFI_IP_PROTO_ICMP) {
//
// The protocol value in the header of the receveid packet should be EFI_IP_PROTO_ICMP.
//
gBS->SignalEvent (RxData->RecycleSignal);
goto ON_EXIT;
}
Type = *((UINT8 *) RxData->FragmentTable[0].FragmentBuffer);
if (Type != ICMP_DEST_UNREACHABLE &&
Type != ICMP_SOURCE_QUENCH &&
Type != ICMP_REDIRECT &&
Type != ICMP_TIME_EXCEEDED &&
Type != ICMP_PARAMETER_PROBLEM) {
//
// The type of the receveid ICMP message should be ICMP_ERROR_MESSAGE.
//
gBS->SignalEvent (RxData->RecycleSignal);
goto ON_EXIT;
}
//
// Copy the right ICMP error message into mode data.
//
CopiedLen = 0;
IcmpError = (UINT8 *) &Mode->IcmpError;
for (Index = 0; Index < RxData->FragmentCount; Index++) {
CopiedLen += RxData->FragmentTable[Index].FragmentLength;
if (CopiedLen <= sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR)) {
CopyMem (
IcmpError,
RxData->FragmentTable[Index].FragmentBuffer,
RxData->FragmentTable[Index].FragmentLength
);
} else {
CopyMem (
IcmpError,
RxData->FragmentTable[Index].FragmentBuffer,
CopiedLen - sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR)
);
}
IcmpError += CopiedLen;
}
ON_EXIT:
Private->IcmpToken.Status = EFI_NOT_READY;
Ip4->Receive (Ip4, &Private->IcmpToken);
}
/**
Callback function to update the latest ICMP6 error message.
@param Event The event signalled.
@param Context The context passed in using the event notifier.
**/
VOID
EFIAPI
PxeBcIcmpErrorUpdate (
IN EFI_EVENT Event,
IN VOID *Context
)
{
QueueDpc (TPL_CALLBACK, PxeBcIcmpErrorDpcHandle, Context);
}
/**
Notify function to handle the received ICMP6 message in DPC.
@param Context The PXEBC private data.
**/
VOID
EFIAPI
PxeBcIcmp6ErrorDpcHandle (
IN VOID *Context
)
{
PXEBC_PRIVATE_DATA *Private;
EFI_IP6_RECEIVE_DATA *RxData;
EFI_IP6_PROTOCOL *Ip6;
EFI_PXE_BASE_CODE_MODE *Mode;
EFI_STATUS Status;
UINTN Index;
UINT8 Type;
UINT32 CopiedLen;
UINT8 *Icmp6Error;
Private = (PXEBC_PRIVATE_DATA *) Context;
Mode = &Private->Mode;
Status = Private->Icmp6Token.Status;
RxData = Private->Icmp6Token.Packet.RxData;
Ip6 = Private->Ip6;
ASSERT (Mode->UsingIpv6);
if (Status == EFI_ABORTED) {
//
// It's triggered by user cancellation.
//
return;
}
if (RxData == NULL) {
goto ON_EXIT;
}
if (Status != EFI_ICMP_ERROR) {
//
// The return status should be recognized as EFI_ICMP_ERROR.
//
gBS->SignalEvent (RxData->RecycleSignal);
goto ON_EXIT;
}
if (!NetIp6IsValidUnicast (&RxData->Header->SourceAddress)) {
//
// The source address of the received packet should be a valid unicast address.
//
gBS->SignalEvent (RxData->RecycleSignal);
goto ON_EXIT;
}
if (!NetIp6IsUnspecifiedAddr (&Mode->StationIp.v6) &&
!EFI_IP6_EQUAL (&RxData->Header->DestinationAddress, &Mode->StationIp.v6)) {
//
// The destination address of the received packet should be equal to the host address.
//
gBS->SignalEvent (RxData->RecycleSignal);
goto ON_EXIT;
}
if (RxData->Header->NextHeader != IP6_ICMP) {
//
// The nextheader in the header of the receveid packet should be IP6_ICMP.
//
gBS->SignalEvent (RxData->RecycleSignal);
goto ON_EXIT;
}
Type = *((UINT8 *) RxData->FragmentTable[0].FragmentBuffer);
if (Type != ICMP_V6_DEST_UNREACHABLE &&
Type != ICMP_V6_PACKET_TOO_BIG &&
Type != ICMP_V6_PACKET_TOO_BIG &&
Type != ICMP_V6_PARAMETER_PROBLEM) {
//
// The type of the receveid packet should be an ICMP6 error message.
//
gBS->SignalEvent (RxData->RecycleSignal);
goto ON_EXIT;
}
//
// Copy the right ICMP6 error message into mode data.
//
CopiedLen = 0;
Icmp6Error = (UINT8 *) &Mode->IcmpError;
for (Index = 0; Index < RxData->FragmentCount; Index++) {
CopiedLen += RxData->FragmentTable[Index].FragmentLength;
if (CopiedLen <= sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR)) {
CopyMem (
Icmp6Error,
RxData->FragmentTable[Index].FragmentBuffer,
RxData->FragmentTable[Index].FragmentLength
);
} else {
CopyMem (
Icmp6Error,
RxData->FragmentTable[Index].FragmentBuffer,
CopiedLen - sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR)
);
}
Icmp6Error += CopiedLen;
}
ON_EXIT:
Private->Icmp6Token.Status = EFI_NOT_READY;
Ip6->Receive (Ip6, &Private->Icmp6Token);
}
/**
Callback function to update the latest ICMP6 error message.
@param Event The event signalled.
@param Context The context passed in using the event notifier.
**/
VOID
EFIAPI
PxeBcIcmp6ErrorUpdate (
IN EFI_EVENT Event,
IN VOID *Context
)
{
QueueDpc (TPL_CALLBACK, PxeBcIcmp6ErrorDpcHandle, Context);
}
/**
This function is to configure a UDPv4 instance for UdpWrite.
@param[in] Udp4 The pointer to EFI_UDP4_PROTOCOL.
@param[in] StationIp The pointer to the station address.
@param[in] SubnetMask The pointer to the subnet mask.
@param[in] Gateway The pointer to the gateway address.
@param[in, out] SrcPort The pointer to the source port.
@param[in] DoNotFragment If TRUE, fragment is not enabled.
Otherwise, fragment is enabled.
@retval EFI_SUCCESS Successfully configured this instance.
@retval Others Failed to configure this instance.
**/
EFI_STATUS
PxeBcConfigUdp4Write (
IN EFI_UDP4_PROTOCOL *Udp4,
IN EFI_IPv4_ADDRESS *StationIp,
IN EFI_IPv4_ADDRESS *SubnetMask,
IN EFI_IPv4_ADDRESS *Gateway,
IN OUT UINT16 *SrcPort,
IN BOOLEAN DoNotFragment
)
{
EFI_UDP4_CONFIG_DATA Udp4CfgData;
EFI_STATUS Status;
ZeroMem (&Udp4CfgData, sizeof (Udp4CfgData));
Udp4CfgData.TransmitTimeout = PXEBC_DEFAULT_LIFETIME;
Udp4CfgData.ReceiveTimeout = PXEBC_DEFAULT_LIFETIME;
Udp4CfgData.TypeOfService = DEFAULT_ToS;
Udp4CfgData.TimeToLive = DEFAULT_TTL;
Udp4CfgData.AllowDuplicatePort = TRUE;
Udp4CfgData.DoNotFragment = DoNotFragment;
CopyMem (&Udp4CfgData.StationAddress, StationIp, sizeof (*StationIp));
CopyMem (&Udp4CfgData.SubnetMask, SubnetMask, sizeof (*SubnetMask));
Udp4CfgData.StationPort = *SrcPort;
//
// Reset the UDPv4 instance.
//
Udp4->Configure (Udp4, NULL);
Status = Udp4->Configure (Udp4, &Udp4CfgData);
if (!EFI_ERROR (Status) && !EFI_IP4_EQUAL (Gateway, &mZeroIp4Addr)) {
//
// The basic configuration is OK, need to add the default route entry
//
Status = Udp4->Routes (Udp4, FALSE, &mZeroIp4Addr, &mZeroIp4Addr, Gateway);
if (EFI_ERROR (Status)) {
Udp4->Configure (Udp4, NULL);
}
}
if (!EFI_ERROR (Status) && *SrcPort == 0) {
Udp4->GetModeData (Udp4, &Udp4CfgData, NULL, NULL, NULL);
*SrcPort = Udp4CfgData.StationPort;
}
return Status;
}
/**
This function is to configure a UDPv6 instance for UdpWrite.
@param[in] Udp6 The pointer to EFI_UDP6_PROTOCOL.
@param[in] StationIp The pointer to the station address.
@param[in, out] SrcPort The pointer to the source port.
@retval EFI_SUCCESS Successfully configured this instance.
@retval Others Failed to configure this instance.
**/
EFI_STATUS
PxeBcConfigUdp6Write (
IN EFI_UDP6_PROTOCOL *Udp6,
IN EFI_IPv6_ADDRESS *StationIp,
IN OUT UINT16 *SrcPort
)
{
EFI_UDP6_CONFIG_DATA CfgData;
EFI_STATUS Status;
ZeroMem (&CfgData, sizeof (EFI_UDP6_CONFIG_DATA));
CfgData.ReceiveTimeout = PXEBC_DEFAULT_LIFETIME;
CfgData.TransmitTimeout = PXEBC_DEFAULT_LIFETIME;
CfgData.HopLimit = PXEBC_DEFAULT_HOPLIMIT;
CfgData.AllowDuplicatePort = TRUE;
CfgData.StationPort = *SrcPort;
CopyMem (&CfgData.StationAddress, StationIp, sizeof (EFI_IPv6_ADDRESS));
//
// Reset the UDPv6 instance.
//
Udp6->Configure (Udp6, NULL);
Status = Udp6->Configure (Udp6, &CfgData);
if (EFI_ERROR (Status)) {
return Status;
}
if (!EFI_ERROR (Status) && *SrcPort == 0) {
Udp6->GetModeData (Udp6, &CfgData, NULL, NULL, NULL);
*SrcPort = CfgData.StationPort;
}
return Status;
}
/**
This function is to configure a UDPv4 instance for UdpWrite.
@param[in] Udp4 The pointer to EFI_UDP4_PROTOCOL.
@param[in] Session The pointer to the UDP4 session data.
@param[in] TimeoutEvent The event for timeout.
@param[in] Gateway The pointer to the gateway address.
@param[in] HeaderSize An optional field which may be set to the length of a header
at HeaderPtr to be prefixed to the data at BufferPtr.
@param[in] HeaderPtr If HeaderSize is not NULL, a pointer to a header to be
prefixed to the data at BufferPtr.
@param[in] BufferSize A pointer to the size of the data at BufferPtr.
@param[in] BufferPtr A pointer to the data to be written.
@retval EFI_SUCCESS Successfully send out data using Udp4Write.
@retval Others Failed to send out data.
**/
EFI_STATUS
PxeBcUdp4Write (
IN EFI_UDP4_PROTOCOL *Udp4,
IN EFI_UDP4_SESSION_DATA *Session,
IN EFI_EVENT TimeoutEvent,
IN EFI_IPv4_ADDRESS *Gateway OPTIONAL,
IN UINTN *HeaderSize OPTIONAL,
IN VOID *HeaderPtr OPTIONAL,
IN UINTN *BufferSize,
IN VOID *BufferPtr
)
{
EFI_UDP4_COMPLETION_TOKEN Token;
EFI_UDP4_TRANSMIT_DATA *TxData;
UINT32 TxLength;
UINT32 FragCount;
UINT32 DataLength;
BOOLEAN IsDone;
EFI_STATUS Status;
//
// Arrange one fragment buffer for data, and another fragment buffer for header if has.
//
FragCount = (HeaderSize != NULL) ? 2 : 1;
TxLength = sizeof (EFI_UDP4_TRANSMIT_DATA) + (FragCount - 1) * sizeof (EFI_UDP4_FRAGMENT_DATA);
TxData = (EFI_UDP4_TRANSMIT_DATA *) AllocateZeroPool (TxLength);
if (TxData == NULL) {
return EFI_OUT_OF_RESOURCES;
}
TxData->FragmentCount = FragCount;
TxData->FragmentTable[FragCount - 1].FragmentLength = (UINT32) *BufferSize;
TxData->FragmentTable[FragCount - 1].FragmentBuffer = BufferPtr;
DataLength = (UINT32) *BufferSize;
if (HeaderSize != NULL) {
TxData->FragmentTable[0].FragmentLength = (UINT32) *HeaderSize;
TxData->FragmentTable[0].FragmentBuffer = HeaderPtr;
DataLength += (UINT32) *HeaderSize;
}
if (Gateway != NULL) {
TxData->GatewayAddress = Gateway;
}
TxData->UdpSessionData = Session;
TxData->DataLength = DataLength;
Token.Packet.TxData = TxData;
Token.Status = EFI_NOT_READY;
IsDone = FALSE;
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
PxeBcCommonNotify,
&IsDone,
&Token.Event
);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
Status = Udp4->Transmit (Udp4, &Token);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
//
// Poll the UDPv6 read instance if no packet received and no timeout triggered.
//
while (!IsDone &&
Token.Status == EFI_NOT_READY &&
EFI_ERROR (gBS->CheckEvent (TimeoutEvent))) {
Udp4->Poll (Udp4);
}
Status = (Token.Status == EFI_NOT_READY) ? EFI_TIMEOUT : Token.Status;
ON_EXIT:
if (Token.Event != NULL) {
gBS->CloseEvent (Token.Event);
}
FreePool (TxData);
return Status;
}
/**
This function is to configure a UDPv4 instance for UdpWrite.
@param[in] Udp6 The pointer to EFI_UDP6_PROTOCOL.
@param[in] Session The pointer to the UDP6 session data.
@param[in] TimeoutEvent The event for timeout.
@param[in] HeaderSize An optional field which may be set to the length of a header
at HeaderPtr to be prefixed to the data at BufferPtr.
@param[in] HeaderPtr If HeaderSize is not NULL, a pointer to a header to be
prefixed to the data at BufferPtr.
@param[in] BufferSize A pointer to the size of the data at BufferPtr.
@param[in] BufferPtr A pointer to the data to be written.
@retval EFI_SUCCESS Successfully sent out data using Udp6Write.
@retval Others Failed to send out data.
**/
EFI_STATUS
PxeBcUdp6Write (
IN EFI_UDP6_PROTOCOL *Udp6,
IN EFI_UDP6_SESSION_DATA *Session,
IN EFI_EVENT TimeoutEvent,
IN UINTN *HeaderSize OPTIONAL,
IN VOID *HeaderPtr OPTIONAL,
IN UINTN *BufferSize,
IN VOID *BufferPtr
)
{
EFI_UDP6_COMPLETION_TOKEN Token;
EFI_UDP6_TRANSMIT_DATA *TxData;
UINT32 TxLength;
UINT32 FragCount;
UINT32 DataLength;
BOOLEAN IsDone;
EFI_STATUS Status;
//
// Arrange one fragment buffer for data, and another fragment buffer for header if has.
//
FragCount = (HeaderSize != NULL) ? 2 : 1;
TxLength = sizeof (EFI_UDP6_TRANSMIT_DATA) + (FragCount - 1) * sizeof (EFI_UDP6_FRAGMENT_DATA);
TxData = (EFI_UDP6_TRANSMIT_DATA *) AllocateZeroPool (TxLength);
if (TxData == NULL) {
return EFI_OUT_OF_RESOURCES;
}
TxData->FragmentCount = FragCount;
TxData->FragmentTable[FragCount - 1].FragmentLength = (UINT32) *BufferSize;
TxData->FragmentTable[FragCount - 1].FragmentBuffer = BufferPtr;
DataLength = (UINT32) *BufferSize;
if (HeaderSize != NULL) {
TxData->FragmentTable[0].FragmentLength = (UINT32) *HeaderSize;
TxData->FragmentTable[0].FragmentBuffer = HeaderPtr;
DataLength += (UINT32) *HeaderSize;
}
TxData->UdpSessionData = Session;
TxData->DataLength = DataLength;
Token.Packet.TxData = TxData;
Token.Status = EFI_NOT_READY;
IsDone = FALSE;
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
PxeBcCommonNotify,
&IsDone,
&Token.Event
);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
Status = Udp6->Transmit (Udp6, &Token);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
//
// Poll the UDPv6 read instance if no packet received and no timeout triggered.
//
while (!IsDone &&
Token.Status == EFI_NOT_READY &&
EFI_ERROR (gBS->CheckEvent (TimeoutEvent))) {
Udp6->Poll (Udp6);
}
Status = (Token.Status == EFI_NOT_READY) ? EFI_TIMEOUT : Token.Status;
ON_EXIT:
if (Token.Event != NULL) {
gBS->CloseEvent (Token.Event);
}
FreePool (TxData);
return Status;
}
/**
Check the received packet using the Ip filter.
@param[in] Mode The pointer to the mode data of PxeBc.
@param[in] Session The pointer to the current UDPv4 session.
@param[in] OpFlags Operation flag for UdpRead/UdpWrite.
@retval TRUE Passed the Ip filter successfully.
@retval FALSE Failed to pass the Ip filter.
**/
BOOLEAN
PxeBcCheckByIpFilter (
IN EFI_PXE_BASE_CODE_MODE *Mode,
IN VOID *Session,
IN UINT16 OpFlags
)
{
EFI_IP_ADDRESS DestinationIp;
UINTN Index;
if ((OpFlags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_USE_FILTER) == 0) {
return TRUE;
}
if ((Mode->IpFilter.Filters & EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS) != 0) {
return TRUE;
}
//
// Convert the destination address in session data to host order.
//
if (Mode->UsingIpv6) {
CopyMem (
&DestinationIp,
&((EFI_UDP6_SESSION_DATA *) Session)->DestinationAddress,
sizeof (EFI_IPv6_ADDRESS)
);
NTOHLLL (&DestinationIp.v6);
} else {
ZeroMem (&DestinationIp, sizeof (EFI_IP_ADDRESS));
CopyMem (
&DestinationIp,
&((EFI_UDP4_SESSION_DATA *) Session)->DestinationAddress,
sizeof (EFI_IPv4_ADDRESS)
);
EFI_NTOHL (DestinationIp);
}
if ((Mode->IpFilter.Filters & EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS_MULTICAST) != 0 &&
(IP4_IS_MULTICAST (DestinationIp.Addr[0]) ||
IP6_IS_MULTICAST (&DestinationIp))) {
return TRUE;
}
if ((Mode->IpFilter.Filters & EFI_PXE_BASE_CODE_IP_FILTER_BROADCAST) != 0 &&
IP4_IS_LOCAL_BROADCAST (DestinationIp.Addr[0])) {
ASSERT (!Mode->UsingIpv6);
return TRUE;
}
if ((Mode->IpFilter.Filters & EFI_PXE_BASE_CODE_IP_FILTER_STATION_IP) != 0 &&
(EFI_IP4_EQUAL (&Mode->StationIp.v4, &DestinationIp) ||
EFI_IP6_EQUAL (&Mode->StationIp.v6, &DestinationIp))) {
//
// Matched if the dest address is equal to the station address.
//
return TRUE;
}
for (Index = 0; Index < Mode->IpFilter.IpCnt; Index++) {
ASSERT (Index < EFI_PXE_BASE_CODE_MAX_IPCNT);
if (EFI_IP4_EQUAL (&Mode->IpFilter.IpList[Index].v4, &DestinationIp) ||
EFI_IP6_EQUAL (&Mode->IpFilter.IpList[Index].v6, &DestinationIp)) {
//
// Matched if the dest address is equal to any of address in the filter list.
//
return TRUE;
}
}
return FALSE;
}
/**
Filter the received packet using the destination Ip.
@param[in] Mode The pointer to the mode data of PxeBc.
@param[in] Session The pointer to the current UDPv4 session.
@param[in, out] DestIp The pointer to the destination Ip address.
@param[in] OpFlags Operation flag for UdpRead/UdpWrite.
@retval TRUE Passed the IPv4 filter successfully.
@retval FALSE Failed to pass the IPv4 filter.
**/
BOOLEAN
PxeBcCheckByDestIp (
IN EFI_PXE_BASE_CODE_MODE *Mode,
IN VOID *Session,
IN OUT EFI_IP_ADDRESS *DestIp,
IN UINT16 OpFlags
)
{
if ((OpFlags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_IP) != 0) {
//
// Copy the destination address from the received packet if accept any.
//
if (DestIp != NULL) {
if (Mode->UsingIpv6) {
CopyMem (
DestIp,
&((EFI_UDP6_SESSION_DATA *)Session)->DestinationAddress,
sizeof (EFI_IPv6_ADDRESS)
);
} else {
ZeroMem (DestIp, sizeof (EFI_IP_ADDRESS));
CopyMem (
DestIp,
&((EFI_UDP4_SESSION_DATA *)Session)->DestinationAddress,
sizeof (EFI_IPv4_ADDRESS)
);
}
}
return TRUE;
} else if (DestIp != NULL &&
(EFI_IP4_EQUAL (DestIp, &((EFI_UDP4_SESSION_DATA *)Session)->DestinationAddress) ||
EFI_IP6_EQUAL (DestIp, &((EFI_UDP6_SESSION_DATA *)Session)->DestinationAddress))) {
//
// The destination address in the received packet is matched if present.
//
return TRUE;
} else if (EFI_IP4_EQUAL (&Mode->StationIp, &((EFI_UDP4_SESSION_DATA *)Session)->DestinationAddress) ||
EFI_IP6_EQUAL (&Mode->StationIp, &((EFI_UDP6_SESSION_DATA *)Session)->DestinationAddress)) {
//
// The destination address in the received packet is equal to the host address.
//
return TRUE;
}
return FALSE;
}
/**
Check the received packet using the destination port.
@param[in] Mode The pointer to the mode data of PxeBc.
@param[in] Session The pointer to the current UDPv4 session.
@param[in, out] DestPort The pointer to the destination port.
@param[in] OpFlags Operation flag for UdpRead/UdpWrite.
@retval TRUE Passed the IPv4 filter successfully.
@retval FALSE Failed to pass the IPv4 filter.
**/
BOOLEAN
PxeBcCheckByDestPort (
IN EFI_PXE_BASE_CODE_MODE *Mode,
IN VOID *Session,
IN OUT UINT16 *DestPort,
IN UINT16 OpFlags
)
{
UINT16 Port;
if (Mode->UsingIpv6) {
Port = ((EFI_UDP6_SESSION_DATA *) Session)->DestinationPort;
} else {
Port = ((EFI_UDP4_SESSION_DATA *) Session)->DestinationPort;
}
if ((OpFlags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_PORT) != 0) {
//
// Return the destination port in the received packet if accept any.
//
if (DestPort != NULL) {
*DestPort = Port;
}
return TRUE;
} else if (DestPort != NULL && *DestPort == Port) {
//
// The destination port in the received packet is matched if present.
//
return TRUE;
}
return FALSE;
}
/**
Filter the received packet using the source Ip.
@param[in] Mode The pointer to the mode data of PxeBc.
@param[in] Session The pointer to the current UDPv4 session.
@param[in, out] SrcIp The pointer to the source Ip address.
@param[in] OpFlags Operation flag for UdpRead/UdpWrite.
@retval TRUE Passed the IPv4 filter successfully.
@retval FALSE Failed to pass the IPv4 filter.
**/
BOOLEAN
PxeBcFilterBySrcIp (
IN EFI_PXE_BASE_CODE_MODE *Mode,
IN VOID *Session,
IN OUT EFI_IP_ADDRESS *SrcIp,
IN UINT16 OpFlags
)
{
if ((OpFlags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_IP) != 0) {
//
// Copy the source address from the received packet if accept any.
//
if (SrcIp != NULL) {
if (Mode->UsingIpv6) {
CopyMem (
SrcIp,
&((EFI_UDP6_SESSION_DATA *)Session)->SourceAddress,
sizeof (EFI_IPv6_ADDRESS)
);
} else {
ZeroMem (SrcIp, sizeof (EFI_IP_ADDRESS));
CopyMem (
SrcIp,
&((EFI_UDP4_SESSION_DATA *)Session)->SourceAddress,
sizeof (EFI_IPv4_ADDRESS)
);
}
}
return TRUE;
} else if (SrcIp != NULL &&
(EFI_IP4_EQUAL (SrcIp, &((EFI_UDP4_SESSION_DATA *)Session)->SourceAddress) ||
EFI_IP6_EQUAL (SrcIp, &((EFI_UDP6_SESSION_DATA *)Session)->SourceAddress))) {
//
// The source address in the received packet is matched if present.
//
return TRUE;
}
return FALSE;
}
/**
Filter the received packet using the source port.
@param[in] Mode The pointer to the mode data of PxeBc.
@param[in] Session The pointer to the current UDPv4 session.
@param[in, out] SrcPort The pointer to the source port.
@param[in] OpFlags Operation flag for UdpRead/UdpWrite.
@retval TRUE Passed the IPv4 filter successfully.
@retval FALSE Failed to pass the IPv4 filter.
**/
BOOLEAN
PxeBcFilterBySrcPort (
IN EFI_PXE_BASE_CODE_MODE *Mode,
IN VOID *Session,
IN OUT UINT16 *SrcPort,
IN UINT16 OpFlags
)
{
UINT16 Port;
if (Mode->UsingIpv6) {
Port = ((EFI_UDP6_SESSION_DATA *) Session)->SourcePort;
} else {
Port = ((EFI_UDP4_SESSION_DATA *) Session)->SourcePort;
}
if ((OpFlags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT) != 0) {
//
// Return the source port in the received packet if accept any.
//
if (SrcPort != NULL) {
*SrcPort = Port;
}
return TRUE;
} else if (SrcPort != NULL && *SrcPort == Port) {
//
// The source port in the received packet is matched if present.
//
return TRUE;
}
return FALSE;
}
/**
This function is to receive packet using Udp4Read.
@param[in] Udp4 The pointer to EFI_UDP4_PROTOCOL.
@param[in] Token The pointer to EFI_UDP4_COMPLETION_TOKEN.
@param[in] Mode The pointer to EFI_PXE_BASE_CODE_MODE.
@param[in] TimeoutEvent The event for timeout.
@param[in] OpFlags The UDP operation flags.
@param[in] IsDone The pointer to the IsDone flag.
@param[out] IsMatched The pointer to the IsMatched flag.
@param[in, out] DestIp The pointer to the destination address.
@param[in, out] DestPort The pointer to the destination port.
@param[in, out] SrcIp The pointer to the source address.
@param[in, out] SrcPort The pointer to the source port.
@retval EFI_SUCCESS Successfully read the data using Udp4.
@retval Others Failed to send out data.
**/
EFI_STATUS
PxeBcUdp4Read (
IN EFI_UDP4_PROTOCOL *Udp4,
IN EFI_UDP4_COMPLETION_TOKEN *Token,
IN EFI_PXE_BASE_CODE_MODE *Mode,
IN EFI_EVENT TimeoutEvent,
IN UINT16 OpFlags,
IN BOOLEAN *IsDone,
OUT BOOLEAN *IsMatched,
IN OUT EFI_IP_ADDRESS *DestIp OPTIONAL,
IN OUT EFI_PXE_BASE_CODE_UDP_PORT *DestPort OPTIONAL,
IN OUT EFI_IP_ADDRESS *SrcIp OPTIONAL,
IN OUT EFI_PXE_BASE_CODE_UDP_PORT *SrcPort OPTIONAL
)
{
EFI_UDP4_RECEIVE_DATA *RxData;
EFI_UDP4_SESSION_DATA *Session;
EFI_STATUS Status;
Token->Status = EFI_NOT_READY;
*IsDone = FALSE;
Status = Udp4->Receive (Udp4, Token);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Poll the UDPv6 read instance if no packet received and no timeout triggered.
//
while (!(*IsDone) &&
Token->Status == EFI_NOT_READY &&
EFI_ERROR (gBS->CheckEvent (TimeoutEvent))) {
//
// Poll the token utill reply/ICMPv6 error message received or timeout.
//
Udp4->Poll (Udp4);
if (Token->Status == EFI_ICMP_ERROR ||
Token->Status == EFI_NETWORK_UNREACHABLE ||
Token->Status == EFI_HOST_UNREACHABLE ||
Token->Status == EFI_PROTOCOL_UNREACHABLE ||
Token->Status == EFI_PORT_UNREACHABLE) {
break;
}
}
Status = (Token->Status == EFI_NOT_READY) ? EFI_TIMEOUT : Token->Status;
if (!EFI_ERROR (Status)) {
//
// check whether this packet matches the filters
//
RxData = Token->Packet.RxData;
Session = &RxData->UdpSession;
*IsMatched = PxeBcCheckByIpFilter (Mode, Session, OpFlags);
if (*IsMatched) {
*IsMatched = PxeBcCheckByDestIp (Mode, Session, DestIp, OpFlags);
}
if (*IsMatched) {
*IsMatched = PxeBcCheckByDestPort (Mode, Session, DestPort, OpFlags);
}
if (*IsMatched) {
*IsMatched = PxeBcFilterBySrcIp (Mode, Session, SrcIp, OpFlags);
}
if (*IsMatched) {
*IsMatched = PxeBcFilterBySrcPort (Mode, Session, SrcPort, OpFlags);
}
if (!(*IsMatched)) {
//
// Recycle the receiving buffer if not matched.
//
gBS->SignalEvent (RxData->RecycleSignal);
}
}
return Status;
}
/**
This function is to receive packets using Udp6Read.
@param[in] Udp6 The pointer to EFI_UDP6_PROTOCOL.
@param[in] Token The pointer to EFI_UDP6_COMPLETION_TOKEN.
@param[in] Mode The pointer to EFI_PXE_BASE_CODE_MODE.
@param[in] TimeoutEvent The event for timeout.
@param[in] OpFlags The UDP operation flags.
@param[in] IsDone The pointer to the IsDone flag.
@param[out] IsMatched The pointer to the IsMatched flag.
@param[in, out] DestIp The pointer to the destination address.
@param[in, out] DestPort The pointer to the destination port.
@param[in, out] SrcIp The pointer to the source address.
@param[in, out] SrcPort The pointer to the source port.
@retval EFI_SUCCESS Successfully read data using Udp6.
@retval Others Failed to send out data.
**/
EFI_STATUS
PxeBcUdp6Read (
IN EFI_UDP6_PROTOCOL *Udp6,
IN EFI_UDP6_COMPLETION_TOKEN *Token,
IN EFI_PXE_BASE_CODE_MODE *Mode,
IN EFI_EVENT TimeoutEvent,
IN UINT16 OpFlags,
IN BOOLEAN *IsDone,
OUT BOOLEAN *IsMatched,
IN OUT EFI_IP_ADDRESS *DestIp OPTIONAL,
IN OUT EFI_PXE_BASE_CODE_UDP_PORT *DestPort OPTIONAL,
IN OUT EFI_IP_ADDRESS *SrcIp OPTIONAL,
IN OUT EFI_PXE_BASE_CODE_UDP_PORT *SrcPort OPTIONAL
)
{
EFI_UDP6_RECEIVE_DATA *RxData;
EFI_UDP6_SESSION_DATA *Session;
EFI_STATUS Status;
Token->Status = EFI_NOT_READY;
*IsDone = FALSE;
Status = Udp6->Receive (Udp6, Token);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Poll the UDPv6 read instance if no packet received and no timeout triggered.
//
while (!(*IsDone) &&
Token->Status == EFI_NOT_READY &&
EFI_ERROR (gBS->CheckEvent (TimeoutEvent))) {
//
// Poll the token utill reply/ICMPv6 error message received or timeout.
//
Udp6->Poll (Udp6);
if (Token->Status == EFI_ICMP_ERROR ||
Token->Status == EFI_NETWORK_UNREACHABLE ||
Token->Status == EFI_HOST_UNREACHABLE ||
Token->Status == EFI_PROTOCOL_UNREACHABLE ||
Token->Status == EFI_PORT_UNREACHABLE) {
break;
}
}
Status = (Token->Status == EFI_NOT_READY) ? EFI_TIMEOUT : Token->Status;
if (!EFI_ERROR (Status)) {
//
// check whether this packet matches the filters
//
RxData = Token->Packet.RxData;
Session = &RxData->UdpSession;
*IsMatched = PxeBcCheckByIpFilter (Mode, Session, OpFlags);
if (*IsMatched) {
*IsMatched = PxeBcCheckByDestIp (Mode, Session, DestIp, OpFlags);
}
if (*IsMatched) {
*IsMatched = PxeBcCheckByDestPort (Mode, Session, DestPort, OpFlags);
}
if (*IsMatched) {
*IsMatched = PxeBcFilterBySrcIp (Mode, Session, SrcIp, OpFlags);
}
if (*IsMatched) {
*IsMatched = PxeBcFilterBySrcPort (Mode, Session, SrcPort, OpFlags);
}
if (!(*IsMatched)) {
//
// Recycle the receiving buffer if not matched.
//
gBS->SignalEvent (RxData->RecycleSignal);
}
}
return Status;
}
/**
This function is to display the IPv4 address.
@param[in] Ip The pointer to the IPv4 address.
**/
VOID
PxeBcShowIp4Addr (
IN EFI_IPv4_ADDRESS *Ip
)
{
UINTN Index;
for (Index = 0; Index < 4; Index++) {
AsciiPrint ("%d", Ip->Addr[Index]);
if (Index < 3) {
AsciiPrint (".");
}
}
}
/**
This function is to display the IPv6 address.
@param[in] Ip The pointer to the IPv6 address.
**/
VOID
PxeBcShowIp6Addr (
IN EFI_IPv6_ADDRESS *Ip
)
{
UINTN Index;
for (Index = 0; Index < 16; Index++) {
if (Ip->Addr[Index] != 0) {
AsciiPrint ("%x", Ip->Addr[Index]);
}
Index++;
if (Index > 15) {
return;
}
if (((Ip->Addr[Index] & 0xf0) == 0) && (Ip->Addr[Index - 1] != 0)) {
AsciiPrint ("0");
}
AsciiPrint ("%x", Ip->Addr[Index]);
if (Index < 15) {
AsciiPrint (":");
}
}
}
/**
This function is to convert UINTN to ASCII string with the required formatting.
@param[in] Number Numeric value to be converted.
@param[in] Buffer The pointer to the buffer for ASCII string.
@param[in] Length The length of the required format.
**/
VOID
PxeBcUintnToAscDecWithFormat (
IN UINTN Number,
IN UINT8 *Buffer,
IN INTN Length
)
{
UINTN Remainder;
while (Length > 0) {
Length--;
Remainder = Number % 10;
Number /= 10;
Buffer[Length] = (UINT8) ('0' + Remainder);
}
}
/**
This function is to convert a UINTN to a ASCII string, and return the
actual length of the buffer.
@param[in] Number Numeric value to be converted.
@param[in] Buffer The pointer to the buffer for ASCII string.
@param[in] BufferSize The maxsize of the buffer.
@return Length The actual length of the ASCII string.
**/
UINTN
PxeBcUintnToAscDec (
IN UINTN Number,
IN UINT8 *Buffer,
IN UINTN BufferSize
)
{
UINTN Index;
UINTN Length;
CHAR8 TempStr[64];
Index = 63;
TempStr[Index] = 0;
do {
Index--;
TempStr[Index] = (CHAR8) ('0' + (Number % 10));
Number = (UINTN) (Number / 10);
} while (Number != 0);
AsciiStrCpyS ((CHAR8 *) Buffer, BufferSize, &TempStr[Index]);
Length = AsciiStrLen ((CHAR8 *) Buffer);
return Length;
}
/**
This function is to convert unicode hex number to a UINT8.
@param[out] Digit The converted UINT8 for output.
@param[in] Char The unicode hex number to be converted.
@retval EFI_SUCCESS Successfully converted the unicode hex.
@retval EFI_INVALID_PARAMETER Failed to convert the unicode hex.
**/
EFI_STATUS
PxeBcUniHexToUint8 (
OUT UINT8 *Digit,
IN CHAR16 Char
)
{
if ((Char >= L'0') && (Char <= L'9')) {
*Digit = (UINT8) (Char - L'0');
return EFI_SUCCESS;
}
if ((Char >= L'A') && (Char <= L'F')) {
*Digit = (UINT8) (Char - L'A' + 0x0A);
return EFI_SUCCESS;
}
if ((Char >= L'a') && (Char <= L'f')) {
*Digit = (UINT8) (Char - L'a' + 0x0A);
return EFI_SUCCESS;
}
return EFI_INVALID_PARAMETER;
}
/**
Calculate the elapsed time.
@param[in] Private The pointer to PXE private data
**/
VOID
CalcElapsedTime (
IN PXEBC_PRIVATE_DATA *Private
)
{
EFI_TIME Time;
UINT64 CurrentStamp;
UINT64 ElapsedTimeValue;
//
// Generate a time stamp of the centiseconds from 1900/1/1, assume 30day/month.
//
ZeroMem (&Time, sizeof (EFI_TIME));
gRT->GetTime (&Time, NULL);
CurrentStamp = (UINT64)
(
((((((Time.Year - 1900) * 360 +
(Time.Month - 1)) * 30 +
(Time.Day - 1)) * 24 + Time.Hour) * 60 +
Time.Minute) * 60 + Time.Second) * 100
+ DivU64x32(Time.Nanosecond, 10000000)
);
//
// Sentinel value of 0 means that this is the first DHCP packet that we are
// sending and that we need to initialize the value. First DHCP Solicit
// gets 0 elapsed-time. Otherwise, calculate based on StartTime.
//
if (Private->ElapsedTime == 0) {
Private->ElapsedTime = CurrentStamp;
} else {
ElapsedTimeValue = CurrentStamp - Private->ElapsedTime;
//
// If elapsed time cannot fit in two bytes, set it to 0xffff.
//
if (ElapsedTimeValue > 0xffff) {
ElapsedTimeValue = 0xffff;
}
//
// Save the elapsed time
//
Private->ElapsedTime = ElapsedTimeValue;
}
}