DRM设备文件创建与open内核流程分析

一、介绍

本文主要介绍设备文件/dev/dri/cardxx、/dev/dri/renderDxx的创建与open内核流程。分析的内核代码版本为linux-5.4.191。

二、创建DRM设备文件

pci probe到GPU设备后,会调用函数devtmpfs_create_node将创建设备文件(或称为设备结点)的请求加入链表,并唤醒内核线程kdevtmpfs,最终创建设备文件由内核线程kdevtmpfs完成,pci probe相关调用流程如下:

pci probe到GPU设备
	-》drm_dev_alloc
		-》drm_dev_init
			-》drm_minor_alloc
				-》drm_sysfs_minor_alloc
					-》minor_str = "renderD%d";/minor_str = "card%d";
					-》kdev->class = drm_class;     drm_class->devnode被设置为函数drm_devnode,具体见函数drm_sysfs_init
					-》dev_set_name(kdev, minor_str, minor->index);
	-》drm_dev_register	
		-》drm_minor_register
			-》device_add
				-》devtmpfs_create_node
					-》req.name = device_get_devnode  将请求创建的结点名字设置为"dri/renderDxx"或"dri/cardxx"
						-》dev->class->devnode 		  即调用函数drm_devnode,该函数返回字符串"dri/renderDxx"或"dri/cardxx"
					-》req.next = requests;
					   requests = &req;    将创建设备结点请求加入链表
					-》wake_up_process 	   唤醒内核线程kdevtmpfs

内核启动初始化时,会调用函数driver_init->devtmpfs_init,函数devtmpfs_init的相关工作如下:

  1. 调用vfs_kern_mount mount devtmpfs,根据下面的流程可知,创建的devtmpfs基于shmem,函数shmem_fill_super创建了超级块,且超级块的成员s_root(类型为struct dentry *,指devtmpfs文件系统的根路径的目录项)的inode操作被设置为shmem directory的操作(shmem_dir_inode_operations)。
  2. 调用kthread_run创建内核线程kdevtmpfs。
static struct file_system_type internal_fs_type = {
	.name = "devtmpfs",
#ifdef CONFIG_TMPFS
	.init_fs_context = shmem_init_fs_context,
	.parameters	= &shmem_fs_parameters,
#else
	.init_fs_context = ramfs_init_fs_context,
	.parameters	= &ramfs_fs_parameters,
#endif
	.kill_sb = kill_litter_super,
};

static const struct fs_context_operations shmem_fs_context_ops = {
	.free			= shmem_free_fc,
	.get_tree		= shmem_get_tree,
#ifdef CONFIG_TMPFS
	.parse_monolithic	= shmem_parse_options,
	.parse_param		= shmem_parse_one,
	.reconfigure		= shmem_reconfigure,
#endif
};

driver_init
	-》devtmpfs_init
		-》vfs_kern_mount(&internal_fs_type, 0, "devtmpfs", opts);
			-》fs_context_for_mount
				-》alloc_fs_context
					-》fc->fs_type	= get_filesystem(fs_type);
					-》init_fs_context = fc->fs_type->init_fs_context;  fc->fs_type->init_fs_context即 shmem_init_fs_context
					-》ret = init_fs_context(fc);  						即调用函数 shmem_init_fs_context
						-》fc->ops = &shmem_fs_context_ops;
			-》fc_mount(fc);
				-》vfs_get_tree
					-》fc->ops->get_tree(fc);   						调用shmem_fs_context_ops.get_tree即函数 shmem_get_tree
						-》get_tree_nodev(fc, shmem_fill_super);
							-》vfs_get_super
								-》err = fill_super(sb, fc);    		fill_super函数指针指向shmem_fill_super,即调用 shmem_fill_super
									-》inode = shmem_get_inode(sb, NULL, S_IFDIR | sbinfo->mode, 0, VM_NORESERVE);
										-》 inode->i_op = &shmem_dir_inode_operations;
											inode->i_fop = &simple_dir_operations;
									-》sb->s_root = d_make_root(inode);
		-》kthread_run(devtmpfsd, &err, "kdevtmpfs"); 					创建内核线程kdevtmpfs

