简介

  • 本文系统使用: Ubuntu 20.04.6 LTS
  • Linux 内核版本为:5.15.0-136-generic
  • 嵌入式Linux学习交流群:1005210698
  • 本文相关源码以及更多免费资料可加群获取

一、subdev子系统

1.1 概述

  • 在 Linux 嵌入式系统中,​V4L2 Subdev 子系统是管理复杂视频采集设备的核心机制,尤其适用于摄像头、视频编解码器等多模块硬件。
  • 其核心思想是将功能复杂的设备(如 RK3566 开发板上的 OV5695 传感器+ISP 处理器+H.264 编码器)拆分为独立子设备(Subdevice),每个子设备负责特定功能(如传感器捕获原始数据、ISP 调整色彩与噪声、编码器压缩视频流),并通过主设备(如 /dev/video0)统一协调数据流和参数控制。
  • 这种分层设计实现了模块化管理功能解耦动态扩展性,简化了驱动开发和系统集成。例如,在 Rockchip 平台上,设备树通过 v4l2-subdev 节点声明子设备(如传感器、ISP),主设备通过 ioctl 调用子设备的参数(如曝光时间、编码码率),并管理缓冲区流转,形成高效的视频处理流水线。

在这里插入图片描述


1.2 结构体分析

v4l2_subdev结构体
  • 位于kernel/include/media/v4l2-subdev.h
  • Linux V4L2 框架中的核心子设备管理结构体,主要用于表示和管理视频采集链中的硬件模块(如传感器、ISP、编码器等)
struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
	struct media_entity entity;      // 媒体实体指针(指向 &struct media_entity)
#endif
	struct list_head list;           // 链表头,用于子设备管理
	struct module *owner;            // 模块所有权指针
	bool owner_v4l2_dev;             // 是否属于V4L2设备标志
	u32 flags;                       // 子设备状态位掩码
	struct v4l2_device *v4l2_dev;    // 父V4L2设备指针
	const struct v4l2_subdev_ops *ops;        // 子设备操作函数表(如ioctl处理)
	const struct v4l2_subdev_internal_ops *internal_ops;  // 内部回调函数表
	struct v4l2_ctrl_handler *ctrl_handler;   // 控制参数处理器
	char name[V4L2_SUBDEV_NAME_SIZE];         // 子设备名称(调试标识)
	u32 grp_id;                      // 子设备组ID(分组管理)
	void *dev_priv;				     // 子设备私有数据
	void *host_priv;			     // 主机私有数据
	struct video_device *devnode;    // 视频设备节点(如/dev/video0/subdev/0)
	struct device *dev;              // Linux设备号
	struct fwnode_handle *fwnode;	 // 固件节点句柄
	struct list_head async_list;	 // 异步操作队列
	struct v4l2_async_subdev *asd;   // 异步子设备描述符
	struct v4l2_async_notifier *notifier;     // 事件通知器
	struct v4l2_async_notifier *subdev_notifier;          // 子设备通知器
	struct v4l2_subdev_platform_data *pdata;			  // 平台特定配置数据
};
v4l2_subdev_ops结构体:
  • 位于kernel/include/media/v4l2-subdev.h
  • Linux V4L2 框架中用于定义子设备操作接口的核心结构体,它通过功能分片的方式将硬件模块的复杂行为抽象为可扩展的标准化接口
struct v4l2_subdev_ops {
	const struct v4l2_subdev_core_ops	*core;      // 核心操作(初始化/资源管理)
	const struct v4l2_subdev_tuner_ops	*tuner;     // 调谐器操作(频率控制)
	const struct v4l2_subdev_audio_ops	*audio;     // 音频处理操作
	const struct v4l2_subdev_video_ops	*video;     // 视频流操作
	const struct v4l2_subdev_vbi_ops	*vbi;       // VBI(垂直消隐期)数据操作
	const struct v4l2_subdev_ir_ops		*ir;        // 红外遥控操作
	const struct v4l2_subdev_sensor_ops	*sensor;    // 传感器操作(如曝光、白平衡)
	const struct v4l2_subdev_pad_ops	*pad;       // 视频端口操作(多路复用)
};

说明1:v4l2_subdev结构体:

  1. 里面一个结构体成员尤为重要 v4l2_subdev_ops,该结构体里有各类 ops 结构体,比如 coretuneraudiovideo 等等。编写 subdev 驱动程序时,核心就是实现各类 ops 结构体的函数,也可选择性提供。
  2. 在驱动程序中,一般将 v4l2_sbudev 结构体嵌入在更大的结构体中,与更多设备状态信息保存在一起。也可以单独代表一个简单的子设备,
    • kernel/drivers/media/platform/rockchip/cif/mipi-csi2.c文件下的驱动可以看到
      在这里插入图片描述
v4l2_device结构体
  • kernel/include/media/v4l2-device.h
  • Linux V4L2 框架的顶层管理结构体,负责协调视频采集设备的整体运作,包括子设备管理、资源分配、用户空间交互及电源控制
struct v4l2_device {
	struct device *dev;                     // Linux字符设备关联
	struct media_device *mdev;              // 媒体控制器实例
	struct list_head subdevs;               // 子设备链表
	spinlock_t lock;                        // 自旋锁(并发保护)
	char name[V4L2_DEVICE_NAME_SIZE];       // 设备名称(调试标识)
	void (*notify)(struct v4l2_subdev *sd,  // 事件通知回调
			unsigned int notification, void *arg);
	struct v4l2_ctrl_handler *ctrl_handler; // 控制参数处理器
	struct v4l2_prio_state prio;            // 优先级状态(内部机制)
	struct kref ref;                        // 引用计数器(生命周期管理)
	void (*release)(struct v4l2_device *v4l2_dev); // 释放函数(资源清理)
};

说明2:如何管理多个v4l2_subdev

  • 将多个 v4l2_subdev 放入 v4l2_device 结构体中的结构体成员 list_head 的链表里,进行统一管理
  • v4l2_subdev 结构体中,media_entity 的作用就是将当前的 v4l2_subdev 和其他的 v4l2_subdev 建立联系

二、media子系统

2.1 概述

  • media 子系统是现代计算设备中负责高效处理、管理和传输多媒体数据的核心架构,涵盖硬件与软件的协同设计。
  • 其核心功能包括音视频编解码(如H.265/AV1)、图像渲染、传感器数据处理及网络流媒体传输,通过专用硬件加速单元(如GPU、DSP、NPU)实现高性能计算,结合开放式软件框架(如FFmpeg、GStreamer)支持动态协议适配。系统采用模块化设计,具备低延迟处理、高吞吐量特性,并集成AI推理引擎以增强内容分析能力(如人脸识别、语音识别)。
  • 典型应用场景包括AR/VR交互、8K视频编辑、智能安防监控及云游戏传输,通过标准化接口(如DP Alt Mode、HDMI 2.1)实现跨设备无缝协作,同时支持端到端加密与DRM版权保护,成为智能化终端与物联网生态的关键基础设施。

在这里插入图片描述

说明1:在 media 子系统里

  • 每个对象都是一个 media_entity
  • media_entitymedia_pad ,可以认为是端点(不能简单认为是硬件引脚),有 source pad(输出数据),sink pad(输入数据)
  • media_entity 之间的连接被称为 media_link
  • media_link 仅仅表示两个 media_entity 之间的连接,要构成一个完整的数据通道,需要多个系列的连接,这被称为 pipeline
  • media 子系统的作用就在于管理"拓扑关系",就是各个对象的连接关系。

2.2 结构体分析

media_device结构体
  • 位于kernel/include/media/media-device.h
  • Linux 媒体框架的核心管理结构体,负责协调多媒体设备的拓扑结构、资源管理和 power 状态
struct media_device {
	struct device *dev;                     // 关联的Linux字符设备
	struct media_devnode *devnode;          // 媒体设备节点(如/dev/media0)
	char model[32];                         // 设备型号(如"Rockchip CSI2 ISP")
	char driver_name[32];                   // 驱动名称(如"rk-media")
	char serial[40];                        // 序列号(用于设备唯一标识)
	char bus_info[32];                      // 总线信息(如"I2C-1:00")
	u32 hw_revision;                        // 硬件修订版本
	u64 topology_version;                   // 拓扑版本号(用于兼容性管理)
	u32 id;                                 // 设备唯一ID(自动生成)
	struct ida entity_internal_idx;         // 实体内部索引管理
	int entity_internal_idx_max;            // 最大实体索引
	struct list_head entities;              // 所有媒体实体链表
	struct list_head interfaces;            // 接口链表(如MJPEG、HDMI)
	struct list_head pads;                  // 垫(Pad)链表(输入/输出端点)
	struct list_head links;                 // 连接链表(实体间的媒体流路径)
	struct list_head entity_notify;         // 实体注册通知回调列表
	struct mutex graph_mutex;               // 媒体图操作互斥锁
	struct media_graph pm_count_walk;       // 电源管理计数器遍历结构
	void *source_priv;                      // 源私有数据(如传感器配置)
	int (*enable_source)(struct media_entity *entity,   // 启用媒体源
			     struct media_pipeline *pipe); 
	void (*disable_source)(struct media_entity *entity); // 禁用媒体源
	const struct media_device_ops *ops;     // 媒体设备操作集(如注册实体、枚举接口)
};

三、subdev注册与使用

subdev 中含有模块的操作函数,有两种路径调用:

  • 内核调用:subdev 完全可以不暴露给用户,在摄像头驱动程序内部"偷偷地"调用 subdev 的函数,用户感觉不到 subdev 的存在。
  • APP调用:对于比较复杂的硬件,驱动程序应该"让用户有办法调节各类参数",比如 ISP 模块几乎都是闭源的,对它的设置只能通过 APP 进行。这类 subdev 的函数,应该暴露给用户,让用户可以调用它们。

3.1 内核注册过程

1. v4l2_device_register_subdev()

调用 v4l2_device_register_subdev:函数,把 subdev 放入 v4l2_device 的链表

函数原型:

  • 位于:kernel/drivers/media/v4l2-core/v4l2-device.c
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,struct v4l2_subdev *sd)

函数作用:

  • v4l2_subdev(子设备)注册到 v4l2_device(父设备)中,使其成为父设备管理的子模块
  • 此函数是V4L2 框架中子设备与父设备关联的核心接口

参数说明:

  • v4l2_dev :已初始化的父设备实例(如 /dev/video0 对应的设备结构体)
  • sd :待注册的子设备实例(如 OV5695 传感器或 CSI 接口结构体)

说明1:核心代码:

list_add_tail(&sd->list, &v4l2_dev->subdevs);

是真正负责将子设备动态添加到父设备的子设备链表中

函数原型:

static inline void list_add_tail(struct list_head *new, struct list_head *head);

函数作用:

  • new 指向的链表节点动态添加到以 head 为头节点的链表末尾,使其成为新的最后一个节点
  • 此函数是 Linux 内核链表操作的核心接口之一,用于维护动态链表结构

参数说明:

  • new:待插入的链表节点(必须已初始化,且 new->nextnew->prev 未设置)
  • head:链表的头节点(可能为空链表)

2. v4l2_device_register_subdev_nodes()

遍历 v4l2_device 链表里各个 subdev ,如果它想暴露给 APP ,就把它注册为普通字符设备

函数原型:

  • 位于 kernel/drivers/media/v4l2-core/v4l2-device.c
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev, 
                                      struct v4l2_subdev**subdevs, 
                                      unsigned int count);

函数作用:

  • 用于批量注册子设备的函数,其作用可概括为: 将一组子设备(如传感器、ISP、编码器)一次性关联到父 V4L2 设备中,简化驱动开发中的链表管理和注册逻辑

参数说明:

  • v4l2_dev:已初始化的父设备实例(如 /dev/video0 对应的设备结构体)
  • subdevs:子设备指针数组(如 &ov5695_sd, &isp_sd, &vpu_sd
  • count:子设备数量(如 3 表示注册三个子设备)

调用过程:

  1. 创建一个 video_device 结构体,并分配空间
struct video_device *vdev;
vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
  1. 设置 video_device 中的私有数据
video_set_drvdata(vdev, sd);
	->dev_set_drvdata(&vdev->dev, data);
		->dev->driver_data = data;
  1. 提供一个中转的函数
vdev->fops = &v4l2_subdev_fops;

v4l2_subdev_fops 里的函数会调用到 v4l2_subdev 里面的各种操作函数

  1. 注册字符设备
err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1, sd->owner);

函数原型:

int __video_register_device(struct v4l2_device *v4l2_dev, unsigned int minor, 
                            const struct file_operations *fops, void *priv);

参数说明:

  • v4l2_dev:已初始化的 V4L2 设备结构体(如 RK3566 的 csi0 设备)
  • minor:请求的设备号(如 0),框架会自动分配可用号
  • fops:文件操作函数集(如 v4l2_fops),定义用户空间如何操作设备
  • priv:私有数据指针(如驱动特定的上下文),可通过 file->private_data 访问

说明1:

  • 该函数会通过传进来的设备号,自动选择对应对设备节点名字(生成 /dev/v4l2-dubdevX 子节点)
    在这里插入图片描述

  • 之后会调用 cdev_add 来注册设备节点
    在这里插入图片描述


3. 注册后的内核结构

在这里插入图片描述

  • 对于每个模块,都会构造一个 v4l2_subdev 结构体
  • 多个 v4l2_subdev 则通过 v4l2_device 里的 list_head 链表链接起来
  • 在每个 v4l2_subdev 里又包含了 v4l2_subdev_ops
  • 如果 v4l2_subdev 愿意将 v4l2_subdev_ops 里的操作函数暴露给应用程序,则有一个注册过程
    • 对于愿意暴露的 v4l2_subdev ,会为它构造一个 video_device ,而 video_device 里的私有数据device 会指向 v4l2_subdev
    • video_device 里会有一个 cdev 指向 file_operations ,而这个 file_operations 会暴露给应用程序
    • 应用程序就会通过这个 file_operations 中转来 video_device ,调用里面的 v4l2_file_operations ,而 v4l2_file_operations 最终指向 v4l2_subdev_fops
    • v4l2_subdev_fops 里面的这些函数最终会使用到 video_device 里的私有数据 device ,进而找到 v4l2_subdev ,调用到里面的各种操作函数

3.2 使用subdev

1. 内核态使用subdev

可以直接调用 subdev 里的操作函数,也可以使用下面的宏:(以这个函数为例)
在这里插入图片描述

宏定义结构原型:

  • 位于 kernel/drivers/media/usb/em28xx/em28xx-camera.c
#define v4l2_subdev_call(sd, o, f, args...)             \
    ({                              \
        int __result;                       \
        if (!(sd))                      \
            __result = -ENODEV;             \
        else if (!((sd)->ops->o && (sd)->ops->o->f))        \
            __result = -ENOIOCTLCMD;            \
        else                            \
            __result = (sd)->ops->o->f((sd), ##args);   \
        __result;                       \
    })

说明1:

  • 会通过 (sd)->ops->o 去判断 subdev 里的 v4l2_subdev_ops 里的 v4l2_subdev_pad_ops 存不存在

    • 存在会通过 (sd)->ops->o->f 去查看 set_fmt 函数是否提供
  • (sd)->ops->o->f) 最终指向 ov2640_set_fmt 函数 (位于 kernel/drivers/media/i2c/ov2640.c)
    在这里插入图片描述

  • 准确来说也是通过应用程序触发的,应用程序通过某个 ioctl 来触发


2. 用户态调用subdev

APP对 /dev/v4l-subdev 这类的设备进行 ioctl 操作时,内核里流程如下:

APP:    ioctl(fd, cmd, arg);
-------------------------------------
Kernel:
	v4l2_fops.unlocked_ioctl, 即 v4l2_ioctl
	ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
		v4l2_subdev_fops.unlocked_ioctl, 即subdev_ioctl
			video_usercopy(file, cmd, arg, subdev_do_ioctl);

在这里插入图片描述


四、media注册与使用

4.1 两个层次

media 子系统的注册分为2个层次:

  • 底层:描述自己的 media_entity。各个 subdev 里含有 media_entity,但是多个 media_entity之间的关系由更上层的驱动决定
  • 上层:描述 media_entity 之间的联系。更上层的、统筹的驱动。它知道各个 subdev 即各个 media_entity 之间的联系:link

4.2 四个步骤

1. 描述自己

各个底层驱动构造 subdev 时,顺便初始里面的 media_entity ,比如这个 media_entity 有哪些 pad
在这里插入图片描述

函数原型:(位于 kernel/include/media/media-entity.h

int media_entity_pads_init(struct media_entity *entity, unsigned int num_pads, 
						  const struct media_pad *pads);

函数作用:

  • 用于初始化媒体实体(media_entity)的 pads(数据端点)

参数说明:

  • entity:指向要初始化的媒体实体结构体
  • num_pads:指定媒体实体包含的 pad 数量
  • pads:指向存储 pad 属性的数组。

说明1:

  • 当指定了n个 Pad,就会提供n个 media_pad 的信息

说明2:media_pad 结构体

struct media_pad {
    struct media_gobj graph_obj;    // 包含媒体对象通用数据的嵌入式结构(必须为第一个字段)
    struct media_entity *entity;    // 所属媒体实体
    u16 index;                      // Pad 在实体中的索引
    unsigned long flags;            // Pad 的标志和方向
};

其中 flags 常用标志值:

  • MEDIA_PAD_FL_SOURCE:垫是媒体管道的起点​(如传感器输出垫)
  • MEDIA_PAD_FL_SINK:垫是媒体管道的终点​(如编码器输入垫)
  • MEDIA_PAD_FL_MUST_BE_LAST:该垫必须是管道中的最后一个节点
  • MEDIA_PAD_TYPE_VIDEO_CAPTURE:垫类型为视频捕获(如传感器输出)
  • MEDIA_PAD_TYPE_VIDEO_OUTPUT:垫类型为视频输出(如显示器输入)

2. 注册自己

底层或上层注册 subdev 时,顺便注册 media_entity(把 media_entity 记录在 media_device 里)
在这里插入图片描述

函数原型:(位于 kernel/drivers/media/media-device.c)

int media_device_register_entity(struct media_device *mdev, 
                                struct media_entity *entity, 
                                u32 flags);

参数说明:

  • mdev:父媒体设备对象(由 media_device_register() 注册的顶层设备)
  • entity:要注册的媒体实体对象(需提前初始化)
  • flags:标志位掩码,用于指定实体的特性(如 MEDIA_ENTITY_FLAG_SOURCE 表示输入实体)

说明1:

  • 需要定义 CONFIG_MEDIA_CONTROLLER 才可以注册

  • media_device_register_entity 函数里有两个 media_gobj_create 函数
    在这里插入图片描述

    • 第一个的作用:把 entity 放入 mdeventity 链表里
    • 第二个的作用:把 entity 的每个 pad,放入 mdevpad 链表里

3. 和别人建立联系

subdev 之上的驱动程序决定各个 media_entity 如何连接**(比如调用 media_create_pad_link 创建连接)

函数原型:

int media_create_pad_link(struct media_entity *source, u16 source_pad, 
                         struct media_entity *sink, u16 sink_pad, 
                         u32 flags);

参数说明:

  • source:链接的起点(如摄像头传感器)。必须已通过 media_device_register_entity() 注册
  • source_pad:源实体中用于输出的垫编号(从 0 开始)。例如,传感器的视频输出垫
  • sink:链接的终点(如图像处理器)。必须已注册
  • sink_pad:汇实体中用于输入的垫编号(如处理器的 raw 图像输入垫)
  • flags:控制链接行为的位掩码。常用值:
    • MEDIA_LINK_FLAG_ACTIVE:自动激活链接。
    • MEDIA_LINK_FLAG_EXTERNAL:标记为外部链接(如 USB 设备)

4. 暴露给APP使用

subdev 之上的驱动程序注册 media_devicemedia_device 里已经汇聚了所有的media_entity

函数原型:

int media_device_register(struct media_device *mdev);

参数说明:

  • mdev:必须为已初始化的 media_device 结构体指针(该结构体代表顶层媒体设备)

4.3 用户态调用media

APP使用 media 子系统时,除了 open 之外,就只涉及5个 ioctl
(位于 kernel/drivers/media/media-device.c)
在这里插入图片描述

  • 需要加上前缀 MEDIA_IOC_

APP对/dev/media0这类的设备进行ioctl操作时,内核里流程如下:

App:   ioctl
---------------------------------------------------------
kernel: 
media_devnode_fops.unlocked_ioctl, 即 media_ioctl
	-> __media_ioctl(filp, cmd, arg, devnode->fops->ioctl);
		-> media_device_ioctl(struct file *filp, unsigned int cmd, unsigned long __arg)
				const struct media_ioctl_info *info;
				info = &ioctl_info[_IOC_NR(cmd)];
				ret = info->arg_from_user(karg, arg, cmd);
				ret = info->fn(dev, karg);
				ret = info->arg_to_user(arg, karg, cmd);

核心在于 ioctl_info
在这里插入图片描述

说明1:

  • 根据两个宏 MEDIA_IOCMEDIA_IOC_ARG,将 ioctl_info 展开后得
    (以 MEDIA_IOC(DEVICE_INFO, media_device_get_info, MEDIA_IOC_FL_GRAPH_MUTEX), 为例)
{MEDIA_IOC_DEVICE_INFO, media_device_get_info, MEDIA_IOC_FL_GRAPH_MUTEX,
copy_arg_from_user, copy_arg_to_user}

1. MEDIA_IOC_DEVICE_INFO

被用来获得 /dev/mediaX 的设备信息,设备信息结构体如下:

struct media_device_info {
    char driver[16];         // 驱动名称
    char model[32];          // 设备型号
    char serial[40];         // 唯一硬件标识符
    char bus_info[32];       // 总线地址(如 "PCI:1:0:0" 或 "USB:1-1.2")
    __u32 media_version;     // 媒体框架版本
    __u32 hw_revision;       // 硬件修订号
    __u32 driver_version;    // 驱动版本号
    __u32 reserved[31];      // 未来扩展预留
};

驱动中对应核心代码如下: (kernel/drivers/media/media-device.c)
在这里插入图片描述

说明1:

  • 会把内核里面的 media 的信息拷贝进某个内核的对象,之后再拷贝进用户空间

2. MEDIA_IOC_ENUM_ENTITIES

被用来获得指定 entity 的信息:
(位于:kernel/drivers/media/media-device.c)
在这里插入图片描述

说明1:

  • 根据APP传入的 id 找到 entity
  • 返回找到的 entity 的信息

3. MEDIA_IOC_ENUM_LINKS

被用来枚举 enitylink
(位于:kernel/drivers/media/media-device.c)
在这里插入图片描述

说明1:

  • 根据APP传入的参数找到 entity
  • entitypad 信息复制给APP
  • link 的信息复制给APP

4. MEDIA_IOC_SETUP_LINK

用来设置link,把源pad、目的pad之间的连接激活(active),或者闲置(inactive)
(位于:kernel/drivers/media/media-device.c)
在这里插入图片描述

说明1:

  1. 根据APP传入的参数找到源 entity目的 entity
  2. 找到 link
  3. 设置它的状态
    1. 设置 link 前,通知 media 系统
    2. 调用两段 entitylink_setup 函数
    3. 设置 link 后,通知 media 系统

5. MEDIA_IOC_G_TOPOLOGY

用来获得整体的拓扑图,包括 entitiesinterfacespadslinks 的信息:
(位于:kernel/drivers/media/media-device.c)

static long media_device_get_topology(struct media_device *mdev, void *arg){
	struct media_v2_topology *topo = arg;
	struct media_entity *entity;
	struct media_interface *intf;
	struct media_pad *pad;
	struct media_link *link;
	struct media_v2_entity kentity, __user *uentity;
	struct media_v2_interface kintf, __user *uintf;
	struct media_v2_pad kpad, __user *upad;
	struct media_v2_link klink, __user *ulink;
	unsigned int i;
	int ret = 0;

	topo->topology_version = mdev->topology_version;

	/* Get entities and number of entities */
	i = 0;
	uentity = media_get_uptr(topo->ptr_entities);
	media_device_for_each_entity(entity, mdev) {
		i++;
		if (ret || !uentity)
			continue;

		if (i > topo->num_entities) {
			ret = -ENOSPC;
			continue;
		}

		/* Copy fields to userspace struct if not error */
		memset(&kentity, 0, sizeof(kentity));
		kentity.id = entity->graph_obj.id;
		kentity.function = entity->function;
		kentity.flags = entity->flags;
		strlcpy(kentity.name, entity->name,
			sizeof(kentity.name));

		if (copy_to_user(uentity, &kentity, sizeof(kentity)))
			ret = -EFAULT;
		uentity++;
	}
	topo->num_entities = i;
	topo->reserved1 = 0;

	/* Get interfaces and number of interfaces */
	i = 0;
	uintf = media_get_uptr(topo->ptr_interfaces);
	media_device_for_each_intf(intf, mdev) {
		i++;
		if (ret || !uintf)
			continue;

		if (i > topo->num_interfaces) {
			ret = -ENOSPC;
			continue;
		}

		memset(&kintf, 0, sizeof(kintf));

		/* Copy intf fields to userspace struct */
		kintf.id = intf->graph_obj.id;
		kintf.intf_type = intf->type;
		kintf.flags = intf->flags;

		if (media_type(&intf->graph_obj) == MEDIA_GRAPH_INTF_DEVNODE) {
			struct media_intf_devnode *devnode;

			devnode = intf_to_devnode(intf);

			kintf.devnode.major = devnode->major;
			kintf.devnode.minor = devnode->minor;
		}

		if (copy_to_user(uintf, &kintf, sizeof(kintf)))
			ret = -EFAULT;
		uintf++;
	}
	topo->num_interfaces = i;
	topo->reserved2 = 0;

	/* Get pads and number of pads */
	i = 0;
	upad = media_get_uptr(topo->ptr_pads);
	media_device_for_each_pad(pad, mdev) {
		i++;
		if (ret || !upad)
			continue;

		if (i > topo->num_pads) {
			ret = -ENOSPC;
			continue;
		}

		memset(&kpad, 0, sizeof(kpad));

		/* Copy pad fields to userspace struct */
		kpad.id = pad->graph_obj.id;
		kpad.entity_id = pad->entity->graph_obj.id;
		kpad.flags = pad->flags;
		kpad.index = pad->index;

		if (copy_to_user(upad, &kpad, sizeof(kpad)))
			ret = -EFAULT;
		upad++;
	}
	topo->num_pads = i;
	topo->reserved3 = 0;

	/* Get links and number of links */
	i = 0;
	ulink = media_get_uptr(topo->ptr_links);
	media_device_for_each_link(link, mdev) {
		if (link->is_backlink)
			continue;

		i++;

		if (ret || !ulink)
			continue;

		if (i > topo->num_links) {
			ret = -ENOSPC;
			continue;
		}

		memset(&klink, 0, sizeof(klink));

		/* Copy link fields to userspace struct */
		klink.id = link->graph_obj.id;
		klink.source_id = link->gobj0->id;
		klink.sink_id = link->gobj1->id;
		klink.flags = link->flags;

		if (copy_to_user(ulink, &klink, sizeof(klink)))
			ret = -EFAULT;
		ulink++;
	}
	topo->num_links = i;
	topo->reserved4 = 0;

	return ret;
}
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