PHY 抽象层

目的

大多数网络设备都包含一组寄存器,这些寄存器提供了一个到 MAC 层的接口,MAC 层通过 PHY 与物理连接进行通信。PHY 负责与网络连接另一端的链路伙伴(通常是以太网电缆)协商链路参数,并提供一个寄存器接口,允许驱动程序确定选择了哪些设置,以及配置允许哪些设置。

虽然这些设备与网络设备不同,并且符合寄存器的标准布局,但将 PHY 管理代码与网络驱动程序集成是一种常见的做法。这导致了大量的冗余代码。此外,在具有多个(有时非常不同的)以太网控制器连接到同一管理总线的嵌入式系统上,很难确保总线的安全使用。

由于 PHY 是设备,并且访问它们的管理总线实际上是总线,因此 PHY 抽象层将其视为设备和总线。这样做,它有以下目标

  1. 提高代码重用率

  2. 提高整体代码可维护性

  3. 加快新网络驱动程序和新系统的开发时间

基本上,该层旨在提供一个到 PHY 设备的接口,该接口允许网络驱动程序编写者编写尽可能少的代码,同时仍然提供完整的功能集。

MDIO 总线

大多数网络设备通过管理总线连接到 PHY。不同的设备使用不同的总线(尽管有些设备共享通用接口)。为了利用 PAL,每个总线接口都需要注册为一个单独的设备。

  1. 必须实现读写功能。它们的原型是

    int write(struct mii_bus *bus, int mii_id, int regnum, u16 value);
    int read(struct mii_bus *bus, int mii_id, int regnum);
    

    mii_id 是 PHY 在总线上的地址,regnum 是寄存器号。保证不会在中断时调用这些函数,因此它们可以安全地阻塞,等待中断信号指示操作完成

  2. 重置功能是可选的。它用于将总线返回到初始化状态。

  3. 需要一个探测函数。该函数应设置总线驱动程序需要的任何内容,设置 mii_bus 结构,并使用 mdiobus_register 向 PAL 注册。类似地,有一个删除函数可以撤消所有这些操作(使用 mdiobus_unregister)。

  4. 像任何驱动程序一样,必须配置 device_driver 结构,并且 init exit 函数用于注册驱动程序。

  5. 总线也必须在某处声明为设备并注册。

作为如何实现 mdio 总线驱动程序的一个示例,请参见 drivers/net/ethernet/freescale/fsl_pq_mdio.c 和一个用户的相关 DTS 文件。(例如,“git grep fsl,.*-mdio arch/powerpc/boot/dts/”)

(RG)MII/电气接口注意事项

精简千兆媒体独立接口 (RGMII) 是一种 12 针电气信号接口,使用同步 125Mhz 时钟信号和多条数据线。由于此设计决策,必须在时钟线(RXC 或 TXC)和数据线之间添加 1.5ns 到 2ns 的延迟,以使 PHY(时钟接收器)有足够大的建立和保持时间来正确采样数据线。PHY 库提供不同类型的 PHY_INTERFACE_MODE_RGMII* 值,以允许 PHY 驱动程序和可选的 MAC 驱动程序实现所需的延迟。phy_interface_t 的值必须从 PHY 设备本身的角度来理解,从而导致以下情况

  • PHY_INTERFACE_MODE_RGMII:PHY 不负责自行插入任何内部延迟,它假定以太网 MAC(如果能够)或 PCB 走线插入正确的 1.5-2ns 延迟

  • PHY_INTERFACE_MODE_RGMII_TXID:PHY 应为 PHY 设备处理的发送数据线 (TXD[3:0]) 插入内部延迟

  • PHY_INTERFACE_MODE_RGMII_RXID:PHY 应为 PHY 设备处理的接收数据线 (RXD[3:0]) 插入内部延迟

  • PHY_INTERFACE_MODE_RGMII_ID:PHY 应为来自/到 PHY 设备的发送和接收数据线插入内部延迟

