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
好的,本次的分析就到這裡了,思路就是這樣的,程式碼我就不詳解了,因為沒什麼難的,思路理清了,程式碼就在這,不會跑,如果遇到問題了,那就按照思路去看程式碼,然後解決問題。