news 2026/6/4 9:40:18

Linux网络驱动之Fixed-Link(11)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux网络驱动之Fixed-Link(11)

接前一篇文章:Linux网络驱动之Fixed-Link(10)

本文内容参考:

linux phy fixed-link-CSDN博客

fixed-link 网口驱动设备树-CSDN博客

GMAC网卡Fixed-Link模式 - StepForwards - 博客园

RTL8367RB的国产P2P替代方案用JL6107-PC的可行性及实现方法-CSDN博客

linux phy处理流程一:探测phy设备_phy link过程-CSDN博客

设备树 fixed-link 使用说明-CSDN博客

Linux 网络驱动-MAC、PHY层驱动框架(三)_ethernet-phy-ieee802.3-c22-CSDN博客

特此致谢!

三、深入了解

4. Linux内核中Fixed-Link的相关内容

(2)PHY驱动(driver)匹配

上一回继续讲解of_phy_get_and_connect函数中的of_phy_connect函数代码,讲到了其中调用的phy_connect_direct函数中的phy_attach_direct函数。上一回总体介绍了该函数,本回深入解析函数代码。为了便于理解和回顾,再次贴出phy_attach_direct函数代码,在drivers/net/mdio/of_mdio.c中,如下:

/** * phy_attach_direct - attach a network device to a given PHY device pointer * @dev: network device to attach * @phydev: Pointer to phy_device to attach * @flags: PHY device's dev_flags * @interface: PHY device's interface * * Description: Called by drivers to attach to a particular PHY * device. The phy_device is found, and properly hooked up * to the phy_driver. If no driver is attached, then a * generic driver is used. The phy_device is given a ptr to * the attaching device, and given a callback for link status * change. The phy_device is returned to the attaching driver. * This function takes a reference on the phy device. */ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, u32 flags, phy_interface_t interface) { struct mii_bus *bus = phydev->mdio.bus; struct device *d = &phydev->mdio.dev; struct module *ndev_owner = NULL; bool using_genphy = false; int err; /* For Ethernet device drivers that register their own MDIO bus, we * will have bus->owner match ndev_mod, so we do not want to increment * our own module->refcnt here, otherwise we would not be able to * unload later on. */ if (dev) ndev_owner = dev->dev.parent->driver->owner; if (ndev_owner != bus->owner && !try_module_get(bus->owner)) { phydev_err(phydev, "failed to get the bus module\n"); return -EIO; } get_device(d); /* Assume that if there is no driver, that it doesn't * exist, and we should use the genphy driver. */ if (!d->driver) { if (phydev->is_c45) d->driver = &genphy_c45_driver.mdiodrv.driver; else d->driver = &genphy_driver.mdiodrv.driver; using_genphy = true; } if (!try_module_get(d->driver->owner)) { phydev_err(phydev, "failed to get the device driver module\n"); err = -EIO; goto error_put_device; } if (using_genphy) { err = d->driver->probe(d); if (err >= 0) err = device_bind_driver(d); if (err) goto error_module_put; } if (phydev->attached_dev) { dev_err(&dev->dev, "PHY already attached\n"); err = -EBUSY; goto error; } phydev->phy_link_change = phy_link_change; if (dev) { phydev->attached_dev = dev; dev->phydev = phydev; if (phydev->sfp_bus_attached) dev->sfp_bus = phydev->sfp_bus; else if (dev->sfp_bus) phydev->is_on_sfp_module = true; } /* Some Ethernet drivers try to connect to a PHY device before * calling register_netdevice() -> netdev_register_kobject() and * does the dev->dev.kobj initialization. Here we only check for * success which indicates that the network device kobject is * ready. Once we do that we still need to keep track of whether * links were successfully set up or not for phy_detach() to * remove them accordingly. */ phydev->sysfs_links = false; phy_sysfs_create_links(phydev); if (!phydev->attached_dev) { err = sysfs_create_file(&phydev->mdio.dev.kobj, &dev_attr_phy_standalone.attr); if (err) phydev_err(phydev, "error creating 'phy_standalone' sysfs entry\n"); } phydev->dev_flags |= flags; phydev->interface = interface; phydev->state = PHY_READY; phydev->interrupts = PHY_INTERRUPT_DISABLED; /* Port is set to PORT_TP by default and the actual PHY driver will set * it to different value depending on the PHY configuration. If we have * the generic PHY driver we can't figure it out, thus set the old * legacy PORT_MII value. */ if (using_genphy) phydev->port = PORT_MII; /* Initial carrier state is off as the phy is about to be * (re)initialized. */ if (dev) netif_carrier_off(phydev->attached_dev); /* Do initial configuration here, now that * we have certain key parameters * (dev_flags and interface) */ err = phy_init_hw(phydev); if (err) goto error; phy_resume(phydev); phy_led_triggers_register(phydev); return err; error: /* phy_detach() does all of the cleanup below */ phy_detach(phydev); return err; error_module_put: module_put(d->driver->owner); d->driver = NULL; error_put_device: put_device(d); if (ndev_owner != bus->owner) module_put(bus->owner); return err; } EXPORT_SYMBOL(phy_attach_direct);

