本文基于linux内核提供的一个PCI驱动程序模板进行分析,驱动程序文件为v4l2-pci-skeleton.c
,放在linux/samples/v4l目录下。从该驱动程序中,将看到一个PCI驱动程序的总体框架和组成结构,同时该驱动基于V4L2设计,从而也能了解到如何基于V4L2开发视频捕获驱动。
本文分析其驱动程序的整体结构和
.probe
、.remove
两个驱动入口函数的具体执行过程。
首先头文件包含如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
然后对模块的基本功能、作者、LICENSE许可进行描述:
MODULE_DESCRIPTION("V4L2 PCI Skeleton Driver");
MODULE_AUTHOR("Hans Verkuil");
MODULE_LICENSE("GPL v2");
接着定义一个struct skeleton
结构体,用于封装、描述在驱动设计中所需要使用到的数据结构、锁、过程参数等,struct skeleton
定义如下:
struct skeleton {
struct pci_dev *pdev; //代表PCI设备
struct v4l2_device v4l2_dev; //顶层v4l2设备结构
struct video_device vdev; //video设备节点
struct v4l2_ctrl_handler ctrl_handler; //v4l2框架的控件处理结构
struct mutex lock; //Ioctl序列化互斥锁
v4l2_std_id std; //当前SDTV标准
struct v4l2_dv_timings timings; //当前高清电视(HDTV)时序
struct v4l2_pix_format format; //当前像素格式
unsigned input; //当前视频输入。(0表示SDTV、1表示HDTV)
struct vb2_queue queue; //Vb2视频采集队列
spinlock_t qlock; //控制访问buf_list和sequence的自旋锁
struct list_head buf_list; //为DMA排队的缓冲区列表
unsigned field; //当前缓冲区的字段(TOP/BOTTOM/other)
unsigned sequence; //帧序列计数器
};
接着定义一个buffer链表:
struct skel_buffer {
struct vb2_v4l2_buffer vb;
struct list_head list;
};
V4L2-pci驱动以PCI驱动框架实现,故需按照PCI驱动设计框架进行驱动封装:创建struct pci_driver
实例skeleton_driver
,并指定其.name
、.probe
、.remove
、.id_table
:
static struct pci_driver skeleton_driver = {
.name = KBUILD_MODNAME,
.probe = skeleton_probe,
.remove = skeleton_remove,
.id_table = skeleton_pci_tbl,
};
最后使用module_pci_driver()
将skeleton_driver
导出为模块:
module_pci_driver(skeleton_driver);
.probe
是驱动程序的入口,在V4L2-pci驱动中,.probe
被设置为skeleton_probe()
,该函数中执行的操作较多,下文将分步描述:
static const struct v4l2_dv_timings timings_def =
V4L2_DV_BT_CEA_1280X720P60;
struct skeleton *skel;
struct video_device *vdev;
struct v4l2_ctrl_handler *hdl;
struct vb2_queue *q;
int ret;
ret = pci_enable_device(pdev);
if (ret)
return ret;
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(&pdev->dev, "no suitable DMA available.\n");
goto disable_pci;
}
skel = devm_kzalloc(&pdev->dev, sizeof(struct skeleton), GFP_KERNEL);
if (!skel) {
ret = -ENOMEM;
goto disable_pci;
}
ret = devm_request_irq(&pdev->dev, pdev->irq,
skeleton_irq, 0, KBUILD_MODNAME, skel);
if (ret) {
dev_err(&pdev->dev, "request_irq failed\n");
goto disable_pci;
}
//将传入的pci_driver结构指针赋值给驱动程序下的pdev。
skel->pdev = pdev;
skel->timings = timings_def;
skel->std = V4L2_STD_625_50;
skeleton_fill_pix_format(skel, &skel->format);
ret = v4l2_device_register(&pdev->dev, &skel->v4l2_dev);
if (ret)
goto disable_pci;
mutex_init(&skel->lock);
hdl = &skel->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 4);
v4l2_ctrl_new_std(hdl, &skel_ctrl_ops,
V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
v4l2_ctrl_new_std(hdl, &skel_ctrl_ops,
V4L2_CID_CONTRAST, 0, 255, 1, 16);
v4l2_ctrl_new_std(hdl, &skel_ctrl_ops,
V4L2_CID_SATURATION, 0, 255, 1, 127);
v4l2_ctrl_new_std(hdl, &skel_ctrl_ops,
V4L2_CID_HUE, -128, 127, 1, 0);
if (hdl->error) {
ret = hdl->error;
goto free_hdl;
}
skel->v4l2_dev.ctrl_handler = hdl;
q = &skel->queue;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ;
q->dev = &pdev->dev;
q->drv_priv = skel;
q->buf_struct_size = sizeof(struct skel_buffer);
q->ops = &skel_qops;
q->mem_ops = &vb2_dma_contig_memops;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
//假设在启动之前这个DMA引擎至少需要两个缓冲区可用。用于确保直到有这么多缓冲区队列,start_streaming()操作才会被调用,
q->min_buffers_needed = 2;
//流ioctl的序列化锁,
q->lock = &skel->lock;
//由于此驱动程序只能做32位DMA,故必须确保vb2内核将在32位DMA内存中分配缓冲区。
q->gfp_flags = GFP_DMA32;
//初始化vb2队列
ret = vb2_queue_init(q);
if (ret)
goto free_hdl;
INIT_LIST_HEAD(&skel->buf_list);
spin_lock_init(&skel->qlock);
vdev = &skel->vdev;
strlcpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name));
/*
* There is nothing to clean up, so release is set to an empty release
* function. The release callback must be non-NULL.
*/
vdev->release = video_device_release_empty;
vdev->fops = &skel_fops,
vdev->ioctl_ops = &skel_ioctl_ops,
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
V4L2_CAP_STREAMING;
/*
* The main serialization lock. All ioctls are serialized by this
* lock. Exception: if q->lock is set, then the streaming ioctls
* are serialized by that separate lock.
*/
vdev->lock = &skel->lock;
vdev->queue = q;
vdev->v4l2_dev = &skel->v4l2_dev;
/* Supported SDTV standards, if any */
vdev->tvnorms = SKEL_TVNORMS;
video_set_drvdata(vdev, skel);
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
if (ret)
goto free_hdl;
.probe
是驱动程序的出口,在V4L2-pci驱动中,.remove
被设置为skeleton_remove()
,该函数实现如下:
static void skeleton_remove(struct pci_dev *pdev)
{
struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev);
struct skeleton *skel = container_of(v4l2_dev, struct skeleton, v4l2_dev);
video_unregister_device(&skel->vdev);
v4l2_ctrl_handler_free(&skel->ctrl_handler);
v4l2_device_unregister(&skel->v4l2_dev);
pci_disable_device(skel->pdev);
}
从上述代码可知,执行remove
时,具体执行流程如下:
(1)首先从struct pci_dev
结构中反解出struct v4l2_device
;接着从struct v4l2_device
中反解出struct skeleton
。
(2)调用video_unregister_device()
释放注册的video设备。
(3)调用v4l2_ctrl_handler_free()
释放控件的处理程序。
(4)调用v4l2_device_unregister()
注销v4l2设备资源。
(5)调用pci_disable_device()
禁用PCI设备。
上述则是驱动程序在退出时执行的资源释放和清理操作。