NVDLA内核态驱动代码整理六


前言

本系列内容力求将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结构体、内核态虚拟地址kvaddrdma相关变量。整个函数实现的功能是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_debugdla_infodla_warndla_error函数处理一般信息的函数,都采用了可变参数的方式来接受消息字符串和参数,通常是通过<stdarg.h>标准库中的宏来实现的。
dla_memsetdla_memcpy函数dla_memsetdla_memcpy函数分别用于将内存块的内容设置为指定的值、将一个内存块的内容复制到另一个内存块中
dla_get_time_us函数dla_get_time_us函数通过调用ktime_get_ns()函数来获取当前时间的纳秒级时间戳,然后将纳秒级时间戳除以NSEC_PER_USEC来将时间转换为微秒,并返回一个int64_t类型的整数表示微秒级的时间戳。
dla_reg_writedla_reg_read函数dla_reg_writedla_reg_read函数分别用于写和读寄存器
nvdla_engine_isr函数nvdla_engine_isr函数负责完成上自旋锁、完成硬件子单元乒乓寄存器组的初始化、执行计算任务、解除等待队列中的锁、释放自旋锁。
spin_lock_irqsavespin_unlock_irqrestore函数上自旋锁和释放自旋锁,前者:首先需要使用nvdla_device的专属锁&nvdla_dev->nvdla_lockcritical region上锁,与此同时,上锁这件事情得通告其余进程不要来打断,所以需要clri指令配合,禁止中断后者:首先需要使用nvdla_device的专属锁&nvdla_dev->nvdla_lockcritical region临界区释放锁,与此同时,释放锁这件事情也就意味着临界区可以允许其余进程来使用相关资源,所以需要seti指令配合,恢复中断启用和优先级
dla_isr_handler函数dla_isr_handler函数,该函数用于处理与NVDLA引擎相关的中断事件。它接受nvdla_dev->engine_context作为参数,该参数通常包含了与引擎相关的上下文信息,以便进行特定的处理。
glb_reg_readglb_reg_write函数调用dla_reg_writedla_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_addressdla_read_cpu_address合并,便于使用统一的destination变量来获取地址。
dla_data_write函数dla_data_write函数的功能就是CPU访问dma_buf(其中的访问流程和DMA一致性dma_buf_begin_cpu_accessdma_buf_end_cpu_access来完成和保证),并希望按照给定的数据源地址src和数据长度size写入由CPU申请好的dma_buf映射到内核态地址空间内的一段空间(这个功能由dma_buf_vmapdma_buf_vunmap来完成),注意还得按照dla_data_write给定的内核态地址空间的偏移量来写入。注意获取dma_buf是由文件描述符fd来完成的(dma_buf_get函数)
dma_buf_get函数根据文件描述符fd返回dma_buf
dma_buf_begin_cpu_accessdma_buf_end_cpu_access函数dma_buf_begin_cpu_access函数的注释中可以看出该函数必须在内核上下文中从cpu访问dma_buf之前调用。调用begin_cpu_access以允许特定于导出程序的准备工作。只有在指定访问方向的指定范围内才能保证一致性。其中dmabuf为缓冲区,为其准备cpu访问;其中directioncpu访问范围的长度。cpu访问完成后,调用方调用dma_buf_end_cpu_access()。只有当cpu访问两个调用阻止时,它才能保证与其他DMA访问一致。
dma_buf_vmapdma_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_taskwait_for_completionspin_lock_irqsavespin_unlock_irqrestoredla_process_eventsdla_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_tableScatter-Gather表,用于描述分散在物理内存中的连续数据块的位置和大小
drm_ioctl_desc定义drmioctl操作,可以自行添加自定义ioctl操作,但需要注意ioctlflags
drm_ioctl_flagsioctlflags说明
drm_driver包含驱动的常见定义变量
nvdla_config实现NVDLA IP Core的内部配置,包括atom_sizebdma_enablerubik_enableweight_compress_support
dla_processordla_processor结构体是dla_processor_groupdla_engine的桥梁。
dla_processor_groupdla_processor_group结构体最重要的是作为乒乓寄存器组而存在,完成设备启动的初始配置,比如idactive,注意根据NVDLA硬件信号和架构设计整理一关于乒乓寄存器组的描述会帮助理解这个结构体的设计思路。另外该结构体也包含了dla_operation_containerdla_surface_containerunion,专门用于指向特定的硬件计算子模块比如bdmaconvsdp等的操作类型image surface
dla_enginedla_engine结构体的作用只有一个,那就是串东西,把用于设置乒乓寄存器组配置寄存器producerconsumer_ptrdla_processor,设置mac阵列大小、是否使能rubikbdmaweight_compressdla_configdla_taskdla_network_desc串起来,可以说是一家之主了。当然了,还有一个最重要的*driver_context,这个要把nvdla_device给映射起来,以便于访问nvdla设备的硬件资源抽象从而支持读取和写入寄存器获取专属锁来申请访问临界区
dla_network_descdla_network_desc囊括了运行网络的全部信息,我们可以很明显注意到几个信息,operation_desc_indexsurface_desc_indexdependency_graph_index,分别是操作、image surface和依赖图(也就是常见元操作)的索引
dla_taskdla_task结构体包含dla任务的common数据,用户态空间数据!!!
dla_bdma_transfer_descbdma的传输细节
dla_bdma_surface_descbdmasurface描述,需要确定source_typedestination_type,以及数据传输的num_transfers,还需要颇为详细的传输细节,相关变量在dla_bdma_transfer_desc结构体中定义。
dla_bdma_op_descbdmaop描述,dma的作用就是传输数据,因此num_transfers成为关键的指标。
dla_bdma_stat_descdla_bdma_stat_desc结构体——这个结构体是为了看bdma的状态,有三种状态:read_stallwrite_stallruntime
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的成员满足作为一个任务的必须要素,包括各个任务的fdfile状态硬件抽象nvdla_device
凡是出现了`drm_device *drm`,必然需要想到`drm_gem_object`结构体和`nvdla_gem_object`结构体,首先完成这三个之间的关系注册。
1struct nvdla_gem_object *nobj
2struct drm_gem_object *dobj = &nobj->object
3struct 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的关系可以回溯12

