uvc驱动中的v4l2

本文涉及的产品
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
数据传输服务 DTS,数据同步 1个月
简介: uvc驱动中的v4l2

v4l2_device_register

/driver/media/v4l2-core/v4l2-device.c

uvc_probe->v4l2_device_register

v4l2_device_register 只是用于初始化一些东西,比如自旋锁、引用计数。

int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
    // 检查v4l2_dev是否为空
    if (v4l2_dev == NULL)
        return -EINVAL;
    // 初始化v4l2_dev的subdevs、lock、ioctl_lock、prio、ref
    INIT_LIST_HEAD(&v4l2_dev->subdevs);
    spin_lock_init(&v4l2_dev->lock);
    mutex_init(&v4l2_dev->ioctl_lock);
    v4l2_prio_init(&v4l2_dev->prio);
    kref_init(&v4l2_dev->ref);
    // 获取dev的引用计数
    get_device(dev);
    v4l2_dev->dev = dev;
    // 如果dev为空,则name必须由调用者填充
    if (dev == NULL) {
        if (WARN_ON(!v4l2_dev->name[0]))
            return -EINVAL;
        return 0;
    }
    // 如果name为空,则设置为driver name + device name
    if (!v4l2_dev->name[0])
        snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
            dev->driver->name, dev_name(dev));
    // 设置dev的私有数据为v4l2_dev
    if (!dev_get_drvdata(dev))
        dev_set_drvdata(dev, v4l2_dev);
    return 0;
}

函数 v4l2_device_register() 在 uvc_driver 中的作用如下:

检查 v4l2_dev 是否为空,如果为空则返回错误码。

初始化 v4l2_dev 的成员变量,包括 subdevs、lock、ioctl_lock、prio 和 ref。

增加设备 dev 的引用计数。

将 v4l2_dev 的 dev 成员设置为传入的设备 dev。

如果设备 dev 为空,则要求调用者填充 v4l2_dev 的 name 字段;如果 name 字段为空,则使用驱动程序的名称和设备的名称拼接而成。

将设备

dev 的私有数据指针设置为 v4l2_dev。

返回成功。

video_register_device

/driver/media/v4l2-core/v4l2-dev.c

uvc_probe->uvc_register_chains->uvc_register_terms->uvc_register_video->video_register_device

这个函数用于向V4L2框架注册视频设备。它调用__video_register_device函数,将warn_if_nr_in_use参数设置为1,这意味着如果所需的设备节点号已经被使用,将发出警告。

__video_register_device函数负责向V4L2框架注册视频设备。它接受与video_register_device相同的参数,以及一个warn_if_nr_in_use参数和一个struct module指针owner。它返回一个整数值。

如果video_register_device失败,则不会调用video_device结构的release()回调函数,因此调用者负责释放任何数据。通常,这意味着在失败时应调用video_device_release()。

总的来说,video_register_device是向V4L2框架注册视频设备的重要函数。

static inline int __must_check video_register_device(struct video_device *vdev,
int type, int nr)
{
return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}

__video_register_device函数用于在内核中注册video4linux设备。它根据请求的类型分配次要号和设备节点号,并将新的设备节点注册到内核中。

