以太网扫盲(一)数据链路层和物理层,以及DMA和环形缓冲区

主要介绍以太网的MAC(Media Access Control,MAC层,即媒体访问控制子层协议)和PHY(物理层)之间的MII(Media Independent Interface ,媒体独立接口),以及MII的各种衍生版本——GMII、SGMII、RMII、RGMII等。

简介

从硬件的角度看,以太网接口电路主要由MAC(Media Access Control)控制器和物理层接口PHY(Physical Layer,PHY)两大部分构成。如下图所示:

DMA控制器通常属于CPU的一部分,用虚线放在这里是为了表示DMA控制器可能会参与到网口数据传输中。

但是,在实际的设计中,以上三部分并不一定独立分开的。 由于,PHY整合了大量模拟硬件,而MAC是典型的全数字器件。考虑到芯片面积及模拟/数字混合架构的原因,通常,将MAC集成进微控制器而将PHY留在片外。更灵活、密度更高的芯片技术已经可以实现MAC和PHY的单芯片整合。可分为下列几种类型:

  • CPU集成MAC与PHY。目前来说并不多见

  • CPU集成MAC,PHY采用独立芯片。比较常见

  • CPU不集成MAC与PHY,MAC与PHY采用集成芯片。比较常见

MAC及PHY工作在OSI七层模型的数据链路层****和物理层。具体如下:

MAC

一、MAC 的主要功能

2.1 帧封装与解封装

  • 发送时:MAC 接收上层传来的数据(如 IP 包),在头部添加 MAC 源地址、目标地址、类型/长度字段,尾部添加 FCS(帧校验序列),形成一个标准以太网

  • 接收时:MAC 从 PHY 收到的比特流中,根据前导码和帧起始定界符定位帧边界,校验 FCS 是否正确,剥离帧头和尾,将有效数据传递给上层。

2.2 介质访问控制(CSMA/CD 或全双工)

  • 在半双工模式下,MAC 通过 CSMA/CD(载波监听多点接入/碰撞检测)机制决定何时可以发送数据,处理冲突。

  • 在全双工模式下,MAC 不再需要冲突检测,发送和接收可同时进行。

2.3 MAC 地址过滤

  • 每个网卡有唯一的 48 位 MAC 地址。MAC 控制器通常会根据配置,只接收目标地址匹配本机 MAC、广播地址或组播地址的帧,丢弃不匹配的帧,减轻 CPU 负担。

2.4 DMA 与描述符管理

  • 现代 MAC 控制器内部集成了 DMA 引擎,通过 描述符环 自动在内存和 MAC 之间搬运数据,无需 CPU 逐字节参与,大大提高性能。驱动只需要维护描述符和缓冲区,告诉 DMA 引擎“数据在哪里、放在哪里”。

二、驱动开发中 MAC 相关的核心工作

在编写网口驱动时,MAC 部分涉及:

  • 初始化:配置 MAC 的寄存器,设置工作模式(全/半双工)、MAC 地址、帧过滤规则(如只接收本机或所有帧)、流控等。

  • 与 DMA 配合:初始化 DMA 描述符环,配置 DMA 寄存器(基址、长度、模式),启动 DMA 收发。

  • 中断处理:处理 MAC 和 DMA 上报的接收完成、发送完成、错误等中断。

  • PHY 交互:通过 MAC 的 MDIO 接口读写 PHY 寄存器,配置自动协商、读取链路状态。

  • 速率/双工同步:当 PHY 链路协商完成后,根据结果动态调整 MAC 的相应寄存器(如速度、双工模式)。

三、MAC 与 PHY 的分工

功能 MAC 负责 PHY 负责
帧格式 组装/解析 MAC 帧 只负责比特流
控制 全双工/半双工、流控 速率、双工协商、链路状态
接口 提供 MII/RMII 等数字接口 提供模拟/数字转换
管理 通过 MDIO 配置 PHY 响应 MDIO 配置,报告状态
简单说,MAC 处理“数字域”的帧,PHY 处理“模拟域”的比特。

