1. 程式人生 > >一、v4l2文件之——v4l2 framework

一、v4l2文件之——v4l2 framework

V4L2驅動框架概述
=====================================

這個文字檔案講述V4L2的框架所提供的各種結構以及它們之間的關係.


1、簡介
------------

由於硬體的複雜性v412驅動往往是非常複雜的: 大多數裝置有多個IC,在/dev目錄下有多個裝置節點, 並也建立non-V4L2的裝置,如DVB,ALSA,FB,I2C和input(IR)裝置。

特別是v412驅動支援很多的音訊/視訊、多路複用/編碼/解碼晶片,使得它比大多數模組更加複雜的。通常這些晶片通過一個或多個I2C匯流排被連線到主橋驅動器匯流排,但也可以使用其他匯流排。這種裝置被稱為“子裝置”。

很長一段時間這個框架被限制在video_device結構,用來建立v4l裝置節點和視訊緩衝區處理的video_buf(請注意,本文件不討論video_buf框架)。

這意味著,所有驅動程式必須設定裝置例項的並連線本身的子裝置。這部分是相當複雜,很多驅動程式很難這樣正確處理。 
由於缺乏一個框架使很多共同的程式碼不可重構。因此,這個框架設定所有驅動到需要的基本結構單元,與此相同的框架更容重構被所有驅動共享的相同程式碼。

2、驅動程式的結構
---------------------

所有的驅動程式有以下結構:
1) 一個結構體,包含裝置的狀態的裝置例項。
2) 一個方法,初始化和控制子裝置(如果有).
3) 建立V4L2的裝置節點 (/dev/videoX, /dev/vbiX and /dev/radioX)和與裝置節點的特殊資料保持關聯。
4) 檔案處理特定的結構,包含每個預先檔案處理資料;
5) 視訊緩衝處理。

這是一個粗略的示意圖:
    device instances(裝置例項)
      |
      +-sub-device instances(子裝置例項)
      |
      \-V4L2 device nodes(V4L2的裝置節點)
            |
            \-filehandle instances(檔案操作例項)
3、框架結構
--------------------------
V4L2_device結構體:代表裝置例項資料 
v4l2_subdev結構體:關聯子裝置例項
video_device結構體:儲存v4l2裝置節點資料
v4l2_fb結構體:跟檔案處理例項聯絡在一起
--------------------------
V4L2框架也包含media 框架,如果一個驅動設定了v4l2_device結構體的mdev成員,子裝置和視訊節點將自動出現media框架中的實體。
4、struct v4l2_device
1).裝置的註冊
每個裝置例項通過結構體v4l2_device(V4L2-device.h中)來代表。只是很簡單的裝置可以僅僅分配這個結構,但大多數的時候要把這個結構體嵌入到一個更大的結構體中。
必須註冊裝置的例項:
 v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);

註冊過程將初始化 v4l2_device 結構體. 如果 dev->driver_data欄位是空, 它將連線到 v4l2_dev.

整合媒體裝置框架的驅動程式需要設定dev-> driver_data指向嵌入結構體v4l2_device例項中driver-specifc裝置結構。這樣就可以通過dev_set_drvdata()呼叫之前註冊V4L2的裝置例項。他們必須還設定結構體v4l2_device MDEV指向一個正確初始化和註冊的media_device例項。

2).v4l2_dev->name的處理
如果v4l2_dev->name是空,那麼這將被設定為從dev取得一個值(通過驅動程式名稱後的bus_id)。 如果你在呼叫v4l2_device_register之前設定它,那麼它將不會被改變。如果dev是NULL,那麼你‘必須’在調v4l2_device_register前設定v4l2_dev->name。

可以使用v4l2_device_set_name()根據驅動程式的名字和
driver-global atomic_t例項設定名稱。這將產生類似於ivtv0,ivtv1等的名字。如果名字最後一個數字,然後將它插入一個破折號:cx18-0,cx18-1等,這個函式返回的例項數量。

第一個引數‘dev’通常是一個pci_dev\ usb介面或者platform_device的struct device指標。dev很少為NULL,但它是ISA裝置或一個裝置建立的多個PCI裝置時,這種情況有可能發生。這可能和v4l2_dev的特殊父裝置有關係。

您也可以提供一個notify()回撥子裝置,可以通過呼叫子裝置通知事件給你。取決於你是否需要設定子裝置。一個子裝置支援的任何通知必須在標頭檔案中定義include/media/<subdevice>.h.

3)設備註銷:

 v4l2_device_unregister(struct v4l2_device *v4l2_dev);

