Linux PHY几个状态的跟踪__cpsw_adjust_link_李迟的博客-程序员宅基地

技术标签: 网络  GNU/Linux  

前面文章零零星星地分析了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日,李迟,于清明假期



版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/subfate/article/details/44900665

智能推荐

解决opencv在vs code中头文件找不到的问题_cv2头文件没有_Jzzzzzzzzzz的博客-程序员宅基地

在一段程序中,有引用opencv头文件#include &lt;iostream&gt;#include &lt;opencv2/core/core.hpp&gt;#include &lt;opencv2/features2d/features2d.hpp&gt;#include &lt;opencv2/highgui/highgui.hpp&gt;尽管cmake会通过,但这时候vs code在这些下面会出现红色波浪线无法打开 源 文件 "opencv2/core/core.hpp"无法打开

股票技术指标atr是什么含义_股票技术指标公式详解K线图分析_atr在股市是什么意思_谷㎡的博客-程序员宅基地

量化分析海龟训练营学员:常常能听到一些技术指标,比如MACD、均线以及ATR等等,ATR与前面两个技术指标的逻辑相同吗?同济桥博士:ATR的逻辑跟我们之前看到的MACD、均线这些指标是不一样的。这里要详细的说一下,它求的是什么值。ATR是求三个数值里面的最大值。哪三个数值呢?一个是最高价减去最低价之间的差。这是什么意思?比如说我们拿最近的一天的K线来看,K线最上面是最高价,最下面是最低价,这之间的差就是这第一个值。第二个是前一天的收盘价减去最高价,第三个是前一天的收盘价减去最低价。量化分析.

电商秒杀系统相关实现_等一杯咖啡的博客-程序员宅基地

本文主要就电商秒杀系统所涉及的相关技术进行探究,相关Demo地址如下:- 个人实现版本:[https://github.com/MrSorrow/seckill](https://github.com/MrSorrow/seckill)- Github Star较高的版本,第一版也是基于慕课网的:[https://github.com/qiurunze123/miaosha](https://github.com/qiurunze123/miaosha)本Demo实现了电商项目的秒杀功能,主要内容包

使用bazel 编译tensorflow serving时报错:fatal error: Python.h: No such file or directory_雪域高原1的博客-程序员宅基地

使用bazel 编译tensorflow serving时报错:fatal error: Python.h: No such file or directory即:____[2,911 / 3,824] Compiling external/org_tensorflow/tensorflow/core/ops/script_ops.ccERROR: /root/.cache/bazel

win10安装Tensorflow-GPU版本和Pytorch-GPU版本_林小李的博客-程序员宅基地

总结:本文安装的版本信息Win10 + 独立显卡Geforce RTX 2080 Ti +CUDA11.1.0 + cuDNN 8.1.0 + Python3.7 +Anaconda3-5.3.0-Windows-x86_64 +conda 4.10.0 +Spyder 3.3.1 + pytorch_gpu1.8.1 +tensorflow_gpu 2.4.1步骤1-19为安装tensorflow_gpu和pytorch_gpu的必须项,基本环境步骤20-22为安装tpytorch_gpu

随便推点

利用new Date()获取的时间与系统不匹配_sdf1.parse 时间不准了_ghostpanttt的博客-程序员宅基地

利用new Date()获取的时间与系统不匹配问题的出现:在使用hibernate进行mysql数据库存储时,出现异常错误 1. Could not execute JDBC batch update错误2. Data truncation: Incorrect datetime value问题的原因:首先这两个异常的原因,都是因为数据库存储日期时datetime类型,而ja...

【TurtleBot3 Waffle (tx2版华夫)ROS平台】使用教程(3)opencr系统安装_智能佳机器人的博客-程序员宅基地

TurtleBot3 Waffle(tx2版华夫)ROS平台 智能小车opencr系统安装 3. 1. 安装 Arduino IDE 3.2. opencr固件包下载 3.3. 端口设置 3.4.固件编译与下载 opencr系统安装3.1.安装 Arduino IDEOpencr的安装环境的安装包,双击打开即可。进入安装的过程。在这里你可以选择自己的安装位置。安装已经完成,点击CLOSE 即可。...

信号与系统——傅里叶变换_信号与系统傅里叶变换_粉刷乌鸦的博客-程序员宅基地

对于所有涉及到电子、通信、控制等等专业的人来说,傅里叶变换是绕不过去的一个坎。傅里叶变换的核心就是:一切的波形都可以由不同频率的正弦波的叠加来表示,这些不同频率的正弦波称为频率分量。cos(0t)就是一个周期无限长的正弦波,也就是一条直线!所以在频域,0频率也被称为直流分量,在傅里叶级数的叠加中,它仅仅影响全部波形相对于数轴整体向上或是向下而不改变波的形状。 傅里叶变换的作用是将原来难以处理的时域信号转换为易于分析的频域信号,也就是信号的频谱。在频域进行处理和加工之后,还可以利用傅里叶反变换将

python input() int str 比较_啊呦ss的博客-程序员宅基地

python input 输入时,返回值为 str类型,如所需为int型,则强制类型转换。int型无len()属性。另 对比两者:

SAP中关于物料主数据里物料类型的修改_乐期无许的博客-程序员宅基地

物料主数据的物料类型建错了还是可以更改的。事务码:MMAM。前提条件是:没有库存;没有预留;没有采购文档(采购申请、采购订单、采购合同等等)。注:若此三个条件部分不满足,还是要改物料类型的话,也是有可能的,条件:改之后的物料类型与改之前的物料类型的数量更新和价值更新要一致,且要有同样的总账分类账参考。...

Linux系统下Python装机软件和环境配置_Spute的博客-程序员宅基地

Linux系统学习Python装机软件和环境配置Linux系统安装Ubuntu操作系统镜像源修改ubuntu下载源中文设置和搜狗拼音的安装基本如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowcha...

推荐文章

热门文章

相关标签