背景:
Philip Sja1000芯片,因为原厂硬件的限制,每次只能写入一个帧,在一个加拿大飞机制造商使用产品时,发现并不能满足他们的超高要求,他们要求相邻的两帧直接的间隔在15μs以内,而当前产品相邻两帧的间隔是75μs,因此不能达到客户的要求,于是和硬件研发一起讨论,得出了如下的方案。
1.在硬件FPGA里面增加FIFO,大小比如4096字节,这样,通过驱动(软件)写FIFO,进而达到缩短相邻两帧的时间间隔到15μs以内。
在开发过程中遇到诸多linux kernel问题,打印出一堆linux内核的log,当时很难解决,不过慢慢都找到原因,解决了。
问题1:内核打印如下日志
can_put_echo_skb: BUG! echo_skb 0 is occupied!
刚开始看到这个有点懵逼,无从下手,但看样子是内核打印的,所以就到内核中搜索can_put_echo_skb函数的源码,列出来如下:
int can_put_echo_skb(struct sk_buff *skb, struct net_device *dev,
unsigned int idx, unsigned int frame_len)
{
struct can_priv *priv = netdev_priv(dev);
BUG_ON(idx >= priv->echo_skb_max);
if (!(dev->flags & IFF_ECHO) ||
(skb->protocol != htons(ETH_P_CAN) &&
skb->protocol != htons(ETH_P_CANFD))) {
kfree_skb(skb);
return 0;
}
if (!priv->echo_skb[idx]) {
skb = can_create_echo_skb(skb);
if (!skb)
return -ENOMEM;
skb->ip_summed = CHECKSUM_UNNECESSARY;
skb->dev = dev;
can_skb_prv(skb)->frame_len = frame_len;
skb_tx_timestamp(skb);
priv->echo_skb[idx] = skb;
} else {
netdev_err(dev, "%s: BUG! echo_skb %d is occupied!n", __func__, idx);
kfree_skb(skb);
return -EBUSY;
}
return 0;
}
EXPORT_SYMBOL_GPL(can_put_echo_skb);
可以看到,日志是在else分支打出来的,进而判断!priv->echo_skb[idx]为假,所以
priv->echo_skb[idx]为真,说明该指针已经有值了,被占用了;但是尚不清楚再哪里给这个指针赋值的,最后阅读内核源码,得知是在该函数else分支的上一行中赋值的,如下图:
所以得找到该指针在哪里释放或者置空,最终阅读内核源码,在can_get_echo_skb内核函数的__can_get_echo_skb函数中,如下:
接着继续追踪,最后发现是因为这次写FIFO后的调用逻辑层次改变了,原来是往设备硬件寄存器写,每次只写一帧(写之前netif_stop_queue停止上次继续写入),然后can_put_echo_skb,将内容放到设备堆栈等待后续处理,等到硬件的Tx buffered released的时候,调用can_get_echo_skb 通知内核去开始接收这一帧的内容;最后通过netif_wake_queue唤醒上层继续写入,才开始传输下一帧;而新的驱动写FIFO时 每次只进行can_put_echo_skb,没有紧接着can_get_echo_skb ,所以打印了“can_put_echo_skb: BUG! echo_skb 0 is occupied!”错误日志!can_put_echo_skb和can_get_echo_skb在写FIFO的函数中成对调用时就不再出现该问题了,该问题得到解决。
问题2:内核打印了一堆莫名的堆栈信息,但是没有奔溃,依然收发
该处这些内核信息报错的排查了好久,本来打算正面硬钢该问题,使用KGDB进行源码级调试,但是对内核进行了编译后,发现内核编译过了,但在双机调试时,无法串口通信,这个离能源码级调试还有很长的路,时间有限就放弃了;
然后想到既然linux内核是开源的,那就再函数上下文看呗,把内核中的代码都粘贴到我的驱动中,然后打日志看,最后发现了问题:
can_put_echo_skb -->can_create_echo_skb-->__kfree_skb-->skb_release_all
-->skb_release_head_state
当时能够确定就是这一行打印出来的,但是没裂解这个怎么会导致异常,请教老大后,一语惊醒:
这个是不能在中断中调度长函数、不能等待等,所以内核会检测,检测到了调用函数,然后就打印了那个日志,并且打出了一堆内核堆栈调用信息。因为我在FIFO保存不下一帧的时候暂停了发送,等到TX中断到来时候,又再次调用了一次发送函数导致的,恍然大悟,茅塞顿开。
于是采用schedule_work的方式,上面的日志倒是不报了,但是又奔溃了,追踪起来又很麻烦~~~
后来和领导又讨论,可以不这样,在FIFO Almost Full的时候,暂停发送,在FIFO Almost Empty的时候在恢复发送,这样就和原来的逻辑完全一样了就不会有问题了,然后立马开干,修改代码后果然好了!!好激动~~~



