4. 导出端驱动准备:实现一个简单的DRM驱动骨架、注册gem对象、实现gem_prime_export回调
在DMA-BUF导出与导入的完整链路中,导出端驱动是数据流的源头。它负责分配物理连续或IOMMU可寻址的内存,并将其包装为DRM GEM对象,最终通过gem_prime_export回调将GEM对象转换为DMA-BUF句柄,传递给其他驱动或用户空间。本章将带领你从零搭建一个最小化的DRM驱动骨架,注册GEM对象,并实现核心的导出回调。
4.1 驱动骨架:最小化DRM驱动结构
一个DRM驱动至少需要定义struct drm_driver和struct drm_device,并完成设备绑定与解除。以下是一个极简的导出端驱动骨架,仅保留DMA-BUF导出所需的最小回调。
#include <linux/module.h>
#include <linux/platform_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_device.h>
#include <drm/drm_gem.h>
#include <drm/drm_prime.h>
static struct drm_driver export_drm_driver = {
.driver_features = DRIVER_GEM | DRIVER_RENDER,
.gem_prime_export = drm_gem_prime_export, // 使用通用实现
.gem_prime_get_sg_table = export_gem_get_sg_table, // 自定义回调
.gem_prime_pin = export_gem_prime_pin,
.gem_prime_unpin = export_gem_prime_unpin,
.name = "export_drm",
.desc = "DMA-BUF Export Demo Driver",
.date = "202501",
.major = 1,
.minor = 0,
};
static int export_probe(struct platform_device *pdev)
{
struct drm_device *ddev;
int ret;
ddev = drm_dev_alloc(&export_drm_driver, &pdev->dev);
if (IS_ERR(ddev))
return PTR_ERR(ddev);
platform_set_drvdata(pdev, ddev);
ret = drm_dev_register(ddev, 0);
if (ret) {
drm_dev_put(ddev);
return ret;
}
return 0;
}
static int export_remove(struct platform_device *pdev)
{
struct drm_device *ddev = platform_get_drvdata(pdev);
drm_dev_unregister(ddev);
drm_dev_put(ddev);
return 0;
}
static struct platform_driver export_pdrv = {
.probe = export_probe,
.remove = export_remove,
.driver = {
.name = "export_drm",
},
};
module_platform_driver(export_pdrv);
MODULE_LICENSE("GPL");
关键点:
DRIVER_GEM 标志必须设置,否则DRM核心不会初始化GEM子系统。DRIVER_RENDER 允许驱动以渲染节点方式工作,无需绑定显示硬件。
4.2 注册GEM对象:分配与初始化
导出端驱动需要实现GEM对象的分配函数。通常,我们使用drm_gem_cma_create(CMA连续内存)或drm_gem_shmem_create(页式内存)来简化工作。但为了展示完整控制流程,这里手动实现一个基于drm_gem_object的分配器,并关联一个私有结构体用于存储物理内存信息。
struct export_gem_object {
struct drm_gem_object base;
struct page **pages; // 物理页指针数组
unsigned int num_pages;
struct sg_table *sg; // 散列表,用于导出
};
static struct export_gem_object *export_gem_create(struct drm_device *ddev,
size_t size)
{
struct export_gem_object *obj;
int ret;
obj = kzalloc(sizeof(*obj), GFP_KERNEL);
if (!obj)
return ERR_PTR(-ENOMEM);
// 调用DRM核心初始化GEM对象
ret = drm_gem_object_init(ddev, &obj->base, size);
if (ret) {
kfree(obj);
return ERR_PTR(ret);
}
// 分配物理页(实际驱动应使用dma_alloc_coherent或类似API)
obj->num_pages = PAGE_ALIGN(size) / PAGE_SIZE;
obj->pages = kcalloc(obj->num_pages, sizeof(struct page *), GFP_KERNEL);
if (!obj->pages) {
drm_gem_object_release(&obj->base);
kfree(obj);
return ERR_PTR(-ENOMEM);
}
// 此处省略实际物理页分配逻辑,生产代码应调用alloc_pages等
return obj;
}
注意: 上述代码仅为教学骨架,省略了物理页分配和释放的完整实现。实际驱动必须确保内存可被DMA访问,并正确处理
drm_gem_object_put引用计数。
4.3 实现gem_prime_export回调
gem_prime_export是GEM对象到DMA-BUF导出的核心入口。DRM核心提供了通用实现drm_gem_prime_export,它会调用驱动注册的gem_prime_get_sg_table回调来获取散列表,然后创建DMA-BUF。因此,驱动只需实现gem_prime_get_sg_table即可。
下面展示完整的导出回调链:
| 回调函数 | 作用 | 必须实现? |
|---|---|---|
gem_prime_export |
将GEM对象导出为DMA-BUF fd | 否(可用通用实现) |
gem_prime_get_sg_table |
从GEM对象构造sg_table,描述物理内存布局 |
是 |
gem_prime_pin |
固定内存,防止导出期间被迁移或释放 | 否(默认空操作) |
gem_prime_unpin |
解除固定 | 否(默认空操作) |
static struct sg_table *export_gem_get_sg_table(struct drm_gem_object *obj)
{
struct export_gem_object *export_obj = to_export_gem_object(obj);
struct sg_table *sg;
int ret;
sg = kzalloc(sizeof(*sg), GFP_KERNEL);
if (!sg)
return ERR_PTR(-ENOMEM);
// 从物理页数组构建散列表
ret = sg_alloc_table_from_pages(sg, export_obj->pages,
export_obj->num_pages, 0,
obj->size, GFP_KERNEL);
if (ret) {
kfree(sg);
return ERR_PTR(ret);
}
return sg;
}
static int export_gem_prime_pin(struct drm_gem_object *obj)
{
// 对于简单驱动,可以空实现或增加引用计数
return 0;
}
static void export_gem_prime_unpin(struct drm_gem_object *obj)
{
// 对应unpin操作
}
流程总结: 用户空间调用
DRM_IOCTL_PRIME_HANDLE_TO_FD → DRM核心调用gem_prime_export → 通用实现调用gem_prime_get_sg_table → 驱动返回sg_table → 核心创建DMA-BUF并返回fd。
4.4 导出流程SVG架构图
下图展示了从GEM对象到DMA-BUF文件描述符的完整导出流程,以及驱动各回调的参与位置。
4.5 驱动注册与模块加载
完成上述代码后,需要将驱动注册到内核。使用module_platform_driver宏可以简化操作。加载模块后,会在/dev/dri/下创建渲染节点(如renderD128),用户空间可通过该节点进行GEM分配和导出操作。
// 模块加载验证命令
# insmod export_drm.ko
# ls /dev/dri/
card0 renderD128
# cat /sys/class/drm/renderD128/device/driver
export_drm
调试建议: 使用
drm_info工具(需安装)查看驱动注册的GEM回调是否生效:drm_info -d /dev/dri/renderD128。重点关注Prime一节中的export和get_sg_table字段。
4.6 常见问题与避坑
- 未设置DRIVER_GEM标志: 导致GEM对象初始化失败,
drm_dev_alloc返回错误。 - sg_table构建错误: 确保物理页数组有效,且
sg_alloc_table_from_pages的偏移和长度参数正确。 - 内存类型不匹配: 如果使用CMA内存,应使用
drm_gem_cma_create而非手动分配,否则导出时可能因非连续内存导致IOMMU映射失败。 - 引用计数泄漏: 每次
drm_gem_object_get必须对应drm_gem_object_put,否则GEM对象无法释放。