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

UNDI

UNDI代码综述

UNDI全称Universal Network Driver Interface,它虽然属于UEFI网络框架的一部分,但是并没有在EDK开源代码中实现。不过目前主流网卡厂商都会提供UEFI下的网络驱动,并且大部分都实现了UNDI,这样BIOS下就可以通过SNP来调用网卡的底层驱动。本节不会具体说明网卡驱动的实现,而将重点放在UNDI框架以及它与SNP的关系。

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

支持
提供
gEfiPciIoProtocolGuid
UNDI
gEfiNetworkInterfaceIdentifierProtocolGuid_31

SNP查询UNDI

UNDI说到底是UEFI规范中定义的一系列接口,然后SNP可以访问这些接口,达到网卡初始化和网络通信的目的。

SNP如何获取到这些接口呢?这需要实现了UNDI的网络设备驱动在初始化时安装一个Network Interface Identifier(NII)协议,目前它的版本是3_10:

///
/// An optional protocol that is used to describe details about the software
/// layer that is used to produce the Simple Network Protocol.
///
struct _EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL {UINT64     Revision;      ///< The revision of the EFI_NETWORK_INTERFACE_IDENTIFIER protocol.UINT64     Id;            ///< The address of the first byte of the identifying structure for this network///< interface. This is only valid when the network interface is started///< (see Start()). When the network interface is not started, this field is set to zero.UINT64     ImageAddr;     ///< The address of the first byte of the identifying structure for this///< network interface.  This is set to zero if there is no structure.UINT32     ImageSize;     ///< The size of unrelocated network interface image.CHAR8      StringId[4];   ///< A four-character ASCII string that is sent in the class identifier field of///< option 60 in DHCP. For a Type of EfiNetworkInterfaceUndi, this field is UNDI.UINT8      Type;          ///< Network interface type. This will be set to one of the values///< in EFI_NETWORK_INTERFACE_TYPE.UINT8      MajorVer;      ///< Major version number.UINT8      MinorVer;      ///< Minor version number.BOOLEAN    Ipv6Supported; ///< TRUE if the network interface supports IPv6; otherwise FALSE.UINT16     IfNum;         ///< The network interface number that is being identified by this Network///< Interface Identifier Protocol. This field must be less than or///< equal to the (IFcnt | IFcntExt <<8 ) fields in the !PXE structure.
};

SNP就可以通过对应的GUID来访问到它,代码如下(位于NetworkPkg\SnpDxe\Snp.c):

  //// Get the NII interface.//Status = gBS->OpenProtocol (Controller,&gEfiNetworkInterfaceIdentifierProtocolGuid_31,(VOID **)&Nii,This->DriverBindingHandle,Controller,EFI_OPEN_PROTOCOL_BY_DRIVER);DEBUG ((DEBUG_INFO, "Start(): UNDI3.1 found\n"));Pxe = (PXE_UNDI *)(UINTN)(Nii->Id);

注意代码最后的强转,得到了NII中最重要的结构体PXE_UNDI

关于NII的安装,以Intel的网卡源码GigUndiDxe\Init.c为例,可以找到如下的代码:

EFI_STATUS
InitNiiProtocol (IN   UNDI_PRIVATE_DATA    *UndiPrivateData)
{NiiProtocol31                 = &UndiPrivateData->NiiProtocol31;NiiProtocol31->Id             = (UINT64) (UINTN) mE1000Pxe31;	// 这个就是PXE_UNDINiiProtocol31->IfNum          = UndiPrivateData->IfId;NiiProtocol31->Revision       = EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_REVISION_31;NiiProtocol31->Type           = EfiNetworkInterfaceUndi;NiiProtocol31->MajorVer       = PXE_ROMID_MAJORVER;NiiProtocol31->MinorVer       = PXE_ROMID_MINORVER_31;NiiProtocol31->ImageSize      = 0;NiiProtocol31->ImageAddr      = 0;NiiProtocol31->Ipv6Supported  = TRUE;NiiProtocol31->StringId[0]    = 'U';NiiProtocol31->StringId[1]    = 'N';NiiProtocol31->StringId[2]    = 'D';NiiProtocol31->StringId[3]    = 'I';Status = gBS->InstallMultipleProtocolInterfaces (&UndiPrivateData->DeviceHandle,&gEfiNetworkInterfaceIdentifierProtocolGuid_31,NiiProtocol31,NULL);
}

