Linux循环DMA驱动FIFO满时读取到未就绪最新数据问题咨询
问题根源及修复方案
1 首先修复代码笔误
你当前logger_read函数存在明显的变量误用问题:从FIFO取出的缓冲区索引存在index变量中,但调用copy_to_user时错误使用了未赋值的period_index,这会直接导致读取的缓冲区地址错误,大概率就是你读到正在被DMA写入的缓冲区的核心原因。
修复方式:将copy_to_user对应的地址参数修改为pd->dest_dma_buffer[index * pd->transfer_size]。
2 现有逻辑的缺陷
除了笔误外,你的DMA处理逻辑还有三处设计问题:
- 静态变量并发风险:
dma_sync_callback中定义的previous_period_counter、period_counter是静态变量,只适用于单设备实例的场景,多个设备驱动实例会共享这两个变量导致计数混乱,建议将这两个变量移入fpgalogger_device结构体中随设备实例存储。 - 信号量与FIFO计数不匹配:你仅在FIFO非满时调用
up(&pd->sema),FIFO满时弹出旧元素插入新元素的操作不会更新信号量计数,会导致信号量的计数值与FIFO实际元素数量不一致,极端情况会出现FIFO有数据但read拿不到信号量、或者拿到信号量后FIFO为空的错误。 - 缓冲区所有权逻辑混乱:你将FIFO大小设为和循环缓冲区总数一致,FIFO满时直接弹出最旧的缓冲区编号,相当于未经过读流程就直接把该缓冲区交还给DMA覆写,既会导致未读数据丢失,也不符合"缓冲区要么归DMA所有、要么归驱动读队列所有"的边界规则。
3 具体解决办法
根据你的业务需求二选一即可:
方案A(允许丢最新帧,保证已入队数据完整)
适合对数据连续性要求高,宁可丢新数据也不要旧数据错乱的场景:
- 将FIFO大小设置为
CYCLIC_DMA_PERIODS - 1,预留1个缓冲区永远归DMA控制,不会出现在FIFO中,从根源避免读写冲突。 - 修改回调函数FIFO满的处理逻辑:如果FIFO已满,直接放弃插入新的缓冲区编号,不弹出旧元素,也不做其他操作,相当于丢弃最新完成的传输数据。
- 保持信号量操作逻辑不变,保证FIFO元素数和信号量计数完全一致。
方案B(允许丢旧帧,永远保留最新数据)
适合需要优先保证最新数据可用的场景:
- 同样将FIFO大小设置为
CYCLIC_DMA_PERIODS - 1,预留1块DMA专属缓冲区。 - 回调函数FIFO满时,弹出最旧的缓冲区编号同时调用
down_trylock(&pd->sema)抵消对应的信号量计数,保证计数匹配,再插入新的缓冲区编号并调用up(&pd->sema)。 - 弹出的旧缓冲区直接标记为可被DMA覆写即可。
4 额外优化建议
你在copy_to_user完成后立刻调用dma_sync_single_for_device的操作不必要,该操作应该在确认缓冲区已经可以交还给DMA写入的时候再调用,你当前的调用时机相当于提前把缓冲区控制权交回给DMA,也可能导致拷贝还没完全完成就被DMA覆写。
内容的提问来源于stack exchange,提问作者NonoxX
相关产品推荐
相关产品推荐

