尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MV88E6390寄存器调试实战:MDIO的Clause22与Clause45协议解析

MV88E6390寄存器调试实战:MDIO的Clause22与Clause45协议解析 1. 为什么还在翻MV88E6390的寄存器一个调试场景引发的思考先交代一下背景。前阵子做一款带管理型交换功能的板卡主控SoC通过MDIO总线去接Marvell的MV88E6390这颗交换芯片。硬件回来后端口link状态一直不对有的口协商到1000M后马上掉线有的口干脆起不来。一开始怀疑硬件设计问题量了差分对、看了变压器中心抽头、测了各路电源都没发现明显异常。后来拿起逻辑分析仪去抓MDIO波形才发现主机侧一直在用Clause22的协议格式去访问MV88E6390而芯片侧的物理层寄存器其实挂在Clause45的地址空间下访问方式从一开始就错了。这个场景在嵌入式网络开发里太常见了。MDIOManagement Data Input/Output这根两线管理总线看起来简单实际用起来有不少门道。尤其是遇到MV88E6390这种同时支持Clause22和Clause45的芯片时如果分不清两种协议的区别、搞不懂寄存器地址映射规则调试起来会非常痛苦。这篇文章就把我实际调试MV88E6390寄存器读写的过程完整梳理一遍从协议原理到代码实现再到调试中遇到的坑尽量一次讲透。这篇文章适合谁看手里正在用MV88E6390做交换板卡的嵌入式工程师或者在Linux内核里写phy driver、switch driver时需要对MDIO总线做底层寄存器操作的开发者。哪怕你用的是其他Marvell交换芯片比如88E6320、88E6341这些读写寄存器的思路也是完全通用的。2. MDIO总线协议基础Clause22和Clause45的区别不能只停留在概念层面2.1 MDIO的物理层与帧格式MDIO总线有两根信号线MDC管理时钟和MDIO管理数据。MDC由主机侧主动输出最高频率通常支持到2.5MHz有些IP核能跑到25MHz甚至更高但实际使用时受PCB走线和从设备负载影响我一般保守一点用2.5MHz。MDIO是双向数据线主机发命令或写数据时驱动它从设备在主机读命令后负责驱动它回数据。MDIO的帧结构分两种也就是IEEE 802.3规定的Clause22和Clause45。Clause22是老的PHY管理协议帧结构简单只有5位PHY地址和5位寄存器地址。Clause45是为了配合10G/40G/100G以太网PHY引入的扩展协议地址空间大幅扩充头部结构完全不同。2.2 Clause22的寄存器空间Clause22的帧格式是字段位数说明PRE32每个bit都是1同步前导码ST2起始码固定01OP2操作码10为读01为写PHYAD5PHY地址范围0~31REGAD5寄存器地址范围0~31TA2转向时间读为Z0写为10DATA16寄存器数据Clause22最多支持32个PHY地址每个PHY只有32个寄存器也就是总共1024个16位寄存器。传统的10/100/1000M PHY比如Marvell的88E1512、Realtek的8211系列基本都支持Clause22访问。2.3 Clause45的帧结构和地址映射Clause45把操作分成了两个阶段地址阶段Address和数据阶段Data。帧结构如下字段位数说明PRE32前导码全1ST2起始码固定00这是和Clause22最大的区别OP2操作码00为地址包11为读数据包01为写数据包PRTAD5PHY设备地址DEVAD5器件地址即MDIO Management Device AddressTA2转向时间ADDR/DATA16地址阶段放寄存器地址数据阶段放读写值Clause45的地址包先写入DEVAD和16位寄存器地址然后再发数据包执行具体读写。也就是说Clause45的总线操作是先定位、再操作而Clause22是一条命令直接到位。MV88E6390的问题恰恰出在这里。这颗芯片的物理层寄存器、SerDes寄存器大量挂在Clause45的设备地址下而很多人在调试时仍然习惯性地先用Clause22去读寄存器0x01Status读出来全零或者全F就开始怀疑硬件其实根本是访问协议没对上。2.4 用生活化的类比理解两种子句可以把MDIO总线理解成去图书馆找一本书。Clause22的模式是你在前台报一个书架号和一个书名管理员一次性帮你拿到。Clause45的模式是两步走第一步先告诉管理员你要去哪一个房间DEVAD、要查哪个书架的哪个格子寄存器地址第二步管理员把书递给你数据。如果第一步没说对房间后面怎么可能拿到正确的书。MV88E6390这种大型交换芯片内部电路极其复杂物理层、SerDes、PCS、MAC管理单元全集成在一起必须用Clause45这样的大地址空间才能把所有寄存器容纳下来。所以芯片手册里绝大多数寄存器地址都是16位而且按Device Address分组这和Clause22时代PHY地址寄存器地址各5位的思维完全不同。3. 理解MV88E6390的寄存器组织方式Device Address是定位的第一把钥匙3.1 MV88E6390的地址空间总览MV88E6390是一颗面向工业和企业级应用的10端口千兆交换芯片内部集成了10个10/100/1000M PHY、SerDes接口、以及完整的交换管理引擎。它的寄存器空间极大手册里你会看到类似Register 0x01, Device Address 0x0这样的描述注意这个Device Address就是Clause45协议里的DEVAD字段。主要地址映射可以整理成这张表DEVAD功能模块典型寄存器0x0全局控制寄存器Global RegistersPort Status、Chip ID等0x1物理层PHY寄存器组1PHY Control、PHY Status等0x2物理层PHY寄存器组2千兆扩展寄存器、自动协商相关0x3SerDes寄存器组Comma Detect、Signal Quality等0x4PCS寄存器组链路状态、错误计数等0x1E调试寄存器间接访问用Test Register、Debug Address等打开MV88E6390的datasheet翻到寄存器章节你会看到几乎每个寄存器的偏移地址都是12位到16位的值而且每个寄存器前面都标注了Device Address。这就是Clause45的典型风格。如果按照Clause22的习惯只看到5位PHY地址和5位寄存器地址很容易把全局寄存器当成PHY寄存器去读读出来的值自然完全对不上。3.2 全局寄存器与PHY寄存器的边界MV88E6390的全局寄存器Global Registers挂在Device Address 0x0下面但还需要通过一个间接访问机制来完成实际读写。手册里有一个Global Registers的访问说明要先将目标寄存器地址写入一个INDIRECT地址寄存器再通过INDIRECT数据寄存器读写。如果你直接用Clause45的地址向DEVAD 0x0发起寄存器读写往往只能读到一部分直接映射的寄存器。这个间接访问机制是很多新手第一次接触这颗芯片时最容易卡住的地方。芯片团队这么设计是为了把大量全局配置寄存器统一放在一个可扩展的窗口中避免总线地址空间不够用。在实际操作中读写MV88E6390全局寄存器至少分成两种情况直接映射的全局寄存器不需要间接访问Clause45地址包直接携带寄存器地址即可。间接映射的全局寄存器需要先写地址寄存器再读写数据寄存器。我在调试中发现像Chip ID这类基础寄存器可以直接读取但很多port-level配置寄存器就必须走间接路径。所以拿到datasheet后第一件事不是急着写代码而是把要操作的寄存器属于哪种访问类型搞清楚。3.3 硬件地址引脚对MDIO设备地址的影响MV88E6390的MDIO物理地址由硬件引脚确定通常是ADDR[4:0]这组引脚。上电时芯片会采样这些引脚的电平形成芯片在MDIO总线上的PHY地址。需要注意的是这个PHY地址和Clause45里的PRTAD字段是同一个东西但和DEVAD是两回事。PRTAD用来选择总线上哪一颗芯片DEVAD用来选择芯片内部哪一个功能模块。打个比方PRTAD是门牌号告诉总线你找的是哪一栋楼DEVAD是楼层号告诉芯片你要去第几层办公。两者缺一不可而且在Clause45协议里前一个阶段发PRTADDEVAD的组合后一个阶段再发PRTADDEVAD数据两步的门牌号和楼层号必须完全一致。我自己就踩过这个坑。当时为了快速验证没有注意ADDR引脚的实际接法直接用PRTAD0去访问芯片结果总线数据一直超时。后来查原理图发现硬件上ADDR引脚通过上拉电阻拉高了几个位实际地址是0x10。这种问题如果不对着原理图一颗引脚一颗引脚核对排查起来非常浪费时间。4. 实战准备Linux下mdio-tools与内核MDIO子系统的选型对比4.1 调试工具选择在Linux环境下调试MDIO寄存器主流方案有几种工具/方案特点适用场景mdio-toolsmdio-read/mdio-write用户态直接访问轻量灵活快速验证寄存器读写phy演tool/mii-tool仅支持Clause22功能简单老PHY调试Linux内核mdio-bus驱动 ethtool标准访问路径支持ioctl驱动开发阶段集成验证自己写裸机驱动完全可控适合MCU环境无操作系统的嵌入式板卡我这次调试用的是mdio-tools因为它对Clause45支持得比较好能够显式指定PHY地址、DEVAD和寄存器地址非常适合MV88E6390这种混合访问场景。mii-tool这种老工具在Clause45面前基本就是废的读不到任何有效数据别在上面浪费时间。4.2 mdio-tools的安装与基本用法如果你的系统是Ubuntu或Debian系直接apt安装就行sudo apt install mdio-tools装好之后有两个核心命令mdio-read和mdio-write。查看帮助信息mdio-read --help mdio-write --help以读取MV88E6390的Chip ID为例需要知道三个参数总线号、PHY地址PRTAD、设备地址DEVAD和寄存器地址。假设MDIO控制器在Linux下注册为mdio bus 0PHY地址是0x10DEVAD是0x0寄存器偏移是0x0003Chip ID的高16位在MV88E6390手册中一般是独立寄存器命令如下mdio-read -b 0 -p 0x10 -d 0x0 -r 0x0003注意mdio-tools对Clause45的支持方式是要求指定-p和-d两个地址参数这和Clause22只指定PHY地址和寄存器地址的用法不同新手最容易在这里混淆。4.3 为什么我建议先用mdio-tools而不是直接写内核驱动很多人一上来就想在内核驱动里写寄存器读写代码我其实不太建议。原因很简单在内核里调试每次修改都要重新编译、加载模块、可能还要重启设备迭代速度太慢。而且一旦触发内核态异常整个系统可能崩溃排查难度成倍上升。正确的做法是先用用户态工具把所有要访问的寄存器读一遍确认地址映射、确认数据值符合预期再把这些地址固化到驱动代码里。用户态工具验证失败问题往往出在总线配置或者地址理解上用户态工具验证成功驱动集成时就只需要关注访问时机和并发保护隔离问题范围会清楚很多。5. 用Clause22/45读MV88E6390寄存器的完整实操5.1 确认MDIO总线是否已经注册进入Linux系统后先查看/sys/class/mdio_bus/目录ls /sys/class/mdio_bus/正常情况会出现类似/sys/class/mdio_bus/f1072000.mdio/这样的目录里面的名字是MDIO控制器的设备树节点名加设备实例。如果没有这个目录说明内核里MDIO控制器驱动没有正确注册需要去查设备树或者控制器驱动加载状态。还可以用下面命令查看MDIO总线上枚举到了哪些PHY设备cat /sys/class/mdio_bus/f1072000.mdio/devices/f1072000.mdio:10不过对MV88E6390这种交换芯片由于它内部集成了多个PHYLinux的MDIO子系统不一定会把它枚举成独立的phy_device具体要看驱动实现。如果枚举不到别慌不影响直接用mdio-tools访问只要总线节点存在就行。5.2 读取Chip ID验证协议访问路径任何芯片调试的第一步都应该先读Chip ID或Device ID确认通信正常。MV88E6390的Chip ID寄存器比较特殊它的设备地址是0x0寄存器地址是0x0003高16位和0x0004低16位但需要先操作间接访问窗口。MV88E6390的全局寄存器间接访问窗口在手册里通常描述为寄存器地址说明Global Register Address RegisterDEVAD 0x0Reg 0x0写入目标寄存器地址Global Register Data RegisterDEVAD 0x0Reg 0x1读写目标寄存器数据操作步骤是通过Clause45向DEVAD 0x0的设备写入寄存器0x0数据为目标寄存器地址。再通过Clause45向DEVAD 0x0的设备读取寄存器0x1得到目标寄存器的内容。假设目标寄存器是Chip ID寄存器它实际上在全局寄存器空间里的地址为0x0000具体看手册定义那么命令如下# 第一步把目标寄存器地址0x0000写入地址窗口寄存器 mdio-write -b 0 -p 0x10 -d 0x0 -r 0x0 -v 0x0000 # 第二步读取数据窗口寄存器得到Chip ID mdio-read -b 0 -p 0x10 -d 0x0 -r 0x1MV88E6390的Chip ID如果读出来是0x6390说明访问路径正确硬件连线和PHY地址都对。如果读出来全F要么PHY地址不对要么Clause45格式没发对如果读出来全0可能MDIO引脚的上下拉配置有问题或者芯片处于复位状态。5.3 Clause22读PHY寄存器的操作示例虽然MV88E6390内部PHY的扩展寄存器大量挂在Clause45下但它的每个内部PHY仍然保留了Clause22兼容的基本寄存器组。也就是说你完全可以用Clause22去读PHY 0的状态寄存器。假设PHY0在MDIO总线上映射的PHY地址就是芯片的PRTAD注意这里要确认PHY地址映射规则MV88E6390内部PHY的MDIO地址由ADDR引脚决定每个PHY地址递增读PHY Status寄存器地址0x01mdio-read -b 0 -p 0x10 -r 0x01如果link up并且协商到千兆寄存器0x01的bit2Link Status应该是1bit6100BASE-TX和bit71000BASE-T根据协商结果置位。但要注意Clause22能读到的只是基本状态。MV88E6390的PHY更多链路信息比如SerDes信号完整性状态、RX信号幅度、均衡配置都藏在Clause45空间里。只靠Clause22远远不够。5.4 Clause45读SerDes链路状态的实操MV88E6390的SerDes寄存器挂在DEVAD 0x3下面。调试光口或者SGMII链路时会有用。假设要读SerDes的链路状态寄存器手册里通常会给出类似Device Address 0x3, Register 0x0001这样的SGMII Link Status寄存器。操作命令如下# 先写地址包指定DEVAD 0x3寄存器地址0x0001 mdio-write -b 0 -p 0x10 -d 0x3 -r 0x0001 # 然后读数据 mdio-read -b 0 -p 0x10 -d 0x3 -r 0x0001等等上面这种先写地址包的操作其实取决于mdio-tools的具体实现方式。多数情况下mdio-tools的-d参数已经隐含了Clause45的地址阶段处理所以直接一条mdio-read -b 0 -p 0x10 -d 0x3 -r 0x0001就行。具体要看工具版本最好的办法是先用逻辑分析仪抓一次总线波形确认工具发的帧序列是地址包读数据包还是别的格式。我的经验是先把工具行为搞清楚再依赖它。尤其是Clause45这种两步操作的协议不同厂家的实现细节可能有差异。从波形上看Clause45的读操作是地址包后紧跟读数据包两个包的PRTAD和DEVAD字段必须一致。如果只发了地址包没发数据包寄存器是读不到值的。6. 核心技术点一MV88E6390的全局寄存器间接访问机制详解6.1 为什么要设计间接访问前面提到了MV88E6390的全局寄存器访问窗口这里展开深入讲一下。全局寄存器很多远超Clause45一个DEVAD下65536个寄存器的理论容量其实不是容量不够而是因为Clause45的DEVAD是5位最多32个设备地址每个设备地址下16位寄存器地址也就是最多64K个寄存器空间够大。那为什么还要间接访问真正的原因是为了寄存器原子性。很多全局配置寄存器是64位甚至更宽而MDIO总线每次只能传16位数据。如果让主机直接对一个64位寄存器发起四次总线事务中间一旦被其他主机操作打断就可能出现数据和地址不匹配的问题。间接访问机制相当于加了一把简单的锁先把地址锁存到窗口寄存器再连续读数据窗口期间同一总线上其他访问者不会干预到你的目标。另外交换芯片内部的很多配置寄存器并不是简单地映射到MDIO地址空间它们可能挂在芯片内部的某种配置总线上。MDIO控制器通过窗口寄存器去桥接内部配置总线这样芯片设计团队可以灵活地调整内部布局而不影响外部MDIO的接口协议。6.2 间接访问窗口寄存器的详细配置以MV88E6390手册定义为例Global Registers的间接访问需要操作下面两个寄存器寄存器名称DEVAD寄存器偏移方向作用Global Address Register0x00x0写锁存目标全局寄存器地址Global Data Register0x00x1读/写读取或写入目标寄存器数据在写操作时顺序是首先写Global Address Register把目标地址写入然后写Global Data Register把目标数据写入。在Linux用户态用mdio-write实现# 写全局寄存器0x1000数据0xABCD mdio-write -b 0 -p 0x10 -d 0x0 -r 0x0 -v 0x1000 mdio-write -b 0 -p 0x10 -d 0x0 -r 0x1 -v 0xABCD读操作也是两步先写地址寄存器再读数据寄存器。6.3 跨页跨设备地址的注意事项MV88E6390内部有一部分寄存器不仅挂在全局窗口下还涉及页面Page选择寄存器。特别是PHY寄存器Marvell的老款PHY有扩展页Extended Page机制MV88E6390继承了这一风格。PHY寄存器22的Page选择寄存器是0x00在Clause22空间扩展页寄存器是0x1E。当你需要读PHY的某个扩展寄存器时要先在当前PHY地址下写Page值然后才能访问扩展地址空间。这套机制和Clause45的DEVAD是两套并行体系调试时一定要区分清楚。我自己因为这个问题浪费了半天用Clause45的DEVAD 0x1去读PHY的某个扩展寄存器怎么读都是0后来发现那个寄存器实际上挂在Clause22的扩展页下面需要先写Page到0x0002再用Clause22寄存器地址0x10去读。MDIO总线本身不区分你是Clause22还是Clause45芯片内部的功能模块自己决定用哪种方式被访问。7. 核心技术点二MDIO控制器时钟与访问时序的坑7.1 MDC时钟频率的选择很多MCU或SoC的MDIO控制器IP默认输出时钟是2.5MHz但对MV88E6390而言这个频率没问题。需要注意的反而是主控端时钟频率过高的情况。有些SoC的MDIO控制器支持25MHz但如果PCB走线比较长、或者MDIO线上有多个设备高频下信号质量会明显变差出现间歇性读写失败。遇到时好时坏的问题我建议先降频测试。Linux下通过设备树或者驱动参数把MDC时钟降下来比如从2.5MHz降到1.25MHz甚至更低。如果降频后问题消失了基本确定是信号完整性问题而不是芯片寄存器配置问题。7.2 读写时序里的TA位MDIO协议里读操作的TA位Turnaround是Z0意味着读命令发出后总线有一个时钟周期的高阻态让从设备接管总线。这个Z0是协议硬性规定的主控侧不能在这个周期驱动MDIO线。如果MDIO控制器实现得不好在TA期间仍然驱动总线就会和从设备产生总线冲突读回来的数据可能是乱的。这个问题的排查比较隐蔽。逻辑分析仪看波形时要特别注意TA那一位。如果看到TA期间MDIO线上有强烈的毛刺或电平翻转很可能主控在TA阶段没有正确释放总线。好消息是绝大多数SoC的MDIO控制器IP都是经过验证的这个问题很少见但如果你用的是FPGA自己写的MDIO控制器就要重点检查状态机的TA处理。7.3 上拉电阻与开漏驱动的匹配MDIO信号线是双向的通常需要接上拉电阻。上拉电阻的阻值会影响信号上升沿进而影响MDC频率上限。标准上拉电阻典型值是1.5kΩ到10kΩ之间。如果PCB空间紧张用了10kΩ走线又比较长上升沿可能会变缓高速时容易采样错误。MV88E6390的MDIO接口内部有弱上拉但外部最好还是保留上拉电阻。不需要太纠结具体阻值我一般用4.7kΩ。调试时如果发现系统上电后MDIO读不到数据先量一下MDIO线的静态电平。正常情况下空闲时应该是高电平如果被拉低说明有设备在占用总线或者某个从设备的MDIO引脚损坏了。8. 实战中的坑从波形到寄存器一次完整的排障过程8.1 症状描述与初始判断前面说的那块板卡症状是eCPU交换芯片内部CPU和外部主控都能访问Switch但主控通过MDIO读不到任何PHY的Link状态。第一次抓波形时我发现MDC时钟一直在跑MDIO线上也有帧活动但读回来的数据全FF。第一反应是PHY地址没配对于是把ADDR引脚的电平重新核对了一遍发现硬件工程师把ADDR[4:0]接成了10111也就是0x17而不是我以为的0x10。修改mdio-tools参数后读到的数据依然是全FF。这个时候问题就复杂了。我开始怀疑是不是MDIO总线上存在设备地址冲突。8.2 逐步排查过程第一步把MDIO总线上其他设备断开只留MV88E6390。有些板卡上MDIO总线会同时挂PHY、交换芯片、光模块I2C转MDIO桥接芯片等多个设备如果没有合理配置上拉和地址总线会被某个设备拉死。第二步确认MDC/MDIO有没有接反。MDC是时钟MDIO是数据看起来简单但在多pin连接器上容易做反。量波形时发现MDC和MDIO都有活动但两个通道非常像我还误以为只有一个信号在跑。第三步检查上电时序。MV88E6390的复位引脚和电源时序必须满足手册要求如果在复位解除前MDIO就开始访问芯片可能没有完成初始化。等到系统完全稳定后手动复位一次芯片再读寄存器居然就正常了。8.3 根因确认最终查出来的问题其实很基础复位信号释放得太早MDIO访问发生在芯片尚未完全启动完成时。因为MV88E6390内部有多个电源域复位释放后需要一定时间完成固件加载和PHY自校准手册上明确写了这个时间要求。后来在硬件上将复位释放延时调整到100ms以上问题彻底消失。这个坑提醒我MDIO寄存器访问出现问题不要一股脑往协议上想。先确认供电、时钟、复位、地址引脚这些基础项没问题再往下查协议和寄存器配置。很多时候问题根本不是软件能解决的硬件时序不对代码写得再好都没用。8.4 如何用逻辑分析仪验证Clause45帧格式调试Clause45时用双通道逻辑分析仪同时抓MDC和MDIO。采样率至少10MHz也就是每个MDC周期采4个点以上。解码时手动对照帧格式来看不要完全依赖协议分析软件因为Clause45的ST字段是00很多通用协议分析器默认只解Clause22会把Clause45的帧当错误帧丢弃。一个简单的判断方法抓一段波形数32个前导1然后看下两位。如果是01就是Clause22如果是00就是Clause45。接着数OP字段地址包OP是00写数据包OP是01读数据包OP是11。这样一眼就能看出主控发的帧序列对不对。我用这种方法确认了mdio-tools在Clause45读操作时的帧序列地址包读数据包。如果你发现你用的工具只发了地址包没有发数据包说明那个工具对Clause45支持不完整需要换工具或者自己写驱动。9. 代码级实现在Linux内核驱动里读写MV88E6390寄存器9.1 mdio-bus访问API的基本用法如果要在内核驱动里操作MV88E6390寄存器主要使用phy_read、phy_write、mdiobus_read、mdiobus_write这组API。前者是针对phy_device的封装后者是直接对mdio_bus操作。最基础的是mdiobus_read和mdiobus_writestruct mii_bus *bus mdiobus_get_bus(0); int val mdiobus_read(bus, phy_addr, reg_addr); mdiobus_write(bus, phy_addr, reg_addr, val);注意mdiobus_read和mdiobus_write默认走的是Clause22协议。对于MV88E6390这种需要Clause45访问的场景不能用这两个函数直接访问DEVAD下的寄存器因为它们没有传DEVAD参数的接口。正确的做法是使用mdiobus_c45_read和mdiobus_c45_writeint mdiobus_c45_read(struct mii_bus *bus, int phy_addr, int dev_addr, u16 reg_addr); int mdiobus_c45_write(struct mii_bus *bus, int phy_addr, int dev_addr, u16 reg_addr, u16 val);这两个函数内部会处理Clause45的地址阶段和数据阶段向设备发送正确的帧序列。9.2 一个可以直接用的Clause45读写函数封装实际驱动开发中我会封装一层统一的访问接口屏蔽Clause22和Clause45的差异。下面这个例子参考了Marvell官方驱动和内核phy驱动常见写法你可以直接改改就用static int mv88e6390_mdio_read(struct mii_bus *bus, int phy_addr, int dev_addr, int reg_addr) { if (dev_addr MDIO_DEVAD_NONE) return mdiobus_read(bus, phy_addr, reg_addr); return mdiobus_c45_read(bus, phy_addr, dev_addr, reg_addr); } static int mv88e6390_mdio_write(struct mii_bus *bus, int phy_addr, int dev_addr, int reg_addr, u16 val) { if (dev_addr MDIO_DEVAD_NONE) return mdiobus_write(bus, phy_addr, reg_addr, val); return mdiobus_c45_write(bus, phy_addr, dev_addr, reg_addr, val); }这里的MDIO_DEVAD_NONE是自己定义的宏比如#define MDIO_DEVAD_NONE (-1)用于表示使用Clause22访问。9.3 全局寄存器间接访问在内核里的实现MV88E6390的全局寄存器间接访问在内核驱动里可以这样实现static int mv88e6390_global_reg_read(struct mii_bus *bus, int phy_addr, u16 reg_addr) { int ret; /* 写地址到Global Address Register */ ret mdiobus_c45_write(bus, phy_addr, MV88E6390_DEVAD_GLOBAL, MV88E6390_GLOBAL_ADDR_REG, reg_addr); if (ret 0) return ret; /* 读Global Data Register */ return mdiobus_c45_read(bus, phy_addr, MV88E6390_DEVAD_GLOBAL, MV88E6390_GLOBAL_DATA_REG); }有几个细节需要特别注意全局间接访问寄存器只有16位所以目标寄存器地址不能超过0xFFFF。写操作时必须先写地址寄存器再写数据寄存器两次操作之间不要插入其他总线事务。如果芯片支持多主机访问MDIO总线需要加互斥锁保护这两步操作。否则另一个主机在中间插入操作读出来的数据就是别的寄存器的。9.4 内核驱动中使用MDIO操作时的互斥保护MDIO总线是一个共享总线内核中mdio子系统本身有一把总线锁。但是在间接访问这种多步操作场景单靠总线锁还不够因为总线锁只保护单次传输的原子性不能保护两次独立的mdio_c45_write调用之间不被插入其他设备访问。我会在驱动里加一个简单的mutexstatic DEFINE_MUTEX(mv88e6390_reg_lock); static int mv88e6390_global_reg_read_locked(struct mii_bus *bus, int phy_addr, u16 reg_addr) { u16 val; int ret; mutex_lock(mv88e6390_reg_lock); ret mdiobus_c45_write(bus, phy_addr, MV88E6390_DEVAD_GLOBAL, MV88E6390_GLOBAL_ADDR_REG, reg_addr); if (ret 0) goto out; val mdiobus_c45_read(bus, phy_addr, MV88E6390_DEVAD_GLOBAL, MV88E6390_GLOBAL_DATA_REG); ret val; out: mutex_unlock(mv88e6390_reg_lock); return ret; }这个mutex是驱动内部的与内核mdio总线锁不冲突。如果总线上还有其他驱动实例也在访问同一个芯片则需要考虑跨驱动的锁不过大多数场景下板卡只有一个主机访问MDIO驱动内部的锁足够用了。10. 从MV88E6390扩展到其他Marvell交换芯片6360、6320、6341的通用套路10.1 不同芯片的寄存器布局差异MV88E6390的寄存器访问模式在Marvell其他交换芯片上也大同小异。MV88E6360、MV88E6320、MV88E6341这些都是同一产品家族它们的全局寄存器间接访问机制基本一致只是具体寄存器偏移和功能配置有所差异。差异主要集中在这几个方面芯片型号端口数量PHY数量SerDes数量主要差异MV88E639010104全千兆功能最全MV88E6360662端口数少适合小系统MV88E6320752混合电口光口MV88E6341541精简版无论哪颗芯片调试流程都是先读Chip ID确认通信再读PHY状态确认链路最后按需配置全局寄存器。这套方法论一旦建立换芯片只是换寄存器地址映射表的问题。10.2 84E6xxx系列如88E6393X的访问方式新版88E6393X、88E6390X这些芯片虽然属于更高端的产品线但MDIO访问的基本框架仍然是Clause45。它们的SDSSerDes寄存器访问方式类似仍然通过DEVAD 0x3这种设备地址去访问。只是寄存器数量和位宽都有增加还引入了MIIM间接访问的复杂机制。遇到这类新芯片时建议先看官方SDK里的寄存器定义头文件。Marvell的SDK如mvsw-api里通常有完整的寄存器偏移宏定义可以直接参考。自己从datasheet逐个寄存器去翻效率太低而且容易漏掉访问模式的特殊说明。10.3 和Broadcom、Realtek芯片的对比思考写到最后想多说一句。做过Broadcom交换芯片的人可能会发现Broadcom习惯用独立的MDIO从设备地址加寄存器偏移的方式和Marvell有些差异。Realtek的8211系列PHY则更喜欢用Clause45访问PHY扩展寄存器。每家的风格不同但底层都是MDIO协议那套东西。把Clause22和Clause45的基础打牢换芯片时只是换一份寄存器映射表不需要重新学协议。11. 调试效率提升技巧脚本化批量读写与自动比对11.1 用shell脚本批量读取关键寄存器实际调试中很多场景需要快速读取一批寄存器的值。逐个敲mdio-read命令效率太低我一般会写一个简单脚本把要读的寄存器列表放在文件里循环执行。#!/bin/bash BUS0 PHY0x10 # 文件每行格式: device_address register_offset while read dev reg; do val$(mdio-read -b $BUS -p $PHY -d $dev -r $reg) echo DEVAD $dev REG $reg VAL $val done registers.txtregisters.txt的内容类似0x0 0x0000 0x0 0x0001 0x3 0x1000 0x1 0x0001每次调整完配置后跑一遍脚本把输出重定向到日志文件对比前后差异问题往往一目了然。我用这个办法定位过一个SerDes配置错误自动协商失败的原因就在一个reserved bit被写成了1对比日志时一眼就看到了寄存器值的变化。11.2 构造mdio raw帧的Python辅助脚本如果mdio-tools对某些特殊帧格式支持不好可以用Python配合spidev或者调用底层驱动接口来构造原始帧。不过更简单的方式是直接操作MDIO控制器的MMIO寄存器用shell的devmem或者Python的mmap访问。这里给一个用Python调用devmem的示例思路假设MDIO控制器寄存器基地址已知import subprocess def devmem_read(addr): return int(subprocess.check_output(fdevmem {addr}, shellTrue).strip(), 0) def devmem_write(addr, val): subprocess.check_call(fdevmem {addr} 32 {val}, shellTrue)具体寄存器的位段定义需要查SoC的MDIO控制器手册。这个方法适合做协议验证但只建议在用户态调试阶段用正式驱动还是要走内核API。11.3 踩坑记录读寄存器值突然跳变的原因调试MV88E6390时还遇到过一种诡异现象同一个寄存器连续读十次其中两三次值不同。排查了很久最后发现是MDIO总线上有另一个I2C转MDIO桥接芯片在定时扫描PHY状态。两个主机同时访问MDIO总线产生了总线竞争。解决方案是在硬件上禁止那个桥接芯片的自动轮询或者在软件上只在扫描间隙访问。这说明一个问题MDIO虽说是管理总线但多主机共享时冲突很常见设计阶段就要考虑清楚总线上到底有多少个主设备、各自什么时候发起访问。MV88E6390本身支持原子访问但如果被其他主机干扰再好的机制也白搭。12. 关键寄存器速查与常见配置示例12.1 Chip ID与版本寄存器MV88E6390的Chip ID可以通过全局寄存器间接访问读取。常见值如下寄存器取值含义Device ID高16位0x6390芯片型号Device ID低16位因版本而异芯片修订版本读出来和这两个值对不上先查访问方式再查芯片型号是否贴错。不要怀疑是其它问题Chip ID读不对后面所有配置都是空中楼阁。12.2 PHY Link状态寄存器通过Clause22读PHY Status寄存器0x01Bit位含义15:13Speed0b0111000M0b010100M0b00110M12Duplex1Full0Half11自动协商完成10自动协商能力2Link Status读后自动清零1Jabber检测0扩展寄存器能力注意bit2 Link Status是一次性读出位每次读操作后自动清零。如果连续两次读得到结果不一样说明链路状态处于翻转中常见于信号质量差的SerDes链路。12.3 SerDes寄存器与信号完整性指示MV88E6390的SerDes寄存器区DEVAD 0x3里有一个很有用的寄存器Signal Quality Indicator。它反映接收端的眼图裕量或者SNR估算值具体寄存器偏移要看芯片手册对应版本。调光模块或者长走线SGMII时这个寄存器非常有用。通过它能看到PRBS错误率是否异常上升调试PCS层时能快速定位是物理层问题还是协议层问题。不过不同版本的MV88E6390芯片这个寄存器的偏移可能不同用之前先确认芯片的修订版本。12.4 端口VLAN和转发控制寄存器示例MV88E6390的端口属于全局寄存器空间通过间接访问配置。以Port Default VLAN IDPort VID为例假设端口3对应的全局寄存器偏移是0x0000这里仅示意真实地址以手册为准配置流程# 写入目标端口寄存器地址 mdio-write -b 0 -p 0x10 -d 0x0 -r 0x0 -v 0x0301 # 写入VID值例如100 mdio-write -b 0 -p 0x10 -d 0x0 -r 0x1 -v 100端口寄存器配置的关键是理解端口在全局寄存器空间里的偏移计算方式。MV88E6390的手册里会在Port Registers章节给出类似Port N Registers offset Port 0 address N * 0x1000这样的公式先算出实际寄存器地址再走间接访问。13. 复盘总结MDIO调试MV88E6390的几条核心经验13.1 从结果反推过程少走弯路这些年调试过不少交换芯片和PHY一个感受是MDIO寄存器读写问题90%以上出在三级问题——地址配错、协议用错、上电时序不对。真正芯片本身损坏的概率极低。所以排查顺序很重要先从物理层、地址配置、协议格式查起别一上来就怀疑芯片坏。13.2 工具要用对更要懂工具背后的协议逻辑mdio-tools这类工具很好用但不要完全依赖。它只是一个把参数编码成帧的工具如果你不懂Clause22和Clause45的帧结构工具报错时你根本不知道错在哪。反而是在嵌入式环境没有工具可用时自己写一段裸机MDIO驱动用GPIO模拟时序把MDIO总线协议彻底吃透后面用任何工具都顺手。13.3 想到哪说到哪的经验碎片最后整理几条操作层面的碎片经验供你参考MV88E6390的MDIO地址由硬件引脚决定上电后无效不要试图用软件修改。确认地址最可靠的方法是读Chip ID而不是量电压。读PHY状态寄存器时Link Status是粘性位读完后清除。判断链路状态最好连续读多次看稳定的bit2值。配置全局寄存器时必须先写地址窗口寄存器再写数据窗口寄存器。顺序反了配置会写进错误的地方。高速SerDes调试时用Clause45读信号质量寄存器不要只看Link Status后者太滞后。如果MDIO总线上挂多个设备考虑用I2C MUX或者MDIO隔离芯片避免地址冲突和总线竞争。MDIO的时序调试过程中保持耐心逐帧分析波形比盲试寄存器高效得多。MV88E6390这颗芯片我用了挺长时间从最初把它当普通PHY用Clause22一阵乱读到后来熟悉了Clause45和全局寄存器的间接访问机制再回头看MDIO调试的本质就是对协议和地址空间的理解深度问题。希望这篇内容能帮你把基础打牢省去一些我当初试错的时间。
返回列表