3.4 网络驱动修改
网络驱动的修改,其复杂程度往往高于 LCD 屏幕,因为屏幕只需要发送数据(单向为主),而网络涉及 MDIO 总线控制(读写 PHY 芯片寄存器)、RMII 时钟同步(50MHz时钟是谁供给谁) 以及 TCP/IP 协议栈的地址校验。
3.4.1 网络驱动原理
在嵌入式 Linux 系统中,一个完整的硬件联网方案由 MAC 控制器、MDIO 管理接口、PHY 芯片以及网络变压器/RJ45 接口共同构成。
1.以太网硬件三层架构:MAC + PHY + 变压器
内部 MAC(介质访问控制层):
集成在 i.MX6ULL 芯片内部。它是一个数字逻辑外设,主要负责处理网络数据帧的打包、拆包、CRC 校验,以及流量控制。
外部 PHY(物理层芯片):
板载的独立芯片(如 LAN8720A)。它负责将 MAC 发出的数字信号转换成网线上的模拟电平信号,或者将网线上的模拟信号转成数字信号送给 MAC。
网络变压器与 RJ45:
起到电气隔离和信号滤波的作用,保护主控芯片不受外部网线过压干扰。
2.MAC 与 PHY 之间的两条“生命线”
主控芯片的内部 MAC,必须通过两套完全独立的接口与外部 PHY 芯片连接,缺一不可:
① 数据接口(RMII / MII)
用于传输实际的网络数据包。
i.MX6ULL 常用的是 RMII(Reduced MII,精简媒体独立接口)。它只需要 2 根数据发送线、2 根数据接收线、以及 1 根 50MHz 的同步时钟线(REF_CLK)。
核心原理:当网络有数据流入时,PHY 芯片通过 RMII 接口将数据一位一位地推给 SoC 内部的 MAC。
② 管理接口(MDIO / MDC)
用于控制和配置 PHY 芯片。
这是一组串行双线总线(MDC 时钟线,MDIO 双向数据线)。SoC 内部的 MAC 充当主设备,外部 PHY 芯片充当从设备(通过不同的硬件 PHY Address 区分)。
核心原理:U-Boot 或 Linux 内核的网络驱动,就是通过 MDIO 总线去读写 PHY 芯片内部的寄存器,从而获取网线有没有插好(Link 状态)、当前是 10M 还是 100M 速率、是全双工还是半双工。
3.网络驱动的核心:专用 DMA 控制器
工业级 SoC(如 i.MX6ULL)之所以网络性能强悍,关键在于其内部 MAC 外设自带了一个专用的以太网 DMA(Direct Memory Access,直接内存访问)控制器。
它的工作流程完全不需要 CPU 的实时干预:
硬件层自动接收:当数据包从网线通过 RMII 涌入 MAC 时,专用 DMA 会自动将这些数据包搬运到你板子上的 DDR3 内存中排队(也就是环形缓冲区 Ring Buffer)。
软件层轻量触发:直到整个数据包搬运完毕,DMA 才会给 CPU 发送一个中断信号。
协议栈解析:CPU 收到中断后,直接去内存里把数据拿出来,喂给 Linux 内核的 TCP/IP 协议栈,驱动层的工作即宣告完成。
硬件不同:
NXP 官方的 I.MX6ULL EVK 开发板使用 KSZ8081 PHY 芯片,接下来我们观察 韦东山的板子:
硬件现状:图中只有 E28(型号为 ULC0544P10_AZ1045-04F 的 ESD 保护芯片)和 J6(带网络变压器的 RJ45 插座)。
对应关系:左侧的信号直接打着 ENET1_TXP/N 和 ENET1_RXP/N 的网络标签。这意味着 ENET1 的 PHY 芯片已经集成在主控 SoC 内部了(也就是所谓的高性能主控自带的内置百兆 MAC+PHY)。主控芯片引脚直接输出差分信号,经过 ESD 芯片保护后就直接进了网络变压器。
总结:
ENET1 的 PHY 芯片:主控 SoC 内置 PHY(无需外挂独立 PHY 芯片)。
ENET2 的 PHY 芯片:LAN8720A(外挂的独立百兆 PHY 芯片)。
3.4.2 LAN8720A 修改内容说明
我们以 ENET2 的 LAN8720A 芯片为基础进行讲解:
状态读取机制:
i.MX6ULL 通过 MDIO 接口(包含时钟线 ENET_MDC 和数据线 ENET_MDIO)来读取 LAN8720A 的内部寄存器,从而判断网络的物理链接、连接速度(10M/100M)以及双工状态。
多设备区分原理:
一个 MDIO 接口最多可管理 32 个 PHY 芯片,系统依靠不同的器件地址(PHY ID)来精准识别和访问同一总线下的具体芯片(如该板 ENET1 上的芯片地址为 0x0)。
驱动修改三大重点:若要适配或修改网络驱动
核心任务包括:
复位引脚初始化、确认并配置正确的 PHY 器件地址、以及实现/加载对应的 LAN8720 芯片驱动。
1.复位引脚初始化
从原理图可知,ENET2 的复位引脚对应 SNVS_TAMPER6
2.PHY 器件地址
对于 LAN8720A 芯片,它的 PHY 地址是由 RXER/PHYAD0 引脚(对应芯片的第 10 脚)在复位那一刻的电平决定的:
如果该引脚被外接电阻下拉到地 (GND),则 PHYAD0 = 0,芯片地址为 0x0。
如果该引脚被外接电阻上拉到 VDD,则 PHYAD0 = 1,芯片地址为 0x1。
3.LAN8720 芯片驱动
前 16 个寄存器无需修改(IEEE 标准化):
寄存器 0 ~ 15 的功能与位域定义由 IEEE 802.3 规范固定(如自协商、网线插拔状态、基础网速读取)。由于全球厂商统一标准,通用驱动直接读取即可让芯片跑起来,不需要任何修改。
后 16 个寄存器必须修改(厂商自定义):
寄存器 16 ~ 31 属于厂商特定自定义区。由于不同芯片(如 LAN8720A)的内部状态机不同,驱动必须主动介入去修改/读取这些特定寄存器(如 LAN8720A 的寄存器 31),才能精准获取最终的网速、双工状态并同步给 SoC 的 MAC 控制器。
时钟与硬件模式配置是修改的核心(如时钟同步):
在特定的硬件电路设计中(例如外部没有接晶振,全靠 i.MX6ULL 提供 50MHz 时钟),必须在网络初始化时通过 MDIO 总线改写 LAN8720A 的特殊模式寄存器(寄存器 18),将其强制配置为“外部时钟输入模式”。如果不动这个寄存器,芯片与 CPU 时钟无法同步,网络将彻底瘫痪。
3.4.3 LAN8720A 修改流程
1.复位引脚初始化
ENET2 的复位引脚对应 SNVS_TAMPER6
我们到 IMX6ULL 参考手册查阅具体位置:
被复用为了 GPIO5_IO06
在 \board\freescale\mx6ull_kd\mx6ull_kd.c 中添加如下定义:
#defineENET2_RESETIMX_GPIO_NR(5,6)/* 网络引脚电气属性配置 */#defineENET_PAD_CTRL(PAD_CTL_PKE|PAD_CTL_PUE|\PAD_CTL_PUS_100K_UP|PAD_CTL_SPEED_LOW|\PAD_CTL_DSE_40ohm|PAD_CTL_SRE_SLOW)staticiomux_v3_cfg_tconstfec2_pads[]={/* 将 SNVS_TAMPER6 显式配置为普通的 GPIO5_IO06 */MX6_PAD_SNVS_TAMPER6__GPIO5_IO06|MUX_PAD_CTRL(ENET_PAD_CTRL),/* 如果后续网络依旧无法通信,需要在这里继续把 RMII 的 TXD0, RXD0, MDIO 等引脚一并补齐 */};在 setup_fec 中实现硬复位逻辑
找到源码中的 static int setup_fec(int fec_id) 函数,在对应的 else 分支(即 fec_id == 1,代表 ENET2)中,使用 imx_iomux_v3_setup_multiple_pads 初始化引脚,并使用标准的 gpio_ 接口进行拉低→\rightarrow→延时→\rightarrow→拉高的硬复位操作。
即在如图所示分支末尾添加代码:
/* 1. 设置引脚复用:将该引脚配置为 GPIO 模式 */imx_iomux_v3_setup_multiple_pads(fec2_pads,ARRAY_SIZE(fec2_pads));/* 2. 申请 GPIO 资源 */ret=gpio_request(ENET2_RESET,"enet2_reset");if(!ret){/* 3. 设置为输出方向,并拉低电平(进行硬复位) */gpio_direction_output(ENET2_RESET,0);udelay(10000);/* 持续拉低 10ms,确保 PHY 芯片彻底复位 *//* 4. 拉高电平(释放复位状态,使 PHY 芯片开始工作) */gpio_set_value(ENET2_RESET,1);udelay(100);/* 略微延时等待 PHY 内部状态机稳定 */2.PHY 器件地址
\include\configs\mx6ull_kd.h
中可以看到
#defineCONFIG_FEC_ENET_DEV1说明默认使用的 ENET 网口是 ENET2(0对应ENET1,1对应ENET2)
(这里我们顺手修改一下,0对应的是 ENET1 ,地址应该是 0x0)
ENET2 的地址为 0x1 不需要修改。
代码中还有一个宏定义
#defineCONFIG_PHY_MICRELNXP使用的外部PHY芯片KSZ8081是Micrel公司生产的,所以该宏为CONFIG_PHY_MICREL;韦东山IMX6ULL开发板使用的外部PHY芯片是SMSC公司生产的,所以应该删除原来的宏,重新定义一个宏:CONFIG_PHY_SMSC
#defineCONFIG_PHY_SMSC3. 修改 drivers/net/phy/phy.c
在 U-Boot 中,如果使用通用 PHY 驱动(Generic PHY)去驱动 LAN8720A,确实经常会遇到无法读取网络链接状态、速度或全双工状态的问题,导致网络 Ping 不通。
这并不是代码写错了,而是因为 LAN8720A 的状态寄存器设计有一点非标准。
为什么要修改 genphy_update_link?
在标准 drivers/net/phy/phy.c 的 genphy_update_link 函数中,U-Boot 是通过读取通用寄存器 1(BMSR)来判断网线是否拔插的。
但是,LAN8720A 的自协商最终结果(到底协商成了 10M 还是 100M,全双工还是半双工),虽然它也符合 IEEE 标准,但在实际应用中,直接用通用函数去读取它时,它的寄存器状态在 U-Boot 极其快速的初始化链路中经常更新不及时,或者由于时钟同步问题导致读出来的数据是错的。
为了让它稳定可靠,我们需要在这个函数里手动介入,直接读取 LAN8720A 专属的寄存器 31(特殊功能控制/状态寄存器,SPSR),强制让 U-Boot 拿到最准确的网速和双工状态。
具体修改步骤:
打开 drivers/net/phy/phy.c,找到 genphy_update_link 函数,在unsigned int mii_reg;后添加以下代码:
staticintlan8720_flag=0;intbmcr_reg=0;if(lan8720_flag==0){bmcr_reg=phy_read(phydev,MDIO_DEVAD_NONE,MII_BMCR);phy_write(phydev,MDIO_DEVAD_NONE,MII_BMCR,BMCR_RESET);while(phy_read(phydev,MDIO_DEVAD_NONE,MII_BMCR)&0X8000){udelay(100);}phy_write(phydev,MDIO_DEVAD_NONE,MII_BMCR,bmcr_reg);lan8720_flag=1;}4. 修改 \configs\mx6ull_kd_defconfig
include/configs/mx6ull_kd.h,会发现在文件靠后的地方有这样几行代码:
#ifdefCONFIG_DM_ETH#defineCONFIG_ETHPRIME"eth1"#else#defineCONFIG_ETHPRIME"FEC1"#endif核心冲突:
在 U-Boot 2017.03 中,如果系统通过 Kconfig(也就是 configs/mx6ull_kd_defconfig)开启了 CONFIG_DM_ETH=y,那么:
U-Boot 的全局网络初始化函数 eth_initialize() 将只会去设备树(Device Tree)里寻找带有 compatible = “fsl,imx6ul-fec” 属性的节点进行绑定。
它会完全跳过并无视在 mx6ull_kd.c 里面写的所有传统初始化函数(包括 board_eth_init 和 fecmxc_initialize_multi)。
也就是说,哪怕底层引脚复用、电压、硬复位全部正确,只要 CONFIG_DM_ETH 是开启的,U-Boot 就会当 board_eth_init 根本不存在,从而继续打印 No ethernet found.。
解决办法:
修改 configs/mx6ull_kd_defconfig
找到CONFIG_DM_ETH=n
改成# CONFIG_DM_ETH is not set
3.4.4 验证
完成以上步骤后,编译烧录进开发板这种,可以在串口看到以下信息:
NET:FEC 说明找到了网络