Linux图形子系统之dma-buf

dma-buf是Linux内核提供的跨模块内存共享机制,解耦了生产者和消费者。文章详细介绍了如何通过DRM框架的ioctl接口实现buf的导出和导入,涉及gem对象、dma-buf对象的转换以及相关回调函数的作用,如pin、unpin、get_sg_table等。同时,讨论了自定义导出和外部导入的实现,强调了驱动需实现的相关回调以支持特定行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

dma-buf是linux内核提供的一种机制,用于不同模块实现内存共享。它提供生产者和消费者模式来实现不同模块对内存共享同时,不用关心各个模块的内部实现细节,从而解耦。在drm框架中也集成了dma-buf方式的内存管理。

1 导出buf

drm通过DRM_IOCTL_PRIME_HANDLE_TO_FD实现将一个gem对象句柄转为dma-buf的fd。其中会调用struct drm_driver的prime_handle_to_fd回调,drm_gem_prime_handle_to_fd函数是prime_handle_to_fd回调的默认实现函数:

int drm_gem_prime_handle_to_fd(struct drm_device *dev,
			       struct drm_file *file_priv, uint32_t handle,
			       uint32_t flags,
			       int *prime_fd)
{
    # 略略略... ...

	mutex_lock(&file_priv->prime.lock);
	obj = drm_gem_object_lookup(file_priv, handle);
	if (!obj)  {
		ret = -ENOENT;
		goto out_unlock;
	}

	dmabuf = drm_prime_lookup_buf_by_handle(&file_priv->prime, handle);
	if (dmabuf) {
		get_dma_buf(dmabuf);
		goto out_have_handle;
	}

	mutex_lock(&dev->object_name_lock);
	if (obj->import_attach) {
		dmabuf = obj->import_attach->dmabuf;
		get_dma_buf(dmabuf);
		goto out_have_obj;
	}

	if (obj->dma_buf) {
		get_dma_buf(obj->dma_buf);
		dmabuf = obj->dma_buf;
		goto out_have_obj;
	}

	dmabuf = export_and_register_object(dev, obj, flags);
	if (IS_ERR(dmabuf)) {
		ret = PTR_ERR(dmabuf);
		mutex_unlock(&dev->object_name_lock);
		goto out;
	}

out_have_obj:
	ret = drm_prime_add_buf_handle(&file_priv->prime,
				       dmabuf, handle);
	mutex_unlock(&dev->object_name_lock);
	if (ret)
		goto fail_put_dmabuf;

out_have_handle:
    # 略略略... ...
fail_put_dmabuf:
    # 略略略... ...
out:
    # 略略略... ...
out_unlock:
	mutex_unlock(&file_priv->prime.lock);
	return ret;
}

整个函数流程概况:

  • 首先,将handle转为gem对象;
  • 然后,在file_priv->prime内查询handle对应的dma-buf,若存在就挑战到out_have_handle;否则下一步;
  • 继续判断obj->import_attach是否非空,若非空(说明该obj是外部dma-buf导入所构建的对象)就将obj->import_attach->dmabuf作为dma-buf,然后就挑战到out_have_handle;否则下一步;
  • 继续判断obj->dma_buf(说明该obj或已导出,或已外部导入)是否非空,若非空就将obj->dmabuf作为dma-buf,然后就挑转到out_have_handle;否则下一步;
  • 经过以上,说明该对象是原始对象且没有导出过,则通过函数export_and_register_object构建一个dma-buf;
  • 最后,经过drm_prime_add_buf_handle函数将dma-buf添加到file_priv->prime。

export_and_register_object函数:

static struct dma_buf *export_and_register_object(struct drm_device *dev,
						  struct drm_gem_object *obj,
						  uint32_t flags)
{
    # 略略略... ...

	if (obj->funcs && obj->funcs->export)
		dmabuf = obj->funcs->export(obj, flags);
	else if (dev->driver->gem_prime_export)
		dmabuf = dev->driver->gem_prime_export(obj, flags);
	else
		dmabuf = drm_gem_prime_export(obj, flags);
	if (IS_ERR(dmabuf)) {
		return dmabuf;
	}

	obj->dma_buf = dmabuf;
    # 略略略... ...
}

注:若gem对象实现了drm_gem_object_funcs的export则调用其构建dma-buf;否则,若驱动实现了struct drm_driver的gem_prime_export回调,则调用其构建dma-buf;若两个回调都未实现,则通过drm_gem_prime_export函数构建dma-buf。最后将构建的dma-buf赋值给obj->dma_buf,然后返回该dma-buf。

