TI EMAC/MDIO嵌入式以太网控制器:从硬件原理到驱动开发的深度解析

📅 2026/7/21 12:15:33 👁️ 阅读次数 📝 编程学习
TI EMAC/MDIO嵌入式以太网控制器:从硬件原理到驱动开发的深度解析

1. 项目概述

在嵌入式网络开发中,以太网控制器(EMAC)和其配套的管理数据输入输出(MDIO)接口是连接软件世界与物理网络硬件的关键桥梁。很多工程师在初次接触时,往往会把重点放在数据通路的配置上,比如DMA描述符、缓冲区管理,却容易忽视MDIO这个看似简单的“配置通道”。实际上,MDIO的稳定与高效,直接决定了PHY(物理层收发器)能否正确初始化、链路能否正常建立,是整个网络功能的地基。我经历过不少项目,调试了半天数据收发,最后发现是MDIO时钟配置不对,或者PHY地址没扫到,白白浪费了大量时间。

本文将以德州仪器(TI)的EMAC/MDIO模块为蓝本,深入解析其工作原理、寄存器操作细节以及在实际开发中的避坑指南。我们将超越手册的简单描述,重点探讨:MDIO模块如何通过硬件自动轮询减轻CPU负担?EMAC的接收/发送路径如何与DMA协同工作?以及在配置流控、自适应性能优化(APO)等高级功能时,有哪些必须注意的细节?无论你是正在调试一块新的嵌入式网络板卡,还是希望深入理解以太网控制器的内部机制,这篇文章都将提供从原理到实操的完整参考。

2. EMAC/MDIO模块整体架构与设计思路

在深入寄存器之前,我们首先要建立起对EMAC和MDIO在整个系统中定位的清晰认知。它们不是一个孤立的黑盒,而是嵌入式SoC(系统级芯片)中负责网络通信的专用协处理器子系统。

2.1 核心角色分工:MAC与PHY的管理者

以太网通信遵循OSI模型,EMAC和MDIO协同工作,分别负责不同层次:

  • EMAC (Ethernet Media Access Controller): 主要实现数据链路层的MAC子层功能。它的核心职责是处理“帧”:按照IEEE 802.3标准封装和解封装以太网帧,进行MAC地址过滤、CRC校验生成与验证、冲突检测(半双工模式下)以及流量控制。你可以把它想象成一个高度专业化的“交通警察”,负责数据包在本地总线(如CPU)和网络介质(通过PHY)之间的有序、可靠流动。
  • MDIO (Management Data Input/Output): 这是一个管理接口,专门用于配置和监控PHY设备。PHY是物理层器件,负责将MAC层的数字信号转换成可以在网线上传输的模拟信号,并处理链路协商、速率和双工模式选择等。MDIO遵循IEEE 802.3 Clause 22/45规范,通过两根线(MDC时钟线和MDIO数据线)以串行方式访问PHY内部多达32个寄存器。MDIO模块的价值在于,它将CPU从繁琐的、周期性的PHY状态查询中解放了出来。

2.2 架构设计精要:硬件加速与效率优先

TI的EMAC/MDIO模块设计体现了嵌入式系统对效率和确定性的追求:

  1. 独立的DMA引擎: EMAC模块内部集成了独立的接收和发送DMA引擎。这一点至关重要,它意味着数据在系统内存和EMAC的FIFO之间搬运,是由硬件DMA完成的,无需CPU参与。CPU只需要设置好描述符链表,处理中断即可。这种设计极大地降低了CPU负载,提高了数据吞吐量的确定性。
  2. 硬件辅助的PHY管理: MDIO模块不仅仅是提供一个读写PHY寄存器的通道。它的“全局PHY检测与链路状态监控”单元,在模块使能后,会自动、周期性地轮询所有32个可能的PHY地址。它会将哪些地址上有PHY响应(ALIVE寄存器)、哪些PHY当前链路已建立(LINK寄存器)这些信息实时更新在硬件寄存器中。软件只需要读取这些寄存器,就能瞬间获知全网PHY的状态,避免了软件轮询带来的延迟和CPU占用。
  3. 多通道与QoS支持: EMAC支持8个独立的发送和接收通道。这不仅仅是简单的8个队列,它允许系统实现初步的服务质量(QoS)和虚拟化功能。例如,可以将高优先级的网络流量分配到高优先级的发送通道;或者让不同的接收通道监听不同的MAC地址,使得单个EMAC可以模拟多个网络适配器的行为,这在网关或交换机类应用中非常有用。
  4. 丰富的统计与调试信息: 模块内置的统计逻辑能记录36种不同的以太网统计数据,如各种类型的帧计数、错误计数等。这对于网络性能监控、故障诊断和产品认证(如RFC 2544测试)提供了硬件级的便利。