如果dev-> driver_data欄位指向v4l2_dev,它將被重置為NULL。登出也將自動登出裝置所有子裝置。

如果你有一個可熱插拔裝置(如USB裝置),然後發生斷開連線的時候父裝置將變為無效。由於v4l2_device有指向父裝置的指標,它被清除時,要標記父裝置消失。要做到這一點呼叫:

 v4l2_device_disconnect(struct v4l2_device *v4l2_dev);

這並*不*登出subdevs,所以你仍然需要呼叫該的v4l2_device_unregister()函式。如果你的驅動是不能熱插拔,則有無需呼叫
v4l2_device_disconnect()。

4)遍歷所有裝置
有時你需要遍歷一個特定的驅動程式註冊的所有裝置。如果多個裝置驅動程式使用相同的硬體時會發生這種情況。例如ivtvfb驅動程式是一個使用IVTV硬體的framebuffer驅動。
ALSA驅動程式也是這樣的。
您可以遍歷所有註冊的裝置如下:
static int callback(struct device *dev, void *p)
{
    struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);

    /* test if this device was inited */
    if (v4l2_dev == NULL)
        return 0;
    ...
    return 0;
}

int iterate(void *p)
{
    struct device_driver *drv;
    int err;

    /* Find driver 'ivtv' on the PCI bus.
       pci_bus_type is a global. For USB busses use usb_bus_type. */
    drv = driver_find("ivtv", &pci_bus_type);
    /* iterate over all ivtv device instances */
    err = driver_for_each_device(drv, NULL, p, callback);
    put_driver(drv);
    return err;
}
5).裝置引用計數
有時你需要保持一個裝置例項執行計數器。這是常用的對映裝置例項模組選項陣列索引。

建議的方法如下:
static atomic_t drv_instance = ATOMIC_INIT(0);

static int __devinit drv_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id)
{
    ...
    state->instance = atomic_inc_return(&drv_instance) - 1;
}

如果你有多個裝置節點,是很難知道什麼時候安全登出v4l2_device。
v4l2_device引用計數的就是為了處理這種情況。video_register_device
時被稱之為增加引用計數,有裝置節點被釋放時減少引用計數。當引用計數達到零,則v4l2_device回撥release()。可以做最後的清理。
如果其他裝置節點(例如ALSA)的建立,然後你可以增加和減少以及手動呼叫引用計數:
void v4l2_device_get(struct v4l2_device *v4l2_dev);
or:
int v4l2_device_put(struct v4l2_device *v4l2_dev);
5、struct v4l2_subdev

1)、作用
許多驅動程式需要與子裝置進行通訊。這些裝置可以完成所有這類任務
最常用子裝置有處理音訊 和/或 視訊混流、編碼或解碼器、攝像頭感測器和攝像頭控制器。

通常情況下,它們都是I2C器件,但也不全是。為了給這些子裝置提供一致的介面,v4l2_subdev結構(V4L2 subdev.h)被創建立。
每個子裝置驅動程式必須有一個v4l2_subdev結構。這個結構可單獨作為簡單的裝置存在或者如果需要儲存更多的狀態資訊時它可能被嵌入在一個更大的結構體中。一般有一個低級別裝置結構(如i2c_client),其中包含通過核心設定的裝置資料。建議通過v4l2_set_subdevdata()函式儲存這種指標到v4l2_subdev的私有資料。這樣就會使從v4l2_subdev中很容易的獲取實際的低階匯流排的特殊裝置資料。

還需要一種方法從低階結構體中獲取v4l2_subdev,對於普通的i2c_client結構體,i2c_set_client()呼叫時可以儲存一個v4l2_subdev指標,對於其他的匯流排可以用相應的方法。
橋式驅動器可能也需要儲存per-subdev 私有資料,例如一個指向橋特定的per-subdev 的私有資料,v4l2_subdev 結構體為了能夠訪問v4l2_get_subdev_hostdata()和v4l2_set_subdev_hostdata,從而提供給主機的私有資料。
從橋驅動角度來說,載入子裝置模組和某種方式下v4l2_subdev指標的成員,對於i2c裝置來說很容易:可以呼叫i2c_get_clientdata()函式。對於其他匯流排來說也要用相似的方法。在i2c總線上子裝置的協助功能可以讓工作更有效。

