Linux v4l2 二 驱动和 usb 摄像头

Android Camera 一 源码路径

Android Camera 二 JNI JAVA和C/CPP图像数据传输流程分析

Android Camera 三 CameraService 和 Client 链接到 HAL

Android Camera 四 Camera HAL 分析

Linux v4l2 一 应用层

Linux v4l2 二 驱动和 usb 摄像头

 

源码目录

 kernel/fs  : 文件系统

 kernel/drivers\usb\core :usb 核心

kernel/drivers/media/v4l2-core : v4l2 核心

kernel/drivers/media/usb/uvc  :  usb 视频设备类

 

字符设备驱动简介

内核提供了三个函数来注册一组字符设备

  • register_chrdev()             # 旧接口,不做讨论
  • register_chrdev_region()  # 静态分配主次设备号, 用 cdev_init 和 cdev_add 完成注册
  • alloc_chrdev_region()       # 动态分配主次设备号, 用 cdev_init 和 cdev_add 完成注册

register_chrdev  比较老的内核注册的形式,不做分析。
register_chrdev_region/alloc_chrdev_region + cdev  新的驱动形式。

  • register_chrdev_region()  : 注册一个字符设备,事先知道要使用的主、次设备号时使用
  • alloc_chrdev_region() :       注册一个字符设备,来让内核自动给我们分配设备号

 

字符设备注册和卸载的顺序如下: 
注册: register_chrdev_region()  → cdev_add()       // 此过程在加载模块中 
卸载: cdev_del() → unregister_chrdev_region()     // 此过程在卸载模块中

register_chrdev_region/alloc_chrdev_region 的源码如下:

// kernel/fs/char_dev.c
/**
 * register_chrdev_region()  : 注册一个字符设备,事先知道要使用的主、次设备号时使用的;
 *                             要先查看cat /proc/devices去查看没有使用的
 * @from:  要分配的设备编号范围的初始值, 这组连续设备号的起始设备号, 相当于register_chrdev()中主设备号
 * @count: 连续编号范围. 是这组设备号的大小(也是次设备号的个数)
 * @name:  编号相关联的设备名称. (/proc/devices); 本组设备的驱动名称
 *
 * 返回值在成功时为 0,失败时为负错误代码
 */
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
    struct char_device_struct *cd;
    dev_t to = from + count;
    dev_t n, next;

    for(n = from; n < to; n = next)
    {
        next = MKDEV(MAJOR(n)+1, 0);
        if(next > to)
            next = to;
        cd = __register_chrdev_region(MAJOR(n), MINOR(n),
                                      next - n, name);
        if(IS_ERR(cd))
            goto fail;
    }
    return 0;
fail:
    to = n;
    for(n = from; n < to; n = next)
    {
        next = MKDEV(MAJOR(n)+1, 0);
        kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
    }
    return PTR_ERR(cd);
}


/**
 * alloc_chrdev_region() : 注册一个字符设备,来让内核自动给我们分配设备号
 * @dev: 是输出参数,获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,获得主设备号和次设备号;
 *       在mknod创建节点和设备文件时用到主设备号和次设备号
 * @baseminor: 次设备号的基准,从第几个次设备号开始分配
 * @count: 次设备号的个数
 * @name: 驱动的名字
 *
 * 返回值在成功时为 0,失败时为负错误代码
 */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                        const char *name)
{
    struct char_device_struct *cd;
    cd = __register_chrdev_region(0, baseminor, count, name);
    if(IS_ERR(cd))
        return PTR_ERR(cd);
    *dev = MKDEV(cd->major, cd->baseminor);
    return 0;
}



cdev 字符设备

cdev是表示字符设备的结构体,注册字符设备时,实例化 struct cdev 结构体的内部成员

// kernel/include/linux/cdev.h

#ifndef _LINUX_CDEV_H
#define _LINUX_CDEV_H

#include <linux/kobject.h>
#include <linux/kdev_t.h>
#include <linux/list.h>

struct file_operations;
struct inode;
struct module;

struct cdev
{
    struct kobject kobj;       //
    struct module *owner;      // 填充时,值要为 THIS_MODULE,表示模块
    const struct file_operations *ops;  // file_operations结构体是注册驱动的关键,要填充这个结构体的成员,实例化一系列回调函数
    struct list_head list;
    dev_t dev;                 // 设备号,主设备号+次设备号,可以用MAJOR宏和MINOR宏,提取主设备号和次设备号
    unsigned int count;        // 次设备号个数
};

void cdev_init(struct cdev *, const struct file_operations *); // 将struct cdev类型的结构体变量和file_operations结构体进行绑定的

struct cdev *cdev_alloc(void);                                 // 动态申请一个 cdev 内存