接着分析内核线程kdevtmpfs被唤醒后的处理流程,首先从请求链表中取出创建设备结点的请求,最终调用函数vfs_mknod创建设备结点,其中dir->i_op(其中dir的类型为struct inode*)就是上面fill_super中创建的inode的成员i_op即shmem_dir_inode_operations,最终创建的设备结点文件对应的inode的成员i_fop被设置为&def_chr_fops、成员i_rdev被设置为device id,调用流程如下:

devtmpfsd  内核线程kdevtmpfs入口
	-》被唤醒后从请求链表取出请求并调用函数 handle
		-》handle_create
			-》kern_path_create  										创建目录/dev/dri
			-》vfs_mknod         										创建设备结点
				-》dir->i_op->mknod  			即shmem_dir_inode_operations->mknod 即函数 shmem_mknod
					-》shmem_get_inode  		创建inode,并初始化inode的成员
						-》inode->i_op = &shmem_special_inode_operations;
						-》init_special_inode(inode, mode, dev);
							-》inode->i_fop = &def_chr_fops;            设置inode成员i_fop为&def_chr_fops
							-》inode->i_rdev = rdev; 					用major和minor初始化成员
					-》security_inode_init_security

三、open DRM设备文件

drm模块加载的时候,模块初始化函数drm_core_init调用函数__register_chrdev主要完成如下工作:

  1. 调用__register_chrdev_region注册major id为226、minior id为0到255的字符设备区域,以标记该major id、minor id范围已经被占用。
  2. 调用函数cdev_alloc分配字符设备并设置字符设备的成员ops(类型为struct file_operations *)为&drm_stub_fops。
  3. 将包含major id为226、minor id为0到255、字符设备地址等信息的probe加入cdev_map,后面就可以通过inode->i_rdev(由major id、minor id组成)去cdev_map中查找字符设备(cdev)。

具体流程如下:

drm_core_init
	-》register_chrdev(DRM_MAJOR, "drm", &drm_stub_fops);   DRM_MAJOR等于226
		-》__register_chrdev(major, 0, 256, name, fops);
			-》__register_chrdev_region
            -》cdev = cdev_alloc();
			-》cdev->ops = fops;							fops等于&drm_stub_fops
			-》cdev_add 	   
				-》kobj_map(cdev_map,...)    				将包含major id为226、minor id为0到255、字符设备地址等信息的probe加入
															cdev_map,后面就可以通过inode->i_rdev去cdev_map中查找字符设备(cdev)

module_init(drm_core_init);

用户态程序调用open后对应的内核系统调用如下:

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
	if (force_o_largefile())
		flags |= O_LARGEFILE;

	return do_sys_open(AT_FDCWD, filename, flags, mode);
}

do_sys_open函数的主要工作如下:

  1. 调用函数link_path_walk将路径(/dev/dri/cardxx、/dev/dri/renderDxx)转换为最终的目录项dentry,通过目录项就可以得到inode。
  2. 语句f->f_op = fops_get(inode->i_fop)将inode的i_fop赋值给打开文件的f_op,即将内核线程kdevtmpfs创建的设备结点的inode的i_fop即&def_chr_fops赋值给打开文件的f_op。
  3. 调用函数指针f->f_op->open指向的函数,即调用(&def_chr_fops)->open即调用函数chrdev_open。
  4. 如果inode->i_cdev已经设置指向字符设备,则调用函数cdev_get增加字符设备对应的模块的引用计数(try_module_get)、增加字符设备的kobj的引用计数即可;否则,调用函数kobj_lookup根据inode的i_rdev(设备号,在内核线程kdevtmpfs)从cdev_map中查找kobj,然后根据kobj找到字符设备,并设置inode->i_cdev为找到的字符设备,避免下次重复查找。
  5. 调用fops_get获取字符设备的ops,即函数drm_core_init->register_chrdev设置的&drm_stub_fops,并调用replace_fops将打开文件的f_op设置为字符设备的ops即设置为&drm_stub_fops。
  6. 调用filp->f_op->open,即调用(&drm_stub_fops)->open即函数drm_stub_open。
  7. 函数drm_stub_open调用fops_get获取drm driver的fops,并调用replace_fops将drm driver的fops赋值给打开文件的f_op。
  8. 最终调用filp->f_op->open,即drm driver的fops的成员open。
