PHY状态机分析

  • PHY状态机分析已关闭评论
  • 118 次浏览
  • A+
所属分类:linux技术
摘要

PHY指PHY芯片,负责数据传送与接收所需要的电与光信号、线路状态、时钟基准、数据编码和电路等,并向数据链路层设备提供标准接口。
MAC指MAC芯片,属于数据链路层,提供寻址机构、数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能。
PHY_DOWN: phy、phy driver、mac都没准备好


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指PHY芯片,负责数据传送与接收所需要的电与光信号、线路状态、时钟基准、数据编码和电路等,并向数据链路层设备提供标准接口。
MAC指MAC芯片,属于数据链路层,提供寻址机构、数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能。
PHY_DOWN: phy、phy driver、mac都没准备好

  1. 如果phy driver被集成在内核中,PHY.probe后,phydev状态为PHY_READY。
  2. 如果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未准备好

  1. 当MAC加载时,PHY.start后,phydev状态为PHY_PENDING。
  2. 当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_RUNNINGn"); 		} 		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 -> %sn", 		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); }