十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

嵌入式Linux下RTL8306MB交换芯片驱动移植全流程解析

嵌入式Linux下RTL8306MB交换芯片驱动移植全流程解析 搞嵌入式Linux的人大概都经历过这种时刻代码看起来全对设备树也加了驱动也加载了但网口就是ping不通。RTL8306MB这颗51口百兆交换芯片在路由器、工业网关、智能家居中控里用得非常广性价比高、功能也够用VLAN、端口镜像、QoS、IGMP Snooping都带但恰恰是这种“功能看着不复杂”的芯片驱动折腾起来一堆暗坑。我这次是从画原理图、定接线到把Realtek的SDK移植进嵌入式Linux环境完整走了一遍全流程中间踩了MDIO时序、PHY地址冲突、设备树节点写法这些典型的坑。这篇就把整个链路从头到尾拆开讲清楚给后面要碰这颗芯片的人省点时间。1. 项目整体设计与思路拆解先看清RTL8306MB的芯片本质1.1 芯片核心能力盘点RTL8306MB是一颗集成了5个百兆PHY的交换芯片加上一个MII/RMII接口的Host端口所以叫“51”口。它的定位很明确让主控SoC通过以太网管理接口接入交换核心由SoC决定转发策略或者干脆让交换芯片自己完成二层转发。对嵌入式产品来说这意味着SoC不需要自带那么多MAC一颗芯片就把5个LAN口全部搞定成本比外挂5颗PHY低一大截。这颗芯片的交换能力其实不弱5个10/100M自协商PHY端口支持Auto-MDIX1个MII/RMII接口用于连接主控SoC的MAC支持802.1Q VLAN、端口镜像、端口限速支持IGMP Snooping、QoS优先级处理通过MDIO/MDC两线管理接口进行寄存器配置选型理由也简单产品需要5个百兆LAN口但SoC自身只有1~2个MAC用RTL8306MB正好补齐。如果产品是纯百兆场景这颗芯片的性价比根本没对手如果是千兆场景就得看RTL8367系列了那就是另一套玩法。1.2 驱动开发的技术路线选型RTKRealtek的这套SDK结构上和很多芯片厂商的SDK一样一堆C源码、一堆API底层是MDIO读写。但SDK本身是跨平台设计的既可以编译成独立应用程序跑在用户态也可以将核心逻辑抽出来做成内核态驱动的一部分。我这次采用的技术路线是“内核态MDIO总线驱动 用户态SDK控制”的组合内核侧只做最基础的工作把MDIO控制器注册好把交换芯片作为一个特殊的MDIO设备挂到总线上保证SoC能通过MDIO正常读写RTL8306MB的寄存器。用户态侧运行Realtek SDK的移植版本由SDK负责VLAN配置、端口设置、镜像等高级功能。为什么这么拆因为SDK代码量大、依赖多放内核里容易把内核搞得又脏又乱而且一旦SDK崩溃会直接殃及内核。用户态跑的另一个好处是调试方便gdb直接挂上去改SDK代码不用重新编译内核。这里要提醒一句如果产品对转发性能要求极高用户态配置交换芯片后Linux的网络协议栈其实是不需要参与转发的。芯片自己就把包转发了SoC只在需要管理流量时才接入。所以性能瓶颈不在驱动而在芯片本身的转发能力驱动只要能正确初始化芯片就达标了。2. 硬件接线画板之前必须先想清楚的事2.1 MII/RMII数据接口选型与连线RTL8306MB的Host端口支持MII和RMII两种模式这个选择在硬件设计阶段就要定死因为涉及走线和时钟频率。MII模式下收发数据各用4根线加上TX_CLK、RX_CLK、TX_EN、TX_ER、RX_DV、RX_ER、CRS、COL信号线数量在16根左右。优点是时序宽松缺点是占引脚、占板面积。RMII模式把收发数据压缩到各2根线时钟统一为50MHz信号线少了一半多但要求SoC侧必须支持RMII接口且50MHz参考时钟的相位要处理好。我的建议是如果SoC的MAC支持RMII优先走RMII。走线少、信号完整性问题少代价是50MHz时钟必须干净。RMII的REF_CLK可以由SoC输出源端时钟也可以由外部晶振提供。RTL8306MB作为从设备时通常由主控提供50MHz。连线这块必须注意TXD[1:0]、RXD[1:0]是MII/RMII的数据线方向是SoC MAC角度定义的TX_EN和RX_DV分别是发送使能和接收数据有效接反了就是数据错乱CRS_DV在RMII模式下合二为一注意芯片手册上标的名字画板的时候还有一个容易被忽略的点Host端口的MII/RMII信号属于高速数字信号虽说是百兆但信号完整性问题照样存在。我建议数据线和时钟线在PCB上做等长处理误差控制在50mil以内然后时钟线远离电源走线避免开关噪声耦合。2.2 MDIO管理接口与PHY地址规划MDIO/MDC是两颗芯片之间最关键的“沟通桥梁”。MDC是时钟MDIO是双向数据线MDC由主控SoC发出MDIO数据在MDC上升沿采样。RTL8306MB的MDIO接口协议是标准的IEEE 802.3 Clause 22帧格式是固定的PRE(32个1) ST(01) OP(读10/写01) PHYAD(5bit) REGAD(5bit) TA(2bit) DATA(16bit)标准MDIO只能访问32个PHY地址RTL8306MB把内部的PHY地址和交换管理寄存器地址都映射到了这个32地址空间里。默认情况下5个PHY对应的地址就是0~4交换核心的管理寄存器通常需要通过特定的扩展寄存器方式访问具体地址映射以SDK头文件里的宏定义为准。规划PHY地址时最容易踩的坑是地址冲突。很多SoC自己带了以太网MACMAC内部有一个PHY地址是从0开始分配的两个设备共用一个MDIO总线时就撞了。解决方式看RTL8306MB的strap引脚能否改PHY地址偏移在内核里通过phy_mask屏蔽掉不需要扫描的地址或者干脆给RTL8306MB单独拉一条MDIO总线我这次就是直接把RTL8306MB挂在SoC的MDIO总线上然后在设备树里指定了它所在的PHY地址同时把MDIO总线自动扫描的phy_mask做了过滤避免内核启动时把不存在的PHY扫出来报错。2.3 电源、复位与时钟最容易忽视的三个炸弹这三个点看着基础实际出问题的概率最高。电源方面RTL8306MB需要核心电压和IO电压两路具体数值以芯片手册为准一般是3.3V IO和1.8V或2.5V核心。上电顺序有讲究如果核心电压先于IO电压到达可能会导致芯片内部逻辑进入不确定状态。硬件设计时加DC-DC或LDO的时序控制软件层面则要在复位释放前确保电源稳定。我见过最隐蔽的问题是电源纹波过大MDIO通信时对时出错现象就是寄存器读回来偶尔是0xFFFF。复位引脚建议用一个GPIO控制而不是直接接到系统复位上。原因很简单软件需要在上电后给芯片一个完整的复位波形再等它稳定然后才去初始化。如果复位和系统绑在一起SoC启动很快、RTL8306MB还没准备好第一次MDIO访问就是失败的而且SDK里不一定有重试逻辑。时钟方面MII模式通常需要外接25MHz晶振给PHY用RMII模式则可能由主控提供50MHz REF_CLK。芯片手册上会明确标注时钟来源和频率照着接就行。这里有个实际教训晶振起振时间不一致会导致同一批板卡有的能起来、有的起不来。我的处理是在复位释放前先延时等晶振稳定一般50ms足够具体看晶振负载电容。3. SDK移植全过程从Realtek源码到Linux内核驱动3.1 SDK包结构与核心API认识Realtek这套SDK拿到的压缩包通常包含几个核心目录API定义、芯片寄存器操作层、平台适配层。平台适配层是移植重点SDK不会知道你的SoC怎么读写MDIO这部分必须自己写。SDK的核心API分几类芯片初始化rtl8306_switch_init()上电后调用一次做芯片的基本配置PHY配置读写PHY寄存器比如自协商、速率双工设置VLAN配置rtl8306_vlan_init()、rtl8306_vlan_makePortVLAN()划分端口VLAN端口控制使能/禁用端口、设置镜像、限速等这些API的底层最终都会落到两个函数上MDIO读和MDIO写。SDK里会有类似rtl8306_reg_read()、rtl8306_reg_write()的接口就是它们调用了平台相关的MDIO操作函数。移植SDK的本质就是把这两个平台函数替换成你SoC能跑的MDIO读写代码。3.2 内核侧MDIO总线驱动的注册如果SoC自带MDIO控制器最简单的方式就是复用内核里的MDIO驱动架构。以设备树方式注册MDIO总线后RTL8306MB作为一个挂在MDIO总线上的设备由内核的MDIO子系统识别。这里有个关键点RTL8306MB虽然是交换芯片但在内核MDIO子系统看来它更像一个“伪装成PHY的设备”。所以驱动模型上可以这样处理在内核里写一个平台驱动probe()里注册一个mii_busmii_bus的read/write回调实现MDIO时序读写将RTL8306MB的PHY地址注册到总线上初始化完成的标志就是能通过read回调读到正确的芯片ID如果SoC的MDIO控制器比较弱或者因为MDC时钟时序和RTL8306MB不兼容导致读写不稳定另一个办法是GPIO模拟MDIObit-bang。GPIO模拟MDIO在调试阶段特别好用因为每一根线上的波形都能拿逻辑分析仪看。我这次在开发前期就是用GPIO模拟MDIO确认时序没问题后再切回SoC原生的MDIO控制器。GPIO模拟MDIO的核心函数大概这样static void mdio_send_bit(struct gpio_desc *mdc, struct gpio_desc *mdio, int bit) { gpiod_set_value(mdc, 0); gpiod_set_value(mdio, bit); gpiod_set_value(mdc, 1); } static void mdio_send_op(struct gpio_desc *mdc, struct gpio_desc *mdio, int op) { int i; // 32个1的preamble for (i 0; i 32; i) mdio_send_bit(mdc, mdio, 1); // ST01 mdio_send_bit(mdc, mdio, 0); mdio_send_bit(mdc, mdio, 1); // OP mdio_send_bit(mdc, mdio, (op 1) 1); mdio_send_bit(mdc, mdio, op 1); // PHYAD和REGAD ... }写一次发送函数调试时设个断点或者干脆在关键位置打printk每读一个寄存器打一条log很快就能定位是芯片没响应还是时序不对。3.3 设备树配置的完整写法设备树配置在RTL8306MB移植里属于“看起来简单写错就全盘翻车”的环节。核心是把MDIO总线和交换芯片的节点关系写对。我的做法是先注册MDIO总线再把RTL8306MB作为该总线下的PHY设备子节点挂上去mdio0 { status okay; pinctrl-names default; pinctrl-0 mdio_pins; switch0 { compatible realtek,rtl8306mb; reg 0; /* MDIO PHY地址 */ reset-gpios gpio3 15 GPIO_ACTIVE_LOW; status okay; }; };几个容易出错的地方reg值必须和硬件上RTL8306MB所处的MDIO PHY地址一致否则内核扫描不到reset-gpios触发极性要看原理图GPIO输出高电平时芯片是复位还是工作一定要对照芯片手册如果SoC的MDIO控制器有自带的PHY扫描逻辑需要检查mdio节点里是否要配置phy_mask来屏蔽不存在的PHY地址避免启动log刷一堆“PHY not found”设备树写完之后/sys/class/mdio_bus/下应该能看到对应的总线加载驱动后/sys/class/mdio_bus/mdio0/目录下会出现寄存器可读的调试接口。这可以作为一个判断驱动是否注册成功的基本标准。3.4 用户态SDK层与内核驱动的对接SDK跑在用户态但它必须通过某种途径访问MDIO寄存器。两种常见方式通过内核提供的/sys/class/mdio_bus读写接口每次操作一个寄存器一条命令一读一写缺点是慢通过自己写一个字符设备驱动开放ioctl接口把用户态传下来的PHY地址、寄存器地址、数据直接转发给MDIO总线读写我用了字符设备的方案因为SDK初始化要读写几百个寄存器走sysfs太慢了。字符设备接口也不复杂核心是定义一个结构体把读写请求从用户态传进内核struct rtl8306_ioctl_args { int phyaddr; int regaddr; u16 data; int is_write; }; static long rtl8306_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct rtl8306_ioctl_args args; if (copy_from_user(args, (void __user *)arg, sizeof(args))) return -EFAULT; if (args.is_write) mdiobus_write(bus, args.phyaddr, args.regaddr, args.data); else args.data mdiobus_read(bus, args.phyaddr, args.regaddr); if (copy_to_user((void __user *)arg, args, sizeof(args))) return -EFAULT; return 0; }用户态SDK的注册函数里把底层的rtl8306_reg_read和rtl8306_reg_write替换成打开这个字符设备、填充结构体、调用ioctlSDK就算移植完成一半了。4. 实操VLAN与端口管理功能的落地4.1 两步跑通基础通信移植完成的第一步不是急着配VLAN而是先把纯二层通信跑通。具体操作分两步第一步初始化芯片。调用SDK的rtl8306_switch_init()这个函数会让芯片回到一个默认状态。如果初始化之后5个PHY端口和Host端口能互相转发报文那就说明硬件、MDIO、SDK三者已经全部打通。此时从任意一个LAN口接入终端应该能从Host端口所在的SoC网络接口上ping通。第二步确认SoC网络接口的MAC驱动正常工作。这里有个容易误解的点SoC的MAC和RTL8306MB的Host端口之间是MII/RMII直连SoC网络接口比如eth0是作为一个纯MAC存在的它的PHY其实是RTL8306MB的Host端口。所以内核网络驱动里需要想办法把RTL8306MB的Host端口伪装成eth0的PHY或者干脆把网卡固定成“无PHY”模式强制link up。我这次用的方法是在SoC网络驱动的平台数据里把PHY地址指向RTL8306MB的Host端口地址这样内核通过phy_connect()就能正确读到Host端口的状态。如果不做这一步即使芯片转发正常SoC侧的网络接口也会一直显示“Link is Down”。4.2 VLAN划分配置实操VLAN配置是RTL8306MB最常见的需求。默认情况下所有端口都属于同一个VLAN通常是VLAN 1所有端口之间可以任意互通。产品上通常需要把不同LAN口隔离或者把管理口和数据口分开。SDK里配置VLAN的核心API是rtl8306_vlan_makePortVLAN()参数包括VLAN ID、端口成员、是否打tag等。完整的流程是先调用rtl8306_vlan_init()清除默认VLAN表再逐个VLAN配置端口成员。举个例子5个LAN口中1~3口属于VLAN 104~5口属于VLAN 20Host端口要同时属于这两个VLAN并且作为上行端口对帧打上tagrtl8306_vlan_init(); vlan_group_t vlan10; memset(vlan10, 0, sizeof(vlan10)); vlan10.vlanID 10; RTL8306_SET_BIT(vlan10.portMembers, 0); RTL8306_SET_BIT(vlan10.portMembers, 1); RTL8306_SET_BIT(vlan10.portMembers, 2); RTL8306_SET_BIT(vlan10.portMembers, 5); /* Host端口 */ /* untag端口LAN口不带tagHost端口打tag */ vlan10.untagPorts (1 0) | (1 1) | (1 2); rtl8306_vlan_makePortVLAN(vlan10, vlan10.vlanID, vlan10.portMembers, vlan10.untagPorts);这里要特别强调一点Host端口在所有VLAN里都必须存在因为SoC要通过它管理所有VLAN的流量。只发给LAN口的VLAN配置里漏掉Host端口管理流量就断了产品一上线就出事故。4.3 端口镜像与限速端口镜像在调试和生产排障里特别有用。RTL8306MB支持把某个端口的收发报文镜像到另一个端口这样就能在不中断业务的情况下用抓包工具直接看某条链路上的流量。SDK里配置镜像的API一般是/* 镜像源端口、方向、目的端口 */ rtl8306_mirror_set(sourcePort, MIRROR_RX | MIRROR_TX, monitorPort);配置完之后把PC接到监控口上用Wireshark就能看到镜像过来的报文。排查“为什么两个LAN口之间不通”这类问题时这个方法比任何log都直观。限速功能也简单SDK提供端口速率限制接口可以分别配置入方向和出方向的速率上限单位一般是Mbps或Kbps。注意百兆端口的限速配置和实际吞吐之间有个固定开销如果配置成100Mbps但实际最多跑到94Mbps左右这是正常的不要以为芯片限速出问题了。5. 问题排查与避坑经验实录5.1 高频问题速查表把这次调试中遇到的高频问题整理成一张表方便后来人直接对照现象可能原因排查方法MDIO读回全0xFFFFMDC时序太快、接线错误、芯片未复位先用GPIO模拟MDIO降到1MHz以下读芯片ID设备树加载正常但网络不通Host端口未正确连接、PHY地址绑定错误检查eth0的PHY地址配置看dmesg里的link状态局域网互Ping时通时不通MII/RMII走线不等长、时钟质量差逻辑分析仪看RX_DV和RXD时序检查REF_CLK波形VLAN配置后管理口失联Host端口漏加VLAN成员重新初始化VLAN表确保Host端口在全部VLAN中芯片初始化耗时极长MDC频率太低导致寄存器读写慢确认MDC配置芯片手册允许范围内调高频率LAN口link能起来但速率固定100M自协商失败检查PHY寄存器自协商状态确认对端设备5.2 MDC时序问题的实战排查整个项目里最让我记忆深刻的就是MDC时序问题。SDK初始化几百个寄存器如果MDC频率超过芯片支持的极限这类芯片的MDC上限一般就是2.5MHz左右不是完全读不出来而是偶发读错寄存器值随机变成0xFFFF或者0x0000导致芯片状态完全错乱而且问题表现极不规律今天能跑明天跑不了。排查过程不复杂但很磨人先拿逻辑分析仪抓MDIO波形看MDC频率对比芯片手册的spec果然超了。把SoC MDIO控制器的时钟分频调到2.5MHz以下之后问题立刻消失。还有一个和MDC相关的坑SoC的MDIO控制器如果支持PHY自动扫描设备树里没屏蔽RTL8306MB的PHY地址内核会在启动时对这个地址反复发起读操作。这个读操作本身没问题但如果RTL8306MB正好处于复位状态MDIO可能一直被拉低拖累整个总线的扫描流程导致启动阶段卡几十秒。解决方式是上电后尽快释放复位GPIO并且在设备树里通过phy_mask把不需要扫描的地址排除掉。5.3 关于SDK版本和内核版本兼容的体会最后聊点代码之外的体会。Realtek的SDK更新不算勤快拿到手的老版本SDK在编译时经常和新的交叉编译工具链有冲突最常见的两个问题一是老代码用了一些GCC在新版本里已经废弃的语法编译报warning直接升error的需要在Makefile里加-Wno-error...降级处理。二是SDK里自带的printf等标准库依赖和嵌入式交叉工具链的版本对不上链接报undefined reference这类问题通常需要看看SDK里是不是有个config文件需要关掉某些feature。我的做法是给SDK单独建一个编译目录不和内核源码树混在一起产出一个静态库再让最终的应用程序链接这个库。这样SDK的编译问题只影响SDK自己不会污染整个内核工程。调试工具方面强烈建议在开发阶段做两件事一是在SDK的MDIO底层读写函数里加上debug开关每次读写都打印PHY地址、寄存器地址和数据这样能直接看到SDK在实际操作哪些寄存器二是提前做一个脚本能通过字符设备接口批量读写寄存器比如一键dump芯片ID、PHY状态这在定位问题时会给你省下无数时间。说实话RTL8306MB这套东西难的不是某一个环节而是整个链路从头到尾每个环节都可能出问题而且往往是多个环节叠加。硬件上MDIO时序和PHY地址规划出问题设备树写错导致驱动加载不到SDK移植时底层没接对这几个坑叠加起来足够让人怀疑人生。但只要先把MDIO读写这条路验证通再把设备树、PHY绑定、VLAN配置一步步做扎实这颗芯片最终还是非常听话的。我个人的体会是碰到交换芯片问题永远先从物理层查起MDIO能稳定读到数据后面的一切都好说。
返回列表