高通msm-V4L2-Camera驱动浅析1-初识

系列文章

高通msm-V4L2-Camera驱动浅析1-初识
高通msm-V4L2-Camera驱动浅析2-框架详解
高通msm-V4L2-Camera驱动浅析3-session
高通msm-V4L2-Camera驱动浅析4-stream
高通msm-V4L2-Camera驱动浅析5-buffer

前言

V4L2是 linux 设备设计的一套视频框架,网上已经有很多博文可以研究学习。
V4L2源码路径:kernel/msm-4.9/drivers/media/v4l2-core
阅读本文之前,需要对V4L2有一定的了解。

推荐一些不错的V4L2文章
0.V4L2_htjacky的专栏-CSDN博客
1.v4l2的学习建议和流程解析 - silenceer - 博客园 (cnblogs.com)
2.linux内核之 V4L2框架分析_zhc的博客-CSDN博客_v4l2 框架
3.深入学习Linux摄像头(一)v4l2应用编程_JT同学的博客-CSDN博客_v4l2应用编程
4.linux-V4L2系列博客
5.深入理解Android相机体系结构之七_u012596975的博客-CSDN博客

本文希望从一个高通开发者的角度,去研究如何使用V4L2架构实现高通的camera功能。
如果你是高通开发者,你会怎么写代码去实现呢?

一、V4L2架构和一些关键结构体

关键结构体

  • v4l2_device:用来描述一个v4l2设备实例,可以包含多个子设备,对应的是例如 I2C、CSI、MIPI 等设备,它们是从属于一个 V4L2 device 之下的。
  • v4l2_subdev:用来初始化和控制子设备的方法
  • video_device:创建设备节点/dev/videoX,用户空间和内核空间沟通的桥梁
  • videobuf2:视频缓冲的处理
  • v4l2_fh:文件访问控制
  • v4l2_ctrl_handler:控制模块,提供子设备(主要是 video 和 ISP 设备)在用户空间的特效操作接口
  • media_device:用于运行时数据流的管理,嵌入在 V4L2 device 内部

**video_device、v4l2_device和v4l2_subdev的关系


二、Camera应该挂在什么总线上?

Linux的设备和驱动通常都要挂在一种总线上,如I2C总线,USB总线等,但在Soc系统中集成的独立外设控制器是不依附于此类总线的,为了统一性,Linux发明了一种虚拟总线——platform总线,相应的设备就是platform_device,驱动就是platform_driver。


Camera也属于一颗集成的Soc,它有很多子设备:sensor感光芯片,eeprom,flash等,通过MIPI传输图像数据,通过I2C控制sensor的行为。
因此,Camera 设备属于platform_device,挂在platform总线上。

假设我是高通的开发者,那我是不是应该写一个platform_device,一个platform_driver,然后调用platform_driver_register去初始化?

果不其然,让我们来看看源码:

  • platform_device

kernel/msm-4.9/arch/arm64/boot/dts/qcom/msm8937-camera.dtsi

&soc {
    qcom,msm-cam@1b00000 {
        compatible = "qcom,msm-cam";
        reg = <0x1b00000 0x40000>;
        reg-names = "msm-cam";
        status = "ok";
        bus-vectors = "suspend", "svs", "nominal", "turbo";
        qcom,bus-votes = <0 160000000 320000000 320000000>;
    };
}
  • platform_driver
    kernel/msm-4.9/drivers/media/platform/msm/camera_v2/msm.c
static const struct of_device_id msm_dt_match[] = {
    {.compatible = "qcom,msm-cam"},
    {}
};
MODULE_DEVICE_TABLE(of, msm_dt_match);

static struct platform_driver msm_driver = {
    .probe = msm_probe,
    .driver = {
        .name = "msm",
        .owner = THIS_MODULE,
        .of_match_table = msm_dt_match,
    },
};
  • platform_driver_register
static int __init msm_init(void)
{
    return platform_driver_register(&msm_driver);
}

static void __exit msm_exit(void)
{
    platform_driver_unregister(&msm_driver);
}
module_init(msm_init);
module_exit(msm_exit);
MODULE_DESCRIPTION("MSM V4L2 Camera");
MODULE_LICENSE("GPL v2");

