3.2 vidioc_int_* 类函数的调用过程

在《2.masterslave的匹配过程》中我们分析了master设备和slave设备的匹配过程,但是在操作过程中,如果想要获取slave设备的一些信息,该如何获得呢?你也许会说,只需要定义一个函数,去slave设备文件中获取即可,但是,如果一个驱动会支持好几个slave设备,那么对于不同的slave设备,驱动中都需要为它们定义一个功能相似的函数。如果是多master对应多slave设备的情况下,那么这些函数就需要在多个文件中定义多次,这种现象在内核中是肯定不会允许的。对于相似功能的函数,在驱动中只存在一个,而这个函数会根据不同的slave设备在不同的slave设备文件中转换成各自的子函数。所以,这里使用了Linux设备驱动框架设计中的分割的思想,提炼出一种通用的函数接口,在不同的slave设备中会转换成不同的底层调用函数,这些就是vidioc_int_*类函数调用。


首先以ov5640.c为例,回忆slave设备的注册过程:

module_i2c_driver(ov5640_i2c_driver); 
static struct i2c_driver ov5640_i2c_driver = { 
	.driver = { 
		  .owner = THIS_MODULE, 
		  .name  = "ov564x", 
		  }, 
	.probe  = ov5640_probe, 
	.remove = ov5640_remove, 
	.id_table = ov5640_id, 
}; 

调用ov5640_probe函数,在ov5640_probe函数中,首先会为structsensor_data ov5640_data设置初始值,然后通ov5640_int_device.priv= &ov5640_data;

retval= v4l2_int_device_register(&ov5640_int_device);

v4l2_int_device_register函数中,会在里面通过list_add将这个设备添加到int_list链表中,由于无论master还是slave设备都会调用到这个函数,所以这个int_list链表中会存在masterslave设备,然后根据不同的type类型来区分它们。之后在v4l2_int_device_register函数继续调用v4l2_int_device_try_attach_all()函数,会从int_list链表找到master设备和第一个没有设置masterslave设备,然后将这个slave设备的master设置成找到的master,并且调用masterattach函数完成匹配过程。

v4l2_int_device_register函数注册的是ov5640_int_device结构体,这个结构体如下所示:

static struct v4l2_int_device ov5640_int_device = { 
	.module = THIS_MODULE, 
	.name = "ov564x", 
	.type = v4l2_int_type_slave, 
	.u = { 
		.slave = &ov5640_slave, 
	}, 
};

这个结构体里面u.slave变量是v4l2_int_slave类型的,如下所示:

static struct v4l2_int_slave ov5640_slave = { 
	.ioctls = ov5640_ioctl_desc, 
	.num_ioctls = ARRAY_SIZE(ov5640_ioctl_desc), 
}; 

同样继续追踪ov5640_ioctl_desc结构体

