PHY 状态简介

本贴最后更新于 879 天前,其中的信息可能已经时移世改

PHY 的 12 种状态

enum phy_state {
	PHY_DOWN = 0, //关闭网卡
	PHY_STARTING, //PHY设备准备好了,PHY driver尚为准备好
	PHY_READY,       //PHY设备注册成功
	PHY_PENDING,  //PHY芯片挂起
	PHY_UP,              //开启网卡
	PHY_AN,             //网卡自协商
	PHY_RUNNING, //网卡已经插入网线并建立物理连接,该状态可切换到PHY_CHANGELINK
	PHY_NOLINK,    //断网,拔掉网线
	PHY_FORCING,//自动协商失败,强制处理(读phy状态寄存器,设置速率,设置工作模式)
	PHY_CHANGELINK, //LINK检查,当物理连接存在时切换到PHY_RUNING,物理连接不存在时切换到PHY_NOLINK
	PHY_HALTED,   //网卡关闭时,PHY挂起
	PHY_RESUMING //网卡开启时,PHY恢复
};

PHY 状态机

PHY 状态切换图
PHY 指 PHY 芯片,负责数据传送与接收所需要的电与光信号、线路状态、时钟基准、数据编码和电路等,并向数据链路层设备提供标准接口。
MAC 指 MAC 芯片,属于数据链路层,提供寻址机构、数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能。
PHY_DOWN: phy、phy driver、mac 都没准备好

  1. 如果 phy driver 被集成在内核中,PHY.probe 后,phydev 状态为 PHY_READY。
  2. 如果 phy driver 被未集成在内核中,PHY.probe 后,phydev 状态为 PHY_STARTING。

PHY_READY:phy、phy driver 已经就绪,mac 未准备好
当 MAC 层加载时,在 PHY.start 后,phydev 状态切换为 PHY_UP。

PHY_STARTING:phy 准备就绪,phy driver、mac 未准备好

  1. 当 MAC 加载时,PHY.start 后,phydev 状态为 PHY_PENDING。
  2. 当 phy driver 加载时,phydev 状态为 PHY_READY。

PHY_PENDING:phy、mac 准备就绪,phy driver 未准备好
当 phy dirver 加载后,phdev 状态为 PHY_UP

上图中 0-->1-->2-->4、0-->2-->4 代表 phy、phy dirver、mac 顺序加载。
0-->1-->3-->4 代表 phy、mac、phy driver 顺序加载。

PHY_UP:phy、phy driver、mac 准备就绪
当前状态将启动自动协商,若启动成功则进入 PHY_AN,若启动失败则进入 PHY_FORCING。

PHY_AN:网卡自协商模式,检测自协商是否完成。
先判断物理链路的状态,如果未 LINK 则进入 PHY_NOLINK,如果 LINK 则判断自协商是否完成,
自协商完成进入 PHY_RUNNING,若自协商超时则重新开启自协商。

PHY_FORCING:强制协商
读 link 和自协商状态寄存器,如果状态正常则进入 PHY_RUNNING 模式。

PHY_NOLINK:物理链路未连接
判断物理链路状态,如果 LINK,再判断是否支持自协商,若支持待自协商完成后进入 PHY_RUNNING 模式,
若不支持,直接进入 PHY_RUNNING 模式。若自协商处于挂起状态,则进入 PHY_AN 模式。

PHY_RUNNING:正常运行中
获取当前 link 状态,当 link 状态发生改变时,进入 PHY_CHANGELINK 模式。

PHY_CHANGELINK:检查物理链路
物理链路 link 时,切换到 PHY_RUNNING,非 LINK 时切换到 PHY_NOLINK。

PHY_HALTED:网卡关闭 phy_stop
挂起 phy
PHY_RESUMING: 网卡启用 phy_start
恢复 phy