int __video_register_device(struct video_device *vdev, int type, int nr,
        int warn_if_nr_in_use, struct module *owner)
{
    // 初始化变量
    int i = 0;
    int ret;
    int minor_offset = 0;
    int minor_cnt = VIDEO_NUM_DEVICES;
    const char *name_base;
    // A minor value of -1 marks this video device as never having been registered
    // 初始化 minor 值为 -1
    vdev->minor = -1;
    // the release callback MUST be present
    // 检查 release 回调是否存在
    if (WARN_ON(!vdev->release))
        return -EINVAL;
    // the v4l2_dev pointer MUST be present
    // 检查 v4l2_dev 指针是否存在
    if (WARN_ON(!vdev->v4l2_dev))
        return -EINVAL;
    /* v4l2_fh support */
    spin_lock_init(&vdev->fh_lock); // 初始化锁
    INIT_LIST_HEAD(&vdev->fh_list); // 初始化链表
    /* Part 1: check device type */
    switch (type) { // 根据设备类型选择设备名
    case VFL_TYPE_GRABBER:
        name_base = "video";
        break;
    case VFL_TYPE_VBI:
        name_base = "vbi";
        break;
    case VFL_TYPE_RADIO:
        name_base = "radio";
        break;
    case VFL_TYPE_SUBDEV:
        name_base = "v4l-subdev";
        break;
    case VFL_TYPE_SDR:
        /* Use device name 'swradio' because 'sdr' was already taken. */
        name_base = "swradio";
        break;
    default:
        printk(KERN_ERR "%s called with unknown type: %d\n",
               __func__, type); // 打印错误信息
        return -EINVAL;
    }
    // 设置设备类型
    vdev->vfl_type = type;
    // 初始化字符设备指针
    vdev->cdev = NULL;
    // 如果设备的父设备指针为空,则将其指向 v4l2_dev 的设备指针
    if (vdev->dev_parent == NULL)
        vdev->dev_parent = vdev->v4l2_dev->dev;
    // 如果控制处理程序指针为空,则将其指向 v4l2_dev 的控制处理程序指针
    if (vdev->ctrl_handler == NULL)
        vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
    /* 如果优先级状态指针为空,则使用 v4l2_device 的优先级状态。*/
    if (vdev->prio == NULL)
        vdev->prio = &vdev->v4l2_dev->prio;
    /* Part 2: find a free minor, device node number and device index. */
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
    /* 为前四种类型保留范围,出于历史原因。
     * 新设备(尚未就位)应使用范围
     * 128-191,只需在那里选择第一个空闲的次要设备
     * (新样式)。 */
    switch (type) {
    case VFL_TYPE_GRABBER: // 如果设备类型为 VFL_TYPE_GRABBER
        minor_offset = 0; // 次设备号偏移量为 0
        minor_cnt = 64; // 次设备号数量为 64
        break;
    case VFL_TYPE_RADIO: // 如果设备类型为 VFL_TYPE_RADIO
        minor_offset = 64; // 次设备号偏移量为 64
        minor_cnt = 64; // 次设备号数量为 64
        break;
    case VFL_TYPE_VBI: // 如果设备类型为 VFL_TYPE_VBI
        minor_offset = 224; // 次设备号偏移量为 224
        minor_cnt = 32; // 次设备号数量为 32
        break;
    default: // 如果设备类型为其他类型
        minor_offset = 128; // 次设备号偏移量为 128
        minor_cnt = 64; // 次设备号数量为 64
        break;
    }
#endif
    /* 选择设备节点号 */
    mutex_lock(&videodev_lock); // 加锁
    nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt); // 查找设备节点号
    if (nr == minor_cnt) // 如果找不到空闲的设备节点号
        nr = devnode_find(vdev, 0, minor_cnt); // 从头开始查找
    if (nr == minor_cnt) { // 如果还是找不到空闲的设备节点号
        printk(KERN_ERR "could not get a free device node number\n"); // 打印错误信息
        mutex_unlock(&videodev_lock); // 解锁
        return -ENFILE; // 返回错误码
    }
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
    /* 1-on-1 mapping of device node number to minor number */
    i = nr; // 将设备节点号赋值给 i
#else
    /* The device node number and minor numbers are independent, so
       we just find the first free minor number. */
    for (i = 0; i < VIDEO_NUM_DEVICES; i++) // 遍历所有设备
        if (video_device[i] == NULL) // 如果设备未被占用
            break; // 跳出循环
    if (i == VIDEO_NUM_DEVICES) { // 如果所有设备都被占用
        mutex_unlock(&videodev_lock); // 解锁
        printk(KERN_ERR "could not get a free minor\n"); // 打印错误信息
        return -ENFILE; // 返回错误码
    }
#endif
    vdev->minor = i + minor_offset; // 设置设备节点号
    vdev->num = nr; // 设置设备编号
    devnode_set(vdev); // 设置设备节点
    /* Should not happen since we thought this minor was free */
    WARN_ON(video_device[vdev->minor] != NULL); // 如果设备节点已被占用,打印警告信息
    vdev->index = get_index(vdev); // 获取设备索引
    video_device[vdev->minor] = vdev; // 将设备添加到 video_device 数组中
    mutex_unlock(&videodev_lock); // 解锁
    if (vdev->ioctl_ops)
        determine_valid_ioctls(vdev); // 确定设备支持的 ioctl 操作
    /* Part 3: Initialize the character device */
    vdev->cdev = cdev_alloc(); // 分配 cdev 结构体
    if (vdev->cdev == NULL) { // 如果分配失败
        ret = -ENOMEM; // 返回错误码
        goto cleanup; // 跳转到 cleanup 标签处
    }
    vdev->cdev->ops = &v4l2_fops; // 设置 cdev 的操作函数
    vdev->cdev->owner = owner; // 设置 cdev 的 owner
    ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); // 添加 cdev
    if (ret < 0) { // 如果添加失败
        printk(KERN_ERR "%s: cdev_add failed\n", __func__); // 打印错误信息
        kfree(vdev->cdev); // 释放 cdev
        vdev->cdev = NULL; // 将 cdev 置为空
        goto cleanup; // 跳转到 cleanup 标签处
    }
    /* Part 4: register the device with sysfs */
    vdev->dev.class = &video_class; // 设置设备的 class
    vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor); // 设置设备的 devt
    vdev->dev.parent = vdev->dev_parent; // 设置设备的 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; // 跳转到 cleanup 标签处
    }
    /* Register the release callback that will be called when the last
       reference to the device goes away. */
    vdev->dev.release = v4l2_device_release; // 设置设备的 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 */
    v4l2_device_get(vdev->v4l2_dev); // 增加 v4l2_device 的引用计数
