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信息,通过下面几个宏来调用 + ![image-20230826003824317](pic/35_call_buf_ops.png) +* `const struct vb2_mem_ops *mem_ops`:分配内存用的回调函数,通过下面几个宏来调用 + ![image-20230826004001342](pic/36_call_mem_ops.png) + +* `const struct vb2_ops *ops`:硬件相关的回调函数,通过下面几个宏来调用 ![image-20230816184848876](pic/32_call_vbs_ops.png) -* `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`示例如下: +![image-20230826003154388](pic/37_three_ops.png) -![image-20230816171236918](pic/31_vbs_ops_example.png) + + +完整的注册流程(参考`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`示例如下: + +![image-20230818151510055](pic/34_vb2_buf_ops_example.png) 原型如下: ```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`示例如下: -![image-20230818151510055](pic/34_vb2_buf_ops_example.png) +#### 1.3.5 vb2_ops + +`struct vb2_ops`示例如下: + +![image-20230816171236918](pic/31_vbs_ops_example.png) 原型如下: ```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个操作结构体: + +![image-20230816154549808](pic/30_vb2_queue_ops.png) + +这3个操作结构体的作用为: + +* `const struct vb2_buf_ops *buf_ops`:在用户空间、内核空间之间传递buffer信息,通过下面几个宏来调用 + ![image-20230826003824317](pic/35_call_buf_ops.png) +* `const struct vb2_mem_ops *mem_ops`:分配内存用的回调函数,通过下面几个宏来调用 + ![image-20230826004001342](pic/36_call_mem_ops.png) + +* `const struct vb2_ops *ops`:硬件相关的回调函数,通过下面几个宏来调用 + ![image-20230816184848876](pic/32_call_vbs_ops.png) + + + +这3个ops的层次图如下: + +![image-20230826003154388](pic/37_three_ops.png) + + + +完整的注册流程(参考`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`示例如下: + +![image-20230818151510055](pic/34_vb2_buf_ops_example.png) + +原型如下: + +```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`示例如下: + +![image-20230817162952015](pic/33_vb2_mem_ops_example.png) + +原型如下: + +```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`示例如下: + +![image-20230816171236918](pic/31_vbs_ops_example.png) + +原型如下: + +```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 + */ + +``` +