2)、子裝置操作函式集
每個v4l2_subdev包含子裝置驅動程式可以實現函式指標(或保留NULL,如果它不適用)。由於子裝置可以做很多不同的東西,因此不能結束一個只實現了很少操作的函式操作集結構,函式指標按類別儲存的,每個類別都有其自己的OPS結構。
頂層OPS結構包含指向的類別的OPS結構,如果在subdev驅動程式不支援任何策略,這可能是NULL。
它看起來像這樣:
struct v4l2_subdev_core_ops {
    int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip);
    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;
};
核心操作對所有的子裝置都是一樣的,其他類別的完成依靠子裝置的功能,例如視訊裝置是不大可能支援音訊操作,反之亦然。

這種設定限制的函式指標的數目,同時還使其易於新增新操作函式和類別。

3)、子裝置初始化
子裝置驅動初始化的v4l2_subdev結構使用:

 v4l2_subdev_init(sd, &ops);

之後,您需要初始化一個獨一無二的名稱subdev->name和設定模組所有者。這樣做是為了如果你使用的I2C協助功能。

如果需要整合媒介框架,你必須通過呼叫media_entity_init()初始化嵌入在v4l2_subdev結構中的media_entity結構:

 struct media_pad *pads = &my_sd->pads;
 int err;

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

pads陣列必須此前已初始化。不需要手動設定結構media_entity型別和name欄位,但如果需要,修改欄位必須初始化。

參考實體將在subdev裝置節點(如果有的話)開啟/關閉時自動獲得/釋放時。
在子設備註銷之前要呼叫下邊函式清除媒介實體:
              media_entity_cleanup(&sd->entity);

4)、註冊和登出:
 
裝置(橋)驅動程式需要註冊用v4l2_subdevv4l2_device:
           int err = v4l2_device_register_subdev(v4l2_dev, sd);

如果subdev模組消失之前它被註冊,可能會失敗。如果subdev模組消失之後,這個功能呼叫成功,它使subdev-> dev指標指向v4l2_device。
如果的v4l2_device父裝置具有非空MDEV欄位,子裝置實體將自動註冊media裝置。
你可以登出一個子裝置使用:
              v4l2_device_unregister_subdev(sd);

呼叫之後subdev模組被解除安裝,sd->dev == NULL.

5)、操作函式的呼叫:
你可以直接呼叫操作函式:
             err = sd->ops->core->g_chip_ident(sd, &chip);
但是使用這個巨集將更好和更容易:
 err = v4l2_subdev_call(sd, core, g_chip_ident, &chip);

巨集將具有正確的空指標檢測功能,如果subdev是NULL則返回-ENODEV;
如果其他的subdev->core或者subdev->core->g_chip_ident是空的,那麼返回
-ENOIOCTLCMD;或者執行正常的操作subdev->ops->core->g_chip_ident().

它也可以通過下邊的函式呼叫全部或部分子裝置:

 v4l2_device_call_all(v4l2_dev, 0, core, g_chip_ident, &chip);

不支援這個OPS的任何子裝置跳過並且錯誤的結構將被忽略。如果你想檢查是否有錯誤,使用這個:

 err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_chip_ident, &chip);

任何不是-ENOIOCTLCMD 的錯誤將退出迴圈,如果沒有錯誤發生,將返回0.

這兩個呼叫的第二個引數是一組ID。如果為0,然後所有subdevs被呼叫。如果不為零,但那麼只有那些其組ID匹配值將被呼叫。橋驅動註冊一個subdev之前它可以設定sd-> grp_id的值(預設值是0)。這個值被橋驅動器和子裝置驅動擁有,程式將不會修改或使用它。
這組ID給橋驅動器控制如何呼叫回撥。例如,可能有多個音訊晶片在板子上,每一個都能改變音量。但通常情況下只有一個將被用來當用戶想改變音量時被操作。你可以設定subdev組ID,例如AUDIO_CONTROLLER和改變指定組ID值時呼叫v4l2_device_call_all()。以確保它只會去的subdev需要。

如果子裝置需要通知v4l2_device父裝置一個事件,那麼它可以呼叫v4l2_subdev_notify(sd,notification,arg).這個巨集檢查是否有一個notify()的回撥函式被定義,如果沒有返回-ENODEV。否則呼叫回掉函式並正常返回。

使用v4l2_subdev優勢是它是一個通用結構,不包含任何對底層硬體操作。因此驅動程式可能包含幾個subdevs,使用I2C匯流排進行控制,但也通過GPIO引腳控制subdev。這種區別只是設定裝置時有關,一旦subdev註冊是後是完全透明的。


6、V4L2的子裝置使用者空間的API
-----------------------------
除了通過v4l2_subdev_ops結構給出V4L2的核心API,V4L2的子裝置也可以直接被使用者空間控制,可以直接訪問。

