The videobuf2 API
Author:CJOK
Contact:cjok.liao#gmail.com
SinaWeibo:@廖野cjok
原文地址:http://lwn.net/Articles/447435/
Video4linux2驅動主要負責從sensor(通常是通過DMA)上獲取視頻資料然後把這些視頻幀傳輸到使用者空間,大量資料的傳輸是性能要考慮。出於此目的,V4L2定義了複雜的API去處理流資料。V4L2子系統為實現這些API也加入了不少複雜的程式碼,當然大部分的程式碼沿用了V4L。為 了使驅動工程師更方便實現視頻驅動程式碼,videobuf子系統提供了一組用於管理流IO buffer的介面。
Linux kernel在不斷的更新和完善,videobuf在2009已經被videobuf2取代,相比videobuf,videobuf2更加完善,更加實用。本文用於介紹videobuf2。
為什麼出現videobuf2?雖然videobuf工作的很好,但是在一些方面並不是那麼完善,各種不同的API相當依賴buffer的類型,也沒有提供相關的介面用於視訊緩衝區管理。Videobuf2提供了一組統一的API介面,允許驅動自己有更多的配置。
Buffers
像原來的的videobuf一樣,videobuf2也實現了三種buffer類型。
Vmalloc buffer這 類buffer由vmalloc()分配,因此在內核空間虛擬位址上是連續的,drivers working with these buffers almost invariably end up copyingthe data once as it passes through the system;
contiguous DMA buffers在實體記憶體上連續,通常硬體設備需要在連續物理位址空間上執行DMA操作;
S/G DMA buffers在實體記憶體上是不連續的,如果硬體上支援scatter/gather DMA,驅動可以使用這種方式。
驅動程式使用不同的類型buffer,需要包含以下對應的標頭檔:
#include <media/videobuf2-vmalloc.h>
#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-dma-sg.h>
和videobuf相比,videobuf2有一個好處就是能夠是一個驅動程式同時支援多種類型的buffer,上面的標頭檔不會相互衝突,而且videobuf2提供的這三種buffer類型的操作介面非常類似。
結構體vb2_buffer代表一個視訊緩衝區,此結構體定義在<media/videobuf2-core.h>。通常驅動程式都想 保存每個緩衝區特有的資訊,因此,驅動程式會自己定義一個緩衝區結構體,然後把vb2_buffer嵌入在其中。可是,videobuf2作者沒有讀過 Neil Brown’s object-oriented design patterns in the kernel(內核中的物件導向設計模型),沒有提供分配這些結構體的函數。這就意味著驅動程式需要把自訂結構體的大小通知videobuf2子系統, 而且vb2_buffer實例必須放在自訂結構體的第一個成員域。
管理buffer的回呼函數
一個視頻驅動程式需要實現一組用於管理buffer的回呼函數並註冊到videobuf2子系統,在原來的videobuf中也有類似的回呼函數。這五個函數如下:
int (*buf_init)(struct vb2_buffer *vb);
int (*buf_prepare)(struct vb2_buffer *vb);
void (*buf_queue)(struct vb2_buffer *vb);
int (*buf_finish)(struct vb2_buffer *vb);
void (*buf_cleanup)(struct vb2_buffer *vb);
videobuf2會呼叫buf_init()對每個分配好的新buffer執行一些必要的額外初始化,如果buffer_init返回錯誤碼將會中斷緩衝佇列的設置;
buf_prepare()在用戶空間入隊這些buffer時被呼叫(比如,回應VIDIOC_QBUF操作),在這些buffer被流I/O使用之前完成一些必要的準備工作。呼叫Buf_queue()把buffers傳遞給驅動程式,開始流I/O操作。
譯者注:buf_queue是回應VIDIOC_STREAMON操作,開始流IO操作,然後會往buffers中填充視頻資料。
Buf_finish()在buffers傳回到用戶空間前被呼叫,你可能需要對這個回呼函數考慮的問題是,驅動程式已經知道buffer有一幀新 的資料傳遞到使用者空間,事實上,還需要告訴videobuf2。一個可能的答案是完成流I/O資料到buffer的操作通常是在中斷上下文處理的,而 buf_finish()會在進程上下文呼叫。
譯者注:buf_finish()在每個buffer出隊傳遞到用戶空間前被呼叫,通常在用戶空間訪問buffers前做一些必要操作。
Buf_cleanup()在buffer釋放前被呼叫,用來做一些清理工作。
Other videobuf2 callbacks
在原來的videobuf版本中,驅動程式只提供上述的回呼函數來管理緩衝區,videobuf2新增了一些回呼函數用來完善對緩衝區的操作。
int (*queue_setup)(
struct vb2_queue *q,
unsigned int *num_buffers,
unsigned int *num_planes,
unsigned long sizes[],
void *alloc_ctxs[]);
queue_setup()在用戶空間執行ioctl:VIDIOC_REQBUFS操作時被呼叫,作用是用來建立緩衝佇列。結構體 vb2_queue用來描述緩衝佇列,num_buffers參數是應用程式申請的緩衝數。Num_planes是每個buffer中的視頻層數目。 Size陣列包含了每個視頻層的大小(bytes)。
Alloc_ctxs指標陣列包含了每個視頻層的“分配上下文”,當前還只被contiguous DMA模式使用到。使用contiguous DMA的驅動程式需要呼叫獲取/登出上下文資訊:
void *vb2_dma_contig_init_ctx(struct device *dev)
voidvb2_dma_contig_cleanup_ctx(void *alloc_ctx)
下面兩個回呼函數用於開始和停止獲取視頻資料:
int (*start_streaming)(struct vb2_queue *q);
int (*stop_streaming)(struct vb2_queue *q);
start_streaming()用來回應ioctl:VIDIOC_STREAMON操 作開始抓取視頻資料,如果驅動程式實現了read()方法也會呼叫到start_streaming()。
Stop_streaming()用來回應 ioctl:VIDIOC_STREAMOFF操作停止抓取視頻資料,等待DMA停止後函數才會返回。這裡值得注意的是,呼叫 stop_streaming()後,videobuf2子系統會回收掉所有傳遞到驅動程式的buffers,此時驅動程式不能訪問這些buffers。
Locking
最後兩個回呼函數是用於保護視頻設備訪問——鎖機制:
呼叫驅動程式訪問視頻設備時需要獲取到鎖;
Videobuf2子系統呼叫到驅動的回呼函數前應該先獲取到設備訪問鎖。比如,使用者空間獲取視頻資料會導致start_streaming()呼叫,需 要通過videobuf2子系統呼叫到相關的設備驅動程式,所以呼叫start_streaming()時,將要持有設備鎖。
在這樣的情況下,有一個需要考慮的問題:用戶空間呼叫ioctl:VIDIOC_DQBUF從內核獲取緩衝區的資料,當緩衝區不可用時,進程可能會阻塞。
void (*wait_prepare)(struct vb2_queue *q);
void (*wait_finish)(struct vb2_queue *q);
在進行VIDIOC_DQBUF操作阻塞等待一個buffer資料之前呼叫wait_prepare()去釋放設備鎖,一旦退出阻塞,會呼叫 wait_finish()獲取設備鎖。可能這兩個回呼函數用release_lock()和reacquire_lock()名字更好。
Queue setup
上面介紹了vb2_ops,瞭解了視頻驅動程式如何註冊到videobuf2子系統。實現需要構建vb2_ops,並實現其中的回呼函數:
static const struct vb2_ops my_special_callbacks = {
.queue_setup = my_special_queue_setup,
/* ... */
};
然後,建立一個videobuf2佇列(通常是在設備註冊或者設備open的時候),驅動需要分配一個vb2_queue結構體:
struct vb2_queue {
enum v4l2_buf_type type;
unsigned int io_modes;
unsigned int io_flags;
const struct vb2_ops *ops;
const struct vb2_mem_ops *mem_ops;
void *drv_priv;
unsigned int buf_struct_size;
/* Lots of private stuff omitted */
};
這個結構體需要實現上面的成員,type代表buffer類型,通常是V4L2_BUF_TYPE_VIDEO_CAPTURE,io_modes是一個標誌位元,用來描述以何種方式來訪問buffer:
VB2_MMAP:緩衝區由內核空間分配,通過mmap映射緩衝區到用戶空間。通常vmalloc和contiguous DMA buffers來分配此方式的緩衝區;
VB2_USERPTR:緩衝區由使用者空間分配,通常需要設備支援集散IO才可以分配用戶空間緩衝區。有趣的是,videobuf2支持分配連續的緩衝區 在使用者空間,但是需要運用一些特別的機制,比如android的pmem驅動。在使用者空間分配連續大量的頁面是不支援的。
VB2_READ,VB2_WRITE:使用者空間緩衝區提供read()和write()方式訪問視頻設備。
Mem_ops成員是緩衝區操作函數集合,提供給上層videobuf2子系統操作緩衝區,內核已經實現三組供我們使 用: vb2_vmalloc_memops,vb2_dma_contig_memops和 vb2_dma_sg_memops,如果這三組都不符 合,驅動工程師可以自己實現一組。在撰寫本文時,內核主線還有沒有提供記憶體操作的驅動程式。
最後,drv_priv是驅動私有的指標(通常指向device結構),buf_struct_size用於表示buffer結構體的大小。Vb2_queue結構體實現後,呼叫以下函數進行初始化:
intvb2_queue_init(struct vb2_queue *q)
在設備關閉時呼叫 vb2_queue_release()進行銷毀。
Device operations
到現在我們已經分析了videobuf2底層的基礎設施,接下來看下videobuf2子系統是怎麼把使用者空間的操作和視頻設備驅動關聯起來。通常 第一步驅動程式需要實現大量的ioctl()呼叫介面,大部分的函數介面只是簡單的獲取設備鎖,然後直接呼叫videobuf2實現的ops。下面我們來 看這些ops:
int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b);
int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req);
int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b);
int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b,
bool nonblocking);
int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type);
int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type);
int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma);
unsigned int vb2_poll(struct vb2_queue *q, struct file *file,
poll_table *wait);
size_t vb2_read(struct vb2_queue *q, char __user *data, size_t count,
loff_t *ppos, int nonblock);
size_t vb2_write(struct vb2_queue *q, char __user *data, size_t count,
loff_t *ppos, int nonblock);
這些函數介面遮罩了底層大量緩衝區管理的細節,使程式碼十分簡潔,提供了統一的介面給上層呼叫。
最後還有一點工作需要做:獲取資料放到緩衝區裡。對於vmalloc buffers,通常是通過memcpy()這樣的操作來完成,但是videobuf2提供了一個有用函數:
void *vb2_plane_vaddr(struct vb2_buffer*vb, unsigned int plane_no)
返回在緩衝區中給定的內核虛擬位址。
Contiguous DMA驅動需要獲取處理
dma_addr_t vb2_dma_contig_plane_paddr(struct vb2_buffer *vb,
unsigned int plane_no);
對於scatter/gather DMA驅動,提供的介面略顯複雜:
struct vb2_dma_sg_desc {
unsigned long size;
unsigned int num_pages;
struct scatterlist *sglist;
};
static inline struct vb2_dma_sg_desc *vb2_dma_sg_plane_desc(
struct vb2_buffer *vb, unsigned int plane_no)
驅動程式從videobuf2獲取到能用於設備的scatterlist。
對於這三種情況,緩衝區將會填充滿需要傳遞到使用者空間的幀資料。Vb2_buffer結構體中內嵌了一個v4l2_buf,通常包含圖像大小、序號、時間幀等資訊。最後呼叫以下函數:
void vb2_buffer_done(struct vb2_buffer *vb,enum vb2_buffer_state state)
state參數:
VB2_BUF_STATE_DONE,通知videobuf2子系統有一個buffer處理完成;
VB2_BUF_STATE_ERROR,通知videobuf2子系統有一個buffer處理時出現ERROR。
本文大致介紹總結videobuf2 API,如果想瞭解完整的介面函數,可以參考內核源碼。內核提供一個虛擬視頻驅動實例供參考(vivi.c)。