void cdev_put(struct cdev *p);                                 // 释放一个 cdev 内存

int cdev_add(struct cdev *, dev_t, unsigned);                  // 向内核里面添加一个驱动,注册驱动

void cdev_del(struct cdev *);                                  // 从内核中注销掉一个驱动。注销驱动

void cd_forget(struct inode *);

extern struct backing_dev_info directly_mappable_cdev_bdi;

#endif

 

 

V4L2 驱动分析

 

我们知道 video 设备是在 v4l2 中注册字符驱动。

在 Linux 内核的 v4l2 源码目录中执行搜索注册字符设备的函数名 register_chrdev_region

# 查找哪个文件注册 v4l2 设备
grep -R "register_chrdev_region" kernel/drivers/media/v4l2-core/

打印如下: 定位到 v4l2-dev.c 

 

下面来分析: v4l2-dev.c 中的与设备注册相关的函数 :

设备注册和卸载 : __init和 __exit  

// kernel/drivers/media/v4l2-core/v4l2-dev.c

/*
 *  Initialise video for linux
 */
static int __init videodev_init(void)
{
    dev_t dev = MKDEV(VIDEO_MAJOR, 0);
    int ret;

    printk(KERN_INFO "Linux video capture interface: v2.00\n");
    ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);
    if(ret < 0)
    {
        printk(KERN_WARNING "videodev: unable to get major %d\n",
               VIDEO_MAJOR);
        return ret;
    }

    ret = class_register(&video_class);
    if(ret < 0)
    {
        unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
        printk(KERN_WARNING "video_dev: class_register failed\n");
        return -EIO;
    }

    return 0;
}

static void __exit videodev_exit(void)
{
    dev_t dev = MKDEV(VIDEO_MAJOR, 0);

    class_unregister(&video_class);
    unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
}

在 Linux 系统中添加一个 v4l2 设备节点,如下函数 __video_register_device()  添加一个视频类字符设备

// kernel/drivers/media/v4l2-core/v4l2-dev.c

int __video_register_device(struct video_device *vdev, int type, int nr,
                            int warn_if_nr_in_use, struct module *owner)
{
    ......

    /* Part 1: 检查设备类型 */
    ......

    /* Part 2: 找到一个空闲次设备号、设备节点号和设备索引 */
    ......

    /* Part 3: 添加一个字符设备 */
    vdev->cdev = cdev_alloc();
    if(vdev->cdev == NULL)
    {
        ret = -ENOMEM;
        goto cleanup;
    }
    vdev->cdev->ops = &v4l2_fops;
    vdev->cdev->owner = owner;
    ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
    if(ret < 0)
    {
        printk(KERN_ERR "%s: cdev_add failed\n", __func__);
        kfree(vdev->cdev);
        vdev->cdev = NULL;
        goto cleanup;
    }

    /* Part 4: 将设备注册到sysfs */
    vdev->dev.class = &video_class;
    vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
    if(vdev->parent)
        vdev->dev.parent = vdev->parent;
    dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
    ret = device_register(&vdev->dev);   /* 创建设备节点 */
    if(ret < 0)
    {
        printk(KERN_ERR "%s: device_register failed\n", __func__);
        goto cleanup;
    }
    /* 注册释放回调函数 */
    vdev->dev.release = v4l2_device_release;

    if(nr != -1 && nr != vdev->num && warn_if_nr_in_use)
        printk(KERN_WARNING "%s: requested %s%d, got %s\n", __func__,
               name_base, nr, video_device_node_name(vdev));

    /* Increase v4l2_device refcount */
    if(vdev->v4l2_dev)
        v4l2_device_get(vdev->v4l2_dev);

#if defined(CONFIG_MEDIA_CONTROLLER)
    /* Part 5: 注册实例 */
    if(vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
            vdev->vfl_type != VFL_TYPE_SUBDEV)
    {
        vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
        vdev->entity.name = vdev->name;
        vdev->entity.info.v4l.major = VIDEO_MAJOR;
        vdev->entity.info.v4l.minor = vdev->minor;
        ret = media_device_register_entity(vdev->v4l2_dev->mdev,
                                           &vdev->entity);
        if(ret < 0)
            printk(KERN_WARNING
                   "%s: media_device_register_entity failed\n",
                   __func__);
    }
#endif
    /* Part 6: 在video_device 数组中添加当前 vdev 设备,通过次设备号索引并使用该设备 */
    set_bit(V4L2_FL_REGISTERED, &vdev->flags);
    mutex_lock(&videodev_lock);
    video_device[vdev->minor] = vdev;
    mutex_unlock(&videodev_lock);

    return 0;

cleanup:
    mutex_lock(&videodev_lock);
    if(vdev->cdev)
        cdev_del(vdev->cdev);
    devnode_clear(vdev);
    mutex_unlock(&videodev_lock);
    /* Mark this video device as never having been registered. */
    vdev->minor = -1;
    return ret;
}

 