PHY

PHY 是 物理层收发器(Physical Layer Transceiver)的缩写,是以太网硬件架构中负责处理物理信号传输的模块。它把 MAC 发来的数字比特流转换成适合在网线(或光纤)上传输的模拟电信号(或光信号),反过来也将从介质上接收到的模拟信号还原成数字比特流交给 MAC。

一、PHY 的核心功能

1.1 信号转换

  • 发送方向:从 MAC 通过 MII/RMII 等接口接收并行数字数据 → 编码(如 MLT-3、PAM5 等) → 串行化 → 转换为适合介质的差分模拟信号(如差分电压)输出到网线。

  • 接收方向:从网线接收微弱模拟信号 → 放大、均衡 → 时钟恢复 → 解码 → 转换为并行数字数据交给 MAC。

1.2 自动协商(Auto-Negotiation)

  • PHY 与对端 PHY 通过交换特定的脉冲(Fast Link Pulse)自动协商出双方都支持的最高速率(10/100/1000 Mbps)和双工模式(全双工/半双工)。

  • 驱动可以通过读取 PHY 的状态寄存器获知协商结果,并相应配置 MAC 控制器。

1.3 链路状态监测

  • PHY 持续监测链路是否存在有效信号(载波),通过寄存器(如 BMSR)中的“Link Status”位指示链路是否建立。

  • 当网线插拔或链路抖动时,PHY 会更新状态,驱动可以轮询或通过中断感知,通知上层协议栈。

1.4 介质相关接口

  • PHY 适应不同的物理介质:双绞线(RJ45)、光纤、背板等。

  • 对于铜缆,还负责处理极性校正、交叉检测与自动切换(Auto-MDIX)。

二、PHY 与 MAC 的分工

功能 MAC PHY
所处层 数据链路层(MAC 子层) 物理层
数据形式 数字帧(并行数据) 串行模拟信号(电/光)
核心任务 帧封装/解封、寻址、差错检测、介质访问控制 编码/解码、串行化/解串、信号放大、自动协商
接口 通过 MII/RMII/GMII 等连接 PHY 通过 MII/RMII/GMII 等连接 MAC,通过 MDIO/MDC 接受管理
地址 有 MAC 地址 无地址,只提供物理层服务
在实际硬件中,MAC 可以集成在 SoC 内部,PHY 通常是一个独立芯片(或集成在 RJ45 插座内,如某些网络变压器一体模块),二者通过 PCB 走线连接。

三、PHY 与 MAC 之间的硬件接口

3.1 数据接口(用于传输数据帧)

常见的有:

  • MII(Media Independent Interface):25MHz 时钟,4 位数据,支持 10/100 Mbps。

  • RMII(Reduced MII):50MHz 时钟,2 位数据,简化引脚数。

  • GMII(Gigabit MII):125MHz 时钟,8 位数据,支持 1000 Mbps。

  • RGMII(Reduced GMII):双沿采样,8 位数据,降低引脚数。

这些接口是数字的,直接连接 MAC 和 PHY,传输的是经过编码前的原始帧数据。

3.2 管理接口(用于配置和监控 PHY)

  • MDIO(Management Data Input/Output) + MDC(Management Data Clock)

  • 这是一个两线串行总线(类似 I²C),MAC(或 CPU)通过它读写 PHY 内部的寄存器。

  • 驱动通过 MDIO 完成:

    • 复位 PHY

    • 读取 PHY ID 以识别型号

    • 启动/读取自动协商结果

    • 读取链路状态、速度、双工

    • 配置特殊功能(如节能、中断使能)

四、在网口驱动中 PHY 相关的典型工作

4.1 初始化

• 复位 PHY(硬件复位引脚或软件复位寄存器)

• 读取 PHY ID,验证硬件连接

• 配置自动协商或强制模式(通常建议自动协商)

• 启动自动协商

4.2 链路状态监控

• 驱动通过轮询(每隔几百毫秒)或 PHY 中断,读取 PHY 的“Link Status”寄存器。