子裝置在/ dev中建立名為V4L-subdevX裝置節點。如果一個子裝置支援直接使用者空間的配置,它必須在註冊之前設定V4L2_SUBDEV_FL_HAS_DEVNODE標誌。
子設備註冊之後,v4l2_device驅動可以通過呼叫v4l2_device_register_subdev_nodes()函式給所有標記為V4L2_SUBDEV_FL_HAS_DEVNODE的已註冊子裝置建立裝置節點。子裝置未註冊的那些裝置節點會被自動刪除。

裝置節點處理的是V4L2 API的一個子集。
VIDIOC_QUERYCTRL
VIDIOC_QUERYMENU
VIDIOC_G_CTRL
VIDIOC_S_CTRL
VIDIOC_G_EXT_CTRLS
VIDIOC_S_EXT_CTRLS
VIDIOC_TRY_EXT_CTRLS
     控制ioctls函式與V4L2中定義是相同的。他們的行為是相同的,唯一不同的是,他們只處理子裝置的執行控制。根據驅動程式,這些控制也可以通過一個(或幾個)V4L2的裝置節點來訪問。
VIDIOC_DQEVENT
VIDIOC_SUBSCRIBE_EVENT
VIDIOC_UNSUBSCRIBE_EVENT

      事件ioctls函式與V4L2中定義是相同的。他們的行為是相同的,唯一不同的是,他們只處理子裝置的事件產生。根據驅動程式,這些控制也可以通過一個(或幾個)V4L2的裝置節點來報告。

     子裝置驅動程式要使用事件時,需要設定的V4L2_SUBDEV_USES_EVENTS v4l2_subdev::flags和在註冊之前初始化子裝置v4l2_subdev:: nevents事件佇列深度。註冊事件後可以像往常一樣在v4l2_subdev:: devnode裝置節上排隊。

     要正確地支援事件,poll()的檔案操作函式必須實現。
私有ioctl:
沒有在上述列表所有ioctls函式呼叫會直接傳遞到子裝置驅動程式通過core::ioctl操作。

7、I2C sub-device drivers
----------------------
由於這些驅動程式是很常見的,特殊的輔助功能可用於更容易使用這些驅動程式(V4L2-common.h)。
1)、建立i2c client和subdev之間的關係
對加入v4l2_subdev支援I2C驅動的推薦的方法是:嵌入V4L2_subdev結構體到一個狀態結構體,這個結構體被每個I2C裝置例項建立。很簡單的裝置,沒有狀態結構,在這種情況下,你可以直接建立一個v4l2_subdev。
一個典型的狀態結構看起來像這樣(where 'chipname' is replaced bythe name of the chip):
struct chipname_state {
 struct v4l2_subdev sd;
 ...  /* additional state fields */
};

如下初始化v4l2_subdev結構:
        v4l2_i2c_subdev_init(&state->sd, client, subdev_ops);
此函式將給4l2_subdev的所有成員賦值,並且確保v4l2_sudev和i2c_client都指向彼此。

您還應該新增一個輔助inline函式從v4l2_subdev指標得到chipname_state結構:

static inline struct chipname_state *to_state(struct v4l2_subdev *sd)
{
    return container_of(sd, struct chipname_state, sd);
}

使用這個從v4l2_subdev結構得到i2c_client結構:

    struct i2c_client *client = v4l2_get_subdevdata(sd);

這從一個i2c_client得到v4l2_subdev結構:

    struct v4l2_subdev *sd = i2c_get_clientdata(client);

確保remove()回撥時被呼叫時呼叫v4l2_device_unregister_subdev(sd函式。這將從橋驅動中登出子裝置,即使是沒有註冊子裝置,這個呼叫也是安全的。
這樣做的原因時:當橋式驅動器的登出I2C介面卡時,remove()回撥函式被介面卡上的I2C裝置呼叫。這樣之後相應v4l2_subdev結構就會變成無效,所以它們必須首先被登出。從remove()回掉函式呼叫v4l2_device_unregister_subdev(sd)是為了保證這樣做的正確性。

2)、i2c 從裝置地址探測
橋式驅動器也有一些輔助功能,它可以使用:

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

這個是載入模組(如果沒有需要載入的模組,可以為NULL)並調i2c_new_device()輸入的引數是i2c_adapter和晶片/地址。如果一切順利,那麼它註冊這個v4l2_device 子裝置。

您還可以使用v4l2_i2c_new_subdev()最後一個引數傳遞一個可能的I2C地址陣列,它可以被探測。如果之前的引數是零,那麼這些探測地址就會被用。一個非零的引數意味著你有已知的i2c地址,所以在這種情況下,測將不會發生。

