PHY自动协商和其在Linux下的初始化

以太网PHY自动协商和其在Linux下的初始化

一:以太网的自动协商

相信很多人对以太网的自动协商原理已经很熟悉了,很多博客也将其描述得十分清楚,本文就不再详细描述了。

我们将换个角度来看待这个问题。

首先,以太网的自动协商功能是由PHY硬件自己完成的,不需要我们的内核去做什么指导工作,只要设置相应寄存器启动自动协商后,我们就可以读相关的寄存器来得到现在协商成啥了。那么具体是什么寄存器呢?


二、Linux下的初始化

1,关键的代码:

    drivers\net\phy\Phy_devices.c            // 主要是实现接口

    drivers\net\phy\Phy.c                         // 实现了Phy的状态机

2,PHY初始化的main函数:

    既然我们要分析这个PHY在内核的初始化过程,那么我们当然要找到一个入口函数,然后从这个函数进行分析。这个函数,我们就成为是PHY的main函数吧。

    那么这个函数是什么呢?

    它就是在Phy_devices.c中的phy_init函数。

subsys_initcall(phy_init);

3,打开Phy的大门——phy_init函数的分析:

    函数具体代码如下,粗体字为关键代码:

static int __init phy_init(void)
{
	int rc;

	rc = mdio_bus_init();
	if (rc)
		return rc;

	rc = phy_drivers_register(genphy_driver,
				  ARRAY_SIZE(genphy_driver));
	if (rc)
		mdio_bus_exit();

	return rc;
}

   该函数及其简单,实际干活的函数就是中间的phy_drivers_register()函数;

rc = phy_drivers_register(genphy_driver,
				  ARRAY_SIZE(genphy_driver));    

    这是一个很经典的函数,往内核中注册一个驱动,而这个驱动就是其参数,genphy_driver

    其原型如下:

static struct phy_driver genphy_driver = {
{
	.phy_id		= 0xffffffff,
	.phy_id_mask	= 0xffffffff,
	.name		= "Generic PHY",
	.soft_reset	= genphy_soft_reset,
	.config_init	= genphy_config_init,
	.features	= PHY_GBIT_FEATURES | SUPPORTED_MII |
			  SUPPORTED_AUI | SUPPORTED_FIBRE |
			  SUPPORTED_BNC,
	.config_aneg	= genphy_config_aneg,
	.aneg_done	= genphy_aneg_done,
	.read_status	= genphy_read_status,
	.suspend	= genphy_suspend,
	.resume		= genphy_resume,
	.driver		= { .owner = THIS_MODULE, },
};

    看上面东西,貌似看不出啥,甚至连probe函数都没有。那么怎么办?

    不要急,让我们接着看下去。现在我们来看phy_drivers_register()的函数实现

int phy_driver_register(struct phy_driver *new_driver)
{
	int retval;

	new_driver->driver.name = new_driver->name;
	new_driver->driver.bus = &mdio_bus_type;
	new_driver->driver.probe = phy_probe;
	new_driver->driver.remove = phy_remove;

	retval = driver_register(&new_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;
}

    我们发现是在这个函数里面,完成了probe的赋值。

    那么也就是说,这个驱动初始化的时候会先调用phy_probe

    4,正式进入phy驱动世界——phy_probe函数分析()

static int phy_probe(struct device *dev)
{
	struct phy_device *phydev = to_phy_device(dev);
	struct device_driver *drv = phydev->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;

	/* Set the state to READY by default */
	phydev->state = PHY_READY;

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

	mutex_unlock(&phydev->lock);

	return err;
}

    我们会发现,貌似这个phy_probe函数什么都没干啊,这个驱动匹配了和没匹配感觉没有什么区别啊。

    是的,这里啥事都没干,只干了一件十分关键的事情,那就是将phydev->state设置为了PHY_READY。

   OK,在看看文章的一开始,我们说PHY驱动是以状态机的形式进行工作了,而现在,我们修改了它的状态,那么状态机肯定会有相关的操作。

    那么,问题来了,状态机呢?状态机在哪启动的。

    OK,我们可以先思考一下,这个状态机,应该存在于哪里,应该由谁启动呢?是由驱动管理呢,还是设备管理呢?

    我认为,应该由设备管理,由设备启动。为什么呢? 很简单,如果我们系统上有两个PHY呢?他们之间肯定有自己的状态,那么他们就应该保存自己的状态,从代码中我也可以知道,state是存在于phy_devices这个结构体中。我们知道在由谁管理后,那么我们可以猜测,状态机是创建设备的时候启动的。那么就让我们来看看phy_device_create这个创建设备的函数吧。

5,PHY是怎么工作的呢?phy_device_create函数分析。

函数的实现如下:

struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id,
				     bool is_c45,
				     struct phy_c45_device_ids *c45_ids)
{
	struct phy_device *dev;

