大多数网络设备由一组寄存器组成,这些寄存器提供到MAC层的接口,MAC层通过PHY与物理连接通信. PHY关注于与网络连接另一端的链路伙伴(通常是以太网电缆)协商链路参数,并提供注册接口以允许驱动程序确定选择了哪些设置,并配置允许的设置.
虽然这些设备不同于网络设备,并且符合寄存器的标准布局,但是通常的做法是将PHY管理代码与网络驱动器集成. 这导致了大量的冗余代码. 此外,在具有连接到同一管理总线的多个(有时是完全不同的)以太网控制器的嵌入式系统上,难以确保总线的安全使用.
由于PHY是设备,并且实际上它们被访问的管理总线实际上是总线,因此PHY抽象层对它们进行处理. 这样做,它有以下目标:
基本上,该层旨在为PHY设备提供接口,允许网络驱动程序编写者尽可能少地编写代码,同时仍提供完整的功能集.
大多数网络设备通过管理总线连接到PHY. 不同的设备使用不同的总线(尽管有些共享通用接口). 为了利用PAL,每个总线接口需要注册为不同的设备.
必须实现读写功能. 他们的原型是:
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是寄存器号. 保证不会从中断时间调用这些函数,因此它们可以安全地阻塞,等待中断发出操作完成的信号
复位功能是可选的. 这用于将总线返回到初始化状态.
需要探测功能. 此功能应设置总线驱动程序所需的任何内容,设置mii_bus结构,并使用mdiobus_register注册PAL. 同样,还有一个删除所有功能的删除功能(使用mdiobus_unregister).
与任何驱动程序一样,必须配置device_driver结构,并使用init exit函数注册驱动程序.
总线也必须在某处作为设备声明,并进行注册.
作为一个驱动程序如何实现mdio总线驱动程序的示例,请参阅drivers / net / ethernet / freescale / fsl_pq_mdio.c以及其中一个用户的关联DTS文件. (例如"git grep fsl,.* - mdio arch / powerpc / boot / dts /")
简化的千兆位媒体独立接口(RGMII)是一个12引脚电信号接口,使用同步125Mhz时钟信号和多条数据线. 由于此设计决定,必须在时钟线(RXC或TXC)与数据线之间添加1.5ns至2ns的延迟,以使PHY(时钟接收器)具有足够的设置和保持时间来正确采样数据线. PHY库提供不同类型的PHY_INTERFACE_MODE_RGMII *值,以使PHY驱动程序和可选的MAC驱动程序实现所需的延迟. 必须从PHY设备本身的角度理解phy_interface_t的值,从而导致以下结果:
出于以下原因,尽可能使用PHY端RGMII延迟:
对于PHY无法提供此延迟但是以太网MAC驱动程序能够执行此操作的情况,正确的phy_interface_t值应为PHY_INTERFACE_MODE_RGMII,并且应正确配置以太网MAC驱动程序以提供所需的传输和/或或者从PHY设备的角度接收侧延迟. 相反,如果以太网MAC驱动程序查看phy_interface_t值,对于除PHY_INTERFACE_MODE_RGMII之外的任何其他模式,它应确保禁用MAC级延迟.
如果根据RGMII标准定义的以太网MAC和PHY都不能提供所需的延迟,则可以使用以下几个选项:
当以太网MAC和PHY之间存在RGMII延迟不匹配时,当PHY或MAC对这些信号进行快照以将其转换为逻辑1或0状态时,这很可能导致时钟和数据线信号不稳定并重建正在传输/接收的数据. 典型症状包括:
在启动期间的某个时间,网络驱动程序需要在PHY设备和网络设备之间建立连接. 此时,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->支持和phydev->广告具有从它们中删除的任何值,这对您的控制器没有意义(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_IGNORE_INTERRUPT,并使用来自网络驱动程序的phy_mac_interrupt()
. 如果您不想使用中断,请将phydev-> irq设置为PHY_POLL. phy_start()
启用PHY中断(如果适用)并启动phylib状态机.
如果要断开与网络的连接(即使只是简单地断开),则调用phy_stop(phydev). 此功能还会停止phylib状态机并禁用PHY中断.
phy_connect()
系列函数中提供的PHY接口模式定义了PHY接口的初始操作模式. 这不能保证保持不变; 有些PHY根据协商结果动态改变其接口模式而无需软件交互.
一些接口模式如下所述:
PHY_INTERFACE_MODE_1000BASEX
这定义了由802.3标准部分36定义的1000BASE-X单通道serdes链路.该链路使用10B / 8B编码方案以1.25Gbaud的固定比特率工作,从而产生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链路由单个serdes通道组成,以1.25Gbaud的固定比特率运行,具有10B / 8B编码. 基础数据速率为1Gbps,通过复制每个数据符号实现100Mbps和10Mbps的较慢速度. 重新使用802.3控制字以将协商的速度和双工信息发送到MAC,并且MAC确认接收. 这不包括"高速"变体,例如2.5Gbps速度.
注意:链路上不匹配的SGMII与1000BASE-X配置在某些情况下可以成功传递数据,但16位控制字将无法正确解释,这可能会导致双工,暂停或其他设置不匹配. 这取决于MAC和/或PHY行为.
PHY不直接参与流控制/暂停帧,除非确保在MII_ADVERTISE中设置SUPPORTED_Pause和SUPPORTED_AsymPause位以向链路伙伴指示以太网MAC控制器支持这样的事情. 由于流控制/暂停帧生成涉及以太网MAC驱动程序,因此建议此驱动程序通过相应地设置SUPPORTED_Pause和SUPPORTED_AsymPause位来正确指示广告并支持此类功能. 这可以在phy_connect()
之前或之后和/或由于实现ethtool :: set_pauseparam功能而完成.
PAL的内置状态机可能需要一些帮助才能使您的网络设备和PHY正确同步. 如果是这样,您可以在连接到PHY时注册辅助函数,PHY将在状态机对任何更改做出反应之前每秒调用一次. 为此,您需要手动调用phy_attach()
和phy_prepare_link()
,然后调用phy_start_machine()
并将第二个参数设置为指向您的特殊处理程序.
目前没有关于如何使用此功能的示例,并且对它的测试受到限制,因为作者没有任何使用它的驱动程序(它们都使用选项1). 所以Caveat Emptor.
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);
A convenience function to print out the PHY status neatly.
void phy_request_interrupt(struct phy_device *phydev);
请求IRQ进行PHY中断.
struct phy_device * phy_attach(struct net_device *dev, const char *phy_id,phy_interface_t interface);
将网络设备连接到特定PHY,如果在总线初始化期间没有找到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_sset(struct phy_device *phydev, struct ethtool_cmd *cmd);
Ethtool便利功能.
int phy_mii_ioctl(struct phy_device *phydev,struct mii_ioctl_data *mii_data, int cmd);
The MII ioctl. Note that this function will completely screw up the state machine if you write registers like BMCR, BMSR, ADVERTISE, etc. Best to use this only to write registers which are not standard, and don't set off a renegotiation.
通过PHY抽象层,添加对新PHY的支持非常容易. 在某些情况下,根本不需要工作! 但是,许多PHY需要一点点手持操作才能启动并运行.
如果所需的PHY没有您想要支持的任何勘误,怪癖或特殊功能,那么最好不要添加支持,并让PHY抽象层的通用PHY驱动程序完成所有工作.
If you do need to write a PHY driver, the first thing to do is make sure it can be matched with an appropriate PHY device. This is done during bus initialization by reading the device's UID (stored in registers 2 and 3), then comparing it to each driver's phy_id field by ANDing it with each driver's phy_id_mask field. Also, it needs a name. Here's an example:
static struct phy_driver dm9161_driver = {.phy_id = 0x0181b880,.name = "Davicom DM9161E",.phy_id_mask = 0x0ffffff0,...
}
接下来,您需要指定PHY设备和驱动程序支持的功能(速度,双工,自动等). 大多数PHY支持PHY_BASIC_FEATURES,但您可以在include / mii.h中查找其他功能.
每个驱动程序都包含许多函数指针,在phy_driver结构下的include / linux / phy.h中有记录.
其中,驱动程序代码只需要分配config_aneg和read_status. 其余的是可选的. 此外,如果可能的话,最好使用这两个函数的通用phy驱动程序版本:genphy_read_status和genphy_config_aneg. 如果这不可能,您可能只需要在调用这些函数之前和之后执行某些操作,因此您的函数将包装泛型函数.
请随意查看驱动程序/ 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层将调用与fixup相关联的运行功能. 该函数被传递给指向感兴趣的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()时,需要取消注册fixup和free allocate memory.
在卸载模块之前调用以下功能之一:
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
Next Previous