UEFI开发探索52 – UEFI与网络3(UEFI TCP4)

147 阅读4分钟

(本文已参与“新人创作礼”活动,一起开启掘金创作之路。)
(转载请保留-《UEFI编程实践》并行博客,作者:罗冰) ​

搭建好网络测试环境之后,可以着手进行网络编程了。

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()中调用了,截图如下:

​编辑

图1 代码截图

图中红框处的代码,与Spec中要求的略有不同。在测试中,如果使用EVT_NOYIFY_SIGNAL类型的话,数据是发送不了的(在TianCore模拟器中做的实验),所以做了一点改变。

其他的函数,理解相对简单,对照Spec中的Protocol说明,比较容易看懂,就不解释了。

3 测试

对照前几篇中的说明,搭建好网络测试环境。按如下命令进行代码编译:

C:\MyWorkspace>build -p RobinPkg\RobinPkg.dsc -a IA32 -m RobinPkg\Applications\EchoTCP4\ EchoTCP4.inf

在搭好的环境中,测试情况如下:

​编辑

图2 测试TCP通信

**Gitee地址: gitee.com/luobing4365…
项目代码位于:/ **FF RobinPkg/ RobinPkg /Applications/EchoTCP4