目录
2 分析 vivi.c 的 open,read,write,ioctl 过程
解压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 函数