drm对内存使用抽象成GEM对象,用户空间通过句柄或文件映射的方式访问。
drm_mode_create_dumb_ioctl是DRM_IOCTL_MODE_CREATE_DUMB的处理函数,它直接调用了drm_mode_create_dumb函数,该函数通过参数解析和检查后,调用drm_driver的dumb_create回调函数。
dumb_create回调的常规实现如下:
int xxx_gem_dumb_create(struct drm_file *file,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
# 略略略... ...
obj = kzalloc(sizeof(*obj), GFP_KERNEL);
drm_gem_private_object_init(dev, &obj->base, args->size);
obj->base.funcs = &xxx_gem_object_funcs;
# 略略略... ...
ret = drm_gem_object_create(file, obj->base, &handle);
if (ret)
return error;
args->handle = handle;
return 0;
# 略略略... ...
}
整个函数流程概况:
注:一般gem扩展结构体的字段包括对象对应的内存信息记录
drm_mode_mmap_dumb_ioctl是DRM_IOCTL_MODE_MAP_DUMB的处理函数,该函数优先调用drm_driver的dumb_map_offset回调,如果没有实现则调用默认的处理函数drm_gem_dumb_map_offset:
int drm_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev,
u32 handle, u64 *offset)
{
struct drm_gem_object *obj;
int ret;
obj = drm_gem_object_lookup(file, handle);
if (!obj)
return -ENOENT;
/* Don't allow imported objects to be mapped */
if (obj->import_attach) {
ret = -EINVAL;
goto out;
}
ret = drm_gem_create_mmap_offset(obj);
if (ret)
goto out;
*offset = drm_vma_node_offset_addr(&obj->vma_node);
out:
drm_gem_object_put_unlocked(obj);
return ret;
}
整个函数流程概况:
注:在对象释放的时候,需要通过drm_gem_free_mmap_offset去归还占用的offset
drm_gem_mmap函数是drm_file的mmap默认实现:
int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
{
# 略略略... ...
drm_vma_offset_lock_lookup(dev->vma_offset_manager);
node = drm_vma_offset_exact_lookup_locked(dev->vma_offset_manager,
vma->vm_pgoff,
vma_pages(vma));
if (likely(node)) {
obj = container_of(node, struct drm_gem_object, vma_node);
if (!kref_get_unless_zero(&obj->refcount))
obj = NULL;
}
drm_vma_offset_unlock_lookup(dev->vma_offset_manager);
# 略略略... ...
ret = drm_gem_mmap_obj(obj, drm_vma_node_size(node) << PAGE_SHIFT,
vma);
drm_gem_object_put_unlocked(obj);
return ret;
}
整个函数流程概况:
gem对象或驱动需要实现vm_ops回调,该回调的open/close默认实现为drm_gem_vm_open/drm_gem_vm_close。vm_ops的fault实现过程如下:
vm_fault_t xxx_gem_fault(struct vm_fault *vmf)
{
struct vm_area_struct *area = vmf->vma;
struct drm_gem_object *obj = area->vm_private_data;
struct drm_xxx_gem_object *xxx_obj = to_intel_bo(obj);
pgoff_t page_offset;
u64 page;
page_offset = (vmf->address - area->vm_start) >> PAGE_SHIFT;
page = gem_xxx_get_one_page(obj, page_offset);
ret = vmf_insert_pfn(vma, vmf->address, page >> PAGE_SHIFT);
if (ret)
return VM_FAULT_SIGBUS;
return VM_FAULT_NOPAGE;
}
注: 此外,还有如下实现方式:
在多个渲染上下文中,可能存在同时访问某个GEM对象的可能。为了解决资源竞争的问题,提供了两个函数:
注:本质上是加解锁gem对象的resv->lock,这是一个ww_mutex,用于保护渲染上下文对gem添加读写fence的过程。实际的使用过程就是:先获取上一个上下文添加的dma fence,等待其触发;然后根据访问方式,插入一个dma fence;过程中是支持共享读/互斥写。
上面只是介绍了GEM对象的创建和映射。具体对内存的管理,一般的驱动要么用drm提供的内存块管理,或者自定义实现方式。
drm内部提供了drm_mm_init和drm_mm_insert_node/drm_mm_remove_node函数管理设备内存块。前者用于初始化内存块的范围;后者用于内存的申请和释放。
struct drm_mm {
# 略略略... ...
struct list_head hole_stack;
struct drm_mm_node head_node;
struct rb_root_cached interval_tree;
struct rb_root_cached holes_size;
struct rb_root holes_addr;
unsigned long scan_active;
};
字段描述:
struct drm_mm_node {
unsigned long color;
u64 start;
u64 size;
/* private: */
struct drm_mm *mm;
struct list_head node_list;
struct list_head hole_stack;
struct rb_node rb;
struct rb_node rb_hole_size;
struct rb_node rb_hole_addr;
# 略略略... ...
u64 hole_size;
# 略略略... ...
};
字段描述:
整个内存分配主要实现在函数drm_mm_insert_node_in_range中,相关概况如下:
注:整个分配算法特别有意思的是struct drm_mm的head_node的size为负数,其答案就在函数add_hole中,通过这种巧妙的设计让空闲内存和已分配内存的数据结构统一。
常规中,一般在驱动程序中扩展struct drm_device结构,使其内嵌一个struct drm_mm结构,然后设备创建的时候通过drm_mm_init对其初始化;在扩展的GEM对象中嵌套struct drm_mm_node,使用内存的时候通过drm_mm_insert_node分配内存,不再需要的时候,通过drm_mm_remove_node释放。
注:操作过程中,需要添加锁保护。