转:Linux PHY几个状态的跟踪

 

 

MAC驱动和PHY驱动

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

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片

  1. davinci_mdio_probe  
  2.  ->mdiobus_register  
  3.     -> device_register  
  4.     -> mdiobus_scan  
  5.          -> get_phy_device  
  6.               -> get_phy_id // 读寄存器  
  7.               -> phy_device_create  
  8.                    -> INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine); // !!!!!!初始化状态机函数  
  9.          -> 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状态机开启。

这个过程主要的函数如下:

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片

  1. cpsw_ndo_open  
  2. -> cpsw_slave_open  
  3.    -> phy_connect (传递cpsw_adjust_link)  
  4.       -> phy_connect_direct (PHY_READY)  
  5.       ->  phy_prepare_link (赋值cpsw_adjust_link为adjust_link)  
  6.       -> phy_start_machine  
  7.    -> phy_start (PHY_READY变成PHY_UP)  

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

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片

  1. static void cpsw_adjust_link(struct net_device *ndev)  
  2. {  
  3.     struct cpsw_priv    *priv = netdev_priv(ndev);  
  4.     bool            link = false;  
  5.   
  6.     for_each_slave(priv, _cpsw_adjust_link, priv, &link);  
  7.   
  8.     if (link) {  
  9.         netif_carrier_on(ndev); // 通知内核子系统网络,当前链接是OK的  
  10.         if (netif_running(ndev))  
  11.             netif_wake_queue(ndev);  
  12.     } else {  
  13.         netif_carrier_off(ndev); // 通知内核子系统网络,当前链接断开了  
  14.         netif_stop_queue(ndev);  
  15.     }  
  16. }  

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

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片

  1. static void _cpsw_adjust_link(struct cpsw_slave *slave,  
  2.                   struct cpsw_priv *priv, bool *link)  
  3. {  
  4.     struct phy_device   *phy = slave->phy;  
  5.     u32         mac_control = 0;  
  6.     u32         slave_port;  
  7.   
  8.     if (!phy)  
  9.         return;  
  10.   
  11.     slave_port = cpsw_get_slave_port(priv, slave->slave_num);  
  12.   
  13.     if (phy->link) {  
  14.         mac_control = priv->data.mac_control;  
  15.   
  16.           
  17.         cpsw_ale_control_set(priv->ale, slave_port,  
  18.                      ALE_PORT_STATE, ALE_PORT_STATE_FORWARD);  
  19.   
  20.         if (phy->speed == 1000) // 千兆  
  21.             mac_control |= BIT(7);    
  22.         if (phy->duplex)  
  23.             mac_control |= BIT(0);    
  24.   
  25.           
  26.         if (phy->speed == 100) // 百兆  
  27.             mac_control |= BIT(15);  
  28.         else if (phy->speed == 10) // 十兆  
  29.             mac_control |= BIT(18);   
  30.   
  31.         *link = true;  
  32.     } else {  
  33.         mac_control = 0;  
  34.           
  35.         cpsw_ale_control_set(priv->ale, slave_port,  
  36.                      ALE_PORT_STATE, ALE_PORT_STATE_DISABLE);  
  37.     }  
  38.   
  39.     if (mac_control != slave->mac_control) {  
  40.         phy_print_status(phy); // 当状态不同时,需要写寄存器时,才打印网络状态  
  41.         __raw_writel(mac_control, &slave->sliver->mac_control);  
  42.     }  
  43.   
  44.     slave->mac_control = mac_control;  
  45. }  

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

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片

  1. void phy_print_status(struct phy_device *phydev)  
  2. {  
  3.     if (phydev->link) {  
  4.         netdev_info(phydev->attached_dev,  
  5.             "Link is Up - %s/%s - flow control %s\n",  
  6.             phy_speed_to_str(phydev->speed),  
  7.             DUPLEX_FULL == phydev->duplex ? "Full" : "Half",  
  8.             phydev->pause ? "rx/tx" : "off");  
  9.     } else  {  
  10.         netdev_info(phydev->attached_dev, "Link is Down\n");  
  11.     }  
  12. }  

