linux phy处理流程一:探测phy设备

我们首先从一个简单但是非常重要的问题开始,soc的mac mdio总线上可以外接哪些设备?

(i)、一般而言soc的mac mdio总线上挂接一个和mac直接相连的phy设备

(ii)、在有些复杂的情况下soc的mac mdio总线上可能挂接多个Phy设备以及多个mdio设备(如L2层交换机switch),其中的多个phy设备中有一个可能是直接和soc mac控制器直连,其他phy设备是和switch交换机的某些端口链接。

(iii)、有些场景下soc的mac直接和其他soc的mac直连即无phy设备的情况。

总而言之上面几乎囊括了mac外接设备的所有场景,其他情况基本就是上述几种情况的拓展而已。

本文以imx8为例,说明在imx8上phy或者mdio设备是如何从被探测到驱动加载到最终正常工作的整理流程。为什么我们拔掉网线,soc能够感知到link down?我们插入网线时,soc能够感知到link up?

为什么在设备树中不配置phy addr也能识别到phy?什么情况下会去遍历0-31所有的phy地址?为什么没有编写phy对应的驱动程序,phy也能正常工作?等等一大堆问题都会迎刃而解。

1、phy设备创建并注册

其实单说phy不提及mac是不现实的,因为在linux驱动中,phy设备(phydev)的创建和注册流程是由mac发起的,即在mac驱动初始化过程中才会去探测phy。

下面源代码是imx8 fec_enet_mii_init接口中涉及phy的代码片段:

node = of_get_child_by_name(pdev->dev.of_node, "mdio"); 

if (node) {

        err = of_mdiobus_register(fep->mii_bus, node);

        of_node_put(node);

} else if (fep->phy_node && !fep->fixed_link) {

        err = -EPROBE_DEFER;

} else {

         err = mdiobus_register(fep->mii_bus);

}

代码中去获取设备树中mac节点中"mdio"子节点的配置信息,

如果存在"mdio"配置则调用of_mdiobus_register(fep->mii_bus, node);

如果无"mdio"配置,无"phy-handle"(phy_node)属性且也无"fixed-link"属性则返回错误;

否则调用mdiobus_register(fep->mii_bus);

上面的"fixed-link"属性则对应(iii)有些场景下soc的mac直接和其他soc的mac直连即无phy设备的情况。因为无phy设备,mac无法获取连接双方协商出来的工作状态(speed,full-duplex,half-duplex等),所以只能通过解析"fixed-link"属性来进行配置(fixed-link计划在后面章节讲解)。

一个关键的问题来了,为什么无"mdio"属性,无"phy-handle"(phy_node)属性且也无"fixed-link"属性则返回错误?这个问题留给读者来回答。

剩下的问题其实就很简单了,我们只要把of_mdiobus_register和mdiobus_register的差异弄清楚了就把phy设备探测的迷雾解开了。我们要的是梳理流程,具体的代码还是留给读者去读。

读者如果去阅读这两个函数的话,就会明白什么时候需要进行0-31的所有phy addr的遍历了。遍历在mdiobus_scan接口中进行的。

of_mdiobus_register 和mdiobus_register 的差异?

of_mdiobus_register流程如下:

1).注册mdio总线但不遍历0-31所有的phy地址的phy_id,因为设备树中存在mdio节点信息,只会注册mdio节点下的设备,所以不需要盲目的进行所有phy_addr的遍历。

2).遍历设备树中"mdio"节点中的所有子节点,如果能从子节点中获取到"reg"值,则判断该节点是否为phy设备(of_mdiobus_child_is_phy(),即设备树中配置了"ethernet-phy-idAAAA.BBBB", "ethernet-phy-ieee802.3-c45","ethernet-phy-ieee802.3-c22",白名单列表以及无"compatible"属性值认为其为phy设备),如果为phy设备则调用of_mdiobus_register_phy()注册phy设备,否则调用of_mdiobus_register_device注册mdio设备。

如果从节点中无法获取到"reg"值,然后对无"reg"值的节点调用of_mdiobus_child_is_phy()判断其是否为phy设备,如果为phy设备则尝试phy addr从0-31(跳过以存在phy设备的phy addr)调用of_mdiobus_register_phy()注册phy设备,

3).of_mdiobus_register_phy函数先在非C45情况下,通过"compatible"属性("compatible=ethernet-phy-idAAAA.BBBB")来获取phy id成功则调用phy_device_create()创建phy设备(这种情况下不会实际去读取phy_id,而是直接创建phy设备,会不会注册大量的phy设备???),否则在C45或者在非C45情况下,通过"compatible"属性("compatible=ethernet-phy-idAAAA.BBBB")来获取phy id失败调用get_phy_device通过mdio read phy_id成功则创建phy_device以及注册phy设备(phy设备和phy驱动进行匹配),否则注册失败。

mdiobus_register流程如下:

1).注册mii bus总线

2).如果设备树配置了"reset-gpios = <xxx xxxx xxxx>;"复位管脚则进行复位操作

3).对0-31所有phy地址,依次调用mdiobus_scan()进行phy设备扫描

4).mdiobus_scan()函数通过mido获取phy id成功则创建phy设备,否则返回设备不存在错误码(-ENODEV),创建phy设备成功后则注册phy设备(phy设备和phy驱动进行匹配操作)。

struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45)

{

struct phy_c45_device_ids c45_ids = {0};

u32 phy_id = 0;

int r;

r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids); /*获取phy id*/

if (r)

return ERR_PTR(r);

/* If the phy_id is mostly Fs, there is no device there */

if ((phy_id & 0x1fffffff) == 0x1fffffff)

        return ERR_PTR(-ENODEV); //虽然能够获取到phy id但是phy id无效的话依然认为phy设备不存在

return phy_device_create(bus, addr, phy_id, is_c45, &c45_ids); /*能够读取到phy id,表明存在phy设备*/

}

if ((phy_id & 0x1fffffff) == 0x1fffffff)

        return ERR_PTR(-ENODEV);

对phy_id做了一个判断。一般如果phy addr对应的phy设备不存在的话,返回的phy_id为0xffff ffff,这样的话上面的判断条件就其作用了,返回设备不存在的错误。

2、phy驱动的触发

根据Linux设备驱动模型可以,一旦注册phy设备,如果内核中存在对应的phy驱动的话,必然导致mii_bus_type总线的匹配操作,从而触发phydrv->probe()函数。

下面将把这个过程设计的接口一一贴出:

module_phy_driver用于声明phy驱动,接口定义如下:

#define module_phy_driver(__phy_drivers) phy_module_driver(__phy_drivers, ARRAY_SIZE(__phy_drivers))

#define phy_module_driver(__phy_drivers, __count) \

static int __init phy_module_init(void) \

{ \

return phy_drivers_register(__phy_drivers, __count, THIS_MODULE); \

} \

module_init(phy_module_init); \

static void __exit phy_module_exit(void) \

{ \

phy_drivers_unregister(__phy_drivers, __count); \

} \

module_exit(phy_module_exit)

int phy_drivers_register(struct phy_driver *new_driver, int n,

struct module *owner)

{

int i, ret = 0;

for (i = 0; i < n; i++) {

ret = phy_driver_register(new_driver + i, owner);

if (ret) {

while (i-- > 0)

phy_driver_unregister(new_driver + i);

break;

}

}

return ret;

}

int phy_driver_register(struct phy_driver *new_driver, struct module *owner)

{

int retval;

new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY; //注意flag标志

new_driver->mdiodrv.driver.name = new_driver->name;

new_driver->mdiodrv.driver.bus = &mdio_bus_type;

new_driver->mdiodrv.driver.probe = phy_probe; //注意这个probe函数是mdiodrv的驱动入口

new_driver->mdiodrv.driver.remove = phy_remove;

new_driver->mdiodrv.driver.owner = owner;

retval = driver_register(&new_driver->mdiodrv.driver); //注意这里注册的驱动是phy_driver的mdio_driver.driver

if (retval) {

pr_err("%s: Error %d in registering driver\n",

new_driver->name, retval);

return retval;

}

pr_debug("%s: Registered new driver\n", new_driver->name);

return 0;

}

static int phy_probe(struct device *dev)

{

struct phy_device *phydev = to_phy_device(dev);

struct device_driver *drv = phydev->mdio.dev.driver;

struct phy_driver *phydrv = to_phy_driver(drv);

int err = 0;

phydev->drv = phydrv;

/* Disable the interrupt if the PHY doesn't support it

* but the interrupt is still a valid one

*/

if (!(phydrv->flags & PHY_HAS_INTERRUPT) &&

phy_interrupt_is_valid(phydev))

phydev->irq = PHY_POLL;

if (phydrv->flags & PHY_IS_INTERNAL)

phydev->is_internal = true;

mutex_lock(&phydev->lock);

/* Start out supporting everything. Eventually,

* a controller will attach, and may modify one

* or both of these values

*/

phydev->supported = phydrv->features;

of_set_phy_supported(phydev);

phydev->advertising = phydev->supported;

/* Get the EEE modes we want to prohibit. We will ask

* the PHY stop advertising these mode later on

*/

of_set_phy_eee_broken(phydev);

/* The Pause Frame bits indicate that the PHY can support passing

* pause frames. During autonegotiation, the PHYs will determine if

* they should allow pause frames to pass. The MAC driver should then

* use that result to determine whether to enable flow control via

* pause frames.

*

* Normally, PHY drivers should not set the Pause bits, and instead

* allow phylib to do that. However, there may be some situations

* (e.g. hardware erratum) where the driver wants to set only one

* of these bits.

*/

if (phydrv->features & (SUPPORTED_Pause | SUPPORTED_Asym_Pause)) {

phydev->supported &= ~(SUPPORTED_Pause | SUPPORTED_Asym_Pause);

phydev->supported |= phydrv->features &

(SUPPORTED_Pause | SUPPORTED_Asym_Pause);

} else {

phydev->supported |= SUPPORTED_Pause | SUPPORTED_Asym_Pause;

}

/* Set the state to READY by default */

phydev->state = PHY_READY; //这里设置phy状态为ready

if (phydev->drv->probe) //调用phy驱动真正的probe函数

err = phydev->drv->probe(phydev);

mutex_unlock(&phydev->lock);

return err;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值