	/* We allocate the device, and initialize the default values */
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return ERR_PTR(-ENOMEM);

	dev->dev.release = phy_device_release;

	dev->speed = 0;
	dev->duplex = -1;
	dev->pause = 0;
	dev->asym_pause = 0;
	dev->link = 1;
	dev->interface = PHY_INTERFACE_MODE_GMII;

	dev->autoneg = AUTONEG_ENABLE;

	dev->is_c45 = is_c45;
	dev->addr = addr;
	dev->phy_id = phy_id;
	if (c45_ids)
		dev->c45_ids = *c45_ids;
	dev->bus = bus;
	dev->dev.parent = &bus->dev;
	dev->dev.bus = &mdio_bus_type;
	dev->irq = bus->irq ? bus->irq[addr] : PHY_POLL;
	dev_set_name(&dev->dev, PHY_ID_FMT, bus->id, addr);

	dev->state = PHY_DOWN;

	mutex_init(&dev->lock);
	INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
	INIT_WORK(&dev->phy_queue, phy_change);

	/* Request the appropriate module unconditionally; don't
	 * bother trying to do so only if it isn't already loaded,
	 * because that gets complicated. A hotplug event would have
	 * done an unconditional modprobe anyway.
	 * We don't do normal hotplug because it won't work for MDIO
	 * -- because it relies on the device staying around for long
	 * enough for the driver to get loaded. With MDIO, the NIC
	 * driver will get bored and give up as soon as it finds that
	 * there's no driver _already_ loaded.
	 */
	request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT, MDIO_ID_ARGS(phy_id));

	device_initialize(&dev->dev);

	return dev;
}

具体的细节我们就不看了,我们就看两处关键的代码:

一是,将设备的默认状态初始化为PHY_DOWN

二是,启动了PHY的状态机phy_state_machine

那么,就让我们走进PHY的状态机~~吧。

6,PHY的状态切换,phy_state_machine的分析

    首先看看phy_state_machine的源码

