1. 程式人生 > >深入理解l核心v4l2框架之video for linux 2(轉載)

深入理解l核心v4l2框架之video for linux 2(轉載)

在看了很多關於v4l2驅動的例程之後,想深入研究下linux核心的v4l2框架,順便把這些記錄下來,以備查用。

Video for Linux 2

     隨著一些視訊或者影象硬體的複雜化,V4L2驅動也越來越趨於複雜。許多硬體有多個IC,在/dev下生成多個video裝置或者其他的諸如,DVB,ALSA,FB,I2C ,IR等等非V4L2的裝置。所以,V4L2驅動程式就要為這些硬體裝置提供音視訊的合成以及編解碼的功能介面,另外,通常這些裝置都通過多個I2C匯流排實現和CPU的通訊,不僅是I2C匯流排,其他的也有可能被使用,比如SPI,1-wire,等等。掛在這些總線上的裝置叫做sub-devices,即V4L2裝置的子裝置。

     之前相當長的一段時間內,V4L2被限制在使用video_device來建立V4L2裝置節點,使用videobuf來處理視訊快取。這就意味著,所有的驅動驅動程式除了建立一個裝置例項外,還要單獨實現連線”子裝置”的步驟。這個過程比較複雜,也容易產生錯誤。正是缺少這樣一種框架,使得在程式碼重用方面做得不夠好,驅動程式看起來很臃腫。

     所以V4L2框架才被整理出來,提供一些基礎的元件,通過一些共享的功能函式簡化驅動的編寫,使程式碼的重用性增強,硬體裝置驅動只需要實現相關的操作而不必關心互動式的應用,同時應用可以更加透明地使用硬體來驅動音視訊的處理。而且這個框架也在不斷地更新擴充套件,基礎部分就是提供的v4l2API。但這裡不討論V4L2提供了哪些API和他們如何被使用,我們只討論和v4l2核心及驅動相關的知識。

首先來看看所有的v4l2驅動都必須要有的幾個組成部分:

– 用來描述每一個v4l2裝置例項狀態的結構(structv4l2_device)。

– 用來初始化和控制子裝置的方法(structv4l2_subdev)。

– 要能建立裝置節點並且能夠對該節點所持有的資料進行跟蹤(structvideo_device)。

– 為每一個被開啟的節點維護一個檔案控制代碼(structv4l2_fh)。

– 視訊緩衝區的處理(videobuf或者videobuf2 framework)。

在linux3.0以上的核心對這些結構的定義,從定義當中就可以窺探整個v4l2的框架。這些結構體有:

struct v4l2_device; 用來描述一個v4l2裝置例項

struct v4l2_subdev, 用來描述一個v4l2的子裝置例項

struct video_device; 用來建立裝置節點/dev/videoX

struct v4l2_fh; 用來跟蹤檔案控制代碼例項

我們把videobuf及videobuf2框架放到後面的系列來討論。

用一個比較粗糙的圖來表現他們之間的關係,大致為:

裝置例項(v4l2_device)

           |______子裝置例項(v4l2_subdev)

           |______視訊裝置節點(video_device)

           |______檔案訪問控制(v4l2_fh)

           |______視訊緩衝的處理(videobuf/videobuf2)

好了,接下來我們一一分析一下這些結構的定義。

1、v4l2_device

這個定義在linux/media/v4l2-device.h當中定義

struct v4l2_device {

//指向裝置模型的指標

struct device *dev;

#if defined(CONFIG_MEDIA_CONTROLLER)

//指向一個媒體控制器的指標

struct media_device *mdev;

#endif

//管理子裝置的雙向連結串列,所有註冊到的子裝置都需要加入到這個連結串列當中

struct list_head subdevs;

//全域性鎖

spinlock_t lock;

//裝置名稱

char name[V4L2_DEVICE_NAME_SIZE];

//通知回撥函式,通常用於子裝置傳遞事件,這些事件可以是自定義事件

void (*notify)(struct v4l2_subdev*sd, uint notification, void *arg);

//控制控制代碼

struct v4l2_ctrl_handler*ctrl_handler;

//裝置的優先順序狀態,一般有後臺,互動,記錄三種優先順序,依次變高

struct v4l2_prio_state prio;

//ioctl操作的互斥量

struct mutex ioctl_lock;

//本結構體的引用追蹤

struct kref ref;

//裝置釋放函式

void (*release)(struct v4l2_device*v4l2_dev);

};

要註冊一個例項,需要使用函式

v4l2_device_register(struct device*dev, struct v4l2_device *v4l2_dev);