phy_state_machine 是 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;
	enum phy_state old_state;
	int err = 0;
	int old_link;

	mutex_lock(&phydev->lock);

	old_state = phydev->state;

	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);
		if (err < 0)
			break;

		/* If the link is down, give up on negotiation for now */
		if (!phydev->link) {
			phydev->state = PHY_NOLINK;
			netif_carrier_off(phydev->attached_dev);
			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;
			netif_carrier_on(phydev->attached_dev);
			phydev->adjust_link(phydev->attached_dev);

		} else if (0 == phydev->link_timeout--)
			needs_aneg = true;
		break;
	case PHY_NOLINK:
		if (phy_interrupt_is_valid(phydev))
			break;

		err = phy_read_status(phydev);
		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 and link changed since latest checking.
		 */
		if (!phy_interrupt_is_valid(phydev)) {
			old_link = phydev->link;
			err = phy_read_status(phydev);
			if (err)
				break;

			if (old_link != phydev->link)
				phydev->state = PHY_CHANGELINK;
		}
		/*
		 * Failsafe: check that nobody set phydev->link=0 between two
		 * poll cycles, otherwise we won't leave RUNNING state as long
		 * as link remains down.
		 */
		if (!phydev->link && phydev->state == PHY_RUNNING) {
			phydev->state = PHY_CHANGELINK;
			dev_err(&phydev->dev, "no link in PHY_RUNNING\n");
		}
		break;
	case PHY_CHANGELINK:
		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;
			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:
		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);
			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);
		}
		break;
	}

	mutex_unlock(&phydev->lock);

	if (needs_aneg)
		err = phy_start_aneg(phydev);
	else if (do_suspend)
		phy_suspend(phydev);

	if (err < 0)
		phy_error(phydev);

	dev_dbg(&phydev->dev, "PHY state change %s -> %s\n",
		phy_state_to_str(old_state), phy_state_to_str(phydev->state));

	queue_delayed_work(system_power_efficient_wq, &phydev->state_queue,
			   PHY_STATE_TIME * HZ);
}

问:若操作系统没有加载网卡驱动,网卡虽然在系统设备树上,但网卡接口创建不了,那网卡实际能不能接收到数据?
答:这里面有很多细节, 我根据 Intel 网卡的 Spec 大概写了写, 想尽量写的通俗一些,所以没有刻意用 Spec 里的术语,另外本文虽然讲的是 MAC/PHY,但光口卡的(SERDES)也是类似的.

  1. PCI 设备做 reset 以后进入 D0uninitialized(非初始化的 D0 状态, 参考 PCI 电源管理规范),此时网卡的 MAC 和 DMA 都不工作,PHY 是工作在一个特殊的低电源状态的;
  2. 操作系统创建设备树时,初始化这个设备,PCI 命令寄存器的 Memory Access Enable or the I/O Access Enable bit 会被 enable, 这就是 D0active.此时 PHY/MAC 就使能了;
  3. PHY 被使能应该就可以接收物理链路上的数据了,否则不能收到 FLP/NLP, PHY 就不能建立物理连接.但这类包一般是流量间歇发送的;
  4. 驱动程序一般要通过寄存器来控制 PHY, 比如自动协商 speed/duplex, 查询物理链路的状态 Link up/down;
  5. MAC 被使能后, 如果没有驱动设置控制寄存器的一个位(CTRL.SLU )的话, MAC 和 PHY 是不能通讯的, 就是说 MAC 不知道 PHY 的 link 已经 ready, 所以收不到任何数据的.这位设置以后, PHY 完成自协商, 网卡才会有个 Link change 的中断,知道物理连接已经 Link UP 了;
  6. 即使 Link 已经 UP, MAC 还需要 enable 接收器的一个位(RCTL.RXEN ),包才可以被接收进来,如果网卡被 reset,这位是 0,意味着所有的包都会被直接 drop 掉,不会存入网卡的 FIFO.老网卡在驱动退出前利用这位关掉接收.Intel 的最新千兆网卡发送接收队列的动态配置就是依靠这个位的,重新配置的过程一定要关掉流量;
  7. 无论驱动加载与否, 发生 reset 后,网卡 EEPOM 里的 mac 地址会写入网卡的 MAC 地址过滤寄存器, 驱动可以去修改这个寄存器,现代网卡通常支持很多 MAC 地址,也就是说,MAC 地址是可以被软件设置的.例如,Intel 的千兆网卡就支持 16 个单播 MAC 地址,但只有 1 个是存在 EEPROM 里的,其它是软件声称和设置的;
  8. 但如果驱动没有加载,网卡已经在设备树上,操作系统完成了步骤 1-2 的初始化,此时网卡的 PHY 应该是工作的,但因为没有人设置控制位(CTRL.SLU)来让 MAC 和 PHY 建立联系,所以 MAC 是不收包的.这个控制位在 reset 时会再设置成 0;
  9. PHY 可以被软件设置加电和断电, 断电状态除了接收管理命令以外,不会接收数据.另外,PHY 还能工作在 Smart Power Down 模式下,link down 就进入省电状态;
  10. 有些多口网卡,多个网口共享一个 PHY, 所以 BIOS 里设置 disbale 了某个网口, 也未必会把 PHY 的电源关掉,反过来,也要小心地关掉 PHY 的电源;
  11. 要详细了解 PHY,最终还是要熟悉 IEEE 以太网的相关协议.

