ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux下IP101G PHY驱动移植与调试实战指南

Linux下IP101G PHY驱动移植与调试实战指南 简介ICPlus IP101G以太网PHY芯片驱动程序源码面向嵌入式网络开发、Linux内核驱动编写入门者以及需要移植或调试IP101G硬件的工程师。IP101G是常见的10/100M以太网PHY芯片负责将数据帧转换为可在双绞线等介质上传输的电信号并完成自动协商、链路状态监测、速度与双工模式配置等任务是网络接口卡实现有效通信的基础环节。源码采用C语言编写呈现出驱动的完整骨架涵盖设备注册、中断处理、I/O操作、链路检测与错误处理等核心逻辑既能帮助读者理解操作系统与PHY硬件之间的交互过程也可为其他以太网PHY驱动的学习提供通用参考C语言的高效与底层特性在这里体现得较为典型。压缩包仅含1个C源代码文件大小约2KB体积非常小巧结构清晰适合直接阅读剖析。已有101人学习下载。1. 拿到 icplus.rar 先别急着解压先确认 IP101G 这颗 PHY 在板上的角色icplus.rar 这个名字直接把硬件选型写在了压缩包名里里面那颗 PHY 是 IC Plus 的 IP101G单端口 10/100M 以太网物理层收发器常见于嵌入式路由器、串口服务器、工业控制板的 MAC 侧。解压之前真正决定工作量的不是那个驱动文件本身而是 IP101G 在板子上以什么接口模式接到 MAC、用哪个 PHY 地址、RMII 的 50MHz 参考时钟由谁提供。这三件事如果不在读 datasheet 阶段确认后面所有调试都会在链路起不来和 ping 不通之间反复打转。这篇文章面向的是把 IP101G 接进 Linux 系统的工程师可能是第一次移植 phylib 驱动的新手也可能是被 link 状态不稳定折腾过几天的老手。内容从 PHY 的寄存器模型讲到驱动骨架、设备树配置、寄存器读写工具最后落到一个可以抄走的厂商扩展寄存器配置套路。所有命令和代码基于 Linux 内核 5.10 以上的 phylib 接口拿到 icplus.rar 之后按这套流程走能省掉大半盲目试错的周期。2. IP101G 的软硬件边界为什么驱动包里是源码而不是固件2.1 PHY 芯片在链路中的位置MAC、MDIO 与 MII/RMII 的分工以太网接口从逻辑上分为 MAC 和 PHY 两层。MAC 负责组帧、寻址、CRC 校验PHY 负责物理编码、信号收发、链路协商和 MDI/MDIX 极性转换。CPU 通过 MDIO 总线Management Data Input/Output访问 PHY 寄存器时钟由 MDC 提供最高约 25MHz。IP101G 工作在 MII 或 RMII 模式MII 需要 16 根数据线加时钟RMII 把数据线砍到 2 根、时钟固定 50MHz是资源紧张的板子最常用的接法。芯片数据手册上的功能框图会标出 TXD/RXD、TX_EN、CRS_DV、MDC/MDIO 这些引脚但对应到 Linux 驱动你真正关心的只是它的寄存器空间怎么映射。PHY 寄存器地址是 5 位范围 0x00 到 0x1FIP101G 的前 16 个寄存器遵循 IEEE 802.3 第 22 条规范从 0x10 开始是厂商扩展区。驱动做的所有事情本质上就是对这两段寄存器做读改写。2.2 IP101G 的寄存器模型IEEE 802.3 标准寄存器与厂商扩展区标准寄存器区里有几个关键的0x00 控制寄存器软件复位、回环、速率、双工、自动协商开关0x01 状态寄存器链路状态、协商完成、能力位0x02 和 0x03 是 PHY ID 寄存器0x04 自动协商广告0x05 链路伙伴能力0x06 自动协商扩展。Linux phylib 里的 genphy_read_status、genphy_config_aneg 就是围绕这些寄存器实现的通用逻辑。表格里把这几个常用寄存器的作用列一下后续调试和写驱动的循环都绕不开寄存器地址名称关键位/作用排错时看什么0x00控制bit15 复位bit14 回环bit13 100Mbit8 双工复位后读回是否为 0x80000x01状态bit2 链路建立bit5 协商完成bit11/10 能力反复读看链路是否抖动0x02/0x03PHY ID厂商 OUI 和型号/版本号读出值应为 0x0243xxxx 系列0x04广告100Base-TX、10Base-T、全双工位确认对端能力是否被正确通告0x05伙伴能力对端 PHY 或交换芯片回送的广告与 0x04 做与运算得出协商结果0x10厂商扩展IC Plus 私有寄存器节能、LED、测试模式见第 5 章IP101G 的 PHY ID 落在 0x0243 开头这个 OUI 段完整 ID 取决于芯片版本。正式移植时不要直接抄驱动里的 ID 宏先写个小工具把 0x02/0x03 读出来再决定 phy_id_mask 怎么遮罩。这一步做错了内核 phylib 会把你的芯片匹配成通用 PHY功能上能跑但厂商扩展寄存器完全不可用。2.3 icplus 驱动包与 Linux phylib 的对应关系icplus.rar 这类包通常是原厂或方案商打包的驱动集合里面一般有 ip101g.c、ip101g.h、数据手册摘要和 README。这些源码大多基于老版本内核编写拿回来不可能直接用真正有价值的是头文件里的寄存器宏定义——那些 0x10 到 0x1F 扩展寄存器的位含义是 datasheet 之外最容易踩坑的地方。Linux 内核自带的 drivers/net/phy/icplus.c 已经实现了 IP101A、IP101G 的基础驱动所以拿到 icplus.rar 后的常规做法不是把整个包塞进内核而是对照包里头的寄存器宏把内核自带驱动里缺失的扩展配置补上。这样既保留 phylib 的标准协商流程又能借到厂商私有能力的入口。3. 解包 icplus.rar 并把它接进 Linux 内核 phylib 的最小步骤3.1 解压与内容确认rar 包在 Ubuntu 构建机上的处理办法icplus.rar 是 RAR 格式Ubuntu 构建机上没有 rar 命令是常态。用 7-Zip 解压最省事它能读 RAR 但不用装闭源工具。解压后先用 ls 和 file 确认文件类型再用 head 看头文件的宏定义风格判断这是哪个内核版本的风格。如果头文件里既有#define MII_TPISTATUS这种标准宏又有IP101G_REG_EDPD之类的私有宏说明包是按“标准 扩展”两层组织的跟 phylib 的驱动模型正好对应。sudo apt install p7zip-full 7z x icplus.rar mkdir -p ~/src/ip101g mv ip101g* ~/src/ip101g/ cd ~/src/ip101g ls -la file ip101g.c ip101g.h head -80 ip101g.h解压后先看head -80 ip101g.h输出的寄存器宏名称再到内核源码树下执行grep -r IP101G drivers/net/phy/对比差异。逻辑很简单官方包里的扩展寄存器定义是权威来源内核自带驱动里的 phy_driver 框架是运行骨架两者结合才是完整的移植路径。注意不要把整个目录拷进内核只需要挑出宏和补充函数按 phylib 风格重写。3.2 内核态驱动的骨架phy_driver 结构体里必须实现的 5 个回调phylib 通过 phy_driver 结构体描述一颗 PHY 的所有行为。关键字段包括 name驱动名称、phy_id 和 phy_id_mask用于匹配、features能力位图、config_init初始化、config_aneg自动协商、read_status读取状态、ack_interrupt/config_intr中断处理。对于 IP101G至少要实现 config_init 和 read_status其余回调可以直接复用 genphy 系列通用函数。#include linux/phy.h #define PHY_ID_IP101G 0x02430c54 /* 以实际读回的 IDR1/IDR2 为准 */ #define PHY_ID_MASK_IP101G 0xfffffff0 /* 遮蔽版本号放宽匹配 */ static int ip101g_config_init(struct phy_device *phydev) { int val; /* 读取厂商扩展寄存器 0x10示例关闭 EDPD 省电模式 */ val phy_read(phydev, 0x10); if (val 0) return val; val ~BIT(3); return phy_write(phydev, 0x10, val); } static int ip101g_read_status(struct phy_device *phydev) { int ret; /* 标准协商状态先用通用函数解析 */ ret genphy_read_status(phydev); if (ret 0) return ret; /* 如果链路正常再读扩展寄存器判断实际速率的附加信息 */ if (phydev-link) phydev-speed SPEED_100; return 0; } static struct phy_driver ip101g_driver[] { { .phy_id PHY_ID_IP101G, .phy_id_mask PHY_ID_MASK_IP101G, .name IC Plus IP101G, .features PHY_BASIC_FEATURES, .config_init ip101g_config_init, .config_aneg genphy_config_aneg, .read_status ip101g_read_status, .suspend genphy_suspend, .resume genphy_resume, } }; module_phy_driver(ip101g_driver); MODULE_DESCRIPTION(IC Plus IP101G PHY driver); MODULE_LICENSE(GPL);phy_id_mask 用 0xfffffff0 是刻意的IP101G 的版本修订位在低 4 位工程上不同批次芯片型号 ID 完全相同、版本号可能不同遮蔽版本位可以避免同一颗芯片因为修订号差异匹配不上驱动。config_init 里操作扩展寄存器之前务必先判断 phy_read 的返回值MDIO 总线上没有设备时读到的是 0xffff这时直接做写操作会把错误值写回去。read_status 里手动强制 speed 是演示如何在标准协商流程之外附加私有判断实际项目中应改为读取扩展寄存器里的真实状态位。3.3 设备树与 Kconfig 配置RMII 模式下的双端设定IP101G 接到 MAC 后硬件上只有设备树能表达“这颗 PHY 挂在哪个 MDIO 总线、地址几、什么接口模式”。以 RMII 为例设备树里要保证 phy-mode 与 MAC 侧时钟设置一致RMII 的 50MHz 参考时钟可以由 MAC 输出也可以由外部晶振直接供给 PHY这决定 phy-mode 是 rmii 还是需要在内核配置里打开对应的时钟方向标志。mac1 { status okay; pinctrl-names default; pinctrl-0 eth1_rmii_pins; phy-mode rmii; phy-handle ip101g_phy; }; mdio1 { ip101g_phy: ethernet-phy1 { reg 1; reset-gpios gpio0 12 GPIO_ACTIVE_LOW; reset-assert-us 10000; reset-deassert-us 50000; }; };reg 1 对应 IP101G 的 PHY 地址由芯片的 PHYAD[0] 引脚电平决定。这个值必须和硬件原理图对上常见的情况下板子把 PHYAD 引脚拉低导致实际地址是 0而设备树写 1结果内核扫描 MDIO 总线时找不到设备。reset-gpios 的 assert 和 deassert 时间 10ms/50ms 不是随便写的IP101G 的上电复位时间在数据手册里通常标称 10ms 级给足余量能避免上电瞬间 MDIO 访问返回 0xffff 导致的误判。Kconfig 侧只需打开 PHYLIB 相关配置。驱动的 phy_driver 通过 module_phy_driver 注册后会自动挂在 PHYLIB 的驱动链表上不需要额外 select。如果内核里已经打开CONFIG_PHY_ICPLUS要先确认它包含 IP101G 的条目老内核里可能只写了 IP101A这时要么 patch 内核自带驱动要么把你的驱动编成独立模块。推荐后者调试期间重新编译模块比重编内核快一个数量级。make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- menuconfig # Device Drivers - PHY Device support and infrastructure # - Drivers for PHY devices # - M IC Plus IP101G PHY driver编译选项里M表示模块*表示内建。开发阶段用Minsmod 失败可以直接看内核日志量产阶段改为内建避免 rootfs 里模块加载顺序问题。3.4 编译与加载模块方式与内建方式的取舍make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- Mdrivers/net/phy/ modules scp drivers/net/phy/icplus_ip101g.ko roottarget:/lib/modules/ ssh roottarget insmod /lib/modules/icplus_ip101g.ko dmesg | tail -20模块加载成功后dmesg 里应出现IC Plus IP101G开头的驱动注册信息。如果内核日志里没有任何输出大概率是 phy_id 或 phy_id_mask 匹配失败。此时回到 2.2 节的表格先通过 MDIO 工具读回 PHY ID 再核对宏。内建方式则把M改成*重新编译内核烧写检查/sys/bus/mdio_bus/devices/下是否枚举出了ethernet-phy1节点。4. 上电后确认 IP101G 是否被正确识别链路命令与 PHY 寄存器排错4.1 用 ifconfig/ethtool 确认链路状态与协商结果驱动加载只是第一步真正工作从链路建立开始。先把接口 up 起来然后用 ethtool 看协商结果。IP101G 是百兆 PHY如果板子和对端都是千兆交换机协商结果必然是 100M——这不是问题这是物理层能力上限。重点看 Speed、Duplex 和 Link detected 三个字段任何一项和预期不符都先查对端设备再去怀疑 PHY 配置。ifconfig eth0 up ethtool eth0 # Speed: 100Mb/s # Duplex: Full # Port: Twisted Pair # PHYAD: 1 # Link detected: yesPHYAD 字段会直接显示设备树里配置的地址确认它和你读到的实际地址一致。Link detected 为 no 时先看 RJ45 连接器上的 LED 是否闪烁——LED 状态由 IP101G 的 LED 引脚直接驱动不经过 MAC能隔离出“PHY 没工作”和“MAC 没工作”两个方向。再用ethtool -S eth0看 rx_crc_errors 和 rx_errors 计数持续递增说明物理层信号质量有问题方向大概率在 RMII 时钟或网络变压器。4.2 用 ioctl 读写 IP101G 寄存器的调试工具写法ethool 只能看到抽象结果排错必须落到寄存器级。内核提供了 SIOCGMIIREG/SIOCSMIIREG 两个 ioctl可以从用户态直接读写 PHY 寄存器不依赖任何第三方工具。这个接口非常适合写一个几十行的 C 工具专门用来读 IP101G 的 0x00 到 0x1F 全寄存器空间。#include stdio.h #include string.h #include fcntl.h #include sys/ioctl.h #include net/if.h #include linux/mii.h #include linux/sockios.h static int phy_reg_read(const char *ifname, int phy_id, int reg, unsigned short *val) { int fd; struct ifreq ifr; struct mii_ioctl_data *mii; fd socket(AF_INET, SOCK_DGRAM, 0); if (fd 0) return -1; memset(ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, ifname); mii (struct mii_ioctl_data *)ifr.ifr_data; mii-phy_id phy_id; mii-reg_num reg; if (ioctl(fd, SIOCGMIIREG, ifr) 0) { close(fd); return -1; } *val mii-val_out; close(fd); return 0; } int main(int argc, char *argv[]) { unsigned short val; int reg; if (argc ! 4) { fprintf(stderr, usage: %s ifname phy_id reg\n, argv[0]); return 1; } reg (int)strtol(argv[3], NULL, 0); if (phy_reg_read(argv[1], (int)strtol(argv[2], NULL, 0), reg, val) 0) { perror(SIOCGMIIREG); return 1; } printf(reg 0x%02x 0x%04x\n, reg, val); return 0; }编译方法gcc phyreg.c -o phyreg在目标板上执行./phyreg eth0 1 0x10。使用前提是网络接口已经 up否则 ioctl 会因为设备未就绪返回 ENODEV。如果对端是交换机建议把对端口 shutdown 再测试避免反复自动协商干扰寄存器状态的观察。读 0x01 状态寄存器时连续读 20 次每次间隔 100ms看 bit2 链路位是否稳定——这是判断物理层连接可靠性的最直接手段。4.3 RMII 时钟与 PHY 地址这两个高频坑RMII 模式下 IP101G 的 REF_CLK 必须是干净的 50MHz 时钟。时钟源选错会导致一个非常隐蔽的症状ethtool 显示 Link up但 ping 大包必丢、小包偶发超时。排查办法是用示波器测 REF_CLK 引脚的抖动或者直接看 MAC 侧是否有 CRS_DV 信号在无数据时跳变。常见做法是把这个时钟交给 MAC 输出设备树里 phy-mode 保持 rmii同时确认 MAC 驱动的 RMII 时钟方向配置位打开如果外部晶振供时钟要确保晶振靠近 PHY 的 XI 引脚走线长度控制在 10mm 内。PHY 地址坑则更隐蔽。IP101G 的 PHYAD[0] 引脚内部有下拉默认地址是 0但很多开发板参考设计把它上拉到 1导致同一颗芯片在不同板子上地址不同。驱动匹配失败时dmesg 会显示MDIO bus: unknown PHY而驱动本身没有任何报错。设备树里把 reg 从 1 改成 0 之前先用第 4.2 节的工具配合 MDIO bus 扫描或者直接在驱动里打印 phydev-mdio.addr。提示修改设备树 reg 后不要只 rebuild dtb确认 bootloader 加载的是新 dtb。不少板子 U-Boot 自带 dtb 优先于内核分区里的 dtb改了半天没生效往往是这个原因。4.4 丢包、CRC 与性能类问题的检查参数表现象检查命令/方法期望值异常时的方向大包丢包ping -s 1472 -c 1000% 丢包RMII 时钟抖动、FIFO 深度CRC 错误递增ethtool -S eth0 | grep crc不增长网络变压器中心抽头接地问题协商只有 10Methtool eth0100Mb/s对端强制 10M、线缆劣化链路反复 up/down循环读 0x01 寄存器 bit2持续为 1PHY 复位信号毛刺、供电不稳收不到广播tcpdump 抓包 对端发广播能抓到MAC 过滤配置不在 PHY 层这张表是排查顺序的浓缩先确认物理层CRC、link 抖动再看协商结果最后才查驱动配置。多数 IP101G 的“怪异问题”最后都落在供电和时钟上驱动代码反而是最后才需要怀疑的对象。5. 把 IP101G 的厂商扩展寄存器用起来一个最小可用的 LED 配置脚本5.1 扩展寄存器的读写入口与 libmii 工具的对比标准 ethtool 接口能覆盖 0x00 到 0x06但 IP101G 的 LED 指示模式、省电模式、测试信号这些功能都在扩展区。Linux 里没有现成的 ethtool 子命令直达厂商扩展寄存器自己写 ioctl 工具是唯一通用路径。第 4.2 节那个 phyreg 工具只做了读动手配置前把它扩写成支持写操作照抄读路径把 SIOCGMIIREG 换成 SIOCSMIIREG把 val_out 改成 val_in 即可。5.2 通过 ioctl 把 IP101G 配置成 Link/Act 分开指示的示例以最常见的需求为例板子上一颗双色 LED绿色指示 Link up黄色指示 Activity。IP101G 的 LED 控制寄存器在扩展区通过对寄存器 0x1A 写值选择 LED 输出源。数据手册里的定义是 bit0 到 bit3 控制 LED0 的模式值 0001 表示 Link值 0010 表示 Activity。下面的代码直接演示了读改写流程掩码部分按实际手册调整。#include stdio.h #include string.h #include fcntl.h #include sys/ioctl.h #include net/if.h #include linux/mii.h #include linux/sockios.h #define MII_IP101G_LED_REG 0x1A #define LED_LINK 0x01 #define LED_ACT 0x02 #define LED_MODE_MASK 0x000F static int phy_reg_write(const char *ifname, int phy_id, int reg, unsigned short val) { int fd, ret; struct ifreq ifr; struct mii_ioctl_data *mii; fd socket(AF_INET, SOCK_DGRAM, 0); if (fd 0) return -1; memset(ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, ifname); mii (struct mii_ioctl_data *)ifr.ifr_data; mii-phy_id phy_id; mii-reg_num reg; mii-val_in val; ret ioctl(fd, SIOCSMIIREG, ifr); close(fd); return ret; } int main(int argc, char *argv[]) { int reg_val 0; if (argc ! 3) { fprintf(stderr, usage: %s ifname phy_id\n, argv[0]); return 1; } /* 先读当前值再改 LED 字段避免抹掉其他配置位 */ reg_val | LED_LINK; reg_val | (LED_ACT 4); if (phy_reg_write(argv[1], atoi(argv[2]), MII_IP101G_LED_REG, reg_val) 0) { perror(SIOCSMIIREG); return 1; } printf(LED config written: 0x%04x\n, reg_val); return 0; }这段代码把 LED0 配成 Link 指示、LED1 配成 Activity核心教训是任何对扩展寄存器的写操作都必须走读改写三步先读回原始值按掩码清掉要修改的位再写入新值。直接整寄存器覆盖写入会把 IP101G 出厂默认的测试模式位、中断使能位一并清掉轻则 LED 不亮重则芯片进测试模式。改完寄存器后拔插网线观察 LED 状态切换即可验证。如果用这套脚本碰到扩展寄存器写不进的情况先确认芯片是否处于省电模式——IP101G 在 EDPD 模式下扩展寄存器对 MDIO 的响应会变慢连续写要加几十微秒延时。这个细节就是 icplus.rar 里那份老驱动代码常常失效的根源老代码基于立即写立即读的时序假设现代内核的 MDIO 控制器普遍比旧平台快时序裕量不够时只能看到 intermittent 的写失败。本文还有配套的精品资源点击获取
返回列表