注意: 虽然手册描述了强大的功能,但在具体的芯片上,这些功能可能受限于实际的引脚复用、时钟资源和内存带宽。在选型和方案设计阶段,务必查阅芯片的勘误表和具体型号的数据手册,确认EMAC/MDIO模块的完整功能是否可用。

3. MDIO模块深度解析与实操要点

MDIO接口是工程师与PHY对话的“嘴巴”和“耳朵”。理解其硬件自动化的机制,能让我们编写出更稳健、高效的驱动。

3.1 MDIO时钟生成器:稳定通信的基石

MDIO通信是同步串行通信,时钟(MDC)的稳定性是第一要务。MDIO时钟生成器通过对EMAC控制模块的外设时钟(peripheral clock)进行分频来产生MDC。

  • 时钟规格与配置: IEEE标准规定MDC时钟最高为2.5MHz,典型运行频率为1.0MHz。由于外设时钟频率因芯片和PLL配置而异,所以必须在软件中正确配置MDIO控制寄存器(CONTROL)中的CLKDIV分频位。
  • 分频计算示例: 假设你的系统外设时钟为100MHz,目标MDC时钟为1MHz。那么分频系数CLKDIV应设置为(100MHz / 1MHz) - 1 = 99。计算时切记“减一”,因为很多硬件分频器是计数到N后翻转,周期为N+1。
  • ** preamble(前导码)**:CONTROL寄存器中的PREAMBLE位控制是否在每次MDIO帧传输前发送32位“1”和“2”位“01”起始符的前导码。绝大多数PHY都需要前导码来同步。仅在确认你所使用的PHY明确支持无前导码模式时,才可禁用此功能,否则会导致通信失败。

3.2 全局PHY检测与链路监控:硬件自动化的魅力

这是MDIO模块最实用的功能之一,其工作流程完全由硬件自动完成:

  1. 自动枚举(Auto-detection): 一旦MDIO模块被使能(CONTROL.ENABLE=1),其内部状态机就会开始不间断地轮询MDIO地址0至31。对于每个地址,它尝试读取PHY的标准状态寄存器(通常为地址1的寄存器2)。
  2. 状态记录
    • ALIVE寄存器: 如果某个地址的PHY对读请求做出了有效响应(返回了有效的ACK),则该地址对应的比特位会被置1。这相当于一张“PHY在位图”。
    • LINK寄存器: 如果PHY不仅响应,并且其状态寄存器报告链路已建立(Link Up),则LINK寄存器中对应的比特位也会被置1。
  3. 软件查询: 驱动初始化时,无需自己写循环去探测PHY。只需要读取ALIVE寄存器,就能知道系统里连接了哪些PHY;读取LINK寄存器,就能立刻知道哪些PHY已经连上网线并协商成功。这极大地加快了系统启动和网络恢复的速度。

3.3 用户访问寄存器:主动配置PHY的通道

当我们需要主动配置PHY(如设置自协商参数、软件复位、读取特定诊断信息)时,就需要使用用户访问寄存器USERACCESSn(通常有USERACCESS0USERACCESS1两个,用于支持并发操作)。

关键寄存器位解析:

  • GO: 启动传输位。软件置1,硬件完成操作后清0。
  • WRITE: 读写方向。1为写,0为读。
  • PHYADR(5位): 目标PHY的地址(0-31)。
  • REGADR(5位): 目标PHY内部寄存器的地址(0-31)。
  • DATA(16位): 写入的数据或读回的数据。
  • ACK: 仅用于读操作。当GO位被硬件清0��读操作成功时,此位被置1。这是一个重要的状态位,用于判断读操作是否被PHY确认

