收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
// 驱动需要明确定义的函数,用于操作vb2\_queue
[include/media/videobuf2-core.h]
struct vb2_ops {
// 设置缓冲区队列相关参数
int (\*queue_setup)(struct vb2_queue \*q, const struct v4l2_format \*fmt,
unsigned int \*num_buffers, unsigned int \*num_planes,
unsigned int sizes[], void \*alloc_ctxs[]);
// 在调用ioctl等待新的缓冲区时释放所有锁,避免阻塞时产生死锁
void (\*wait_prepare)(struct vb2_queue \*q);
// 重新获取在前一个回调函数中释放的锁
void (\*wait_finish)(struct vb2_queue \*q);
// buffer初始化
int (\*buf_init)(struct vb2_buffer \*vb);
// buffer准备好
int (\*buf_prepare)(struct vb2_buffer \*vb);
// 缓冲区每次出队到用户空间都需要调用,驱动可以访问或修改缓冲区。
void (\*buf_finish)(struct vb2_buffer \*vb);
// 调用后缓冲区被释放,驱动可以做一些清理工作
void (\*buf_cleanup)(struct vb2_buffer \*vb);
// 调用后流进入开启状态,在调用之前驱动必须先调用buf\_queue接收缓冲区,
int (\*start_streaming)(struct vb2_queue \*q, unsigned int count);
// 调用后流进入关闭状态,驱动需要停止DMA传输或等待工作完成和缓冲区全部入队。
void (\*stop_streaming)(struct vb2_queue \*q);
// 缓冲区加入驱动的队列中
void (\*buf_queue)(struct vb2_buffer \*vb);
};
struct vb2_buffer
是保存视频数据和信息的核心结构体,每一帧的图像都对应一个struct vb2_buffer
结构体,图像信息保存在struct v4l2_buffer
结构中,如时间戳、编号、序列号等信息。应用使用ioctl的VIDIOC_REQBUFS
命令请求缓冲区时,分配struct vb2_buffer
。struct vb2_buffer
的状态由enum vb2_buffer_state
枚举定义描述。若是V4L2_MEMORY_MMAP
类型,则会额外分配内存,图像数据则保存在额外分配的内存中,额外分配的内存指针保存在planes[VIDEO_MAX_PLANES]
数组中。
[include/media/videobuf2-core.h]
enum vb2_buffer_state { // 缓冲区状态枚举
VB2_BUF_STATE_DEQUEUED, // 缓冲区出队,处于用户空间的控制下,默认状态
VB2_BUF_STATE_PREPARING, // videobuf2正在准备缓冲区
VB2_BUF_STATE_PREPARED, // 缓冲区已准备好
VB2_BUF_STATE_QUEUED, // 缓冲区入队,处于videobuf2中,不处于驱动中
VB2_BUF_STATE_ACTIVE, // 缓冲区位于驱动中
VB2_BUF_STATE_DONE, // 缓冲区从驱动返回到videobuf2,但还没出队到用户空间
VB2_BUF_STATE_ERROR, // 出错,dequeued到用户空间会报错
};
struct vb2_buffer { // video缓冲区描述符
struct v4l2_buffer v4l2_buf; // 关联的缓冲区,可以被驱动read
// 可以由driver读写,(byteused)即使是对于single-planar类型,v4l2\_planes[0]应该被
// 使用而不是仅仅使用v4l2\_buf的byteused。
// 驱动使用vb2\_set\_plane\_payload()设置byteused.
struct v4l2_plane v4l2_planes[VIDEO_MAX_PLANES];
struct vb2_queue \*vb2_queue; // 该vb2\_buffer所属的vb2\_queue
unsigned int num_planes; // 该buffer有多少个planes
enum vb2_buffer_state state; // buffer的当前状态
// queued buffer链表,保存所有从userspace queued进的buffers
struct list_head queued_entry;
// 保存所有准备dequeued到userspace的buffers链表
struct list_head done_entry;
// 私有的per-plane信息,驱动禁止修改
struct vb2_plane planes[VIDEO_MAX_PLANES];
......
};
// 分配struct vb2\_buffer时,若是V4L2\_MEMORY\_MMAP类型,则会额外分配内存,
// 图像数据则保存在额外分配的内存中,额外分配的内存指针保存在该结构体当中
struct vb2_plane {
void \*mem_priv; //存放一帧图片数据(针对MMAP类型模式)
struct dma_buf \*dbuf; //(针对DMA类型模式)
unsigned int dbuf_mapped;
};
// struct v4l2\_buffer用来指定与描述一帧帧缓冲,应用可以设置
struct v4l2_buffer {
__u32 index; // buffer的编号
__u32 type; // buffer的类型,由enum v4l2\_buf\_type定义
// 数据在缓冲区(有效负载)中所占的字节数,对于多平面缓冲区未使用(设置为0)
__u32 bytesused;
// 标志位,见V4L2\_BUF\_FLAG\_XX宏定义,常见值有V4L2\_BUF\_FLAG\_MAPPED、
// V4L2\_BUF\_FLAG\_QUEUED、V4L2\_BUF\_FLAG\_DONE,分别代表当前缓存已经映射、
// 缓存可以采集数据、缓存可以提取数据
__u32 flags;
__u32 field;
struct timeval timestamp; // 视频帧时间戳
struct v4l2_timecode timecode; // 时间码
__u32 sequence; // 该帧的序列号
__u32 memory; // enum v4l2\_memory枚举定义
union {
// V4L2\_MEMORY\_MMAP,从将要mapping的device memory头到数据头的offset
__u32 offset;
// V4L2\_MEMORY\_USERPTR,用户空间指针指向此buffer
unsigned long userptr;
// for multiplanar buffers; userspace pointer to the array of plane
// info structs for this buffer
struct v4l2_plane \*planes;
// V4L2\_MEMORY\_DMABUF,用户空间的描述符关联此描述符
__s32 fd;
} m;
// 对于single-plane,表示buffer的字节数
// 对于multi-plane buffers,则表示planes array中的元素数量
__u32 length;
__u32 reserved2;
__u32 reserved;
};
struct vb2_mem_ops
是buffer内存分配和处理的操作函数集合,这些函数和buffer的类型有关系,即enum v4l2_memory
枚举定义的类型。具体如下。
(1)get_userptr
和put_userptr
函数用于处理USERPTR
类型的buffer。
(2)alloc
、put
、num_users
和mmap
函数用于处理MMAP
类型的buffer。
(3)alloc
、put
、num_users
和vaddr
函数用于处理read/write
访问类型的buffer。
(4)attach_dmabuf
、detach_dmabuf
、map_dmabuf
和unmap_dmabuf
函数用于处理DMABUF
类型的buffer。
struct vb2_mem_ops
结构体通常被初始化为vb2_dma_contig_memops
结构体,该结构体是内核提供的,可以直接使用。
[include/linux/dma-direction.h]
enum dma_data_direction { // DMA数据传输方向
DMA_BIDIRECTIONAL = 0, // 双向
DMA_TO_DEVICE = 1, // 传输到设备
DMA_FROM_DEVICE = 2, // 从设备往外传输
DMA_NONE = 3,
};
[include/media/videobuf2-core.h]
// 缓冲区内存处理和分配操作函数集合
struct vb2_mem_ops {
// 分配video内存和私有数据(可选)分配器
void \*(\*alloc)(void \*alloc_ctx, 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);
// 获取用户空间指针指向的内存,在V4L2\_MEMORY\_USERPTR模式中使用
void \*(\*get_userptr)(void \*alloc_ctx, unsigned long vaddr,
unsigned long size, enum dma_data_direction dma_dir);
// 告诉分配器USERPTR缓冲区不再使用
void (\*put_userptr)(void \*buf_priv);
// 缓冲区每次从用户空间添加到队列中就会被调用,对缓存同步很有用
void (\*prepare)(void \*buf_priv);
// 缓冲区每次从内核队列添加到用户空间就会被调用
void (\*finish)(void \*buf_priv);
// 为硬件操作添加共享的struct dma\_buf,在V4L2\_MEMORY\_DMABUF模式中使用
// alloc\_ctx-分配上下文,dbuf-共享的dma\_buf
void \*(\*attach_dmabuf)(void \*alloc_ctx, struct dma_buf \*dbuf,
unsigned long size, enum dma_data_direction dma_dir);
// 通知缓冲区的exporter目前的DMABUF不再使用
void (\*detach_dmabuf)(void \*buf_priv);
// 从分配器请求访问DMABUF,此DMABUF的分配器将通知驱动该DMABUF将要被使用
int (\*map_dmabuf)(void \*buf_priv);
// 释放访问DMABUF的控制权,此DMABUF的分配器将通知驱动该DMABUF已经使用完毕
void (\*unmap_dmabuf)(void \*buf_priv);
// 返回给定缓冲区的内核虚拟地址,该缓冲区与私有数据结构向关联
void \*(\*vaddr)(void \*buf_priv);
// 返回给定缓冲区的分配器定义的cookie
void \*(\*cookie)(void \*buf_priv);
// 返回此缓冲区的当前使用者,若只有videobuf2层使用,则返回1
unsigned int (\*num_users)(void \*buf_priv);
// 建立用户空间到给定缓冲区虚拟地址区域的映射
int (\*mmap)(void \*buf_priv, struct vm_area_struct \*vma);
};
4.使用方法分析
videobuf2的使用方法复杂,需要结合具体的驱动实例进行说明,这样比较好理解。下图是imx6ull平台上,CSI控制器的videobuf2使用方法总结。应用可以通过调用open
、close
、ioctl
、mmap
、read
系统调用访问Video设备,内核根据不同的系统调用采用相对应的方法访问videobuf2。下面从这些系统调用入手,分析内核中videobuf2的使用方法。
4.1.open
应用调用open
打开Video设备,获取设备的描述符。内核中首先调用v4l2_open
,然后调用驱动提供的mx6s_csi_open
函数。缓冲区队列vb2_queue
就是在mx6s_csi_open
函数中完成初始化。缓冲区队列数据结构vb2_queue
一般嵌入到其他结构体中,由驱动进行动态分配。首先必须设置缓冲区类型type
、I/O模型io_modes
、缓冲区操作函数集合ops
、缓冲区内存管理函数集合mem_ops
、时间戳类型timestamp_flags
(通常设置为V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
),其他成员可根据实际情况设置,最后调用vb2_queue_init
完成缓冲区队列vb2_queue
的初始化。
[include/media/videobuf2-core.h]
q-缓冲区队列数据结构struct vb2_queue指针
返回值-0成功,小于0失败
int vb2\_queue\_init(struct vb2_queue \*q)
{
// 必须设置下面的成员,否则返回-EINVAL的错误
if (WARN\_ON(!q) || WARN\_ON(!q->ops) || WARN\_ON(!q->mem_ops) ||
WARN\_ON(!q->type) || WARN\_ON(!q->io_modes) ||
WARN\_ON(!q->ops->queue_setup) || WARN\_ON(!q->ops->buf_queue) ||
WARN\_ON(q->timestamp_flags & ~(V4L2_BUF_FLAG_TIMESTAMP_MASK |
V4L2_BUF_FLAG_TSTAMP_SRC_MASK)))
return -EINVAL;
// 驱动必须选择合适的时间戳,否则内核会发出警告
WARN\_ON((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==
V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN);
INIT\_LIST\_HEAD(&q->queued_list); // 初始化queued\_list链表节点
INIT\_LIST\_HEAD(&q->done_list); // 初始化done\_list链表节点
spin\_lock\_init(&q->done_lock); // 初始化自旋锁
mutex\_init(&q->mmap_lock); // 初始化互斥锁
init\_waitqueue\_head(&q->done_wq); // 初始化等待队列头
// 若缓冲区大小驱动没有设置,则内核使用默认值初始化
if (q->buf_struct_size == 0)
q->buf_struct_size = sizeof(struct vb2_buffer);
return 0;
}
4.2.ioctl
V4L2子系统定义了很多ioctl命令供应用程序使用。VIDIOC_REQBUFS
命令用于向内核申请缓冲区,VIDIOC_QUERYBUF
命令用于获取缓冲区信息,VIDIOC_QBUF
命令将读取完数据的空缓存返还给驱动的缓存队列,VIDIOC_DQBUF
命令将填充满数据的缓存从驱动中返回给应用,VIDIOC_STREAMOFF
命令用于关闭流,即停止图像采集,VIDIOC_STREAMON
命令用于开启流,即开启图像采集。内核中的调用流程为v4l2_ioctl
->video_ioctl2
->__video_do_ioctl
->根据不同的命令调用不同的驱动函数->调用对应的videobuf2处理函数,具体调用流程参考上图。下面具体分析一下ioctl调用的videobuf2处理函数。
4.2.1.VIDIOC_REQBUFS
使用VIDIOC_REQBUFS
命令调用ioctl,最终会调用到vb2_reqbufs
函数,内核使用vb2_reqbufs
函数创建缓冲区。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2\_queue指针
// req-申请缓冲区所需信息存放的结构体,应用需要设置里面的成员
// 返回值-0成功,小于0失败
int vb2\_reqbufs(struct vb2_queue \*q, struct v4l2_requestbuffers \*req);
[include/uapi/linux/videodev2.h]
struct v4l2_requestbuffers {
__u32 count; // 申请缓冲区的数量,一帧图像对应一个缓冲区
__u32 type; // 缓冲区类型,摄像头为V4L2\_BUF\_TYPE\_VIDEO\_CAPTURE
__u32 memory; // 通常为V4L2\_MEMORY\_MMAP或V4L2\_MEMORY\_USERPTR
__u32 reserved[2]; // 保留
};
vb2_reqbufs
调用流程可总结如下:
(1)验证缓冲区的memory type和buffer type是否正确。
__verify_memory_type
函数用于校验缓冲区类型和缓冲区内存类型。vb2_queue
中的缓冲区类型type
和v4l2_requestbuffers
中的缓冲区类型type
需一致。缓冲区内存类型必须是V4L2_MEMORY_MMAP
、V4L2_MEMORY_USERPTR
、V4L2_MEMORY_DMABUF
其中之一。
若是V4L2_MEMORY_MMAP
类型,则q->io_modes
必须设置为VB2_MMAP
,mem_ops->alloc
、q->mem_ops->put
和q->mem_ops->mmap
的函数指针也必须设置。
若是V4L2_MEMORY_USERPTR
类型,则q->io_modes
必须设置为VB2_USERPTR
,mem_ops->get_userptr
和q->mem_ops->put_userptr
的函数指针也必须设置。
若是V4L2_MEMORY_DMABUF
类型,则q->io_modes
必须设置为VB2_DMABUF
,mem_ops->attach_dmabuf
、q->mem_ops->detach_dmabuf
、q->mem_ops->map_dmabuf
和q->mem_ops->unmap_dmabuf
的函数指针也必须设置。
(2)判断缓冲区参数是否正确,若不正确,则需要做一些处理。
申请的缓冲区数量为0或缓冲区队列中缓冲区数量不为0或申请的缓冲区内存类型和缓冲区队列中缓冲区内存类型不一致,则进入额外的处理逻辑。若内存类型为V4L2_MEMORY_MMAP且缓冲区正在使用,则直接返回错误。清理处于PREPARED或QUEUED状态的缓冲区并释放缓冲区内存。
(3)计算需要分配的缓冲区数量。
(4)调用驱动实现的函数queue_setup
,驱动函数需要设置num_buffers、num_buffers、q->plane_sizes和q->alloc_ctx。
(5)调用__vb2_queue_alloc
分配缓冲区内存。此时缓冲区的状态为VB2_BUF_STATE_DEQUEUED
。后面详细说明该函数。
(6)若分配的缓冲区数量小于需要分配的数量,需要再次调用驱动提供的queue_setup
函数。
(7)设置分配的缓冲区数量并向应用返回分配的缓冲区数量。
vb2_reqbufs
// 验证缓冲区的memory type和buffer type是否正确
->__verify_memory_type
->__reqbufs
// 申请的缓冲区数量为0或缓冲区队列中缓冲区数量不为0或
// 申请的缓冲区内存类型和缓冲区队列中缓冲区内存类型不一致,则需要则额外的处理
if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {
mutex\_lock(&q->mmap_lock);
// 内存类型为V4L2\_MEMORY\_MMAP且缓冲区正在使用,则直接返回错误
if (q->memory == V4L2_MEMORY_MMAP && \_\_buffers\_in\_use(q)) {
mutex\_unlock(&q->mmap_lock);
return -EBUSY;
}
// 若缓冲区处于PREPARED或QUEUED状态,则需要清理,
// 一般在申请了缓冲区,但没调用STREAMON,会出现这种情况
\_\_vb2\_queue\_cancel(q);
// 释放已经分配内存的缓冲区
ret = \_\_vb2\_queue\_free(q, q->num_buffers);
mutex\_unlock(&q->mmap_lock);
}
// 缓冲区数量取应用申请的数量和最大数量中的较小值,VIDEO\_MAX\_FRAME定义为32
num_buffers = min\_t(unsigned int, req->count, VIDEO_MAX_FRAME);
// 缓冲区数量取num\_buffers和需要最少的缓冲区数量的较大值
num_buffers = max\_t(unsigned int, num_buffers, q->min_buffers_needed);
// 设置缓冲区队列中缓冲区的内存模型,内存模型应该和应用申请的模型保持一致
q->memory = req->memory;
// 回调用驱动提供的queue\_setup函数,imx6ull提供的函数为mx6s\_videobuf\_setup
// 驱动函数需要设置num\_buffers、num\_buffers、q->plane\_sizes和q->alloc\_ctx
call\_qop(...queue_setup...)
// 分配缓冲区内存
->__vb2_queue_alloc
// 若分配的缓冲区数量小于需要分配的数量,需要再次调用驱动提供的queue\_setup函数
if (!ret && allocated_buffers < num_buffers) {
num_buffers = allocated_buffers;
// 若驱动能处理这种情况,则不会返回错误,若无法处理,则会返回错误
// mx6s\_videobuf\_setup不具备这种功能
ret = call\_qop(q, queue_setup, q, NULL, &num_buffers,
&num_planes, q->plane_sizes, q->alloc_ctx);
if (!ret && allocated_buffers < num_buffers)
ret = -ENOMEM;
}
->mutex\_lock(&q->mmap_lock); // 缓冲区共享成员访问需要同步
// 设置分配的缓冲区数量
q->num_buffers = allocated_buffers;
->mutex\_unlock(&q->mmap_lock);
req->count = allocated_buffers // 向应用返回分配的缓冲区数量
queue_setup
函数需要驱动提供。imx6ull提供的函数为mx6s_videobuf_setup
,主要的作用是设置__reqbufs
函数中的缓冲区数量num_buffers
、plane的数量num_planes
、plane的大小q->plane_sizes
及q->alloc_ctx
。上述设置的变量都是调用函数以指针的形式传入。
static int mx6s\_videobuf\_setup(struct vb2_queue \*vq,
const struct v4l2_format \*fmt,
unsigned int \*count, unsigned int \*num_planes,
unsigned int sizes[], void \*alloc_ctxs[])
{
struct mx6s_csi_dev \*csi_dev = vb2\_get\_drv\_priv(vq);
alloc_ctxs[0] = csi_dev->alloc_ctx; // 设置alloc\_ctxs
sizes[0] = csi_dev->pix.sizeimage; // 设置plane\_sizes为图像的大小
if (0 == \*count) // 如果传入的缓冲区数量为0,则设置为32
\*count = 32;
// 如果num\_planes为0且缓冲区占用的总内存超过了规定的最大值,则要重新计算缓冲区数量
// 最大值为64MB,MAX\_VIDEO\_MEM定义为64
if (!\*num_planes && sizes[0] \* \*count > MAX_VIDEO_MEM \* 1024 \* 1024)
// 则缓冲区数量按最大内存计算
\*count = (MAX_VIDEO_MEM \* 1024 \* 1024) / sizes[0];
\*num_planes = 1; // 设置plane的数量为1
return 0;
}
缓冲区分配主要由__vb2_queue_alloc
函数实现。分配num_buffers
个缓冲区,即分配num_buffers
个struct vb2_buffer
结构退。所有的缓冲区内存地址都保存到vb2_queue
结构体中的bufs
数组中。若是缓冲区内存是V4L2_MEMORY_MMAP
类型,则还需要额外分配保存图像的缓冲区,一个缓冲区分配num_planes
个保存图像的缓冲区。此缓冲区由vb2_dc_alloc
分配。分配完后缓冲器的结构示意如下图所示。V4L2_MEMORY_MMAP
类型的缓冲区需要分配额外的内存空间用于存储图像数据,如图中绿框所属,首选分配一个管理的结构体struct vb2_dc_buf
,再分配真正存储图像数据的缓冲区,存储图像的缓冲区物理地址和虚拟地址一致,其虚拟地址保存到管理结构体的vaddr
成员中,虚拟地址保存到管理结构体的dma_addr
成员中,缓冲区大小保存到管理结构体的size
成员中。
static int \_\_vb2\_queue\_alloc(struct vb2_queue \*q, enum v4l2_memory memory,
unsigned int num_buffers, unsigned int num_planes)
{
unsigned int buffer;
struct vb2_buffer \*vb;
int ret;
// 总共分配num\_buffers个缓冲区
for (buffer = 0; buffer < num_buffers; ++buffer) {
// 分配缓冲区内存,缓冲区大小为buf\_struct\_size,一般由驱动设置,
// imx6ull平台设置为sizeof(struct mx6s\_buffer)
vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
// 如果是multiplanar buffers,则缓冲区长度length保存的是plane的数量
if (V4L2\_TYPE\_IS\_MULTIPLANAR(q->type))
vb->v4l2_buf.length = num_planes;
vb->state = VB2_BUF_STATE_DEQUEUED; // 设置缓冲区状态
vb->vb2_queue = q; // 设置管理缓冲区的缓冲区队列
vb->num_planes = num_planes; // 设置plane数量
vb->v4l2_buf.index = q->num_buffers + buffer; // 设置缓冲区编号
vb->v4l2_buf.type = q->type; // 设置缓冲区类型
vb->v4l2_buf.memory = memory; // 设置缓冲区内存类型
// 对于V4L2\_MEMORY\_MMAP类型,则还需要分配额外的内存用于保存图像数据
// 然后映射到用户空间,用户可以直接读取额外内存中的数据
if (memory == V4L2_MEMORY_MMAP) {
// \*\*\*\*\*\*分配存储图像数据内存的函数\*\*\*\*\*\*\*
->\_\_vb2\_buf\_mem\_alloc(vb);
// 每一个缓冲区,分配num\_planes块额外的内存
for (plane = 0; plane < vb->num_planes; ++plane) {
// 分配的内存大小按页对齐
unsigned long size = PAGE\_ALIGN(q->plane_sizes[plane]);
// 调用驱动提供的alloc函数进行内存分配,
// imx6ull平台调用vb2\_dc\_alloc函数
mem_priv = call\_ptr\_memop(vb, alloc, q->alloc_ctx[plane],
size, dma_dir, q->gfp_flags);
// 将额外分配的内存保存到mem\_priv成员中
vb->planes[plane].mem_priv = mem_priv;
// 保存长度
vb->v4l2_planes[plane].length = q->plane_sizes[plane];
}
// 调用驱动提供的buf\_init函数进行初始化,imx6ull没有提供
->call\_vb\_qop(vb, buf_init, vb);
}
// 保存缓冲区地址
q->bufs[q->num_buffers + buffer] = vb;
}
// 设置所有缓冲区的每个plane的长度
->\_\_setup\_lengths(q, buffer);
vb->v4l2_planes[plane].length = q->plane_sizes[plane]
// MMAP类型还要设置偏移,每个buffer的每个plane偏移都不一样
if (memory == V4L2_MEMORY_MMAP)
->\_\_setup\_offsets(q, buffer);
return buffer;
}
vb2_dc_alloc
由驱动提供,其分配的缓冲区虚拟地址和物理地址都连续,属于第三种类型的videobuf2。
[drivers\media\v4l2-core\videobuf2-dma-contig.c]
struct vb2_dc_buf {
struct device \*dev;
void \*vaddr; // 内存虚拟地址
unsigned long size; // 内存大小
dma_addr_t dma_addr; // 内存物理地址
enum dma_data_direction dma_dir; // DMA传输方向
struct sg_table \*dma_sgt; // SG DMA相关
/\* MMAP相关变量 \*/
struct vb2_vmarea_handler handler;
atomic_t refcount;
struct sg_table \*sgt_base;
struct vm_area_struct \*vma; // USERPTR相关变量
struct dma_buf_attachment \*db_attach; // DMABUF相关变量
};
static void \*vb2\_dc\_alloc(void \*alloc_ctx, unsigned long size,
enum dma_data_direction dma_dir, gfp_t gfp_flags)
{
struct vb2_dc_conf \*conf = alloc_ctx;
struct device \*dev = conf->dev;
struct vb2_dc_buf \*buf;
// 首先分配一个struct vb2\_dc\_buf结构体
buf = kzalloc(sizeof \*buf, GFP_KERNEL);
// 然后分配存储图像数据的缓冲区,此缓冲区的物理地址和虚拟地址都连续
buf->vaddr = dma\_alloc\_coherent(dev, size, &buf->dma_addr, GFP_KERNEL | gfp_flags);
buf->dev = get\_device(dev); // 设置父设备指针
buf->size = size; // 保存图像数据缓冲区的大小
buf->dma_dir = dma_dir; // 记录DMA传输方向
// 设置struct vb2\_vmarea\_handler结构体
buf->handler.refcount = &buf->refcount;
buf->handler.put = vb2_dc_put; // 回调函数
buf->handler.arg = buf; // 回调函数参数
atomic\_inc(&buf->refcount); // 增加引用计数,引用计数为0时释放缓冲区
return buf; // 返回分配的vb2\_dc\_buf地址
}
4.2.2.VIDIOC_QUERYBUF
使用VIDIOC_QUERYBUF
命令调用ioctl,最终会调用到vb2_querybuf
函数,内核使用vb2_querybuf
函数将缓冲区信息拷贝到用户空间,主要有时间戳timestamp
、标志flags
、缓冲区长度length
、缓冲区偏移offset
等信息。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2\_queue指针
// b-v4l2\_buffer结构体指针,内核将缓冲区信息存放到里面
// 返回值-0成功,小于0失败
int vb2\_querybuf(struct vb2_queue \*q, struct v4l2_buffer \*b);
4.2.3.VIDIOC_QBUF
使用VIDIOC_QBUF
命令调用ioctl,最终会调用到vb2_qbuf
函数,内核使用vb2_qbuf
函数将读取完数据的空缓存返还给驱动的缓存队列。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2\_queue指针
// b-v4l2\_buffer结构体指针
// 返回值-0成功,小于0失败
int vb2\_qbuf(struct vb2_queue \*q, struct v4l2_buffer \*b);
vb2_qbuf
的主要工作如下:
(1)通过bufs
数组获取对应编号的缓冲区地址。
(2)根据缓冲区的不同状态做不同的处理。
VB2_BUF_STATE_DEQUEUED
状态的缓冲区需要调用__buf_prepare
函数执行一些准备工作。首先将缓冲区状态设置为VB2_BUF_STATE_PREPARING
,V4L2_MEMORY_MMAP
类型调用buf_prepare
,imx6ull平台调用mx6s_videobuf_prepare
,V4L2_MEMORY_USERPTR
调用get_userptr
,imx6ull平台调用vb2_dc_get_userptr
。VB2_BUF_STATE_PREPARED
状态的缓冲区无需准备工作。其他状态的缓冲区直接返回错误,不能进行VIDIOC_QBUF
操作。
(3)将缓冲区挂到queued_list
链表中。
(4)设置缓冲区状态为VB2_BUF_STATE_QUEUED
。
(5)若VIDIOC_STREAMON
已经被调用,说明流已经打开,则需要调用__enqueue_in_driver
函数将缓冲区添加到驱动的队列中。
vb2_qbuf
->vb2_internal_qbuf
// 获取对应编号的缓冲区地址
vb = q->bufs[b->index];
// 检查缓冲区状态,只有VB2\_BUF\_STATE\_DEQUEUED和
// VB2\_BUF\_STATE\_PREPARED状态才能被加入到驱动的缓存队列
switch (vb->state) {
case VB2_BUF_STATE_DEQUEUED:
-> \_\_buf\_prepare(vb, b);
->__verify_length; // 验证数据长度
vb->state = VB2_BUF_STATE_PREPARING; // 设置缓冲区状态为PREPARING
vb->v4l2_buf.timestamp.tv_sec = 0; // 清空时间戳
vb->v4l2_buf.timestamp.tv_usec = 0;
vb->v4l2_buf.sequence = 0; // 设置序列号为0
->\_\_qbuf\_mmap(vb, b); // V4L2\_MEMORY\_MMAP类型调用
->__fill_vb2_buffer // 填充信息并校验
// 调用buf\_prepare函数,imx6ull平台调用mx6s\_videobuf\_prepare函数
// 主要的作用设置payload和校验缓冲区虚拟地址是否正确
call\_vb\_qop(vb, buf_prepare, vb)
->\_\_qbuf\_userptr(vb, b); // V4L2\_MEMORY\_USERPTR类型调用
->__fill_vb2_buffer // 填充信息并校验
// 调用驱动提供的get\_userptr回调函数,
// imx6ull平台调用vb2\_dc\_get\_userptr回调函数
call\_ptr\_memop(...get_userptr...);
// 调用buf\_prepare函数,imx6ull平台调用mx6s\_videobuf\_prepare函数
call\_vb\_qop(vb, buf_prepare, vb);
// dmabuf目前没有接触到,不讨论
->\_\_qbuf\_dmabuf(vb, b); // V4L2\_MEMORY\_DMABUF类型调用
break;
case VB2_BUF_STATE_PREPARED:
break;
// videobuf2正在准备缓冲区,不能被加入到驱动的缓存队列
// \_\_buf\_prepare函数内部会设置VB2\_BUF\_STATE\_PREPARING状态
case VB2_BUF_STATE_PREPARING:
return -EINVAL;
default:
return -EINVAL;
}
// 将应用传入的缓冲区挂到queued\_list链表中
list\_add\_tail(&vb->queued_entry, &q->queued_list)
q->queued_count++; // queued\_list链表中的缓冲区数量加1
vb->state = VB2_BUF_STATE_QUEUED;
// 如果VIDIOC\_STREAMON已经被调用,则需要将缓冲区添加到驱动的缓冲区队列中
if (q->start_streaming_called)
// 将缓冲区挂到imx6ull CSI设备驱动结构体的capture链表上
->\_\_enqueue\_in\_driver(vb);
\_\_fill\_v4l2\_buffer(vb, b); // 向用户空间填充信息
imx6ull平台buf_prepare
的回调函数为mx6s_videobuf_prepare
,主要的作用是设置payload,检查缓冲区虚拟地址是否存在和payload是否正确设置。
static int mx6s\_videobuf\_prepare(struct vb2_buffer \*vb)
{
struct mx6s_csi_dev \*csi_dev = vb2\_get\_drv\_priv(vb->vb2_queue);
int ret = 0;
// 设置payload,payload为图像大小
vb2\_set\_plane\_payload(vb, 0, csi_dev->pix.sizeimage);
// 缓冲区的有效字节数为图像大小
vb->v4l2_planes[plane_no].bytesused = size
// 检查缓冲区虚拟地址是否存在和payload是否正确设置
if (vb2\_plane\_vaddr(vb, 0) &&
vb2\_get\_plane\_payload(vb, 0) > vb2\_plane\_size(vb, 0)) {
ret = -EINVAL;
goto out;
}
return 0;
out:
return ret;
}
// 获取plane的虚拟地址
vb2\_plane\_vaddr(vb, 0);
// 调用驱动提供的vaddr函数,imx6ull平台调用vb2\_dc\_vaddr
call\_ptr\_memop(vb, vaddr, vb->planes[plane_no].mem_priv)
->vb2_dc_vaddr
return buf->vaddr // 返回存储图像内存的虚拟地址
4.2.4.VIDIOC_STREAMON
使用VIDIOC_STREAMON
命令调用ioctl,最终会调用到vb2_streamon
函数,内核使用vb2_streamon
函数将开启视频流,对于图像采集设备而言,则设备开始采集图像,并将图像数据保存到缓冲区中。
[include/media/videobuf2-core.h]
// q-缓冲区队列数据结构struct vb2\_queue指针
// type-缓冲区类型
// 返回值-0成功,小于0失败
int vb2\_streamon(struct vb2_queue \*q, enum v4l2_buf_type type);
vb2_streamon
主要的工作如下:
(1)遍历queued_list
链表,首先将缓冲区状态设置为VB2_BUF_STATE_ACTIVE
,然后将入队的缓冲区都添加到驱动的队列中。imx6ull平台调用mx6s_videobuf_queue
将缓冲区添加到capture
链表中。
(2)调用start_streaming
开始视频流,imx6ull平台调用mx6s_start_streaming
函数使能设备,开始图像采集。
(3)流开启标志设置streaming
设置为1。
vb2_streamon
->vb2_internal_streamon
->vb2_start_streaming
// 将queued\_list上的所有缓冲区添加到驱动中
list\_for\_each\_entry(vb, &q->queued_list, queued_entry)
__enqueue_in_driver
vb->state = VB2_BUF_STATE_ACTIVE // 设置缓冲区状态
atomic\_inc(&q->owned_by_drv_count) // 增加驱动使用缓冲区的计数
// 对每一缓冲区的plane调用prepare函数
all\_void\_memop(vb, prepare, vb->planes[plane].mem_priv)
**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
![img](https://img-blog.csdnimg.cn/img_convert/2f655c1b3fd406ca2c893df36f964274.png)
![img](https://img-blog.csdnimg.cn/img_convert/9772720b3e0de655335e9dc1906dc915.png)
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
__enqueue_in_driver
vb->state = VB2_BUF_STATE_ACTIVE // 设置缓冲区状态
atomic\_inc(&q->owned_by_drv_count) // 增加驱动使用缓冲区的计数
// 对每一缓冲区的plane调用prepare函数
all\_void\_memop(vb, prepare, vb->planes[plane].mem_priv)
**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
[外链图片转存中...(img-szWTXc43-1715672179146)]
[外链图片转存中...(img-h9AzS0VR-1715672179147)]
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**