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. 需要一个 probe 函数。此函数应设置总线驱动程序需要的任何内容,设置 mii_bus 结构,并使用 mdiobus_register 向 PAL 注册。类似地,还有一个 remove 函数可以撤消所有这些操作(使用 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 设备本身的角度理解 phy_interface_t 的值,从而得出以下结论

  • 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 位(请参见下文)之外,您不应设置任何位,否则 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 控制字被重新用于将协商的速度和双工信息从 PHY 发送到 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 定义的带有 Clause 73 自协商的 10GBASE-R。 有关详细信息,请参考 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 四通道 USGMII 模式,它是 USGMII(通用 SGMII)链路的四通道变体。 它与 QSGMII 非常相似,但使用数据包控制头 (PCH) 而不是 7 字节的前导码来携带不仅是端口 ID,还有所谓的“扩展”。规范中迄今为止唯一记录的扩展是包含时间戳,用于启用 PTP 的 PHY。 此模式与 QSGMII 不兼容,但在链路速度和协商方面提供相同的功能。

PHY_INTERFACE_MODE_1000BASEKX

这是 IEEE 802.3 Clause 36 定义的带有 Clause 73 自协商的 1000BASE-X。 通常,它将与 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 驱动程序可以覆盖它。 如果 PHY 在 IEEE 标准化 MMD 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 层将调用与修复程序关联的 run 函数。此函数会传递一个指向感兴趣的 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