备注:
1. Kernel版本:5.4
2. 使用工具:Source Insight 4.0
Video设备使用struct video_device结构体表示。设备的操作方法由fops和ioctl_ops函数指针集合指定,fops是文件操作方法,实现常见的open、read、write方法,ioctl_ops实现应用层的ioctl函数,功能较多。
具体的设备类型由vfl_type指定,可用VFL_TYPE_XXXX表示。Video设备是一个字符设备,由cdev指向创建的字符设备结构体。
使用video_device_alloc函数动态分配video_device结构体内存,使用video_device_release释放分配的video_device结构体内存,video_device的release函数可以初始化为video_device_release,当设备注销时释放内存。
使用video_register_device注册该结构体,使用video_unregister_device注销该结构体。
// 源码:include/media/v4l2-dev.h
/**
* enum vfl_devnode_type - type of V4L2 device node
*
* @VFL_TYPE_GRABBER: for video input/output devices
* @VFL_TYPE_VBI: for vertical blank data (i.e. closed captions, teletext)
* @VFL_TYPE_RADIO: for radio tuners
* @VFL_TYPE_SUBDEV: for V4L2 subdevices
* @VFL_TYPE_SDR: for Software Defined Radio tuners
* @VFL_TYPE_TOUCH: for touch sensors
* @VFL_TYPE_MAX: number of VFL types, must always be last in the enum
*/
// video设备类型,由video_device的vfl_type成员指定
enum vfl_devnode_type {
VFL_TYPE_GRABBER = 0, // 图像采集设备,包括摄像头、调谐器等
VFL_TYPE_VBI, // 从视频消隐的时间段取得信息的设备
VFL_TYPE_RADIO, // 无线电设备,如收音机等
VFL_TYPE_SUBDEV, // v4l2从设备
VFL_TYPE_SDR, // Software Defined Radio
VFL_TYPE_TOUCH, // 触摸传感器
VFL_TYPE_MAX /* Shall be the last one */
};
// 源码:include/media/v4l2-dev.h
/**
* enum vfl_direction - Identifies if a &struct video_device corresponds
* to a receiver, a transmitter or a mem-to-mem device.
*
* @VFL_DIR_RX: device is a receiver.
* @VFL_DIR_TX: device is a transmitter.
* @VFL_DIR_M2M: device is a memory to memory device.
*
* Note: Ignored if &enum vfl_devnode_type is %VFL_TYPE_SUBDEV.
*/
// 传输方向,设备类型为VFL_TYPE_SUBDEV时忽略,由video_device的vfl_dir成员指定
enum vfl_devnode_direction {
VFL_DIR_RX, // 接收
VFL_DIR_TX, // 发送
VFL_DIR_M2M,//内存到内存
};
// 源码:include/media/v4l2-dev.h
struct v4l2_ioctl_callbacks;
struct video_device;
struct v4l2_device;
struct v4l2_ctrl_handler;
/**
* enum v4l2_video_device_flags - Flags used by &struct video_device
*
* @V4L2_FL_REGISTERED:
* indicates that a &struct video_device is registered.
* Drivers can clear this flag if they want to block all future
* device access. It is cleared by video_unregister_device.
* @V4L2_FL_USES_V4L2_FH:
* indicates that file->private_data points to &struct v4l2_fh.
* This flag is set by the core when v4l2_fh_init() is called.
* All new drivers should use it.
* @V4L2_FL_QUIRK_INVERTED_CROP:
* some old M2M drivers use g/s_crop/cropcap incorrectly: crop and
* compose are swapped. If this flag is set, then the selection
* targets are swapped in the g/s_crop/cropcap functions in v4l2-ioctl.c.
* This allows those drivers to correctly implement the selection API,
* but the old crop API will still work as expected in order to preserve
* backwards compatibility.
* Never set this flag for new drivers.
*/
// video_device注册状态掩码,若注册成功则video_device的flags的bit0为1,
enum v4l2_video_device_flags {
// 注销时video_device的flags的bit0清零
V4L2_FL_REGISTERED = 0,
// video_device的flags的bit1为1,则file->private_data指向struct v4l2_fh
V4L2_FL_USES_V4L2_FH = 1,
V4L2_FL_QUIRK_INVERTED_CROP = 2,
};
// 源码:include/media/v4l2-dev.h
/*
* Newer version of video_device, handled by videodev2.c
* This version moves redundant code from video device code to
* the common handler
*/
/**
* struct video_device - Structure used to create and manage the V4L2 device
* nodes.
*
* @entity: &struct media_entity
* @intf_devnode: pointer to &struct media_intf_devnode
* @pipe: &struct media_pipeline
* @fops: pointer to &struct v4l2_file_operations for the video device
* @device_caps: device capabilities as used in v4l2_capabilities
* @dev: &struct device for the video device
* @cdev: character device
* @v4l2_dev: pointer to &struct v4l2_device parent
* @dev_parent: pointer to &struct device parent
* @ctrl_handler: Control handler associated with this device node.
* May be NULL.
* @queue: &struct vb2_queue associated with this device node. May be NULL.
* @prio: pointer to &struct v4l2_prio_state with device's Priority state.
* If NULL, then v4l2_dev->prio will be used.
* @name: video device name
* @vfl_type: V4L device type, as defined by &enum vfl_devnode_type
* @vfl_dir: V4L receiver, transmitter or m2m
* @minor: device node 'minor'. It is set to -1 if the registration failed
* @num: number of the video device node
* @flags: video device flags. Use bitops to set/clear/test flags.
* Contains a set of &enum v4l2_video_device_flags.
* @index: attribute to differentiate multiple indices on one physical device
* @fh_lock: Lock for all v4l2_fhs
* @fh_list: List of &struct v4l2_fh
* @dev_debug: Internal device debug flags, not for use by drivers
* @tvnorms: Supported tv norms
*
* @release: video device release() callback
* @ioctl_ops: pointer to &struct v4l2_ioctl_ops with ioctl callbacks
*
* @valid_ioctls: bitmap with the valid ioctls for this device
* @lock: pointer to &struct mutex serialization lock
*
* .. note::
* Only set @dev_parent if that can't be deduced from @v4l2_dev.
*/
struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity entity; // 用于运行时数据流的管理
struct media_intf_devnode *intf_devnode;
struct media_pipeline pipe;
#endif
// videox字符设备的文件操作集
const struct v4l2_file_operations *fops;
// 设备类型
u32 device_caps;
// video_device对应的设备结构体
struct device dev;
// 字符设备指针,注册时创建字符设备结构体
struct cdev *cdev;
// 指向v4l2_device
struct v4l2_device *v4l2_dev;
struct device *dev_parent;
struct v4l2_ctrl_handler *ctrl_handler;
//videobuffer2 队列
struct vb2_queue *queue;
struct v4l2_prio_state *prio;
/* device info */
char name[32];
enum vfl_devnode_type vfl_type; // 设备类型
enum vfl_devnode_direction vfl_dir; // 传输方向
int minor; // 次设备号,如果注册失败,将被设置为-1
u16 num; // 设备数量
unsigned long flags; // 标志位,可以使用set/clear/test操作
int index; // 物理设备的索引,用来区分不同的物理设备
/* V4L2 file handles */
spinlock_t fh_lock;
struct list_head fh_list;
int dev_debug;
v4l2_std_id tvnorms;
/* callbacks */
// 设备引用计数为0时被调用,用来释放资源,如释放动态分配的struct video_device结构体
void (*release)(struct video_device *vdev);
// 控制函数,应用层的ioctl都在这里实现,比较重要
const struct v4l2_ioctl_ops *ioctl_ops;
DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);
struct mutex *lock;
};
//源码:drivers/media/v4l2-core/vl2-dev.c
struct video_device *video_device_alloc(void)
{
return kzalloc(sizeof(struct video_device), GFP_KERNEL);
}
EXPORT_SYMBOL(video_device_alloc);
void video_device_release(struct video_device *vdev)
{
kfree(vdev);
}
EXPORT_SYMBOL(video_device_release);
//源码:drivers/media/v4l2-core/vl2-dev.c
void video_device_release_empty(struct video_device *vdev)
{
/* Do nothing */
/* Only valid when the video_device struct is a static. */
}
EXPORT_SYMBOL(video_device_release_empty);
如果需要将 video_device 结构体嵌入到更大的结构体里面的话,就需要设置 vdev 的 release 成员。内核提供了两个默认的 release 回调函数,如下:
video_device_release() // 仅仅调用kfree释放分配的内存,用于动态分配情况下 video_device_release_empty() // 不做任何事情,静态变量
video_device创建后,必定要初始化的成员如下:
v4l2_dev : 设置为指向v4l2_device
name : 设置一个唯一的名字
fops: 设置为指向我们自己的v4l2_file_operations结构体
ioctl_ops :设置为指向我们自己的v4l2_ioctl_ops结构体
lock :互斥量。若想要自己实现互斥操作,则可以不设置此变量。不过强烈建设让V4L2使用此变量确保互斥。
prio:优先级,用来实现VIDIOC_G/S_PRIORITY。若为NULL,则V4L2会使用v4l2_device的优先级。
互斥锁-lock成员。当lock不为NULL时,V4L2框架会使用此mutex确保操作的互斥性。
然而,对于某个比较耗时的操作,若在运行过程中加锁,会导致驱动无法为其他任务服务。此时,可以将lock设置为NULL,互斥操作由驱动自己负责,V4L2框架不参与。驱动编写者必须确保在调用V4L2的函数时,打开mutex,退出时关闭mutex。
示例:
//源码:drivers/media/platform/sunxi/sun-csi/sun6i_video.c
/* Register video device */
strscpy(vdev->name, name, sizeof(vdev->name));
vdev->release = video_device_release_empty;
vdev->fops = &sun6i_video_fops;
vdev->ioctl_ops = &sun6i_video_ioctl_ops;
vdev->vfl_type = VFL_TYPE_GRABBER;
vdev->vfl_dir = VFL_DIR_RX;
vdev->v4l2_dev = &csi->v4l2_dev;
vdev->queue = vidq;
vdev->lock = &video->lock;
vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
video_set_drvdata(vdev, video);
video_device所支持的功能
//源码:include/uapi/linux/videodev2.h
/* Values for 'capabilities' field */
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* Is a video capture device */
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 /* Is a video output device */
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 /* Can do video overlay */
#define V4L2_CAP_VBI_CAPTURE 0x00000010 /* Is a raw VBI capture device */
#define V4L2_CAP_VBI_OUTPUT 0x00000020 /* Is a raw VBI output device */
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 /* Is a sliced VBI capture device */
#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 /* Is a sliced VBI output device */
#define V4L2_CAP_RDS_CAPTURE 0x00000100 /* RDS data capture */
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200 /* Can do video output overlay */
#define V4L2_CAP_HW_FREQ_SEEK 0x00000400 /* Can do hardware frequency seek */
#define V4L2_CAP_RDS_OUTPUT 0x00000800 /* Is an RDS encoder */
/* Is a video capture device that supports multiplanar formats */
#define V4L2_CAP_VIDEO_CAPTURE_MPLANE 0x00001000
/* Is a video output device that supports multiplanar formats */
#define V4L2_CAP_VIDEO_OUTPUT_MPLANE 0x00002000
/* Is a video mem-to-mem device that supports multiplanar formats */
#define V4L2_CAP_VIDEO_M2M_MPLANE 0x00004000
/* Is a video mem-to-mem device */
#define V4L2_CAP_VIDEO_M2M 0x00008000
#define V4L2_CAP_TUNER 0x00010000 /* has a tuner */
#define V4L2_CAP_AUDIO 0x00020000 /* has audio support */
#define V4L2_CAP_RADIO 0x00040000 /* is a radio device */
#define V4L2_CAP_MODULATOR 0x00080000 /* has a modulator */
#define V4L2_CAP_SDR_CAPTURE 0x00100000 /* Is a SDR capture device */
#define V4L2_CAP_EXT_PIX_FORMAT 0x00200000 /* Supports the extended pixel format */
#define V4L2_CAP_SDR_OUTPUT 0x00400000 /* Is a SDR output device */
#define V4L2_CAP_META_CAPTURE 0x00800000 /* Is a metadata capture device */
#define V4L2_CAP_READWRITE 0x01000000 /* read/write systemcalls */
#define V4L2_CAP_ASYNCIO 0x02000000 /* async I/O */
#define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */
#define V4L2_CAP_TOUCH 0x10000000 /* Is a touch device */
#define V4L2_CAP_DEVICE_CAPS 0x80000000 /* sets device capabilities field */
// 源码:include/media/v4l2-dev.h
struct v4l2_prio_state {
atomic_t prios[4];
};
V4L2有三个优先级,定义如下:
//源码:include/uapi/linux/videodev2.h
enum v4l2_priority {
V4L2_PRIORITY_UNSET = 0, /* not initialized */
V4L2_PRIORITY_BACKGROUND = 1,
V4L2_PRIORITY_INTERACTIVE = 2,
V4L2_PRIORITY_RECORD = 3,
V4L2_PRIORITY_DEFAULT = V4L2_PRIORITY_INTERACTIVE,
};
V4L2_PRIORITY_BACKGROUND:最低优先级,正如字面意思,主要用于后台处理,比如监视VBI数据的传输。
V4L2_PRIORITY_INTERACTIVE : 默认优先级。
V4L2_PRIORITY_RECORD :最高优先级。系统中只能有一个此优先级应用,此优先级的应用会阻塞其他所有的应用。
驱动中,是如何操作v4l2_prio_stat结构体中的4个atomic_t变量,从而实现优先级管理呢?概括之,如下:
若这四个变量中的任何一个不为零,则表示该变量所对应的优先级有效。比如若prios[V4L2_PRIORITY_BACKGROUND]不为零,则程序处于V4L2_PRIORITY_BACKGROUND优先级。
优先级的确定过程:从数组(atomic_t prios[4])末尾依次查询,返回第一个不为零的变量的位置。此位置即为程序的优先级。
V4L2中与优先级相关的函数有下列几个:
//清零所有的优先级变量。
void v4l2_prio_init(struct v4l2_prio_state *global);
//设置指定的优先级(new)变量的值为1,并将以前的优先级(local)变量设置为0.
int v4l2_prio_change(struct v4l2_prio_state *global, enum v4l2_priority *local, enum v4l2_priority new);
//设置程序的优先级为V4L2_PRIORITY_DEFAULT
void v4l2_prio_open(struct v4l2_prio_state *global, enum v4l2_priority *local);
//设置当前优先级变量的值为0
void v4l2_prio_close(struct v4l2_prio_state *global, enum v4l2_priority local);
//查询程序的优先级。查询方法如上面所示。
enum v4l2_priority v4l2_prio_max(struct v4l2_prio_state *global);
//查询以确定程序是否能在指定的优先级下工作。如可以,则返回0;不可以则返回-EBUSY。
int v4l2_prio_check(struct v4l2_prio_state *global, enum v4l2_priority local);
该结构体提供了一种简单的保存文件句柄特定数据的方法。v4l2_fh 的使用者-v4l2 framework 可以通过检查 video_device->flags 的 V4L2_FL_USES_V4L2_FH
位来知道驱动是否使用 v4l2_fh 作为 file->private_data 指针,该标志位通过调用函数 v4l2_fh_init 来设置。
v4l2_fh 结构体作为驱动自己的文件句柄存在,并且在驱动的 open 函数里面设置 file->private_data 指向它,v4l2_fh 有多个的时候会作为一个链表存在于 file->private_data 中,可以遍历访问。在大多数情况下 v4l2_fh 结构体都被嵌入到更大的结构体里面,此时需要在 open 函数里面调用
v4l2_fh_init+v4l2_fh_add 进行添加,在 release 函数里面调用 v4l2_fh_del
+v4l2_fh_exit
进行退出。
//源码:include/media/v4l2-dev.h
/**
* struct v4l2_file_operations - fs operations used by a V4L2 device
*
* @owner: pointer to struct module
* @read: operations needed to implement the read() syscall
* @write: operations needed to implement the write() syscall
* @poll: operations needed to implement the poll() syscall
* @unlocked_ioctl: operations needed to implement the ioctl() syscall
* @compat_ioctl32: operations needed to implement the ioctl() syscall for
* the special case where the Kernel uses 64 bits instructions, but
* the userspace uses 32 bits.
* @get_unmapped_area: called by the mmap() syscall, used when %!CONFIG_MMU
* @mmap: operations needed to implement the mmap() syscall
* @open: operations needed to implement the open() syscall
* @release: operations needed to implement the release() syscall
*
* .. note::
*
* Those operations are used to implemente the fs struct file_operations
* at the V4L2 drivers. The V4L2 core overrides the fs ops with some
* extra logic needed by the subsystem.
*/
struct v4l2_file_operations {
struct module *owner;
// 读函数
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
// 写函数
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
// poll函数
__poll_t (*poll) (struct file *, struct poll_table_struct *);
// ioctl函数
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
#ifdef CONFIG_COMPAT
long (*compat_ioctl32) (struct file *, unsigned int, unsigned long);
#endif
unsigned long (*get_unmapped_area) (struct file *, unsigned long,
unsigned long, unsigned long, unsigned long);
// 内存映射函数
int (*mmap) (struct file *, struct vm_area_struct *);
// 打开设备
int (*open) (struct file *);
// 关闭设备
int (*release) (struct file *);
};
示例:
//源码:drivers/media/platform/sunxi/sun-csi/sun6i_video.c
static const struct v4l2_file_operations sun6i_video_fops = {
.owner = THIS_MODULE,
.open = sun6i_video_open,
.release = sun6i_video_close,
.unlocked_ioctl = video_ioctl2,
.mmap = vb2_fop_mmap,
.poll = vb2_fop_poll
};
定义详见v4l2的结构体之ioctl
// include/media/v4l2-dev.h
// 此时将会有/dev/videox的设备节点。
/**
* video_register_device - register video4linux devices
*
* @vdev: struct video_device to register
* @type: type of device to register, as defined by &enum vfl_devnode_type
* @nr: which device node number is desired:
* (0 == /dev/video0, 1 == /dev/video1, ..., -1 == first free)
*
* Internally, it calls __video_register_device(). Please see its
* documentation for more details.
*
* .. note::
* if video_register_device fails, the release() callback of
* &struct video_device structure is *not* called, so the caller
* is responsible for freeing any data. Usually that means that
* you video_device_release() should be called on failure.
*/
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);
}
/**
* video_unregister_device - unregister a video4linux device
* @vdev: the device to unregister
*
* This unregisters the passed device. Future open calls will
* be met with errors.
*/
void video_unregister_device(struct video_device *vdev)
{
/* Check if vdev was ever registered at all */
if (!vdev || !video_is_registered(vdev))
return;
mutex_lock(&videodev_lock);
/* This must be in a critical section to prevent a race with v4l2_open.
* Once this bit has been cleared video_get may never be called again.
*/
clear_bit(V4L2_FL_REGISTERED, &vdev->flags);
mutex_unlock(&videodev_lock);
device_unregister(&vdev->dev);
}
EXPORT_SYMBOL(video_unregister_device);
该段代码会注册一个字符设备驱动程序并在用户空间生成一个设备节点。如果 v4l2_device
父设备的 mdev
成员不为空的话,video_device
的 entity 会被自动的注册到 media framework 里面。函数最后一个参数是设备节点索引号,如果是 -1 的话就取用第一个内核中可用的索引号值。注册的设备类型以及用户空间中的节点名称取决于以下标识:
VFL_TYPE_GRABBER: videoX 输入输出设备
VFL_TYPE_VBI: vbiX
VFL_TYPE_RADIO: radioX 硬件定义的音频调谐设备
VFL_TYPE_SDR: swradioX 软件定义的音频调谐设备
// 源码:drivers/media/v4l2-code/v4l2-dev.c
int __video_register_device(struct video_device *vdev,
enum vfl_devnode_type 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;
/* A minor value of -1 marks this video device as never
having been registered */
vdev->minor = -1;
/* the release callback MUST be present */
if (WARN_ON(!vdev->release))
return -EINVAL;
/* the v4l2_dev pointer MUST be present */
if (WARN_ON(!vdev->v4l2_dev))
return -EINVAL;
/* the device_caps field MUST be set for all but subdevs */
if (WARN_ON(type != VFL_TYPE_SUBDEV && !vdev->device_caps))
return -EINVAL;
/* v4l2_fh support */
spin_lock_init(&vdev->fh_lock);
INIT_LIST_HEAD(&vdev->fh_list);
/* 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;
case VFL_TYPE_SDR:
/* Use device name 'swradio' because 'sdr' was already taken. */
name_base = "swradio";
break;
case VFL_TYPE_TOUCH:
name_base = "v4l-touch";
break;
default:
pr_err("%s called with unknown type: %d\n",
__func__, type);
return -EINVAL;
}
vdev->vfl_type = type;
vdev->cdev = NULL;
if (vdev->dev_parent == NULL)
vdev->dev_parent = vdev->v4l2_dev->dev;
if (vdev->ctrl_handler == NULL)
vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
/* If the prio state pointer is NULL, then use the v4l2_device
prio state. */
if (vdev->prio == NULL)
vdev->prio = &vdev->v4l2_dev->prio;
/* Part 2: find a free minor, device node number and device index. */
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
/* Keep the ranges for the first four types for historical
* reasons.
* Newer devices (not yet in place) should use the range
* of 128-191 and just pick the first free minor there
* (new style). */
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;
}
#endif
/* Pick a device node number */
// 获取一个次设备号
mutex_lock(&videodev_lock);
nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
if (nr == minor_cnt)
nr = devnode_find(vdev, 0, minor_cnt);
if (nr == minor_cnt) {
pr_err("could not get a free device node number\n");
mutex_unlock(&videodev_lock);
return -ENFILE;
}
#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. */
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_devices[i] == NULL)
break;
if (i == VIDEO_NUM_DEVICES) {
mutex_unlock(&videodev_lock);
pr_err("could not get a free minor\n");
return -ENFILE;
}
#endif
vdev->minor = i + minor_offset;
vdev->num = nr;
/* Should not happen since we thought this minor was free */
if (WARN_ON(video_devices[vdev->minor])) {
mutex_unlock(&videodev_lock);
pr_err("video_device not empty!\n");
return -ENFILE;
}
devnode_set(vdev);
vdev->index = get_index(vdev);
video_devices[vdev->minor] = vdev;
mutex_unlock(&videodev_lock);
if (vdev->ioctl_ops)
determine_valid_ioctls(vdev);
/* Part 3: Initialize the character device */
// 申请一个字符设备
vdev->cdev = cdev_alloc();
if (vdev->cdev == NULL) {
ret = -ENOMEM;
goto cleanup;
}
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
// 添加一个字符设备
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
if (ret < 0) {
pr_err("%s: cdev_add failed\n", __func__);
kfree(vdev->cdev);
vdev->cdev = NULL;
goto cleanup;
}
/* Part 4: register the device with sysfs */
// 自动创建设备节点
vdev->dev.class = &video_class;
vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
vdev->dev.parent = vdev->dev_parent;
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
ret = device_register(&vdev->dev);
if (ret < 0) {
pr_err("%s: device_register failed\n", __func__);
goto cleanup;
}
/* Register the release callback that will be called when the last
reference to the device goes away. */
vdev->dev.release = v4l2_device_release;
if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)
pr_warn("%s: requested %s%d, got %s\n", __func__,
name_base, nr, video_device_node_name(vdev));
/* Increase v4l2_device refcount */
v4l2_device_get(vdev->v4l2_dev);
/* Part 5: Register the entity. */
ret = video_register_media_controller(vdev);
/* Part 6: Activate this minor. The char device can now be used. */
set_bit(V4L2_FL_REGISTERED, &vdev->flags);
return 0;
cleanup:
mutex_lock(&videodev_lock);
if (vdev->cdev)
cdev_del(vdev->cdev);
video_devices[vdev->minor] = NULL;
devnode_clear(vdev);
mutex_unlock(&videodev_lock);
/* Mark this video device as never having been registered. */
vdev->minor = -1;
return ret;
}
// 源码:drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c
int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
const char *name)
{
......
/* Register video device */
strscpy(vdev->name, name, sizeof(vdev->name));
vdev->release = video_device_release_empty;
vdev->fops = &sun6i_video_fops;
vdev->ioctl_ops = &sun6i_video_ioctl_ops;
vdev->vfl_type = VFL_TYPE_GRABBER;
vdev->vfl_dir = VFL_DIR_RX;
vdev->v4l2_dev = &csi->v4l2_dev;
vdev->queue = vidq;
vdev->lock = &video->lock;
vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
video_set_drvdata(vdev, video);
ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
if (ret < 0) {
v4l2_err(&csi->v4l2_dev,
"video_register_device failed: %d\n", ret);
goto release_vb2;
}
......
}