来看phy_attach_direct函数具体代码。

先来看第1段代码片段:

/* Assume that if there is no driver, that it doesn't * exist, and we should use the genphy driver. */ if (!d->driver) { if (phydev->is_c45) d->driver = &genphy_c45_driver.mdiodrv.driver; else d->driver = &genphy_driver.mdiodrv.driver; using_genphy = true; }

如果无附加驱动,则使用默认phy驱动(通用驱动程序),即将using_genphy置为true。

genphy_c45_driver的初始化代码在kernel/linux-5.10-origin/drivers/net/phy/phy-c45.c中,如下:

struct phy_driver genphy_c45_driver = { .phy_id = 0xffffffff, .phy_id_mask = 0xffffffff, .name = "Generic Clause 45 PHY", .read_status = genphy_c45_read_status, };

genphy_driver的初始化代码在kernel/linux-5.10-origin/drivers/net/phy/phy_device.c中,如下:

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, };

特别说明:phy_id是在mac驱动加载中获取的,同时创建对应的phy_device并注册,与phy驱动进行匹配。

接下来看以下代码片段:

if (using_genphy) { err = d->driver->probe(d); if (err >= 0) err = device_bind_driver(d); if (err) goto error_module_put; }

如果using_genphy为true,也就是使用通用phy驱动,则调用驱动的probe函数(d->driver->probe)。d->driver上边已经设置,对于genphy_c45_driver 来说是genphy_c45_driver.mdiodrv.driver;对于genphy_driver来说是genphy_driver.mdiodrv.driver。

.mdiodrv.driver并未在上边的genphy_c45_driver和genphy_driver的初始化中被赋值,而是在kernel/linux-5.10-origin/drivers/net/phy/phy_device.c中的phy_init函数中的phy_driver_register函数中赋值的(也就是在初始化的时候动态赋值的),相关代码如下:

tatic int __init phy_init(void) { int rc; ethtool_set_ethtool_phy_ops(&phy_ethtool_phy_ops); rc = mdio_bus_init(); if (rc) goto err_ethtool_phy_ops; features_init(); rc = phy_driver_register(&genphy_c45_driver, THIS_MODULE); if (rc) goto err_mdio_bus; rc = phy_driver_register(&genphy_driver, THIS_MODULE); if (rc) goto err_c45; return 0; err_c45: phy_driver_unregister(&genphy_c45_driver); err_mdio_bus: mdio_bus_exit(); err_ethtool_phy_ops: ethtool_set_ethtool_phy_ops(NULL); return rc; }
/** * phy_driver_register - register a phy_driver with the PHY layer * @new_driver: new phy_driver to register * @owner: module owning this PHY */ int phy_driver_register(struct phy_driver *new_driver, struct module *owner) { int retval; /* Either the features are hard coded, or dynamically * determined. It cannot be both. */ if (WARN_ON(new_driver->features && new_driver->get_features)) { pr_err("%s: features and get_features must not both be set\n", new_driver->name); return -EINVAL; } new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY; new_driver->mdiodrv.driver.name = new_driver->name; new_driver->mdiodrv.driver.bus = &mdio_bus_type; new_driver->mdiodrv.driver.probe = phy_probe; new_driver->mdiodrv.driver.remove = phy_remove; new_driver->mdiodrv.driver.owner = owner; new_driver->mdiodrv.driver.probe_type = PROBE_FORCE_SYNCHRONOUS; retval = driver_register(&new_driver->mdiodrv.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; } EXPORT_SYMBOL(phy_driver_register);

可以看到,不论是genphy_c45_driver还是genphy_driver,d->driver->probe都是phy_probe()。phy_probe函数在同文件(kernel/linux-5.10-origin/drivers/net/phy/phy_device.c)中,代码如下:

/** * phy_probe - probe and init a PHY device * @dev: device to probe and init * * Description: Take care of setting up the phy_device structure, * set the state to READY (the driver's init function should * set it to STARTING if needed). */ 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 (!phy_drv_supports_irq(phydrv) && phy_interrupt_is_valid(phydev)) phydev->irq = PHY_POLL; if (phydrv->flags & PHY_IS_INTERNAL) phydev->is_internal = true; /* Deassert the reset signal */ phy_device_reset(phydev, 0); if (phydev->drv->probe) { err = phydev->drv->probe(phydev); if (err) goto out; } /* Start out supporting everything. Eventually, * a controller will attach, and may modify one * or both of these values */ if (phydrv->features) { linkmode_copy(phydev->supported, phydrv->features); } else if (phydrv->get_features) { err = phydrv->get_features(phydev); } else if (phydev->is_c45) { err = genphy_c45_pma_read_abilities(phydev); } else { err = genphy_read_abilities(phydev); } if (err) goto out; if (!linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported)) phydev->autoneg = 0; if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, phydev->supported)) phydev->is_gigabit_capable = 1; if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported)) phydev->is_gigabit_capable = 1; of_set_phy_supported(phydev); phy_advertise_supported(phydev); /* 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 (!test_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported) && !test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported)) { linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported); linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported); } /* Set the state to READY by default */ phydev->state = PHY_READY; out: /* Re-assert the reset signal on error */ if (err) phy_device_reset(phydev, 1); return err; }

