【正点原子Linux连载】 第四十章 Linux网络驱动实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

时间:2024-05-08 17:10:41

1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban

第四十章 Linux网络驱动实验

网络驱动是linux里面驱动三巨头之一,linux下的网络功能非常强大,嵌入式linux中也常常用到网络功能。前面我们已经讲过了字符设备驱动和块设备驱动,本章我们就来学习一下linux里面的网络设备驱动。

40.1 嵌入式网络简介
40.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等),所以三星的开发板都是通过外置的DM9000来完成有线网络功能的,DM9000对SoC提供了一个SRAM接口,SOC会以SRAM的方式操作DM9000。
有些外置的网络芯片更强大,内部甚至集成了硬件TCP/IP协议栈,对外提供一个SPI接口,比如W5500。这个一般用于单片机领域,单片机通过SPI接口与W5500进行通信,由于W5500内置了硬件TCP/IP协议栈,因此单片机就不需要移植负责的软件协议栈,直接通过SPI来操作W5500,简化了单片机联网方案。
这种方案的优点就是让不支持网络的SoC能够另辟蹊径,实现网络功能,但是缺点就是网络效率不高,因为一般芯片内置的MAC会有网络加速引擎,比如网络专用DMA,网络处理效率会很高。而且此类芯片网速都不快,基本就是10/100M。另外,相比PHY芯片而言,此类芯片的成本也比较高,可选择比较少。
SoC与外部MAC+PHY芯片的连接如图40.1.1.1所示:
在这里插入图片描述

图40.1.1.1 主控SOC与外置MAC+PHY芯片连接
2、SOC内部集成网络MAC外设
我们一般说某个SoC支持网络,说的就是他内部集成网络MAC外设,此时我们还需要外接一个网络PHY芯片。此时就有朋友会有疑问,PHY芯片不能也集成进SoC吗?笔者目前还没见过将PHY也集成到芯片里面的SoC。
一般常见的通用SoC都会集成网络MAC外设,比如RK系列、NXP的I.MX系列以及STM32MP1系列,内部集成网络MAC的优点如下:
①、内部MAC外设会有专用的加速模块,比如专用的DMA,加速网速数据的处理。
②、网速快,可以支持10/100/1000M网速。
③、外接PHY可选择性多,成本低。
内部的MAC外设会通过相应的接口来连接外部PHY芯片,根据数据传输模式不同,大致可以分为以下两类:
①、MII/RMII接口:支持10Mbit/s和100Mbit/s数据传输模式;
②、GMII/RGMII接口:支持10Mbit/s、100Mbit/s以及1000Mbit/s数据传输模式。
从这里可以知道,MII/RMII接口最大传输速率为100Mbit/s,而GMII/RGMII接口最大传输速率可达1000Mbit/s;所以笔者一般把MII/RMII称为百兆以太网接口,而把GMII/RGMII称为千兆以太网接口。
关于这两组接口更加详细的内容会在后面给大家进行介绍,MII/RMII或GMII/RGMII接口是用来传输网络数据的,另外主控SoC需要配置或读取PHY芯片,也就是读写PHY的内部寄存器,所以还需要一个控制接口,叫做MIDO,MDIO很类似IIC,也是两根线,一根数据线叫做MDIO,一根时钟线叫做MDC。
SoC内部MAC外设与外部PHY芯片的连接如图40.1.1.2、图40.1.1.3所示:
在这里插入图片描述

图40.1.1.2 内部MAC与外部PHY之间的连接(千兆以太网)
在这里插入图片描述

图40.1.1.3 内部MAC与外部PHY之间的连接(百兆以太网)
大家在做项目的时候,如果要用到网络功能,强烈建议大家选择内部带有网络MAC外设的主控SOC!比如RK3568就有两颗10M/100M/1000M的网络MAC外设,正点原子的ATK-DLRK3568开发板板载了两颗PHY芯片,PHY芯片型号为 YT8531C,Pin to Pin 兼容 RTL8211F,因此在我们的开发板上 YT8531C 和 RTL8211F 可以随意互换。
因此,本章节只讲解SOC内部MAC+外置PHY芯片这种方案。
40.1.2 MII/RMII、GMII/RGMII接口
前面我们说了,内部MAC通过MII/RMII接口或者GMII/RGMII接口来与外部的PHY芯片连接,完成网络数据传输,本节我们就来学习一下什么是MII/GMII、GMII/RGMII接口。
1、MII接口
MII全称是Media Independent Interface,直译过来就是介质独立接口,它是IEEE-802.3定义的以太网标准接口,MII接口用于以太网MAC连接PHY芯片,连接示意图如图40.1.2.1所示:
在这里插入图片描述