如果出錯了,函式返回NULL。

請注意,你傳遞 給v4l2_i2c_new_subdev()的chipid引數通常跟模組名稱相同。它允許你指定一個晶片的變型,例如“SAA7114”或“SAA7115”。一般來說,雖然I2C驅動程式會自動檢測這個。在後些時候,要用的的chipid一般是尋找最近。它不同於之間的I2C驅動程式和因為有時候可能會造成混淆。你可以在I2C驅動程式程式碼的i2c_device_id表中尋找支援該的晶片變種,它將列出所有的可能性晶片。

有兩個輔助函式:
v4l2_i2c_new_subdev_cfg: 這個函式將新增新的中端和平臺數據引數,有 'addr' 和 'probed_addrs' 引數,如果’addr’不為0,就用它,否則‘probed_addrs’將被探測。

例如:這將探測0x10地址
struct v4l2_subdev *sd = v4l2_i2c_new_subdev_cfg(v4l2_dev, adapter,
           "module_foo", "chipid", 0, NULL, 0,I2C_ADDRS(0x10));

v4l2_i2c_new_subdev_board 用一個 i2c_board_info 結構體通過i2c驅動來替代irq、 platform_data 和 addr 引數。

如果subdev支援s_config core ops,在子裝置被設定之前,這個操作將被呼叫,其引數就是ird和platform_data。舊的v4l2_i2c_new_(probed_)subdev 函式也會呼叫 s_config,但是引數ira和platform都是0.
8、struct video_device
1)、video device的建立
在/dev目錄下實際的裝置節點是使用的video_device結構(V4L2 dev.h)建立的。這個結構可以動態分配或嵌入在一個更大的結構中。
如下動態分配:
    struct video_device *vdev = video_device_alloc();
    if (vdev == NULL)
        return -ENOMEM;
    vdev->release = video_device_release;
如果是嵌入到一個更大的結構,那麼你必須設定的release()函式回撥到自己的函式:
          struct video_device *vdev = &my_vdev->vdev;
                     vdev->release = my_vdev_release;
最後一個使用者退出使用的視訊裝置後,release回掉函式必須被設定和呼叫。
預設情況下video_device_release()回撥函式只是僅僅呼叫kfree釋放分配的記憶體。

2)、video_device結構體成員介紹
你也應該設定這些欄位(video_device的成員):
- v4l2_dev: 設定這個v4l2_device父裝置。
- name: 設定為描述性和獨特的東西。
- fops: 設定v4l2_file_operations結構。
- ioctl_ops: 
如果你想用v4l2_ioctl_ops簡化ioctl的維護(強烈推薦使用這個引數,因為在以後可能會被強制使用),設定你的v4l2_ioctl_ops結構體。
- lock:
 如果你想要做驅動中的所有鎖,那麼設定其為NULL。否則你可以使其指向一個mutex_lock的結構體,在任何v4l2_file_operations操作被呼叫之前,這個鎖將有效,並在之後釋放。
- prio:
 記錄優先順序。. 經常實現為 VIDIOC_G/S_PRIORITY.
  如果左邊是NULL(If left to NULL),將在v4l2_device中用 v4l2_prio_state結構體。如果你想在每個裝置節點擁有一個分離的優先順序狀態你可以指向你自己的v4l2_prio_state結構體。


- parent:
 當v4l2_device作為父裝置被註冊為NULL時,應該設定這個成員。這僅僅發生在一個硬體裝置有多個PCI裝置,並且它們都共享相同的v4l2_device核心。.

  cx88驅動就是這樣的一個例子: 只有一個v4l2_device結構作為核心, 但是他被用在原始視訊PCI裝置和MPEG PCI 裝置,因此v4l2_device結構沒有連線到特殊的PCI裝置,這個裝置沒有設定父裝置 。 But when the struct video_device is setup you do know which parent PCI device to use.
- flags: 
可選擇的.如果你想讓框架處理VIDIOC_G/S_PRIORITY控制,那麼設定為 V4L2_FL_USE_FH_PRIO 。這需要你用結構體v4l2_fh。
 一旦所有的驅動用核心優先順序處理,最終這個標記將消失。但是現在需要顯式的指定。.
如果使用v4l2_ioctl_ops,那麼你應該在v4l2_file_operations結構中設定unlocked_ioctl到video_ioctl2。

不要使用ioctl,它在將來會被丟棄。

v4l2_file_operations結構是file_operations的一個子集。主要區別是inode引數被忽略,因為它從來沒有使用過。
3)、media entity
如果需要整合media框架,你必須初始化media_entity結構體並通過呼叫media_entity_init()函式嵌入式video_device結構體中(entity欄位):

 struct media_pad *pad = &my_vdev->pad;
 int err;
 err = media_entity_init(&vdev->entity, 1, pad, 0);

pad陣列必須預先初始化。沒有必要手動設定的結構media_entity的 type和name欄位。

一個被引用的media 實體在視訊裝置開啟和關閉時自動請求和釋放


9、v4l2_file_operations 和 鎖
--------------------------------
你可以在結構體video_device中設定一個指標mutex_lock。通常這將是一個頂層互斥或者使每個裝置節點互斥。如果你想更精細的鎖那麼你必須將其設定為NULL,在用你自己的鎖。

驅動開發者會來決定用哪種方法。然而,你的驅動中有一個很長的延遲操作,那麼這是就最好不要用這個mutex_lock,而用你自己的鎖,這樣可以讓使用者在等待這個長延遲完成時做其他的工作。

如果一個鎖被指定,那麼所有的檔案操作將被依次上鎖。如果你使用videobuf那麼你必須通過相同的鎖來鎖videobuf佇列初始化函式:如果videobuf在等待一幀到達,那麼它要短暫的解鎖,之後在重新上鎖。如果你的驅動程式程式碼中的存在等待,那麼你應該在第一個程序等待時,允許其他程序訪問該裝置節點


一個熱插拔斷開實施也應採取之前呼叫v4l2_device_disconnect鎖。

10、video_device 註冊
1)、註冊視訊裝置
接下來將註冊的視訊裝置:在這個過程中會建立的字元裝置
    err = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
    if (err) {
        video_device_release(vdev); /* or kfree(my_vdev); */
        return err;
    }
如果的v4l2_device其父節點裝置具有非空MDEV欄位,視訊裝置的實體將被自動註冊media裝置。
註冊哪種裝置依賴於裝置型別引數。存在以下幾種裝置型別:

VFL_TYPE_GRABBER: 視訊輸入/輸出裝置videoX
XVFL_TYPE_VBI: 垂直空白資料vbiX(即關閉字幕,圖文電視)
VFL_TYPE_RADIO: radioX無線電調諧器
VFL_TYPE_VTX: vtxX圖文電視裝置(不建議使用,不使用)
2)、裝置節點號的獲取
最後一個引數給你一定數量的控制了裝置使用裝置節點(即在videoX中的X)。通常情況下只需傳-1讓V4L2框架選擇第一個空閒的節點號。
但有時使用者想指定一個特定的節點號。常見的驅動程式允許使用者通過驅動模組選項選擇一個特定的裝置節點號。然後這一數字被傳遞給video_register_device函式,它將嘗試選擇該裝置節點號。如果該節點號已經使用,那麼下一個空閒的裝置節點數量將被選中,同時會向核心日誌發出警告資訊。

另外使用情況是,如果一個驅動程式建立許多裝置。在這種情況下它可以是有用的放置在不同範圍的不同視訊裝置。例如,視訊捕獲裝置節點從0開始,視訊輸出裝置節點從16開始。

所以,你可以使用最後一個引數指定一個最小裝置節點號,V4L2框架將盡量選擇與你傳遞的節點號相近的空閒號。如果失敗,那麼它會選擇第一個空閒的節點號。

在這種情況下,如果你不想關心繫統不能夠選擇你指定的裝置節點時發出的有關警告,可以呼叫的函式video_register_device_no_warn()代替註冊視訊裝置。

3)、系統中裝置的屬性
每當建立一個裝置節點時,它的的一些屬性也會被建立。
在/ sys/class/video4linux目錄下你會檢視到這些屬性。查詢video0時,
你會看到它的“name”和“index”屬性。 'name'的屬性是由video_device結構體中的'name'欄位的確定的。

“index”屬性是裝置節點的索引:每次呼叫video_register_device()之後,該索引就會自加1。註冊第一個視訊裝置的裝置節點總是以0開始的。

使用者可以設定udev的規則,利用索引屬性設定一些花哨的裝置名稱(如視訊捕捉裝置節點的名稱為mpegX MPEG)。
該設備註冊成功之後,你可以使用這些欄位:

- vfl_type: 傳遞到video_register_device()函式的裝置型別。
- minor: 分配給裝置的次裝置號。
- num: 裝置節點數目(i.e. the X in videoX).
- index: 裝置索引號。

