Linux v4l2子系统(2):v4l2框架分析
v4l为Video子系统提供统一框架,驱动通过将v4l2_dev/v4l2_subdev注册到Video子系统,在用户空间创建设备节点。
使用如下命令在kernel(5.10.110)生成driver-api.pdf帮助文件:
make SPHINXDIRS="driver-api" pdfdocs
在《CHAPTER FIFTYTWO MEDIA SUBSYSTEM KERNEL INTERNAL API》介绍了v4l2 subsystem。
或者参考《1. Video4Linux devices — The Linux Kernel documentation》。
1 v4l子系统初始化
static int __init videodev_init(void) { dev_t dev = MKDEV(VIDEO_MAJOR, 0); int ret; pr_info("Linux video capture interface: v2.00\n"); ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);--注册主设备号为81的video设备区间,子设备数量为256,名称为video4linux。 if (ret < 0) { pr_warn("videodev: unable to get major %d\n", VIDEO_MAJOR); return ret; } ret = class_register(&video_class);--注册名称为video4linux名称的video类。 if (ret < 0) { unregister_chrdev_region(dev, VIDEO_NUM_DEVICES); pr_warn("video_dev: class_register failed\n"); return -EIO; } return 0; }
所有video类型设备位于/sys/class/video4linux,并且没有设备都会有如下属性:
- name:对应struce video_device的name。
- dev_debug:使能不通模块的调试信息。
Definition Mask Description V4L2_DEV_DEBUG_IOCTL 0x01 Log the ioctl name and error code. VIDIOC_(D)QBUF ioctls are only logged if bit 0x08
is also set.V4L2_DEV_DEBUG_IOCTL_ARG 0x02 Log the ioctl name arguments and error code. VIDIOC_(D)QBUF ioctls are only logged
if bit 0x08 is also set.V4L2_DEV_DEBUG_FOP 0x04 Log the fle operations open, release, read, write, mmap and get_unmapped_area. The
read and write operations are only logged if bit 0x08 is also set.V4L2_DEV_DEBUG_STREAMING 0x08 Log the read and write fle operations and the VIDIOC_QBUF and VIDIOC_DQBUF
ioctls.V4L2_DEV_DEBUG_POLL 0x10 Log the poll fle operation. V4L2_DEV_DEBUG_CTRL 0x20 Log error and messages in the control operations - index:设备序号。
static struct class video_class = { .name = VIDEO_NAME, .dev_groups = video_device_groups, }; static struct attribute *video_device_attrs[] = { &dev_attr_name.attr, &dev_attr_dev_debug.attr, &dev_attr_index.attr, NULL, }; ATTRIBUTE_GROUPS(video_device);
2 video_device
struct video_device { #if defined(CONFIG_MEDIA_CONTROLLER) struct media_entity entity;--该设备在Media子系统中对应的Entity。 struct media_intf_devnode *intf_devnode;--该video_device的设备在Media子系统中对应的Interface。 struct media_pipeline pipe; #endif const struct v4l2_file_operations *fops;--v4l子系统中对v4l2设备的操作函数集。 u32 device_caps; /* sysfs */ struct device dev; struct cdev *cdev; struct v4l2_device *v4l2_dev; struct device *dev_parent; struct v4l2_ctrl_handler *ctrl_handler; struct vb2_queue *queue;--此设备所使用的vb2_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; u16 num; unsigned long flags; int index; /* V4L2 file handles */ spinlock_t fh_lock; struct list_head fh_list; int dev_debug;--调试配置,可以通过设备的dev_debug节点配置。 v4l2_std_id tvnorms; /* callbacks */ void (*release)(struct video_device *vdev); const struct v4l2_ioctl_ops *ioctl_ops;--ioctl扩展。 ... };
struct v4l2_file_operations是对v4l2设备进行操作的函数集:
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_t (*poll) (struct file *, struct poll_table_struct *); 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 *); ANDROID_KABI_RESERVE(1); };
video_device主要API有:
struct video_device * __must_check video_device_alloc(void);--分配video_device。 static inline int __must_check video_register_device(struct video_device *vdev, enum vfl_devnode_type type, int nr)--注册v4l设备,根据type生成设备节点。 void video_unregister_device(struct video_device *vdev);--注销v4l设备。 void video_device_release(struct video_device *vdev);--释放video_device数据。
__video_register_device是注册v4l设备的主体:
int __video_register_device(struct video_device *vdev, enum vfl_devnode_type type, int nr, int warn_if_nr_in_use, struct module *owner) { ... spin_lock_init(&vdev->fh_lock); INIT_LIST_HEAD(&vdev->fh_list); /* Part 1: check device type */--根据type生成设备节点,名称根据节点类型不同而异。 switch (type) { case VFL_TYPE_VIDEO: name_base = "video"; break; ... default: ... } ... /* Part 2: find a free minor, device node number and device index. */--根据类型不同minor基准也不同,并找到合适的minor号。 #ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES switch (type) { case VFL_TYPE_VIDEO: minor_offset = 0; minor_cnt = 64; break; ... } #endif ... /* Part 3: Initialize the character device */--分配并初始化video字符设备,操作函数集为v4l2_fops。 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); ... /* Part 4: register the device with sysfs */--注册为video4linux类设备,创建设备节点。 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; } vdev->dev.release = v4l2_device_release;--当设备引用计数为0时调用,释放资源。 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);--如果定义了vdev->v4l2_dev->mdev,则注册Entity到Media,并且创建Media设备节点。 /* Part 6: Activate this minor. The char device can now be used. */ set_bit(V4L2_FL_REGISTERED, &vdev->flags);--video_device设备标志置为V4L2_FL_REGISTERED。 return 0; ... }
类似/dev/videoX设备节点系统调用对应的函数集:
static const struct file_operations v4l2_fops = { .owner = THIS_MODULE, .read = v4l2_read, .write = v4l2_write, .open = v4l2_open, .get_unmapped_area = v4l2_get_unmapped_area, .mmap = v4l2_mmap, .unlocked_ioctl = v4l2_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = v4l2_compat_ioctl32, #endif .release = v4l2_release, .poll = v4l2_poll, .llseek = no_llseek, };
3 v4l2_device
v4l2_device表示一个v4l2设备实例,分别和设备节点、Media子系统、subdev等关联。
struct v4l2_device { struct device *dev;--对应的device设备节点数据结构。 struct media_device *mdev;--作为Media设备注册到Media子系统中。 struct list_head subdevs;--下属的v4l2_subdev列表。 spinlock_t lock; char name[V4L2_DEVICE_NAME_SIZE]; void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);--v4l2_subdev的通知。 struct v4l2_ctrl_handler *ctrl_handler; struct v4l2_prio_state prio; struct kref ref; void (*release)(struct v4l2_device *v4l2_dev);--应用计数为0时被调用。 };
struct v4l2_device用于表示v4l2的sub-device:
struct v4l2_subdev { #if defined(CONFIG_MEDIA_CONTROLLER) struct media_entity entity;--subdev在Media子系统中作为Entity。 #endif struct list_head list;--统一v4l2_device下的subdev列表。 struct module *owner; bool owner_v4l2_dev; u32 flags;--标志位,V4L2_SUBDEV_FL_HAS_DEVNODE表示要为此subdev创建一个设备节点。 struct v4l2_device *v4l2_dev; const struct v4l2_subdev_ops *ops;--v4l2_subdev私有的操作函数差异体现在v4l2_subdev_ops中。 const struct v4l2_subdev_internal_ops *internal_ops; struct v4l2_ctrl_handler *ctrl_handler; char name[V4L2_SUBDEV_NAME_SIZE]; u32 grp_id; void *dev_priv; void *host_priv; struct video_device *devnode;--在v4l2子系统中实例。 struct device *dev; struct fwnode_handle *fwnode; struct list_head async_list;--连接到全局subdev_list列表中。 struct v4l2_async_subdev *asd; struct v4l2_async_notifier *notifier; struct v4l2_async_notifier *subdev_notifier; struct v4l2_subdev_platform_data *pdata; };
其中v4l2_subdev_ops函数集包括:Core操作函数集,以及针对特定类型设备的操作函数集。
struct v4l2_subdev_ops { const struct v4l2_subdev_core_ops *core; const struct v4l2_subdev_tuner_ops *tuner; const struct v4l2_subdev_audio_ops *audio; const struct v4l2_subdev_video_ops *video; ... }; struct v4l2_subdev_core_ops { int (*log_status)(struct v4l2_subdev *sd); int (*s_io_pin_config)(struct v4l2_subdev *sd, size_t n, struct v4l2_subdev_io_pin_config *pincfg); ... }; struct v4l2_subdev_video_ops { int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config); ... };
v4l2_subdev_init()注册时将v4l2_subdev_ops和v4l2_subdev关联。
v4l2_subdev_call()优先使用v4l2_subdev_call_wrappers的函数,然后使用特定v4l2_subdev的v4l2_subdev_ops。
#define v4l2_subdev_call(sd, o, f, args...) \ ({ \ struct v4l2_subdev *__sd = (sd); \ int __result; \ if (!__sd) \ __result = -ENODEV; \ else if (!(__sd->ops->o && __sd->ops->o->f)) \ __result = -ENOIOCTLCMD; \ else if (v4l2_subdev_call_wrappers.o && \ v4l2_subdev_call_wrappers.o->f) \ __result = v4l2_subdev_call_wrappers.o->f( \ __sd, ##args); \ else \ __result = __sd->ops->o->f(__sd, ##args); \ __result; \ })
v4l2_dev和v4l2_subdev相关函数主要包括:
- v4l2注册注销:v4l2_device_register、v4l2_device_unregister。
- v4l2引用计数:v4l2_device_get、v4l2_device_put。
- subdev注册注销,设备创建:v4l2_device_register_subdev、v4l2_device_unregister_subdev、v4l2_device_register_subdev_nodes等。
static inline void v4l2_device_get(struct v4l2_device *v4l2_dev) int v4l2_device_put(struct v4l2_device *v4l2_dev); int __must_check v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);--初始化v4l2_dev成员,若dev->driver_data为空则指向v4l2_dev。 int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename, atomic_t *instance);--配置v4l2_device的那么。 void v4l2_device_disconnect(struct v4l2_device *v4l2_dev);--对于支持热插拔的USB设备,设置状态位disconnected。 void v4l2_device_unregister(struct v4l2_device *v4l2_dev);--去注册所有sub-devices,以及其他相关资源。 int __must_check v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, struct v4l2_subdev *sd);--将v4l2_subdev和v4l2_dev关联,创建Media Entity。 void v4l2_device_unregister_subdev(struct v4l2_subdev *sd);--将v4l2_subdev和其所属的v4l2_dev解除关联,并且注销相关Media Entity、释放相关资源等。 int __must_check __v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev, bool read_only); static inline int __must_check v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)--遍历v4l2_dev的所有v4l2_subdev,为具有V4L2_SUBDEV_FL_HAS_DEVNODE标志位的v4l2_subdev创建设备节点。 { return __v4l2_device_register_subdev_nodes(v4l2_dev, false); } static inline int __must_check v4l2_device_register_ro_subdev_nodes(struct v4l2_device *v4l2_dev) { return __v4l2_device_register_subdev_nodes(v4l2_dev, true); }
v4l2_device_register_subdev_nodes()为v4l2_device下所有v4l2_subdev:
- 注册到v4l2子系统,创建/dev/v4l-subdevX节点。
- 建立Media Graph中Entity到设备节点链接。
int __v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev, bool read_only) { ... list_for_each_entry(sd, &v4l2_dev->subdevs, list) {--遍历v4l2_dev->subdevs上所有v4l2_subdev。 if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE)) continue; ... vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);--为v4l2_subdev创建video_device。 ... 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;--v4l2_subdev类型的设备操作函数集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); err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1, sd->owner);--将v4l2_subdev类型设备注册到v4l2子系统,创建的设备名称为/dev/v4l-subdevX。 ... sd->devnode = vdev; #if defined(CONFIG_MEDIA_CONTROLLER) sd->entity.info.dev.major = VIDEO_MAJOR; sd->entity.info.dev.minor = vdev->minor; 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);--如果对应v4l2_subdev设备创建了设备节点,则在Media子系统中建立和此设备节点关联的Entity。 ... } #endif } return 0; ... }
v4l2_subdev对应的v4l2_file_operations函数集为:
const struct v4l2_file_operations v4l2_subdev_fops = { .owner = THIS_MODULE, .open = subdev_open, .unlocked_ioctl = subdev_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl32 = subdev_compat_ioctl32, #endif .release = subdev_close, .poll = subdev_poll, };
对于v4l2_device和v4l2_subdev对应的操作函数关系如下:
有填充色的表示私有函数集。
4 v4l2_fh
参考《1.6. V4L2 File handlers — The Linux Kernel documentation》。
struct v4l2_fh用于v4l2框架中file handle保存特殊数据。如果video_device->flags置位V4L2_FL_USES_V4L2_FH,则表示file->private_data指向v4l2_fh。
struct v4l2_fh { struct list_head list; struct video_device *vdev; struct v4l2_ctrl_handler *ctrl_handler; enum v4l2_priority prio; /* Events */ wait_queue_head_t wait; struct mutex subscribe_lock; struct list_head subscribed; struct list_head available; unsigned int navailable; u32 sequence; struct v4l2_m2m_ctx *m2m_ctx; };
v4l2_fh对应的API有:
void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev); void v4l2_fh_add(struct v4l2_fh *fh); int v4l2_fh_open(struct file *filp); int v4l2_fh_release(struct file *filp); void v4l2_fh_del(struct v4l2_fh *fh); void v4l2_fh_exit(struct v4l2_fh *fh);
5 v4l2_ctrl
参考《1.13. V4L2 Controls — The Linux Kernel documentation》。
struct v4l2_ctrl是对某一个属性可控制属性的抽象,通过v4l2_ctrl对属性进行配置。
可配置的属性定义位于include\uapi\linux\v4l2-controls.h中。
struct v4l2_ctrl { /* Administrative fields */ struct list_head node; struct list_head ev_subs; struct v4l2_ctrl_handler *handler;--拥有此v4l2_ctrl的handler。 struct v4l2_ctrl **cluster; unsigned int ncontrols; ... const struct v4l2_ctrl_ops *ops; const struct v4l2_ctrl_type_ops *type_ops; u32 id; const char *name; enum v4l2_ctrl_type type; s64 minimum, maximum, default_value;--此v4l2_ctrl的最小、最大和默认值。 ... unsigned long flags; void *priv; s32 val; struct { s32 val; } cur; ... };
struct v4l2_ctrl_handler是一系列v4l2_ctrl的集合
struct v4l2_ctrl_handler { struct mutex _lock; struct mutex *lock; struct list_head ctrls;--所属的v4l2_ctrl列表。 struct list_head ctrl_refs; struct v4l2_ctrl_ref *cached; struct v4l2_ctrl_ref **buckets; v4l2_ctrl_notify_fnc notify; void *notify_priv; ... };
struct v4l2_ctrl_ops是v4l2_ctrl对应的操作函数:
struct v4l2_ctrl_ops { int (*g_volatile_ctrl)(struct v4l2_ctrl *ctrl); int (*try_ctrl)(struct v4l2_ctrl *ctrl);--测试control值是否有效。 int (*s_ctrl)(struct v4l2_ctrl *ctrl);--设置control值。 ANDROID_KABI_RESERVE(1); };
v4l2_ctrl相关API有:
#define v4l2_ctrl_handler_init(hdl, nr_of_controls_hint) \ v4l2_ctrl_handler_init_class(hdl, nr_of_controls_hint, NULL, NULL) void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl); int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl); struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, s64 min, s64 max, u64 step, s64 def); struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, u8 max, u64 mask, u8 def); struct v4l2_ctrl *v4l2_ctrl_new_int_menu(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, u8 max, u8 def, const s64 *qmenu_int); int v4l2_ctrl_add_handler(struct v4l2_ctrl_handler *hdl, struct v4l2_ctrl_handler *add, v4l2_ctrl_filter filter, bool from_other_dev);
6 v4l2 event
参考《1.12. V4L2 events — The Linux Kernel documentation》。
7 v4l2 flash
参考《1.18. V4L2 flash functions and data structures — The Linux Kernel documentation》。
8 v4l2 async
参考《1.22. V4L2 async kAPI — The Linux Kernel documentation》。
9 v4l2 fwnode
参考《1.23. V4L2 fwnode kAPI — The Linux Kernel documentation》。