DPC全称Deferred Procedure Call。Deferred的意思是“延迟”,这个DPC的作用就是注册函数,然后在之后的某个时刻调用,所以确实是有“延迟”的意思。DPC在UEFI的实现中包括两个部分。一部分是库函数DxeDpcLib
,对应代码NetworkPkg\Library\DxeDpcLib\DxeDpcLib.inf;另一部分是EFI_DPC_PROTOCOL
,对应代码NetworkPkg\DpcDxe\DpcDxe.inf。
库中只实现了两个函数:
/**
Add a Deferred Procedure Call to the end of the DPC queue.
@param[in] DpcTpl The EFI_TPL that the DPC should be invoked.
@param[in] DpcProcedure Pointer to the DPC's function.
@param[in] DpcContext Pointer to the DPC's context. Passed to DpcProcedure
when DpcProcedure is invoked.
@retval EFI_SUCCESS The DPC was queued.
@retval EFI_INVALID_PARAMETER DpcTpl is not a valid EFI_TPL.
@retval EFI_INVALID_PARAMETER DpcProcedure is NULL.
@retval EFI_OUT_OF_RESOURCES There are not enough resources available to
add the DPC to the queue.
**/
EFI_STATUS
EFIAPI
QueueDpc (
IN EFI_TPL DpcTpl,
IN EFI_DPC_PROCEDURE DpcProcedure,
IN VOID *DpcContext OPTIONAL
);
/**
Dispatch the queue of DPCs. ALL DPCs that have been queued with a DpcTpl
value greater than or equal to the current TPL are invoked in the order that
they were queued. DPCs with higher DpcTpl values are invoked before DPCs with
lower DpcTpl values.
@retval EFI_SUCCESS One or more DPCs were invoked.
@retval EFI_NOT_FOUND No DPCs were invoked.
**/
EFI_STATUS
EFIAPI
DispatchDpc (
VOID
);
库函数的实现只是简单调用了EFI_DPC_PROTOCOL
的接口函数:
EFI_STATUS
EFIAPI
QueueDpc (
IN EFI_TPL DpcTpl,
IN EFI_DPC_PROCEDURE DpcProcedure,
IN VOID *DpcContext OPTIONAL
)
{
//
// Call the EFI_DPC_PROTOCOL to queue the DPC
//
return mDpc->QueueDpc (mDpc, DpcTpl, DpcProcedure, DpcContext);
}
我们在使用DPC的时候直接调用库函数更方便,不过在了解DPC的实现时还是要重点关注EFI_DPC_PROTOCOL
。
EFI_DPC_PROTOCOL
在一个DXE模块中安装的,安装的过程也很简单,查看具体的模块入口代码(位于NetworkPkg\DpcDxe\Dpc.c,只包含重点代码,下同):
EFI_STATUS
EFIAPI
DpcDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// Initialize the DPC queue for all possible TPL values
//
for (Index = 0; Index <= TPL_HIGH_LEVEL; Index++) {
InitializeListHead (&mDpcQueue[Index]);
}
//
// Install the EFI_DPC_PROTOCOL instance onto a new handle
//
Status = gBS->InstallMultipleProtocolInterfaces (
&mDpcHandle,
&gEfiDpcProtocolGuid,
&mDpc,
NULL
);
}
这里唯一需要关注的是mDpcQueue
这个全局变量。它是一个链表数组:
//
// An array of DPC queues. A DPC queue is allocated for every level EFI_TPL value.
// As DPCs are queued, they are added to the end of the linked list.
// As DPCs are dispatched, they are removed from the beginning of the linked list.
//
LIST_ENTRY mDpcQueue[TPL_HIGH_LEVEL + 1]; // TPL_HIGH_LEVEL的值是31,所以数组大小是32个元素
每一个数组元素对应一种优先级。这里创建了所有可能的优先级,但实际上常用的也就下面几种:
//
// Task priority level
//
#define TPL_APPLICATION 4
#define TPL_CALLBACK 8
#define TPL_NOTIFY 16
#define TPL_HIGH_LEVEL 31
这个链表真正的有效元素是DpcEntry
,其结构如下:
//
// Internal data structure for managing DPCs. A DPC entry is either on the free
// list or on a DPC queue at a specific EFI_TPL.
//
typedef struct {
LIST_ENTRY ListEntry;
EFI_DPC_PROCEDURE DpcProcedure;
VOID *DpcContext;
} DPC_ENTRY;
第一个参数ListEntry
用来处理链表,可以不关注;后面的两个参数,一个是DPC函数指针,一个是DPC函数的入参。EFI_DPC_PROCEDURE
定义如下:
/**
Invoke a Deferred Procedure Call.
@param DpcContext The pointer to the Deferred Procedure Call's context,
which is implementation dependent.
**/
typedef
VOID
(EFIAPI *EFI_DPC_PROCEDURE)(
IN VOID *DpcContext
);
这个函数就是我们的主角:Deferred Procedure Call,简称DPC。
EFI_DPC_PROTOCOL
包含两个接口函数,下面具体介绍。
DpcQueueDpc()
:EFI_STATUS
EFIAPI
DpcQueueDpc (
IN EFI_DPC_PROTOCOL *This,
IN EFI_TPL DpcTpl,
IN EFI_DPC_PROCEDURE DpcProcedure,
IN VOID *DpcContext OPTIONAL
)
该函数的操作流程如下:
DpcTpl
是否满足要求: //
// Make sure DpcTpl is valid
//
if ((DpcTpl < TPL_APPLICATION) || (DpcTpl > TPL_HIGH_LEVEL)) {
return EFI_INVALID_PARAMETER;
}
mDpcEntryFreeList
的状态。这个mDpcEntryFreeList
是另外的一个链表://
// Free list of DPC entries. As DPCs are queued, entries are removed from this
// free list. As DPC entries are dispatched, DPC entries are added to the free list.
// If the free list is empty and a DPC is queued, the free list is grown by allocating
// an additional set of DPC entries.
//
LIST_ENTRY mDpcEntryFreeList = INITIALIZE_LIST_HEAD_VARIABLE (mDpcEntryFreeList);
一开始它是空的,在首次使用时会创建64个空的DPC_ENTRY
,其基本代码:
//
// Check to see if there are any entries in the DPC free list
//
if (IsListEmpty (&mDpcEntryFreeList)) {
//
// Add 64 DPC entries to the free list
//
for (Index = 0; Index < 64; Index++) {
//
// Allocate a new DPC entry
//
DpcEntry = AllocatePool (sizeof (DPC_ENTRY));
//
// Add the newly allocated DPC entry to the DPC free list
//
InsertTailList (&mDpcEntryFreeList, &DpcEntry->ListEntry);
}
}
这里去掉了一系列的条件判断,只保留基本的操作,就是一个分配内存给DPC_ENTRY
,然后放到mDpcEntryFreeList
的过程,表示的是没有使用到的DPC。
mDpcEntryFreeList
中拿出一个DpcEntry
,为它赋值DpcProcedure
和DpcContext
,这么做之后DPC才真正生效了: //
// Retrieve the first node from the free list of DPCs
//
DpcEntry = (DPC_ENTRY *)(GetFirstNode (&mDpcEntryFreeList));
//
// Remove the first node from the free list of DPCs
//
RemoveEntryList (&DpcEntry->ListEntry);
//
// Fill in the DPC entry with the DpcProcedure and DpcContext
//
DpcEntry->DpcProcedure = DpcProcedure;
DpcEntry->DpcContext = DpcContext;
DpcEntry
插入到mDpcQueue
中,mDpcQueueDepth
全局变量的值加1,表示又多了一个DPC: //
// Add the DPC entry to the end of the list for the specified DplTpl.
//
InsertTailList (&mDpcQueue[DpcTpl], &DpcEntry->ListEntry);
//
// Increment the measured DPC queue depth across all TPLs
//
mDpcQueueDepth++;
mDpcQueue
和mDpcEntryFreeList
的关系大致如下:
这里预分配资源给mDpcEntryFreeList
,以及mDpcQueue
和mDpcEntryFreeList
之间的DPC转换使用,其目的主要是能够减少资源的反复分配导致的内存碎片化,也能够实现名字中的“延迟”一说,而且执行速度上也有保证。
DpcDispatchDpc()
:EFI_STATUS
EFIAPI
DpcDispatchDpc (
IN EFI_DPC_PROTOCOL *This
)
该函数的操作流程如下:
mDpcQueueDepth
值是否大于0,如果大于0,表示有DPC可以被调用,才会有后续的操作。 //
// Check to see if there are 1 or more DPCs currently queued
//
if (mDpcQueueDepth > 0) {
mDpcQueue
,找到非空的链表节点,将它从mDpcQueue
中删去,mDpcQueueDepth
的值相应的减1。 //
// Loop from TPL_HIGH_LEVEL down to the current TPL value
//
for (Tpl = TPL_HIGH_LEVEL; Tpl >= OriginalTpl; Tpl--) {
//
// Check to see if the DPC queue is empty
//
while (!IsListEmpty (&mDpcQueue[Tpl])) {
//
// Retrieve the first DPC entry from the DPC queue specified by Tpl
//
DpcEntry = (DPC_ENTRY *)(GetFirstNode (&mDpcQueue[Tpl]));
//
// Remove the first DPC entry from the DPC queue specified by Tpl
//
RemoveEntryList (&DpcEntry->ListEntry);
//
// Decrement the measured DPC Queue Depth across all TPLs
//
mDpcQueueDepth--;
注意这里的优先级是从高到低来执行对应的DPC的,这也是符合UEFI规范的。
mDpcQueue
去除下来的DpcEntry
对应的DpcProcedure
: //
// Invoke the DPC passing in its context
//
(DpcEntry->DpcProcedure)(DpcEntry->DpcContext);
mDpcQueue
去除下来的DpcEntry
在放回到mDpcEntryFreeList
中,之后就可以重复利用: //
// Add the invoked DPC entry to the DPC free list
//
InsertTailList (&mDpcEntryFreeList, &DpcEntry->ListEntry);
以上就是DpcDispatchDpc()
的流程。注意一次DpcDispatchDpc
会将mDpcQueue
中的所有DPC都执行一遍。
DpcLib的使用示例大致如下:
//
// Create the event used in the RxToken.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
ArpOnFrameRcvd,
ArpService,
&ArpService->RxToken.Event
);
if (EFI_ERROR (Status)) {
goto ERROR_EXIT;
}
VOID
EFIAPI
ArpOnFrameRcvd (
IN EFI_EVENT Event,
IN VOID *Context
)
{
//
// Request ArpOnFrameRcvdDpc as a DPC at TPL_CALLBACK
//
QueueDpc (TPL_CALLBACK, ArpOnFrameRcvdDpc, Context);
}
这里就会调用QueueDpc()
。这实际上很像是Linux中断处理中的上半部和下半部,上述的代码实际上是上半部,下半部就是执行DispatchDpc()
,下面是一个示例(位于NetworkPkg\ArpDxe\ArpImpl.c):
UINTN
ArpAddressResolved (
IN ARP_CACHE_ENTRY *CacheEntry,
IN ARP_INSTANCE_DATA *Instance OPTIONAL,
IN EFI_EVENT UserEvent OPTIONAL
)
{
//
// Dispatch the DPCs queued by the NotifyFunction of the Context->UserRequestEvent.
//
DispatchDpc ();
return Count;
}
执行DispatchDpc()
的位置需要根据实际的代码逻辑来确定,这个将在后续实际使用模块中进一步说明。
下面是自己写的一个使用DpcLib的简单示例,代码可以在BeniPkg\DynamicCommand\TestDynamicCommand\TestDpc.c中找到:
/**
DPC function.
@param NA
@retval NA
**/
VOID
EFIAPI
DpcCallback (
IN VOID *Context
)
{
Print (L"DPC callback function\r\n");
}
/**
Test DpcLib code.
@param NA
@return NA
**/
VOID
TestDpc (
VOID
)
{
QueueDpc (TPL_CALLBACK, DpcCallback, NULL);
}
这里执行TestDpc()
之后就会注册一个DPC函数,该函数也只是简单的打印信息而已,执行结果如下:
有几点需要注意:
DispatchDpc()
,但是我们注册的DPC函数DpcCallback()
还是被执行了,这是因为当前BIOS已经包含了UEFI网络协议栈,所以会在某个网络驱动的执行代码中调用DispatchDpc()
,而前面也已经说过DispatchDpc()
会调用所有的DPC函数,所以这里注册的DPC也会被调用。DpcCallback()
执行时间的变化。BIOS网络协议中的数据基本都是通过Token的形式来处理的,它有很多种不同的描述方式,整体描述如下:
需要注意的是Token实际上的使用者并不是提供者本身,而是提供者的上层协议,比如MNP的Token使用者是ARP、IP4等使用MNP的上层协议。
Token中比较通用的一种是:
typedef struct {
EFI_EVENT Event;
EFI_STATUS Status;
// 其它结构体
union {
XXX_DATA *RxData;
XXX_DATA *TxData;
// 其它结构体
} Packet;
} YYY;
注意结构体中写的XXX
和YYY
并不是真实的名称,在不同协议层下有不同的表示。当然也有其它的形式(图中深色部分),后面也会说明。下面将例举出所有的Token的具体格式。
typedef struct {
///
/// This Event will be signaled after the Status field is updated
/// by the MNP. The type of Event must be
/// EFI_NOTIFY_SIGNAL. The Task Priority Level (TPL) of
/// Event must be lower than or equal to TPL_CALLBACK.
///
EFI_EVENT Event;
///
/// The status that is returned to the caller at the end of the operation
/// to indicate whether this operation completed successfully.
///
EFI_STATUS Status;
union {
///
/// When this token is used for receiving, RxData is a pointer to the EFI_MANAGED_NETWORK_RECEIVE_DATA.
///
EFI_MANAGED_NETWORK_RECEIVE_DATA *RxData;
///
/// When this token is used for transmitting, TxData is a pointer to the EFI_MANAGED_NETWORK_TRANSMIT_DATA.
///
EFI_MANAGED_NETWORK_TRANSMIT_DATA *TxData;
} Packet;
} EFI_MANAGED_NETWORK_COMPLETION_TOKEN;
typedef struct {
///
/// This Event will be signaled after the Status field is updated
/// by the EFI IPv4 Protocol driver. The type of Event must be
/// EFI_NOTIFY_SIGNAL. The Task Priority Level (TPL) of
/// Event must be lower than or equal to TPL_CALLBACK.
///
EFI_EVENT Event;
///
/// The status that is returned to the caller at the end of the operation
/// to indicate whether this operation completed successfully.
///
EFI_STATUS Status;
union {
///
/// When this token is used for receiving, RxData is a pointer to the EFI_IP4_RECEIVE_DATA.
///
EFI_IP4_RECEIVE_DATA *RxData;
///
/// When this token is used for transmitting, TxData is a pointer to the EFI_IP4_TRANSMIT_DATA.
///
EFI_IP4_TRANSMIT_DATA *TxData;
} Packet;
} EFI_IP4_COMPLETION_TOKEN;
typedef union {
EFI_IP4_COMPLETION_TOKEN Ip4Token;
EFI_IP6_COMPLETION_TOKEN Ip6Token;
} IP_IO_IP_COMPLETION_TOKEN;
typedef struct {
EFI_EVENT Event;
EFI_STATUS Status;
} EFI_TCP4_COMPLETION_TOKEN;
typedef struct {
///
/// The Status in the CompletionToken will be set to one of
/// the following values if the active open succeeds or an unexpected
/// error happens:
/// EFI_SUCCESS: The active open succeeds and the instance's
/// state is Tcp4StateEstablished.
/// EFI_CONNECTION_RESET: The connect fails because the connection is reset
/// either by instance itself or the communication peer.
/// EFI_CONNECTION_REFUSED: The connect fails because this connection is initiated with
/// an active open and the connection is refused.
/// EFI_ABORTED: The active open is aborted.
/// EFI_TIMEOUT: The connection establishment timer expires and
/// no more specific information is available.
/// EFI_NETWORK_UNREACHABLE: The active open fails because
/// an ICMP network unreachable error is received.
/// EFI_HOST_UNREACHABLE: The active open fails because an
/// ICMP host unreachable error is received.
/// EFI_PROTOCOL_UNREACHABLE: The active open fails
/// because an ICMP protocol unreachable error is received.
/// EFI_PORT_UNREACHABLE: The connection establishment
/// timer times out and an ICMP port unreachable error is received.
/// EFI_ICMP_ERROR: The connection establishment timer timeout and some other ICMP
/// error is received.
/// EFI_DEVICE_ERROR: An unexpected system or network error occurred.
/// EFI_NO_MEDIA: There was a media error.
///
EFI_TCP4_COMPLETION_TOKEN CompletionToken;
} EFI_TCP4_CONNECTION_TOKEN;
typedef union {
EFI_TCP4_CONNECTION_TOKEN Tcp4Token;
EFI_TCP6_CONNECTION_TOKEN Tcp6Token;
} TCP_IO_CONNECTION_TOKEN;
注意这个结构体不涉及到具体的数据(即Packet
成员)。
typedef struct {
///
/// When transmission finishes or meets any unexpected error it will
/// be set to one of the following values:
/// EFI_SUCCESS: The receiving or transmission operation
/// completes successfully.
/// EFI_CONNECTION_FIN: The receiving operation fails because the communication peer
/// has closed the connection and there is no more data in the
/// receive buffer of the instance.
/// EFI_CONNECTION_RESET: The receiving or transmission operation fails
/// because this connection is reset either by instance
/// itself or the communication peer.
/// EFI_ABORTED: The receiving or transmission is aborted.
/// EFI_TIMEOUT: The transmission timer expires and no more
/// specific information is available.
/// EFI_NETWORK_UNREACHABLE: The transmission fails
/// because an ICMP network unreachable error is received.
/// EFI_HOST_UNREACHABLE: The transmission fails because an
/// ICMP host unreachable error is received.
/// EFI_PROTOCOL_UNREACHABLE: The transmission fails
/// because an ICMP protocol unreachable error is received.
/// EFI_PORT_UNREACHABLE: The transmission fails and an
/// ICMP port unreachable error is received.
/// EFI_ICMP_ERROR: The transmission fails and some other
/// ICMP error is received.
/// EFI_DEVICE_ERROR: An unexpected system or network error occurs.
/// EFI_NO_MEDIA: There was a media error.
///
EFI_TCP4_COMPLETION_TOKEN CompletionToken;
union {
///
/// When this token is used for receiving, RxData is a pointer to EFI_TCP4_RECEIVE_DATA.
///
EFI_TCP4_RECEIVE_DATA *RxData;
///
/// When this token is used for transmitting, TxData is a pointer to EFI_TCP4_TRANSMIT_DATA.
///
EFI_TCP4_TRANSMIT_DATA *TxData;
} Packet;
} EFI_TCP4_IO_TOKEN;
typedef union {
EFI_TCP4_IO_TOKEN Tcp4Token;
EFI_TCP6_IO_TOKEN Tcp6Token;
} TCP_IO_IO_TOKEN;
typedef struct {
EFI_TCP4_COMPLETION_TOKEN CompletionToken;
EFI_HANDLE NewChildHandle;
} EFI_TCP4_LISTEN_TOKEN;
typedef union {
EFI_TCP4_LISTEN_TOKEN Tcp4Token;
EFI_TCP6_LISTEN_TOKEN Tcp6Token;
} TCP_IO_LISTEN_TOKEN;
这里的数据不再是Packet
,而是一个EFI_HANDLE
,它表示的是一个TCP Socket对应的虚拟Handle:
///
/// The socket structure representing a network service access point.
///
struct _TCP_SOCKET {
EFI_HANDLE SockHandle; ///< The virtual handle of the socket
typedef struct {
///
/// When transmission finishes or meets any unexpected error it will
/// be set to one of the following values:
/// EFI_SUCCESS: The receiving or transmission operation
/// completes successfully.
/// EFI_CONNECTION_FIN: The receiving operation fails because the communication peer
/// has closed the connection and there is no more data in the
/// receive buffer of the instance.
/// EFI_CONNECTION_RESET: The receiving or transmission operation fails
/// because this connection is reset either by instance
/// itself or the communication peer.
/// EFI_ABORTED: The receiving or transmission is aborted.
/// EFI_TIMEOUT: The transmission timer expires and no more
/// specific information is available.
/// EFI_NETWORK_UNREACHABLE: The transmission fails
/// because an ICMP network unreachable error is received.
/// EFI_HOST_UNREACHABLE: The transmission fails because an
/// ICMP host unreachable error is received.
/// EFI_PROTOCOL_UNREACHABLE: The transmission fails
/// because an ICMP protocol unreachable error is received.
/// EFI_PORT_UNREACHABLE: The transmission fails and an
/// ICMP port unreachable error is received.
/// EFI_ICMP_ERROR: The transmission fails and some other
/// ICMP error is received.
/// EFI_DEVICE_ERROR: An unexpected system or network error occurs.
/// EFI_NO_MEDIA: There was a media error.
///
EFI_TCP4_COMPLETION_TOKEN CompletionToken;
union {
///
/// When this token is used for receiving, RxData is a pointer to EFI_TCP4_RECEIVE_DATA.
///
EFI_TCP4_RECEIVE_DATA *RxData;
///
/// When this token is used for transmitting, TxData is a pointer to EFI_TCP4_TRANSMIT_DATA.
///
EFI_TCP4_TRANSMIT_DATA *TxData;
} Packet;
} EFI_TCP4_IO_TOKEN;
typedef union {
EFI_TCP4_CLOSE_TOKEN Tcp4Token;
EFI_TCP6_CLOSE_TOKEN Tcp6Token;
} TCP_IO_CLOSE_TOKEN;
typedef struct {
EFI_EVENT Event;
EFI_STATUS Status;
union {
EFI_UDP4_RECEIVE_DATA *RxData;
EFI_UDP4_TRANSMIT_DATA *TxData;
} Packet;
} EFI_UDP4_COMPLETION_TOKEN;
typedef union {
EFI_UDP4_COMPLETION_TOKEN Udp4;
EFI_UDP6_COMPLETION_TOKEN Udp6;
} UDP_COMPLETION_TOKEN;
///
/// EFI_DNS4_COMPLETION_TOKEN
///
typedef struct {
///
/// This Event will be signaled after the Status field is updated by the EFI DNS
/// protocol driver. The type of Event must be EFI_NOTIFY_SIGNAL.
///
EFI_EVENT Event;
///
/// Will be set to one of the following values:
/// EFI_SUCCESS: The host name to address translation completed successfully.
/// EFI_NOT_FOUND: No matching Resource Record (RR) is found.
/// EFI_TIMEOUT: No DNS server reachable, or RetryCount was exhausted without
/// response from all specified DNS servers.
/// EFI_DEVICE_ERROR: An unexpected system or network error occurred.
/// EFI_NO_MEDIA: There was a media error.
///
EFI_STATUS Status;
///
/// Retry number if no response received after RetryInterval. If zero, use the
/// parameter configured through Dns.Configure() interface.
///
UINT32 RetryCount;
///
/// Minimum interval of retry is 2 second. If the retry interval is less than 2
/// seconds, then use the 2 seconds. If zero, use the parameter configured through
/// Dns.Configure() interface.
UINT32 RetryInterval;
///
/// DNSv4 completion token data
///
union {
///
/// When the Token is used for host name to address translation, H2AData is a pointer
/// to the DNS_HOST_TO_ADDR_DATA.
///
DNS_HOST_TO_ADDR_DATA *H2AData;
///
/// When the Token is used for host address to host name translation, A2HData is a
/// pointer to the DNS_ADDR_TO_HOST_DATA.
///
DNS_ADDR_TO_HOST_DATA *A2HData;
///
/// When the Token is used for a general lookup function, GLookupDATA is a pointer to
/// the DNS_GENERAL_LOOKUP_DATA.
///
DNS_GENERAL_LOOKUP_DATA *GLookupData;
} RspData;
} EFI_DNS4_COMPLETION_TOKEN;
这个Token中的数据相比其它的Token多了很多内容,不过形式并没有太大的变化,后面还有一些类似的Token。
struct _EFI_MTFTP4_TOKEN {
///
/// The status that is returned to the caller at the end of the operation
/// to indicate whether this operation completed successfully.
///
EFI_STATUS Status;
///
/// The event that will be signaled when the operation completes. If
/// set to NULL, the corresponding function will wait until the read or
/// write operation finishes. The type of Event must be
/// EVT_NOTIFY_SIGNAL. The Task Priority Level (TPL) of
/// Event must be lower than or equal to TPL_CALLBACK.
///
EFI_EVENT Event;
///
/// If not NULL, the data that will be used to override the existing configure data.
///
EFI_MTFTP4_OVERRIDE_DATA *OverrideData;
///
/// The pointer to the null-terminated ASCII file name string.
///
UINT8 *Filename;
///
/// The pointer to the null-terminated ASCII mode string. If NULL, "octet" is used.
///
UINT8 *ModeStr;
///
/// Number of option/value string pairs.
///
UINT32 OptionCount;
///
/// The pointer to an array of option/value string pairs. Ignored if OptionCount is zero.
///
EFI_MTFTP4_OPTION *OptionList;
///
/// The size of the data buffer.
///
UINT64 BufferSize;
///
/// The pointer to the data buffer. Data that is downloaded from the
/// MTFTPv4 server is stored here. Data that is uploaded to the
/// MTFTPv4 server is read from here. Ignored if BufferSize is zero.
///
VOID *Buffer;
///
/// The pointer to the context that will be used by CheckPacket,
/// TimeoutCallback and PacketNeeded.
///
VOID *Context;
///
/// The pointer to the callback function to check the contents of the received packet.
///
EFI_MTFTP4_CHECK_PACKET CheckPacket;
///
/// The pointer to the function to be called when a timeout occurs.
///
EFI_MTFTP4_TIMEOUT_CALLBACK TimeoutCallback;
///
/// The pointer to the function to provide the needed packet contents.
///
EFI_MTFTP4_PACKET_NEEDED PacketNeeded;
};
typedef struct _SOCK_COMPLETION_TOKEN {
EFI_EVENT Event; ///< The event to be issued
EFI_STATUS Status; ///< The status to be issued
} SOCK_COMPLETION_TOKEN;
typedef struct _SOCK_IO_TOKEN {
SOCK_COMPLETION_TOKEN Token;
SOCK_IO_DATA Packet;
} SOCK_IO_TOKEN;
typedef struct {
///
/// The completion status of transmitting and receiving.
///
EFI_STATUS Status;
///
/// If not NULL, the event that will be signaled when the collection process
/// completes. If NULL, this function will busy-wait until the collection process competes.
///
EFI_EVENT CompletionEvent;
///
/// The pointer to the server IP address. This address may be a unicast, multicast, or broadcast address.
///
EFI_IPv4_ADDRESS RemoteAddress;
///
/// The server listening port number. If zero, the default server listening port number (67) will be used.
///
UINT16 RemotePort;
///
/// The pointer to the gateway address to override the existing setting.
///
EFI_IPv4_ADDRESS GatewayAddress;
///
/// The number of entries in ListenPoints. If zero, the default station address and port number 68 are used.
///
UINT32 ListenPointCount;
///
/// An array of station address and port number pairs that are used as receiving filters.
/// The first entry is also used as the source address and source port of the outgoing packet.
///
EFI_DHCP4_LISTEN_POINT *ListenPoints;
///
/// The number of seconds to collect responses. Zero is invalid.
///
UINT32 TimeoutValue;
///
/// The pointer to the packet to be transmitted.
///
EFI_DHCP4_PACKET *Packet;
///
/// Number of received packets.
///
UINT32 ResponseCount;
///
/// The pointer to the allocated list of received packets.
///
EFI_DHCP4_PACKET *ResponseList;
} EFI_DHCP4_TRANSMIT_RECEIVE_TOKEN;
///
/// EFI_HTTP_TOKEN
///
typedef struct {
///
/// This Event will be signaled after the Status field is updated by the EFI HTTP
/// Protocol driver. The type of Event must be EFI_NOTIFY_SIGNAL. The Task Priority
/// Level (TPL) of Event must be lower than or equal to TPL_CALLBACK.
///
EFI_EVENT Event;
///
/// Status will be set to one of the following value if the HTTP request is
/// successfully sent or if an unexpected error occurs:
/// EFI_SUCCESS: The HTTP request was successfully sent to the remote host.
/// EFI_HTTP_ERROR: The response message was successfully received but contains a
/// HTTP error. The response status code is returned in token.
/// EFI_ABORTED: The HTTP request was cancelled by the caller and removed from
/// the transmit queue.
/// EFI_TIMEOUT: The HTTP request timed out before reaching the remote host.
/// EFI_DEVICE_ERROR: An unexpected system or network error occurred.
///
EFI_STATUS Status;
///
/// Pointer to storage containing HTTP message data.
///
EFI_HTTP_MESSAGE *Message;
} EFI_HTTP_TOKEN;
这里开始的Token的结构相比前面的Token已经有很大的差别:
typedef struct {
UINT32 Signature;
UDP_IO *UdpIo;
UDP_IO_CALLBACK CallBack;
VOID *Context;
UINT32 HeadLen;
UDP_COMPLETION_TOKEN Token;
} UDP_RX_TOKEN;
typedef struct {
UINT32 Signature;
LIST_ENTRY Link;
UDP_IO *UdpIo;
UDP_IO_CALLBACK CallBack;
NET_BUF *Packet;
VOID *Context;
EFI_IPv4_ADDRESS Gateway;
UDP_SESSION_DATA Session;
UDP_COMPLETION_TOKEN Token;
UDP_TRANSMIT_DATA Data;
} UDP_TX_TOKEN;
typedef struct {
UINT32 Signature;
LIST_ENTRY Link;
IP4_INTERFACE *Interface;
IP4_SERVICE *IpSb;
IP4_PROTOCOL *IpInstance;
IP4_FRAME_CALLBACK CallBack;
NET_BUF *Packet;
VOID *Context;
EFI_MAC_ADDRESS DstMac;
EFI_MAC_ADDRESS SrcMac;
EFI_MANAGED_NETWORK_COMPLETION_TOKEN MnpToken;
EFI_MANAGED_NETWORK_TRANSMIT_DATA MnpTxData;
} IP4_LINK_TX_TOKEN;
typedef struct {
UINT32 Signature;
IP4_INTERFACE *Interface;
IP4_PROTOCOL *IpInstance;
IP4_FRAME_CALLBACK CallBack;
VOID *Context;
EFI_MANAGED_NETWORK_COMPLETION_TOKEN MnpToken;
} IP4_LINK_RX_TOKEN;
typedef struct _SOCK_TOKEN {
LIST_ENTRY TokenList; ///< The entry to add in the token list
SOCK_COMPLETION_TOKEN *Token; ///< The application's token
UINT32 RemainDataLen; ///< Unprocessed data length
SOCKET *Sock; ///< The pointer to the socket this token
///< belongs to
} SOCK_TOKEN;
UDP4和TCP4中,有不少操作实际上是放在IpIoLib库中完成的,它实际上是上层网络驱动对IP层调用的包装接口。
IpIoLib首先对IPv4和IPv6中会使用到的数据进行了包装,这样就可以用统一的接口来处理IP层,比如:
typedef union {
EFI_IP4_COMPLETION_TOKEN Ip4Token;
EFI_IP6_COMPLETION_TOKEN Ip6Token;
} IP_IO_IP_COMPLETION_TOKEN;
typedef union {
EFI_IP4_TRANSMIT_DATA Ip4TxData;
EFI_IP6_TRANSMIT_DATA Ip6TxData;
} IP_IO_IP_TX_DATA;
typedef union {
EFI_IP4_RECEIVE_DATA Ip4RxData;
EFI_IP6_RECEIVE_DATA Ip6RxData;
} IP_IO_IP_RX_DATA;
typedef union {
EFI_IP4_OVERRIDE_DATA Ip4OverrideData;
EFI_IP6_OVERRIDE_DATA Ip6OverrideData;
} IP_IO_OVERRIDE;
typedef union {
EFI_IP4_CONFIG_DATA Ip4CfgData;
EFI_IP6_CONFIG_DATA Ip6CfgData;
} IP_IO_IP_CONFIG_DATA;
typedef union {
EFI_IP4_HEADER *Ip4Hdr;
EFI_IP6_HEADER *Ip6Hdr;
} IP_IO_IP_HEADER;
typedef union {
EFI_IP4_PROTOCOL *Ip4;
EFI_IP6_PROTOCOL *Ip6;
} IP_IO_IP_PROTOCOL;
这里包含了收发和处理数据的结构体,配置IP的结构体等内容,另外还有一个重要的结构体IP_IO,会在后续进一步介绍。
IP_IO
是对IP4和IP6接口的包装,这样上层的TCP和UDP就可以直接使用它而不需要分别对待IP4和IP6。该结构体位于NetworkPkg\Include\Library\IpIoLib.h:
///
/// This data structure wraps Ip4/Ip6 instances. The IpIo Library uses it for all
/// Ip4/Ip6 operations.
///
typedef struct _IP_IO {
///
/// The node used to link this IpIo to the active IpIo list.
///
LIST_ENTRY Entry;
///
/// The list used to maintain the IP instance for different sending purpose.
///
LIST_ENTRY IpList;
EFI_HANDLE Controller;
EFI_HANDLE Image;
EFI_HANDLE ChildHandle;
//
// The IP instance consumed by this IP_IO
//
IP_IO_IP_PROTOCOL Ip;
BOOLEAN IsConfigured;
///
/// Some ip configuration data can be changed.
///
UINT8 Protocol;
///
/// Token and event used to get data from IP.
///
IP_IO_IP_COMPLETION_TOKEN RcvToken;
///
/// List entry used to link the token passed to IP_IO.
///
LIST_ENTRY PendingSndList;
//
// User interface used to get notify from IP_IO
//
VOID *RcvdContext; ///< See IP_IO_OPEN_DATA::RcvdContext.
VOID *SndContext; ///< See IP_IO_OPEN_DATA::SndContext.
PKT_RCVD_NOTIFY PktRcvdNotify; ///< See IP_IO_OPEN_DATA::PktRcvdNotify.
PKT_SENT_NOTIFY PktSentNotify; ///< See IP_IO_OPEN_DATA::PktSentNotify.
UINT8 IpVersion;
IP4_ADDR StationIp;
IP4_ADDR SubnetMask;
} IP_IO;
该结构体通过IpIoCreate()
函数创建,它们在UDP和TCP模块中使用,比如TCP中:
EFI_STATUS
TcpCreateService (
IN EFI_HANDLE Controller,
IN EFI_HANDLE Image,
IN UINT8 IpVersion
)
{
TcpServiceData->IpIo = IpIoCreate (Image, Controller, IpVersion);
还有UDP中:
EFI_STATUS
Udp4CreateService (
IN OUT UDP4_SERVICE_DATA *Udp4Service,
IN EFI_HANDLE ImageHandle,
IN EFI_HANDLE ControllerHandle
)
{
Udp4Service->IpIo = IpIoCreate (ImageHandle, ControllerHandle, IP_VERSION_4);
下面介绍IP_IO
中比较重要的成员:
IpList
:IP实例链表,它在IpIoCreate()
中初始化,并在IpIoAddIp()
中添加链表成员:IP_IO_IP_INFO *
EFIAPI
IpIoAddIp (
IN OUT IP_IO *IpIo
)
{
InsertTailList (&IpIo->IpList, &IpInfo->Entry);
这里涉及到另外一个结构体IP_IO_IP_INFO
:
typedef struct _IP_IO_IP_INFO {
EFI_IP_ADDRESS Addr;
IP_IO_IP_MASK PreMask;
LIST_ENTRY Entry;
EFI_HANDLE ChildHandle;
IP_IO_IP_PROTOCOL Ip;
IP_IO_IP_COMPLETION_TOKEN DummyRcvToken;
INTN RefCnt;
UINT8 IpVersion;
} IP_IO_IP_INFO;
这个结构体也是在IpIoAddIp()
中创建的,这个函数虽然说是增加IP_IO_IP_INFO
,但是其中只是对IP_IO_IP_INFO
做了初始化而已,其中最重要的代码是:
IP_IO_IP_INFO *
EFIAPI
IpIoAddIp (
IN OUT IP_IO *IpIo
)
{
Status = IpIoCreateIpChildOpenProtocol (
IpIo->Controller,
IpIo->Image,
&IpInfo->ChildHandle,
IpInfo->IpVersion,
(VOID **)&IpInfo->Ip
);
// 中间略
InsertTailList (&IpIo->IpList, &IpInfo->Entry);
而IpIoCreateIpChildOpenProtocol()
打开了gEfiIp4ProtocolGuid
对应的EFI_IP4_PROTOCOL
(当然实际代码中是IP_IO_IP_PROTOCOL
)。所以IP_IO_IP_INFO
这个结构体的重点是对IP通信接口EFI_IP4_PROTOCOL
的包装。最终IpIoAddIp()
函数会被上层的TCP和UDP使用,来创建它们的IP通信接口:
// TCP模块中:
EFI_STATUS
TcpAttachPcb (
IN SOCKET *Sk
)
{
Tcb->IpInfo = IpIoAddIp (IpIo);
// UDP模块中:
EFI_STATUS
EFIAPI
Udp4ServiceBindingCreateChild (
IN EFI_SERVICE_BINDING_PROTOCOL *This,
IN EFI_HANDLE *ChildHandle
)
{
Instance->IpInfo = IpIoAddIp (Udp4Service->IpIo);
通过上述的方式,UPD和TCP就与其下层的IP联系到了一起:
Ip
:对IP4和IP6的Protocol的包装,我们主要关注IP4就可以了:typedef union {
EFI_IP4_PROTOCOL *Ip4;
EFI_IP6_PROTOCOL *Ip6;
} IP_IO_IP_PROTOCOL;
RcvToken
:如注释描述,是从IP4获取数据的Token。PendingSndList
:需要传递给IP4的Token的列表。RcvdContext
、SndContext
、PktRcvdNotify
、PktSentNotify
:对应数据处理的函数,它们来自另外的一个结构体IP_IO_OPEN_DATA
:///
/// The struct is for the user to pass IP configuration and callbacks to IP_IO.
/// It is used by IpIoOpen().
///
typedef struct _IP_IO_OPEN_DATA {
IP_IO_IP_CONFIG_DATA IpConfigData; ///< Configuration of the IP instance.
VOID *RcvdContext; ///< Context data used by receive callback.
VOID *SndContext; ///< Context data used by send callback.
PKT_RCVD_NOTIFY PktRcvdNotify; ///< Receive callback.
PKT_SENT_NOTIFY PktSentNotify; ///< Send callback.
} IP_IO_OPEN_DATA;
IP_IO_OPEN_DATA
其实是一个临时参数,会在打开IP_IO
的时候使用,比如UDP模块中:
EFI_STATUS
Udp4CreateService (
IN OUT UDP4_SERVICE_DATA *Udp4Service,
IN EFI_HANDLE ImageHandle,
IN EFI_HANDLE ControllerHandle
)
{
OpenData.RcvdContext = (VOID *)Udp4Service;
OpenData.SndContext = NULL;
OpenData.PktRcvdNotify = Udp4DgramRcvd;
OpenData.PktSentNotify = Udp4DgramSent;
Status = IpIoOpen (Udp4Service->IpIo, &OpenData);
还有TCP中:
EFI_STATUS
TcpCreateService (
IN EFI_HANDLE Controller,
IN EFI_HANDLE Image,
IN UINT8 IpVersion
)
{
ZeroMem (&OpenData, sizeof (IP_IO_OPEN_DATA));
if (IpVersion == IP_VERSION_4) {
CopyMem (
&OpenData.IpConfigData.Ip4CfgData,
&mIp4IoDefaultIpConfigData,
sizeof (EFI_IP4_CONFIG_DATA)
);
OpenData.IpConfigData.Ip4CfgData.DefaultProtocol = EFI_IP_PROTO_TCP;
} else {
CopyMem (
&OpenData.IpConfigData.Ip6CfgData,
&mIp6IoDefaultIpConfigData,
sizeof (EFI_IP6_CONFIG_DATA)
);
OpenData.IpConfigData.Ip6CfgData.DefaultProtocol = EFI_IP_PROTO_TCP;
}
OpenData.PktRcvdNotify = TcpRxCallback;
Status = IpIoOpen (TcpServiceData->IpIo, &OpenData);
都是在创建TCP或者UDP服务时作为参数使用。这个数据中最重要的是两个回调函数,分别对应IP数据收发的处理:
/**
The prototype is called back when an IP packet is received.
@param[in] Status The result of the receive request.
@param[in] IcmpErr Valid when Status is EFI_ICMP_ERROR.
@param[in] NetSession The IP session for the received packet.
@param[in] Pkt The packet received.
@param[in] Context The data provided by the user for the received packet when
the callback is registered in IP_IO_OPEN_DATA::RcvdContext.
**/
typedef
VOID
(EFIAPI *PKT_RCVD_NOTIFY)(
IN EFI_STATUS Status,
IN UINT8 IcmpErr,
IN EFI_NET_SESSION_DATA *NetSession,
IN NET_BUF *Pkt,
IN VOID *Context
);
/**
The prototype is called back when an IP packet is sent.
@param[in] Status Result of the IP packet being sent.
@param[in] Context The data provided by user for the received packet when
the callback is registered in IP_IO_OPEN_DATA::SndContext.
@param[in] Sender A Union type to specify a pointer of EFI_IP4_PROTOCOL
or EFI_IP6_PROTOCOL.
@param[in] NotifyData The Context data specified when calling IpIoSend()
**/
typedef
VOID
(EFIAPI *PKT_SENT_NOTIFY)(
IN EFI_STATUS Status,
IN VOID *Context,
IN IP_IO_IP_PROTOCOL Sender,
IN VOID *NotifyData
);
IpVersion
:对应IP_VERSION_4
或者IP_VERSION_6
,值分别是4和6,表示IPv4和IPv6。StationIp
、SubnetMask
:IP使用的地址和掩码。比较重要的函数:
/**
Create a new IP_IO instance.
If IpVersion is not IP_VERSION_4 or IP_VERSION_6, then ASSERT().
This function uses IP4/IP6 service binding protocol in Controller to create
an IP4/IP6 child (aka IP4/IP6 instance).
@param[in] Image The image handle of the driver or application that
consumes IP_IO.
@param[in] Controller The controller handle that has IP4 or IP6 service
binding protocol installed.
@param[in] IpVersion The version of the IP protocol to use, either
IPv4 or IPv6.
@return The pointer to a newly created IP_IO instance, or NULL if failed.
**/
IP_IO *
EFIAPI
IpIoCreate (
IN EFI_HANDLE Image,
IN EFI_HANDLE Controller,
IN UINT8 IpVersion
);
/**
Open an IP_IO instance for use.
If Ip version is not IP_VERSION_4 or IP_VERSION_6, then ASSERT().
This function is called after IpIoCreate(). It is used for configuring the IP
instance and register the callbacks and their context data for sending and
receiving IP packets.
@param[in, out] IpIo The pointer to an IP_IO instance that needs
to open.
@param[in] OpenData The configuration data and callbacks for
the IP_IO instance.
@retval EFI_SUCCESS The IP_IO instance opened with OpenData
successfully.
@retval EFI_ACCESS_DENIED The IP_IO instance is configured, avoid to
reopen it.
@retval EFI_UNSUPPORTED IPv4 RawData mode is no supported.
@retval EFI_INVALID_PARAMETER Invalid input parameter.
@retval Others Error condition occurred.
**/
EFI_STATUS
EFIAPI
IpIoOpen (
IN OUT IP_IO *IpIo,
IN IP_IO_OPEN_DATA *OpenData
);
/**
Send out an IP packet.
This function is called after IpIoOpen(). The data to be sent is wrapped in
Pkt. The IP instance wrapped in IpIo is used for sending by default but can be
overridden by Sender. Other sending configs, like source address and gateway
address etc., are specified in OverrideData.
@param[in, out] IpIo Pointer to an IP_IO instance used for sending IP
packet.
@param[in, out] Pkt Pointer to the IP packet to be sent.
@param[in] Sender The IP protocol instance used for sending.
@param[in] Context Optional context data.
@param[in] NotifyData Optional notify data.
@param[in] Dest The destination IP address to send this packet to.
This parameter is optional when using IPv6.
@param[in] OverrideData The data to override some configuration of the IP
instance used for sending.
@retval EFI_SUCCESS The operation is completed successfully.
@retval EFI_INVALID_PARAMETER The input parameter is not correct.
@retval EFI_NOT_STARTED The IpIo is not configured.
@retval EFI_OUT_OF_RESOURCES Failed due to resource limit.
@retval Others Error condition occurred.
**/
EFI_STATUS
EFIAPI
IpIoSend (
IN OUT IP_IO *IpIo,
IN OUT NET_BUF *Pkt,
IN IP_IO_IP_INFO *Sender OPTIONAL,
IN VOID *Context OPTIONAL,
IN VOID *NotifyData OPTIONAL,
IN EFI_IP_ADDRESS *Dest OPTIONAL,
IN IP_IO_OVERRIDE *OverrideData OPTIONAL
);
/**
Add a new IP instance for sending data.
If IpIo is NULL, then ASSERT().
If Ip version is not IP_VERSION_4 or IP_VERSION_6, then ASSERT().
The function is used to add the IP_IO to the IP_IO sending list. The caller
can later use IpIoFindSender() to get the IP_IO and call IpIoSend() to send
data.
@param[in, out] IpIo The pointer to an IP_IO instance to add a new IP
instance for sending purposes.
@return The pointer to the created IP_IO_IP_INFO structure; NULL if failed.
**/
IP_IO_IP_INFO *
EFIAPI
IpIoAddIp (
IN OUT IP_IO *IpIo
);
/**
Configure the IP instance of this IpInfo and start the receiving if IpConfigData
is not NULL.
If Ip version is not IP_VERSION_4 or IP_VERSION_6, then ASSERT().
@param[in, out] IpInfo The pointer to the IP_IO_IP_INFO instance.
@param[in, out] IpConfigData The IP4 or IP6 configure data used to configure
the IP instance. If NULL, the IP instance is reset.
If UseDefaultAddress is set to TRUE, and the configure
operation succeeds, the default address information
is written back in this IpConfigData.
@retval EFI_SUCCESS The IP instance of this IpInfo was configured successfully,
or there is no need to reconfigure it.
@retval Others The configuration failed.
**/
EFI_STATUS
EFIAPI
IpIoConfigIp (
IN OUT IP_IO_IP_INFO *IpInfo,
IN OUT VOID *IpConfigData OPTIONAL
);