tiny4412 linux-4.2 移植(十三)v4l2 camera(2)从v4l2 api 深入框架

tiny4412 linux-4.2 移植(十三)v4l2 camera(2)从v4l2 api 深入框架

 


上一节我们了解了v4l2 api的使用方法,这一节我们来看下相关框架。不过这里先不介绍media framework,media的相关内容后面的文章再讲。

框架图

 

 

这个框架图分为4个部分:用户空间、v4l2核心、平台驱动、寄存器。这里的平台驱动指的是camera interface(CAMIF)驱动,例如三星的fimc驱动,下面我会以fimc驱动为例子讲述这里的平台驱动。上一节我们知道了获取摄像头图像是一般流程:
1、以O_RDWR | O_NONBLOCK的方式open设备节点,为啥是O_NONBLOCK,因为在VIDIOC_DQBUF的时候如果在输出队列中没有数据,那默认是阻塞的。
2、通过VIDIOC_QUERYCAP获取摄像头的相关信息。
3、通过VIDIOC_S_FMT设置摄像头格式
4、通过VIDIOC_REQBUFS请求buffer,一般是4个buffer。
5、通过VIDIOC_QUERYBUF查询buffer,如果buffer已请求成功,则调用mmap映射驱动分配的buffer到应用层
6、通过VIDIOC_QBUF把buffer放到传入队列
7、通过VIDIOC_STREAMON启动流传输
8、在文件描述符上poll或者select等待数据
9、被唤醒后通过VIDIOC_DQBUF从传出队列中取出数据,同时会告诉你数据放在哪个buffer。
10、处理图像、显示图像,然后回到6操作继续。
这个框架图以buffer为核心描述了VIDIOC_REQBUFS、VIDIOC_QBUF、VIDIOC_STREAMON和VIDIOC_DQBUF涉及到的操作。我们以这几个ioctl宏来讲下v4l2框架。

VIDIOC_REQBUFS
fimc驱动实现了v4l2_ioctl_ops的成员函数vidioc_reqbufs。通过对/dev/videox节点的VIDIOC_REQBUFS操作会调用到vb2_ioctl_reqbufs,来看下vb2_ioctl_reqbufs里面的操作。

//注:以缩进的形式或者"---->"来表示函数调用。
int vb2_ioctl_reqbufs(struct file *file, void *priv,struct v4l2_requestbuffers *p){
struct video_device *vdev = video_devdata(file);
.....
res = vb2_core_reqbufs(vdev->queue, p->memory, &p->count);---->
---> {
__vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes) --->
-->{
for (buffer = 0; buffer < num_buffers; ++buffer) {//用户层指定的buffer数量
struct vb2_buffer *vb;
vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
vb->vb2_queue = q;
..... //fill vb
if (memory == VB2_MEMORY_MMAP) { //上层指定stream I/O方式为mmap
ret = __vb2_buf_mem_alloc(vb);
mem_priv = call_ptr_memop(vb, alloc,.....);
struct vb2_dc_buf *buf;
buf = kzalloc(sizeof *buf, GFP_KERNEL);
buf->cookie = dma_alloc_attrs(dev, size,
&buf->dma_addr,GFP_KERNEL | gfp_flags, buf->attrs);
buf->dma_dir = dma_dir;
return buf;
vb->planes[plane].mem_priv = mem_priv;
}
}
}
}
}

在这里,我们可以清晰的看到请求的buffer实际上就是vb2_buffer,我们应用层通过struct v4l2_requestbuffers的count成员来指定申请的buffer数量,指定多少个buffer就分配多少个vb2_buffer。再往__vb2_buf_mem_alloc分析代码,可以发现里面还为分配的buffer申请了dma相关资源。这些资源会在VIDIOC_QBUF和mmap的时候用到,一个是把dma输出地址写入cameif dma输出寄存器,另一个是映射一个一致性dma内存到应用空间。

mmap
在mmap buffer的时候会利用到前面申请的dma资源,下面的dma_mmap_attrs会映射一个一致性dma内存到应用空间。

//注:以缩进的形式或者"---->"来表示函数调用。
mmap
vb2_dc_mmap(void *buf_priv, struct vm_area_struct *vma) //mmap刚才分配的dma地址到虚拟内存,即到应用层
ret = dma_mmap_attrs(buf->dev, vma, buf->cookie,buf->dma_addr, buf->size, buf->attrs)


VIDIOC_QBUF
这个操作会把前面分配的buffer放入传入队列中,也就是放入vb2_queue的queued_list中。前面的请求buffer操作申请了v4l2_requestbuffers.count个vb2_buffer,并且做了初始化的操作,但是我们应用空间也有一个类似的v4l2_buffer,那这两个buffer有什么关系呢?在我看来这两个buffer是一个映射关系,在QBUF的时候会把v4l2_buffer的部分数据信息拷贝到vb2_buffer中。具体的拷贝动作在vb2_fill_vb2_v4l2_buffer中,它会把v4l2_buffer的planes等信息拷贝给vb2_buffer。

