文章目录
前言
本系列内容力求将nvdla的内核态驱动整理清楚,如果有分析不对的请指出。
前面已经分析了一大块代码了,链接分别如下:
系列文章1:NVDLA内核态驱动代码整理一
系列文章2:NVDLA内核态驱动代码整理二
系列文章3:NVDLA内核态驱动代码整理三
系列文章4:NVDLA内核态驱动代码整理四
系列文章5:NVDLA内核态驱动代码整理五
欢迎阅读硬件信号和架构分析系列文章1:NVDLA硬件信号和架构设计整理一
本章是分析nvdla_core_callbacks.c代码第四部分。
可能仍然需要用到前面五篇文章下的结构体、函数和三件套,因此此处贴出:
nvdla_gem.c
所有函数:
函数原型 | 功能 |
---|---|
static int32_t nvdla_fill_task_desc(struct nvdla_ioctl_submit_task *local_task,struct nvdla_task *task) |
将local_task 的任务地址数量num_addresses 和任务具体内容的指针handles ,其中local_task->num_addresses * sizeof(struct nvdla_mem_handle) 就是在申请所有具体任务相关数据的地址空间 |
static int32_t nvdla_submit(struct drm_device *drm, void *arg,struct drm_file *file) |
nvdla_submit 函数传入参数arg (该参数的使之内容是nvdla_submit_args 结构体类型的变量,包含内容为任务、任务的数量等),arg 传入的任务转换为nvdla_ioctl_submit_task 结构体类型的任务,随后调用nvdla_fill_task_desc 完成用户态空间任务数据到内核态空间任务数据的下陷。与此同时,利用传入的drm_device 结构体指针drm 通过dev_get_drvdata 来获取与其他子系统交互的过程中当前的driver data ,从而引入完成nvdla_fill_task_desc 功能的另一个关键变量task ,并将drm_file 结构体提交给task ,其中drm_file 结构体包含针对该file的每个文件描述符操作后的状态变量。最后使用nvdla_task_submit 函数提交 NVDLA 任务并等待任务完成的函数。 |
static int32_t nvdla_gem_alloc(struct nvdla_gem_object *nobj) |
nvdla_gem_alloc 函数,该函数传入的变量是nvdla用于存储管理的结构体nvdla_gem_object ,根据前面介绍,该结构含有三个重要的变量,负责drm 下存储分配和管理的drm_gem_object 结构体、内核态虚拟地址kvaddr 和dma 相关变量。整个函数实现的功能是dma地址分配。 |
static void nvdla_gem_free(struct nvdla_gem_object *nobj) |
释放nvdla_gem_alloc 申请到的设备dma缓冲区 |
static struct nvdla_gem_object * nvdla_gem_create_object(struct drm_device *drm, uint32_t size) |
用于创建 NVDLA GEM对象的函数,随后分配和管理 DMA缓冲区的内核对象。前半部分的创建通过内核定义APIdrm_gem_private_object_init 函数实现,后半部分调用nvdla_gem_alloc 实现 |
static void nvdla_gem_free_object(struct drm_gem_object *dobj) |
用于释放 NVDLA GEM对象的函数,用于销毁和释放先前分配的 DMA缓冲区的内核对象 |
static struct nvdla_gem_object * nvdla_gem_create_with_handle(struct drm_file *file_priv,struct drm_device *drm, uint32_t size,uint32_t *handle) |
用于创建具有句柄(handle)的 NVDLA GEM对象的函数。它允许用户空间应用程序创建 GEM 对象,并返回一个句柄 |
static int32_t nvdla_gem_create(struct drm_device *drm, void *data, struct drm_file *file) |
和nvdla_gem_create_with_handle(struct drm_file *file_priv,struct drm_device *drm, uint32_t size,uint32_t *handle) 完全一样 |
static int32_t nvdla_drm_gem_object_mmap(struct drm_gem_object *dobj,struct vm_area_struct *vma) |
用于实现 NVDLA GEM对象的内存映射(mmap)操作的函数。内存映射允许用户空间应用程序将内核中的 GEM 对象映射到应用程序的地址空间中,以便应用程序可以直接访问该对象的数据。 |
static int32_t nvdla_drm_gem_mmap_buf(struct drm_gem_object *obj,struct vm_area_struct *vma) |
功能同nvdla_drm_gem_object_mmap |
static int32_t nvdla_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) |
功能同nvdla_drm_gem_object_mmap |
static struct sg_table *nvdla_drm_gem_prime_get_sg_table(struct drm_gem_object *dobj) |
该函数实现了实现了在 GEM对象上获取 Scatter-Gather 表(SG 表)的操作。SG 表是一种数据结构,用于描述分散在物理内存中的连续数据块的位置和大小,通常在 DMA操作中使用,以便可以有效地传输分散的数据块。 |
static void *nvdla_drm_gem_prime_vmap(struct drm_gem_object *obj) |
用于返回虚拟地址 |
int32_t nvdla_gem_dma_addr(struct drm_device *dev, struct drm_file *file,uint32_t fd, dma_addr_t *addr) |
该函数的目的是获取给定文件描述符(fd) 对应的GEM 对象的DMA 地址。首先,通过 drm_gem_prime_fd_to_handle 函数将文件描述符 转换为GEM 对象的句柄(handle) 。然后,通过 drm_gem_object_lookup 函数查找具有给定句柄 的GEM 对象。接着将找到的GEM 对象转换为特定类型的GEM 对象指针。最后,将GEM 对象的DMA 地址(dma_addr) 赋值给addr 参数,并释放GEM 对象的引用计数。总的来说,该函数目的是交给用户态空间数据handle 来管理DRM device |
static int32_t nvdla_gem_destroy(struct drm_device *drm, void *data, struct drm_file *file) |
销毁给定句柄对应的GEM 对象 |
nvdla_core_callbacks.c
所有函数:
函数原型 | 功能 |
---|---|
dla_debug 、dla_info 、dla_warn 和dla_error 函数 |
处理一般信息的函数,都采用了可变参数的方式来接受消息字符串和参数,通常是通过<stdarg.h> 标准库中的宏来实现的。 |
dla_memset 和dla_memcpy 函数 |
dla_memset 和dla_memcpy 函数分别用于将内存块的内容设置为指定的值、将一个内存块的内容复制到另一个内存块中 |
dla_get_time_us 函数 |
dla_get_time_us 函数通过调用ktime_get_ns() 函数来获取当前时间的纳秒级时间戳,然后将纳秒级时间戳除以NSEC_PER_USEC 来将时间转换为微秒,并返回一个int64_t 类型的整数表示微秒级的时间戳。 |
dla_reg_write 和dla_reg_read 函数 |
dla_reg_write 和dla_reg_read 函数分别用于写和读寄存器 |
nvdla_engine_isr 函数 |
nvdla_engine_isr 函数负责完成上自旋锁、完成硬件子单元乒乓寄存器组的初始化、执行计算任务、解除等待队列中的锁、释放自旋锁。 |
spin_lock_irqsave 和spin_unlock_irqrestore 函数 |
上自旋锁和释放自旋锁,前者 :首先需要使用nvdla_device 的专属锁&nvdla_dev->nvdla_lock 对critical region 上锁,与此同时,上锁这件事情得通告其余进程不要来打断,所以需要clri 指令配合,禁止中断 。 后者 :首先需要使用nvdla_device 的专属锁&nvdla_dev->nvdla_lock 对critical region 临界区释放锁,与此同时,释放锁这件事情也就意味着临界区可以允许其余进程来使用相关资源,所以需要seti 指令配合,恢复中断启用和优先级 。 |
dla_isr_handler 函数 |
dla_isr_handler 函数,该函数用于处理与NVDLA 引擎相关的中断事件。它接受nvdla_dev->engine_context 作为参数,该参数通常包含了与引擎相关的上下文信息,以便进行特定的处理。 |
glb_reg_read 和glb_reg_write 函数 |
调用dla_reg_write 和dla_reg_read 函数分别用于写和读寄存器,顺带挖出来一个三件套:dla_engine(实例化为engine) => driver_context(实例化为nvdla_device) 、nvdla_device(实例化为nvdla_dev) => engine_context(实例化为engine_data) 、engine_context => dla_engine(实例化为engine,见dla_isr_handler函数定义) 三条链 |
completion 函数 |
complete 函数用于唤醒等待中断事件完成的进程或线程 。这通常用于实现异步通知机制,以便用户空间或其他内核组件可以等待某个事件的完成。依次完成:获取传入completiom 的等待队列锁 ,获取该锁的目的在于控制对等待队列增删的并发,并保存当前的中断状态;将x->done++ ;调用swake_up_locked() 函数,将x->wait 链表中的等待队列的任务唤醒;释放等待队列锁。 |
dla_read_dma_address 函数 |
dla_read_dma_address 函数的核心在于利用nvdla_gem_dma_addr 函数来获取dma_addr ,注意这个地址是总线地址 ,也就是从设备角度看到的地址。 |
dla_read_cpu_address 函数 |
用于读取的地址是CPU 视角的地址 ,注意和dla_read_dma_address 函数的区别。(两者可以很好地对比,对于理解总线视角 和cpu视角 的地址差异很有帮助) |
dla_get_dma_address 函数 |
dla_get_dma_address 函数将dla_read_dma_address 和dla_read_cpu_address 合并,便于使用统一的destination 变量来获取地址。 |
dla_data_write 函数 |
dla_data_write 函数的功能就是CPU 访问dma_buf (其中的访问流程和DMA一致性 由dma_buf_begin_cpu_access 和dma_buf_end_cpu_access 来完成和保证),并希望按照给定的数据源地址src 和数据长度size 写入由CPU 申请好的dma_buf 映射到内核态地址空间 内的一段空间(这个功能由dma_buf_vmap 和dma_buf_vunmap 来完成),注意还得按照dla_data_write 给定的内核态地址空间 的偏移量来写入。注意获取dma_buf 是由文件描述符fd 来完成的(dma_buf_get 函数) |
dma_buf_get 函数 |
根据文件描述符fd 返回dma_buf 。 |
dma_buf_begin_cpu_access 和dma_buf_end_cpu_access 函数 |
从dma_buf_begin_cpu_access 函数的注释中可以看出该函数必须在内核上下文 中从cpu 访问dma_buf 之前调用。调用begin_cpu_access 以允许特定于导出程序的准备工作。只有在指定访问方向的指定范围内才能保证一致性。其中dmabuf 为缓冲区,为其准备cpu 访问;其中direction 为cpu 访问范围的长度。cpu 访问完成后,调用方 应调用dma_buf_end_cpu_access() 。只有当cpu访问 被两个调用阻止 时,它才能保证与其他DMA访问 一致。 |
dma_buf_vmap 和dma_buf_vunmap 函数 |
dma_buf_vmap 函数的功能是为缓冲区对象 创建到内核地址空间 的虚拟映射 。而dma_buf_vunmap 则是解除前者发起的虚拟映射 。 |
dla_data_read 函数 |
dla_data_read 函数和dla_data_write 函数类似,只是实现了读操作。 |
nvdla_task_submit 函数 |
nvdla_task_submit 函数用于提交NVDLA任务 并等待任务完成的函数,很核心的函数,由dla_execute_task 、wait_for_completion 、spin_lock_irqsave 和spin_unlock_irqrestore 、dla_process_events 和dla_clear_task 函数组成!!! |
dla_execute_task 函数 |
该函数接受任务的上下文、任务指针和配置数据作为参数,读取网络配置、初始化处理器和处理任务。 |
wait_for_completion 函数 |
wait_for_completion 函数等待 NVDLA 设备的事件通知完成。在等待期间,线程会被阻塞,直到事件发生。 |
dla_process_events 函数 |
dla_process_events 函数用于处理 NVDLA 设备的事件。有个十分核心的函数!!! |
dla_clear_task 函数 |
dla_clear_task 函数用于清除任务的上下文。 |
所有结构体:
结构体 | 功能 |
---|---|
nvdla_gem_object |
包含重要的变量,首先是drm_gem_object ,用于drm 存储管理和分配的结构体;其次是*kvaddr :这是一个指针成员,通常用于存储内核虚拟地址。这个地址指向内核中的数据缓冲区,该缓冲区可能包含了与图形或DMA相关的数据。这个成员可能被用于快速访问数据,而无需进行物理内存地址转换;最后是和dma 相关的地址和属性 |
nvdla_mem_handle |
作为媒介联通用户态空间任务结构体nvdla_ioctl_submit_task 和内核态空间任务结构体nvdla_task |
nvdla_ioctl_submit_task |
用户态空间任务结构体 |
nvdla_task |
内核态空间任务结构体 |
nvdla_device |
包含的信息是设备常用信息,比如中断、平台设备、drm设备等 |
nvdla_submit_args |
该结构体包含任务信息,用于用户态空间传入任务相关数据的参数,并通过该参数和nvdla_ioctl_submit_task 交互,总体来说,任务粒度高于nvdla_ioctl_submit_task |
drm_file |
包含针对该file的每个文件描述符操作后的状态变量 |
drm_gem_object |
描述drm 的存储分配对象,包含了该对象归属的设备drm_device 和对象的大小size |
drm_device |
描述了drm 设备结构体,包含了该总线设备的数据结构 |
sg_table |
Scatter-Gather 表,用于描述分散在物理内存中的连续数据块的位置和大小 |
drm_ioctl_desc |
定义drm 的ioctl 操作,可以自行添加自定义ioctl 操作,但需要注意ioctl 的flags |
drm_ioctl_flags |
ioctl 的flags 说明 |
drm_driver |
包含驱动的常见定义变量 |
nvdla_config |
实现NVDLA IP Core 的内部配置,包括atom_size 、bdma_enable 、rubik_enable 、weight_compress_support |
dla_processor |
dla_processor 结构体是dla_processor_group 和dla_engine 的桥梁。 |
dla_processor_group |
dla_processor_group 结构体最重要的是作为乒乓寄存器组 而存在,完成设备启动的初始配置,比如id 和active ,注意根据NVDLA硬件信号和架构设计整理一关于乒乓寄存器组 的描述会帮助理解这个结构体的设计思路。另外该结构体也包含了dla_operation_container 和dla_surface_container 的union ,专门用于指向特定的硬件计算子模块 比如bdma 、conv 、sdp 等的操作类型 和image surface 。 |
dla_engine |
dla_engine 结构体的作用只有一个,那就是串东西 ,把用于设置乒乓寄存器组配置寄存器 、producer 和consumer_ptr 的dla_processor ,设置mac阵列大小 、是否使能rubik 、bdma 与weight_compress 的dla_config ,dla_task 和dla_network_desc 给串起来 ,可以说是一家之主了。当然了,还有一个最重要的*driver_context ,这个要把nvdla_device 给映射起来,以便于访问nvdla 设备的硬件资源抽象从而支持读取和写入寄存器 、获取专属锁来申请访问临界区 。 |
dla_network_desc |
dla_network_desc 囊括了运行网络的全部信息,我们可以很明显注意到几个信息,operation_desc_index 、surface_desc_index 和dependency_graph_index ,分别是操作、image surface 和依赖图(也就是常见元操作)的索引 |
dla_task |
dla_task 结构体包含dla任务的common数据,用户态空间数据!!! |
dla_bdma_transfer_desc |
bdma 的传输细节 |
dla_bdma_surface_desc |
bdma 的surface 描述,需要确定source_type 和destination_type ,以及数据传输的num_transfers ,还需要颇为详细的传输细节,相关变量在dla_bdma_transfer_desc 结构体中定义。 |
dla_bdma_op_desc |
bdma 的op 描述,dma 的作用就是传输数据,因此num_transfers 成为关键的指标。 |
dla_bdma_stat_desc |
dla_bdma_stat_desc 结构体——这个结构体是为了看bdma 的状态,有三种状态:read_stall 、write_stall 和runtime 。 |
completion |
有两个成员变量,done 代表信号量是否已满足,wait 是一个链表的头 |
swait_queue_head |
链表swait_queue_head 有一个spinlock ,在操作链表前需要先获取该锁 |
nvdla_mem_handle(重新解释) |
作为媒介联通用户态空间任务结构体nvdla_ioctl_submit_task 和内核态空间任务结构体nvdla_task ,作为基本的地址描述要素十分丝滑地描述基地址 和偏移量 。 |
nvdla_task(重新解释) |
内核态空间任务结构体。nvdla_device 除了包含硬件抽象信息 之外,还是driver_context ,也就是驱动上下文 ,得益于nvdla_device 的存在,其载体drm_device 也是硬件抽象信息 之一,因此关于drm_file 也就有了存在意义,因为drm_file 包含了每个文件描述符操作后的状态变量,除此之外,address_list 包含了所有待处理文件的指针,可以认为就是fd(文件描述符) 。总结下来一句话就是nvdla_task 之所以区别于nvdla_device 是因为nvdla_task 的成员满足作为一个任务的必须要素,包括各个任务的fd 、file状态 、硬件抽象nvdla_device 。 |
凡是出现了`drm_device *drm`,必然需要想到`drm_gem_object`结构体和`nvdla_gem_object`结构体,首先完成这三个之间的关系注册。
1、struct nvdla_gem_object *nobj
2、struct drm_gem_object *dobj = &nobj->object
3、struct drm_device *drm = dobj->dev
4、`dla_engine(实例化为engine)` => `driver_context(实例化为nvdla_device)`
5、`nvdla_device(实例化为nvdla_dev)` => `engine_context(实例化为engine_data,见dla_isr_handler函数定义)`
6、`engine_context ` => `dla_engine(实例化为engine,见dla_isr_handler函数定义)`
7、`nvdla_device(实例化为nvdla_dev)` ==> `drm_device(实例化为drm_dev)`
8、从而借助7的关系可以回溯1和2
一、nvdla_core_callbacks.c代码解读四
1. dla_process_events函数详细展开
继续读代码:
dla_process_events
函数的使用实例是:err = dla_process_events(nvdla_dev->engine_context, &task_complete);
,该函数用于处理 NVDLA 设备的事件。代码定义如下:
int
dla_process_events(void *engine_context, uint32_t *task_complete)
{
int32_t i;
int32_t ret = 0;
struct dla_engine *engine = (struct dla_engine *)engine_context;
for (i = 0; i < DLA_OP_NUM; i++) {
struct dla_processor *processor; // 声明一个名为 processor 的指向 struct dla_processor 结构体的指针,用于表示当前迭代中的处理器。
processor = &engine->processors[i];
// 调用 dla_handle_events 函数来处理当前处理器上的事件
ret = dla_handle_events(processor);
/**
* Incase engine status is non-zero, then don't
* update the engine status. We should keep its
* status for later cleaning of engine.
*/
if (!engine->status)
engine->status = ret;
}
// 这是一个条件语句,检查引擎处理的硬件任务数量是否等于网络中的操作数量。
if (engine->network->num_operations == engine->num_proc_hwl)
// 如果条件成立,表示所有的硬件任务已经完成,因此将 task_complete 指针所指向的整数值设为 1。
*task_complete = 1;
RETURN(ret);
}
从这里可以看出最核心的是dla_handle_enevts
函数,这块本质上属于scheduler.c
的内容,但由于是从nvdla_core_callbacks.c
带出来的且nvdla_core_callbacks.c
还没讲完,所以就仍然以nvdla_core_callbacks.c
作为标题咯。然后说一件重要的事情!!!如果有幸读到这里,那么已经基本解读完内核态.c
代码中的大部分:
nvdla_core_callbacks.c (大部分)
nvdla_gem.c (划掉)
scheduler.c (大部分)
engine.c (划掉)
bdma.c
conv.c
sdp.c
cdp.c
pdp.c
rubik.c
cache.c
common.c
engine_data.c (划掉)
engine_isr.c (划掉)
engine_debug.c (纯打印文件用,划掉)
之后的计划是选取几个硬件子单元的.c
文件进行解读。
继续回到dla_handle_enevts
函数,代码定义如下:
static int
dla_handle_events(struct dla_processor *processor)
{
int32_t j;
int32_t ret = 0;
uint8_t group_id; // 声明一个无符号8位整数类型的变量 group_id,用于表示处理器当前操作的组(group)的标识。
// 声明一个指向 struct dla_processor_group 结构体的指针,用于表示当前迭代中的处理器组。
struct dla_processor_group *group;
// 这是一个调试信息的输出,显示了函数进入时的信息,包括函数名称和处理器的名称。
dla_debug("Enter:%s, processor:%s\n", __func__, processor->name);
group_id = !processor->last_group;
for (j = 0; j < DLA_NUM_GROUPS; j++) {
group = &processor->groups[group_id];
/**
* @brief 接下来的一系列条件语句检查特定事件是否发生,例如 CDMA(Channel DMA)传输的数据和权重完成事件以及操作完成事件。
* 如果事件发生,将输出相关的信息,例如处理的处理器、组和事件类型。
* 然后,调用不同的处理函数,如 dla_update_consumers 和 dla_op_completion,来处理这些事件。
* 如果处理事件的函数返回一个非零值,将 ret 设置为该值,并跳转到 exit 标签。
*
*/
if ((1 << DLA_EVENT_CDMA_WT_DONE) & group->events) {
dla_info("Handle cdma weight done event, processor %s "
"group %u\n", processor->name, group->id);
ret = dla_update_consumers(group,
group->op_desc,
DLA_EVENT_CDMA_WT_DONE);
if (ret)
goto exit;
}
if ((1 << DLA_EVENT_CDMA_DT_DONE) & group->events) {
dla_info("Handle cdma data done event, processor %s "
"group %u\n", processor->name, group->id);
ret = dla_update_consumers(group,
group->op_desc,
DLA_EVENT_CDMA_DT_DONE);
if (ret)
goto exit;
}
/**
* Handle complete after all other events
*/
if ((1 << DLA_EVENT_OP_COMPLETED) & group->events) {
dla_info("Handle op complete event, processor %s "
"group %u\n", processor->name, group->id);
ret = dla_op_completion(processor, group);
if (ret)
goto exit;
}
/**
* Clear all events
*/
group->events = 0;
group_id = !group_id;
}
exit:
dla_debug("Exit:%s, ret:%x\n", __func__, ret);
RETURN(ret);
}
根据打印的信息也可以理解这个函数的功能:遍历Groups
,依次传输权重数据、其他必要数据、执行任务,在处理完事件后,将当前组的事件标志清零,表示已经处理过这些事。暂时没必要往下深挖,了解清楚dla_update_consumer
和dla_op_completion
这些函数的功能即可。
2. of_device_id结构体
继续读代码,结构体代码如下:
static const struct of_device_id nvdla_of_match[] = {
// 对于platform_driver来说,添加OF匹配表
{
.compatible = "nvidia,nvdla_os_initial",
.data = &nvdla_config_os_initial,
},
{
.compatible = "nvidia,nvdla_2", "nvidia,nv_small",
.data = &nvdla_config_small,
},
{
.compatible = "nvidia,nvdla_2", "nvidia,nv_large",
.data = &nvdla_config_large,
},
{
},
};
这块对照设备树源码来看,会比较直观:
不多说!
3. 平台设备注册和注销函数:nvdla_probe和nvdla_remove
继续读代码,
static int32_t nvdla_probe(struct platform_device *pdev)
{
int32_t err = 0;
struct resource *res; // 声明一个指向 struct resource 结构的指针 res,用于处理设备资源信息。
struct nvdla_device *nvdla_dev;
// 声明一个指向 struct device 结构的指针 dev,并将其初始化为指向 pdev 中的 dev 成员,以便后续操作中可以方便地访问设备的 device 结构。
struct device *dev = &pdev->dev;
// 声明一个指向 const struct of_device_id 结构的指针 match,用于存储设备的兼容性信息。
const struct of_device_id *match;
if (!pdev->dev.of_node)
// 检查设备的设备树节点是否存在,如果不存在,则执行以下操作:
return -EINVAL;
match = of_match_device(nvdla_of_match, &pdev->dev);
// 使用设备的兼容性信息数组 nvdla_of_match 和设备的 dev 结构,尝试匹配设备的兼容性。将匹配结果存储在 match 变量中。
if (!match) {
pr_err("Missing DT entry!\n");
return -EINVAL;
}
// 使用 devm_kzalloc 分配内核内存,用于存储 nvdla_device 结构的数据。这个结构将在后续用于设备的管理和控制。
nvdla_dev = devm_kzalloc(dev, sizeof(*nvdla_dev), GFP_KERNEL);
if (!nvdla_dev)
return -ENOMEM;
// 将设备私有数据指针设置为 nvdla_dev,以便在后续的操作中可以轻松地访问与设备相关的数据。
platform_set_drvdata(pdev, nvdla_dev);
// 将 pdev 存储在 nvdla_dev 结构中,以保留对设备的引用。
nvdla_dev->pdev = pdev;
// 将从设备兼容性匹配中获取的配置数据存储在 nvdla_dev 结构中。
nvdla_dev->config_data = (struct nvdla_config *)match->data;
// 初始化一个用于同步事件通知的完成(completion)对象,以便后续使用。
init_completion(&nvdla_dev->event_notifier);
// 获取设备的内存资源,这里获取了第一个内存资源。将结果存储在 res 变量中。
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// 使用 devm_ioremap_resource 函数将设备的内存资源映射到内核地址空间,并将映射后的地址存储在 nvdla_dev->base 中。
nvdla_dev->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(nvdla_dev->base)) // 检查内存映射是否成功
return PTR_ERR(nvdla_dev->base);
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
// 获取设备的中断资源,这里获取了第一个中断资源。将结果存储在 res 变量中。
if (!res) {
dev_err(&pdev->dev, "no irq resource\n");
return -EINVAL;
}
// 将中断资源的起始地址存储在 nvdla_dev->irq 中,以便后续使用。
nvdla_dev->irq = res->start;
// 使用 devm_request_irq 函数请求设备的中断服务,并将中断处理函数设置为 nvdla_engine_isr。如果请求失败,将错误码存储在 err 中。
err = devm_request_irq(&pdev->dev, nvdla_dev->irq,
nvdla_engine_isr, 0,
dev_name(&pdev->dev), nvdla_dev);
if (err)
return err;
// 调用 dla_register_driver 函数注册驱动程序,并传递设备的引用和配置数据。
dla_register_driver(&nvdla_dev->engine_context, (void *)nvdla_dev);
// 调用 dla_clear_task 函数,用于清除任务相关的数据。此函数接受设备的引用作为参数,其中 nvdla_dev->engine_context 存储了引擎相关的上下文信息。
dla_clear_task(nvdla_dev->engine_context);
// 调用 nvdla_drm_probe 函数,用于注册 DRM 设备,这是与图形和显示相关的设备。如果注册失败,将错误码存储在 err 中。
err = nvdla_drm_probe(nvdla_dev);
if (err)
dev_err(&pdev->dev, "failed to register drm device\n");
return err;
}
static int32_t __exit nvdla_remove(struct platform_device *pdev)
{
struct nvdla_device *nvdla_dev = dev_get_drvdata(&pdev->dev);
nvdla_drm_remove(nvdla_dev);
return 0;
}
static struct platform_driver nvdla_driver = {
// 注意有个paltform_device的变量,可以搜到
.probe = nvdla_probe, // 一旦总线把设备和驱动匹配上(可以使用设备树的compatible或者使用drv与dev的name成员匹配)就执行probe
.remove = __exit_p(nvdla_remove),
.driver = {
.owner = THIS_MODULE,
.name = "NVDLA",
.of_match_table = of_match_ptr(nvdla_of_match), // 这里的of_match_table是OF匹配表的成员,设备树中的compatible和它对比
},
};
module_platform_driver(nvdla_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("NVIDIA");
MODULE_DESCRIPTION("Nvidia Deep Learning Accelerator driver");
这里面有意思的函数都已经完成注释,不再赘述。
二、nvdla_core_callbacks.c代码内函数整理四
函数原型 | 功能 |
---|---|
dla_handle_enevts 函数 |
是dla_process_events 函数的核心函数。遍历Groups ,依次传输权重数据、其他必要数据、执行任务,在处理完事件后,将当前组的事件标志清零,表示已经处理过这些事。 |
dla_update_consumer 和dla_op_completion 函数 |
分别用于传输必要数据、执行任务。 |
nvdla_probe 和nvdla_remove 函数 |
平台设备注册和注销函数 |
三、nvdla_core_callbacks.c代码结构体整理四
结构体 | 功能 |
---|---|
of_device_id 结构体 |
用于将不同type 的nvdla 与compatible 属性结合在一起,在设备树文件中用到。 |
总结
本章将nvdla_core_callbacks.c
代码都整理完了。接下来的计划是在解读核心计算、传输功能的代码之前,对官网的计算模块
、传输模块
的设计细节进行讲解。