1. 程式人生 > 其它 >tiny4412 linux-4.2 移植(十三)v4l2 camera(2)從v4l2 api 深入框架

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);