該函式將會初始化v4l2_device結構,如果dev->driver_data為空,那麼將把v4l2_dev賦值給這個driver_data。

如果驅動要整合媒體裝置框架,就需要手動設定dev->driver_data來指向一個嵌入了v4l2_device結構的媒體裝置結構,這個結構可以是指定驅動的狀態描述。在註冊函式之前呼叫dev_set_drvdata來完成,這些都要在呼叫register函式之前就要設定好,同樣,如果有媒體裝置的話,必須也要在此之前就要初始化好,並且設定v4l2_device的mdev域來指向一個已經初始化過的媒體裝置例項。

如果v4l2_dev->name是空,註冊函式也將根據dev->name來設定v4l2_dev的name,如果已經設定,那麼註冊函式將不再過問。如果dev是空,那麼就必須在呼叫註冊函式之前設定v4l2_dev->name。當然,也可以使用v4l2_device_set_name()來設定裝置例項名稱。

要移除註冊的話,呼叫函式:

v4l2_device_unregister(structv4l2_device *vd)

如果是可熱插拔的裝置,那麼還需要呼叫

v4l2_device_disconnect(structv4l2_device *vd)

來斷開裝置的連線,否則會產生空指標的問題。

有的時候需要迭代驅動註冊的所有裝置,這個通常出現在多個裝置驅動使用相同的硬體的情況,比如說ivtvfb是一個framebuffer驅動,它使用ivtv硬體,同時也是一個tv驅動。相同的情況對於alsa驅動也是適用的。那麼,迭代該怎麼完成呢?看下面這個例程:

static int callback(struce device *dev,void *p)

{

struct v4l2_device *vdev =dev_get_drvdata(dev);

if (vdev == NULL) return 0;

/*do something*/

return 0;

}

int iterate(void *p)

{

struct device_driver *drv;

int err = 0;

/*Find driver 'vivi' on the PCI bus*/

drv = driver_find(“vivi”,&pci_bus_type);

err = driver_for_each_device(drv,NULL, p, callback);

put_driver(drv);

return err;

}

有時候還需要維護一個執行時的裝置例項計數,定義一個原子變數就可以了。如果一個v4l2裝置上註冊了很多裝置節點,我們在移除註冊v4l2_device的時候,就要等到所有的裝置節點移除之後,ref成員幫助我們記錄v4l2_device的節點註冊數,每次呼叫video_register_device都會加1,反之則減一。一旦這個值為0的時候,我們才可以呼叫v4l2_device_unregister。如果不是視訊節點,那麼手動呼叫這兩個函式來計數:

void v4l2_device_get(struct v4l2_device*vd) //ref +1

int v4l2_device_put(struct v4l2_device*vd) // ref -1

2、v4l2_subdev

struct v4l2_subdev {

#if defined(CONFIG_MEDIA_CONTROLLER)

//媒體控制器的實體,和v4l2_device

struct media_entity entity;

#endif

struct list_head list;

struct module *owner;

u32 flags;

//指向一個v4l2裝置

struct v4l2_device *v4l2_dev;

//子裝置的操作函式集

const struct v4l2_subdev_ops *ops;

//子裝置的內部操作函式集

const struct v4l2_subdev_internal_ops*internal_ops;

//控制函式處理器

struct v4l2_ctrl_handler*ctrl_handler;

//子裝置的名稱

char name[V4L2_SUBDEV_NAME_SIZE];

//子裝置所在的組標識

u32 grp_id;

//子裝置私有資料指標,一般指向匯流排介面的客戶端

void *dev_priv;

//子裝置私有的資料指標,一般指向匯流排介面的host端

void *host_priv;

//裝置節點

struct video_device devnode;

//子裝置的事件

unsigned int nevents;

};

很多v4l2驅動程式都需要和子裝置(sub_device)來進行通訊,這些裝置實際上完成了所有的任務,比如說音視訊的合成,編碼,解碼。對於webcam來說,子裝置就是sensor和camera控制器。通常這些都是I2C裝置,但也不是必須的。為了給這些子裝置提供一個一致的介面,v4l2_subdev結構才應運而生。

每一個子裝置都必須有一個v4l2_subdev結構。這個結構可以單獨地使用或者被嵌入一個更大的結構。通常有一個更低階的裝置結構(比如i2c_client),它包含裝置的一些初始化資料,所以建議v4l2_subdev->dev_priv指向該資料,可以通過函式:

v4l2_set_subdevdata()

v4l2_get_subdevdata()

