1. 简介
在调试网口驱动的过程中发现phy芯片的驱动框架结构还有点复杂,不仔细研究的话还不好搞懂,另外百度到的资料也不够全面,这篇就总结梳理一下这方面的知识。
我们知道一个 phy 驱动的原理是非常简单的,一般流程如下:
- 1、用轮询/中断的方式通过 mdio 总线读取 phy 芯片的状态。
- 2、在 phy link 状态变化的情况下,正确配置 mac 的状态。(例如:根据 phy 自协商的速率 10/100/1000M 把 mac 配置成对应速率)
下面就以 stmmac 网口驱动为例,展示一下 phy 驱动整个调用过程。整个 phy 驱动的主要调用流程如下图所示:
2. phy_device
首先每个 phy 芯片会创建一个 struct phy_device
类型的设备,对应的有 struct phy_driver
类型的驱动,这两者实际上是挂载在 mdio_bus_type
总线上的。
2.1 mdiobus
mdio 总线的定义:
struct bus_type mdio_bus_type = {
|
|
.name = "mdio_bus", |
|
.dev_groups = mdio_bus_dev_groups, |
|
.match = mdio_bus_match, |
|
.uevent = mdio_uevent, |
|
}; |
2.2 mdio device
网口驱动在初始化 probe()
时遍历 dts 的定义创建相应struct phy_device
类型的设备:
stmmac_dvr_probe() |
|
`-| stmmac_mdio_register() |
|
`-| stmmac_mdio_register() |
|
`-| {
|
|
| new_bus = mdiobus_alloc(); |
|
| new_bus->read = &stmmac_xgmac2_mdio_read; // mdio 读写函数 |
|
| new_bus->write = &stmmac_xgmac2_mdio_write; |
|
| |
|
| of_mdiobus_register(new_bus, mdio_node); |
|
`-| of_mdiobus_register_phy(mdio, child, addr); |
|
`-| get_phy_device() |
|
`-| get_phy_c22_id(bus, addr, &phy_id); |
|
`-| {
|
|
| phy_reg = mdiobus_read(bus, addr, MII_PHYSID1); // 通过 mdio 总线读取 phy 芯片 id |
|
| phy_reg = mdiobus_read(bus, addr, MII_PHYSID2); |
|
| } |
|
| phy_device_create(bus, addr, phy_id, is_c45, &c45_ids); |
|
`-| {
|
|
| mdiodev->dev.bus = &mdio_bus_type; |
|
| mdiodev->dev.type = &mdio_bus_phy_type; |
|
| mdiodev->bus_match = phy_bus_match; |
|
| INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); // 这里就是 phy device 的轮询任务 |
|
| } |
|
| of_mdiobus_phy_device_register() |
|
`-| phy_device_register() |
|
`-| device_add() |
2.3 mdio driver
mdio bus 会根据 struct phy_device
的 phy id 和 struct phy_driver
进行 match,如果没有找到对应驱动会使用通用驱动 genphy_driver
:
static struct phy_driver genphy_driver = {
|
|
.phy_id = 0xffffffff, |
|
.phy_id_mask = 0xffffffff, |
|
.name = "Generic PHY", |
|
.get_features = genphy_read_abilities, |
|
.suspend = genphy_suspend, |
|
.resume = genphy_resume, |
|
.set_loopback = genphy_loopback, |
|
}; |
以 genphy_driver
为例 struct phy_device
的注册过程如下:
phy_init() |
|
`-| phy_driver_register(&genphy_driver, THIS_MODULE); |
|
`-| {
|
|
| new_driver->mdiodrv.driver.bus = &mdio_bus_type; |
|
| new_driver->mdiodrv.d |