Linux V4l2子系统分析4

Linux V4l2子系统分析4(基于Linux6.6)---设备注册及联系介绍

一、概述

1.1、V4L2 设备注册过程概述

V4L2 设备注册的过程通常包括以下几个主要步骤:

  1. 设备结构体初始化: 在 V4L2 中,所有设备通过 v4l2_device 结构体进行管理。这个结构体定义了设备的基本信息和控制操作。每个视频设备都需要初始化一个 v4l2_device 结构体,并填充其中的字段。

  • struct v4l2_device {
        struct device dev;               // 内核设备结构体
        struct v4l2_device *parent;      // 父设备(通常是设备总线)
        struct v4l2_ioctl_ops *ioctl_ops; // 用于设备控制的操作集
        struct list_head videodev_list;  // 设备列表
        // 更多字段...
    };
    
  • 创建 v4l2_device 设备对象: 在设备驱动中,首先需要使用 v4l2_device_register() 函数来注册一个 v4l2_device 结构体,并将设备与硬件相关联。通过这个函数,V4L2 框架将设备暴露给内核的设备管理器。

  • int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
    

    该函数将设备注册到 V4L2 子系统中,使设备能够参与后续的视频流控制、事件处理等。

  • 设备功能的实现: 对于每种类型的视频设备,V4L2 框架要求实现相应的功能接口。这些接口通常通过 v4l2_ioctl_ops 结构体进行管理,它包含了设备支持的控制操作、查询操作等。

  • struct v4l2_ioctl_ops {
        int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);
        int (*vidioc_enum_fmt_vid_cap)(struct file *file, void *fh, struct v4l2_fmtdesc *f);
        // 更多 ioctl 操作函数指针...
    };
    

    每种设备(例如视频捕获设备、视频输出设备)会实现不同的操作集,以满足特定的功能需求。

  • 注册 video_device 设备: V4L2 框架中的每个视频设备(如摄像头、视频采集卡等)都需要通过 video_device 结构体进行注册。通过调用 video_device_register() 函数,将设备与内核的视频子系统绑定。

  • struct video_device {
        struct v4l2_device *v4l2_dev;    // 设备的 V4L2 设备结构体
        char name[32];                   // 设备名称
        struct v4l2_fmtdesc *format;     // 支持的格式
        // 更多字段...
    };
    
    int video_device_register(struct video_device *vdev);
    
  • 设备初始化和控制接口: 一旦 v4l2_devicevideo_device 被成功注册,设备就可以开始接收来自用户空间的控制命令(如通过 ioctl)。这时,应用程序可以通过 V4L2 提供的标准 API 来与设备进行交互。

  • 事件注册和通知机制: V4L2 支持设备事件机制,允许设备向应用程序发送事件通知(如帧同步、错误报告等)。设备通过 v4l2_event 结构体来管理事件的注册和通知。

  • struct v4l2_event {
        __u32 type;           // 事件类型
        __u32 id;             // 事件的标识符
        union {
            __u32 sequence;   // 帧序列号等信息
            // 更多字段...
        };
    };
    
  • 设备卸载与清理: 当设备不再需要时,需要通过 v4l2_device_unregister()video_device_unregister() 来注销设备,并清理相关的资源。

  1. int v4l2_device_unregister(struct v4l2_device *v4l2_dev);
    int video_device_unregister(struct video_device *vdev);
    

1.2、V4L2 设备之间的联系

V4L2 框架中的设备关系是通过 v4l2_devicevideo_device 结构体来建立的。不同的设备之间的关系通常可以通过以下方式来理解:

  1. 父子设备关系: V4L2 设备可以通过 v4l2_device 结构体的 parent 字段与父设备关联。例如,一个视频捕获设备(摄像头)可能与一个硬件加速的视频编码器(如 Rockchip 的硬件编码器)是父子关系,父设备提供硬件加速支持,子设备则负责捕获视频流。

  2. 视频流的上下游设备: 在多媒体处理链中,设备之间通常是上下游关系。例如,一个视频捕获设备将视频流提供给一个视频处理设备,再通过一个编码设备将视频流编码成特定格式,最后通过输出设备显示或存储。各个设备通过 V4L2 的控制和格式接口进行协调工作。

  3. 设备功能的扩展: V4L2 支持多种类型的设备,包括视频捕获设备、视频输出设备、编码解码设备等。每个设备可以根据需要实现不同的功能模块,通过 v4l2_devicevideo_device 的注册接口,形成设备间的协作关系。具体的功能扩展,如设备间的数据流转、格式转换、硬件加速等,都是通过设备间的交互实现的。

  4. 设备之间的事件通信: V4L2 支持设备事件的注册和通知,多个设备可以通过事件机制进行通信。例如,一个视频捕获设备通过事件通知处理器设备,告知捕获成功,或者通知流结束,从而实现设备间的协调工作。