static struct v4l2_int_ioctl_desc ov5640_ioctl_desc[] = { 
	{ vidioc_int_dev_init_num, 
	  (v4l2_int_ioctl_func *)ioctl_dev_init }, 
	{ vidioc_int_dev_exit_num, 
	  ioctl_dev_exit}, 
	{ vidioc_int_s_power_num, 
	  (v4l2_int_ioctl_func *)ioctl_s_power }, 
	{ vidioc_int_g_ifparm_num, 
	  (v4l2_int_ioctl_func *)ioctl_g_ifparm }, 
	{ vidioc_int_init_num, 
	  (v4l2_int_ioctl_func *)ioctl_init }, 
	{ vidioc_int_enum_fmt_cap_num, 
	  (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap }, 
	{ vidioc_int_g_fmt_cap_num, 
	  (v4l2_int_ioctl_func *)ioctl_g_fmt_cap }, 
	{ vidioc_int_g_parm_num, 
	  (v4l2_int_ioctl_func *)ioctl_g_parm }, 
	{ vidioc_int_s_parm_num, 
	  (v4l2_int_ioctl_func *)ioctl_s_parm }, 
	{ vidioc_int_g_ctrl_num, 
	  (v4l2_int_ioctl_func *)ioctl_g_ctrl }, 
	{ vidioc_int_s_ctrl_num, 
	  (v4l2_int_ioctl_func *)ioctl_s_ctrl }, 
	{ vidioc_int_enum_framesizes_num, 
	  (v4l2_int_ioctl_func *)ioctl_enum_framesizes }, 
	{ vidioc_int_enum_frameintervals_num, 
	  (v4l2_int_ioctl_func *)ioctl_enum_frameintervals }, 
	{ vidioc_int_g_chip_ident_num, 
	  (v4l2_int_ioctl_func *)ioctl_g_chip_ident }, 
}; 


这些ioctl函数是如何调用的?下面来分析这一个过程v4l2-int-device.h中有这样的定义:

enum v4l2_int_ioctl_num { 
	/* 
	 * 
	 * "Proper" V4L ioctls, as in struct video_device. 
	 * 
	 */ 
	vidioc_int_enum_fmt_cap_num = 1, 
	vidioc_int_g_fmt_cap_num, 
	vidioc_int_s_fmt_cap_num, 
	vidioc_int_try_fmt_cap_num, 
	vidioc_int_queryctrl_num, 
	vidioc_int_g_ctrl_num, 
	vidioc_int_s_ctrl_num, 
	vidioc_int_cropcap_num, 
	vidioc_int_g_crop_num, 
	vidioc_int_s_crop_num, 
	vidioc_int_g_parm_num, 
	vidioc_int_s_parm_num, 
	vidioc_int_querystd_num, 
	vidioc_int_s_std_num, 
	vidioc_int_s_video_routing_num, 

	..............
	/* 
	* Get slave private data, e.g. platform-specific slave 
	* configuration used by the master. 
	*/ 
	vidioc_int_g_priv_num, 
	/* Get slave interface parameters. */ 
	vidioc_int_g_ifparm_num, 
	/* Does the slave need to be reset after VIDIOC_DQBUF? */ 
	vidioc_int_g_needs_reset_num, 
	vidioc_int_enum_framesizes_num, 
	vidioc_int_enum_frameintervals_num, 
	 .................
	vidioc_int_priv_start_num = 2000, 
};
#define V4L2_INT_WRAPPER_1(name, arg_type, asterisk)			\ 
	static inline int vidioc_int_##name(struct v4l2_int_device *d,	\ 
					    arg_type asterisk arg)	\ 
	{								\ 
		return v4l2_int_ioctl_1(d, vidioc_int_##name##_num,	\ 
					(void *)(unsigned long)arg);	\ 
	}								\ 
									\ 
	static inline struct v4l2_int_ioctl_desc			\ 
	vidioc_int_##name##_cb(int (*func)				\ 
			       (struct v4l2_int_device *,		\ 
				arg_type asterisk))			\ 
	{								\ 
		struct v4l2_int_ioctl_desc desc;			\ 
									\ 
		desc.num = vidioc_int_##name##_num;			\ 
		desc.func = (v4l2_int_ioctl_func *)func;		\ 
									\ 
		return desc;						\ 
	} 

