【UEFI基础】EDK网络框架(MNP)

MNP

MNP代码综述

MNP全称是Managed Network Protocol,它对SNP进行了一层包装,事实上上层网络应用或者驱动一般都是调用MNP的接口来完成网络通信,而不会直接使用SNP。MNP是UEFI中网络数据收发的基础。MNP的作用主要有以下的几点:

  1. 作为网络数据收发的基本单元。在介绍SNP的时候已经说过,它只是一个单纯的数据收发接口,初始化SNP之后并没有直接使用,而MNP初始化之后,会有一系列的定时事件被创建,可以通过操作MNP的接口来进行真正的网络收发。
  2. 管理VLAN配置。
  3. 单播、组播、广播、混杂模式等的处理。

MNP也是一个UEFI Driver Model,所以会安装EFI_DRIVER_BINDING_PROTOCOL,其实现如下:

EFI_DRIVER_BINDING_PROTOCOL  gMnpDriverBinding = {
  MnpDriverBindingSupported,
  MnpDriverBindingStart,
  MnpDriverBindingStop,
  0xa,
  NULL,
  NULL
};

MNP在UEFI网络协议栈中的关系图:

支持
提供
支持
支持
提供
支持
提供
提供
提供
gEfiPciIoProtocolGuid
UNDI
gEfiNetworkInterfaceIdentifierProtocolGuid_31
gEfiDevicePathProtocolGuid
SNP
gEfiSimpleNetworkProtocolGuid
MNP
gEfiVlanConfigProtocolGuid
gEfiManagedNetworkServiceBindingProtocolGuid
gEfiManagedNetworkProtocolGuid

注意,MNP最终起到网络访问的接口是EFI_MANAGED_NETWORK_PROTOCOL,对应的GUID是gEfiManagedNetworkProtocolGuid,但是它并不会由MNP驱动直接安装,这一点非常重要,这也是MNP之上大部分上层网络协议的一般做法,会先安装服务、然后通过服务创建子项,而子项才会进行网络操作。步骤大致如下:

  1. 获取服务,并由该服务创建子项。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);
  1. 通过子项获取网络传输接口,本例对应的就是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
                  );
  1. 后续通过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驱动的初始化主要做了以下的是事情:

  1. 初始化MNP_DEVICE_DATA,跟SNP类似,每个网卡有一个MNP_DEVICE_DATA
  2. 判断gEfiVlanConfigProtocolGuid是否存在。因为UNDI也可以安装它,这属于硬件VLAN,如果有安装,则只需要一份服务,对应VLAN的ID和优先级都是0,MNP不需要特别的操作,只需要将创建服务数据的函数执行一次:
MnpServiceData = MnpCreateServiceData (MnpDeviceData, 0, 0);
  1. 如果没有硬件VLAN,则需要MNP使用软件VLAN,此时需要安装gEfiVlanConfigProtocolGuid
  //
  // Install VLAN Config Protocol
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &ControllerHandle,
                  &gEfiVlanConfigProtocolGuid,
                  &MnpDeviceData->VlanConfig,
                  NULL
                  );
  1. 然后获取当前需要支持的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一样的操作。

  1. 如果有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接口。

其流程大致如下:

MnpDriverBindingStart
初始化MNP_DEVICE_DATA数据
是否已经存在gEfiVlanConfigProtocolGuid
创建VLAN为0/0的MNP_SERVICE_DATA
安装gEfiManagedNetworkServiceBindingProtocolGuid
结束
安装gEfiVlanConfigProtocolGuid
获取VLAN配置的变量
遍历所有的VLAN配置
为每个VLAN创建相应配置的MNP_SERVICE_DATA
为每个MNP_SERVICE_DATA安装gEfiManagedNetworkServiceBindingProtocolGuid

从这里也可以看出来:

  • 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只需要一个就够了。

  • NumberOfVlanVLAN配置的个数,通过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,后面会进一步介绍。

  • FreeTxBufListAllTxBufListTxBufCount:管理内存,用于发数据,最终会在EFI_MANAGED_NETWORK_CONFIG_DATATransmit成员函数中用到。

  • FreeNbufQueNbufCntRxNbufCache:管理内存,用于收数据,最终会在EFI_MANAGED_NETWORK_CONFIG_DATAPoll成员函数中用到。

  • PollTimerEnableSystemPollPollTimer是一个UEFI定时事件,EnableSystemPoll用来控制定时器的开启和关闭,关于它们的用法同样会在MnpStart和MnpStop中介绍。

  • TimeoutCheckTimerMediaDetectTimer:跟PollTimer一样是UEFI定时事件,可以在MnpStart和MnpStop看到具体的使用方式。

  • GroupAddressListGroupAddressCountUnicastCountBroadcastCountMulticastCountPromiscuousCount:处理单播、组播、广播等的参数。

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

  • ServiceHandleServiceBinding:对应Handle及安装其上的EFI_SERVICE_BINDING_PROTOCOL

  • ChildrenListChildrenNumber:这两个值涉及到EFI_SERVICE_BINDING_PROTOCOLCreateChild()函数创建的实例,这里就是MNP_INSTANCE_DATA结构体,通过这里的两个成员可以访问到MNP创建的子项,最终获取到EFI_MANAGED_NETWORK_PROTOCOL,这才是收发数据的主体。

  • Mtu:最大传输单元,会因为VLAN的引入而减少几个字节(一般是4个字节)。

  • VlanIdPriorityVLAN配置,每个VLAN配置对应一个MNP_SERVICE_DATA

