4. 全志DRM GEM实现:GEM对象创建、dumb buffer分配、mmap映射、全志自定义GEM操作
GEM(Graphics Execution Manager)是DRM框架中负责显存管理的核心组件。在全志平台上,GEM的实现既要遵循DRM/KMS的通用规范,又要适配全志显示引擎(如DE2.0/DE3.0)对物理连续内存的特殊需求。本章深入剖析全志DRM驱动中GEM对象的生命周期,从创建、分配、映射到自定义操作,并结合DMA-BUF机制讲解缓存共享的实现细节。
4.1 GEM对象创建与dumb buffer分配
GEM对象是DRM驱动管理显存的基本单位。用户空间通过DRM_IOCTL_MODE_CREATE_DUMB请求分配一个dumb buffer,驱动内部会创建一个对应的GEM对象。全志平台在此基础上增加了对物理连续性和对齐要求的处理。
4.1.1 dumb buffer分配流程
- 用户空间发起请求: 应用程序调用
drmModeCreateDumbBuffer(),传入宽度、高度和bpp。 - 内核驱动处理: DRM核心调用驱动的
.dumb_create回调函数。 - 全志实现: 驱动计算所需大小(考虑对齐),调用
dma_alloc_writecombine()分配物理连续内存。 - GEM对象初始化: 填充
drm_gem_object结构体,设置size、handle、vma_node等字段。 - 返回句柄: 用户空间获得一个整数句柄,用于后续操作。
| 步骤 | 函数/接口 | 说明 |
|---|---|---|
| 1 | drmModeCreateDumbBuffer() |
用户空间libdrm接口 |
| 2 | sunxi_gem_dumb_create() |
全志驱动回调函数 |
| 3 | dma_alloc_writecombine() |
分配物理连续、非缓存内存 |
| 4 | drm_gem_handle_create() |
创建用户空间句柄 |
4.1.2 全志自定义GEM结构体
全志驱动在标准drm_gem_object基础上扩展了自定义结构体,用于存储平台相关的内存信息:
struct sunxi_gem_object {
struct drm_gem_object base; // 标准GEM对象
struct dma_buf *dma_buf; // 关联的DMA-BUF
dma_addr_t dma_addr; // DMA地址(物理地址)
void *vaddr; // 内核虚拟地址
unsigned long size; // 实际分配大小
bool is_contiguous; // 是否物理连续
struct sg_table *sgt; // 散列表(用于DMA-BUF导出)
struct list_head list; // 驱动内部链表节点
};
/sys/kernel/debug/dri/0/gem_names中可以查看所有GEM对象的名称、大小和引用计数。全志驱动额外在/sys/kernel/debug/sunxi_gem中导出了物理地址信息,便于验证连续性。
4.2 mmap映射实现
用户空间需要通过mmap将GEM对象映射到进程地址空间,才能直接读写帧缓冲内容。全志平台的mmap实现需要处理缓存一致性问题和物理地址到用户空间的映射。
4.2.1 mmap调用链
| 层级 | 函数 | 作用 |
|---|---|---|
| 用户空间 | mmap() |
标准POSIX mmap调用 |
| libdrm | drm_mmap_dumb() |
封装dumb buffer的mmap |
| DRM核心 | drm_gem_mmap() |
通用GEM mmap入口 |
| 全志驱动 | sunxi_gem_mmap() |
建立页表映射 |
| 内核 | remap_pfn_range() |
将物理页帧映射到用户空间 |
// 全志驱动mmap实现核心代码
static int sunxi_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
{
struct sunxi_gem_object *sunxi_obj = to_sunxi_gem_object(obj);
unsigned long pfn;
int ret;
// 设置VMA标志:禁止交换、允许DMA
vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
// 计算物理页帧号
pfn = sunxi_obj->dma_addr >> PAGE_SHIFT;
// 建立页表映射
ret = remap_pfn_range(vma, vma->vm_start, pfn,
vma->vm_end - vma->vm_start,
vma->vm_page_prot);
return ret;
}
pgprot_writecombine而非pgprot_noncached,前者允许写合并但禁止缓存,在性能和正确性之间取得平衡。如果使用pgprot_noncached,写入性能会急剧下降。
4.3 全志自定义GEM操作
全志平台在标准GEM操作基础上,增加了针对显示引擎优化的自定义操作集。这些操作通过drm_driver.gem_prime_ops和自定义ioctl暴露给用户空间。
4.3.1 自定义操作集
| 操作 | 函数名 | 功能描述 |
|---|---|---|
| 创建 | sunxi_gem_create_object() |
分配自定义GEM结构体 |
| 释放 | sunxi_gem_free_object() |
释放DMA内存和GEM对象 |
| mmap | sunxi_gem_mmap() |
建立用户空间映射 |
| 导出 | sunxi_gem_prime_export() |
导出为DMA-BUF |
| 导入 | sunxi_gem_prime_import() |
从DMA-BUF导入 |
| 物理地址查询 | sunxi_gem_get_phys() |
获取GEM对象的物理地址(自定义ioctl) |
4.3.2 物理地址查询自定义ioctl
全志显示引擎的硬件寄存器需要直接配置帧缓冲的物理地址。因此驱动提供了一个自定义ioctl,允许用户空间查询GEM对象对应的物理地址:
#define SUNXI_DRM_GEM_GET_PHYS _IOR('S', 0x01, struct sunxi_drm_gem_phys)
struct sunxi_drm_gem_phys {
__u32 handle; // GEM对象句柄
__u32 reserved; // 保留字段
__u64 phys_addr; // 返回的物理地址
__u64 size; // 返回的分配大小
};
// 驱动中ioctl处理函数
static int sunxi_gem_get_phys_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct sunxi_drm_gem_phys *args = data;
struct drm_gem_object *obj;
struct sunxi_gem_object *sunxi_obj;
obj = drm_gem_object_lookup(file_priv, args->handle);
if (!obj)
return -ENOENT;
sunxi_obj = to_sunxi_gem_object(obj);
args->phys_addr = sunxi_obj->dma_addr;
args->size = sunxi_obj->size;
drm_gem_object_put(obj);
return 0;
}
drm_info工具或自定义测试程序调用SUNXI_DRM_GEM_GET_PHYSioctl,对比返回的物理地址与/proc/iomem中显示的内存区域是否一致。如果物理地址落在非连续区域,说明dumb buffer分配可能失败。
4.3.3 GEM操作注册
全志驱动通过drm_driver结构体注册自定义GEM操作:
static struct drm_driver sunxi_drm_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_RENDER,
.gem_create_object = sunxi_gem_create_object,
.gem_free_object = sunxi_gem_free_object,
.gem_prime_export = sunxi_gem_prime_export,
.gem_prime_import = sunxi_gem_prime_import,
.dumb_create = sunxi_gem_dumb_create,
.dumb_map_offset = sunxi_gem_dumb_map_offset,
.dumb_destroy = sunxi_gem_dumb_destroy,
.ioctls = sunxi_ioctls, // 包含自定义ioctl
.num_ioctls = ARRAY_SIZE(sunxi_ioctls),
// ... 其他字段
};
4.4 DMA-BUF集成与缓存共享
DMA-BUF是Linux内核中用于在不同设备驱动之间共享缓冲区的框架。全志DRM驱动通过DMA-BUF机制,实现GPU、显示引擎、编码器之间的零拷贝缓存共享。
4.4.1 DMA-BUF导出实现
导出操作将GEM对象转换为DMA-BUF,使得其他设备可以访问同一段物理内存:
static struct dma_buf *sunxi_gem_prime_export(struct drm_gem_object *obj,
int flags)
{
struct sunxi_gem_object *sunxi_obj = to_sunxi_gem_object(obj);
struct dma_buf_export_info exp_info = {
.exp_name = "sunxi-gem",
.owner = THIS_MODULE,
.ops = &sunxi_dmabuf_ops,
.size = sunxi_obj->size,
.flags = flags,
.priv = sunxi_obj,
};
return dma_buf_export(&exp_info);
}
4.4.2 全志DMA-BUF操作
| 操作 | 函数 | 说明 |
|---|---|---|
| attach | sunxi_dmabuf_attach() |
设备附加到DMA-BUF |
| detach | sunxi_dmabuf_detach() |
设备分离 |
| map_dma_buf | sunxi_dmabuf_map() |
创建DMA映射(返回sg_table) |
| unmap_dma_buf | sunxi_dmabuf_unmap() |
释放DMA映射 |
| mmap | sunxi_dmabuf_mmap() |
DMA-BUF的mmap实现 |
| release | sunxi_dmabuf_release() |
释放DMA-BUF |
map_dma_buf操作中直接返回预先分配的sg_table,避免每次映射时重新创建散列表。对于物理连续的内存,sg_table只包含一个entry,DMA映射开销极小。
4.4.3 缓存同步与一致性管理
当DMA-BUF在不同设备之间共享时,需要处理缓存一致性问题。全志平台使用dma_buf_begin_cpu_access()和dma_buf_end_cpu_access()接口进行同步:
// CPU访问前的缓存同步
static int sunxi_dmabuf_begin_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction dir)
{
struct sunxi_gem_object *obj = dmabuf->priv;
// 对于writecombine内存,只需要在CPU写入后刷新写缓冲区
// 不需要完整的缓存刷新操作
wmb(); // 写内存屏障
return 0;
}
// CPU访问结束后的缓存同步
static int sunxi_dmabuf_end_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction dir)
{
struct sunxi_gem_object *obj = dmabuf->priv;
// 确保CPU写入对DMA设备可见
wmb();
return 0;
}
4.5 调试方法与常见问题
4.5.1 调试接口
- debugfs节点:
/sys/kernel/debug