1. 为什么“Switch数据接收驱动框架”不是普通网卡驱动的简单复刻?
很多人第一次接触MTK7621平台上的交换芯片驱动开发时,会下意识地把它当成一个“带多个端口的高级网卡”来处理——毕竟Linux内核里有net_device、有ndo_start_xmit、有skb收发流程,看起来逻辑很像。但实测下来,这种类比在MT7530集成场景中会迅速撞墙:你改好了单端口收包路径,发现VLAN转发乱序;你优化了中断聚合,结果CPU软中断负载反而飙升300%;你加了ethtool -S统计项,却发现rx_packets和硬件寄存器值长期差27个包,且这个差值还随流量模式动态漂移。
根本原因在于:交换芯片的数据接收,本质是“跨端口+跨协议栈+跨调度层级”的协同行为,而非单一网络接口的线性收包。MT7530作为一颗全千兆集成交换芯片,其内部包含独立的DMA引擎、专用报文解析单元(PPU)、可编程的入口/出口ACL表、以及与CPU通路深度耦合的DMA描述符环(Descriptor Ring)。它不依赖eth0或eth1这类传统网络设备的收包上下文,而是通过switchdev子系统向上暴露统一的L2转发视图,向下则需精确控制每个端口的DMA缓冲区对齐、描述符状态机跳转、以及报文元数据(如port_id、vlan_id、priority)的硬件注入时机。
我参与过某款基于MTK7621+MT7530的工业网关项目,在调试初期就踩过一个典型坑:当启用QoS策略后,高优先级语音流(DSCP=46)的延迟突增到80ms以上,远超设计指标。抓包发现并非网络拥塞,而是报文在进入内核协议栈前就被ksoftirqd/0进程反复重调度。最终定位到问题出在驱动层——MT7530的RX描述符环中,OWN_BIT(表示DMA是否拥有该描述符)的清除操作被放在了napi_poll()函数末尾,而napi_poll()本身又在软中断上下文中执行。这意味着:只要有一个描述符未及时释放,整个RX环就卡死,后续所有端口的报文都会堆积在DMA缓冲区,触发硬件背压,进而导致CPU不断轮询空环,形成恶性循环。
提示:MT7530的RX描述符结构体中,第31位是
OWN_BIT,第0~7位是RX_BYTE_CNT,第16~23位是PORT_ID。很多开发者只关注RX_BYTE_CNT是否非零,却忽略OWN_BIT必须为0才表示CPU已取走该报文。这是驱动稳定性的第一道生死线。
真正理解这个框架,首先要抛弃“网卡思维”,建立“交换流水线思维”:报文从物理端口进入→经PPU打上元数据→写入对应端口的RX DMA环→CPU通过NAPI批量收割→按PORT_ID分发至对应虚拟接口(如lan1、wan0)→再由br_forward或ip_rcv进入协议栈。每一个环节的时序、内存屏障、缓存一致性,都必须严格遵循MT7530数据手册第5.2节“Receive Descriptor Ring Operation”的时序图。比如dma_cache_sync()调用的位置,必须在OWN_BIT置0之后、skb指针赋值之前,否则极大概率出现DMA写入旧skb->data地址导致内存越界。
这解释了为什么官方SDK中mt7530.c驱动文件长达3200行,其中近900行都在处理RX路径的状态同步与错误恢复——它不是代码冗余,而是对硬件行为边界的敬畏。当你看到mt7530_rx_poll()函数里嵌套了三层while循环(外层轮询NAPI budget、中层遍历RX ring、内层处理multi-descriptor packet),你就该明白:这里的每一行,都是用真实丢包日志换来的。
2. MT7530 RX驱动框架的四大核心模块拆解
MT7530的数据接收驱动框架,并非一个扁平化的函数集合,而是由四个强耦合、职责分明的核心模块构成。它们共同构成了从硬件中断触发到内核协议栈接收的完整链路。下面我将结合实际代码片段(基于Linux 4.14内核分支适配版),逐层拆解每个模块的设计意图、关键实现与常见陷阱。
2.1 硬件中断与NAPI调度模块:如何让CPU不被千兆流量“冲垮”
MT7530支持两种中断模式:共享中断(Shared IRQ)和独立端口中断(Per-Port IRQ)。在MTK7621平台上,默认使用共享中断,即所有端口事件(RX ready、TX done、link change)共用一个GPIO引脚触发。驱动初始化时,通过request_irq()注册中断处理函数mt7530_irq_handler(),其核心逻辑非常精简:
static irqreturn_t mt7530_irq_handler(int irq, void *dev_id) { struct mt7530_priv *priv = dev_id; u32 stat; stat = mt7530_read(priv, MT7530_IRQ_STAT); if (!stat) return IRQ_NONE; /* 清除中断源 */ mt7530_write(priv, MT7530_IRQ_STAT, stat); /* 仅当RX事件发生时,触发NAPI轮询 */ if (stat & MT7530_IRQ_RX_DONE) napi_schedule(&priv->napi); return IRQ_HANDLED; }这段代码看似简单,但藏着两个关键设计点:
第一,中断清除必须在NAPI调度前完成。若先调用napi_schedule()再清中断,可能在NAPI执行间隙再次触发相同中断,导致重复调度或丢失事件。MT7530硬件要求写IRQ_STAT寄存器以清除中断标志,这是硬性时序约束。
第二,NAPI调度不等于立即执行。napi_schedule()只是将&priv->napi加入CPU的softirq队列,具体执行时机由内核软中断调度器决定。这意味着:即使RX ring中有100个待处理描述符,CPU也不会立刻全部处理,而是受net.core.netdev_budget(默认300)参数限制,每次最多处理300个包。这个机制正是防止CPU被突发流量“饿死”的核心保障。
我曾在一个视频监控项目中遇到极端案例:当8路1080P摄像头同时推流时,/proc/interrupts显示该IRQ每秒触发超12万次,top中ksoftirqd/0CPU占用率飙到98%。排查发现,是驱动中误将napi_schedule()放在了for循环内(每收到一个包就调一次),导致NAPI被高频重入。修正方案是严格遵循“中断顶半部只做最小化工作”原则:顶半部只清中断+调度NAPI;底半部(mt7530_napi_poll)集中处理所有待收包。
注意:MT7530的RX中断阈值可通过
MT7530_PDMA_GLO_CFG寄存器的RX_THRES字段配置,默认为1,即每个描述符就触发中断。生产环境强烈建议设为8~16,用中断合并换取CPU效率。修改方法是在mt7530_setup_port函数中添加:mt7530_write(priv, MT7530_PDMA_GLO_CFG, (mt7530_read(priv, MT7530_PDMA_GLO_CFG) & ~0xf) | 0x8);
2.2 RX描述符环管理模块:DMA内存布局与状态机的生死博弈
MT7530的RX路径依赖一个环形DMA描述符数组(Ring Buffer),每个描述符大小为32字节,包含DMA地址、包长、端口ID、校验状态等关键字段。驱动必须在内存中为其分配连续的、cache一致的缓冲区。这里极易出错的点在于:DMA缓冲区必须物理连续,且CPU与DMA访问时需保证缓存一致性。
在MTK7621平台(MIPS架构)上,我们采用dma_alloc_coherent()分配RX ring内存:
priv->rx_ring = dma_alloc_coherent(&priv->pdev->dev, MT7530_RX_RING_SIZE * sizeof(*desc), &priv->rx_ring_dma, GFP_KERNEL);MT7530_RX_RING_SIZE通常设为128或256(不能为2的幂次方以外的数,因硬件使用掩码寻址)。分配成功后,还需初始化每个描述符的DMA_ADDR字段指向对应的skb->data缓冲区。关键代码如下:
for (i = 0; i < MT7530_RX_RING_SIZE; i++) { struct mt7530_rx_desc *desc = &priv->rx_ring[i]; struct sk_buff *skb; skb = __netdev_alloc_skb(dev, MT7530_RX_BUF_SIZE + NET_SKB_PAD, GFP_KERNEL | __GFP_NOWARN); if (!skb) break; /* 预留NET_SKB_PAD空间,确保IP头对齐 */ skb_reserve(skb, NET_SKB_PAD); desc->dma_addr = dma_map_single(&priv->pdev->dev, skb->data, MT7530_RX_BUF_SIZE, DMA_FROM_DEVICE); desc->rx_byte_cnt = 0; desc->own_bit = 1; /* 初始状态:DMA拥有 */ priv->rx_buf[i] = skb; }这段初始化代码有三个魔鬼细节:
NET_SKB_PAD(通常为16字节)的预留,是为了确保skb->data起始地址满足IP头16字节对齐要求,避免某些MIPS CPU因未对齐访问触发异常;dma_map_single()必须在desc->own_bit = 1之前调用,否则DMA引擎可能在映射完成前就开始写入,导致数据损坏;- 所有描述符的
own_bit初始必须为1,表示DMA拥有权,这是硬件启动RX引擎的前提。
最常被忽视的是描述符环的“水位线”管理。驱动需维护两个游标:rx_ring_head(硬件写入位置,即下一个将被DMA填充的描述符索引)和rx_ring_tail(CPU读取位置,即下一个将被CPU处理的描述符索引)。两者相等时表示环空,head超前tail表示有包待收。更新tail时必须用barrier()保证顺序:
smp_mb(); priv->rx_ring_tail = next_tail;否则在多核环境下,CPU可能读取到未完全更新的rx_byte_cnt值,导致skb长度计算错误,引发后续协议栈崩溃。
2.3 报文收割与元数据提取模块:PORT_ID、VLAN、优先级的硬件注入真相
当NAPI开始收割RX ring时,mt7530_rx_poll()函数会遍历从rx_ring_tail到rx_ring_head的所有描述符。对每个own_bit == 0的描述符,驱动需完成三件事:提取报文长度、获取端口ID、构造skb并送入协议栈。其中,端口ID(PORT_ID)的提取方式,直接决定了VLAN和桥接功能的正确性。
MT7530在报文进入RX DMA前,会将PORT_ID(0~5,对应6个物理端口)写入描述符的第16~23位。驱动代码中这样提取:
u8 port_id = (desc->rx_status >> 16) & 0xff;但问题来了:如果该报文是来自CPU端口(port 6)的管理帧,或经过VLAN翻译后的报文,PORT_ID字段是否还可靠?答案是否定的。MT7530数据手册明确指出:当启用VLAN Translation或CPU Port Injection功能时,硬件会覆盖原始PORT_ID,写入的是“报文最终归属端口”,而非“物理入端口”。这就要求驱动必须结合RX_STATUS寄存器的其他位来判断上下文。
例如,RX_STATUS的bit 8(IS_VLAN_TAGGED)和bit 9(IS_PRIO_TAGGED)共同指示报文是否携带802.1Q或802.1p标签。驱动需据此决定是否调用vlan_hwaccel_receive_skb()还是br_handle_frame_finish():
if (status & MT7530_RX_STATUS_VLAN_TAGGED) { __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan_id); skb->protocol = eth_type_trans(skb, dev); netif_receive_skb(skb); } else { skb->protocol = eth_type_trans(skb, priv->ports[port_id].dev); br_handle_frame_finish(skb); }这里有个隐藏巨坑:eth_type_trans()函数内部会修改skb->mac_header,而br_handle_frame_finish()则依赖原始MAC头。若顺序颠倒,桥接模块会解析出错的源MAC,导致STP BPDU被丢弃,整个VLAN拓扑分裂。我在某款企业级AP固件中就因此导致客户现场出现间歇性广播风暴,排查耗时两周——根源就是skb->protocol赋值时机错误。
2.4 错误处理与恢复模块:CRC错误、Overrun、Descriptor Exhaustion的实战对策
任何稳定的驱动,其错误处理代码量往往超过主逻辑。MT7530 RX路径有三大高频错误类型,每种都需要独立的恢复策略:
| 错误类型 | 触发条件 | 硬件寄存器标志 | 驱动应对措施 |
|---|---|---|---|
| CRC错误 | 物理层干扰、线缆质量差 | RX_STATUSbit 0 (RX_CRC_ERR) | 丢弃报文,dev->stats.rx_crc_errors++,不重填描述符(避免无限循环) |
| RX Overrun | CPU处理太慢,DMA缓冲区溢出 | MT7530_IRQ_STATbit 3 (RX_OVERRUN) | 停止RX引擎(MT7530_PDMA_GLO_CFGbit 0 = 0),清空ring,重置DMA地址,重启引擎 |
| Descriptor Exhaustion | ring满且无可用描述符 | RX_STATUSbit 1 (RX_DESC_EXHAUSTED) | 触发netif_stop_queue(),唤醒ksoftirqd加速处理,严禁在中断中分配新skb(GFP_ATOMIC可能失败) |
其中,“Descriptor Exhaustion”的处理最具迷惑性。很多开发者看到RX_DESC_EXHAUSTED就立刻在中断里__netdev_alloc_skb(),结果在高负载下频繁触发slab_out_of_memory警告。正确做法是:在NAPI poll函数中检测到此错误时,记录priv->rx_exhausted++计数器,并在mt7530_napi_poll()末尾检查该计数器,若>3则主动调用netif_carrier_off(dev)模拟链路down,强制上层协议栈重传,给驱动喘息时间。
我整理了一份MT7530 RX错误日志对照表,供快速定位:
| 日志特征 | 可能原因 | 验证命令 |
|---|---|---|
rx_crc_errors每秒增长>100 | 网线超过100米、RJ45水晶头氧化 | ethtool -S lan0 | grep crc |
rx_over_errors突然归零后持续为0 | RX引擎被意外关闭 | cat /sys/kernel/debug/mt7530/rx_status |
rx_dropped持续增长且rx_packets不变 | skb分配失败,ring未及时重填 | dmesg | grep "mt7530: low on rx bufs" |
这些不是教科书里的理论,而是我在产线老化测试中,盯着watch -n 1 'cat /proc/net/dev'连续72小时后,从百万行日志里抠出来的血泪经验。
3. MTK7621与MT7530的硬件协同关键:SoC侧DMA引擎与交换芯片的握手协议
MTK7621作为SoC,其内置的PDMA(Packet DMA)引擎与外部MT7530交换芯片之间,并非简单的“读写内存”关系,而是一套精密的硬件握手协议。理解这套协议,是解决“驱动编译通过但无法收包”这类玄学问题的钥匙。很多开发者卡在最后一步,就是因为忽略了SoC与交换芯片在DMA层面的时序配合。
3.1 PDMA通道配置:为何必须禁用“Scatter-Gather”模式
MTK7621的PDMA引擎支持两种工作模式:Normal Mode(单缓冲区)和Scatter-Gather Mode(多段缓冲区)。对于MT7530的RX ring,必须使用Normal Mode。原因在于:MT7530的RX描述符格式是固定32字节,且每个描述符只指向一个连续的skb->data缓冲区。若开启Scatter-Gather,PDMA会尝试将一个报文拆分成多个片段写入不同地址,而这与MT7530硬件设计完全冲突。
驱动中对应的配置代码位于mt7530_init_pdma():
/* 禁用Scatter-Gather,启用Normal Mode */ mtk_w32(priv, 0, MTK_QDMA_GLO_CFG); /* 设置RX通道0的基地址和大小 */ mtk_w32(priv, priv->rx_ring_dma, MTK_QDMA_RQS0_BASE); mtk_w32(priv, MT7530_RX_RING_SIZE << 16, MTK_QDMA_RQS0_MAX_CNT);MTK_QDMA_GLO_CFG寄存器的bit 0是SG_MODE_EN,必须为0。我曾见过一个案例:某开发者为追求性能,擅自将此位设为1,并修改描述符结构体增加next_desc_ptr字段,结果驱动加载后,dmesg疯狂刷屏"PDMA channel 0 error: invalid descriptor address"——因为MT7530根本不认识这个扩展字段,把next_desc_ptr当成了dma_addr去访问非法内存。
3.2 Cache一致性:MIPS架构下dma_cache_sync()的精确插入点
MTK7621采用MIPS 24KEc CPU,其cache line大小为32字节,且为write-back策略。这意味着:当CPU修改了skb->data中的某个字节,该修改可能只停留在L1 cache中,尚未写回物理内存。而MT7530的DMA引擎直接访问物理内存,若此时DMA读取,就会拿到脏数据。
解决方案是使用dma_cache_sync()函数,强制同步cache与内存。但调用时机极其关键。以RX报文收割为例,正确顺序是:
- CPU读取描述符
rx_byte_cnt→ 2.dma_unmap_single()解除DMA映射 → 3.dma_cache_sync()同步skb->data区域 → 4.skb_put()设置有效长度 → 5.netif_receive_skb()提交
若将步骤3放在步骤2之前,dma_unmap_single()会失效;若放在步骤4之后,则skb_put()修改的len字段可能未同步,导致协议栈解析长度错误。我在调试一个VoIP网关时,就因dma_cache_sync()位置错误,导致SIP信令包的SDP字段随机出现0x00填充,通话建立失败。用逻辑分析仪抓取skb->data物理地址的内存波形,才确认是cache未同步导致的字节错乱。
3.3 中断路由:GPIO到CPU Core的映射陷阱
MTK7621的中断控制器(INTC)将外部GPIO中断路由到指定CPU core。默认配置下,所有GPIO中断都路由到core 0。但在多核系统中,若NAPI poll函数被调度到core 1执行,而中断服务程序(ISR)在core 0运行,就会出现priv->napi结构体的并发访问风险——napi_schedule()和napi_complete_done()可能同时修改napi->state字段。
解决方案是在设备树(DTS)中显式绑定中断到特定core:
&pio { mt7530_intc: interrupt-controller@0 { compatible = "mediatek,mt7621-sysirq"; interrupt-controller; #interrupt-cells = <2>; mtk,cpu-offset = <0x200>; /* 路由到core 0 */ }; }; ðernet { interrupts = <&mt7530_intc 12 IRQ_TYPE_LEVEL_HIGH>; };mtk,cpu-offset属性指定了中断向量偏移,确保所有MT7530相关中断都落在同一core上。这个配置在官方SDK中常被忽略,导致在4核版本的MTK7621上,驱动稳定性下降40%以上。
4. 实战排错:从“ping不通”到“全端口线速转发”的七步定位法
在真实项目交付中,最常遇到的不是功能缺失,而是“看似正常却性能低下”或“偶发性丢包”。下面是我总结的、经过数十个项目验证的七步定位法,每一步都对应一个可执行的命令或代码检查点,帮你从混沌中快速锚定问题域。
4.1 第一步:确认硬件链路层状态(绕过驱动,直击PHY)
不要一上来就查驱动日志。先用最底层的工具确认物理连接是否真实建立:
# 查看所有PHY状态 cat /sys/bus/mdio_bus/devices/0:00/phy_status # 强制重协商(适用于自适应失败) echo "1" > /sys/bus/mdio_bus/devices/0:00/autoneg # 读取PHY寄存器0(控制寄存器),bit 12=1表示AN完成 mdio read 0:00 0我曾在一个项目中,客户抱怨“LAN口无法上网”,ifconfig显示UP,ethtool lan0显示Link detected: yes,但ping全丢。执行mdio read 0:00 0返回0x3100(bit 12=0),说明自动协商根本没完成。最终发现是客户使用的网线为非标五类线,仅用了4芯,而MT7530的1000BASE-T需要全部8芯。更换网线后问题消失。永远先怀疑物理层,这是工程师的黄金守则。
4.2 第二步:验证RX描述符环的实时水位
驱动是否真的在收包?最直接的证据是看RX ring的head和tail是否在移动:
# 开启debugfs(需内核配置CONFIG_DEBUG_FS=y) mount -t debugfs none /sys/kernel/debug # 查看MT7530私有状态 cat /sys/kernel/debug/mt7530/rx_ring_status # 输出示例:head=127, tail=125, size=128 → 表示有2个包待处理若head == tail且长时间不变,说明中断没触发或RX引擎未启动;若head持续增长但tail不动,说明NAPI poll函数未被调度或卡死在某处。这时应检查/proc/interrupts中对应IRQ的计数是否增长。
4.3 第三步:捕获硬件原始报文(绕过驱动,直连DMA)
当怀疑驱动丢包时,最狠的招是绕过整个Linux网络栈,用逻辑分析仪或专用抓包芯片(如KSZ9477的Mirror Port)直接捕获MT7530的MDIO总线或GMII信号。但这成本太高。一个折中方案是:在驱动中临时添加printk,打印每个描述符的rx_byte_cnt和port_id:
// 在mt7530_rx_poll()循环内添加 printk(KERN_INFO "RX[%d]: len=%d, port=%d, own=%d\n", i, desc->rx_byte_cnt, port_id, desc->own_bit);编译加载后,用dmesg -w实时观察。若看到大量len=0或own=1,说明DMA未写入,问题在硬件配置;若len>0但port_id全为0,说明PPU配置错误,VLAN或端口映射没生效。
4.4 第四步:压力测试下的缓存与内存瓶颈
用iperf3进行线速测试时,若rx_packets增长但rx_bytes不增长,或tx_dropped飙升,大概率是内存子系统瓶颈。检查点:
# 查看DMA内存分配是否成功 dmesg | grep "mt7530.*dma" # 检查系统内存碎片(MIPS平台尤其敏感) cat /proc/buddyinfo # 监控kswapd活动 vmstat 1 10MTK7621的DDR控制器对内存bank分布敏感。若skb缓冲区跨bank分配,DMA吞吐量会下降30%。解决方案是在skbuff.h中修改SKB_TRUESIZE,强制skb在单一bank内分配,但这需要修改内核内存管理子系统,属于高阶技巧。
4.5 第五步:VLAN与桥接功能的原子性验证
创建VLAN和网桥后,务必用tcpdump在物理端口和虚拟接口上同时抓包:
# 在物理端口lan0抓包 tcpdump -i lan0 -nn -e vlan and port 53 # 在虚拟接口br-lan抓包 tcpdump -i br-lan -nn -e port 53若lan0能看到DNS请求,但br-lan看不到,说明br_handle_frame()未被调用,检查br_vlan_enabled()返回值;若br-lan能看到但ping不通,检查br_fdb_update()是否成功学习到MAC地址,用bridge fdb show命令验证。
4.6 第六步:中断延迟与软中断堆积分析
高负载下,ksoftirqd的延迟是隐形杀手。用perf工具分析:
# 记录软中断事件 perf record -e irq:softirq_entry -a sleep 10 perf script | grep "NET_RX" # 查看平均延迟 cat /proc/softirqs若NET_RX列数值远高于TIMER,说明网络软中断处理不过来。此时应调整net.core.netdev_budget(增大)和net.core.netdev_max_backlog(增大),并确保irqbalance服务已禁用,将MT7530中断绑定到专用core。
4.7 第七步:固件与硬件版本交叉验证
MT7530存在多个硬件修订版(A0, B0, C0),不同版本的寄存器定义有细微差异。驱动中必须通过读取CHIP_VERSION寄存器识别:
u32 chip_ver = mt7530_read(priv, MT7530_CHIP_VERSION); if ((chip_ver & 0xff) == 0x10) // A0 revision priv->flags |= MT7530_A0; else if ((chip_ver & 0xff) == 0x11) // B0 revision priv->flags |= MT7530_B0;若驱动未做版本适配,A0版的RX_STATUS字段定义与B0版不同,会导致port_id提取错误。这个坑我在一个海外项目中踩过,客户设备混用了A0和B0芯片,固件升级后部分设备VLAN失效,折腾了三天才发现是版本判断逻辑缺失。
这七步不是线性流程,而是根据现象选择切入口的决策树。真正的高手,往往一眼就能从dmesg的第一行错误信息,锁定到具体的寄存器地址和bit位。这种能力,来自于把数据手册读烂、把示波器探头焊在板子上、把每一行驱动代码在脑中执行了上千遍。
5. 性能调优实战:从200Mbps到线速980Mbps的五个关键参数
MT7530在MTK7621平台上,理论最大吞吐为6×1Gbps=6Gbps,但受限于SoC总线带宽和驱动效率,实测单向转发极限通常在950~980Mbps。要逼近这个极限,必须精细调整五个核心参数。下面是我在线上设备中实测有效的调优组合,每个参数都附带原理说明和风险提示。
5.1 RX描述符环大小:128 vs 256 —— 内存占用与中断频率的平衡术
MT7530_RX_RING_SIZE是第一个要调的参数。增大它可降低中断频率,但会吃掉宝贵的片上SRAM:
| Ring Size | 中断频率(1Gbps流) | 内存占用 | 推荐场景 |
|---|---|---|---|
| 64 | ~15600次/秒 | 2KB | 超低功耗IoT节点 |
| 128 | ~7800次/秒 | 4KB | 主流家用路由器 |
| 256 | ~3900次/秒 | 8KB | 企业级网关、视频流 |
实测数据显示,从128增至256,ksoftirqdCPU占用率下降12%,但free -m显示可用内存减少8MB。对于MTK7621(通常配128MB DDR),256是性价比最优解。调整方法是在mt7530_probe()中修改:
#define MT7530_RX_RING_SIZE 256风险提示:Ring Size必须是2的幂次方,且不能超过硬件最大值(MT7530为1024)。若设为300,驱动会静默截断为256,但
rx_ring_head计算会溢出,导致ring逻辑混乱。
5.2 NAPI Budget:300 vs 600 —— 单次轮询的“工作包”大小
net.core.netdev_budget默认300,即每次NAPI poll最多处理300个包。在千兆线速下,这不够。将其提升至600:
echo 600 > /proc/sys/net/core/netdev_budget效果立竿见影:/proc/net/softnet_stat中第1列(processed)与第2列(dropped)的比值从3.2:1提升至5.8:1,说明丢包率下降。但副作用是单次NAPI执行时间变长,可能影响实时任务响应。我的做法是:在mt7530_napi_poll()中动态调整budget:
int budget = priv->msg_enable ? 600 : 300; work = mt7530_rx_poll(priv, budget); if (work >= budget && mt7530_rx_ring_full(priv)) budget = 300; // 检测到ring快满,降级保稳定5.3 DMA Burst Length:4 vs 8 —— 总线效率的微观调控
MTK7621的PDMA引擎支持Burst Length配置,影响每次DMA传输的数据量。MT7530推荐设为8(128字节):
/* 在PDMA初始化中 */ mtk_w32(priv, 0x8, MTK_QDMA_RQS0_CFG); // burst_len = 8实测对比:Burst Length=4时,iperf3 -c 192.168.1.1 -t 60 -P 4结果为820Mbps;设为8后提升至945Mbps。原理是减少了总线仲裁次数,提升了有效带宽利用率。但注意:Burst Length过大可能导致DDR控制器饥饿,需配合/proc/sys/vm/swappiness调低(设为10)。
5.4 SKB缓冲区大小:1536 vs 2048 —— MTU与内存对齐的妥协
MT7530的RX缓冲区默认1536字节,刚好容纳标准1500字节MTU+14字节以太网头+18字节FCS+4字节VLAN。但现代网络常启用Jumbo Frame(9000字节MTU),1536就不够了。增大到2048:
#define MT7530_RX_BUF_SIZE 2048好处是支持大包,坏处是每个skb内存开销增加512字节,128个ring就是64KB。更优解是:按端口动态分配。WAN口设为2048(对接运营商大包),LAN口保持1536(节省内存)。这需要修改mt7530_setup_port(),为不同端口配置不同rx_buf_size。
5.5 中断合并阈值:RX_THRES=16 —— 用延迟换吞吐的终极艺术
这是最激进的调优,直接修改MT7530寄存器MT7530_PDMA_GLO_CFG的RX_THRES字段:
mt7530_write(priv, MT7530_PDMA_GLO_CFG, (mt7530_read(priv, MT7530_PDMA_GLO_CFG) & ~0xf) | 0x10);RX_THRES=16表示:DMA引擎攒够16个包才触发一次RX中断。实测效果: