1. 现象与分析
RK3568芯片的CAN模块在实际使用中出现了一个奇怪的BUG:应用层读取到的CAN报文长度(DLC)竟然变成了对端发送的CAN报文的帧长度。这个BUG在瑞芯微官方的CAN勘误手册中并未提及,属于一个隐藏的硬件缺陷。
通过实际测试发现,当内核态CPU使用率超过50%时,这个BUG出现的概率会显著增加。而且CAN总线的负载率越高,BUG复现的速度就越快。一旦出现这个BUG,系统将持续处于异常状态,直到对CAN接口执行down-up操作(即先关闭再重新打开接口)才能恢复正常。
经过深入分析,发现问题出在CAN模块的RX-FIFO(接收缓冲区)上。当往FIFO里存放数据或者从FIFO读取数据出错时,都会导致这种异常现象。具体来说,这是FIFO存储出错导致的。
提示:这个BUG的典型特征是应用层读取到的CAN报文DLC字段异常,而实际数据内容可能被填充为0。
使用KGDB调试工具对驱动进行调试后,发现问题的根源在于RX-FIFO中的数据出错。真实数据中被错误地填充了0值,导致驱动误将DLC(数据长度代码)当成了完整的帧报文来处理。这种情况会严重干扰正常的CAN通信。
2. 更改CAN的接收模式
RK3568原本的CAN收发驱动逻辑设计如下:
- 发送使用TXDATA寄存器
- 接收使用能存储6帧数据的RX-FIFO
为了尝试解决这个问题,我们首先考虑改变CAN的接收模式。原生的RX-FIFO模式存在BUG,那么是否可以改用RX_DATA寄存器直接接收数据呢?
经过实际测试发现,切换到RX_DATA寄存器接收模式后,系统完全读取不到任何数据,表现比使用RX-FIFO还要糟糕。这说明RX_DATA模式在该芯片上可能没有正确实现,或者存在其他兼容性问题。
这个测试结果让我们意识到,简单地改变接收模式并不能解决问题,必须寻找其他解决方案。
3. 驱动层规避该芯片BUG
既然对CAN接口执行down-up操作可以临时解决这个BUG,那么我们可以考虑在软件层面实现自动化的规避方案。作为一名BSP工程师,我们的目标是在驱动层解决问题,尽可能不让应用层感知到这个硬件BUG的存在。
在驱动层的rockchip_canfd_rx函数中,我们添加了一个特殊的判断逻辑:当检测到dlc=0且can_id<9时,就自动触发can_close-open操作。这个条件判断是基于大量测试数据得出的,能够准确识别出BUG发生的场景。
具体实现代码如下:
c复制static int rockchip_canfd_rx(struct net_device *ndev) {
struct rockchip_canfd *rcan = netdev_priv(ndev);
struct net_device_stats *stats = &ndev->stats;
struct canfd_frame *cf;
struct sk_buff *skb;
u32 id_rockchip_canfd, dlc;
int i = 0;
u32 __maybe_unused ts, ret;
u32 data[16];
#if USE_CAN_RX_FIFO
dlc = rockchip_canfd_read(rcan, CAN_RXFRD);
id_rockchip_canfd = rockchip_canfd_read(rcan, CAN_RXFRD);
ts = rockchip_canfd_read(rcan, CAN_RXFRD);
/* may be an empty frame */
if (!dlc && id_rockchip_canfd < 9) {
netif_stop_queue(ndev);
rockchip_canfd_stop(ndev);
can_free_echo_skb(ndev, 0);
rockchip_canfd_start(ndev);
netif_start_queue(ndev);
return 1;
}
for (i = 0; i < ARRAY_SIZE(data); i++)
data[i] = rockchip_canfd_read(rcan, CAN_RXFRD);
这个解决方案虽然有效,但会带来一个副作用:每次执行down-up操作时,会导致CAN总线短暂失效。根据实测数据,失效时间在60-500微秒之间,具体时长取决于当前CPU内核态的负载率。
注意:在实现这个解决方案时,需要特别注意时序控制,确保down-up操作不会影响关键的总线通信。
4. 性能对比与方案选择
面对这个硬件BUG,我们实际上有两种解决方案可选:
- 继续使用原生CAN接口,采用驱动层的规避方案
- 改用SPI-CAN转换方案
我们对两种方案进行了详细的性能对比测试:
原生CAN方案(带BUG规避):
- 每帧CAN报文处理时间:约16微秒
- 优点:处理速度快,实时性好
- 缺点:规避BUG会导致60-500微秒的通信中断
SPI-CAN转换方案:
- 每帧CAN报文处理时间:约180微秒
- 优点:不受原生CAN硬件BUG影响
- 缺点:
- 处理速度慢(是原生方案的10倍以上)
- 需要3-4次SPI通信才能完成一帧CAN报文的接收
- 存在丢帧风险
- 需要运行RT-Linux并精心设计应用层实时线程
使用ftrace工具进行实测发现:
- SPI-CAN读取一帧耗时180+微秒
- 原生CAN(带规避方案)读取一帧耗时16+微秒
5. 实际应用建议
基于以上分析,我们给出以下实用建议:
-
对实时性要求高的场景:
- 建议继续使用原生CAN接口
- 接受规避方案带来的短暂通信中断
- 在应用层做好错误处理和重试机制
- 监控系统日志,及时发现和处理异常情况
-
对稳定性要求高的场景:
- 如果无法接受任何通信中断,可考虑SPI-CAN方案
- 必须使用RT-Linux实时系统
- 精心设计应用层线程优先级和调度策略
- 实现完善的数据校验和重传机制
-
长期解决方案:
- 向芯片厂商反馈此问题,寻求硬件修复
- 在新版芯片中验证是否已修复该BUG
- 考虑使用其他型号的芯片替代
6. 深入技术细节与优化建议
6.1 BUG触发机制分析
通过大量测试,我们发现这个BUG的触发与以下因素密切相关:
- 内核态CPU使用率:超过50%时风险显著增加
- CAN总线负载率:负载越高,BUG出现越快
- 环境温度:高温环境下更容易出现
- 电源质量:电压波动会加剧问题出现
6.2 驱动层规避方案的优化
为了最小化规避方案带来的影响,我们可以进行以下优化:
-
智能检测机制:
- 不是每次异常都立即触发down-up
- 设置一个短时间窗口内的异常计数阈值
- 只有达到阈值才执行规避操作
-
时序优化:
- 选择总线空闲时段执行down-up
- 监测总线活动,寻找最佳时机
- 实现快速重启机制,缩短中断时间
-
状态保存与恢复:
- 在执行down前保存关键状态
- 在up后快速恢复通信状态
- 减少重新初始化的开销
6.3 SPI-CAN方案的性能提升技巧
如果必须使用SPI-CAN方案,以下技巧可以帮助提升性能:
-
SPI接口优化:
- 使用最高支持的SPI时钟频率
- 启用DMA传输减少CPU开销
- 优化SPI消息队列处理
-
中断处理优化:
- 使用线程化中断处理
- 合理设置中断优先级
- 减少中断处理程序的耗时操作
-
数据缓冲设计:
- 实现多级缓冲机制
- 预读取和缓存CAN数据
- 批量处理提高效率
7. 系统集成注意事项
在实际系统集成时,需要特别注意以下问题:
-
电源管理:
- 确保CAN模块供电稳定
- 添加适当的去耦电容
- 监控电源质量
-
散热设计:
- 保证芯片良好散热
- 监控芯片温度
- 高温环境下降低总线负载
-
系统监控:
- 实现CAN通信质量监控
- 记录异常事件
- 提供诊断接口
-
兼容性测试:
- 在不同负载下进行长时间测试
- 模拟各种异常情况
- 验证系统恢复能力
在实际项目中,我们通过以上方法成功地将这个硬件BUG的影响降到了最低。虽然不能完全消除问题,但通过软件层的巧妙设计,确保了系统在大多数情况下的稳定运行。这个案例也提醒我们,在面对芯片级BUG时,深入理解硬件工作原理和创造性的软件解决方案同样重要。