Linux phy system
PHY芯片为OSI的最底层-物理层(Physical Layer),通过MII与数据链路层的MAC芯片相连,对于MAC与PHY之间的一些知识可以查看Mac与Phy组成原理的简单分析,这篇文章进行熟悉。
PHY与MAC整体的连接框架:
PHY的硬件系统算是比较复杂的,PHY与MAC相连,MAC与CPU相通,PHY与MAC通过MII和MDIO/MDC相连,MII是走网络数据的,MDIO/MDC是用来与PHY的寄存器通讯的,对PHY进行配置。类似的SWITCH芯片一般也有两种接口,MII用来走网络数据,SPI用来设置SWITCH的寄存器。
跟以前分析I2C/SPI的驱动一样,分为控制器驱动和设备器驱动。
1、控制器驱动
控制器的驱动使用的一样是platform总线的连接方式,在arch或dts下面进行对phy的platform进行add,platform_driver
的register一般也放在/driver/net/phy/下面,device和driver的name匹配后就会执行platform_driver
结构体所对应的probe接口,都大同小异,如下:
static struct platform_driver pfe_platform_driver = {
.probe = pfe_platform_probe,
.remove = pfe_platform_remove,
.driver = {
.name = "pfe",
#ifdef CONFIG_PM
.pm = &pfe_platform_pm_ops,
#endif
},
};
static int __init pfe_module_init(void)
{
return platform_driver_register(&pfe_platform_driver);
}
static void __exit pfe_module_exit(void)
{
platform_driver_unregister(&pfe_platform_driver);
}
MODULE_LICENSE("GPL");
module_init(pfe_module_init);
module_exit(pfe_module_exit);
因为phy与cpu的通讯配置使用的是MII、MDIO/MDC来进行传输控制的,所以probe函数里面如要对MII总线进行配置,最后调用mdiobus_register()
或of_mdiobus_register()
对mdio_bus进行注册。
of_mdiobus_register()
函数位于drivers/of/of_mdio.c
中,该函数最后还是会调用mdiobus_register()
函数,mdiobus_register()
函数位于drivers\net\phy\mdio_bus.c
中,通过一层一层的往下拨,会有如下结构:
‐‐> mdiobus_register
‐‐> device_register
‐‐> mdiobus_scan
‐‐> get_phy_device
‐‐> get_phy_id // 读寄存器
‐‐> phy_device_create // 创建phy设备
‐‐> INIT_DELAYED_WORK(&dev‐>state_queue, phy_state_machine); //初始化状态机
这边就是控制器和设备器的交接了,创建phy设备初始化PHY状态机,接下去就看设备器的驱动。
2、设备器驱动
设备器的驱动也是三个方面device、driver、bus。PHY的device接口为phy_device_register
和phy_device_release
,driver接口为phy_driver_register
和phy_driver_unregister
,bus接口为mdiobus_register
和mdiobus_unregister
。
这样一分析感觉就跟清晰了,有关PHY驱动的内容都位于/drivers/net/phy中。
phy的设备驱动不像i2c/spi有一个board_info函数进行添加设备,而是直接读取phy中的寄存器,根据IEEE的规定PHY芯片的前16个寄存器的内容必须是固定的,如下:
其中寄存器0x02和0x03即设备ID的高低位,每一种型号的PHY有其一串ID号,这我们查看对于的手册即可知道。
在设备的driver中使用MODULE_DEVICE_TABLE
将对应的设备ID添加进入,他的效果可以理解为board_info函数所实现的内容,如broadcom的table:
static struct mdio_device_id __maybe_unused broadcom_tbl[] = {
{ PHY_ID_BCM5411, 0xfffffff0 },
{ PHY_ID_BCM5421, 0xfffffff0 },
{ PHY_ID_BCM54210S, 0xfffffff0 },
{ PHY_ID_BCM5461, 0xfffffff0 },
{ PHY_ID_BCM5464, 0xfffffff0 },
{ PHY_ID_BCM5482, 0xfffffff0 },
{ PHY_ID_BCM5482, 0xfffffff0 },
{ PHY_ID_BCM50610, 0xfffffff0 },
{ PHY_ID_BCM50610M, 0xfffffff0 },
{ PHY_ID_BCM57780, 0xfffffff0 },
{ PHY_ID_BCMAC131, 0xfffffff0 },
{ PHY_ID_BCM5241, 0xfffffff0 },
{ }
};
MODULE_DEVICE_TABLE(mdio, broadcom_tbl);
phy_driver
的register大概为如下一个过程,进行简单分析:
drivers/net/phy/phy_device.c
phy_init
‐‐> mdio_bus_init 注册mdio总线
‐‐> class_register(&mdio_bus_class);
‐‐> bus_register(&mdio_bus_type);
‐‐> phy_driver_register(&genphy_driver);
在mdio总线注册的时候会调用mdio_bus_match
,如果match函数找到设备则会进行设备驱动的注册,match函数位于/drivers/net/phy/mdio_bus.c
中,如下,进行phy_id的打印。
static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{
struct phy_device *phydev = to_phy_device(dev);
struct phy_driver *phydrv = to_phy_driver(drv);
printk("phydev->phy_id:%x ",phydev->phy_id);
printk("phydrv->phy_id:%x \n",phydrv->phy_id);
return ((phydrv->phy_id & phydrv->phy_id_mask) ==
(phydev->phy_id & phydrv->phy_id_mask));
}
由log可以看出,设备dev的ID为600d8595,然后就去查找对应的驱动drv的ID,知道找到600d8595则进入probe函数。
[ 23.306611] phydev->phy_id:600d8595 phydrv->phy_id:ffffffff
[ 23.312150] phydev->phy_id:600d8595 phydrv->phy_id:ffffffff
[ 23.317731] phydev->phy_id:600d8595 phydrv->phy_id:4dd072
[ 23.323084] phydev->phy_id:600d8595 phydrv->phy_id:4dd033
[ 23.328461] phydev->phy_id:600d8595 phydrv->phy_id:206070
[ 23.333812] phydev->phy_id:600d8595 phydrv->phy_id:2060e0
[ 23.339182] phydev->phy_id:600d8595 phydrv->phy_id:600d8595
这样找到ID后就会进行设备驱动的注册,对应的phy_driver_register()
函数才能返回成功,才会执行phy_driver
结构体下面的内容,如下:
static struct phy_driver bcm54210s_driver = {
.phy_id = PHY_ID_BCM54210S,
.phy_id_mask = 0xfffffff0,
.name = "Broadcom BCM54210S",
.features = PHY_GBIT_FEATURES |
SUPPORTED_Pause | SUPPORTED_Asym_Pause,
.flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
.config_init = bcm54210s_config_init,
.config_aneg = bcm54210s_config_aneg,
.read_status = bcm54210s_read_status,
.ack_interrupt = bcm54xx_ack_interrupt,
.config_intr = bcm54xx_config_intr,
.driver = { .owner = THIS_MODULE },
};
里面对phy的寄存器等进行初始化配置,这边对PHY的驱动进行简单的介绍,关于PHY的内容还有好多,比如:PHY状态机、ethtool工具这些都是在后面应用的时候需要用到的,等我自己深入研究后再进行学习总结。
Linux phy system的分析就到这边,有感悟时会持续会更新。
注:以上内容都是本人在学习过程积累的一些心得,难免会有参考到其他文章的一些知识,如有侵权,请及时通知我,我将及时删除或标注内容出处,如有错误之处也请指出,进行探讨学习。文章只是起一个引导作用,详细的数据解析内容还请查看Linux相关教程,感谢您的查阅。