请保留-> 【原文: https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】
搭建好网络测试环境之后,可以着手进行网络编程了。
UEFI下提供了相应的Protocol,可以进行TCP和UDP的编程,而且针对IPv4和IPv6都提供了相应的支持。另外,也可以通过StdLib中封装好的Socket接口进行编程。
如果所有的编程方式都实现一遍,博客的篇幅就太长了。我原计划是用5篇左右的博客,把网络编程探索完的,因此,我准备用UEFI Protocol编写TCP(IPv4)示例和UDP(IPv4) 示例,以及StdLib接口把上述例子重新实现。
1 EFI_TCP4_PROTOCOL的使用
大部分的UEFI Protocol,可通过设备的GUID找到,直接访问即可。与其他UEFI Protocol不同,网络需要频繁地生成新的Socket。在规范中,针TCP4,提供了两种Protocol。
一是EFI_TCP4_PROTOCOL,可以进行TCP的网络配置和通讯;二是EFI_TCP4_SERVICE_BINDING_PROTOCOL,用来生成EFI_TCP4_PROTOCOL实例。
不过,在EDK2的实现中,并没有提供EFI_TCP4_SERVICE_BINDING_PROTOCOL,而是为所有的网络协议提供了EFI_SERVICE_BINDING_PROTOCOL。也就是说,虽然在Spec中提供了各种名为EFI_XXXX_SERVICE_BINDING_PROTOCOL的协议,其实都是使用EFI_SERVICE_BINDING_PROTOCOL。
相对的,EFI_SERVICE_BINDING_PROTOCOL本身并没有GUID。其他网络协议的Protocol都有自己的GUID,比如TCP4、UDP4、TCP6等,它们共享同一EFI_SERVICE_BINDING_PROTOCOL接口,用来生成本身的实例。
1) EFI_SERVICE_BINDING_PROTOCOL接口说明
typedef struct _EFI_SERVICE_BINDING_PROTOCOL {
EFI_SERVICE_BINDING_CREATE_CHILD CreateChild; //生成子设备,并安装对应的Protocol
EFI_SERVICE_BINDING_DESTROY_CHILD DestroyChild;//销毁生成的子设备
} EFI_SERVICE_BINDING_PROTOCOL;
typedef
EFI_STATUS (EFIAPI *EFI_SERVICE_BINDING_CREATE_CHILD) (
IN EFI_SERVICE_BINDING_PROTOCOL *This, //EFI_SERVICE_BINDING_PROTOCOL实例
IN OUT EFI_HANDLE *ChildHandle
//创建的子设备句柄
);
typedef
EFI_STATUS (EFIAPI *EFI_SERVICE_BINDING_DESTROY_CHILD) (
IN EFI_SERVICE_BINDING_PROTOCOL *This,//EFI_SERVICE_BINDING_PROTOCOL实例
IN EFI_HANDLE ChildHandle //创建的子设备句柄
);
基本的操作流程如下:
1-A) 通过gEfiArpServiceBindingProtocolGuid打开EFI_SERVICE_BINDING_PROTOCOL实例;
1-B) 使用此实例的CreateChild创建子设备;
1-C) 使用各网络协议的GUID,在子设备上安装对应的Protocol。具体的例子可以参考UEFI sepc 2.8 P390。
2) EFI_TCP4_PROTOCOL接口说明
typedef
struct _EFI_TCP4_PROTOCOL {
EFI_TCP4_GET_MODE_DATA GetModeData;//获取当前协议栈状态
EFI_TCP4_CONFIGURE Configure;//配置TCP地址、端口等属性
EFI_TCP4_ROUTES Routes;//添加或删除此TCP实例的路由
EFI_TCP4_CONNECT Connect;//初始化TCP三次握手,建立TCP连接
EFI_TCP4_ACCEPT Accept;//侦听TCP连接请求
EFI_TCP4_TRANSMIT Transmit;//发送数据
EFI_TCP4_RECEIVE Receive;//接收数据
EFI_TCP4_CLOSE Close;//关闭连接
EFI_TCP4_CANCEL Cancel;//取消当前连接上的异步操作
EFI_TCP4_POLL Poll; //完成当前连接上的发送或接收操作
} EFI_TCP4_PROTOCOL;
typedef
EFI_STATUS (EFIAPI *EFI_TCP4_TRANSMIT) (
IN EFI_TCP4_PROTOCOL *This, //实例
IN EFI_TCP4_IO_TOKEN *Token
//指向完成令牌的队列
);
typedef
EFI_STATUS (EFIAPI *EFI_TCP4_RECEIVE) (
IN EFI_TCP4_PROTOCOL *This, //实例
IN EFI_TCP4_IO_TOKEN *Token
//指向完成令牌的队列
);
Config、Connect以及Accept,可以在Spec中查到说明,这里只对发送和传输重点解释。发送和传输都使用了EFI_TCP_IO_TOKEN型指针,其原型如下:
typedef
struct {
EFI_TCP4_COMPLETION_TOKEN CompletionToken;//完成操作的令牌
union {
EFI_TCP4_RECEIVE_DATA *RxData; //接收数据缓冲
EFI_TCP4_TRANSMIT_DATA *TxData; //发送数据缓冲
} Packet;
} EFI_TCP4_IO_TOKEN;
typedef
struct {
EFI_EVENT Event;//此事件在请求完成之后触发
EFI_STATUS Status;//完成操作之后的状态标志
} EFI_TCP4_COMPLETION_TOKEN;
typedef struct {
BOOLEAN UrgentFlag; //TCP头的紧急标志位
UINT32 DataLength; //数据总长度
UINT32 FragmentCount; //数据分段个数
EFI_TCP4_FRAGMENT_DATA FragmentTable[1];//数据分段的数组
} EFI_TCP4_RECEIVE_DATA;
typedef
struct {
BOOLEAN Push; //TCP头的PSH标志位
BOOLEAN Urgent; //TCP头的URG标志位
UINT32 DataLength; //数据总长度
UINT32 FragmentCount; //数据分段个数
EFI_TCP4_FRAGMENT_DATA FragmentTable[1];
//数据分段的数组
} EFI_TCP4_TRANSMIT_DATA;
数据结构有点多,不过条理还是比较清楚的。网络协议中大量的使用了事件,具体不一一解释了,参照数据结构还是比较容易理解的。
2 TCP4的编程
在实验中,我把UEFI作为客户端进行测试。设想中,服务端接收到客户端的数据后,将数据原样返回。
服务器的代码还没有编写,暂时使用“网络助手”来替代,模拟操作。
客户端代码编写过程大致可以分为以下几步:
1) 使用EFI_SERVICE_BINDING_PROTOCOL生成EFI_TCP4_PROTOCOL对象;
2) 对生成的对象进行配置,同时创建所需要的各种Event对象;
3) 向服务端发起连接;
4) 数据传输;
5) 关闭连接,并销毁EFI_TCP4_PROTOCOL对象。
下面对提供的示例代码作简单的解释。
为了保存网络通信的相关配置项和缓冲区地址,构造了名为MYTCP4SOCKET的数据结构,包括所用到的各种句柄、缓冲区、令牌等,都在此结构内。同时,定义了此类型的全局数组TCP4SocketFd[32],方便各个函数使用。
创建EFI_TCP4_PROTOCOL对象的函数为UINTN CreateTCP4Socket(VOID),大致是按照Spec中提供的例子编写的。
需要着重了解的是函数EFI_STATUS InitTcp4SocketFd(INTN index)。此函数在CreateTCP4Socket()中调用了,截图如下:
图中红框处的代码,与Spec中要求的略有不同。在测试中,如果使用EVT_NOYIFY_SIGNAL类型的话,数据是发送不了的(在TianCore模拟器中做的实验),所以做了一点改变。
其他的函数,理解相对简单,对照Spec中的Protocol说明,比较容易看懂,就不解释了。
3 测试
对照前几篇中的说明,搭建好网络测试环境。按如下命令进行代码编译:
C:\MyWorkspace>build -p RobinPkg\RobinPkg.dsc -a IA32 -m RobinPkg\Applications\EchoTCP4\ EchoTCP4.inf
在搭好的环境中,测试情况如下:
百度云链接:https://pan.baidu.com/s/1gccSosw8_UAGTI5gZPnLCA
提取码:dx23
文件在 FF RobinPkg/ RobinPkg /Applications/EchoTCP4
5,423 total views, 2 views today
罗老师有意向写UEFI网络编程的服务端程序吗
没啥兴趣,UEFI下的网络本身完善度不是很够。
罗老师,我之前自己写的代码是Connect失败了。调试无果后我直接用您的代码编译了一遍,不过也是出现了问题在ConnectTCP4Socket函数中:Status = gBS->WaitForEvent(1, &(CurSocket->ConnectToken.CompletionToken.Event), &waitIndex);
这一个Event失败,返回的Status是Invalid Parameter 然后和主机的服务端也一直连接不上(这个事件类型是EVT_NOTIFY_SIGNAL在init阶段改成EVT_NOTIFY_WAIT后返回Status是成功,但是还是连接不上服务端)IP配过之后也能和主机Ping的通,您的代码没有改动也是遇到这种情况,不知罗老师有遇到么?
没有遇到过,我没有深究过这部分的代码。可以去看下StdLib中的实现,对比看看问题在哪