二、v4l2_device_register

  v4l2的设备注册其实没有注册设备或者设备驱动,只是将v4l2的大结构体与其他设备进行捆绑。如下面是v4l2注册设备的过程。

 drivers/media/v4l2-core/v4l2-device.c

int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
	if (v4l2_dev == NULL)
		return -EINVAL;

	INIT_LIST_HEAD(&v4l2_dev->subdevs);
	spin_lock_init(&v4l2_dev->lock);
	v4l2_prio_init(&v4l2_dev->prio);
	kref_init(&v4l2_dev->ref);
	get_device(dev);
	v4l2_dev->dev = dev;
	if (dev == NULL) {
		/* If dev == NULL, then name must be filled in by the caller */
		if (WARN_ON(!v4l2_dev->name[0]))
			return -EINVAL;
		return 0;
	}

	/* Set name to driver name + device name if it is empty. */
	if (!v4l2_dev->name[0])
		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
			dev->driver->name, dev_name(dev));
	if (!dev_get_drvdata(dev))
		dev_set_drvdata(dev, v4l2_dev);
	return 0;
}
EXPORT_SYMBOL_GPL(v4l2_device_register);

这里上面代码比较简单,这需要知道此函数功能是将当前设备和v4l2设备捆绑,并保存v4l2设备对象到当前设备的driverdata中。此函数的作用就是将当前设备驱动和v4l2对象进行捆绑,便于挂接子设备。

三、video_register_device注册过程

3.1、注册过程

include/media/v4l2-dev.h 

static inline int __must_check video_register_device(struct video_device *vdev,
						     enum vfl_devnode_type type,
						     int nr)
{
	return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}

上面方法又封装了一层,转而调用下面的方法:

drivers/media/v4l2-core/v4l2-dev.c

