DMA问题定位小结

前言

最近项目上出了个比较重大的bug,驱动注册的时候会偶发挂死、计算结果不正确,而且在不同内核版本,不同PAGE_SIZE下还表现不一样。 鉴于有合作方已经在使用了我们的驱动了,问题等级就上升了。项目组的小妹搞了快1个半月了,依然没有任何头绪,期间组内也组织过一次代码评审,都没有看出啥问题。 最后我就临时被拉去救火了,其他的工作全部暂停。 目前大部分问题已经定位解决,除了异常分支错误处理外,其他就是DMA-API的滥用了。

DMA-API

关于DMA映射API的使用在去年八月份有写过一篇文章介绍周谈(34)-DMA地址映射api使用,文章是刚接触DMA学习后的小结,只是对API函数及概念有了基础的理解,这次是遇到事儿,再看一遍内核的Document,就有不同的看法了。

带着问题去学习目的是比较明确的,在看API接口的同时会有更多的理解,下面简要地讲下遇到的问题及以后需要注意的点。

一致性和流式DMA

DMA分为两种,一致性DMA和流式DMA。

前者使用的是带coherent的API,不需要开发者关心内存的一致性,系统会自动进行同步(但是你需要保证buffer在被设备读取时已经flush了?没太看懂这个说法?),申请时一般至少以页为单位,即使你申请小于1页,内部也会占用一个页的空间,一般在驱动注册时申请,卸载后注销,使用的生命周期比较长一些。

流式DMA,则是通过已有的内存进行映射,使用完后再注销,最后才释放内存。流式DMA需要开发者关注内存的DMA方向,一旦进行映射后,理论上这块内存就属于设备的了,如果在注销映射之前CPU要访问这块内存,需要调用XXX_for_cpu的接口获取该内存的所有权,xxx_for_cpu接口根据传入的direction参数,会进行数据的同步,如果是DMA_FROM_DEVICE,那么会把数据从设备端同步到内存,并无效掉对应的CACHE,这样CPU访问的内容就是设备返回的内容了。如果是DMA_TO_DEVICE,那么CPU就可以往该内存写入数据。CPU访问结束后,再通过xxx_for_device接口把内存的所有权还给设备,设备就可以使用修改后的内存了。如果xxx_for_cpu方向为DMA_FROM_DEVICE,那么写入内存的地址是无法同步到设备端的,这个要特别注意。

工具接口

dma_max_mapping_size会返回映射API接口支持的最大内存长度,一般为0xffffffff; dma_need_sync返回一个dma地址是否需要调用xxx_for_cpu/device修改内存权限,奇怪的是即使你调用的是xxx_coherent接口申请的地址,返回的也是true; dma_get_merge_boundary返回DMA合并的边界,这个也没太搞懂,默认为PAGE_SIZE-1,当dma_map_sg的sglist大小超过boundary时,开启dma api debug时会报错误; dma_get_max_seg_size获取dma_map_sg支持的segment的最大长度,默认返回64k; dma_mapping_error用于判断返回的地址是否合法,当我们开启了DMA-API DEBUG功能时,如果某个地址没有调用该API接口判断,那么在注销映射的时候将会有警告调用栈输出; dma_get_cache_alignment返回处理器的缓存对齐,处理mapping后的内存需要以该大小对齐进行处理,可能返回的值大于实际的缓存行,基于此后续进行mapping的地址最好都是CACHELIEN对齐的,长度至少是CACHELINE大小。

dma_map_sg

这个接口用来映射scatterlist的,使用方式如下:

c 复制代码
	int i, count = dma_map_sg(dev, sglist, nents, direction);
	struct scatterlist *sg;

	for_each_sg(sglist, sg, count, i) {
		hw_address[i] = sg_dma_address(sg);
		hw_len[i] = sg_dma_len(sg);
	}

参数nents为sglist的段数,返回count可能小于nents,因为接口内部会合并相邻物理地址连续的段,需要注意的是,经过dma_map_sg后,我们需要使用sg_dma_address, sg_dma_len接口获取地址的值及长度。失败的话,count返回值为0。相应的注销时调用dma_unmap_sg, 参数必须和映射时一致。

dma api debug

dma-api有许多限制,随着硬件IOMMU的出现,驱动程序变得越来越重要不要违反这些限制,任意一个地方违法了就会导致系统挂掉。开启DMA API debug功能,可以帮助开发者查找隐藏的bug,在编译内核的时候,在kernel configuration中勾选 "Enable debugging of DMA-API usage"。

