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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kl1125290220/article/details/79859742

以太网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

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


猜你喜欢

转载自blog.csdn.net/kl1125290220/article/details/79859742