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.cint __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);elseuvc_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);elsesnprintf(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_CONTROLLERdev->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;elsestream->chain->caps |= V4L2_CAP_VIDEO_OUTPUT;atomic_inc(&dev->nstreams);return 0;
}