通过开启了DMA API debug后,我也是找到了许多问题了的。dma api debug还通过debugfs暴露了一些接口用于调试定位。

默认情况只会输出一个错误/警告信息,其他错误只会计数不输出, 这样可以防止内核信息泛滥,可以通过debugfs过滤指定的驱动,并配置相应的参数。

c 复制代码
dma-api/all_errors 只要不为0,就会打印错误调用栈

dma-api/disabled 指示当前debug功能是否禁用了,一般只有内存耗尽或者启动时禁用时为1

dma-api/dump 显示当前所有的内存映射

dma-api/error_count	显示总的错误数

dma-api/num_errors 设置最多打印的错误数目默认是1 

dma-api/min_free_entrie 为0时会继续申请entry

dma-api/num_free_entries free的entry数

dma-api/nr_total_entries 已申请的entry数,包含free、used

dma-api/driver_filter 可以用于指定驱动, 比如要调试test.ko, 则echo test>/sys/kernel/debug/dma-api/driver_filter,那么前面的计数也都只针对这个驱动了。

遇到的问题回顾

  1. IOMMU报物理地址错误,但是那个地址其实已经释放掉了,理论上不会再用到的。这个主要是同步内存给设备的时候,代码都是按64字节大小依次同步的,每个队列有一片缓冲区用于下发命令给设备,每次都最后一段时,错误就发生了。需要扩充缓冲区的大小,最后一片64字节其实也是按dma_get_cache_alignment即128字节同步的,最后64字节长度不足128字节,同步就异常了,再往后扩个64字节问题就解决了。

  2. 结果报wrong result,这个主要就是在内存mapping之后,没有调用xxx_for_cpu就去更新内存,导致最终内容没有同步到设备,设备使用的数据是错误的。还有一种情况,就是dma_map_single的内存不是cacheline对齐的,这个通过在相应的结构体中使用____cacheline_aligned定义,使该成员的首地址是cacheline对齐的。

  3. umap的时候报错,这个主要是映射后地址没有调用dma_mapping_error接口检查导致的。

  4. 挂死问题,这个是由于使用的是sg->length而不是sg_dma_len函数获取映射后的长度,最终导致地址访问越界。

  5. 其他就是特殊的异常分支处理的问题了。

更多

其实问题也就几类,但是架不住文件多啊,而且先前是两三个人协作写的代码,大家便于调试,很多本可以合并的代码都是重复存在了好几处,改起来也是累,最后花时间重构了一部分代码,代码行数都少了3000多行。剩余的代码交给另外一个同事改的,能解决问题就ok了,等有空再去重构吧。

中间遇到一部分新员工解bug的代码,各种if else判断,遇到问题解决问题,补丁一大堆,剪不断理还乱的,各种条件写的莫名其妙,调试起来还是一堆错。无奈,又花了三四天琢磨算法标准,解决异常处理问题。

这个救火任务前后也花了我一个月左右,终于要告一段落。搞的我这段时间茶饭不思的,白天想问题,晚上睡觉也在想问题,憔悴了许多,就是不减肥啊,哎!


行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。

博客地址: fishmwei.github.io

掘金主页: juejin.cn/user/208432...


相关推荐
zimoyin2 小时前
Kotlin 使用 Springboot 反射执行方法并自动传参
spring boot·后端·kotlin
SomeB1oody3 小时前
【Rust自学】18.1. 能用到模式(匹配)的地方
开发语言·后端·rust
LiuYuHani4 小时前
Spring Boot面试题
java·spring boot·后端
萧月霖4 小时前
Scala语言的安全开发
开发语言·后端·golang
电脑玩家粉色男孩4 小时前
八、Spring Boot 日志详解
java·spring boot·后端
ChinaRainbowSea5 小时前
八. Spring Boot2 整合连接 Redis(超详细剖析)
java·数据库·spring boot·redis·后端·nosql
叫我DPT5 小时前
Go 中 defer 的机制
开发语言·后端·golang
我们的五年6 小时前
【Linux网络编程】:守护进程,前台进程,后台进程
linux·服务器·后端·ubuntu
谢大旭7 小时前
ASP.NET Core自定义 MIME 类型配置
后端·c#
SomeB1oody8 小时前
【Rust自学】19.5. 高级类型
开发语言·后端·设计模式·rust