• 当链路状态发生变化(up/down),驱动需要:

◦ 调用上层注册的回调(如 link_callback)

◦ 如果链路建立,读取协商结果(速度、双工),并相应配置 MAC 控制器(例如调整 MAC 的时钟源、双工模式)

◦ 更新 MAC 的寄存器以匹配 PHY 的工作模式

4.3 异常处理

• 链路不稳定时可能触发错误计数器,驱动可读取 PHY 的统计寄存器(如 CRC 错误、符号错误)用于诊断。

4.4 节能管理

• 支持 EEE(Energy Efficient Ethernet)的 PHY 在空闲时可进入低功耗状态,驱动需要配合 MAC 进行相应配置。

五、总结

概念 说明
PHY 是什么 物理层收发器,负责模拟信号与数字比特流的转换
主要功能 信号转换、自动协商、链路状态监测、介质适配
与 MAC 的关系 MAC 负责帧处理,PHY 负责物理传输,二者通过 MII 数据接口和 MDIO 管理接口连接
驱动中处理内容 PHY 复位、自动协商配置、链路状态轮询/中断、速度/双工同步到 MAC
常见硬件形态 独立芯片,或集成在 RJ45 连接器内(如网口变压器模块)

DMA

DMA 是 Direct Memory Access(直接存储器访问)的缩写。

简单理解:DMA 是一个硬件模块,它可以在不占用 CPU 的情况下,独立地在内存和外设之间(或内存与内存之间)搬运数据。

一、为什么需要 DMA?(以网口为例)

如果没有 DMA,CPU 处理网络数据的方式是:

  1. 网卡收到一个字节,触发一次中断。

  2. CPU 停下当前工作,把这个字节从网卡寄存器读到内存。

  3. 重复 1500 次(一个以太网帧的大小)。

  4. 处理完一个包,CPU 回去继续原来的工作。

问题:千兆以太网每秒可能收到十几万个包,每个包 1500 字节。如果每个字节都要 CPU 亲自搬运,CPU 将被完全占满,根本无法处理协议栈和应用逻辑。

有了 DMA 之后:

  1. CPU 预先在内存中分配好缓冲区,并将缓冲区地址告诉 DMA 控制器。

  2. 网卡收到数据后,DMA 控制器自动将数据直接写入内存,整个过程 CPU 完全不用参与

  3. 整个数据包接收完成后,网卡才触发一次中断,通知 CPU:“数据已经在内存里了,你来处理吧。”

这样,CPU 的开销从 每字节一次中断 降为 每包一次中断,效率提升数千倍。

二、DMA 在网口驱动中的典型工作流程

接收数据(RX)

  1. 驱动初始化:在内存中分配一块区域作为接收缓冲区,并将这块内存的物理地址写入 DMA 描述符。

  2. 启动 DMA:配置 DMA 控制器,告诉它“数据来了就往这个地址放”。

  3. 硬件工作:网卡收到数据,DMA 自动将数据从网卡 FIFO 搬运到指定的内存缓冲区。

  4. 通知 CPU:整个数据包搬运完成后,网卡触发中断。

  5. 驱动处理:CPU 在中断服务程序中,直接从内存中读取已就绪的数据包,交给协议栈。

发送数据(TX)

  1. 协议栈准备:CPU 将要发送的数据包准备好放在内存中。

  2. 驱动配置:驱动将数据包的物理地址和长度写入 DMA 发送描述符,然后通知 DMA 控制器。

  3. DMA 搬运:DMA 控制器自动将数据从内存搬运到网卡的发送 FIFO,网卡逐位发送到物理介质。

  4. 完成通知:发送完成后,网卡触发中断,CPU 回收缓冲区。

三、关键数据结构:描述符环(Descriptor Ring)

DMA 需要一种机制来管理多个数据包,因为网络数据是连续不断、大小不一的。硬件通常采用 描述符环(环形队列) 来管理。

每个描述符是一个小结构体(通常 16 字节或 32 字节),包含:

缓冲区指针:指向实际数据所在的内存地址

