深入理解linux核心v4l2框架之videobuf
Videobuf
下面來介紹以下videobuffer相關的一些東西。
V4L2核心api提供了一套標準的方法來處理視訊緩衝,這些方法允許驅動實現read(),mmap(), overlay()等操作。同樣也有方法支援DMA的scatter/gather操作,並且支援vmallocbuffer(這個大多用在USB驅動上)。
videobuf層功能是一種在v4l2驅動和使用者空間當中的依附層,這話看起來有點繞,說白了就是提供一種功能框架,用來分配和管理視訊緩衝區,它相對獨立,卻又被v4l2驅動使用。它有一組功能函式集用來實現許多標準的POSIX系統呼叫,包括read(),poll()和mmap()等等,還有一組功能函式集用來實現流式
緩衝型別
並不是所有的視訊裝置都使用相同的緩衝型別。實際上,有三種通用的型別:
–被分散在物理和核心虛擬地址空間的緩衝,幾乎所有的使用者空間緩衝都是這種型別,
如果可能的話分配核心空間的緩衝也很有意義,但是不幸的是,這個通常需要那些支持離散聚合DMA操作的硬體裝置。
–物理上離散的但是虛擬地址是連續的,換句話說,就是用vmalloc分配的核心緩衝。這些緩衝很難用於DMA操作。
– 物理上連續的緩衝。
videobuf可以很好地處理這三種類型的緩衝,但是在此之前,驅動程式作者必須選擇一種型別,並且以此型別為基礎設計驅動。
資料結構,回撥函式和初始化
根據選擇的型別,包含不同的標頭檔案,這些標頭檔案在include/media/下面
<media/videobuf-dma-sg.h>
<media/videobuf-vmalloc.h>
<media/videobuf-dma-contig.h>
v4l2驅動需要包含一個videobuf_queue的例項用來管理緩衝佇列,同時還要一個連結串列來維護這個佇列,另外還要一箇中斷安全的spin_lock來保護佇列的操作。
下一步就是要填充一個回撥函式集來處理實際的緩衝區佇列,這個函式集用videobuf_queue_ops來描述:
struct videobuf_queue_ops {
int *(buf_setup)(struct videobuf_queue*q, uint *count, uint *size);
int *(buf_prepare)(structvideobuf_queue *q, struct videobuf_buffer *vb,
enum v4l2_field field);
void *(buf_queue)(structvideobuf_queue*q,struct videobuf_buffer *vb);
void *(buf_release)(...);
}
buf_setup在IO處理請求之前被呼叫。目的是告訴videobuf關於IO的資訊。 count引數提供一個緩衝區個數的參考,驅動必須檢查它的合理性,一個經驗是大於等於2,小於等於32個。Size引數指定了每一幀資料的大小。
buf_prepare每一個緩衝(videobuf_buffer結構描述的)將被傳遞給該回調函式,用來配置緩衝的height,width和fileds。如果field引數被設定為 VIDEOBUF_NEEDS_INIT,那麼驅動將把vb傳遞給videobuf_iolock()這個函式。除此之外,該回調函式通常也將為vb分配記憶體,最後把vb的狀態置為VIDEOBUF_PREPARED。
buf_queue當一個vb需要被放入IO請求佇列時,呼叫該回調。它將把這個buffer放到可用的buffer連結串列當中去,然後把狀態置為VIDEOBUF_QUEUED。
buf_release當一個buffer不再使用的時候,呼叫該回調函式。驅動必須保證 buffer上沒有活躍的IO請求,之後就可以將這個buffer傳遞給合適的 free函式,根據申請的buffer型別呼叫對應的釋放函式:
scatter/gather型別的呼叫
videobuf_dma_unmap(structvideobuf_queue, videobuf_dmabuf)
videobuf_dma_free(videobuf_dmabuf)
vmalloc型別的呼叫
videobuf_vmalloc_free(videobuf_buffer)
contiguous型別的呼叫
videobuf_dma_contig_free(videobuf_queue,videobuf_buffer)
有一種方法可以保證buffer上沒有IO請求,呼叫函式
videobuf_waiton(videobuf_buffer,non_blocking, intr)
檔案操作(v4l2_file_operations)
到了這兒,很多工作也就做完了,剩下的事情就是將對videobuf的呼叫傳遞給具體的驅動實現了。首先就是開啟操作,這個操作要先對videobuf_queue進行初始化,初始化取決於申請的buffer是什麼型別,有如下三種初始化函式可供呼叫:
void videobuf_queue_sg_init(structvideobuf_queue *q,
struct videobuf_queue_ops *ops,
struct device *dev,
spinlock_t *irqlock,
enum v4l2_buf_type type,
enum v4l2_field_ field,
unsigned int msize,
void *priv,
struct mutex *ext_lock)
void videobuf_queue_vmalloc_init(structvideobuf_queue *q,
struct videobuf_queue_ops *ops,
struct device *dev,
spinlock_t *irqlock,
enum v4l2_buf_type type,
enum v4l2_field field,
unsigned int mszie,
void *priv,
struct mutex *ext_lock);
voidvideobuf_queue_dma_contig_init(struct videobuf_queue *q,
struct videobuf_queue_ops *ops,
struct device *dev,
spinlock_t *irqlock,
enum v4l2_buf_type type,
enum v4l2_field field,
unsigned int mszie,
void *priv,
struct mutex *ext_lock);
以上三種初始化函式,有相同的引數,這些引數的從他們的名稱就可以看出來鎖代表的意義是什麼。
這裡著重說下v4l2_buf_type型別,
V4L2_BUF_TYPE_VIDEO_CAPTURE 指定buf的型別為capture,用於視訊捕獲裝置
V4L2_BUF_TYPE_VIDEO_OUTPUT 指定buf的型別output,用於視訊輸出裝置
V4L2_BUF_TYPE_VIDEO_OVERLAY 指定buf的型別為overlay,用於overlay裝置
V4L2_BUF_TYPE_VBI_CAPTURE 用於vbi捕獲裝置
V4L2_BUF_TYPE_VBI_OUTPUT 用於vbi輸出裝置
V4L2_BUF_TYPE_SLICED_VBI_CAPTURE 用於切片vbi捕獲裝置
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT 用於切片vbi輸出裝置
V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY 用於視訊輸出overlay裝置
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 用於多平面儲存格式的視訊捕獲裝置
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE用於多平面儲存格式的視訊輸出裝置
v4l2_field指定video的field,也就是說interleaved或者progressive的,一般都是指定為V4L2_FIELD_NONE,用於逐行掃描的裝置。
V4L2捕獲裝置驅動可以支援兩種API:read()系統呼叫和更為複雜的流機制。一般的做法是兩種都支援以確保所有的應用都可以使用該裝置。videobuf框架使得這種驅動的編寫變得更為簡單。比如說要實現read()系統呼叫,那麼驅動程式只需要呼叫
ssize_t videobuf_read_one(structvideobuf_queue *q,
char __user *data, size_t count,loff_t *ppos, int nonblocking)
ssize_t videobuf_read_streaming(structvideobuf_queue *q, char __user *data, size_t count, loff_t *ppos,int vbihack, int nonblocking)
這兩個函式都是把幀資料讀入到data當中,然後返回實際上讀取的位元組數。不同的是前者只讀取一幀資料,而後者可以選擇讀取多幀。一個典型的應用read()系統呼叫必須開啟捕獲裝置,然後返回之前停止該裝置。
poll()系統呼叫通常由以下函式來實現:
unsigned intvideobuf_poll_stream(struct file *file, struct videobuf_queue *q,
poll_table *wait)
注意,實際最終使用的q是可用的第一個buffer。
當核心空間緩衝的流IO請求完成後,驅動還必須支援mmap系統呼叫以使能使用者空間可以訪問data資料。在v4l2驅動中,通常很複雜的mmap的實現被簡化了,只需要呼叫下面這個函式就可以了:
int videobuf_mmap_mapper(structvideobuf_queue *q,
struct vma_area_struct * vma)
剩下的事情就交給videobuf核心層來完成好了。
release函式需要呼叫兩個單獨的函式來完成:
void videobuf_stop(structvideobuf_queue *q);
int videobuf_mmap_free(structvideobuf_queue *q)
前者終止所有buffer的IO操作。後者保證所有的buffer被unmap掉,如果已經被unmap掉的話,這個buffer就會被傳遞給buf_release回撥函式。如果buffer還沒有被unmap,那麼後者將返回一個錯誤程式碼。
Ioctl操作:
v4l2api涵蓋了很長一組驅動回撥函式來響應使用者的ioctl操作,有很大一部分和流IO操作相關的都是直接呼叫到videobuf裡面來。相關的函式如下:
int videobuf_reqbufs(structvideobuf_queue *q,
structv4l2_requestbuffers *req);
int videobuf_querybuf(structvideobuf_queue *q, struct v4l2_buffer *vb)
int videobuf_qbuf(strurctvideobuf_queue *q, struct v4l2_buffer *vb)
int videobuf_dqbuf(structvideobuf_queue *q, struct v4l2_buffer *vb)
int videobuf_streamon(structvideobuf_queue *q);
int videobuf_streamoff(structvideobuf_queue *q);
Buffer的分配
講到這兒,我們討論了很多關於buffer的話題,但是卻沒有提到他們是怎麼分配的。Scatter/gather例子比較複雜,驅動程式可以完全讓videobuf層去完成buffer的分配,在這種情況下,buffer將被分配為匿名使用者空間頁並且實際上將非常分散。如果應用程式使用使用者空間的buffer的話,驅動也就不需要分配了,videobuf層將小心的呼叫get_user_pages()並且填充離散列表陣列(scatterlistarray)。
如果驅動程式要自己做記憶體分配,那麼將在vidioc_reqbufs函式中進行,在呼叫了videobuf_reqbufs()之後,首先第一步就是要呼叫到
struct videobuf_dmabuf*videobuf_to_dma(struct videobuf_buffer *buf)
返回的videobuf_dmabuf包含了一對相關的域
struct scatterlist *sglist;
int sglen;
驅動必須分配合適大小的scatterlist陣列,並且將分配的記憶體片和指標對應起來,sglen指定了scatterlist陣列的大小。
驅動當中如果使用了vmalloc()來分配記憶體的話,就不用關心buffer的分配了,videobuf層將處理具體的細節,一些驅動程式使用了小技巧,就是在系統啟動的時候就分配好了dma記憶體,以避免動態申請有的時候會申請不到的問題,但是對於此類的設計,videobuf層目前還不能很好的勝任。在3.0以上的核心當中,出現了一種新的框架videobuf2,已經解決了這個問題,我們會在後面詳細介紹這個框架。
Filling緩衝區
videobuf層的最後一部分就是關於將幀資料傳遞到buffer中的實現。這一部分沒有直接的回撥函式,通常都是在裝置的中斷響應中來完成。對於所有型別的驅動,流程大概是這個樣子的:
– 獲取下一個buffer,並且確保有人正在等待這個buffer
– 得到一個記憶體指標,然後將視訊資料放到那個地方
-- 標記buffer完成,並且喚醒等待的程序
第一步,buffer可以通過驅動管理的一個連結串列獲得,這個連結串列由buf_queue回撥函式填充,所以驅動最先要是連結串列初始化為空,並且如果連結串列當中的buffer沒有一個程序在其上等待的話,是不能被移除或者填充的。
另外buffer在被map到dma之前,要把它的狀態設定為VIDEOBUF_ACTIVE,這將保證在裝置傳輸資料的時候videobuf層不去嘗試任何操作。
第二步,得到一個記憶體指標,對於scatter/gather型別的記憶體來說,可以從scatterlist當中找到記憶體指標;對於vmalloc型別的來說呼叫
void * videobuf_to_vmalloc(structvideobuf_buffer *vb)
對於連續實體記憶體型別來說呼叫
dma_addr_tvideobuf_to_dma_contig(struct videobuf_buffer *buf)
第三步,就是設定videobuf_buffer中的大小,並且把buffer的狀態設定為VIDEOBUF_DONE,然後在完成佇列上呼叫wake_up().到此,buffer就真正的屬於videobuf層了,驅動程式不用再去關心它如何被排程。
最後,一個很好的關於v4l2的例子就是drviers/media/video/vivi.c,它使用了vmalloc型別的videobuf,可以通過閱讀這份例子來學習v4l2驅動的寫法。