#if defined(CONFIG_MEDIA_CONTROLLER)
    /* Part 5: Register the entity. */
    if (vdev->v4l2_dev->mdev && // 如果 v4l2_dev 的 mdev 不为空
        vdev->vfl_type != VFL_TYPE_SUBDEV) { // 如果 vfl_type 不是 VFL_TYPE_SUBDEV
        vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L; // 设置 entity 的 type
        vdev->entity.name = vdev->name; // 设置 entity 的 name
        vdev->entity.info.dev.major = VIDEO_MAJOR; // 设置 entity 的 major
        vdev->entity.info.dev.minor = vdev->minor; // 设置 entity 的 minor
        ret = media_device_register_entity(vdev->v4l2_dev->mdev, // 注册 entity
            &vdev->entity);
        if (ret < 0) // 如果注册失败
            printk(KERN_WARNING
                   "%s: media_device_register_entity failed\n",
                   __func__); // 打印警告信息
    }
#endif
    /* Part 6: Activate this minor. The char device can now be used. */
    set_bit(V4L2_FL_REGISTERED, &vdev->flags); // 设置设备已注册标志位
    return 0; // 返回 0
cleanup:
    mutex_lock(&videodev_lock);
    if (vdev->cdev)
        cdev_del(vdev->cdev);
    video_device[vdev->minor] = NULL;
    devnode_clear(vdev);
    mutex_unlock(&videodev_lock);
    /* Mark this video device as never having been registered. */
    vdev->minor = -1;
    return ret;
}

这个函数是用于注册视频设备的关键函数,它执行以下操作:

初始化变量和数据结构。

检查必需的回调函数和指针是否存在。

初始化 v4l2_fh 相关的数据结构。

根据设备类型选择设备名。

设置设备的类型和相关指针。

查找可用的设备节点号和次设备号。

分配并添加字符设备。

在 sysfs 中注册设备。

增加 v4l2_device 的引用计数。

注册媒体实体(如果配置了 MEDIA_CONTROLLER)。

激活该设备,使字符设备可以使用。

返回注册结果。

总体而言,该函数负责完成视频设备的注册过程,包括分配设备号、字符设备的添加、在 sysfs 中注册设备等操作。它还处理了设备节点的分配和媒体实体的注册(如果适用)。

vdev->cdev->ops = &v4l2_fops设置 cdev 的操作函数

static const struct file_operations v4l2_fops = {
    .owner = THIS_MODULE, // 指向拥有此结构的模块的指针
    .read = v4l2_read, // 读取函数
    .write = v4l2_write, // 写入函数
    .open = v4l2_open, // 打开函数
    .get_unmapped_area = v4l2_get_unmapped_area, // 获取未映射区域函数
    .mmap = v4l2_mmap, // 内存映射函数
    .unlocked_ioctl = v4l2_ioctl, // 未加锁的ioctl函数
#ifdef CONFIG_COMPAT
    .compat_ioctl = v4l2_compat_ioctl32, // 兼容ioctl函数
#endif
    .release = v4l2_release, // 释放函数
    .poll = v4l2_poll, // 轮询函数
    .llseek = no_llseek, // 无寻址函数
};

v4l2_ioctls

/driver/media/v4l2-core/v4l2-ioctl.c

在 Linux 内核中,static struct v4l2_ioctl_info v4l2_ioctls[] 是一个数组,它包含了视频设备驱动程序支持的 IOCTL 命令的信息。

每个数组元素都是一个 struct v4l2_ioctl_info 结构体,用于描述一个特定的 IOCTL 命令。该结构体的成员包括:

cmd:IOCTL 命令的数值标识。

flags:IOCTL 命令的属性标志,如读取、写入、读写等。

ioctl:IOCTL 命令的处理函数指针,用于执行该命令所需的操作。

这个数组的作用是提供了 IOCTL 命令的映射关系,当用户空间的应用程序通过系统调用 ioctl() 向视频设备驱动程序发送 IOCTL 命令时,内核可以通过查询这个数组,找到对应的处理函数来执行相应的操作。

通过定义和填充这个数组,视频设备驱动程序告诉内核它所支持的 IOCTL 命令以及它们的处理方式。这样,当应用程序调用 ioctl() 时,内核可以根据 IOCTL 命令的数值在 v4l2_ioctls[] 数组中查找对应的处理函数,并调用该函数来处理该命令。

#define IOCTL_INFO_FNC(_ioctl, _func, _debug, _flags)           \
    [_IOC_NR(_ioctl)] = {                       \
        .ioctl = _ioctl,                    \
        .flags = _flags | INFO_FL_FUNC,             \
        .name = #_ioctl,                    \
        .u.func = _func,                    \
        .debug = _debug,                    \
    }
