2. 网卡硬件基础:PCIe总线与网卡接口、MAC与PHY芯片功能、网卡寄存器与DMA引擎、Ring Buffer原理

在深入Linux网络驱动源码之前,必须牢固掌握网卡硬件的核心组成与数据通路。本章将拆解现代网卡的四个关键硬件模块:PCIe总线接口、MAC与PHY芯片、寄存器与DMA引擎以及Ring Buffer(环形缓冲区)。理解这些基础是编写高效、稳定网卡驱动的先决条件。

2.1 PCIe总线与网卡接口

绝大多数独立网卡(如Intel I350、Mellanox ConnectX系列)通过PCI Express(PCIe)总线与主机CPU和内存相连。PCIe采用点对点串行差分信号传输,取代了传统的并行PCI总线,提供了更高的带宽和更低的延迟。

2.1.1 PCIe拓扑与配置空间

在系统启动时,BIOS/UEFI会枚举PCIe总线树,为每个设备分配总线号、设备号、功能号(BDF)以及内存/IO地址空间。网卡驱动通过读取PCI配置空间(Configuration Space)来获取设备ID、厂商ID、中断线、BAR(基地址寄存器)等信息。

PCIe配置空间字段 偏移 说明
Vendor ID 0x00 厂商编号,如Intel为0x8086
Device ID 0x02 设备型号编号
Command/Status 0x04 控制IO、内存、总线主控使能
BAR0 ~ BAR5 0x10 ~ 0x24 基地址寄存器,映射MMIO或IO端口
Capabilities Pointer 0x34 指向PCIe能力链表
Interrupt Line 0x3C 中断向量号(传统INTx)
驱动开发要点: 在Linux中,pci_alloc_irq_vectors() 用于分配MSI/MSI-X中断向量,而 pci_iomap() 用于将BAR映射的MMIO地址转换为CPU可访问的虚拟地址。

2.1.2 PCIe数据流与DMA

网卡通过PCIe总线进行DMA(直接内存访问),将网络数据包直接写入主机内存(RX路径),或从主机内存读取数据发送到网络(TX路径),无需CPU逐字节拷贝。PCIe事务层包(TLP)负责承载内存读写请求和完成报文。

关键概念: 网卡作为PCIe Bus Master(总线主控),可以主动发起DMA读写。驱动必须在初始化时设置 PCI_COMMAND_MASTER 标志位。
PCIe拓扑与网卡连接示意 PCIe拓扑与网卡连接示意 CPU / Root Complex PCIe Switch NIC 1 (PCIe EP) NIC 2 (PCIe EP) DDR 内存 DMA:网卡直接读写内存,无需CPU介入 驱动通过BAR映射访问网卡寄存器

2.2 MAC与PHY芯片功能

网卡的数据链路层和物理层功能通常由两个核心芯片(或集成在一个芯片内)完成:MAC(Media Access Control) 和 PHY(Physical Layer)。

2.2.1 MAC芯片

MAC层负责数据帧的封装与解封装、地址过滤(如MAC地址匹配)、帧校验(FCS)、流量控制(Pause帧)以及CSMA/CD(半双工模式)。在驱动层面,MAC通常通过内部总线(如MII/GMII/RGMII)与PHY连接。

2.2.2 PHY芯片

PHY层负责信号的编码/解码、串行化/解串行化、链路协商(Auto-Negotiation)、以及物理介质接口(如RJ45、SFP+光模块)。PHY通过MDIO/MDC管理接口被MAC或CPU配置。

特性 MAC PHY
主要功能 帧封装、地址过滤、流量控制 信号编码、链路协商、介质接口
管理接口 PCIe(与主机通信) MDIO/MDC(与MAC通信)
寄存器 控制/状态寄存器(CSR) PHY特定寄存器(如BMCR、BMSR)
驱动关注点 DMA引擎、Ring Buffer、中断 链路状态、速率、双工模式
常见陷阱: 许多SoC集成MAC,但外部PHY通过RGMII连接。驱动初始化时必须通过MDIO读写PHY寄存器,等待链路建立(Link Up),否则数据无法收发。
MAC与PHY芯片连接架构 MAC与PHY芯片连接架构 主机 / CPU MAC 控制器 (集成DMA引擎) PHY 芯片 (Marvell/Broadcom) RJ45 / SFP+ MDIO/MDC 管理接口 MAC与PHY通过MII/GMII/RGMII传输数据 驱动通过MDIO读写PHY寄存器

2.3 网卡寄存器与DMA引擎

网卡内部包含一组控制/状态寄存器(CSR),驱动通过MMIO(Memory-Mapped I/O)访问这些寄存器来配置网卡、启动DMA、查询状态。DMA引擎是网卡的核心数据搬运模块。