其中的phy_speed_to_str函数是将网速转化成字符串,在内核的旧版本上是没有的。

当网络连接时,会打印如下信息:

PHY: 2:50 - Link is Up - 100Mbps/Full - flow control off
当网络断开时,会打印:

PHY: 2:50 - Link is Down

PHY状态机

先看看PHY有的状态定义:

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片

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

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

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片

  1.   
  2. void phy_state_machine(struct work_struct *work)  
  3. {  
  4.     struct delayed_work *dwork = to_delayed_work(work);  
  5.     struct phy_device *phydev =  
  6.             container_of(dwork, struct phy_device, state_queue);  
  7.     bool needs_aneg = false, do_suspend = false, do_resume = false;  
  8.     int err = 0;  
  9.   
  10.     mutex_lock(&phydev->lock);  
  11.   
  12.     if (phydev->drv->link_change_notify)  
  13.         phydev->drv->link_change_notify(phydev);  
  14.   
  15.     switch (phydev->state) {  
  16.     case PHY_DOWN:  
  17.     case PHY_STARTING:  
  18.     case PHY_READY:  
  19.     case PHY_PENDING:  
  20.         break;  
  21.     case PHY_UP:  
  22.         needs_aneg = true;  
  23.   
  24.         phydev->link_timeout = PHY_AN_TIMEOUT; // 超时,自动协商不成功时,则会在超时后强制设置速率等参数  
  25.   
  26.         break;  
  27.     case PHY_AN:  
  28.         err = phy_read_status(phydev); // 读phy状态,包括link,速率、双工,等等  
  29.         if (err < 0)  
  30.             break;  
  31.   
  32.           
  33.         if (!phydev->link) {  
  34.             phydev->state = PHY_NOLINK; // 没有连接,则状态变成PHY_NOLINK  
  35.             netif_carrier_off(phydev->attached_dev); // 通知内核其它网络模块(phy是最底一层)断网了。  
  36.             phydev->adjust_link(phydev->attached_dev); // 调整参数(速率、双工)  
  37.             break;  
  38.         }  
  39.   
  40.           
  41.         err = phy_aneg_done(phydev); // 检测是否完成自动协商  
  42.         if (err < 0)  
  43.             break;  
  44.   
  45.           
  46.         if (err > 0) {  
  47.             phydev->state = PHY_RUNNING; // 完成后,变成PHY_RUNNING状态  
  48.             netif_carrier_on(phydev->attached_dev); // 发通知,连接OK  
  49.             phydev->adjust_link(phydev->attached_dev); // 打印、调用参数  
  50.   
  51.         } else if (0 == phydev->link_timeout--)  
  52.             needs_aneg = true;  
  53.         break;  
  54.     case PHY_NOLINK:  
  55.         err = phy_read_status(phydev); // 读phy状态,包括link,速率、双工,等等  
  56.         if (err)  
  57.             break;  
  58.   
  59.         if (phydev->link) { // 在断开网络再连接(即拨掉再插上网线),就进入此语句  
  60.             if (AUTONEG_ENABLE == phydev->autoneg) {  
  61.                 err = phy_aneg_done(phydev); // 如果是自动协商使能,就进行自动协商  
  62.                 if (err < 0)  
  63.                     break;  
  64.   
  65.                 if (!err) {  
  66.                     phydev->state = PHY_AN;  
  67.                     phydev->link_timeout = PHY_AN_TIMEOUT;  
  68.                     break;  
  69.                 }  
  70.             }  
  71.             phydev->state = PHY_RUNNING; // 运行时。。。。。  
  72.             netif_carrier_on(phydev->attached_dev);  
  73.             phydev->adjust_link(phydev->attached_dev);  
  74.         }  
  75.         break;  
  76.     case PHY_FORCING:  
  77.         err = genphy_update_link(phydev); // 先更新状态  
  78.         if (err)  
  79.             break;  
  80.   
  81.         if (phydev->link) {  
  82.             phydev->state = PHY_RUNNING; // 运行。。。  
  83.             netif_carrier_on(phydev->attached_dev);  
  84.         } else {  
  85.             if (0 == phydev->link_timeout--)  
  86.                 needs_aneg = true;  
  87.         }  
  88.   
  89.         phydev->adjust_link(phydev->attached_dev);  
  90.         break;  
  91.     case PHY_RUNNING:  
  92.           
  93.         if (!phy_interrupt_is_valid(phydev))  
  94.             phydev->state = PHY_CHANGELINK; // 如果是RUNNING,则改变为CHANGELINK。  
  95.         break;  
  96.     case PHY_CHANGELINK:  
  97.         err = phy_read_status(phydev); // 读phy状态,包括link,速率、双工,等等  
  98.         if (err)  
  99.             break;  
  100.   
  101.         if (phydev->link) {  
  102.             phydev->state = PHY_RUNNING; // 连接网络时,则变成RUNNING  
  103.             netif_carrier_on(phydev->attached_dev);  
  104.         } else {  
  105.             phydev->state = PHY_NOLINK;  // 不连网时,变成NOLINK  
  106.             netif_carrier_off(phydev->attached_dev);  
  107.         }  
  108.   
  109.         phydev->adjust_link(phydev->attached_dev);  
  110.   
  111.         if (phy_interrupt_is_valid(phydev))  
  112.             err = phy_config_interrupt(phydev,  
  113.                            PHY_INTERRUPT_ENABLED);  
  114.         break;  
  115.     case PHY_HALTED:  
  116.         if (phydev->link) {  
  117.             phydev->link = 0;  
  118.             netif_carrier_off(phydev->attached_dev);  
  119.             phydev->adjust_link(phydev->attached_dev);  
  120.             do_suspend = true;  
  121.         }  
  122.         break;  
  123.     case PHY_RESUMING:  
  124.         err = phy_clear_interrupt(phydev);  
  125.         if (err)  
  126.             break;  
  127.   
  128.         err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED);  
  129.         if (err)  
  130.             break;  
  131.   
  132.         if (AUTONEG_ENABLE == phydev->autoneg) {  
  133.             err = phy_aneg_done(phydev);  
  134.             if (err < 0)  
  135.                 break;  
  136.   
  137.               
  138.             if (err > 0) {  
  139.                 err = phy_read_status(phydev);  
  140.                 if (err)  
  141.                     break;  
  142.   
  143.                 if (phydev->link) {  
  144.                     phydev->state = PHY_RUNNING;  
  145.                     netif_carrier_on(phydev->attached_dev);  
  146.                 } else  {  
  147.                     phydev->state = PHY_NOLINK;  
  148.                 }  
  149.                 phydev->adjust_link(phydev->attached_dev);  
  150.             } else {  
  151.                 phydev->state = PHY_AN;  
  152.                 phydev->link_timeout = PHY_AN_TIMEOUT;  
  153.             }  
  154.         } else {  
  155.             err = phy_read_status(phydev); // 读phy状态,包括link,速率、双工,等等  
  156.             if (err)  
  157.                 break;  
  158.   
  159.             if (phydev->link) {  
  160.                 phydev->state = PHY_RUNNING;  
  161.                 netif_carrier_on(phydev->attached_dev);  
  162.             } else  {  
  163.                 phydev->state = PHY_NOLINK;  
  164.             }  
  165.             phydev->adjust_link(phydev->attached_dev);  
  166.         }  
  167.         do_resume = true;  
  168.         break;  
  169.     }  
  170.   
  171.     mutex_unlock(&phydev->lock);  
  172.   
  173.     if (needs_aneg)  
  174.         err = phy_start_aneg(phydev);  
  175.     else if (do_suspend)  
  176.         phy_suspend(phydev);  
  177.     else if (do_resume)  
  178.         phy_resume(phydev);  
  179.   
  180.     if (err < 0)  
  181.         phy_error(phydev);  
  182.   
  183.     queue_delayed_work(system_power_efficient_wq, &phydev->state_queue,  
  184.                PHY_STATE_TIME * HZ);  
  185. }  

经过一大段的分析研究后,当网络发生变化时,就十分清晰了。

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值