來設定,然後呼叫v4l2_get_subdevdata()這樣就會很方便的從v4l2_subdev找到實際的匯流排相關的裝置資料。總之是一些私有的資料,可以是平臺相關的資料,可以是自己定義的包含了v4l2_subdev結構的裝置例項等等。

同樣也需要一個從匯流排相關的裝置方便的找到v4l2_subdev,可以這樣來實現例如:i2c_set_clientdata().呼叫這個函式來把v4l2_subdev結構指標賦給i2c_client的private資料。然後呼叫i2c_get_clientdata()獲得v4l2_subdev的指標。當然也可以通過container_of來操作,但是核心既然提供了這樣的api,用之何樂不為呢?

每一個v4l2_subdev包含了子裝置可以實現的函式指標。這些函式可以做很多很多不同的事情,它們根據不同的操作類別被放在不同的結構當中。最高一級的操作函式集涵蓋了各種操作大類。比如:

struct v4l2_subdev_core_ops {

int (*g_chip_ident)(struct v4l2_subdev*, struct v4l2_dbg_chip_ident *);

int (*log_status)(struct v4l2_subdev*sd);

int (*init)(struct v4l2_subdev *sd,u32 val);

….

};

struct v4l2_subdev_tuner_ops {};

struct v4l2_subdev_audio_ops {};

struct v4l2_subdev_video_ops {};

struct v4l2_subdev_ops {

const struct v4l2_subdev_core_ops*core;

const struct v4l2_subdev_tuner_ops*tuner;

const struct v4l2_subdev_audio_ops*audio;

const struct v4l2_subdev_video_ops*video;

};

core操作對於所有子裝置來說是共通的,其他的型別可以根據子裝置的需要來實現,比如一個視訊子裝置不太可能實現音訊的操作等等。

至此我們介紹了v4l2_subdev的一些成員及操作函式,那麼下面就可以進行初始化了,初始化函式呼叫:

v4l2_subdev_init(sd, &ops);

之後就需要初始化子裝置的名字和owner等等。如果需要整合媒體框架,那麼我們就必須初始化這個media_entity結構,並將其嵌入到v4l2_subdev的結構當中,這個通過呼叫media_entity_init()來實現。

struct media_pad *pads = &my_sd->pads;

media_entity_init(&sd->entity,npads, pads, 0);

[html] view plaincopyprint?

pads陣列必須在之前就已經初始化好。這裡不需要進行media_entity的型別和名字的手動初始化,但是revision域如果需要的話就必須要初始化。Entity的引用引數會自動在子裝置節點被開啟和關閉的時候進行加減。在子裝置被銷燬之前不要忘了cleanup這個mediaentity.呼叫media_entity_cleanup(&sd->entity)。關於media_entity的相關知識這裡不做討論。下面繼續討論v4l2_subdev的註冊。

註冊v4l2_subdev子裝置例項到v4l2_device裝置系統當中,用這個函式:

v4l2_device_unregister_subdev(sd)

這個函式執行成功之後,subdev->dev將指向v4l2_device,如果v4l2_device的mdev是一個非空的值,那麼subdev->entity也將會被自動註冊為mdev。要移除註冊的子裝置,呼叫:

v4l2_device_unregister_subdev(sd)

接下來介紹子裝置提供的功能呼叫,如果要使用子裝置提供的介面函式,有兩種方法,第一種就是直接使用ops中的回撥函式,但是不推薦這樣做,一般是用第二種方法,呼叫函式:

v4l2_subdev_call(sd, o, f, arg...)

來獲取子裝置晶片的標識。其中,sd就是子裝置例項,o是子裝置下操作函式的大類,例如可以是core/video/audio/tuner,f是大類下面的功能回撥函式,arg是傳入的引數。另外,還可以通過v4l2裝置例項呼叫全部子裝置的功能回撥函式,使用這個函式:

v4l2_device_call_all(v4l2, grp_id, o,f, arg...)

其中grp_id就是子裝置的組標識。舉個例子:

v4l2_subdev_call(sd, video,g_chip_cap, &cap);

v4l2_device_call_all(v4l2, 0, core,g_chip_id, &cap);

前者是呼叫子裝置sd的video類下的g_chip_cap功能回撥函式;後者是v4l2裝置呼叫所有子裝置的core類下的g_chip_id功能回撥函式。grp_id非0則指定呼叫相同組標識的該方法。子裝置還需要通知它的v4l2父裝置發生了什麼事件,這個通過呼叫下面這個函式實現。

v4l2_subdev_notify(sd, notification,arg)

但是父裝置必須要有能夠處理這些事件的能力,就是實現v4l2_device的notify功能。