2.3.1 关键寄存器分类

  • 控制寄存器: 全局复位、中断使能、链路控制、流控配置。
  • 状态寄存器: 链路状态、中断状态、错误计数、队列状态。
  • 描述符基址寄存器: 指向Ring Buffer在内存中的物理地址。
  • DMA控制寄存器: 启动/停止DMA引擎、设置DMA阈值。

2.3.2 DMA引擎工作模式

现代网卡支持多队列DMA,每个队列对应一个独立的Ring Buffer对(TX/RX)。DMA引擎根据哈希或流分类将数据包分发到不同队列,实现多核CPU负载均衡(RSS/Flow Director)。

性能关键: 驱动在初始化时分配DMA缓冲区(使用 dma_alloc_coherent() 或 dma_map_single()),并将物理地址写入网卡描述符基址寄存器。网卡DMA引擎自动从Ring Buffer中取描述符执行传输。
网卡DMA引擎与寄存器交互 网卡DMA引擎与寄存器交互 主机内存 TX Ring Buffer RX Ring Buffer 数据包缓冲区 网卡芯片 DMA 引擎 CSR 寄存器组 描述符获取器 DMA 读写 驱动 MMIO 读写 驱动配置描述符基址寄存器 DMA引擎自动搬运数据

2.4 Ring Buffer原理

Ring Buffer(环形缓冲区)是网卡驱动与硬件DMA引擎之间交换数据描述符的核心数据结构。它本质上是一个在主机内存中分配的固定大小的环形数组,每个元素称为一个描述符(Descriptor)。

2.4.1 描述符结构

每个描述符通常包含以下字段(以Intel I350为例):

struct e1000_tx_desc {
    __le64 buffer_addr;   // 数据缓冲区的物理地址
    __le16 length;        // 数据长度
    __le8  cso;           // 校验和卸载设置
    __le8  cmd;           // 命令(如EOP、RS)
    __le16 olinfo_status; // 状态与卸载信息
};

2.4.2 发送(TX)流程

  1. 驱动将待发送的数据包放入DMA缓冲区,填写TX描述符(地址、长度、命令)。
  2. 驱动更新尾指针寄存器(Tail Register),通知网卡有新的描述符待处理。
  3. 网卡DMA引擎从Ring Buffer中读取描述符,根据描述符中的地址发起DMA读操作,将数据从内存搬运到网卡内部FIFO。
  4. 网卡发送完成后,更新描述符的状态字段,并触发中断(或通过写回状态描述符)。
  5. 驱动在中断处理或轮询中回收已完成的描述符,释放缓冲区。

2.4.3 接收(RX)流程

  1. 驱动预先分配空的DMA缓冲区,填写RX描述符(地址、长度),并更新尾指针。
  2. 网卡收到数据包后,DMA引擎将数据写入描述符指向的缓冲区。
  3. 网卡更新描述符状态(如长度、错误标志),并触发中断或写回描述符。
  4. 驱动在NAPI轮询中遍历RX Ring Buffer,处理已接收的数据包,并重新填充新的空描述符。
驱动优化关键: 保持Ring Buffer大小适中(如256~4096个描述符)。太小会导致丢包(RX overrun),太大会增加延迟和缓存压力。使用 ethtool -g eth0 可查看当前Ring Buffer参数。
Ring Buffer 数据流示意 Ring Buffer 数据流示意 TX Ring Buffer (主机内存) Desc 0 Desc 1 Desc 2 ... (每个描述符指向一个DMA缓冲区) RX Ring Buffer (主机内存) Desc 0 Desc 1 Desc 2 ... (驱动预先填充空缓冲区地址) 网卡 DMA 引擎 读取/写入描述符 DMA 读 (TX) DMA 写 (RX) 驱动通过更新尾指针 通知网卡新描述符就绪

2.4.4 头尾指针机制

Ring Buffer通过头指针(Head Pointer)和尾指针(Tail Pointer)管理生产者和消费者关系:

  • 驱动(生产者TX/消费者RX): 驱动通过写尾指针通知网卡有新的描述符可用(TX),或通过读头指针知道哪些描述符已被网卡消费(RX)。
  • 网卡(消费者TX/生产者RX): 网卡通过读尾指针知道有新的TX描述符待处理,通过写头指针通知驱动RX描述符已填充数据。
避免Race Condition: 驱动和网卡通过DMA一致性内存共享描述符状态。驱动在更新尾指针前必须确保描述符已完全写入内存(使用 wmb() 写屏障),网卡硬件则保证描述符读取的顺序性。

掌握Ring Buffer的运作机制,是理解NAPI、中断合并、零拷贝等高级网卡驱动技术的基础。下一层我们将深入Linux内核的网络设备驱动框架,将这里的硬件知识转化为实际的驱动代码。