1.1 drm_gem_prime_export

在drm_gem_prime_export中,将gem对象导出成dma-buf的导出信息如下:

struct dma_buf_export_info exp_info = {
	.exp_name = KBUILD_MODNAME, /* white lie for debug */
	.owner = dev->driver->fops->owner,
	.ops = &drm_gem_prime_dmabuf_ops,
	.size = obj->size,
	.flags = flags,
	.priv = obj,
	.resv = obj->resv,
};

drm_gem_prime_dmabuf_ops是dma相关操作的回调函数集合:

static const struct dma_buf_ops drm_gem_prime_dmabuf_ops =  {
	.cache_sgt_mapping = true,
	.attach = drm_gem_map_attach,
	.detach = drm_gem_map_detach,
	.map_dma_buf = drm_gem_map_dma_buf,
	.unmap_dma_buf = drm_gem_unmap_dma_buf,
	.release = drm_gem_dmabuf_release,
	.mmap = drm_gem_dmabuf_mmap,
	.vmap = drm_gem_dmabuf_vmap,
	.vunmap = drm_gem_dmabuf_vunmap,
};

相关回调函数概括:

  • drm_gem_map_attach:若gem对象实现了drm_gem_object_funcs的pin回调,则调用之;否则,若驱动实现了struct drm_driver的gem_prime_pin回调,则调用之;
  • drm_gem_map_detach:若gem对象实现了drm_gem_object_funcs的unpin回调,则调用之;否则,若驱动实现了struct drm_driver的的gem_prime_unpin回调,则调用之;
  • drm_gem_map_dma_buf:若gem对象实现了drm_gem_object_funcs的get_sg_table回调,则调用之;否则,若驱动实现了struct drm_driver的的gem_prime_get_sg_table回调,则调用之。最后,通过dma_map_sg_attrs将回调返回的CPU视角物理地址映射为设备视角的总线地址;
  • drm_gem_unmap_dma_buf:通过函数dma_unmap_sg_attrs将之前建立的映射解除映射;
  • drm_gem_dmabuf_mmap:通过struct drm_driver的gem_prime_mmap回调,将gem对象物理地址映射到用户空间的虚拟地址空间;
  • drm_gem_dmabuf_vmap:若gem对象实现了drm_gem_object_funcs的vmap回调,则调用之;否则,若驱动实现了struct drm_driver的的gem_prime_vmap回调,则调用之;最后,若都没实现,则返回异常地址;
  • drm_gem_dmabuf_vunmap:若gem对象实现了drm_gem_object_funcs的vunmap回调,则调用之;否则,若驱动实现了struct drm_driver的的gem_prime_vunmap回调,则调用之;

注:若gem对象未实现drm_gem_object_funcs的export回调、struct drm_driver也未实现gem_prime_export回调,则gem对象或struct drm_driver必须实现pin/gem_prime_pin、unpin/gem_prime_unpin、get_sg_table/gem_prime_get_sg_table、gem_prime_mmap、vmap/gem_prime_vmap、vunmap/gem_prime_vunmap回调(gem_prime_*为drm_driver函数集)。

1.2 自定义导出

通过实现drm_gem_object_funcs的export或struct drm_driver的gem_prime_export,可以自定义dma-buf的行为。一般是自定义struct dma_buf_ops集合

2 导入buf

drm通过DRM_IOCTL_PRIME_FD_TO_HANDLE实现将一个dma-buf的fd转为gem对象的handle。其中会调用struct drm_driver的prime_fd_to_handle回调,drm_gem_prime_fd_to_handle函数是prime_fd_to_handle回调的默认实现函数:

int drm_gem_prime_fd_to_handle(struct drm_device *dev,
			       struct drm_file *file_priv, int prime_fd,
			       uint32_t *handle)
{
    # 略略略... ...

	dma_buf = dma_buf_get(prime_fd);
	if (IS_ERR(dma_buf))
		return PTR_ERR(dma_buf);

	mutex_lock(&file_priv->prime.lock);

	ret = drm_prime_lookup_buf_handle(&file_priv->prime,
			dma_buf, handle);
	if (ret == 0)
		goto out_put;

	mutex_lock(&dev->object_name_lock);
	if (dev->driver->gem_prime_import)
		obj = dev->driver->gem_prime_import(dev, dma_buf);
	else
		obj = drm_gem_prime_import(dev, dma_buf);
	if (IS_ERR(obj)) {
		ret = PTR_ERR(obj);
		goto out_unlock;
	}

	if (obj->dma_buf) {
		WARN_ON(obj->dma_buf != dma_buf);
	} else {
		obj->dma_buf = dma_buf;
		get_dma_buf(dma_buf);
	}

	ret = drm_gem_handle_create_tail(file_priv, obj, handle);
	drm_gem_object_put_unlocked(obj);
	if (ret)
		goto out_put;

	ret = drm_prime_add_buf_handle(&file_priv->prime,
			dma_buf, *handle);
	mutex_unlock(&file_priv->prime.lock);
	if (ret)
		goto fail;

	dma_buf_put(dma_buf);

	return 0;

fail:
    # 略略略... ...

out_unlock:
    # 略略略... ...
out_put:
    # 略略略... ...
}

整个函数流程概况:

  • 首先,将fd转为dma-buf;
  • 然后,在file_priv->prime中查询dma-buf对应的handle,若存在就跳到out_put执行返回;否则,继续下一步;
  • 继续,若实现了struct drm_driver的gem_prime_import回调,则调用其构建gem对象;否则,调用drm_gem_prime_import构建gem对象;
  • 继续,将构建的dma-buf赋值给obj->dma_buf;
  • 继续,通过drm_gem_handle_create_tail将gem对象转为handle;
  • 最后,通过drm_prime_add_buf_handle建立dma-buf与handle的映射。

drm_gem_prime_import函数会直接调用drm_gem_prime_import_dev构建gem对象,drm_gem_prime_import_dev关键源码:

struct drm_gem_object *drm_gem_prime_import_dev(struct drm_device *dev,
					    struct dma_buf *dma_buf,
					    struct device *attach_dev)
{
    # 略略略... ...

	if (dma_buf->ops == &drm_gem_prime_dmabuf_ops) {
		obj = dma_buf->priv;
		if (obj->dev == dev) {
			drm_gem_object_get(obj);
			return obj;
		}
	}

    # 略略略... ...

	attach = dma_buf_attach(dma_buf, attach_dev);
	if (IS_ERR(attach))
		return ERR_CAST(attach);

	get_dma_buf(dma_buf);

	sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
	if (IS_ERR(sgt)) {
		ret = PTR_ERR(sgt);
		goto fail_detach;
	}

	obj = dev->driver->gem_prime_import_sg_table(dev, attach, sgt);
	if (IS_ERR(obj)) {
		ret = PTR_ERR(obj);
		goto fail_unmap;
	}

	obj->import_attach = attach;
	obj->resv = dma_buf->resv;

	return obj;

fail_unmap:
    # 略略略... ...
fail_detach:
    # 略略略... ...
}

整个函数流程概况:

  • 首先,若dma_buf->ops为drm_gem_prime_dmabuf_ops,说明是通过drm_gem_prime_export函数默认构建的。读出dma_buf->priv转为gem对象指针便可获取gem对象指针,同时若obj->dev等于当前dev,则可将obj返回;否则,若dma_buf->ops不是drm_gem_prime_dmabuf_ops或obj->dev不是当前dev,继续下一步;
  • 然后,通过函数dma_buf_attach构建struct dma_buf_attachment;
  • 继续,通过dma_buf_map_attachment函数调用dma-buf的map_dma_buf回调,通过该回调导出gem的设备内存信息;
  • 继续,将上一步返回的设备内存信息传入struct drm_driver的gem_prime_import_sg_table回调,用其构建gem对象;
  • 最后,将attach赋值给obj->import_attach、dma_buf->resv赋值给obj->resv;

2.1 外部导入

所谓外部导入是指dma-buf由其他模块导出,或无法通过dma-buf的field直接求出gem对象指针。此时,需要驱动实现struct drm_driver的gem_prime_import_sg_table回调,在该回调中实现根据struct sg_table的内存信息构建gem对象。

3 总结

最后,导出/导入dma-buf涉及到的回调和函数如下:
请添加图片描述

