当前位置: 首页 > GNU/Linux程序, 嵌入式Linux > 正文

Linux PHY几个状态的跟踪

前面文章零零星星地分析了PHY,本来想完整地,系统地做分析,发现工程量太大了,而自己又一知半解,所以只好各个击破,一点一点来分析。本文主要分析了设备上电、拨出网线、插上网线、自动协商等过程的PHY状态。

MAC驱动和PHY驱动

PHY一般和具体的MAC控制驱动联系一起,这里以TI的MAC驱动为例,由它切入到PHY驱动。Linux内核通过mdio总线访问、控制PHY,源码实现在driver/net/phy/mdio_bus.c中。下面是mdio扫描、找到并注册phy的过程:

davinci_mdio_probe ->mdiobus_register -> device_register -> mdiobus_scan -> get_phy_device -> get_phy_id // 读寄存器 -> phy_device_create -> INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); // !!!!!!初始化状态机函数 -> phy_device_register

在phy_device_create中做了大量的初始化工作,比如默认就是使能自动协商,另外调用INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine)创建phy的状态机,——实际上它是一个延时工作队列。
cpsw驱动在net_device_ops的ndo_open函数,亦即cpsw_ndo_open中调用cpsw_slave_open,通过phy_connect与phy连接,同时将cpsw_adjust_link赋值给phy的状态调整函数指针adjust_link。在些过程将将PHY状态机开启。
这个过程主要的函数如下:

cpsw_ndo_open -> cpsw_slave_open -> phy_connect (传递cpsw_adjust_link) -> phy_connect_direct (PHY_READY) -> phy_prepare_link (赋值cpsw_adjust_link为adjust_link) -> phy_start_machine -> phy_start (PHY_READY变成PHY_UP)

当系统启动时,经过上述的步骤,一切已经准备妥当。就等着迎接PHY的状态变更了。在这里,需要提及的函数是cpsw_adjust_link,它调用了_cpsw_adjust_link,之后通知内核其它网络模块当前的状态。这个函数将在phy状态机函数中时时被调用,所以要关注一下。代码如下:

static void cpsw_adjust_link(struct net_device *ndev) { struct cpsw_priv *priv = netdev_priv(ndev); bool link = false; for_each_slave(priv, _cpsw_adjust_link, priv, &link); if (link) { netif_carrier_on(ndev); // 通知内核子系统网络,当前链接是OK的 if (netif_running(ndev)) netif_wake_queue(ndev); } else { netif_carrier_off(ndev); // 通知内核子系统网络,当前链接断开了 netif_stop_queue(ndev); } }

真正干活(设置)的是这个函数:

static void _cpsw_adjust_link(struct cpsw_slave *slave, struct cpsw_priv *priv, bool *link) { struct phy_device *phy = slave->phy; u32 mac_control = 0; u32 slave_port; if (!phy) return; slave_port = cpsw_get_slave_port(priv, slave->slave_num); if (phy->link) { mac_control = priv->data.mac_control; /* enable forwarding */ cpsw_ale_control_set(priv->ale, slave_port, ALE_PORT_STATE, ALE_PORT_STATE_FORWARD); if (phy->speed == 1000) // 千兆 mac_control |= BIT(7); /* GIGABITEN */ if (phy->duplex) mac_control |= BIT(0); /* FULLDUPLEXEN */ /* set speed_in input in case RMII mode is used in 100Mbps */ if (phy->speed == 100) // 百兆 mac_control |= BIT(15); else if (phy->speed == 10) // 十兆 mac_control |= BIT(18); /* In Band mode */ *link = true; } else { mac_control = 0; /* disable forwarding */ cpsw_ale_control_set(priv->ale, slave_port, ALE_PORT_STATE, ALE_PORT_STATE_DISABLE); } if (mac_control != slave->mac_control) { phy_print_status(phy); // 当状态不同时,需要写寄存器时,才打印网络状态 __raw_writel(mac_control, &slave->sliver->mac_control); } slave->mac_control = mac_control; }

它实际上写mac_control寄存器,这个寄存器控制着速率(千兆、百兆、十兆)和双工。之前不太理解,问了高手,才知道不单单要设置PHY寄存器,还要设置mac控制模块的寄存器。phy_print_status是phy驱动的通用函数,用以打印网络状态(初步查了下,像Intel的网络驱动,不调用此函数,等有空再研究研究)。

void phy_print_status(struct phy_device *phydev) { if (phydev->link) { netdev_info(phydev->attached_dev, "Link is Up - %s/%s - flow control %s\n", phy_speed_to_str(phydev->speed), DUPLEX_FULL == phydev->duplex ? "Full" : "Half", phydev->pause ? "rx/tx" : "off"); } else { netdev_info(phydev->attached_dev, "Link is Down\n"); } }

其中的phy_speed_to_str函数是将网速转化成字符串,在内核的旧版本上是没有的。
当网络连接时,会打印如下信息:

PHY: 2:50 - Link is Up - 100Mbps/Full - flow control off

当网络断开时,会打印:

PHY: 2:50 - Link is Down

PHY状态机

先看看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函数,该函数一直在运行(每隔一秒检测一次网络状态),该函数判断不同的网络状态作出不同的动作。其中CHANGELINK是会根据网络连、断来判断是RUNNING还是NOLINK。这样,就知道网络是连接上还是断开。当连接上网络后(注:不断开情况),状态为RUNNING时,之后重新赋值CHANGELINK,到了CHANGELINK又赋值RUNNING,这两种状态之间不断切换。完整代码如下:

/** * 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状态。

本文源码分析基于3.17版本内核,地址:http://lxr.oss.org.cn/source/drivers/net/ethernet/ti/?v=3.17
本文分析基于一定的实践经验,限于能力,个中错误难免,将会择机更正。
2015年4月6日,李迟,于清明假期

本文固定链接: http://www.latelee.org/programming-under-linux/linux-phy-state.html

如无特别说明,迟思堂工作室文章均为原创,转载请注明: Linux PHY几个状态的跟踪 | 迟思堂工作室

Linux PHY几个状态的跟踪:目前有1 条留言

  1. 1楼
    羽落:

    您好,

    请教您一个问题

    关于网口 以下连接方式是否可行:
    (SGMII)
    RJ45 — PHY ————- PHY — RJ45

    2016-11-18 下午6:19 [回复]

发表评论

*

快捷键:Ctrl+Enter