在《2.master和slave的匹配过程》中我们分析了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链表中会存在master和slave设备,然后根据不同的type类型来区分它们。之后在v4l2_int_device_register函数继续调用v4l2_int_device_try_attach_all()函数,会从int_list链表找到master设备和第一个没有设置master的slave设备,然后将这个slave设备的master设置成找到的master,并且调用master的attach函数完成匹配过程。
在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
可以自己分析分析这几个函数的调用过程,就会对这种方式比较理解。