总体来看,phy_attach_direct函数中检查phydev是否已经匹配了phydrv。如果未匹配,则使用通用phydrv。这就是为什么我们并没有专门编写phy驱动,但是phy还是能够正常工作的原因。

更多内容请看下回。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/4 9:38:10

优刻得云部署智谱GLM-5实战:GPU推理服务化全链路指南

1. 项目概述:为什么要在优刻得云上跑智谱GLM-5? 最近两周,我连续接到三类客户咨询:一类是做智能客服中台的SaaS厂商,想把本地部署的GLM-4模型升级为GLM-5,但自建GPU集群运维成本太高;第二类是高…

作者头像 李华
网站建设 2026/6/4 9:38:09

API优先架构:为什么大模型服务不再需要讨好开源

1. 项目概述:一场被误读的开源姿态“MiniMax不需要讨好开源”——这句话在2024年中旬的技术圈传播时,像一块投入静水的石头,涟漪扩散得又快又远。很多人第一反应是:这是一家AI公司公开diss开源社区?是在挑衅GPL精神&am…

作者头像 李华
网站建设 2026/6/4 9:38:09

用STM32F103驱动HT1621段码屏,手把手教你做一个简易FM收音机频率显示器

基于STM32F103与HT1621的FM收音机频率显示器实战指南在嵌入式开发领域,将硬件驱动与实际应用结合是提升技能的关键一步。本文将以STM32F103微控制器为核心,搭配HT1621段码屏驱动芯片,构建一个完整的FM收音机频率显示系统。不同于简单的驱动演…

作者头像 李华
网站建设 2026/6/4 9:37:12

轻量化大模型工程实践:低延迟高保真LLM端侧部署指南

我需要澄清一个关键事实:截至目前(2024年),OpenAI官方从未发布、宣布或确认过任何名为“GPT-5.4 mini”或“GPT-5.4 nano”的模型。OpenAI未公开GPT-5系列的任何版本,更不存在带小数点编号(如5.4&#xff0…

作者头像 李华
网站建设 2026/6/4 9:35:17

PostgreSQL 技术日报 (5月18日)|POSETTE 精彩演讲预告,迁移方案重磅发布

🗞️ 行业新闻 🧩 Cerebras曾月烧800万美元,这家60亿美元AI芯片新贵险些早早夭折 Cerebras Systems 是 2026 年迄今最大的科技 IPO,但多年前该公司在开发革命性 AI 芯片时烧掉数亿美元资金,几乎濒临倒闭。这家初创公司在早期开发阶段每月支出…

作者头像 李华
网站建设 2026/6/4 9:34:23

Vibe-Trading:赋予交易助手全面能力,多特性助力金融研究与交易!

Vibe-Trading:你的个人交易助手一键赋予你的助手全面交易能力。网站、文档、新闻、特性、影子账户、演示、快速开始、示例、API / MCP、路线图、贡献指南各有对应链接。新闻2026-06-03社区分类与跟踪关联方面,工具调用跟踪条目携带原始调用 ID&#xff0…

作者头像 李华