static struct v4l2_ioctl_info v4l2_ioctls[] = {
    IOCTL_INFO_FNC(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0), // 查询设备能力
    IOCTL_INFO_FNC(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, INFO_FL_CLEAR(v4l2_fmtdesc, type)), // 枚举设备支持的格式
    IOCTL_INFO_FNC(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, 0), // 获取当前格式
    IOCTL_INFO_FNC(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO), // 设置当前格式
    IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE), // 请求缓冲区
    IOCTL_INFO_FNC(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)), // 查询缓冲区
    IOCTL_INFO_STD(VIDIOC_G_FBUF, vidioc_g_fbuf, v4l_print_framebuffer, 0), // 获取帧缓冲区
    IOCTL_INFO_STD(VIDIOC_S_FBUF, vidioc_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO), // 设置帧缓冲区
    IOCTL_INFO_FNC(VIDIOC_OVERLAY, v4l_overlay, v4l_print_u32, INFO_FL_PRIO), // 设置叠加
    IOCTL_INFO_FNC(VIDIOC_QBUF, v4l_qbuf, v4l_print_buffer, INFO_FL_QUEUE), // 将缓冲区放入队列
    IOCTL_INFO_STD(VIDIOC_EXPBUF, vidioc_expbuf, v4l_print_exportbuffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_exportbuffer, flags)), // 导出缓冲区
    IOCTL_INFO_FNC(VIDIOC_DQBUF, v4l_dqbuf, v4l_print_buffer, INFO_FL_QUEUE), // 从队列中取出缓冲区
    IOCTL_INFO_FNC(VIDIOC_STREAMON, v4l_streamon, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE), // 开始数据流
    IOCTL_INFO_FNC(VIDIOC_STREAMOFF, v4l_streamoff, v4l_print_buftype, INFO_FL_PRIO | INFO_FL_QUEUE), // 停止数据流
    IOCTL_INFO_FNC(VIDIOC_G_PARM, v4l_g_parm, v4l_print_streamparm, INFO_FL_CLEAR(v4l2_streamparm, type)), // 获取流参数
    IOCTL_INFO_FNC(VIDIOC_S_PARM, v4l_s_parm, v4l_print_streamparm, INFO_FL_PRIO), // 设置流参数
    IOCTL_INFO_STD(VIDIOC_G_STD, vidioc_g_std, v4l_print_std, 0), // 获取标准
    IOCTL_INFO_FNC(VIDIOC_S_STD, v4l_s_std, v4l_print_std, INFO_FL_PRIO), // 设置标准
    IOCTL_INFO_FNC(VIDIOC_ENUMSTD, v4l_enumstd, v4l_print_standard, INFO_FL_CLEAR(v4l2_standard, index)), // 枚举标准
    IOCTL_INFO_FNC(VIDIOC_ENUMINPUT, v4l_enuminput, v4l_print_enuminput, INFO_FL_CLEAR(v4l2_input, index)), // 枚举输入
    IOCTL_INFO_FNC(VIDIOC_G_CTRL, v4l_g_ctrl, v4l_print_control, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_control, id)), // 获取控制
    IOCTL_INFO_FNC(VIDIOC_S_CTRL, v4l_s_ctrl, v4l_print_control, INFO_FL_PRIO | INFO_FL_CTRL), // 设置控制
    IOCTL_INFO_FNC(VIDIOC_G_TUNER, v4l_g_tuner, v4l_print_tuner, INFO_FL_CLEAR(v4l2_tuner, index)), // 获取调谐器
    IOCTL_INFO_FNC(VIDIOC_S_TUNER, v4l_s_tuner, v4l_print_tuner, INFO_FL_PRIO), // 设置调谐器
    IOCTL_INFO_STD(VIDIOC_G_AUDIO, vidioc_g_audio, v4l_print_audio, 0), // 获取音频
    IOCTL_INFO_STD(VIDIOC_S_AUDIO, vidioc_s_audio, v4l_print_audio, INFO_FL_PRIO), // 设置音频
    IOCTL_INFO_FNC(VIDIOC_QUERYCTRL, v4l_queryctrl, v4l_print_queryctrl, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_queryctrl, id)), // 查询控制器
    IOCTL_INFO_FNC(VIDIOC_QUERYMENU, v4l_querymenu, v4l_print_querymenu, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_querymenu, index)), // 查询菜单
    IOCTL_INFO_STD(VIDIOC_G_INPUT, vidioc_g_input, v4l_print_u32, 0), // 获取输入
    IOCTL_INFO_FNC(VIDIOC_S_INPUT, v4l_s_input, v4l_print_u32, INFO_FL_PRIO), // 设置输入
    IOCTL_INFO_STD(VIDIOC_G_EDID, vidioc_g_edid, v4l_print_edid, 0), // 获取EDID
    IOCTL_INFO_STD(VIDIOC_S_EDID, vidioc_s_edid, v4l_print_edid, INFO_FL_PRIO), // 设置EDID
    IOCTL_INFO_STD(VIDIOC_G_OUTPUT, vidioc_g_output, v4l_print_u32, 0), // 获取输出
    IOCTL_INFO_FNC(VIDIOC_S_OUTPUT, v4l_s_output, v4l_print_u32, INFO_FL_PRIO), // 设置输出
    IOCTL_INFO_FNC(VIDIOC_ENUMOUTPUT, v4l_enumoutput, v4l_print_enumoutput, INFO_FL_CLEAR(v4l2_output, index)), // 枚举输出
    IOCTL_INFO_STD(VIDIOC_G_AUDOUT, vidioc_g_audout, v4l_print_audioout, 0), // 获取音频输出
    IOCTL_INFO_STD(VIDIOC_S_AUDOUT, vidioc_s_audout, v4l_print_audioout, INFO_FL_PRIO), // 设置音频输出
    IOCTL_INFO_FNC(VIDIOC_G_MODULATOR, v4l_g_modulator, v4l_print_modulator, INFO_FL_CLEAR(v4l2_modulator, index)), // 获取调制器
    IOCTL_INFO_STD(VIDIOC_S_MODULATOR, vidioc_s_modulator, v4l_print_modulator, INFO_FL_PRIO), // 设置调制器
    IOCTL_INFO_FNC(VIDIOC_G_FREQUENCY, v4l_g_frequency, v4l_print_frequency, INFO_FL_CLEAR(v4l2_frequency, tuner)), // 获取频率
    IOCTL_INFO_FNC(VIDIOC_S_FREQUENCY, v4l_s_frequency, v4l_print_frequency, INFO_FL_PRIO), // 设置频率
    IOCTL_INFO_FNC(VIDIOC_CROPCAP, v4l_cropcap, v4l_print_cropcap, INFO_FL_CLEAR(v4l2_cropcap, type)), // 裁剪能力
    IOCTL_INFO_FNC(VIDIOC_G_CROP, v4l_g_crop, v4l_print_crop, INFO_FL_CLEAR(v4l2_crop, type)), // 获取裁剪
    IOCTL_INFO_FNC(VIDIOC_S_CROP, v4l_s_crop, v4l_print_crop, INFO_FL_PRIO), // 设置裁剪
    IOCTL_INFO_STD(VIDIOC_G_SELECTION, vidioc_g_selection, v4l_print_selection, INFO_FL_CLEAR(v4l2_selection, r)), // 获取选择
    IOCTL_INFO_STD(VIDIOC_S_SELECTION, vidioc_s_selection, v4l_print_selection, INFO_FL_PRIO | INFO_FL_CLEAR(v4l2_selection, r)), // 设置选择
    IOCTL_INFO_STD(VIDIOC_G_JPEGCOMP, vidioc_g_jpegcomp, v4l_print_jpegcompression, 0), // 获取JPEG压缩
    IOCTL_INFO_STD(VIDIOC_S_JPEGCOMP, vidioc_s_jpegcomp, v4l_print_jpegcompression, INFO_FL_PRIO), // 设置JPEG压缩
    IOCTL_INFO_FNC(VIDIOC_QUERYSTD, v4l_querystd, v4l_print_std, 0), // 查询标准
    IOCTL_INFO_FNC(VIDIOC_TRY_FMT, v4l_try_fmt, v4l_print_format, 0), // 尝试格式
    IOCTL_INFO_STD(VIDIOC_ENUMAUDIO, vidioc_enumaudio, v4l_print_audio, INFO_FL_CLEAR(v4l2_audio, index)), // 枚举音频
    IOCTL_INFO_STD(VIDIOC_ENUMAUDOUT, vidioc_enumaudout, v4l_print_audioout, INFO_FL_CLEAR(v4l2_audioout, index)), // 枚举音频输出
    IOCTL_INFO_FNC(VIDIOC_G_PRIORITY, v4l_g_priority, v4l_print_u32, 0), // 获取优先级
    IOCTL_INFO_FNC(VIDIOC_S_PRIORITY, v4l_s_priority, v4l_print_u32, INFO_FL_PRIO), // 设置优先级
    IOCTL_INFO_FNC(VIDIOC_G_SLICED_VBI_CAP, v4l_g_sliced_vbi_cap, v4l_print_sliced_vbi_cap, INFO_FL_CLEAR(v4l2_sliced_vbi_cap, type)), // 获取切片VBI能力
    IOCTL_INFO_FNC(VIDIOC_LOG_STATUS, v4l_log_status, v4l_print_newline, 0), // 记录状态
    IOCTL_INFO_FNC(VIDIOC_G_EXT_CTRLS, v4l_g_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL), // 获取扩展控制
    IOCTL_INFO_FNC(VIDIOC_S_EXT_CTRLS, v4l_s_ext_ctrls, v4l_print_ext_controls, INFO_FL_PRIO | INFO_FL_CTRL), // 设置扩展控制
    IOCTL_INFO_FNC(VIDIOC_TRY_EXT_CTRLS, v4l_try_ext_ctrls, v4l_print_ext_controls, INFO_FL_CTRL), // 尝试扩展控制
    IOCTL_INFO_STD(VIDIOC_ENUM_FRAMESIZES, vidioc_enum_framesizes, v4l_print_frmsizeenum, INFO_FL_CLEAR(v4l2_frmsizeenum, pixel_format)), // 枚举帧大小
    IOCTL_INFO_STD(VIDIOC_ENUM_FRAMEINTERVALS, vidioc_enum_frameintervals, v4l_print_frmivalenum, INFO_FL_CLEAR(v4l2_frmivalenum, height)), // 枚举帧间隔
    IOCTL_INFO_STD(VIDIOC_G_ENC_INDEX, vidioc_g_enc_index, v4l_print_enc_idx, 0), // 获取编码器索引
    IOCTL_INFO_STD(VIDIOC_ENCODER_CMD, vidioc_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_PRIO | INFO_FL_CLEAR(v4l2_encoder_cmd, flags)), // 编码器命令
    IOCTL_INFO_STD(VIDIOC_TRY_ENCODER_CMD, vidioc_try_encoder_cmd, v4l_print_encoder_cmd, INFO_FL_CLEAR(v4l2_encoder_cmd, flags)), // 尝试编码器命令
    IOCTL_INFO_STD(VIDIOC_DECODER_CMD, vidioc_decoder_cmd, v4l_print_decoder_cmd, INFO_FL_PRIO), // 解码器命令
    IOCTL_INFO_STD(VIDIOC_TRY_DECODER_CMD, vidioc_try_decoder_cmd, v4l_print_decoder_cmd, 0), // 尝试解码器命令
    IOCTL_INFO_FNC(VIDIOC_DBG_S_REGISTER, v4l_dbg_s_register, v4l_print_dbg_register, 0), // 调试设置寄存器
    IOCTL_INFO_FNC(VIDIOC_DBG_G_REGISTER, v4l_dbg_g_register, v4l_print_dbg_register, 0), // 调试获取寄存器
    IOCTL_INFO_FNC(VIDIOC_DBG_G_CHIP_IDENT, v4l_dbg_g_chip_ident, v4l_print_dbg_chip_ident, 0), // 调试获取芯片标识
    IOCTL_INFO_FNC(VIDIOC_S_HW_FREQ_SEEK, v4l_s_hw_freq_seek, v4l_print_hw_freq_seek, INFO_FL_PRIO), // 设置硬件频率搜索
    IOCTL_INFO_STD(VIDIOC_S_DV_TIMINGS, vidioc_s_dv_timings, v4l_print_dv_timings, INFO_FL_PRIO), // 设置DV时序
    IOCTL_INFO_STD(VIDIOC_G_DV_TIMINGS, vidioc_g_dv_timings, v4l_print_dv_timings, 0), // 获取DV时序
    IOCTL_INFO_FNC(VIDIOC_DQEVENT, v4l_dqevent, v4l_print_event, 0), // 从事件队列中取出事件
    IOCTL_INFO_FNC(VIDIOC_SUBSCRIBE_EVENT, v4l_subscribe_event, v4l_print_event_subscription, 0), // 订阅事件
    IOCTL_INFO_FNC(VIDIOC_UNSUBSCRIBE_EVENT, v4l_unsubscribe_event, v4l_print_event_subscription, 0), // 取消订阅事件
    IOCTL_INFO_FNC(VIDIOC_CREATE_BUFS, v4l_create_bufs, v4l_print_create_buffers, INFO_FL_PRIO | INFO_FL_QUEUE), // 创建缓冲区
    IOCTL_INFO_FNC(VIDIOC_PREPARE_BUF, v4l_prepare_buf, v4l_print_buffer, INFO_FL_QUEUE), // 准备缓冲区
    IOCTL_INFO_STD(VIDIOC_ENUM_DV_TIMINGS, vidioc_enum_dv_timings, v4l_print_enum_dv_timings, 0), // 枚举DV时序
    IOCTL_INFO_STD(VIDIOC_QUERY_DV_TIMINGS, vidioc_query_dv_timings, v4l_print_dv_timings, 0), // 查询DV时序
    IOCTL_INFO_STD(VIDIOC_DV_TIMINGS_CAP, vidioc_dv_timings_cap, v4l_print_dv_timings_cap, INFO_FL_CLEAR(v4l2_dv_timings_cap, type)), // DV时序能力
    IOCTL_INFO_FNC(VIDIOC_ENUM_FREQ_BANDS, v4l_enum_freq_bands, v4l_print_freq_band, 0), // 枚举频段
    IOCTL_INFO_FNC(VIDIOC_DBG_G_CHIP_INFO, v4l_dbg_g_chip_info, v4l_print_dbg_chip_info, INFO_FL_CLEAR(v4l2_dbg_chip_info, match)), // 获取芯片信息
    IOCTL_INFO_FNC(VIDIOC_QUERY_EXT_CTRL, v4l_query_ext_ctrl, v4l_print_query_ext_ctrl, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_query_ext_ctrl, id)), // 查询扩展控制器
};

