
简介本资源为基于 Linux DSA 框架的 YT9215 switch 驱动源码包面向嵌入式网络开发与交换机调试人员。驱动加载后会生成 lan* 网卡用于获取各网口状态实际数据通信则依赖 eth* 网卡并支持通过 VLAN 划分实现每个接口独立管理具体配置可参考包内 patch 说明。压缩包共 3 个文件包含 1 个 patch 补丁、1 个 C 源文件与 1 个头文件整体约 12KB分别用于设备树适配、驱动核心逻辑实现与寄存器及接口定义。目前已有 455 人学习下载。读者可借此理解 DSA 框架下 switch 驱动的注册流程、网口状态获取方式以及 VLAN 隔离思路适合作为移植与二次开发的参考底稿。1. YT9215 交换芯片驱动落地从 DSA 框架到可加载模块的完整路径手上有一块基于 YT9215 的交换板芯片资料少、原厂 SDK 不公开、Linux 主线也没有对应的 DSA 驱动这种场景在国产交换芯片里太常见了。YT9215 是一颗多端口千兆以太网交换芯片支持 VLAN、端口镜像、ACL、QoS 等常规二层特性通常通过 SPI 或 PCIe 挂到主控 SoC 上。问题在于内核自带的 DSA 子系统并不认识它你需要自己写一个 DSA 驱动把芯片的寄存器操作、端口注册、tag 协议全部对接进 Linux 网络栈。这篇笔记拆的就是这条路——从 DSA 框架怎么套、寄存器怎么摸、tag 怎么选到驱动加载后ip link能不能看到端口、bridge能不能通一步步落到可复现的操作。适合手里有 YT9215 硬件、需要把它接进 Linux 做交换或路由的嵌入式网络工程师也适合想通过一个真实芯片理解 DSA 驱动模型的开发者。2. DSA 驱动模型拆解YT9215 该挂在哪一层2.1 为什么选 DSA 而不是自己写 netdevLinux 下接交换芯片有三条路一是自己写一个纯 netdev 驱动把交换芯片当成一个多队列网卡二是用 switchdev 模型把芯片的转发面暴露给内核三是用 DSADistributed Switch Architecture框架。YT9215 这种芯片端口数量固定、支持标准二层转发、需要和主控的 CPU 口对接DSA 是最省事的选型。DSA 的核心思路是交换芯片的每个物理端口在内核里注册成一个独立的 netdev比如swp0、swp1CPU 口通过一个 tag 协议把帧从交换芯片送到主控的以太网控制器。内核的net/dsa/子系统帮你处理了端口注册、桥接、VLAN、STP 这些通用逻辑你只需要实现芯片相关的dsa_switch_ops回调。自己写 netdev 的话桥接、VLAN、STP 全部要自己实现工作量翻几倍不说和内核网络栈的兼容性也是坑。switchdev 更适合可编程交换芯片比如支持 P4 的那种YT9215 这种固定功能的芯片用 DSA 刚好。2.2 YT9215 的硬件接口与寄存器访问方式YT9215 和主控的连接方式通常有两种SPI 和 PCIe。SPI 方式下主控通过 SPI 控制器读写芯片内部寄存器PCIe 方式下芯片的寄存器空间会映射到主控的 MMIO 区域。不管哪种方式驱动里都需要一层“寄存器读写”的抽象。常见做法是定义一个yt9215_reg_read/yt9215_reg_write函数对内部根据总线类型走 SPI 传输或readl/writel。SPI 方式要注意时序YT9215 的 SPI 寄存器访问一般需要先发命令字节读/写 地址高位再发地址低位然后读/写数据。具体命令格式必须查芯片数据手册不同批次可能不一样。/* yt9215_reg.h - 寄存器访问抽象层 */ struct yt9215_priv { struct spi_device *spi; /* SPI 方式 */ void __iomem *base; /* PCIe/MMIO 方式 */ struct mutex reg_lock; /* 寄存器访问互斥 */ }; static int yt9215_reg_read(struct yt9215_priv *priv, u32 reg, u32 *val) { int ret; mutex_lock(priv-reg_lock); if (priv-spi) { /* SPI 读命令字节 地址 读回数据 */ u8 tx[8] { YT9215_SPI_CMD_READ, (reg 16) 0xff, (reg 8) 0xff, reg 0xff, 0, 0, 0, 0 }; u8 rx[8] {0}; struct spi_transfer xfer { .tx_buf tx, .rx_buf rx, .len sizeof(tx), }; ret spi_sync_transfer(priv-spi, xfer, 1); if (ret) goto out; *val (rx[4] 24) | (rx[5] 16) | (rx[6] 8) | rx[7]; } else { *val readl(priv-base reg); } ret 0; out: mutex_unlock(priv-reg_lock); return ret; }这段代码的关键点有三个reg_lock保证多端口并发访问寄存器时不会交错SPI 传输里命令字节和地址的排列顺序必须和数据手册一致写错一位就读不到正确值PCIe 方式下readl的地址偏移要和芯片手册的寄存器映射表对上。参数方面YT9215_SPI_CMD_READ通常是0x03或0x0B具体值查手册确认。2.3 DSA 驱动的骨架dsa_switch_ops 要填哪些回调DSA 驱动的入口是dsa_switch_ops结构体必须实现的回调包括回调作用YT9215 是否必须setup芯片初始化注册端口必须phy_read/phy_write内部 PHY 读写有内部 PHY 则必须port_enable/port_disable端口使能/关闭必须port_bridge_join/port_bridge_leave桥接加入/离开必须port_vlan_add/port_vlan_delVLAN 配置必须port_fdb_add/port_fdb_delFDB 表操作必须port_mdb_add/port_mdb_del组播表操作可选port_stp_state_setSTP 状态设置必须port_mirror_add/port_mirror_del端口镜像可选setup回调里要做的事读芯片 ID 确认型号、复位芯片、配置 CPU 口、注册每个用户端口。注册端口用dsa_register_switch之前填充的ds-ports[]数组每个端口指定index、phy_mode、cpu_port等字段。static int yt9215_setup(struct dsa_switch *ds) { struct yt9215_priv *priv ds-priv; u32 chip_id; int i, ret; /* 读芯片 ID 确认型号 */ ret yt9215_reg_read(priv, YT9215_REG_CHIP_ID, chip_id); if (ret) return ret; if ((chip_id YT9215_CHIP_ID_MASK) ! YT9215_CHIP_ID_VAL) { dev_err(ds-dev, chip id mismatch: 0x%08x\n, chip_id); return -ENODEV; } /* 软复位 */ yt9215_reg_write(priv, YT9215_REG_RESET, YT9215_RESET_SW); msleep(50); /* 配置 CPU 口为 tag 模式 */ yt9215_reg_write(priv, YT9215_REG_CPU_PORT_CTRL, YT9215_CPU_PORT_TAG_EN | YT9215_CPU_PORT_FWD_ALL); /* 使能所有用户端口 */ for (i 0; i YT9215_NUM_PORTS; i) { yt9215_reg_write(priv, YT9215_REG_PORT_CTRL(i), YT9215_PORT_EN | YT9215_PORT_FWD_EN); } return 0; }setup里最容易翻车的是复位后的等待时间。YT9215 软复位后需要至少 30ms 才能稳定访问寄存器msleep(50)是留了余量。CPU 口的 tag 模式配置决定了后续帧怎么封装这个要和 tag 协议实现保持一致。3. 从零写一个可加载的 YT9215 DSA 驱动模块3.1 驱动文件结构与 Makefile一个最小可用的 YT9215 DSA 驱动通常拆成三个文件yt9215_main.cDSA 回调实现、yt9215_reg.h寄存器定义和访问函数、yt9215_tag.ctag 协议收发。如果芯片有内部 PHY再加一个yt9215_phy.c。Makefile 用内核的 kbuild 体系# Makefile obj-$(CONFIG_YT9215_DSA) yt9215_dsa.o yt9215_dsa-objs : yt9215_main.o yt9215_tag.o对应的 Kconfigconfig YT9215_DSA tristate YT9215 DSA switch driver depends on SPI || PCI depends on NET_DSA select NET_DSA_TAG_YT9215 help Say Y or M here to enable the YT9215 DSA switch driver.NET_DSA_TAG_YT9215是 tag 协议的 Kconfig 选项需要在net/dsa/Kconfig里加对应的条目。如果不想改内核源码树也可以把 tag 协议直接编进驱动模块里但那样就和 DSA 子系统的 tag 注册机制脱节了不推荐。3.2 tag 协议实现CPU 口帧的封装与解析DSA 的 tag 协议决定了 CPU 口收到的帧怎么区分来自哪个端口、发出去的帧怎么指定目标端口。YT9215 的 tag 格式通常是 4 字节或 8 字节包含源端口号、目标端口号、VLAN 信息等。具体格式必须查芯片手册的“CPU Port Tag”章节。常见做法是实现tag_xmit和tag_rcv两个回调/* yt9215_tag.c - tag 协议收发 */ static struct sk_buff *yt9215_tag_xmit(struct sk_buff *skb, struct net_device *dev) { struct dsa_port *dp dsa_slave_to_port(dev); u8 *tag; /* 在帧头前插入 4 字节 tag */ skb_push(skb, YT9215_TAG_LEN); tag skb-data; tag[0] YT9215_TAG_VER; tag[1] dp-index 0x1f; /* 目标端口号 */ tag[2] 0; /* 优先级等 */ tag[3] 0; return skb; } static struct sk_buff *yt9215_tag_rcv(struct sk_buff *skb, struct net_device *dev) { u8 *tag skb-data; int source_port; if (skb-len YT9215_TAG_LEN) return NULL; source_port tag[1] 0x1f; skb_pull(skb, YT9215_TAG_LEN); skb-dev dsa_master_find_slave(dev, 0, source_port); if (!skb-dev) return NULL; return skb; }tag_xmit里skb_push之后要确保skb-data指向新插入的 tag 起始位置dp-index是 DSA 分配的端口索引不是物理端口号两者在setup里要对应好。tag_rcv里dsa_master_find_slave的第二个参数是树索引单芯片场景填 0。3.3 编译、加载与端口验证驱动写完后编译成模块加载# 编译模块 make -C /lib/modules/$(uname -r)/build M$(pwd) modules # 加载驱动 sudo insmod yt9215_dsa.ko # 查看内核日志 dmesg | tail -30加载成功后dmesg里应该能看到类似yt9215_dsa spi0.0: registered switch with 5 ports的输出。然后检查端口# 查看 DSA 注册的端口 ip link show | grep swp # 应该看到 swp0 到 swp4 等端口 # 启用端口 sudo ip link set swp0 up sudo ip link set swp1 up # 创建桥接并加入端口 sudo ip link add br0 type bridge sudo ip link set swp0 master br0 sudo ip link set swp1 master br0 sudo ip link set br0 up如果ip link看不到swpX端口先查dmesg里有没有failed to register switch或chip id mismatch。前者通常是setup回调返回了错误后者是 SPI 读到的芯片 ID 不对检查 SPI 命令字节和地址排列。4. 避坑与排查YT9215 驱动调试中的五类翻车现场4.1 芯片 ID 读出来是 0xFFFFFFFF 或 0x00000000现象setup里读YT9215_REG_CHIP_ID返回全 1 或全 0驱动直接-ENODEV退出。原因SPI 时序不对或者片选CS没有正确拉低。全 1 通常是 MISO 线悬空全 0 通常是 MOSI 没发出去。也有可能是 SPI 模式CPOL/CPHA设错了YT9215 一般用 Mode 0。解决先用逻辑分析仪抓 SPI 波形确认 CS、CLK、MOSI、MISO 四根线的时序。检查设备树里spi-max-frequency是否超过芯片手册的上限YT9215 通常支持到 25MHz。如果用的是 PCIe 方式检查 BAR 空间有没有正确映射ioremap的返回值有没有判空。4.2 端口注册了但 ping 不通现象ip link能看到swp0、swp1ip link set up也成功但两台机器接在交换板上 ping 不通。原因CPU 口的 tag 模式没配对或者 tag 协议里目标端口号填错了。也有可能是芯片的转发使能位没打开。解决先确认setup里 CPU 口的YT9215_CPU_PORT_TAG_EN和YT9215_CPU_PORT_FWD_ALL都置位了。然后在tag_xmit里打印dp-index确认发出去的帧 tag 里目标端口号正确。如果 tag 正确但还不通检查芯片的端口转发使能寄存器有些批次需要额外配置“端口间转发允许”位。4.3 桥接后广播风暴现象把两个端口加入同一个 bridge 后网络里出现广播风暴CPU 负载飙升。原因STP 没启用或者芯片的未知单播/广播转发策略配置成了“全部端口泛洪”而 CPU 口也在泛洪范围内。解决在 bridge 上启用 STPip link set br0 type bridge stp_state 1。同时检查芯片的泛洪控制寄存器把 CPU 口从泛洪端口列表里排除。DSA 框架下port_stp_state_set回调要正确实现否则 STP 状态机不生效。4.4 加载模块后内核 panic现象insmod后直接 kernel panic日志里有Unable to handle kernel NULL pointer dereference。原因dsa_switch_ops里某个必须实现的回调是 NULLDSA 子系统调用时解引用空指针。常见的是port_bridge_join或port_vlan_add没实现。解决对照第 2 章的表格把必须实现的回调全部填上。如果某个功能暂时不想支持也要填一个返回-EOPNOTSUPP的空实现不能留 NULL。4.5 VLAN 配置后端口不通现象bridge vlan add dev swp0 vid 100执行成功但带 VLAN 100 的帧过不去。原因芯片的 VLAN 表没正确写入或者端口的 VLAN 成员关系没配置。DSA 的port_vlan_add回调里需要同时更新芯片的 VLAN 表和端口的 VLAN 成员寄存器。解决在port_vlan_add里加打印确认回调被调用了。然后检查芯片手册里 VLAN 表的写入顺序——有些芯片要求先写 VLAN 表项再更新端口成员关系顺序反了不生效。5. 进阶用 ethtool 和寄存器 dump 验证驱动行为驱动跑起来只是第一步真正要确认它工作正常得能观测。DSA 驱动可以通过ethtool -S暴露端口统计也可以在 debugfs 里加一个寄存器 dump 接口。先看ethtool统计。在dsa_switch_ops里实现get_ethtool_stats和get_sset_count回调把芯片的端口计数器读出来static void yt9215_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data) { struct yt9215_priv *priv ds-priv; u32 val; int i; for (i 0; i YT9215_NUM_STATS; i) { yt9215_reg_read(priv, YT9215_REG_PORT_STAT(port, i), val); data[i] val; } }加载后执行ethtool -S swp0应该能看到rx_packets、tx_packets、rx_errors等计数器。如果全是 0先确认端口有流量再检查寄存器地址偏移对不对。再做一个 debugfs 寄存器 dump。在驱动probe里创建 debugfs 文件static int yt9215_reg_show(struct seq_file *s, void *unused) { struct yt9215_priv *priv s-private; u32 val; int i; for (i 0; i YT9215_REG_MAX; i 4) { yt9215_reg_read(priv, i, val); seq_printf(s, 0x%04x: 0x%08x\n, i, val); } return 0; }挂载 debugfs 后cat /sys/kernel/debug/yt9215/regs就能看到全部寄存器值。这个接口在调试 VLAN 和 FDB 问题时特别有用——配置前后各 dump 一次对比哪些位变了比翻手册猜快得多。我自己的习惯是每次改完port_vlan_add或port_fdb_add先 dump 寄存器确认写入生效再用bridge fdb show和bridge vlan show看内核侧状态两边对上了才算过。从那以后我每次调 DSA 驱动都强制走一遍“寄存器 dump → 内核状态 → 实际流量”三步验证省了很多来回猜的时间。希望帮到你。本文还有配套的精品资源点击获取