PXE_UNDI

PXE_UNDI结构体格式如下:

typedef union u_pxe_undi {PXE_HW_UNDI hw;PXE_SW_UNDI sw;
} PXE_UNDI;

可以看到它存在两种类型,从字面意思上看一种是硬件的格式,一种是软件的格式。对应的结构体描述如下(这种结构体有一个奇怪的名字叫!PXE,不知道这里的叹号表示什么意思。由于BIOS下PXE主要用来形容一种网络启动方式,所以这里的!PXE似乎是想说明这是一种“并不是用来启动的网络”?):

在这里插入图片描述

从图中可以看出来,硬件UNDI和软件UNDI有一个重要区别:即硬件UNDI通过往MMIO或者IO寄存器写命令(Command)来调用底层接口,而软件UNDI通过网络设备驱动提供出来的入口(Entry Point)来调用底层接口。UEFI下主要关注的是S/W的版本:

typedef struct s_pxe_sw_undi {PXE_UINT32    Signature;      ///< PXE_ROMID_SIGNATURE.PXE_UINT8     Len;            ///< sizeof(PXE_SW_UNDI).PXE_UINT8     Fudge;          ///< makes 8-bit cksum zero.PXE_UINT8     Rev;            ///< PXE_ROMID_REV.PXE_UINT8     IFcnt;          ///< physical connector count lower byte.PXE_UINT8     MajorVer;       ///< PXE_ROMID_MAJORVER.PXE_UINT8     MinorVer;       ///< PXE_ROMID_MINORVER.PXE_UINT8     IFcntExt;       ///< physical connector count upper byte.PXE_UINT8     reserved1;      ///< zero, not used.PXE_UINT32    Implementation; ///< Implementation flags.PXE_UINT64    EntryPoint;     ///< API entry point.PXE_UINT8     reserved2[3];   ///< zero, not used.PXE_UINT8     BusCnt;         ///< number of bustypes supported.PXE_UINT32    BusType[1];     ///< list of supported bustypes.
} PXE_SW_UNDI;

从目前SNP的实现来看,硬件UNDI并不支持:

  if ((Pxe->hw.Implementation & PXE_ROMID_IMP_HW_UNDI) != 0) {Snp->IsSwUndi             = FALSE;Snp->IssueUndi32Command   = &IssueHwUndiCommand;} else {Snp->IsSwUndi = TRUE;if ((Pxe->sw.Implementation & PXE_ROMID_IMP_SW_VIRT_ADDR) != 0) {Snp->IssueUndi32Command = (ISSUE_UNDI32_COMMAND) (UINTN) Pxe->sw.EntryPoint;} else {Snp->IssueUndi32Command = (ISSUE_UNDI32_COMMAND) (UINTN) ((UINT8) (UINTN) Pxe + Pxe->sw.EntryPoint);}}

因为这里的IssueHwUndiCommand()并没有实现:

EFI_STATUS
EFIAPI
IssueHwUndiCommand (UINT64  Cdb)
{DEBUG ((DEBUG_ERROR, "\nIssueHwUndiCommand() - This should not be called!"));if (Cdb == 0) {return EFI_INVALID_PARAMETER;}////  %%TBD - For now, nothing is done.//return EFI_UNSUPPORTED;
}

所以目前BIOS下使用的都是软件UNDI,它的接口是从!PXE这个结构体中获取的,所以最终在SNP模块中使用IssueUndi32Command()函数相当于调用UNDI中的某个EntryPoint。

SNP调用UNDI

IssueUndi32Command()函数的声明如下:

typedef
EFI_STATUS
(EFIAPI *ISSUE_UNDI32_COMMAND)(UINT64         Cdb);

它接受一个参数cdb,虽然从这里看类型是UINT64,但是其实它是一个指针,指向结构体PXE_CDB,其描述如下:

