本文转自https://blog.csdn.net/m0_46525308/article/details/115446332
平台V4L2设备驱动的工作:根据平台自身的特性实现与平台相关的V4L2驱动部分,最主要的是包括设置并注册video_device和v4l2_device。
来看看v4l2_device结构体,定义如下:
struct v4l2_device {
struct device *dev;
......
//subdevs链表,属于该v4l2_device的子设备会挂入到该链表
struct list_head subdevs;
spinlock_t lock;
//v4l2_device nam
char name[V4L2_DEVICE_NAME_SIZE];
void (*notify)(struct v4l2_subdev *sd,
unsigned int notification, void *arg);
struct v4l2_ctrl_handler *ctrl_handler;
......
struct kref ref;
//当ref为0,调用该函数释放v4l2_device
void (*release)(struct v4l2_device *v4l2_dev);
};
设置好v4l2_device后,调用v4l2_device_register函数进行注册:
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
......
//初始化链表、锁等
INIT_LIST_HEAD(&v4l2_dev->subdevs);
spin_lock_init(&v4l2_dev->lock);
mutex_init(&v4l2_dev->ioctl_lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
get_device(dev);
v4l2_dev->dev = dev;
......
/* 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;
}
video_device为用户空间提供设备节点,提供系统调用的相关操作(open、ioctl…),video_device结构体定义如下:
struct video_device
{
......
//应用层open、ioctl等,会调用到fops里的操作函数
const struct v4l2_file_operations *fops;
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;
//v4l2的缓存管理
struct vb2_queue *queue;
//video device name
char name[32];
//v4l2设备类型,对于摄像头,该字段为VFL_TYPE_GRABBER
int vfl_type;
//次设备号
int minor;
/* 设备编号,同一设备类型的设备编号不能有重复,不同类型的话,设备编号可以相同
* 比如可以有video0、radio0(不同类型,同编号)
*/
u16 num;
unsigned long flags;
int index;
/* V4L2 file handles */
spinlock_t fh_lock;
struct list_head fh_list;
......
const struct v4l2_ioctl_ops *ioctl_ops;
......
};
驱动程序需要设置video_device的ioctl_ops、queue、v4l2_dev、fops等字段,之后调用video_register_device函数进行注册:
/* 参数说明:
* vdev,要注册的video_device
* type,v4l2设备的类型,有VFL_TYPE_GRABBER、VFL_TYPE_VBI、VFL_TYPE_RADIO等
* nr,为-1表示让系统找一个不冲突的设备编号
*/
static inline int __must_check video_register_device(struct video_device *vdev,
int type, int nr)
{
return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}
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;
vdev->minor = -1;
......
/* v4l2_fh support */
spin_lock_init(&vdev->fh_lock);
INIT_LIST_HEAD(&vdev->fh_list);
/* Part 1: check device type */
switch (type) {
//摄像头为VFL_TYPE_GRABBER类型的v4l2设备
case VFL_TYPE_GRABBER:
name_base = "video";
break;
......
}
......
/* Part 2: find a free minor, device node number and device index. */
......
mutex_lock(&videodev_lock);
//找到一个未使用的设备编号
nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
......
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
......
#else
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
//在全局数据video_device里,找到一个空位置,对应位置的i即为次设备号
if (video_device[i] == NULL)
break;
if (i == VIDEO_NUM_DEVICES) {
mutex_unlock(&videodev_lock);
printk(KERN_ERR "could not get a free minor\n");
return -ENFILE;
}
#endif
vdev->minor = i + minor_offset;
vdev->num = nr;
devnode_set(vdev);
......
//以次设备为下标,把注册的struct video_device保存在全局数组video_device里
video_device[vdev->minor] = vdev;
mutex_unlock(&videodev_lock);
if (vdev->ioctl_ops)
determine_valid_ioctls(vdev);
/* Part 3: 初始化字符设备 */
//申请字符设备
vdev->cdev = cdev_alloc();
......
//设置字符设备的file_operations,为v4l2_fops
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;
vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor); //主设备为81
vdev->dev.parent = vdev->dev_parent;
//设置设备的名称,如果为VFL_TYPE_GRABBER类型,设备编号为0,则名称为video0
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
ret = device_register(&vdev->dev); //注册device,生成设备文件
if (ret < 0) {
printk(KERN_ERR "%s: device_register failed\n", __func__);
goto cleanup;
}
vdev->dev.release = v4l2_device_release;
......
}
/* drivers/media/v4l2-core/v4l2-dev.c */
#define VIDEO_NUM_DEVICES 256
static struct video_device *video_device[VIDEO_NUM_DEVICES];
注册video_device之后,会在/dev目录下产生设备文件,open、ioctl等操作会调用到v4l2_fops里对应的操作函数:
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,
};
如"open /dev/video0",调用到v4l2_open:
static int v4l2_open(struct inode *inode, struct file *filp)
{
struct video_device *vdev;
int ret = 0;
/* Check if the video device is available */
mutex_lock(&videodev_lock);
//以次设备号在video_device数组中找到对应的struct video_device
vdev = video_devdata(filp);
......
/* and increase the device refcount */
video_get(vdev);
mutex_unlock(&videodev_lock);
if (vdev->fops->open) {
if (video_is_registered(vdev))
//调用struct video_device的fops成员里的open回调
ret = vdev->fops->open(filp);
else
ret = -ENODEV;
}
......
}
同样的,进行ioctl、poll等操作、最终会转接到调用struct video_device的fops成员里的回调函数。struct video_device的fops成员,为struct v4l2_file_operations类型,定义如下:
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 *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*ioctl) (struct file *, unsigned int, unsigned long);
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 *);
};
struct video_device的fops成员是驱动程序设置的**,但fops里的ioctl和poll成员可以不用驱动程序实现,内核提供的有实现好的函数,驱动程序可以这样设置struct video_device的fops成员:**
static struct v4l2_file_operations xxx_fops = {
.owner = THIS_MODULE,
......
.poll = vb2_fop_poll,
.unlocked_ioctl = video_ioctl2,
......
};
应用层操控v4l2设备基本上是通过ioctl,如查询设备功能、设置输入设备、设置图像格式、申请缓存等。
当应用层"ioctl /dev/video0",其调用流程图:
struct video_device的ioctl_ops成员,类型为struct v4l2_ioctl_ops,定义如下:
struct v4l2_ioctl_ops {
/* ioctl callbacks */
/* VIDIOC_QUERYCAP handler */
int (*vidioc_querycap)(struct file *file, void *fh,
struct v4l2_capability *cap);
/* VIDIOC_ENUM_FMT handlers */
int (*vidioc_enum_fmt_vid_cap)(struct file *file, void *fh,
struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_overlay)(struct file *file, void *fh,
struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_out)(struct file *file, void *fh,
struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_cap_mplane)(struct file *file, void *fh,
struct v4l2_fmtdesc *f);
int (*vidioc_enum_fmt_vid_out_mplane)(struct file *file, void *fh,
struct v4l2_fmtdesc *f);
......
/* VIDIOC_G_FMT handlers */
int (*vidioc_g_fmt_vid_cap)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_g_fmt_vid_overlay)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_g_fmt_vid_out)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_g_fmt_vid_out_overlay)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_g_fmt_vbi_cap)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_g_fmt_vbi_out)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_g_fmt_sliced_vbi_cap)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_g_fmt_sliced_vbi_out)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_g_fmt_vid_cap_mplane)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_g_fmt_vid_out_mplane)(struct file *file, void *fh,
struct v4l2_format *f);
......
/* VIDIOC_S_FMT handlers */
int (*vidioc_s_fmt_vid_cap)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_s_fmt_vid_overlay)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_s_fmt_vid_out)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_s_fmt_vid_out_overlay)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_s_fmt_vbi_cap)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_s_fmt_vbi_out)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_s_fmt_sliced_vbi_cap)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_s_fmt_sliced_vbi_out)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_s_fmt_vid_cap_mplane)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_s_fmt_vid_out_mplane)(struct file *file, void *fh,
struct v4l2_format *f);
......
/* VIDIOC_TRY_FMT handlers */
int (*vidioc_try_fmt_vid_cap)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_try_fmt_vid_overlay)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_try_fmt_vid_out)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_try_fmt_vid_out_overlay)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_try_fmt_vbi_cap)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_try_fmt_vbi_out)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_try_fmt_sliced_vbi_cap)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_try_fmt_sliced_vbi_out)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_try_fmt_vid_cap_mplane)(struct file *file, void *fh,
struct v4l2_format *f);
int (*vidioc_try_fmt_vid_out_mplane)(struct file *file, void *fh,
struct v4l2_format *f);
......
/* Buffer handlers */
int (*vidioc_reqbufs)(struct file *file, void *fh,
struct v4l2_requestbuffers *b);
int (*vidioc_querybuf)(struct file *file, void *fh,
struct v4l2_buffer *b);
int (*vidioc_qbuf)(struct file *file, void *fh,
struct v4l2_buffer *b);
int (*vidioc_expbuf)(struct file *file, void *fh,
struct v4l2_exportbuffer *e);
int (*vidioc_dqbuf)(struct file *file, void *fh,
struct v4l2_buffer *b);
int (*vidioc_create_bufs)(struct file *file, void *fh,
struct v4l2_create_buffers *b);
int (*vidioc_prepare_buf)(struct file *file, void *fh,
struct v4l2_buffer *b);
int (*vidioc_overlay)(struct file *file, void *fh, unsigned int i);
int (*vidioc_g_fbuf)(struct file *file, void *fh,
struct v4l2_framebuffer *a);
int (*vidioc_s_fbuf)(struct file *file, void *fh,
const struct v4l2_framebuffer *a);
/* Stream on/off */
int (*vidioc_streamon)(struct file *file, void *fh,
enum v4l2_buf_type i);
int (*vidioc_streamoff)(struct file *file, void *fh,
enum v4l2_buf_type i);
/*
* Standard handling
*
* Note: ENUMSTD is handled by videodev.c
*/
int (*vidioc_g_std)(struct file *file, void *fh, v4l2_std_id *norm);
int (*vidioc_s_std)(struct file *file, void *fh, v4l2_std_id norm);
int (*vidioc_querystd)(struct file *file, void *fh, v4l2_std_id *a);
/* Input handling */
int (*vidioc_enum_input)(struct file *file, void *fh,
struct v4l2_input *inp);
int (*vidioc_g_input)(struct file *file, void *fh, unsigned int *i);
int (*vidioc_s_input)(struct file *file, void *fh, unsigned int i);
/* Output handling */
int (*vidioc_enum_output)(struct file *file, void *fh,
struct v4l2_output *a);
int (*vidioc_g_output)(struct file *file, void *fh, unsigned int *i);
int (*vidioc_s_output)(struct file *file, void *fh, unsigned int i);
......
/* For other private ioctls */
long (*vidioc_default)(struct file *file, void *fh,
bool valid_prio, unsigned int cmd, void *arg);
};
当然struct video_device的ioctl_ops成员里的各种回调函数不需要全部实现,来看一个具体的例子:
static const struct v4l2_ioctl_ops vir_v4l2_ioctl_ops = {
//查询设备功能
.vidioc_querycap = vir_vidioc_querycap,
//枚举设备支持的图像格式
.vidioc_enum_fmt_vid_cap = vir_enum_fmt_vid,
//测试设备是否支持此格式
.vidioc_try_fmt_vid_cap = vir_vidioc_try_fmt_vid_cap,
//获取当前设备的图像格式
.vidioc_g_fmt_vid_cap = vir_vidioc_g_fmt_vid_cap,
//设置图像格式
.vidioc_s_fmt_vid_cap = vir_vidioc_s_fmt_vid_cap,
//申请缓存
.vidioc_reqbufs = vir_vidioc_reqbufs,
//获取缓存信息
.vidioc_querybuf = vir_vidioc_querybuf,
//将缓存放入队列中
.vidioc_qbuf = vir_vidioc_qbuf,
//将缓存从队列中取出
.vidioc_dqbuf = vir_vidioc_dqbuf,
//枚举视频输入设备
.vidioc_enum_input = vir_vidioc_enum_input,
//获取当前的视频输入设备
.vidioc_g_input = vir_vidioc_g_input,
//设置视频输入设备
.vidioc_s_input = vir_vidioc_s_input,
//打开设备
.vidioc_streamon = vir_vidioc_streamon,
//关闭设备
.vidioc_streamoff = vir_vidioc_streamoff,
}