
简介该资源为基于 Linux DSA 框架的 YT9215 switch 驱动源码包面向嵌入式网络开发与交换机调试人员。驱动加载后会生成 lan* 网卡用于获取各网口状态实际数据通信则依赖 eth* 网卡并支持通过 VLAN 划分实现每个接口独立管理具体划分方式可参考包内 patch 说明。压缩包为 rar 格式共 3 个文件约 12KB包含 1 个 patch 补丁、1 个头文件与 1 个 C 源文件分别用于设备树配置、寄存器定义与驱动主体实现结构精简便于快速移植与二次开发。目前已有 455 人学习下载适合需要理解 DSA 驱动框架、调试多网口交换机或进行 VLAN 隔离配置的开发者参考可帮助快速掌握网口状态获取与数据转发分离的设计思路。1. yt9215 switch 驱动从一颗车机主控到可维护的以太网交换驱动如果你手上有块 yt9215 车机主板或者正在给基于这颗 SoC 的设备做网络功能大概率会遇到同一个问题板载的交换芯片怎么在 Linux 下跑起来。yt9215 本身是车机领域常见的多媒体主控很多方案会把一颗以太网交换芯片挂在它的某个 MAC 接口上用来扩展出多路网口。所谓 yt9215 switch 驱动本质就是让内核认识这颗交换芯片、把每个物理口映射成独立的网络设备、并且能正确转发和配置 VLAN。它解决的不是「能不能上网」而是「多路网口能不能被系统当成独立接口管理」。适合谁看做车机、工控网关、多网口终端的嵌入式工程师尤其是拿到 SDK 却不知道怎么把 switch 接进内核网络栈的人。下面按「先搞清硬件连接 → 再写驱动骨架 → 再调参数 → 再避坑」的顺序讲透。2. 先搞清 yt9215 与交换芯片的硬件连接方式2.1 三种常见挂载方式与选型判断yt9215 这类主控通常带一到两个以太网 MAC 控制器交换芯片和它之间的连接方式决定了驱动怎么写。常见做法有三种第一种是 MAC 到 MAC 的 RGMII/RMII 直连交换芯片的一个口作为 CPU 口主控的 MAC 直接对接这种最省事驱动里只需要把 CPU 口当成普通 PHY 处理第二种是主控通过 SPI 或 I2C 去配置交换芯片的寄存器数据面仍然走 RGMII这种需要两套驱动配合控制面和数据面分离第三种是交换芯片自带 PCIe 接口主控通过 PCIe 枚举这种在车机上少见但工控板上有。判断你手上是哪种最直接的办法是看原理图找交换芯片的 RGMII/TXD 引脚有没有连到 yt9215 的 MAC 引脚如果有就是数据面直连再看交换芯片的 SCL/SDA 或 SPI_CLK 有没有连到主控的 I2C/SPI 控制器如果有说明配置走独立总线。我一般会先用万用表确认 CPU 口的 TX_CLK 是否有 25MHz 或 50MHz 输出没有时钟基本可以断定数据面没接对。选型上如果只是要扩展两三个百兆口RGMII 直连加内核自带的 DSA 框架最稳如果要配 VLAN 和端口镜像SPI 配置型更灵活但驱动工作量翻倍。别一上来就选最复杂的方案先确认交换芯片型号是否在内核 DSA 支持列表里不在的话就得自己写 switchdev 或纯 PHY 驱动。2.2 用设备树描述 CPU 口与 PHY 的对应关系不管哪种连接设备树都是第一步。以 RGMII 直连为例需要在 yt9215 的 MAC 节点下描述 CPU 口并声明交换芯片的 mdio 总线。下面是一个最小可用的设备树片段基于常见做法整理具体寄存器地址要按你手上的芯片手册改。/* yt9215 MAC0 对接交换芯片 CPU 口 */ mac0 { status okay; phy-mode rgmii-id; phy-handle sw_phy0; mdio { #address-cells 1; #size-cells 0; sw_phy0: ethernet-phy0 { reg 0; /* CPU 口对应的 PHY 地址查手册确认 */ }; }; }; /* 交换芯片配置总线假设走 I2C1 */ i2c1 { status okay; switch5f { compatible vendor,sw-chip; reg 0x5f; /* 交换芯片复位脚 */ reset-gpios gpio1 12 GPIO_ACTIVE_LOW; }; };逻辑说明phy-mode rgmii-id表示 MAC 内部做延迟这是 RGMII 直连最常用的模式如果时钟采样有问题可以改成rgmii或rgmii-txid。phy-handle指向 CPU 口对应的 PHY 节点交换芯片内部通常把 CPU 口映射成一个固定 PHY 地址这个地址必须查芯片手册写错会导致eth0起不来。reset-gpios很关键很多板子交换芯片复位没做好内核跑起来后读寄存器全是 0xFFFF就是复位时序不对。参数怎么改reg地址由硬件原理图上的片选或地址引脚决定I2C 地址一般是 7 位注意内核里写的是不含读写位的值。phy-mode如果写错现象是ifconfig能看到接口但 ping 不通或者大量 CRC 错误。改完设备树后用dmesg | grep -i eth看 MAC 是否 probe 成功再用mdio-tool读一下 PHY 的 ID 寄存器能读到非 0xFFFF 的值说明 MDIO 通了。3. 写一个能跑的最小 switch 驱动骨架3.1 注册 mdio 总线与 PHY 设备如果交换芯片不在内核 DSA 支持列表里最省事的做法是先把它当成一组 PHY 来驱动每个物理口注册一个phy_device数据面靠 CPU 口透传。下面是一个基于mdio_device的骨架展示怎么在 I2C 配置总线上注册 PHY。#include linux/module.h #include linux/i2c.h #include linux/phy.h #include linux/gpio/consumer.h struct sw_chip { struct i2c_client *client; struct gpio_desc *reset_gpio; struct mii_bus *mdio_bus; }; static int sw_chip_mdio_read(struct mii_bus *bus, int addr, int reg) { struct sw_chip *chip bus-priv; u8 buf[2]; int ret; /* 交换芯片内部寄存器通过 I2C 间接访问 */ ret i2c_smbus_read_i2c_block_data(chip-client, reg 0xff, 2, buf); if (ret 0) return ret; return (buf[0] 8) | buf[1]; } static int sw_chip_mdio_write(struct mii_bus *bus, int addr, int reg, u16 val) { struct sw_chip *chip bus-priv; u8 buf[2] { val 8, val 0xff }; return i2c_smbus_write_i2c_block_data(chip-client, reg 0xff, 2, buf); } static int sw_chip_probe(struct i2c_client *client) { struct sw_chip *chip; int ret; chip devm_kzalloc(client-dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip-client client; chip-reset_gpio devm_gpiod_get_optional(client-dev, reset, GPIOD_OUT_LOW); if (chip-reset_gpio) { gpiod_set_value_cansleep(chip-reset_gpio, 1); msleep(50); /* 复位保持 50ms查手册确认 */ gpiod_set_value_cansleep(chip-reset_gpio, 0); msleep(100); } chip-mdio_bus devm_mdiobus_alloc(client-dev); if (!chip-mdio_bus) return -ENOMEM; chip-mdio_bus-name sw_chip_mdio; chip-mdio_bus-read sw_chip_mdio_read; chip-mdio_bus-write sw_chip_mdio_write; chip-mdio_bus-priv chip; chip-mdio_bus-parent client-dev; ret devm_mdiobus_register(client-dev, chip-mdio_bus); if (ret) return ret; i2c_set_clientdata(client, chip); dev_info(client-dev, switch chip probed\n); return 0; }逻辑说明sw_chip_mdio_read/write把标准 MDIO 读写转成 I2C 块读写这是 SPI/I2C 配置型交换芯片最常见的做法。devm_mdiobus_alloc分配一个虚拟 MDIO 总线注册后内核就能通过它扫描 PHY。复位时序里msleep(50)和msleep(100)是经验值不同芯片要求不同复位时间不够会导致 PHY ID 读出来是 0xFFFF。参数说明reg 0xff是因为很多交换芯片的寄存器地址只有 8 位高位用于区分页如果你的芯片有分页机制这里要改成先写页寄存器再读数据。GPIOD_OUT_LOW表示复位脚低有效如果你的板子是高有效改成GPIOD_OUT_HIGH并反转gpiod_set_value的参数。probe 成功后/sys/class/mdio_bus/下会出现sw_chip_mdio用ls能看到挂在上面的 PHY 地址。3.2 把每个物理口映射成独立 net_device光有 PHY 还不够用户要的是eth1、eth2这样的独立接口。常见做法是用 DSA 框架的dsa_switch_ops如果芯片不支持 DSA就自己实现一个轻量级 net_device 包装。下面展示怎么在 DSA 框架下声明端口映射这是最推荐的方式因为内核已经帮你处理了桥接和 VLAN。#include net/dsa.h static const struct dsa_port ports[] { { .index 0, .name cpu, .phy_mode PHY_INTERFACE_MODE_RGMII }, { .index 1, .name lan1, .phy_mode PHY_INTERFACE_MODE_INTERNAL }, { .index 2, .name lan2, .phy_mode PHY_INTERFACE_MODE_INTERNAL }, { .index 3, .name lan3, .phy_mode PHY_INTERFACE_MODE_INTERNAL }, }; static int sw_dsa_setup(struct dsa_switch *ds) { int i; for (i 0; i ARRAY_SIZE(ports); i) { if (ports[i].phy_mode PHY_INTERFACE_MODE_INTERNAL) continue; /* 配置 CPU 口和外部口的 MAC 模式 */ dsa_port_setup(ds, ports[i].index); } return 0; } static const struct dsa_switch_ops sw_dsa_ops { .setup sw_dsa_setup, .phy_read sw_chip_mdio_read, .phy_write sw_chip_mdio_write, };逻辑说明dsa_port数组里index对应交换芯片的物理端口号cpu口是连到 yt9215 MAC 的那个口lan1到lan3是面板上的网口。PHY_INTERFACE_MODE_INTERNAL表示这些口是芯片内部 PHY不需要外部 MAC 配置。dsa_switch_ops里的phy_read/write直接复用前面实现的 MDIO 函数。参数说明index必须和硬件端口号一致写错会导致lan1实际对应到别的物理口。phy_mode对 CPU 口要写实际连接方式对内部口写INTERNAL。注册成功后ip link会看到lan1、lan2等接口默认都是 down 状态需要ip link set lan1 up才能用。如果接口出不来先检查dsa_switch_ops是否注册成功看dmesg | grep dsa。4. 交换芯片寄存器配置与 VLAN 参数怎么调4.1 必调的四个寄存器组交换芯片能跑起来只是第一步要让它按预期转发至少得配四组寄存器端口使能、VLAN 表、转发规则、CPU 口 tagging。下面用表格列出常见寄存器偏移和推荐值具体地址以你手上的手册为准这里给的是同类芯片的典型布局。寄存器组典型偏移作用推荐值PORT_EN0x00端口使能0x1F使能 0-4 口VLAN_MAP0x10端口默认 VLAN1所有口默认 VLAN 1FWD_CTRL0x20转发规则0x03允许未知单播泛洪CPU_TAG0x30CPU 口 tagging0x01启用 802.1Q tag配置顺序很重要先使能端口再配 VLAN最后开 CPU tagging。顺序反了会出现 CPU 口收到包但转发不出去。我一般会在驱动 probe 的最后一步统一写这些寄存器写完后读回校验读回值不一致说明 I2C 时序有问题。参数怎么改PORT_EN的位对应物理口比如只用了 0-2 口就写 0x07。VLAN_MAP如果要做端口隔离每个口写不同的 VLAN ID然后在 Linux 侧用bridge vlan命令配合。CPU_TAG启用后CPU 口收到的包会带 VLAN tag内核需要能解析否则tcpdump看到的是带 tag 的帧但协议栈不认。4.2 用 bridge 和 vlan 命令验证转发驱动跑起来后验证转发最直接的办法是用 Linux 的 bridge 工具。假设lan1和lan2要互通lan3隔离可以这样配# 创建桥把 lan1 lan2 加进去 ip link add br0 type bridge ip link set lan1 master br0 ip link set lan2 master br0 ip link set br0 up ip link set lan1 up ip link set lan2 up # lan3 单独配 VLAN 100 ip link add link lan3 name lan3.100 type vlan id 100 ip addr add 192.168.100.1/24 dev lan3.100 ip link set lan3.100 up # 查看 VLAN 表 bridge vlan show逻辑说明ip link add br0 type bridge创建软件桥交换芯片的硬件转发和软件桥可以共存但要注意如果芯片已经做了硬件转发软件桥可能看到重复包。lan3.100是在lan3上创建 VLAN 子接口前提是交换芯片的 VLAN 表里lan3的 PVID 设成了 100。参数说明bridge vlan show会列出每个端口的 VLAN 配置如果显示不全说明交换芯片的 VLAN 表没写对。ip addr add的地址段要和你的网络规划一致。验证时用ping从lan1侧设备 pinglan2侧设备通的话说明硬件转发正常如果不通但tcpdump -i lan1能看到包说明转发规则没配对。5. yt9215 switch 驱动避坑五个血泪翻车记录5.1 复位时序不对导致 PHY ID 读成 0xFFFF现象驱动 probe 成功但mdiobus下扫不到任何 PHY或者读出来的 ID 全是 0xFFFF。原因交换芯片复位脚保持时间不够或者复位释放后没有等待内部 PLL 锁定。解决把复位低电平时间从 10ms 加到 50ms 以上释放后加 100ms 延时再读 PHY ID。如果还不行用示波器看复位脚波形确认没有毛刺。5.2 RGMII 延迟模式写错导致大量 CRC 错误现象接口能 upping小包通大包丢ifconfig看到 RX errors 和 CRC 计数飞涨。原因phy-mode写成了rgmii但硬件实际需要rgmii-id或者反过来。解决依次试rgmii、rgmii-id、rgmii-txid、rgmii-rxid四种模式每次改完重新编译设备树用ethtool -S lan1看 CRC 错误是否归零。我一般会先试rgmii-id因为大部分车机板子 MAC 侧不做延迟。5.3 I2C 地址冲突导致配置寄存器写不进去现象驱动加载时i2c_smbus_write返回-EREMOTEIO或者写进去读回来不变。原因交换芯片的 I2C 地址和板上其他器件冲突或者上拉电阻阻值不对。解决用i2cdetect -y 1扫描总线确认地址没被占用。如果地址冲突改硬件片选或换总线。上拉电阻一般 4.7k太小会导致波形上升沿过冲太大导致上升沿变缓I2C 时钟跑不到 400kHz。5.4 CPU 口 tagging 没开导致 VLAN 包被丢弃现象配了 VLAN 子接口tcpdump能看到带 tag 的包但内核协议栈不处理ping不通。原因交换芯片 CPU 口的 tagging 没启用或者内核的 VLAN 模块没加载。解决确认CPU_TAG寄存器写的是启用状态然后modprobe 8021q加载内核 VLAN 模块再用ip link add link创建子接口。如果还不行检查dmesg有没有unknown VLAN相关报错。5.5 端口映射顺序和面板丝印不一致现象lan1实际对应面板上的第三个口用户插错口找不到设备。原因设备树里dsa_port的index按芯片端口号写但面板丝印是按物理位置标的两者顺序可能相反。解决拿一根网线逐个口插用ethtool lan1看 link 状态确认哪个lanX对应哪个物理口然后调整设备树里的index或改 udev 规则重命名。这个坑不致命但很烦建议在驱动 probe 时打印每个端口的 PHY 地址和物理口对应关系。6. 用 ethtool 和寄存器回读做驱动自检驱动写完不是终点得有一套自检手段。我习惯在 probe 成功后加一段回读逻辑把关键寄存器写一遍再读回来不一致就打印警告。下面是一个自检函数放在sw_chip_probe最后调用。static int sw_chip_self_test(struct sw_chip *chip) { u16 val; int ret; /* 回读 PORT_EN确认端口使能生效 */ ret sw_chip_mdio_read(chip-mdio_bus, 0, 0x00); if (ret 0) { dev_err(chip-client-dev, read PORT_EN failed\n); return ret; } val ret; if ((val 0x1F) ! 0x1F) { dev_warn(chip-client-dev, PORT_EN mismatch: 0x%04x\n, val); } /* 回读 CPU_TAG确认 tagging 开启 */ ret sw_chip_mdio_read(chip-mdio_bus, 0, 0x30); if (ret 0 !(ret 0x01)) dev_warn(chip-client-dev, CPU tagging not enabled\n); return 0; }逻辑说明sw_chip_mdio_read复用前面的 MDIO 读函数地址 0 是交换芯片内部 PHY 地址寄存器 0x00 和 0x30 对应 PORT_EN 和 CPU_TAG。回读不一致时只警告不返回错误因为有些芯片上电默认值就是对的写不进去也不影响功能。参数说明0x1F是五个端口都使能的掩码如果你的板子只用了三个口改成0x07。0x30寄存器的 bit0 是 tagging 使能位不同芯片可能在不同 bit查手册确认。自检函数跑完后用dmesg | grep sw_chip看有没有警告有警告就按第 5 章的避坑条目排查。进阶用法上如果要做端口镜像可以在dsa_switch_ops里实现port_mirror_add回调把某个口的流量复制到 CPU 口然后用tcpdump -i lan1抓包分析。验证方法很简单配好镜像后从lan2侧发广播包在lan1上能抓到就说明镜像生效。我踩过的最大教训是别在驱动里硬编码寄存器地址一定用宏定义并加注释否则换一颗芯片就得重读一遍代码。希望帮到你。本文还有配套的精品资源点击获取