背景
3月底接了个私活,开发一个基于XDMA的PCIE板卡驱动,要支持245.76M采样率、8天线、32bit采样精度的采集卡,PCIE插槽是gen 3.0 x16,主机是x86服务器(至强处理器,PCIE5.0,DDR5.0 性能杠杠的)。
经过近5个月的业余时间开发,总算在上周调通了接收,发送以后再说。
系统各节点带宽配置
采集数据的带宽要求是32bit x 8天线 x 245.76M / 8bit = 7864.32MB/s ≈ 8GB/s
PCIE3.0的理论速度是16GB/s,扣除编码开销也高达15.754GB/s,不是瓶颈。
PCIE转接板上的DDR4.0-2400M,理论带宽19.2GB/s,因为DDR IP未优化因此实际带宽13GB/s左右,曾经因参数问题时钟只有1600当过瓶颈。
x86的DDR5.0时钟4800M,位宽128bit,肯定不是瓶颈。
系统框架
接收框架如下图:
XDMA支持6个BAR空间,但参考驱动只定义了user、config、bypass这3个BAR,其中config BAR是给XDMA通道使用,bypass BAR用于将XDMA当作纯PCIE IP核使用,只有user BAR是我们关注的。
FPGA实现user logic,用INTC软核实现MSI中断的发送和状态查询,并将寄存器读写接口映射到user BAR。
系统的大致流程
- FPGA往板载DDR写入80MB数据后,产生一次MSI中断通知x86
- x86收到MSI中断后读取user BAR的中断状态寄存器,看是哪个板载DDR的,然后发起XDMA传输
- XDMA控制器通过x86的PCIE host控制器将数据写入x86的DDR控制器
- XDMA控制器写入完毕后,产生DMA完成中断,x86驱动通过文件接口poll通知app
- app通过系统调用read读走数据
优化措施
提高dma page mapping的速度
- 驱动开启user中断前先对每个板载DDR发起一次XDMA传输,这样mapping结果就会缓存在cache内(猜测),user中断开启后的mapping操作就会快很多(缩短至0.7ms甚至0.5ms)
- 如果上述步骤不起效,reboot一下,这样cache就全给XDMA用了。
减少FPGA和XDMA对板载DDR的访问冲突
- FPGA例化2个DDR控制器,FPGA写入DDR0时XDMA读取DDR1,FPGA写入DDR1时XDMA读取DDR0,实现乒乓访问
提高user中断响应速度
- 将用户态接收线程的调度策略改成
SCHED_RR
,确保MSI中断延迟不超过1ms
提高DMA完成中断的响应速度
- 启用
poll_mode
,即加载驱动ko时传递poll_mode=1
选项 - 将poll线程调度类型改为FIFO,因为用户态接收线程的优先级提高了,驱动的poll线程cmpl_status_th也必须跟着提高。
让app线程可以跟用户态接收线程乒乓读取DDR
- app线程也设置成
SCHED_RR
- 用户态接收线程绑定到核0,app线程绑定到核1
- 两个线程间通过
pthread_cond_wait
和pthread_cond_broadcast
来同步
走过的弯路
- 当发现板载DDR因为时钟降速而带宽不足成为瓶颈时,试过
stream模式
,没跑通,读32MB数据只有前8MB读到,现在看来应该是驱动一次最多配置2048页导致的限制,至于为啥MM模式
就能配置多个8MB而stream模式不行,不清楚 - 当FPGA解决板载DDR4.0时钟标称2400实际1600的问题后,驱动仍然存在超时,当时以为FPGA的XDMA读写粒度有问题,后来引入性能测量IP核APM,排除了FPGA的嫌疑,才逼得我去定位驱动问题。
- 当实现app线程乒乓功能时,一开始尝试用native async IO接口,结果进入中断后很快卡死,当时觉得是异步IO的CPU开销更大,现在看来也跟没绑定到不同CPU核上有关系
后记
- 为规避板载DDR时钟只有1600M的问题,还试过在中断上下文里启动XDMA传输(为了不做page mapping还将其结果放到context里),这样最多可以节省1ms的调度时延,代码写了80%后听闻DDR时钟问题解决了,也就没有测试运行。
- 项目做了近5个月,比预期的长了很多,这主要是自己和搭档对系统的微观细节缺乏了解,基本上是遇到一个问题阅读一部分驱动源码,才算理解整个系统的各个组件(DDR4控制器->AXI总线->XDMA->PCIE总线->FSB总线->DDR5控制器)有了较深理解。
- 如果我知道4月份公司要派给我一个实现变性版USB-TMC的需求,我就不接这个私活了,周内做USB-TMC周末做XDMA真是太累了。