int __video_register_device(struct video_device *vdev, int type, int nr,
		int warn_if_nr_in_use, struct module *owner)
{
	int i = 0;
	int ret;
	int minor_offset = 0;
	int minor_cnt = VIDEO_NUM_DEVICES;
	const char *name_base;
	
    //这里省略若干查找空闲次设备号的过程--------------
    	/* Part 1: check device type */
	switch (type) {
	case VFL_TYPE_GRABBER:
		name_base = "video";
		break;
	case VFL_TYPE_VBI:
		name_base = "vbi";
		break;
	case VFL_TYPE_RADIO:
		name_base = "radio";
		break;
	case VFL_TYPE_SUBDEV:
		name_base = "v4l-subdev";
		break;
	default:
		printk(KERN_ERR "%s called with unknown type: %d\n",
		       __func__, type);
		return -EINVAL;
	}
		switch (type) {
	case VFL_TYPE_GRABBER:
		minor_offset = 0;
		minor_cnt = 64;
		break;
	case VFL_TYPE_RADIO:
		minor_offset = 64;
		minor_cnt = 64;
		break;
	case VFL_TYPE_VBI:
		minor_offset = 224;
		minor_cnt = 32;
		break;
	default:
		minor_offset = 128;
		minor_cnt = 64;
		break;
	}
    #ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES//目前高通是没有打开这个宏
	/* 1-on-1 mapping of device node number to minor number */
	i = nr;//这里保存下空闲的次设备号
    #else
	/* The device node number and minor numbers are independent, so
	   we just find the first free minor number. */
	/* 查找到第一个空闲的minor号,可以理解成是次设备号*/
	for (i = 0; i < VIDEO_NUM_DEVICES; i++)
		if (video_device[i] == NULL)//所有注册的video_device都会保存到这个数组中
			break;
	if (i == VIDEO_NUM_DEVICES) {
		mutex_unlock(&videodev_lock);
		printk(KERN_ERR "could not get a free minor\n");
		return -ENFILE;
	}
#endif
    //minor_offset一般是0,i就是查找到的空闲次设备号,这里总的次设备支持到256个
    vdev->minor = i + minor_offset;//这里记录下次设备号
    //这里num会保存到session_id中,唯一表示一个任务。一般情况下nr == minor
	vdev->num = nr;
	devnode_set(vdev);//将标准位置为已用。

	/* Should not happen since we thought this minor was free */
	WARN_ON(video_device[vdev->minor] != NULL);
	/* 下面这个方法字面意思看起来是获取一个index,但是查看源代码会发现
	   “这里会去查找不是直系的设备空闲号”,就是说只有video_device不为空,而且v4l2 parent
	   对象相同都会认为是同类,直接跳过相应的index号*/
	vdev->index = get_index(vdev);
	/* 下面要重点了,这里根据次设备号将当前video_device保存到video_device[]数组中*/
	video_device[vdev->minor] = vdev;
	mutex_unlock(&videodev_lock);

	/* Part 3: Initialize the character device */
	/* 分配字符设备文件*/
	vdev->cdev = cdev_alloc();
	//务必留意这个ioctl,后面子设备中的ioctl都是通过这里查找的。
	vdev->cdev->ops = &v4l2_fops;
	vdev->cdev->owner = owner;
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);

	/* Part 4: register the device with sysfs */
	vdev->dev.class = &video_class;//根目录sys中有对应的属性可操作。
	vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);//主设备号为81
	vdev->dev.parent = vdev->dev_parent;//这里一般是v4l2_device对象
	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
	ret = device_register(&vdev->dev);//注册该video_device到kernel中。
	vdev->dev.release = v4l2_device_release;

	/* Increase v4l2_device refcount */
	/* 下面这一步很关键,增加v4l2设备对象引用计数*/
	if (vdev->v4l2_dev)
		v4l2_device_get(vdev->v4l2_dev);

#if defined(CONFIG_MEDIA_CONTROLLER)
	/* Part 5: Register the entity. */
	if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
	    vdev->vfl_type != VFL_TYPE_SUBDEV) {
		vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
		vdev->entity.name = vdev->name;
		vdev->entity.info.v4l.major = VIDEO_MAJOR;
		vdev->entity.info.v4l.minor = vdev->minor;
		ret = media_device_register_entity(vdev->v4l2_dev->mdev,
			&vdev->entity);
		if (ret < 0)
			printk(KERN_WARNING
			       "%s: media_device_register_entity failed\n",
			       __func__);
	}
#endif
	/* Part 6: Activate this minor. The char device can now be used. */
	set_bit(V4L2_FL_REGISTERED, &vdev->flags);	

  由这里可以发现,创建video_device时,也创建了一个字符设备。并将该设备的parent节点指定为v4l2_device所依附的那个节点。主要需要注意下面几点。

  • 1.根据设备类型确定设备名字和次设备数量
    由于系统可能包含很多媒体设备,所以v4l2核心将0~255次设备编号划分了区域如下所示:其中VFL_TYPE_GRABBER一般表示提供数据的设备如camera。
