v4l2驅動框架相對還是挺複雜的,
最好的參考例子有
v4l2-pci-skeleton.c
和
vivi (Virtual Video)
其中vivi在最新的Linux 4.xx版本也變得非常複雜。
所以採用Linux-3.16.74版本作爲學習...
1. 該版本沒有使用platform_driver框架, 故模塊初始化入口函數vivi_init直接進行video設備的註冊。
(如果使用platform driver的框架, 會在probe函數中進行註冊)
2. vivi_init初始化過程, 最主要的動作就是創建vivi實例。
主要函數爲vivi_create_instance
通常的, 裏面會分配好vivi設備描述結構體, vivi_dev
創建v4l2_dev設備, 調用v4l2_device_register註冊一個v4l2設備, 這個相比video_device來說, 相當於一個父節點, 描述的信息更加common, 註冊函數中會對該設備進行一些通用的初始化和資源分配。
下一步是構建video_device並註冊該設備。
關於ctrl_handler, 如亮度, 對比度, 透明度等等暫時先不具體研究。
4. 創建vb2_queue, video設備內部使用的隊列。
該結構體也很複雜, 直接貼圖...
struct vb2_queue {
› enum v4l2_buf_type› › type;
› unsigned int› › › io_modes;
› unsigned int› › › io_flags;
› struct mutex› › › *lock;
› struct v4l2_fh› › › *owner;
› const struct vb2_ops› › *ops;
› const struct vb2_mem_ops› *mem_ops;
› void› › › › *drv_priv;
› unsigned int› › › buf_struct_size;
› u32›› › › timestamp_flags;
› gfp_t› › › › gfp_flags;
› u32›› › › min_buffers_needed;
/* private: internal use only */
› enum v4l2_memory› › memory;
› struct vb2_buffer› › *bufs[VIDEO_MAX_FRAME];
› unsigned int› › › num_buffers;
› struct list_head› › queued_list;
› unsigned int› › › queued_count;
› atomic_t› › › owned_by_drv_count;
› struct list_head› › done_list;
› spinlock_t› › › done_lock;
› wait_queue_head_t› › done_wq;
› void› › › › *alloc_ctx[VIDEO_MAX_PLANES];
› unsigned int› › › plane_sizes[VIDEO_MAX_PLANES];
› unsigned int› › › streaming:1;
› unsigned int› › › start_streaming_called:1;
› unsigned int› › › waiting_for_buffers:1;
m
› struct vb2_fileio_data› › *fileio;
› struct vb2_threadio_data› *threadio;
可以看到vb2_queue內部有兩個隊列, queued_list和done_list
即用戶req bufs並mmap到用戶態後, 調用queue buffer時, 會將這些空buffer放入queued_list,
vivi設備內部線程, 或工作隊列, 或定時器等 隔段時間將要顯示的yuv數據填充到申請的緩存中。
並放入done_list, 即完成隊列中。
如此, 用戶態就能通過dequeue buffer取出done_list中的數據, 進行渲染, 存儲或傳輸等操作了。
5. 繼續填充video_device數據結構, 比如file_operations, ioctls等等
1457 › vfd = &dev->vdev;
1458 › *vfd = vivi_template;
1459 › vfd->debug = debug;
1460 › vfd->v4l2_dev = &dev->v4l2_dev;
1461 › vfd->queue = q;
6. 設備都填充好後, 調用video_register_device即可
ok, 是不是很簡單...
是的, 當你對linux設備框架理解後, 它就會變得越來越簡單...
所以我還需不斷實踐加總結, 加深理解...
後面我們跟下代碼, 最關鍵的VIDIOC_QBUF和VIDIOC_DQBUF,
看下內部的buffer是如何流動的。
1. VIDIOC_QBUF - >
查看vivi_ioctl_ops
1320 static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
1321 › .vidioc_querycap = vidioc_querycap,
1322 › .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
1323 › .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
1324 › .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
1325 › .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
1326 › .vidioc_enum_framesizes = vidioc_enum_framesizes,
1327 › .vidioc_reqbufs = vb2_ioctl_reqbufs,
1328 › .vidioc_create_bufs = vb2_ioctl_create_bufs,
1329 › .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
1330 › .vidioc_querybuf = vb2_ioctl_querybuf,
1331 › .vidioc_qbuf = vb2_ioctl_qbuf,
1332 › .vidioc_dqbuf = vb2_ioctl_dqbuf,
1333 › .vidioc_enum_input = vidioc_enum_input,
1334 › .vidioc_g_input = vidioc_g_input,
1335 › .vidioc_s_input = vidioc_s_input,
1336 › .vidioc_enum_frameintervals = vidioc_enum_frameintervals,
1337 › .vidioc_g_parm = vidioc_g_parm,
1338 › .vidioc_s_parm = vidioc_s_parm,
1339 › .vidioc_streamon = vb2_ioctl_streamon,
1340 › .vidioc_streamoff = vb2_ioctl_streamoff,
1341 › .vidioc_log_status = v4l2_ctrl_log_status,
1342 › .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, m
1343 › .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
1344 };
1345
1346 static const struct video_device vivi_template = {
1347 › .name› › = "vivi",
1348 › .fops = &vivi_fops,
1349 › .ioctl_ops ›= &vivi_ioctl_ops,
1350 › .release› = video_device_release_empty,
1351 };
對應實現爲vb2_ioctl_qbuf,
是的v4l2-core核心層幫忙實現的邏輯越來越多...這個queue buffer的操作也有通用的一些流程了...
2. vb2_ioctl_qbuf
-> vb2_qbuf->vb2_internal_qbuf->
1785 static int vb2_internal_qbuf(struct vb2_queue *q, struct v4l2_buffer *b)
1786 {
1787 › int ret = vb2_queue_or_prepare_buf(q, b, "qbuf");
1788 › struct vb2_buffer *vb;
1789
1790 › if (ret)
1791 › › return ret;
1792
1793 › vb = q->bufs[b->index];
1794
1795 › switch (vb->state) {
1796 › case VB2_BUF_STATE_DEQUEUED:
1797 › › ret = __buf_prepare(vb, b);
1798 › › if (ret)
1799 › › › return ret;
1800 › › break;
1801 › case VB2_BUF_STATE_PREPARED:
1802 › › break;
1803 › case VB2_BUF_STATE_PREPARING:
1804 › › dprintk(1, "buffer still being prepared\n");
1805 › › return -EINVAL;
1806 › default:
1807 › › dprintk(1, "invalid buffer state %d\n", vb->state);
1808 › › return -EINVAL;
1809 › }
1810
1811 › /*
1812 › * Add to the queued buffers list, a buffer will stay on it until
1813 › * dequeued in dqbuf.
1814 › */
1815 › list_add_tail(&vb->queued_entry, &q->queued_list);
1816 › q->queued_count++;
1817 › q->waiting_for_buffers = false;
1818 › vb->state = VB2_BUF_STATE_QUEUED;
首先vb2_queue_or_prepare_buf(q, b, "buf"), 進去看了下, 貌似沒幹啥事, 很多是檢查數據合法性。先不管了。
下面vb = q->bufs[b->index];
即從vb2_buffer中取得當前要入列的幀。 是的, vb2_queue內部使用vb2_buffer來對buffer進行描述。
vb2_buffer中有兩個list_head, 分別是queued_entry和done_entry,
list_head結構很簡單, 就兩個指針, 分別指向前一個和下一個。(對linux kernel裏的list就是一個雙向鏈表)
struct list_head {
› struct list_head *next, *prev;
};
在reqbufs階段, 會根據用戶需求, 分配好需求的幀緩存, 其中vb2_buffer就是描述幀緩存的信息。
vb2_buffer分配好後, queued_entry和done_entry同時被分配出來。入隊出隊的就是list_head這個指針。
看到註釋那裏, 就是將vb->queued_entry放入到vb2的queued_list中。 入列就完成了...
ok, queued_list中的buffer 最終是要被vivi設備拿來處理的...嗯 需要填充後丟給done_list..
那queued_list中的buffer如何取出並處理呢?
3. 關於queued_list中的數據如何取出並處理?
直接搜索queued_list 貌似沒搜着... (可能又用了一些啥技巧...比如CALL(a, b, op) 啥的)....
但根據常識, 將queued_list中的buffer取出並進行處理的過程(這裏是填充數據), 不應該由v4l2-core核心來實現,
而應該由具體的驅動來實現。 這裏是vivi.c
所以去vivi.c中進行查看...
677 static void vivi_thread_tick(struct vivi_dev *dev) 678 { 679 › struct vivi_dmaqueue *dma_q = &dev->vidq; 680 › struct vivi_buffer *buf; 681 › unsigned long flags = 0; 682 683 › dprintk(dev, 1, "Thread tick\n"); 684 685 › spin_lock_irqsave(&dev->slock, flags); 686 › if (list_empty(&dma_q->active)) { 687 › › dprintk(dev, 1, "No active queue to serve\n"); 688 › › spin_unlock_irqrestore(&dev->slock, flags); 689 › › return; 690 › } 691
692 › buf = list_entry(dma_q->active.next, struct vivi_buffer, list);
693 › list_del(&buf->list);
694 › spin_unlock_irqrestore(&dev->slock, flags);
695
696 › v4l2_get_timestamp(&buf->vb.v4l2_buf.timestamp);
697
698 › /* Fill buffer */
699 › vivi_fillbuff(dev, buf);
700 › dprintk(dev, 1, "filled buffer %p\n", buf);
701
702 › vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
703 › dprintk(dev, 2, "[%p/%d] done\n", buf, buf->vb.v4l2_buf.index);
704 }
很容易找到, vivi.c中拉起了一個內核線程。
主要工作是定時從dma_q->active隊列中取出buffer進行填充。
查看vivi_dmaqueue數據結構,
192 struct vivi_dmaqueue {
193 › struct list_head active;
194
195 › /* thread for generating video stream*/
196 › struct task_struct *kthread;
197 › wait_queue_head_t wq;
198 › /* Counters to control fps rate */
199 › int frame;
200 › int ini_jiffies;
201 };
內部有個active隊列。
即從活躍的隊列中取出數據進行填充。 ok, 繼續...
875 static void buffer_queue(struct vb2_buffer *vb)
876 {
877 › struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
878 › struct vivi_buffer *buf = container_of(vb, struct vivi_buffer, vb);
879 › struct vivi_dmaqueue *vidq = &dev->vidq;
880 › unsigned long flags = 0;
881
882 › dprintk(dev, 1, "%s\n", __func__);
883
884 › spin_lock_irqsave(&dev->slock, flags);
885 › list_add_tail(&buf->list, &vidq->active);
886 › spin_unlock_irqrestore(&dev->slock, flags);
887 }
在buffer_queue函數中找到了, 將vivi_buffer的list_head放入活躍隊列的尾部。
看下vivi_buffer的結構體
185 /* buffer for one video frame */
186 struct vivi_buffer {
187 › /* common v4l buffer stuff -- must be first */
188 › struct vb2_buffer› vb;
189 › struct list_head› list;
190 };
關於buffer_queue的調用,
929 static const struct vb2_ops vivi_video_qops = {
930 › .queue_setup› › = queue_setup,
931 › .buf_prepare› › = buffer_prepare,
932 › .buf_queue› › = buffer_queue,
933 › .start_streaming› = start_streaming,
934 › .stop_streaming›› = stop_streaming,
935 › .wait_prepare› › = vivi_unlock,
936 › .wait_finish› › = vivi_lock,
937 };
它是vb2_queue隊列的一個操作回調函數。
它何時會被調用?
搜索下只能在videobuf-core.c中找到...
但我們是vb2... 所以到
videobuf2-core.c中尋找, 果然使用了技巧...
/**
* __enqueue_in_driver() - enqueue a vb2_buffer in driver for processing
*/
static void __enqueue_in_driver(struct vb2_buffer *vb)
{
struct vb2_queue *q = vb->vb2_queue;
unsigned int plane;
vb->state = VB2_BUF_STATE_ACTIVE;
atomic_inc(&q->owned_by_drv_count);
/* sync buffers */
for (plane = 0; plane < vb->num_planes; ++plane)
call_void_memop(vb, prepare, vb->planes[plane].mem_priv);
call_void_vb_qop(vb, buf_queue, vb);
}
看註釋... __enqueue_in_driver這個函數將最終會利用call_void_vb_qop 調用buf_queue函數, 進行驅動層面的數據處理。
如果是編解碼器, 需要丟給編解碼器進行數據處理。
如果是攝像頭, 需要把攝像頭採集的數據填充給這一個buffer。
我們這裏是vivi驅動, buf_queue函數會將vb2_buffer放入活躍隊列中。
內核線程會從活躍隊列中取數據並填充。最終放入done_list中。
ok, 我們看看__enqueue_in_driver被誰調用了?
很好找, 就在之前描述的v4l2_internel_qbuf中... 之前沒貼全... 我們繼續貼代碼。
1811 › /*
1812 › * Add to the queued buffers list, a buffer will stay on it until
1813 › * dequeued in dqbuf.
1814 › */
1815 › list_add_tail(&vb->queued_entry, &q->queued_list);
1816 › q->queued_count++;
1817 › q->waiting_for_buffers = false;
1818 › vb->state = VB2_BUF_STATE_QUEUED;
1819 › if (V4L2_TYPE_IS_OUTPUT(q->type)) {
1820 › › /*
1821 › › * For output buffers copy the timestamp if needed,
1822 › › * and the timecode field and flag if needed.
1823 › › */
1824 › › if ((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==
1825 › › V4L2_BUF_FLAG_TIMESTAMP_COPY)
1826 › › › vb->v4l2_buf.timestamp = b->timestamp;
1827 › › vb->v4l2_buf.flags |= b->flags & V4L2_BUF_FLAG_TIMECODE;
1828 › › if (b->flags & V4L2_BUF_FLAG_TIMECODE)
1829 › › › vb->v4l2_buf.timecode = b->timecode;
1830 › }
1831
1832 › /*
1833 › * If already streaming, give the buffer to driver for processing.
1834 › * If not, the buffer will be given to driver on next streamon.
1835 › */
1836 › if (q->start_streaming_called)
1837 › › __enqueue_in_driver(vb);
所以這個數據放入queued_list的同時, 又調用驅動的buf_queue, 放入了vivi驅動中的active隊列。
但這個buffer並沒有從queued_list中刪除, 那具體數據會在什麼時候被刪除呢?
根據註釋的意思, a buffer will stay on it util dequeued in dqbuf。
是的這個buffer會在dqbuf時纔會被刪除...
其實也好理解, 對於用戶態來說, 它並不認爲是對兩個隊列進行操作, dequeue和enqueue的感覺上是同一個隊列。。。
從隊列頭部取一個, 處理完, 放入隊列尾部, 感覺上就是這樣的。
所以vivi設備內部的buf雖然已經從queued_list取出並放入活躍隊列, 並最終進行數據填充放入done_list。
但該數據不會從queued_list中刪除, 只有當dequeue buffer成功後, 纔會在queued_list中刪除!!!
後面繼續看dequeue buffer的邏輯.
4. VIDIOC_DQBUF
很快找到, vivi驅動中會調用vb2_ioctl_dqbuf
5. vb2_ioctl_dqbuf
-> vb2_dqbuf -> vb2_internal_dqbuf ->
static int vb2_internal_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
{
struct vb2_buffer *vb = NULL;
int ret;
if (b->type != q->type) {
dprintk(1, "invalid buffer type\n");
return -EINVAL;
}
ret = __vb2_get_done_vb(q, &vb, b, nonblocking);
if (ret < 0)
return ret;
switch (vb->state) {
case VB2_BUF_STATE_DONE:
dprintk(3, "returning done buffer\n");
break;
case VB2_BUF_STATE_ERROR:
dprintk(3, "returning done buffer with errors\n");
break;
default:
dprintk(1, "invalid buffer state\n");
return -EINVAL;
}
call_void_vb_qop(vb, buf_finish, vb);
/* Fill buffer information for the userspace */
__fill_v4l2_buffer(vb, b);
/* Remove from videobuf queue */
list_del(&vb->queued_entry);
q->queued_count--;
/* go back to dequeued state */
__vb2_dqbuf(vb);
dprintk(1, "dqbuf of buffer %d, with state %d\n",
vb->v4l2_buf.index, vb->state);
/*
* After calling the VIDIOC_DQBUF V4L2_BUF_FLAG_DONE must be
* cleared.
*/
b->flags &= ~V4L2_BUF_FLAG_DONE;
return 0;
}
代碼不長, 直接全貼了。邏輯很簡單, 從done_list取出一個buffer, 並從done_list中刪除, 填充到v4l2_buffer中給用戶態,
同時刪除queued_entry!!!
關鍵函數是 __vb2_get_done_vb
/**
* __vb2_get_done_vb() - get a buffer ready for dequeuing
*
* Will sleep if required for nonblocking == false.
*/
static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb,
struct v4l2_buffer *b, int nonblocking)
{
unsigned long flags;
int ret;
/*
* Wait for at least one buffer to become available on the done_list.
*/
ret = __vb2_wait_for_done_vb(q, nonblocking);
if (ret)
return ret;
/*
* Driver's lock has been held since we last verified that done_list
* is not empty, so no need for another list_empty(done_list) check.
*/
spin_lock_irqsave(&q->done_lock, flags);
*vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);
/*
* Only remove the buffer from done_list if v4l2_buffer can handle all
* the planes.
*/
ret = __verify_planes_array(*vb, b);
if (!ret)
list_del(&(*vb)->done_entry);
spin_unlock_irqrestore(&q->done_lock, flags);
return ret;
}
可以看到, 從done_list中取出一個數據, 並在done_list中刪除該數據。