ps: pipe.b3log.org 提供的站点无法同步文章了,显示同步成功,实际并没有。

  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    915 引用 • 931 回帖

相关帖子

1 回帖

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • stepforwards
    作者

    今天又上了 pipe,结果就像是第一次使用,文章全丢了

推荐标签 标签

  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    106 引用 • 152 回帖 • 1 关注
  • Spark

    Spark 是 UC Berkeley AMP lab 所开源的类 Hadoop MapReduce 的通用并行框架。Spark 拥有 Hadoop MapReduce 所具有的优点;但不同于 MapReduce 的是 Job 中间输出结果可以保存在内存中,从而不再需要读写 HDFS,因此 Spark 能更好地适用于数据挖掘与机器学习等需要迭代的 MapReduce 的算法。

    74 引用 • 46 回帖 • 548 关注
  • 周末

    星期六到星期天晚,实行五天工作制后,指每周的最后两天。再过几年可能就是三天了。

    14 引用 • 297 回帖 • 1 关注
  • gRpc
    10 引用 • 8 回帖 • 55 关注
  • 百度

    百度(Nasdaq:BIDU)是全球最大的中文搜索引擎、最大的中文网站。2000 年 1 月由李彦宏创立于北京中关村,致力于向人们提供“简单,可依赖”的信息获取方式。“百度”二字源于中国宋朝词人辛弃疾的《青玉案·元夕》词句“众里寻他千百度”,象征着百度对中文信息检索技术的执著追求。

    63 引用 • 785 回帖 • 250 关注
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 393 关注
  • Logseq

    Logseq 是一个隐私优先、开源的知识库工具。

    Logseq is a joyful, open-source outliner that works on top of local plain-text Markdown and Org-mode files. Use it to write, organize and share your thoughts, keep your to-do list, and build your own digital garden.

    4 引用 • 55 回帖 • 11 关注
  • OAuth

    OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。oAuth 是 Open Authorization 的简写。

    36 引用 • 103 回帖 • 8 关注
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    76 引用 • 37 回帖
  • 生活

    生活是指人类生存过程中的各项活动的总和,范畴较广,一般指为幸福的意义而存在。生活实际上是对人生的一种诠释。生活包括人类在社会中与自己息息相关的日常活动和心理影射。

    228 引用 • 1450 回帖 • 2 关注
  • 创业

    你比 99% 的人都优秀么?

    82 引用 • 1398 回帖
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    261 引用 • 662 回帖 • 1 关注
  • CSS

    CSS(Cascading Style Sheet)“层叠样式表”是用于控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。

    180 引用 • 447 回帖
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    21 引用 • 37 回帖 • 509 关注
  • Google

    Google(Google Inc.,NASDAQ:GOOG)是一家美国上市公司(公有股份公司),于 1998 年 9 月 7 日以私有股份公司的形式创立,设计并管理一个互联网搜索引擎。Google 公司的总部称作“Googleplex”,它位于加利福尼亚山景城。Google 目前被公认为是全球规模最大的搜索引擎,它提供了简单易用的免费服务。不作恶(Don't be evil)是谷歌公司的一项非正式的公司口号。

    49 引用 • 192 回帖
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 74 关注
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    41 引用 • 130 回帖 • 292 关注
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    173 引用 • 990 回帖
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    140 引用 • 441 回帖
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖
  • 安装

    你若安好,便是晴天。

    128 引用 • 1184 回帖
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    534 引用 • 3528 回帖 • 1 关注
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖 • 4 关注
  • 禅道

    禅道是一款国产的开源项目管理软件,她的核心管理思想基于敏捷方法 scrum,内置了产品管理和项目管理,同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能,在一个软件中就可以将软件研发中的需求、任务、bug、用例、计划、发布等要素有序的跟踪管理起来,完整地覆盖了项目管理的核心流程。

    5 引用 • 15 回帖 • 218 关注
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    132 引用 • 3651 回帖
  • OnlyOffice
    4 引用 • 17 关注
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖 • 1 关注