问题描述
最近有同事在搞 stmmac 网卡时发现这个网卡在接口 down 掉后执行 ethtool 就获取不到任何信息了,这个问题有点奇怪。
根据我的经验,ethtool_ops 是在驱动 probe 的时候注册的,注册之后一直可以调用,除非它没有实现相应的接口。但是在这里明显不是没有实现的问题,毕竟接口在 up 的情况下 ethtool 能够正常获取信息。难不成这个驱动在 ndo_stop 函数中会重置 ethtool_ops,然后在 ndo_open 中重新注册吗?
问题的根本原因在哪里?
有了上面的猜想,我阅读了下接口 up、down 调用的驱动底层函数,没有发现与 ethtool_ops 相关的设定,搜索代码也没有搜出来,只搜出来在 probe 函数中设定 ethtool_ops 的代码。
这让我感到非常的不解,一段时间后我重新阅读 stmmac 驱动的代码,终于发现了问题的根本原因。
ethtool_ops 中的 begin 函数
我之前有阅读过 ethtool 执行过程中的代码,对于 ethtool 调用 ioctl 操作网卡的过程还算熟悉,但是我没有意识到 ethtool_ops 中 begin 函数的作用。
内核在 dev_ethtool 函数中分发 ethtool 的不同子命令,在分发子命令之前会调用 begin 函数(如果存在的话),如果 begin 函数返回值小于 0 ,dev_ethtool 函数将直接返回,子命令不会得到执行。
相关代码如下:
/* The main entry point in this file. Called from net/core/dev_ioctl.c */
int dev_ethtool(struct net *net, struct ifreq *ifr)
············
if (dev->ethtool_ops->begin) {
rc = dev->ethtool_ops->begin(dev);
if (rc < 0)
return rc;
}
old_features = dev->features;
switch (ethcmd) {
上述代码中 switch 语句用于分发不同的 ethtool 子命令到不同的函数中。有了这个基础,我们再来看看 stmmac 驱动代码中 ethtoo_ops 中 begin 函数的实现代码,至少我们确定在接口 down 的情况下该 begin 函数一定返回了一个小于 0 的值。
static const struct ethtool_ops stmmac_ethtool_ops = { .begin = stmmac_check_if_running,
..............
static int stmmac_check_if_running(struct net_device *dev) { if (!netif_running(dev)) return -EBUSY; return 0; }
可以看到当接口状态不为 RUNNING 的情况下,stmmac_check_if_running 函数会返回 -EBUSY,这就导致当接口在 down 的情况下 ethtool 获取不到任何信息。
complete 方法
类似的还有 complete 方法,它在 ethtool 子命令执行完成后调用,相关代码如下:
if (dev->ethtool_ops->complete)
dev->ethtool_ops->complete(dev);
这个接口在 ethtool 子命令执行完成后被调用,它是子命令的统一出口,可以被设计为执行一些额外的处理。
一点点联想
我之前编写 gui 程序的时候也遇到过类似的情况。当时的业务需求是,在一个页面中有一个电源选项,当用户点击了这个电源选项,关闭电源后,页面上的其它控件将不会再响应点击事件,除非电源被开启。
由于每个控件有自己单独的方法,要实现这种功能,可以在每个控件中都检查电源状态,只有当电源开启的时候才向下执行,可是这样的实现不太优雅。
最终我选择将这些控件的事件回调都注册为一个相同的入口,不同控件的回调函数以参数的形式传递到这个统一入口中,入口函数中首先判断电源状态,当电源状态开启时就调用不同控件的回调函数来处理,反之则直接返回。
按照这个想法,上述 stmmac 驱动中实现的需求就是当接口 down 的情况下,不允许 ethtool 获取信息,可是这个与常见的网卡驱动行为不同,算是一个异类行为吧。
reset 方法
阅读代码的过程中,发现 ethtool_ops 中已经支持 reset 方法了。这个 reset 方法能够在网卡出现异常的时候 reset 网卡来恢复。
适配层的调用代码如下:
static int ethtool_reset(struct net_device *dev, char __user *useraddr)
{
struct ethtool_value reset;
int ret;
if (!dev->ethtool_ops->reset)
return -EOPNOTSUPP;
if (copy_from_user(&reset, useraddr, sizeof(reset)))
return -EFAULT;
ret = dev->ethtool_ops->reset(dev, &reset.data);
if (ret)
return ret;
if (copy_to_user(useraddr, &reset, sizeof(reset)))
return -EFAULT;
return 0;
}
与之类似的是 nway_reset 方法,在 linux-4.4.19 中 igb、 e1000、 ixgbe、 i40e、 均支持 nway_reset 功能来 reset 网卡。