赞
踩
多台公设备重启过程中出现网络无法使用问题,使用ifconfig查看网络节点此时没有收到任何包,并且重启机器也存在无法恢复问题。
图1.1 异常后ifconfig信息
在PC中使用ping命令查看设备是否有反应,此时可以看到RJ45的数据灯在闪烁,说明此时是有数据进入的,通过查看 cat /proc/interrupts没有看到网络中断。
图2.1 PC ping 设备
图2.2 网络中断
经过了解该问题之前就存在,解决办法是通过将eth0节点先down掉然后在up起来,出现问题后使用该方法进行测试,测试结果如下图所示,从下图可以看到第一次对eth0重启没有恢复,第二次时才恢复正常。从这点我们可以知道两三个信息,
1)重启eth0不一定能恢复网络。
2)重启eth0概率性可恢复,恢复后有提示。
3)重启eth0 实际是对MAC进行操作,说明问题可能出现在MAC。
图2.3 重启eth0 恢复网络
通过错误提示找到其代码所在位置。打印在./net/netfilter/nf_conntrack_helper.c:215中,如下图所示,直接看代码不太能够理解其含义,首先需要知道一个整体的概念,不能钻到代码里面去,否则很容易不知道要干啥。
图2.4 错误打印代码位置
从打印中可以看到两个关键词iptables和nf_conntrack,通过查阅资料可以了解到如下信息。
iptables:
netfilter/iptables(简称为iptables)组成Linux平台下的包过滤防火墙,与大多数的Linux软件一样,这个包过滤防火墙是免费的,它可以代替昂贵的商业防火墙解决方案,完成封包过滤、封包重定向和网络地址转换(NAT)等功能。
nf_conntrack:
nf_conntrack(在老版本的 Linux 内核中叫 ip_conntrack)是一个内核模块,用于跟踪一个连接的状态的。连接状态跟踪可以供其他模块使用,最常见的两个使用场景是 iptables 的 nat 的 state 模块。
了解到了如上信息知道这个是防火墙的以及跟踪连接状态的,首先想起是否是由于这个防火墙将数据包给隔离了,所以通过配置的方式关闭了,iptables和nf_conntrack结果发现并没有用,网络还是存在异常。说明问题不在这部分。
经过了解,最早该问题已经定位到了是RXERR的问题,RMII接口本身是不需要RXERR引脚,但NUC970的MAC的RXERR引脚不接入就会导致设备无法使用,具体原因是当启动过程中该引脚异常时候,此时NUC970的RXERR会进入错误状态,将MIEN寄存器的RXEN关掉。
了解到这里知道读取相关寄存器地址,MAC0基地址EMACn_BA = 0xB0002000,
EMACn_MIEN = EMACn_BA + 0x0AC = 0xB00020AC, 使用devmem读取寄存器值,具体如下:
- #异常时
- $devmem 0xB00020AC
- 0x01258C10
- $devmem 0xB00020b0
- 0x00800610
- #恢复正常后
- $devmem 0xB00020AC
- 0x01258C11
- devmem 0xB00020b0
- 0x00800000
通过对比正常和异常的差异点,发现0xB00020AC的bit0在异常时候为0,该位描述如下,该位是控制接收中断使能的,若该位置0了则CPU将不会受到RX中断,由此可以和上面对应上,MAC0没有任何中断的原因就是因为RXIEN没有使能导致的。
接收中断启用控制RXIEN控制RX中断的产生。如果启用RXIEN,且RXINTR (EMACn_MISTA[0])值高,则EMAC对CPU产生RX中断。如果关闭RXIEN,即使设置任何状态位EMACn_MISTA[15:1],并使能相应的EMACn_MIEN,也不会对CPU产生RX中断。换句话说,如果S/W想从EMAC接收RX中断,这个位必须是启用。并且,如果S/W不想从EMAC接收任何RX中断,禁用此功能一些。
0 = RXINTR (EMACn_MISTA[0])被屏蔽,RX中断生成被禁用。1 = RXINTR (EMACn_MISTA[0]) is not mask and RX interrupt generation Enabled。
图2.5 RXIEN寄存器描述
基于上面我们尝试在异常的时候对RXIEN位使能,操作如下所示,当将该位置1后,此时网络恢复,使用ping命令可正常与PC通讯。说明问题就在这里。
$devmem 0xB00020AC w 0x01258C11
图2.6 开启RX中断使能后网络恢复
到这里需要就有三个问题需要考虑。具体如下:
什么情况下RXIEN会被禁止
为什么通过down和up重启eth0不一定有用。
若默认将RXIEN一直开启是否会影响网络
带着这三个问题开始排查,首先查看新塘驱动看到3处和RXIEN相关的,分别是
a. 使能mac 中断
该函数是在open ether的时候开启的,用于打开MAC中断。
- static void nuc970_enable_mac_interrupt(struct net_device *dev)
- {
- unsigned int val;
-
- val = ENTXINTR | ENRXINTR | ENRXGD | ENTXCP | ENRDU;
- val |= ENTXBERR | ENRXBERR | ENTXABT | ENWOL;
-
- __raw_writel(val, REG_MIEN);
- }
b. RX中断处理函数
该函数中收到RX中断后会关闭RX中断,然后调用napi_schedule触发poll方法,然后再poll方法中处理网络数据,这样做的目的是减少硬件中断,避免消耗CPU资源。因为中断很多会使CPU一直陷入硬中断而没有时间处理别的事情。
- static irqreturn_t nuc970_rx_interrupt(int irq, void *dev_id)
- {
- struct net_device *dev = (struct net_device *)dev_id;
- struct nuc970_ether *ether = netdev_priv(dev);
- unsigned int status;
- struct platform_device *pdev = ether->pdev;
-
- nuc970_get_and_clear_int(dev, &status, 0xFFFF);
-
- dev_err(&pdev->dev, "zhuajian %s +%d status = 0x%x \n",__FUNCTION__, __LINE__, status);
-
- if (unlikely(status & MISTA_RXBERR)) {
-
- dev_err(&pdev->dev, "emc rx bus error\n");
- nuc970_reset_mac(dev, 1);
-
- } else {
- if(status & MISTA_WOL) {
-
- }
-
- if(status & MISTA_RXGD) {
- __raw_writel(__raw_readl(REG_MIEN) & ~ENRXINTR, REG_MIEN);
- napi_schedule(ðer->napi);
- }
- }
- return IRQ_HANDLED;
- }
c. nuc970 poll函数
网络数据接收,当数据接收完成或处理时间超了后会重新开启中断。
- static int nuc970_poll(struct napi_struct *napi, int budget)
- {
- int rx_cnt = 0;
- int complete = 0;
- struct platform_device *pdev = ether->pdev;
-
- rxbd = (ether->rdesc + ether->cur_rx);
- dev_err(&pdev->dev, "zhuajian %s +%d budget = 0x%x rx_cnt = 0x%x \n",__FUNCTION__, __LINE__, budget, rx_cnt);
-
- while(rx_cnt < budget) {
-
- if((rxbd->sl & RX_OWEN_DMA) == RX_OWEN_DMA) {
- complete = 1;
- break;
- }
- .......
- wmb(); // This is dummy function for ARM9
- rxbd->sl = RX_OWEN_DMA;
-
- if (++ether->cur_rx >= RX_DESC_SIZE)
- ether->cur_rx = 0;
-
- rxbd = (ether->rdesc + ether->cur_rx);
-
- }
- .......
-
- if(complete) {
- napi_complete(napi); //changed from __napi_complete(napi); by tanshi li for ifconfig eth0 down error
- __raw_writel(__raw_readl(REG_MIEN) | ENRXINTR, REG_MIEN);
- }
-
- rx_out:
-
- ETH_TRIGGER_RX;
- return(rx_cnt);
- }
通过在三个函数中增加打印,发现异常时候进入了nuc970_rx_interrupt但是没有进入到poll中,也就是在nuc970_rx_interrupt关闭了中断,但由于没有进入到poll中所以无法重新开启中断,导致后续网络都是异常的。
图2.7 打印输出
所以这里就要看为什么没有调用到nuc970_poll,理论上在nuc970_rx_interrupt中断中调用napi_schedule就会调用到nuc970_poll,所以现在就要看在什么情况下napi_schedule不会调用nuc970_poll。
napi_schedule实际调用napi_schedule_prep判断该poll是否可被调度,经过测试发现异常时候就是在该函数返回异常,导致poll方法无法被调度,napi_schedule_prep的函数如下,主要是检测两个方面:
1、是否poll已经在调度
2、是否禁止了napi pending
- /**
- * napi_schedule_prep - check if napi can be scheduled
- * @n: napi context
- *
- * Test if NAPI routine is already running, and if not mark
- * it as running. This is used as a condition variable
- * insure only one NAPI poll instance runs. We also make
- * sure there is no pending NAPI disable.
- */
- static inline bool napi_schedule_prep(struct napi_struct *n)
- {
- return !napi_disable_pending(n) &&
- !test_and_set_bit(NAPI_STATE_SCHED, &n->state);
- }
增加打印发现问题出在test_and_set_bit这里说明此时有其他poll方法被调度了,说明此时NAPI的状态中NAPI_STATE_SCHED位已经被设置。继续查看代码可以发现在eth0驱动加载时候会调用netif_napi_add,将poll增加到队列中。该函数就会设置状态为NAPI_STATE_SCHED,具体代码如下所示:
-
- static int nuc970_ether_probe(struct platform_device *pdev)
- {
- ....
- //增加napi 。
- netif_napi_add(dev, ðer->napi, nuc970_poll, /*16*/32);
-
- ether_setup(dev);
-
- if((error = nuc970_mii_setup(dev)) < 0) {
- dev_err(&pdev->dev, "nuc970_mii_setup err\n");
- goto err2;
- }
-
- error = register_netdev(dev);
- if (error != 0) {
- dev_err(&pdev->dev, "register_netdev() failed\n");
- error = -ENODEV;
- goto err2;
- }
-
- return 0;
- ....
而在open函数中会调用napi_enable函数将NAPI_STATE_SCHED状态清除,这样做的目的是为了在驱动加载时候不工作,仅在用户ifconfig up了网络节点时才正常工作。
通过上面的描述和代码走读,绘制代码逻辑图如下:
图2.8 驱动加载流程
图2.9 网络数据处理简化流程
通过走读代码发现问题出现在了open函数,由上可知在初始化时候调用了netif_napi_add,此时napi的状态是NAPI_STATE_SCHED,此时若有网络数据进来,则此时会触发RX中断,在中断中会关闭RX中断,且判断poll是否可被调度,此时判断状态为NAPI_STATE_SCHED,就不会调用到nuc970_poll,也就无法再开启网络中断。
那为什么open的时候回导致进入中断了,查看open的代码如下,发现在nuc970_reset_mac 和nuc970_enable_mac_interrupt中都会开启RX使能,并且在reset_mac的时候会初始化rx中断,而前面也说了清除NAPI_STATE_SCHED是在open函数的napi_enable调用中做的,若还未调用到napi_enable就有数据来了,此时就会出现上一段所描述的场景。
- static int nuc970_ether_open(struct net_device *dev)
- {
- struct nuc970_ether *ether;
- struct platform_device *pdev;
-
- ether = netdev_priv(dev);
- pdev = ether->pdev;
-
- nuc970_reset_mac(dev, 0);
- nuc970_set_fifo_threshold(dev);
- nuc970_set_curdest(dev);
- nuc970_enable_cam(dev);
- nuc970_enable_cam_command(dev);
- nuc970_enable_mac_interrupt(dev);
- nuc970_set_global_maccmd(dev);
- ETH_ENABLE_RX;
-
-
- if (request_irq(ether->txirq, nuc970_tx_interrupt,
- 0x0, pdev->name, dev)) {
- dev_err(&pdev->dev, "register irq tx failed\n");
- return -EAGAIN;
- }
-
- if (request_irq(ether->rxirq, nuc970_rx_interrupt,
- IRQF_NO_SUSPEND, pdev->name, dev)) {
- dev_err(&pdev->dev, "register irq rx failed\n");
- free_irq(ether->txirq, dev);
- return -EAGAIN;
- }
-
- phy_start(ether->phy_dev);
- netif_start_queue(dev);
- napi_enable(ðer->napi);
-
- ETH_TRIGGER_RX;
-
- dev_info(&pdev->dev, "%s is OPENED\n", dev->name);
-
- return 0;
- }
到这里根本原因已经非常清除了。就是新塘网络驱动的逻辑缺陷导致的。包括上面的三个问题也非常清楚了。
1、什么情况下RXIEN会被禁止
中断来的时候就会被静止掉,这样做的目的是减少系统频繁处理中断,而导致无法去做其他事情。内核采用NAPI技术,当中断来时开启软中断,通过轮训的方式处理数据。
2、为什么通过down和up重启eth0不一定有用。
在ifconfig eth0 up的时候调用的就是nuc970_open函数,若此时网络中有数据,例如有广播包的转发时候,此时就会出现关闭掉中断且不会调用到poll处理数据函数内。
3、若默认将RXIEN一直开启是否会影响网络
会,若大量网络数据过来会导致CPU去处理中断而无法去处理数据。非常影响性能。
将nuc970_open函数中的不必要初始化去掉,并且将nuc970_reset_mac函数中的RX使能给去掉,将RX使能部分移动到napi_enable之后,修改后的代码如下:
图3.1 修复后的代码
修改后运行测试代码,测试代码思路是通过ping命令去ping网关,若成功则进行重启,若ping失败则不进行重启。此时就会卡住。当我们拷机测试出现设备部重启时就说明网络异常。
测试拿了两台设备,一台是最新的带PHY版本,一台是老的不带PHY版本,经过一天的测试网络数据均正常。每次都能够ping通,说明此时网络问题已修复。
为了节约成本,这里测试两个版本的带宽和稳定性。通过在局域网下使用iperf3工具进行带宽测试,测试结果如下:
从测试结果看两者无任何差异,说明从带宽的角度两者并没有差异。若在抗干扰能力都一样的情况下,可以使用不带PHY的版本。
此次排查过程了解了nuc970网络数据处理过程,同时也学习了NAPI的处理逻辑,对后续还是有非常大的帮助,同时也说明新塘SDK还是有缺陷的,后续使用过程中需要做好测试工作,将问题在产品开发阶段就暴露出来,避免问题产品流出。