嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】
轉自:https://blog.csdn.net/Guet_Kite/article/details/78574781
權聲明:本文為 風箏 博主原創文章,未經博主允許不得轉載!!!!!!謝謝合作 https://blog.csdn.net/Guet_Kite/article/details/78574781
你好!這里是風箏的博客,
歡迎和我一起交流。
上一章寫了V4L2框架:嵌入式Linux驅動筆記(十七)——詳解V4L2框架(UVC驅動)
現在來寫V4L2的重點,他的用戶空間操作函數集合:
?
看下open函數:
static int uvc_v4l2_open(struct file *file) {/*部分函數省略*/struct uvc_streaming *stream;struct uvc_fh *handle; stream = video_drvdata(file);//獲取uvc視頻流 ret = usb_autopm_get_interface(stream->dev->intf);//喚醒設備 handle = kzalloc(sizeof *handle, GFP_KERNEL);//創建uvc句柄 if (stream->dev->users == 0) {//第一次時 ret = uvc_status_start(stream->dev, GFP_KERNEL);//uvc狀態開始,里面提交urb } stream->dev->users++; v4l2_fh_init(&handle->vfh, &stream->vdev); v4l2_fh_add(&handle->vfh); handle->chain = stream->chain;//捆綁uvc句柄和uvc視頻鏈 handle->stream = stream;//捆綁uvc句柄和uvc視頻流 handle->state = UVC_HANDLE_PASSIVE;//設置uvc狀態為未激活 file->private_data = handle;//將uvc句柄作為文件的私有數據 return 0; }?
open函數不是我們這章的重點,用戶空間對V4L2設備的操作基本都是ioctl來實現的,我們看下ioctl函數:
long video_ioctl2(struct file *file,unsigned int cmd, unsigned long arg) { return video_usercopy(file, cmd, arg, __video_do_ioctl); }?
可以看出video_usercopy函數就是從user空間copy復制ioctl的cmd和arg參數,然后進入__video_do_ioctl函數:
static long __video_do_ioctl(struct file *file,unsigned int cmd, void *arg) { /*部分內容省略*/ struct video_device *vfd = video_devdata(file); const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops; const struct v4l2_ioctl_info *info; if (v4l2_is_known_ioctl(cmd)) { info = &v4l2_ioctls[_IOC_NR(cmd)];//判斷是INFO_FL_STD還是INFO_FL_FUNC } if (info->flags & INFO_FL_STD) {//如果是INFO_FL_STD typedef int (*vidioc_op)(struct file *file, void *fh, void *p); const void *p = vfd->ioctl_ops;//調用到ioctl_ops真正的ioctrl操作集 const vidioc_op *vidioc = p + info->u.offset;//通過偏移值找到要執行函數的地址 ret = (*vidioc)(file, fh, arg);//直接調用到視頻設備驅動中video_device->ioctl_ops } else if (info->flags & INFO_FL_FUNC) {//如果是INFO_FL_FUNC ret = info->u.func(ops, file, fh, arg);//調用到v4l2自己實現的標準回調函數 } }?
我們可以看出,如果info->flags是INFO_FL_FUNC,會調用vfd->ioctl_ops函數集合里的某個函數(通過info->u.offset偏移值確定),那vfd->ioctl_ops是什么呢?其實就是上一章說的,uvc_register_video函數里的vdev->ioctl_ops = &uvc_ioctl_ops了。
如果info->flags是INFO_FL_FUNC,直接調用info->u.func(ops, file, fh, arg)函數
那info又是怎么確定的呢?當然是:info = &v4l2_ioctls[_IOC_NR(cmd)];
?
其實,雖然是調用info->u.func(ops, file, fh, arg)函數,但是ops是vfd->ioctl_ops;,以v4l2_ioctls[]數組里的v4l_querycap函數(就是u.func字段)為例,里面也會調用:ops->vidioc_querycap(file, fh, cap);
所以,最終還是會回到uvc_ioctl_ops函數集合里。其實和info->flags 是 INFO_FL_STD的情況沒什么大的差別。
我們看下uvc_ioctl_ops 這個真正的ioctl操作函數集合:
const struct v4l2_ioctl_ops uvc_ioctl_ops = {.vidioc_querycap = uvc_ioctl_querycap,.vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,//列舉支持哪種格式.vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out,.vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,//獲取格式、分辨率.vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out, .vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,//先try測試,然后把要設置的格式/分辨率存起來 .vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out, .vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,//檢測是否支持用戶輸入的格式 .vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out, .vidioc_reqbufs = uvc_ioctl_reqbufs,//請求分配緩存,APP將從這些緩存中讀到視頻數據 .vidioc_querybuf = uvc_ioctl_querybuf,//查詢緩存狀態, 比如地址信息(APP可以用mmap進行映射) .vidioc_qbuf = uvc_ioctl_qbuf,//把緩沖區放入隊列,底層的硬件操作函數將會把數據放入這個隊列的緩存 .vidioc_expbuf = uvc_ioctl_expbuf, .vidioc_dqbuf = uvc_ioctl_dqbuf,//APP通過poll/select確定有數據后,把緩存從隊列中取出來 .vidioc_create_bufs = uvc_ioctl_create_bufs, .vidioc_streamon = uvc_ioctl_streamon,//啟動視頻傳輸 .vidioc_streamoff = uvc_ioctl_streamoff, .vidioc_enum_input = uvc_ioctl_enum_input, .vidioc_g_input = uvc_ioctl_g_input, .vidioc_s_input = uvc_ioctl_s_input, /*太長了,后續省略......*/ };?
可以看到非常的多,帶_cap的是捕獲設備,帶_out的是輸出設備。
雖然這些ioctl非常多,但是在韋東山第三期視頻里說道,簡化的攝像頭驅動程序至少需要11個ioctl,我能力不大,所以也就按照這個來分析:
.vidioc_querycap = uvc_ioctl_querycap,
.vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,
.vidioc_reqbufs = uvc_ioctl_reqbufs,
.vidioc_querybuf = uvc_ioctl_querybuf,
.vidioc_qbuf = uvc_ioctl_qbuf,
.vidioc_dqbuf = uvc_ioctl_dqbuf,
.vidioc_streamon = uvc_ioctl_streamon,
.vidioc_streamoff = uvc_ioctl_streamoff,
我們先看第【1】個ioctl:.vidioc_querycap = uvc_ioctl_querycap函數:
static int uvc_ioctl_querycap(struct file *file, void *fh,struct v4l2_capability *cap) {struct video_device *vdev = video_devdata(file); struct uvc_fh *handle = file->private_data; struct uvc_video_chain *chain = handle->chain; struct uvc_streaming *stream = handle->stream; strlcpy(cap->driver, "uvcvideo", sizeof(cap->driver)); strlcpy(cap->card, vdev->name, sizeof(cap->card)); usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info)); cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING | chain->caps;//獲取這是一個什么設備: if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)//如果是視頻捕獲設備 cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; else cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; return 0; }?
很簡單的函數,.vidioc_querycap 里的回調函數主要就是說明這個是什么設備而已,輸入設備?輸出設備?
接下來看第【2】個ioctl:.vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap函數:
uvc_ioctl_enum_fmt_vid_cap函數里又會調用uvc_ioctl_enum_fmt函數:
?
.vidioc_enum_fmt_vid_cap 里的回調函數主要就是列舉出支持的格式,把他放到fmt->description描述符中,然后返回給用戶空間傳進來的fmt。
繼續看第【3】個ioctl:.vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap函數:
uvc_ioctl_g_fmt_vid_cap函數里又會調用uvc_v4l2_get_format函數:
?
.vidioc_g_fmt_vid_cap里的回調函數主要是獲取格式、分辨率,把他存放在fmt->fmt.pix里。
第【4】個ioctl:.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap函數:
uvc_ioctl_try_fmt_vid_cap函數里面又調用uvc_v4l2_try_format函數:
?
我們看下.vidioc_try_fmt_vid_cap的回調函數做了什么:
1.檢測硬件是否支持請求的格式,否則使用默認格式
2.然后在format->frame[i]查找最近的分辨率來使用
3.設置每一幀的時間間隙,也就是一秒多少幀
4.填充probe,然后在uvc_probe_video里設置video控制
5.最后把嘗試好的數據填充到fmt->fmt.pix
所以.vidioc_try_fmt_vid_cap的回調函數主要是起一個測試作用。
第【5】個ioctl:.vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap函數:
uvc_ioctl_s_fmt_vid_cap函數里調用uvc_v4l2_set_format函數:
?
其實我感覺這個函數都沒啥啥,大部分都在uvc_v4l2_try_format函數里做了,最后就是把參數存起來到stream里而已。
第【6】個ioctl:.vidioc_reqbufs = uvc_ioctl_reqbufs函數:
調用關系:
?
int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,unsigned int *count) { /*部分內容省略*/ __vb2_queue_cancel(q);//清理任何緩沖的準備或排隊隊列狀態 memset(q->alloc_devs, 0, sizeof(q->alloc_devs));//初始化清零 q->memory = memory;//指向內存 ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes, plane_sizes, q->alloc_devs);//查詢需要多少緩沖區buffers,設置緩存區隊列 allocated_buffers = __vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);//申請緩存區 }?
其中call_qop(q, queue_setup, q, &num_buffers, &num_planes,plane_sizes, q->alloc_devs)里面其實就是調用:(q)->ops->queue_setup(q, &num_buffers, &num_planes,plane_sizes, q->alloc_devs)
那這個(q)->ops又是在哪設置呢?
其實就是我們上一章里的uvc_queue_init函數里:
queue->queue.ops = &uvc_queue_qops;
?
這個結構體很重要,之后我們還會調用到。
知道需要多少緩存之后,就會調用__vb2_queue_alloc函數申請空間:
?
這里面就是申請num_buffers個緩存了,設置好然后放到q->bufs[]里,再mmap到用戶空間。
為什么mmap呢?
read和write,是基本幀IO訪問方式,每一幀都要通過IO操作,通過read讀取每一幀數據,數據需要在內核和用戶之間拷貝,這種方式訪問速度可能會非常慢;
但是通過mmap在內核空間開辟緩沖區,這些緩沖區可以是大而連續DMA緩沖區、通過vmalloc()創建的虛擬緩沖區,或者直接在設備的IO內存中開辟的緩沖區(如果硬件支持);是流IO訪問方式,不需要內存拷貝,訪問速度比較快。
設置好偏移值就對buf進行初始化了。
所以.vidioc_reqbufs的回調函數主要是請求分配緩存。
第【7】個.ioctl:.vidioc_querybuf = uvc_ioctl_querybuf函數:
調用關系:
- 1
- 2
- 3
- 4
可以看出,這里根據傳進來的參數,查找到使用到的緩存,會調用fill_user_buffer返回bufs[]給用戶空間。
所以.vidioc_querybuf 的回調函數主要是查詢緩存狀態,把他返回給用戶空間。
第【8】個.vidioc_qbuf = uvc_ioctl_qbuf函數:
調用關系為:
- 1
- 2
- 3
- 4
?
這里面,將對應的vb2_buffer 添加到 q->queued_list 鏈表中,并改變state狀態。
然后調用__enqueue_in_driver把緩沖區放入隊列。
然后就是ret = vb2_start_streaming(q)函數,這個函數在uvc_ioctl_streamon函數里也會調用有,所以放在后面寫吧,這里雖然也會調用,但是是有條件的:
只有start_streaming沒被調用且到達最小需要的buffers數目,才嘗試啟動
第【9】個.vidioc_dqbuf = uvc_ioctl_dqbuf,函數:
調用關系為:
?
int vb2_core_dqbuf(struct vb2_queue *q, unsigned int *pindex, void *pb, bool nonblocking) { /*部分內容省略*/ struct vb2_buffer *vb = NULL; ret = __vb2_get_done_vb(q, &vb, pb, nonblocking); call_void_vb_qop(vb, buf_finish, vb); call_void_bufop(q, fill_user_buffer, vb, pb); list_del(&vb->queued_entry); q->queued_count--; }?
這里面,就是在__vb2_get_done_vb函數里,將q->done_list 中的vb2_buffer中提出來,然后 將vb2_buffer中的v4l2_buffer信息返回,并將其從q->done_list 中刪除。
然后就調用(q)->vb2_queue->ops->buf_finish代表buf操作完成。
接著調用fill_user_buffer函數把把數據返回給用戶空間,最后list_del(&vb->queued_entry)把他移除掉。
第【10】個.vidioc_streamon = uvc_ioctl_streamon,,函數:
調用關系為:
?
static int vb2_start_streaming(struct vb2_queue *q) {/*部分內容省略*/struct vb2_buffer *vb; q->start_streaming_called = 1;//標志以使用streaming ret = call_qop(q, start_streaming, q, atomic_read(&q->owned_by_drv_count));//調用q->ops->start_streaming q->start_streaming_called = 0; for (i = 0; i < q->num_buffers; ++i) { vb = q->bufs[i]; if (vb->state == VB2_BUF_STATE_ACTIVE) vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED);//數據完成后 } }?
函數里面q->ops->start_streaming,也就是queue->queue.ops = &uvc_queue_qops里的函數:uvc_start_streaming
但是uvc_start_streaming函數里又會調用uvc_video_enable(stream, 1);
uvc_video_enable函數里面就有這兩句:
?
這樣就成功的啟動視頻傳輸了。
然后數據完成后就是調用vb2_buffer_done函數:函數里面就是:
?
數據完成就要把buffers放到done_list這個完成的鏈表中,然后改變狀態,最后就喚醒poll函數里的休眠進程。
第【11】個.vidioc_streamoff = uvc_ioctl_streamoff,,函數:
其實就和uvc_ioctl_streamoff是相反的,uvc_ioctl_streamoff是調用uvc_video_enable(stream, 1);
uvc_ioctl_streamoff就是調用uvc_video_enable(stream, 0)了,進行關閉視頻傳輸。
好了,十一個ioctl就馬馬虎虎的講完了。還有一些其他的ioctl,比如設置亮度之類的,就自己去看了,是在太多了。
其實一路寫來,我對這個buffers這塊輸出還是很模糊的,不知道怎么寫,,,,,,,,,還在研究中。不過大致的流程還是了解了一點。
最后,附上一位大佬的blog:http://www.cnblogs.com/surpassal/archive/2012/12/22/zed_webcam_lab2.html
轉載于:https://www.cnblogs.com/sky-heaven/p/9559104.html
總結
以上是生活随笔為你收集整理的嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Liunx 重新mount
- 下一篇: cudnn的下载地址