注册成功了,就可以在sys/bus/platform找到相应的设备和驱动:


platform总线会调用match函数去匹配驱动和设备,一旦匹配成功,就会调用probe函数。
一般compatible = "qcom,msm-cam"节点匹配设备和驱动。

匹配成功了,就会调用msm_probe函数去做实现一些功能。

作为开发者,我应该在msm_probe实现什么功能呢?platform只是虚拟的,我们还是要回归现实。

现实中,camera是用来拍照,录视频的,
那么我是不是应该把camera定义为一个v4l2_device实例
子设备比如sensor,eeprom我就定义为v4l2_subdev,归v4l2_device统一管理;

同时一个 V4L2 device 下属可能有非常多同类型的子设备(两个或者多个 sensor、ISP 等),
那么在设备运行的时候我怎么知道我的数据流需要用到哪一个类型的哪一个子设备呢。
这个时候就轮到 media_device出手了,它为这一坨的子设备建立一条虚拟的连线,建立起来一个运行时的 pipeline(管道),并且可以在运行时动态改变、管理接入的设备。

另外,如果内核空间要和用户空间沟通,我们还应该初始化 video_device

msm_probe函数应该完成v4l2_device,media_device,video_device等相关结构体的初始化工作

三、MSM V4L2 Camera驱动的初始化

3.1 一些V4L2相关变量

kernel/msm-4.9/drivers/media/platform/msm/camera_v2/msm.c

//v4l2_device实例
static struct v4l2_device *msm_v4l2_dev;

static struct v4l2_fh  *msm_eventq;

struct msm_video_device {
    struct video_device *vdev;
    atomic_t opened;
    struct mutex video_drvdata_mutex;
};
struct msm_video_device *pvdev = NULL;

3.2 在msm_probe进行初始化

  • msm_probe