V4L2_INT_WRAPPER_1(enum_fmt_cap, struct v4l2_fmtdesc, *); 
V4L2_INT_WRAPPER_1(g_fmt_cap, struct v4l2_format, *); 
V4L2_INT_WRAPPER_1(s_fmt_cap, struct v4l2_format, *); 
V4L2_INT_WRAPPER_1(try_fmt_cap, struct v4l2_format, *); 
V4L2_INT_WRAPPER_1(queryctrl, struct v4l2_queryctrl, *); 
V4L2_INT_WRAPPER_1(g_ctrl, struct v4l2_control, *); 
V4L2_INT_WRAPPER_1(s_ctrl, struct v4l2_control, *); 
V4L2_INT_WRAPPER_1(cropcap, struct v4l2_cropcap, *); 
V4L2_INT_WRAPPER_1(g_crop, struct v4l2_crop, *); 
V4L2_INT_WRAPPER_1(s_crop, struct v4l2_crop, *); 
V4L2_INT_WRAPPER_1(g_parm, struct v4l2_streamparm, *); 
V4L2_INT_WRAPPER_1(s_parm, struct v4l2_streamparm, *); 
V4L2_INT_WRAPPER_1(querystd, v4l2_std_id, *); 
V4L2_INT_WRAPPER_1(s_std, v4l2_std_id, *); 
V4L2_INT_WRAPPER_1(s_video_routing, struct v4l2_routing, *); 

V4L2_INT_WRAPPER_0(dev_init); 
V4L2_INT_WRAPPER_0(dev_exit); 
V4L2_INT_WRAPPER_1(s_power, enum v4l2_power, /*dummy arg*/); 
V4L2_INT_WRAPPER_1(g_priv, void, *); 
V4L2_INT_WRAPPER_1(g_ifparm, struct v4l2_ifparm, *); 
V4L2_INT_WRAPPER_1(g_needs_reset, void, *); 
V4L2_INT_WRAPPER_1(enum_framesizes, struct v4l2_frmsizeenum, *); 
V4L2_INT_WRAPPER_1(enum_frameintervals, struct v4l2_frmivalenum, *); 

V4L2_INT_WRAPPER_0(reset); 
V4L2_INT_WRAPPER_0(init); 
V4L2_INT_WRAPPER_1(g_chip_ident, int, *);

上面这个宏定义中的##是连字符,相当于直接将##后面的字符连到##号之前的字符后面。这样做的目的是什么?

这种用法一般用在宏定义中,比如定义一个宏:

#defineAAAAA(name, type, num) xxxxx_##name(type, num)

如果在代码中使用到这个宏,编译器就会根据宏中不同的name字段来自动生成几个不同的函数。

#defineAAAAA(aaa, int, 1)

#defineAAAAA(bbb, int, 2)

#defineAAAAA(ccc, int, 3)

在编译的时候,就会生成:

xxxxx_aaa(int,1)

xxxxx_bbb(int,2)

xxxxx_ccc(int,3)


在后面用到的mxc_v4l2_capture.c中的open函数中,调用了vidioc_int_g_ifparm这样一个函数,我在内核源码中搜索都没有找到这个函数的定义,但是与vidioc_int...相关的头文件只有这个v4l2-int-device.h,所以仔细看这个头文件中,它采用一种gcc宏扩展的方式定义了一个宏V4L2_INT_WRAPPER_1,如上所示,就以这个vidioc_int_g_ifparm为例来说明:

V4L2_INT_WRAPPER_1(g_ifparm, struct v4l2_ifparm, *);

通过上面这个宏就相当于声明创建了两个内联函数:

static inline int vidioc_int_g_ifparm(struct v4l2_int_device *d,	\ 
					    arg_type asterisk arg)	\ 
	{								\ 
		return v4l2_int_ioctl_1(d, vidioc_int_g_ifparm_num,	\ 
					(void *)(unsigned long)arg);	\ 
	}								\ 
									\ 
	static inline struct v4l2_int_ioctl_desc			\ 
	vidioc_int_g_ifparm_cb(int (*func)				\ 
			       (struct v4l2_int_device *,		\ 
				arg_type asterisk))			\ 
	{								\ 
		struct v4l2_int_ioctl_desc desc;			\ 
									\ 
		desc.num = vidioc_int_g_ifparm_num;			\ 
		desc.func = (v4l2_int_ioctl_func *)func;		\ 
									\ 
		return desc;						\ 
	}