操作流程的精髓在于“异步”: CPU配置好USERACCESSn寄存器并置位GO后,就可以去执行其他任务。MDIO模块会接管后续的串行时序生成、数据收发等所有工作。完成后,它会通过中断(如果使能)或清除GO位的方式来通知CPU。

3.4 链路变化中断:实现快速网络事件响应

网络链路的通断是一个关键事件。MDIO模块提供了硬件级的链路状态监控与中断上报机制。

  1. 配置监控: 通过USERPHYSELn寄存器,告诉MDIO模块你需要监控哪个PHY地址的链路状态。
  2. 使能中断: 设置USERPHYSELn.LINKINTENB位,使能该PHY的链路变化中断。
  3. 硬件监控与触发: MDIO模块会透明地(无需软件干预)定期读取被监控PHY的链路状态寄存器。一旦发现状态发生变化(Link Up -> Down 或 Down -> Up),就会置位LINKINTRAW寄存器中的相应位。
  4. 中断处理: 如果对应的中断未被屏蔽(通过LINKINTMASKED寄存器判断),则会向CPU产生一个中断。中断服务程序(ISR)需要读取LINKINTRAWLINKINTMASKED来确定是哪个PHY的链路发生了变化,并执行相应的网络重连或通知逻辑。

实操心得: 在驱动中,强烈建议使能链路变化中断,而不是采用轮询方式检查LINK寄存器。这不仅能降低CPU负载,更能实现亚秒级的链路状态感知,对于需要高网络可用性的应用(如工业控制、音视频流)至关重要。在中断服务程序里,记得要清除中断标志位。

4. EMAC模块核心机制与数据通路实现

理解了MDIO如何管理PHY后,我们来看数据是如何高速流动的。EMAC模块是数据平面的核心,其设计围绕着效率和可靠性展开。

4.1 发送路径详解:从内存到网线

发送数据的旅程始于CPU,终于PHY。

  1. 描述符准备: CPU在系统内存中准备好要发送的数据包,并构建一个或多个16字节的“缓冲区描述符”(Buffer Descriptor)。这个描述符包含了数据缓冲区的物理地址、长度、以及控制信息(如是否为帧起始SOP/帧结束EOP,是否传递CRCPASSCRC等)。
  2. 触发DMA: CPU将发送通道的“头描述符指针”(TXnHDP)寄存器更新为第一个描述符的地址。这个写操作就像扣动了扳机,发送DMA引擎被激活。
  3. 数据获取: 发送DMA引擎根据描述符,通过系统总线(经过EMAC控制模块的仲裁器)将数据从内存搬运到发送FIFO中。FIFO是一个深度为3个64字节单元的缓冲区,用于平滑总线访问和网络发送之间的速率差异。
  4. 帧发送: 当发送FIFO中的数据达到设定的阈值(FIFOCONTROL.TXCELLTHRESH)或一个完整的数据包已存入时,MAC发送器开始工作。它从FIFO中取出数据,为其添加前导码、帧起始定界符(SFD),如果需要还会计算并附加帧校验序列(FCS,即CRC32)。最后,按照MII/RMII接口的时序,将数据一位一位地交给PHY。
  5. 完成通知: 当整个数据包(或描述符链)发送完毕后,EMAC会更新描述符中的状态字段(通常通过DMA写回内存),并可能产生一个发送完成中断,通知CPU可以回收描述符和缓冲区内存了。

4.2 接收路径详解:从网线到内存

接收路径是发送路径的逆过程,但增加了地址过滤和错误检查。

  1. 资源预备: 在接收开始前,CPU需要在内存中准备一系列空的缓冲区,并用接收描述符链起来。同样,将接收通道的RXnHDP指向这个描述符链的首个描述符,并使能接收DMA(RXCONTROL.RXEN=1)。
  2. 帧接收与过滤: PHY将恢复出的数字信号和时钟送给MAC接收器。接收器检测到帧起始,开始接收数据。它会进行地址匹配检查:目的MAC地址是否与本地地址(8个通道的MAC地址)、广播地址或哈希匹配的组播地址相符?如果启用混杂模式(Promiscuous),则接收所有帧。不匹配的帧在此阶段被丢弃。
  3. 数据缓冲: 通过地址过滤的帧数据被存入接收FIFO
  4. DMA存储接收DMA引擎将数据从接收FIFO搬运到由当前接收描述符指向的系统内存缓冲区中。
  5. 完成处理: 当一个帧接收完成(或缓冲区满),DMA引擎会更新描述符状态(包括帧长度、可能出现的错误标志等),并可能产生接收完成中断。CPU的中断处理程序会解析描述符,将接收到的数据包传递给上层网络协议栈,并回收或分配新的描述符以准备接收下一个包。