<think>好的,用户想了解Linux摄像头驱动框架的学习内容。首先,我需要确定用户的基础水平。可能他已经有一定的Linux驱动开发经验,或者只是刚开始接触。这会影响我回答的深度和侧重点。 接下来,我应该考虑用户的需求是什么。他可能正在开发摄像头相关的项目,或者在学习嵌入式系统时遇到了摄像头驱动的问题。了解他的实际应用场景有助于提供更有针对性的信息。 Linux摄像头驱动框架涉及多个子系统,比如V4L2、媒体控制器、设备树等。我需要分模块讲解,结构清晰,便于用户逐步理解。可能需要从基础概念开始,再深入到各个组件的作用和交互方式。 用户可能需要具体的例子或者学习路径,比如推荐参考资料、代码示例或者调试方法。我应该包括这些内容,帮助他实际动手操作。 另外,常见的问题和调试技巧也是用户关心的部分。比如如何排查摄像头无法识别的问题,或者图像显示异常的处理方法。这些实战经验能增加回答的实用性。 还要注意使用中文,避免专业术语的过度堆砌,用更易懂的语言解释复杂概念。同时,保持回答的结构化,使用标题和列表,让用户更容易浏览和理解。 最后,确认是否覆盖了用户可能关心的所有方面,比如内核版本的影响、社区资源、相关工具等。确保信息全面,帮助用户建立系统的学习框架。</think>Linux摄像头驱动框架学习指南(基于V4L2框架) 一、核心框架组成 1. V4L2(Video4Linux2)架构 - 用户空间接口:通过/dev/videoX设备节点提供ioctl调用 - 核心层:v4l2-core模块(v4l2-dev.c/v4l2-ioctl.c等) - 驱动层:具体摄像头控制器驱动实现 2. 关键数据结构 - struct video_device:代表视频设备实例 - struct v4l2_file_operations:文件操作接口 - struct v4l2_ioctl_ops:控制接口实现 二、驱动开发核心步骤 1. 设备注册流程 ```c struct video_device *vdev = video_device_alloc(); vdev->fops = &my_fops; vdev->ioctl_ops = &my_ioctl_ops; video_register_device(vdev, VFL_TYPE_VIDEO, -1); ``` 2. 实现关键操作 - open/release:设备打开/关闭管理 - querycap:设备能力查询 - enum_fmt:枚举支持的像素格式 - try_fmt/s_fmt:设置视频格式 - reqbufs/qbuf/dqbuf:缓冲区管理 - streamon/streamoff:启停数据流 三、关键子系统集成 1. 与媒体控制器框架交互 - 建立entities间连接(sensor -> CSI -> ISP) - 配置pipeline拓扑结构 2. 时钟与电源管理 - 实现v4l2_subdev_pad_ops - 控制sensor的reset/power引脚 - 配置MIPI时钟参数 四、典型开发流程示例 1. 硬件初始化 - 配置I2C总线(sensor寄存器访问) - 初始化MIPI CSI接口 - 设置DMA传输通道 2. 数据传输实现 ```c struct vb2_queue *q = &my_dev->queue; q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; q->io_modes = VB2_MMAP | VB2_USERPTR; q->ops = &my_vb2_ops; vb2_queue_init(q); ``` 五、调试与测试工具 1. 用户空间工具链 - v4l2-ctl:参数配置与查询 - yavta:RAW数据捕获 - qv4l2:图形化控制工具 2. 内核调试手段 - CONFIG_VIDEO_ADV_DEBUG 编译选项 - v4l2跟踪点(tracepoints) - 动态日志等级控制(videobuf2-core模块) 六、学习路径建议 1. 基础准备 - 掌握Linux设备驱动模型 - 理解字符设备工作原理 - 熟悉设备树绑定方法 2. 推荐学习资料 - 内核文档:Documentation/media/v4l-drivers/ - V4L2规范文档(v4l2-spec.pdf) - 典型驱动参考(如ov5640.c) 七、常见问题解决方案 1. 设备未识别问题 - 检查I2C地址配置 - 验证时钟信号与电源 - 确认设备树节点正确性 2. 图像异常处理 - 验证MIPI时序配置 - 检查DMA缓冲区对齐 - 调试CSI错误中断 最新注意事项(2024年): 1. 新内核趋势:逐步采用V4L2框架的异步子设备注册 2. 硬件加速:关注V4L2 MEM2MEM设备开发模式 3. 安全要求:强化用户空间缓冲区访问验证 建议实践项目: 开发USB摄像头驱动 → 实现MIPI-CSI摄像头支持 → 集成ISP图像处理管线
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值