一、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_consumerdla_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_consumerdla_op_completion函数分别用于传输必要数据、执行任务。
nvdla_probenvdla_remove函数平台设备注册和注销函数

三、nvdla_core_callbacks.c代码结构体整理四

结构体功能
of_device_id结构体用于将不同typenvdlacompatible属性结合在一起,在设备树文件中用到。

总结

本章将nvdla_core_callbacks.c代码都整理完了。接下来的计划是在解读核心计算、传输功能的代码之前,对官网的计算模块传输模块的设计细节进行讲解。

以下是对提供的参考资料的总结,按照要求结构化多个要点分条输出: 4G/5G无线网络优化与网规案例分析: NSA站点下终端掉4G问题:部分用户反馈NSA终端频繁掉4G,主要因终端主动发起SCGfail导致。分析显示,在信号较好的环境下,终端可能因节能、过热保护等原因主动释放连接。解决方案建议终端侧进行分析处理,尝试关闭节电开关等。 RSSI算法识别天馈遮挡:通过计算RSSI平均值及差值识别天馈遮挡,差值大于3dB则认定有遮挡。不同设备分组规则不同,如64T和32T。此方法可有效帮助现场人员识别因环境变化引起的网络问题。 5G 160M组网小区CA不生效:某5G站点开启100M+60M CA功能后,测试发现UE无法正常使用CA功能。问题原因在于CA频点集标识配置错误,修正后测试正常。 5G网络优化与策略: CCE映射方式优化:针对诺基亚站点覆盖农村区域,通过优化CCE资源映射方式(交织、非交织),提升RRC连接建立成功率和无线接通率。非交织方式相比交织方式有显著提升。 5G AAU两扇区组网:与三扇区组网相比,AAU两扇区组网在RSRP、SINR、下载速率和上传速率上表现不同,需根据具体场景选择适合的组网方式。 5G语音解决方案:包括沿用4G语音解决方案、EPS Fallback方案和VoNR方案。不同方案适用于不同的5G组网策略,如NSA和SA,并影响语音连续性和网络覆盖。 4G网络优化与资源利用: 4G室分设备利旧:面对4G网络投资压减与资源需求矛盾,提出利旧多维度调优策略,包括资源整合、统筹调配既有资源,以满足新增需求和提质增效。 宏站RRU设备1托N射灯:针对5G深度覆盖需求,研究使用宏站AAU结合1托N射灯方案,快速便捷地开通5G站点,提升深度覆盖能力。 基站与流程管理: 爱立信LTE基站邻区添加流程:未提供具体内容,但通常涉及邻区规划、参数配置、测试验证等步骤,以确保基站间顺畅切换和覆盖连续性。 网络规划与策略: 新高铁跨海大桥覆盖方案试点:虽未提供详细内容,但可推测涉及高铁跨海大桥区域的4G/5G网络覆盖规划,需考虑信号穿透、移动性管理、网络容量等因素。 总结: 提供的参考资料涵盖了4G/5G无线网络优化、网规案例分析、网络优化策略、资源利用、基站管理等多个方面。 通过具体案例分析,展示了无线网络优化中的常见问题及解决方案,如NSA终端掉4G、RSSI识别天馈遮挡、CA不生效等。 强调了5G网络优化与策略的重要性,包括CCE映射方式优化、5G语音解决方案、AAU扇区组网选择等。 提出了4G网络优化与资源利用的策略,如室分设备利旧、宏站RRU设备1托N射灯等。 基站与流程管理方面,提到了爱立信LTE基站邻区添加流程,但未给出具体细节。 新高铁跨海大桥覆盖方案试点展示了特殊场景下的网络规划需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DentionY

谢谢您的支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值