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

    好的,本次的分析就到這裏了,思路就是這樣的,代碼我就不詳解了,因爲沒什麼難的,思路理清了,代碼就在這,不會跑,如果遇到問題了,那就按照思路去看代碼,然後解決問題。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章