1. 程式人生 > >韋東山專案視訊之攝像頭驅動1 V4L2框架分析

韋東山專案視訊之攝像頭驅動1 V4L2框架分析

一、攝像頭驅動 V4L2框架分析

攝像頭驅動是屬於字元裝置驅動程式

V4L2:  vidio for linux version 2,我們分析的是linux3.4.2核心。

回顧二期,怎麼寫驅動?

1.構造一個file_operations:.open=drv_open .read=drv_read
2.告訴核心:register_chrdev(主裝置號,名字,&file_operations)
3.入口函式:呼叫register_chrdev
4.出口函式:解除安裝
一般採用register_chrdev的代替方法:分配、設定cdev,cdev_add

而對於複雜的驅動,採用分層的概念。
例如LCD驅動中分為兩層:上層通用的核心層核心已經幫我們做好,即在fbmem.c
1.構造file_operations(open read write 。。)
2.註冊 

 3.入口、出口

我們做的是硬體相關層,供上層file_operations呼叫
1.分配一個fb_info 結構體
2.設定
3.註冊
4.硬體相關的操作

因此,對於這種複雜的驅動,我們的做法:
1.分配某個結構體
2.設定
3.註冊
4.硬體相關

現在分析V4L2框架:

把usb裝置接到系統前臺,會有列印資訊,根據列印資訊在核心裡找出驅動,用dmsg命令檢視;
grep "Found UVC" * -nR 搜尋 在uvc_driver.c裡,這是個硬體相關的驅動。

分析程式碼,猜測V4L2  框架 肯定也是分為至少兩層  。

app 呼叫 open read write -->呼叫 v4l2_fops 裡的 open read write->呼叫硬體相關層的video_device 裡提供的函式
----------------------------------------------------------------------------------------------------------
核心層:v4l2-dev.c   __video_register_device  
    構造:v4l2_fops(.read = v4l2_read,
     .write = v4l2_write,
                             .open = v4l2_open, 。。。。)
    註冊:
    vdev->cdev = cdev_alloc();     //1.字元裝置cdev_alloc
    vdev->cdev->ops = &v4l2_fops;  //2.設定fops
    cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);  //3.cdev_add


----------------------------------------------------------------------------------------------------------
硬體相關層:如uvc_driver.c   Found UVC ->v4l2_device_register(這個不重要)
    ->video_device_alloc->video_register_device(向核心層註冊) 
    ->v4l2-dev.h->__video_register_device(v4l2-dev.c)     
    
    即分配結構體  video_device (裡面的函式供上層v4l2_fops呼叫)   
    設定  註冊video_register_device            

以vivi.c(virtual video driver )虛擬視訊驅動 作為例子
1.分配一個video_device 結構體
2.設定
3.註冊 video_register_device     

----------------------------------------------------------------------------------------------------------
硬體相關層


從入口函式開始分析
vivi_init
vivi_create_instance


v4l2_device_register //不是主要的


下面大堆函式是用來設定音量、亮度、增益等屬性,用於APP的ioctl
dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
V4L2_CID_CONTRAST, 0, 255, 1, 16);


等等...


vfd = video_device_alloc(); //分配video_device 
*vfd = vivi_template; //內容設定為vivi_template
/*最底層的vivi 操作函式*/
static struct video_device vivi_template = {
.name= "vivi",
.fops           = &vivi_fops,
.ioctl_ops = &vivi_ioctl_ops,
.release = video_device_release,
.tvnorms              = V4L2_STD_525_60,
.current_norm         = V4L2_STD_NTSC_M,
};
vfd->v4l2_dev = &dev->v4l2_dev;


video_register_device(video_device結構體vfd, 型別VFL_TYPE_GRABBER, video_nr); //向上註冊
----------------------------------------------------------------------------------------------------------
核心層
__video_register_device
根據型別VFL_TYPE_GRABBER建立不同的裝置節點
case VFL_TYPE_GRABBER:
name_base = "video";
根據型別VFL_TYPE_GRABBER得到不同的次裝置號
case VFL_TYPE_GRABBER:
minor_offset = 0;
minor_cnt = 64;
vdev->cdev = cdev_alloc(); 
vdev->cdev->ops = &v4l2_fops;
cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);     //3.cdev_add


video_device[vdev->minor] = vdev;//以次裝置號為下標在數組裡面得到一項把vdev存進來

分析vivi.c 的open read write ioctl 過程-
1.open過程 -
APP  :open (“/dev/video0”...)-
-
drv  : v4l2_fops裡的v4l2_open函式-
vdev = video_devdata(filp)//根據次裝置號從陣列中得到video_device---------
if (vdev->fops->open)     //如果有open函式
if (video_is_registered(vdev))
ret = vdev->fops->open(filp);//呼叫open 函式
呼叫vivi.c 裡的v4l2_fh_open


2.read過程
APP  :read (“/dev/video0”...)


drv  :v4l2_fops裡的v4l2_read 函式
struct video_device *vdev = video_devdata(filp);//根據次裝置號從陣列中得到video_device
if (video_is_registered(vdev))
       ret = vdev->fops->read(filp, buf, sz, off);//呼叫read 函式
呼叫vivi.c 裡的vivi_read


2.ioctl 過程(比較複雜)
APP  :ioctl(“/dev/video0”...)


drv  : v4l2_fops裡的v4l2_ioctl函式
struct video_device *vdev = video_devdata(filp);//根據次裝置號從陣列中得到video_device
if (vdev->fops->unlocked_ioctl)
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);//呼叫v4l2_ioctl函式
呼叫vivi.c 裡的video_ioctl2

這個video_ioctl2做什麼呢?
video_usercopy(file, cmd, arg, __video_do_ioctl); 
video_usercopy:從使用者空間把使用者的命令複製進來,呼叫__video_do_ioctl
__video_do_ioctl
struct video_device *vfd = video_devdata(file);//根據次裝置號從陣列中得到video_device
根據使用者空間APP得到的命令(cmd) 設定某些屬性
switch (cmd) 


怎麼設定這些屬性?還得由vivi.c來提供,在vivi.c 裡一開始的vivi_create_instance裡設定


v4l2_ctrl_handler的使用過程
分析__video_do_ioctl

二、怎麼寫v4l2驅動?
1.分配、設定、註冊v4l2_device  v4l2_device_register video_register_device
2.分配一個video_device video_device_alloc
.vfd->v4l2_dev
.fops           設定vfd的fops 裡的open、read、write 被上層呼叫
.ioctl_ops      設定屬性被上層呼叫    

思考:APP可以通過ioctl來設定(獲得)亮度等資訊,在驅動程式裡,誰來接收、儲存、設定到硬體(提供這些資訊)?
答:在驅動程式中抽象出來一個結構體v4l2_ctrl,每個Ctrl對應其中的一項(音量、亮度等等);
    由v4l2_ctrl_handler來管理他們
  1.初始化
    v4l2_ctrl_handler_init 
  2.設定
    v4l2_ctrl_new_std
    v4l2_ctrl_new_custom
    這些函式就是建立各個屬性,並且放入v4l2_ctrl_handler的連結串列
  3.跟vdev關聯
    dev->v4l2_dev.ctrl_handler = hdl;