以太网扫盲(一)数据链路层和物理层,以及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 处理网络数据的方式是:
-
网卡收到一个字节,触发一次中断。
-
CPU 停下当前工作,把这个字节从网卡寄存器读到内存。
-
重复 1500 次(一个以太网帧的大小)。
-
处理完一个包,CPU 回去继续原来的工作。
问题:千兆以太网每秒可能收到十几万个包,每个包 1500 字节。如果每个字节都要 CPU 亲自搬运,CPU 将被完全占满,根本无法处理协议栈和应用逻辑。
有了 DMA 之后:
-
CPU 预先在内存中分配好缓冲区,并将缓冲区地址告诉 DMA 控制器。
-
网卡收到数据后,DMA 控制器自动将数据直接写入内存,整个过程 CPU 完全不用参与。
-
整个数据包接收完成后,网卡才触发一次中断,通知 CPU:“数据已经在内存里了,你来处理吧。”
这样,CPU 的开销从 每字节一次中断 降为 每包一次中断,效率提升数千倍。
二、DMA 在网口驱动中的典型工作流程
接收数据(RX)
-
驱动初始化:在内存中分配一块区域作为接收缓冲区,并将这块内存的物理地址写入 DMA 描述符。
-
启动 DMA:配置 DMA 控制器,告诉它“数据来了就往这个地址放”。
-
硬件工作:网卡收到数据,DMA 自动将数据从网卡 FIFO 搬运到指定的内存缓冲区。
-
通知 CPU:整个数据包搬运完成后,网卡触发中断。
-
驱动处理:CPU 在中断服务程序中,直接从内存中读取已就绪的数据包,交给协议栈。
发送数据(TX)
-
协议栈准备:CPU 将要发送的数据包准备好放在内存中。
-
驱动配置:驱动将数据包的物理地址和长度写入 DMA 发送描述符,然后通知 DMA 控制器。
-
DMA 搬运:DMA 控制器自动将数据从内存搬运到网卡的发送 FIFO,网卡逐位发送到物理介质。
-
完成通知:发送完成后,网卡触发中断,CPU 回收缓冲区。
三、关键数据结构:描述符环(Descriptor Ring)
DMA 需要一种机制来管理多个数据包,因为网络数据是连续不断、大小不一的。硬件通常采用 描述符环(环形队列) 来管理。
每个描述符是一个小结构体(通常 16 字节或 32 字节),包含:
• 缓冲区指针:指向实际数据所在的内存地址
• 长度:数据长度
• 状态位:如 OWN 位(表示当前是硬件拥有还是软件拥有)、完成标志等
驱动和 DMA 控制器通过这个环形队列协作,CPU 处理完一个描述符后将其重新放回队列,形成循环。
四、DMA 的类型与驱动开发
在网口驱动中,通常使用 总线主控 DMA(Bus Mastering DMA):
-
DMA 控制器集成在网卡内部(而不是主板上的独立 DMA 控制器)
-
网卡作为总线主控,直接发起对内存的读写
-
常见于 PCIe 网卡、SoC 内置的以太网 MAC(如 STM32 的 Ethernet DMA)
这种模式下,驱动需要做的是:
-
分配物理连续的****内存(或通过 IOMMU/SMMU 实现非连续映射)
-
将缓冲区的物理地址写入网卡的 DMA 描述符
-
确保在 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 执行以下步骤:
-
告诉 DMA 控制器“把下一个收到的包放到地址 A”
-
等待 DMA 完成
-
处理包
-
再次告诉 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...)。
接收环初始化步骤:
-
在连续物理内存中分配一个描述符数组
rx_desc[0..N-1]。 -
为每个描述符分配一个固定大小的接收缓冲区(如 2048 字节),并填写缓冲区指针。
-
设置每个描述符的控制字段:缓冲区长度、清空状态标志,并将 OWN 位设为 1(交给硬件)。
-
将最后一个描述符的“链结束”或“环回”标志设置为指向第一个描述符(硬件支持环形)。
-
将描述符数组的物理基地址写入 DMA 控制器的
RX_DESC_BASE寄存器,将长度N-1写入RX_DESC_LEN寄存器。 -
启动 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 控制器)行为
-
等待网络数据包到达。
-
当收到一个包时,DMA 查看当前指向的描述符:
-
检查 OWN 位是否为 1。如果是,说明该描述符可用。
-
将包数据直接写入该描述符指定的缓冲区。
-
更新描述符的状态字段(实际数据长度、错误标志等)。
-
将 OWN 位清零(交还给软件)。
-
移动当前指针到下一个描述符。
-
-
重复步骤 2,直到环形队列满(所有描述符 OWN=0)或者收到中断阈值触发。
-
触发接收中断,通知 CPU 有数据包已就绪。
3、软件(驱动)行为(中断或轮询中)
-
读取当前软件维护的接收索引(
rx_sw_idx),找到第一个 OWN=0 的描述符。 -
提取缓冲区指针和数据长度,将数据交给上层协议栈(或应用)。
-
一旦数据处理完成(或为了重用),驱动重新初始化该描述符:
-
(可选)重新分配缓冲区,或重用原缓冲区(注意:如果上层保存了数据指针,则需要新分配,否则直接复用)。
-
重置状态字段。
-
将 OWN 位重新置为 1。
-
更新
rx_sw_idx到下一个描述符。
-
-
如果硬件支持“尾指针”寄存器,驱动在重新填充了一批描述符后,需要更新 DMA 的尾指针,告知硬件又有新的可用描述符。
注意:头指针和尾指针机制。很多 DMA 控制器使用生产者-消费者模型:软件写尾指针,硬件消费头指针。当软件将 OWN 置 1 后,必须更新尾指针寄存器,硬件才知道有新描述符可用。
五、发送方向的描述符环
发送环与接收环对称,但数据流方向相反。
一、初始状态
-
所有发送描述符的 OWN = 0(软件拥有)。
-
缓冲区指针为空或指向预分配的空缓冲区(也可动态指定)。
二、软件(驱动)行为(上层要发送数据时)
-
找到一个 OWN=0 的发送描述符(由
tx_sw_idx指示)。 -
将待发送数据的物理地址填入缓冲区指针(注意:数据必须驻留在内存中直到发送完成)。
-
设置长度、VLAN 等字段。
-
将 OWN 位置 1(交给硬件)。
-
更新尾指针,通知 DMA 有新的发送请求。
-
如果硬件支持,写“发送请求”寄存器触发 DMA 立即开始发送。
三、硬件行为
-
DMA 控制器检查当前发送描述符(由头指针指示)。
-
如果 OWN=1,从缓冲区读取数据,发送到 MAC 控制器。
-
发送完成后,清除 OWN 位(置 0),可设置“发送完成”状态位。
-
移动头指针到下一个描述符。
-
如果使能了发送完成中断,触发中断通知软件。
四、软件回收
- 在发送完成中断中,驱动遍历 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 来实现。

浙公网安备 33010602011771号