文章目录
前言
本系列内容力求将nvdla的用户态驱动整理清楚,如果有分析不对的请指出。
前面已经花了不少章节详细解释NVDLA内核态驱动代码,链接分别如下:
系列文章1:NVDLA内核态驱动代码整理一
系列文章2:NVDLA内核态驱动代码整理二
系列文章3:NVDLA内核态驱动代码整理三
系列文章4:NVDLA内核态驱动代码整理四
系列文章5:NVDLA内核态驱动代码整理五
系列文章6:NVDLA内核态驱动代码整理六
系列文章7:NVDLA内核态驱动代码整理七
系列文章8:NVDLA内核态驱动代码整理八
系列文章8:NVDLA内核态驱动代码整理汇总篇
欢迎阅读硬件信号和架构分析系列文章1:
架构开篇介绍文章:NVDLA内核态驱动代码整理三
系列文章1:NVDLA硬件信号和架构设计整理一
系列文章2:NVDLA硬件信号和架构设计整理二
系列文章3:NVDLA硬件信号和架构设计整理三
这是用户态解读的第二篇,也是收官之作。(不过后面有机会解读行为级代码的时候另说,可能会花大把时间搞明白RTL的寄存器是啥规范!)
一、Runtime.cpp代码解读
按照官网的描述,使用运行时接口进行推测任务需要完成以下步骤:
1、Create NVDLA runtime instance
# 包括IRuntime *nvdla::createRuntime()函数
2、Get NVDLA device information
# 包括NvU16 nvdla::IRuntime::getMaxDevices()函数、NvU16 nvdla::IRuntime::getNumDevices()函数
3、Load network data
# 包括NvError nvdla::IRuntime::load(const NvU8 *buf, int instance)函数
4、Get input and output tensors information
# 包括NvError nvdla::IRuntime::getNumInputTensors(int *input_tensors)函数、NvError nvdla::IRuntime::getInputTensorDesc(int id, NvDlaTensor *tensors)函数、NvError nvdla::IRuntime::getNumOutputTensors(int *output_tensors)函数、NvError nvdla::IRuntime::getOutputTensorDesc(int id, NvDlaTensor *tensors)函数
5、Update input and output tensors information
# 包括NvError nvdla::IRuntime::setInputTensorDesc(int id, const NvDlaTensor *tensors)函数、NvError nvdla::IRuntime::setOutputTensorDesc(int id, const NvDlaTensor *tensors)函数
6、Allocate memory for input and output tensors
# 包括NvDlaError allocateSystemMemory(void **h_mem, NvU64 size, void **pData)函数
7、Bind memory handle with tensor
# 包括NvError nvdla::IRuntime::bindInputTensor(int id, void *hMem)函数、NvError nvdla::IRuntime::bindOutputTensor(int id, void *hMem)函数
8、Submit task for inference
# 包括NvError nvdla::IRuntime::submit()函数
9、Unload network resources
# 包括NvError nvdla::IRuntime::unload(int instance)函数
我们掌握到这一层足矣!
二、nvdla.c函数整理
本来打算机械式逐个解释nvdla.c的函数,后来想到其实这个文件内的所有函数直接看函数名就可以理解。尽管用户态函数确实定义十分清楚,但是我们始终没有搞明白几件事情!因此接下来我打算就几件事情拨开nvdla.c函数的某些有意思的点。
关于nvdla_os.c的函数不再赘述。直接看函数名就可以理解函数功能,和Runtime.cpp和nvdla.c类似。
三、由nvdla.c引发的几个问题
这3个问题分别是怎么联系用户态和内核态?明明我只insmod了一个.ko,但为什么出现了2个不同结构体下的fd?为什么memset和mmap没有同时出现在fops内?可能这三个问题会帮助理解一部分究竟什么是用户态?
3.1 两个不同的结构体都有fd,但只insmod了1个ko?

关于第1个结构体:NvDlaMemHandleRec,使用情况如下:

关于第2个结构体:NvDlaMemHandle,使用情况如下:

(以上2个结构体完全一致!)
关于第3个结构体:NvDlaHandleRec,使用情况如下:无
关于第4个结构体:NvDlaHandle,使用情况如下:无
(以上2个结构体完全一致!)
关于第5个结构体:NvDlaContextRec,使用情况如下:无
关于第6个结构体:NvDlaContext,使用情况如下:

关于第7个结构体:NvDlaDeviceHandle,使用情况如下:

(以上3个结构体完全一致!)
所以答案显而易见,有两套fd,那哪一套对应哪一套?
先做个整理:
| 驱动对应fd | 结构体1 | 结构体2 |
|---|---|---|
| 1 | NvDlaMemHandleRec | NvDlaMemHandle |
| 2 | NvDlaContext | NvDlaDeviceHandle |
两套fd究竟怎么来的?先观察在加载opendla.ko时打印的输出情况:

我们在这里介绍DRM时看到一张久违的图片:

DRM框架成功加载后,会创建一个设备文件/dev/dri/card0,上层用户应用可以通过该文件节点,获取显卡的各种操作。【不过始终无法解释一个问题:为什么用户态中/dev/dri/renderD128在open的时候加载了,DRM的card0却不需要加载,但是其中一步close关闭了?我怀疑有一种可能就是renderD128这个opendla的驱动已经包含了DRM的card0驱动!】
这也就解释了为什么出现了两个fd。
3.2 怎么联系用户态和内核态?
想到这个问题的原因是,用户态也有和内核态一样的open等操作。那到底是怎么回事?
从ioctl函数出发,从用户态再看到内核态。先找找该文件下的ioctl出现的情况:
第一处:

其中宏DRM_IOCTL_NVDLA_GEM_CREATE指的是:

第二处:

然而此处的宏DRM_IOCTL_PRIME_HANDLE_TO_FD并不在上述截图中,而是在drm.h中:

第三处:

根据linux官方对于ioctl的解释:

ioctl()系统调用用于操纵特殊文件的底层设备参数。特别是,许多字符特殊文件(例如终端)的操作特性可以通过ioctl()请求进行控制。参数fd必须是一个打开的文件描述符。第2个参数是一个与设备相关的请求代码。第3个参数是一个untyped的内存指针。通常,成功时返回零。一些ioctl()请求将返回值用作输出参数,并在成功时返回非负值。发生错误时,返回-1,并设置errno以指示错误。

所以用户态传入的参数本质上补充第二个参数:


继续追溯宏函数:

在linux官网继续追溯:

继续:

基本上可以确认本质就是四个参数做移位!那么用户态ioctl怎么对应内核态ioctl?
在内核态找到ioctl:

随后该nvdla_drm_ioctls提交给nvdla_drm_driver结构体:

注意尽管在用户态代码中出现memset、malloc这些函数,结合fs.h中的file operation结构体基本可以确认这些函数不是file operation中的。

ioctl或许是一个特例,我们再看看open是怎么一回事?

用户态的open函数=>高级版本抽象的do_sys_open函数=>借助file operation下的特定文件系统类型的open函数。

所以调用链就是:用户态open调用=>内核态do_sys_open函数=>文件系统的open函数。
自此,案件已破!
3.3 memset/malloc和mmap没有同时出现在fops内?

到这里为止,大部分问题我们已经搞明白了!从看NVDLA的代码以来,一直很疑惑内核态和用户态到底是怎么关联的?我也遇到过各种奇奇怪怪的bug,但所幸有着自己的耐心一路推过去。也有各种各样的疑惑,有时候疑惑满脑子,有种剪不断、理还乱的感觉,还好,认知内各种各样的问题都一一去尝试解释。可以说,这一章的内容,突破了同辈们在搭建NVDLA过程中实现全栈建立的已有知识范畴,加入了我自己的理解和感悟!如果您对我的想法有疑惑或者质疑,欢迎提出,非常乐意交流。
总结
总结,撒花,欢迎阅读!
本文详细解读了NVDLA的Runtime.cpp代码,介绍了nvdla.c中的关键函数,讨论了用户态与内核态的交互、多个FD结构的来源以及memset/malloc和mmap在内核操作中的角色,解答了一些驱动开发中的疑问。
2817

被折叠的 条评论
为什么被折叠?