在可能的情况下,请使用 PHY 侧 RGMII 延迟,原因如下

  • PHY 设备可能提供亚纳秒级的粒度,以允许指定接收器/发送器侧延迟(例如:0.5、1.0、1.5ns)。可能需要这种精度来解释 PCB 走线长度的差异

  • PHY 设备通常适用于各种应用(工业、医疗、汽车...),并且它们在温度/压力/电压范围内提供恒定且可靠的延迟

  • PHYLIB 中的 PHY 设备驱动程序本质上是可重用的,能够正确配置指定的延迟使得具有相似延迟要求的更多设计能够正确运行

对于 PHY 无法提供此延迟,但以太网 MAC 驱动程序能够提供延迟的情况,正确的 phy_interface_t 值应为 PHY_INTERFACE_MODE_RGMII,并且应正确配置以太网 MAC 驱动程序,以便从 PHY 设备的角度来看,提供所需的发送和/或接收侧延迟。相反,如果以太网 MAC 驱动程序查看 phy_interface_t 值,对于除 PHY_INTERFACE_MODE_RGMII 之外的任何其他模式,它应确保禁用 MAC 级别的延迟。

如果以太网 MAC 和 PHY 都无法提供 RGMII 标准定义的所需延迟,则可能存在多种选择

  • 某些 SoC 可能会提供引脚焊盘/多路复用器/控制器,能够配置给定引脚的强度、延迟和电压;这可能是插入预期 2ns RGMII 延迟的合适选择。

  • 修改 PCB 设计以包括固定延迟(例如:使用专门设计的蛇形线),这可能根本不需要软件配置。

RGMII 延迟不匹配的常见问题

如果以太网 MAC 和 PHY 之间存在 RGMII 延迟不匹配,则最有可能导致时钟和数据线信号不稳定,当 PHY 或 MAC 获取这些信号的快照以将它们转换为逻辑 1 或 0 状态并重建正在发送/接收的数据时。典型症状包括

  • 传输/接收部分工作,并且观察到频繁或偶尔的丢包

  • 以太网 MAC 可能会报告带有 FCS/CRC 错误的某些或所有进入数据包,或者只是丢弃所有数据包

  • 切换到较低的速度(如 10/100Mbits/秒)可以解决问题(因为在这种情况下有足够的建立/保持时间)

连接到 PHY

在启动期间的某个时间,网络驱动程序需要建立 PHY 设备和网络设备之间的连接。此时,PHY 的总线和驱动程序需要全部加载,以便准备好进行连接。此时,有几种连接到 PHY 的方法

  1. PAL 处理所有事情,并且仅在链路状态更改时调用网络驱动程序,以便它可以做出反应。

  2. PAL 处理除中断之外的所有事情(通常是因为控制器具有中断寄存器)。

  3. PAL 处理所有事情,但每秒与驱动程序检查一次,允许网络驱动程序首先对 PAL 采取任何操作之前的任何更改做出反应。

  4. PAL 仅用作函数库,网络设备手动调用函数以更新状态并配置 PHY

让 PHY 抽象层完成所有事情

如果选择选项 1(希望每个驱动程序都可以,但仍然对不能的驱动程序有用),则连接到 PHY 非常简单

首先,需要一个函数来对链路状态的变化做出反应。此函数遵循以下协议

static void adjust_link(struct net_device *dev);

接下来,需要知道连接到此设备的 PHY 的设备名称。该名称看起来像“0:00”,其中第一个数字是总线 ID,第二个数字是 PHY 在该总线上的地址。通常,总线负责使其 ID 唯一。

现在,要连接,只需调用此函数

phydev = phy_connect(dev, phy_name, &adjust_link, interface);

phydev是指向表示 PHY 的 phy_device 结构的指针。如果 phy_connect 成功,它将返回该指针。这里的 dev 是指向 net_device 的指针。完成后,此函数将启动 PHY 的软件状态机,并注册 PHY 的中断(如果它有中断)。phydev 结构将填充有关当前状态的信息,但此时 PHY 尚未真正运行。

应在调用 phy_connect() 之前在 phydev->dev_flags 中设置 PHY 特定的标志,以便底层 PHY 驱动程序可以检查标志并根据它们执行特定的操作。如果系统对 PHY/控制器施加了硬件限制,而 PHY 需要知道这些限制,则此功能很有用。

interface是一个 u32,它指定控制器和 PHY 之间使用的连接类型。示例包括 GMII、MII、RGMII 和 SGMII。有关完整列表,请参见下面的“PHY 接口模式”和 include/linux/phy.h。

现在只需确保 phydev->supported 和 phydev->advertising 中删除了对控制器没有意义的任何值(10/100 控制器可能连接到具有千兆功能的 PHY,因此需要屏蔽 SUPPORTED_1000baseT*)。有关这些位字段的定义,请参见 include/linux/ethtool.h。请注意,除了 SUPPORTED_Pause 和 SUPPORTED_AsymPause 位(参见下文)之外,不应 SET 任何位,否则 PHY 可能会进入不受支持的状态。

最后,一旦控制器准备好处理网络流量,就可以调用 phy_start(phydev)。这会告诉 PAL 您已准备就绪,并将 PHY 配置为连接到网络。如果网络驱动程序的 MAC 中断也处理 PHY 状态更改,只需在调用 phy_start 之前将 phydev->irq 设置为 PHY_MAC_INTERRUPT,然后使用网络驱动程序中的 phy_mac_interrupt()。如果不希望使用中断,请将 phydev->irq 设置为 PHY_POLL。phy_start() 启用 PHY 中断(如果适用)并启动 phylib 状态机。

如果要断开与网络的连接(即使只是短暂地断开),则可以调用 phy_stop(phydev)。此函数还会停止 phylib 状态机并禁用 PHY 中断。

PHY 接口模式

phy_connect() 系列函数中提供的 PHY 接口模式定义了 PHY 接口的初始操作模式。不能保证它保持恒定;有些 PHY 会动态更改其接口模式,而无需软件交互,具体取决于协商结果。

下面描述了一些接口模式

PHY_INTERFACE_MODE_SMII

这是串行 MII,时钟频率为 125MHz,支持 100M 和 10M 速度。可以在 https://opencores.org/ocsvn/smii/smii/trunk/doc/SMII.pdf 中找到一些详细信息

PHY_INTERFACE_MODE_1000BASEX

这定义了 802.3 标准第 36 节定义的 1000BASE-X 单通道 serdes 链路。该链路以 1.25Gbaud 的固定比特率运行,使用 10B/8B 编码方案,从而产生 1Gbps 的底层数据速率。数据流中嵌入了一个 16 位控制字,用于与远程端协商双工和暂停模式。这不包括“升频”变体,例如 2.5Gbps 速度(参见下文)。

PHY_INTERFACE_MODE_2500BASEX

这定义了 1000BASE-X 的一种变体,其时钟频率是 802.3 标准的 2.5 倍,从而给出 3.125Gbaud 的固定比特率。

PHY_INTERFACE_MODE_SGMII

这用于 Cisco SGMII,它是 802.3 标准定义的 1000BASE-X 的修改版。SGMII 链路由以 1.25Gbaud 的固定比特率运行的单通道 serdes 组成,并采用 10B/8B 编码。底层数据速率为 1Gbps,通过复制每个数据符号来实现较慢的 100Mbps 和 10Mbps 速度。重新调整 802.3 控制字的用途,以将协商的速度和双工信息从 MAC 发送到 MAC,并让 MAC 确认收到信息。这不包括“升频”变体,例如 2.5Gbps 速度。

注意:在某些情况下,链路上的 SGMII 与 1000BASE-X 配置不匹配可以成功传递数据,但不会正确解释 16 位控制字,这可能会导致双工、暂停或其他设置不匹配。这取决于 MAC 和/或 PHY 的行为。

PHY_INTERFACE_MODE_5GBASER

这是 IEEE 802.3 Clause 129 定义的 5GBASE-R 协议。它与 Clause 49 中定义的 10GBASE-R 协议相同,唯一的区别是它以一半的频率运行。有关定义,请参阅 IEEE 标准。

PHY_INTERFACE_MODE_10GBASER

这是 IEEE 802.3 Clause 49 定义的 10GBASE-R 协议,用于各种不同的介质。有关定义,请参阅 IEEE 标准。

注意:10GBASE-R 只是可以与 XFI 和 SFI 一起使用的一种协议。XFI 和 SFI 允许在单个 SERDES 通道上使用多个协议,并且还定义了将主机合规性板插入主机 XFP/SFP 连接器时信号的电气特性。因此,XFI 和 SFI 本身不是 PHY 接口类型。

PHY_INTERFACE_MODE_10GKR

这是 IEEE 802.3 Clause 49 定义的 10GBASE-R,具有 Clause 73 自动协商。有关更多信息,请参阅 IEEE 标准。

注意:由于历史原因,一些 10GBASE-R 的使用错误地使用了此定义。

PHY_INTERFACE_MODE_25GBASER

这是 IEEE 802.3 PCS Clause 107 定义的 25GBASE-R 协议。PCS 与 10GBASE-R 相同,即 64B/66B 编码以 2.5 倍的速度运行,从而给出 25.78125 Gbaud 的固定比特率。有关更多信息,请参阅 IEEE 标准。

PHY_INTERFACE_MODE_100BASEX

这定义了 IEEE 802.3 Clause 24。该链路以 125Mpbs 的固定数据速率运行,使用 4B/5B 编码方案,从而产生 100Mpbs 的底层数据速率。

PHY_INTERFACE_MODE_QUSGMII

这定义了 Cisco 的 Quad USGMII 模式,它是 USGMII(通用 SGMII)链路的四通道变体。它与 QSGMII 非常相似,但使用数据包控制标头 (PCH) 而不是 7 字节的前导码来携带端口 ID 以及所谓的“扩展”。规范中迄今为止唯一记录的扩展是包含时间戳,用于启用 PTP 的 PHY。此模式与 QSGMII 不兼容,但在链路速度和协商方面提供相同的功能。

PHY_INTERFACE_MODE_1000BASEKX

这是 IEEE 802.3 Clause 36 定义的 1000BASE-X,具有 Clause 73 自动协商。通常,它将与 Clause 70 PMD 一起使用。为了与用于 Clause 38 和 39 PMD 的 1000BASE-X phy 模式形成对比,此接口模式具有不同的自动协商,并且仅支持全双工。

PHY_INTERFACE_MODE_PSGMII

这是 Penta SGMII 模式,它类似于 QSGMII,但它将 5 条 SGMII 线组合到一个链路上,而 QSGMII 上有 4 条线。

PHY_INTERFACE_MODE_10G_QXGMII

表示 Cisco USXGMII 多端口铜接口文档定义的 10G-QXGMII PHY-MAC 接口。它在 10.3125 GHz SerDes 通道上支持 4 个端口,每个端口的速度为 2.5G / 1G / 100M / 10M,通过符号复制实现。PCS 期望标准的 USXGMII 代码字。

暂停帧 / 流量控制

PHY 不直接参与流量控制/暂停帧,除非通过确保 MII_ADVERTISE 中设置了 SUPPORTED_Pause 和 SUPPORTED_AsymPause 位来向链路伙伴表明以太网 MAC 控制器支持此类功能。由于流量控制/暂停帧生成涉及以太网 MAC 驱动程序,因此建议该驱动程序通过相应地设置 SUPPORTED_Pause 和 SUPPORTED_AsymPause 位来负责正确指示对此类功能的通告和支持。这可以在 phy_connect() 之前或之后以及/或者作为实现 ethtool::set_pauseparam 功能的结果来完成。

