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