图40.1.2.1 MII接口
MII接口一共有16根信号线,含义如下:
TX_CLK:发送时钟,如果网速为100M的话时钟频率为25MHz,10M网速的话时钟频率为2.5MHz,此时钟由PHY产生并发送给MAC。
TX_EN:发送使能信号。
TX_ER:发送错误信号,高电平有效,表示TX_ER有效期内传输的数据无效。10Mpbs网速下TX_ER不起作用。
TXD[3:0]:发送数据信号线,一共4根。
RXD[3:0]:接收数据信号线,一共4根。
RX_CLK:接收时钟信号,如果网速为100M的话时钟频率为25MHz,10M网速的话时钟频率为2.5MHz,RX_CLK也是由PHY产生的。
RX_ER:接收错误信号,高电平有效,表示RX_ER有效期内传输的数据无效。10Mpbs网速下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芯片的示意图如图40.1.2.2所示:
在这里插入图片描述

图40.1.2.2 RMII接口
TX_EN:发送使能信号。
TXD[1:0]:发送数据信号线,一共2根。
RXD[1:0]:接收数据信号线,一共2根。
CRS_DV:相当于MII接口中的RX_DV和CRS这两个信号的混合。
REF_CLK:参考时钟,由外部时钟源提供,频率为50MHz。这里与MII不同,MII的接收和发送时钟是独立分开的,而且都是由PHY芯片提供的。
3、GMII接口
GMII(Gigabit Media Independant Interface),千兆MII接口。GMII采用8位接口数据,工作时钟125MHz,因此传输速率可达1000Mbps;同时兼容MII所规定的10/100Mbps工作方式。GMII接口数据结构符合IEEE以太网标准,该接口定义见IEEE 802.3-2000。信号定义如下:
在这里插入图片描述

图40.1.2.3 GMII接口
GTX_CLK:1000M工作模式下的发送时钟(125MHz)。
TX_EN:发送使能信号。
TX_ER:发送错误信号,高电平有效,表示TX_ER有效期内传输的数据无效。
TXD[7:0]:发送数据信号线,一共8根。
RXD[7:0]:接收数据信号线,一共8根。
RX_CLK:接收时钟信号。
RX_ER:接收错误信号,高电平有效,表示RX_ER有效期内传输的数据无效。
RX_DV:接收数据有效,作用类似TX_EN。
CRS:载波侦听信号。
COL:冲突检测信号。
与MII接口相比,GMII的数据宽度由4位变为8位,GMII接口中的控制信号如TX_ER、TX_EN、RX_ER、RX_DV、CRS和COL的作用同MII接口中的一样,发送参考时钟GTX_CLK和接收参考时钟RX_CLK的频率均为125MHz(在1000Mbps工作模式下)。
在实际应用中,绝大多数GMII接口都是兼容MII接口的,所以,一般的GMII接口都有两个发送参考时钟:TX_CLK和GTX_CLK(两者的方向是不一样的,前面已经说过了),在用作MII模式时,使用TX_CLK和8根数据线中的4根。
4、RGMII接口
RGMII(Reduced Gigabit Media Independant Interface),精简版GMII接口。将接口信号线数量从24根减少到14根(COL/CRS端口状态指示信号,这里没有画出),时钟频率仍旧为125MHz,TX/RX数据宽度从8为变为4位,为了保持1000Mbps的传输速率不变,RGMII接口在时钟的上升沿和下降沿都采样数据,在参考时钟的上升沿发送GMII接口中的TXD[3:0]/RXD[3:0],在参考时钟的下降沿发送GMII接口中的TXD[7:4]/RXD[7:4]。RGMII同时也兼容100Mbps和10Mbps两种速率,此时参考时钟速率分别为25MHz和2.5MHz。
TX_EN信号线上传送TX_EN和TX_ER两种信息,在TX_CLK的上升沿发送TX_EN,下降沿发送TX_ER;同样的,RX_DV信号线上也传送RX_DV和RX_ER两种信息,在RX_CLK的上升沿发送RX_DV,下降沿发送RX_ER。
RGMII接口定义如下所示:
在这里插入图片描述

