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

MAC驱动和PHY驱动

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

1

2

3

4

5

6

7

8

9davinci_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状态机开启。

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

1

2

3

4

5

6

7cpsw_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状态机函数中时时被调用,所以要关注一下。代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16static 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);

}

}

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45static 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的网络驱动,不调用此函数,等有空再研究研究)。

1

2

3

4

5

6

7

8

9

10

11

12void 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函数是将网速转化成字符串,在内核的旧版本上是没有的。 当网络连接时,会打印如下信息:

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

当网络断开时,会打印:

1PHY: 2:50 - Link is Down

PHY状态机

先看看PHY有的状态定义:

1

2

3

4

5

6

7

8

9

10

11

12

13

14enum 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,这两种状态之间不断切换。完整代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192/**

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

上电时状态变化:

1PHY_READY -> PHY_UP -> PHY_AN -> PHY_RUNNING

拨出网线时状态变化:

1PHY_RUNNING ->PHY_NOLINK

插上网线时状态变化:

1PHY_NOLINK -> PHY_RUNNING

自动协商过程:

1cpsw_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 命令 读phy_linux phy运行状态

    https://blog.csdn.net/chenliang0224/article/details/81024026 一.phy执行的枚举状态定义enum phy_state { PHY_DOWN ...

  2. linux 命令 读phy_CentOS教程(七)- 常用命令使用介绍(上)

    "一切皆文件"是Linux的基本哲学之一.普通的文件.目录.设备等一切"东西"在Linux中均是以文件形式展现. 好的,让我们来记住上面引言的话: 一切皆文件 ...

  3. Linux PHY几个状态的跟踪

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

  4. linux 获取phy状态,Linux PHY几个状态的跟踪

    https://blog.csdn.net/subfate/article/details/44900665 前面文章零零星星地分析了PHY,本来想完整地,系统地做分析,发现工程量太大了,而自己又一知 ...

  5. linux 命令 读phy_PHY LINUX (转载整理)

    每每分析网络问题的时候,总要梳理层次关系,本想自己写一个关于PHY的文档,找到网上有人写的一篇比较好,所以转载下来,仅供初学者参考. 以太网 MAC(链路层)+PHY(物理层/RTL8201F,88E ...

  6. linux 命令读db文件格式,使用linux的db_load命令生成db数据库

    使用linux的db_load命令生成db数据库 发布时间:2020-07-22 10:17:25 来源:亿速云 阅读:113 作者:清晨 栏目:服务器 不懂使用linux的db_load命令生成db ...

  7. linux文件状态,Linux命令之stat - 显示文件或文件系统状态

    用途说明 stat命令可以用来显示文件或文件系统状态信息(display file or file system status),它显示的信息比ls -l命令显示出来的更多些. 常用方式 格式:sta ...

  8. java查询线程状态命令_JAVA 线程死锁,以及linux 命令和jstack 命令 查看线程死锁状态信息...

    /* * Copyright (C) 2009 The doctor Authors * https://github.com/doctorwho1986 * * Licensed under the ...

  9. linux怎么读其实很简单 微星为你详解Z77主板BIOS设置

    近期,微星科技发布了大量的7系主板,它们全部配备厂商最新的军规三代组件技术,并且同时配备了PCI-E3.0,USB3.0还有SATA3.0技术,所以我们可以称呼它们为微星3.0主板.随着微星Z77主板 ...

最新文章

  1. shiro 解决 跨域(仅端口不同) 登陆 问题
  2. SM02 发送系统消息给所有Client用户
  3. mfc color 亮度_双十一4K投影仪怎么选?小心别掉“亮度坑” - 电视
  4. State_状态模式_PHP语言描述
  5. svn基本常见操作设置
  6. Boring Old Menu Bar for Mac(Big Sur菜单栏优化工具)
  7. 基于物联网的温度采集系统(一):底层感知网络搭建
  8. 基因组变异类型详解及区分
  9. uni-app 打包小程序体验版
  10. Axure软件的简单使用
  11. 编写Java程序,根据提供的 IP 地址,获取主机名称和域名
  12. 微信小程序分享小程序码的生成,多参数以及参数的获取
  13. [经验分享] 覃超线上直播课-模拟面试
  14. Ubuntu系统下安装Python3
  15. 读《战国策.赵策一》
  16. 三菱FX3U——ST编程中的字符串
  17. JS javascript 睡眠
  18. 一套效果图适配(Android和IOS)全尺寸和标注规范-(三)(360x640)
  19. 机器学习西瓜书笔记:神经网络:BP算法公式推导
  20. ExtrudeGeometry

热门文章

  1. BootStrap里面好看的背景色
  2. c#获取本地ip地址网关子网掩码_这样解释IP地址、子网掩码、网关之间的联系,不会技术也能听懂...
  3. 使用ThreadLocal和AtomicInteger将int属性改为线程安全的计数器
  4. JavaSE----数组
  5. mysql数据=_mysql 数据操作
  6. mysqlbinlog工具_mysqlbinlog命令详解 Part 5 通过位置和时间查看日志
  7. vue中接受后台传过来的图片文件流blob前端进行展示实现方法
  8. @Pathvariable的参数允许为空的问题的解决
  9. Vue中的join(),reverse()与 split()函数
  10. Android 8.0 Settings新添加的重写getMetricsCategory