密切关注 PAL

PAL 的内置状态机可能需要一些帮助才能使网络设备和 PHY 正确同步。如果是这样,可以在连接到 PHY 时注册一个帮助程序函数,该函数将在状态机对任何更改做出反应之前每秒调用一次。为此,需要手动调用 phy_attach()phy_prepare_link(),然后调用 phy_start_machine(),并将第二个参数设置为指向特殊处理程序。

目前没有如何使用此功能的示例,并且对其的测试受到限制,因为作者没有任何使用它的驱动程序(它们都使用选项 1)。所以,买者自负。

自己完成所有事情

PAL 的内置状态机可能无法跟踪 PHY 和网络设备之间复杂的交互,这只是一个很小的机会。如果是这样,只需调用 phy_attach(),而不要调用 phy_start_machine 或 phy_prepare_link()。这意味着 phydev->state 完全由您处理(phy_start 和 phy_stop 在某些状态之间切换,因此您可能需要避免使用它们)。

已经努力确保可以在不运行状态机的情况下访问有用的功能,并且大多数这些功能都源于不与复杂状态机交互的功能。但是,同样,到目前为止,还没有努力测试在没有状态机的情况下运行,因此请谨慎尝试。

以下是这些函数的简要概述

int phy_read(struct phy_device *phydev, u16 regnum);
int phy_write(struct phy_device *phydev, u16 regnum, u16 val);

简单的读/写原语。它们调用总线的读/写函数指针。

void phy_print_status(struct phy_device *phydev);

一个方便的函数,用于整齐地打印出 PHY 状态。

void phy_request_interrupt(struct phy_device *phydev);

请求 PHY 中断的 IRQ。

struct phy_device * phy_attach(struct net_device *dev, const char *phy_id,
                               phy_interface_t interface);

将网络设备连接到特定的 PHY,如果在总线初始化期间未找到任何通用驱动程序,则将该 PHY 绑定到通用驱动程序。

int phy_start_aneg(struct phy_device *phydev);

使用 phydev 结构中的变量,配置广告并重置自动协商,或者禁用自动协商,并配置强制设置。

static inline int phy_read_status(struct phy_device *phydev);

使用有关 PHY 中当前设置的最新信息填充 phydev 结构。

int phy_ethtool_ksettings_set(struct phy_device *phydev,
                              const struct ethtool_link_ksettings *cmd);

Ethtool 方便函数。

int phy_mii_ioctl(struct phy_device *phydev,
                  struct mii_ioctl_data *mii_data, int cmd);

MII ioctl。请注意,如果您写入 BMCR、BMSR、ADVERTISE 等寄存器,此函数将完全搞乱状态机。最好仅使用它来写入非标准寄存器,并且不要触发重新协商。

PHY 设备驱动程序

使用 PHY 抽象层,添加对新 PHY 的支持非常容易。在某些情况下,根本不需要任何工作!但是,许多 PHY 需要一些帮助才能启动并运行。

通用 PHY 驱动程序

如果所需的 PHY 没有任何勘误、怪癖或您想要支持的特殊功能,那么最好不要添加支持,而让 PHY 抽象层的通用 PHY 驱动程序完成所有工作。

编写 PHY 驱动程序

如果确实需要编写 PHY 驱动程序,首先要做的是确保它可以与合适的 PHY 设备匹配。这在总线初始化期间通过读取设备的 UID(存储在寄存器 2 和 3 中),然后将其与每个驱动程序的 phy_id 字段进行比较,并通过将它与每个驱动程序的 phy_id_mask 字段进行 AND 运算来完成。此外,它还需要一个名称。这是一个示例

static struct phy_driver dm9161_driver = {
      .phy_id         = 0x0181b880,
      .name           = "Davicom DM9161E",
      .phy_id_mask    = 0x0ffffff0,
      ...
}

接下来,需要指定 PHY 设备和驱动程序支持哪些功能(速度、双工、自动协商等)。大多数 PHY 支持 PHY_BASIC_FEATURES,但可以在 include/mii.h 中查找其他功能。

每个驱动程序都包含许多函数指针,这些函数指针记录在 include/linux/phy.h 中的 phy_driver 结构下。

在这些函数指针中,只需要驱动程序代码分配 config_aneg 和 read_status。其余的是可选的。此外,如果可能,最好使用通用 phy 驱动程序的这两个函数的版本:genphy_read_status 和 genphy_config_aneg。如果这不可能,很可能您只需要在调用这些函数之前和之后执行一些操作,因此您的函数将包装通用函数。

请随意查看 drivers/net/phy/ 中的 Marvell、Cicada 和 Davicom 驱动程序以获取示例(截至本文撰写时,尚未测试 lxt 和 qsemi 驱动程序)。

PHY 的 MMD 寄存器访问默认情况下由 PAL 框架处理,但如果需要,可以由特定的 PHY 驱动程序覆盖。如果在 IEEE 对 MMD PHY 寄存器定义进行标准化之前发布了用于制造的 PHY,则可能会出现这种情况。大多数现代 PHY 将能够使用通用 PAL 框架来访问 PHY 的 MMD 寄存器。此类用法的一个示例是节能以太网支持,该支持在 PAL 中实现。如果 PHY 支持 IEEE 标准访问机制,则此支持使用 PAL 访问 MMD 寄存器以进行 EEE 查询和配置,如果特定的 PHY 驱动程序覆盖了该机制,则可以使用 PHY 的特定访问接口。有关如何实现此功能的示例,请参见 drivers/net/phy/ 中的 Micrel 驱动程序。

板级修复

有时,平台和 PHY 之间的特定交互需要特殊处理。例如,更改 PHY 的时钟输入的位置,或添加延迟以解决数据路径中的延迟问题。为了支持此类意外情况,PHY 层允许平台代码注册在 PHY 启动(或随后重置)时运行的修复。

当 PHY 层启动 PHY 时,它会检查是否已为其注册任何修复,基于 UID(包含在 PHY 设备的 phy_id 字段中)和总线标识符(包含在 phydev->dev.bus_id 中)进行匹配。两者必须匹配,但是提供了两个常量 PHY_ANY_ID 和 PHY_ANY_UID 作为总线 ID 和 UID 的通配符。

找到匹配项时,PHY 层将调用与修复关联的运行函数。此函数将指向感兴趣的 phy_device 的指针传递给它。因此,它应该仅对该 PHY 进行操作。

平台代码可以使用 phy_register_fixup() 注册修复

int phy_register_fixup(const char *phy_id,
        u32 phy_uid, u32 phy_uid_mask,
        int (*run)(struct phy_device *));

或使用两个存根之一 phy_register_fixup_for_uid() 和 phy_register_fixup_for_id()

int phy_register_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask,
               int (*run)(struct phy_device *));
int phy_register_fixup_for_id(const char *phy_id,
               int (*run)(struct phy_device *));

存根设置两个匹配标准之一,并将另一个设置为匹配任何内容。

当在模块加载时调用 phy_register_fixup() 或 *_for_uid()/*_for_id() 时,模块需要在卸载时注销修复并释放已分配的内存。

在卸载模块之前调用以下函数之一

int phy_unregister_fixup(const char *phy_id, u32 phy_uid, u32 phy_uid_mask);
int phy_unregister_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask);
int phy_register_fixup_for_id(const char *phy_id);

标准

IEEE 标准 802.3:CSMA/CD 访问方法和物理层规范,第二节:http://standards.ieee.org/getieee802/download/802.3-2008_section2.pdf

RGMII v1.3:http://web.archive.org/web/20160303212629/http://www.hp.com/rnd/pdfs/RGMIIv1_3.pdf

RGMII v2.0:http://web.archive.org/web/20160303171328/http://www.hp.com/rnd/pdfs/RGMIIv2_0_final_hp.pdf