4.3 多通道与流量分类:高级功能应用

EMAC的8个通道并非冗余,而是为了实现复杂的网络处理:

  • 发送优先级: 可以配置为轮询(Round-Robin)或固定优先级模式。在固定优先级下,通道7优先级最高,通道0最低。这允许高优先级的控制帧或实时数据优先被发送。
  • 接收分类: 这是更强大的功能。你可以为8个通道设置8个不同的MAC地址(仅最低字节不同),让EMAC像8个虚拟网卡一样工作。此外,可以通过RXMBPENABLE等寄存器,将广播帧、哈希匹配的组播帧、控制帧、错误帧等特定类型的流量,定向到指定的接收通道。这样,驱动或上层应用可以轻松地将不同业务流隔离到不同的处理队列中。

配置示例:实现一个简单的双MAC接收假设你想让通道0接收发往主MAC地址的普通数据帧,让通道1接收所有的网络控制报文(如LLDP, PTP事件报文等,它们通常是特定的组播地址或被视为控制帧)。

  1. 设置通道0的MAC地址为你的设备主地址(如00:1A:2B:3C:4D:00)。
  2. 设置通道1的MAC地址为一个不同的地址(如00:1A:2B:3C:4D:01),但实际上我们主要用它来过滤。
  3. RXMBPENABLE寄存器中,设置RXCMFEN=1(使能控制帧接收),并将RXPROMCH设置为1,意味着控制帧将被发送到通道1。
  4. 这样,普通数据帧会进入通道0的缓冲区,而网络控制帧会自动进入通道1的缓冲区,方便你的协议栈分别处理。

5. 高级功能配置与问题排查实录

掌握了基本的数据通路后,一些高级功能的正确配置是保证网络性能稳定、可靠的关键。

5.1 流量控制配置:防止数据丢失的保险阀

流量控制(Flow Control)用于防止接收方因处理不过来而丢失数据。EMAC支持两种模式:

  • 半双工模式下的碰撞流控: 当接收缓冲区快满时(RXnFREEBUFFER <= RXnFLOWTHRESH),EMAC会对任何新来的帧主动产生“碰撞”信号,迫使发送方回退。这是一种比较“粗暴”但有效的机制。
  • 全双工模式下的IEEE 802.3x PAUSE帧流控: 这是标准做法。当接收缓冲区不足时,EMAC会向对端发送一个PAUSE帧,其中包含一个暂停时间(以512位时间为单位)。对端收到后,会在指定时间内停止发送数据。

配置要点与避坑

  1. 模式匹配: 确保MACCONTROL.FULLDUPLEX的设置与PHY实际协商出的双工模式一致。全双工下用PAUSE帧,半双工下用碰撞流控。
  2. 使能与阈值设置: 通过MACCONTROL.RXBUFFERFLOWEN使能接收流控。RXnFLOWTHRESH的设置需要谨慎。设置得太小,会过早触发流控,降低吞吐量;设置得太大,可能在流控生效前缓冲区就已溢出。通常建议设置为总缓冲区深度的1/4到1/2,并根据实际流量测试调整。
  3. PAUSE帧处理: 使能发送流控(MACCONTROL.TXFLOWEN)以响应对端的PAUSE帧。注意,PAUSE帧的接收和处理是硬件自动完成的,但你需要确保RXMAXLEN设置得足够大(至少64字节),以容纳标准的PAUSE帧。

5.2 自适应性能优化:应对网络拥塞的小技巧