除了通過v4l2_subdev_ops結構暴露給核心的API之外,v4l2子裝置也同樣可以被使用者程式直接控制。裝置節點名為v4l-subdevX建立在/dev目錄下,這樣就可以通過開啟裝置檔案來直接訪問子裝置。如果一個子裝置支援直接的使用者空間訪問,那麼它就必須在被註冊之前就設定V4L2_SUBDEV_FL_HAS_DEVNODE標誌。註冊子裝置之後,v4l2_device驅動就會為所有持此標誌的子裝置建立裝置節點。這個通過v4l2_device_register_subdev_nodes()來實現。這個裝置節點可以處理一組標準的V4l2API子集,如下:

VIDIOC_QUERYCTRL

VIDIOC_QUERYMENU

VIDIOC_G_CTRL

VIDIOC_S_CTRL

VIDIOC_G_EXT_CTRLS

VIDIOC_S_EXT_CTRLS

VIDIOC_TRY_EXT_CTRLS

所有上面這些控制呼叫都可以通過core:ioctl操作來完成。至此,關於v4l2子裝置相關的知識就介紹完畢。

備註:

核心為我們提供了很多幫助函式,比如v4l2_i2c子裝置驅動框架,使編寫此類驅動變得容易很多。因為此類驅動有很多共通之處,所以可以將其抽象出來以便易於使用。這個抽象在v4l2_common.h當中。

給一個I2C驅動新增v4l2_subdev支援的推薦方法是將v4l2_subdev結構嵌入I2C裝置例項的state結構當中。非常簡單的裝置沒有這個結構,那麼就直接建立一個v4l2_subdev例項就好了。一個典型的state結構就像這樣:

struct chipname_state {

struct v4l2_subdev sd;

….. /*這裡存放額外的狀態域*/

};

初始化一個v4l2_subdev並且將其和i2c匯流排裝置連線起來

v4l2_i2c_subdev_init(&state->sd,client, subdev_ops);

這個函式將填充所有v4l2_subdev結構的域,並且保證v4l2_subdev和i2c_client能夠互相找到。最好是能夠實現一個state和subdev互訪的inline函式:

static inline struct chipname_state*to_state(struct v4l2_subdev *sd)

{

return container_of(sd, structchipname_state, sd);

}

然後通過如下函式實現v4l2_subdev和i2c_client的互訪:

struct i2c_client *client =v4l2_get_subdevdata(sd);

struct v4l2_subdev *sd =i2c_get_clientdata(client);

要確保在subdev的驅動被remove的時候呼叫如下函式:

v4l2_device_unregister_subdev(sd);

另外還有一些幫助函式可以使用:

struct v4l2_subdev *sd =v4l2_i2c_new_subdev(v4l2_dev, adapter, “module_foo”,“chipid”, 0x36, NULL);

這個函式會load一個i2c的adapter然後呼叫i2d_new_device並且根據chipid和i2c的地址(0x36)來建立一個新的i2c裝置,之後會將模組名為module_foo的subdev註冊到v4l2_dev裡面去。關於其他的幫助函式,請查閱v4l2-common.h檔案。

3、video_device

struct video_device{

#if defined(CONFIG_MEDIA_CONTROLLER)

struct media_entity entity;

#endif

const struct v4l2_file_operations*fops;

struct device dev; /* v4l device */

struct cdev *cdev; /* characterdevice */

struct device *parent; /* deviceparent */

struct v4l2_device *v4l2_dev; /*v4l2_device parent */

struct v4l2_ctrl_handler*ctrl_handler;

struct v4l2_prio_state *prio;

char name[32];

int vfl_type;

int minor;

u16 num;

unsigned long flags;

int index;

spinlock_t fh_lock; /* Lock forall v4l2_fhs */

struct list_head fh_list; /* List ofstruct v4l2_fh */

int debug; /* Activates debuglevel*/

v4l2_std_id tvnorms; /* Supported tvnorms */

v4l2_std_id current_norm; /* Currenttvnorm */

void (*release)(struct video_device*vdev);

const struct v4l2_ioctl_ops*ioctl_ops;

struct mutex *lock;

};

在/dev中實際的裝置節點使用video_deice結構建立。該結構既可以被動態建立,也可以被嵌入到更大的結構當中:

動態建立:

struct video_device *vdev =video_device_alloc();

vdec->release =video_device_release;

嵌入到更大的結構當中:

Struct video_device *vdev =&my_vdev->vdev;

vdev->relase = my_vdev_release;

要完成這個結構的初始化,還需要設定以下的域:

.v4l2_dev 設定v4l2_device父裝置