类型次设备号区间设备基名称
VFL_TYPE_GRABBER0~63“video”
VFL_TYPE_VBI224~255“vbi”
VFL_TYPE_RADIO64~127“radio”
其它(含VFL_TYPE_SUBDEV)128~223含“v4l-subdev”
  • 2.确定设备编号,注册字符设备驱动。这里我曾今迷糊了,我以为下面这会注册2个设备到kernel中,后来才发现我错了。这是注册字符设备的一种方式。
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
	ret = device_register(&vdev->dev);
  • 3.确定设备的入口
	if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
	    vdev->vfl_type != VFL_TYPE_SUBDEV) {
		vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
		vdev->entity.name = vdev->name;
		vdev->entity.info.v4l.major = VIDEO_MAJOR;
		vdev->entity.info.v4l.minor = vdev->minor;
		ret = media_device_register_entity(vdev->v4l2_dev->mdev,&vdev->entity);

上面能发现,如果不是SUBDEV才能进来,注意上面name一般代表真实的设备名称,最后一行可以看到将入口注册到v4l2_device包含的media_device设备中了。而SUBDEV设备的注册一般都是设备驱动中赋值,如下是高通eeprom子设备。

	e_ctrl->msm_sd.sd.entity.type = MEDIA_ENT_T_V4L2_SUBDEV;
	e_ctrl->msm_sd.sd.entity.group_id = MSM_CAMERA_SUBDEV_EEPROM;
	e_ctrl->msm_sd.sd.entity.name = video_device_node_name(vdev);

上面group_id在判断该设备类型时,起到确定作用。要注意。

3.2、video在系统中的位置

	struct v4l2_device ------|                      struct video_device   
		|                    |------------引用----------> |----*v4l2_dev
        |                                                |
		|-->ctrlhandler<------------挂载------------------|----ctrl_handler
		|--->media->entry-list<------------挂载-----------|----->entry

四、subdev注册过程

4.1、v4l2_device_register_subdev()注册过程

这里只是将子设备挂接到v4l2_device的subdevs链表上,还没有真正的注册设备节点。真正注册设备节点的是v4l2_device_register_subdev_nodes方法。下面会介绍。

drivers/media/v4l2-core/v4l2-device.c 

int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
				struct v4l2_subdev *sd)
{
#if defined(CONFIG_MEDIA_CONTROLLER)
//获取到子设备的入口对象,方面后面注册到media_device上面。
	struct media_entity *entity = &sd->entity;
#endif
	int err;
   //此处省略一些错误检查代码
	/*
	 * The reason to acquire the module here is to avoid unloading
	 * a module of sub-device which is registered to a media
	 * device. To make it possible to unload modules for media
	 * devices that also register sub-devices, do not
	 * try_module_get() such sub-device owners.
	 */
	sd->owner_v4l2_dev = v4l2_dev->dev && v4l2_dev->dev->driver &&
		sd->owner == v4l2_dev->dev->driver->owner;

	if (!sd->owner_v4l2_dev && !try_module_get(sd->owner))
		return -ENODEV;
  //下面v4l2_dev一般为系统根v4l2_device设备。
	sd->v4l2_dev = v4l2_dev;
	//这里对应具体的子设备,可以发现调用了registered()回调,如果有需要可以在对应的设备驱动中实现。
	if (sd->internal_ops && sd->internal_ops->registered) {
		err = sd->internal_ops->registered(sd);
		if (err)
			goto error_module;
	}
  //印证了之前说的,子设备的ctrl_handler都会挂载到根设备v4l2_device的ctrl_handler上面。
	/* This just returns 0 if either of the two args is NULL */
	err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler, NULL);
	if (err)
		goto error_unregister;

#if defined(CONFIG_MEDIA_CONTROLLER)
	/* Register the entity. */
	if (v4l2_dev->mdev) {
	//上面将
		err = media_device_register_entity(v4l2_dev->mdev, entity);
		if (err < 0)
			goto error_unregister;
	}
#endif

	spin_lock(&v4l2_dev->lock);
	//将子设备链接到跟设备的,subdevs链表上。
	list_add_tail(&sd->list, &v4l2_dev->subdevs);
	spin_unlock(&v4l2_dev->lock);

	return 0;

error_unregister:
	if (sd->internal_ops && sd->internal_ops->unregistered)
		sd->internal_ops->unregistered(sd);
error_module:
	if (!sd->owner_v4l2_dev)
		module_put(sd->owner);
	sd->v4l2_dev = NULL;
	return err;
}
EXPORT_SYMBOL_GPL(v4l2_device_register_subdev);
  • 获取到子设备的entity对象,并将当前根设备对象赋值给subdev->v4l2_dev域。
  • 将子设备的ctrl_handler对象,添加到根设备的v4l2_dev->ctrl_handler域。
  • 将子设备的entity注册到根设备上的media_device设备中。

4.2、v4l2_device_register_subdev_nodes()注册设备节点

drivers/media/v4l2-core/v4l2-device.c 

