Snp的Start和Stop函数最终由Mnp的Config函数调用
Arp的每一个ServiceData与MnpService中的一个instance对应,因为Service中的mnp结构体是创建Instance的时候安装的,而且安装的Efihandle都是新创建的
GE驱动在UEFI下的适配工作内容
1、edk2中网络协议栈的一些驱动还有这些驱动依赖的组件添加到编译和启动
uefi下的整个网络协议栈
要是使用完整的网络功能,就要把MNP及以上的模块都添加到编译和启动
我们现在使用的edk2版本,大部分网络协议栈相关的驱动模块都放在了edk2下的
ModeModulePkg/Universal/Network/
后边添加进来的网络驱动模块都放在edk2下的NetworkPkg下
比较新的版本的edk2已经把所有网络相关的移到NetworkPkg下了
2、edk2中shell的网络相关的内置命令添加到编译
edk2的shell本体以及Shell的所有内置命令位于根目录下ShellPkg
shell的内置命令以Lib的形式提供
每个lib可能包含多条命令
网络相关的内置命令在UefiShellNetwork1CommandsLib和UefiShellNetwork2CommandsLib
包括ping和ifconfig
Note:ifconfig没有up和down的功能
3、开发Snp驱动和它对应的PlatformDevice驱动
edk2也提供了SnpDxe这个模块,如果使用这个SnpDxe,网卡驱动就要实现一个undi驱动,适用于网卡模块作为pci外设的情况
参考edk2wPlatdorm下多个ArmSoc厂商的开源驱动,都是直接开发一个Snp驱动替代SnpDxe直接与上层的Mnp交互
主要参考BroadCom的开源驱动,实现一个Snp驱动(标准驱动)和一个PlatformDecice驱动(Dxe驱动)
PlatformDecice驱动主要作用是提供一个ControllerHandle,并在其上安装一个PlatformDeviceProtocol当然也可以将一些平台相关的操作封装成函数,作为PlatformDeviceProtocol的函数指针成员提供给Snp驱动调用
Snp驱动是标准驱动,采用DriverBindingProtocol,在Supported函数中判断ControllerHandle上是否安装了PlatformDeviceProtocol,并在Start函数中创建并初始化EFI_SIMPLE_NETWORK_PROTOCOL的PrivateData,将EFI_SIMPLE_NETWORK_PROTOCOL安装到ControllerHandle上
因此Snp的主要任务就是实现EFI_SIMPLE_NETWORK_PROTOCOL
与linux中网卡驱动提供的Ops有点类似
Initialze函数完成网卡驱动运行所需资源的申请
ShutDown函数是Initialize函数的反向操作
Start–>Initialize–>Shutdown–>Stop
Reset函数好像没有上层调用
Transmit函数被上层调用发送一帧的数据
Receice函数被上层调用查看是否收到一帧数据,如果有拷贝到上层申请好的存储区,没有返回ERROR
GetStatus函数被上层调用查看Phy的Link状态是否有变化,以及是否有数据发送完成,如果有直接返回发送数据的存储区指针
Mnp中创建了轮询事件,每500ms调用一次Snp->GetStatus
DriverBindingProtocol
uefi下的驱动分成两种
标准的和不标准的
不标准的就是常使用的.inf文件里边MODULE_TYPE:DXE_DRIVER的这种
标准的MODULE_TYPE:UEFI_DRIVER
不标准的驱动在戴正华那本书里称之为一种服务
也就是说这个模块的entryPoint中创建一个controller的ef_handle直接安装上一个功能性Protocol,供其他模块打开使用
而标准的驱动要在entryPoint中安装一个非功能性的Protocol,即DriverBindingProtocol
安装在这个模块的ImageHandle上边
这个就很像linux里驱动模块的入口函数中把一个驱动注册上去,驱动的功能暂时不会发挥作用,除非出现一个device,匹配上之后,prob函数执行,这个driver上的功能才会安装到device上
DriverBindingProtocl也是这个意思
它里边有三个重要的函数指针成员变量
其中Supported函数就是用来判断新产生的ControllerHandle是不是跟这个Driver匹配,如果匹配就返回EFI_SUCCESS,然后Start就得到执行,这个Start函数就类似Prob函数,Supported函数完成compatible的比对
执行Start的时候,会把这个驱动的功能性的一个Protocol安装到匹配的那个ControllerHandle上,这才是这个驱动真正要干的活儿
当然也可以在DxeDriver的erntryPoint中判断是否存在有匹配的ControllerHandle,不存在我就不安装,不安装就结束,这样后边如果有匹配的ControllerHandle被创建也不会运行这个驱动了,这样就需要保证这个DxeDriver必须在ControllerHandle产生之后创建,当然可以通过fdf文件去调整,但这样就很蹩脚
由此可见DriverBindingProtocl就是为了干这个而生的
ServiceBindingProtocol
ServiceBindingProtocol与DriverBindingProtocol配合使用
如果只使用DriverBindingProtocol,在ControllerHandle上安装一个功能性的Protocol,提供其他模块去使用
如果只有一个模块使用这个Protocol也还好
或者多个模块使用这个Protocol但是这个Protocol提供的功能比较简单,那也还好
但是,如果有多个模块使用这个Ptotocol,那在打开这个Protocol之后对应的PrivateData就会是同一个,这样多个模块使用Protocol的时候就要有一些同步互斥的行为,因为要访问同一个数据结构
这个时候使用ServiceBindingProtocol就比较合适,它的行为很像应用开发的时候,TCP的server端,它会在初始化的时候创建一个监听Socket,如果有Client端来连接,Sever端会建立一个Socket与Client的Socket去连接,通过这一对socket就可以提供服务了,监听Socket继续监听其他Client端的连接请求。
如果要使用它的话,也要先使用DriverBindingProtocol,在驱动模块的EntryPoint中安装DriverBindingProtocol到与之匹配的ControllerHandle上,在DriverBindingProtocol的Start函数中并不安装一个功能性的Protocol提供这个驱动真正要提供的功能,而是在ControllerHandle上安装一个ServiceBindingProtocol,然后结束
这个ServiceBindingProtocol只有两个函数指针成员变量
struct _EFI_SERVICE_BINDING_PROTOCOL {
EFI_SERVICE_BINDING_CREATE_CHILD CreateChild;
EFI_SERVICE_BINDING_DESTROY_CHILD DestroyChild;
};
如果只使用DriverBindingProtocol驱动提供的功能只要OpenProtocol打开ControllerHandle上安装的功能性的Protocol就可以了,如果使用ServiceBindingProtocol的话就要先打开安装在ControllerHandle上的ServiceBindingProtocol,然后调用CreateChild,这个函数中首先创建一个EfiHandle,然后把功能性的Protocol安装到新创建的EfiHandle上,当然也会创建一个这个功能性的Protocol对应的PrivateData
如果多个模块想使用这个驱动的功能,就会多次调用CreateChild,每次创建一份EfiHandle + 功能性的Protocol + 对应的PrivateData(Instance)
ServiceBindingProtocol也有对应的PrivateData(ServiceData)
通常一个ServiceBindingProtocol创建的多个Intance会通过双向链表连在一起,并在ServiceData有一个指向这个链表的指针
这样多个模块同时使用这个功能性的Protocol的时候,同步的工作就交给驱动内部完成,每个模块使用Protocol的时候不感知其他模块。
举例说明:snp+mnp+arp
这是UEFI网络驱动框架的一部分的调用关系
每个网卡对应一个SnpDxe
每个Snp对应一个MnpDxe
MnpDxe主要通过安装EFI_MANAGED_NETWORK_PROTOCOL向其他模块提供服务
Mnp并不属于网络协议栈,对于数据主要起一个透传的作用,也有Transit和Receive函数
但是可以通过Mnp管理Vlan,在网卡硬件不提供Vlan功能的时候
调用关系可以看出,Mnp向上层提供服务对象不只一个,因此MnpDxe中使用了ServiceBingingProtocol
在ControllerHandle上安装EFI_SERVICE_BINDING_PROTOCOL
EFI_STATUS
EFIAPI
MnpServiceBindingCreateChild (
IN EFI_SERVICE_BINDING_PROTOCOL *This,
IN OUT EFI_HANDLE *ChildHandle
)
{
EFI_STATUS Status;
MNP_SERVICE_DATA *MnpServiceData;
MNP_INSTANCE_DATA *Instance;
VOID *MnpSb;
EFI_TPL OldTpl;
MnpServiceData = MNP_SERVICE_DATA_FROM_THIS (This);
//
// 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;
Status = gBS->OpenProtocol (
MnpServiceData->ServiceHandle,
&gEfiManagedNetworkServiceBindingProtocolGuid,
(VOID **)&MnpSb,
gMnpDriverBinding.DriverBindingHandle,
Instance->Handle,
EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
);
//
// Add the child instance into ChildrenList.
//
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
InsertTailList (&MnpServiceData->ChildrenList, &Instance->InstEntry);
MnpServiceData->ChildrenNumber++;
gBS->RestoreTPL (OldTpl);
return Status;
}
DevicePathProtocol
DevicePathProtocol这也是一个要安装到ControllerHandle上的非功能性的Protocol,它没有函数指针成员,只有数据成员,这个成员表示这个ControllerHandle的路径
这个路径用来描述设备或者功能的从属关系
每个DevicePath由多个路径节点组成,存储上,这些节点都是一个挨一个的,uefi定义了很多种路径节点,每种节点的数据结构都不同,但是每种路径节点的第一个成员变量是相同的EFI_DEVICE_PATH_PROTOCOL,通过EFI_DEVICE_PATH_PROTOCOL的Type和SubType区分不同的类型,结合Length可以完成对一个DevicePath上所有节点的遍历
当然有专门的类型去标记一个DevicePath的结束
DevicePath每个节点根据类型不同,EFI_DEVICE_PATH_PROTOCOL之后的数据成员不同,有不同的意义
typedef struct {
UINT8 Type; ///< 0x01 Hardware Device Path.
///< 0x02 ACPI Device Path.
///< 0x03 Messaging Device Path.
///< 0x04 Media Device Path.
///< 0x05 BIOS Boot Specification Device Path.
///< 0x7F End of Hardware Device Path.
UINT8 SubType; ///< Varies by Type
///< 0xFF End Entire Device Path, or
///< 0x01 End This Instance of a Device Path and start a new
///< Device Path.
UINT8 Length[2]; ///< Specific Device Path data. Type and Sub-Type define
///< type of data. Size of data is included in Length.
} EFI_DEVICE_PATH_PROTOCOL;
UEFI提供函数去解析DevicePath,遍历一个DevicePath,根据节点类型将后边的数据成员的意义解析并打印出来,就是我们在Shell中常见的DevicePath的样子
比如
一个pci的root上边挂了一个pci的usb控制器,pci口上又插了一个usb网卡
PciRoot节点/Usb节点/Mac节点
比如
一个网卡下管理了多个vlan
Mac节点/Vlan节点/Ip节点
比如
一个UsbController(Interface:0)连接到一个UsbHub(Interface:0)的第三个Port
UsbHub连接到一个PCI口的Usb根集线器(PCI Function Number:31;PCI_Device Number:2)的第一个Port上
USB根集线器连接到PCI Root上
在Shell中打印出来是这样的
PciRoot(0)/PCI(31,2)/USB(1,0)/USB(3,0).
在内存中是这样的
除此之外DevicePath产生
1、可以直接定义一个Static的数据结构,可以包括多个节点
2、也可以只定义一个路径节点,调用UEFI提供的Append函数将ParentDevicePath拷贝一份并在之后添加自己定义的路径节点,形成自己的DevicePath