自适应性能优化(APO)是一个针对半双工模式(共享介质)的智能特性。当网络繁忙、碰撞频繁时,APO通过主动在帧发送之间插入额外的延迟(约4个IPG),来降低后续发生碰撞的概率,从而在整体上提高网络吞吐量。

  • 工作原理: 内部有一个“步调计数器”(Pacing Counter)。当一帧发送遭遇延迟、单次碰撞、多次碰撞或过度碰撞时,计数器被重置为31。当一帧成功发送(无延迟无碰撞)时,计数器减1,直到0。只有当计数器为0时,新帧才能在一个标准IPG后立即发送;否则,需要等待一个“步进延迟”。
  • 何时启用: 在典型的全双工交换网络环境中,APO收益不大,可以关闭(MACCONTROL.TXPACE=0)。但在半双工集线器(Hub)环境或无线网络(CSMA/CA)模拟场景下,启用APO(TXPACE=1)可以有效改善网络在重负载下的整体性能。

5.3 常见问题排查速查表

在实际调试中,以下问题最为常见:

问题现象可能原因排查步骤与解决方案
PHY无法识别,MDIO读写失败1. MDC时钟频率或极性错误。
2. PHY地址不对。
3. 硬件连接问题(上拉电阻)。
4. PHY处于复位状态。
1. 用示波器测量MDC波形,检查频率(<2.5MHz)和CLKDIV配置。
2. 读取ALIVE寄存器,确认PHY响应的地址。尝试地址0-31扫描。
3. 检查MDIO线路上是否有4.7kΩ上拉电阻。
4. 检查PHY的复位引脚(RESET_N)电平,确保PHY已脱离复位。
链路能Up,但无法Ping通1. EMAC的MAC地址未正确设置。
2. 发送/接收DMA描述符未正确初始化或链接。
3. 发送描述符中PASSCRC位设置错误。
4. 接收缓冲区不足或描述符耗尽。
1. 确认MACADDRHI/LO寄存器已写入正确的MAC地址。
2. 检查描述符的Next Descriptor Pointer是否有效,SOP/EOP标志是否正确。
3. 如果由EMAC生成CRC,则PASSCRC应清0,且数据缓冲区不应包含CRC字节。反之则需置1。
4. 检查接收完成中断是否正常触发,描述符回收逻辑是否正确。
网络吞吐量低,性能不稳定1. 系统内存带宽或延迟成为瓶颈。
2. 中断处理延迟过大。
3. 描述符缓冲区大小不合适。
4. 流控配置不当,频繁触发。
1. 优化DMA缓冲区内存位置(如使用非缓存、对齐的内存),确保总线带宽。
2. 考虑使用NAPI(轮询)模式替代纯中断模式,或提高中断优先级。
3. 增大描述符缓冲区大小(如从1522字节增至2K或4K),减少DMA次数。
4. 监控RXnFREEBUFFER,调整流控阈值RXnFLOWTHRESH
只能接收,不能发送(或反之)1. 对应方向的DMA未使能。
2. 对应方向的中断被屏蔽或未处理。
3. PHY双工模式与EMAC配置不匹配。
1. 检查TXCONTROL.TXENRXCONTROL.RXEN是否已置1。
2. 检查中断屏蔽寄存器TXINTMASKSET/CLEARRXINTMASKSET/CLEAR,并确认ISR已正确注册和清除中断标志。
3. 通过MDIO读取PHY状态寄存器,确认实际链路速率和双工模式,并与MACCONTROL.FULLDUPLEX等配置比对。
大量CRC错误或对齐错误1. 时钟不同步或存在抖动。
2. PCB布线问题导致信号完整性差。
3. MII/RMII接口的时序参数(Setup/Hold time)不满足。
1. 检查PHY和EMAC的参考时钟是否同源且稳定。
2. 检查MII/RMII数据线、时钟线的走线长度匹配和端接。
3. 在芯片数据手册中查阅MII/RMII接口的AC时序要求,必要时调整驱动强度或通过寄存器微调时序(如果芯片支持)。

调试技巧: 善用EMAC的统计寄存器。当遇到丢包、错包时,首先去查看统计寄存器中各种错误计数器(如CRC错误、对齐错误、长度错误、符号错误等)的增长情况,可以快速定位问题是出在物理链路层、MAC帧过滤层还是DMA/内存层面。这比漫无目的地抓包分析要高效得多。

6. 驱动开发实践与代码示例分析

理论最终要落实到代码。我们基于TI的芯片支持库(CSL)风格,来分析一个稳健的MDIO/EMAC驱动初始化与操作流程。

6.1 MDIO模块初始化与PHY探测

驱动初始化第一步是让MDIO和PHY正常工作。

