Linux PHY几个状态的跟踪

前面文章零零星星地分析了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
9
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状态机开启。
这个过程主要的函数如下:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
}

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

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

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

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

当网络断开时,会打印:

1
PHY: 2:50 - Link is Down

PHY状态机

先看看PHY有的状态定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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,这两种状态之间不断切换。完整代码如下:

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

上电时状态变化:

1
PHY_READY -> PHY_UP -> PHY_AN -> PHY_RUNNING

拨出网线时状态变化:

1
PHY_RUNNING ->PHY_NOLINK

插上网线时状态变化:

1
PHY_NOLINK -> PHY_RUNNING

自动协商过程:

1
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日,李迟,于清明假期