v4l2編程接口——內核driver

V4L2 驅動隨着硬件的變化也愈來愈複雜,如今大部分設備有裏面包含了多個IC, 在/dev目錄下不只要創建 V4L2 的節點,並且還須要創建如:DVB、ALSA、FB、I2C、input等設備節點。事實上 V4L2 驅動須要支持音頻/視頻的混音/編碼/解碼等IC因此比其餘驅動都要複雜不少,一般這些IC經過 i2c 總線鏈接到主板,這些設備都統稱爲sub-devices。在很長的一段時間裏 V4L2 被限制只能在 video_device 結 構體裏面建立,而且用video_buf 控制視頻緩存,這意味着全部的驅動建立本身的實例都將鏈接到本身的sub-devices,這些工做一般很複雜並常常引發錯誤,許多常見的代碼由於缺少一 個框架而沒法重構。所以這個框架創建起了基本的機制,全部的驅動都須要和這個框架結合以便共用其中的函數。所以 V4L2  框架做了相應的優化:它有一個 v4l2_device 結構做爲設備實例,一個v4l2_subdev結構做爲子設備實例,video_device 結構包含了v4l2_device 節點,之後將會有一個 v4l2_fh 的結構做爲與文件句柄的實例。每一個設備都採用 v4l2_device 結構來表示。很是簡單的設備均可以申請這個結構,但一般會將這個結構嵌入一個更大的結構中。linux

一、 video_device緩存

在 v4l2 中用 struct video_device 表明一個視頻設備,該結構說明以下:網絡

[cpp] view plain copy app

  1. struct video_device  框架

  2. {  ide

  3.     /* 設備操做合集 */  函數

  4.     const struct v4l2_file_operations *fops;  優化

  5.   

  6.     /* sysfs節點 */  編碼

  7.     struct device dev;      /* v4l device */  spa

  8.     struct cdev *cdev;      /* 字符設備節點 */  

  9.   

  10.     /* Set either parent or v4l2_dev if your driver uses v4l2_device */  

  11.     struct device *parent;      /* 父設備 */  

  12.     struct v4l2_device *v4l2_dev;   /* v4l2_device parent */  

  13.   

  14.     /* 設備信息 */  

  15.     char name[32];  

  16.     int vfl_type;  

  17.     /* 若是註冊失敗 minor 將被設置爲 -1 */  

  18.     int minor;  

  19.     u16 num;  

  20.     /* 須要用位操做 flags */  

  21.     unsigned long flags;  

  22.     /* attribute to differentiate multiple indices on one physical device */  

  23.     int index;  

  24.   

  25.     int debug;          /* Activates debug level*/  

  26.   

  27.     /* Video standard vars */  

  28.     v4l2_std_id tvnorms;        /* Supported tv norms */  

  29.     v4l2_std_id current_norm;   /* Current tvnorm */  

  30.   

  31.     /* 釋放設備的回調函數 */  

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

  33.   

  34.     /* ioctl 回調函數 */  

  35.     const struct v4l2_ioctl_ops *ioctl_ops;  

  36. };  

  37. 其中  

  38. struct cdev {  

  39.     struct kobject kobj;    /* 內核對象 */  

  40.     struct module *owner;  

  41.     const struct file_operations *ops;  /* 設備操做合集 */  

  42.     struct list_head list;  

  43.     dev_t dev;  

  44.     unsigned int count;  

  45. };  

  46.   

  47. struct v4l2_file_operations {  

  48.     struct module *owner;  

  49.     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);        /* 讀數據 */  

  50.     ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); /* 寫數據 */  

  51.     unsigned int (*poll) (struct file *, struct poll_table_struct *);        /* 同步操做 */  

  52.     long (*ioctl) (struct file *, unsigned int, unsigned long);              /* 特殊命令 */  

  53.     long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);  

  54.     unsigned long (*get_unmapped_area) (struct file *, unsigned long,  

  55.                 unsigned long, unsigned long, unsigned long);  

  56.     int (*mmap) (struct file *, struct vm_area_struct *);                    /* 內存映射 */  

  57.     int (*open) (struct file *);                                             /* 打開設備 */  

  58.     int (*release) (struct file *);                                          /* 釋放設備 */  

  59. };  

