V4L2-PCI驱动程序样例分析(一)

V4L2-PCI驱动程序样例分析

文章目录

    • V4L2-PCI驱动程序样例分析
      • 一、简介
      • 二、驱动头文件和基础数据机构定义
      • 三、PCI驱动框架骨架
      • 四、.probe详解
        • 1、定义probe过程所需要的结构
        • 2、使能PCI设备
        • 3、设置DMA
        • 4、创建新的驱动实例
        • 5、分配中断
        • 6、填写与格式相关的初始设置
        • 7、注册v4l2设备
        • 8、初始化互斥锁
        • 9、添加控制器
        • 10、设置vb2相关参数并初始化vb2队列
        • 11、初始化buf链表和自旋锁
        • 12、初始化video_device结构数据并注册video_device
      • 五、.remove详解

一、简介

本文基于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;
};

三、PCI驱动框架骨架

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详解

.probe是驱动程序的入口,在V4L2-pci驱动中,.probe被设置为skeleton_probe(),该函数中执行的操作较多,下文将分步描述:

1、定义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;

2、使能PCI设备

	ret = pci_enable_device(pdev);
	if (ret)
		return ret;

3、设置DMA

	ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
	if (ret) {
		dev_err(&pdev->dev, "no suitable DMA available.\n");
		goto disable_pci;
	}

4、创建新的驱动实例

	skel = devm_kzalloc(&pdev->dev, sizeof(struct skeleton), GFP_KERNEL);
	if (!skel) {
		ret = -ENOMEM;
		goto disable_pci;
	}

5、分配中断

	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;

6、填写与格式相关的初始设置

	skel->timings = timings_def;
	skel->std = V4L2_STD_625_50;
	skeleton_fill_pix_format(skel, &skel->format);

7、注册v4l2设备

	ret = v4l2_device_register(&pdev->dev, &skel->v4l2_dev);
	if (ret)
		goto disable_pci;

8、初始化互斥锁

mutex_init(&skel->lock);

9、添加控制器

	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;

10、设置vb2相关参数并初始化vb2队列

	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;

11、初始化buf链表和自旋锁

	INIT_LIST_HEAD(&skel->buf_list);
	spin_lock_init(&skel->qlock);

12、初始化video_device结构数据并注册video_device

	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;

五、.remove详解

.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设备。

上述则是驱动程序在退出时执行的资源释放和清理操作。

你可能感兴趣的:(小生聊【嵌入式linux】,linux,pci,linux驱动开发,v4l2,linux内核)