在《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
可以自己分析分析这几个函数的调用过程,就会对这种方式比较理解。