Linux 内核中的 PHY 状态定义

enum phy_state {
	PHY_DOWN = 0, // PHY芯片和驱动没准备好,一般情况下少发生
	PHY_STARTING, // PHY芯片OK了,但驱动还没有准备好
	PHY_READY,    // 准备好了,在probe中赋值,接下来会切到PHY_UP
	PHY_PENDING,
	PHY_UP,       // phy启动了,可以工作了,接下来会到PHY_AN
	PHY_AN,       // 自动协商
	PHY_RUNNING,  // 正在运行中,在网络连接(插上网线)时会到这个状态
	PHY_NOLINK,   // 断网了
	PHY_FORCING,  // 强制,当自动协商不使能时,就会进行此状态(实际上会读PHY寄存器进行设置速率、双工,等)
	PHY_CHANGELINK, // 变化,这个状态很重要,当连接时,会换到PHY_RUNNING,当断网时,会切到PHY_NOLINK
	PHY_HALTED,
	PHY_RESUMING
};

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, do_resume = false;
	int err = 0;
 
	mutex_lock(&phydev->lock);
 
	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); // 读phy状态,包括link,速率、双工,等等
		if (err < 0)
			break;
 
		/* If the link is down, give up on negotiation for now */
		if (!phydev->link) {
			phydev->state = PHY_NOLINK; // 没有连接,则状态变成PHY_NOLINK
			netif_carrier_off(phydev->attached_dev); // 通知内核其它网络模块(phy是最底一层)断网了。
			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; // 完成后,变成PHY_RUNNING状态
			netif_carrier_on(phydev->attached_dev); // 发通知,连接OK
			phydev->adjust_link(phydev->attached_dev); // 打印、调用参数
 
		} else if (0 == phydev->link_timeout--)
			needs_aneg = true;
		break;
	case PHY_NOLINK:
		err = phy_read_status(phydev); // 读phy状态,包括link,速率、双工,等等
		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
		 */
		if (!phy_interrupt_is_valid(phydev))
			phydev->state = PHY_CHANGELINK; // 如果是RUNNING,则改变为CHANGELINK。
		break;
	case PHY_CHANGELINK:
		err = phy_read_status(phydev); // 读phy状态,包括link,速率、双工,等等
		if (err)
			break;
 
		if (phydev->link) {
			phydev->state = PHY_RUNNING; // 连接网络时,则变成RUNNING
			netif_carrier_on(phydev->attached_dev);
		} else {
			phydev->state = PHY_NOLINK;  // 不连网时,变成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:
		err = phy_clear_interrupt(phydev);
		if (err)
			break;
 
		err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED);
		if (err)
			break;
 
		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); // 读phy状态,包括link,速率、双工,等等
			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);
		}
		do_resume = true;
		break;
	}
 
	mutex_unlock(&phydev->lock);
 
	if (needs_aneg)
		err = phy_start_aneg(phydev);
	else if (do_suspend)
		phy_suspend(phydev);
	else if (do_resume)
		phy_resume(phydev);
 
	if (err < 0)
		phy_error(phydev);
 
	queue_delayed_work(system_power_efficient_wq, &phydev->state_queue,
			   PHY_STATE_TIME * HZ);
}

PHY 状态

上电时状态变化:
PHY_READY -> PHY_UP -> PHY_AN -> PHY_RUNNING

拨出网线时状态变化:
PHY_RUNNING ->PHY_NOLINK

插上网线时状态变化:
PHY_NOLINK -> PHY_RUNNING

自动协商过程:
cpsw_ndo_open->cpsw_slave_open -> PHY_UP -> phy_start_aneg -> genphy_config_aneg -> genphy_config_advert -> genphy_restart_aneg -> PHY_AN -> PHY_NOLINK(串口打印Down) -> phy_aneg_done -> PHY_RUNNING(串口打印Up)

注:在 AN 后出现 NOLINK 状态,我猜是因为自动协商需要时间,此时间大于 1 秒,然后执行到状态机判断成 NOLINK,然后判断是否完成自动协商,然后再到 RUNNING 状态。

参考

Linux PHY几个状态的跟踪

基于STM32+W5500 的Ethernet和Internet移植

基于STM32和W5500的Modbus TCP通讯

W5500 = 28J60 + LwIP