2.1.1.1 节_摄像头驱动_V4L2 框架分析-环境搭建

目录

1 回顾简单字符设备驱动和复杂字符设备驱动的写法

2 分析 V4L2 框架和驱动

2.1 框架

2.2 驱动编写

2.3 分析 V4l2 内部实现细节

1 虚拟视频驱动 vivi.c 分析:

2 分析 vivi.c 的 open,read,write,ioctl 过程

3 v4l2_ctrl 结构体

总结

重要结构体:

struct video_device

struct vivi_dev

struct v4l2_device

Q:疑惑:

解释:


解压linux-3.4.2.tar.bz2

/work/system$ tar jxvf linux-3.4.2.tar.bz2

解压arm-linux-gcc-4.3.2.tgz

/usr/local/arm$ sudo tar zxvf arm-linux-gcc-4.3.2.tgz
/usr/local/arm$ sudo mv usr/local/arm/4.3.2/ .

由于编译2.66是另一款编译器,所以这里手动导出4.3.2编译器

export PATH=/usr/local/arm/4.3.2/bin:$PATH

打补丁:暂时不打linux-3.4.2_camera_jz2440.patch这个补丁

/work/system/linux-3.4.2$ patch -p1 < ../linux-3.4.2_100ask.patch 
/work/system/linux-3.4.2$ cp config_ok .config

编译

/work/system/linux-3.4.2$ make uImage

1 回顾简单字符设备驱动和复杂字符设备驱动的写法

在分析摄像头驱动之前,他属于字符型驱动
我们回顾下我们之前分析过的字符型驱动应该怎么写
对于简单的字符驱动,我们无非就是分配一个 fops 结构体,分配,设置,注册他,还有设备号
对于比较复杂的字符驱动,我们就引入了分层的概念,以 LCD 的驱动为例
对于简单的字符设备:
应用层调用标准接口,open,read 等等,我们驱动层只要实现对应的操作:drv_open 等

那么怎么去写这个驱动程序呢?
我们写驱动就是把上面的这些 drv_open 函数放到一个结构体 file_operations 结构体里面,
构造完成之后,要把它用起来啊,所以用 register_chrdev 来注册,告诉内核,我们可以简单的认为 register_chrdev 注册就是实现
了把 file_operations 放到了一个数组里面,比如 chrdev,那么放到这个数组的哪一项呢?(可以写 0,让系统帮我们分配)就是根据
主设备号来的.当然还有个名字,名字不重要.
上面这种说法只是便于我们理解,其实这个 chrdev 数组并不是用来存放我们的 file_operations 结构体的,可以去看二期视频:


那么注册进内核之后,那么谁来调用我们的 register_chrdev 呢?在装载驱动的时候,内核会自动调用入口函数,在入口函数里面会
去调用 register_chrdev,当我们卸载驱动的时候,内核会自动调用出口函数,里面就会调用 unregister_chrdev.
有人说我们的 register_chrdev 函数比较老了,不建议使用,那么用什么代替呢?
用 cdev_add 来代替

对于复杂的字符设备驱动程序(以 LCD 为例)
1、 引入分层的概念
(1)上层 fbmem.c(对于 LCD 驱动程序来说,内核已经帮我们做好了,这个 fbmem.c 是内核提供的)

(2)我们要做的是硬件相关这一层
分配、设置、注册 fb_info 结构体,这里的注册是把这个结构体告诉 fbmem.c,当应用程序调用读写等函数操作 LCD 的时候,
首先会调用 fbmem.c 的 file_operation 结构体的读写函数,在这些读写函数里面就会调用之前的 fb_info 结构体里面的函数
或属性来操作硬件。

(3)总结:如何写对于分层的设备驱动程序

至于是要分配哪个结构体,这就需要我们去具体分析了

2 分析 V4L2 框架和驱动

V4L2 框架: video for linux version 2,第一个版本在 linux 内核里面已经消失了,我们现在看到的摄像头的驱动程序都是第二版

其实他也是比较复杂的字符设备驱动,所以也会分层

2.1 框架