在这里插入图片描述

对应到代码中的结构体是PXE_CDB

typedef struct s_pxe_cdb {PXE_OPCODE       OpCode;PXE_OPFLAGS      OpFlags;PXE_UINT16       CPBsize;PXE_UINT16       DBsize;PXE_UINT64       CPBaddr;PXE_UINT64       DBaddr;PXE_STATCODE     StatCode;PXE_STATFLAGS    StatFlags;PXE_UINT16       IFnum;PXE_CONTROL      Control;
} PXE_CDB;

下面简单说明:

  • OpCode是操作码,不同的操作码对OpFlags、CPB结构体、DB结构体(就是CPBxxx,DBxxx那几个成员,它们对应到结构体中)都会有影响。
  • StatCodeStatFlags是返回的参数,也受到OpCode的影响。
  • IFnum用来处理一个NII对应多个物理网络设备的情况,值从0开始,算是一个Index。
  • Control可以指示使用了一个CDB还是多个,还可以指示当操作忙时是等待命令执行还是直接返回失败。

OpCode的值可以在UefiPxe.h中找到:

///
/// Return UNDI operational state.
///
#define PXE_OPCODE_GET_STATE  0x0000///
/// Change UNDI operational state from Stopped to Started.
///
#define PXE_OPCODE_START  0x0001///
/// Change UNDI operational state from Started to Stopped.
///
#define PXE_OPCODE_STOP 0x0002///
/// Get UNDI initialization information.
///
#define PXE_OPCODE_GET_INIT_INFO  0x0003///
/// Get NIC configuration information.
///
#define PXE_OPCODE_GET_CONFIG_INFO  0x0004///
/// Changed UNDI operational state from Started to Initialized.
///
#define PXE_OPCODE_INITIALIZE 0x0005///
/// Re-initialize the NIC H/W.
///
#define PXE_OPCODE_RESET  0x0006///
/// Change the UNDI operational state from Initialized to Started.
///
#define PXE_OPCODE_SHUTDOWN 0x0007///
/// Read & change state of external interrupt enables.
///
#define PXE_OPCODE_INTERRUPT_ENABLES  0x0008///
/// Read & change state of packet receive filters.
///
#define PXE_OPCODE_RECEIVE_FILTERS  0x0009///
/// Read & change station MAC address.
///
#define PXE_OPCODE_STATION_ADDRESS  0x000A///
/// Read traffic statistics.
///
#define PXE_OPCODE_STATISTICS 0x000B///
/// Convert multicast IP address to multicast MAC address.
///
#define PXE_OPCODE_MCAST_IP_TO_MAC  0x000C///
/// Read or change non-volatile storage on the NIC.
///
#define PXE_OPCODE_NVDATA 0x000D///
/// Get & clear interrupt status.
///
#define PXE_OPCODE_GET_STATUS 0x000E///
/// Fill media header in packet for transmit.
///
#define PXE_OPCODE_FILL_HEADER  0x000F///
/// Transmit packet(s).
///
#define PXE_OPCODE_TRANSMIT 0x0010///
/// Receive packet.
///
#define PXE_OPCODE_RECEIVE  0x0011///
/// Last valid PXE UNDI OpCode number.
///
#define PXE_OPCODE_LAST_VALID 0x0011

这里包含了所有需要传递从SNP传递给UNDI的信息索引,不过并不是所有网卡都支持这些操作。

然后简单说明SNP调用UNDI的流程,如下图所示:

在这里插入图片描述

从软件来看,实际上就是下面的几个步骤:

  1. 填充CDB。
  2. 调用Snp->IssueUndi32Command(),参数就是CDB。
  3. 判断返回值。

以SNP中的PxeStart()函数为例:

/**Call UNDI to start the interface and changes the snp state.@param  Snp                    pointer to snp driver structure.@retval EFI_SUCCESS            UNDI is started successfully.@retval EFI_DEVICE_ERROR       UNDI could not be started.**/
EFI_STATUS
PxeStart (IN SNP_DRIVER  *Snp)
{PXE_CPB_START_31  *Cpb31;Cpb31 = Snp->Cpb;//// Initialize UNDI Start CDB for H/W UNDI//Snp->Cdb.OpCode    = PXE_OPCODE_START;Snp->Cdb.OpFlags   = PXE_OPFLAGS_NOT_USED;Snp->Cdb.CPBsize   = PXE_CPBSIZE_NOT_USED;Snp->Cdb.DBsize    = PXE_DBSIZE_NOT_USED;Snp->Cdb.CPBaddr   = PXE_CPBADDR_NOT_USED;Snp->Cdb.DBaddr    = PXE_DBADDR_NOT_USED;Snp->Cdb.StatCode  = PXE_STATCODE_INITIALIZE;Snp->Cdb.StatFlags = PXE_STATFLAGS_INITIALIZE;Snp->Cdb.IFnum     = Snp->IfNum;Snp->Cdb.Control   = PXE_CONTROL_LAST_CDB_IN_LIST;//// Make changes to H/W UNDI Start CDB if this is// a S/W UNDI.//if (Snp->IsSwUndi) {Snp->Cdb.CPBsize = (UINT16)sizeof (PXE_CPB_START_31);Snp->Cdb.CPBaddr = (UINT64)(UINTN)Cpb31;Cpb31->Delay = (UINT64)(UINTN)&SnpUndi32CallbackDelay;Cpb31->Block = (UINT64)(UINTN)&SnpUndi32CallbackBlock;//// Virtual == Physical.  This can be set to zero.//Cpb31->Virt2Phys = (UINT64)(UINTN)0;Cpb31->Mem_IO    = (UINT64)(UINTN)&SnpUndi32CallbackMemio;Cpb31->Map_Mem   = (UINT64)(UINTN)&SnpUndi32CallbackMap;Cpb31->UnMap_Mem = (UINT64)(UINTN)&SnpUndi32CallbackUnmap;Cpb31->Sync_Mem  = (UINT64)(UINTN)&SnpUndi32CallbackSync;Cpb31->Unique_ID = (UINT64)(UINTN)Snp;}//// Issue UNDI command and check result.//DEBUG ((DEBUG_NET, "\nsnp->undi.start()  "));(*Snp->IssueUndi32Command)((UINT64)(UINTN)&Snp->Cdb);if (Snp->Cdb.StatCode != PXE_STATCODE_SUCCESS) {//// UNDI could not be started. Return UNDI error.//DEBUG ((DEBUG_ERROR,"\nsnp->undi.start()  %xh:%xh\n",Snp->Cdb.StatCode,Snp->Cdb.StatFlags));return EFI_DEVICE_ERROR;}//// Set simple network state to Started and return success.//Snp->Mode.State = EfiSimpleNetworkStarted;return EFI_SUCCESS;
}

SNP中的所有操作,实际上到最后都是使用类似上述的方式来完成的。最后简单说明CPB和DB,CPB的结构如下:

typedef struct s_pxe_cpb_start_31 {////// PXE_VOID Delay(UINT64 UnqId, UINTN microseconds);////// UNDI will never request a delay smaller than 10 microseconds/// and will always request delays in increments of 10 microseconds./// The Delay() CallBack routine must delay between n and n + 10/// microseconds before returning control to the UNDI.////// This field cannot be set to zero.///UINT64    Delay;////// PXE_VOID Block(UINT64 unq_id, UINT32 enable);////// UNDI may need to block multi-threaded/multi-processor access to/// critical code sections when programming or accessing the network/// device.  To this end, a blocking service is needed by the UNDI./// When UNDI needs a block, it will call Block() passing a non-zero/// value.  When UNDI no longer needs a block, it will call Block()/// with a zero value.  When called, if the Block() is already enabled,/// do not return control to the UNDI until the previous Block() is/// disabled.////// This field cannot be set to zero.///UINT64    Block;////// PXE_VOID Virt2Phys(UINT64 UnqId, UINT64 virtual, UINT64 physical_ptr);////// UNDI will pass the virtual address of a buffer and the virtual/// address of a 64-bit physical buffer.  Convert the virtual address/// to a physical address and write the result to the physical address/// buffer.  If virtual and physical addresses are the same, just/// copy the virtual address to the physical address buffer.////// This field can be set to zero if virtual and physical addresses/// are equal.///UINT64    Virt2Phys;////// PXE_VOID Mem_IO(UINT64 UnqId, UINT8 read_write, UINT8 len, UINT64 port,///              UINT64 buf_addr);////// UNDI will read or write the device io space using this call back/// function. It passes the number of bytes as the len parameter and it/// will be either 1,2,4 or 8.////// This field can not be set to zero.///UINT64    Mem_IO;////// PXE_VOID Map_Mem(UINT64 unq_id, UINT64 virtual_addr, UINT32 size,///                 UINT32 Direction, UINT64 mapped_addr);////// UNDI will pass the virtual address of a buffer, direction of the data/// flow from/to the mapped buffer (the constants are defined below)/// and a place holder (pointer) for the mapped address./// This call will Map the given address to a physical DMA address and write/// the result to the mapped_addr pointer.  If there is no need to/// map the given address to a lower address (i.e. the given address is/// associated with a physical address that is already compatible to be/// used with the DMA, it converts the given virtual address to it's/// physical address and write that in the mapped address pointer.////// This field can be set to zero if there is no mapping service available.///UINT64    Map_Mem;////// PXE_VOID UnMap_Mem(UINT64 unq_id, UINT64 virtual_addr, UINT32 size,///            UINT32 Direction, UINT64 mapped_addr);////// UNDI will pass the virtual and mapped addresses of a buffer./// This call will un map the given address.////// This field can be set to zero if there is no unmapping service available.///UINT64    UnMap_Mem;////// PXE_VOID Sync_Mem(UINT64 unq_id, UINT64 virtual,///            UINT32 size, UINT32 Direction, UINT64 mapped_addr);////// UNDI will pass the virtual and mapped addresses of a buffer./// This call will synchronize the contents of both the virtual and mapped./// buffers for the given Direction.////// This field can be set to zero if there is no service available.///UINT64    Sync_Mem;////// protocol driver can provide anything for this Unique_ID, UNDI remembers/// that as just a 64bit value associated to the interface specified by/// the ifnum and gives it back as a parameter to all the call-back routines/// when calling for that interface!///UINT64    Unique_ID;
} PXE_CPB_START_31;

虽然看到的都是UINT64的成员,但是它们其实都是一个个的函数指针,通过它UEFI可以给网卡驱动提供一些最基础的操作函数,比如延时操作,内存读写操作,IO读写操作等,这样的目的是为了能够处理不同的平台,上述基本操作的底层实现在不同的平台可能不同。

DB对应的结构体有很多,它表示的是UNDI的返回值,由于返回值不同,所以对应的结构体也会跟着改变,比如获取状态的结构体是这样的:

typedef struct s_pxe_db_get_status {////// Length of next receive frame (header + data).  If this is zero,/// there is no next receive frame available.///PXE_UINT32    RxFrameLen;////// Reserved, set to zero.///PXE_UINT32    reserved;//////  Addresses of transmitted buffers that need to be recycled.///PXE_UINT64    TxBuffer[MAX_XMIT_BUFFERS];
} PXE_DB_GET_STATUS;

获取MAC地址的结构体是这样的:

typedef struct s_pxe_db_mcast_ip_to_mac {////// Multicast MAC address.///PXE_MAC_ADDR    MAC;
} PXE_DB_MCAST_IP_TO_MAC;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/321144.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Alice Bob推出16量子比特量子处理单元——“Helium 1”

​&#xff08;图片来源&#xff1a;网络&#xff09; 容错量子计算机硬件开发商Alice & Bob宣布已成功流片一款新芯片“Helium 1”&#xff0c;希望能借助该芯片降低随着量子比特数增加而提高的错误率&#xff0c;这是该公司第一个纠错逻辑量子比特&#xff08;纠错量子计…

C++_模板