// 假设外设时钟为150MHz, 目标MDC为2.5MHz #define PERIPH_CLK_MHZ 150 #define MDC_TARGET_MHZ 2.5 #define MDIO_CLKDIV_VAL ((PERIPH_CLK_MHZ / MDC_TARGET_MHZ) - 1) // 1. 配置MDIO时钟与前导码 MDIO_REGS->CONTROL = CSL_FMK(MDIO_CONTROL_CLKDIV, MDIO_CLKDIV_VAL) | CSL_FMK(MDIO_CONTROL_PREAMBLE, 1); // 使能前导码 // 2. 使能MDIO模块 CSL_FINS(MDIO_REGS->CONTROL, MDIO_CONTROL_ENABLE, 1); // 3. 等待并探测PHY (轮询ALIVE寄存器) uint32_t alive_status = 0; uint32_t timeout = 100000; // 超时计数 while (--timeout) { alive_status = MDIO_REGS->ALIVE; if (alive_status != 0) { break; // 探测到PHY } // 可添加短延时 } if (timeout == 0) { // 错误处理:未探测到任何PHY return ERROR_PHY_NOT_FOUND; } // 4. 确定使用的PHY地址 (假设使用第一个响应的PHY) uint8_t phy_addr = 0; while (!(alive_status & 0x1)) { alive_status >>= 1; phy_addr++; } // 此时 phy_addr 即为探测到的PHY地址 // 5. 配置USERPHYSEL,监控此PHY的链路状态,并启用链路变化中断 // 假设使用 USERPHYSEL0 和 USERACCESS0 MDIO_REGS->USERPHYSEL0 = CSL_FMK(MDIO_USERPHYSEL0_PHYADRMON, phy_addr) | CSL_FMK(MDIO_USERPHYSEL0_LINKINTENB, 1); // 使能链路中断 // 6. (可选)使能USERACCESS0操作完成中断 CSL_FINS(MDIO_REGS->USERINTMASKSET, MDIO_USERINTMASKSET_USERINT0MASK, 1);

6.2 稳健的PHY寄存器读写函数

直接使用手册中的宏虽然简洁,但缺乏错误处理。下面是一个更健壮的实现:

typedef enum { MDIO_OK = 0, MDIO_ERROR_BUSY, MDIO_ERROR_NO_ACK, MDIO_ERROR_TIMEOUT } mdio_status_t; #define MDIO_ACCESS_TIMEOUT 1000 mdio_status_t mdio_read_reg(uint8_t phy_addr, uint8_t reg_addr, uint16_t *data) { // 1. 等待USERACCESS0就绪 uint32_t timeout = MDIO_ACCESS_TIMEOUT; while (CSL_FEXT(MDIO_REGS->USERACCESS0, MDIO_USERACCESS0_GO) && timeout) { timeout--; } if (timeout == 0) { return MDIO_ERROR_BUSY; } // 2. 发起读请求 MDIO_REGS->USERACCESS0 = CSL_FMK(MDIO_USERACCESS0_GO, 1) | CSL_FMK(MDIO_USERACCESS0_REGADR, reg_addr) | CSL_FMK(MDIO_USERACCESS0_PHYADR, phy_addr); // WRITE位默认为0,表示读 // 3. 等待操作完成 timeout = MDIO_ACCESS_TIMEOUT; while (CSL_FEXT(MDIO_REGS->USERACCESS0, MDIO_USERACCESS0_GO) && timeout) { timeout--; } if (timeout == 0) { return MDIO_ERROR_TIMEOUT; } // 4. 检查ACK位,确认PHY响应成功 if (CSL_FEXT(MDIO_REGS->USERACCESS0, MDIO_USERACCESS0_ACK) == 0) { return MDIO_ERROR_NO_ACK; // PHY无应答,可能地址错误或PHY故障 } // 5. 读取数据 *data = (uint16_t)CSL_FEXT(MDIO_REGS->USERACCESS0, MDIO_USERACCESS0_DATA); return MDIO_OK; } mdio_status_t mdio_write_reg(uint8_t phy_addr, uint8_t reg_addr, uint16_t data) { // 1. 等待USERACCESS0就绪 (同上) // ... // 2. 发起写请求 MDIO_REGS->USERACCESS0 = CSL_FMK(MDIO_USERACCESS0_GO, 1) | CSL_FMK(MDIO_USERACCESS0_WRITE, 1) | CSL_FMK(MDIO_USERACCESS0_REGADR, reg_addr) | CSL_FMK(MDIO_USERACCESS0_PHYADR, phy_addr) | CSL_FMK(MDIO_USERACCESS0_DATA, data); // 3. 等待操作完成 (同上) // ... // 写操作无ACK位检查 return MDIO_OK; }