图40.1.2.4 RGMII接口
关于这些接口定义相关的内容我们就讲到这里了,除了上面说到4种接口以外,还有其他接口,比如SMII、SSMII和SGMII等,关于其他接口基本都是大同小异的,这里就不做讲解了。正点原子的ATK-DLRK3568开发板上的网口是采用RGMII接口来连接MAC与外部PHY芯片。
40.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(百兆网)或GMII/RGMII(千兆网)和MDIO接口,另外可能还需要复位、中断等其他引脚。
40.1.4 RJ45接口
网络设备是通过网线连接起来的,插入网线的叫做RJ45座,如图40.1.4.1所示:
在这里插入图片描述

图40.1.4.1 RJ45座子
RJ45座要与PHY芯片连接在一起,但是中间需要一个网络变压器,网络编译器用于隔离以及滤波等,网络变压器也是一个芯片,外形一般如图40.1.4.2所示:
在这里插入图片描述

图40.1.4.2 网络变压器
但是现在很多RJ45座子内部已经集成了网络变压器,比如正点原子的ATK-DLRK3568开发板所使用的ATK911130A就是内置网络变压器的RJ45座。内置网络变压器的RJ45座和不内置的引脚一样,但是一般不内置的RJ45座会短一点。因此,大家在画板的时候一定要考虑你所使用的RJ45座是否内置网络变压器,如果不内置的话就要自行添加网络变压器部分电路!同理,如果你所设计的硬件是需要内置网络变压器的RJ45座,肯定不能随便焊接一个不内置变压器的RJ45座,否则网络工作不正常!
RJ45座子上一般有两个灯,一个黄色(橙色),一个绿色,一般绿色亮的话表示网络连接正常,黄色闪烁的话说明当前正在进行网络通信,当然了有时候两个灯的状态会反过来,大家以实际为准。这两个灯由PHY芯片控制,对于千M网络PHY芯片,一般PHY芯片会有3个LED灯引脚,多了一个千M网络状态指示灯。PHY芯片会通过这几个LED灯引脚来连接RJ45座上的这两个灯(千M PHY会有3个LED灯引脚,一般硬件设计人员会自行选择将其中的哪两个连接到RJ45座上)。由于正点原子ATK-DLRK3568开发板采用的千M网络PHY,所以后面只讲千M网络。内部MAC+外部PHY+RJ45座(内置网络变压器)就组成了一个完整的嵌入式网络接口硬件,如图40.1.4.3所示:
在这里插入图片描述

图40.1.4.3 嵌入式网络硬件接口示意图
40.1.5 RK3568 GMAC接口简介
RK3568内核集成了两个10M/100M/1000M的网络MAC,符合IEEE802.3-2002标准,MAC层支持全双工或者半双工模式下运行。MAC可编程,有直接存储器接口的专用DMA,将数据格式化符合IEEE802.3-2002标准的数据包,并把这些数据传输到以太网的物理接口中(PHY),还可以把数据包从RXFIFO移动到微处理器的存储器中。
RK3568内部ENET外设主要特性如下:
①、支持全工和半双工操作。
②、全双工流控制操作(IEEE 802.3X 暂停包和优先级流控制)
③、报头和帧起始数据(SFD)在发送模式下自动插入、在接收中自动删除。
④、可逐帧控制CRC和pad自动生成
⑤、可编程数据包长度,支持标准以太网数据包或高达16KB的巨型以太网数据包
⑥、可编程数据包间隙
⑦、两组FIFO:一个具有可编程阈值功能的4096字节发送FIFO和一个具有可配置阈值功能的4096字节接收FIFO。
……
RK3568的GMAC外设内容比较多,详细的介绍请查阅开发板光盘->03、核心板资料->核心板板载芯片资料->Rockchip RK3568 TRM Part2 V1.1-20210301(RK3568参考手册 2).pdf。我们在编写驱动的时候其实并不需要关注GMAC控制器外设的具体内容,因为这部分驱动是SoC厂商写的,我们重点关注的是更换PHY芯片以后哪里需要调整。
40.2 PHY芯片详解
40.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 协议英文原版已经放到了开发板光盘中,路径为:06、参考资料802.3 协议英文原版_2018年.pdf 打开此文档,此文档有5600页,按照SECTION进行分类,一共8个SECTION。选中“802.3-2018_SECTION2”,找到“22.2.4 Management functions”章节,此章节对PHY的前16个寄存器功能进行了规定,如图40.2.1.1所示:

在这里插入图片描述

图40.2.1.1 IEEE规定的前16个寄存器
关于这16个寄存器的内容协议里面也进行了详细的讲解,这里就不分析了。我们后面会以正点原子ATK-DLRK3568开发板所使用的YT8531C(RTL8211F)这个PHY为例,详细的分析一下PHY芯片的寄存器。
40.2.2 YT8531C详解
YT8531C这颗PHY芯片是由裕太微电子股份有限公司的,它的数据手册已经放到开发板资料包里,路径为:开发板A盘-基础资料07、硬件资料02、底板板载芯片资料 YT8531(D)H_YT8531(D)C_YT8531P_Datasheet_v1.00.pdf。
前面说了,PHY芯片的前16个寄存器都是一样的,而且只需要使用这前16个寄存器基本就可以驱动起来PHY芯片,这里就不详细讲解了。
1、YT8531C简介
YT8531C是高度集成的以太网收发器,符合10Base-T、100Base-TX和1000Base-T IEEE 802.3标准。它提供了所有通过CAT.5 UTP电缆收发以太网数据包所需的必要物理层功能。
YT8531C使用最新的DSP技术和模拟前端(AFE),可以通过UTP电缆进行高速数据传输和接收。RTL8211F中实现了交叉检测和自动校正、极性校正、自适应均衡、串扰消除、回声消除、定时恢复和错误校正等功能,以提供10Mbps,100Mbps或1000Mbps的强大收发功能。
MAC和PHY之间的数据传输是通过RGMII接口进行的,YT8531C支持RGMII的1.5V信号。
YT8531C的主要特点如下:
·兼容1000Base-T IEEE 802.3ab标准。
·兼容100Base-TX IEEE 802.3u标准。
·兼容10Base-T IEEE 802.3标准。
·支持GMII、RGMII接口。
·支持IEEE 802.3az-2010(节能以太网)。
·内置LAN唤醒(WOL)。
·支持中断、并行检测、交叉检测、自动校正、自动极性校正。
·支持120m的1000Base-T的CAT.5类电缆。
·支持RGMII的1.5V信号。
……
YT8531C千兆PHY的系统应用场景如下:
·数字电视(DTV)。
·媒体访问单元(MAU)。
·通讯和网络提升板(CNR)。
·游戏机。
·打印机和办公机器。
·DVD播放机和刻录机。
·以太网集线器、交换机。
YT8531C功能框图如下所示:
在这里插入图片描述

图40.2.2.1 YT8531C详解
2、节能以太网(EEE)
YT8531C支持IEEE 802.3az-2010(也称为节能以太网(EEE)),速率为10Mbps、100Mbps或1000Mbps时它提供了一个协议,可根据链路利用率协调进出较低功耗级别(低功耗空闲模式)的转换,当没有数据包传输时,系统进入低功耗空闲模式以降低功耗,一旦需要发送数据包,系统将返回正常模式,并且无需更改链接状态和丢弃/破坏帧即可进行此操作。
为了节省功率,当系统处于低功耗空闲模式时,大多数电路功能都被禁用;但是,低功耗空闲模式的过渡时间保持足够小,以使得对上层协议和应用程序透明。
3、中断管理
每当YT8531C检测到介质状态发生变化时,它就会将中断引脚(INTB)拉低发出中断事件。SoC MAC端会感应到状态更改,并通过MDC/MDIO接口访问相关的寄存器。
一旦MAC通过MDC/MDIO读取了这些状态寄存器,就会将INTB置为无效。所以不需要通过轮训的方式去反复查询状态寄存器的变化。
4、PHY地址设置
MAC层通过MDIO/MDC总线对PHY进行读写操作,MDIO最多可以控制32个PHY芯片,通过不同的PHY芯片地址来对不同的PHY操作。YT8531C通过配置PHYAD[2:0]这三个引脚来设置PHY地址,通常情况下都是设置为地址0X00,配置如下图40.2.2.2所示。
在这里插入图片描述