//注:以缩进的形式或者"---->"来表示函数调用。
vb2_qbuf
vb2_queue_or_prepare_buf
vb2_fill_vb2_v4l2_buffer(vb, b) //(usrspace data)v4l2_buffer data to vb2_buffer
vb2_core_qbuf ---->
--->{
/* Fill buffer information for the userspace */
if (pb) {
call_void_bufop(q, copy_timestamp, vb, pb);
call_void_bufop(q, fill_user_buffer, vb, pb);
}
if (!vb->prepared) { //这里还没有准备,所以要prepare
ret = __buf_prepare(vb); ---->
---->{
switch (q->memory) {
case VB2_MEMORY_MMAP: //
ret = __prepare_mmap(vb);---->
---->{ //struct vb2_buffer *vb
//fill planes
ret = call_bufop(vb->vb2_queue, fill_vb2_buffer, vb, vb->planes);
return ret ? ret : call_vb_qop(vb, buf_prepare, vb);
}
}
vb->prepared = true;
}
}
//添加到queued buffers list中,这个buffer会在dqbuf dequeued的时候被取出
list_add_tail(&vb->queued_entry, &q->queued_list);
__enqueue_in_driver(vb);
call_void_vb_qop(vb, buf_queue, vb);---->
----->void buffer_queue(struct vb2_buffer *vb) {//in fimc_capture.c
//设置dma地址到fimc 寄存器中
}
}

VIDIOC_STREAMON
fimc驱动实现了v4l2_ioctl_ops的成员函数vidioc_streamon,这个函数会调用到vb2_ioctl_streamon,来看下vb2_ioctl_streamon会去做什么操作。

//注:以缩进的形式或者"---->"来表示函数调用。
vb2_ioctl_streamon
vb2_streamon
vb2_core_streamon
vb2_start_streaming
ret = call_qop(q, start_streaming, q,atomic_read(&q->owned_by_drv_count));
start_streaming //(struct vb2_ops fimc_capture_qops)
fimc_capture_hw_init //写寄存器
fimc_hw_set_out_dma
fimc_activate_capture //写寄存器
1
2
3
4
5
6
7
8
9
10
其实streamon的主要操作是调用平台驱动往cameraif的流传输寄存器写入数据,一般就是写入输出dma的地址,然后启动摄像头流模式传输。

中断
当cameraif接受到图像数据的时候,会产生硬件中断。在4412的fimc驱动中会这样处理:

//注:以缩进的形式或者"---->"来表示函数调用。
fimc_capture_irq_handler
vb2_buffer_done(&v_buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
list_add_tail(&vb->done_entry, &q->done_list);//把完成数据填充的buffer放入vb2_queue的done_list中done_list
wake_up(&q->done_wq); //唤醒在等待队列

VIDIOC_DQBUF
这个操作会调用到vb2_dqbuf,这个函数会从done_list中取出vb2_buffer,然后从vb2_buffer提取出相关信息保存给应用层的v4l2_buffer。当函数返回后,就可以在应用拿到对应的buffer数据了。

//注:以缩进的形式或者"---->"来表示函数调用。
vb2_dqbuf
vb2_core_dqbuf
__vb2_get_done_vb(....,struct vb2_buffer **vb,....)
__vb2_wait_for_done_vb //wait for a buffer to become available for dequeuing
wait_event_interruptible(q->done_wq,.....)
*vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry)
call_void_vb_qop(vb, buf_finish, vb);
vb->prepared = false;
if (pb) //userspace 有提供v4l2_buffer,所以会进入这里
call_void_bufop(q, fill_user_buffer, vb, pb); --->
--->__fill_v4l2_buffer(struct vb2_buffer *vb, void *pb){
//fill in a struct v4l2_buffer with information to be returned to userspace
for (plane = 0; plane < vb->num_planes; ++plane) {
struct v4l2_plane *pdst = &b->m.planes[plane];
struct vb2_plane *psrc = &vb->planes[plane];
pdst->bytesused = psrc->bytesused;
pdst->length = psrc->length;
if (q->memory == VB2_MEMORY_MMAP) //走这里,
pdst->m.mem_offset = psrc->m.offset;
else if (q->memory == VB2_MEMORY_USERPTR)
pdst->m.userptr = psrc->m.userptr;
else if (q->memory == VB2_MEMORY_DMABUF)
pdst->m.fd = psrc->m.fd;
pdst->data_offset = psrc->data_offset;
memset(pdst->reserved, 0, sizeof(pdst->reserved));
}
}
list_del(&vb->queued_entry);

posted @ 2021-12-09 15:46  DMCF  阅读(596)  评论(0编辑  收藏  举报