网卡驱动死机调试经验案例

一、现象

执行iperf打流后,不管是udp打流还是tcp打流,命令行没有响应,无法敲入任何字符,也没有回显,但一段时间后,串口有打印,查看代码后,发现是EMAC的DMA中断里打印的,打印的内容如下:

[13:39:23:743]37.4320 Mbps!
[13:39:46:157]37.4490 Mbps!
[13:40:08:589]37.4490 Mbps!
[14:26:59:361]intr 0x86980c0
[14:27:02:590]intr 0x8688080
[14:27:03:390]intr 0x8688080
[14:27:11:837]intr 0x8688080
[14:27:15:579]intr 0x8688080
[14:27:16:331]intr 0x8688080
[14:27:28:217]intr 0x8688080
[14:27:30:680]intr 0x8688080
[14:27:35:575]intr 0x8688080
[14:27:42:598]intr 0x8688080
[14:27:45:103]intr 0x8688080
………

二、问题排查

首先找到打印对应的中断函数,如下所示:

irqreturn_t vmc_interrupt(int irqno, void *param)
{
    struct emac_device *edev = (struct emac_device *)param;
    struct eth_device *eth = &edev->parent;
    int ints = emac_read_reg((u32 *)(edev->dma_base), DmaStatus);
    int en = emac_read_reg((u32 *)(edev->dma_base), DmaInterrupt);
	
    emac_write_reg((u32 *)(edev->dma_base), DmaStatus, ints);
    emac_write_reg((u32 *)(edev->dma_base), DmaInterrupt, 0);
	
    if (ints & DmaIntTxCompleted) 
	{
		rt_event_send(emac_event, EMAC_EVENT_TX_COMPLETE);
    }
	
    if (ints & DmaIntRxCompleted) 
	{
        eth_device_ready(eth);
    }

    if (ints & DmaIntAbnormal) 
	{
        EMAC_PRINTF(VIM_EMAC_DEBUG, "intr 0x%x\n", ints);
    }

    emac_write_reg((u32 *)(edev->dma_base), DmaInterrupt, en);
	return 0;
}

可以看到,是发生了异常的DMA中断打印的,第一个异常的DmaStatus寄存器值是0x86980c0,对照寄存器手册发现网卡接收DMA块被用完了,如下所示。
在这里插入图片描述

在注意到死机时的串口打印时间,异常日志与正常iperf打流的最后一条日志相差了46分钟,因此可以推测是这段时间内网卡的接收函数没有执行,导致网卡DMA接收buffer消耗殆尽,从串口不能输入也侧面印证了这一点,因此可以推测程序可能是死在某个循环里了,可能是比网卡接收线程、shell线程优先级更高的线程,或是卡在某个中断里出不来了。

为了验证是否卡在线程里,有很多种办法,例如在rtt线程调度器里加打印,看是从哪个线程切到哪个线程最后卡住了,但是这样打印会非常多,所以简单的办法就是提高shell线程的优先级去验证,实测把shell现场优先级提高最高,仍然会死机,因此可以推测大概率是卡死在中断里了。这里直接用Jlink+GDB调试的方式去验证,结果每次全速运行暂停时,程序总停在中断里,中断入口函数如下所示,其中cnt是调试加的,当一直卡在中断里出不来时,会打印对应的中断号,果然这个中断号和EMAC的中断号是一致的。

void gic_handle_irq(irq_regs_t *regs)
{
    uint32_t irqstat, irqnr;
    static uint32_t cnt = 0;

    do
    {
        irqstat = readl(GIC_CPU_AIAR_REG);
        irqnr = irqstat & GICC_IAR_INT_ID_MASK;

        if (likely(irqnr > 15 && irqnr < 1020))
        {
            writel(irqstat, GIC_CPU_AEOI_REG);
            isb();
            handle_domain_irq(NULL, irqnr, regs);
			if(++cnt > 1000)
			{
				cnt = 0;
				__log("int %d",irqnr);
			}
            continue;
        }
        if (irqnr < 16)
        {
            writel(irqstat, GIC_CPU_AEOI_REG);
            writel(irqstat, GIC_CPU_DEACTIVATE);
#ifdef RT_USING_SMP
            /*
            ¦* Ensure any shared data written by the CPU sending
            ¦* the IPI is read after we've read the ACK register
            ¦* on the GIC.
            ¦*
            ¦* Pairs with the write barrier in gic_raise_softirq
            ¦*/
            smp_rmb();
            handle_IPI(irqnr, regs);
#endif
            continue;
        }
		cnt = 0;
        break;
    } while (1);
}

因此可以推测是EMAC的某个中断没清除导致反复进入EMAC的中断,细查发现正常DmaStatus寄存器的bit27是0,每次卡住时这个位都是1,这个bit位的描述如下:
在这里插入图片描述
查看代码和寄存器手册发现EMAC不止DmaStatus这个状态寄存器,还有个GmacInterruptStatus,于是异常时将这个寄存器的值也打印出来,发现是0x50,对应的寄存器描述如下:
在这里插入图片描述

好吧,这个寄存器描述讲了个寂寞,于是看linux sdk的代码,发现有个函数叫vmc_emac_disable_mmc_tx_interrupt,好像和这个有点关系,再看这个函数操作的寄存器,实际是屏蔽了和发送统计相关的功能,这个寄存器有25个bit位,刚好对应25种发送数据统计。在linux sdk里,所有的mask都置1了,相当于关闭了所有的发送统计。虽然没有功能说明什么时候会产生中断,但翻过头看串口日志发现,每次都是打印了19次iperf结果后卡住,怀疑应该是发送的数据量达到了某个数,比如超过32位的数据量时,会产生对应的中断,而这个中断没有去清除。
在这里插入图片描述

三、问题修复

参考linux sdk的做法,将发送和接收的统计功能关闭,问题不再复现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dokin丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值