这样的话,当mxc_v4l2_capture.c中的open函数中调用vidioc_int_g_ifparm的话,就会调用到v4l2_int_ioctl_1函数,这个函数如下所示:

int v4l2_int_ioctl_1(struct v4l2_int_device *d, int cmd, void *arg) 
{ 
	return ((v4l2_int_ioctl_func_1 *) 
		find_ioctl(d->u.slave, cmd, 
			   (v4l2_int_ioctl_func *)no_such_ioctl_1))(d, arg); 
} 

然后就转到find_ioctl函数里面,

static v4l2_int_ioctl_func *find_ioctl(struct v4l2_int_slave *slave, int cmd, 
				       v4l2_int_ioctl_func *no_such_ioctl) 
{ 
	const struct v4l2_int_ioctl_desc *first = slave->ioctls; 
	const struct v4l2_int_ioctl_desc *last = 
		first + slave->num_ioctls - 1; 

	while (first <= last) { 
		const struct v4l2_int_ioctl_desc *mid; 

		mid = (last - first) / 2 + first; 

		if (mid->num < cmd) 
			first = mid + 1; 
		else if (mid->num > cmd) 
			last = mid - 1; 
		else 
			return mid->func; 
/* 找到就返回具体的函数,具体的说这里的函数就是ov5640 slave定义的 ov5640_ioctl_desc 中的ioctl_g_ifparm 函数! */
	} 

	return no_such_ioctl; 
}

这个find_ioctl函数通过一个二分查找,根据vidioc_int_g_ifparm_num来找到具体的函数即ioctl_g_ifparm函数。

也就是说如果其他函数中有调用vidioc_int_g_ifparm的话,最终就会调用到ov5640.c中的

ioctl_g_ifparm函数。


同理,对于其他vidioc_int*类函数调用,最终都会根据不同的slave设备来对应找到vidioc_int_*_num函数,然后根据v4l2_int_ioctl_desc中的定义来找到对应的函数。


其他相似的函数比如:

vidioc_int_enum_fmt

vidioc_int_g_fmt

vidioc_int_g_ctrl

可以自己分析分析这几个函数的调用过程,就会对这种方式比较理解。


int init_camera_attribute(int fd) { int numBufs; v4l2_std_id id; struct v4l2_format fmt; struct v4l2_requestbuffers req; struct v4l2_buffer buf; //检查当前视频设备支持的标准 ioctl(fd,VIDIOC_QUERYSTD,&id); //设置视频捕获格式 memset(&fmt,0,sizeof(fmt)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = 640; fmt.fmt.pix.height = 480; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; if(ioctl(fd,VIDIOC_S_FMT,&fmt) == -1){ perror("set VIDIOC_S_FMT is fail"); exit(EXIT_FAILURE); } //分配内存 memset(&req,0,sizeof(req)); req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if(ioctl(fd,VIDIOC_REQBUFS,&req) == -1){ perror("set VIDIOC_REQBUFS is fail"); exit(EXIT_FAILURE); } //获取并记录缓存的物理空间 buffers = calloc(req.count,sizeof(*buffers)); for(numBufs = 0; numBufs < req.count; numBufs ++){ memset(&buf,0,sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = numBufs; //读取缓存 if(ioctl(fd,VIDIOC_QUERYBUF,&buf) == -1){ perror("set VIDIOC_REQBUFS is fail"); exit(EXIT_FAILURE); } // 转换成相对地址 buffers[numBufs].length = buf.length; buffers[numBufs].start = mmap(NULL,buf.length,PROT_READ|PROT_WRITE, MAP_SHARED,fd,buf.m.offset); if(buffers[numBufs].start == MAP_FAILED){ perror("mmap is fail"); exit(EXIT_FAILURE); } // 放入缓存队列 if(ioctl(fd,VIDIOC_QBUF,&buf) == -1){ perror("set VIDIOC_QBUF is fail"); exit(EXIT_FAILURE); } } return 0; }
06-12
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值