在Linux系统中,PHY(物理层)设备通常用于处理网络接口的物理传输。初始化PHY设备和驱动其工作通常涉及Linux内核的PHY框架。以下是一般的流程和关键步骤:
1. PHY设备初始化
在Linux中,PHY设备的初始化主要由设备树配置、驱动程序加载以及内核的PHY子系统来处理。
设备树配置:PHY设备通常在设备树文件(*.dts文件)中定义,包含其地址和属性等信息。例如:
dts
复制代码
ethernet-phy@0 {
reg = <0>;
compatible = "ethernet-phy-idXXXX.XXXX";
};
设备树文件会将PHY地址和兼容性信息映射到驱动,从而使内核能够识别设备。
加载驱动:Linux内核的PHY驱动通常位于drivers/net/phy/目录下。通过内核配置选项(如CONFIG_PHYLIB)来启用PHY库,并在网络驱动中初始化PHY。
MDIO总线注册:MDIO(管理数据输入输出)是PHY芯片的标准管理接口。PHY驱动通过MDIO总线和设备通信,获取和设置PHY的状态。在Linux网络驱动中,初始化时会调用phy_connect等函数将PHY和MAC(媒体访问控制)层驱动绑定。
2. 配置和工作流程
PHY的初始化和工作流程通常由Linux内核的phy_device结构体和相关函数管理。
PHY扫描与连接:在驱动中,通过phy_connect函数扫描和连接PHY设备。该函数会搜索MDIO总线上的设备,并尝试匹配兼容的PHY驱动。
PHY状态管理:PHY设备状态机负责处理PHY的连接、断开、重启和功耗等状态。内核会调用phy_start函数启动PHY设备,之后在内核线程中定期检查设备的连接状态。
中断和状态检测:PHY设备支持基于中断或轮询的状态检测。当PHY检测到状态变化时,会通过中断或轮询的方式触发事件,内核将相应处理连接状态的变化。
速度和双工模式设置:PHY驱动还负责配置链路速度、双工模式等参数,通常由phy_ethtool_get_settings和phy_ethtool_set_settings函数处理,以适应链路需求。
3. 关键函数
在Linux内核代码中,以下几个函数是驱动PHY设备的关键:
phy_init_hw():初始化PHY硬件。
phy_connect():将PHY和网络设备连接。
phy_start():启动PHY,进入正常工作状态。
phy_stop():停止PHY设备,进入低功耗模式。
phy_polling_mode():启用轮询模式检测PHY状态。
phy_ethtool_get_settings() / phy_ethtool_set_settings():用于获取和设置PHY的链路参数。
示例流程
在驱动中,一般会这样调用PHY框架的API:
struct phy_device *phydev;
phydev = phy_connect(netdev, bus_id, &adjust_link, PHY_INTERFACE_MODE_GMII);
phy_start(phydev); // 启动PHY设备
其中,adjust_link是一个回调函数,用于更新链路状态。
通过实现phy_driver结构体和注册/注销函数来实现。以下是增强后的代码示例:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/phy.h>
#include <linux/netdevice.h>
#include <linux/ethtool.h>
#include <linux/platform_device.h>
static struct net_device *netdev;
static struct phy_device *phydev;
static struct mii_bus *mdio_bus;
// 定义PHY驱动结构体,描述支持的操作
static struct phy_driver my_phy_driver = {
.phy_id = 0x12345678, // 替换为实际的PHY ID
.phy_id_mask = 0xffffffff,
.name = "My PHY Driver",
.features = PHY_BASIC_FEATURES,
.config_init = NULL,
.config_aneg = genphy_config_aneg,
.read_status = genphy_read_status,
.suspend = genphy_suspend,
.resume = genphy_resume,
};
// 自定义初始化函数,执行PHY特定的初始化配置
static int my_config_init(struct phy_device *phydev)
{
int ret;
// 示例:写入寄存器配置PHY特定功能
ret = phy_write(phydev, 0x1F, 0x8000); // 写入寄存器0x1F
if (ret < 0)
return ret;
printk(KERN_INFO "PHY initialized with custom configuration\n");
return 0;
}
// 自定义自动协商配置函数
static int my_config_aneg(struct phy_device *phydev)
{
int ret;
// 开启自动协商
ret = genphy_config_aneg(phydev); // 使用通用函数
if (ret < 0)
return ret;
printk(KERN_INFO "PHY auto-negotiation configured\n");
return 0;
}
// 自定义读取状态函数
static int my_read_status(struct phy_device *phydev)
{
int ret;
// 调用通用状态读取
ret = genphy_read_status(phydev);
if (ret < 0)
return ret;
if (phydev->link) {
printk(KERN_INFO "PHY link up - %d Mbps %s\n",
phydev->speed,
phydev->duplex == DUPLEX_FULL ? "Full Duplex" : "Half Duplex");
} else {
printk(KERN_INFO "PHY link down\n");
}
return 0;
}
// 自定义挂起函数
static int my_suspend(struct phy_device *phydev)
{
// 写入寄存器以实现PHY的低功耗模式
phy_write(phydev, MII_BMCR, BMCR_PDOWN); // 进入断电模式
printk(KERN_INFO "PHY suspended to low power mode\n");
return 0;
}
// 自定义恢复函数
static int my_resume(struct phy_device *phydev)
{
// 恢复PHY功耗模式
phy_write(phydev, MII_BMCR, 0); // 退出断电模式
printk(KERN_INFO "PHY resumed from low power mode\n");
return 0;
}
// 连接PHY设备时的回调函数
static void my_adjust_link(struct net_device *dev)
{
struct phy_device *phydev = dev->phydev;
if (phydev->link) {
printk(KERN_INFO "PHY: Link is up - %d Mbps %s\n",
phydev->speed,
phydev->duplex == DUPLEX_FULL ? "Full Duplex" : "Half Duplex");
} else {
printk(KERN_INFO "PHY: Link is down\n");
}
}
// 扫描并注册MDIO总线上的PHY设备
static int my_scan_and_register_phy(void)
{
int i;
struct phy_device *phy;
for (i = 0; i < PHY_MAX_ADDR; i++) {
phy = mdiobus_get_phy(mdio_bus, i);
if (phy) {
printk(KERN_INFO "Found PHY at address %d\n", i);
// 注册找到的PHY设备
if (!try_module_get(phy->mdio.dev.driver->owner)) {
printk(KERN_ERR "Failed to get module for PHY at address %d\n", i);
continue;
}
// 连接PHY设备
phydev = phy_connect(netdev, phy_name(phy), &my_adjust_link, PHY_INTERFACE_MODE_RGMII);
if (IS_ERR(phydev)) {
printk(KERN_ERR "Failed to connect PHY at address %d\n", i);
module_put(phy->mdio.dev.driver->owner);
continue;
}
phydev->supported &= PHY_BASIC_FEATURES;
phydev->advertising = phydev->supported;
// 启动PHY设备
phy_start(phydev);
printk(KERN_INFO "PHY device initialized and started\n");
return 0;
}
}
printk(KERN_ERR "No PHY device found\n");
return -ENODEV;
}
static int __init my_phy_driver_init(void)
{
int ret;
// 注册PHY驱动
ret = phy_driver_register(&my_phy_driver, THIS_MODULE);
if (ret) {
printk(KERN_ERR "Failed to register PHY driver\n");
return ret;
}
// 分配并初始化MDIO总线
mdio_bus = mdiobus_alloc();
if (!mdio_bus) {
printk(KERN_ERR "Failed to allocate MDIO bus\n");
phy_driver_unregister(&my_phy_driver);
return -ENOMEM;
}
snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "mdio_bus-0");
ret = mdiobus_register(mdio_bus);
if (ret) {
printk(KERN_ERR "Failed to register MDIO bus\n");
mdiobus_free(mdio_bus);
phy_driver_unregister(&my_phy_driver);
return ret;
}
// 创建网络设备
netdev = alloc_etherdev(0);
if (!netdev) {
printk(KERN_ERR "Failed to allocate net device\n");
mdiobus_unregister(mdio_bus);
mdiobus_free(mdio_bus);
phy_driver_unregister(&my_phy_driver);
return -ENOMEM;
}
// 扫描并连接MDIO总线上的PHY设备
ret = my_scan_and_register_phy();
if (ret) {
printk(KERN_ERR "No suitable PHY device found or failed to register PHY\n");
free_netdev(netdev);
mdiobus_unregister(mdio_bus);
mdiobus_free(mdio_bus);
phy_driver_unregister(&my_phy_driver);
return ret;
}
return 0;
}
static void __exit my_phy_driver_exit(void)
{
phy_disconnect(phydev);
free_netdev(netdev);
mdiobus_unregister(mdio_bus);
mdiobus_free(mdio_bus);
phy_driver_unregister(&my_phy_driver);
printk(KERN_INFO "PHY device driver unloaded\n");
}
module_init(my_phy_driver_init);
module_exit(my_phy_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("PHY device initialization with scanning and driver registration demo");
代码说明
- MDIO总线的注册和初始化:分配并注册MDIO总线,用于与PHY设备通信。
- 网络设备的创建:分配网络设备(如以太网接口)。
- PHY设备连接:通过
phy_connect
函数连接PHY设备,指定回调函数my_adjust_link
处理链路状态变化。 - PHY设备配置与启动:设置PHY的支持模式和配置,然后使用
phy_start
启动PHY设备。
-
PHY驱动结构体 (
phy_driver
):定义了PHY设备的基本信息和回调函数,例如config_init
、config_aneg
、read_status
等。这些函数用于初始化配置、管理状态、挂起和恢复PHY设备。phy_id
:用于匹配设备的PHY ID。config_aneg
:配置自动协商。read_status
:读取设备状态,这里使用了通用的genphy_read_status
。
-
PHY驱动注册 (
phy_driver_register
):在初始化时注册PHY驱动,并在卸载时取消注册。 -
状态机检查:
read_status
回调将读取PHY的状态,并在链路状态变化时更新状态机。 -
扫描PHY设备:在
my_scan_and_register_phy
函数中,通过mdiobus_get_phy()
函数扫描MDIO总线的每个地址(通常为0到31),检查是否存在可用的PHY设备,并尝试连接。 -
注册找到的PHY设备:如果找到了PHY设备,通过
try_module_get()
来确保该设备的驱动模块被引用,然后使用phy_connect()
连接到网络设备,并配置PHY的支持模式。 -
错误处理:在扫描和注册过程中,加入了错误处理逻辑,确保在没有找到PHY设备或连接失败时,能够清理资源并退出。
-
代码自定义部分说明
-
my_config_init
:在config_init
回调中可以添加PHY的特定初始化配置,比如写入特定寄存器以启用特定功能。在本例中,写入寄存器0x1F
来执行初始化配置。 -
my_config_aneg
:配置自动协商功能,通常使用通用的genphy_config_aneg
即可,同时可以在该函数中扩展或自定义自动协商的细节。 -
my_read_status
:自定义状态读取函数。此函数读取PHY的链路状态,并打印当前链路的速度和双工模式。这一部分通常是通过调用genphy_read_status
来获取当前状态。 -
my_suspend
和my_resume
:挂起和恢复函数。在挂起时写入BMCR_PDOWN
以进入低功耗模式,在恢复时退出低功耗模式,确保PHY恢复到工作状态。