在 i.MX6 的视频接口(CSI)驱动 mx6s_capture 中发现了一个 BUG,如果使用该驱动的应用在没有更改 SIGINT 的行为的情况下,键入 ctrl+C 退出应用后会卡住。
经过调试,发现驱动卡在了函数 mx6s_csi_disable 中,驱动此时无法对 CSI 寄存器进行任何操作:
static void mx6s_csi_disable(struct mx6s_csi_dev *csi_dev)
{
struct v4l2_pix_format *pix = &csi_dev->pix;
csi_dmareq_rff_disable(csi_dev);
csi_disable_int(csi_dev);
/* set CSI_CSIDMASA_FB1 and CSI_CSIDMASA_FB2 to default value */
csi_write(csi_dev, 0, CSI_CSIDMASA_FB1);
csi_write(csi_dev, 0, CSI_CSIDMASA_FB2);
csi_buf_stride_set(csi_dev, 0);
if (pix->field == V4L2_FIELD_INTERLACED) {
csi_deinterlace_enable(csi_dev, false);
csi_tvdec_enable(csi_dev, false);
}
csi_enable(csi_dev, 0);
}
加入调试信息才发现 ctrl+C 的退出调用顺序变成了:
由系统自行调用 mx6s_csi_close,相当于调用了 close(fd) 关闭文件。
-> mx6s_csi_close
-> mx6s_csi_deinit
-> csi_clk_disable
-> clk_disable_unprepare(先关闭了 CSI 时钟)由系统自行调用 mx6s_stop_streaming,相当于调用了 ioctl(fd, VIDIOC_STREAMOFF, ...) 停止数据传输。
-> mx6s_stop_streaming
-> mx6s_csi_disable(无法对 CSI 寄存器进行任何操作)
然而应用程序的正常退出调用是这样的顺序:
ioctl(fd, VIDIOC_STREAMOFF, ...)
-> video_ioctl2
-> mx6s_vidioc_streamoff
-> vb2_streamoff
-> mx6s_stop_streaming
-> mx6s_csi_disable(CSI 寄存器相关清理工作)
close(fd)
-> mx6s_csi_close
-> mx6s_csi_deinit
-> csi_clk_disable
-> clk_disable_unprepare(最后关闭 CSI 时钟)
结论是 ctrl+C 导致 stop_streaming 和 close 的顺序调转了。因为 stop_streaming 必须 CSI 的时钟工作才会正常运行,并且 close 会关闭 CSI 时钟,所以它们的顺序是不能调转的,必须先 stop_streaming 然后再 close。
解决办法如下二选一:
一、修改应用
设置 SIGINT 的行为,加上 ioctl(fd, VIDIOC_STREAMOFF, ...) 和 close(fd) 语句。
二、修改驱动
mx6s_capture 的 clock_prepare_enable 只存在于文件操作 open 中,clk_disable_unprepare 只存在于 close 操作中,这是引起 BUG 的根本原因,把这两个函数加到别的地方去。
凡是涉及到寄存器初始化和清理工作的封装函数的头尾加上 clock_prepare_enable / clk_disable_unprepare。而对于 start_streaming 和 stop_streaming 这两个与传输有关的函数, 前者加上 clock_prepare_enable,后者加上 clk_disable_unprepare。
这两个函数重复使用不会造成寄存器出现问题,因为它们调用的 clk_core_enable / clk_core_disable 函数中会判断时钟的引用次数,当时钟未开启,调用 clock_prepare_enable 会真正开启时钟,否则都是把时钟的引用次数加一。