.name      設定唯一的描述名

.fops        設定v4l2_file_operations結構

.ioctl_ops 使用v4l2_ioctl_ops來簡化ioctl的維護

.lock         如果想在驅動中進行全域性鎖定的話設定為NULL,否則初始化為一個 mutex_lock,這樣就可以在unlocked_ioctl的操作之前和之後對操作內容進行保護

.prio          跟蹤屬性。用來實現VIDIOC_G/S_PRIORITY,如果設定為NULL,將使用v4l2_device中的v4l2_prio_state。

.parent     NULL . 如果硬體中有多個PCI裝置共享v4l2_device核心,那麼就要設定父裝置。

.flags        可選,設定V4L2_FL_USE_FH_PRIO,如果想讓framework來處理VIDIOC_G/S_PRIORITYioctls的話。

             如果要使用v4l2_ioctl_ops,那麼就需要將.unlocked_ioctl設定為video_ioctl2,這樣的話,就可以打通上層應用在使用ioctl操作/dev/videoX的時候和v4l2裝置之間的信                息交換通道。

在某些應用場景下,還需要mask掉在v4l2_ioctl_ops中指定的功能,那麼需要呼叫

            void v4l2_disable_ioctl(structvideo_device *vdev, unsigned int cmd)來遮蔽對該cmd的呼叫。

v4l2_file_operations結構是一個file_operations的子集,主要的區別是inode引數不再使用了,因為從來沒有用到過。

Ioctls和locking域:

V4L2核心提供了可選的鎖定服務,主要體現在video_device結構當中的lock域,指向一個mutex,如果你初始化了這個域,那麼它將被用在unlocked_ioctl中用來序列化所有的ioctl操作。

如果使用videobuf2框架來管理視訊緩衝,那麼還得在初始化一個video_device->queue->lock,並且這個鎖會替代video_device->lock來序列化所有佇列ioctl的操作。

佇列ioctl的操作使用不同的鎖有個優點,就是某些裝置的操作需要很長的時間,而在此期間的其他非佇列ioctl操作也可以進行。舉個例子,有個場景就是既要設定camera的閃光燈亮,也要從videobuf當中讀取資料的時候,我們就需要分開來鎖定兩個ioctl,以便兩者能夠基本上保持同步。

在videobuf2當中,需要實現wait_prepare和wait_finish回撥函式來lock/unlock,如果想要使用queue->lock,最好是使用vb2_ops_wait_prepare/finish

video_device 的註冊:

接下來,通過video_device_alloc()分配好一個video_device之後,就要把它註冊到系統當中,這將會為你建立一個字元裝置。

err = video_register_device(vdev,VFL_TYPE_GRABBER, -1);

if (err) {

video_device_release(vdev);

return err;

}

如果v4l2_device有一個非空的mdev,那麼video_device實體也會自動註冊媒體裝置。

具體註冊成什麼型別的V4l2裝置,要看type指定為什麼:

VFL_TYPE_GRABBER video的輸入輸出裝置,體現為/dev/videoX

VFL_TYPE_VBI vertical blank data體現為/dev/vbiX

VFL_TYPE_RADIO radio tuners裝置/dev/radioX

第三個引數是指定裝置號,如果傳入-1的話,就是讓v4l2框架自動選擇一個可用的node號,如果指定了非-1的引數,並且這個引數代表的裝置已經被註冊了,那麼系統也會自動選擇下一個可用的裝置號,但是會給出警告。

一旦一個video_device被建立,那麼框架也會同時為你建立一些裝置屬性節點,在/sys/class/video4linux/videoX/下你會看到諸如name,index等的屬性節點。

如果註冊失敗了,那麼就要呼叫video_device_release()來釋放所有申請的資源。

video_device的清理工作:

當要移除視訊裝置節點的時候,就要呼叫video_unregister_device(vdev)將之前註冊到系統中的資訊銷燬掉。

一些videodevice的幫助函式

file/video_device的private_data,我們可以通過以下這些函式來設定和獲取驅動的private_data:

void * video_get_drvdata(structvideo_device *vdev)

void video_set_drvdata(structvideo_device *vdev, void * data)

struct video_device*video_devdata(struct file *file)會返回一個屬於file的video_device結構體指標。

Void * video_drvdata(struct file*file)首先使用video_devdata獲取video_device,然後通過video_get_drvdata獲取private_data.

裝置節點名:

video_device_node_name(structvideo_device *vdev)返回一個字串。

到這兒,就基本上了解了v4l2的控制框架。接下來的博文中,會介紹videobuf及videobuf2的相關知識。