int __v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev,
					bool read_only)
{
	struct video_device *vdev;
	struct v4l2_subdev *sd;
	int err;

	/* Register a device node for every subdev marked with the
	 * V4L2_SUBDEV_FL_HAS_DEVNODE flag.
	 */
	list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
		if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
			continue;

		if (sd->devnode)
			continue;

		vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
		if (!vdev) {
			err = -ENOMEM;
			goto clean_up;
		}

		video_set_drvdata(vdev, sd);
		strscpy(vdev->name, sd->name, sizeof(vdev->name));
		vdev->dev_parent = sd->dev;
		vdev->v4l2_dev = v4l2_dev;
		vdev->fops = &v4l2_subdev_fops;
		vdev->release = v4l2_device_release_subdev_node;
		vdev->ctrl_handler = sd->ctrl_handler;
		if (read_only)
			set_bit(V4L2_FL_SUBDEV_RO_DEVNODE, &vdev->flags);
		sd->devnode = vdev;
		err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,
					      sd->owner);
		if (err < 0) {
			sd->devnode = NULL;
			kfree(vdev);
			goto clean_up;
		}
#if defined(CONFIG_MEDIA_CONTROLLER)
		sd->entity.info.dev.major = VIDEO_MAJOR;
		sd->entity.info.dev.minor = vdev->minor;

		/* Interface is created by __video_register_device() */
		if (vdev->v4l2_dev->mdev) {
			struct media_link *link;

			link = media_create_intf_link(&sd->entity,
						      &vdev->intf_devnode->intf,
						      MEDIA_LNK_FL_ENABLED |
						      MEDIA_LNK_FL_IMMUTABLE);
			if (!link) {
				err = -ENOMEM;
				goto clean_up;
			}
		}
#endif
	}
	return 0;

clean_up:
	list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
		if (!sd->devnode)
			break;
		video_unregister_device(sd->devnode);
	}

	return err;
}
EXPORT_SYMBOL_GPL(__v4l2_device_register_subdev_nodes);

static inline int __must_check
v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)
{
#if defined(CONFIG_VIDEO_V4L2_SUBDEV_API)
	return __v4l2_device_register_subdev_nodes(v4l2_dev, false);
#else
	return 0;
#endif
}

上面这个方法才是真正注册设备节点的地方,不过一般请看下不这样用,一般稍微修改一下v4l2_device_register_subdev方法,或者直接重新写一个注册方法,在注册设备到v4l2_device根设备上的同时注册设备文件到系统中。

4.3、subdev在系统中的位置

 struct v4l2_device
 		|
 		|-------->subdevs <-----> list <-----> list <-----> list
 		|       					|           |
 						|---------------|   |--------------|
 						|struct sub_dev |   |struct sub_dev|
 						|---------------|   |--------------|

子设备就这样一个一个挂在v4l2_device设备上了。

五、media_device_register()多媒体设备注册

其实这里又用宏转了一下,THIS_MODULE就是代表当前模块的struct module对象,也就代表当前驱动的实例对象。

#define media_device_register(mdev) __media_device_register(mdev, THIS_MODULE)
__media_device_register(mdev, THIS_MODULE)

 drivers/media/mc/mc-device.c

int __must_check __media_device_register(struct media_device *mdev,
					 struct module *owner)
{
	struct media_devnode *devnode;
	int ret;

	devnode = kzalloc(sizeof(*devnode), GFP_KERNEL);
	if (!devnode)
		return -ENOMEM;

	/* Register the device node. */
	mdev->devnode = devnode;
	devnode->fops = &media_device_fops;
	devnode->parent = mdev->dev;
	devnode->release = media_device_release;

	/* Set version 0 to indicate user-space that the graph is static */
	mdev->topology_version = 0;

	ret = media_devnode_register(mdev, devnode, owner);
	if (ret < 0) {
		/* devnode free is handled in media_devnode_*() */
		mdev->devnode = NULL;
		return ret;
	}

	ret = device_create_file(&devnode->dev, &dev_attr_model);
	if (ret < 0) {
		/* devnode free is handled in media_devnode_*() */
		mdev->devnode = NULL;
		media_devnode_unregister_prepare(devnode);
		media_devnode_unregister(devnode);
		return ret;
	}

	dev_dbg(mdev->dev, "Media device registered\n");

