ARTICLE DETAIL

资讯详情

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

RTL8367 API源码移植:从SMI总线到VLAN配置实战

RTL8367 API源码移植:从SMI总线到VLAN配置实战 简介面向网络设备驱动开发与嵌入式工程师Realtek RTL8367千兆交换芯片驱动源代码包提供完整的驱动源码源自官方API覆盖芯片初始化、寄存器配置、数据包处理及上层接口对接等模块帮助理解芯片级交换功能实现。开发者可据此掌握端口配置、VLAN划分、QoS策略以及rtl8367_led的LED状态控制方法还能学习到驱动与硬件寄存器交互时的时序与数据结构设计。压缩包共114个文件包含59个.h头文件与55个.c源文件约328KB按功能模块划分包含asicdrv、l2、svlan、acl、vlan、qos等子模块便于按需查阅。源码特别适合驱动移植、功能定制与故障排查也可作为学习Realtek交换芯片API及网络驱动的参考。目前已有1050人学习下载对家用路由器、企业交换机或数据中心网络设备的开发者均有实用价值。1. Realtek 8367 的 API_Source 是什么把哑交换机变成可管交换机的源头拿到一块基于 RTL8367 或 RTL8367RB 的主板上电默认它就是台纯二层哑交换机所有端口互通、没有 VLAN、没有限速、也没有端口镜像。Realtek_API_Source.rar 这类源码包是把 datasheet 里零散的寄存器操作封装成 rtk_vlan_set、rtk_port_phyEnableAll 这类可调用 API 的源头。主控 CPU 通过 GPIO 模拟的 SMI 总线或 MDIO 访问芯片应用层就能动态切 VLAN、做风暴抑制、读 MIB 统计。这个包适合做主控是 ARM Linux、需要把 RTL8367 这颗 Realtek switch 真正管起来的嵌入式工程师也适合想搞懂硬件手册和应用代码之间那层胶水的人。下面按我从拆包到上线的顺序讲。2. 拆包与架构从 rar 压缩包识别出 8367 的驱动分层和调用链路这种带_API_Source_字样的压缩包里面装的不是可以直接跑的二进制而是一整套源码工程。第一步不是读代码是先搞清楚目录里谁是谁。很多老工程师拿到就开 IDE 全局搜索结果被rtk_和rtl8367b_两套前缀绕晕浪费一整天。先分层再动手。2.1 拆包后先分清四类文件$ file Realtek_API_Source.rar Realtek_API_Source.rar: RAR archive data, v5, ... $ 7z x Realtek_API_Source.rar -ortl8367 $ cd rtl8367 find . -maxdepth 2 -type d -print ./src ./src/rtl8367b ./include ./include/rtl8367b ./hal ./example ./doc在我经手过的多数 8367 API 包里目录长相不完全一致但分层逻辑几乎相同我习惯按四类去对应ASIC 驱动层src/rtl8367b/下的rtl8367b_asicdrv_*.c一个文件管一个功能块比如 vlan、port、mib、phy、qos、led。它负责把“业务语义”翻译成具体寄存器地址和位操作。业务 API 层带rtk_前缀的.c是你业务代码里要调用的接口比如rtk_vlan_set、rtk_port_phyEnableAll。这层做参数校验、语义转换再往下调 ASIC 驱动。平台适配层通常叫platform/、hal/或直接放在一个board_io.c里包含 SMI 读写回调、复位 GPIO、延时函数。这是你唯一必须按自己板子改的地方。示例和文档example/里的 demo 是现成的调用范本doc/里一般是 SMI 时序和寄存器总表。先跑find src/rtl8367b -name *.c | wc -l看看规模如果 ASIC 驱动文件超过 20 个说明这个包把 8367 各个功能块拆得很细编译时不要图省事只挑几个文件编否则链接时会报一堆 undefined reference。2.2 寄存器模型API 本质是包了一层寄存器读写为什么要有两层 API而不是直接用寄存器因为一个 VLAN 配置散落在很多寄存器里VLAN 表索引、成员端口掩码、untag 端口掩码、端口 PVID、端口收发规则。你手动去拼这些位一周之内必翻车。API 层把这件事收口了。看一个最简单的 PVID 配置调用链ret_t rtk_vlan_portPvid_set(rtk_port_t port, rtk_vlan_t pvid) { /* 1) 参数边界检查8367 是 52 口port 0..6VID 0..4095 */ if (port RTL8367B_MAX_PORT || pvid RTL8367B_MAX_VID) return RTK_FAILED; /* 2) 业务调用转成 ASIC 层寄存器翻译 */ return rtl8367b_setAsicPortPvid(port, pvid); }这段是示意代码但逻辑是真的。往里挖一层rtl8367b_setAsicPortPvid内部做的是读寄存器、改位、写回而像 VLAN 表写入这种操作更繁琐要先写表索引寄存器、再写数据寄存器、最后轮询一个 busy 状态位等硬件把表刷进去。ASIC 层把这些时序细节全消化了。所以有一个很重要的原则不要拿着 datasheet 上的寄存器地址到应用层自己写宏去访问。RTL8367 的 RB、S、M 这些版本寄存器映射有差异API 包里的 ASIC 驱动已经针对型号归一化了你直接读地址换一颗料就全碎。改代码前先把rtl8367b_asicdrv_vlan.c的通篇注释读一遍多数踩坑都是因为没读这块。2.3 SMI 总线和底层回填函数移植最先要改的代码RTL8367 对外提供两种主控访问方式一种是走芯片上管理口的 SMI 两线串行协议靠 GPIO 模拟另一种是走 EXT 口上的 MDIO。SMI 更常见因为不占用网口资源两个 GPIO 就够。包里一般把底层回调留成一个空壳或一个需要你改的实现通常长这样/* board_io.c —— 你板子上唯一必须自己写的适配文件 */ static void smi_delay(void) { udelay(2); } int32_t rtl8367b_smi_read(uint32_t reg, uint32_t *data) { /* 1. 把 SMD 拉低产生起始条件 */ /* 2. 在 SCK 上升沿逐位送出操作码和寄存器地址 */ /* 3. SMD 切为输入在 SCK 下降沿采样数据位 */ /* 4. 拉高 SMD 产生结束条件 */ return SMI_OK; }SMI 一次读事务由起始条件、操作码、地址、读数据、结束条件组成核心就是两线上下沿的配合。参数上有几个经验值参数常见取值说明SCK 半周期1~2 µs偏慢更稳别低于 500 nsSMD 空闲电平高两根线都建议加 4.7kΩ 上拉写后读回间隔 1 µs部分寄存器写入后需要几个时钟稳定写完 SMI 后第一件事是读芯片 ID 做自检。包里通常有一句类似chip_id 0x8367的判断读到 0xFFFFFFFF 基本是 SMD 方向切晚了或缺上拉读到错误型号则要检查地址位宽。这一步过了后面才谈得上配 VLAN。另外要控制 API 调用量一次 SMI 读事务几十微秒热点路径上滥用会把数据面拖慢MIB 轮询这种循环调用尤其要限制频率。3. 把 8367 API 编进嵌入式 Linux 工程最小 Makefile 与上电初始化顺序目录看懂了下一步是把源码变成libswitch.a静态库再和你的业务程序链接。这里有几个宏和编译选项决定成败别全按包里的示例工程来那是给评估板用的。3.1 三个必看的预定义宏与头文件路径在写 Makefile 之前先翻include/rtl8367b/底下的头文件确认三个东西宏或选项作用设错后果芯片型号宏选择 8367B/RB/S 对应的寄存器偏移表配置写错地址功能错乱位域打包用#pragma pack保证结构体按 1 字节对齐掩码错位端口号对不上大小端确认 PHY 和 MIB 结构体字节序与主控一致读到的数值被字节交换大多数包默认按小端和打包来但 ARM 主控的字节序取决于 SoC。我一般会写一个-DCHIP_RTL8367RB先编一遍然后跑一个读 ID 的自检程序确认宏没选错。别小看#pragma packSDK 里的端口掩码结构体如果被编译器对齐到 2 字节portmask.bits[0] 0x000F这类写法会把 port1 的位顶到错误位置VLAN 配置看起来成功实际全串台。3.2 一个能编出 libswitch.a 的最小 MakefileCROSS ? arm-linux-gnueabihf- CC : $(CROSS)gcc AR : $(CROSS)ar CFLAGS : -Os -Wall -fno-strict-aliasing \ -DCHIP_RTL8367RB \ -I./include -I./include/rtl8367b -I./src/rtl8367b OBJS : $(patsubst %.c,%.o,$(wildcard src/rtl8367b/*.c)) \ $(patsubst %.c,%.o,$(wildcard src/rtk/*.c)) \ board_io.o all: libswitch.a libswitch.a: $(OBJS) $(AR) rcs $ $(OBJS) %.o: %.c $(CC) $(CFLAGS) -c $ -o $ clean: rm -f $(OBJS) libswitch.a三个说明点。第一只编asicdrv加上rtk业务层加你的board_io.oexample/下的 demo 程序不要编进来那不是库的一部分。第二-fno-strict-aliasing建议保留SDK 里大量用联合体把寄存器值拆成位域开严格别名优化在 GCC 高版本上会出非常诡异的问题比如读回的寄存器值一直不变。第三编译告警只需-Wall不要上-Werror这套源码是厂商年代的代码风格Werror会把一堆无害告警变成障碍。链接阶段如果报undefined reference to rtl8367b_smi_read说明你的board_io.o没进库或者你在包里看到的回调函数名不叫这个。以你手里的头文件声明为准名字不一致时改你的函数名去对齐不要动库里的调用点。3.3 上电初始化顺序复位延迟和 phyEnableAll 缺一不可库编出来了初始化顺序错一样都可能引发“API 返回成功但功能全无”的假象。常见做法是这样void switch_board_init(void) { /* 1. 硬件复位拉低再拉高留够上电稳定时间 */ gpio_set(RESET_GPIO, 0); udelay(10); gpio_set(RESET_GPIO, 1); mdelay(20); /* 8367 内部 PHY 起来要时间别省 */ /* 2. GPIO 初始化 SMI 总线 */ rtl8367b_smi_init(); /* 3. 芯片级初始化内部会做寄存器基线配置 */ if (rtk_switch_init() ! RTK_SUCCESS) { printf(switch init failed\n); return; } /* 4. 打开全部内置 PHY默认可能处于低功耗态 */ rtk_port_phyEnableAll(); /* 5. 把 MTU 放宽到 1536给 VLAN 头留余量 */ rtk_switch_maxPktLen_set(RTK_ALL_PORT, 1536); }rtk_switch_init内部会做复位、遍历关键寄存器、建立基线状态这一步失败后面全白搭。:20ms不是玄学PHY 上电和内部校准有这个量级的稳定时间缩短到 5ms 在某些批次的芯片上会读出半初始化状态。rtk_port_phyEnableAll经常被忽略不做的话部分内置 PHY 处于低功耗模式表现为某个端口插线不亮灯。maxPktLen设 1536 是因为后面要带 VLAN tag默认 1522 也够但留点余量可以少为一个 jumbo 帧的问题加班。初始化跑完别急着配业务先做一次完整的寄存器写读回环比如往一个通用寄存器写0xAAAA读回来比对。这步过了再进 VLAN 配置阶段。4. 用 API 配出可验证的 VLAN成员掩码、Tag 规则和抓包验证VLAN 是最常见的起步配置但也是翻车率最高的功能。原因在于一堆端口掩码和 untag 掩码同时出现时人的直觉往往和硬件实际行为相反。4.1 一个两端隔离加 CPU 接入的标准配置假设板子是典型的 52 口用法port0~3 是四个 LAN 口内置 PHYport4 是 WAN 口内置 PHYport5/6 是 EXT 口其中一个接 CPU 的 GMAC。目标是一眼能看懂的拓扑端口角色所在的 VLAN出帧 Tagport0~3LANVLAN 1untaggedport4WANVLAN 2untaggedport5CPU 上行VLAN 1 VLAN 2tagged代码分三步写清楚/* 1. 造端口掩码的小工具 */ static rtk_portmask_t mk_mask(unsigned int bits) { rtk_portmask_t m {{0}}; m.bits[0] bits; return m; } /* 2. 清空 VLAN 表回到全空状态 */ rtk_vlan_init(); /* 3. 定义两个 VLAN各自包含 CPU 口 */ rtk_vlan_set(1, mk_mask(0x000F | 0x0020), /* 成员port0-3 port5 */ mk_mask(0x000F)); /* untag只有 port0-3 剥 tag */ rtk_vlan_set(2, mk_mask(0x0010 | 0x0020), /* 成员port4 port5 */ mk_mask(0x0010)); /* untag只有 port4 剥 tag */ /* 4. 给接入端口设 PVIDuntagged 帧进来后打哪个 VID */ rtk_vlan_portPvid_set(0, 1); rtk_vlan_portPvid_set(1, 1); rtk_vlan_portPvid_set(2, 1); rtk_vlan_portPvid_set(3, 1); rtk_vlan_portPvid_set(4, 2);rtk_vlan_set的前两个掩码参数是整篇代码的关键member_portmask决定哪些端口属于这个 VLANuntag_portmask决定这些端口发出帧时是否剥掉标签。CPU 口要想同时收两个 VLAN 的流量就必须在 untag 掩码之外让帧带着 tag 进 CPU这样主控才能根据 802.1Q 头区分业务。如果你把 CPU 口也放进 untag 掩码两个 VLAN 的帧进 CPU 后长得一模一样上层 vlan 子接口就废了。每配完一个rtk_vlan_set建议立刻用rtk_vlan_entry_get读回来逐位打印成员掩码和 untag 掩码。这是查掩码写错最快的方法比自己盯着十六进制数发懵强。4.2 用 tcpdump 和 ping 验证配置真的隔离了寄存器读回只证明“写进去了”不证明“交换行为正确”。真正确认要做数据面验证。在 Linux 主控侧先建两个 VLAN 子接口ip link add link eth0 name vlan.1 type vlan id 1 ip link add link eth0 name vlan.2 type vlan id 2 ip link set vlan.1 up ip link set vlan.2 up tcpdump -i vlan.1 -ne -c 5 然后从 port0 接一台电脑去 ping port4 那个网段的地址预期是 ping 不通因为两个 VLAN 数据面隔离。接着用支持 VLAN 的电脑或者再拉一台 Linux 设备设成带 tag 的 trunk 接入 port5从vlan.1口 ping LAN 侧主机看tcpdump能不能抓到带 VID 1 的帧。这里有个常被误解的点ping 不通不一定是配置错了也可能是因为你没有把 CPU 口加进对应 VLAN 的成员掩码。验证顺序应该是先读回寄存器确认掩码再看数据面通断最后抓包看 tag 内容。三步都过这个 VLAN 才算真配好了。5. RTL8367 API 移植避坑5 条真实翻车记录与排查方法这部分写的都是我实际碰过的问题按“现象 → 原因 → 解决”记录照着排查能省掉大半天的抓瞎时间。5.1 现象API 返回成功但配置不生效寄存器读回全 0原因GPIO SMI 时序不对写操作根本没被芯片接收而业务 API 层只校验参数、不校验写后回读错误被吞掉了。最常见是 SMD 方向切换慢半拍或者上拉电阻缺失导致空闲电平被拉低。解决加一个写后读回的自检函数往一个普通寄存器写0xAAAA、读回比对循环 100 次。失败时先量两根线的空闲电平再调smi_delay到更保守的值比如udelay(3)。不要一上来就怀疑芯片坏了8367 坏的概率远低于 SMI 时序错的概率。5.2 现象千兆口协商到百兆或者 link 反复翻转原因EXT 口对接 CPU GMAC 时RGMII 的 TX/RX delay 配置不对或者主控 dts 里配了rgmii-id、芯片侧也开了内部 delay两边的延迟叠在一起。多见于自己画的板子评估板基本不会出这个问题。解决用包里的 PHY 配置接口去设 EXT 口的延迟模式不要直接改 PHY 页寄存器。改完看主控侧ethtool eth0的自协商结果。如果主控 GMAC 已经开了 delay芯片侧必须关掉对应延时二选一。5.3 现象VLAN 配置时好时坏MIB 数据偶发 0xFFFFFFFF原因Linux 内核的 MDIO 驱动和你的用户态 API 在抢同一条总线。内核轮询 PHY link 状态时你的 SMI 事务被打断读到半个寄存器。32 位主控上读 64 位 MIB 计数器更是重灾区两次 32 位读之间值发生进位就撕裂了。解决给所有rtk_调用包一层互斥锁如果 MDIO 和 SMI 共用引脚Kernel 侧把 PHY 轮询关掉改成由你的监控程序上报 link 状态。MIB 读 64 位值时先读高 32 位、再读低 32 位、再读一次高 32 位两次高位一致才采用否则重读。5.4 现象断电重启后配置全丢回到所有口互通原因RTL8367 上电后EFUSE/EEPROM 只自动加载少数芯片级参数VLAN、QoS、端口速率这类业务配置全部靠软件写寄存器掉电即失。你没把配置重放逻辑做成开机自启。解决把初始化配置写进一个启动脚本或 systemd unit开机后按“reset → init → VLAN → QoS”顺序重放。别想着写 EEPROM 一劳永逸EEPROM 自动加载模型并不覆盖完整业务表而且擦写有寿命。把它当成“每次开机电灯”而不是“永久装修”。5.5 现象MIB 计数器和 tcpdump 对不上一个包都没错但数字差一倍原因MIB 计数器没有在初始化时清零累计了上次跑测的数据或者读的是错误的方向统计比如把 RX 错看成 TX。还有一种是 32 位计数器已经溢出回绕。解决初始化后显示调用一次 MIB 清零接口确认包内语义。然后做基准校准打 100 个已知单播包读回 RX/TX 各 100。如果差固定偏移基本是没清零如果差值随机查 64 位撕裂问题。6. 进阶技巧把 MIB 统计和 link 状态读进应用层做端口监控6.1 用一条轮询封装把交换芯片变成可观测设备void monitor_ports(void) { int p; rtk_port_link_status_t ls; rtk_mib_cntr_t c; for (p 0; p 5; p) { /* 只监控内置 PHY 口 */ rtk_port_link_status_get(p, ls); rtk_mib_get(p, RTK_MIB_RX_OCTETS, c); printf(port%d %s rx_octets%llu\n, p, ls RTK_LINKUP ? UP : DOWN, (unsigned long long)c); } }这段代码可以挂到一个定时线程里1 秒轮询一次。rtk_mib_get的名字在不同包里有出入以你头文件里的声明为准参数语义一致。轮询频率不要超过 1Hz原因回到 2.3 说的 API 调用量问题MIB 底层是多次 SMI 事务高频轮询会吃掉 CPU 周期还可能和转发路径上的配置调用打架。链路状态link_status_get相对便宜但也没必要跑在 10Hz 以上二层交换机的 link 抖动本来就以秒级计。我做这类交换芯片移植有个习惯拿到 API 包先把 SMI 读写回环跑上几个小时的稳定性测试再碰 VLAN 和 QoS这一步看着慢实际省掉的是后面无数个“配置偶尔抽风”的排查夜。用这套流程你手里的 RTL8367 就能从哑交换机变成带监控、可隔离、能排障的可管设备。希望帮到你。本文还有配套的精品资源点击获取
返回列表