简介:面向嵌入式网络开发者的 REALTEK8367 交换芯片驱动源码包,系统梳理了 Realtek API 与底层驱动实现,适合需要做交换芯片驱动移植、调试或性能优化的工程师。压缩包共 114 个文件,由 59 个 .h 头文件与 55 个 .c 源文件组成,整体仅 328KB,文件组织紧凑,便于快速查阅;包内文件结构清晰,头文件主要提供寄存器映射与接口声明,C 源文件则实现各功能模块的具体逻辑。源码覆盖 L2 转发、VLAN、ACL、QoS、IGMP 等关键功能,并单列 rtl8367_led 的 LED 控制实现;开发者可以据此理解芯片初始化、端口配置、报文处理和状态指示的完整链路。通过 API 可完成 VLAN 划分、QoS 策略、端口镜像等配置,同时支持 IGMP Snooping,便于构建稳定高效的数据交换。已有 1039 人学习下载,既可作为入门 Realtek 交换芯片驱动的学习材料,也能在二开和故障排查中作为直接参考,对设备维护与驱动优化都有实用价值。
1. 拿到 Realtek_API_Source.rar 之后的第一个动作:先别急着解压编译
看到这个压缩包名字,基本能确认两件事:一是你手上这颗芯片是 RTL8367 系列,二是这份代码不是某个团队自研的协议栈,而是 Realtek 官方放出来的 SDK 源码。这类包在网管型交换机开发里很常见,通常包含寄存器定义、HAL 层、API 层和一个 demo 主程序,打包方式大多是 rar 或 tar.gz。很多人在 Linux 上解压后直接 make,结果要么缺少头文件,要么编译通过但烧上去端口全不通。原因多半不在代码本身,而是子型号、CPU 接口模式和交叉工具链没对号入座。下面按“解压配置—底层初始化—VLAN 配置—寄存器调试”这条实际开发路径展开,适合正在用 8367 做接入设备、想在 OS 之外直接控制交换芯片的 BSP 工程师,也适合那些已经能编译出固件、但遇到链路不通时不知道从哪里下手的嵌入式开发者。
2. RTL8367 API 源码包的目录定位:从解压到编译前的 3 项必做配置
2.1 解压 rar 后的文件属性修复:这不是玄学
拿到.rar包,如果直接在 Windows 下用 WinRAR 解压再拷贝到 Linux,常见问题是:脚本失去了可执行权限、软链接变成了普通文本文件、行尾是 CRLF。SDK 里的 build 脚本和 makefile 对行尾敏感,尤其是config目录下自动生成的配置文件,一旦带\r,make 会把整个路径截断。我一般会在目标机器上重新解压,或者解压后先跑一遍:
unrar x Realtek_API_Source.rar find ./ -type f -name "*.sh" -exec chmod +x {} \; find ./ -type l -delete sed -i 's/\r$//' config/*.mki这段命令的逻辑:先用 unrar 解出源码,再把所有脚本加可执行位;删除可能变成普通文件的软链接,因为很多 SDK 用软链接指向公共头文件;最后把config目录下的 make 配置文件统一还原成 Unix 行尾。如果你只用unrar x而不做后面两步,编译时很容易出现$'\r': command not found或者 include 失败。参数说明:find -type l -delete是直接删除失效链接,比逐个 ln -s 重新创建更快,代价是之后如果 make 脚本硬编码了软链接路径,需要手动补回来。
提示:rar 包解压后如果顶层目录直接散落所有文件,先
mkdir sdk_8367 && unrar x Realtek_API_Source.rar -d sdk_8367/,避免make distclean误删当前目录下其他工程文件。
2.2 确定芯片子型号:RTL8367 / RTL8367S / RTL8367RB 的区别
RTL8367 是一个系列,不同后缀的 PHY 数量、电容校正参数、内部寄存器的保留位都不完全一致。SDK 里通常会有一个rtk_chip_id_get或编译宏来区分。比如rtk_switch_init前会读取芯片 ID 寄存器(0x0000 和 0x0002),如果不匹配,API 会直接返回FAILURE。编译前需要查看src/app/下的 makefile 或config.h:
#define RTK_CHIP_ID RTL8367B #define CONFIG_RTK_8367B 1这里的RTL8367B是 SDK 内部的枚举,不是丝印上的型号全部。通常丝印 RTL8367RB 对应枚举RTL8367B,RTL8367N 对应RTL8367C,选错会导致 PHY 无法 link。我一般会先拿逻辑分析仪抓两线 SMI 的读操作,确认 0x0000 寄存器返回的芯片 ID 是多少,再回头改宏。如果芯片 ID 是 0x8367 而宏里配的是 0x8367B,虽然能初始化,但 PHY 端口的 offset 会错位,四个电口可能映射到不同的物理 PHY 地址上。
2.3 工具链与 CPU 侧接口:决定你用网口还是 SMI 口
8367 的上行接口支持 RGMII 和 MII,SDK 里对外的寄存器读写方式有两条路径:一是使用内核 mdio 总线,通过mdio_mux或自定义 platform 驱动;二是直接映射 SMI(Serial Management Interface)到 GPIO。我一般先用内核自带 mdio 验证硬件通路:
mii-tool -v eth0 mdio-tool eth0 read 0x1 0x0如果这个能读出 PHY 芯片的 ID,说明 CPU 和交换芯片之间已经通了。如果读取超时,就要检查复位引脚和时钟。SDK 的hal/hal_init.c里通常会有一份hal_smi_read/write的注意点,建议对照自己的板子修改 SMI 的 GPIO 端口号。这个阶段不要急着移植全套 API,先把一只寄存器读通比什么都重要——很多网口驱动写不出来就是因为 SMI 的 GPIO 方向或时序不对,导致读回的永远是 0xFFFF。
2.4 目录功能对照表及最小编译目标
| 目录 | 内容 | 移植时需要改的地方 |
|---|---|---|
src/rtk | API 层,如 vlan, port, mirror | 一般不动 |
src/hal | 硬件访问层,SMI/MDIO/PHY | 必须改寄存器读写函数 |
include | 公共头文件 | 芯片型号宏 |
src/app | demo 主程序 | 你的用户逻辑 |
config | 自动生成的配置 | 编译器路径 |
常见操作是在src/app里直接make,如果 SDK 自带自动配置脚本,先跑./configure --chip=8367 --platform=embedded。注意 configure 只是生成 config.h,不会验证你的交叉编译器是否在 PATH 里。需要先执行export CROSS_COMPILE=arm-linux-gnueabi-,否则编译头文件时会因为找不到 stdint.h 报错。最小编译目标通常是make all生成一个switch_cli或rtk_demo.bin,前者是串口命令行工具,强烈建议先编译它,因为调试时能直接输入命令查看端口状态,比反复烧固件有效率得多。
3. 用 RTL8367 API 跑通最小交换机初始化:从低层 SMI 到第一个转发
3.1 硬件复位与时钟准备好之前,不要调用 rtk_switch_init
很多移植失败并不是 API 用错,而是给交换芯片上电后就直接 init。RTL8367 的复位引脚需要持续至少 10ms,且芯片自身的 25MHz 晶振稳定后还要等 PLL 锁定。常见做法是在板级驱动里先完成 GPIO 拉低、延迟、拉高,再调用 SDK 的初始化函数。如果忽略这一步,rtk_switch_init可能返回 OK,但后续 PHY 状态全部是 down。我一般会加一个对 0x0000 寄存器的轮询,确认芯片 ID 正确后再继续。
可以使用 SDK 自带的调试函数hal_switch_read_register(0x0000, &chid)。如果读回0x8367或0x8367B,说明 SMI 通道正常。如果返回 0xffff,多半是 SMI 的 GPIO 方向错了,或者芯片还在复位状态。注意:SMI 与 MDIO 不是同一套信号,前者是两线私有协议,后者是标准以太网管理接口,寄存器访问函数不能混用。SMI 的时钟线是 SCK,数据线是 SDA,典型拉高时序是空闲时 SCK 与 SDA 同时为高,读操作靠起始位触发,不能用内核 mdio 总线直接访问。
3.2 最小初始化代码:把所有端口默认成一个广播域
直接看一段基于 SDK 常见调用序列的示意代码,函数名取自实际 API,但省去了具体 SDK 版本的差异:
#include "rtk_types.h" #include "rtk_api.h" #include "rtk_api_ext.h" #include "hal/hal_init.h" #include "rtk_switch.h" #include "rtk_port.h" #include "rtk_vlan.h" #include "rtk_phy.h" static void rtl8367_mini_init(void) { rtk_uint32 chip_id = 0; rtk_enum_port_t port; // 1. 底层 SMI 初始化:绑定读写函数和访问锁 hal_init(RTL8367B); // 2. 读取芯片 ID,验证 SMI 通路 rtk_switch_chipId_get(&chip_id); if ((chip_id & 0xFFFF) != 0x8367) { printf("chip id mismatch: 0x%04x\n", chip_id); return; } // 3. 核心初始化:按默认配置加载内部寄存器,默认所有端口丢弃帧 rtk_switch_init(); // 4. 使能全部 PHY:允许自动协商,不关注速率结果 for (port = RTK_PORT_ID_P0; port <= RTK_PORT_ID_P4; port++) rtk_phy_enable(port, ENABLED); // 5. 设置端口状态:接收和发送都允许,双向转发 for (port = RTK_PORT_ID_P0; port <= RTK_PORT_ID_P4; port++) { rtk_port_phy_ability_set(port, NULL); } // 6. 清空原 VLAN 配置:默认 VLAN 0 包含所有端口,重新规划前先 clear rtk_vlan_init(); rtk_vlan_clear(); }代码逻辑说明:hal_init只做底层函数注册,不碰任何寄存器;rtk_switch_chipId_get是后续所有 API 能正常工作的前提。rtk_switch_init会把芯片内部 RAM 里的默认配置全部加载一遍,包括 MAC 地址表、VLAN 表、端口状态。第 4 步使能 PHY 后,第 5 步再用NULL表示所有能力都交给 PHY 默认值。第 6 步的rtk_vlan_init会创建一个默认 VLAN 0 并包含 4 个端口,此时如果不 clear,后面自己建 VLAN 容易和默认配置冲突。
参数说明:RTK_PORT_ID_P0到P4是 8367 的物理端口编号,CPU 上行口一般可以作为第五个普通口使用,取决于硬件接法。rtk_switch_init参数为空,但需要在它之前保证芯片 ID 已验证,否则内部会直接 returnRT_ERR_CHIP_NOT_FOUND。rtk_phy_enable的第二个参数ENABLED通常定义为 1,对应 PHY 控制寄存器中的 power down 位。
3.3 验证链路状态:不要只看 PHY 灯
初始化完成后,PHY 灯变绿不一定代表链路已经加入转发域。用 API 主动查询每个端口的状态,比看灯更可靠:
# 交叉编译完成的命令行工具,进入交换子命令 switch_cli # 进入 cli 后执行 port status 0-4如果输出link up speed 1000 duplex full,说明端口物理层已经 OK。如果只有link up没有 speed,说明 PHY 能力读取失败。此时可以用rtk_switch_portLinkStatus_get(port, &status)获取原始状态,甚至直接读取寄存器 0x2010 来核对速率字段。下面这张表总结了初始化阶段最常见的几个现象:
| 现象 | 可能的寄存器原因 | 排查方向 |
|---|---|---|
link down | PHY 未上电或能力未设置 | 确认rtk_phy_enable已调用 |
link up但无速率 | 寄存器 0x2010 读回与能力不匹配 | 检查 PHY 地址是否偏移 |
| 芯片 ID 错误 | SMI 引脚接错或时序不对 | 用示波器抓 SCK/SDA 波形 |
| 能 link 但 ping 不通 | CPU 口未加入转发域 | 查 VLAN 成员,见下一章 |
若链路正常但 ping 不通,重点查 CPU 收发端口是否已加入 VLAN 0。很多人的失误在于只关注物理端口,而忽略 CPU 的上行口。关于 CPU 口和 VLAN 的关系,这是下一章要解决的核心问题。
4. 在 RTL8367 上配置 802.1Q VLAN:API 调用顺序与端口隔离排错
4.1 从 API 源码理解 VLAN 数据结构:mbr 和 untag 是两个维度
RTL8367 的 VLAN 表是典型的 CAM 结构,API 层面的rtk_vlan_entry_t可以理解为这样:
typedef struct { rtk_vlan_t vid; /* 0-4095 */ rtk_portmask_t mbr; /* 成员端口掩码 */ rtk_portmask_t untag; /* 不打标签的端口掩码 */ rtk_uint32 fid; /* 过滤数据库标识 */ } rtk_vlan_entry_t;很多初学者以为设置了mbr就万事大吉,实际上untag位图决定的是「帧从这些端口出去时是否带 802.1Q 标签」。如果untag没有包括当前端口,那么对端设备就会发现所有报文都带 VLAN Tag。另外,FID 如果不一致,会导致同一个 MAC 地址在不同 VLAN 中学习时被踢来踢去,出现间歇性丢包。更隐蔽的是,rtk_vlan_init之后硬件 VLAN 表可能已经有若干默认条目,如果不先clear,新写入的条目会覆盖掉旧条目,但覆盖顺序取决于芯片内部哈希,不一定按 VID 顺序生效。
4.2 一个可复现的配置流程:让两个接入口互通,CPU 口打 Tag 上行
下面这段代码演示如何建一个 VLAN 10,包含物理端口 P0、P1,以及 CPU 口 P4。P0 和 P1 作为 access 口收发不带 Tag,P4 作为 trunk 口带上 Tag:
static void vlan_setup_example(void) { rtk_vlan_entry_t vlan; rtk_portmask_t allports; rtk_vlan_cfg_t igr_filter; rtk_portmask_init(&allports); rtk_portmask_add(&allports, RTK_PORT_ID_P0); rtk_portmask_add(&allports, RTK_PORT_ID_P1); rtk_portmask_add(&allports, RTK_PORT_ID_P4); rtk_vlan_init(); rtk_vlan_clear(); memset(&vlan, 0, sizeof(vlan)); vlan.vid = 10; vlan.mbr = allports; // 只有 P0/P1 出去时不带标签,P4 始终保持 Tag rtk_portmask_init(&vlan.untag); rtk_portmask_add(&vlan.untag, RTK_PORT_ID_P0); rtk_portmask_add(&vlan.untag, RTK_PORT_ID_P1); rtk_vlan_entry_set(&vlan); // 设置 P0/P1 的 PVID 为 10,并允许入口帧的 VLAN 检查 rtk_vlan_portPvid_set(RTK_PORT_ID_P0, 10); rtk_vlan_portPvid_set(RTK_PORT_ID_P1, 10); memset(&igr_filter, 0, sizeof(igr_filter)); igr_filter.igr_port = RTK_PORT_ID_P0; igr_filter.igr_vid = 10; igr_filter.igr_type = ALLOWED; rtk_vlan_portIgrFilter_set(&igr_filter); igr_filter.igr_port = RTK_PORT_ID_P1; rtk_vlan_portIgrFilter_set(&igr_filter); }代码逻辑说明:rtk_vlan_entry_set把 VLAN 10 的成员表写入硬件,untag位图中包含的端口出去不打标签,所以 P0、P1 对端设备收到的是普通以太网帧,P4 发出的帧带 VLAN Tag。rtk_vlan_portPvid_set决定每个端口收到 untagged 帧时默认归入哪个 VID。rtk_vlan_portIgrFilter_set这一步是可选的,但建议加上,否则入口帧可能绕过 VLAN 检查直接按 PVID 转发。第 6 步的rtk_vlan_clear必须在entry_set之前,否则旧 VLAN 0 的成员会和 VLAN 10 的交集冲突,导致某些帧同时匹配两条表项。
参数说明:rtk_portmask_t是一个位图,rtk_portmask_add(portmask, port)用于添加端口。untag位图为空时,所有成员端口出的帧都带 Tag。rtk_vlan_cfg_t中的igr_type通常有两个值:ALLOWED和DROPPED,设置为 DROPPED 时该端口会丢弃收到的该 VLAN 帧,常用于端口隔离。这里没有把 P4 加进untag,CPU 口上行时必然带 Tag,这样才能让上层路由协议区分不同业务 VLAN。
4.3 常见坑:默认 VLAN 0 与 CPU 口漏配
最常遇到的现象是:P0、P1 互相能通,但 P0 ping 不通上层路由器。原因是 CPU 口 P4 虽然加入了 VLAN 10,但上层驱动收包时可能把 Tag 剥掉,也可能不剥。如果交换机与 CPU 内部连接采用 RGMII,需要确认 CPU 口的 Tag 模式:如果寄存器配置成 access 模式,所有帧都会剥掉 VLAN 头,那你上层看到的 VLAN ID 全是 0,自然匹配不上路由表。另一个坑是rtk_vlan_clear()会连默认 VLAN 0 一起删掉,此时如果某些端口还在 VLAN 0 的成员表里,而这些端口又没被重新添加进新 VLAN,它们就完全被隔离了。
| 现象 | 原因 | 检查点 |
|---|---|---|
| 两个端口 ping 不通 | mbr没包含 CPU 口 | rtk_vlan_entry_get(vid)打印成员 |
| 收到帧带 802.1Q 标签 | untag位图漏了端口 | 确认 untag 包含所有接入设备 |
| 管理口不通 | CPU 口被配成 access 模式 | 检查端口 egress tag 模式字段 |
| 所有广播包不通 | 风暴抑制默认开启 | 检查rtk_rate_storm_control_get |
最后一个值得注意的地方是:rtk_vlan_portState_set可以控制端口在 VLAN 内的隔离状态,比如同一个 VLAN 里,P0 和 P1 不允许互访,只允许访问上行口。这时要把 P0 和 P1 的端口状态设置成PORT_DISABLE,只保留上行口PORT_ENABLE。如果只是简单地把某个端口从mbr中移除,它连加入 VLAN 的资格都没有,而端口隔离是指在转发阶段丢弃同 VLAN 内两个端口之间的帧,两者语义完全不同。
4.4 排错套路:先查成员表,再查 egress tag,最后查 PVID
我调试时的固定路径是:首先用rtk_vlan_entry_set之后立刻rtk_vlan_entry_get读回来比对,确认硬件表里的mbr和untag与预期一致。如果一致但 ping 不通,再看端口 PVID 是否等于 VID。如果 PVID 是 0,交换机收到 untagged 帧后会归属到 VLAN 0,而 VLAN 0 已经被clear,所以丢帧。如果 PVID 也正确,最后检查 egress tag 模式:有些 API 把untag位图硬件化时,如果某个端口既在mbr又不在untag,会打上标签;但如果上层 CPU 驱动不解析 Tag,管理帧就全被当成未知协议丢弃。此时可以在抓包软件里看收到的帧是否带 VLAN ID 10,如果带,那就是上层软件的问题,不是交换芯片的问题。
5. 进阶技巧:用寄存器 dump 反向验证 RTL8367 的 VLAN 表配置
API 配置完成后,软件层很难确认硬件表是否真的写对。RTL8367 的 VLAN 表是 CAM 结构,通常通过寄存器窗口间接访问。常见做法是使用 SDK 自带的hal_switch_read_register,或者直接用 mdio-tool 读寄存器。8367 的 VLAN 查询有两种方法:一种是通过 API 层rtk_vlan_entry_get读回来,这种能看到软件透视的配置;另一种是直接操作寄存器窗口,适合怀疑 API 自身有 bug 的时候。
static void dump_vlan_hw_entry(int vid) { rtk_uint32 addr = (rtk_uint32)vid; rtk_uint32 reg_val; // 将 VLAN id 写入索引寄存器,然后读取数据寄存器窗口 hal_switch_write_register(0x0E00, addr); hal_switch_read_register(0x0E01, ®_val); printf("VLAN %d reg: 0x%08x\n", vid, reg_val); }这段代码不是某个官方标准接口,而是描述一种利用寄存器窗口的常见方式。不同 SDK 版本中索引寄存器和数据寄存器地址可能不同,直接套用前要查阅芯片手册中 VLAN CAM 部分。更稳妥的方法是在rtk_vlan_entry_get返回值的基础上,再写一个读出整个表的循环,把每个 VLAN 条目都打出来。如果发现读回值和写入值不一致,优先怀疑mbr位图在 API 内部发生了端口号偏移,例如 P4 的位被映射到了 P0。
调试寄存器时,强烈建议利用 SDK 提供的 debug 宏。在rtk_types.h中通常有RTK_DBG或rtk_dbg打印开关,makefile 里加CFLAGS += -DDBG_LEVEL=1,就能看到每个 API 的入参和操作结果。对于rtk_vlan_entry_set这类操作,如果返回RT_ERR_VLAN_ENTRY_NOT_EXIST,说明 VID 超出硬件支持范围,而不是配置失败。如果返回RT_ERR_INPUT,则多半是端口掩码越界,比如用了RTK_PORT_ID_TOTAL。
最后一个实用技巧:把源码包里的 example 目录单独编译成一个命令行工具,挂到串口或者内网 telnet 接口上。这样即使不接网线,也可以通过串口实时查看端口状态、VLAN 表和 MAC 表。命令行工具的调试效率远高于反复烧写固件,尤其适合在现场拿到设备但找不到网管页面时快速定位是交换芯片配置问题还是上层软件问题。把常用调试命令封装成脚本,配合寄存器 dump,基本能解决 80% 的 8367 交换芯片移植故障。
本文还有配套的精品资源,点击获取