图40.2.2.2 YT8531C PHY地址设置
ATK-RLDK3568开发板上的YT8531C的地址为0X00。
芯片YT8511C还有很多设置,这边就不一一列出了,大家直接查看芯片原理图就知道了。
40.3 Linux内核网络驱动框架
40.3.1 new_device结构体
Linux内核使用net_device结构体表示一个具体的网络设备,net_device是整个网络驱动的灵魂。网络驱动的核心就是初始化net_device结构体中的各个成员变量,然后将初始化完成以后的net_device注册到Linux内核中。net_device结构体定义在include/linux/netdevice.h中,net_device是一个庞大的结构体,内容如下(有缩减):
示例代码40.3.1.1 net_device结构体

1  struct net_device {
2   char            name[IFNAMSIZ];
3   struct hlist_node   name_hlist;
4   struct dev_ifalias  __rcu *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;
......
21 
22  struct list_head    dev_list;
23  struct list_head    napi_list;
24  struct list_head    unreg_list;
25  struct list_head    close_list;
26  struct list_head    ptype_all;
27  struct list_head    ptype_specific;
......
59  const struct net_device_ops *netdev_ops;
60  const struct ethtool_ops *ethtool_ops;
......
79  const struct header_ops *header_ops;
80 
81  unsigned int        flags;
82  unsigned int        priv_flags;
83 
84  unsigned short      gflags;
85  unsigned short      padded;
86 
87  unsigned char       operstate;
88  unsigned char       link_mode;
89 
90  unsigned char       if_port;
91  unsigned char       dma;
......
98  unsigned int        mtu;
99  unsigned int        min_mtu;
100 unsigned int        max_mtu;
101 unsigned short      type;
102 unsigned short      hard_header_len;
103 unsigned char       min_header_len;
104
105 unsigned short      needed_headroom;
106 unsigned short      needed_tailroom;
107
108 /* Interface address info. */
109 unsigned char       perm_addr[MAX_ADDR_LEN];
110 unsigned char       addr_assign_type;
111 unsigned char       addr_len;
112 unsigned char       upper_level;
......
163 unsigned char       *dev_addr;
164
165 struct netdev_rx_queue  *_rx;
166 unsigned int        num_rx_queues;
167 unsigned int        real_num_rx_queues;
......
191 struct netdev_queue *_tx ____cacheline_aligned_in_smp;
192 unsigned int        num_tx_queues;
193 unsigned int        real_num_tx_queues;
......
264 /* for setting kernel sock attribute on TCP connection setup */
265#define GSO_MAX_SIZE     65536
266 unsigned int        gso_max_size;
267#define GSO_MAX_SEGS     65535
268 u16         gso_max_segs;
269
270#ifdef CONFIG_DCB
271 const struct dcbnl_rtnl_ops *dcbnl_ops;
272#endif
273 s16         num_tc;
274 struct netdev_tc_txq    tc_to_txq[TC_MAX_QUEUE];
275 u8          prio_tc_map[TC_BITMASK + 1];
276
277#if IS_ENABLED(CONFIG_FCOE)
278 unsigned int        fcoe_ddp_xid;
279#endif
280#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
281 struct netprio_map __rcu *priomap;
282#endif
283 struct phy_device   *phydev;
284 struct sfp_bus      *sfp_bus;
......
299};
下面介绍一些关键的成员变量,如下:
第2行:name是网络设备的名字。

第9行:mem_end是共享内存结束地址。
第10行:mem_start是共享内存起始地址。
第11行:base_addr是网络设备I/O地址。
第12行:irq是网络设备的中断号。
第22行:dev_list是全局网络设备列表。
第23行:napi_list是napi网络设备的列表入口。
第24行:unreg_list是注销(unregister)的网络设备列表入口。
第25行:close_list是关闭的网络设备列表入口。
第59行:netdev_ops是网络设备的操作集函数,包含了一系列的网络设备操作回调函数,类似字符设备中的file_operations,稍后会讲解netdev_ops结构体。
第60行:ethtool_ops是网络管理工具相关函数集,用户空间网络管理工具会调用此结构体中的相关函数获取网卡状态或者配置网卡。
第79行:header_ops是头部的相关操作函数集,比如创建、解析、缓冲等。
第81行:flags是网络接口标志,标志类型定义在include/uapi/linux/if.h文件中,为一个枚举类型,内容如下:
示例代码40.3.1.2 网络标志类型

