我试图在我的OpenBSD笔记本电脑上使用V4L2 Ruby模块,但遇到了一个问题,从该模块发送V4L2 IOCTL将失败,而OpenBSD上的其他V4L2程序工作正常。
由于我最近收到了一些关于内核开发和调试的问题,我想我应该写下我最终是如何找到并修复它的。(剧透:这不是OpenBSD的问题。)
首先在OpenBSD上编译模块后,我运行了一些简单的Ruby代码来设置参数,并开始从我的相机获取数据:
... 结构v4l2_格式fmt;。。。BZERO(fmt);fmt。type=V4L2_BUF_type_VIDEO_CAPTURE;fmt。fmt。皮克斯。宽度=wd;fmt。fmt。皮克斯。高度=ht;fmt。fmt。皮克斯。pixelformat=fcc;fmt。fmt。皮克斯。场=V4L2_场_交错;err=xioctl(fd、VIDIOC___FMT和FMT);如果(err<;0){perror(";ioctl(VIDIOC___FMT)";)。。。
该模块正在使用一个只调用BZERO的BZERO宏,以及一个xioctlwrapper,当ioctl失败且errno==EINTR时,它将重试ioctl。
bzero(&;fmt,sizeof(struct v4l2_格式));fmt。类型=d->;buf_型;fmt。fmt。皮克斯。宽度=视频->;宽度fmt。fmt。皮克斯。高度=视频->;身高fmt。fmt。皮克斯。pixelformat=encs[vid->;enc]。身份证件fmt。fmt。皮克斯。field=V4L2_field_ANY;如果(ioctl(d->;fd,VIDIOC___FMT,&;FMT)<;0){warn(";VIDIOC#u S#u FMT";);返回0; }
我尝试将字段更改为V4L2_field_ANY以匹配OpenBSD实用程序,但在ENOTTY中仍然失败。
我通常会求助于KTrace来诊断系统调用问题,但在本例中,它只是显示它正在对文件句柄14调用ioctl,向其发送VIDIOC_S_FMT,并传递一些数据(v4l2_格式的结构)。
ioctl通过/dev/video1发送,到达视频(4)驱动程序的videoioctl函数。然后,它会执行一些错误检查,并将其与USB特定的uvideo(4)驱动程序的uvideo_s_fmt功能一起传递。
uvideo_s_fmt中已经有一些调试打印文件,它们是通过一个DPRINTF宏完成的,该宏实际上只在启用uvideo_调试时打印:
#ifdef UVIDEO_DEBUGint UVIDEO_debug=1#定义DPRINTF(l,x…)do{if((l)<;=uvideo_调试)printf(x);}而(0)#else#定义DPRINTF(l,x…)#恩迪夫
我在该块上添加了一个#define UVIDEO_调试,重新编译内核并引导到它。运行video实用程序会使内核打印出一行,但它不会从我的Ruby代码中打印任何内容。
由于DPRINTF打印的功能非常强大,我想它甚至还没有到达uvideo,所以让我们回到视频驱动程序。
DPRINTF(3,";视频操作(%zu,';%c';,%zu)\n";,IOCPARM_LEN(cmd),(int)IOCGROUP(cmd),cmd&;0xff);错误=EOPNOTSUPP;开关(cmd){…case VIDIOC__FMT:if(!(flags&;FWRITE))返回(EACCES);if(sc->;hw_if->;S_FMT)错误=(sc->;hw_if->;S_FMT)(sc->;hw_hdl,(struct v4l2_格式*)数据);中断;
类似地,我们可以通过在顶部定义video_DEBUG、编译和重新启动来在视频驱动程序中调试printfs。现在,当我运行Ruby代码时,我得到:
查看/usr/src/sys/sys/videoio。h、 我可以看到,它看起来确实是组V中正确的ioctl,编号5,以及v4l2_格式结构的适当大小:
_IOWR宏是一系列宏,这些宏构建了一个无符号long,对应于内核中所有可能的ioctl。
因此,ioctl正在到达视频驱动程序,似乎被认为是VIDIOC__FMT,但实际上并没有通过大开关到达uvideo驱动程序的_FMT功能。维迪奥苏菲姆特的案子里有没有这个问题?让我们添加我们自己的调试printf,以显示我们达到了这一点,以及它在函数内部的路径。
视频ioctl(208,';V';5)uvideo1:uvideo#U s#fmt:请求的宽度=640,高度=480uvideo1:uvideo#U fmt:提供的宽度=640,高度=480S#fmt:返回0视频ioctl:最终检索0
但是当运行我的Ruby代码时,它只打印第一行,这表明它甚至没有到达案例VIDIOC_s_FMT。由于开关位于整个cmd上,而不仅仅是数字(5)或组(V),因此它必须匹配所有ioctl参数。让我们把整件事打印出来:
视频ioctl(208和39;V和39;5)(3234878981对s#fmt 3234878981)uvideo1:uvideo#U s#fmt:请求的宽度=640,高度=480uvideo1:uvideo#U fmt:提供的宽度=640,高度=480S#fmt:返回的0videoioctl:final ret 0
在这一点上,我回到了Ruby C代码。它是如何传递ioctl请求参数的?
静态int-xioctl(int-fh,int-request,void*arg){int-r;do{r=ioctl(fh,request,arg);}而(r=-1&;EINTR==errno);返回r;}
当然,请求必须是无符号long,但这个Ruby模块的xioctlwrapper在将其传递给内核之前将其截断为有符号int。
Linux上的ioctl函数也需要一个无符号长,因此此修复程序不是OpenBSD特有的,但Linux上的V4L2 ioctl值可能不会超过32位整数的大小,因此这不会给Linux上的任何人带来问题。