对于uvc驱动,调用uvc_ioctl_ops中的ioctl函数

// 定义V4L2的ioctl操作函数集合
const struct v4l2_ioctl_ops uvc_ioctl_ops = {
    .vidioc_querycap = uvc_ioctl_querycap, // 查询设备的能力
    .vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap, // 枚举视频捕获格式
    .vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out, // 枚举视频输出格式
    .vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap, // 获取视频捕获格式
    .vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out, // 获取视频输出格式
    .vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap, // 设置视频捕获格式
    .vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out, // 设置视频输出格式
    .vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap, // 尝试设置视频捕获格式
    .vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out, // 尝试设置视频输出格式
    .vidioc_reqbufs = uvc_ioctl_reqbufs, // 请求缓冲区
    .vidioc_querybuf = uvc_ioctl_querybuf, // 查询缓冲区
    .vidioc_qbuf = uvc_ioctl_qbuf, // 将缓冲区放入队列
    .vidioc_dqbuf = uvc_ioctl_dqbuf, // 从队列中取出缓冲区
    .vidioc_create_bufs = uvc_ioctl_create_bufs, // 创建缓冲区
    .vidioc_streamon = uvc_ioctl_streamon, // 开始视频流
    .vidioc_streamoff = uvc_ioctl_streamoff, // 停止视频流
    .vidioc_enum_input = uvc_ioctl_enum_input, // 枚举输入
    .vidioc_g_input = uvc_ioctl_g_input, // 获取输入
    .vidioc_s_input = uvc_ioctl_s_input, // 设置输入
    .vidioc_queryctrl = uvc_ioctl_queryctrl, // 查询控制器
    .vidioc_query_ext_ctrl = uvc_ioctl_query_ext_ctrl, // 查询扩展控制器
    .vidioc_g_ctrl = uvc_ioctl_g_ctrl, // 获取控制器
    .vidioc_s_ctrl = uvc_ioctl_s_ctrl, // 设置控制器
    .vidioc_g_ext_ctrls = uvc_ioctl_g_ext_ctrls, // 获取扩展控制器
    .vidioc_s_ext_ctrls = uvc_ioctl_s_ext_ctrls, // 设置扩展控制器
    .vidioc_try_ext_ctrls = uvc_ioctl_try_ext_ctrls, // 尝试设置扩展控制器
    .vidioc_querymenu = uvc_ioctl_querymenu, // 查询菜单
    .vidioc_g_selection = uvc_ioctl_g_selection, // 获取选择
    .vidioc_g_parm = uvc_ioctl_g_parm, // 获取参数
    .vidioc_s_parm = uvc_ioctl_s_parm, // 设置参数
    .vidioc_enum_framesizes = uvc_ioctl_enum_framesizes, // 枚举帧大小
    .vidioc_enum_frameintervals = uvc_ioctl_enum_frameintervals, // 枚举帧间隔
    .vidioc_subscribe_event = uvc_ioctl_subscribe_event, // 订阅事件
    .vidioc_unsubscribe_event = v4l2_event_unsubscribe, // 取消订阅事件
    .vidioc_default = uvc_ioctl_default, // 默认操作
};

video_usercopy

uvc_fops的unlocked_ioctl

long video_ioctl2(struct file *file,
           unsigned int cmd, unsigned long arg)
{
    return video_usercopy(file, cmd, arg, __video_do_ioctl);
}

video_usercopy

函数video_usercopy的作用是处理视频设备的用户空间和内核空间之间的数据传输。它通常在视频驱动程序中使用。

该函数的参数解释如下:

file:表示要操作的文件指针,通常是视频设备文件。

cmd:表示要执行的命令或操作,通常是一个特定的ioctl命令。

arg:表示传递给ioctl命令的参数,可以是一个指针或一个整数。

func:表示指向v4l2_kioctl函数的指针,用于执行实际的数据传输操作。

video_usercopy函数的一般调用流程如下:

用户空间的应用程序使用ioctl系统调用向视频设备文件发送命令(cmd)和参数(arg)。

在内核空间中,视频驱动程序的ioctl方法被调用,并根据传入的cmd确定需要执行的操作。

如果cmd对应于需要进行数据传输的操作,驱动程序将调用

video_usercopy函数。

video_usercopy函数将执行必要的数据传输操作,将数据从用户空间复制到内核空间,或从内核空间复制到用户空间,具体取决于操作的需求。

video_usercopy函数中的

func指针将被用于执行实际的数据传输操作。

总的来说,video_usercopy函数用于在视频驱动程序中处理用户空间和内核空间之间的数据传输,确保正确地将数据从应用程序传递到设备驱动程序,或从设备驱动程序传递回应用程序。

long
video_usercopy(struct file *file, unsigned int cmd, unsigned long arg,
           v4l2_kioctl func)
{
    char    sbuf[128]; // 用于存储较小的参数
    void    *mbuf = NULL; // 用于存储较大的参数
    void    *parg = (void *)arg; // 参数指针
    long    err  = -EINVAL; // 函数执行结果
    bool    has_array_args; // 是否有数组参数
    size_t  array_size = 0; // 数组参数的大小
    void __user *user_ptr = NULL; // 用户空间指针
    void    **kernel_ptr = NULL; // 内核空间指针
    /* 从用户空间复制参数到内核空间 */
    if (_IOC_DIR(cmd) != _IOC_NONE) {
        if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { // 参数较小,使用sbuf存储
            parg = sbuf;
        } else { // 参数较大,使用mbuf存储
            mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
            if (NULL == mbuf)
                return -ENOMEM;
            parg = mbuf;
        }
        err = -EFAULT;
        if (_IOC_DIR(cmd) & _IOC_WRITE) { // 写操作
            unsigned int n = _IOC_SIZE(cmd);
            /*
             * 在某些情况下,只有少数字段用作输入,
             * 例如当应用程序设置“index”时,驱动程序将为具有该索引的事物填充其余结构。
             * 我们只需要复制第一个非输入字段。
             */
            if (v4l2_is_known_ioctl(cmd)) {
                u32 flags = v4l2_ioctls[_IOC_NR(cmd)].flags;
                if (flags & INFO_FL_CLEAR_MASK)
                    n = (flags & INFO_FL_CLEAR_MASK) >> 16;
            }
            if (copy_from_user(parg, (void __user *)arg, n))
                goto out;
            /* 将未从用户空间复制的内容清零 */
            if (n < _IOC_SIZE(cmd))
                memset((u8 *)parg + n, 0, _IOC_SIZE(cmd) - n);
        } else { // 读操作
            memset(parg, 0, _IOC_SIZE(cmd));
        }
    }
    // 检查是否有数组参数
    err = check_array_args(cmd, parg, &array_size, &user_ptr, &kernel_ptr);
    if (err < 0)
        goto out;
    has_array_args = err;
    if (has_array_args) {
        /*
         * 当添加新类型的数组参数时,请确保ioctl的父参数(其中包含指向数组的指针)适合于sbuf(以便mbuf仍然保持未使用状态)。
         */
        mbuf = kmalloc(array_size, GFP_KERNEL);
        err = -ENOMEM;
        if (NULL == mbuf)
            goto out_array_args;
        err = -EFAULT;
        if (copy_from_user(mbuf, user_ptr, array_size))
            goto out_array_args;
        *kernel_ptr = mbuf;
    }
    /* 处理IOCTL */
    err = func(file, cmd, parg);
    if (err == -ENOIOCTLCMD)
        err = -ENOTTY;
    if (err == 0) {
        if (cmd == VIDIOC_DQBUF)
            trace_v4l2_dqbuf(video_devdata(file)->minor, parg);
        else if (cmd == VIDIOC_QBUF)
            trace_v4l2_qbuf(video_devdata(file)->minor, parg);
    }
    // 如果有数组参数
    if (has_array_args) {
        *kernel_ptr = (void __force *)user_ptr; // 将内核空间指针指向用户空间指针
        if (copy_to_user(user_ptr, mbuf, array_size)) // 将内核空间的数组参数复制回用户空间
            err = -EFAULT;
        goto out_array_args;
    }
    /* VIDIOC_QUERY_DV_TIMINGS 可能返回错误,但仍然必须返回有效结果。*/
    if (err < 0 && cmd != VIDIOC_QUERY_DV_TIMINGS)
        goto out;
out_array_args:
    /* 将结果复制回用户空间 */
    switch (_IOC_DIR(cmd)) {
    case _IOC_READ:
    case (_IOC_WRITE | _IOC_READ):
        if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
            err = -EFAULT;
        break;
    }
out:
    kfree(mbuf); // 释放内存
    return err;
}
EXPORT_SYMBOL(video_usercopy);

这个函数的作用是处理视频设备的用户空间和内核空间之间的数据传输,并执行相应的ioctl命令。

该函数的主要步骤和功能概述如下:

定义了一些变量和指针用于存储参数和结果,如sbuf用于存储较小的参数,mbuf用于存储较大的参数,parg为参数指针,err为函数执行结果,has_array_args表示是否有数组参数等。

通过copy_from_user将用户空间的参数复制到内核空间,确保数据传输的正确性。

检查是否有数组参数,并进行相关处理。

调用传入的func函数执行实际的ioctl操作,将参数传递给相应的处理函数。

处理ioctl的返回结果,如果成功执行,可能会执行一些跟踪操作。

如果存在数组参数,将内核空间的数组参数复制回用户空间。

最后,根据ioctl的方向,将结果复制回用户空间。

释放申请的内存空间。

总的来说,这个函数主要负责处理视频设备的用户空间和内核空间之间的数据传输,以及执行相应的ioctl命令,并确保数据的正确性和完整性。

如果文章对您有帮助,点赞👍支持,感谢🤝


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
目录
相关文章
|
存储 缓存 索引
uvc驱动ioctl分析下
uvc驱动ioctl分析下
230 0
|
存储 缓存 流计算
uvc驱动ioctl分析上
uvc驱动ioctl分析上
161 0
|
监控 Linux
uvc摄像头驱动uvc设备的注册分析
uvc摄像头驱动uvc设备的注册分析
392 0
|
XML 测试技术 网络安全
开发工具:USB转IIC/I2C/SPI/UART适配器模块可编程开发板
总的思路是通过USB或者UART接口发送一些协议字符串,由模块转换成上面几种接口的硬件时序电信号,实现与这几种接口芯片、设备的快速测试。 首先声明一下,大家都是搞硬件开发的,这几种接口当然是很简单的事,但有些时候对于一个新的设备或者芯片的测试,有个现成的工具当然更顺手,节省时间,也更可靠嘛。
|
缓存 Linux 芯片
Linux驱动分析之Uart驱动
之前对Uart驱动的整体架构做了介绍,现在来分析具体的驱动程序。我们以NXP 的 IMX6来进行分析。
|
XML 传感器 测试技术
开发调试工具:USB转IIC/I2C/SPI/UART适配器模块可编程开发板
发个方便测试I2C、SPI、1Wire接口的工具模块 总的思路是通过USB或者UART接口发送一些协议字符串,由模块转换成上面几种接口的硬件时序电信号,实现与这几种接口芯片、设备的快速测试。 首先声明一下,大家都是搞硬件开发的,这几种接口当然是很简单的事,但有些时候对于一个新的设备或者芯片的测试,有个现成的工具当然更顺手,节省时间,也更可靠嘛。
开发调试工具:USB转IIC/I2C/SPI/UART适配器模块可编程开发板