目录 1、函数模板 1.2 模板原理 2、多个模板参数 3、模板的显示实例化 4、模板的匹配 5、类模板 结语&#xff1a; 前言&#xff1a; 在C中&#xff0c;模板分为函数模板和类模板&#xff0c;而模板的作用就是避免了重复的工作&#xff0c;把原本是程序员要做的重复工作…

解决docker容器内无法连接宿主redis

背景 小程序的发短信服务挂了&#xff0c;随查看日志&#xff0c;该报错日志如下 Error 111 connecting to 127.0.0.1:6379. Connection refused. 6379是监听redis服务的端口&#xff0c;那大概是redis出错了。 首先查看了redis是否正常启动&#xff0c;检查出服务正常。 由于小…

C# WinForm MessageBox自定义按键文本 COM组件版

c# 更改弹窗MessageBox按钮文字_c# messagebox.show 字体-CSDN博客 需要用到大佬上传到百度云盘的Hook类&#xff0c;在大佬给的例子的基础上改动了点。 应用时自己加GUID和ProgID。 组件实现&#xff1a; using System; using System.Collections.Generic; using System.L…

Python计算圆的面积

Python 计算圆的面积 圆的面积公式为 &#xff1a; 公式中 r 为圆的半径。 # 定义一个方法来计算圆的面积 def findArea(r): PI 3.142 return PI * (r*r) # 调用方法 r float( input("请输入圆的半径:") ) print( "圆的面积为 %.3f&qu…

C 练习实例16 - 最大公约数和最小公倍数

题目&#xff1a;输入两个正整数a和b&#xff0c;求其最大公约数和最小公倍数 数学&#xff1a;最大公约数*最小公倍数a*b 例如&#xff1a;a16&#xff0c;b20。最小公倍数80&#xff0c;最大公约数4。80*416*20。 算法&#xff1a;辗转相除法&#xff0c;又称欧几里德算法…

【LeetCode-剑指offer】-- 23.相交链表

23.相交链表 方法一&#xff1a;哈希集合 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) {* val x;* next null;* }* }*/ public class Solution {public ListNode getIn…

vue3中标签form插件

想写一个系统&#xff0c;对八字进行标注&#xff0c;比如格局&#xff0c;有些八字就有很多格局&#xff0c;于是就想着使用el-tag但是&#xff0c;form表单中如何处理呢&#xff1f; 这个时候&#xff0c;就需要自己写一个,modelValue是表单的默认属性 <template><…

Mysql事务transaction简介

文章目录 什么是事务针对Mysql隔离级别读未提交读提交可重复读串行化 mysql中的数据结构索引数据结构mysql中的锁种类**共享锁和独占锁**表锁、行锁(记录锁、间隙锁、临键锁) spring中的事务事务特性 什么是事务 事务是一个不可分割的数据库操作序列&#xff0c;也是数据库并发…

顶顶通呼叫中心中间件配置背景音乐(mod_cti基于FreeSWITCH)

介绍 配置外呼任务拨打时的背景音乐&#xff0c;一步步配置 一、配置队列外呼模板 打开ccadmin->队列呼叫模板->添加一条变量根据下方图片配置 二、上传音乐文件 后缀格式为.wav格式的声音文件&#xff0c;声音文件需要上传在这个目录&#xff1a;/ddt/fs/sounds/ct…

笔记——C语言基础讲义(黑马程序员)

1. C 语言概述 1.1 什么是 C 语言 一提到语言这个词语&#xff0c;自然会想到的是像英语、汉语等这样的自然语言&#xff0c;因为它是人和人交换信息不可缺少的工具。 而今天计算机遍布了我们生活的每一个角落&#xff0c;除了人和人的相互交流之外&#xff0c;我们必…

osg-材质 (osg::Material)

1.材质类 材质类 (osg::Material)继承自osg::StateAttribute 类。osg::Material 封装了 OpenGL的 glMaterial()和glColorMaterial()指令的函数功能&#xff0c;其继承关系图如图5-27 所示。 图 5-27 osg::Material 的继承关系图 在场景中设置节点的材质属性&#xff0c;首先要…