• 长度:数据长度

• 状态位:如 OWN 位(表示当前是硬件拥有还是软件拥有)、完成标志等

驱动和 DMA 控制器通过这个环形队列协作,CPU 处理完一个描述符后将其重新放回队列,形成循环

四、DMA 的类型与驱动开发

在网口驱动中,通常使用 总线主控 DMA(Bus Mastering DMA):

  • DMA 控制器集成在网卡内部(而不是主板上的独立 DMA 控制器)

  • 网卡作为总线主控,直接发起对内存的读写

  • 常见于 PCIe 网卡、SoC 内置的以太网 MAC(如 STM32 的 Ethernet DMA)

这种模式下,驱动需要做的是:

  1. 分配物理连续的****内存(或通过 IOMMU/SMMU 实现非连续映射)

  2. 将缓冲区的物理地址写入网卡 DMA 描述符

  3. 确保在 DMA 传输期间,CPU 不访问正在被 DMA 使用的内存区域(通过 cache 一致性维护或使用 non-cacheable 内存)

五、DMA 与 Cache 一致性

这是嵌入式驱动开发中容易出问题的地方。

CPU 有 Cache(高速缓冲存储器),DMA 直接访问物理内存。如果 CPU 修改了内存中的数据但还在 Cache 中(未写回),DMA 读取时读到的是旧数据;反之,DMA 写入了新数据,CPU 从 Cache 读取时可能还是旧数据。