我们可以猜测他和 LCD 一样至少也分了两层,核心层,和硬件相关层
那么他的核心层做什么呢?
那么核心层应该也是和 LCD 一样,分配一个 file_operations 结构体,然后注册这个结构体
但是我们现在还不知道摄像头的核心层的代码是哪个文件:
打开虚拟机,并位于前面,接上 USB 摄像头到电脑上去,然后看内核的输出信息(用 dmesg 命令查看)

进入内核目录搜索关键词 Found UVC,最终会发现在 uvc_driver.c 里面

r 表示递归,n 表示显示行号
根据搜索结果反推摄像头驱动框架, uvc 是 usb video class,是 usb 类的驱动程序,是硬件相关的驱动程序。

uvc_driver.c 会向核心层注册一个结构体 usb_driver,这个结构体里面会有一个 probe 函数,当发现支持的设备(通过
id_table 比较)就会调用 probe 函数。
在这个 probe 函数里面分配、设置、注册
注册 file_operation 结构体就是把这个结构体变成结构体 cdev 的 ops 成员。
应用程序调用读写函数,会调用 v4l2_fops 结构体(也就是 file_operation 结构体)里面的读写函数。 v4l2_fops 结构体肯定
会调用到 video_device 结构体提供的各种函数或属性(如里面的 file_operation 结构体)。

uvc_probe
	v4l2_device_register//不重要
	uvc_register_chains
		uvc_register_terms
			uvc_register_video
				video_device_alloc
				vdev->fops = &uvc_fops;
				video_register_device//重要,从这个函数可以反推出核心层


video_register_device
	__video_register_device//v4l2-dev.c
	cdev_alloc();//1.分配 cdev 结构体
	//2.设置结构体;cdev 结构体的 ops 成员等于 
	//v4l2_fops(file_operation 结构体,这就是应用层调用到的)
	vdev->cdev->ops = &v4l2_fops;
	//注册 cdev 结构体
	cdev_add

我们要分析这个V4l2的内部细节:2中方法

一种是看代码,一种是看文档;

linux内核提供一些文档,比如说v4l2-framework.txt,太细节了,不注重框架

 


 

2.2 驱动编写

分配、设置、注册 v4l2_device 结构体
分配、设置、注册 video_device(vfd)结构体,这个结构体的 v4l2_device 成员指向上面的 v4l2_device 结构体

2.3 分析 V4l2 内部实现细节

通过分析 vivi.c(是一个虚拟的视频驱动)

1 虚拟视频驱动 vivi.c 分析:

关键点

1.分配 video_device
2.设置
3.注册: video_register_device

入口函数分析
vivi_init
  vivi_create_instance 函数
    v4l2_device_register // 不是主要, 只是用于初始化一些东西,比如自旋锁、引用计数
    //分配 video_device
    video_device_alloc
    // 设置
    1. vfd:
    *vfd = vivi_template;
    /*
    vivi_template 的成员只定义三个,也就是进行了如下的赋值,其他的成员未赋值
    .fops = &vivi_fops, //file_operation 结构体
    .ioctl_ops = &vivi_ioctl_ops,//ioctl 操作函数相关结构体
    .release = video_device_release,
    */
    2. 设置"ctrl 属性"(用于 APP 的 ioctl):
    v4l2_ctrl_handler_init(hdl, 11);//初始化 ctrl_handler,下面是设置这个 ctrl_handler(类似于链表),下面函数是
    创建各个属性并填充到这个链表
    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);
    。。。。。。
    3.
    dev->v4l2_dev.ctrl_handler = hdl;//hdl 就是上面所有方法的合集链表
    vfd->v4l2_dev = &dev->v4l2_dev;// 这样就将 dev->v4l2_dev. ctrl_handler 与 vfd 中的 v4l2_dev 建立的联系
    //注册 (根据 VFL_TYPE_GRABBER 这个类型自动创建设备节点)根据类型得到不同的名字、次设备号等
    video_register_device(video_device, type:VFL_TYPE_GRABBER, nr)
    /*我们 vivi.c 中传入的是 VFL_TYPE_GRABBER
    •VFL_TYPE_GRABBER 表明是一个图像采集设备–包括摄像头、调谐器,诸如此类。
    •VFL_TYPE_VBI 代表的设备是从视频消隐的时间段取得信息的设备。 •VFL_TYPE_RADIO 代表无线电设备。
    •VFL_TYPE_VTX 代表视传设备。


        __video_register_device
            vdev->cdev = cdev_alloc(); //分配 cdev 结构体
            vdev->cdev->ops = &v4l2_fops;//cdev 结构体的 ops 成员等于 v4l2_fops(file_operation 结构体)
            cdev_add //注册 cdev 结构体video_device[vdev->minor] = vdev; //video_device 数组以次设备号为下标把 video_device 结构体放进
            去
            if (vdev->ctrl_handler == NULL) //包含 V4L2_ctrl 结构体的 ctrl_handler 跟 video_device 关联
            vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;//而 vdev->v4l2_dev->ctrl_handler 中的内容在
            vivi_create_instance 中就已经赋值了

2 分析 vivi.c 的 open,read,write,ioctl 过程

 

1.open

 

3 v4l2_ctrl 结构体

(每个结构体对应一项(音量、亮度))
(1)作用

 

总结

(1) video_device 结构体的 v4l2_dev 等于我们创建的 v4l2_device 结构体
(2) vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
(3) v4l2_ctrl_handler(类似链表)包含 V4L2_ctrl 结构体 (代表各种属性)
 

重要结构体:

struct video_device
 


/*
 * Newer version of video_device, handled by videodev2.c
 * 	This version moves redundant code from video device code to
 *	the common handler
 */

struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
	struct media_entity entity;
#endif
	/* device ops */
	const struct v4l2_file_operations *fops;

	/* sysfs */
	struct device dev;		/* v4l device */
	struct cdev *cdev;		/* character device */

	/* Set either parent or v4l2_dev if your driver uses v4l2_device */
	struct device *parent;		/* device parent */
	struct v4l2_device *v4l2_dev;	/* v4l2_device parent */

	/* Control handler associated with this device node. May be NULL. */
	struct v4l2_ctrl_handler *ctrl_handler;

	/* Priority state. If NULL, then v4l2_dev->prio will be used. */
	struct v4l2_prio_state *prio;

	/* device info */
	char name[32];
	int vfl_type;
	/* 'minor' is set to -1 if the registration failed */
	int minor;
	u16 num;
	/* use bitops to set/clear/test flags */
	unsigned long flags;
	/* attribute to differentiate multiple indices on one physical device */
	int index;

	/* V4L2 file handles */
	spinlock_t		fh_lock; /* Lock for all v4l2_fhs */
	struct list_head	fh_list; /* List of struct v4l2_fh */

	int debug;			/* Activates debug level*/

	/* Video standard vars */
	v4l2_std_id tvnorms;		/* Supported tv norms */
	v4l2_std_id current_norm;	/* Current tvnorm */

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

	/* ioctl callbacks */
	const struct v4l2_ioctl_ops *ioctl_ops;

	/* serialization lock */
	struct mutex *lock;
};

 

struct vivi_dev


struct vivi_dev {
	struct list_head           vivi_devlist;
	struct v4l2_device 	   v4l2_dev;
	struct v4l2_ctrl_handler   ctrl_handler;

	/* controls */
	struct v4l2_ctrl	   *brightness;
	struct v4l2_ctrl	   *contrast;
	struct v4l2_ctrl	   *saturation;
	struct v4l2_ctrl	   *hue;
	struct {
		/* autogain/gain cluster */
		struct v4l2_ctrl	   *autogain;
		struct v4l2_ctrl	   *gain;
	};
	struct v4l2_ctrl	   *volume;
	struct v4l2_ctrl	   *button;
	struct v4l2_ctrl	   *boolean;
	struct v4l2_ctrl	   *int32;
	struct v4l2_ctrl	   *int64;
	struct v4l2_ctrl	   *menu;
	struct v4l2_ctrl	   *string;
	struct v4l2_ctrl	   *bitmask;

	spinlock_t                 slock;
	struct mutex		   mutex;

	/* various device info */
	struct video_device        *vfd;

	struct vivi_dmaqueue       vidq;

	/* Several counters */
	unsigned 		   ms;
	unsigned long              jiffies;
	unsigned		   button_pressed;

	int			   mv_count;	/* Controls bars movement */

	/* Input Number */
	int			   input;