MNP_INSTANCE_DATA

MNP_INSTANCE_DATA是通过EFI_SERVICE_BINDING_PROTOCOLCreateChild()接口生成的,所以它的主要作用是描述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
  • HandleManagedNetwork:实际安装EFI_MANAGED_NETWORK_PROTOCOL所对应的Handle和Protocol。
  • ConfiguredConfigData: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_PROTOCOLReceive成员函数 (其实现是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
MnpReceivePacket
MnpDeliverPacket
MnpInstanceDeliverPacket

MnpSystemPoll()是一个定时事件的回调函数,所以就有了一个定时接收数据的过程。

需要注意下面的代码:

  //
  // Get the receive token from the RxTokenMap.
  //
  RxToken = NetMapRemoveHead (&Instance->RxTokenMap, NULL);

即一个Token只会被MNP回调一次,之后就会被回收。

整个过程简单来说就是一个注册事件并执行数据处理的过程,被执行的前提是MNP收到数据。

  • RxDeliveredPacketQueueRcvdPacketQueue:处理数据包的成员。
  • 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;
  }

该函数的调用流程:

回调
PollTimer事件
MnpSystemPoll
MnpReceivePacket
MnpPoll
MnpEnqueuePacket
MnpMatchPacket

所以ProtocolTypeFilter的作用就是收到数据之后的类型判断,只有收到的数据和配置的数据匹配时,对应的上层网络协议的回调处理函数才会被执行。

  • EnableUnicastReceiveEnableMulticastReceiveEnableBroadcastReceiveEnablePromiscuousReceive:这四个可以一起说明,它们表示的是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++;
  }

组播和混杂模式还涉及到其它的操作。

  1. 组播还涉及到两个操作:
  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;
  }
  1. 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上层网络协议对这些模式的使用情况:

  1. ARP
  ArpService->MnpConfigData.EnableUnicastReceive      = TRUE;
  ArpService->MnpConfigData.EnableMulticastReceive    = FALSE;
  ArpService->MnpConfigData.EnableBroadcastReceive    = TRUE;
  ArpService->MnpConfigData.EnablePromiscuousReceive  = FALSE;
  1. DNS4
  MnpConfigData.EnableUnicastReceive      = TRUE;
  MnpConfigData.EnableMulticastReceive    = TRUE;
  MnpConfigData.EnableBroadcastReceive    = TRUE;
  MnpConfigData.EnablePromiscuousReceive  = FALSE;
  1. 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是否要在配置之后就开始接收网络数据。目前ARPDNS4IPv4都是默认的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,而且这样的上层可以有多个,比如ARPIP4。这个时候如果直接在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_PROTOCOLStartInitialize成员函数。

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中的StartStop接口,但是通过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()调用了SNPReceive成员函数来接收数据,并通过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()函数,这里介绍其流程。

  1. 首先是一些判断条件和初始化。
  2. 使用SNP接收数据:
  //
  // Receive packet through Snp.
  //
  Status = Snp->Receive (Snp, &HeaderSize, &BufLen, BufPtr, NULL, NULL, NULL);

此时的数据会放到MNP_DEVICE_DATA结构体成员RxNbufCache中。

  1. 如果配置了VLAN,去掉其Tag:
  if (MnpDeviceData->NumberOfVlan != 0) {
    //
    // VLAN is configured, remove the VLAN tag if any
    //
    IsVlanPacket = MnpRemoveVlanTag (MnpDeviceData, Nbuf, &VlanId);
  } else {
    IsVlanPacket = FALSE;
  }
  1. 找到VLAN配置(需要注意至少有一个配置,因为没有配置也是一种配置)对应的MNP服务:
MnpServiceData = MnpFindServiceData (MnpDeviceData, VlanId);
  1. 将数据放入队列:
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)
  1. 处理数据:
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);

MNPReceive()接口中也有增加的操作:

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中,供后续代码处理。

  1. 结束。

从这里可以看出,真正处理数据的代码,还是通过上层网络协议传递过来的Token中的回调函数。

**综上所示,MnpSystemPoll()是网络数据收发的底层接口,它从SNP接收数据,并回调上层网络协议的处理函数,来完成最终的网路数据处理。**就像下图那样:

在这里插入图片描述

MNP代码示例

由于MNP和SNP没有本质的区别,都是直接收发数据的,所以完全可以改写SNP代码示例,来达到相同的效果。代码可以在beni/BeniPkg/DynamicCommand/TestDynamicCommand/TestMnp.c · jiangwei/edk2-beni - 码云 - 开源中国 (gitee.com)中找到。

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值