本文主要是介绍【UEFI基础】EDK网络框架(MNP),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
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);
- 如果没有硬件VLAN,则需要MNP使用软件VLAN,此时需要安装
gEfiVlanConfigProtocolGuid
:
//// 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;}
如果没有VLAN相关的变量,那就跟硬件VLAN一样的操作。
- 如果有VLAN数据,则需要遍历所有的VLAN配置,每一个都对应一个服务:
//// 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只需要一个就够了。
NumberOfVlan
:VLAN配置的个数,通过VLAN变量获取:
MnpGetVlanVariable (MnpDeviceData, &NumberOfVlan, &VlanVariable);
MnpDeviceData->NumberOfVlan = NumberOfVlan;
MacString
:网卡MAC地址对应的字符串,这个值的作用是为了设置VLAN,它作为VLAN配置的变量名使用,比如在设置VLAN变量的函数中可以看到如下的代码:
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个字节)。 -
VlanId
、Priority
:VLAN配置,每个VLAN配置对应一个MNP_SERVICE_DATA
。
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_DATAInstance = 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_DATAInstance = 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 TDclassDef 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)中找到。
这篇关于【UEFI基础】EDK网络框架(MNP)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!