https://blog.csdn.net/subfate/article/details/44900665

前面文章零零星星地分析了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);

boollink = 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;

u32mac_control = 0;

u32slave_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状态。

本文分析基于一定的实践经验,限于能力,个中错误难免,将会择机更正。

2015年4月6日,李迟,于清明假期

linux 获取phy状态,Linux PHY几个状态的跟踪相关推荐

  1. linux 获取启动时间不对,linux获取系统启动时间

    1.前言 时间对操作系统来说非常重要,从内核级到应用层,时间的表达方式及精度各部相同.linux内核里面用一个名为jiffes的常量来计算时间戳.应用层有time.getdaytime等函数.今天需要 ...

  2. linux获取互联网时间,linux中用shell获取时间,日期

    linux中用shell获取昨天.明天或多天前的日期: 在Linux中对man date -d 参数说的比较模糊,以下举例进一步说明: # -d, --date=STRING display time ...

  3. linux获取随机数脚本,Linux下对拍脚本与随机数生成器

    对拍脚本 新建一个文档 check.sh 作为对拍脚本. #!/bin/bash while(true)do #死循环 ./data > .in #运行数据生成器,将数据输出到1.in ./st ...

  4. 编程linux 获取cpu核数,Linux命令查看以及编程获取CPU核数

    ㈠ 概念 ① 物理CPU 实际Server中插槽上的CPU个数 物理cpu数量,可以数不重复的 physical id 有几个 ② 逻辑CPU Linux用户对 /proc/cpuinfo 这个文件肯 ...

  5. linux 获取文件名函数,Linux shell中提取文件名和路径

    前言 有个软件叫HLAreporter,它真的搞人,基本上就是shell脚本联合起来的,关键是居然还有各种小问题,其中之一就是关于文件名和目录名这个,导致程序只能在软件本身的目录运行,然后各种bug, ...

  6. linux 获取硬件时间,Linux的硬件时间及系统时间调用流程--转自网络

    事实上在Linux中有两个时钟系统,分别是系统时间和硬件时间 UTC是协调世界时(Universal Time Coordinated)英文缩写,它比北京时间早8个小时. 二)date date可以打 ...

  7. kali linux获取root,kali linux 安装keybase 并使用root来运行keybase

    1. keybase 是什么? 可以参考这个页面 https://zhuanlan.zhihu.com/p/23953972 2. 在kali linux上安装keybase 点击(此处)折叠或打开 ...

  8. linux获取进程io,linux查看哪个进程占用磁盘IO

    [python实现设计模式]-4.观察者模式-吃食啦! 观察者模式是一个非常重要的设计模式. 我们先从一个故事引入. 工作日的每天5点左右,大燕同学都会给大家订饭. 然后7点左右,饭来了. 于是燕哥大 ...

  9. linux获取字符格式化,Linux 格式化字符串漏洞利用

    目的是接觸一些常見的漏洞,增加自己的視野.格式化字符串危害最大的就兩點,一點是leak memory,一點就是可以在內存中寫入數據,簡單來說就是格式化字符串可以進行內存地址的讀寫.下面結合着自己的學習 ...

  10. linux 获取视频截图,linux ffmpeg 视频截图 安装使用

    Ubuntu 12.04 在安装ffmpeg之前,首先要安装yasm 1.安装yasm 我用的yasm是yasm-1.2.0.tar.gz这个版本的 #wget http://www.tortall. ...

最新文章

  1. requests payload_python+Requests接口自动化测试之传递 URL 参数
  2. 滴滴海量离线数据的在线化 — FastLoad
  3. 139.00.007 Git学习-Cheat Sheet
  4. 嵌入式成长轨迹23 【Linux应用编程强化】【Linux下的C编程 下】【Linux GUI编程】...
  5. 5G边缘计算:开源架起5G MEC生态发展新通路
  6. c语言怎么加分数,用C语言编程平均分数
  7. WEB标准学习路程之CSS:7.表格,滚动条,打印
  8. Q85:对比“直接光照”和“间接光照”的反射模型
  9. SSH: scp 拉取云端文件到本地端
  10. 关于阿里云,有什么故事?
  11. html - 鼠标悬停文本内容与边框变色
  12. HTML下拉菜单代码实现
  13. “crol/cror”函数实现流水灯
  14. ubuntu端口映射
  15. 从本钢板B看低市盈率投资收益
  16. 象棋大战 v1.075 免费
  17. 为什么abc三相电压加起来是0,而坐标变换之后在dq0坐标系中有值呢?——矢量控制中abc到dq0坐标系的坐标变换的思考
  18. 网络安全-WEB中的常见编码
  19. java 连接wtc_Tuxedo通过WTC调用weblogic配置
  20. Java二维数组及面向对象介绍--------08

热门文章

  1. Day267/300 Mac微信小程序引入@vant/weapp样式不生效问题
  2. tree view android,AndroidTreeView
  3. 我找不到我的电子邮件从 Outlook 数据文件 (.pst) 导入后,收发功能正常
  4. 【算法分析】ABCDE*A=EEEEEE的问题
  5. js时间戳转字符串 时间戳转自定义格式
  6. python双色球的开发原理_我用Python爬虫获取数据,分析双色球中奖概率,差点就中了...
  7. 数据库管理-第二十期(20210304)
  8. javamail发送邮件带附件发送
  9. keepalived的vip无法ping通排查过程
  10. 奥特曼的音乐简谱计算机,奥特曼简谱(歌词)-林吾耀