Android Camera 二 JNI JAVA和C/CPP图像数据传输流程分析
Android Camera 三 CameraService 和 Client 链接到 HAL
Android Camera 四 Camera HAL 分析
源码目录
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;
}