	return 0;
}
EXPORT_SYMBOL_GPL(__media_device_register);

上面初始化了一些media_devnode的设备域。便于后续标准接口的调用。紧接着重量级的操作来了,注释部分写的也很精彩。

drivers/media/mc/mc-devnode.c 

int __must_check media_devnode_register(struct media_device *mdev,
					struct media_devnode *devnode,
					struct module *owner)
{
	int minor;
	int ret;

	/* Part 1: Find a free minor number */
	mutex_lock(&media_devnode_lock);
	minor = find_first_zero_bit(media_devnode_nums, MEDIA_NUM_DEVICES);
	if (minor == MEDIA_NUM_DEVICES) {
		mutex_unlock(&media_devnode_lock);
		pr_err("could not get a free minor\n");
		kfree(devnode);
		return -ENFILE;
	}

	set_bit(minor, media_devnode_nums);
	mutex_unlock(&media_devnode_lock);

	devnode->minor = minor;
	devnode->media_dev = mdev;

	/* Part 1: Initialize dev now to use dev.kobj for cdev.kobj.parent */
	devnode->dev.bus = &media_bus_type;
	devnode->dev.devt = MKDEV(MAJOR(media_dev_t), devnode->minor);
	devnode->dev.release = media_devnode_release;
	if (devnode->parent)
		devnode->dev.parent = devnode->parent;
	dev_set_name(&devnode->dev, "media%d", devnode->minor);
	device_initialize(&devnode->dev);

	/* Part 2: Initialize the character device */
	cdev_init(&devnode->cdev, &media_devnode_fops);
	devnode->cdev.owner = owner;
	kobject_set_name(&devnode->cdev.kobj, "media%d", devnode->minor);

	/* Part 3: Add the media and char device */
	ret = cdev_device_add(&devnode->cdev, &devnode->dev);
	if (ret < 0) {
		pr_err("%s: cdev_device_add failed\n", __func__);
		goto cdev_add_error;
	}

	/* Part 4: Activate this minor. The char device can now be used. */
	set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);

	return 0;

cdev_add_error:
	mutex_lock(&media_devnode_lock);
	clear_bit(devnode->minor, media_devnode_nums);
	devnode->media_dev = NULL;
	mutex_unlock(&media_devnode_lock);

	put_device(&devnode->dev);
	return ret;
}

上面代码注册了一个字符设备驱动,上面代码注释可以发现函数分成4部分功能.

  • part1:这一部分功能就是找一个空闲的次设备号。
    1.MEDIA_NUM_DEVICES:该宏表示系统最大支持256个子设备.
    2.media_devnode_nums:该全局变量用来表示所有位号是否已经使用了,这里可以理解成这个变量有256位,每一位都是一个标志。例如:我们得到一个次设备号是5,则该变量的第5位就会被置位。
  • part2:这里会根据主设备号media_dev_t和次设备号,创建字符设备。并将该字符设备的owner赋值为当前模块,然后添加到字符设备集合中。
  • part3:注册设备
  • part4:置标志变量,表示该字符设备可以使用了。

六、举例应用

上面media_device在v4l2中只用于遍历子设备,就没有描述出media_device在系统中的位置,等后面需要时在分析吧。总结到目前了解到v4l2需要一个根节点v4l2_device来管理子设备,然后上层用户空间,根据根节点,来查找所有子设备。

1. 示例背景

假设我们有一个虚拟视频设备(例如一个虚拟摄像头),我们需要通过 V4L2 在 Linux 内核中注册该设备,以便用户空间的应用程序(如 ffmpegmplayer 等)可以通过 /dev/video0 访问它。

2. V4L2 设备注册代码示例

以下是一个简单的示例代码,展示了如何创建和注册一个虚拟的视频设备。

a) 包含必要的头文件

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/videodev2.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/videodev2.h>

b) 定义设备操作函数

在注册设备之前,我们需要实现与设备交互的操作函数。这些操作函数包括打开设备、关闭设备、处理 IO 控制命令等。

static int my_video_open(struct file *file)
{
    printk(KERN_INFO "My video device opened\n");
    return 0;
}

static int my_video_release(struct file *file)
{
    printk(KERN_INFO "My video device released\n");
    return 0;
}