6.3 EMAC发送/接收描述符初始化示例

描述符是CPU与EMAC DMA引擎之间的契约。这里展示一个简单的线性描述符环初始化。

typedef struct emac_bd { uint32_t next; // 下一个描述符的物理地址 uint32_t buf_ptr; // 数据缓冲区的物理地址 uint16_t buf_len; // 缓冲区长度 uint16_t flags_len; // [15:0] 数据包长度, [31:16] 标志位 } emac_bd_t; // 假设我们有4个发送描述符和4个接收描述符 #define NUM_TX_BD 4 #define NUM_RX_BD 4 emac_bd_t tx_bd_ring[NUM_TX_BD] __attribute__((aligned(8))); // 对齐很重要! emac_bd_t rx_bd_ring[NUM_RX_BD] __attribute__((aligned(8))); uint8_t tx_buffers[NUM_TX_BD][1522]; // 每个缓冲区容纳一个标准MTU帧 uint8_t rx_buffers[NUM_RX_BD][1522]; void init_emac_descriptors(void) { // 1. 初始化发送描述符环 for (int i = 0; i < NUM_TX_BD; i++) { tx_bd_ring[i].next = (uint32_t)&tx_bd_ring[(i + 1) % NUM_TX_BD]; tx_bd_ring[i].buf_ptr = (uint32_t)&tx_buffers[i][0]; tx_bd_ring[i].buf_len = sizeof(tx_buffers[i]); tx_bd_ring[i].flags_len = 0; // 初始化为空描述符 // 注意:SOP/EOP标志在组包时由软件设置 } // 将环的起始地址告知EMAC EMAC_REGS->TX0HDP = (uint32_t)&tx_bd_ring[0]; // 2. 初始化接收描述符环 for (int i = 0; i < NUM_RX_BD; i++) { rx_bd_ring[i].next = (uint32_t)&rx_bd_ring[(i + 1) % NUM_RX_BD]; rx_bd_ring[i].buf_ptr = (uint32_t)&rx_buffers[i][0]; rx_bd_ring[i].buf_len = sizeof(rx_buffers[i]); // 设置OWNERSHIP位为1,表示描述符和缓冲区由EMAC硬件所有(准备接收) rx_bd_ring[i].flags_len = CSL_FMK(EMAC_BD_FLAGS, EMAC_BD_FLAGS_OWNERSHIP); } // 将环的起始地址告知EMAC EMAC_REGS->RX0HDP = (uint32_t)&rx_bd_ring[0]; // 3. 使能接收DMA CSL_FINS(EMAC_REGS->RXCONTROL, EMAC_RXCONTROL_RXEN, 1); }

关键点

  • 物理地址: 描述符中的nextbuf_ptr必须是物理地址(或总线地址),因为DMA引擎直接访问内存,不经过MMU。在启用缓存或使用虚拟内存的系统中,需要确保缓冲区位于非缓存、一致性内存区域,并进行正确的地址转换。
  • OWNERSHIP位: 这是描述符中最重要的标志之一。对于接收描述符,软件在初始化或回收时将其置1,表示缓冲区空闲,硬件可以填入数据。硬件接收完一帧后,会将该位清0,并设置其他状态位。软件在消费数据后,必须再次将其置1,将描述符归还给硬件,形成闭环。
  • 描述符环: 通过next指针将描述符链接成一个环,EMAC硬件会自动遍历这个环,实现了高效的缓冲区管理。

最后,在调试这类底层驱动时,逻辑分析仪或支持MII/RMII解码的示波器是必不可少的工具。它们可以直观地让你看到MDIO总线上的命令、MII接口上的数据流,帮助你快速区分是软件配置问题还是硬件时序问题。从稳定的PHY通信开始,逐步构建数据通路,是成功实现嵌入式网络功能的不二法门。