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对象。全志平台在此基础上增加了对物理连续性和对齐要求的处理。

关键点: 全志显示引擎要求帧缓冲必须位于物理连续的内存区域,且起始地址需按256字节对齐。dumb buffer分配时,驱动通过DMA API分配连续内存,并记录物理地址供后续使用。

4.1.1 dumb buffer分配流程

  1. 用户空间发起请求: 应用程序调用drmModeCreateDumbBuffer(),传入宽度、高度和bpp。
  2. 内核驱动处理: DRM核心调用驱动的.dumb_create回调函数。
  3. 全志实现: 驱动计算所需大小(考虑对齐),调用dma_alloc_writecombine()分配物理连续内存。
  4. GEM对象初始化: 填充drm_gem_object结构体,设置size、handle、vma_node等字段。
  5. 返回句柄: 用户空间获得一个整数句柄,用于后续操作。
步骤 函数/接口 说明
1 drmModeCreateDumbBuffer() 用户空间libdrm接口
2 sunxi_gem_dumb_create() 全志驱动回调函数
3 dma_alloc_writecombine() 分配物理连续、非缓存内存
4 drm_gem_handle_create() 创建用户空间句柄
dumb buffer分配流程 用户空间应用 libdrm (drmModeCreateDumbBuffer) DRM核心 (ioctl分发) 全志驱动 (sunxi_gem_dumb_create) dma_alloc_writecombine() 物理连续内存 返回dumb句柄 说明:全志驱动在dumb_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;
}
常见问题: 如果mmap后显示画面出现撕裂或花屏,通常是因为缓存一致性问题。全志平台使用pgprot_writecombine而非pgprot_noncached,前者允许写合并但禁止缓存,在性能和正确性之间取得平衡。如果使用pgprot_noncached,写入性能会急剧下降。
mmap映射地址空间布局 用户进程地址空间 mmap返回虚拟地址 0x7f1234560000 (页表映射) 内核地址空间 dma_alloc_writecombine 返回内核虚拟地址 0xC0000000(示例) 物理连续内存(DDR) 物理地址:0x48000000 说明:用户空间虚拟地址和内核虚拟地址映射到同一段物理内存,实现零拷贝访问。

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),
    // ... 其他字段
};
全志GEM对象生命周期 1. GEM对象创建 2. DMA内存分配 3. mmap映射 4. 渲染/显示 5. DMA-BUF导出/导入 6. GEM对象释放 生命周期关键点: • 创建时分配自定义结构体,初始化引用计数为1 • mmap映射增加内核映射计数,但不增加引用计数 • DMA-BUF导出/导入通过引用计数管理生命周期 • 释放时先检查引用计数,确保所有映射和DMA-BUF都已释放

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;
}
DMA-BUF跨设备共享架构 GEM对象 (物理内存) DMA-BUF封装 显示引擎 (DE2.0) GPU (Mali/其他) 视频编码器 (VE) 所有设备共享同一物理内存

4.5 调试方法与常见问题

4.5.1 调试接口