解决Linux平台的SPI-CAN(MCP2515)丢帧问题

解决Linux平台的SPI-CAN(MCP2515)丢帧问题 目录1. 背景2. 分析2.1 SPI-CAN原理2.2 驱动侧对收发函数的设置3. 优化驱动3.1 提高接收和发送函数的优先级3.2 打上RT补丁4. 结果1. 背景由于瑞芯微的RK3568/3588的原生CAN有芯片级BUG所以大多数厂家会采用spi-can方案。但是实测MCP2515这款芯片会有丢帧问题该芯片的接收FIFO只能存两帧发送是三个buffer。同时测试22%负载率就会报MCP2515接收缓冲区满的错误帧。SPI速率10MHzCAN波特率500K。2. 分析2.1 SPI-CAN原理该芯片读取单帧CAN报文需要进行4次SPI传输SPI物理通信时线程会陷入睡眠直到被SPI中断唤醒所以会导致MCP2515缓冲区满的罪魁祸首是调度延时。原驱动常规接收一帧CAN报文需要4次SPI传输mcp251x_read_2regs(CANINTF) 读取 4 字节读取中断标志 CANINTF 和错误标志 EFLGmcp251x_hw_rx_frame() 读取 14 字节使用 READ_RXB 指令一次性读取整个 RX Buffermcp251x_read_2regs(CANINTF) 读取 4 字节再次读取中断标志可能RX1又有数据mcp251x_read_2regs(CANINTF) while循环里再读取4 字节如果对端大批量发送CAN报文MCP2515出现RX0和RX1同时有数据并且第一次SPI传输读取到RX0IFRX1IF1后读取两帧CAN报文就只需要SPI传输五次。2.2 驱动侧对收发函数的设置接收逻辑放在了mcp251x_can_ist中断函数里同时该中断为线程化中断发送逻辑为使用了工作队列当需要发送时就唤醒该工作队列。3. 优化驱动既然问题出在调度延时那么就有两个思路一减少SPI传输次数二减少调度延时。SPI传输次数是不能减少了否则驱动就会不稳定无法处理非常规情况。如何减少调度延时3.1 提高接收和发送函数的优先级需要合理配置应用层与SPI-CAN通讯线程的优先级发送逻辑由工作队列改为内核态工作队列使用SCHED_FIFO且优先级设置很高绑定到CPU2核心运行priv-tx_worker kthread_create_worker(0, mcp251x_tx_%s, dev_name(spi-dev)); if (IS_ERR(priv-tx_worker)) { ret PTR_ERR(priv-tx_worker); priv-tx_worker NULL; goto error_probe; } { struct sched_param param { .sched_priority 91 }; sched_setscheduler(priv-tx_worker-task, SCHED_FIFO, param); set_cpus_allowed_ptr(priv-tx_worker-task, cpumask_of(2)); } kthread_init_work(priv-tx_work, mcp251x_tx_work_handler);接收逻辑由线程化中断优化为使用SCHED_FIFO且优先级设置很高绑定到CPU3核心运行ret request_threaded_irq(spi-irq, NULL, mcp251x_can_ist, flags | IRQF_ONESHOT, dev_name(spi-dev), priv); if (ret) { dev_err(spi-dev, failed to acquire irq %d\n, spi-irq); goto out_close; } { struct irq_desc *desc irq_to_desc(spi-irq); if (desc desc-action desc-action-thread) { struct sched_param param { .sched_priority 91 }; sched_setscheduler(desc-action-thread, SCHED_FIFO, param); set_cpus_allowed_ptr(desc-action-thread, cpumask_of(3)); } }用ftrace抓取事件发现SPI-CAN通讯中除了接收、发送线程还涉及到了spi0线程该线程用于spi传输完成后处理队列和spi控制器空闲时清理资源。该spi0线程默认是SCHED_OTHER调度优先级为PR 120。优先级太低也会导致SPI-CAN效率低从而接收丢帧。所以在设备树中对spi0节点加上rockchip,rt这样在spi_probe时就会设置ctlr-rttrue从而设置spi0线程为SCHED_FIFO优先级PR -51。若后续还是有丢帧情况发生可将优先级提高。static int rockchip_spi_probe(struct platform_device *pdev) { ...... ctlr-rt device_property_read_bool(pdev-dev, rockchip,rt); ...... }spi0 { status okay; rockchip,rt; mcp2515: mcp25150 { ...... }; };3.2 打上RT补丁RT补丁目的是让Linux更实时能够极大的减少调度延时。RT-Linux主要是将自旋锁改为可抢占的互斥锁将硬件中断线程化变成一个可抢占的内核。同时还需要将CPU由动态调频改为定频最高频系统时钟中断由动态调整改为1000Hz。4. 结果通过ftrace抓取事件结果显示用原生CAN接收一帧耗时16us优化前SPI-CAN接收一帧耗时235us优化后182us。