81  enum net_device_flags {
82  /* for compatibility with glibc net/if.h */
83  #if __UAPI_DEF_IF_NET_DEVICE_FLAGS
84      IFF_UP           	= 1<<0,  /* sysfs */
85      IFF_BROADCAST   	= 1<<1,  /* volatile */
86      IFF_DEBUG        	= 1<<2,  /* sysfs */
87      IFF_LOOPBACK     	= 1<<3,  /* volatile */
88      IFF_POINTOPOINT 	= 1<<4,  /* volatile */
89      IFF_NOTRAILERS   	= 1<<5,  /* sysfs */
90      IFF_RUNNING    	= 1<<6,  /* volatile */
91      IFF_NOARP        	= 1<<7,  /* sysfs */
92      IFF_PROMISC       	= 1<<8,  /* sysfs */
93      IFF_ALLMULTI     	= 1<<9,  /* sysfs */
94      IFF_MASTER       	= 1<<10, /* volatile */
95      IFF_SLAVE         	= 1<<11, /* volatile */
96      IFF_MULTICAST   	= 1<<12, /* sysfs */
97      IFF_PORTSEL      	= 1<<13, /* sysfs */
98      IFF_AUTOMEDIA    	= 1<<14, /* sysfs */
99      IFF_DYNAMIC      	= 1<<15, /* sysfs */
100 #endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS */
101 #if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO
102     IFF_LOWER_UP    	= 1<<16, /* volatile */
103     IFF_DORMANT      	= 1<<17, /* volatile */
104     IFF_ECHO         	= 1<<18, /* volatile */
105 #endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */
106 };
继续回到示例代码40.3.1.1中,接着看net_device结构体。

第90行:if_port指定接口的端口类型,如果设备支持多端口的话就通过if_port来指定所使用的端口类型。可选的端口类型定义在include/uapi/linux/netdevice.h中,为一个枚举类型,如下所示:
示例代码40.3.1.3 端口类型

49 enum {
50 		IF_PORT_UNKNOWN = 0,
51    	IF_PORT_10BASE2,
52    	IF_PORT_10BASET,
53     	IF_PORT_AUI,
54     	IF_PORT_100BASET,
55     	IF_PORT_100BASETX,
56    	IF_PORT_100BASEFX
57 };
第91行:dma 是网络设备所使用的 DMA 通道,不是所有的设备都会用到 DMA。

第98行:mtu 是网络最大传输单元,为 1500。
第101行:type 用于指定 ARP 模块的类型,以太网的 ARP 接口为 ARPHRD_ETHER,Linux内核所支持的 ARP 协议定义在 include/uapi/linux/if_arp.h 中,大家自行查阅。
第109行:perm_addr是永久的硬件地址,如果某个网卡设备有永久的硬件地址,那么就会填充perm_addr。
第111行:addr_len是硬件地址长度。
第163行:dev_addr也是硬件地址,是当前分配的MAC地址,可以通过软件修改。
第165行:_rx是接收队列。
第166行:num_rx_queues是接收队列数量,在调用register_netdev注册网络设备的时候会分配指定数量的接收队列。
第167行:real_num_rx_queues是当前活动的队列数量。
第191行:_tx是发送队列。
第192行:num_tx_queues是发送队列数量,通过alloc_netdev_mq函数分配指定数量的发送队列。
第193行:real_num_tx_queues是当前有效的发送队列数量。
第283行:phydev是对应的PHY设备。
1、申请net_device
编写网络驱动的时候首先要申请net_device,使用alloc_netdev函数来申请net_device,这是一个宏,宏定义如下:
#define alloc_netdev(sizeof_priv, name, name_assign_type, setup)
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,
		unsigned char name_assign_type,
		void (*setup)(struct net_device *),
		unsigned int txqs, unsigned int rxqs)