static long my_video_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    switch (cmd) {
    case VIDIOC_QUERYCAP:
        {
            struct v4l2_capability cap;
            memset(&cap, 0, sizeof(cap));
            strlcpy(cap.driver, "my_v4l2_device", sizeof(cap.driver));
            strlcpy(cap.card, "My Virtual Video Device", sizeof(cap.card));
            cap.capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
            return copy_to_user((void __user *)arg, &cap, sizeof(cap)) ? -EFAULT : 0;
        }
    default:
        return -EINVAL;
    }
}

c) 定义设备操作结构体 v4l2_file_operations

static const struct v4l2_file_operations my_video_fops = {
    .owner = THIS_MODULE,
    .open = my_video_open,
    .release = my_video_release,
    .unlocked_ioctl = my_video_ioctl,
};

d) 设备结构体定义与初始化

设备的核心结构体是 video_device,它描述了设备的基本信息,如设备名称、操作函数等。

static struct video_device *vdev;

static int __init my_video_device_init(void)
{
    int ret;

    // 分配视频设备结构体
    vdev = video_device_alloc();
    if (!vdev) {
        printk(KERN_ERR "Failed to allocate video device\n");
        return -ENOMEM;
    }

    // 初始化视频设备
    snprintf(vdev->name, sizeof(vdev->name), "My Virtual Video Device");
    vdev->fops = &my_video_fops;
    vdev->release = video_device_release;

    // 注册设备
    ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
    if (ret < 0) {
        printk(KERN_ERR "Failed to register video device\n");
        video_device_release(vdev);
        return ret;
    }

    printk(KERN_INFO "Video device registered as %s\n", vdev->devnode);
    return 0;
}

e) 清理函数(卸载设备)

当设备驱动卸载时,我们需要注销视频设备并释放相关资源。

static void __exit my_video_device_exit(void)
{
    video_unregister_device(vdev);
    video_device_release(vdev);
    printk(KERN_INFO "Video device unregistered\n");
}

f) 模块入口和出口

最后,我们需要定义模块的入口和出口函数。

module_init(my_video_device_init);
module_exit(my_video_device_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple V4L2 video device driver example");

3. 代码解析

  • video_device_alloc():分配一个 video_device 结构体,该结构体包含了设备的基本信息和操作函数。
  • vdev->name:设置视频设备的名称。
  • vdev->fops:设置设备的操作函数,包括打开、释放和 IO 控制等操作。
  • video_register_device():将设备注册到 V4L2 子系统,并在 /dev 目录下创建设备节点(如 /dev/video0)。
  • video_unregister_device():注销设备并释放相关资源。
  • video_device_release():释放 video_device 结构体。

4. 编译与加载

将上述代码保存为 my_v4l2_device.c 文件,编写 Makefile 来编译模块:

a) 编写 Makefile

obj-m += my_v4l2_device.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

b) 编译和加载模块

make
sudo insmod my_v4l2_device.ko

c) 查看设备

设备成功注册后,可以在 /dev 目录下看到设备节点 /dev/video0。使用 v4l2-ctl 等工具进行测试:

ls /dev/video*
v4l2-ctl --list-devices

5. 测试

此时,用户空间的应用程序(如 ffmpegmplayer)可以通过 V4L2 接口访问该虚拟设备进行视频捕捉、播放等操作。

