ARTICLE DETAIL

资讯详情

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

Marvell 88E6095交换芯片Linux驱动移植与调试实战

Marvell 88E6095交换芯片Linux驱动移植与调试实战 简介Marvell 88e6095是一款常用于主板的千兆以太网控制芯片本资源即为配套该芯片的驱动及DSDT_2.3c补丁整合包适用于因系统兼容性或ACPI配置问题导致网络异常的用户也可供驱动开发与嵌入式网络调试人员参考。压缩包共116个文件约323KB主体为C语言源码59个.c文件和头文件20个.h文件辅以15个txt说明文档和6个makefile构建脚本另含readme、rules、defs等工程配置覆盖驱动编译、加载与调试环节。已有372人学习下载。包内源码结构完整从端口控制、PHY寄存器读写、桥接转发表到链路聚合等模块均有涉及并附带若干调试与配置工具便于研究驱动的初始化、数据传输、错误处理及电源管理逻辑DSDT相关配置则有助于修复特定主板上的识别与兼容性问题适合需要深度排错或二次开发的用户。 做嵌入式或者工控的朋友看到“88e6095驱动”这几个字大概率是遇到了两种情况要么是板子上的网口突然不通了要么是拿了块带交换芯片的底板想把它管起来却发现内核里根本没有对应代码。这颗Marvell的88E6095是一颗5口千兆交换芯片在很多老式工业主板、网络打印服务器、机顶盒方案上都能见到它的身影说老实话资料不算多驱动写起来也有点绕。这篇文章我就以实际移植过程为线索把这颗芯片的驱动思路、寄存器操作、调试方法和踩坑记录都整理出来。内容不只针对88E6095本身凡是基于Marvell Link Street家族的交换芯片比如88E6060、88E6071、88E6095F等遇到驱动问题时这套排查逻辑基本都能复用。适合正在做Linux内核驱动移植的工程师也适合刚接触交换芯片、想搞清楚驱动和普通网卡驱动到底差在哪儿的初学者。1. 项目整体思路先弄清楚这颗芯片到底是什么1.1 88e6095不是普通网卡搞清楚角色再动手很多人拿到88e6095第一反应是“这不就是个网卡芯片吗”直接用Linux自带的phy驱动去点结果怎么点都没反应。原因在于88e6095是一颗交换芯片和常见的RTL8211、AR8035这类PHY芯片完全是两个层级的东西。普通网卡芯片的职责很单一把MAC层发来的数据帧编码成电信号发出去再把收到的电信号解码成数据帧交给MAC。而88e6095本身就是一个完整的二层交换设备它的内部集成了MAC、PHY、地址查找引擎、VLAN表、端口映射逻辑。换句话说它本身就是一台“迷你交换机”你通过MDIO或者SPI接口去访问它不是为了控制某个PHY的链接状态而是为了配置它的交换行为。搞清楚这一点驱动开发的方向就完全不一样了。你需要做的是告诉这颗芯片“端口0是CPU口端口1到4是用户口”、“哪些端口之间的数据允许转发”、“MAC地址表怎么老化”而不是简单读个PHY ID、配置下协商模式就完事。我刚接触这颗芯片时犯过一个典型错误拿着datasheet去搜“88e6095 linux driver”搜出来一堆Marvell提供的官方SDK包反而把自己绕晕了。后来才明白官方SDK适合做完整交换机产品的场景如果只是想把这颗芯片接到自家主控上做端口扩展自己写一个轻量驱动反而更可控。1.2 驱动方案的选型DSA框架、switchdev还是裸寄存器操作Linux内核针对交换芯片提供了几个层次的驱动框架选型直接决定了后续的开发量。DSADistributed Switch Architecture这是目前最推荐的方式。内核里已经有marvell DSA驱动drivers/net/dsa/mv88e6xxx88E6095正好在这个驱动的支持列表里。DSA框架会把交换芯片的每个用户口注册成一个独立的net_device对上层应用来说各个口就是普通的网口可以用标准工具管理。switchdev这个更高级可以把交换芯片暴露给用户态让tc、bridge等工具直接操作硬件转发表。但88E6095功能相对简单用switchdev有点杀鸡用牛刀而且内核版本要求高老平台不一定方便升级。裸寄存器操作不借助框架自己写platform_driver通过MDIO或SPI直接读写芯片寄存器。这种方式的优点是代码量小、逻辑清晰适合芯片功能用得很浅的场景缺点是失去了内核网络子系统的很多便利VLAN、链路状态上报、网卡统计等都得自己实现。我在实际项目中用的是DSA框架理由很现实内核对mv88e6xxx驱动已经维护了很多年BUG修复及时而且DSA框架天然解决了“5个口分别对应哪个netdev”的问题不用自己在驱动里维护端口映射表。如果你的内核版本比较老比如3.x时代DSA驱动可能还不支持88E6095或者需要打补丁。这时候建议回退到裸寄存器方案后面我会把关键的寄存器读写流程写清楚不管走哪种框架底层操作都是一样的。2. 核心细节解析寄存器、端口和PHY的三层关系2.1 看懂88e6095的寄存器地图数据手册里寄存器描述动辄几百页新手很容易迷失。其实抓住主线就够用了88E6095的寄存器空间可以分为三大类。全局寄存器Global Registers控制芯片整体行为的包括芯片ID寄存器、全局状态寄存器、全局控制寄存器。通过芯片ID寄存器地址0x00可以确认访问是否成功我每次做驱动移植都会先读这个寄存器验证通信链路如果读出来不是0x6095后面的调试都无从谈起。端口寄存器Port Registers每个端口有一组独立寄存器控制这个端口的使能、流量控制、VLAN成员关系、速率双工配置等。注意88E6095的端口寄存器不是简单线性排列的而是通过间接访问机制读取这一点和第3节讲实操时细说。PHY寄存器PHY Registers这是最容易混淆的部分。88E6095的每个端口内部都集成了一颗PHY访问这些PHY的方式和访问外部独立PHY完全一样都是走MDIOManagement Data Input/Output协议寄存器地址空间也和标准IEEE 802.3规定的一样。这套“全局-端口-PHY”三层的结构有点像一个公司组织架构全局寄存器是老板决定整个公司的运行方向端口寄存器是部门主管管各自团队的日常运作PHY寄存器是基层员工负责最底层的物理链路收发信号。驱动开发时你要知道当前操作的是哪个层级选错层级就像找错人办事指令发出去对方根本不理你。2.2 CPU口与用户口理解交换芯片的工作模式88E6095有5个端口实际使用中通常有一个端口连接主控CPU称为CPU口其余端口对外提供网络接入。在DSA框架里CPU口被标记为DSA_CPU_PORT用户口标记为DSA_USER_PORT。CPU口连接方式不同驱动配置差异很大。如果CPU口走RGMIIReduced Gigabit Media Independent Interface接口接主控MAC驱动需要配置该端口的MAC工作模式为RGMII并匹配主控侧MAC的速率、双工设置。如果CPU口也走MDIO连接到一颗独立PHY再到主控配置逻辑又不相同。我常用的一种经验是把CPU口当作VLAN的上行口来理解。在交换芯片内部数据帧从用户口进来查MAC地址表和VLAN表后要么转发到另一个用户口要么上报给CPU口。驱动要做的事情本质上就是把这套决策规则用寄存器写清楚。调试时最容易出的问题是用户口之间ping不通但用户口和CPU口之间通。这种情况通常是地址学习功能没打开或者端口之间的转发规则没配置好。先用最粗暴的方式排查把所有端口的转发状态都设为转发允许关掉VLAN过滤先把二层通起来再一步步加限制策略。3. 实操过程与核心环节实现几分钟跑通一个最小驱动3.1 硬件连接确认与环境准备动手写代码前务必先确认两件事不然代码里翻来覆去查BUG都不会有结果。第一确定88E6095和主控之间用的是MDIO接口还是SPI接口。88E6095支持两种访问方式硬件管脚上通过STRAP引脚配置工作模式。多数板子用MDIO因为可以复用主控已有的MDIO控制器。用mdio-tool或者内核调试接口先确认能不能读到PHY ID是最早的探路动作。第二确认复位管脚接在主控的哪个GPIO上。88E6095上电后有一段时间的初始化过程如果驱动在芯片还没准备好时就发MDIO指令大概率读到全F的无效数据。我习惯在驱动probe函数的开头加一个gpio_set_value操作拉低复位脚延时100ms再拉高再延时300ms然后才开始读芯片ID。环境准备好以后先做一个最简单的MDIO扫描确认总线上能看到88E6095的PHY地址。一般每端口PHY的地址规律和端口号有关我的经验是直接读0到31共32个地址找到能返回有效PHY ID的地址记录下来。# 假设MDIO控制器挂在bus 0上遍历读PHY ID # 我用的是一个简单的mdio-read工具 for i in $(seq 0 31); do ret$(mdio-read --bus 0 --phy $i --reg 2 2/dev/null) if [ $ret ! 0xffff ]; then echo addr $i: PHY ID2 $ret fi done3.2 最小驱动代码读取芯片ID与基础端口配置下面这段代码是DSA框架下88E6095最小驱动的核心片段展示了怎么通过全局寄存器读芯片ID、怎么通过间接方式访问端口寄存器。它涵盖了你写第一个驱动时最需要关心的几个操作。#include linux/kernel.h #include linux/module.h #include linux/mdio.h #include linux/platform_device.h #include linux/gpio.h #define MV88E6XXX_PORT_SWITCH_ID 0x03 #define MV88E6XXX_PORT_SWITCH_ID_6095 0x0950 /* 间接访问端口寄存器的流程 * 1. 先往 Global Register 0x18 写入想访问的端口号和寄存器地址 * 2. 再从 Global Register 0x19 读取数据或写入数据 */ #define GLOBAL_REG_PORT_BASE 0x18 #define GLOBAL_REG_PORT_DATA 0x19 static struct mii_bus *g_bus; static int g_phy_base 0x10; /* 这个值不固定要按板子是哪个phy地址来决定 */ static int chip_read(u8 reg) { /* 直接读全局寄存器 */ return mdiobus_read(g_bus, g_phy_base, reg); } static int port_read(int port, u8 reg) { /* 端口寄存器间接读 */ u16 addr (port 8) | reg; mdiobus_write(g_bus, g_phy_base, GLOBAL_REG_PORT_BASE, addr); return mdiobus_read(g_bus, g_phy_base, GLOBAL_REG_PORT_DATA); } static int probe(struct platform_device *pdev) { int id; /* 复位芯片拉低延时100ms拉高延时300ms */ gpio_set_value(reset_gpio, 0); msleep(100); gpio_set_value(reset_gpio, 1); msleep(300); g_bus mdiobus_get_from_device(pdev-dev); if (!g_bus) return -ENODEV; id port_read(0, MV88E6XXX_PORT_SWITCH_ID); dev_info(pdev-dev, switch id 0x%04x\n, id); if (id ! MV88E6XXX_PORT_SWITCH_ID_6095) return -ENODEV; /* 到这里说明MDIO通路正常可以做后续配置了 */ return 0; }注意port_read这个函数里的间接访问机制这是88E6095和普通PHY驱动的最大不同。端口寄存器和PHY寄存器并不是能直接通过MDIO地址空间访问的必须先写一个“选择寄存器”的指令到全局寄存器0x18然后再通过0x19读写实际数据。这个概念和I2C设备里先写寄存器地址再读数据很像理解了这一点就理解了整个访问模型。我在一开始忽略了这个间接机制直接用标准MDIO读端口寄存器结果读出来的值永远不对。后来对照datasheet的“Port Register Access”章节才反应过来白白浪费了半天时间。你如果遇到类似问题先检查是不是间接访问的流程写错了。3.3 在设备树里声明88e6095节点DSA框架下设备树节点有固定格式声明正确才能让驱动匹配上。下面是一个实际项目中使用的设备树片段。mdio { switch0: switch10 { compatible marvell,mv88e6095; reg 0x10; dsa,member 0 0; ports { #address-cells 1; #size-cells 0; port0 { reg 0; label cpu; ethernet gmac0; phy-mode rgmii-id; fixed-link { speed 1000; full-duplex; }; }; port1 { reg 1; label lan1; phy-mode internal; }; port2 { reg 2; label lan2; phy-mode internal; }; }; }; };设备树里关键的几个点分别是compatible必须匹配驱动里的字符串reg是MDIO总线上的PHY地址要和实际地址一致port0必须声明为CPU口并且指定和哪个主控MAC对接剩下几个用户口的label会直接影响生成出来的网卡名。之前我把CPU口的phy-mode写成了internal结果主控MAC侧一直不稳定改成rgmii-id加上fixed-link配置以后才恢复正常。调试时如果发现CPU口不通优先检查phy-mode和fixed-link这一段。3.4 编译、加载与基本功能验证驱动和设备树都写好后编译烧录重启会让DSA框架自动注册网络设备。验证的最短路径我从上到下是这三步看内核日志有没有mv88e6xxx相关的probe信息以及DSA端口注册成功信息。用ip link查看是否有lan1、lan2这样的网口出现确认用户口都成功注册。插上网线用ethtool lan1看链路状态是否变成UP速率协商是否正确。这三步都过了基本的数据通路就通了。剩下的VLAN配置、链路聚合、统计信息上报都可以基于DSA框架逐步加上去。如果某一步卡住了不要急着改代码先把硬件连接、MDIO地址、复位时序全部重新核对一遍很多时候问题出在硬件层面而不是驱动代码。4. 常见问题与排查技巧实录适配流程中遇到的坎4.1 常见问题速查表现象可能原因排查方法读不到芯片IDMDIO地址配置错误扫描0~31所有地址找有效PHY ID读芯片ID全是0xffff复位时序不对拉长复位后延时到500ms以上端口插入网线不UP端口PHY地址不对逐个端口读状态寄存器看是否检测到链路用户口之间不能互通地址学习或转发规则没配置先关VLAN过滤允许所有端口间转发CPU口通但用户口不通CPU口VLAN成员关系不对检查CPU口是否加入了所有必要的VLANethtool读不到速率PHY协商没配好检查端口PHY的控制寄存器强制全双工千兆测试驱动加载成功但发包丢包CPU口MAC模式不匹配检查RGMII的TX/RX延时配置是否和主控一致4.2 一个典型的CPU口丢包排查实录我有一次调一块板子4个用户口互相ping都通但用户口ping不通外部网络。从现象看问题肯定出在CPU口的上行链路上排查思路可以分享给遇到类似问题的人。先看主控MAC侧用ethtool -S gmac0看有没有大量tx_errors这个命令很快就能判断出主控MAC根本没把数据发出去还是发出去了但交换芯片没处理。我那次查下来tx_errors一直是0说明主控MAC工作正常数据应该在交换芯片这边丢了。接下来看交换芯片的CPU端口状态读端口状态寄存器和端口丢包计数器寄存器。结果发现端口0的收发计数器都在走但用户口的计数器没动静。这说明问题出在交换芯片内部的数据转发逻辑上而不是物理链路。最后定位到VLAN表配置。88E6095这类芯片有个特性如果CPU口没有被加入某个VLAN的成员组那么这个VLAN的流量根本不会被送到CPU口。我把端口0的VLAN成员关系重新配置以后丢包问题直接消失。这次排查给我的教训是交换芯片驱动的问题不能只盯着一层寄存器看必须纵向把“主控MAC-交换芯片转发逻辑-用户口PHY”整条链路都打通了才能定位。推荐从CPU口开始逐层检查和OSI模型里逐层排查的思路一模一样。4.3 独家避坑技巧直接抄作业的几个建议第一复位延时宁可长不可短。88E6095的上电初始化时间比一般PHY要久我现在的代码里固定延时300ms有的板子需要500ms才能稳定。如果你在probe阶段经常读到0xffff先把延时加到1秒试试能省很多排查时间。第二MDIO总线的速度不要跑太高。88E6095对MDIO时序要求不算很严但有些老板子走线质量一般标准MDIO频率2.5MHz是稳妥的选择。我见过有人为了性能把MDIO频率调到5MHz以上结果偶尔出现读值漂移这种偶发问题特别难查。第三别用调试器单步去调交换芯片驱动的初始化流程。交换芯片的很多寄存器写入是有先后依赖关系的你单步卡在中间再去读别的寄存器看到的中间状态往往没有任何意义。正确做法是写一个完整的初始化函数一口气执行完然后再通过打印日志逐步验证。第四官方SDK不能照搬但很有参考价值。Marvell官方SDK里的寄存器初始化序列基本可用但那是针对完整交换方案设计的包含了很多你用不到的全局配置。我的做法是取出“芯片复位初始化”那一段裁剪掉不需要的部分保留端口使能和转发基醚配置再结合DSA框架去适配。第五全程用串口打印辅助调试效果远好过JTAG。交换芯片驱动的状态复杂串口打印可以随时调整输出粒度而不用打断程序运行。我习惯在关键寄存器读写的函数里加一个可开关的DEBUG宏需要时打开不需要时关掉比每次重新编译要高效很多。结尾几点额外经验最后再分享一个个人体会88E6095驱动之所以难上手主要因为它涉及的“交换”概念超过了普通网卡驱动的范畴。但一旦你理解了“全局-端口-PHY”三个层级的关系把间接访问搞清楚后续再去碰其他Marvell交换机芯片都会觉得一通百通。建议刚开始接触的人不要贪多先把“CPU口两个用户口”的最小系统调通再逐步扩展到更多端口、VLAN、QoS等高级功能。这样既能降低排查难度也能在每一步改动中学到更扎实的东西我有几次报错很诡异的问题最后发现都是在功能扩展时不小心破坏了之前调通的基础配置。驱动开发这种事还是得一步一个脚印来。本文还有配套的精品资源点击获取
返回列表