函数参数和返回值含义如下:
sizeof_priv:私有数据块大小。
name:设备名字。
name_assign_type:设备名字的来源。
setup:回调函数,初始化设备的设备后调用此函数。
txqs:分配的发送队列数量。
rxqs:分配的接收队列数量。
返回值:如果申请成功的话就返回申请到的net_device指针,失败的话就返回NULL。
事实上网络设备有多种,大家不要以为就只有以太网一种。Linux内核支持的网络接口有很多,比如光纤分布式数据接口(FDDI)、以太网设备(Ethernet)、红外数据接口(InDA)、高性能并行接口(HPPI)、CAN网络等。内核针对不同的网络设备在alloc_netdev的基础上提供了一层封装,比如我们本章讲解的以太网,针对以太网封装的net_device申请函数是alloc_etherdev和alloc_etherdev_mq,这也是一个宏,内容如下:
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
#define alloc_etherdev_mq(sizeof_priv, count) alloc_etherdev_mqs(sizeof_priv, count, count)
可以看出,alloc_etherdev最终依靠的是alloc_etherdev_mqs函数,此函数就是对alloc_netdev_mqs的简单封装,函数内容如下:
示例代码40.3.1.4 alloc_etherdev_mqs函数

411 struct net_device *alloc_etherdev_mqs(int sizeof_priv, 
unsigned int txqs,
412                       						unsigned int rxqs)
413 {
414     return alloc_netdev_mqs(sizeof_priv, "eth%d", NET_NAME_UNKNOWN,
415                 ether_setup, txqs, rxqs);
416 }

第414行调用alloc_netdev_mqs来申请net_device,注意这里设置网卡的名字为“eth%d”,这是格式化字符串,大家进入开发板的linux系统以后看到的“eth0”、“eth1”这样的网卡名字就是从这里来的。同样的,这里设置了以太网的setup函数为ether_setup,不同的网络设备其setup函数不同,比如CAN网络里面setup函数就是can_setup。
ether_setup函数会对net_device做初步的初始化,函数内容如下所示:
示例代码40.3.1.5 ether_setup函数

377 void ether_setup(struct net_device *dev)
378 {
379     dev->header_ops     	= &eth_header_ops;
380     dev->type      	 	= ARPHRD_ETHER;
381     dev->hard_header_len	= ETH_HLEN;
382     dev->min_header_len 	= ETH_HLEN;
383     dev->mtu        		= ETH_DATA_LEN;
384     dev->min_mtu        	= ETH_MIN_MTU;
385     dev->max_mtu        	= ETH_DATA_LEN;
386     dev->addr_len       	= ETH_ALEN;
387     dev->tx_queue_len  	= DEFAULT_TX_QUEUE_LEN;
388     dev->flags      		= IFF_BROADCAST|IFF_MULTICAST;
389     dev->priv_flags     	|= IFF_TX_SKB_SHARING;
390 
391     eth_broadcast_addr(dev->broadcast);
392 
393 }
关于 net_device 的申请就讲解到这里,对于网络设备而言,使用 alloc_etherdev 或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指针。
返回值:无。
40.3.2 net_device_ops结构体
net_device有个非常重要的成员变量:netdev_ops,为net_device_ops结构体指针类型,这就是网络设备的操作集,net_device_ops结构体定义在include/linux/netdevice.h文件中,net_device_ops结构体里面都是一些以“ndo_”开头的函数,这些函数就需要网络驱动人员去实现,不需要全部都实现,根据实际驱动情况实现其中一小部分即可。结构体内容如下所示(结构体成员变量比较多,这里有缩减):
示例代码40.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   netdev_features_t   (*ndo_features_check)(struct sk_buff *skb,
9                             struct net_device *dev,
10                            netdev_features_t features);
11  u16         (*ndo_select_queue)(struct net_device *dev,
12                          struct sk_buff *skb,
13                          struct net_device *sb_dev,
14                          select_queue_fallback_t fallback);
15  void            (*ndo_change_rx_flags)(struct net_device *dev,
16                             int flags);
17  void            (*ndo_set_rx_mode)(struct net_device *dev);
18  int         (*ndo_set_mac_address)(struct net_device *dev,
19                             void *addr);
20  int         (*ndo_validate_addr)<