PHY的12種狀態 enum phy_state { PHY_DOWN = 0, //關閉網卡 PHY_STARTING, //PHY設備準備好了,PHY driver尚為準備好 PHY_READY, //PHY設備註冊成功 PHY_PENDING, //PHY晶元掛起 PHY_UP, //開啟網卡 ...
PHY的12種狀態
enum phy_state {
PHY_DOWN = 0, //關閉網卡
PHY_STARTING, //PHY設備準備好了,PHY driver尚為準備好
PHY_READY, //PHY設備註冊成功
PHY_PENDING, //PHY晶元掛起
PHY_UP, //開啟網卡
PHY_AN, //網卡自協商
PHY_RUNNING, //網卡已經插入網線並建立物理連接,該狀態可切換到PHY_CHANGELINK
PHY_NOLINK, //斷網,拔掉網線
PHY_FORCING,//自動協商失敗,強制處理(讀phy狀態寄存器,設置速率,設置工作模式)
PHY_CHANGELINK, //LINK檢查,當物理連接存在時切換到PHY_RUNING,物理連接不存在時切換到PHY_NOLINK
PHY_HALTED, //網卡關閉時,PHY掛起
PHY_RESUMING //網卡開啟時,PHY恢復
};
PHY狀態機
PHY指PHY晶元,負責數據傳送與接收所需要的電與光信號、線路狀態、時鐘基準、數據編碼和電路等,並向數據鏈路層設備提供標準介面。
MAC指MAC晶元,屬於數據鏈路層,提供定址機構、數據幀的構建、數據差錯檢查、傳送控制、向網路層提供標準的數據介面等功能。
PHY_DOWN: phy、phy driver、mac都沒準備好
- 如果phy driver被集成在內核中,PHY.probe後,phydev狀態為PHY_READY。
- 如果phy driver被未集成在內核中,PHY.probe後,phydev狀態為PHY_STARTING。
PHY_READY:phy、phy driver已經就緒,mac未準備好
當MAC層載入時,在PHY.start後,phydev狀態切換為PHY_UP。
PHY_STARTING:phy準備就緒,phy driver、mac未準備好
- 當MAC載入時,PHY.start後,phydev狀態為PHY_PENDING。
- 當phy driver載入時,phydev狀態為PHY_READY。
PHY_PENDING:phy、mac準備就緒,phy driver未準備好
當phy dirver載入後,phdev狀態為PHY_UP
上圖中0-->1-->2-->4、0-->2-->4代表phy、phy dirver、mac順序載入。
0-->1-->3-->4代表phy、mac、phy driver順序載入。
PHY_UP:phy、phy driver、mac準備就緒
當前狀態將啟動自動協商,若啟動成功則進入PHY_AN,若啟動失敗則進入PHY_FORCING。
PHY_AN:網卡自協商模式,檢測自協商是否完成。
先判斷物理鏈路的狀態,如果未LINK則進入PHY_NOLINK,如果LINK則判斷自協商是否完成,
自協商完成進入PHY_RUNNING,若自協商超時則重新開啟自協商。
PHY_FORCING:強制協商
讀link和自協商狀態寄存器,如果狀態正常則進入PHY_RUNNING模式。
PHY_NOLINK:物理鏈路未連接
判斷物理鏈路狀態,如果LINK,再判斷是否支持自協商,若支持待自協商完成後進入PHY_RUNNING模式,
若不支持,直接進入PHY_RUNNING模式。若自協商處於掛起狀態,則進入PHY_AN模式。
PHY_RUNNING:正常運行中
獲取當前link狀態,當link狀態發生改變時,進入PHY_CHANGELINK模式。
PHY_CHANGELINK:檢查物理鏈路
物理鏈路link時,切換到PHY_RUNNING,非LINK時切換到PHY_NOLINK。
PHY_HALTED:網卡關閉phy_stop
掛起phy
PHY_RESUMING: 網卡啟用phy_start
恢復phy
phy_state_machine
是PHY的狀態機函數
/**
* phy_state_machine - Handle the state machine
* @work: work_struct that describes the work to be done
*/
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:
if (phy_interrupt_is_valid(phydev))
break;
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;
}
/*
* Failsafe: check that nobody set phydev->link=0 between two
* poll cycles, otherwise we won't leave RUNNING state as long
* as link remains down.
*/
if (!phydev->link && phydev->state == PHY_RUNNING) {
phydev->state = PHY_CHANGELINK;
dev_err(&phydev->dev, "no link in PHY_RUNNING\n");
}
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);
}