如果註冊失敗,那麼你需要呼叫video_device_release()來釋放分配的video_device結構,或者自己定義的嵌入video_deviced的結構。如果註冊失敗,vdev-〉release()回撥函式將永遠不會被呼叫。如果註冊失敗,也不應該嘗試登出該裝置。

4)、清除video_device 
--------------------
當裝置節點被刪除、驅動程式被解除安裝、或因為USB裝置被斷開,那麼你應該用下邊函式登出它們:

    video_unregister_device(vdev);

這將從sysfs系統中移除裝置節點(會引起從/dev中移除udev).

video_unregister_device()函式返回之後,沒有新的裝置開啟。然而,在這種情況下,USB裝置一些應用可能仍然有節點開啟,因此要登出所有操作函式(除了release() ),有可能返回錯誤。

當最後一個使用者視訊裝置節點退出後,個vdev->release()回撥函式被呼叫,做最後的清除。

如果它已經被初始化媒體視訊裝置的相關實體,那麼也要掉用下邊的函式清除它:

    media_entity_cleanup(&vdev->entity);
它在release()中被呼叫

5)、video_device的輔助函式
-----------------------------

有一些有用的輔助功能:
A、檔案或視訊裝置的私有資料
可以用下邊的操作設定或獲取video_device結構體中的驅動私有資料

void *video_get_drvdata(struct video_device *vdev);
void video_set_drvdata(struct video_device *vdev, void *data);
注意,可以在video_register_device()函式之前安全的調video_set_drvdata()

還有這個函式:
struct video_device *video_devdata(struct file *file);
返回屬於file結構體的video_device結構體

video_drvdata function和video_get_drvdata 結合到一塊產video_devdata函式 : 獲取video_device結構體中的驅動私有資料
void *video_drvdata(struct file *file);

你可以從video_device結構體中獲取v4l2_device結構體
struct v4l2_device *v4l2_dev = vdev->v4l2_dev;

B、裝置節點資料
video_device節點核心名稱可以通過下邊函式獲取:
const char *video_device_node_name(struct video_device *vdev);
這個名稱被用來作為使用者空間的線索,例如udev。這個功能在可能的情況下通過video_device:: num和的video_device::minor欄位替代。

6)、video buffer 輔助函式
-----------------------------
V4l2核心介面提供了一組處理視訊緩衝的標準方法(稱作videobuf),這些方法允許一個驅動實現統一的read()、mmap()和overlay操作。並且支援DMA 散聚方法(videobuf-dma-sg), DMA線性訪問(videobuf-dma-contig)和記憶體緩衝訪問,多數用於USB驅動(videobuf-vmalloc).

有關videobuf層的資訊可以檢視Documentation/video4linux/videobuf檔案

11、V4L2 檔案處理:struct v4l2_fh

1)、功能和使用方法
結構體v4l2_fh提供了一種輕鬆地保持檔案處理特定資料的方法,這個資料被V4L2框架使用。新的驅動程式必須使用v4l2_fh結構體,如果的video_device標誌中V4L2_FL_USE_FH_PRIO也設定,那麼它也可以用來實現優先處理VIDIOC_G/ S_PRIORITY)。

通過測試video_device-> flags中V4L2_FL_USES_V4L2_FH位的,v4l2_fh(在V4L2的框架中,而不是驅動中)的使用者就會知道驅動程式是否使用v4l2_fh作為其file->private_data指標。當v4l2_fh_init()被呼叫時設定此位。
結構體v4l2_fh被分配作為驅動自己檔案處理結構的一部分,並且在驅動開啟函式中將 file->private_data 設定為v4l2_fh。
在許多情況下,結構體v4l2_fh將嵌到一個大的結構體中,在這種情況下,你將要在open()函式中呼叫v4l2_fh_init和v4l2_fh_add,在release()函式中呼叫v4l2_fh_del+v4l2_fh_exit。

驅動可以用過巨集container_of來提取自己的檔案處理結構。
Example:
struct my_fh {
    int blah;
    struct v4l2_fh fh;
};

...

int my_open(struct file *file)
{
    struct my_fh *my_fh;
    struct video_device *vfd;
    int ret;

    ...

    my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL);

    ...

    ret = v4l2_fh_init(&my_fh->fh, vfd);
    if (ret) {
        kfree(my_fh);
        return ret;
    }

    ...

    file->private_data = &my_fh->fh;
    v4l2_fh_add(&my_fh->fh);
    return 0;
}

int my_release(struct file *file)
{
    struct v4l2_fh *fh = file->private_data;
    struct my_fh *my_fh = container_of(fh, struct my_fh, fh);

    ...
    v4l2_fh_del(&my_fh->fh);
    v4l2_fh_exit(&my_fh->fh);
    kfree(my_fh);
    return 0;
}