用 usb 摄像头来分析硬件层

usb core 中读取设备描述符和解析描述符中的的协议,调用 uvc 类来注册一个 usb 摄像头设备。

usb 驱动内容很多,不做展开,仅分析 uvc 驱动。

uvc 驱动调用 uvc_probe() 函数来探测并加载 usb 摄像头;

// kernel/drivers/media/usb/uvc/uvc_driver.c
// usb 摄像头设备结构体
struct uvc_driver uvc_driver =
{
    .driver = {
        .name       = "uvcvideo",
        .probe      = uvc_probe,        // probe 方法,探测并加载 usb 摄像头
        .disconnect = uvc_disconnect,
        .suspend    = uvc_suspend,
        .resume     = uvc_resume,
        .reset_resume   = uvc_reset_resume, // 支持的设备id列表
        .id_table   = uvc_ids,
        .supports_autosuspend = 1,
    },
};

// init 初始化函数,注册结构体 uvc_driver
static int __init uvc_init(void)
{
    int ret;

    uvc_debugfs_init();

    ret = usb_register(&uvc_driver.driver);
    if(ret < 0)
    {
        uvc_debugfs_cleanup();
        return ret;
    }
    printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
    return 0;
}

接下来分析 probe() 中的函数调用:

// kernel/drivers/media/usb/uvc/uvc_driver.c


/* ------------------------------------------------------------------------
 * USB probe, disconnect, suspend and resume
 */

static int uvc_probe(struct usb_interface *intf,
                     const struct usb_device_id *id)
{
    struct usb_device *udev = interface_to_usbdev(intf);
    struct uvc_device *dev;
    int ret;

    if(id->idVendor && id->idProduct)
        uvc_trace(UVC_TRACE_PROBE, "Probing known UVC device %s "
                  "(%04x:%04x)\n", udev->devpath, id->idVendor,
                  id->idProduct);
    else
        uvc_trace(UVC_TRACE_PROBE, "Probing generic UVC device %s\n",
                  udev->devpath);

    /* 内核为设备分配一个 struct usb_device 大小的内存并初始化 */
    if((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)
        return -ENOMEM;

    INIT_LIST_HEAD(&dev->entities);
    INIT_LIST_HEAD(&dev->chains);
    INIT_LIST_HEAD(&dev->streams);
    atomic_set(&dev->nstreams, 0);
    atomic_set(&dev->users, 0);
    atomic_set(&dev->nmappings, 0);

    dev->udev = usb_get_dev(udev);
    dev->intf = usb_get_intf(intf);
    dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
    dev->quirks = (uvc_quirks_param == -1)
                  ? id->driver_info : uvc_quirks_param;

    if(udev->product != NULL)
        strlcpy(dev->name, udev->product, sizeof dev->name);
    else
        snprintf(dev->name, sizeof dev->name,
                 "UVC Camera (%04x:%04x)",
                 le16_to_cpu(udev->descriptor.idVendor),
                 le16_to_cpu(udev->descriptor.idProduct));

    /* Parse the Video Class control descriptor. */
    if(uvc_parse_control(dev) < 0)
    {
        uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC "
                  "descriptors.\n");
        goto error;
    }

    uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n",
               dev->uvc_version >> 8, dev->uvc_version & 0xff,
               udev->product ? udev->product : "<unnamed>",
               le16_to_cpu(udev->descriptor.idVendor),
               le16_to_cpu(udev->descriptor.idProduct));

    if(dev->quirks != id->driver_info)
    {
        uvc_printk(KERN_INFO, "Forcing device quirks to 0x%x by module "
                   "parameter for testing purpose.\n", dev->quirks);
        uvc_printk(KERN_INFO, "Please report required quirks to the "
                   "linux-uvc-devel mailing list.\n");
    }

    /* Register the media and V4L2 devices. */
#ifdef CONFIG_MEDIA_CONTROLLER
    dev->mdev.dev = &intf->dev;
    strlcpy(dev->mdev.model, dev->name, sizeof(dev->mdev.model));
    if(udev->serial)
        strlcpy(dev->mdev.serial, udev->serial,
                sizeof(dev->mdev.serial));
    strcpy(dev->mdev.bus_info, udev->devpath);
    dev->mdev.hw_revision = le16_to_cpu(udev->descriptor.bcdDevice);
    dev->mdev.driver_version = LINUX_VERSION_CODE;
    if(media_device_register(&dev->mdev) < 0)
        goto error;

    dev->vdev.mdev = &dev->mdev;