void phy_state_machine(struct work_struct *work)
{
	struct delayed_work *dwork = to_delayed_work(work);
	struct phy_device *phydev =
			container_of(dwork, struct phy_device, state_queue);
	bool needs_aneg = false, do_suspend = false;
	enum phy_state old_state;
	int err = 0;
	int old_link;

	mutex_lock(&phydev->lock);

	old_state = phydev->state;

	if (phydev->drv->link_change_notify)
		phydev->drv->link_change_notify(phydev);

	switch (phydev->state) {
	case PHY_DOWN:
	case PHY_STARTING:
	case PHY_READY:
	case PHY_PENDING:
		break;
	case PHY_UP:
		needs_aneg = true;

		phydev->link_timeout = PHY_AN_TIMEOUT;

		break;
	case PHY_AN:
		err = phy_read_status(phydev);
		if (err < 0)
			break;

		/* If the link is down, give up on negotiation for now */
		if (!phydev->link) {
			phydev->state = PHY_NOLINK;
			netif_carrier_off(phydev->attached_dev);
			phydev->adjust_link(phydev->attached_dev);
			break;
		}

		/* Check if negotiation is done.  Break if there's an error */
		err = phy_aneg_done(phydev);
		if (err < 0)
			break;

		/* If AN is done, we're running */
		if (err > 0) {
			phydev->state = PHY_RUNNING;
			netif_carrier_on(phydev->attached_dev);
			phydev->adjust_link(phydev->attached_dev);

		} else if (0 == phydev->link_timeout--)
			needs_aneg = true;
		break;
	case PHY_NOLINK:
		err = phy_read_status(phydev);
		if (err)
			break;

		if (phydev->link) {
			if (AUTONEG_ENABLE == phydev->autoneg) {
				err = phy_aneg_done(phydev);
				if (err < 0)
					break;

				if (!err) {
					phydev->state = PHY_AN;
					phydev->link_timeout = PHY_AN_TIMEOUT;
					break;
				}
			}
			phydev->state = PHY_RUNNING;
			netif_carrier_on(phydev->attached_dev);
			phydev->adjust_link(phydev->attached_dev);
		}
		break;
	case PHY_FORCING:
		err = genphy_update_link(phydev);
		if (err)
			break;

		if (phydev->link) {
			phydev->state = PHY_RUNNING;
			netif_carrier_on(phydev->attached_dev);
		} else {
			if (0 == phydev->link_timeout--)
				needs_aneg = true;
		}

		phydev->adjust_link(phydev->attached_dev);
		break;
	case PHY_RUNNING:
		/* Only register a CHANGE if we are polling or ignoring
		 * interrupts and link changed since latest checking.
		 */
		if (!phy_interrupt_is_valid(phydev)) {
			old_link = phydev->link;
			err = phy_read_status(phydev);
			if (err)
				break;

			if (old_link != phydev->link)
				phydev->state = PHY_CHANGELINK;
		}
		break;
	case PHY_CHANGELINK:
		err = phy_read_status(phydev);
		if (err)
			break;

		if (phydev->link) {
			phydev->state = PHY_RUNNING;
			netif_carrier_on(phydev->attached_dev);
		} else {
			phydev->state = PHY_NOLINK;
			netif_carrier_off(phydev->attached_dev);
		}

		phydev->adjust_link(phydev->attached_dev);

		if (phy_interrupt_is_valid(phydev))
			err = phy_config_interrupt(phydev,
						   PHY_INTERRUPT_ENABLED);
		break;
	case PHY_HALTED:
		if (phydev->link) {
			phydev->link = 0;
			netif_carrier_off(phydev->attached_dev);
			phydev->adjust_link(phydev->attached_dev);
			do_suspend = true;
		}
		break;
	case PHY_RESUMING:
		if (AUTONEG_ENABLE == phydev->autoneg) {
			err = phy_aneg_done(phydev);
			if (err < 0)
				break;

			/* err > 0 if AN is done.
			 * Otherwise, it's 0, and we're  still waiting for AN
			 */
			if (err > 0) {
				err = phy_read_status(phydev);
				if (err)
					break;

				if (phydev->link) {
					phydev->state = PHY_RUNNING;
					netif_carrier_on(phydev->attached_dev);
				} else	{
					phydev->state = PHY_NOLINK;
				}
				phydev->adjust_link(phydev->attached_dev);
			} else {
				phydev->state = PHY_AN;
				phydev->link_timeout = PHY_AN_TIMEOUT;
			}
		} else {
			err = phy_read_status(phydev);
			if (err)
				break;

			if (phydev->link) {
				phydev->state = PHY_RUNNING;
				netif_carrier_on(phydev->attached_dev);
			} else	{
				phydev->state = PHY_NOLINK;
			}
			phydev->adjust_link(phydev->attached_dev);
		}
		break;
	}

	mutex_unlock(&phydev->lock);

	if (needs_aneg)
		err = phy_start_aneg(phydev);
	else if (do_suspend)
		phy_suspend(phydev);

	if (err < 0)
		phy_error(phydev);

	dev_dbg(&phydev->dev, "PHY state change %s -> %s\n",
		phy_state_to_str(old_state), phy_state_to_str(phydev->state));

	queue_delayed_work(system_power_efficient_wq, &phydev->state_queue,
			   PHY_STATE_TIME * HZ);
}

    好的,让我们先想想,PHY的状态是怎么样的一个变化过程。

    1,首先,在初始化设备的时候,设置为PHY_DOWN

    2,然后,注册设备,匹配到驱动的时候,设置为PHY_READY

    3,然后呢?然后我也不知道,不知道哪里将PHY的状态设置为PHY_UP。

    代码大家继续看下去就知道,当设备为PHY_UP的时候,PHY便开始了它的自动协商或者强制设定了。

    如果是处于自动协商的话,那么PHY的状态变化为

    READY->AN->RUNNING

    如果是强制指定的话,那就是

    READY->FORCING->RUNNING

    好的,本次的分析就到这里了,思路就是这样的,代码我就不详解了,因为没什么难的,思路理清了,代码就在这,不会跑,如果遇到问题了,那就按照思路去看代码,然后解决问题。


  • 9
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Linux PHY 初始化过程包括以下步骤: 1. 确定 PHY 的类型和地址:在启动时,Linux 内核会扫描系统中的所有 PHY 设备,并确定每个 PHY 的类型和地址。 2. 初始化 PHY 的寄存器:内核会根据 PHY 的类型和地址,初始化 PHY 的寄存器,以便与其他设备进行通信。 3. 配置 PHY 的参数:内核会根据系统的需求,配置 PHY 的参数,例如速度、双工模式等。 4. 检测 PHY 的连接状态:内核会检测 PHY 是否连接到了网络中,并根据连接状态来配置网络接口。 5. 启动 PHY:最后,内核会启动 PHY,使其能够正常工作,并与其他设备进行通信。 ### 回答2: Linux PHY 初始化过程是指在Linux系统中对 PHY 硬件进行初始化设置的过程。PHYPhysical Layer Transceiver)是指物理层收发器,它是实现物理层与外界的通信接口。PHY 初始化过程包括对各个寄存器的配置、对驱动程序的设置等。本文将从以下几个方面来详细介绍 Linux PHY 初始化过程。 一、 PHY 设备绑定 在Linux系统中,PHY初始化是通过phylib来实现的,该库提供了一个框架,用于管理和控制PHY设备。在PHY初始化过程中,首先需要将PHY设备绑定到相应的网络接口上。 二、 PHY 寄存器配置 PHY芯片内部包含了许多寄存器,这些寄存器用于配置PHY芯片的参数,比如说速率、双工模式、带宽等。因此,在进行PHY初始化时,需要对这些寄存器进行配置。具体的寄存器配置流程如下: (1)设置寄存器读取和写入方式 在进行寄存器配置前,首先需要设置寄存器访问的方式。可以使用MDIO总线与PHY进行通信,也可以使用专用的SPI总线进行通信。 (2)配置寄存器参数 接下来,需要对PHY内部的寄存器进行配置。常见的配置包括速率、双工模式、带宽等。配置方式比较简单,只需要向对应的寄存器写入相应的值即可。 三、 PHY 驱动程序设置 在PHY初始化过程中,需要对PHY的驱动程序进行一些设置。驱动程序是实现和控制PHY硬件操作的程序,因此,设置驱动程序非常重要。常见的设置包括PHY的模式、速率、带宽等。 四、 PHY 能力检测 在PHY初始化结束后,需要进行PHY能力检测。这个过程用于检测PHY芯片的各种参数,以保证其正常工作。常见的检测项目包括速率、带宽、双工模式等。如果检测失败,需要重新进行寄存器配置和驱动程序设置,直到检测通过为止。 综上所述,Linux PHY初始化过程主要包括PHY设备绑定、PHY寄存器配置、PHY驱动程序设置、PHY能力检测等几个重要步骤。在实际应用中,需要根据实际情况进行相应的配置和调试,以保证PHY芯片正常工作,从而实现网络通信的目的。 ### 回答3: Linux PHY(物理层)初始化过程主要是指在启动Linux系统时,初始化网络设备驱动程序的各个组成部分中的PHY部分,使其能够正常工作。以下是Linux PHY初始化过程的详细步骤: 1. 选择合适的驱动程序:在Linux系统中,每个硬件设备都有一个对应的驱动程序。在初始化网络设备的PHY部分之前,需要先根据硬件设备的类型选择合适的驱动程序。Linux系统中已经内建了许多常见的网络设备驱动程序,如果没有对应的驱动程序可用,还可以通过外部模块的方式添加。 2. 载入驱动程序:在选择了合适的驱动程序后,需要进行安装和载入。如果该驱动程序已经内建在内核中,则可以直接使用。如果是外部模块,则需要使用modprobe或insmod命令进行载入。 3. PHY初始化:在驱动程序安装和载入之后,需要进行PHY初始化操作,包括读取PHY寄存器、配置寄存器、设置PHY的速率和模式等。 4. 端口初始化:在PHY初始化完成后,还需要对端口进行初始化。这包括设置MAC地址、配置速率和模式、分配内存区域等操作。 5. 连接检测:在PHY和端口初始化完成后,需要进行连接检测,以确保设备和网线之间的连接正常。 6. 启动设备:所有初始化操作完成后,就可以启动设备了。如果是网络设备,还需要进行IP地址的设置和路由表的配置。 总之,Linux PHY初始化过程是一个比较复杂的过程,需要对硬件设备和驱动程序有深入的了解。只有在各个部分都正常工作的情况下,网络设备才能正常连接网络并进行通信。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值