2)、一些函式說明

下面是一些v4l2_fh函式的簡短說明:

int v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev)

  初始化檔案處理,必須在驅動呼叫v4l2_file_operations->open()時被執行

void v4l2_fh_add(struct v4l2_fh *fh)

   新增v4l2_fh到video_device檔案處理列表。必須在一個檔案處理被完全初始化之後呼叫,即在v4l2_fh_init()函式之後呼叫。

void v4l2_fh_del(struct v4l2_fh *fh)

  從video_device()中登出檔案處理,檔案處理退出函式可能被呼叫.

void v4l2_fh_exit(struct v4l2_fh *fh)

  Uninitialise the file handle. After uninitialisation the v4l2_fh
  memory can be freed.
 當第一個檔案處理被開啟和最後一個檔案處理被關閉時,許多驅動程式需要做一些事情時。增加了兩個輔助功能檢查,是否v4l2_fh結構是否僅僅開啟相關裝置節點的檔案處理:
int v4l2_fh_is_singular(struct v4l2_fh *fh)

  Returns 1 if the file handle is the only open file handle, else 0.

int v4l2_fh_is_singular_file(struct file *filp)

  Same, but it calls v4l2_fh_is_singular with filp->private_data.
12、V4L2 事件
-----------
V4L2事件提供了一個通用的方式來傳遞事件到使用者空間。驅動程式必須使用v4l2_fh才能夠支援V4L2的事件。

1)、事件的定義
事件通過一個型別和一個可選擇的ID被定義。這個ID可能跟V4L2目標有關係,例如控制ID,通常情況下,這個ID為0 。
當用戶要用一個事件時,驅動將會給這個事件分配一個kevent結構體,這可以確保驅動在短時間內產生單一型別大量事件時,不會覆蓋其他型別的事件。
如果你得到的單一型別事件多於保留的kevents數目時,舊的事件將被丟棄,新的事件被新增。
此外,內部結構體v4l2_subscribed_event 的兩個回掉函式merge()和replace()要在驅動中設定。當一個新的事件被提高,並且沒有更多的空間時,這些回掉函式被呼叫,replace()被呼叫時允許用新的事件代替有負載效應的舊事件,會將舊事件中相關資料合併到新的事件中去。當這個事件型別僅僅有一個kevent被分配時會呼叫它,merge()回掉函式允許你合併舊的事件到第二個舊的事件,當兩個或更多個kevent被分配時,它也會被呼叫。
這種方法不會有狀態資訊丟失,正確的內部資訊導致了這種狀態。
Replace和merge回掉函式一個好的例子是在v4l2-event.c中的ctrls_replace和ctrls_merge()回掉函式控制的事件。
Useful functions:
2)、事件處理函式
- v4l2_event_alloc()
  使用事件驅動程式必須分配的檔案控制代碼的事件。驅動程式通過呼叫函式不止一次,
  可以保證至少n個總事件已分配。功能可能無法在原子上下文中被呼叫。
- v4l2_event_queue()
  視訊裝置的佇列中的事件。驅動程式的唯一責任是填寫它的型別和資料欄位。其他欄位將被V4L2填充。
- v4l2_event_subscribe()
  video_device-> ioctl_ops> vidioc_subscribe_event必須檢查驅動程式是否能夠產生與特定事件ID的事件。然後,它呼叫v4l2_event_subscribe()認同該事件。最後一個引數是這個事件佇列的大小,如果為0,V4L2架構依據事件型別設定一個預設的值。
- v4l2_event_unsubscribe()
 vidioc_unsubscribe_event在結構v4l2_ioctl_ops中。一個驅動程式可以直v4l2_event_unsubscribe()接使用取消訂閱程序。

  在特殊型別V4L2_EVENT_ALL的可能被用來取消所有事件。驅動程式可能用一種特殊的方式來處理這些事件。
- v4l2_event_pending()  
返回掛起的事件數量。實施調查時非常有用。
使用者空間通過poll系統呼叫實現事件的傳遞,驅動可以用v4l2_fh->wait作為poll_wait呼叫的引數。
有標準的和私有的事件,新的標準事件必須用最小的可用事件型別,驅動必須從它們所屬的類開始到類基址分配它們的事件。類基址是
V4L2_EVENT_PRIVATE_START + n * 1000 ,n是最小的有效數.
類中的第一個事件型別是被保留將來用的,一次第一個有效類型別是“基址+1”