#endif
    /* 注册一个媒体类设备 */
    if(v4l2_device_register(&intf->dev, &dev->vdev) < 0)
        goto error;

    /* Initialize controls. */
    if(uvc_ctrl_init_device(dev) < 0)
        goto error;

    /* Scan the device for video chains. */
    if(uvc_scan_device(dev) < 0)
        goto error;

    /* Register video device nodes. */
    /* 注册 video 设备节点 */
    if(uvc_register_chains(dev) < 0)
        goto error;

    /* Save our data pointer in the interface data. */
    usb_set_intfdata(intf, dev);

    /* Initialize the interrupt URB. */
    if((ret = uvc_status_init(dev)) < 0)
    {
        uvc_printk(KERN_INFO, "Unable to initialize the status "
                   "endpoint (%d), status interrupt will not be "
                   "supported.\n", ret);
    }

    uvc_trace(UVC_TRACE_PROBE, "UVC device initialized.\n");
    usb_enable_autosuspend(udev);
    return 0;

error:
    uvc_unregister_video(dev);
    return -ENODEV;
}

阅读源码通过注释,得知 uvc_register_chains() 时注册设备节点的,来重点分析这个函数

    /* Register video device nodes. */
    /* 注册 video 设备节点 */
    if(uvc_register_chains(dev) < 0)
        goto error;

 

uvc_probe() →  uvc_register_chains() → uvc_register_terms() → uvc_register_video()

uvc_register_video() 调用 v4l2 核心层的v4l2-dev.c 来注册 video 字符设备:  

  •  video_device_alloc()     
  • video_register_device()  调用  __video_register_device()

在上文中分析了  __video_register_device() 函数中注册了 video 字符设备。  到此 v4l2 框架的注册的过程分析完成。 

// kernel/drivers/media/usb/uvc/uvc_v4l2.c
// v4l2 驱动中 ioctl 操作,被用户层调用
const struct v4l2_file_operations uvc_fops = {
    .owner      = THIS_MODULE,
    .open       = uvc_v4l2_open,
    .release    = uvc_v4l2_release,
    .unlocked_ioctl = uvc_v4l2_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl32 = uvc_v4l2_compat_ioctl32,
#endif
    .read       = uvc_v4l2_read,
    .mmap       = uvc_v4l2_mmap,
    .poll       = uvc_v4l2_poll,
#ifndef CONFIG_MMU
    .get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};


// kernel/drivers/media/usb/uvc/uvc_driver.c
static int uvc_register_video(struct uvc_device *dev,
                              struct uvc_streaming *stream)
{
    struct video_device *vdev;
    int ret;

    /* Initialize the streaming interface with default streaming
     * parameters.
     */
    ret = uvc_video_init(stream);
    if(ret < 0)
    {
        uvc_printk(KERN_ERR, "Failed to initialize the device "
                   "(%d).\n", ret);
        return ret;
    }

    uvc_debugfs_init_stream(stream);

    /* Register the device with V4L. */
    vdev = video_device_alloc();
    if(vdev == NULL)
    {
        uvc_printk(KERN_ERR, "Failed to allocate video device (%d).\n",
                   ret);
        return -ENOMEM;
    }

    /* We already hold a reference to dev->udev. The video device will be
     * unregistered before the reference is released, so we don't need to
     * get another one.
     */
    vdev->v4l2_dev = &dev->vdev;
    vdev->fops = &uvc_fops;                    //  注册 struct v4l2_file_operations uvc_fops , v4l2 open/close/ioctl 等函数
    vdev->release = uvc_release;
    vdev->prio = &stream->chain->prio;
    set_bit(V4L2_FL_USE_FH_PRIO, &vdev->flags);
    if(stream->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
        vdev->vfl_dir = VFL_DIR_TX;
    strlcpy(vdev->name, dev->name, sizeof vdev->name);

    /* Set the driver data before calling video_register_device, otherwise
     * uvc_v4l2_open might race us.
     */
    stream->vdev = vdev;
    video_set_drvdata(vdev, stream);

    ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
    if(ret < 0)
    {
        uvc_printk(KERN_ERR, "Failed to register video device (%d).\n",
                   ret);
        stream->vdev = NULL;
        video_device_release(vdev);
        return ret;
    }

    if(stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
        stream->chain->caps |= V4L2_CAP_VIDEO_CAPTURE;
    else
        stream->chain->caps |= V4L2_CAP_VIDEO_OUTPUT;

    atomic_inc(&dev->nstreams);
    return 0;
}

 

 

 

 

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值