do_sys_open
	-》do_filp_open
		-》path_openat
			-》link_path_walk								   将路径(/dev/dri/cardxx、/dev/dri/renderDxx)转换为最终的目录项dentry
			-》do_last
				-》vfs_open
					-》do_dentry_open
						-》f->f_op = fops_get(inode->i_fop);      inode->i_fop即def_chr_fops的地址
						-》f->f_op->open -- chrdev_open           等于def_chr_fops.open即函数chrdev_open
							-》kobj_lookup(cdev_map, inode->i_rdev, &idx); 或 cdev_get
							-》fops = fops_get(p->ops)            p->ops即字符设备的ops,即函数register_chrdev设置的&drm_stub_fops
							-》replace_fops(filp, fops);          将打开文件的f_op设置为字符设备的ops即设置为&drm_stub_fops
							-》filp->f_op->open -- drm_stub_open  等于drm_stub_fops.open即函数drm_stub_open
								-》new_fops = fops_get(minor->dev->driver->fops);   获取drm driver的fops
								-》replace_fops(filp, new_fops);   					将filp->f_op替换为new_fops即drm driver的fops
								-》filp->f_op->open	-- drm driver fops open			调用drm driver的成员fops->open

四、总结

根据上面流程分析可知,打开设备文件file的成员f_op(类型为struct file_operations *)最终被设置为drm driver(类型为struct drm_driver)的fops,后面应用程序对该打开文件进行mmap、close、flush、ioctl、poll、read等操作时,实际就是调用drm driver的fops定义的函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的用户空间程序,用于接收5.15.32版本内核上报的vblank事件。该程序使用DRM库中提供的函数来实现。 ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <drm/drm.h> #include <drm/drm_mode.h> int main() { int fd; int ret; uint64_t cap_value = 1; uint64_t handle; struct drm_event_vblank event; fd_set fds; fd = open("/dev/dri/card0", O_RDWR); if (fd < 0) { perror("open"); exit(1); } // 请求事件句柄 ret = ioctl(fd, DRM_IOCTL_SET_CLIENT_CAP, &cap_value); if (ret < 0) { perror("DRM_IOCTL_SET_CLIENT_CAP"); exit(1); } // 等待第一个vblank事件 ret = ioctl(fd, DRM_IOCTL_WAIT_VBLANK_EVENT, &handle); if (ret < 0) { perror("DRM_IOCTL_WAIT_VBLANK_EVENT"); exit(1); } while (1) { FD_ZERO(&fds); FD_SET(fd, &fds); // 等待事件的发生 ret = select(fd + 1, &fds, NULL, NULL, NULL); if (ret < 0) { perror("select"); exit(1); } // 读取事件数据 ret = read(handle, &event, sizeof(event)); if (ret < 0) { perror("read"); exit(1); } // 处理事件 printf("Got vblank event %d\n", event.sequence); // 再次等待vblank事件 ret = ioctl(fd, DRM_IOCTL_WAIT_VBLANK_EVENT, &handle); if (ret < 0) { perror("DRM_IOCTL_WAIT_VBLANK_EVENT"); exit(1); } } close(fd); return 0; } ``` 这个程序会不断等待vblank事件的发生,并在事件发生时打印事件序列号。可以使用Ctrl+C来停止程序的运行。需要注意的是,这个程序只是一个演示,实际应用中需要根据具体需求进行修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值