我们首先从一个简单但是非常重要的问题开始,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;
}