static int msm_probe(struct platform_device *pdev)
{
···省略部分源码
    struct msm_video_device *pvdev = NULL;
    int rc = 0;

    //为结构体v4l2_device申请内存
    msm_v4l2_dev = kzalloc(sizeof(*msm_v4l2_dev),GFP_KERNEL);
    //为结构体msm_video_device申请内存,里面包含video_device结构体
    pvdev = kzalloc(sizeof(struct msm_video_device),GFP_KERNEL);
    //为video_device申请内存
    pvdev->vdev = video_device_alloc();

#if defined(CONFIG_MEDIA_CONTROLLER)
    //为media_device申请内存
    msm_v4l2_dev->mdev = kzalloc(sizeof(struct media_device),GFP_KERNEL);
    //初始化media_device
    media_device_init(msm_v4l2_dev->mdev);
    //这里 MSM_CONFIGURATION_NAME "msm_config"
    strlcpy(msm_v4l2_dev->mdev->model, MSM_CONFIGURATION_NAME,
            sizeof(msm_v4l2_dev->mdev->model));
    //记录父设备
    msm_v4l2_dev->mdev->dev = &(pdev->dev);
    //注册一个媒体设备元素
    rc = media_device_register(msm_v4l2_dev->mdev);

    //初始化硬件设备端口pad
    if (WARN_ON((rc == media_entity_pads_init(&pvdev->vdev->entity,
            0, NULL)) < 0))
        goto entity_fail;

    pvdev->vdev->entity.function = QCAMERA_VNODE_GROUP_ID;
#endif
    //注册notify回调函数,通常用于子设备传递事件,这些事件可以是自定义事件
    msm_v4l2_dev->notify = msm_sd_notify;
    //video_device中记录msm_v4l2_dev
    pvdev->vdev->v4l2_dev = msm_v4l2_dev;
    //注册一个v4l2_device实例
    rc = v4l2_device_register(&(pdev->dev), pvdev->vdev->v4l2_dev);
    //v4l2_device实例名称为"msm-config"
    strlcpy(pvdev->vdev->name, "msm-config", sizeof(pvdev->vdev->name));
    //释放函数
    pvdev->vdev->release  = video_device_release;
    //设置v4l2_file_operations
    pvdev->vdev->fops     = &msm_fops;
    //设置ioctl_ops:可以通过设备节点被用户空间程序访问
    pvdev->vdev->ioctl_ops = &g_msm_ioctl_ops;
    //次设备号
    pvdev->vdev->minor     = -1;
    //设备类型
    pvdev->vdev->vfl_type  = VFL_TYPE_GRABBER;
    //注册一个视频设备
    rc = video_register_device(pvdev->vdev,VFL_TYPE_GRABBER, -1);
    //初始设备的打开次数为0
    atomic_set(&pvdev->opened, 0);
    //保存msm_video_device指针信息
    video_set_drvdata(pvdev->vdev, pvdev);
    //申请内存
    msm_session_q = kzalloc(sizeof(*msm_session_q), GFP_KERNEL);
    //其他一些相关初始化,如自旋锁,等待队列等待
    msm_init_queue(msm_session_q);
    spin_lock_init(&msm_eventq_lock);
    spin_lock_init(&msm_pid_lock);
    mutex_init(&ordered_sd_mtx);
    mutex_init(&v4l2_event_mtx);
    INIT_LIST_HEAD(&ordered_sd_list);

    return rc;
}
  • 1.为相关结构体分配内存
    msm_v4l2_dev = kzalloc(sizeof(*msm_v4l2_dev),GFP_KERNEL);
    pvdev = kzalloc(sizeof(struct msm_video_device),GFP_KERNEL);
    pvdev->vdev = video_device_alloc();

  • 2. 注册一个媒体设备
    media_device_register(msm_v4l2_dev->mdev);

    media_device结构体有一个model成员,代表的是:设备型号名称
    这里我们注册的媒体设备名称为:msm_config

    注册完成之后,可以在sys/bus/media/devices/下看到一个media0节点

  • 3. 注册一个v4l2_device实例
    v4l2_device_register(&(pdev->dev), pvdev->vdev->v4l2_dev);
    该函数将会初始化v4l2_device结构体,主要做了如下工作:

    • 初始化管理子设备的双向链表subdevs,所有注册到的子设备都需要加入到这个链表当中
    • 初始化自旋锁
    • 初始引用计数
  • 4.注册一个视频设备:video_device
    video_register_device(pvdev->vdev,VFL_TYPE_GRABBER, -1);
    该函数将会初始化video_device结构体,主要做了如下工作:

    • 1.检查设备类型
    • 2.查找空闲的次要节点、设备节点号和设备索引
    • 3.初始化字符设备(主设备号为81,次设备号从0开始)
    • 4.向sysfs注册设备
类型(type) 名称(name)
VFL_TYPE_GRABBER video
VFL_TYPE_VBI vbi
VFL_TYPE_RADIO radio
VFL_TYPE_SUBDEV v4l-subdev
VFL_TYPE_SDR swradio
VFL_TYPE_TOUCH v4l-touch

ps: GRABBER在英文中的意思是掠夺者,video grabber 即 视频捕获器

video_register_device本质上是一个字符设备,注册完成后,通过如下指令可以查到:

另外,在sys/class/video4linux/下可以找到我们的设备节点。

我们注册的设备类型为VFL_TYPE_GRABBER,对应的节点名称video
第一个注册的设备从0开始,以此类推,因此节点名称为video0
视频设备节点video0的name为:msm-config
视频设备节点video0的主设备号和次设备号为:81:2

同时 udev文件系统会为我们在dev/目录下创建一个video0节点,即dev/video0


用户可以打开dev/video0节点,通过IOCTL命令和内核空间进行通信。

四、总结

  • camera是一个集成soc,camera设备挂在platform总线上,属于platform_device,相应的驱动是platform_driver.
  • msm_probe函数完成了v4l2_device,media_device,video_device等相关结构体的初始化工作

本文只写了msm v4l2驱动是怎么进行初始化的,一个V4L2驱动应该还要提供其他的功能,例如:

  • 数据流的创建和删除:msm_create_stream,msm_delete_stream
  • 会话的创建和删除:msm_create_session,msm_destroy_session
  • 事件分发:msm_post_event等等

Stay hungry,Stay foolish!

你可能感兴趣的:(高通msm-V4L2-Camera驱动浅析1-初识)