【嵌入式Linux驱动开发 | V4L2】MIPI摄像头驱动程序分析(一)
嵌入式Linux驱动开发,MIPI摄像头驱动程序简单分析
简介
- 本文系统使用: 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结构体:
- 里面一个结构体成员尤为重要
v4l2_subdev_ops,该结构体里有各类ops结构体,比如core、tuner、audio、video等等。编写subdev驱动程序时,核心就是实现各类ops结构体的函数,也可选择性提供。 - 在驱动程序中,一般将
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_entity有media_pad,可以认为是端点(不能简单认为是硬件引脚),有source pad(输出数据),sink pad(输入数据)media_entity之间的连接被称为media_linkmedia_link仅仅表示两个media_entity之间的连接,要构成一个完整的数据通道,需要多个系列的连接,这被称为pipelinemedia子系统的作用就在于管理"拓扑关系",就是各个对象的连接关系。
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->next和new->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表示注册三个子设备)
调用过程:
- 创建一个
video_device结构体,并分配空间
struct video_device *vdev;
vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
- 设置
video_device中的私有数据
video_set_drvdata(vdev, sd);
->dev_set_drvdata(&vdev->dev, data);
->dev->driver_data = data;
- 提供一个中转的函数
vdev->fops = &v4l2_subdev_fops;
v4l2_subdev_fops里的函数会调用到v4l2_subdev里面的各种操作函数
- 注册字符设备
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放入mdev的entity链表里 - 第二个的作用:把
entity的每个pad,放入mdev的pad链表里
- 第一个的作用:把
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_device (media_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_IOC和MEDIA_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
被用来枚举 enity 的 link:
(位于:kernel/drivers/media/media-device.c)
说明1:
- 根据APP传入的参数找到
entity - 把
entity的pad信息复制给APP - 把
link的信息复制给APP
4. MEDIA_IOC_SETUP_LINK
用来设置link,把源pad、目的pad之间的连接激活(active),或者闲置(inactive)
(位于:kernel/drivers/media/media-device.c)
说明1:
- 根据APP传入的参数找到源 entity、目的 entity
- 找到
link - 设置它的状态
- 设置
link前,通知media系统 - 调用两段
entity的link_setup函数 - 设置
link后,通知media系统
- 设置
5. MEDIA_IOC_G_TOPOLOGY
用来获得整体的拓扑图,包括 entities、interfaces、pads、links 的信息:
(位于: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;
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐




所有评论(0)