邢台专业做网站,建站极速通,莆田中小企业网站制作,有没有做链接的网站目录 嵌入式网络简介嵌入式下的网络硬件接口MII/RMII 接口MDIO 接口RJ45 接口I.MX6ULL ENET 接口简介 PHY 芯片详解PHY 基础知识简介LAN8720A 详解SR8201F 详解 Linux 内核网络驱动框架net_device 结构体net_device_ops 结构体sk_buff 结构体网络NAPI 处理机制 I.MX6ULL 网络驱… 目录 嵌入式网络简介嵌入式下的网络硬件接口MII/RMII 接口MDIO 接口RJ45 接口I.MX6ULL ENET 接口简介 PHY 芯片详解PHY 基础知识简介LAN8720A 详解SR8201F 详解 Linux 内核网络驱动框架net_device 结构体net_device_ops 结构体sk_buff 结构体网络NAPI 处理机制 I.MX6ULL 网络驱动简介I.MX6ULL 网络外设设备树I.MX6ULL 网络驱动源码简析fec_netdev_ops 操作集Linux 内核PHY 子系统与MDIO 总线简析 网络驱动实验测试LAN8720 PHY 驱动测试通用PHY 驱动测试DHCP 功能配置 单网卡使用只使用ENET2 网卡只使用ENET1 网卡 网络驱动是linux 里面驱动三巨头之一linux 下的网络功能非常强大嵌入式linux 中也常常用到网络功能。前面我们已经讲过了字符设备驱动和块设备驱动本章我们就来学习一下linux 里面的网络设备驱动。
嵌入式网络简介
嵌入式下的网络硬件接口
本章节讨论的都是有线网络 注意正点原子I.MX6U-ALPHA开发板V2.4版本以前的底板使用的网络PHY 为LAN8720V2.4 及其以后的版本使用的网络PHY 为SR8201F而且网络PHY 地址有改变大家一定要看准自己所使用的底板版本号原理基本一致本章就以LAN8720 为例进行讲解。 提起网络我们一般想到的硬件就是“网卡”“网卡”这个概念最早从电脑领域传出来顾名思义就是能上网的卡。在电脑领域的“原始社会”网卡是独立的硬件如果电脑要上网就得买个网卡插上去类似现在的显卡一样。但是大家现在观察自己的笔记本或者台式机主板会发现并没有类似显卡一样的网卡设备原因是随着技术的不断发展现在只需要一个芯片就可以实现有线网卡功能因此网卡芯片都直接放到了主板上。所以大家在接触嵌入式的时候听到“网卡”这两个字不要急着在开发板上找“卡”一样的东西。 既然现在网卡已经是通过一个芯片来完成了那么是什么样的芯片呢这个就要先了解一下嵌入式中的网络硬件方案。首先嵌入式网络硬件分为两部分MAC 和PHY大家都是通过看数据手册来判断一款SOC 是否支持网络如果一款芯片数据手册说自己支持网络一般都是说的这款SOC 内置MACMAC 类似I2C 控制器、SPI 控制器一样的外设。但是光有MAC还不能直接驱动网络还需要另外一个芯片PHY因此对于内置MAC 的SOC其外部必须搭配一个PHY 芯片。但是有些SOC 内部没有MAC那也就没法搭配PHY 芯片了这些内部没有网络MAC 的芯片如何上网呢这里就要牵扯出常见的两个嵌入式网络硬件方案了。 1、SOC 内部没有网络MAC 外设 我们一般说某个SOC 不支持网络说的就是它没有网络MAC。那么这个芯片就不能上网了吗显然不是的既然没有内部MAC那么可以找个外置的MAC 芯片啊不过一般这种外置的网络芯片都是MACPHY 一体的。比如三星linux 开发板里面用的最多的DM9000因为三星的芯片基本没有内部MAC(比如S3C2440、S5PV2104412 等)所以三星的开发板都是通过外置的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 与外部MACPHY 芯片的连接如图69.1.1.1 所示 图69.1.1.1 主控SOC 与外置MACPHY 芯片连接 2、SOC 内部集成网络MAC 外设 我们一般说某个SOC 支持网络说的就是他内部集成网络MAC 外设此时我们还需要外接一个网络PHY 芯片。此时就有朋友会有疑问PHY 芯片不能也集成进SOC 吗笔者目前还没见过将PHY 也集成到芯片里面的SOC。 一般常见的通用SOC 都会集成网络MAC 外设比如STM32F4/F7/H7 系列、NXP 的I.MX系列内部集成网络MAC 的优点如下 ①、内部MAC 外设会有专用的加速模块比如专用的DMA加速网速数据的处理。 ②、网速快可以支持10/100/1000M 网速。 ③、外接PHY 可选择性多成本低。 内部的MAC 外设会通过MII 或者RMII 接口来连接外部的PHY 芯片MII/RMII 接口用来传输网络数据。另外主控需要配置或读取PHY 芯片也就是读写PHY 的内部寄存器所以还需要一个控制接口叫做MIDOMDIO 很类似IIC也是两根线一根数据线叫做MDIO一根时钟线叫做MDC。 SOC 内部MAC 外设与外部PHY 芯片的连接如图69.1.1.2 所示 图69.1.1.2 内部MAC 与外部PHY 之间的连接 大家在做项目的时候如果要用到网络功能强烈建议大家选择内部带有网络MAC 外设的主控SOCI.MX6ULL 就有两个10M/100M 的网络MAC 外设正点原子ALPHA 开发板板载了两颗PHY 芯片型号为LAN8720 或SR8201F。因此本章节只讲解SOC 内部MAC外置PHY 芯片这种方案。
MII/RMII 接口
前面我们说了内部MAC 通过MII/RMII 接口来与外部的PHY 芯片连接完成网络数据传输本节我们就来学习一下什么是MII 和RMII 接口。 1、MII 接口 MII 全称是Media Independent Interface直译过来就是介质独立接口它是IEEE-802.3 定义的以太网标准接口MII 接口用于以太网MAC 连接PHY 芯片连接示意图如图69.1.2.1 所示 图69.1.2.1 MII 接口 MII 接口一共有16 根信号线含义如下 TX_CLK发送时钟如果网速为100M 的话时钟频率为25MHz10M 网速的话时钟频率为2.5MHz此时钟由PHY 产生并发送给MAC。 TX_EN发送使能信号。 TX_ER发送错误信号高电平有效表示TX_ER 有效期内传输的数据无效。10Mpbs 网速下TX_ER 不起作用。 TXD[3:0]发送数据信号线一共4 根。 RXD[3:0]接收数据信号线一共4 根。 RX_CLK接收时钟信号如果网速为100M 的话时钟频率为25MHz10M 网速的话时钟频率为2.5MHzRX_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 芯片的示意图如图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 芯片。
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 接口另外可能还需要复位、中断等其他引脚。
RJ45 接口
网络设备是通过网线连接起来的插入网线的叫做RJ45 座如图69.1.4.1 所示 图69.1.4.1 RJ45 座子 RJ45 座要与PHY 芯片连接在一起但是中间需要一个网络变压器网络变压器用于隔离以及滤波等网络变压器也是一个芯片外形一般如图69.1.4.2 所示
图69.1.4.2 网络变压器 但是现在很多RJ45 座子内部已经集成了网络变压器比如正点原子ALPHA 开发板所使用的HR911105A 就是内置网络变压器的RJ45 座。内置网络变压器的RJ45 座和不内置的引脚一样但是一般不内置的RJ45 座会短一点。因此大家在画板的时候一定要考虑你所使用的RJ45座是否内置网络变压器如果不内置的话就要自行添加网络变压器部分电路同理如果你所设计的硬件是需要内置网络变压器的RJ45 座肯定不能随便焊接一个不内置变压器的RJ45座否则网络工作不正常 RJ45 座子上一般有两个灯一个黄色(橙色)一个绿色绿色亮的话表示网络连接正常黄色闪烁的话说明当前正在进行网络通信。这两个灯由PHY 芯片控制PHY 芯片会有两个引脚来连接RJ45 座上的这两个灯。内部MAC外部PHYRJ45 座(内置网络变压器)就组成了一个完整的嵌入式网络接口硬件如图69.1.4.2 所示 图69.1.4.2 嵌入式网络硬件接口示意图
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 芯片以后哪里需要调整。
PHY 芯片详解
PHY 基础知识简介
PHY 是IEEE 802.3 规定的一个标准模块前面说了SOC 可以对PHY 进行配置或者读取PHY 相关状态这个就需要PHY 内部寄存器去实现了。PHY 芯片寄存器地址空间为5 位地址0 ~ 31 共32 个寄存器IEEE 定义了0 ~ 15 这16 个寄存器的功能16 ~ 31 这16 个寄存器由厂商自行实现。也就是说不管你用的哪个厂家的PHY 芯片其中0~15 这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/SR8201F 这两个PHY 为例详细的分析一下PHY 芯片的寄存器。
LAN8720A 详解
V2.4 版本以前的底板PHY 芯片采用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 所示。 表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 所示。 表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)寄存器地址为0BCR 寄存器各位如表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 IdentifierOUI 一共32 位分为三部分22 位的ID6位厂商型号ID4 位厂商版本ID组成如图69.2.2.5 所示 图69.2.2.5 OUI 组成方式 LAN8720A 的ID 寄存器2 如表69.2.2.5 所示 表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 就讲解到这里。
SR8201F 详解
V2.4 及以后版本的底板PHY 芯片采用SR8201F因此请看本小节 1、SR8201F 简介 SR8201F 是单芯片/单端口的10/100Mbps 以太网PHY 收发器。芯片支持RMII (简化介质独立)接口SR8201F 实现了所有10/100M 以太网物理层包括物理编码子层(PCS)物理介质接入层(PMA)双绞线物理介质相关子层(TP-PMD)同时芯片支持端口自动翻转Auto-MDIX。 SR8201F 的主要特点如下 · 支持IEEE 802.3az-2010EEE · 兼容100Base-TX IEEE 802.3u · 兼容10Base-T IEEE 802.3 · 支持RMII 模式 · 全/半双工工作 · 支持自协商模式 · 支持Auto-MDIX · 支持中断功能 · SR8201F 提供两个网络状态的LED ·支持25MHz 晶振或外部振荡输入 · RMII 模式支持50MHz 外部时钟输入 SR8201F 功能框图如图69.2.3.1 所示。 图69.2.3.1 SR8201F 功能框图 2、SR8201F 中断管理 SR8201F 检测到媒介端的状态改变就会写入对应中断状态的寄存器第0 页寄存器14并且在中断管脚使能时中断管脚会被置低表示发生一个中断事件。当MAC 检测到中断事件时可以通过MDC/MDIO 端口访问到第0 页寄存器14 获取相应的中断状态。正点原子的ALPHA 开发板虽然将SR8201F 的中断引脚连接到了I.MX6ULL 上但是并没有使用中断功能关于中断的具体用法可以参考SR8201F 数据手册。 3、PHY 地址设置 MAC 层通过MDIO/MDC 总线对PHY 进行读写操作MDIO 最多可以控制32 个PHY 芯片通过不同的PHY 芯片地址来对不同的PHY 操作。SR8201F 通过设置LED0/PHYAD[0]和LED1/PHYAD[1]这两个引脚来设置其PHY 地址其地址设置如表69.2.3.1 所示。 表69.2.3.1 SR9201F 地址设置 正点原子ALPHA 开发板ENET1 网络的SR8201F 上的LED1/PHYAD1 引脚上拉 LED0/PHYDAD0 引脚下来下拉因此ENET1 上的SR8201F 地址为0X02。ENET2 网络上的SR8201F 的LED1/PHYAD1 引脚下拉LED0/PHYAD0 引脚上拉因此ENET2 上的SR8201F地址为1。 4、SR8021F 内部寄存器 SR8201F 的前16 个寄存器满足IEEE 的要求在这里我们只介绍几个常用的寄存器首先是BCR(Basic Control Rgsister)寄存器地址为0BCR 寄存器各位如表69.2.3.2 所示。 表69.2.3.3 BCR 寄存器 我们说的配置PHY 芯片重点就是配置BCR 寄存器由于SR8201F 是个10/100M 的PHY因此表69.2.3.3 中没有体现出1000M 相关的配置。但是10/100M 相关的配置是和IEEE 的规定完全相符的大家可以选择一个其他的10/100M 的PHY 芯片对比看一下比如NXP 官方EVK开发板使用的KSZ8081。 接下来看一下BSR(Basic Status Register)寄存器地址为1。此寄存器为PHY 的状态寄存器通过此寄存器可以获取到PHY 芯片的工作状态BSR 寄存器各位如表69.2.3.4 所示 表69.2.3.4 BSR 寄存器 从表69.2.3.4 可以看出和IEEE 标准规定相比SR8201F 的BSR 寄存器少了几个位这个没关系的不管什么PHY 芯片只要它实现了的位和IEEE 规定相符就行。通过读取BSR 寄存器的值我们可以得到当前的连接速度、双工状态和连接状态等。 接下来看一下SR8201F 的PHY ID 寄存器1 和ID 寄存器2地址为2 和3后面就称为寄存器2 和寄存器3。这两个寄存器都是PHY 的ID 寄存器。IEEE 规定寄存器2 和寄存器3 为PHY 的ID 寄存器这两个寄存器组成一个32 位的唯一ID 值。 SR8201F 的ID 寄存器2 如表69.2.3.5 所示 表69.2.2.5 PHY ID 寄存器2 ID 寄存器3 如表69.2.3.6 所示 表69.2.3.6 PHY ID 寄存器3 关于SR8201F 的PHY 寄存器就简单讲这几个其他的寄存器请自行查阅SR8201F 数据手册。
Linux 内核网络驱动框架
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 10, /* sysfs */
3 IFF_BROADCAST 11, /* volatile */
4 IFF_DEBUG 12, /* sysfs */
5 IFF_LOOPBACK 13, /* volatile */
6 IFF_POINTOPOINT 14, /* volatile */
7 IFF_NOTRAILERS 15, /* sysfs */
8 IFF_RUNNING 16, /* volatile */
9 IFF_NOARP 17, /* sysfs */
10 IFF_PROMISC 18, /* sysfs */
11 IFF_ALLMULTI 19, /* sysfs */
12 IFF_MASTER 110, /* volatile */
13 IFF_SLAVE 111, /* volatile */
14 IFF_MULTICAST 112, /* sysfs */
15 IFF_PORTSEL 113, /* sysfs */
16 IFF_AUTOMEDIA 114, /* sysfs */
17 IFF_DYNAMIC 115, /* sysfs */
18 IFF_LOWER_UP 116, /* volatile */
19 IFF_DORMANT 117, /* volatile */
20 IFF_ECHO 118, /* 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_ETHERLinux 内核所支持的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 eth_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 注册成功负值注册失败。 4、注销net_device 既然有注册那么必然有注销注销net_device 使用函数unregister_netdev函数原型如下
void unregister_netdev(struct net_device *dev)函数参数和返回值含义如下 dev要注销的net_device 指针。 返回值无。
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 struct netpoll_info *info);
40 void (*ndo_netpoll_cleanup)(struct net_device *dev);
41 #endif
......
104 int (*ndo_set_features)(struct net_device *dev,
105 netdev_features_t features);
......
166 };
第2 行ndo_init 函数当第一次注册网络设备的时候此函数会执行设备可以在此函数中做一些需要推后初始化的内容不过一般驱动中不使用此函数虚拟网络设备可能会使用。 第3 行ndo_uninit 函数卸载网络设备的时候此函数会执行。 第4 行ndo_open 函数打开网络设备的时候此函数会执行网络驱动程序需要实现此函数非常重要以NXP 的I.MX 系列SOC 网络驱动为例会在此函数中做如下工作 ·使能网络外设时钟。 ·申请网络所使用的环形缓冲区。 ·初始化MAC 外设。 ·绑定接口对应的PHY。 ·如果使用NAPI 的话要使能NAPI 模块通过napi_enable 函数来使能。 ·开启PHY。 ·调用netif_tx_start_all_queues 来使能传输队列也可能调用netif_start_queue 函数。 ·…… 第5 行ndo_stop 函数关闭网络设备的时候此函数会执行网络驱动程序也需要实现此函数。以NXP 的I.MX 系列SOC 网络驱动为例会在此函数中做如下工作 ·停止PHY。 ·停止NAPI 功能。 ·停止发送功能。 ·关闭MAC。 ·断开PHY 连接。 ·关闭网络时钟。 ·释放数据缓冲区。 ·…… 第6 行ndo_start_xmit 函数当需要发送数据的时候此函数就会执行此函数有一个参数为sk_buff 结构体指针sk_buff 结构体在Linux 的网络驱动中非常重要sk_buff 保存了上层传递给网络驱动层的数据。也就是说要发送出去的数据都存在了sk_buff 中关于sk_buff 稍后会做详细的讲解。如果发送成功的话此函数返回NETDEV_TX_OK如果发送失败了就返回NETDEV_TX_BUSY如果发送失败了我们就需要停止队列。 第8 行ndo_select_queue 函数当设备支持多传输队列的时候选择使用哪个队列。 第14 行ndo_set_rx_mode 函数此函数用于改变地址过滤列表根据net_device 的flags成员变量来设置SOC 的网络外设寄存器。比如flags 可能为IFF_PROMISC、IFF_ALLMULTI 或IFF_MULTICAST分别表示混杂模式、单播模式或多播模式。 第15 行ndo_set_mac_address 函数此函数用于修改网卡的MAC 地址设置net_device的dev_addr 成员变量并且将MAC 地址写入到网络外设的硬件寄存器中。 第17 行ndo_validate_addr 函数验证MAC 地址是否合法也就是验证net_device 的dev_addr 中的MAC 地址是否合法直接调用is_valid_ether_addr 函数。 第18 行ndo_do_ioctl 函数用户程序调用ioctl 的时候此函数就会执行比如PHY 芯片相关的命令操作一般会直接调用phy_mii_ioctl 函数。 第22 行ndo_change_mtu 函数更改MTU 大小。 第26 行ndo_tx_timeout 函数当发送超时的时候函数会执行一般都是网络出问题了导致发送超时。一般可能会重启MAC 和PHY重新开始数据发送等。 第37 行ndo_poll_controller 函数使用查询方式来处理网卡数据的收发。 第104 行ndo_set_features 函数修改net_device 的features 属性设置相应的硬件属性。
sk_buff 结构体
网络是分层的对于应用层而言不用关心具体的底层是如何工作的只需要按照协议将要发送或接收的数据打包好即可。打包好以后都通过dev_queue_xmit 函数将数据发送出去接收数据的话使用netif_rx 函数即可我们依次来看一下这两个函数。 1、dev_queue_xmit 函数 此函数用于将网络数据发送出去函数定义在include/linux/netdevice.h 中函数原型如下
static inline int dev_queue_xmit(struct sk_buff *skb)函数参数和返回值含义如下 skb要发送的数据这是一个sk_buff 结构体指针sk_buff 是Linux 网络驱动中一个非常重要的结构体网络数据就是以sk_buff 保存的各个协议层在sk_buff 中添加自己的协议头最终由底层驱动将sk_buff 中的数据发送出去。网络数据的接收过程恰好相反网络底层驱动将接收到的原始数据打包成sk_buff然后发送给上层协议上层会取掉相应的头部然后将最终的数据发送给用户。 返回值0 发送成功负值发送失败。 dev_queue_xmit 函数太长这里就不详细的分析了dev_queue_xmit 函数最终是通过net_device_ops 操作集里面的ndo_start_xmit 函数来完成最终发送了ndo_start_xmit 就是网络驱动编写人员去实现的整个流程如图69.3.3.1 所示 图69.3.3.1 dev_queue_xmit 执行流程 2、netif_rx 函数 上层接收数据的话使用netif_rx 函数但是最原始的网络数据一般是通过轮询、中断或NAPI的方式来接收。netif_rx 函数定义在net/core/dev.c 中函数原型如下
int netif_rx(struct sk_buff *skb)函数参数和返回值含义如下 skb保存接收数据的sk_buff。 返回值NET_RX_SUCCESS 成功NET_RX_DROP 数据包丢弃。 我们重点来看一下sk_buff 这个结构体sk_buff 是Linux 网络重要的数据结构用于管理接收或发送数据包sk_buff 结构体定义在include/linux/skbuff.h 中结构体内容如下(由于结构体比较大为了缩小篇幅只列出部分重要的内容)
示例代码69.3.3.1 sk_buff 结构体
1 struct sk_buff {
2 union {
3 struct {
4 /* These two members must be first. */
5 struct sk_buff *next;
6 struct sk_buff *prev;
7
8 union {
9 ktime_t tstamp;
10 struct skb_mstamp skb_mstamp;
11 };
12 };
13 struct rb_node rbnode; /* used in netem tcp stack */
14 };
15 struct sock *sk;
16 struct net_device *dev;
17
18 /*
19 * This is the control buffer. It is free to use for every
20 * layer. Please put your private variables there. If you
21 * want to keep them across layers you have to do a skb_clone()
22 * first. This is owned by whoever has the skb queued ATM.
23 */
24 char cb[48] __aligned(8);
25
26 unsigned long _skb_refdst;
27 void (*destructor)(struct sk_buff *skb);
......
37 unsigned int len, data_len;
38 __u16 mac_len, hdr_len;
......
145 __be16 protocol;
146 __u16 transport_header;
147 __u16 network_header;
148 __u16 mac_header;
149
150 /* private: */
151 __u32 headers_end[0];
152 /* public: */
153
154 /* These elements must be at the end, see alloc_skb() for details. */
155 sk_buff_data_t tail;
156 sk_buff_data_t end;
157 unsigned char *head, *data;
158 unsigned int truesize;
159 atomic_t users;
160 };
第5~6 行next 和prev 分别指向下一个和前一个sk_buff构成一个双向链表。 第9 行tstamp 表示数据包接收时或准备发送时的时间戳。 第15 行sk 表示当前sk_buff 所属的Socket。 第16 行dev 表示当前sk_buff 从哪个设备接收到或者发出的。 第24 行cb 为控制缓冲区不管哪个层都可以自由使用此缓冲区用于放置私有数据。 第27 行destructor 函数当释放缓冲区的时候可以在此函数里面完成某些动作。 第37 行len 为实际的数据长度包括主缓冲区中数据长度和分片中的数据长度。data_len为数据长度只计算分片中数据的长度。 第38 行mac_len 为连接层头部长度也就是MAC 头的长度。 第145 行protocol 协议。 第146 行transport_header 为传输层头部。 第147 行network_header 为网络层头部 第148 行mac_header 为链接层头部。 第155 行tail 指向实际数据的尾部。 第156 行end 指向缓冲区的尾部。 第157 行head 指向缓冲区的头部data 指向实际数据的头部。data 和tail 指向实际数据的头部和尾部head 和end 指向缓冲区的头部和尾部。结构如图69.3.3.2 所示 图69.3.3.2 sk_buff 数据区结构示意图 针对sk_buff 内核提供了一系列的操作与管理函数我们简单看一些常见的API 函数 1、分配sk_buff 要使用sk_buff 必须先分配首先来看一下alloc_skb 这个函数此函数定义在 include/linux/skbuff.h 中函数原型如下
static inline struct sk_buff *alloc_skb(unsigned int size,
gfp_t priority)函数参数和返回值含义如下 size要分配的大小也就是skb 数据段大小。 priority为GFP MASK 宏比如GFP_KERNEL、GFP_ATOMIC 等。 返回值分配成功的话就返回申请到的sk_buff 首地址失败的话就返回NULL。 在网络设备驱动中常常使用netdev_alloc_skb 来为某个设备申请一个用于接收的skb_buff此函数也定义在include/linux/skbuff.h 中函数原型如下
static inline struct sk_buff *netdev_alloc_skb(struct net_device *dev,
unsigned int length)函数参数和返回值含义如下 dev要给哪个设备分配sk_buff。 length要分配的大小。 返回值分配成功的话就返回申请到的sk_buff 首地址失败的话就返回NULL。 2、释放sk_buff 当使用完成以后就要释放掉sk_buff 释放函数可以使用kfree_skb 函数定义在 include/linux/skbuff.c 中函数原型如下
void kfree_skb(struct sk_buff *skb)函数参数和返回值含义如下 skb要释放的sk_buff。 返回值无。 对于网络设备而言最好使用如下所示释放函数
void dev_kfree_skb (struct sk_buff *skb)函数只要一个参数skb就是要释放的sk_buff。 3、skb_put、skb_push、sbk_pull 和skb_reserve 这四个函数用于变更sk_buff先来看一下skb_put 函数此函数用于在尾部扩展skb_buff的数据区也就将skb_buff 的tail 后移n 个字节从而导致skb_buff 的len 增加n 个字节原型如下
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)函数参数和返回值含义如下 skb要操作的sk_buff。 len要增加多少个字节。 返回值扩展出来的那一段数据区首地址。 skb_put 操作之前和操作之后的数据区如图69.3.3.3 所示 图69.3.3.3 skb_put 函数操作前后对比 skb_push 函数用于在头部扩展skb_buff 的数据区函数原型如下所示
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)函数参数和返回值含义如下 skb要操作的sk_buff。 len要增加多少个字节。 返回值扩展完成以后新的数据区首地址。 skb_push 操作之前和操作之后的数据区如图69.3.3.4 所示 图69.3.3.4 skb_push 函数操作前后对比 sbk_pull 函数用于从sk_buff 的数据区起始位置删除数据函数原型如下所示
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)函数参数和返回值含义如下 skb要操作的sk_buff。 len要删除的字节数。 返回值删除以后新的数据区首地址。 skb_pull 操作之前和操作之后的数据区如图69.3.3.5 所示 图69.3.3.5 skb_pull 函数操作前后对比 sbk_reserve 函数用于调整缓冲区的头部大小方法很简单将skb_buff 的data 和tail 同时后移n 个字节即可函数原型如下所示
static inline void skb_reserve(struct sk_buff *skb, int len)函数参数和返回值含义如下 skb要操作的sk_buff。 len要增加的缓冲区头部大小。 返回值无。
网络NAPI 处理机制
如果玩过单片机的话应该都知道像IIC、SPI、网络等这些通信接口接收数据有两种方法轮询或中断。Linux 里面的网络数据接收也轮询和中断两种中断的好处就是响应快数据量小的时候处理及时速度快但是一旦当数据量大而且都是短帧的时候会导致中断频繁发生消耗大量的CPU 处理时间在中断自身处理上。轮询恰好相反响应没有中断及时但是在处理大量数据的时候不需要消耗过多的CPU 处理时间。Linux 在这两个处理方式的基础上提出了另外一种网络数据接收的处理方法NAPI(New API)NAPI 是一种高效的网络处理技术。 NAPI 的核心思想就是不全部采用中断来读取网络数据而是采用中断来唤醒数据接收服务程序在接收服务程序中采用POLL 的方法来轮询处理数据。这种方法的好处就是可以提高短数据包的接收效率减少中断处理的时间。目前NAPI 已经在Linux 的网络驱动中得到了大量的应用NXP 官方编写的网络驱动都是采用的NAPI 机制。 关于NAPI 详细的处理过程本章节不讨论本章节就简单讲解一下如何在驱动中使用NAPILinux 内核使用结构体napi_struct 表示NAPI在使用NAPI 之前要先初始化一个napi_struct 实例。 1、初始化NAPI 首先要初始化一个napi_struct 实例使用netif_napi_add 函数此函数定义在net/core/dev.c中函数原型如下
void netif_napi_add(struct net_device *dev,
struct napi_struct *napi,
int (*poll)(struct napi_struct *, int),
int weight)函数参数和返回值含义如下 dev每个NAPI 必须关联一个网络设备此参数指定NAPI 要关联的网络设备。 napi要初始化的NAPI 实例。 pollNAPI 所使用的轮询函数非常重要一般在此轮询函数中完成网络数据接收的工作。 weightNAPI 默认权重(weight)一般为NAPI_POLL_WEIGHT。 返回值无。 2、删除NAPI 如果要删除NAPI使用netif_napi_del 函数即可函数原型如下
void netif_napi_del(struct napi_struct *napi)函数参数和返回值含义如下 napi要删除的NAPI。 返回值无。 3、使能NAPI 初始化完NAPI 以后必须使能才能使用使用函数napi_enable函数原型如下
inline void napi_enable(struct napi_struct *n)函数参数和返回值含义如下 n要使能的NAPI。 返回值无。 4、关闭NAPI 关闭NAPI 使用napi_disable 函数即可函数原型如下
void napi_disable(struct napi_struct *n)函数参数和返回值含义如下 n要关闭的NAPI。 返回值无。 5、检查NAPI 是否可以进行调度 使用napi_schedule_prep 函数检查NAPI 是否可以进行调度函数原型如下
inline bool napi_schedule_prep(struct napi_struct *n)函数参数和返回值含义如下 n要检查的NAPI。 返回值如果可以调度就返回真如果不可调度就返回假。 6、NAPI 调度 如果可以调度的话就进行调度使用__napi_schedule 函数完成NAPI 调度函数原型如下
void __napi_schedule(struct napi_struct *n)函数参数和返回值含义如下 n要调度的NAPI。 返回值无。
我们也可以使用napi_schedule 函数来一次完成napi_schedule_prep 和__napi_schedule 这两 个函数的工作napi_schedule 函数内容如下所示
示例代码69.3.4.1 napi_schedule 函数
1 static inline void napi_schedule(struct napi_struct *n)
2 {
3 if (napi_schedule_prep(n))
4 __napi_schedule(n);
5 }从示例代码69.3.4.1 可以看出napi_schedule 函数就是对napi_schedule_prep 和 __napi_schedule 的简单封装一次完成判断和调度。 7、NAPI 处理完成 NAPI 处理完成以后需要调用napi_complete 函数来标记NAPI 处理完成函数原型如下
inline void napi_complete(struct napi_struct *n)函数参数和返回值含义如下 n处理完成的NAPI。 返回值无。
I.MX6ULL 网络驱动简介
I.MX6ULL 网络外设设备树
上一小节我们对Linux 的网络驱动框架进行了一个简单的介绍本节我们就来简单分析一下I.MX6ULL 的网络驱动源码。I.MX6ULL 有两个10/100M 的网络MAC 外设因此I.MX6ULL网络驱动主要就是这两个网络MAC 外设的驱动。这两个外设的驱动都是一样的我们分析其中一个就行了首先肯定是设备树NXP 的I.MX 系列SOC 网络绑定文档为Documentation/devicetree/bindings/net/fsl-fec.txt此绑定文档描述了I.MX 系列SOC 网络设备树节点的要求。 ①、必要属性 compatible这个肯定是必须的一般是“fsl,-fec”比如I.MX6ULL 的compatible 属性就是fsl,imx6ul-fec,和fsl,imx6q-fec。 regSOC 网络外设寄存器地址范围。 interrupts网络中断。 phy-mode网络所使用的PHY 接口模式是MII 还是RMII。 ②、可选属性 phy-reset-gpiosPHY 芯片的复位引脚。 phy-reset-durationPHY 复位引脚复位持续时间单位为毫秒。只有当设置了phy-reset-gpios 属性此属性才会有效如果不设置此属性的话PHY 芯片复位引脚的复位持续时间默认为 1 毫秒数值不能大于1000 毫秒大于1000 毫秒的话就会强制设置为1 毫秒。 phy-supplyPHY 芯片的电源调节。 phy-handle连接到此网络设备的PHY 芯片句柄。 fsl,num-tx-queues此属性指定发送队列的数量如果不指定的话默认为1。 fsl,num-rx-queues此属性指定接收队列的数量如果不指定的话默认为2。 fsl,magic-packet此属性不用设置具体的值直接将此属性名字写到设备树里面即可表示支持硬件魔术帧唤醒。 fsl,wakeup_irq此属性设置唤醒中断索引。 stop-mode如果此属性存在的话表明SOC 需要设置GPR 位来请求停止模式。 ③、可选子节点 mdio可以设置名为“mdio”的子节点此子节点用于指定网络外设所使用的MDIO 总线主要作为PHY 节点的容器也就是在mdio 子节点下指定PHY 相关的属性信息具体信息可以参考PHY 的绑定文档Documentation/devicetree/bindings/net/phy.txt。 PHY 节点相关属性内容如下 interrupts中断属性可以不需要。 interrupt-parent中断控制器句柄可以不需要。 regPHY 芯片地址必须的 compatible兼容性列表一般为“ethernet-phy-ieee802.3-c22”或“ethernet-phy-ieee802.3-c45”分别对应IEEE802.3 的22 簇和45 簇默认是22 簇。也可以设置为其他值如果PHY的ID 不知道的话可以compatible 属性可以设置为“ethernet-phy-idAAAA.BBBB”AAAA 和BBBB 的含义如下 AAAAPHY 的16 位ID 寄存器1 值也就是OUI 的bit3~1816 进制格式。 BBBBPHY 的16 位ID 寄存器2 值也就是OUI 的bit19~2416 进制格式。 max-speedPHY 支持的最高速度比如10、100 或1000。 打开imx6ull.dtsi找到如下I.MX6ULL 的两个网络外设节点如下所示
示例代码69.4.1.1 网络节点信息
1 fec1: ethernet02188000 {
2 compatible fsl,imx6ul-fec, fsl,imx6q-fec;
3 reg 0x02188000 0x4000;
4 interrupts GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH,
5 GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH;
6 clocks clks IMX6UL_CLK_ENET,
7 clks IMX6UL_CLK_ENET_AHB,
8 clks IMX6UL_CLK_ENET_PTP,
9 clks IMX6UL_CLK_ENET_REF,
10 clks IMX6UL_CLK_ENET_REF;
11 clock-names ipg, ahb, ptp,
12 enet_clk_ref, enet_out;
13 stop-mode gpr 0x10 3;
14 fsl,num-tx-queues1;
15 fsl,num-rx-queues1;
16 fsl,magic-packet;
17 fsl,wakeup_irq 0;
18 status disabled;
19 };
20
21 fec2: ethernet020b4000 {
22 compatible fsl,imx6ul-fec, fsl,imx6q-fec;
23 reg 0x020b4000 0x4000;
24 interrupts GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH,
25 GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH;
26 clocks clks IMX6UL_CLK_ENET,
27 clks IMX6UL_CLK_ENET_AHB,
28 clks IMX6UL_CLK_ENET_PTP,
29 clks IMX6UL_CLK_ENET2_REF_125M,
30 clks IMX6UL_CLK_ENET2_REF_125M;
31 clock-names ipg, ahb, ptp,
32 enet_clk_ref, enet_out;
33 stop-mode gpr 0x10 4;
34 fsl,num-tx-queues1;
35 fsl,num-rx-queues1;
36 fsl,magic-packet;
37 fsl,wakeup_irq 0;
38 status disabled;
39 };
fec1 和fec2 分别对应I.MX6ULL 的ENET1 和ENET2至于节点的具体属性就不分析了上面在讲解绑定文档的时候就已经详细的讲过了。示例代码69.4.1.1 是NXP 官方编写的我们不需要去修改但是示例代码69.4.1.1 是不能工作的还需要根据实际情况添加或修改一些属性。打开imx6ull-alientek-emmc.dts找到如下内容
示例代码69.4.1.2 imx6ull-alientek-emmc.dts 中的网络节点
1 fec1 {
2 pinctrl-names default;
3 pinctrl-0 pinctrl_enet1
4 pinctrl_enet1_reset;
5 phy-mode rmii;
6 phy-handle ethphy0;
7 phy-reset-gpios gpio5 7 GPIO_ACTIVE_LOW;
8 phy-reset-duration 200;
9 status okay;
10 };
11
12 fec2 {
13 pinctrl-names default;
14 pinctrl-0 pinctrl_enet2
15 pinctrl_enet2_reset;
16 phy-mode rmii;
17 phy-handle ethphy1;
18 phy-reset-gpios gpio5 8 GPIO_ACTIVE_LOW;
19 phy-reset-duration 200;
20 status okay;
21
22 mdio {
23 #address-cells 1;
24 #size-cells 0;
25
26 ethphy0: ethernet-phy0 {
27 compatible ethernet-phy-ieee802.3-c22;
28 reg 0;
29 };
30
31 ethphy1: ethernet-phy1 {
32 compatible ethernet-phy-ieee802.3-c22;
33 reg 1;
34 };
35 };
36 };
示例代码69.4.1.2 是作者在移植Linux 内核的时候已经根据ALPHA 开发板修改后的并不是NXP 官方原版节点信息所以会有一点出入这个不要紧。 第1~10 行ENET1 网口的节点属性第3、4 行设置ENET1 所使用的引脚pinctrl 节点信息第5 行设置网络对应的PHY 芯片接口为RMII这个要根据实际的硬件来设置。第6 行设置PHY 芯片的句柄为ethphy0MDIO 节点会设置PHY 信息。其他的属性信息就很好理解了 基本已经在上面讲解绑定文档的时候说过了。 第12 ~ 36 行ENET2 网口的节点属性基本和ENET1 网口一致区别就是多了第22~35行的mdio 子节点前面讲解绑定文档的时候说了mido 子节点用于描述MIDO 总线在此子节点内会包含PHY 节点信息。这里一共有两个PHY 子节点ethphy0 和ethphy1分别对应ENET1 和ENET2 的PHY 芯片。比如第26 行的“ethphy0: ethernet-phy0”就是ENET1 的PHY节点名字“”后面的0 就是此PHY 芯片的芯片地址reg 属性也是描述PHY 芯片地址的这点和IIC 设备节点很像。其他地方就没什么好多的了绑定文档已经讲解的很清楚了。 最后就是设备树中网络相关引脚的描述打开imx6ull-alientek-emmc.dts找到如下所示内容
示例代码69.4.1.2 网络引脚pinctrl 信息
1 pinctrl_enet1: enet1grp {
2 fsl,pins
3 MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0
4 MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0
5 MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0
6 MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0
7 MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0
8 MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0
9 MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0
10 MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b009
11 ;
12 };
13
14 pinctrl_enet2: enet2grp {
15 fsl,pins
16 MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0
17 MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0
18 MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0
19 MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0
20 MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0
21 MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0
22 MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0
23 MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0
24 MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0
25 MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b009
26 ;
27 };
28
29 /*enet1 reset zuozhongkai*/
30 pinctrl_enet1_reset: enet1resetgrp {
31 fsl,pins
32 /* used for enet1 reset */
33 MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0
34 ;
35 };
36
37 /*enet2 reset zuozhongkai*/
38 pinctrl_enet2_reset: enet2resetgrp {
39 fsl,pins
40 /* used for enet2 reset */
41 MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0
42 ;
43 };
pinctrl_enet1 和pinctrl_enet1_reset 是ENET1 所有的IO 引脚pinctrl 信息之所以分两部分主要是因为ENET1 的复位引脚为GPIO5_IO07 而GPIO5_IO07 对应的引脚就是SNVS_TAMPER7要放到iomuxc_snvs 节点下所以就分成了两部分。 注意第16、17 行这两行设置GPIO1_IO07 和GPIO1_IO06 为ENET2 的MDC 和MDIO大家可能会疑问为什么不将其设置为ENET1 的MDC 和MDIO 呢经过笔者实测在开启两个网口的情况下将GPIO1_IO07 和GPIO1_IO06 设置为ENET1 的MDC 和MDIO 会对导致网络工作不正常。前面说了一个MDIO 接口可以管理32 个PHY所以设置ENET2 的MDC 和MDIO 以后也是可以管理ENET1 上的PHY 芯片。
I.MX6ULL 网络驱动源码简析
1、fec_probe 函数简析 对于I.MX6ULL 而言网络驱动主要分两部分I.MX6ULL 网络外设驱动以及PHY 芯片驱动网络外设驱动是NXP 编写的PHY 芯片有通用驱动文件有些PHY 芯片厂商还会针对自己的芯片编写对应的PHY 驱动。总体来说SOC 内置网络MAC外置PHY 芯片这种方案我们是不需要编写什么驱动的基本可以直接使用。但是为了学习我们还是要简单分析一下具体的网络驱动编写过程。 首先来看一下I.MX6ULL 的网络控制器部分驱动从示例代码69.4.1.1 中可以看出compatible 属性有两个值“fsl,imx6ul-fec”和“fsl,imx6q-fec”通过在linux 内核源码中搜索这两个字符串即可找到对应的驱动文件驱动文件为drivers/net/ethernet/freescale/fec_main.c打开fec_main.c找到如下所示内容
示例代码69.4.2.1 I.MX 系列SOC 网络平台驱动匹配表
1 static const struct of_device_id fec_dt_ids[] {
2 { .compatible fsl,imx25-fec, .data
fec_devtype[IMX25_FEC], },
3 { .compatible fsl,imx27-fec, .data
fec_devtype[IMX27_FEC], },
4 { .compatible fsl,imx28-fec, .data
fec_devtype[IMX28_FEC], },
5 { .compatible fsl,imx6q-fec, .data
fec_devtype[IMX6Q_FEC], },
6 { .compatible fsl,mvf600-fec, .data
fec_devtype[MVF600_FEC], },
7 { .compatible fsl,imx6sx-fec, .data
fec_devtype[IMX6SX_FEC], },
8 { .compatible fsl,imx6ul-fec, .data
fec_devtype[IMX6UL_FEC], },
9 { /* sentinel */ }
10 };
11
12 static struct platform_driver fec_driver {
13 .driver {
14 .name DRIVER_NAME,
15 .pm fec_pm_ops,
16 .of_match_table fec_dt_ids,
17 },
18 .id_table fec_devtype,
19 .probe fec_probe,
20 .remove fec_drv_remove,
21 };第8 行匹配表包含“fsl,imx6ul-fec”因此设备树和驱动匹配上当匹配成功以后第19 行的fec_probe 函数就会执行我们简单分析一下fec_probe 函数函数内容如下
示例代码69.4.2.1 fec_probe 函数
1 static int fec_probe(struct platform_device *pdev)
2 {
3 struct fec_enet_private *fep;
4 struct fec_platform_data *pdata;
5 struct net_device *ndev;
6 int i, irq, ret 0;
7 struct resource *r;
8 const struct of_device_id *of_id;
9 static int dev_id;
10 struct device_node *np pdev-dev.of_node, *phy_node;
11 int num_tx_qs;
12 int num_rx_qs;
13
14 fec_enet_get_queue_num(pdev, num_tx_qs, num_rx_qs);
15
16 /* Init network device */
17 ndev alloc_etherdev_mqs(sizeof(struct fec_enet_private),
18 num_tx_qs, num_rx_qs);
19 if (!ndev)
20 return -ENOMEM;
21
22 SET_NETDEV_DEV(ndev, pdev-dev);
23
24 /* setup board info structure */
25 fep netdev_priv(ndev);
26
27 of_id of_match_device(fec_dt_ids, pdev-dev);
28 if (of_id)
29 pdev-id_entry of_id-data;
30 fep-quirks pdev-id_entry-driver_data;
31
32 fep-netdev ndev;
33 fep-num_rx_queues num_rx_qs;
34 fep-num_tx_queues num_tx_qs;
35
36 #if !defined(CONFIG_M5272)
37 /* default enable pause frame auto negotiation */
38 if (fep-quirks FEC_QUIRK_HAS_GBIT)
39 fep-pause_flag | FEC_PAUSE_FLAG_AUTONEG;
40 #endif
41
42 /* Select default pin state */
43 pinctrl_pm_select_default_state(pdev-dev);
44
45 r platform_get_resource(pdev, IORESOURCE_MEM, 0);
46 fep-hwp devm_ioremap_resource(pdev-dev, r);
47 if (IS_ERR(fep-hwp)) {
48 ret PTR_ERR(fep-hwp);
49 goto failed_ioremap;
50 }
51
52 fep-pdev pdev;
53 fep-dev_id dev_id;
54
55 platform_set_drvdata(pdev, ndev);
56
57 fec_enet_of_parse_stop_mode(pdev);
58
59 if (of_get_property(np, fsl,magic-packet, NULL))
60 fep-wol_flag | FEC_WOL_HAS_MAGIC_PACKET;
61
62 phy_node of_parse_phandle(np, phy-handle, 0);
63 if (!phy_node of_phy_is_fixed_link(np)) {
64 ret of_phy_register_fixed_link(np);
65 if (ret 0) {
66 dev_err(pdev-dev,
67 broken fixed-link specification\n);
68 goto failed_phy;
69 }
70 phy_node of_node_get(np);
71 }
72 fep-phy_node phy_node;
73
74 ret of_get_phy_mode(pdev-dev.of_node);
75 if (ret 0) {
76 pdata dev_get_platdata(pdev-dev);
77 if (pdata)
78 fep-phy_interface pdata-phy;
79 else
80 fep-phy_interface PHY_INTERFACE_MODE_MII;
81 } else {
82 fep-phy_interface ret;
83 }
84
85 fep-clk_ipg devm_clk_get(pdev-dev, ipg);
86 if (IS_ERR(fep-clk_ipg)) {
87 ret PTR_ERR(fep-clk_ipg);
88 goto failed_clk;
89 }
90
91 fep-clk_ahb devm_clk_get(pdev-dev, ahb);
92 if (IS_ERR(fep-clk_ahb)) {
93 ret PTR_ERR(fep-clk_ahb);
94 goto failed_clk;
95 }
96
97 fep-itr_clk_rate clk_get_rate(fep-clk_ahb);
98
99 /* enet_out is optional, depends on board */
100 fep-clk_enet_out devm_clk_get(pdev-dev, enet_out);
101 if (IS_ERR(fep-clk_enet_out))
102 fep-clk_enet_out NULL;
103
104 fep-ptp_clk_on false;
105 mutex_init(fep-ptp_clk_mutex);
106
107 /* clk_ref is optional, depends on board */
108 fep-clk_ref devm_clk_get(pdev-dev, enet_clk_ref);
109 if (IS_ERR(fep-clk_ref))
110 fep-clk_ref NULL;
111
112 fep-bufdesc_ex fep-quirks FEC_QUIRK_HAS_BUFDESC_EX;
113 fep-clk_ptp devm_clk_get(pdev-dev, ptp);
114 if (IS_ERR(fep-clk_ptp)) {
115 fep-clk_ptp NULL;
116 fep-bufdesc_ex false;
117 }
118
119 pm_runtime_enable(pdev-dev);
120 ret fec_enet_clk_enable(ndev, true);
121 if (ret)
122 goto failed_clk;
123
124 fep-reg_phy devm_regulator_get(pdev-dev, phy);
125 if (!IS_ERR(fep-reg_phy)) {
126 ret regulator_enable(fep-reg_phy);
127 if (ret) {
128 dev_err(pdev-dev,
129 Failed to enable phy regulator: %d\n, ret);
130 goto failed_regulator;
131 }
132 } else {
133 fep-reg_phy NULL;
134 }
135
136 fec_reset_phy(pdev);
137
138 if (fep-bufdesc_ex)
139 fec_ptp_init(pdev);
140
141 ret fec_enet_init(ndev);
142 if (ret)
143 goto failed_init;
144
145 for (i 0; i FEC_IRQ_NUM; i) {
146 irq platform_get_irq(pdev, i);
147 if (irq 0) {
148 if (i)
149 break;
150 ret irq;
151 goto failed_irq;
152 }
153 ret devm_request_irq(pdev-dev, irq, fec_enet_interrupt,
154 0, pdev-name, ndev);
155 if (ret)
156 goto failed_irq;
157
158 fep-irq[i] irq;
159 }
160
161 ret of_property_read_u32(np, fsl,wakeup_irq, irq);
162 if (!ret irq FEC_IRQ_NUM)
163 fep-wake_irq fep-irq[irq];
164 else
165 fep-wake_irq fep-irq[0];
166
167 init_completion(fep-mdio_done);
168 ret fec_enet_mii_init(pdev);
169 if (ret)
170 goto failed_mii_init;
171
172 /* Carrier starts down, phylib will bring it up */
173 netif_carrier_off(ndev);
174 fec_enet_clk_enable(ndev, false);
175 pinctrl_pm_select_sleep_state(pdev-dev);
176
177 ret register_netdev(ndev);
178 if (ret)
179 goto failed_register;
180
181 device_init_wakeup(ndev-dev, fep-wol_flag
182 FEC_WOL_HAS_MAGIC_PACKET);
183
184 if (fep-bufdesc_ex fep-ptp_clock)
185 netdev_info(ndev, registered PHC device %d\n, fep-dev_id);
186
187 fep-rx_copybreak COPYBREAK_DEFAULT;
188 INIT_WORK(fep-tx_timeout_work, fec_enet_timeout_work);
189 return 0;
......
206 return ret;
207 }
第14 行使用fec_enet_get_queue_num 函数来获取设备树中的“fsl,num-tx-queues”和“fsl,num-rx-queues”这两个属性值也就是发送队列和接收队列的大小设备树中这两个属性都设置为1。 第17 行使用alloc_etherdev_mqs 函数申请net_device。 第25 行获取net_device 中私有数据内存首地址net_device 中的私有数据用来存放I.MX6ULL 网络设备结构体此结构体为fec_enet_private。 第30 行接下来所有以“fep-”开头的代码行就是初始化网络设备结构体各个成员变量结构体类型为fec_enet_privatede这个结构体是NXP 自己定义的。 第45 行获取设备树中I.MX6ULL 网络外设(ENET)相关寄存器起始地址ENET1 的寄存器起始地址0X02188000ENET2 的寄存器起始地址0X20B4000。 第46 行对第45 行获取到的地址做虚拟地址转换转换后的ENET 虚拟寄存器起始地址保存在fep 的hwp 成员中。 第57 行使用fec_enet_of_parse_stop_mode 函数解析设备树中关于ENET 的停止模式属性值属性名字为“stop-mode”我们没有用到。 第59 行从设备树查找“fsl,magic-packet”属性是否存在如果存在的话就说明有魔术包有魔术包的话就将fep 的wol_flag 成员与FEC_WOL_HAS_MAGIC_PACKET 进行或运算也就是在wol_flag 中做登记登记支持魔术包。 第62 行获取“phy-handle”属性的值phy-handle 属性指定了I.MX6ULL 网络外设所对应获取PHY 的设备节点。在设备树的fec1 和fec2 两个节点中phy-handle 属性值分别为
phy-handle ethphy0;
phy-handle ethphy1;而ethphy0 和ethphy1 都定义在mdio 子节点下内容如下所示
mdio {
#address-cells 1;
#size-cells 0;
ethphy0: ethernet-phy0 {
compatible ethernet-phy-ieee802.3-c22;
reg 0;
};
ethphy1: ethernet-phy1 {
compatible ethernet-phy-ieee802.3-c22;
reg 1;
};
};
可以看出ethphy0 和ethphy1 都是与MDIO 相关的而MDIO 接口是配置PHY 芯片的通过一个MDIO 接口可以配置多个PHY 芯片不同的PHY 芯片通过不同的地址进行区别。正点原子ALPHA 开发板中ENET 的PHY 地址为0X00ENET2 的PHY 地址为0X01。这两个PHY地址要通过设备树告诉Linux 系统下面两行代码后面的数值就是PHY 地址
ethphy0: ethernet-phy2
ethphy1: ethernet-phy1并且ethphy0 和ethphy1 节点中的reg 属性也是PHY 地址如果我们要更换其他的网络PHY 芯片第一步就是要修改设备树中的PHY 地址。 第74 行获取PHY 工作模式函数of_get_phy_mode 会读取属性phy-mode 的值”phy-mode”中保存了PHY 的工作方式即PHY 是RMII 还是MIIIMX6ULL 中的PHY 工作在RMII 模式设备树描述如下所示 第85、91、100、108 和113 行分别获取时钟ipg、ahb、enet_out、enet_clk_ref 和ptp对应结构体fec_enet_private 有如下成员函数
struct clk *clk_ipg;
struct clk *clk_ahb;
struct clk *clk_ref;
struct clk *clk_enet_out;
struct clk *clk_ptp;第120 行使能时钟。 第136 行调用函数fec_reset_phy 复位PHY。 第141 行调用函数fec_enet_init()初始化enet此函数会分配队列、申请dma、设置MAC地址初始化net_device 的netdev_ops 和ethtool_ops 成员如图69.4.2.1 所示
图69.4.2.1 设置netdev_ops 和ethtool_ops 从图69.4.2.1 可以看出net_device 的netdev_ops 和ethtool_ops 成变量分别初始化成了fec_netdev_ops 和fec_enet_ethtool_ops。fec_enet_init 函数还会调用netif_napi_add 来设置poll 函数说明NXP 官方编写的此网络驱动是NAPI 兼容驱动如图69.4.2.2 所示
图69.4.2.2 netif_napi_add 函数
从图69.4.2.2 可以看出通过netif_napi_add 函数向网卡添加了一个napi 示例使用NAPI驱动要提供一个poll 函数来轮询处理接收数据此处的poll 函数为fec_enet_rx_napi后面分析网络数据接收处理流程的时候详细讲解此函数。 最后fec_enet_init 函数会设置IMX6ULL 网络外设相关硬件寄存器。 第146 行从设备树中获取中断号。 第153 行申请中断中断处理函数为fec_enet_interrupt重点后面会分析此函数。 第161 行从设备树中获取属性“fsl,wakeup_irq”的值也就是唤醒中断 第167 行初始化完成量completion用于一个执行单元等待另一个执行单元执行完某事。 第168 行函数fec_enet_mii_init 完成MII/RMII 接口的初始化此函数重点是图69.4.2.3中的两行代码
图69.4.2.3 mdio 读写函数 mii_bus 下的read 和write 这两个成员变量分别是读/写PHY 寄存器的操作函数这里设置为fec_enet_mdio_read 和fec_enet_mdio_write这两个函数就是I.MX 系列SOC 读写PHY 内部寄存器的函数读取或者配置PHY 寄存器都会通过这两个MDIO 总线函数完成。 fec_enet_mii_init 函数最终会向Linux 内核注册MIDO 总线相关代码如下所示
示例代码69.4.2.2 fec_enet_mii_init 函数注册mdio 总线
1 node of_get_child_by_name(pdev-dev.of_node, mdio);
2 if (node) {
3 err of_mdiobus_register(fep-mii_bus, node);
4 of_node_put(node);
5 } else {
6 err mdiobus_register(fep-mii_bus);
7 }示例代码代码69.4.2.2 中第1 行就是从设备树中获取mdio 节点如果节点存在的话就会通过of_mdiobus_register 或者mdiobus_register 来向内核注册MDIO 总线如果采用设备树的话就使用of_mdiobus_register 来注册MDIO 总线否则就使用mdiobus_register 函数。 继续回到示例代码69.4.2.1接着分析fec_probe 函数。 第173 行先调用函数netif_carrier_off 通知内核先关闭链路phylib 会打开。 第174 行调用函数fec_enet_clk_enable 使能网络相关时钟。 第177 行调用函数register_netdev 注册net_device 2、MDIO 总线注册 MDIO 我们讲了很多次了就是用来管理PHY 芯片的分为MDIO 和MDC 两根线Linux内核专门为MDIO 准备一个总线叫做MDIO 总线采用mii_bus 结构体表示定义在include/linux/phy.h 文件中mii_bus 结构体如下所示
示例代码69.4.2.3 mii_bus 结构体
1 struct mii_bus {
2 const char *name;
3 char id[MII_BUS_ID_SIZE];
4 void *priv;
5 int (*read)(struct mii_bus *bus, int phy_id, int regnum);
6 int (*write)(struct mii_bus *bus, int phy_id, int regnum,
u16 val);
7 int (*reset)(struct mii_bus *bus);
8
9 /*
10 * A lock to ensure that only one thing can read/write
11 * the MDIO bus at a time
12 */
13 struct mutex mdio_lock;
14
15 struct device *parent;
16 enum {
17 MDIOBUS_ALLOCATED 1,
18 MDIOBUS_REGISTERED,
19 MDIOBUS_UNREGISTERED,
20 MDIOBUS_RELEASED,
21 } state;
22 struct device dev;
23
24 /* list of all PHYs on bus */
25 struct phy_device *phy_map[PHY_MAX_ADDR];
26
27 /* PHY addresses to be ignored when probing */
28 u32 phy_mask;
29
30 /*
31 * Pointer to an array of interrupts, each PHYs
32 * interrupt at the index matching its address
33 */
34 int *irq;
35 };
重点是第5、6 两行的read 和write 函数这两个函数就是读/些PHY 芯片的操作函数不同的SOC 其MDIO 主控部分是不一样的因此需要驱动编写人员去编写。我们前面在分析fec_probe 函数的时候已经讲过了fec_probe 函数会调用fec_enet_mii_init 函数完成MII 接口的初始化其中就包括初始化mii_bus 下的read 和write 这两个函数。最终通过of_mdiobus_register或者mdiobus_register 函数将初始化以后的mii_bus 注册到Linux 内核of_mdiobus_register 函数其实最终也是调用的mdiobus_register 函数来完成mii_bus 注册的。of_mdiobus_register 函数内容如下(限于篇幅有省略)
示例代码69.4.2.4 of_mdiobus_register 函数
1 int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
2 {
3 struct device_node *child;
4 const __be32 *paddr;
5 bool scanphys false;
6 int addr, rc, i;
7
8 /* Mask out all PHYs from auto probing. Instead the PHYs listed
9 * in the device tree are populated after the bus has been
*registered */
10 mdio-phy_mask ~0;
11
12 /* Clear all the IRQ properties */
13 if (mdio-irq)
14 for (i0; iPHY_MAX_ADDR; i)
15 mdio-irq[i] PHY_POLL;
16
17 mdio-dev.of_node np;
18
19 /* Register the MDIO bus */
20 rc mdiobus_register(mdio);
21 if (rc)
22 return rc;
23
24 /* Loop over the child nodes and register a phy_device for each
one */
25 for_each_available_child_of_node(np, child) {
26 addr of_mdio_parse_addr(mdio-dev, child);
27 if (addr 0) {
28 scanphys true;
29 continue;
30 }
31
32 rc of_mdiobus_register_phy(mdio, child, addr);
33 if (rc)
34 continue;
35 }
36
37 if (!scanphys)
38 return 0;
39
......
62 return 0;
63 }
第20 行调用mdiobus_register 函数来向Linux 内核注册mdio 总线 第25 行轮询mdio 节点下的所有子节点比如示例代码69.4.1.2 中的“ethphy0: ethernet-phy0”和“ethphy1: ethernet-phy1”这两个子节点这两个子节点描述的是PHY 芯片信息。 第26 行提取设备树子节点中PHY 地址也就是ethphy0: ethernet-phy0”和“ethphy1: ethernet-phy1”这两个子节点对应的PHY 芯片地址分别为0 和1。 第32 行调用of_mdiobus_register_phy 函数向Linux 内核注册phy。 简单总结一下of_mdiobus_register 函数有两个主要的功能一个是通过mdiobus_register函数向Linux 内核注册mdio 总线另一个就是通过of_mdiobus_register_phy 函数向内核注册PHY。 接下来简单分析一下of_mdiobus_register_phy 函数看看是如何向Linux 内核注册PHY 设备的of_mdiobus_register_phy 函数内容如下所示
示例代码69.4.2.5 of_mdiobus_register_phy 函数
1 static int of_mdiobus_register_phy(struct mii_bus *mdio,
struct device_node *child,
2 u32 addr)
3 {
4 struct phy_device *phy;
5 bool is_c45;
6 int rc;
7 u32 phy_id;
8
9 is_c45 of_device_is_compatible(child,
10 ethernet-phy-ieee802.3-c45);
11
12 if (!is_c45 !of_get_phy_id(child, phy_id))
13 phy phy_device_create(mdio, addr, phy_id, 0, NULL);
14 else
15 phy get_phy_device(mdio, addr, is_c45);
16 if (!phy || IS_ERR(phy))
17 return 1;
18
19 rc irq_of_parse_and_map(child, 0);
20 if (rc 0) {
21 phy-irq rc;
22 if (mdio-irq)
23 mdio-irq[addr] rc;
24 } else {
25 if (mdio-irq)
26 phy-irq mdio-irq[addr];
27 }
28
29 /* Associate the OF node with the device structure so it
30 * can be looked up later */
31 of_node_get(child);
32 phy-dev.of_node child;
33
4 /* All data is now stored in the phy struct;
35 * register it */
36 rc phy_device_register(phy);
37 if (rc) {
38 phy_device_free(phy);
39 of_node_put(child);
40 return 1;
41 }
42
43 dev_dbg(mdio-dev, registered phy %s at address %i\n,
44 child-name, addr);
45
46 return 0;
47 }
第9 行使用函数of_device_is_compatible 检查PHY 节点的compatible 属性是否为“ethernet-phy-ieee802.3-c45”如果是的话要做其他的处理本章节我们设置的compatible 属性为“ethernet-phy-ieee802.3-c22”。 第15 行调用get_phy_device 函数获取PHY 设备此函数里面会调用phy_device_create 来创建一个phy_device 设备并返回。 第19 行获取PHY 芯片的中断信息本章节并未用到。 第36 行调用phy_device_register 函数向Linux 内核注册PHY 设备。 从上面的分析可以看出向Linux 内核注册MDIO 总线的时候也会同时向Linux 内核注册PHY 设备流程如图69.4.2.3 所示 图69.4.2.3 MDIO 总线注册流程 注册MIDO 总线的时候会从设备树中查找PHY 设备然后通过phy_device_register 函数向内核注册PHY 设备接下来我们就来学习一下PHY 子系统。 3、fec_drv_remove 函数简析 卸载I.MX6ULL 网络驱动的时候fec_drv_remove 函数就会执行函数内容如下所示
示例代码69.4.2.6 fec_drv_remove 函数
1 static int fec_drv_remove(struct platform_device *pdev)
2 {
3 struct net_device *ndev platform_get_drvdata(pdev);
4 struct fec_enet_private *fep netdev_priv(ndev);
5
6 cancel_delayed_work_sync(fep-time_keep);
7 cancel_work_sync(fep-tx_timeout_work);
8 unregister_netdev(ndev);
9 fec_enet_mii_remove(fep);
10 if (fep-reg_phy)
11 regulator_disable(fep-reg_phy);
12 if (fep-ptp_clock)
13 ptp_clock_unregister(fep-ptp_clock);
14 of_node_put(fep-phy_node);
15 free_netdev(ndev);
16
17 return 0;
18 }
第8 行调用unregister_netdev 函数注销前面注册的net_device。 第9 行调用fec_enet_mii_remove 函数来移除掉MDIO 总线相关的内容此函数会调用mdiobus_unregister 来注销掉mii_bus并且通过函数mdiobus_free 释放掉mii_bus 第15 行调用free_netdev 函数释放掉前面申请的net_device。
fec_netdev_ops 操作集
fec_probe 函数设置了网卡驱动的net_dev_ops 操作集为fec_netdev_opsfec_netdev_ops 内容如下
示例代码69.4.3.1 fec_netdev_ops 操作集
1 static const struct net_device_ops fec_netdev_ops {
2 .ndo_open fec_enet_open,
3 .ndo_stop fec_enet_close,
4 .ndo_start_xmit fec_enet_start_xmit,
5 .ndo_select_queue fec_enet_select_queue,
6 .ndo_set_rx_mode set_multicast_list,
7 .ndo_change_mtu eth_change_mtu,
8 .ndo_validate_addr eth_validate_addr,
9 .ndo_tx_timeout fec_timeout,
10 .ndo_set_mac_address fec_set_mac_address,
11 .ndo_do_ioctl fec_enet_ioctl,
12 #ifdef CONFIG_NET_POLL_CONTROLLER
13 .ndo_poll_controller fec_poll_controller,
14 #endif
15 .ndo_set_features fec_set_features,
16 };1、fec_enet_open 函数简析 打开一个网卡的时候fec_enet_open 函数就会执行函数源码如下所示(限于篇幅原因有省略)
示例代码69.4.3.2 fec_enet_open 函数
1 static int fec_enet_open(struct net_device *ndev)
2 {
3 struct fec_enet_private *fep netdev_priv(ndev);
4 const struct platform_device_id *id_entry
5 platform_get_device_id(fep-pdev);
6 int ret;
7
8 pinctrl_pm_select_default_state(fep-pdev-dev);
9 ret fec_enet_clk_enable(ndev, true);
10 if (ret)
11 return ret;
12
13 /* I should reset the ring buffers here, but I dont yet know
14 * a simple way to do that.
15 */
16
17 ret fec_enet_alloc_buffers(ndev);
18 if (ret)
19 goto err_enet_alloc;
20
21 /* Init MAC prior to mii bus probe */
22 fec_restart(ndev);
23
24 /* Probe and connect to PHY when open the interface */
25 ret fec_enet_mii_probe(ndev);
26 if (ret)
27 goto err_enet_mii_probe;
28
29 napi_enable(fep-napi);
30 phy_start(fep-phy_dev);
31 netif_tx_start_all_queues(ndev);
32
......
47
48 return 0;
49
50 err_enet_mii_probe:
51 fec_enet_free_buffers(ndev);
52 err_enet_alloc:
53 fep-miibus_up_failed true;
54 if (!fep-mii_bus_share)
55 pinctrl_pm_select_sleep_state(fep-pdev-dev);
56 return ret;
57 }
第9 行调用fec_enet_clk_enable 函数使能enet 时钟。 第17 行调用fec_enet_alloc_buffers 函数申请环形缓冲区buffer此函数里面会调用fec_enet_alloc_rxq_buffers 和fec_enet_alloc_txq_buffers 这两个函数分别实现发送队列和接收队列缓冲区的申请。 第22 行重启网络一般连接状态改变、传输超时或者配置网络的时候都会调用fec_restart函数。 第25 行打开网卡的时候调用fec_enet_mii_probe 函数来探测并连接对应的PHY 设备。 第29 行调用napi_enable 函数使能NAPI 调度。 第30 行调用phy_start 函数开启PHY 设备。 第31 行调用netif_tx_start_all_queues 函数来激活发送队列。 2、fec_enet_close 函数简析 关闭网卡的时候fec_enet_close 函数就会执行函数内容如下
示例代码69.4.3.3 fec_enet_close 函数
1 static int fec_enet_close(struct net_device *ndev)
2 {
3 struct fec_enet_private *fep netdev_priv(ndev);
4
5 phy_stop(fep-phy_dev);
6
7 if (netif_device_present(ndev)) {
8 napi_disable(fep-napi);
9 netif_tx_disable(ndev);
10 fec_stop(ndev);
11 }
12
13 phy_disconnect(fep-phy_dev);
14 fep-phy_dev NULL;
15
16 fec_enet_clk_enable(ndev, false);
17 pm_qos_remove_request(fep-pm_qos_req);
18 pinctrl_pm_select_sleep_state(fep-pdev-dev);
19 pm_runtime_put_sync_suspend(ndev-dev.parent);
20 fec_enet_free_buffers(ndev);
21
22 return 0;
23 }第5 行调用phy_stop 函数停止PHY 设备。 第8 行调用napi_disable 函数关闭NAPI 调度。 第9 行调用netif_tx_disable 函数关闭NAPI 的发送队列。 第10 行调用fec_stop 函数关闭I.MX6ULL 的ENET 外设。 第13 行调用phy_disconnect 函数断开与PHY 设备的连接。 第16 行调用fec_enet_clk_enable 函数关闭ENET 外设时钟。 第20 行调用fec_enet_free_buffers 函数释放发送和接收的环形缓冲区内存。 3、fec_enet_start_xmit 函数简析
I.MX6ULL 的网络数据发送是通过fec_enet_start_xmit 函数来完成的这个函数将上层传递过来的sk_buff 中的数据通过硬件发送出去函数源码如下
示例代码69.4.3.4 fec_enet_start_xmit 函数
1 static netdev_tx_t fec_enet_start_xmit(struct sk_buff *skb,
struct net_device *ndev)
2 {
3 struct fec_enet_private *fep netdev_priv(ndev);
4 int entries_free;
5 unsigned short queue;
6 struct fec_enet_priv_tx_q *txq;
7 struct netdev_queue *nq;
8 int ret;
9
10 queue skb_get_queue_mapping(skb);
11 txq fep-tx_queue[queue];
12 nq netdev_get_tx_queue(ndev, queue);
13
14 if (skb_is_gso(skb))
15 ret fec_enet_txq_submit_tso(txq, skb, ndev);
16 else
17 ret fec_enet_txq_submit_skb(txq, skb, ndev);
18 if (ret)
19 return ret;
20
21 entries_free fec_enet_get_free_txdesc_num(fep, txq);
22 if (entries_free txq-tx_stop_threshold)
23 netif_tx_stop_queue(nq);
24
25 return NETDEV_TX_OK;
26 }此函数的参数第一个参数skb 就是上层应用传递下来的要发送的网络数据第二个参数ndev 就是要发送数据的设备。 第14 行判断skb 是否为GSO(Generic Segmentation Offload)如果是GSO 的话就通过fec_enet_txq_submit_tso 函数发送如果不是的话就通过fec_enet_txq_submit_skb 发送。这里简单讲一下TSO 和GSO: TSO全称是TCP Segmentation Offload利用网卡对大数据包进行自动分段处理降低CPU负载。 GSO全称是Generic Segmentation Offload在发送数据之前先检查一下网卡是否支持TSO如果支持的话就让网卡分段不过不支持的话就由协议栈进行分段处理分段处理完成以后再交给网卡去发送。 第21 行通过fec_enet_get_free_txdesc_num 函数获取剩余的发送描述符数量。 第23 行如果剩余的发送描述符的数量小于设置的阈值(tx_stop_threshold)的话就调用函数netif_tx_stop_queue 来暂停发送通过暂停发送来通知应用层停止向网络发送skb发送中断中会重新开启的。 4、fec_enet_interrupt 中断服务函数简析 前面说了I.MX6ULL 的网络数据接收采用NAPI 框架所以肯定要用到中断。fec_probe 函数会初始化网络中断中断服务函数为fec_enet_interrupt函数内容如下
示例代码69.4.3.5 fec_enet_interrupt 函数
1 static irqreturn_t fec_enet_interrupt(int irq, void *dev_id)
2 {
3 struct net_device *ndev dev_id;
4 struct fec_enet_private *fep netdev_priv(ndev);
5 uint int_events;
6 irqreturn_t ret IRQ_NONE;
7
8 int_events readl(fep-hwp FEC_IEVENT);
9 writel(int_events, fep-hwp FEC_IEVENT);
10 fec_enet_collect_events(fep, int_events);
11
12 if ((fep-work_tx || fep-work_rx) fep-link) {
13 ret IRQ_HANDLED;
14
15 if (napi_schedule_prep(fep-napi)) {
16 /* Disable the NAPI interrupts */
17 writel(FEC_ENET_MII, fep-hwp FEC_IMASK);
18 __napi_schedule(fep-napi);
19 }
20 }
21
22 if (int_events FEC_ENET_MII) {
23 ret IRQ_HANDLED;
24 complete(fep-mdio_done);
25 }
26
27 if (fep-ptp_clock)
28 fec_ptp_check_pps_event(fep);
29
30 return ret;
31 }可以看出中断服务函数非常短而且也没有见到有关数据接收的处理过程那是因为I.MX6ULL 的网络驱动使用了NAPI具体的网络数据收发是在NAPI 的poll 函数中完成的中断里面只需要进行napi 调度即可这个就是中断的上半部和下半部处理机制。 第8 行读取NENT 的中断状态寄存器EIR获取中断状态 第9 行将第8 行获取到的中断状态值又写入EIR 寄存器用于清除中断状态寄存器。 第10 行调用fec_enet_collect_events 函数统计中断信息也就是统计都发生了哪些中断。 fep 中成员变量work_tx 和work_rx 的bit0、bit1 和bit2 用来做不同的标记work_rx 的bit2 表示接收到数据帧work_tx 的bit2 表示发送完数据帧。 第15 行调用napi_schedule_prep 函数检查NAPI 是否可以进行调度。 第17 行如果使能了相关中断就要先关闭这些中断向EIMR 寄存器的bit23 写1 即可关闭相关中断。 第18 行调用__napi_schedule 函数来启动NAPI 调度这个时候napi 的poll 函数就会执行在本网络驱动中就是fec_enet_rx_napi 函数。 5、fec_enet_interrupt 中断服务函数简析 fec_enet_init 函数初始化网络的时候会调用netif_napi_add 来设置NAPI 的poll 函数为fec_enet_rx_napi函数内容如下
示例代码69.4.3.6 fec_enet_rx_napi 轮询函数
1 static int fec_enet_rx_napi(struct napi_struct *napi, int budget)
2 {
3 struct net_device *ndev napi-dev;
4 struct fec_enet_private *fep netdev_priv(ndev);
5 int pkts;
6
7 pkts fec_enet_rx(ndev, budget);
8
9 fec_enet_tx(ndev);
10
11 if (pkts budget) {
12 napi_complete(napi);
13 writel(FEC_DEFAULT_IMASK, fep-hwp FEC_IMASK);
14 }
15 return pkts;
16 }第7 行调用fec_enet_rx 函数进行真正的数据接收。 第9 行调用fec_enet_tx 函数进行数据发送。 第12 行调用napi_complete 函数来宣布一次轮询结束 第13 行设置ENET 的EIMR 寄存器重新使能中断。
Linux 内核PHY 子系统与MDIO 总线简析
上一小节在讲解MDIO 总线的时候讲过注册MDIO 总线的时候也会向内核注册PHY 设备本节我们就来简单了解一下PHY 子系统。PHY 子系统就是用于PHY 设备相关内容的分为PHY 设备和PHY 驱动和platform 总线一样PHY 子系统也是一个设备、总线和驱动模型。 1、PHY 设备 首先看一下PHY 设备Linux 内核使用phy_device 结构体来表示PHY 设备结构体定义在include/linux/phy.h结构体内容如下
示例代码69.4.3.1 phy_device 结构体
1 struct phy_device {
2 /* Information about the PHY type */
3 /* And management functions */
4 struct phy_driver *drv; /* PHY设备驱动*/
5 struct mii_bus *bus; /* 对应的MII总线*/
6 struct device dev; /* 设备文件*/
7 u32 phy_id; /* PHY ID */
8
9 struct phy_c45_device_ids c45_ids;
10 bool is_c45;
11 bool is_internal;
12 bool has_fixups;
13 bool suspended;
14
15 enum phy_state state; /* PHY状态*/
16 u32 dev_flags;
17 phy_interface_t interface; /* PHY接口*/
18
19 /* Bus address of the PHY (0-31) */
20 int addr; /* PHY地址(0~31) */
21
22 /*
23 * forced speed duplex (no autoneg)
24 * partner speed duplex pause (autoneg)
25 */
26 int speed; /* 速度*/
27 int duplex; /* 双共模式*/
28 int pause;
29 int asym_pause;
30
31 /* The most recently read link state */
32 int link;
33
34 /* Enabled Interrupts */
35 u32 interrupts; /* 中断使能标志*/
36
37 /* Union of PHY and Attached devices supported modes */
38 /* See mii.h for more info */
39 u32 supported;
40 u32 advertising;
41 u32 lp_advertising;
42 int autoneg;
43 int link_timeout;
44
45 /*
46 * Interrupt number for this PHY
47 * -1 means no interrupt
48 */
49 int irq; /* 中断号*/
50
51 /* private data pointer */
52 /* For use by PHYs to maintain extra state */
53 void *priv; /* 私有数据*/
54
55 /* Interrupt and Polling infrastructure */
56 struct work_struct phy_queue;
57 struct delayed_work state_queue;
58 atomic_t irq_disable;
59 struct mutex lock;
60 struct net_device *attached_dev; /* PHY芯片对应的网络设备*/
61 void (*adjust_link)(struct net_device *dev);
62 };
一个PHY 设备对应一个phy_device 实例然后需要向Linux 内核注册这个实例。使用phy_device_register 函数完成PHY 设备的注册函数原型如下
int phy_device_register(struct phy_device *phy)函数参数和返回值含义如下 phy需要注册的PHY 设备。 返回值0 成功负值失败。 PHY 设备的注册过程一般是先调用get_phy_device 函数获取PHY 设备此函数内容如下
示例代码69.4.3.2 get_phy_device 函数
1 struct phy_device *get_phy_device(struct mii_bus *bus, int addr,
bool is_c45)
2 {
3 struct phy_c45_device_ids c45_ids {0};
4 u32 phy_id 0;
5 int r;
6
7 r get_phy_id(bus, addr, phy_id, is_c45, c45_ids);
8 if (r)
9 return ERR_PTR(r);
10
11 /* If the phy_id is mostly Fs, there is no device there */
12 if ((phy_id 0x1fffffff) 0x1fffffff)
13 return NULL;
14
15 return phy_device_create(bus, addr, phy_id, is_c45, c45_ids);
16 }第7 行调用get_phy_id 函数获取PHY ID也就是读取PHY 芯片的那两个ID 寄存器得到PHY 芯片ID 信息。 第15 行调用phy_device_create 函数创建phy_device此函数先申请phy_device 内存然后初始化phy_device 的各个结构体成员最终返回创建好的phy_device。phy_device_register 函数注册的就是这个创建好的phy_device。 2、PHY 驱动 PHY 驱动使用结构体phy_driver 表示结构体也定义在include/linux/phy.h 文件中结构体内容如下(为了缩小篇幅省略了注释部分)
示例代码69.4.3.3 phy_driver 结构体
1 struct phy_driver {
2 u32 phy_id; /* PHY ID */
3 char *name;
4 unsigned int phy_id_mask; /* PHY ID掩码*/
5 u32 features;
6 u32 flags;
7 const void *driver_data;
8
9 int (*soft_reset)(struct phy_device *phydev);
10 int (*config_init)(struct phy_device *phydev);
11 int (*probe)(struct phy_device *phydev);
12 int (*suspend)(struct phy_device *phydev);
13 int (*resume)(struct phy_device *phydev);
14 int (*config_aneg)(struct phy_device *phydev);
15 int (*aneg_done)(struct phy_device *phydev);
16 int (*read_status)(struct phy_device *phydev);
17 int (*ack_interrupt)(struct phy_device *phydev);
18 int (*config_intr)(struct phy_device *phydev);
19 int (*did_interrupt)(struct phy_device *phydev);
20 void (*remove)(struct phy_device *phydev);
21 int (*match_phy_device)(struct phy_device *phydev);
22 int (*ts_info)(struct phy_device *phydev,
struct ethtool_ts_info *ti);
23 int (*hwtstamp)(struct phy_device *phydev, struct ifreq *ifr);
24 bool (*rxtstamp)(struct phy_device *dev, struct sk_buff *skb,
int type);
25 void (*txtstamp)(struct phy_device *dev, struct sk_buff *skb,
int type);
26 int (*set_wol)(struct phy_device *dev,
struct ethtool_wolinfo *wol);
27 void (*get_wol)(struct phy_device *dev,
struct ethtool_wolinfo *wol);
28 void (*link_change_notify)(struct phy_device *dev);
29 int (*read_mmd_indirect)(struct phy_device *dev, int ptrad,
30 int devnum, int regnum);
31 void (*write_mmd_indirect)(struct phy_device *dev, int ptrad,
32 int devnum, int regnum, u32 val);
33 int (*module_info)(struct phy_device *dev,
34 struct ethtool_modinfo *modinfo);
35 int (*module_eeprom)(struct phy_device *dev,
36 struct ethtool_eeprom *ee, u8 *data);
37
38 struct device_driver driver;
39 };
可以看出phy_driver 重点是大量的函数编写PHY 驱动的主要工作就是实现这些函数但是不一定全部实现稍后我们会简单分析一下Linux 内核通用PHY 驱动。 ①、注册PHY 驱动 phy_driver 结构体初始化完成以后就需要向Linux 内核注册PHY 驱动的注册使用phy_driver_register 函数注册phy 驱动的时候会设置驱动的总线为mdio_bus_type也就是MDIO总线关于MDIO 总线稍后会讲解函数原型如下
int phy_driver_register(struct phy_driver *new_driver)函数参数和返回值含义如下 new_driver需要注册的PHY 驱动。 返回值0 成功负值失败。 ②、连续注册多个PHY 驱动 一个厂家会生产多种PHY 芯片这些PHY 芯片内部差别一般不大如果一个个的去注册驱动将会导致一堆重复的驱动文件因此Linux 内核提供了一个连续注册多个PHY 驱动的函数phy_drivers_register。首先准备一个phy_driver 数组一个数组元素就表示一个PHY 芯片的驱动然后调用phy_drivers_register 一次性注册整个数组中的所有驱动函数原型如下
int phy_drivers_register(struct phy_driver *new_driver, int n)函数参数和返回值含义如下 new_driver需要注册的多个PHY 驱动数组。 n要注册的驱动数量。 返回值0 成功负值失败。 ③、卸载PHY 驱动 卸载PHY 驱动的话使用phy_driver_unregister 函数函数原型如下 void phy_driver_unregister(struct phy_driver *drv) 函数参数和返回值含义如下 new_driver需要卸载的PHY 驱动。 返回值无。 3、MDIO 总线 前面说了PHY 子系统也是遵循设备、总线、驱动模型的设备和驱动就是phy_device 和phy_driver。总线就是MDIO 总线因为PHY 芯片是通过MIDO 接口来管理的MDIO 总线最主要的工作就是匹配PHY 设备和PHY 驱动。在文件drivers/net/phy/mdio_bus.c 中有如下定义
示例代码69.4.3.4 mdio 总线
1 struct bus_type mdio_bus_type {
2 .name mdio_bus,
3 .match mdio_bus_match,
4 .pm MDIO_BUS_PM_OPS,
5 .dev_groups mdio_dev_groups,
6 };
示例代码69.4.3.4 定义了一个名为“mdio_bus_type”的总线这个就是MDIO 总线总线的名字为“mdio_bus”重点是总线的匹配函数为mdio_bus_match。此函数内容如下
示例代码69.4.3.5 mdio_bus_match 匹配函数
1 static int mdio_bus_match(struct device *dev,
struct device_driver *drv)
2 {
3 struct phy_device *phydev to_phy_device(dev);
4 struct phy_driver *phydrv to_phy_driver(drv);
5
6 if (of_driver_match_device(dev, drv))
7 return 1;
8
9 if (phydrv-match_phy_device)
10 return phydrv-match_phy_device(phydev);
11
12 return (phydrv-phy_id phydrv-phy_id_mask)
13 (phydev-phy_id phydrv-phy_id_mask);
14 }第6 行采用设备树的话先尝试使用of_driver_match_device 来对设备和驱动进行匹配也就是检查compatible 属性值与匹配表of_match_table 里面的内容是否一致。但是对于本章教程而言并不是通过of_driver_match_device 来完成PHY 驱动和设备匹配的。 第9、10 行检查PHY 驱动有没有提供匹配函数match_phy_device如果有的话就直接调用PHY 驱动提供的匹配函数完成与设备的匹配。 第12、13 行如果上面两个匹配方法都无效的话就使用最后一种phy_driver 里面有两个成员变量phy_id 和phy_id_mask表示此驱动所匹配的PHY 芯片ID 以及ID 掩码PHY 驱动编写人员需要给这两个成员变量赋值。phy_device 也有一个phy_id 成员变量表示此PHY 芯片的IDphy_device 里面的phy_id 是在注册PHY 设备的时候调用get_phy_id 函数直接读取PHY 芯片内部ID 寄存器得到的很明显PHY 驱动和PHY 设备中的ID 要一样这样才能匹配起来。所以最后一种方法就是对比PHY 驱动和PHY 设备中的phy_id 是否一致这里需要与 PHY 驱动里面的phy_id_mask 进行与运算如果结果一致的话就说明驱动和设备匹配。 如果PHY 设备和PHY 驱动匹配那么就使用指定的PHY 驱动如果不匹配的话就使用Linux 内核自带的通用PHY 驱动。 4、通用PHY 驱动 前面多次提到Linux 内核已经集成了通用PHY 驱动通用PHY驱动名字为“Generic PHY”打开drivers/net/phy/phy_device.c找到phy_init 函数内容如下
示例代码69.4.3.6 phy_init 函数
1 static int __init phy_init(void)
2 {
3 int rc;
4
5 rc mdio_bus_init();
6 if (rc)
7 return rc;
8
9 rc phy_drivers_register(genphy_driver,
10 ARRAY_SIZE(genphy_driver));
11 if (rc)
12 mdio_bus_exit();
13
14 return rc;
15 }
phy_init 是整个PHY 子系统的入口函数第9 行会调用phy_drivers_register 函数向内核直接注册一个通用PHY 驱动genphy_driver也就是通用PHY 驱动也就是说Linux 系统启动以后默认就已经存在了通用PHY 驱动。 genphy_driver 是一个数组有两个数组元素表示有两个通用的PHY 驱动一个是针对10/100/1000M 网络的一个是针对10G 网络的。genphy_driver 定义在drivers/net/phy/phy_device.c里面内容如下
示例代码69.4.3.7 通用PHY 驱动
1 static struct phy_driver genphy_driver[] {
2 {
3 .phy_id 0xffffffff,
4 .phy_id_mask 0xffffffff,
5 .name Generic PHY,
6 .soft_reset genphy_soft_reset,
7 .config_init genphy_config_init,
8 .features PHY_GBIT_FEATURES | SUPPORTED_MII |
9 SUPPORTED_AUI | SUPPORTED_FIBRE |
10 SUPPORTED_BNC,
11 .config_aneg genphy_config_aneg,
12 .aneg_done genphy_aneg_done,
13 .read_status genphy_read_status,
14 .suspend genphy_suspend,
15 .resume genphy_resume,
16 .driver { .owner THIS_MODULE, },
17 }, {
18 .phy_id 0xffffffff,
19 .phy_id_mask 0xffffffff,
20 .name Generic 10G PHY,
21 .soft_reset gen10g_soft_reset,
22 .config_init gen10g_config_init,
23 .features 0,
24 .config_aneg gen10g_config_aneg,
25 .read_status gen10g_read_status,
26 .suspend gen10g_suspend,
27 .resume gen10g_resume,
28 .driver {.owner THIS_MODULE, },
29 } };
genphy_driver 数组有两个元素genphy_driver[0]为10/100/1000M 的PHY 驱动名字为“Generic PHY”genphy_driver[1]为10G 的PHY 驱动名字为“Generic 10G PHY”。注意很多另外编写的PHY 驱动也会用到通用PHY 驱动的一些函数比如正点原子ALPHA 开发板所用的LAN8720A 是SMSC 公司的产品此公司针对自家的所有PHY 芯片编写了一个驱动文件smsc.c这驱动文件里面用到了大量的通用PHY 驱动相关函数。 5、LAN8720A 驱动 最后我们来看一下LAN8720A 的Linux 驱动LAN8720A 的驱动文件为 drivers/net/phy/smsc.c这个文件是SMSC 针对自家的一些PHY 芯片编写的驱动文件其中就包含了LAN8720A 这个PHY 芯片。默认情况下LAN8720A 这个驱动是没有打开的我们需要配置linux 内核打开此驱动选项配置路径如下
- Device Drivers- Network device support- PHY Device support and infrastructure- Drivers for SMSC PHYs配置界面如图69.4.3.1 所示
图69.4.3.1 使能LAN8720A 驱动 选中图69.4.3.1 中的“Drivers for SMSC PHYs”然后编译内核即可这个在37.4.3 小节讲解linux 移植的时候都已经说过了。 打开smsc.c找到如下所示内容(限于篇幅有删减)
示例代码69.4.3.8 通用PHY 驱动
1 static struct phy_driver smsc_phy_driver[] {
2 {
3 .phy_id 0x0007c0a0, /* OUI0x00800f, Model#0x0a */
4 .phy_id_mask 0xfffffff0,
5 .name SMSC LAN83C185,
......
24 .driver { .owner THIS_MODULE, }
25 }, {
26 .phy_id 0x0007c0b0, /* OUI0x00800f, Model#0x0b */
27 .phy_id_mask 0xfffffff0,
28 .name SMSC LAN8187,
......
47 .driver { .owner THIS_MODULE, }
48 }, {
49 .phy_id 0x0007c0c0, /* OUI0x00800f, Model#0x0c */
50 .phy_id_mask 0xfffffff0,
51 .name SMSC LAN8700,
......
70 .driver { .owner THIS_MODULE, }
71 }, {
72 .phy_id 0x0007c0d0, /* OUI0x00800f, Model#0x0d */
73 .phy_id_mask 0xfffffff0,
74 .name SMSC LAN911x Internal PHY,
......
92 .driver { .owner THIS_MODULE, }
93 }, {
94 .phy_id 0x0007c0f0, /* OUI0x00800f, Model#0x0f */
95 .phy_id_mask 0xfffffff0,
96 .name SMSC LAN8710/LAN8720,
97
98 .features (PHY_BASIC_FEATURES | SUPPORTED_Pause
99 | SUPPORTED_Asym_Pause),
100 .flags PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
101
102 /* basic functions */
103 .config_aneg genphy_config_aneg,
104 .read_status lan87xx_read_status,
105 .config_init smsc_phy_config_init,
106 .soft_reset smsc_phy_reset,
107
108 /* IRQ related */
109 .ack_interrupt smsc_phy_ack_interrupt,
110 .config_intr smsc_phy_config_intr,
111
112 .suspend genphy_suspend,
113 .resume genphy_resume,
114
115 .driver { .owner THIS_MODULE, }
116 } };
117
118 module_phy_driver(smsc_phy_driver);
从示例代码69.4.3.8 可以看出smsc_phy_driver 还是支持了不少SMSC 家的PHY 芯片比如LAN83C185、LAN8187、LAN8700 等等当然了肯定也包括了LAN8720 系列第93~116行就是LAN8710/LAN8720 系列PHY 驱动。 第94 行PHY ID 为0X0007C0F0。 第95 行PHY 的ID 掩码为0XFFFFFFF0也就是前28 位有效在进行匹配的时候只需要比较前28 位第4 位不用比较。 第74 行驱动名字为“SMSC LAN8710/LAN8720”系统启动以后打开网卡就会提示当前PHY 驱动名字为“SMSC LAN8710/LAN8720”。 最后第118 行使用module_phy_driver(本质是一个宏)来完成smsc_phy_driver 的注册。 此驱动里面的成员函数有一些是SMSC 自己编写的有一些是直接用的通用PHY 驱动的比如第103 行的genphy_config_aneg、第112 行的genphy_suspend 等。
网络驱动实验测试
LAN8720 PHY 驱动测试
首先肯定是驱动修改这个已经在37.4.3 小节做了详细的讲解参考修改即可。系统启动以后就会打印出当前PHY 驱动名字为“SMSC LAN8710/LAN8720”如图69.5.1.1 所示
图69.5.1.1 网络PHY 驱动信息 从图69.5.1.1 可以看出此时PHY 驱动使用的是“SMSC LAN8710/8720”当我们使用ifconfig 命令打开网卡的时候也会提示当前PHY 驱动名字。至于网络的测试就很简单了大家可以ping 一下主机或者ubuntu 的地址如果能ping 通就说明网络工作正常。
通用PHY 驱动测试
前面我们说了按道理来讲通用PHY 驱动肯定是可以驱动起任何的PHY 芯片的但是有时候社会就是这样的不讲道理笔者一开始就是使用通用PHY 驱动来驱动LAN8720结果现实给年轻的笔者上了一课失败了经过搜索发现在I.MX6ULL 下使用LAN8720 的话需要ENET1_TX_CLK 和ENET2_TX_CLK 这两个引脚的配置也就是需要修改fec_main.c 文件中的fec_probe 函数具体修改内容参看37.4.3 小节。修改完成以后就可以尝试使用通用PHY 驱动不保证会成功首先配置Linux 内核关闭自带的LAN8720 驱动即可然后重新编译linux 内 核使用新的linux 内核启动即可。 系统启动以后就会使用通用PHY 驱动会输出如图69.5.1.2 所示信息 图69.5.2.1 通用PHY 驱动信息 从图69.5.2.1 可以看出此时网卡驱动名字为“Generic PHY”说明使用的是通用PHY 驱动。
DHCP 功能配置
我们以前做实验的时候开发板的IP 地址都是手动设置的但是自己设置IP 地址很容易和网络中其他设备的IP 地址冲突最好的办法就是让路由器自动分配IP 地址通过路由器分配到的IP 地址肯定是在本网络中独一无二的。我们需要通过udhcpc 命令来实现从路由器动态申请IP 地址udhcpc 命令已经集成到了busybox 中所以不需要我们另外移植。 另外我们还需要一个文件否则直接使用udhcpc 申请IP 地址会失败。在busybox 源码中找到examples/udhcp/simple.script将其拷贝到开发板的/usr/share/udhcpc 目录下(如果没有的话请自行创建此目录)拷贝完成以后将根文件系统下的simple.script 并且重命名为default.script命令如下
cd busybox-1.29.0/examples/udhcp
cp simple.script /home/zuozhongkai/linux/nfs/rootfs/usr/share/udhcpc/default.script //拷贝并重
命名完成以后就可以使用udhcpc 来给指定的网卡申请IP 地址了通过“-i”参数来指定给哪个网卡申请IP 地址“-i”参数后面紧跟要申请IP 地址的网卡名字。比如这里我们以正点原子的ALPHA 开发板为例给eth1 这个网卡申请IP 地址命令如下
ifconfig eth1 up //打开eth1 网卡
udhcpc -i eth1 //为eth1 网卡申请IP 地址申请过程如图69.5.3.1 所示
图69.5.3.1 udhcpc 申请IP 地址过程 从图69.5.3.1 可以看出eth1 申请到的IP 地址是192.168.1.156并且也修改了/etc/resolv.conf文件中DNS 服务器地址。可以输入“ifconfig”命令来查看eth1 网卡的详细信息这里就不演示了。
单网卡使用
有时候我们在实际的产品开发中由于I.MX6ULL 引脚数量的限制用到的一些外设引脚和网卡冲突了但是我们的产品又不需要两个网卡有一个就够了。这个时候就需要修改linux 内核实现一个网卡的使用另外一个网卡的IO 用作其他的外设。
只使用ENET2 网卡
如果只使用ENET2 网卡的话修改起来比较简单打开设备树imx6ull-alientek-emmc.dtsENET1 网卡对应的节点名字为fec1ENET2 网卡对应的节点名字为fec2。我们需要关闭ENET1网卡首先找到fec1 节点然后将其中的status 属性改为“disabled”即可修改完成以后的fec1节点内容如下
示例代码69.6.1.1 fec1 节点信息
1 fec1 {
2 pinctrl-names default;
3 pinctrl-0 pinctrl_enet1
4 pinctrl_enet1_reset;
5 phy-mode rmii;
6 phy-handle ethphy0;
7 phy-reset-gpios gpio5 7 GPIO_ACTIVE_LOW;
8 phy-reset-duration 200;
9 status disabled;
10 };第9 行将status 改为“disabled”。 修改完成以后重新编译设备树使用新的设备树启动内核。内核启动以后输入如下命令查看当前系统中所有网卡
ifconfig -a //查看所有网卡结果如图69.6.1.1 所示
图69.6.1.1 当前系统网卡信息 从图69.6.1.1 可以看出此时只要一个eth0 网卡并没有eth1说明fec1 被我们关闭了接下来大家就可以将fec1 网卡对应的IO 复用为其他外设功能了。
只使用ENET1 网卡
如果只使用ENET1 网卡的话就稍微复杂一点了不是简单的将fec2 节点下的status 改为“disabled”按照如下步骤来修改设备树imx6ull-alientek-emmc.dts 1、屏蔽或删除掉fec2 节点内容 首先屏蔽或删除掉imx6ull-alientek-emmc.dts 文件中ENET2 网卡对应的fec2 节点内容如图69.6.2.1 所示
图69.6.2.1 屏蔽掉fec2 节点内容 2、修改ENET1 对应的fec1 节点信息。 接下来需要fec1 节点进行修改重点是在fec1 节点下添加mdio 子节点修改后的fec1 节点内容如下所示
示例代码69.6.1.2 修改后的fec1 节点
1 fec1 {
2 pinctrl-names default;
3 pinctrl-0 pinctrl_enet1
4 pinctrl_enet1_reset;
5 phy-mode rmii;
6 phy-handle ethphy0;
7 phy-reset-gpios gpio5 7 GPIO_ACTIVE_LOW;
8 phy-reset-duration 200;
9 status okay;
10
11 mdio {
12 #address-cells 1;
13 #size-cells 0;
14
15 ethphy0: ethernet-phy0 {
16 compatible ethernet-phy-ieee802.3-c22;
17 reg 0;
18 };
19 };
20 };
重点是在fec1 中添加第11~19 行的mdio 子节点而且仅仅留下ENET1 对应的PHY 子节点。 3、屏蔽或删除掉ENET2 对应的pinctrl 节点 屏蔽后删除掉ENET2 对应的pinctrl_enet2如图69.6.2.2 所示
图69.6.2.2 屏蔽掉pinctrl_enet2 节点 4、在ENET1 网卡对应的pinctrl 节点中添加MDIO 和MDC 引脚配置 默认情况下GPIO1_IO07 和GPIO1_IO06 复用为了ENET2 的MDC 和MDIO因此这里我们需要重新将GPIO1_IO07 和GPIO1_IO06 这两个IO 复用为ENET1 的MDC 和DMIO修改后的pinctrl_enet1 内容如下所示
//示例代码69.6.1.3 修改后的pinctrl_enet1节点
1 pinctrl_enet1: enet1grp {
2 fsl,pins
3 MX6UL_PAD_GPIO1_IO07__ENET1_MDC 0x1b0b0
4 MX6UL_PAD_GPIO1_IO06__ENET1_MDIO 0x1b0b0
5 MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0
6 MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0
7 MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0
8 MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0
9 MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0
10 MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0
11 MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0
12 MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b009
13 ;
14 };
第3 行将GPIO1_IO07 复用为ENET1_MDC 引脚。 第4 行将GPIO1_IO06 复用为ENET1_MDIO 引脚。 至此设备树修改完成重新编译设备树然后用新的设备树启动系统。启动以后输入如下命令查看开发板所有网卡信息
ifconfig -a结果如图69.6.2.3 所示
图69.6.2.3 开发板网卡信息 从图69.6.2.3 可以看出此时整个系统只有一个eth0 网卡注意这里的eth0 网卡就是ENET1不要和前面的混淆了以为eth0 是ENET2 网卡名字。 至此关于Linux 的网络驱动就讲到这里整体比较复杂但是实际使用起来确实非常简单的尤其是对这种内置MAC外置PHY 的网络方案而言几乎不需要我们修改驱动因为内核已经继承了通用PHY 驱动了。