diff --git a/IMX6ULL/doc_pic/13_V4L2/02_V4L2驱动程序框架.md b/IMX6ULL/doc_pic/13_V4L2/02_V4L2驱动程序框架.md
index 23680d2..6534bf5 100644
--- a/IMX6ULL/doc_pic/13_V4L2/02_V4L2驱动程序框架.md
+++ b/IMX6ULL/doc_pic/13_V4L2/02_V4L2驱动程序框架.md
@@ -142,7 +142,7 @@ APP操作buffer的示意图如下:
-#### 1.3.2 三类操作结构体
+#### 1.3.2 videobuffer2的3个ops
V4L2中使用vb2_queue来管理缓冲区,里面有3个操作结构体:
@@ -150,56 +150,101 @@ V4L2中使用vb2_queue来管理缓冲区,里面有3个操作结构体:
这3个操作结构体的作用为:
-* `const struct vb2_ops *ops`:驱动相关的回调函数,通过下面几个宏来调用
+* `const struct vb2_buf_ops *buf_ops`:在用户空间、内核空间之间传递buffer信息,通过下面几个宏来调用
+ 
+* `const struct vb2_mem_ops *mem_ops`:分配内存用的回调函数,通过下面几个宏来调用
+ 
+
+* `const struct vb2_ops *ops`:硬件相关的回调函数,通过下面几个宏来调用

-* `const struct vb2_mem_ops *mem_ops`:分配内存用的回调函数
-* `const struct vb2_buf_ops *buf_ops`:在用户空间、内核空间之间传递buffer信息
-#### 1.3.3 vb2_ops
+这3个ops的层次图如下:
-`struct vb2_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);
+
+```
+
+
+
+
+
+#### 1.3.3 vb2_buf_ops
+
+`struct vb2_buf_ops`示例如下:
+
+
原型如下:
```c
-struct vb2_ops {
- int (*queue_setup)(struct vb2_queue *q,
- unsigned int *num_buffers, unsigned int *num_planes,
- unsigned int sizes[], struct device *alloc_devs[]);
-
- void (*wait_prepare)(struct vb2_queue *q);
- void (*wait_finish)(struct vb2_queue *q);
-
- int (*buf_init)(struct vb2_buffer *vb);
- int (*buf_prepare)(struct vb2_buffer *vb);
- void (*buf_finish)(struct vb2_buffer *vb);
- void (*buf_cleanup)(struct vb2_buffer *vb);
-
- int (*start_streaming)(struct vb2_queue *q, unsigned int count);
- void (*stop_streaming)(struct vb2_queue *q);
-
- void (*buf_queue)(struct vb2_buffer *vb);
+struct vb2_buf_ops {
+ int (*verify_planes_array)(struct vb2_buffer *vb, const void *pb);
+ void (*fill_user_buffer)(struct vb2_buffer *vb, void *pb);
+ int (*fill_vb2_buffer)(struct vb2_buffer *vb, const void *pb,
+ struct vb2_plane *planes);
+ void (*copy_timestamp)(struct vb2_buffer *vb, const void *pb);
};
```
各成员的作用为:
-| vb2_ops结构体成员 | 作用 |
-| ----------------- | ------------------------------------------------------------ |
-| queue_setup | APP调用ioctl VIDIOC_REQBUFS或VIDIOC_CREATE_BUFS时,
驱动程序在分配内存之前,会调用此函数。
作用:通过它来询问驱动程序"你需要多少个buffer?每个buffer需要多少个plane?"
这个函数被调用2次:第1次用来表明驱动程序对buffer的需求,但是不一定能全部分配这些buffer,当分配出buffer后,再调用第2次以验证"这些buffer是否足够"。 |
-| wait_prepare | 释放驱动自己的互斥锁 |
-| wait_finish | 申请驱动自己的互斥锁 |
-| buf_init | 分配vb2_buffer及它内部存储数据的buffer后,使用buf_init进行驱动相关的初始化 |
-| buf_prepare | APP调用ioctl VIDIOC_QBUF或VIDIOC_PREPARE_BUF时,驱动程序会在执行硬件操作前,调用此函数进行必要的初始化。 |
-| buf_finish | APP调用ioctl VIDIOC_DQBUF后,在驱动程序返回用户空间之前,会调用此函数,可以在这个函数里修改buffer。或者驱动程序内部停止或暂停streaming时,也会调用此函数。 |
-| buf_cleanup | 在buffer被释放前调用,驱动程序在这个函数里执行额外的清除工作。 |
-| start_streaming | 驱动相关的"启动streaming"函数 |
-| stop_streaming | 驱动相关的"停止streaming"函数 |
-| buf_queue | 把buffer传送给驱动,驱动获得数据、填充好buffer后会调用vb2_buffer_done函数返还buffer。 |
+| vb2_buf_ops结构体成员 | 作用 |
+| --------------------- | ------------------------------------------------------------ |
+| verify_planes_array | APP调用ioctl VIDIOC_DQBUF时,在驱动内部会调用此函数,用来验证这个buffer含有足够多的plane。 |
+| fill_user_buffer | 使用驱动的vb2_buffer结构体来填充一个v4l2_buffer结构体,用来给用户空间提供更多信息。APP调用ioctl VIDIOC_QUERYBUF、VIDIOC_PREPARE_BUF、VIDIOC_QBUF、VIDIOC_DQBUF时,都会传入一个v4l2_buffer结构体。 |
+| fill_vb2_buffer | APP调用ioctl VIDIOC_QBUF时,传入一个v4l2_buffer结构体,驱动里会用它来填充vb2_buffer结构体。 |
+| copy_timestamp | APP调用ioctl VIDIOC_QBUF时,传入一个v4l2_buffer结构体,用户程序可以在它的timestamp里记下时间。驱动程序可以调用此函数把timestamp写入vb2_buffer.timestamp里。 |
+
+
@@ -251,7 +296,7 @@ struct vb2_mem_ops {
| --------------------- | ------------------------------------------------------------ |
| alloc | 分配真正用于存储视频数据的buffer,可能还分配私有数据 |
| put | 通知分配器:这块buffer不再使用了。通常会释放内存。 |
-| get_dmabuf | 申请驱动自己的互斥锁 |
+| get_dmabuf | 获得DMA BUF给底层驱动使用 |
| get_userptr | 如果存储视频数据的buffer是userptr(由APP提供),那么APP调用ioctl VIDIOC_QBUF时,需要传入APP的buffer指针。驱动程序内部通过此函数把用户空间的buffer映射到内核空间。 |
| put_userptr | 通知分配器:这块USERPTR缓冲区不再使用 |
| attach_dmabuf | 如果存储视频数据的buffer是DMA Buf,那么在把这个buffer放入队列前会调用此函数:记录这个DMA Buf。 |
@@ -267,82 +312,198 @@ struct vb2_mem_ops {
-#### 1.3.5 vb2_buf_ops
-`struct vb2_buf_ops`示例如下:
-
+#### 1.3.5 vb2_ops
+
+`struct vb2_ops`示例如下:
+
+
原型如下:
```c
-struct vb2_buf_ops {
- int (*verify_planes_array)(struct vb2_buffer *vb, const void *pb);
- void (*fill_user_buffer)(struct vb2_buffer *vb, void *pb);
- int (*fill_vb2_buffer)(struct vb2_buffer *vb, const void *pb,
- struct vb2_plane *planes);
- void (*copy_timestamp)(struct vb2_buffer *vb, const void *pb);
+struct vb2_ops {
+ int (*queue_setup)(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[]);
+
+ void (*wait_prepare)(struct vb2_queue *q);
+ void (*wait_finish)(struct vb2_queue *q);
+
+ int (*buf_init)(struct vb2_buffer *vb);
+ int (*buf_prepare)(struct vb2_buffer *vb);
+ void (*buf_finish)(struct vb2_buffer *vb);
+ void (*buf_cleanup)(struct vb2_buffer *vb);
+
+ int (*start_streaming)(struct vb2_queue *q, unsigned int count);
+ void (*stop_streaming)(struct vb2_queue *q);
+
+ void (*buf_queue)(struct vb2_buffer *vb);
};
```
各成员的作用为:
-| vb2_buf_ops结构体成员 | 作用 |
-| --------------------- | ------------------------------------------------------------ |
-| verify_planes_array | APP调用ioctl VIDIOC_DQBUF时,在驱动内部会调用此函数,用来验证这个buffer含有足够多的plane。 |
-| fill_user_buffer | 使用驱动的vb2_buffer结构体来填充一个v4l2_buffer结构体,用来给用户空间提供更多信息。APP调用ioctl VIDIOC_QUERYBUF、VIDIOC_PREPARE_BUF、VIDIOC_QBUF、VIDIOC_DQBUF时,都会传入一个v4l2_buffer结构体。 |
-| fill_vb2_buffer | APP调用ioctl VIDIOC_QBUF时,传入一个v4l2_buffer结构体,驱动里会用它来填充vb2_buffer结构体。 |
-| copy_timestamp | APP调用ioctl VIDIOC_QBUF时,传入一个v4l2_buffer结构体,用户程序可以在它的timestamp里记下时间。驱动程序可以调用此函数把timestamp写入vb2_buffer.timestamp里。 |
+| vb2_ops结构体成员 | 作用 |
+| ----------------- | ------------------------------------------------------------ |
+| queue_setup | APP调用ioctl VIDIOC_REQBUFS或VIDIOC_CREATE_BUFS时,
驱动程序在分配内存之前,会调用此函数。
作用:通过它来询问硬件驱动"你需要多少个buffer?每个buffer需要多少个plane?"
这个函数被调用2次:第1次用来表明驱动程序对buffer的需求,但是不一定能全部分配这些buffer,当分配出buffer后,再调用第2次以验证"这些buffer是否足够"。 |
+| wait_prepare | 释放驱动自己的互斥锁 |
+| wait_finish | 申请驱动自己的互斥锁 |
+| buf_init | 分配vb2_buffer及它内部存储数据的buffer后,使用buf_init进行驱动相关的初始化 |
+| buf_prepare | APP调用ioctl VIDIOC_QBUF或VIDIOC_PREPARE_BUF时,驱动程序会在执行硬件操作前,调用此函数进行必要的初始化。 |
+| buf_finish | APP调用ioctl VIDIOC_DQBUF后,在驱动程序返回用户空间之前,会调用此函数,可以在这个函数里修改buffer。或者驱动程序内部停止或暂停streaming时,也会调用此函数。 |
+| buf_cleanup | 在buffer被释放前调用,驱动程序在这个函数里执行额外的清除工作。 |
+| start_streaming | 驱动相关的"启动streaming"函数 |
+| stop_streaming | 驱动相关的"停止streaming"函数 |
+| buf_queue | 把buffer传送给驱动,驱动获得数据、填充好buffer后会调用vb2_buffer_done函数返还buffer。 |
+#### 1.3.6 videobuffer2情景分析
+情景分析1:申请buffer
```c
-APP ioctl VIDIOC_PREPARE_BUF
+APP ioctl VIDIOC_REQBUFS
------------------------------
- v4l2_ioctl_ops.vidioc_prepare_buf
- vb2_ioctl_prepare_buf
- vb2_prepare_buf(vdev->queue, p);
- vb2_core_prepare_buf(q, b->index, b);
- ret = __buf_prepare(vb, pb);
- ret = __qbuf_mmap(vb, pb);
+ v4l_reqbufs // v4l2-ioctl.c
+ ops->vidioc_reqbufs(file, fh, p);
+ vb2_ioctl_reqbufs // videobuf2-v4l2.c
+ vb2_core_reqbufs
+ /*
+ * Ask the driver how many buffers and planes per buffer it requires.
+ * Driver also sets the size and allocator context for each plane.
+ */
+ /* 调用硬件相关的vb2_ops.queue_setup,确认需要多少个buffer、每个buffer里有多少个plane */
+ ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes,
+ plane_sizes, q->alloc_devs);
+
+
+ /* Finally, allocate buffers and video memory */
+ allocated_buffers =
+ __vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);
+ ret = __vb2_buf_mem_alloc(vb);
+ // 调用vb2_mem_ops.alloc分配内存
+ mem_priv = call_ptr_memop(vb, alloc,
+ q->alloc_devs[plane] ? : q->dev,
+ q->dma_attrs, size, dma_dir, q->gfp_flags);
+
+ /* 驱动想得到M个buffer,但是可能只分配了N个buffer,可以吗?问驱动
+ * 再次调用硬件相关的vb2_ops.queue_setup,确认已经分配的buffer个数、每个buffer的plane个数是否符合硬件需求
+ */
+ ret = call_qop(q, queue_setup, q, &num_buffers,
+ &num_planes, plane_sizes, q->alloc_devs);
+```
+
+
+
+情景分析2:把buffer放入队列
+
+```c
+APP ioctl VIDIOC_QBUF
+------------------------------
+ v4l_qbuf // v4l2-ioctl.c
+ ops->vidioc_qbuf(file, fh, p);
+ vb2_ioctl_qbuf // videobuf2-v4l2.c
+ vb2_qbuf(vdev->queue, p); // videobuf2-v4l2.c
+ vb2_core_qbuf(q, b->index, b); // videobuf2-core.c
+ ret = __buf_prepare(vb, pb);
+ ret = __qbuf_mmap(vb, pb); // videobuf2-core.c
+
+ if (pb) // 调用vb2_buf_ops.fill_vb2_buffer, 使用APP传入的v4l2_buffer来设置驱动的vb2_buffer
+ ret = call_bufop(vb->vb2_queue, fill_vb2_buffer,
+ vb, pb, vb->planes);
+ // 硬件相关的vb2_ops.buf_prepare, 对buffer做些准备工作
+ return ret ? ret : call_vb_qop(vb, buf_prepare, vb);
+ // 把buffer放入空闲链表
+ list_add_tail(&vb->queued_entry, &q->queued_list);
+
+ if (pb) // 调用vb2_buf_ops.copy_timestamp, 使用APP传入的v4l2_buffer来设置驱动的vb2_buffer
+ call_void_bufop(q, copy_timestamp, vb, pb);
+
+ /*
+ * If already streaming, give the buffer to driver for processing.
+ * If not, the buffer will be given to driver on next streamon.
+ */
+ if (q->start_streaming_called)
+ __enqueue_in_driver(vb);
+ /* sync buffers */
+ for (plane = 0; plane < vb->num_planes; ++plane)
+ // 调用vb2_mem_ops.prepare做准备
+ call_void_memop(vb, prepare, vb->planes[plane].mem_priv);
+
+ // 调用硬件相关的vb2_ops.buf_queue把buffer传送给驱动
+ call_void_vb_qop(vb, buf_queue, vb);
+
+ /* Fill buffer information for the userspace */
+ if (pb) // 调用vb2_buf_ops.fill_user_buffer, 使用驱动的vb2_buffer为APP设置v4l2_buffer
+ call_void_bufop(q, fill_user_buffer, vb, pb);
+
+ /*
+ * If streamon has been called, and we haven't yet called
+ * start_streaming() since not enough buffers were queued, and
+ * we now have reached the minimum number of queued buffers,
+ * then we can finally call start_streaming().
+ */
+ if (q->streaming && !q->start_streaming_called &&
+ q->queued_count >= q->min_buffers_needed) {
+ ret = vb2_start_streaming(q);
+ /*
+ * If any buffers were queued before streamon,
+ * we can now pass them to driver for processing.
+ */
+ list_for_each_entry(vb, &q->queued_list, queued_entry)
+ __enqueue_in_driver(vb);
+ /* sync buffers */
+ for (plane = 0; plane < vb->num_planes; ++plane)
+ // 调用vb2_mem_ops.prepare做准备
+ call_void_memop(vb, prepare, vb->planes[plane].mem_priv);
+ // 调用硬件相关的vb2_ops.buf_queue把buffer传送给驱动
+ call_void_vb_qop(vb, buf_queue, vb);
+
+ /* Tell the driver to start streaming */
+ q->start_streaming_called = 1;
+ // 调用硬件相关的vb2_ops.start_streaming启动硬件传输
+ ret = call_qop(q, start_streaming, q,
+ atomic_read(&q->owned_by_drv_count));
+
+
+```
+
+
+
+
+
+情景分析3:把buffer取出队列
+
+```shell
+APP ioctl VIDIOC_DQBUF
+------------------------------
+ v4l_dqbuf // v4l2-ioctl.c
+ ops->vidioc_dqbuf(file, fh, p);
+ vb2_ioctl_dqbuf // videobuf2-v4l2.c
+ vb2_dqbuf(vdev->queue, p, file->f_flags & O_NONBLOCK);
+ ret = vb2_core_dqbuf(q, NULL, b, nonblocking);
+ ret = __vb2_get_done_vb(q, &vb, pb, nonblocking);
+ ret = __vb2_wait_for_done_vb(q, nonblocking);
+ call_void_qop(q, wait_prepare, q);
+ wait_event_interruptible
+ call_void_qop(q, wait_finish, q);
+ *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);
if (pb)
- ret = call_bufop(vb->vb2_queue, fill_vb2_buffer,
- vb, pb, vb->planes);
- __fill_vb2_buffer
- return ret ? ret : call_vb_qop(vb, buf_prepare, vb);
+ ret = call_bufop(q, verify_planes_array, *vb, pb);
+ __verify_planes_array_core
+ __verify_planes_array(vb, pb);
+ if (!ret)
+ list_del(&(*vb)->done_entry);
+ call_void_vb_qop(vb, buf_finish, vb);
- /* Fill buffer information for the userspace */
- call_void_bufop(q, fill_user_buffer, vb, pb);
- __fill_v4l2_buffer
-
-vb2_core_dqbuf
- ret = __vb2_get_done_vb(q, &vb, pb, nonblocking);
- ret = __vb2_wait_for_done_vb(q, nonblocking);
- call_void_qop(q, wait_prepare, q);
- wait_event_interruptible
- call_void_qop(q, wait_finish, q);
- *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);
- if (pb)
- ret = call_bufop(q, verify_planes_array, *vb, pb);
- __verify_planes_array_core
- __verify_planes_array(vb, pb);
- if (!ret)
- list_del(&(*vb)->done_entry);
- call_void_vb_qop(vb, buf_finish, vb);
-
- /* go back to dequeued state */
- __vb2_dqbuf(vb);
- call_void_memop(vb, unmap_dmabuf, vb->planes[i].mem_priv);
- vb2_vmalloc_unmap_dmabuf
- dma_buf_vunmap(buf->dbuf, buf->vaddr);
-
-vb2_core_qbuf
- if (pb)
- call_void_bufop(q, copy_timestamp, vb, pb);
- __copy_timestamp
- vb->timestamp = timeval_to_ns(&b->timestamp);
+ /* go back to dequeued state */
+ __vb2_dqbuf(vb);
+ call_void_memop(vb, unmap_dmabuf, vb->planes[i].mem_priv);
+ vb2_vmalloc_unmap_dmabuf
+ dma_buf_vunmap(buf->dbuf, buf->vaddr);
```
diff --git a/IMX6ULL/doc_pic/13_V4L2/2-4_videobuffer2的3个ops.tif b/IMX6ULL/doc_pic/13_V4L2/2-4_videobuffer2的3个ops.tif
new file mode 100644
index 0000000..8376499
Binary files /dev/null and b/IMX6ULL/doc_pic/13_V4L2/2-4_videobuffer2的3个ops.tif differ
diff --git a/IMX6ULL/doc_pic/13_V4L2/2-5_videobuffer2情景分析.tif b/IMX6ULL/doc_pic/13_V4L2/2-5_videobuffer2情景分析.tif
new file mode 100644
index 0000000..8d0e071
Binary files /dev/null and b/IMX6ULL/doc_pic/13_V4L2/2-5_videobuffer2情景分析.tif differ
diff --git a/IMX6ULL/doc_pic/13_V4L2/pic/35_call_buf_ops.png b/IMX6ULL/doc_pic/13_V4L2/pic/35_call_buf_ops.png
new file mode 100644
index 0000000..41ae796
Binary files /dev/null and b/IMX6ULL/doc_pic/13_V4L2/pic/35_call_buf_ops.png differ
diff --git a/IMX6ULL/doc_pic/13_V4L2/pic/36_call_mem_ops.png b/IMX6ULL/doc_pic/13_V4L2/pic/36_call_mem_ops.png
new file mode 100644
index 0000000..fb08665
Binary files /dev/null and b/IMX6ULL/doc_pic/13_V4L2/pic/36_call_mem_ops.png differ
diff --git a/IMX6ULL/doc_pic/13_V4L2/pic/37_three_ops.png b/IMX6ULL/doc_pic/13_V4L2/pic/37_three_ops.png
new file mode 100644
index 0000000..22f5e3b
Binary files /dev/null and b/IMX6ULL/doc_pic/13_V4L2/pic/37_three_ops.png differ
diff --git a/README.md b/README.md
index 3d85ebc..9b70b41 100644
--- a/README.md
+++ b/README.md
@@ -802,6 +802,13 @@ git clone https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
[02-3]_videobuffer2缓冲区结构体
```
+* 2023.08.26 发布"摄像头驱动"
+
+ ```shell
+ [02-4]_videobuffer2的3个ops
+ [02-5]_videobuffer2情景分析
+ ```
+
diff --git a/STM32MP157/doc_pic/13_V4L2/02_V4L2驱动程序框架.md b/STM32MP157/doc_pic/13_V4L2/02_V4L2驱动程序框架.md
index ab7b274..6534bf5 100644
--- a/STM32MP157/doc_pic/13_V4L2/02_V4L2驱动程序框架.md
+++ b/STM32MP157/doc_pic/13_V4L2/02_V4L2驱动程序框架.md
@@ -142,6 +142,372 @@ APP操作buffer的示意图如下:
+#### 1.3.2 videobuffer2的3个ops
+
+V4L2中使用vb2_queue来管理缓冲区,里面有3个操作结构体:
+
+
+
+这3个操作结构体的作用为:
+
+* `const struct vb2_buf_ops *buf_ops`:在用户空间、内核空间之间传递buffer信息,通过下面几个宏来调用
+ 
+* `const struct vb2_mem_ops *mem_ops`:分配内存用的回调函数,通过下面几个宏来调用
+ 
+
+* `const struct vb2_ops *ops`:硬件相关的回调函数,通过下面几个宏来调用
+ 
+
+
+
+这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);
+
+```
+
+
+
+
+
+#### 1.3.3 vb2_buf_ops
+
+`struct vb2_buf_ops`示例如下:
+
+
+
+原型如下:
+
+```c
+struct vb2_buf_ops {
+ int (*verify_planes_array)(struct vb2_buffer *vb, const void *pb);
+ void (*fill_user_buffer)(struct vb2_buffer *vb, void *pb);
+ int (*fill_vb2_buffer)(struct vb2_buffer *vb, const void *pb,
+ struct vb2_plane *planes);
+ void (*copy_timestamp)(struct vb2_buffer *vb, const void *pb);
+};
+```
+
+各成员的作用为:
+
+| vb2_buf_ops结构体成员 | 作用 |
+| --------------------- | ------------------------------------------------------------ |
+| verify_planes_array | APP调用ioctl VIDIOC_DQBUF时,在驱动内部会调用此函数,用来验证这个buffer含有足够多的plane。 |
+| fill_user_buffer | 使用驱动的vb2_buffer结构体来填充一个v4l2_buffer结构体,用来给用户空间提供更多信息。APP调用ioctl VIDIOC_QUERYBUF、VIDIOC_PREPARE_BUF、VIDIOC_QBUF、VIDIOC_DQBUF时,都会传入一个v4l2_buffer结构体。 |
+| fill_vb2_buffer | APP调用ioctl VIDIOC_QBUF时,传入一个v4l2_buffer结构体,驱动里会用它来填充vb2_buffer结构体。 |
+| copy_timestamp | APP调用ioctl VIDIOC_QBUF时,传入一个v4l2_buffer结构体,用户程序可以在它的timestamp里记下时间。驱动程序可以调用此函数把timestamp写入vb2_buffer.timestamp里。 |
+
+
+
+
+
+#### 1.3.4 vb2_mem_ops
+
+`struct vb2_mem_ops`示例如下:
+
+
+
+原型如下:
+
+```c
+struct vb2_mem_ops {
+ void *(*alloc)(struct device *dev, unsigned long attrs,
+ unsigned long size,
+ enum dma_data_direction dma_dir,
+ gfp_t gfp_flags);
+ void (*put)(void *buf_priv);
+ struct dma_buf *(*get_dmabuf)(void *buf_priv, unsigned long flags);
+
+ void *(*get_userptr)(struct device *dev, unsigned long vaddr,
+ unsigned long size,
+ enum dma_data_direction dma_dir);
+ void (*put_userptr)(void *buf_priv);
+
+ void (*prepare)(void *buf_priv);
+ void (*finish)(void *buf_priv);
+
+ void *(*attach_dmabuf)(struct device *dev,
+ struct dma_buf *dbuf,
+ unsigned long size,
+ enum dma_data_direction dma_dir);
+ void (*detach_dmabuf)(void *buf_priv);
+ int (*map_dmabuf)(void *buf_priv);
+ void (*unmap_dmabuf)(void *buf_priv);
+
+ void *(*vaddr)(void *buf_priv);
+ void *(*cookie)(void *buf_priv);
+
+ unsigned int (*num_users)(void *buf_priv);
+
+ int (*mmap)(void *buf_priv, struct vm_area_struct *vma);
+};
+```
+
+各成员的作用为:
+
+| vb2_mem_ops结构体成员 | 作用 |
+| --------------------- | ------------------------------------------------------------ |
+| alloc | 分配真正用于存储视频数据的buffer,可能还分配私有数据 |
+| put | 通知分配器:这块buffer不再使用了。通常会释放内存。 |
+| get_dmabuf | 获得DMA BUF给底层驱动使用 |
+| get_userptr | 如果存储视频数据的buffer是userptr(由APP提供),那么APP调用ioctl VIDIOC_QBUF时,需要传入APP的buffer指针。驱动程序内部通过此函数把用户空间的buffer映射到内核空间。 |
+| put_userptr | 通知分配器:这块USERPTR缓冲区不再使用 |
+| attach_dmabuf | 如果存储视频数据的buffer是DMA Buf,那么在把这个buffer放入队列前会调用此函数:记录这个DMA Buf。 |
+| detach_dmabuf | 不再使用这个DMA Buf时,做些清理工作(比如在attach_dmabuf里分配了数据,就在这里释放掉) |
+| map_dmabuf | 把DMA Buf映射到内核空间 |
+| unmap_dmabuf | map_dmabuf的反操作 |
+| prepare | 每当buffer被从用户空间传递到驱动时,此函数被调用,可以用来做某些同步操作。可选。 |
+| finish | 每当buffer被从驱动传递到用户空间时,此函数被调用,可以用来做某些同步操作。可选。 |
+| vaddr | 返回这块内存的内核空间地址 |
+| cookie | 没什么用 |
+| num_users | 返回这块内存的引用计数 |
+| mmap | 把这块内存,映射到用户空间 |
+
+
+
+
+
+#### 1.3.5 vb2_ops
+
+`struct vb2_ops`示例如下:
+
+
+
+原型如下:
+
+```c
+struct vb2_ops {
+ int (*queue_setup)(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[]);
+
+ void (*wait_prepare)(struct vb2_queue *q);
+ void (*wait_finish)(struct vb2_queue *q);
+
+ int (*buf_init)(struct vb2_buffer *vb);
+ int (*buf_prepare)(struct vb2_buffer *vb);
+ void (*buf_finish)(struct vb2_buffer *vb);
+ void (*buf_cleanup)(struct vb2_buffer *vb);
+
+ int (*start_streaming)(struct vb2_queue *q, unsigned int count);
+ void (*stop_streaming)(struct vb2_queue *q);
+
+ void (*buf_queue)(struct vb2_buffer *vb);
+};
+```
+
+各成员的作用为:
+
+| vb2_ops结构体成员 | 作用 |
+| ----------------- | ------------------------------------------------------------ |
+| queue_setup | APP调用ioctl VIDIOC_REQBUFS或VIDIOC_CREATE_BUFS时,
驱动程序在分配内存之前,会调用此函数。
作用:通过它来询问硬件驱动"你需要多少个buffer?每个buffer需要多少个plane?"
这个函数被调用2次:第1次用来表明驱动程序对buffer的需求,但是不一定能全部分配这些buffer,当分配出buffer后,再调用第2次以验证"这些buffer是否足够"。 |
+| wait_prepare | 释放驱动自己的互斥锁 |
+| wait_finish | 申请驱动自己的互斥锁 |
+| buf_init | 分配vb2_buffer及它内部存储数据的buffer后,使用buf_init进行驱动相关的初始化 |
+| buf_prepare | APP调用ioctl VIDIOC_QBUF或VIDIOC_PREPARE_BUF时,驱动程序会在执行硬件操作前,调用此函数进行必要的初始化。 |
+| buf_finish | APP调用ioctl VIDIOC_DQBUF后,在驱动程序返回用户空间之前,会调用此函数,可以在这个函数里修改buffer。或者驱动程序内部停止或暂停streaming时,也会调用此函数。 |
+| buf_cleanup | 在buffer被释放前调用,驱动程序在这个函数里执行额外的清除工作。 |
+| start_streaming | 驱动相关的"启动streaming"函数 |
+| stop_streaming | 驱动相关的"停止streaming"函数 |
+| buf_queue | 把buffer传送给驱动,驱动获得数据、填充好buffer后会调用vb2_buffer_done函数返还buffer。 |
+
+
+
+#### 1.3.6 videobuffer2情景分析
+
+情景分析1:申请buffer
+
+```c
+APP ioctl VIDIOC_REQBUFS
+------------------------------
+ v4l_reqbufs // v4l2-ioctl.c
+ ops->vidioc_reqbufs(file, fh, p);
+ vb2_ioctl_reqbufs // videobuf2-v4l2.c
+ vb2_core_reqbufs
+ /*
+ * Ask the driver how many buffers and planes per buffer it requires.
+ * Driver also sets the size and allocator context for each plane.
+ */
+ /* 调用硬件相关的vb2_ops.queue_setup,确认需要多少个buffer、每个buffer里有多少个plane */
+ ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes,
+ plane_sizes, q->alloc_devs);
+
+
+ /* Finally, allocate buffers and video memory */
+ allocated_buffers =
+ __vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);
+ ret = __vb2_buf_mem_alloc(vb);
+ // 调用vb2_mem_ops.alloc分配内存
+ mem_priv = call_ptr_memop(vb, alloc,
+ q->alloc_devs[plane] ? : q->dev,
+ q->dma_attrs, size, dma_dir, q->gfp_flags);
+
+ /* 驱动想得到M个buffer,但是可能只分配了N个buffer,可以吗?问驱动
+ * 再次调用硬件相关的vb2_ops.queue_setup,确认已经分配的buffer个数、每个buffer的plane个数是否符合硬件需求
+ */
+ ret = call_qop(q, queue_setup, q, &num_buffers,
+ &num_planes, plane_sizes, q->alloc_devs);
+```
+
+
+
+情景分析2:把buffer放入队列
+
+```c
+APP ioctl VIDIOC_QBUF
+------------------------------
+ v4l_qbuf // v4l2-ioctl.c
+ ops->vidioc_qbuf(file, fh, p);
+ vb2_ioctl_qbuf // videobuf2-v4l2.c
+ vb2_qbuf(vdev->queue, p); // videobuf2-v4l2.c
+ vb2_core_qbuf(q, b->index, b); // videobuf2-core.c
+ ret = __buf_prepare(vb, pb);
+ ret = __qbuf_mmap(vb, pb); // videobuf2-core.c
+
+ if (pb) // 调用vb2_buf_ops.fill_vb2_buffer, 使用APP传入的v4l2_buffer来设置驱动的vb2_buffer
+ ret = call_bufop(vb->vb2_queue, fill_vb2_buffer,
+ vb, pb, vb->planes);
+ // 硬件相关的vb2_ops.buf_prepare, 对buffer做些准备工作
+ return ret ? ret : call_vb_qop(vb, buf_prepare, vb);
+ // 把buffer放入空闲链表
+ list_add_tail(&vb->queued_entry, &q->queued_list);
+
+ if (pb) // 调用vb2_buf_ops.copy_timestamp, 使用APP传入的v4l2_buffer来设置驱动的vb2_buffer
+ call_void_bufop(q, copy_timestamp, vb, pb);
+
+ /*
+ * If already streaming, give the buffer to driver for processing.
+ * If not, the buffer will be given to driver on next streamon.
+ */
+ if (q->start_streaming_called)
+ __enqueue_in_driver(vb);
+ /* sync buffers */
+ for (plane = 0; plane < vb->num_planes; ++plane)
+ // 调用vb2_mem_ops.prepare做准备
+ call_void_memop(vb, prepare, vb->planes[plane].mem_priv);
+
+ // 调用硬件相关的vb2_ops.buf_queue把buffer传送给驱动
+ call_void_vb_qop(vb, buf_queue, vb);
+
+ /* Fill buffer information for the userspace */
+ if (pb) // 调用vb2_buf_ops.fill_user_buffer, 使用驱动的vb2_buffer为APP设置v4l2_buffer
+ call_void_bufop(q, fill_user_buffer, vb, pb);
+
+ /*
+ * If streamon has been called, and we haven't yet called
+ * start_streaming() since not enough buffers were queued, and
+ * we now have reached the minimum number of queued buffers,
+ * then we can finally call start_streaming().
+ */
+ if (q->streaming && !q->start_streaming_called &&
+ q->queued_count >= q->min_buffers_needed) {
+ ret = vb2_start_streaming(q);
+ /*
+ * If any buffers were queued before streamon,
+ * we can now pass them to driver for processing.
+ */
+ list_for_each_entry(vb, &q->queued_list, queued_entry)
+ __enqueue_in_driver(vb);
+ /* sync buffers */
+ for (plane = 0; plane < vb->num_planes; ++plane)
+ // 调用vb2_mem_ops.prepare做准备
+ call_void_memop(vb, prepare, vb->planes[plane].mem_priv);
+ // 调用硬件相关的vb2_ops.buf_queue把buffer传送给驱动
+ call_void_vb_qop(vb, buf_queue, vb);
+
+ /* Tell the driver to start streaming */
+ q->start_streaming_called = 1;
+ // 调用硬件相关的vb2_ops.start_streaming启动硬件传输
+ ret = call_qop(q, start_streaming, q,
+ atomic_read(&q->owned_by_drv_count));
+
+
+```
+
+
+
+
+
+情景分析3:把buffer取出队列
+
+```shell
+APP ioctl VIDIOC_DQBUF
+------------------------------
+ v4l_dqbuf // v4l2-ioctl.c
+ ops->vidioc_dqbuf(file, fh, p);
+ vb2_ioctl_dqbuf // videobuf2-v4l2.c
+ vb2_dqbuf(vdev->queue, p, file->f_flags & O_NONBLOCK);
+ ret = vb2_core_dqbuf(q, NULL, b, nonblocking);
+ ret = __vb2_get_done_vb(q, &vb, pb, nonblocking);
+ ret = __vb2_wait_for_done_vb(q, nonblocking);
+ call_void_qop(q, wait_prepare, q);
+ wait_event_interruptible
+ call_void_qop(q, wait_finish, q);
+ *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);
+ if (pb)
+ ret = call_bufop(q, verify_planes_array, *vb, pb);
+ __verify_planes_array_core
+ __verify_planes_array(vb, pb);
+ if (!ret)
+ list_del(&(*vb)->done_entry);
+ call_void_vb_qop(vb, buf_finish, vb);
+
+ /* go back to dequeued state */
+ __vb2_dqbuf(vb);
+ call_void_memop(vb, unmap_dmabuf, vb->planes[i].mem_priv);
+ vb2_vmalloc_unmap_dmabuf
+ dma_buf_vunmap(buf->dbuf, buf->vaddr);
+```
+
+
+
## 2. 从0编写一个虚拟的摄像头驱动
diff --git a/STM32MP157/doc_pic/13_V4L2/2-4_videobuffer2的3个ops.tif b/STM32MP157/doc_pic/13_V4L2/2-4_videobuffer2的3个ops.tif
new file mode 100644
index 0000000..8376499
Binary files /dev/null and b/STM32MP157/doc_pic/13_V4L2/2-4_videobuffer2的3个ops.tif differ
diff --git a/STM32MP157/doc_pic/13_V4L2/2-5_videobuffer2情景分析.tif b/STM32MP157/doc_pic/13_V4L2/2-5_videobuffer2情景分析.tif
new file mode 100644
index 0000000..8d0e071
Binary files /dev/null and b/STM32MP157/doc_pic/13_V4L2/2-5_videobuffer2情景分析.tif differ
diff --git a/STM32MP157/doc_pic/13_V4L2/pic/30_vb2_queue_ops.png b/STM32MP157/doc_pic/13_V4L2/pic/30_vb2_queue_ops.png
new file mode 100644
index 0000000..a71320d
Binary files /dev/null and b/STM32MP157/doc_pic/13_V4L2/pic/30_vb2_queue_ops.png differ
diff --git a/STM32MP157/doc_pic/13_V4L2/pic/31_vbs_ops_example.png b/STM32MP157/doc_pic/13_V4L2/pic/31_vbs_ops_example.png
new file mode 100644
index 0000000..40a8b9b
Binary files /dev/null and b/STM32MP157/doc_pic/13_V4L2/pic/31_vbs_ops_example.png differ
diff --git a/STM32MP157/doc_pic/13_V4L2/pic/32_call_vbs_ops.png b/STM32MP157/doc_pic/13_V4L2/pic/32_call_vbs_ops.png
new file mode 100644
index 0000000..eef50e7
Binary files /dev/null and b/STM32MP157/doc_pic/13_V4L2/pic/32_call_vbs_ops.png differ
diff --git a/STM32MP157/doc_pic/13_V4L2/pic/33_vb2_mem_ops_example.png b/STM32MP157/doc_pic/13_V4L2/pic/33_vb2_mem_ops_example.png
new file mode 100644
index 0000000..204283c
Binary files /dev/null and b/STM32MP157/doc_pic/13_V4L2/pic/33_vb2_mem_ops_example.png differ
diff --git a/STM32MP157/doc_pic/13_V4L2/pic/34_vb2_buf_ops_example.png b/STM32MP157/doc_pic/13_V4L2/pic/34_vb2_buf_ops_example.png
new file mode 100644
index 0000000..04ba66d
Binary files /dev/null and b/STM32MP157/doc_pic/13_V4L2/pic/34_vb2_buf_ops_example.png differ
diff --git a/STM32MP157/doc_pic/13_V4L2/pic/35_call_buf_ops.png b/STM32MP157/doc_pic/13_V4L2/pic/35_call_buf_ops.png
new file mode 100644
index 0000000..41ae796
Binary files /dev/null and b/STM32MP157/doc_pic/13_V4L2/pic/35_call_buf_ops.png differ
diff --git a/STM32MP157/doc_pic/13_V4L2/pic/36_call_mem_ops.png b/STM32MP157/doc_pic/13_V4L2/pic/36_call_mem_ops.png
new file mode 100644
index 0000000..fb08665
Binary files /dev/null and b/STM32MP157/doc_pic/13_V4L2/pic/36_call_mem_ops.png differ
diff --git a/STM32MP157/doc_pic/13_V4L2/pic/37_three_ops.png b/STM32MP157/doc_pic/13_V4L2/pic/37_three_ops.png
new file mode 100644
index 0000000..22f5e3b
Binary files /dev/null and b/STM32MP157/doc_pic/13_V4L2/pic/37_three_ops.png differ
diff --git a/STM32MP157/doc_pic/13_V4L2/笔记.md b/STM32MP157/doc_pic/13_V4L2/笔记.md
index 576b0cd..93780c7 100644
--- a/STM32MP157/doc_pic/13_V4L2/笔记.md
+++ b/STM32MP157/doc_pic/13_V4L2/笔记.md
@@ -231,10 +231,18 @@ V4L2 capture overlay
参考资料:
+* https://www.linuxtv.org/downloads/v4l-dvb-apis-new/
* https://www.linuxtv.org/downloads/v4l-dvb-apis-old/vidioc-create-bufs.html
-* https://www.linuxtv.org/downloads/v4l-dvb-apis-old/buffer.html#v4l2-memory
+* 3种buffer:https://www.linuxtv.org/downloads/v4l-dvb-apis-old/index.html
+ * https://www.linuxtv.org/downloads/v4l-dvb-apis-old/buffer.html#v4l2-memory
+ * https://www.linuxtv.org/downloads/v4l-dvb-apis-old/mmap.html
+ * https://www.linuxtv.org/downloads/v4l-dvb-apis-old/userp.html
+ * https://www.linuxtv.org/downloads/v4l-dvb-apis-old/dmabuf.html
+ * https://www.linuxtv.org/downloads/v4l-dvb-apis-old/vidioc-expbuf.html
+ * 基于Streaming I/O的V4L2设备使用: https://blog.csdn.net/coroutines/article/details/70141086
* https://www.linuxtv.org/downloads/v4l-dvb-apis-old/mmap.html
* V4l2应用框架-Videobuf2数据结构 https://blog.csdn.net/weixin_42581177/article/details/126582465
+* user ptr:https://github.com/h4tr3d/v4l2-capture-complex/blob/master/main.cpp
@@ -460,3 +468,19 @@ airspy_urb_complete
+### 4.7 vidioc_prepare_buf
+
+```c
+v4l2_ioctl_ops.vidioc_prepare_buf
+ vb2_ioctl_prepare_buf
+ vb2_prepare_buf(vdev->queue, p);
+ vb2_core_prepare_buf(q, b->index, b)
+ call_void_bufop(q, fill_user_buffer, vb, pb);
+ __fill_v4l2_buffer
+ /**
+ * __fill_v4l2_buffer() - fill in a struct v4l2_buffer with information to be
+ * returned to userspace
+ */
+
+```
+