解决方案:

  • 使用 一致性 DMA 映射(如 Linux 的 dma_alloc_coherent),分配的内存是 non-cacheable 或硬件自动维护一致性的

  • 或在使用前手动调用 Cache 刷新/无效化 操作(如 dma_map_single / dma_unmap_single

DMA描述符环形缓冲区(描述符环)

在网口驱动中,描述符环 是连接 CPU(驱动软件)和 DMA 控制器(硬件)的“契约”数据结构。它让 DMA 能够自主地处理多个数据包,而不需要 CPU 为每个包、每个字节单独下达指令。

简单理解:描述符环就像一个流水线上的工单队列。CPU 作为“管理员”预先在内存中创建一批工单(描述符),每个工单上写明了“数据应该放在哪块缓冲区”、“数据长度多少”、“当前由谁处理”。DMA 控制器作为“工人”按照工单顺序自动工作,完成后更新工单状态,然后继续下一个。整个流程是循环的,所以叫“环”。

一、为什么需要描述符环?

如果没有描述符环,每次收发一个包都需要 CPU 执行以下步骤:

  1. 告诉 DMA 控制器“把下一个收到的包放到地址 A”

  2. 等待 DMA 完成

  3. 处理包

  4. 再次告诉 DMA 下一个地址

这样 CPU 仍然要参与每个包的配置,效率低。描述符环允许 CPU 一次性提交一批缓冲区,DMA 控制器自动遍历这个环,收发包时按顺序使用缓冲区,完成后再通过中断批量通知 CPU。这样 CPU 只需在初始化时设置好环,然后处理完成事件即可,极大减少了配置开销

二、描述符的结构

一个典型的 DMA 描述符(以千兆以太网为例)通常包含以下几个关键字段(硬件定义,通常 16 或 32 字节对齐):

td {white-space:nowrap;border:0.5pt solid #dee0e3;font-size:10pt;font-style:normal;font-weight:normal;vertical-align:middle;word-break:normal;word-wrap:normal;}
字段 含义 说明
缓冲区指针 指向内存中实际数据缓冲区的物理地址 DMA 控制器直接使用该地址读写内存,必须物理连续且可访问
控制/状态 包含长度、OWN 位、中断使能、错误标志等 不同硬件有差异,但核心是 OWN(所有权) 位
扩展字段 VLAN 标记、校验和卸载状态、时间戳等 用于高级功能

OWN 位 是描述符环的灵魂:

  • OWN = 1:表示当前描述符由硬件(DMA 控制器)拥有。硬件可以读写该描述符指向的缓冲区。驱动软件不能触碰。

  • OWN = 0:表示当前描述符由软件(驱动)拥有。驱动可以处理其中的数据或重新初始化,硬件不会使用。

通过这一个位,实现了软件和硬件对描述符的安全交接。

三、描述符环的初始化

假设我们创建了 N 个接收描述符和 N 个发送描述符(通常 N=64,128,256...)。

接收环初始化步骤:

  1. 在连续物理内存中分配一个描述符数组 rx_desc[0..N-1]

  2. 为每个描述符分配一个固定大小的接收缓冲区(如 2048 字节),并填写缓冲区指针。

  3. 设置每个描述符的控制字段:缓冲区长度、清空状态标志,并将 OWN 位设为 1(交给硬件)。

  4. 将最后一个描述符的“链结束”或“环回”标志设置为指向第一个描述符(硬件支持环形)。

  5. 将描述符数组的物理基地址写入 DMA 控制器的 RX_DESC_BASE 寄存器,将长度 N-1 写入 RX_DESC_LEN 寄存器。

  6. 启动 DMA 接收引擎。

发送环初始化步骤类似,但发送缓冲区初始时为空,OWN 位为 0(软件拥有),等待上层协议栈填入数据后才会交给硬件。

四、描述符环的工作流程(以接收为例)

1、初始状态

  • 环上所有描述符的 OWN = 1(硬件拥有)。

  • 硬件 DMA 控制器内部有一个当前指针(cur_rx_desc),指向第一个描述符。

七、描述符环的关键设计要点

7.1 物理地址要求

  • 描述符环本身必须在物理内存中连续(因为硬件通过基址+偏移访问)。

  • 缓冲区不一定要求连续(硬件描述符通常只支持一个缓冲区指针,所以缓冲区本身需要物理连续或通过多个描述符链支持,但大多数简单 DMA 要求每个描述符指向一个连续的物理块)。

7.2 Cache 一致性

  • 描述符环和缓冲区通常位于 DMA 一致性内存(如 Linux 的 dma_alloc_coherent),或者驱动在每次访问前后手动进行 Cache 刷新/无效化。因为 CPU 和 DMA 可能同时访问这些内存,若不处理会导致数据不一致。

7.3 中断优化

  • 可以配置硬件在接收/发送了一定数量的包后再触发中断(中断聚合),减少中断频率,提高吞吐量。

  • 也可以在最后一个描述符上设置“中断使能”位,让硬件在完成该描述符时才触发中断。

7.4 描述符环的大小

  • 环越大,抗瞬时突发能力越强,但占用更多内存和可能增加延迟。

  • 典型值:64、128、256。现代 10G 网卡可能使用 1024 或更大。

八、总结

td {white-space:nowrap;border:0.5pt solid #dee0e3;font-size:10pt;font-style:normal;font-weight:normal;vertical-align:middle;word-break:normal;word-wrap:normal;}
概念 说明
描述符环 一个环形数组,每个元素是一个描述符,定义了 DMA 传输的缓冲区地址、长度和控制信息
核心机制 通过 OWN 位实现软件与硬件之间的所有权交接,无锁协调
驱动职责 初始化环、管理索引、处理完成事件、重新填充描述符
硬件职责 遍历环,根据 OWN 位执行 DMA 传输,更新状态
优势 批量提交、减少 CPU 干预、支持高速网络数据流

理解描述符环是理解高性能网口驱动的关键。无论是 Linux 内核驱动、RTOS 驱动还是裸机驱动,其底层原理都是基于这种生产者-消费者环形队列模型。实际编写驱动时,你需要仔细阅读芯片手册中关于描述符格式DMA 控制器的章节,然后实现相应的初始化、接收处理、发送处理和中断服务例程。

2、硬件(DMA 控制器)行为

  1. 等待网络数据包到达。

  2. 当收到一个包时,DMA 查看当前指向的描述符:

    1. 检查 OWN 位是否为 1。如果是,说明该描述符可用。

    2. 将包数据直接写入该描述符指定的缓冲区。

    3. 更新描述符的状态字段(实际数据长度、错误标志等)。

    4. 将 OWN 位清零(交还给软件)。

    5. 移动当前指针到下一个描述符。

  3. 重复步骤 2,直到环形队列满(所有描述符 OWN=0)或者收到中断阈值触发。

  4. 触发接收中断,通知 CPU 有数据包已就绪。

3、软件(驱动)行为(中断或轮询中)

  1. 读取当前软件维护的接收索引(rx_sw_idx),找到第一个 OWN=0 的描述符。

  2. 提取缓冲区指针和数据长度,将数据交给上层协议栈(或应用)。

  3. 一旦数据处理完成(或为了重用),驱动重新初始化该描述符:

    1. (可选)重新分配缓冲区,或重用原缓冲区(注意:如果上层保存了数据指针,则需要新分配,否则直接复用)。

    2. 重置状态字段。

    3. 将 OWN 位重新置为 1。

    4. 更新 rx_sw_idx 到下一个描述符。

  4. 如果硬件支持“尾指针”寄存器,驱动在重新填充了一批描述符后,需要更新 DMA 的尾指针,告知硬件又有新的可用描述符。

注意:头指针和尾指针机制。很多 DMA 控制器使用生产者-消费者模型:软件写尾指针,硬件消费头指针。当软件将 OWN 置 1 后,必须更新尾指针寄存器,硬件才知道有新描述符可用。

五、发送方向的描述符环

发送环与接收环对称,但数据流方向相反。

一、初始状态

  • 所有发送描述符的 OWN = 0(软件拥有)。

  • 缓冲区指针为空或指向预分配的空缓冲区(也可动态指定)。

二、软件(驱动)行为(上层要发送数据时)

  1. 找到一个 OWN=0 的发送描述符(由 tx_sw_idx 指示)。

  2. 将待发送数据的物理地址填入缓冲区指针(注意:数据必须驻留在内存中直到发送完成)。

  3. 设置长度、VLAN 等字段。

  4. 将 OWN 位置 1(交给硬件)。

  5. 更新尾指针,通知 DMA 有新的发送请求。

  6. 如果硬件支持,写“发送请求”寄存器触发 DMA 立即开始发送。

三、硬件行为

  1. DMA 控制器检查当前发送描述符(由头指针指示)。

  2. 如果 OWN=1,从缓冲区读取数据,发送到 MAC 控制器。

  3. 发送完成后,清除 OWN 位(置 0),可设置“发送完成”状态位。

  4. 移动头指针到下一个描述符。

  5. 如果使能了发送完成中断,触发中断通知软件。

四、软件回收

  • 在发送完成中断中,驱动遍历 OWN=0 且已完成发送的描述符,释放或重用缓冲区,然后更新软件索引,准备下次发送。

六、环形队列的索引管理

通常驱动软件维护两个指针(索引):

  • sw_tail:软件下一次可以使用的空闲描述符位置(生产者指针)。

  • hw_head:硬件当前正在处理的描述符位置(消费者指针)。

但由于 DMA 控制器内部维护自己的头指针,驱动往往只需要维护一个软件当前处理指针(sw_idx)和一个硬件尾部指针寄存器。

更常见的做法是:

  • 接收:软件维护 rx_consume_idx,表示下一个待处理的描述符(OWN=0 的起始)。驱动处理完一个后,将其重新初始化并设置 OWN=1,同时更新一个 rx_produce_idx。最后将 rx_produce_idx 写入硬件的尾指针寄存器。

  • 发送:软件维护 tx_produce_idx,表示下一个可用的空闲描述符。上层调用发送时,在此位置填入数据并设置 OWN=1,然后递增 tx_produce_idx 并更新硬件尾指针。硬件发送完成后,在中断中更新 tx_consume_idx,回收描述符。

环形队列为空或满的判断通常通过 (produce_idx - consume_idx) % N 来实现。

posted @ 2026-04-03 11:19  AureoleZhang  阅读(46)  评论(0)    收藏  举报