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 芯片,负责数据传送与接收所需要的电与光信号、线路状态、时钟基准、数据编码和电路等,并向数据链路层设备提供标准接口。
MAC 指 MAC 芯片,属于数据链路层,提供寻址机构、数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能。
PHY_DOWN: phy、phy driver、mac 都没准备好
- 如果 phy driver 被集成在内核中,PHY.probe 后,phydev 状态为 PHY_READY。
- 如果 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 未准备好
- 当 MAC 加载时,PHY.start 后,phydev 状态为 PHY_PENDING。
- 当 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)也是类似的.
- PCI 设备做 reset 以后进入 D0uninitialized(非初始化的 D0 状态, 参考 PCI 电源管理规范),此时网卡的 MAC 和 DMA 都不工作,PHY 是工作在一个特殊的低电源状态的;
- 操作系统创建设备树时,初始化这个设备,PCI 命令寄存器的 Memory Access Enable or the I/O Access Enable bit 会被 enable, 这就是 D0active.此时 PHY/MAC 就使能了;
- PHY 被使能应该就可以接收物理链路上的数据了,否则不能收到 FLP/NLP, PHY 就不能建立物理连接.但这类包一般是流量间歇发送的;
- 驱动程序一般要通过寄存器来控制 PHY, 比如自动协商 speed/duplex, 查询物理链路的状态 Link up/down;
- MAC 被使能后, 如果没有驱动设置控制寄存器的一个位(CTRL.SLU )的话, MAC 和 PHY 是不能通讯的, 就是说 MAC 不知道 PHY 的 link 已经 ready, 所以收不到任何数据的.这位设置以后, PHY 完成自协商, 网卡才会有个 Link change 的中断,知道物理连接已经 Link UP 了;
- 即使 Link 已经 UP, MAC 还需要 enable 接收器的一个位(RCTL.RXEN ),包才可以被接收进来,如果网卡被 reset,这位是 0,意味着所有的包都会被直接 drop 掉,不会存入网卡的 FIFO.老网卡在驱动退出前利用这位关掉接收.Intel 的最新千兆网卡发送接收队列的动态配置就是依靠这个位的,重新配置的过程一定要关掉流量;
- 无论驱动加载与否, 发生 reset 后,网卡 EEPOM 里的 mac 地址会写入网卡的 MAC 地址过滤寄存器, 驱动可以去修改这个寄存器,现代网卡通常支持很多 MAC 地址,也就是说,MAC 地址是可以被软件设置的.例如,Intel 的千兆网卡就支持 16 个单播 MAC 地址,但只有 1 个是存在 EEPROM 里的,其它是软件声称和设置的;
- 但如果驱动没有加载,网卡已经在设备树上,操作系统完成了步骤 1-2 的初始化,此时网卡的 PHY 应该是工作的,但因为没有人设置控制位(CTRL.SLU)来让 MAC 和 PHY 建立联系,所以 MAC 是不收包的.这个控制位在 reset 时会再设置成 0;
- PHY 可以被软件设置加电和断电, 断电状态除了接收管理命令以外,不会接收数据.另外,PHY 还能工作在 Smart Power Down 模式下,link down 就进入省电状态;
- 有些多口网卡,多个网口共享一个 PHY, 所以 BIOS 里设置 disbale 了某个网口, 也未必会把 PHY 的电源关掉,反过来,也要小心地关掉 PHY 的电源;
- 要详细了解 PHY,最终还是要熟悉 IEEE 以太网的相关协议.
ps: pipe.b3log.org 提供的站点无法同步文章了,显示同步成功,实际并没有。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于