vivi代码
v4l2测试代码
step 5 : 设置缓存
1,申请缓存
struct v4l2_requestbuffers req;
req.count = nr_bufs; //缓存数量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
这里会调用到v4l_reqbufs
static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops,
struct file *file, void *fh, void *arg)
{
struct v4l2_requestbuffers *p = arg;
int ret = check_fmt(file, p->type);
if (ret)
return ret;
CLEAR_AFTER_FIELD(p, memory);
return ops->vidioc_reqbufs(file, fh, p);
}
这里的 ops->vidioc_reqbufs对应vivi驱动的 vidioc_requbufs
static int vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
struct vivi_dev *dev = video_drvdata(file);
return vb2_reqbufs(&dev->vb_vidq, p);
}
这里就开始分析如何申请缓存了,下面的可能大部分都是贴代码,比较枯燥。但是希望可以从枯燥中学到东西,这样就值得了。
int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
/*
* vb2_verify_memory_type
* 1. 检测memory的值,需要是下面3种类型才可以
* VB2_MEMORY_MMAP
* VB2_MEMORY_USERPTR
* VB2_MEMORY_DMABUF
* 应用代码中传入的是MMAP,没有问题
*
* 2. type值要等于q->type
* 这里需要明白一个问题参数 struct vb2_queue *q是哪里来的?
* 根据函数调用知道 q = &dev->vb_vidq
* vivi驱动vivi_create_instance函数中,有这样的代码
* q = &dev->vb_vidq;
* memset(q, 0, sizeof(dev->vb_vidq));
* q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
* q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
* q->drv_priv = dev;
* q->buf_struct_size = sizeof(struct vivi_buffer);
* q->ops = &vivi_video_qops;
* q->mem_ops = &vb2_vmalloc_memops;
* 可以看到 type等于q->type
*
* 3. 对于当前使用MMAP方式
* q->mem_ops成员 alloc put mmap不能为空
* mem_ops 对应 vb2_vmalloc_memops 不为空,满足条件
*
* 4. q->fileio值要为空
* 这里暂时还没有看到赋值,分析的时候默认为空
*/
int ret = vb2_verify_memory_type(q, req->memory, req->type);
/*
* 接着分析vb2_core_reqbufs
*/
return ret ? ret : vb2_core_reqbufs(q, req->memory, &req->count);
}
接着分析vb2_core_reqbufs
int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
unsigned int *count)
{
unsigned int num_buffers, allocated_buffers, num_planes = 0;
unsigned plane_sizes[VB2_MAX_PLANES] = { };
int ret;
/*
* streaming表示数据流的状态,分析的时候默认为flase
* 对于这个值什么时候true,后面遇到再分析
* 对于waiting_in_dqbuf也同样,遇到再分析
*/
if (q->streaming) {
dprintk(1, "streaming active\n");
return -EBUSY;
}
if (q->waiting_in_dqbuf && *count) {
dprintk(1, "another dup()ped fd is waiting for a buffer\n");
return -EBUSY;
}
/*
* if条件满足,只要符合以下3种就可以
* 1. 申请的缓冲区个数为0,有人会这么写吗?
写0的意义是什么?申请之前申请的buffer???
* 2. 有之前申请且现在没有释放的缓冲区
* 3. memory类型发生了变化,比如mmap变成了dmabuf
*/
if (*count == 0 || q->num_buffers != 0 ||
(q->memory != VB2_MEMORY_UNKNOWN && q->memory != memory)) {
/*
* We already have buffers allocated, so first check if they
* are not in use and can be freed.
*/
mutex_lock(&q->mmap_lock);
/*
* 对于之前是mmap的方式,需要判断之前申请的buffer是不是在使用中
* 对于判断方式后面分析
*/
if (q->memory == VB2_MEMORY_MMAP && __buffers_in_use(q)) {
mutex_unlock(&q->mmap_lock);
dprintk(1, "memory in use, cannot free\n");
return -EBUSY;
}
/*
* Call queue_cancel to clean up any buffers in the PREPARED or
* QUEUED state which is possible if buffers were prepared or
* queued without ever calling STREAMON.
*/
/*
* 释放之前申请的所有的buffer
* 释放方法同样后面分析,这里主要分析申请
* 主意这里会free所有的buffer
* q->num_buffers值会变成0
*/
__vb2_queue_cancel(q);
ret = __vb2_queue_free(q, q->num_buffers);
mutex_unlock(&q->mmap_lock);
if (ret)
return ret;
/*
* In case of REQBUFS(0) return immediately without calling
* driver's queue_setup() callback and allocating resources.
*/
/*
* 申请0个buffer,走到这里好像释放了之前的buffer....
*/
if (*count == 0)
return 0;
}
/*
* 这里我们可以骗自己,认为是正常的申请
* 比如首次申请,申请buffer也不为0
* 给自己减少一些额外的因素
*/
/*
* Make sure the requested values and current defaults are sane.
*/
/*
* VB2_MAX_FRAME 值 32
* min_buffers_needed vivi驱动中并没有设置,所以为0
*/
WARN_ON(q->min_buffers_needed > VB2_MAX_FRAME);
num_buffers = max_t(unsigned int, *count, q->min_buffers_needed);
num_buffers = min_t(unsigned int, num_buffers, VB2_MAX_FRAME);
memset(q->alloc_devs, 0, sizeof(q->alloc_devs));
/*
* 记录本次申请的memory类型
*/
q->memory = memory;
/*
* Ask the driver how many buffers and planes per buffer it requires.
* Driver also sets the size and allocator context for each plane.
*/
/*
* q->ops->queue_setup
* 对应vivi_video_qops.queue_setup 也就是queue_setup
*/
ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes,
plane_sizes, q->alloc_devs);
if (ret)
return ret;
}
新的一天,继续写,,,
分析queue_setup
static int queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
unsigned int *nplanes, unsigned int sizes[],
struct device* alloc_devs[])
{
struct vivi_dev *dev = vb2_get_drv_priv(vq);
unsigned long size;
size = dev->width * dev->height * 2;
/*
* *nbuffers 有机会等于0吗?
* 应该在调用之前就返回了吧
*/
if (0 == *nbuffers)
*nbuffers = 32;
/*
* vid_limit值为16
* vid_limit * 1024 * 1024 = 16Mbytes
* 这里加了一个条件,会影响最终可以申请到底buffers的个数
* 所以说并不是申请多少就一定返回多少的
*/
while (size * *nbuffers > vid_limit * 1024 * 1024)
(*nbuffers)--;
/*
* 对于这个nplanes 字面意思是平面的个数
* 个人的理解
* 比如rgb,rgb是连续排放,所以是不是就是1个平面?
* 对于YUV422P,Y分量放完了,接着U分量,然后接着V分量,这样的话是3个平面?
* 对于YUV422SP,Y分量放完了,UV分量交错放,这样的话是2个平面?
* YUV相关的可以看这篇文章 https://www.jianshu.com/p/3e44c2262775
* 以上只是猜测,待验证
*/
*nplanes = 1;
sizes[0] = size;
/*
* videobuf2-vmalloc allocator is context-less so no need to set
* alloc_ctxs array.
*/
dprintk(dev, 1, "%s, count=%d, size=%ld\n", __func__,
*nbuffers, size);
return 0;
}
回到vb2_core_reqbufs接着分析
/*
* buffer个数,平面个数,平面大小都已经知道了
*/
/* Finally, allocate buffers and video memory */
allocated_buffers =
__vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);
if (allocated_buffers == 0) {
dprintk(1, "memory allocation failed\n");
return -ENOMEM;
}
跟进 分析__vb2_queue_alloc
static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
unsigned int num_buffers, unsigned int num_planes,
const unsigned plane_sizes[VB2_MAX_PLANES])
{
unsigned int buffer, plane;
struct vb2_buffer *vb;
int ret;
/*
* 之前申请的buffer个数加上现在要申请的,总数不能超过VB2_MAX_FRAME
* 对于VIDIOC_REQBUFS ioctl 走到这里q->num_buffers值是0
* 这里为什么还要有个减q->num_buffers的操作呢?
* 其实这是为VIDIOC_CREATE_BUFS ioctl准备的,扩充buffer
*/
/* Ensure that q->num_buffers+num_buffers is below VB2_MAX_FRAME */
num_buffers = min_t(unsigned int, num_buffers,
VB2_MAX_FRAME - q->num_buffers);
for (buffer = 0; buffer < num_buffers; ++buffer) {
/*
* 为每个buffer申请一个struct vb2_buffer结构体,用于管理buffer
*/
/* Allocate videobuf buffer structures */
vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
if (!vb) {
dprintk(1, "memory alloc for buffer struct failed\n");
break;
}
/*
* VB2_BUF_STATE_DEQUEUED,表示buffer在用户空间控制
* 猜测是用户空间发起申请的意思
* 填充vb的其他成员信息
* index: 每个buffer都有自己的编号
*/
vb->state = VB2_BUF_STATE_DEQUEUED;
vb->vb2_queue = q;
vb->num_planes = num_planes;
vb->index = q->num_buffers + buffer;
vb->type = q->type;
vb->memory = memory;
/*
* 这里明显的可以看到,每个buffer还有平面的结构
* 平面个数越多,后面申请的内存空间越大
*/
for (plane = 0; plane < num_planes; ++plane) {
vb->planes[plane].length = plane_sizes[plane];
vb->planes[plane].min_length = plane_sizes[plane];
}
/*
* bufs记录每个vb
*/
q->bufs[vb->index] = vb;
/*
* MMAP是我们这次分析的重点
*/
/* Allocate video buffer memory for the MMAP type */
if (memory == VB2_MEMORY_MMAP) {
ret = __vb2_buf_mem_alloc(vb);
if (ret) {
dprintk(1, "failed allocating memory for buffer %d\n",
buffer);
q->bufs[vb->index] = NULL;
kfree(vb);
break;
}
....
这里要跟进分析 __vb2_buf_mem_alloc
static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
{
struct vb2_queue *q = vb->vb2_queue;
void *mem_priv;
int plane;
int ret = -ENOMEM;
/*
* 针对每一个平面去操作
*/
/*
* Allocate memory for all planes in this buffer
* NOTE: mmapped areas should be page aligned
*/
for (plane = 0; plane < vb->num_planes; ++plane) {
/*
* 主意这里是PAGE_SIZE对齐的
* 比如申请5000byte,最后对齐后大小则是8192byte
* 所以说最终申请到的内存大于等于想要申请的内存
*/
unsigned long size = PAGE_ALIGN(vb->planes[plane].length);
/* Did it wrap around? */
if (size < vb->planes[plane].length)
goto free;
mem_priv = call_ptr_memop(vb, alloc,
q->alloc_devs[plane] ? : q->dev,
q->dma_attrs, size, q->dma_dir, q->gfp_flags);
if (IS_ERR_OR_NULL(mem_priv)) {
if (mem_priv)
ret = PTR_ERR(mem_priv);
goto free;
}
/*
* 这里mem_priv的类型是struct vb2_vmalloc_buf
* 记录每个mem_priv
*/
/* Associate allocator private data with this plane */
vb->planes[plane].mem_priv = mem_priv;
}
return 0;
free:
/* Free already allocated memory if one of the allocations failed */
for (; plane > 0; --plane) {
call_void_memop(vb, put, vb->planes[plane - 1].mem_priv);
vb->planes[plane - 1].mem_priv = NULL;
}
return ret;
}
call_ptr_memop最后解析出来是 vb->vb2_queue->mem_ops->alloc
对应代码 vb2_vmalloc_alloc
static void *vb2_vmalloc_alloc(struct device *dev, unsigned long attrs,
unsigned long size, enum dma_data_direction dma_dir,
gfp_t gfp_flags)
{
struct vb2_vmalloc_buf *buf;
buf = kzalloc(sizeof(*buf), GFP_KERNEL | gfp_flags);
if (!buf)
return ERR_PTR(-ENOMEM);
buf->size = size;
/*
* 对于vmalloc_user具体的实现我也不明白
* 这里贴一下网上的信息,关键信息如下
* 1. 申请一段虚拟地址连续的内存给user space使用
* 2. 添加VM_USERMAP的flag,防止将kernel space的数据泄露到user space
* 摘自博客:https://blog.csdn.net/tiantao2012/article/details/79285721
* 总的来说,这里申请了一个平面大小需要的空间(虚拟地址连续)
*/
buf->vaddr = vmalloc_user(buf->size);
buf->dma_dir = dma_dir;
buf->handler.refcount = &buf->refcount;
buf->handler.put = vb2_vmalloc_put;
buf->handler.arg = buf;
if (!buf->vaddr) {
pr_debug("vmalloc of size %ld failed\n", buf->size);
kfree(buf);
return ERR_PTR(-ENOMEM);
}
/*
* 设置buffer的引用计数
*/
refcount_set(&buf->refcount, 1);
return buf;
}
这里画个图,记录一下buffer的层次结构
回到__vb2_queue_alloc继续分析
if (memory == VB2_MEMORY_MMAP) {
/*
* __vb2_buf_mem_alloc上面已经分析过
*/
ret = __vb2_buf_mem_alloc(vb);
if (ret) {
dprintk(1, "failed allocating memory for buffer %d\n",
buffer);
q->bufs[vb->index] = NULL;
kfree(vb);
break;
}
/*
* 这里主要分析__setup_offset
*/
__setup_offsets(vb);
/*
* Call the driver-provided buffer initialization
* callback, if given. An error in initialization
* results in queue setup failure.
*/
ret = call_vb_qop(vb, buf_init, vb);
if (ret) {
dprintk(1, "buffer %d %p initialization failed\n",
buffer, vb);
__vb2_buf_mem_free(vb);
q->bufs[vb->index] = NULL;
kfree(vb);
break;
}
}
跟进分析 __setup_offset
static void __setup_offsets(struct vb2_buffer *vb)
{
struct vb2_queue *q = vb->vb2_queue;
unsigned int plane;
unsigned long off = 0;
/*
* 对于第一个vb2_buffer,这里不需要处理
* 其off值就是为0
* 对于index非0的情况,下看完下面的for循环再接着看这个
*/
if (vb->index) {
/*
* 之前分析过 vb2_buffer是用来管理buffer的,保存在q->bufs
* 获取上一个编号的vb2_buffer
* prev->num_planes - 1 这里是为了获取上一个vb2_buffer的最后
* 一个plane的信息
* 再次更新off的值,这个off点值就是当前的vb2_buffer的
* planes[0].m.offset的值
* 下面的for循环中就是这么直接赋值的
*/
struct vb2_buffer *prev = q->bufs[vb->index - 1];
struct vb2_plane *p = &prev->planes[prev->num_planes - 1];
off = PAGE_ALIGN(p->m.offset + p->length);
}
/*
* 对于index为0的情况,假设num_planes值为2,也就是2个平面
* plane[0].m.offset值为0
* 那么plane[1].m.offset应该就是
* plane[0].m.offset + PAGE_ALIGN(plane[0].length)
* 其实也就是下面的操作,只是先加了,后PAGE_ALIGN
*
* 对于index非0的情况
* off的值就是当前buffer的planes[plane].m.offset的值
*/
for (plane = 0; plane < vb->num_planes; ++plane) {
vb->planes[plane].m.offset = off;
dprintk(3, "buffer %d, plane %d offset 0x%08lx\n",
vb->index, plane, off);
off += vb->planes[plane].length;
off = PAGE_ALIGN(off);
}
}
那么这个offset的作用是什么???暂时没有看出来具体的作用。
回到__vb2_queue_alloc继续分析
if (memory == VB2_MEMORY_MMAP) {
/*
* __vb2_buf_mem_alloc上面已经分析过
*/
ret = __vb2_buf_mem_alloc(vb);
if (ret) {
dprintk(1, "failed allocating memory for buffer %d\n",
buffer);
q->bufs[vb->index] = NULL;
kfree(vb);
break;
}
/*
* 上面已经分析
*/
__setup_offsets(vb);
/*
* Call the driver-provided buffer initialization
* callback, if given. An error in initialization
* results in queue setup failure.
*/
/*
* 对应vb->vb2_queue->ops->buf_init
* 就是vivi_video_qops.buf_init
*/
ret = call_vb_qop(vb, buf_init, vb);
if (ret) {
dprintk(1, "buffer %d %p initialization failed\n",
buffer, vb);
__vb2_buf_mem_free(vb);
q->bufs[vb->index] = NULL;
kfree(vb);
break;
}
}
vivi_video_qops.buf_init
static int buffer_init(struct vb2_buffer *vb)
{
struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
BUG_ON(NULL == dev->fmt);
return 0;
}
并没有做什么实质性的动作。
到这里分析完了 __vb2_queue_alloc
回到 vb2_core_reqbufs中
int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
unsigned int *count)
{
...
allocated_buffers =
__vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);
if (allocated_buffers == 0) {
dprintk(1, "memory allocation failed\n");
return -ENOMEM;
}
/*
* 既然能走到这里说明allocated_buffers这个值不为0
* 这里会判断allocated_buffers是不是小于驱动中要求的最小min_buffers_needed
* 如果小于的话 将ret给赋值,在这之前ret的值为0
*/
if (allocated_buffers < q->min_buffers_needed)
ret = -ENOMEM;
/*
* 这里的if需要2个条件
* 申请到的buffer个数大于驱动中要求的最小min_buffers_needed
* 申请到的buffer个数小于用户空间期望的buffer
* 这2个要同时满足才会进去
* 这里我们可以这样思考,应用期望申请8个buffer,但是由于某些原因只申请到4个
* 用户空间对于这4个buffer是否满足不需要驱动关心,应用不满意,本次ioctl后
* 直接close就行了
* 这就是驱动的事情了,驱动对未申请到足够的buffer个数是否满意
*/
/*
* Check if driver can handle the allocated number of buffers.
*/
if (!ret && allocated_buffers < num_buffers) {
num_buffers = allocated_buffers;
/*
* num_planes is set by the previous queue_setup(), but since it
* signals to queue_setup() whether it is called from create_bufs()
* vs reqbufs() we zero it here to signal that queue_setup() is
* called for the reqbufs() case.
*/
num_planes = 0;
/*
* 对实际申请的buffer个数是否可以接受
* 接受就返回0,不接受返回非0
*/
ret = call_qop(q, queue_setup, q, &num_buffers,
&num_planes, plane_sizes, q->alloc_devs);
if (!ret && allocated_buffers < num_buffers)
ret = -ENOMEM;
/*
* Either the driver has accepted a smaller number of buffers,
* or .queue_setup() returned an error
*/
}
mutex_lock(&q->mmap_lock);
//总感觉这里不对,待验证后确认
/*
* 对于这里为什么不是 q->num_buffers += allocated_buffers;
* 我专门提了个patch
* 作者回复我VIDIC_REQBUFS ioct会清空所有的buffer
* 在这里q->num_buffers原来的值就是0
*/
q->num_buffers = allocated_buffers;
if (ret < 0) {
/*
* Note: __vb2_queue_free() will subtract 'allocated_buffers'
* from q->num_buffers.
*/
__vb2_queue_free(q, allocated_buffers);
mutex_unlock(&q->mmap_lock);
return ret;
}
mutex_unlock(&q->mmap_lock);
/*
* Return the number of successfully allocated buffers
* to the userspace.
*/
/*
* 将实际申请到的buffer的个数赋值,用于返回到用户空间
*/
*count = allocated_buffers;
/*
* is_output的值为0,所以这里变量waiting_for_buffers为为true
*/
q->waiting_for_buffers = !q->is_output;
return 0;
}
到此这个ioctl过程分析完了,这个过程中,主要明白了buffer中plane的内存的申请到过程。
遗留的一个问题就是offset的作用是什么???
这部分的应用测试代码如下:
struct v4l2_requestbuffers req;
req.count = 5;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
printf("Reqbufs fail\n");
goto err;
}
printf("buffer number: %d\n", req.count);
打印信息:
buffer number: 4
为什么是4呢?
vivi驱动代码queue_setup中
size = dev->width * dev->height * 2;
if (0 == *nbuffers)
*nbuffers = 32;
while (size * *nbuffers > vid_limit * 1024 * 1024)
(*nbuffers)--;
之前设置的分辨率是 1920*1080,vid_limit = 16
所以值为 16*1024*1024 / (1920*1080*2) = 4.xxx ,所以最后得到的buffer个数是4