diff --git a/IMX6ULL/doc_pic/13_V4L2/02_V4L2驱动程序框架.md b/IMX6ULL/doc_pic/13_V4L2/02_V4L2驱动程序框架.md index 6534bf5..a5aca63 100644 --- a/IMX6ULL/doc_pic/13_V4L2/02_V4L2驱动程序框架.md +++ b/IMX6ULL/doc_pic/13_V4L2/02_V4L2驱动程序框架.md @@ -508,7 +508,3 @@ APP ioctl VIDIOC_DQBUF - - -## 2. 从0编写一个虚拟的摄像头驱动 - diff --git a/IMX6ULL/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.md b/IMX6ULL/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.md new file mode 100644 index 0000000..7ad9655 --- /dev/null +++ b/IMX6ULL/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.md @@ -0,0 +1,102 @@ +# 从0编写一个虚拟摄像头驱动 # + +参考资料: + +* 深入理解linux内核v4l2框架之videobuf2:https://blog.csdn.net/yyjsword/article/details/9243717 +* V4L2框架-videobuf2:https://blog.csdn.net/u013904227/article/details/81054611 + + + +## 1. 回顾与编写程序框架 + +本节视频对应的代码: + +```shell +doc_and_source_for_drivers\ + IMX6ULL\source\ + 13_V4L2\ + 06_virtual_driver\ + 01_virtual_driver_framework +``` + + + +参考《1.3.2 videobuffer2的3个ops》里面完整的注册流程。 + +完整的注册流程(参考`drivers\media\usb\airspy\airspy.c`): + +```shell +static struct video_device airspy_template = { + .name = "AirSpy SDR", + .release = video_device_release_empty, + .fops = &airspy_fops, + .ioctl_ops = &airspy_ioctl_ops, +}; + +/* 构造一个vb2_queue */ + /* Init videobuf2 queue structure */ + s->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE; + s->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + s->vb_queue.drv_priv = s; + s->vb_queue.buf_struct_size = sizeof(struct airspy_frame_buf); + s->vb_queue.ops = &airspy_vb2_ops; // vb2_ops, 硬件相关的操作函数 + s->vb_queue.mem_ops = &vb2_vmalloc_memops; // vb2_mem_ops, 辅助结构体,用于mem ops(alloc、mmap) + s->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + ret = vb2_queue_init(&s->vb_queue); + q->buf_ops = &v4l2_buf_ops; // vb2_buf_ops, 用于APP和驱动传递参数 + +// 分配/设置video_device结构体 +s->vdev = airspy_template; +s->vdev.queue = &s->vb_queue; // 指向前面构造的vb2_queue + +// 初始化一个v4l2_device结构体(起辅助作用) +/* Register the v4l2_device structure */ +s->v4l2_dev.release = airspy_video_release; +ret = v4l2_device_register(&intf->dev, &s->v4l2_dev); + +// video_device和4l2_device建立联系 +s->vdev.v4l2_dev = &s->v4l2_dev; + +// 注册video_device结构体 +ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1); + __video_register_device + // 根据次设备号把video_device结构体放入数组 + video_device[vdev->minor] = vdev; + + // 注册字符设备驱动程序 + vdev->cdev->ops = &v4l2_fops; + vdev->cdev->owner = owner; + ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); + +``` + + + +## 2. 编写硬件相关代码 + +本节视频对应的代码: + +```shell +doc_and_source_for_drivers\ + IMX6ULL\source\ + 13_V4L2\ + 06_virtual_driver\ + 02_virtual_driver_hardware +``` + +### 2.1 ioctl相关的代码 + + + +### 2.2 buffer相关的代码 + + + +### 2.3 数据传输 + + + +## 3. 上机测试 + + + diff --git a/IMX6ULL/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.tif b/IMX6ULL/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.tif new file mode 100644 index 0000000..98ded24 Binary files /dev/null and b/IMX6ULL/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.tif differ diff --git a/IMX6ULL/source/13_V4L2/06_virtual_driver/01_virtual_driver_framework/virtual_video_drv.c b/IMX6ULL/source/13_V4L2/06_virtual_driver/01_virtual_driver_framework/virtual_video_drv.c new file mode 100644 index 0000000..f3e74f3 --- /dev/null +++ b/IMX6ULL/source/13_V4L2/06_virtual_driver/01_virtual_driver_framework/virtual_video_drv.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct v4l2_file_operations virtual_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops virtual_ioctl_ops = { + .vidioc_querycap = airspy_querycap, + + .vidioc_enum_fmt_sdr_cap = airspy_enum_fmt_sdr_cap, + .vidioc_g_fmt_sdr_cap = airspy_g_fmt_sdr_cap, + .vidioc_s_fmt_sdr_cap = airspy_s_fmt_sdr_cap, + .vidioc_try_fmt_sdr_cap = airspy_try_fmt_sdr_cap, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_g_tuner = airspy_g_tuner, + .vidioc_s_tuner = airspy_s_tuner, + + .vidioc_g_frequency = airspy_g_frequency, + .vidioc_s_frequency = airspy_s_frequency, + .vidioc_enum_freq_bands = airspy_enum_freq_bands, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_log_status = v4l2_ctrl_log_status, +}; + +static struct video_device g_vdev = { + .name = "100ask_virtual_video", + .release = video_device_release_empty, + .fops = &virtual_fops, + .ioctl_ops = &virtual_ioctl_ops, +}; +static struct v4l2_device g_v4l2_dev; + +static struct vb2_queue g_vb_queue; + +static const struct vb2_ops virtul_vb2_ops = { + .queue_setup = airspy_queue_setup, + .buf_queue = airspy_buf_queue, + .start_streaming = airspy_start_streaming, + .stop_streaming = airspy_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + + +static int virtual_video_drv_init(void) +{ + int ret; + + /* 分配/设置/注册video_device */ + + /* 设置 + * 1. 函数调用(比如ioctl) + * 2. 队列/buffer的管理 + */ + + g_vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE; + g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + g_vb_queue.drv_priv = NULL; + g_vb_queue.buf_struct_size = sizeof(struct airspy_frame_buf); + g_vb_queue.ops = &virtul_vb2_ops; + g_vb_queue.mem_ops = &vb2_vmalloc_memops; + g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + ret = vb2_queue_init(&s->vb_queue); + if (ret) { + printk("Could not initialize vb2 queue\n"); + return -1;; + } + + g_vdev.queue = &g_vb_queue; + g_vdev.queue->lock = &s->vb_queue_lock; + + /* Register the v4l2_device structure(辅助作用) */ + g_v4l2_dev.release = airspy_video_release; + ret = v4l2_device_register(NULL, &g_v4l2_dev); + if (ret) { + printk("Failed to register v4l2-device (%d)\n", ret); + return -1; + } + g_vdev.v4l2_dev = &g_v4l2_dev; + g_vdev.lock = &s->v4l2_lock; + + ret = video_register_device(&g_vdev, VFL_TYPE_SDR, -1); + if (ret) { + printk("Failed to register as video device (%d)\n", ret); + return -1; + } + return 0; +} + +static void virtual_video_drv_exit(void) +{ + /* 反注册/释放video_device */ + v4l2_device_unregister(&g_v4l2_dev); + video_unregister_device(&g_vdev); +} + +module_init(virtual_video_drv_init); +module_exit(virtual_video_drv_exit); + +MODULE_LICENSE("GPL"); + + diff --git a/IMX6ULL/source/13_V4L2/06_virtual_driver/02_virtual_driver_hardware/virtual_video_drv.c b/IMX6ULL/source/13_V4L2/06_virtual_driver/02_virtual_driver_hardware/virtual_video_drv.c new file mode 100644 index 0000000..894b6bc --- /dev/null +++ b/IMX6ULL/source/13_V4L2/06_virtual_driver/02_virtual_driver_hardware/virtual_video_drv.c @@ -0,0 +1,227 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct list_head g_queued_bufs; + +/* intermediate buffers with raw data from the USB device */ +struct virtual_frame_buf { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +static int virtual_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strlcpy(cap->driver, "100ask_virtual_video", sizeof(cap->driver)); + strlcpy(cap->card, "no-card", sizeof(cap->card)); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE ; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int virtual_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index > 0) + return -EINVAL; + + strlcpy(f->description, "100ask motion jpeg", sizeof(f->description)); + f->pixelformat = V4L2_PIX_FMT_MJPEG; + + return 0; +} + + +static int virtual_s_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + /* 分辨用户传入的参数是否可用 + * 如果不可用, 给APP提供最接近的、硬件支持的参数 + */ + + if (f.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f.fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG) + return -EINVAL; + + f.fmt.pix.width = 800; + f.fmt.pix.height = 600; + + return 0; +} + +static int virtual_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index > 0) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = 800; + fsize->discrete.height = 600; + return 0; +} + + +static const struct v4l2_file_operations virtual_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops virtual_ioctl_ops = { + .vidioc_querycap = virtual_querycap, + + .vidioc_enum_fmt_vid_cap = virtual_enum_fmt_cap, + .vidioc_s_fmt_vid_cap = virtual_s_fmt_cap, + .vidioc_enum_framesizes = virtual_enum_framesizes, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static struct video_device g_vdev = { + .name = "100ask_virtual_video", + .release = video_device_release_empty, + .fops = &virtual_fops, + .ioctl_ops = &virtual_ioctl_ops, +}; +static struct v4l2_device g_v4l2_dev; + +static struct vb2_queue g_vb_queue; + +/* Videobuf2 operations */ +static int virtual_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[]) +{ + /* 假装:至少需要8个buffer, 每个buffer只有1个plane */ + + /* Need at least 8 buffers */ + if (vq->num_buffers + *nbuffers < 8) + *nbuffers = 8 - vq->num_buffers; + *nplanes = 1; + sizes[0] = PAGE_ALIGN(800*600*2); + + return 0; +} + + +static void virtual_buf_queue(struct vb2_buffer *vb) +{ + + /* 把这个buffer告诉硬件相关的驱动程序 */ + + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct virtual_frame_buf *buf = + container_of(vbuf, struct virtual_frame_buf, vb); + unsigned long flags; + + //spin_lock_irqsave(&s->queued_bufs_lock, flags); + list_add_tail(&buf->list, &g_queued_bufs); + //spin_unlock_irqrestore(&s->queued_bufs_lock, flags); +} + +static int virtual_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + /* 启动硬件传输 */ + return 0; +} + +static void virtual_stop_streaming(struct vb2_queue *vq) +{ + /* 停止硬件传输 */ +} + + +static const struct vb2_ops virtul_vb2_ops = { + .queue_setup = virtual_queue_setup, + .buf_queue = virtual_buf_queue, + .start_streaming = virtual_start_streaming, + .stop_streaming = virtual_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + + +static int virtual_video_drv_init(void) +{ + int ret; + + /* 分配/设置/注册video_device */ + + /* 设置 + * 1. 函数调用(比如ioctl) + * 2. 队列/buffer的管理 + */ + + g_vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE; + g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + g_vb_queue.drv_priv = NULL; + g_vb_queue.buf_struct_size = sizeof(struct virtual_frame_buf); /* 分配vb时, 分配的空间大小为buf_struct_size */ + g_vb_queue.ops = &virtul_vb2_ops; + g_vb_queue.mem_ops = &vb2_vmalloc_memops; + g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + ret = vb2_queue_init(&s->vb_queue); + if (ret) { + printk("Could not initialize vb2 queue\n"); + return -1;; + } + + g_vdev.queue = &g_vb_queue; + g_vdev.queue->lock = &s->vb_queue_lock; + + /* Register the v4l2_device structure(辅助作用) */ + g_v4l2_dev.release = airspy_video_release; + ret = v4l2_device_register(NULL, &g_v4l2_dev); + if (ret) { + printk("Failed to register v4l2-device (%d)\n", ret); + return -1; + } + g_vdev.v4l2_dev = &g_v4l2_dev; + g_vdev.lock = &s->v4l2_lock; + + ret = video_register_device(&g_vdev, VFL_TYPE_SDR, -1); + if (ret) { + printk("Failed to register as video device (%d)\n", ret); + return -1; + } + + INIT_LIST_HEAD(&g_queued_bufs); + + return 0; +} + +static void virtual_video_drv_exit(void) +{ + /* 反注册/释放video_device */ + v4l2_device_unregister(&g_v4l2_dev); + video_unregister_device(&g_vdev); +} + +module_init(virtual_video_drv_init); +module_exit(virtual_video_drv_exit); + +MODULE_LICENSE("GPL"); + + diff --git a/README.md b/README.md index 9b70b41..0b8c360 100644 --- a/README.md +++ b/README.md @@ -809,6 +809,14 @@ git clone https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git [02-5]_videobuffer2情景分析 ``` +* 2023.09.04 发布"摄像头驱动" + + ```shell + [03-1]_回顾与编写驱动框架 + [03-2]_虚拟摄像头驱动编程_ioctl代码 + [03-3]_虚拟摄像头驱动编程_buffer代码 + ``` + diff --git a/STM32MP157/doc_pic/13_V4L2/02_V4L2驱动程序框架.md b/STM32MP157/doc_pic/13_V4L2/02_V4L2驱动程序框架.md index 6534bf5..a5aca63 100644 --- a/STM32MP157/doc_pic/13_V4L2/02_V4L2驱动程序框架.md +++ b/STM32MP157/doc_pic/13_V4L2/02_V4L2驱动程序框架.md @@ -508,7 +508,3 @@ APP ioctl VIDIOC_DQBUF - - -## 2. 从0编写一个虚拟的摄像头驱动 - diff --git a/STM32MP157/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.md b/STM32MP157/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.md new file mode 100644 index 0000000..7ad9655 --- /dev/null +++ b/STM32MP157/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.md @@ -0,0 +1,102 @@ +# 从0编写一个虚拟摄像头驱动 # + +参考资料: + +* 深入理解linux内核v4l2框架之videobuf2:https://blog.csdn.net/yyjsword/article/details/9243717 +* V4L2框架-videobuf2:https://blog.csdn.net/u013904227/article/details/81054611 + + + +## 1. 回顾与编写程序框架 + +本节视频对应的代码: + +```shell +doc_and_source_for_drivers\ + IMX6ULL\source\ + 13_V4L2\ + 06_virtual_driver\ + 01_virtual_driver_framework +``` + + + +参考《1.3.2 videobuffer2的3个ops》里面完整的注册流程。 + +完整的注册流程(参考`drivers\media\usb\airspy\airspy.c`): + +```shell +static struct video_device airspy_template = { + .name = "AirSpy SDR", + .release = video_device_release_empty, + .fops = &airspy_fops, + .ioctl_ops = &airspy_ioctl_ops, +}; + +/* 构造一个vb2_queue */ + /* Init videobuf2 queue structure */ + s->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE; + s->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + s->vb_queue.drv_priv = s; + s->vb_queue.buf_struct_size = sizeof(struct airspy_frame_buf); + s->vb_queue.ops = &airspy_vb2_ops; // vb2_ops, 硬件相关的操作函数 + s->vb_queue.mem_ops = &vb2_vmalloc_memops; // vb2_mem_ops, 辅助结构体,用于mem ops(alloc、mmap) + s->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + ret = vb2_queue_init(&s->vb_queue); + q->buf_ops = &v4l2_buf_ops; // vb2_buf_ops, 用于APP和驱动传递参数 + +// 分配/设置video_device结构体 +s->vdev = airspy_template; +s->vdev.queue = &s->vb_queue; // 指向前面构造的vb2_queue + +// 初始化一个v4l2_device结构体(起辅助作用) +/* Register the v4l2_device structure */ +s->v4l2_dev.release = airspy_video_release; +ret = v4l2_device_register(&intf->dev, &s->v4l2_dev); + +// video_device和4l2_device建立联系 +s->vdev.v4l2_dev = &s->v4l2_dev; + +// 注册video_device结构体 +ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1); + __video_register_device + // 根据次设备号把video_device结构体放入数组 + video_device[vdev->minor] = vdev; + + // 注册字符设备驱动程序 + vdev->cdev->ops = &v4l2_fops; + vdev->cdev->owner = owner; + ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); + +``` + + + +## 2. 编写硬件相关代码 + +本节视频对应的代码: + +```shell +doc_and_source_for_drivers\ + IMX6ULL\source\ + 13_V4L2\ + 06_virtual_driver\ + 02_virtual_driver_hardware +``` + +### 2.1 ioctl相关的代码 + + + +### 2.2 buffer相关的代码 + + + +### 2.3 数据传输 + + + +## 3. 上机测试 + + + diff --git a/STM32MP157/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.tif b/STM32MP157/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.tif new file mode 100644 index 0000000..98ded24 Binary files /dev/null and b/STM32MP157/doc_pic/13_V4L2/03_从0编写一个虚拟摄像头驱动.tif differ diff --git a/STM32MP157/source/A7/13_V4L2/06_virtual_driver/01_virtual_driver_framework/virtual_video_drv.c b/STM32MP157/source/A7/13_V4L2/06_virtual_driver/01_virtual_driver_framework/virtual_video_drv.c new file mode 100644 index 0000000..f3e74f3 --- /dev/null +++ b/STM32MP157/source/A7/13_V4L2/06_virtual_driver/01_virtual_driver_framework/virtual_video_drv.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct v4l2_file_operations virtual_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops virtual_ioctl_ops = { + .vidioc_querycap = airspy_querycap, + + .vidioc_enum_fmt_sdr_cap = airspy_enum_fmt_sdr_cap, + .vidioc_g_fmt_sdr_cap = airspy_g_fmt_sdr_cap, + .vidioc_s_fmt_sdr_cap = airspy_s_fmt_sdr_cap, + .vidioc_try_fmt_sdr_cap = airspy_try_fmt_sdr_cap, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_g_tuner = airspy_g_tuner, + .vidioc_s_tuner = airspy_s_tuner, + + .vidioc_g_frequency = airspy_g_frequency, + .vidioc_s_frequency = airspy_s_frequency, + .vidioc_enum_freq_bands = airspy_enum_freq_bands, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_log_status = v4l2_ctrl_log_status, +}; + +static struct video_device g_vdev = { + .name = "100ask_virtual_video", + .release = video_device_release_empty, + .fops = &virtual_fops, + .ioctl_ops = &virtual_ioctl_ops, +}; +static struct v4l2_device g_v4l2_dev; + +static struct vb2_queue g_vb_queue; + +static const struct vb2_ops virtul_vb2_ops = { + .queue_setup = airspy_queue_setup, + .buf_queue = airspy_buf_queue, + .start_streaming = airspy_start_streaming, + .stop_streaming = airspy_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + + +static int virtual_video_drv_init(void) +{ + int ret; + + /* 分配/设置/注册video_device */ + + /* 设置 + * 1. 函数调用(比如ioctl) + * 2. 队列/buffer的管理 + */ + + g_vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE; + g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + g_vb_queue.drv_priv = NULL; + g_vb_queue.buf_struct_size = sizeof(struct airspy_frame_buf); + g_vb_queue.ops = &virtul_vb2_ops; + g_vb_queue.mem_ops = &vb2_vmalloc_memops; + g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + ret = vb2_queue_init(&s->vb_queue); + if (ret) { + printk("Could not initialize vb2 queue\n"); + return -1;; + } + + g_vdev.queue = &g_vb_queue; + g_vdev.queue->lock = &s->vb_queue_lock; + + /* Register the v4l2_device structure(辅助作用) */ + g_v4l2_dev.release = airspy_video_release; + ret = v4l2_device_register(NULL, &g_v4l2_dev); + if (ret) { + printk("Failed to register v4l2-device (%d)\n", ret); + return -1; + } + g_vdev.v4l2_dev = &g_v4l2_dev; + g_vdev.lock = &s->v4l2_lock; + + ret = video_register_device(&g_vdev, VFL_TYPE_SDR, -1); + if (ret) { + printk("Failed to register as video device (%d)\n", ret); + return -1; + } + return 0; +} + +static void virtual_video_drv_exit(void) +{ + /* 反注册/释放video_device */ + v4l2_device_unregister(&g_v4l2_dev); + video_unregister_device(&g_vdev); +} + +module_init(virtual_video_drv_init); +module_exit(virtual_video_drv_exit); + +MODULE_LICENSE("GPL"); + + diff --git a/STM32MP157/source/A7/13_V4L2/06_virtual_driver/02_virtual_driver_hardware/virtual_video_drv.c b/STM32MP157/source/A7/13_V4L2/06_virtual_driver/02_virtual_driver_hardware/virtual_video_drv.c new file mode 100644 index 0000000..894b6bc --- /dev/null +++ b/STM32MP157/source/A7/13_V4L2/06_virtual_driver/02_virtual_driver_hardware/virtual_video_drv.c @@ -0,0 +1,227 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct list_head g_queued_bufs; + +/* intermediate buffers with raw data from the USB device */ +struct virtual_frame_buf { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +static int virtual_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strlcpy(cap->driver, "100ask_virtual_video", sizeof(cap->driver)); + strlcpy(cap->card, "no-card", sizeof(cap->card)); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE ; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int virtual_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index > 0) + return -EINVAL; + + strlcpy(f->description, "100ask motion jpeg", sizeof(f->description)); + f->pixelformat = V4L2_PIX_FMT_MJPEG; + + return 0; +} + + +static int virtual_s_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + /* 分辨用户传入的参数是否可用 + * 如果不可用, 给APP提供最接近的、硬件支持的参数 + */ + + if (f.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f.fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG) + return -EINVAL; + + f.fmt.pix.width = 800; + f.fmt.pix.height = 600; + + return 0; +} + +static int virtual_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index > 0) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = 800; + fsize->discrete.height = 600; + return 0; +} + + +static const struct v4l2_file_operations virtual_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops virtual_ioctl_ops = { + .vidioc_querycap = virtual_querycap, + + .vidioc_enum_fmt_vid_cap = virtual_enum_fmt_cap, + .vidioc_s_fmt_vid_cap = virtual_s_fmt_cap, + .vidioc_enum_framesizes = virtual_enum_framesizes, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static struct video_device g_vdev = { + .name = "100ask_virtual_video", + .release = video_device_release_empty, + .fops = &virtual_fops, + .ioctl_ops = &virtual_ioctl_ops, +}; +static struct v4l2_device g_v4l2_dev; + +static struct vb2_queue g_vb_queue; + +/* Videobuf2 operations */ +static int virtual_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[]) +{ + /* 假装:至少需要8个buffer, 每个buffer只有1个plane */ + + /* Need at least 8 buffers */ + if (vq->num_buffers + *nbuffers < 8) + *nbuffers = 8 - vq->num_buffers; + *nplanes = 1; + sizes[0] = PAGE_ALIGN(800*600*2); + + return 0; +} + + +static void virtual_buf_queue(struct vb2_buffer *vb) +{ + + /* 把这个buffer告诉硬件相关的驱动程序 */ + + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct virtual_frame_buf *buf = + container_of(vbuf, struct virtual_frame_buf, vb); + unsigned long flags; + + //spin_lock_irqsave(&s->queued_bufs_lock, flags); + list_add_tail(&buf->list, &g_queued_bufs); + //spin_unlock_irqrestore(&s->queued_bufs_lock, flags); +} + +static int virtual_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + /* 启动硬件传输 */ + return 0; +} + +static void virtual_stop_streaming(struct vb2_queue *vq) +{ + /* 停止硬件传输 */ +} + + +static const struct vb2_ops virtul_vb2_ops = { + .queue_setup = virtual_queue_setup, + .buf_queue = virtual_buf_queue, + .start_streaming = virtual_start_streaming, + .stop_streaming = virtual_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + + +static int virtual_video_drv_init(void) +{ + int ret; + + /* 分配/设置/注册video_device */ + + /* 设置 + * 1. 函数调用(比如ioctl) + * 2. 队列/buffer的管理 + */ + + g_vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE; + g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + g_vb_queue.drv_priv = NULL; + g_vb_queue.buf_struct_size = sizeof(struct virtual_frame_buf); /* 分配vb时, 分配的空间大小为buf_struct_size */ + g_vb_queue.ops = &virtul_vb2_ops; + g_vb_queue.mem_ops = &vb2_vmalloc_memops; + g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + ret = vb2_queue_init(&s->vb_queue); + if (ret) { + printk("Could not initialize vb2 queue\n"); + return -1;; + } + + g_vdev.queue = &g_vb_queue; + g_vdev.queue->lock = &s->vb_queue_lock; + + /* Register the v4l2_device structure(辅助作用) */ + g_v4l2_dev.release = airspy_video_release; + ret = v4l2_device_register(NULL, &g_v4l2_dev); + if (ret) { + printk("Failed to register v4l2-device (%d)\n", ret); + return -1; + } + g_vdev.v4l2_dev = &g_v4l2_dev; + g_vdev.lock = &s->v4l2_lock; + + ret = video_register_device(&g_vdev, VFL_TYPE_SDR, -1); + if (ret) { + printk("Failed to register as video device (%d)\n", ret); + return -1; + } + + INIT_LIST_HEAD(&g_queued_bufs); + + return 0; +} + +static void virtual_video_drv_exit(void) +{ + /* 反注册/释放video_device */ + v4l2_device_unregister(&g_v4l2_dev); + video_unregister_device(&g_vdev); +} + +module_init(virtual_video_drv_init); +module_exit(virtual_video_drv_exit); + +MODULE_LICENSE("GPL"); + +