NXP

3.2 vidioc_int_* 类函数的调用过程

2019-07-12 12:17发布

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