MNP
MNP代码综述
MNP全称是Managed Network Protocol,它对SNP进行了一层包装,事实上上层网络应用或者驱动一般都是调用MNP的接口来完成网络通信,而不会直接使用SNP。MNP是UEFI中网络数据收发的基础。MNP的作用主要有以下的几点:
- 作为网络数据收发的基本单元。在介绍SNP的时候已经说过,它只是一个单纯的数据收发接口,初始化SNP之后并没有直接使用,而MNP初始化之后,会有一系列的定时事件被创建,可以通过操作MNP的接口来进行真正的网络收发。
- 管理VLAN配置。
- 单播、组播、广播、混杂模式等的处理。
MNP也是一个UEFI Driver Model,所以会安装EFI_DRIVER_BINDING_PROTOCOL
,其实现如下:
EFI_DRIVER_BINDING_PROTOCOL gMnpDriverBinding = {
MnpDriverBindingSupported,
MnpDriverBindingStart,
MnpDriverBindingStop,
0xa,
NULL,
NULL
};
MNP在UEFI网络协议栈中的关系图:
注意,MNP最终起到网络访问的接口是EFI_MANAGED_NETWORK_PROTOCOL
,对应的GUID是gEfiManagedNetworkProtocolGuid
,但是它并不会由MNP驱动直接安装,这一点非常重要,这也是MNP之上大部分上层网络协议的一般做法,会先安装服务、然后通过服务创建子项,而子项才会进行网络操作。步骤大致如下:
- 获取服务,并由该服务创建子项。UEFI提供了库函数
NetLibCreateServiceChild()
来完成这个操作,对应代码如下:
//
// Get the ServiceBinding Protocol
//
Status = gBS->OpenProtocol (
Controller,
ServiceBindingGuid,
(VOID **)&Service,
Image,
Controller,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
//
// Create a child
//
Status = Service->CreateChild (Service, ChildHandle);
- 通过子项获取网络传输接口,本例对应的就是
EFI_MANAGED_NETWORK_PROTOCOL
(示例来自ARP驱动):
//
// Open the MNP protocol.
//
Status = gBS->OpenProtocol (
ArpService->MnpChildHandle,
&gEfiManagedNetworkProtocolGuid,
(VOID **)&ArpService->Mnp,
ImageHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
- 后续通过
EFI_MANAGED_NETWORK_PROTOCOL
来完成各类网络操作。
关于为什么要使用到gEfiManagedNetworkServiceBindingProtocolGuid
,而不是像SNP那样一个Protocol对应一张网卡来网络收发,主要原因是MNP需要处理VLAN,而VLAN的不同导致处理的数据包不同,由此导致MNP不能通过同一个接口来发送数据(其实也不是不能,而是当前这样的方式更好),所以整体的处理流程是如下的形式:
MNP子项中包含EFI_MANAGED_NETWORK_PROTOCOL
,通过它就可以进行网络数据收发。
需要注意,所有基于MNP的上层网络协议,都是按照同样的方式来进行网络收发的,这也很好理解,因为上层网络协议都需要依赖于MNP进行网络数据的收发。
MnpDriverBindingSupported
MNP的Supported函数很简单,只是判断gEfiSimpleNetworkProtocolGuid
是否存在,如果存在,则可以执行相应的Start函数:
EFI_STATUS
EFIAPI
Mtftp4DriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
Status = gBS->OpenProtocol (
Controller,
&gEfiUdp4ServiceBindingProtocolGuid,
NULL,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_TEST_PROTOCOL
);
}
MnpDriverBindingStart
MNP驱动的初始化主要做了以下的是事情:
- 初始化
MNP_DEVICE_DATA
,跟SNP类似,每个网卡有一个MNP_DEVICE_DATA
。 - 判断
gEfiVlanConfigProtocolGuid
是否存在。因为UNDI也可以安装它,这属于硬件VLAN,如果有安装,则只需要一份服务,对应VLAN的ID和优先级都是0,MNP不需要特别的操作,只需要将创建服务数据的函数执行一次:
MnpServiceData = MnpCreateServiceData (MnpDeviceData, 0, 0);
//
// Install VLAN Config Protocol
//
Status = gBS->InstallMultipleProtocolInterfaces (
&ControllerHandle,
&gEfiVlanConfigProtocolGuid,
&MnpDeviceData->VlanConfig,
NULL
);
- 然后获取当前需要支持的VLAN数据,它对应的是一个变量:
//
// Get current VLAN configuration from EFI Variable
//
NumberOfVlan = 0;
Status = MnpGetVlanVariable (MnpDeviceData, &NumberOfVlan, &VlanVariable);
if (EFI_ERROR (Status)) {
//
// No VLAN is set, create a default MNP service data for untagged frame
//
MnpDeviceData->NumberOfVlan = 0;
MnpServiceData = MnpCreateServiceData (MnpDeviceData, 0, 0);
Status = (MnpServiceData != NULL) ? EFI_SUCCESS : EFI_OUT_OF_RESOURCES;
goto Exit;
}
//
// Create MNP service data for each VLAN
//
MnpDeviceData->NumberOfVlan = NumberOfVlan;
for (Index = 0; Index < NumberOfVlan; Index++) {
MnpServiceData = MnpCreateServiceData (
MnpDeviceData,
VlanVariable[Index].Bits.Vid,
(UINT8)VlanVariable[Index].Bits.Priority
);
对于MNP驱动来说,最重要的有以下几点:
-
MNP_DEVICE_DATA
结构体初始化。 -
MNP_SERVICE_DATA
结构体初始化。 -
安装
EFI_VLAN_CONFIG_PROTOCOL
接口。 -
安装
EFI_SERVICE_BINDING_PROTOCOL
接口。
其流程大致如下:
从这里也可以看出来:
MNP_DEVICE_DATA
只有一个全局的版本,即一张网卡对应一个MNP。MNP_SERVICE_DATA
针对每一种VLAN配置都存在一个。EFI_VLAN_CONFIG_PROTOCOL
只安装一次。EFI_SERVICE_BINDING_PROTOCOL
针对每一个VLAN配置都会安装一个。
另外EFI_SERVICE_BINDING_PROTOCOL
执行CreateChild()
的时候还会创建和初始化MNP_INSTANCE_DATA
,表示一个子项,并安装EFI_MANAGED_NETWORK_PROTOCOL
,它们也是很重要的结构体。所有这些结构体的对应关系描述如下:
后面将分别介绍这些内容。
MNP_DEVICE_DATA
MNP_DEVICE_DATA
定义在NetworkPkg\MnpDxe\MnpDriver.h中,其中的成员大致可以分为几类:
- 通用成员,比如一些EFI_HANDLE和Protocol。
- 处理服务的成员。
- 处理VLAN的成员。
- 处理数据传输的成员。
- 处理单播、组播、广播等的成员。
- 管理网络的事件。
其结构体如下:
typedef struct {
UINT32 Signature;
EFI_HANDLE ControllerHandle;
EFI_HANDLE ImageHandle;
EFI_VLAN_CONFIG_PROTOCOL VlanConfig;
UINTN NumberOfVlan;
CHAR16 *MacString;
EFI_SIMPLE_NETWORK_PROTOCOL *Snp;
//
// List of MNP_SERVICE_DATA
//
LIST_ENTRY ServiceList;
//
// Number of configured MNP Service Binding child
//
UINTN ConfiguredChildrenNumber;
LIST_ENTRY GroupAddressList;
UINT32 GroupAddressCount;
LIST_ENTRY FreeTxBufList;
LIST_ENTRY AllTxBufList;
UINT32 TxBufCount;
NET_BUF_QUEUE FreeNbufQue;
INTN NbufCnt;
EFI_EVENT PollTimer;
BOOLEAN EnableSystemPoll;
EFI_EVENT TimeoutCheckTimer;
EFI_EVENT MediaDetectTimer;
UINT32 UnicastCount;
UINT32 BroadcastCount;
UINT32 MulticastCount;
UINT32 PromiscuousCount;
//
// The size of the data buffer in the MNP_PACKET_BUFFER used to
// store a packet.
//
UINT32 BufferLength;
UINT32 PaddingSize;
NET_BUF *RxNbufCache;
} MNP_DEVICE_DATA;
内容较多,这里只对其中比较重要的进行说明。
VlanConfig
:对应EFI_VLAN_CONFIG_PROTOCOL
,用于处理VLAN配置的Protocol:
struct _EFI_VLAN_CONFIG_PROTOCOL {
EFI_VLAN_CONFIG_SET Set;
EFI_VLAN_CONFIG_FIND Find;
EFI_VLAN_CONFIG_REMOVE Remove;
};
它就在Start函数中安装的,虽然VLAN配置可以有很多个,但是操作VLAN的Protocol只需要一个就够了。
MnpGetVlanVariable (MnpDeviceData, &NumberOfVlan, &VlanVariable);
MnpDeviceData->NumberOfVlan = NumberOfVlan;
EFI_STATUS
MnpSetVlanVariable (
IN MNP_DEVICE_DATA *MnpDeviceData,
IN UINTN NumberOfVlan,
IN VLAN_TCI *VlanVariable
)
{
return gRT->SetVariable (
MnpDeviceData->MacString, // 变量名
&gEfiVlanConfigProtocolGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
NumberOfVlan * sizeof (VLAN_TCI),
VlanVariable
);
}
这也是一个不错的做法,因为UEFI中的变量名必须是独一无二的,每个MNP_DEVICE_DATA
对应一张网卡(或者一张网卡中的一个网口),而网卡(网口)的MAC地址是唯一的。
-
Snp
:对应EFI_SIMPLE_NETWORK_PROTOCOL
,因为MNP_DEVICE_DATA
每个网卡(网口)一个,两者有对应关系。 -
ServiceList
:指向MNP_SERVICE_DATA
结构体的链表,每个不同的VLAN对应一个这样的结构体,不存在VLAN也算一种配置,只是ID和优先级的值都是0。 -
ConfiguredChildrenNumber
:当MNP配置好之后(会对应到一个MNP_INSTANCE_DATA结构体),在调用MnpStart()
时会自增1,MnpStop()
时自减1,表示的是实际数据收发单元的个数,通过该参数,可以控制MnpStart()
和MnpStop()
中的操作。关于MnpStart和MnpStop,后面会进一步介绍。 -
FreeTxBufList
、AllTxBufList
、TxBufCount
:管理内存,用于发数据,最终会在EFI_MANAGED_NETWORK_CONFIG_DATA
的Transmit
成员函数中用到。 -
FreeNbufQue
、NbufCnt
、RxNbufCache
:管理内存,用于收数据,最终会在EFI_MANAGED_NETWORK_CONFIG_DATA
的Poll
成员函数中用到。 -
PollTimer
、EnableSystemPoll
:PollTimer
是一个UEFI定时事件,EnableSystemPoll
用来控制定时器的开启和关闭,关于它们的用法同样会在MnpStart和MnpStop中介绍。 -
TimeoutCheckTimer
、MediaDetectTimer
:跟PollTimer
一样是UEFI定时事件,可以在MnpStart和MnpStop看到具体的使用方式。 -
GroupAddressList
、GroupAddressCount
、UnicastCount
、BroadcastCount
、MulticastCount
、PromiscuousCount
:处理单播、组播、广播等的参数。
MNP_SERVICE_DATA
MNP_SERVICE_DATA
可以有多个,因为它是与VLAN配置对应的,有多少个VLAN就有多少个MNP_SERVICE_DATA
,因此EFI_SERVICE_BINDING_PROTOCOL
的实例也可以有多个。在Start函数中会通过MnpCreateServiceData()
函数生成所有的MNP_SERVICE_DATA
,主要的参数就是VLAN的ID和优先级:
MNP_SERVICE_DATA *
MnpCreateServiceData (
IN MNP_DEVICE_DATA *MnpDeviceData,
IN UINT16 VlanId,
IN UINT8 Priority OPTIONAL
)
MNP_SERVICE_DATA
定义在NetworkPkg\MnpDxe\MnpDriver.h中,如下所示:
typedef struct {
UINT32 Signature;
LIST_ENTRY Link;
MNP_DEVICE_DATA *MnpDeviceData;
EFI_HANDLE ServiceHandle;
EFI_SERVICE_BINDING_PROTOCOL ServiceBinding;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
LIST_ENTRY ChildrenList;
UINTN ChildrenNumber;
UINT32 Mtu;
UINT16 VlanId;
UINT8 Priority;
} MNP_SERVICE_DATA;
这里说明其中比较重要的值:
-
MnpDeviceData
:就是对应网卡的MNP_DEVICE_DATA
。 -
ServiceHandle
、ServiceBinding
:对应Handle及安装其上的EFI_SERVICE_BINDING_PROTOCOL
。 -
ChildrenList
、ChildrenNumber
:这两个值涉及到EFI_SERVICE_BINDING_PROTOCOL
的CreateChild()
函数创建的实例,这里就是MNP_INSTANCE_DATA
结构体,通过这里的两个成员可以访问到MNP创建的子项,最终获取到EFI_MANAGED_NETWORK_PROTOCOL
,这才是收发数据的主体。 -
Mtu
:最大传输单元,会因为VLAN的引入而减少几个字节(一般是4个字节)。
MNP_INSTANCE_DATA
MNP_INSTANCE_DATA
是通过EFI_SERVICE_BINDING_PROTOCOL
的CreateChild()
接口生成的,所以它的主要作用是描述MNP子项:
EFI_STATUS
EFIAPI
MnpServiceBindingCreateChild (
IN EFI_SERVICE_BINDING_PROTOCOL *This,
IN OUT EFI_HANDLE *ChildHandle
)
{
//
// Allocate buffer for the new instance.
//
Instance = AllocateZeroPool (sizeof (MNP_INSTANCE_DATA));
//
// Init the instance data.
//
MnpInitializeInstanceData (MnpServiceData, Instance);
// 安装EFI_MANAGED_NETWORK_PROTOCOL。
Status = gBS->InstallMultipleProtocolInterfaces (
ChildHandle,
&gEfiManagedNetworkProtocolGuid,
&Instance->ManagedNetwork,
NULL
);
}
这个时候才会安装EFI_MANAGED_NETWORK_PROTOCOL
,它是MNP网络传输的主体。
MNP_INSTANCE_DATA
定义在NetworkPkg\MnpDxe\MnpImpl.h中,其代码如下所示:
typedef struct {
UINT32 Signature;
MNP_SERVICE_DATA *MnpServiceData;
EFI_HANDLE Handle;
LIST_ENTRY InstEntry;
EFI_MANAGED_NETWORK_PROTOCOL ManagedNetwork;
BOOLEAN Configured;
BOOLEAN Destroyed;
LIST_ENTRY GroupCtrlBlkList;
NET_MAP RxTokenMap;
LIST_ENTRY RxDeliveredPacketQueue;
LIST_ENTRY RcvdPacketQueue;
UINTN RcvdPacketQueueSize;
EFI_MANAGED_NETWORK_CONFIG_DATA ConfigData;
UINT8 ReceiveFilter;
} MNP_INSTANCE_DATA;
这里说明其中比较重要的成员:
MnpServiceData
:每一个VLAN配置都有一个MNP_SERVICE_DATA
,子项由服务生成,所以子项数据MNP_INSTANCE_DATA
中的成员会指向创建该子项的服务所对应的数据MNP_SERVICE_DATA
。Handle
、ManagedNetwork
:实际安装EFI_MANAGED_NETWORK_PROTOCOL
所对应的Handle和Protocol。Configured
、ConfigData
:MNP需要经过配置后才能使用(也就是说只有执行EFI_MANAGED_NETWORK_PROTOCOL->Configure()
之后才会开始网络操作),这两个是相关的配置参数。Destroyed
:关于该参数代码中有说明:
//
// MnpServiceBindingDestroyChild may be called twice: first called by
// MnpServiceBindingStop, second called by uninstalling the MNP protocol
// in this ChildHandle. Use destroyed to make sure the resource clean code
// will only excecute once.
//
RxTokenMap
:这个成员是用来存放名为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;
需要注意,这里只是举了一个例子,即MNP下的一个Token格式,这种格式的Token还有很多不同的名字,比如EFI_MANAGED_NETWORK_COMPLETION_TOKEN
、
EFI_IP4_COMPLETION_TOKEN
,等等。它们都具有类似的格式,包含一个事件,一个状态和一个指针指向接收数据或者发送数据。
RxTokenMap
本身的类型是NET_MAP
,它的定义如下:
typedef struct {
LIST_ENTRY Used;
LIST_ENTRY Recycled;
UINTN Count;
} NET_MAP;
实际上就是一个列表集,包含了Token的个数,以及在使用的和使用过的Token列表。
RxTokenMap
首先会在NetMapInit()
中进行初始化:
VOID
EFIAPI
NetMapInit (
IN OUT NET_MAP *Map
)
{
ASSERT (Map != NULL);
InitializeListHead (&Map->Used);
InitializeListHead (&Map->Recycled);
Map->Count = 0;
}
得到一个空的列表集。这个函数包含在MnpServiceBindingCreateChild()
中,它在创建MNP_INSTANCE_DATA
的时候就会初始化,并在MnpServiceBindingDestroyChild()
中清空。
在EFI_MANAGED_NETWORK_PROTOCOL
的Receive
成员函数 (其实现是MnpReceive()
)中,会向RxTokenMap
中插入一个个的Token:
EFI_STATUS
EFIAPI
MnpReceive (
IN EFI_MANAGED_NETWORK_PROTOCOL *This,
IN EFI_MANAGED_NETWORK_COMPLETION_TOKEN *Token
)
{
//
// Check whether this token(event) is already in the rx token queue.
//
Status = NetMapIterate (&Instance->RxTokenMap, MnpTokenExist, (VOID *)Token);
//
// Insert the Token into the RxTokenMap.
//
Status = NetMapInsertTail (&Instance->RxTokenMap, (VOID *)Token, NULL);
}
Token是作为参数传递给MnpReceive()
的,后者会被上层协议调用,因此上层接口的Token也就被放到了这个RxTokenMap
中。
比如下面ARP的Start函数中有如下的代码:
//
// OK, start to receive arp packets from Mnp.
//
Status = ArpService->Mnp->Receive (ArpService->Mnp, &ArpService->RxToken);
这里的ArpService->RxToken
的类型就是EFI_MANAGED_NETWORK_COMPLETION_TOKEN
,它在ArpCreateService()
函数中使用:
//
// Create the event used in the RxToken.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_NOTIFY,
ArpOnFrameRcvd,
ArpService,
&ArpService->RxToken.Event
);
在MnpCancel()
函数中,会释放这些Token。
而最重要的是这些Token的使用,位于MnpInstanceDeliverPacket()
函数中。
//
// Get the receive token from the RxTokenMap.
//
RxToken = NetMapRemoveHead (&Instance->RxTokenMap, NULL);
//
// Signal this token's event.
//
RxToken->Packet.RxData = &RxDataWrap->RxData;
RxToken->Status = EFI_SUCCESS;
gBS->SignalEvent (RxToken->Event);
在这个函数中,就是一个收取数据,然后通过回调,使用Token中的事件来处理这些收到的数据的过程,这样做的目的是因为MNP本身是不知道数据该如何使用的,只有上层的网络协议才知道,因此上层网络协议需要通过Token的方式来注册回调接口以便处理接收到的数据,由于可注册的Token可以有很多,所以这部分数据会被不同的上层协议使用到,还是以前面提到的ARP为例,它的处理程序是ArpOnFrameRcvd()
,关于它的实现可以参考ArpOnFrameRcvd中的说明。
MnpInstanceDeliverPacket()
会在两个地方被调用:一个就是前面说的Receive
成员函数,这个函数是用来注册Token的,注册完了紧接着就收取数据来进行处理,当然不一定当下就能够收到数据;所以就有了第二个地方,它的调用栈如下:
而MnpSystemPoll()
是一个定时事件的回调函数,所以就有了一个定时接收数据的过程。
需要注意下面的代码:
//
// Get the receive token from the RxTokenMap.
//
RxToken = NetMapRemoveHead (&Instance->RxTokenMap, NULL);
即一个Token只会被MNP回调一次,之后就会被回收。
整个过程简单来说就是一个注册事件并执行数据处理的过程,被执行的前提是MNP收到数据。
RxDeliveredPacketQueue
、RcvdPacketQueue
:处理数据包的成员。ConfigData
:MNP的配置数据,EFI_MANAGED_NETWORK_CONFIG_DATA中会进一步介绍。ReceiveFilter
:过滤单播、广播的标志。
EFI_MANAGED_NETWORK_CONFIG_DATA
这是MNP的配置,配置是给MNP子项使用的,其结构如下:
typedef struct {
///
/// Timeout value for a UEFI one-shot timer event. A packet that has not been removed
/// from the MNP receive queue will be dropped if its receive timeout expires.
///
UINT32 ReceivedQueueTimeoutValue;
///
/// Timeout value for a UEFI one-shot timer event. A packet that has not been removed
/// from the MNP transmit queue will be dropped if its receive timeout expires.
///
UINT32 TransmitQueueTimeoutValue;
///
/// Ethernet type II 16-bit protocol type in host byte order. Valid
/// values are zero and 1,500 to 65,535.
///
UINT16 ProtocolTypeFilter;
///
/// Set to TRUE to receive packets that are sent to the network
/// device MAC address. The startup default value is FALSE.
///
BOOLEAN EnableUnicastReceive;
///
/// Set to TRUE to receive packets that are sent to any of the
/// active multicast groups. The startup default value is FALSE.
///
BOOLEAN EnableMulticastReceive;
///
/// Set to TRUE to receive packets that are sent to the network
/// device broadcast address. The startup default value is FALSE.
///
BOOLEAN EnableBroadcastReceive;
///
/// Set to TRUE to receive packets that are sent to any MAC address.
/// The startup default value is FALSE.
///
BOOLEAN EnablePromiscuousReceive;
///
/// Set to TRUE to drop queued packets when the configuration
/// is changed. The startup default value is FALSE.
///
BOOLEAN FlushQueuesOnReset;
///
/// Set to TRUE to timestamp all packets when they are received
/// by the MNP. Note that timestamps may be unsupported in some
/// MNP implementations. The startup default value is FALSE.
///
BOOLEAN EnableReceiveTimestamps;
///
/// Set to TRUE to disable background polling in this MNP
/// instance. Note that background polling may not be supported in
/// all MNP implementations. The startup default value is FALSE,
/// unless background polling is not supported.
///
BOOLEAN DisableBackgroundPolling;
} EFI_MANAGED_NETWORK_CONFIG_DATA;
由于这些参数比较重要,所以这里详细说明:
ReceivedQueueTimeoutValue
:这里涉及到一个Received Queue,用来收集接收到的数据,而这个数据会有一个处理的过程,当数据在队列里面等待处理的时候,MNP会检测它的等待时间,如果等待时间很长,超过了ReceivedQueueTimeoutValue
设置的值,则会丢弃数据。这通过MnpCheckPacketTimeout()
来完成,它是一个定时事件回调函数:
//
// Create the timer for packet timeout check.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL | EVT_TIMER,
TPL_CALLBACK,
MnpCheckPacketTimeout,
MnpDeviceData,
&MnpDeviceData->TimeoutCheckTimer
);
其处理由MnpCheckPacketTimeout()
完成:
VOID
EFIAPI
MnpCheckPacketTimeout (
IN EFI_EVENT Event,
IN VOID *Context
)
{
// 遍历所有的服务,因为服务可以由VLAN的多个配置而产生多个
NET_LIST_FOR_EACH (ServiceEntry, &MnpDeviceData->ServiceList) {
MnpServiceData = MNP_SERVICE_DATA_FROM_LINK (ServiceEntry);
// 遍历服务创建的子项,一个服务可以通过CreateChild()创建多个子项
NET_LIST_FOR_EACH (Entry, &MnpServiceData->ChildrenList) {
// 每个子项由一个MNP_INSTANCE_DATA结构体,其中包含了配置项EFI_MANAGED_NETWORK_CONFIG_DATA
Instance = NET_LIST_USER_STRUCT (Entry, MNP_INSTANCE_DATA, InstEntry);
NET_CHECK_SIGNATURE (Instance, MNP_INSTANCE_DATA_SIGNATURE);
// 可以看到0是一个特殊的值,表示不会超时
if (!Instance->Configured || (Instance->ConfigData.ReceivedQueueTimeoutValue == 0)) {
//
// This instance is not configured or there is no receive time out,
// just skip to the next instance.
//
continue;
}
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
NET_LIST_FOR_EACH_SAFE (RxEntry, NextEntry, &Instance->RcvdPacketQueue) {
RxDataWrap = NET_LIST_USER_STRUCT (RxEntry, MNP_RXDATA_WRAP, WrapEntry);
//
// TimeoutTick unit is microsecond, MNP_TIMEOUT_CHECK_INTERVAL unit is 100ns.
//
if (RxDataWrap->TimeoutTick >= (MNP_TIMEOUT_CHECK_INTERVAL / 10)) {
RxDataWrap->TimeoutTick -= (MNP_TIMEOUT_CHECK_INTERVAL / 10);
} else {
//
// Drop the timeout packet.
//
// 超时之后丢弃数据
DEBUG ((DEBUG_WARN, "MnpCheckPacketTimeout: Received packet timeout.\n"));
MnpRecycleRxData (NULL, RxDataWrap);
Instance->RcvdPacketQueueSize--;
}
}
}
}
}
RxDataWrap->TimeoutTick
的初始化值就是ReceivedQueueTimeoutValue
(从上面可以看到它的单位是微妙),在收到数据之后会设置:
VOID
MnpQueueRcvdPacket (
IN OUT MNP_INSTANCE_DATA *Instance,
IN OUT MNP_RXDATA_WRAP *RxDataWrap
)
{
//
// Update the timeout tick using the configured parameter.
//
RxDataWrap->TimeoutTick = Instance->ConfigData.ReceivedQueueTimeoutValue;
}
目前ReceivedQueueTimeoutValue
都是使用了0这个值,表示并不会超时。
-
TransmitQueueTimeoutValue
:跟ReceivedQueueTimeoutValue
是一个意思,不过实际上MNP并没有使用到这个参数,其它地方也都配置的是0。 -
ProtocolTypeFilter
:这个值涉及到一个以太网帧的概念,它有两种类型:
注意这里的Type/Lengh字段的不同,当Type字段值小于等于1500时,帧使用的是IEEE 802.3格式,当Type字段值大于等于1536 时,帧使用的是Ethernet II格式。这个参数只针对Ethernet II格式,代码中有如下的判断:
EFI_STATUS
EFIAPI
MnpConfigure (
IN EFI_MANAGED_NETWORK_PROTOCOL *This,
IN EFI_MANAGED_NETWORK_CONFIG_DATA *MnpConfigData OPTIONAL
)
{
if ((This == NULL) ||
((MnpConfigData != NULL) &&
(MnpConfigData->ProtocolTypeFilter > 0) &&
(MnpConfigData->ProtocolTypeFilter <= 1500))
)
{
return EFI_INVALID_PARAMETER;
}
目前UEFI下使用到的类型有(实际只有三种类型):
//
// Ethernet protocol type definitions.
//
#define ARP_ETHER_PROTO_TYPE 0x0806
#define IPV4_ETHER_PROTO_TYPE 0x0800
#define IPV6_ETHER_PROTO_TYPE 0x86DD
#define IP4_ETHER_PROTO 0x0800
#define IP6_ETHER_PROTO 0x86DD
可以看到0其实也是支持的,不过UEFI中并没有使用到。在MnpMatchPacket()
会使用到ProtocolTypeFilter
做判断:
BOOLEAN
MnpMatchPacket (
IN MNP_INSTANCE_DATA *Instance,
IN EFI_MANAGED_NETWORK_RECEIVE_DATA *RxData,
IN MNP_GROUP_ADDRESS *GroupAddress OPTIONAL,
IN UINT8 PktAttr
)
{
//
// Check the protocol type.
//
if ((ConfigData->ProtocolTypeFilter != 0) && (ConfigData->ProtocolTypeFilter != RxData->ProtocolType)) {
return FALSE;
}
该函数的调用流程:
所以ProtocolTypeFilter
的作用就是收到数据之后的类型判断,只有收到的数据和配置的数据匹配时,对应的上层网络协议的回调处理函数才会被执行。
EnableUnicastReceive
、EnableMulticastReceive
、EnableBroadcastReceive
、EnablePromiscuousReceive
:这四个可以一起说明,它们表示的是MNP收发数据的方式,分别是单播,组播(多播)、广播和混杂模式。这些参数的设置会被赋值到MNP_DEVICE_DATA
结构体中的对应成员:
//
// Set the receive filter counters and the receive filter of the
// instance according to the new ConfigData.
//
// 单播
if (NewConfigData->EnableUnicastReceive) {
MnpDeviceData->UnicastCount++;
Instance->ReceiveFilter |= MNP_RECEIVE_UNICAST;
}
// 组播
if (NewConfigData->EnableMulticastReceive) {
MnpDeviceData->MulticastCount++;
}
// 广播
if (NewConfigData->EnableBroadcastReceive) {
MnpDeviceData->BroadcastCount++;
Instance->ReceiveFilter |= MNP_RECEIVE_BROADCAST;
}
// 混杂
if (NewConfigData->EnablePromiscuousReceive) {
MnpDeviceData->PromiscuousCount++;
}
组播和混杂模式还涉及到其它的操作。
- 组播还涉及到两个操作:
if (!NewConfigData->EnableMulticastReceive) {
MnpGroupOp (Instance, FALSE, NULL, NULL);
}
以及:
EFI_STATUS
EFIAPI
MnpGroups (
IN EFI_MANAGED_NETWORK_PROTOCOL *This,
IN BOOLEAN JoinFlag,
IN EFI_MAC_ADDRESS *MacAddress OPTIONAL
)
{
if ((!Instance->ConfigData.EnableMulticastReceive) ||
((MacAddress != NULL) && !NET_MAC_IS_MULTICAST (MacAddress, &SnpMode->BroadcastAddress, SnpMode->HwAddressSize)))
{
//
// The instance isn't configured to do multicast receive. OR
// the passed in MacAddress is not a multicast mac address.
//
Status = EFI_INVALID_PARAMETER;
goto ON_EXIT;
}
- 在
MnpMatchPacket()
还有组播和混杂模式的判断:
if (ConfigData->EnablePromiscuousReceive) {
//
// Always match if this instance is configured to be promiscuous.
//
return TRUE;
}
//
// Check multicast addresses.
//
if (ConfigData->EnableMulticastReceive && RxData->MulticastFlag) {
ASSERT (GroupAddress != NULL);
NET_LIST_FOR_EACH (Entry, &Instance->GroupCtrlBlkList) {
GroupCtrlBlk = NET_LIST_USER_STRUCT (Entry, MNP_GROUP_CONTROL_BLOCK, CtrlBlkEntry);
if (GroupCtrlBlk->GroupAddress == GroupAddress) {
//
// The instance is configured to receiveing packets destinated to this
// multicast address.
//
return TRUE;
}
}
}
关于单播、组播等的概念和实现在这里不多做介绍。这里简单说明下UEFI上层网络协议对这些模式的使用情况:
- ARP:
ArpService->MnpConfigData.EnableUnicastReceive = TRUE;
ArpService->MnpConfigData.EnableMulticastReceive = FALSE;
ArpService->MnpConfigData.EnableBroadcastReceive = TRUE;
ArpService->MnpConfigData.EnablePromiscuousReceive = FALSE;
- DNS4:
MnpConfigData.EnableUnicastReceive = TRUE;
MnpConfigData.EnableMulticastReceive = TRUE;
MnpConfigData.EnableBroadcastReceive = TRUE;
MnpConfigData.EnablePromiscuousReceive = FALSE;
- IPv4:
IpSb->MnpConfigData.EnableUnicastReceive = TRUE;
IpSb->MnpConfigData.EnableMulticastReceive = TRUE;
IpSb->MnpConfigData.EnableBroadcastReceive = TRUE;
IpSb->MnpConfigData.EnablePromiscuousReceive = FALSE;
FlushQueuesOnReset
:这个参数也很好理解,就是重新配置之后是否需要丢弃原本的还在队列中的数据:
if (OldConfigData->FlushQueuesOnReset) {
MnpFlushRcvdDataQueue (Instance);
}
EnableReceiveTimestamps
:现在的MNP并不支持。
EFI_STATUS
MnpConfigureInstance (
IN OUT MNP_INSTANCE_DATA *Instance,
IN EFI_MANAGED_NETWORK_CONFIG_DATA *ConfigData OPTIONAL
)
{
if ((ConfigData != NULL) && ConfigData->EnableReceiveTimestamps) {
//
// Don't support timestamp.
//
return EFI_UNSUPPORTED;
}
DisableBackgroundPolling
:在MnpStart()
实现中有:
if (MnpDeviceData->EnableSystemPoll ^ EnableSystemPoll) {
//
// The EnableSystemPoll differs with the current state, disable or enable
// the system poll.
//
TimerOpType = EnableSystemPoll ? TimerPeriodic : TimerCancel;
Status = gBS->SetTimer (MnpDeviceData->PollTimer, TimerOpType, MNP_SYS_POLL_INTERVAL);
MnpDeviceData->EnableSystemPoll = EnableSystemPoll;
}
这里的EnableSystemPoll
就是通过参数传入的!DisableBackgroundPolling
,它开启了一个定时事件,就真正开始接收数据了。注意这里还有一个MnpDeviceData->EnableSystemPoll
,而它来自MNP全局的结构体MNP_DEVICE_DATA
,里面的成员EnableSystemPoll
表示了MNP整体是否在接收数据。
所以DisableBackgroundPolling
的作用就是告诉MNP是否要在配置之后就开始接收网络数据。目前ARP、DNS4和IPv4都是默认的FALSE
:
MnpConfigData.DisableBackgroundPolling = FALSE;
也就是说配置之后MNP就开始接收数据了。
MNP的EFI_SERVICE_BINDING_PROTOCOL
EFI_SERVICE_BINDING_PROTOCOL
的实现如下:
struct _EFI_SERVICE_BINDING_PROTOCOL {
EFI_SERVICE_BINDING_CREATE_CHILD CreateChild;
EFI_SERVICE_BINDING_DESTROY_CHILD DestroyChild;
};
这样的Protocol在更上层的网络驱动和网络应用实现中还有很多,且使用情况也大致是一样的。通过CreateChild()
会创建描述子项的结构体,构成了一个用于数据传输的实例,其MNP版本实现MnpServiceBindingCreateChild()
的主要代码如下:
EFI_STATUS
EFIAPI
MnpServiceBindingCreateChild (
IN EFI_SERVICE_BINDING_PROTOCOL *This,
IN OUT EFI_HANDLE *ChildHandle
)
{
//
// Allocate buffer for the new instance.
//
Instance = AllocateZeroPool (sizeof (MNP_INSTANCE_DATA));
//
// Init the instance data.
//
// 其实现将在下面进一步介绍
MnpInitializeInstanceData (MnpServiceData, Instance);
// 安装用于网络数据传输的接口
Status = gBS->InstallMultipleProtocolInterfaces (
ChildHandle,
&gEfiManagedNetworkProtocolGuid,
&Instance->ManagedNetwork,
NULL
);
//
// Save the instance's childhandle.
//
Instance->Handle = *ChildHandle;
InsertTailList (&MnpServiceData->ChildrenList, &Instance->InstEntry);
MnpServiceData->ChildrenNumber++;
}
MnpInitializeInstanceData()
的实现:
VOID
MnpInitializeInstanceData (
IN MNP_SERVICE_DATA *MnpServiceData,
IN OUT MNP_INSTANCE_DATA *Instance
)
{
NET_CHECK_SIGNATURE (MnpServiceData, MNP_SERVICE_DATA_SIGNATURE);
ASSERT (Instance != NULL);
//
// Set the signature.
//
Instance->Signature = MNP_INSTANCE_DATA_SIGNATURE;
//
// Copy the MNP Protocol interfaces from the template.
//
CopyMem (&Instance->ManagedNetwork, &mMnpProtocolTemplate, sizeof (Instance->ManagedNetwork));
//
// Copy the default config data.
//
CopyMem (&Instance->ConfigData, &mMnpDefaultConfigData, sizeof (Instance->ConfigData));
//
// Initialize the lists.
//
InitializeListHead (&Instance->GroupCtrlBlkList);
InitializeListHead (&Instance->RcvdPacketQueue);
InitializeListHead (&Instance->RxDeliveredPacketQueue);
//
// Initialize the RxToken Map.
//
NetMapInit (&Instance->RxTokenMap);
//
// Save the MnpServiceData info.
//
Instance->MnpServiceData = MnpServiceData;
}
这个函数初始化了MNP_INSTANCE_DATA,并且使用了默认的配置。默认配置一般不能直接使用,所以有EFI_MANAGED_NETWORK_PROTOCOL
中有Configure
成员函数用来配置MNP,其实现MnpConfigure()
的主要内容:
//
// Configure the instance.
//
Status = MnpConfigureInstance (Instance, MnpConfigData);
在使用MNP的时候会有执行Configure()
函数,比如NetworkPkg\ArpDxe\ArpDriver.c中有:
//
// Set the Mnp config parameters.
//
ArpService->MnpConfigData.ReceivedQueueTimeoutValue = 0;
ArpService->MnpConfigData.TransmitQueueTimeoutValue = 0;
ArpService->MnpConfigData.ProtocolTypeFilter = ARP_ETHER_PROTO_TYPE;
ArpService->MnpConfigData.EnableUnicastReceive = TRUE;
ArpService->MnpConfigData.EnableMulticastReceive = FALSE;
ArpService->MnpConfigData.EnableBroadcastReceive = TRUE;
ArpService->MnpConfigData.EnablePromiscuousReceive = FALSE;
ArpService->MnpConfigData.FlushQueuesOnReset = TRUE;
ArpService->MnpConfigData.EnableReceiveTimestamps = FALSE;
ArpService->MnpConfigData.DisableBackgroundPolling = FALSE;
//
// Configure the Mnp child.
//
Status = ArpService->Mnp->Configure (ArpService->Mnp, &ArpService->MnpConfigData);
if (EFI_ERROR (Status)) {
goto ERROR_EXIT;
}
EFI_SERVICE_BINDING_PROTOCOL
会针对每个VLAN配置都安装一次,所以BIOS中可能存在多个这样的Protocol,不过如果存在非0的VLAN配置,则默认的全0的服务会被删除,也就是说,如果配置了一个VLAN,则当前UEFI下也只有一个EFI_SERVICE_BINDING_PROTOCOL
,而不是两个。
到这里还有一个问题需要回答,MNP_SERVICE_DATA
存在多个的原因是VLAN的存在,这个已经说明,但是为什么不能在每个MNP_SERVICE_DATA
中直接存放EFI_MANAGED_NETWORK_PROTOCOL
,而是需要创建一个新的子项呢?这里就涉及到另外的一个事实,因为上层的网络协议需要调用MNP,而且这样的上层可以有多个,比如ARP、IP4。这个时候如果直接在MNP_SERVICE_DATA
中存放EFI_MANAGED_NETWORK_PROTOCOL
,那么上层使用起来就会存在混乱,因此这里通过上层的协议自己创建MNP的子项,就更好管理和进行数据处理。
不过这样又涉及到了另外一个问题,不同的上层协议同时使用MNP的时候是否会因为竞争导致数据混乱呢,毕竟最终收发数据走的都是同一个网卡。实际上当然是不会的,不过原因不是很确定。可以想到的比如:UEFI是单进程的,但是这个不能解决定时器触发导致的代码切换,不过也不确定定时器的具体实现原理,所以无法进一步确认;另外一个可能就是MNP代码中本身规避了这个问题,即所有数据通过SNP的时候都是串行的。UEFI网络驱动代码,应该正是利用了UEFI的单进程以及UEFI的优先级等特征,才保证了不会因为竞争出现问题。
MnpStart和MnpStop
MnpStart()
可以说是MNP驱动中最重要的函数之一,其实现如下:
EFI_STATUS
MnpStart (
IN OUT MNP_SERVICE_DATA *MnpServiceData,
IN BOOLEAN IsConfigUpdate,
IN BOOLEAN EnableSystemPoll
)
{
// MNP可以产生多个子项,如果不是更新配置,则说明又增加了一个子项,所以ConfiguredChildrenNumber的值也会增加
if (!IsConfigUpdate) {
//
// If it's not a configuration update, increase the configured children number.
//
MnpDeviceData->ConfiguredChildrenNumber++;
// 如果是增加的第一个子项(也就是创建子项),则需要启动SNP,因为通过它才有网络数据通信
if (MnpDeviceData->ConfiguredChildrenNumber == 1) {
//
// It's the first configured child, start the simple network.
//
Status = MnpStartSnp (MnpDeviceData->Snp);
//
// Start the timeout timer.
//
Status = gBS->SetTimer (
MnpDeviceData->TimeoutCheckTimer,
TimerPeriodic,
MNP_TIMEOUT_CHECK_INTERVAL
);
//
// Start the media detection timer.
//
Status = gBS->SetTimer (
MnpDeviceData->MediaDetectTimer,
TimerPeriodic,
MNP_MEDIA_DETECT_INTERVAL
);
}
}
if (MnpDeviceData->EnableSystemPoll ^ EnableSystemPoll) {
//
// The EnableSystemPoll differs with the current state, disable or enable
// the system poll.
//
TimerOpType = EnableSystemPoll ? TimerPeriodic : TimerCancel;
Status = gBS->SetTimer (MnpDeviceData->PollTimer, TimerOpType, MNP_SYS_POLL_INTERVAL);
MnpDeviceData->EnableSystemPoll = EnableSystemPoll;
}
//
// Change the receive filters if need.
//
Status = MnpConfigReceiveFilters (MnpDeviceData);
}
这里的入参IsConfigUpdate
来自上一层的MnpConfigureInstance()
:
EFI_STATUS
MnpConfigureInstance (
IN OUT MNP_INSTANCE_DATA *Instance,
IN EFI_MANAGED_NETWORK_CONFIG_DATA *ConfigData OPTIONAL
)
{
IsConfigUpdate = (BOOLEAN)((Instance->Configured) && (ConfigData != NULL));
Instance->Configured = (BOOLEAN)(ConfigData != NULL);
if (Instance->Configured) {
//
// The instance is configured, start the Mnp.
//
Status = MnpStart (
MnpServiceData,
IsConfigUpdate,
(BOOLEAN) !NewConfigData->DisableBackgroundPolling
);
如果是修改原有的配置,IsConfigUpdate
的值被设置成TRUE
,如果是新增配置,则ConfiguredChildrenNumber
的值会自增。如果是第一个次配置MNP,会执行MnpDeviceData->ConfiguredChildrenNumber == 1
这个if判断中的内容,这会导致几个定时器被打开,且SNP也开始工作;最后还有一个定时器PollTimer
会通过其它的配置参数来决定。
关于定时器,后面会进一步介绍,这里需要说明的是定时器在执行MnpStart()
之后才会被打开,所以在EFI_MANAGED_NETWORK_PROTOCOL
中不存在类似“开始”这样的操作,而是配置之后就直接开始MNP的数据收发和检测等操作了。
这里只说明如何让SNP开始工作,对应函数MnpStartSnp()
:
EFI_STATUS
MnpStartSnp (
IN EFI_SIMPLE_NETWORK_PROTOCOL *Snp
)
{
//
// Start the simple network.
//
Status = Snp->Start (Snp);
if (!EFI_ERROR (Status)) {
//
// Initialize the simple network.
//
Status = Snp->Initialize (Snp, 0, 0);
}
}
实际上就是调用EFI_SIMPLE_NETWORK_PROTOCOL
的Start
和Initialize
成员函数。
MnpStop()
完成相反的操作,重点就是关闭定时器和SNP:
EFI_STATUS
MnpStop (
IN OUT MNP_SERVICE_DATA *MnpServiceData
)
{
//
// Configure the receive filters.
//
MnpConfigReceiveFilters (MnpDeviceData);
//
// Decrease the children number.
//
MnpDeviceData->ConfiguredChildrenNumber--;
if (MnpDeviceData->ConfiguredChildrenNumber > 0) {
//
// If there are other configured children, return and keep the timers and
// simple network unchanged.
//
return EFI_SUCCESS;
}
//
// No configured children now.
//
if (MnpDeviceData->EnableSystemPoll) {
//
// The system poll in on, cancel the poll timer.
//
Status = gBS->SetTimer (MnpDeviceData->PollTimer, TimerCancel, 0);
MnpDeviceData->EnableSystemPoll = FALSE;
}
//
// Cancel the timeout timer.
//
Status = gBS->SetTimer (MnpDeviceData->TimeoutCheckTimer, TimerCancel, 0);
//
// Cancel the media detect timer.
//
Status = gBS->SetTimer (MnpDeviceData->MediaDetectTimer, TimerCancel, 0);
//
// Stop the simple network.
//
Status = MnpStopSnp (MnpDeviceData);
}
EFI_MANAGED_NETWORK_PROTOCOL
该Protocol的结构体如下:
///
/// The MNP is used by network applications (and drivers) to
/// perform raw (unformatted) asynchronous network packet I/O.
///
struct _EFI_MANAGED_NETWORK_PROTOCOL {
EFI_MANAGED_NETWORK_GET_MODE_DATA GetModeData;
EFI_MANAGED_NETWORK_CONFIGURE Configure;
EFI_MANAGED_NETWORK_MCAST_IP_TO_MAC McastIpToMac;
EFI_MANAGED_NETWORK_GROUPS Groups;
EFI_MANAGED_NETWORK_TRANSMIT Transmit;
EFI_MANAGED_NETWORK_RECEIVE Receive;
EFI_MANAGED_NETWORK_CANCEL Cancel;
EFI_MANAGED_NETWORK_POLL Poll;
};
对应的实现在NetworkPkg\MnpDxe\MnpConfig.c:
EFI_MANAGED_NETWORK_PROTOCOL mMnpProtocolTemplate = {
MnpGetModeData,
MnpConfigure,
MnpMcastIpToMac,
MnpGroups,
MnpTransmit,
MnpReceive,
MnpCancel,
MnpPoll
};
后面会介绍这些函数的实现。
Mnp.GetModeData
对应的实现是MnpGetModeData
,它的输出是第一个结构体EFI_MANAGED_NETWORK_CONFIG_DATA,在前面已经介绍过,它的大部分内容都可以通过MNP_INSTANCE_DATA中的内容得到;而它输出的第二个结构体EFI_SIMPLE_NETWORK_MODE通过对应的SNP获取,其重点代码如下:
EFI_STATUS
EFIAPI
MnpGetModeData (
IN EFI_MANAGED_NETWORK_PROTOCOL *This,
OUT EFI_MANAGED_NETWORK_CONFIG_DATA *MnpConfigData OPTIONAL,
OUT EFI_SIMPLE_NETWORK_MODE *SnpModeData OPTIONAL
)
{
if (MnpConfigData != NULL) {
//
// Copy the instance configuration data.
//
CopyMem (MnpConfigData, &Instance->ConfigData, sizeof (*MnpConfigData));
}
if (SnpModeData != NULL) {
//
// Copy the underlayer Snp mode data.
//
Snp = Instance->MnpServiceData->MnpDeviceData->Snp;
//
// Upon successful return of GetStatus(), the Snp->Mode->MediaPresent
// will be updated to reflect any change of media status
//
Snp->GetStatus (Snp, &InterruptStatus, NULL);
CopyMem (SnpModeData, Snp->Mode, sizeof (*SnpModeData));
}
if (!Instance->Configured) {
Status = EFI_NOT_STARTED;
} else {
Status = EFI_SUCCESS;
}
}
Mnp.Configure
对应的实现是MnpConfigure()
,其代码实现:
EFI_STATUS
EFIAPI
MnpConfigure (
IN EFI_MANAGED_NETWORK_PROTOCOL *This,
IN EFI_MANAGED_NETWORK_CONFIG_DATA *MnpConfigData OPTIONAL
)
{
// 配置参数是NULL表示的是重置MNP配置,但是如果本来就没有被配置过,说明已经是重置的状态了,所以直接返回成功
if ((MnpConfigData == NULL) && (!Instance->Configured)) {
//
// If the instance is not configured and a reset is requested, just return.
//
Status = EFI_SUCCESS;
goto ON_EXIT;
}
//
// Configure the instance.
//
Status = MnpConfigureInstance (Instance, MnpConfigData);
}
重点包含两个部分,第一部分包含一个if判断,如果配置参数为空且当前没有被配置过,则返回成功;第二部分才是真的配置,其实现:
EFI_STATUS
MnpConfigureInstance (
IN OUT MNP_INSTANCE_DATA *Instance,
IN EFI_MANAGED_NETWORK_CONFIG_DATA *ConfigData OPTIONAL
)
{
// 当前不支持时间戳
if ((ConfigData != NULL) && ConfigData->EnableReceiveTimestamps) {
//
// Don't support timestamp.
//
return EFI_UNSUPPORTED;
}
// 如果之前已经配置过了,则现在就是更新配置
IsConfigUpdate = (BOOLEAN)((Instance->Configured) && (ConfigData != NULL));
OldConfigData = &Instance->ConfigData;
NewConfigData = ConfigData;
// 这里就是进行重置
if (NewConfigData == NULL) {
//
// Restore back the default config data if a reset of this instance
// is required.
//
NewConfigData = &mMnpDefaultConfigData;
}
//
// Reset the instance's receive filter.
//
Instance->ReceiveFilter = 0;
//
// Clear the receive counters according to the old ConfigData.
//
if (OldConfigData->EnableUnicastReceive) {
MnpDeviceData->UnicastCount--;
}
if (OldConfigData->EnableMulticastReceive) {
MnpDeviceData->MulticastCount--;
}
if (OldConfigData->EnableBroadcastReceive) {
MnpDeviceData->BroadcastCount--;
}
if (OldConfigData->EnablePromiscuousReceive) {
MnpDeviceData->PromiscuousCount--;
}
//
// Set the receive filter counters and the receive filter of the
// instance according to the new ConfigData.
//
if (NewConfigData->EnableUnicastReceive) {
MnpDeviceData->UnicastCount++;
Instance->ReceiveFilter |= MNP_RECEIVE_UNICAST;
}
if (NewConfigData->EnableMulticastReceive) {
MnpDeviceData->MulticastCount++;
}
if (NewConfigData->EnableBroadcastReceive) {
MnpDeviceData->BroadcastCount++;
Instance->ReceiveFilter |= MNP_RECEIVE_BROADCAST;
}
if (NewConfigData->EnablePromiscuousReceive) {
MnpDeviceData->PromiscuousCount++;
}
if (OldConfigData->FlushQueuesOnReset) {
MnpFlushRcvdDataQueue (Instance);
}
// 重置操作会调用EFI_MANAGED_NETWORK_PROTOCOL的Cancel接口
if (ConfigData == NULL) {
Instance->ManagedNetwork.Cancel (&Instance->ManagedNetwork, NULL);
}
if (!NewConfigData->EnableMulticastReceive) {
MnpGroupOp (Instance, FALSE, NULL, NULL);
}
//
// Save the new configuration data.
//
CopyMem (OldConfigData, NewConfigData, sizeof (*OldConfigData));
Instance->Configured = (BOOLEAN)(ConfigData != NULL);
if (Instance->Configured) {
//
// The instance is configured, start the Mnp.
//
Status = MnpStart (
MnpServiceData,
IsConfigUpdate,
(BOOLEAN) !NewConfigData->DisableBackgroundPolling
);
} else {
//
// The instance is changed to the unconfigured state, stop the Mnp.
//
Status = MnpStop (MnpServiceData);
}
}
具体的配置数据在EFI_MANAGED_NETWORK_CONFIG_DATA中已经介绍过,这里需要特别说明的是当配置参数在NULL和非NULL情况下导致最后MNP的状态发生不同的变化。如果是NULL的,表示重置配置,此时会调用MnpStop()
接口,否则会调用MnpStart()
接口,这个操作非常重要,通过该函数才开启了UEFI网络数据的收发,具体操作参考MnpStart和MnpStop。虽然看起来有点怪,EFI_MANAGED_NETWORK_PROTOCOL
没有EFI_SIMPLE_NETWORK_PROTOCOL
中的Start
和Stop
接口,但是通过Configure
却完成了相同的任务。
Mnp.Transmit
对应的实现是MnpTransmit()
,其代码也比较简单:
EFI_STATUS
EFIAPI
MnpTransmit (
IN EFI_MANAGED_NETWORK_PROTOCOL *This,
IN EFI_MANAGED_NETWORK_COMPLETION_TOKEN *Token
)
{
// MNP必须是配置过的才能进行通信
if (!Instance->Configured) {
Status = EFI_NOT_STARTED;
goto ON_EXIT;
}
// Token有效才能进行后续的数据处理,所以检查是必须的
if (!MnpIsValidTxToken (Instance, Token)) {
//
// The Token is invalid.
//
Status = EFI_INVALID_PARAMETER;
goto ON_EXIT;
}
//
// Build the tx packet
//
Status = MnpBuildTxPacket (MnpServiceData, Token->Packet.TxData, &PktBuf, &PktLen);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
//
// OK, send the packet synchronously. 从这里可以看到数据是同步发送的,并没有经过什么缓存
//
Status = MnpSyncSendPacket (MnpServiceData, PktBuf, PktLen, Token);
}
在这里会构建传递的数据,这通过MnpBuildTxPacket()
函数完成:
EFI_STATUS
MnpBuildTxPacket (
IN MNP_SERVICE_DATA *MnpServiceData,
IN EFI_MANAGED_NETWORK_TRANSMIT_DATA *TxData,
OUT UINT8 **PktBuf,
OUT UINT32 *PktLen
)
{
// 注意这里分配的内存来自MNP_SERVICE_DATA中的FreeTxBufList,所以需要使用特殊的函数来分配
TxBuf = MnpAllocTxBuf (MnpDeviceData);
//
// Reserve space for vlan tag if needed.
//
if (MnpServiceData->VlanId != 0) {
*PktBuf = TxBuf + NET_VLAN_TAG_LEN;
} else {
*PktBuf = TxBuf;
}
if ((TxData->DestinationAddress == NULL) && (TxData->FragmentCount == 1)) {
CopyMem (
*PktBuf,
TxData->FragmentTable[0].FragmentBuffer,
TxData->FragmentTable[0].FragmentLength
);
*PktLen = TxData->FragmentTable[0].FragmentLength;
} else {
//
// Either media header isn't in FragmentTable or there is more than
// one fragment, copy the data into the packet buffer. Reserve the
// media header space if necessary.
//
SnpMode = MnpDeviceData->Snp->Mode;
DstPos = *PktBuf;
*PktLen = 0;
if (TxData->DestinationAddress != NULL) {
//
// If dest address is not NULL, move DstPos to reserve space for the
// media header. Add the media header length to buflen.
//
DstPos += SnpMode->MediaHeaderSize;
*PktLen += SnpMode->MediaHeaderSize;
}
for (Index = 0; Index < TxData->FragmentCount; Index++) {
//
// Copy the data.
//
CopyMem (
DstPos,
TxData->FragmentTable[Index].FragmentBuffer,
TxData->FragmentTable[Index].FragmentLength
);
DstPos += TxData->FragmentTable[Index].FragmentLength;
}
//
// Set the buffer length.
//
*PktLen += TxData->DataLength + TxData->HeaderLength;
}
}
之后数据通过MnpSyncSendPacket()
函数传递下去,这主要通过SNP的传输接口完成:
Status = Snp->Transmit (
Snp,
HeaderSize,
Length,
Packet,
TxData->SourceAddress,
TxData->DestinationAddress,
&ProtocolType
);
Mnp.Receive
对应的实现是MnpReceive()
,其代码如下:
EFI_STATUS
EFIAPI
MnpReceive (
IN EFI_MANAGED_NETWORK_PROTOCOL *This,
IN EFI_MANAGED_NETWORK_COMPLETION_TOKEN *Token
)
{
// MNP在配置之后才能使用
if (!Instance->Configured) {
Status = EFI_NOT_STARTED;
goto ON_EXIT;
}
// Token的处理
//
// Check whether this token(event) is already in the rx token queue.
//
Status = NetMapIterate (&Instance->RxTokenMap, MnpTokenExist, (VOID *)Token);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
//
// Insert the Token into the RxTokenMap.
//
Status = NetMapInsertTail (&Instance->RxTokenMap, (VOID *)Token, NULL);
if (!EFI_ERROR (Status)) {
//
// Try to deliver any buffered packets.
//
Status = MnpInstanceDeliverPacket (Instance);
//
// Dispatch the DPC queued by the NotifyFunction of Token->Event.
//
DispatchDpc ();
}
}
通过MnpInstanceDeliverPacket()
函数会查看是否有接收到数据,并进行处理:
EFI_STATUS
MnpInstanceDeliverPacket (
IN OUT MNP_INSTANCE_DATA *Instance
)
{
if (NetMapIsEmpty (&Instance->RxTokenMap) || IsListEmpty (&Instance->RcvdPacketQueue)) {
//
// No pending received data or no available receive token, return.
//
return EFI_SUCCESS;
}
// 如果数据会被多个调用这使用,则需要进行拷贝
RxDataWrap = NET_LIST_HEAD (&Instance->RcvdPacketQueue, MNP_RXDATA_WRAP, WrapEntry);
if (RxDataWrap->Nbuf->RefCnt > 2) {
//
// There are other instances share this Nbuf, duplicate to get a
// copy to allow the instance to do R/W operations.
//
DupNbuf = MnpAllocNbuf (MnpDeviceData);
if (DupNbuf == NULL) {
DEBUG ((DEBUG_WARN, "MnpDeliverPacket: Failed to allocate a free Nbuf.\n"));
return EFI_OUT_OF_RESOURCES;
}
//
// Duplicate the net buffer.
//
NetbufDuplicate (RxDataWrap->Nbuf, DupNbuf, 0);
MnpFreeNbuf (MnpDeviceData, RxDataWrap->Nbuf);
RxDataWrap->Nbuf = DupNbuf;
}
// 构建数据并执行上层调用者的回调
//
// All resources are OK, remove the packet from the queue.
//
NetListRemoveHead (&Instance->RcvdPacketQueue);
Instance->RcvdPacketQueueSize--;
RxData = &RxDataWrap->RxData;
SnpMode = MnpDeviceData->Snp->Mode;
//
// Set all the buffer pointers.
//
RxData->MediaHeader = NetbufGetByte (RxDataWrap->Nbuf, 0, NULL);
RxData->DestinationAddress = RxData->MediaHeader;
RxData->SourceAddress = (UINT8 *)RxData->MediaHeader + SnpMode->HwAddressSize;
RxData->PacketData = (UINT8 *)RxData->MediaHeader + SnpMode->MediaHeaderSize;
//
// Insert this RxDataWrap into the delivered queue.
//
InsertTailList (&Instance->RxDeliveredPacketQueue, &RxDataWrap->WrapEntry);
//
// Get the receive token from the RxTokenMap.
//
RxToken = NetMapRemoveHead (&Instance->RxTokenMap, NULL);
//
// Signal this token's event.
//
RxToken->Packet.RxData = &RxDataWrap->RxData;
RxToken->Status = EFI_SUCCESS;
gBS->SignalEvent (RxToken->Event);
return EFI_SUCCESS;
}
需要注意,由于CPU执行代码的速度通常会快于外设网卡,所以直接调用Mnp.Receive
获取到想要的数据,很可能在最开始的if判断中就返回了,因此才有了PollTimer这个定时事件,以及Mnp.Poll。虽然名称是Receive,但是它的作用更像是Register。
Mnp.Poll
对应的实现是MnpPoll()
,其代码实现:
EFI_STATUS
EFIAPI
MnpPoll (
IN EFI_MANAGED_NETWORK_PROTOCOL *This
)
{
//
// Try to receive packets.
//
Status = MnpReceivePacket (Instance->MnpServiceData->MnpDeviceData);
//
// Dispatch the DPC queued by the NotifyFunction of rx token's events.
//
DispatchDpc ();
}
MnpReceivePacket()
调用了SNP的Receive
成员函数来接收数据,并通过DispatchDpc()
执行了所有的Token事件中的回调函数。
MNP事件
关于MNP接收数据的方式,最重要的是前面已经提过的几个定时事件,这跟UEFI的基本机制有关。传统的Legacy使用了中断的方式进行IO的处理,而在UEFI中,使用的主要是轮询,所以接收数据的操作也是通过轮询来完成的,这样就有了事件处理的概念。
MNP通过轮询的方式获取数据,然后进行处理。对应的事件分别是:
PollTimer
:轮询获取数据的事件。TimeoutCheckTimer
:检测数据是否过期的事件。MediaDetectTimer
:判断网线是否连接的事件。
所有事件都在初始化MNP_DEVICE_DATA
时定义,下面将一个个介绍。
MediaDetectTimer
首先是从最简单的MediaDetectTimer
开始,它的创建代码如下:
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL | EVT_TIMER,
TPL_CALLBACK,
MnpCheckMediaStatus,
MnpDeviceData,
&MnpDeviceData->MediaDetectTimer
);
由于入参EVT_TIMER
的存在,就说明了它是一个定时事件。它在初始化时并没有开始执行,而是要通过gBS->SetTimer
才能开启,该操作在MnpStart()
函数中,这部分已经在MnpStart和MnpStop中说明,事实上不止MediaDetectTimer
,它会把所有的事件都打开。
MediaDetectTimer
的回调函数的具体实现也很简单:
VOID
EFIAPI
MnpCheckMediaStatus (
IN EFI_EVENT Event,
IN VOID *Context
)
{
Snp = MnpDeviceData->Snp;
if (Snp->Mode->MediaPresentSupported) {
//
// Upon successful return of GetStatus(), the MediaPresent field of
// EFI_SIMPLE_NETWORK_MODE will be updated to reflect any change of media status
//
Snp->GetStatus (Snp, &InterruptStatus, NULL);
}
}
就是一个调用SNP的状态查询接口而已。
TimeoutCheckTimer
然后是TimeoutCheckTimer
,在前面介绍配置参数ReceivedQueueTimeoutValue
的时候已经有过说明,这里在整体说明。它的创建代码如下:
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL | EVT_TIMER,
TPL_CALLBACK,
MnpCheckPacketTimeout,
MnpDeviceData,
&MnpDeviceData->TimeoutCheckTimer
);
启动的位置同样在MnpStart()
函数中。TimeoutCheckTimer
的回调函数MnpCheckPacketTimeout()
:
VOID
EFIAPI
MnpCheckPacketTimeout (
IN EFI_EVENT Event,
IN VOID *Context
)
{
// 遍历所有的服务,因为服务可以由VLAN的多个配置而产生多个
NET_LIST_FOR_EACH (ServiceEntry, &MnpDeviceData->ServiceList) {
MnpServiceData = MNP_SERVICE_DATA_FROM_LINK (ServiceEntry);
// 遍历服务创建的子项,一个服务可以通过CreateChild()创建多个子项
NET_LIST_FOR_EACH (Entry, &MnpServiceData->ChildrenList) {
// 每个子项由一个MNP_INSTANCE_DATA结构体,其中包含了配置项EFI_MANAGED_NETWORK_CONFIG_DATA
Instance = NET_LIST_USER_STRUCT (Entry, MNP_INSTANCE_DATA, InstEntry);
NET_CHECK_SIGNATURE (Instance, MNP_INSTANCE_DATA_SIGNATURE);
// 可以看到0是一个特殊的值,表示不会超时
if (!Instance->Configured || (Instance->ConfigData.ReceivedQueueTimeoutValue == 0)) {
//
// This instance is not configured or there is no receive time out,
// just skip to the next instance.
//
continue;
}
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
// 遍历所有RcvdPacketQueue中的数据,检测是否有数据超时
NET_LIST_FOR_EACH_SAFE (RxEntry, NextEntry, &Instance->RcvdPacketQueue) {
RxDataWrap = NET_LIST_USER_STRUCT (RxEntry, MNP_RXDATA_WRAP, WrapEntry);
//
// TimeoutTick unit is microsecond, MNP_TIMEOUT_CHECK_INTERVAL unit is 100ns.
//
if (RxDataWrap->TimeoutTick >= (MNP_TIMEOUT_CHECK_INTERVAL / 10)) {
RxDataWrap->TimeoutTick -= (MNP_TIMEOUT_CHECK_INTERVAL / 10);
} else {
//
// Drop the timeout packet.
//
// 超时之后丢弃数据
DEBUG ((DEBUG_WARN, "MnpCheckPacketTimeout: Received packet timeout.\n"));
MnpRecycleRxData (NULL, RxDataWrap);
Instance->RcvdPacketQueueSize--;
}
}
gBS->RestoreTPL (OldTpl);
}
}
}
PollTimer
最后是PollTimer()
,其创建代码:
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL | EVT_TIMER,
TPL_CALLBACK,
MnpSystemPoll,
MnpDeviceData,
&MnpDeviceData->PollTimer
);
启动代码同样在MnpStart()
。TimeoutCheckTimer
的处理函数MnpSystemPoll()
的实现:
VOID
EFIAPI
MnpSystemPoll (
IN EFI_EVENT Event,
IN VOID *Context
)
{
//
// Try to receive packets from Snp.
//
MnpReceivePacket (MnpDeviceData);
//
// Dispatch the DPC queued by the NotifyFunction of rx token's events.
//
DispatchDpc ();
}
它是一个收数据然后分发的过程。关于DispatchDpc()
在DPC中已经介绍过,从这里也可以看出来,只要MNP启动起来,就一直会有分发操作,才有了DPC代码示例中的结果。上述代码中最重要的就是MnpReceivePacket()
函数,这里介绍其流程。
- 首先是一些判断条件和初始化。
- 使用SNP接收数据:
//
// Receive packet through Snp.
//
Status = Snp->Receive (Snp, &HeaderSize, &BufLen, BufPtr, NULL, NULL, NULL);
此时的数据会放到MNP_DEVICE_DATA
结构体成员RxNbufCache
中。
- 如果配置了VLAN,去掉其Tag:
if (MnpDeviceData->NumberOfVlan != 0) {
//
// VLAN is configured, remove the VLAN tag if any
//
IsVlanPacket = MnpRemoveVlanTag (MnpDeviceData, Nbuf, &VlanId);
} else {
IsVlanPacket = FALSE;
}
- 找到VLAN配置(需要注意至少有一个配置,因为没有配置也是一种配置)对应的MNP服务:
MnpServiceData = MnpFindServiceData (MnpDeviceData, VlanId);
- 将数据放入队列:
MnpEnqueuePacket (MnpServiceData, Nbuf);
这里的数据经过几层包装,传递给MNP_INSTANCE_DATA
中的成员RcvdPacketQueue
:
flowchart TD
classDef default fill: #bbb, stroke: #333, stroke-width: 2px;
A(MnpDeviceData->RxNbufCache)-->EFI_MANAGED_NETWORK_RECEIVE_DATA-->MNP_RXDATA_WRAP-->B(MNP_INSTANCE_DATA-->RcvdPacketQueue)
- 处理数据:
MnpDeliverPacket (MnpServiceData);
该操作会遍历服务中的所有实例项(因为此时数据已经在实例的RcvdPacketQueue
成员中),将数据包装成EFI_MANAGED_NETWORK_RECEIVE_DATA
,找到处理数据的Token,执行其回调函数来处理这些数据:
//
// Signal this token's event.
//
RxToken->Packet.RxData = &RxDataWrap->RxData;
RxToken->Status = EFI_SUCCESS;
gBS->SignalEvent (RxToken->Event);
这里的RxData
就是需要处理的数据,Event
就是上层网络协议注册的数据处理事件。需要注意Token在使用之后就会被去掉:
//
// Get the receive token from the RxTokenMap.
//
RxToken = NetMapRemoveHead (&Instance->RxTokenMap, NULL);
不过在上层网路协议的回调函数中还会把它加回来,比如ARP的回调函数中有:
RESTART_RECEIVE:
//
// Continue to receive packets from Mnp.
//
Status = ArpService->Mnp->Receive (ArpService->Mnp, RxToken);
MNP的Receive()
接口中也有增加的操作:
EFI_STATUS
EFIAPI
MnpReceive (
IN EFI_MANAGED_NETWORK_PROTOCOL *This,
IN EFI_MANAGED_NETWORK_COMPLETION_TOKEN *Token
)
{
//
// Check whether this token(event) is already in the rx token queue.
//
Status = NetMapIterate (&Instance->RxTokenMap, MnpTokenExist, (VOID *)Token);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
//
// Insert the Token into the RxTokenMap.
//
Status = NetMapInsertTail (&Instance->RxTokenMap, (VOID *)Token, NULL);
}
这样上层网络协议就又可以继续处理数据了。从这里可以看到,Receive()
接口并没有实际处理数据,只是将数据放到了RxTokenMap
中,供后续代码处理。
- 结束。
从这里可以看出,真正处理数据的代码,还是通过上层网络协议传递过来的Token中的回调函数。
**综上所示,MnpSystemPoll()
是网络数据收发的底层接口,它从SNP接收数据,并回调上层网络协议的处理函数,来完成最终的网路数据处理。**就像下图那样:
MNP代码示例
由于MNP和SNP没有本质的区别,都是直接收发数据的,所以完全可以改写SNP代码示例,来达到相同的效果。代码可以在beni/BeniPkg/DynamicCommand/TestDynamicCommand/TestMnp.c · jiangwei/edk2-beni - 码云 - 开源中国 (gitee.com)中找到。