<think>嗯,用户这次想找关于Linux V4L2 Media子系统的专业书籍。从用户提供的引用材料来看,ta应该是个嵌入式Linux开发者,正在研究摄像头驱动开发,而且已经掌握了V4L2的基本架构概念(比如media controller、v4l2_subdev这些核心组件)。用户特意强"书籍推荐",说明可能需要系统性的学习资料,而不仅仅是零散的博客或文档。 注意到用户在前文展示过对V4L2底层实现的熟悉程度(比如videobuf2、subdev匹配流程这些细节),所以推荐的书籍必须足够深入。不过很遗憾,V4L2子系统作为Linux内核的偏门领域,几乎没有完全聚焦的专著。需要整策略:推荐内核通用驱动开发书籍中相关章节+官方文档+经典论文的组合方案。 比较棘手的是中文资源匮乏问题。用户标注过参考资料都是中文,但Linux内核开发的前沿资料基本都是英文的。得在推荐时平衡这点:既要给出最权威的英文材料(如《Linux Device Drivers》),也要提供中文技术社区的资源线索(比如蜗窝科技这类有质量保证的博客)。 突然想到用户展示的引用[2]提到过"蜗窝科技"的V4L2系列文章,说明用户可能已经接触过这个资源。但既然用户明确要书籍,重点还是应该放在纸质/电子书资源上。《Linux设备驱动程序》第3版虽然旧但V4L2架构部分依然适用,而《Mastering Linux Device Driver Development》较新但深度稍浅——需要明确告知用户这个取舍。 最后补充论文推荐时有点犹豫:用户是否真的需要学术论文?不过考虑到V4L2子系统近年最大的变化就是Media Controller框架的引入,而2014年那篇Linux Symposium的论文正是由框架作者撰写,这种一手资料价值极高,值得冒险推荐。大不了用户不看就是了。 </think>关于Linux V4L2 Media子系统的书籍资源较为稀缺,因其属于内核驱动开发的细分领域。以下是结合引用资料的专业学习建议: ### 📚 推荐学习资源 1. **《Linux Device Drivers》(第3版)** - **重点章节**:第3章(字符设备驱动)、第14章(Linux设备模型)、**第4章扩展阅读V4L2框架** - 虽未直接覆盖Media子系统,但提供了V4L2底层驱动开发基础(如`ioctl`机制、设备注册)。引用[5]中的`v4l2_device`结构体分析可在本书内核设备模型章节找到理论支撑。 2. **《Mastering Linux Device Driver Development》**(Packt出版) - 第10章**Video4Linux Subsystem**详解`v4l2_device`/`video_device`结构体设计(引用[2]内容) - 包含**Media Controller API实战案例**(对应引用[1][4]的拓扑管理) 3. **Linux内核官方文档**(必备) - [V4L2 Core Documentation](https://www.kernel.org/doc/html/latest/userspace-api/media/index.html) 覆盖`v4l2_subdev`抽象(引用[3])、videobuf2机制(引用[2][3])、Media Controller链路配置(引用[4]) - [Media Subsystem Docs](https://linuxtv.org/downloads/v4l-dvb-apis/):含USB摄像头注册流程(引用[1]案例) 4. **开源社区权威指南** - **《Linux Media Infrastructure》**(在线手册):详解实体/链路管理(引用[4]) - **蜗窝科技系列博客**(引用[2][3]来源):分析`v4l2_subdev`透明化设计(引用[3])与ioctl工作流 ### ⚙️ 实践建议(引用资料延伸) 1. **源码分析** - 内核源码`drivers/media`目录: - `v4l2-core/`:核心框架(对应引用[2][5]) - `mc/`:Media Controller实现(引用[1][4]) - 结合`media-ctl`工具试拓扑(引用[4]实战方法) 2. **论文参考** - **《The Linux Media Subsystem》**(Linux Symposium 2014):阐述Media+V4L2协同架构(引用[1][4]的理论基础) ### 📖 替代书籍方案 若需纸质书籍,可组合: ```markdown 1. 《Linux设备驱动开发详解》第5版(宋宝华) - 第16章 V4L2驱动开发(基础API) 2. 《Embedded Linux System Development》第2版 - 第8章 Multimedia Subsystems(Media子系统拓扑配置) ``` ### ❓相关问题 1. V4L2中`v4l2_subdev`如何实现硬件无关性设计?[^3] 2. Media Controller在复杂摄像系统(如MIPI-CSI链路)中如何配置数据流路径?[^4] 3. 如何通过`media-ctl`工具试USB摄像头的实体-链路拓扑?[^1][^4] > 💡 提示:优先阅读**官方文档+内核源码**(引用[2][3][5]来源),书籍作为架构理解补充。开发时需结合具体硬件手册(如Sensor的v4l2_subdev操作集实现)。 [^1]: Media子系统V4L2的协同关系 [^2]: V4L2核心结构体深度解析 [^3]: v4l2_subdev透明化设计原理 [^4]: Media Controller在复杂设备中的应用 [^5]: V4L2设备遍历与多驱动协同
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值