自动协商模式是端口根据另一端设备的连接速度和双工模式,自动把它的速度调节到最高的公共水平,即线路两端能具有的最快速度和双工模式。
自协商功能允许一个网络设备能够将自己所支持的工作模式信息传达给网络上的对端,并接受对方可能传递过来的相应信息,从而解决双工和10M/100M速率自协商问题。自协商功能完全由物理层芯片设计实现,因此并不使用专用数据包或带来任何高层协议开销。
自协商功能的基本机制是:每个网络设备在上电、管理命令发出、或是用户干预时发出FLP(快速连接脉冲),协商信息封装在这些FLP序列中。FLT中包含有时钟/数字序列,将这些数据从中提取出来就可以得到对端设备支持的工作模式,以及一些用于协商握手机制的其他信息。
总结一句话:自协商是PHY与PHY之间的通信协商,与MAC无直接关系!
如果两端都支持自协商,则都会接收到对方的FLP,并且把FLP中的信息解码出来,得到对方的连接能力,并且把对端的自协商能力值记录在自协商对端能力寄存器中(Auto-Negotiation Link Partner Ability Register , PHY标准寄存器地址5 ),同时把状态寄存器(PHY标准寄存器地址1)的自协商完成bit(bit5)置成1,在自协商未完成的情况下,这个bit一直为0。然后各自根据自己和对方的最大连接能力,选择最好的连接方式Link。比如,如果双方都即支持10M也支持100M,则速率按照100M连接;双方都即支持全双工也支持半双工,则按照全双工连接。
以太网的自动协商功能是由PHY硬件自己完成的,不需要我们的内核去做什么指导工作,只要设置相应寄存器启动自动协商后,我们就可以读相关的寄存器来得到现在协商成啥了。
以太网端口两端的工作模式必须协商一致,否则就会出现流量一大速度变慢,或者甚至出现接口掉线的问题。如果此时网络出现故障,不通、或者速度很慢,请检查接口是否协商为100M位以上的速度,以及全双工工作模式。如果没有,且端口为up的状态,可以尝试将强制将一端网络设定位100M位、全双工的工作模式,以调试网络是否恢复正常。但是若之后两端拔插网线,或者其他原因导致端口重新协商,则有可能仍会导致协商不匹配。
OSI模型定义了7层网络模型,以太网MAC层对应OSI模型中的第二层-数据链路层,以太网PHY对应OSI模型中的第一层-物理层。对于以太网而言,物理层的主要功能是将在网线或者光纤中传输的原始数据(电压,电流等)转化为可被接收且符合协议的数字信号,其为数据链路层提供物理连接。
自协商协议的主要内容包括:双工模式,运行速率等。自动协商功能完全由物理层PHY芯片实现,无需额外数据包和高层协议开销。根据广播通信速率10M或者100M的不同,自动协商功能提供两种模式NLP(Figure 6)和FLP(Figure 2)。
使用单独10Base-TE广播自动协商模式时, PHY芯片会通过Figure 1中TXD_P, TXD_N和RXD_P,RXD_N发送NLP(Normal Link Pulse)普通链路脉冲,每个脉冲间隔16ms。
使用100Bast-TX自动协商模式时, PHY芯片会通过Figure 1中TXD_P, TXD_N和RXD_P,RXD_N发送FLP(Fast Link Pulse)快速链路脉冲。
以stmmac为例
//初始化PHY状态机
stmmac_dvr_probe
stmmac_mdio_register
of_mdiobus_register
of_mdiobus_register_phy
phy_device_create
INIT_DELAYED_WORK(&dev->state_queue,phy_state_machine)
//启动状态机
#define PHY_STATE_TIME HZ //Linux核心每隔固定周期会发出timer interrupt (IRQ 0),HZ是用来定义每一秒有几次timer interrupts。举例来说,HZ为1000,代表每秒有1000次timer interrupts。
stmmac_open
phylink_start
phy_start
phy_start_machine
phy_trigger_machine
phy_queue_state_machine(phydev,PHY_STATE_TIME) //每隔1秒启动一次状态机
mod_delayed_work(system_power_efficient_wq, &phydev->state_queue, jiffies);
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;
mutex_lock(&phydev->lock);
old_state = phydev->state;
switch (phydev->state) {
case PHY_DOWN:
case PHY_READY:
break;
case PHY_UP:
needs_aneg = true;
break;
case PHY_NOLINK:
case PHY_RUNNING:
err = phy_check_link_status(phydev);
break;
case PHY_HALTED:
if (phydev->link) {
phydev->link = 0;
phy_link_down(phydev, true);
}
do_suspend = true;
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);
if (old_state != phydev->state) {
phydev_dbg(phydev, "PHY state change %s -> %s\n",
phy_state_to_str(old_state),
phy_state_to_str(phydev->state));
if (phydev->drv && phydev->drv->link_change_notify)
phydev->drv->link_change_notify(phydev);
}
mutex_unlock(&phydev->lock);
}
当phy状态变为PHY_UP时,状态机会配置、启动自协商功能,然后调用phy_check_link_status获取自协商的结果。
int phy_start_aneg(struct phy_device *phydev)
{
int err;
if (!phydev->drv)
return -EIO;
mutex_lock(&phydev->lock);
if (AUTONEG_DISABLE == phydev->autoneg) //如果PHY不支持自适应,从setting总取出合适的speed和duplex进行配置
phy_sanitize_settings(phydev);
err = phy_config_aneg(phydev);
if (err < 0)
goto out_unlock;
if (phy_is_started(phydev))//如果phy->state==PHY_UP
err = phy_check_link_status(phydev);
out_unlock:
mutex_unlock(&phydev->lock);
return err;
}
static int phy_config_aneg(struct phy_device *phydev)
{
if (phydev->drv->config_aneg) //如果PHY driver提供了config_aneg回调则调用回调
return phydev->drv->config_aneg(phydev);
if (phydev->is_c45 && !(phydev->c45_ids.devices_in_package & BIT(0))) //如果指定了C45则调用下面函数
return genphy_c45_config_aneg(phydev);
return genphy_config_aneg(phydev); //如果上面两条都不满足,则用这个函数
}
通常phy driver里会提供read_status回调函数,在这个回调函数中会读取PHY的speed和duplex等等信息并将结果返回给调用者;
在phy_check_link_status中调用phy driver里提供的read_status回调函数获取phy speed,然后调用phy_link_change将获取到的phy speed赋值给pl->phy_state将phy最新状态通知到MAC。
当phy状态变为PHY_RUNNING时,needs_aneg不会被赋值为ture,不会再启动自协商并获取PHY状态了,但是PHY_RUNNING时会一直调用phy_check_link_status获取PHY状态并通知给MAC。
需要特别注意的是,千兆phy必须打开自协商使能方可运行。强制千兆只能通过关闭十兆百兆广播能力的方式实现,这是802.3协议中规定的。
协议规定千兆无法像十兆百兆一样强制的原因通过查找资料和测试猜测如下:
1、千兆协商的过程需要确定两端phy的主从关系
2、无法像十百兆的协商一样通过不同的广播码确认对方的速率