最近在折腾嵌入式Linux底层起因很简单手上有两套开发板核心处理器完全一样都是某芯片原厂的Cortex-A7系列。一块是厂商A出的核心板配套教程资料很全另一块是厂商B出的教学开发板自己定制了一套启动环境。原以为同芯片的U-Boot引导程序移植过去就是“编译一下、烧进去、跑起来”的事结果网络驱动首先给了我一记闷棍——网口灯不亮Ping不通TFTP直接超时。花了两三个晚上对着一份从厂商A教程里理出来的网络驱动移植方案把U-Boot网络驱动从A板完整适配到B板最终通了。整个过程踩了不少坑也把MAC、PHY、MDIO、设备树这些概念彻底串起来了这篇把整个实操过程记录下来给同样在做同SoC不同板卡移植的朋友一个参考。1. 移植前的思路拆解同芯片的引导程序网络驱动为什么还要改1.1 能启动和能联网其实是两码事很多朋友觉得同一颗芯片厂商A能把U-Boot跑起来厂商B理论上也能用同一份代码跑起来顶多改改DDR参数和引脚复用。这个想法对了一半。U-Boot的启动流程确实主要依赖SoC内部的Boot ROM和基础的时钟、DDR、存储初始化这部分往往跟具体板卡关系不大所以A板的U-Boot烧到B板大多能起来串口能看到打印信息。但网络驱动不一样。网络链路从SoC内部的MAC控制器到外部独立的PHY芯片再到网络变压器和RJ45座中间每一段都可能因为PCB设计、芯片选型、引脚连接方式的差异而发生变化。厂商A的板子PHY芯片用A型号厂商B的板子用B型号两边的MDIO地址、复位引脚、RMII模式下50MHz参考时钟的来源都可能完全不同。这些差异在U-Boot里体现为设备树配置、PHY驱动匹配、MDIO操作时序、复位控制逻辑的不一致。不把这些差异梳理清楚直接拷贝A板的网络配置到B板大概率就是“系统能启动但网口是个摆设”。1.2 引导程序里的网络驱动到底管哪些事有人可能会问U-Boot阶段的网络驱动就那么重要吗Linux内核起来之后不是一样能配网络吗这里要区分一个场景如果板子已经烧好了完整的系统U-Boot里的网络确实用得不多。但嵌入式开发的前期阶段尤其在没有调试器、没有完整烧录工具的情况下U-Boot的网络功能几乎是救命稻草。U-Boot的网络功能承担三类事情第一TFTP下载内核和设备树通过网络加载Linux镜像第二通过网络烧写NAND、eMMC或SD卡上的固件省去反复拔卡插卡第三搭配NFS挂载根文件系统直接在主机上改代码、在开发板上跑程序免去每次都要整个打包烧录的步骤。我见过不少项目前期刷系统全靠U-Boot的TFTP下载网络不通意味着每改一次内核都要拆机器插SD卡那个效率谁都扛不住。所以U-Boot阶段的网络驱动通畅是后续一切调试工作的前提。U-Boot阶段的网络驱动并不复杂它没有像Linux内核那样完整的中断处理和协议栈调度更多是轮询方式去收发数据包处理ARP、ICMP和TFTP这些基础协议。但正因为精简它对底层硬件初始化的要求反而更直接PHY必须连上MDIO必须能读写MAC必须能发出帧时钟必须对得上。任何一个环节不对表现就是“驱动加载成功但发包失败”“Ping不通”“TFTP卡在等待确认”。1.3 整体移植路径三层改动缺一不可基于两次踩坑经历我把U-Boot网络驱动从A板移植到B板的过程拆成了三层。第一层是设备树层。现代U-Boot几乎都是设备树驱动模型网络控制器挂在什么地址、用哪个中断、引脚如何复用、PHY挂在哪个MDIO总线上全都在DTS文件里描述。这一层主要解决的是“U-Boot怎么认识B板的硬件”。第二层是板级代码和配置层。U-Boot的板级目录里会有一些头文件和C文件定义默认环境变量、PHY地址、MAC地址来源等内容。同时defconfig里的宏开关决定了网络命令、PHY驱动、DM驱动模型是否编译进固件。这一层解决的是“U-Boot能不能正确驱动这些硬件”。第三层是验证和调试层。网络驱动移植完不能只看编译通过必须真正实现在U-Boot命令行下Ping通主机并且通过TFTP把内核下载下来才算真正打通。这一层解决的是“驱动能用但会不会暗藏隐患”。这个思路看似简单但每一步都有很具体的细节下面从硬件原理开始一层层拆开讲。2. 硬件差异与原理动手之前先把网络链路缕清2.1 一条网线的内部链路从MAC到RJ45先画一张“地图”。一个以太网口从芯片到网口插座正常有四段SoC内部集成的MAC控制器、外部PHY芯片、网络变压器、RJ45插座。SoC负责处理IP包但它不直接输出模拟信号。MAC控制器通过MDIO总线去配置PHYPHY负责把数字信号转成差分模拟信号再经网络变压器隔离后送到RJ45。MAC和PHY之间一般有两种接口百兆网的RMII和千兆网的RGMII。RMII只需2根数据线收发各一根但需要一个50MHz的参考时钟。RGMII则需要4根发送数据线、4根接收数据线还需要125MHz的参考时钟。两块板子如果一个用RMII一个用RGMII那设备树里phy-mode就不一样驱动初始化行为也不同。这个50MHz参考时钟的来历是移植过程中最容易忽略的硬件细节。RMII模式下可以是SoC输出时钟给PHY也可以是PHY用自己的晶振然后输出时钟给SoC。芯片厂家在设计A板时可能用了前者B板为了节省SoC引脚或布局方便用了后者。在U-Boot里MAC驱动对时钟方向的处理有区别不搞清楚PHY可能能读到ID但收不到数据表现就是“能Ping通但丢包严重”甚至完全不通。2.2 两块板子网络部分的实际差异拿到A板和B板的原理图我逐项对比了两者的网络相关设计差异比想象多得多对比项厂商A核心板厂商B教学板SoC同一颗Cortex-A7同一颗Cortex-A7MAC与PHY接口模式RMIIRMIIPHY芯片型号厂商A配套型号厂商B配套型号MDIO总线PHY地址0x010x00PHY复位引脚GPIO4组第20脚GPIO5组第7脚50MHz参考时钟SoC输出给PHY板载晶振经PHY输出MAC地址来源板载EEPROM读取无EEPROM需环境变量这里面任何一个差异都会直接影响U-Boot的网络驱动行为。PHY地址不对MDIO扫描找不到PHY复位引脚不对PHY一直处于复位状态MDIO读出来全0xFFFF时钟来源不对RMII收发时序错乱MAC地址来源不对U-Boot可能用一个随机或全零地址在局域网里同样会有各种怪问题。2.3 设备树、PHY驱动与引导程序驱动模型的关系U-Boot从多年前开始全面转向驱动模型DMDriver Model配合设备树描述硬件。网络相关的设备树节点通常会描述MAC控制器的寄存器基地址、中断、pinctrl配置同时描述MDIO总线和挂在总线上的PHY子节点。设备树里PHY子节点的compatible字段或reg字段用于让U-Boot的PHY驱动框架去匹配具体的PHY驱动。U-Boot的drivers/net/phy/目录下有很多PHY驱动每个驱动有一个ID列表。MDIO总线去读PHY芯片的寄存器拿到芯片ID再与驱动列表里的ID比对匹配上就调用对应驱动的配置函数。所以如果defconfig里没有把对应PHY驱动编译进去或者设备树里PHY地址与实际硬件不一致后面的链路就全断了。DTS还有个特点是U-Boot和Linux内核各有一份两份不完全一样。U-Boot用相对精简的设备树内核用完整版设备树。移植时容易漏掉的就是只改了U-Boot的设备树或者只看内核网络通了没验证U-Boot阶段。后面我单独把这个问题列进了避坑清单。3. 实操移植DTS、板级配置与编译烧录全流程3.1 开始之前搭建工程与定位要改的文件先搭建一个干净的U-Boot源码树。我习惯的做法是复制官方对应SoC平台的默认配置自己命名一个board文件夹不要让后续修改污染原有配置文件。具体操作分三步第一步在configs/目录下复制官方默认defconfig改成自己的名字方便后续make xxx_defconfig。第二步在board/目录下复制参考板卡的板级目录改成自己的板卡名。第三步在arch/arm/dts/目录下找参考板卡的DTS文件复制成新文件并加进对应的Makefile。cp configs/官方默认_defconfig configs/myboard_defconfig cp -r board/厂商A/myboard board/myboard cp arch/arm/dts/厂商A核心板.dts arch/arm/dts/myboard.dts这里为什么建议以厂商A的核心板为基准而不是直接从零写因为同样是厂商A芯片原厂的Cortex-A7核心板已经跑通了官方BSP说明基础硬件初始化和驱动框架基本没问题。我们只需要针对B板的差异点做减法或加法而不是在未知中摸索。这也是“站在原厂基础上移植”的意义。3.2 修改设备树接线差异都体现在这里设备树是网络驱动移植最先动手的地方。打开复制出来的DTS文件找到以太网控制器节点。这里以某个假设的mac1节点为例展示我实际打补丁时的主要改动mac1 { pinctrl-names default; pinctrl-0 pinctrl_mac1; phy-mode rmii; phy-handle ethphy0; status okay; mdio { #address-cells 1; #size-cells 0; ethphy0: ethernet-phy0 { reg 0; reset-gpios gpio5 7 GPIO_ACTIVE_LOW; reset-assert-us 10000; reset-deassert-us 10000; }; }; };注意几个关键字段的含义。phy-mode定义的是MAC与PHY之间的接口模式A板和B板都是RMII这里没有变但如果你遇到不同板卡一个用RMII一个用RGMII这个字段必须改。phy-handle指向MDIO总线上具体PHY子节点的引用。reg 0是PHY在MDIO总线上的地址。A板是1B板硬件设计是0所以这里和板级配置必须一致。reset-gpios是PHY复位引脚B板接在GPIO5组的第7脚低电平复位。reset-assert-us和reset-deassert-us是复位拉低和释放的持续时间单位是微秒。理论上PHY复位后需要一段时间内部逻辑才能稳定我给的是10毫秒绝大多数PHY都满足。pinctrl部分也是必须改的。MAC的MDIO、MDC、TX、RX、时钟引脚每一根都要在引脚复用配置里设置为正确的功能。DTS里一般长这样pinctrl_mac1: mac1grp { pinctrl-single,pins 0x00A0 0x2B 0x00A4 0x2B 0x00A8 0x23 0x00AC 0x23 ; };这里我故意用了简化写法实际值要根据SoC手册和B板的原理图查。具体操作是先看B板原理图上MDIO和RMII信号连到SoC的哪几个引脚再去SoC手册里查这些引脚可复用的功能号和寄存器偏移最后把配置值填进pinctrl节点。这个过程不能靠猜复用功能一错引脚就不是网络功能甚至可能输出异常电平把PHY搞挂。3.3 修正PHY地址与复位控制容易忽略的暗坑PHY地址不像寄存器配置那样写在代码里而是由硬件的strap引脚决定的。PHY芯片一般有ADDR引脚外部上拉或下拉到电源或地组合出0到31的地址。B板设计将PHY地址定为0所以DTS里reg 0。但板上PHY地址变化还可能因为原理图版本、焊接配置而异。实际操作时我建议先在U-Boot命令行执行mii info或mdio list让U-Boot自己扫描MDIO总线上的所有PHY地址。扫描结果列出哪个地址有回应再回头改DTS。复位控制同样暗藏问题。U-Boot对DTS里reset-gpios的处理并不像Linux内核那样自动初始化GPIO控制器。部分情况下需要保证GPIO控制器在U-Boot阶段已经被初始化否则复位操作无效。解决方法是在板级初始化代码里或设备树加入让对应GPIO模块上电和时钟使能的内容。如果忽略这一步表现是PHY的复位引脚一直处于不确定电平PHY根本没法正常工作。3.4 修改板级配置与defconfig让驱动找到正确的“合伙人”设备树解决的是“硬件长什么样”的描述问题但U-Boot的编译配置决定“驱动代码有没有被编译进去”。我建议直接使用make menuconfig在图形界面里找到网络相关的配置项确认。关键配置如下供参考# 网络命令相关 CONFIG_CMD_NETy CONFIG_CMD_MIIy CONFIG_CMD_PINGy CONFIG_CMD_TFTPy # 网络驱动模型 CONFIG_DM_ETHy CONFIG_DM_MDIOy CONFIG_PHYLIBy CONFIG_PHY_GIGEy # 根据实际PHY芯片选择对应的驱动宏 CONFIG_PHY_厂商Ay CONFIG_PHY_厂商By这里有个容易忽略的重点CONFIG_PHY_厂商A和CONFIG_PHY_厂商B不是随便都能开。PHY驱动代码由厂商提供或U-Boot上游内置如果U-Boot源码里没有你板子那颗PHY的驱动就算开了宏MDIO读取到芯片ID后也找不到匹配驱动。解决方法是先确认U-Boot源码里drivers/net/phy/目录有没有对应芯片ID的驱动没有就需要移植PHY驱动文件或者临时通过通用PHY驱动兜底。板级头文件也很重要。这个文件里可能定义默认的PHY地址或MAC相关的宏用于非设备树方式的老式驱动。有些U-Boot版本还保留board_eth_init这类函数如果同一控制器既通过设备树注册又通过board代码注册可能出冲突。我处理B板时直接关掉了board目录里的以太网初始化函数完全依赖设备树和设备驱动模型代码路径更清晰。3.5 编译、烧录与初步启动验证代码改完就开始编译。交叉编译环境假设已经在主机上配好直接执行make myboard_defconfig make CROSS_COMPILEarm-linux-gnueabihf- -j8编译完成后把生成的U-Boot二进制烧到B板的SD卡启动位置。上电后串口终端能看到U-Boot启动信息。此时重点看Net那一行。如果驱动注册成功通常会打印类似Net: eth0: ethernetxxxxxxx如果看到Net: No ethernet found.说明驱动没有加载成功优先检查设备树里MAC节点的status是否设置为okay以及defconfig里DM_ETH相关宏是否有开启。如果Net行正常插上网线看PHY的链接LED是否点亮。注意观察PHY上是百兆连接还是千兆连接。如果链接灯亮直接在U-Boot命令行里试一下Pingsetenv ipaddr 192.168.1.10 setenv serverip 192.168.1.100 ping 192.168.1.100能Ping通说明MAC和PHY之间收发链路正常接下来就可以进入TFTP下载测试。这一步初步通过说明移植已经成功了大半下面系统讲一下完整的验证流程和实际排查时遇到的几个高频坑。4. 网络功能验证与常见问题排查4.1 TFTP网络启动验证移植结果的硬指标网络驱动移植之后不能只看Ping通很多时候Ping通不代表能稳定下载大文件。TFTP下载是U-Boot网络功能的核心场景我建议把TFTP启动内核作为最终验收项。先在主机上搭一个TFTP服务器。Ubuntu系统安装tftpd-hpasudo apt install tftpd-hpa修改配置文件/etc/default/tftpd-hpa把根目录指向一个工作目录例如/srv/tftp然后重启服务。把编译好的内核镜像uImage和设备树文件board.dtb拷到这个目录里。回到开发板的U-Boot命令行依次执行setenv ipaddr 192.168.1.10 setenv serverip 192.168.1.100 setenv ethaddr 02:aa:bb:cc:dd:01 tftp 0x82000000 uImage tftp 0x83000000 board.dtb bootm 0x82000000 - 0x83000000注意ethaddr这个变量。A板有板载EEPROM启动时会自动读取MAC地址并写入环境变量B板没有EEPROM如果不设置U-Boot可能使用一个全零或者随机生成的地址在路由器或交换机上容易异常。手动指定一个合法的、在当前局域网不冲突的MAC地址能减少很多莫名其妙的问题。TFTP整个过程出现太多重传或超时首先怀疑U-Boot网络驱动中断收发存在问题其次怀疑RMII时钟相位第三才考虑主机侧TFTP配置问题。建议先Ping通用大包如ping 192.168.1.100连续多包如果大包丢包说明问题大概率在驱动侧或者硬件链路侧。4.2 常见故障速查PHY无法通信、链接灯不亮、传输超时我在这次移植中和平时帮群友排查时遇到最多的网络驱动移植问题基本可以归成以下五类整理成表故障现象可能原因排查方向启动后Net行显示No ethernet设备树节点未使能、驱动未编译进固件检查status字段和defconfig驱动宏MDIO扫描不到PHYPHY地址不对、复位引脚异常、MDIO引脚复用错执行mii info扫描对照原理图检查PHY地址链接LED不亮PHY供电异常、复位未释放、PHY芯片模式脚配置错测量PHY各供电电压确认复位引脚电平能Ping通但TFTP传大文件卡死MAC收发缓冲配置问题、主机网卡流控关闭网卡流控调整U-Boot环境变量中的ethprime等Ping丢包严重且时通时不通RMII时钟相位问题、PHY没有正确配置为RMII模式检查50MHz时钟来源确认phy-mode驱动一致性其中MDIO扫描不到PHY这个问题有个实用的诊断方法。在U-Boot命令行执行mii info它会遍历MDIO总线的0到31地址。如果任何地址都没有响应优先查MDIO和MDC两根线有没有接到PHY的正确引脚以及PHY的复位脚是否一直处于低电平。如果某个地址能读到PHY ID但也读到一些乱值大概率是时序或电平不匹配。这里再补充一个容易被人忽略的点PHY芯片的某些工作模式是靠硬件引脚电平决定的比如RMII/MII模式选择、PHY地址、是否使用内部时钟。这些引脚在板上可能通过上下拉电阻固定。B板的PHY模式脚配置与A板不一致时就算软件配置再对PHY本身可能被硬件配置到了以太网模式MDIO总线要么无响应要么响应但不工作。动手移植前用万用表过一遍PHY芯片周边的配置电阻能省下大量瞎猜时间。4.3 移植过程中的几条独家避坑经验这块内容是在普通文档里容易被忽略的挑几条特别实际的说。第一U-Boot和内核的设备树是两套文件必须同步改。U-Boot的DTS可能放在arch/arm/dts/内核的DTS可能放在内核源码树里。我一开始只改了U-Boot的DTSU-Boot阶段网络通了结果启动内核之后内核网络又起不来最后发现内核那份DTS里的PHY地址和复位GPIO还是A板的旧值。两条链路都要保持一致尤其PHY地址、复位引脚、phy-mode这三点。第二在RMII模式下PHY的50MHz参考时钟方向必须和硬件设计一致。A板是SoC输出50MHz给PHYB板是板载晶振由PHY输出50MHz给SoC。这两种设计对应的U-Boot驱动初始化有可能不同。如果方向不等于硬件设计可能表现成PHY链接能建立但MAC根本收不到数据。遇到类似情况别急着怀疑PHY芯片坏了先拿示波器或逻辑分析仪量一下MAC侧是否有正确频率的参考时钟输入。第三MAC地址问题不要留到现场才处理。B板没有EEPROM每次启动如果不设置ethaddr环境变量U-Boot会用一个生成出来的随机地址内核起来后网卡地址和U-Boot阶段不一致调试网络启动时非常容易懵。建议在移植阶段就把它固化到环境变量里甚至修改板级代码支持从某个专用Flash分区或OTP区域读取MAC地址这样才能保证量产时每块板地址独立。第四PHY复位时序要留意。有些PHY在上电后需要几十毫秒才能稳定响应MDIO如果U-Boot初始化时立刻去读PHY读到的值大概率是错误的。除了设备树里reset-assert-us和reset-deassert-us参数还要确认GPIO所在的控制器在U-Boot阶段已经初始化。我见过一个案例GPIO控制器本身没有上电复位引脚一直悬空PHY始终处于不确定状态。排查到最后往板级初始化里补了GPIO控制器时钟使能问题立刻消失。最后再说几句这次移植做下来最大的感受是引导程序里的网络驱动表面上是个软件适配问题本质上更多是对硬件原理的理解程度。从A板拷贝配置到B板最快的路径不是一个个文件试错而是坐下来把两块板子的原理图摊开把MAC、PHY、MDIO、复位、时钟这些小事一项项对照清楚。只要原理上的差异点都识别到了改DTS和defconfig其实反而是最机械的部分。如果在做类似移植时遇到网络不通我建议按照“先看PHY有没有被发现再看协议层能不能Ping通最后测TFTP大数据传输”的顺序排查。这三个环节会分别反映硬件连接问题、驱动配置问题、传输稳定性问题。把阶段切开问题定位会容易很多。希望这篇记录能帮你少折腾几个晚上。