1)实验平台:正原子阿尔法Linux开发板 2)平台采购地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码 手册 视频下载地址:http://www.openedv.com/thread-300792-1-1.html 3)正点原子Linux感兴趣的学生可以加入小组讨论:935446741 4)关注正点原子微信官方账号,获取最新信息更新 第六十九章 Linux 网络驱动实验
网络驱动是linux驱动三巨头之一,linux网络功能非常强大,嵌入式linux网络功能也经常使用。我们已经谈到了字符设备驱动和块设备驱动。让我们学习这一章linux内部网络设备驱动。
69.1 嵌入式网络简介 69.1.1嵌入式网络硬件接口 本章讨论有线网络! 说到网络,我们通常想到的硬件是网卡。网卡的概念最早从计算机领域传播开来。顾名思义,它是一张可以上网的卡。在计算机领域的原始社会中,网卡是一个独立的硬件。如果计算机想上网,它必须买一张网卡插入,类似于当前的显卡。然而,当你观察你的笔记本电脑或台式主板时,你会发现没有像显卡这样的网卡设备。原因是随着技术的不断发展,有线网卡功能只需要一个芯片,所以网卡芯片直接放在主板上。所以当你接触嵌入式时,听到网卡这个词,不要急于在开发板上找到卡。 既然网卡已经通过芯片完成了,那么什么样的芯片呢?首先要了解嵌入式网络硬件方案。首先,嵌入式网络硬件分为两部分:MAC和PHY,每个人都是通过看数据手册来判断的SOC是否支持网络,如果芯片数据手册说他们支持网络,一般说这个SOC内置MAC,MAC类似I2C控制器、SPI外设与控制器相同。但是光有MAC不能直接驱动网络,还需要另一个芯片:PHY,所以内置MAC的SOC,它的外部必须搭配一个PHY芯片。但是有些SOC内部没有MAC,那就不能搭配了PHY芯片,这些内部没有网络MAC如何上网芯片?这里有两个常见的嵌入式网络硬件方案。 1、SOC内部没有网络MAC外设 我们通常说某个SOC不支持网络意味着它没有网络MAC。那么这个芯片不能上网吗?显然不是,因为没有内部MAC,然后可以找个外置的MAC芯片,但这种外部网络芯片通常是MAC PHY一体的。比如三星linux开发板中使用最多的开发板DM9000,因为三星芯片基本没有内部芯片MAC(比如S3C2440、S5PV210、4412等。),所以三星的开发板是外置的DM有线网络功能9000,DM9000对SOC提供了一个SRAM接口,SOC会以SRAM的方式操作DM9000。 一些外部网络芯片更强大,内部甚至集成了硬件TCP/IP协议栈,外部提供SPI接口,比如W5500。这通常用于单片机领域,单片机通过SPI接口与W由于5500,因为W5500内置了硬件TCP/IP协议栈,所以单片机不需要移植负责的软件协议栈,直接通过SPI来操作W5500,简化了单片机联网方案。 这种方案的优点就是让不支持网络的SOC可以另辟蹊径实现网络功能,但缺点是网络效率低,因为一般芯片内置MAC会有网络加速引擎,如网络专用DMA,网络处理效率会很高。而且这种芯片网速不快,基本上是10/100M。另外,相比PHY就芯片而言,这种芯片的成本也比较高,可供选择的也比较少。 SOC与外部MAC PHY芯片连接如图69所示.1.1.1所示:
图69.1.1.1 主控SOC与外置MAC PHY芯片连接 2、SOC内部集成网络MAC外设 我们一般说某个SOC支持网络是指他内部集成网络MAC此时,我们还需要外接一个网络PHY芯片。此时有朋友会有疑问,PHY芯片不能集成SOC你还没见过作者会吗?PHY也集成到芯片中SOC。 一般常见的通用SOC网络将集成MAC外设,比如STM32F4/F7/H7系列、NXP的I.MX系列,内部集成网络MAC优点如下: ①、内部MAC外设将有特殊的加速模块,如特殊的加速模块DMA,加快网速数据处理。 ②、网速快,可支持10/100/10000M网速。 ③、外接PHY选择性多,成本低。 内部的MAC外设会通过MII或者RMII接口连接外部PHY芯片,MII/RMII接口用于传输网络数据。此外,主控需要配置或读取PHY芯片,也就是读写PHY内部寄存器也需要一个叫做控制接口的控制接口MIDO,MDIO很类似IIC,也有两条线,一条数据线叫做MDIO,时钟线叫MDC。 SOC内部MAC外设与外部PHY芯片连接如图69所示.1.1.2所示:
图69.1.1.2 内部MAC与外部PHY之间的连接 在做项目时,如果你想使用网络功能,强烈建议你选择内部网络MAC外设的主控SOC!I.MX6ULL就有两个10M/100M的网络MAC外设,正点原子ALPHA两个开发板PHY芯片,型号为LAN8720。因此,本章只解释SOC内部MAC 外置PHY芯片方案。 69.1.2 MII/RMII接口 正如我们前面所说,内部MAC通过MII/RMII与外界接口PHY芯片连接,完成网络数据传输,本节我们将学习什么MII和RMII接口。 1、MII接口 MII全称是Media Independent Interface,直译是介质的独立界面,它是IEEE-802.以太网标准接口的定义,MII以太网采用接口MAC连接PHY芯片连接示意图如图69.1.2.1所示:
图69.1.2.1 MII接口 MII接口有16条信号线,含义如下: TX_CLK:如果网速为100,发送时钟M时钟频率为25MHz,10M网速时钟频率为2.5MHz,此时钟由PHY产生并发送给MAC。 TX_EN:发送使能信号。 TX_ER:高电平有效地发送错误信号表示TX_ER在有效期内传输的数据无效。Mpbs网速下TX_ER不起作用。 TXD[3:0]:发送4条数据信号线。 RXD[3:0]:接收数据信号线,共4条。 RX_CLK:如果网速为100,接收时钟信号M时钟频率为25MHz,10M网速时钟频率为2.5MHz,RX_CLK也是由PHY产生的。 RX_ER:接收错误信号,高电平有效,表示RX_ER在有效期内传输的数据无效。Mpbs网速下RX_ER不起作用。 RX_DV:接收数据有效,功能相似TX_EN。 CRS:载波侦听信号。 COL:冲突检测信号。 MII接口的缺点是所需的信号线太多,还没算MDIO和MDC因此,这两个管理接口的数据线MII界面的使用越来越少。 2、RMII接口 RMII全称是Reduced Media Independent Interface,翻译是简化介质的独立界面,即MII接口的精简版本。RMII接口只需要7条数据线,与MII直接减少9根,大大方便了板材布线,RMII接口连接PHY芯片示意图如图69.1.2.2所示:
图69.1.2.1 RMII接口 TX_EN:发送使能信号。 TXD[1:0]:发送数据信号线,一共2根。 RXD[1:0]:接收数据信号线,共2条。 CRS_DV:相当于MII接口中的RX_DV和CRS这两个信号的混合。 REF_CLK:外部时钟源提供参考时钟, 频率为50MHz。这里与MII不同,MII接收和发送时钟是独立分离的,都是由PHY芯片提供的。 除了MII和RMII此外,还有其他接口,如GMII、RGMII、SMII、SMII等等,其他界面基本相同,这里就不解释了。正点原子ALPAH开发板上的两个网口都是用的RMII接口来连接MAC与外部PHY芯片。 69.1.3 MDIO接口 MDIO全称是Management Data Input/Output,直译是管理数据输入输出接口,是一个简单的两线串行接口,一个MDIO数据线,一个MDC时钟线。可通过驱动程序MDIO和MDC访问这两条线PHY任何寄存器的芯片。MDIO多达32个接口PHY。只能在同一时对一个PHY如何区分这32个操作?PHY芯片呢?和IIC同样,使用设备地址。同一MDIO接口下的一切PHY芯片,其设备地址不能冲突,必须确保唯一,具体设备地址值应咨询相应的PHY数据手册。 因此,MAC和外部PHY芯片连接时,主要是芯片MII/RMII和MDIO此外,还可能需要复位、中断等引脚。 69.1.4 RJ45接口 网络设备通过网线连接,插入网线称为RJ45座,如图69.1.4.1所示:
图69.1.4.1 RJ45座子 RJ45座要与PHY芯片连接在一起,但中间需要一个网络变压器,用于隔离和滤波。网络变压器也是芯片,形状一般如图69所示.1.4.2所示:
图69.1.4.2 网络变压器 但现在有很多RJ网络变压器集成在45座内,如正点原子ALPHA使用开发板HR911105A是内置网络变压器RJ45座。内置网络变压器RJ45座和不内置的引脚一样,但是一般不内置的RJ45座会短一点。所以画板的时候一定要考虑你用的。RJ45座是否内置网络变压器,如果不内置,应自行添加网络变压器部分电路!!!同样,如果您设计的硬件需要内置网络变压器RJ45个座位不能随意焊接不内置变压器的RJ45座,否则网络工作不正常! RJ45座子上一般有两个灯,一个黄色(橙色),一个绿色,绿色亮的话表示网络连接正常,黄色闪烁的话说明当前正在进行网络通信。这两个灯由PHY芯片控制,PHY芯片会有两个引脚来连接RJ45座上的这两个灯。内部MAC+外部PHY+RJ45座(内置网络变压器)就组成了一个完整的嵌入式网络接口硬件,如图69.1.4.2所示:
图69.1.4.2 嵌入式网络硬件接口示意图 69.1.5 I.MX6ULL ENET接口简介 I.MX6ULL有两个网络接口,也就是两个MAC外设,一个MAC连接一个PHY芯片形成一个完整网络接口,本节我们简单了解一下I.MX6ULL自带的ENET接口。I.MX6ULL内部自带的ENET外设其实就是一个网络MAC,支持10/100M。实现了三层网络加速,用于加速那些通用的网络协议,比如IP、TCP、UDP和ICMP等,为客户端应用程序提供加速服务。 I.MX6ULL内核集成了两个10/100Mbit/S的网络MAC,符合IEEE802.3-2002标准,MAC层支持双工、半双工局域网。MAC可编程、可以作为NIC卡或其他一些交换器件。根据IETF RFC 2819协议,MAC实现了RMON(Remote Network Monitoring)计数功能。MAC内核拥有硬件加速处理单元来提高网络性能,硬件加速单元用于处理TCP/IP、UDP、ICMP等协议。通过硬件来处理帧头等信息,效果要比用一大堆软件处理要好很多。ENET外设有一个专用的DMA,此DMA用于在ENET外设和SOC之间传输数据,并且支持可编程的增强型的缓冲描述符,用以支持IEEE 1588。 I.MX6ULL内部ENET外设主要特性如下: 1)、实现了全功能的802.3规范前导码/SFD生成、帧填充、CRC生成和检查。 2)、支持零长的前导码。 3)、支持10/100M动态配置。 4)、兼容AMD远端节点电源管理的魔术帧中断检测。 5)、可以通过如下接口无缝的连接PHY芯片: ·4bit的MII接口,频率为2.5/25MHz。 ·4bit的MII-Lite接口,也就是MII接口取消掉CRS和COL这两根线,频率也是2.5/25MHz。 ·2bit的RMII接口,频率为50MHz。 6)、MAC地址可编程。 7)、多播和单播地址过滤,降低更高层的处理负担。 8)、MDIO主接口,用于管理和配置PHY设备。 …… I.MX6ULL的ENET外设内容比较多,详细的介绍请查阅《I.MX6ULL参考手册》的“Chapter 22 10/100-Mbps Ethernet MAC(ENET)”章节。我们在编写驱动的时候其实并不需要关注ENET外设的具体内容,因为这部分驱动是SOC厂商编写的,我们重点关注的是更换PHY芯片以后哪里需要调整。 69.2 PHY芯片详解 69.2.1 PHY基础知识简介 PHY是IEEE 802.3规定的一个标准模块,前面说了,SOC可以对PHY进行配置或者读取PHY相关状态,这个就需要PHY内部寄存器去实现了。PHY芯片寄存器地址空间为5位,地址 031共32个寄存器,IEEE定义了015这16个寄存器的功能,1631这16个寄存器由厂商自行实现。也就是说不管你用的哪个厂家的PHY芯片,其中015这16个寄存器是一模一样的。仅靠这16个寄存器是完全可以驱动起PHY芯片的,至少能保证基本的网络数据通信,因此Linux内核有通用PHY驱动,按道理来讲,不管你使用的哪个厂家的PHY芯片,都可以使用Linux的这个通用PHY驱动来验证网络工作是否正常。事实上在实际开发中可能会遇到一些其他的问题导致Linux内核的通用PHY驱动工作不正常,这个时候就需要驱动开发人员去调试了。但是,随着现在的PHY芯片性能越来越强大,32个寄存器可能满足不了厂商的需求,因此很多厂商采用分页技术来扩展寄存器地址空间,以求定义更多的寄存器。这些多出来的寄存器可以用于实现厂商特有的一些技术,因此Linux内核的通用PHY驱动就无法驱动这些特色功能了,这个时候就需要PHY厂商提供相应的驱动源码了,所以大家也会在Linux内核里面看到很多具体的PHY芯片驱动源码。不管你的PHY芯片有多少特色功能,按道理来讲,Linux内核的通用PHY驱动是绝对可以让你这PHY芯片实现基本的网络通信,因此大家也不用担心更换PHY芯片以后网络驱动编写是不是会很复杂。 IEEE802.3协议英文原版已经放到了开发板光盘中,路径为4、参考资料->802.3协议英文原版_2018年.pdf,打开此文档,此文档有5600页,按照SECTION进行分类,一共8个SECTION。选中“802.3-2018_SECTION2”,找到“22.2.4 Management functions”章节,此章节对PHY的前16个寄存器功能进行了规定,如图69.2.1.1所示:
图69.2.1.1 IEEE规定的前16个寄存器 关于这16个寄存器的内容协议里面也进行了详细的讲解,这里就不分析了。我们后面会以ALPHA开发板所使用的LAN8720A这个PHY为例,详细的分析一下PHY芯片的寄存器。 69.2.2 LAN8720A详解 虽然本教程讲解的是LAN8720A这颗PHY,但是前面说了,IEEE规定了PHY的前16个寄存器的功能,因此如果你所使用的板子用的其他厂家的PHY芯片,也是可以看本小节的。 1、LAN8720A简介 LAN8720A是低功耗的10/100M单以太网PHY层芯片,可应用于机顶盒、网络打印机、嵌入式通信设备、IP电话等领域。I/O引脚电压符合IEEE802.3-2005标准。LAN8720A支持通过RMII接口与以太网MAC层通信,内置10-BASE-T/100BASE-TX全双工传输模块,支持10Mbps和100Mbps。LAN8720A可以通过自协商的方式选择与目的主机最佳的连接方式(速度和双工模式)。支持HP Auto-MDIX自动翻转功能,无需更换网线即可将连接更改为直连或交叉连接。 LAN8720A的主要特点如下: · 高性能的10/100M以太网传输模块 · 支持RMII接口以减少引脚数 · 支持全双工和半双工模式 · 两个状态LED输出 · 可以使用25M晶振以降低成本 · 支持自协商模式 · 支持HP Auto-MDIX自动翻转功能 · 支持SMI串行管理接口 · 支持MAC接口 LAN8720A功能框图如图69.2.2.1所示。
图69.2.2.1 LAN8720A功能框图 2、LAN8720A中断管理 LAN8720A的器件管理接口支持非IEEE 802.3规范的中断功能。当一个中断事件发生并且相应事件的中断位使能,LAN8720A就会在nINT(14脚)产生一个低电平有效的中断信号。LAN8720A的中断系统提供两种中断模式:主中断模式和复用中断模式。主中断模式是默认中断模式,LAN8720A上电或复位后就工作在主中断模式,当模式控制/状态寄存器(十进制地址为17)的ALTINT位为0时LAN8720A工作在主模式,当ALTINT位为1时工作在复用中断模式。正点原子的ALPHA开发板虽然讲LAN8720A的中断引脚连接到了I.MX6ULL上,但是并没有使用中断功能,关于中断的具体用法可以参考LAN8720A数据手册的29~30页。 3、PHY地址设置 MAC层通过MDIO/MDC总线对PHY进行读写操作,MDIO最多可以控制32个PHY芯片,通过不同的PHY芯片地址来对不同的PHY操作。LAN8720A通过设置RXER/PHYAD0引脚来设置其PHY地址,默认情况下为0,其地址设置如表69.2.2.1所示。 RXER/PHYAD0引脚状态 PHY地址 上拉 0X01 下拉(默认) 0X00
表69.2.2.1 LAN8720A地址设置 正点原子ALPHA开发板的ENET1网络的LAN8720A上的RXER/PHYAD0引脚为默认状态(原理图上有个10K下拉,但是没有焊接),因此ENET1上的LAN8720A地址为0。ENET2网络上的LAN8720A上的RXER/PHYAD0引脚接了个10K上拉电阻,因此ENET2上的LAN8720A地址为1。 4、nINT/REFCLKO配置 nINTSEL引脚(2号引脚)用于设置nINT/REFCLKO引脚(14号引脚)的功能。nINTSEL配置如表69.2.2.2所示。。 nINTSEL引脚值 模式 nINT/REFCLKO引脚功能 nINTSEL= 0 REF_CLK Out模式 nINT/REFCLKO作为REF_CLK时钟源 nINTSEL = 1(默认) REF_CLK In模式 nINT/REFCLKO作为中断引脚
表69.2.2.2 nINTSEL配置 对于正点原子的ALPHA开发板的两个LAN8720A而言,全都工作在默认的REF_CLK In模式下。当LAN8720A工作在REF_CLK In模式时,50MHz的外部时钟信号应接到LAN8720的XTAL1/CKIN引脚(5号引脚)上,如图69.2.2.3所示:
图69.2.2.3 REF_CLK连接外部50MHz时钟信号 为了降低成本,LAN8720A可以从外部的25MHz的晶振中产生REF_CLK时钟。到要使用此功能时应工作在REF_CLK Out模式时。当工作在REF_CLO Out模式时REF_CLK的时钟源如图69.2.2.4所示。
图69.2.2.4 REF_CLK Out模式时的REF_CLK时钟源 前面说了,正点原子的ALPHA开发板工作在REF_CLK In模式下,因此需要外部50MHz时钟信号,I.MX6ULL有专用的网络时钟引脚,因此ALPHA开发板是通过I.MX6ULL的ENET1_REF_CLK和ENET2_REF_CLK这两个网络时钟引脚来为LAN8720A提供50MHz的时钟。 5、LAN8720A内部寄存器 LAN8720A的前16个寄存器满足IEEE的要求,在这里我们只介绍几个常用的寄存器,首先是BCR(Basic Control Rgsister)寄存器,地址为0,BCR寄存器各位如表69.2.2.3所示。 位 描述 类型
表 69.2.2.3 BCR寄存器 我们说的配置PHY芯片,重点就是配置BCR寄存器,由于LAN8720A是个10/100M的PHY,因此表69.2.2.3中没有体现出1000M相关的配置。但是10/100M相关的配置是和IEEE的规定完全相符的,大家可以选择一个其他的10/100M的PHY芯片对比看一下,比如NXP官方EVK开发板使用的KSZ8081。 接下来看一下BSR(Basic Status Register)寄存器,地址为1。此寄存器为PHY的状态寄存器,通过此寄存器可以获取到PHY芯片的工作状态,BSR寄存器各位如表69.2.2.4所示:
表69.2.2.4 BSR寄存器 从表69.2.2.4可以看出,和IEEE标准规定相比,LAN8720A的BSR寄存器少了几个位,这个没关系的,不管什么PHY芯片,只要它实现了的位和IEEE规定相符就行。通过读取BSR寄存器的值我们可以得到当前的连接速度、双工状态和连接状态等。 接下来看一下LAN8720A的PHY ID寄存器1和ID寄存器2,地址为2和3,后面就成为寄存器2和寄存器3。这两个寄存器都是PHY的ID寄存器。IEEE规定寄存器2和寄存器3为PHY的ID寄存器,这两个寄存器组成一个32位的唯一ID值。IEEE规定了一叫做OUI的ID组成方式,全称是Organizationally Unique Identifier,OUI一共32位,分为三部分:22位的ID+6位厂商型号ID+4位厂商版本ID,组成如图69.2.2.5所示:
图69.2.2.5 OUI组成方式
表69.2.2.5 PHY ID寄存器2 ID寄存器3如表69.2.2.6所示:
表69.2.2.6 PHY ID寄存器3 最后来看一下LAN8720A的特殊控制/状态寄存器,此寄存器地址为31,寄存器内容是LAN8720A厂商自定义的,此寄存器的各个位如表69.2.2.7所示:
表69.2.2.7 LAN8720A特殊控制/状态寄存器 特殊控制/状态寄存器中我们关心的是bit2~bit4这三位,因为通过这3位来确定连接的状态和速度,关于LAN8720A这个PHY就讲解到这里。 69.3 Linux内核网络驱动框架 69.3.1 net_device结构体 Linux内核使用net_device结构体表示一个具体的网络设备,net_device是整个网络驱动的灵魂。网络驱动的核心就是初始化net_device结构体中的各个成员变量,然后将初始化完成以后的net_device注册到Linux内核中。net_device结构体定义在include/linux/netdevice.h中,net_device是一个庞大的结构体,内容如下(有缩减):
示例代码69.3.1.1 net_device结构体
1 struct net_device {
2 char name[IFNAMSIZ];
3 struct hlist_node name_hlist;
4 char *ifalias;
5 /* 6 * I/O specific fields 7 * FIXME: Merge these and struct ifmap into one 8 */
9 unsigned long mem_end;
10 unsigned long mem_start;
11 unsigned long base_addr;
12 int irq;
13
14 atomic_t carrier_changes;
15
16 /* 17 * Some hardware also needs these fields (state,dev_list, 18 * napi_list,unreg_list,close_list) but they are not 19 * part of the usual set specified in Space.c. 20 */
21
22 unsigned long state;
23
24 struct list_head dev_list;
25 struct list_head napi_list;
26 struct list_head unreg_list;
27 struct list_head close_list;
......
60 const struct net_device_ops *netdev_ops;
61 const struct ethtool_ops *ethtool_ops;
62 #ifdef CONFIG_NET_SWITCHDEV
63 const struct swdev_ops *swdev_ops;
64 #endif
65
66 const struct header_ops *header_ops;
67
68 unsigned int flags;
......
77 unsigned char if_port;
78 unsigned char dma;
79
80 unsigned int mtu;
81 unsigned short type;
82 unsigned short hard_header_len;
83
84 unsigned short needed_headroom;
85 unsigned short needed_tailroom;
86
87 /* Interface address info. */
88 unsigned char perm_addr[MAX_ADDR_LEN];
89 unsigned char addr_assign_type;
90 unsigned char addr_len;
......
130 /* 131 * Cache lines mostly used on receive path (including eth_type_trans()) 132 */
133 unsigned long last_rx;
134
135 /* Interface address info used in eth_type_trans() */
136 unsigned char *dev_addr;
137
138
139 #ifdef CONFIG_SYSFS
140 struct netdev_rx_queue *_rx;
141
142 unsigned int num_rx_queues;
143 unsigned int real_num_rx_queues;
144
145 #endif
......
158 /* 159 * Cache lines mostly used on transmit path 160 */
161 struct netdev_queue *_tx ____cacheline_aligned_in_smp;
162 unsigned int num_tx_queues;
163 unsigned int real_num_tx_queues;
164 struct Qdisc *qdisc;
165 unsigned long tx_queue_len;
166 spinlock_t tx_global_lock;
167 int watchdog_timeo;
......
173 /* These may be needed for future network-power-down code. */
174
175 /* 176 * trans_start here is expensive for high speed devices on SMP, 177 * please use netdev_queue->trans_start instead. 178 */
179 unsigned long trans_start;
......
248 struct phy_device *phydev;
249 struct lock_class_key *qdisc_tx_busylock;
250 };
下面介绍一些关键的成员变量,如下:
第2行:name是网络设备的名字。
第9行:mem_end是共享内存结束地址。
第10行:mem_start是共享内存起始地址。
第11行:base_addr是网络设备I/O地址。
第12行:irq是网络设备的中断号。
第24行:dev_list是全局网络设备列表。
第25行:napi_list是napi网络设备的列表入口。
第26行:unreg_list是注销(unregister)的网络设备列表入口。
第27行:close_list是关闭的网络设备列表入口。
第60行:netdev_ops是网络设备的操作集函数,包含了一系列的网络设备操作回调函数,类似字符设备中的file_operations,稍后会讲解netdev_ops结构体。
第61行:ethtool_ops是网络管理工具相关函数集,用户空间网络管理工具会调用此结构体中的相关函数获取网卡状态或者配置网卡。
第66行:header_ops是头部的相关操作函数集,比如创建、解析、缓冲等。
第68行:flags是网络接口标志,标志类型定义在include/uapi/linux/if.h文件中,为一个枚举类型,内容如下:
示例代码69.3.1.2 网络标志类型
1 enum net_device_flags {
2 IFF_UP = 1<<0, /* sysfs */
3 IFF_BROADCAST = 1<<1, /* volatile */
4 IFF_DEBUG = 1<<2, /* sysfs */
5 IFF_LOOPBACK = 1<<3, /* volatile */
6 IFF_POINTOPOINT = 1<<4, /* volatile */
7 IFF_NOTRAILERS = 1<<5, /* sysfs */
8 IFF_RUNNING = 1<<6, /* volatile */
9 IFF_NOARP = 1<<7, /* sysfs */
10 IFF_PROMISC = 1<<8, /* sysfs */
11 IFF_ALLMULTI = 1<<9, /* sysfs */
12 IFF_MASTER = 1<<10, /* volatile */
13 IFF_SLAVE = 1<<11, /* volatile */
14 IFF_MULTICAST = 1<<12, /* sysfs */
15 IFF_PORTSEL = 1<<13, /* sysfs */
16 IFF_AUTOMEDIA = 1<<14, /* sysfs */
17 IFF_DYNAMIC = 1<<15, /* sysfs */
18 IFF_LOWER_UP = 1<<16, /* volatile */
19 IFF_DORMANT = 1<<17, /* volatile */
20 IFF_ECHO = 1<<18, /* volatile */
21 };
继续回到示例代码69.3.1.1接着看net_device结构体。
第77行:if_port指定接口的端口类型,如果设备支持多端口的话就通过if_port来指定所使用的端口类型。可选的端口类型定义在include/uapi/linux/netdevice.h中,为一个枚举类型,如下所示:
示例代码69.3.1.3 端口类型
1 enum {
2 IF_PORT_UNKNOWN = 0,
3 IF_PORT_10BASE2,
4 IF_PORT_10BASET,
5 IF_PORT_AUI,
6 IF_PORT_100BASET,
7 IF_PORT_100BASETX,
8 IF_PORT_100BASEFX
9 };
第78行:dma是网络设备所使用的DMA通道,不是所有的设备都会用到DMA。
第80行:mtu是网络最大传输单元,为1500。
第81行:type用于指定ARP模块的类型,以太网的ARP接口为ARPHRD_ETHER,Linux内核所支持的ARP协议定义在include/uapi/linux/if_arp.h中,大家自行查阅。
第88行:perm_addr是永久的硬件地址,如果某个网卡设备有永久的硬件地址,那么就会填充perm_addr。
第90行:addr_len是硬件地址长度。
第133行:last_rx是最后接收的数据包时间戳,记录的是jiffies。
第136行:dev_addr也是硬件地址,是当前分配的MAC地址,可以通过软件修改。
第140行:_rx是接收队列。
第142行:num_rx_queues是接收队列数量,在调用register_netdev注册网络设备的时候会分配指定数量的接收队列。
第143行:real_num_rx_queues是当前活动的队列数量。
第161行:_tx是发送队列。
第162行:num_tx_queues是发送队列数量,通过alloc_netdev_mq函数分配指定数量的发送队列。
第163行:real_num_tx_queues是当前有效的发送队列数量。
第179行:trans_start是最后的数据包发送的时间戳,记录的是jiffies。
第248行:phydev是对应的PHY设备。
1、申请net_device
编写网络驱动的时候首先要申请net_device,使用alloc_netdev函数来申请net_device,这是一个宏,宏定义如下:
示例代码69.3.1.4 alloc_netdev
1 #define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \
2 alloc_netdev_mqs(sizeof_priv, name, name_assign_type, setup, 1, 1)
可以看出alloc_netdev的本质是alloc_netdev_mqs函数,此函数原型如下
struct net_device * alloc_netdev_mqs ( int sizeof_priv, const char *name, void (*setup) (struct net_device *)) unsigned int txqs, unsigned int rxqs); 函数参数和返回值含义如下: sizeof_priv:私有数据块大小。 name:设备名字。 setup:回调函数,初始化设备的设备后调用此函数。 txqs:分配的发送队列数量。 rxqs:分配的接收队列数量。 返回值:如果申请成功的话就返回申请到的net_device指针,失败的话就返回NULL。 事实上网络设备有多种,大家不要以为就只有以太网一种。Linux内核内核支持的网络接口有很多,比如光纤分布式数据接口(FDDI)、以太网设备(Ethernet)、红外数据接口(InDA)、高性能并行接口(HPPI)、CAN网络等。内核针对不同的网络设备在alloc_netdev的基础上提供了一层封装,比如我们本章讲解的以太网,针对以太网封装的net_device申请函数是alloc_etherdev和,这也是一个宏,内容如下:
示例代码69.3.1.5 alloc_etherdev函数
1 #define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
2 #define alloc_etherdev_mq(sizeof_priv, count) alloc_etherdev_mqs(sizeof_priv, count, count)
可以看出,alloc_etherdev最终依靠的是alloc_etherdev_mqs函数,此函数就是对alloc_netdev_mqs的简单封装,函数内容如下:
示例代码69.3.1.6 alloc_etherdev_mqs函数
1 struct net_device *alloc_etherdev_mqs(int sizeof_priv,
2 unsigned int txqs,
3 unsigned int rxqs)
4 {
5 return alloc_netdev_mqs(sizeof_priv, "eth%d", NET_NAME_UNKNOWN,
6 ether_setup, txqs, rxqs);
7 }
第5行调用alloc_netdev_mqs来申请net_device,注意这里设置网卡的名字为“eth%d”,这是格式化字符串,大家进入开发板的linux系统以后看到的“eth0”、“eth1”这样的网卡名字就是从这里来的。同样的,这里设置了以太网的setup函数为ether_setup,不同的网络设备其setup函数不同,比如CAN网络里面setup函数就是can_setup。
ether_setup函数会对net_device做初步的初始化,函数内容如下所示:
示例代码69.3.1.7 ether_setup函数
1 void ether_setup(struct net_device *dev)
2 {
3 dev->header_ops = ð_header_ops;
4 dev->type = ARPHRD_ETHER;
5 dev->hard_header_len = ETH_HLEN;
6 dev->mtu = ETH_DATA_LEN;
7 dev->addr_len = ETH_ALEN;
8 dev->tx_queue_len = 1000; /* Ethernet wants good queues */
9 dev->flags = IFF_BROADCAST|IFF_MULTICAST;
10 dev->priv_flags |= IFF_TX_SKB_SHARING;
11
12 eth_broadcast_addr(dev->broadcast);
13 }
关于net_device的申请就讲解到这里,对于网络设备而言,使用alloc_etherdev或alloc_etherdev_mqs来申请net_device。NXP官方编写的网络驱动就是采用alloc_etherdev_mqs来申请net_device。
2、删除net_device
当我们注销网络驱动的时候需要释放掉前面已经申请到的net_device,释放函数为free_netdev,函数原型如下:
void free_netdev(struct net_device *dev) 函数参数和返回值含义如下: dev:要释放掉的net_device指针。 返回值:无。 3、注册net_device net_device申请并初始化完成以后就需要向内核注册net_device,要用到函数register_netdev,函数原型如下: int register_netdev(struct net_device *dev) 函数参数和返回值含义如下: dev:要注册的net_device指针。 返回值:0 注册成功,负值 注册失败。 3、注销net_device 既然有注册,那么必然有注销,注销net_device使用函数unregister_netdev,函数原型如下: void unregister_netdev(struct net_device *dev) 函数参数和返回值含义如下: dev:要注销的net_device指针。 返回值:无。 69.3.2 net_device_ops结构体 net_device有个非常重要的成员变量:netdev_ops,为net_device_ops结构体指针类型,这就是网络设备的操作集。net_device_ops结构体定义在include/linux/netdevice.h文件中,net_device_ops结构体里面都是一些以“ndo_”开头的函数,这些函数就需要网络驱动编写人员去实现,不需要全部都实现,根据实际驱动情况实现其中一部分即可。结构体内容如下所示(结构体比较大,这里有缩减):
示例代码69.3.2.1 net_device_ops结构体
1 struct net_device_ops {
2 int (*ndo_init)(struct net_device *dev);
3 void (*ndo_uninit)(struct net_device *dev);
4 int (*ndo_open)(struct net_device *dev);
5 int (*ndo_stop)(struct net_device *dev);
6 netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb,
7 struct net_device *dev);
8 u16 (*ndo_select_queue)(struct net_device *dev,
9 struct sk_buff *skb,
10 void *accel_priv,
11 select_queue_fallback_t fallback);
12 void (*ndo_change_rx_flags)(struct net_device *dev,
13 int flags);
14 void (*ndo_set_rx_mode)(struct net_device *dev);
15 int (*ndo_set_mac_address)(struct net_device *dev,
16 void *addr);
17 int (*ndo_validate_addr)(struct net_device *dev);
18 int (*ndo_do_ioctl)(struct net_device *dev,
19 struct ifreq *ifr, int cmd);
20 int (*ndo_set_config)(struct net_device *dev,
21 struct ifmap *map);
22 int (*ndo_change_mtu)(struct net_device *dev,
23 int new_mtu);
24 int (*ndo_neigh_setup)(struct net_device *dev,
25 struct neigh_parms *);
26 void (*ndo_tx_timeout) (struct net_device *dev);
......
36 #ifdef CONFIG_NET_POLL_CONTROLLER
37 void (*ndo_poll_controller)(struct net_device *dev);
38 int (*ndo_netpoll_setup)(struct net_device *dev,
39
标签: 缓冲小电阻2rj48pin座子连接器