視頻設備在 linux 中做爲字符設備出現,域 cdev /dev/videox 節點關聯,打開節點就至關於執行cdev 的 open 函數,cdev 的 ops 域即 file_operations 的一些接口在通過必定的參數過濾後最終都調用了video_device 的 fops 域即v4l2_file_operations的 成員,因此在編寫驅動程序的時候須要實現 v4l2_file_operations 的接口:其中 open 用於打開視頻設備, read 接口用於讀取視頻數據,poll 接口用於視頻流的同步,mmap 將視頻設備的保存數據的內存空間的物理地址映射到用戶空間,ioctl 用於向視頻設備發送命令並查詢相關信息(ioctl 通常設置爲 v4l2 提供的 video_ioctl2 函數,並最終調用 video_device 的 ioctl_ops 域即 v4l2_ioctl_ops),一般須要實現的 ioctl 接口以下:

[cpp] view plain copy

  1. static const struct v4l2_ioctl_ops xxx_cam_ioctl_ops = {  

  2.     .vidioc_querycap         = vidioc_querycap,  

  3.     .vidioc_enum_input       = vidioc_enum_input,  

  4.     .vidioc_g_input          = vidioc_g_input,  

  5.     .vidioc_s_input          = vidioc_s_input,  

  6.     .vidioc_queryctrl        = vidioc_queryctrl,  

  7.     .vidioc_s_ctrl           = vidioc_s_ctrl,  

  8.     .vidioc_g_ctrl           = vidioc_g_ctrl,  

  9.     .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,  

  10.     .vidioc_try_fmt_vid_cap  = vidioc_try_fmt_vid_cap,  

  11.     .vidioc_g_fmt_vid_cap    = vidioc_g_fmt_vid_cap,  

  12.     .vidioc_s_fmt_vid_cap    = vidioc_s_fmt_vid_cap,  

  13.     .vidioc_reqbufs          = vidioc_reqbufs,  

  14.     .vidioc_querybuf         = vidioc_querybuf,  

  15.     .vidioc_qbuf             = vidioc_qbuf,  

  16.     .vidioc_dqbuf            = vidioc_dqbuf,  

  17.     .vidioc_streamon         = vidioc_streamon,  

  18.     .vidioc_streamoff        = vidioc_streamoff,  

  19.     .vidioc_default          = vidioc_default,  

  20. };  

該結構各域的做用如上篇文章所述。video_device 經過 video_register_device 函數註冊,函數原型以下:

[cpp] view plain copy

  1. /** 

  2.  *  video_register_device - 註冊一個 v4l2 設備 

  3.  *  @vdev: video_device 結構 

  4.  *  @type : v4l2 設備的類型 

  5.  *  @nr:   從設備號(0 == /dev/video0, 1 == /dev/video1, -1 == first free) 

  6.  * 

  7.  *  註冊代碼將會根據註冊設備的類型指派從設備號,若是沒有合適的從設備號將會返回錯誤值. 

  8.  * 

  9.  *  一般有以下幾種設備類型 

  10.  * 

  11.  *  %VFL_TYPE_GRABBER - 視頻採集設備 

  12.  * 

  13.  *  %VFL_TYPE_VTX     - 圖文電視設備 

  14.  * 

  15.  *  %VFL_TYPE_VBI     - 場消隱區解碼設備(undecoded) 

  16.  * 

  17.  *  %VFL_TYPE_RADIO   - 無線設備 

  18.  */  

  19. int video_register_device(struct video_device *vdev, int type, int nr)  

  20. {  

  21.     return __video_register_device(vdev, type, nr, 1);  

  22. }  

  23. EXPORT_SYMBOL(video_register_device);  

該 函數註冊流程較簡單:首先會根據設備類型肯定在 /dev 目錄下的節點名稱以及從設備號的偏移和值,而後爲 cdev 申請內存空間並註冊,將 vdev->cdev->ops 設置爲內核提供的 v4l2_fops,最後將 vdev->dev 註冊到 sysfs 中。

二、v4l2_subdev

許多驅動須要與子設備通訊,這些設備作的任務比較常見的是音視頻處理、編解碼等, 網絡攝像頭比較常見的子設備是傳感器和攝像頭控制器。爲了提供一個統一的接口給這些子設備,內核將涉及到子設備控制的那部分(如 vidioc_s_ctrl、vidioc_s_frequency 等)獨立了出來,用 struct v4l2_subdev 來表示以方便用戶實現 v4l2 驅動程序:

[cpp] view plain copy

  1. struct v4l2_subdev {  

  2.     struct list_head list;              /* 連接至 v4l2_device */  

  3.     struct module *owner;  

  4.     u32 flags;  

  5.     struct v4l2_device *v4l2_dev;       /* 指向 v4l2_device */  

  6.     const struct v4l2_subdev_ops *ops;  /* subdev 操做合集 */  

  7.     /* name must be unique */  

  8.     char name[V4L2_SUBDEV_NAME_SIZE];  

  9.     /* can be used to group similar subdevs, value is driver-specific */  

  10.     u32 grp_id;  

  11.     /* 私有數據 */  

  12.     void *priv;  

  13. };  

其中 list 域做爲鏈表節點連接至 v4l2_dev 指向的  v4l2_device 結構中,這個結構中最重要的成員就是 struct v4l2_subdev_ops *ops,該域包含了 v4l2 設備支持的全部操做,定義以下:

[cpp] view plain copy

  1. struct v4l2_subdev_ops {  

  2.     const struct v4l2_subdev_core_ops  *core;   /* 通用操做合集 */  

  3.     const struct v4l2_subdev_tuner_ops *tuner;  /* 調諧器操做合集 */  

  4.     const struct v4l2_subdev_audio_ops *audio;  /* 音頻操做合集 */  

  5.     const struct v4l2_subdev_video_ops *video;  /* 視頻操做合集 */  

  6. };  

v4l2_subdev_core_ops 包含的操做合集是各類類型設備通用的:

[cpp] view plain copy

  1. struct v4l2_subdev_core_ops {  

  2.     int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip);  /* 獲取設備id */  

  3.     int (*log_status)(struct v4l2_subdev *sd);                                      /* 狀態消息 */  

  4.     int (*s_config)(struct v4l2_subdev *sd, int irq, void *platform_data);          /* 設置配置信息 */  

  5.     int (*init)(struct v4l2_subdev *sd, u32 val);                                   /* 初始化設備 */  

  6.     int (*load_fw)(struct v4l2_subdev *sd);                                         /* 加載firmware */  

  7.     int (*reset)(struct v4l2_subdev *sd, u32 val);                                  /* 重置設備 */  

  8.     int (*s_gpio)(struct v4l2_subdev *sd, u32 val);                                 /* 設置gpio */  

  9.     int (*queryctrl)(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc);            /* 查詢設備支持的操做 */  

  10.     int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);               /* 獲取當前命令值 */  

  11.     int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);               /* 設置當前命令值 */  

  12.     int (*g_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);    /* 獲取外置命令值 */  

  13.     int (*s_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);    /* 設置外置命令值 */  

  14.     int (*try_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);  

  15.     int (*querymenu)(struct v4l2_subdev *sd, struct v4l2_querymenu *qm);            /* 查詢操做菜單 */  

  16.     int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm);                         /* 設置數據標準 */  

  17.     long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);             /* 處理特殊命令 */  

  18. #ifdef CONFIG_VIDEO_ADV_DEBUG  

  19.     int (*g_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);       /* 獲取寄存器值 */  

  20.     int (*s_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);       /* 設置寄存器值 */  

  21. #endif  

  22. };  

v4l2_subdev_tuner_ops 包含的操做合集則是調諧器獨有的:

[cpp] view plain copy

  1. struct v4l2_subdev_tuner_ops {  

  2.     int (*s_mode)(struct v4l2_subdev *sd, enum v4l2_tuner_type);               /* 設置調諧器模式 */  

  3.     int (*s_radio)(struct v4l2_subdev *sd);                                    /* 設置無線設備信息 */  

  4.     int (*s_frequency)(struct v4l2_subdev *sd, struct v4l2_frequency *freq);   /* 設置頻率 */  

  5.     int (*g_frequency)(struct v4l2_subdev *sd, struct v4l2_frequency *freq);   /* 獲取頻率 */  

  6.     int (*g_tuner)(struct v4l2_subdev *sd, struct v4l2_tuner *vt);             /* 獲取調諧器信息 */  

  7.     int (*s_tuner)(struct v4l2_subdev *sd, struct v4l2_tuner *vt);             /* 設置調諧器信息 */  

  8.     int (*g_modulator)(struct v4l2_subdev *sd, struct v4l2_modulator *vm);     /* 獲取調幅器信息 */  

  9.     int (*s_modulator)(struct v4l2_subdev *sd, struct v4l2_modulator *vm);     /* 設置調幅器信息 */  

  10.     int (*s_type_addr)(struct v4l2_subdev *sd, struct tuner_setup *type);      /* 安裝調諧器 */  

  11.     int (*s_config)(struct v4l2_subdev *sd, const struct v4l2_priv_tun_config *config);   /* 設置配置信息 */  

  12.     int (*s_standby)(struct v4l2_subdev *sd);                                  /* 設置標準 */  

  13. };  

v4l2_subdev_audio_ops 包含的操做合集則是音頻部分獨有的:

[cpp] view plain copy

  1. struct v4l2_subdev_audio_ops {  

  2.     int (*s_clock_freq)(struct v4l2_subdev *sd, u32 freq);       /* 設置音頻設備頻率 */  

  3.     int (*s_i2s_clock_freq)(struct v4l2_subdev *sd, u32 freq);   /* 設置i2s總線頻率 */  

  4.     int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config);   /* 設置音頻路由 */  

  5. };  

v4l2_subdev_video_ops 包含的操做合集則是視頻部分獨有的:

[cpp] view plain copy

  1. struct v4l2_subdev_video_ops {  

  2.     int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config);             /* 設置視頻路由 */  

  3.     int (*s_crystal_freq)(struct v4l2_subdev *sd, u32 freq, u32 flags);                      /* 設置設備頻率 */  

  4.     int (*decode_vbi_line)(struct v4l2_subdev *sd, struct v4l2_decode_vbi_line *vbi_line);   /* 消隱區信息解碼 */  

  5.     int (*s_vbi_data)(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *vbi_data);  /* 設置消隱區數據 */  

  6.     int (*g_vbi_data)(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_data *vbi_data);        /* 獲取消隱區數據 */  

  7.     int (*g_sliced_vbi_cap)(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_cap *cap);  

  8.     int (*s_std_output)(struct v4l2_subdev *sd, v4l2_std_id std);                            /* 設置標準輸出 */  

  9.     int (*querystd)(struct v4l2_subdev *sd, v4l2_std_id *std);                               /* 查詢標準 */  

  10.     int (*g_input_status)(struct v4l2_subdev *sd, u32 *status);                              /* 獲取輸入狀態 */  

  11.     int (*s_stream)(struct v4l2_subdev *sd, int enable);                                     /* 設置數據流 */  

  12.     int (*enum_fmt)(struct v4l2_subdev *sd, struct v4l2_fmtdesc *fmtdesc);                   /* 枚舉視頻格式 */  

  13.     int (*g_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);                           /* 獲取視頻格式 */  

  14.     int (*try_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);                         /* 嘗試設置視頻格式 */  

  15.     int (*s_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);                           /* 設置視頻格式 */  

  16.     int (*cropcap)(struct v4l2_subdev *sd, struct v4l2_cropcap *cc);                         /* 視頻剪輯功能 */  

  17.     int (*g_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);                           /* 獲取剪輯功能 */  

  18.     int (*s_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);                           /* 設置剪輯功能 */  

  19.     int (*g_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);                    /* 獲取參數 */  

  20.     int (*s_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);                    /* 設置參數 */  

  21.     int (*enum_framesizes)(struct v4l2_subdev *sd, struct v4l2_frmsizeenum *fsize);          /* 枚舉幀大小 */  

  22.     int (*enum_frameintervals)(struct v4l2_subdev *sd, struct v4l2_frmivalenum *fival);      /* 枚舉幀間隔 */  

  23. };  

由於 v4l2 設備通常用 i2c 總線通訊,因此註冊函數須要提供 i2c_client,函數原型以下:

[cpp] view plain copy

  1. /** 

  2.  *  v4l2_i2c_subdev_init - 註冊一個 v4l2_subdev 

  3.  *  @sd : v4l2_subdev 結構 

  4.  *  @client : 通訊用的i2c設備 

  5.  *  @ops: v4l2_subdev_ops 操做合集 

  6.  */  

  7. void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client, const struct v4l2_subdev_ops *ops)  

當 video_device 中的接口須要調用 v4l2_subdev 的成員函數時通常採用以下宏定義:

[cpp] view plain copy

  1. /* 調用成員函數以前須要先檢查成員函數是否被設置 

  2.  *  v4l2_subdev_call - 調用 v4l2_subdev 成員函數 

  3.  *  @sd: v4l2_subdev 結構 

  4.  *  @o:  v4l2_subdev_ops 成員名稱 

  5.  *  @f:  v4l2_subdev 成員函數 

  6.  *  @args:  v4l2_subdev 成員函數的參數 

  7.  

  8.    使用示例: err = v4l2_subdev_call(sd, core, g_chip_ident, &chip); 

  9.  */  

  10. #define v4l2_subdev_call(sd, o, f, args...)             \  

  11.     (!(sd) ? -ENODEV : (((sd) && (sd)->ops->o && (sd)->ops->o->f) ?  \  

  12.         (sd)->ops->o->f((sd) , ##args) : -ENOIOCTLCMD)) 

相關文章
相關標籤/搜索