	/* video capture */
	struct vivi_fmt            *fmt;
	unsigned int               width, height;
	struct vb2_queue	   vb_vidq;
	enum v4l2_field		   field;
	unsigned int		   field_count;

	u8 			   bars[9][3];
	u8 			   line[MAX_WIDTH * 4];
};

 

struct v4l2_device


struct v4l2_ctrl_handler;

struct v4l2_device {
	/* dev->driver_data points to this struct.
	   Note: dev might be NULL if there is no parent device
	   as is the case with e.g. ISA devices. */
	struct device *dev;
#if defined(CONFIG_MEDIA_CONTROLLER)
	struct media_device *mdev;
#endif
	/* used to keep track of the registered subdevs */
	struct list_head subdevs;
	/* lock this struct; can be used by the driver as well if this
	   struct is embedded into a larger struct. */
	spinlock_t lock;
	/* unique device name, by default the driver name + bus ID */
	char name[V4L2_DEVICE_NAME_SIZE];
	/* notify callback called by some sub-devices. */
	void (*notify)(struct v4l2_subdev *sd,
			unsigned int notification, void *arg);
	/* The control handler. May be NULL. */
	struct v4l2_ctrl_handler *ctrl_handler;
	/* Device's priority state */
	struct v4l2_prio_state prio;
	/* BKL replacement mutex. Temporary solution only. */
	struct mutex ioctl_lock;
	/* Keep track of the references to this struct. */
	struct kref ref;
	/* Release function that is called when the ref count goes to 0. */
	void (*release)(struct v4l2_device *v4l2_dev);
};

Q:疑惑:

Demsg:的用法,作用,原理
感觉 LCD 驱动和 video 的驱动相比是不是多注册了一层 fops?
我们 LCD 驱动是在硬件相关层定义了一个 fb_info 结构体,通过 register_framebuffer 注册进了一个 registered_fb 数组里,在
fbmem.c 里面定义了 file_operations 的结构体,然后通过 cdev_add 注册进内核,
当应用层 open 时,进入我们 fb 层的 file_operations 的 open,然后该 open 查找 registered_fb 数组里的硬件层的 fb_info 中

fb_ops 里面的 open
而我们的 video 驱动中:
在 v4l2_dev 中并没有像 LCD 的 fb 层定义一个 file_operations 结构体,然后注册进内核,虽然 v4l2_dev.c 中是有
__video_register_device 函数,但是并没有在 v4l2_dev 中被调用,而是在 vivi.c,也就是我们的硬件相关层调用的,并且在
__video_register_device 里面既通过 cdev_add 把我们的硬件层 vivi.c 里面的 video_device 结构体注册进内核,又放入了全局
数组 video_device 中
感觉 LCD 和 video 在硬件层的上一层,LCD 要比 video 多一个结构体
video 在硬件层 vivi.c 里面既然已经通过 cdev_add 把自己定义的 video_device 结构体放入了内核,为什么还要放入数组里面?
感觉不是多此一举了么?

解释:

我们的 vivi.c 的流程是:
module_init(vivi_init);
vivi_init(void)
__init vivi_create_instance(int inst)
struct video_device *vfd;
*vfd = vivi_template;//vfd->fops = vivi_fops
video_register_device
__video_register_device
vdev->cdev->ops = &v4l2_fops;
cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);//注册 v4l2_fops 进内

video_device[vdev->minor] = vdev;//将 vivi.c 中构造的 video_device 全局结构体数组中
要注意的是: *vfd = vivi_template;这一步中实现的是 vfd->fops = vivi_fops
而在__video_register_device 中实现的是 vdev->cdev->ops = &v4l2_fops;
这两种并没有冲突,可以仔细的看下 video_device 的成员
然后应用层如何调用我们的驱动呢,比如应用层 read,我们开始说是先调用我们的 v4l2 层的 v4l2_fops 然后通过它来调用我
们驱动中自己定义的 fileoperation 结构体,我们看是不是呢?
应用层 read
------------------------------------------
v4l2_read//调用核心层中的 v4l2_fops-> v4l2_read
struct video_device *vdev = video_devdata(filp);
return video_device[iminor(file->f_path.dentry->d_inode)];//调用全局数组中的 read,也就是我们自己驱动
中的 read 函数
 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值