程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3399 - DRM gem基础知识

----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板
eMMC16GB
LPDDR34GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot2023.04
linux6.3
----------------------------------------------------------------------------------------------------------------------------

GEM主要负责显示buffer的分配和释放,linux内核中使用struct drm_gem_object表示GEM对象,驱动一般需要用私有信息来扩展GEM对象,因此struct drm_gem_object都是嵌入在驱动自定义的GEM结构体内的。
gem object的创建以及初始化步骤如下:

  • 创建一个GEM对象,驱动为自定义GEM对象申请内存;
  • 通过drm_gem_object_init来初始化嵌入在其中的struct drm_gem_object
  • 通过drm_gem_handle_create创建GEM对象handle
  • 分配物理buffer
  • 通过mmap将物理buffer映射到用户空间;这样用户空间就可以直接访问了;

一、GEM数据结构

1.1 struct drm_gem_object

linux内核中使用struct drm_gem_object表示GEM对象,struct drm_gem_object定义在include/drm/drm_gem.h:

/**
 * struct drm_gem_object - GEM buffer object
 *
 * This structure defines the generic parts for GEM buffer objects, which are
 * mostly around handling mmap and userspace handles.
 *
 * Buffer objects are often abbreviated to BO.
 */
struct drm_gem_object {
        /**
         * @refcount:
         *
         * Reference count of this object
         *
         * Please use drm_gem_object_get() to acquire and drm_gem_object_put_locked()
         * or drm_gem_object_put() to release a reference to a GEM
         * buffer object.
         */
        struct kref refcount;

        /**
         * @handle_count:
         *
         * This is the GEM file_priv handle count of this object.
         *
         * Each handle also holds a reference. Note that when the handle_count
         * drops to 0 any global names (e.g. the id in the flink namespace) will
         * be cleared.
         *
         * Protected by &drm_device.object_name_lock.
         */
        unsigned handle_count;
    
        /**
         * @dev: DRM dev this object belongs to.
         */
        struct drm_device *dev;
    
        /**
         * @filp:
         *
         * SHMEM file node used as backing storage for swappable buffer objects.
         * GEM also supports driver private objects with driver-specific backing
         * storage (contiguous DMA memory, special reserved blocks). In this
         * case @filp is NULL.
         */
        struct file *filp;
        /**
         * @vma_node:
         *
         * Mapping info for this object to support mmap. Drivers are supposed to
         * allocate the mmap offset using drm_gem_create_mmap_offset(). The
         * offset itself can be retrieved using drm_vma_node_offset_addr().
         *
         * Memory mapping itself is handled by drm_gem_mmap(), which also checks
         * that userspace is allowed to access the object.
         */
        struct drm_vma_offset_node vma_node;
    
        /**
         * @size:
         *
         * Size of the object, in bytes.  Immutable over the object's
         * lifetime.
         */
        size_t size;
    
        /**
         * @name:
         *
         * Global name for this object, starts at 1. 0 means unnamed.
         * Access is covered by &drm_device.object_name_lock. This is used by
         * the GEM_FLINK and GEM_OPEN ioctls.
         */
        int name;
    
        /**
         * @dma_buf:
         *
         * dma-buf associated with this GEM object.
         *
         * Pointer to the dma-buf associated with this gem object (either
         * through importing or exporting). We break the resulting reference
         * loop when the last gem handle for this object is released.
         *
         * Protected by &drm_device.object_name_lock.
         */
        struct dma_buf *dma_buf;
    
        /**
         * @import_attach:
         *
         * dma-buf attachment backing this object.
         *
         * Any foreign dma_buf imported as a gem object has this set to the
         * attachment point for the device. This is invariant over the lifetime
         * of a gem object.
         *
         * The &drm_gem_object_funcs.free callback is responsible for
         * cleaning up the dma_buf attachment and references acquired at import
         * time.
         *
         * Note that the drm gem/prime core does not depend upon drivers setting
         * this field any more. So for drivers where this doesn't make sense
         * (e.g. virtual devices or a displaylink behind an usb bus) they can
         * simply leave it as NULL.
         */
        struct dma_buf_attachment *import_attach;
    
        /**
         * @resv:
         *
         * Pointer to reservation object associated with the this GEM object.
         *
         * Normally (@resv == &@_resv) except for imported GEM objects.
         */
        struct dma_resv *resv;
    
        /**
         * @_resv:
         *
         * A reservation object for this GEM object.
         *
         * This is unused for imported GEM objects.
         */
        struct dma_resv _resv;
    
        /**
         * @funcs:
         *
         * Optional GEM object functions. If this is set, it will be used instead of the
         * corresponding &drm_driver GEM callbacks.
         *
         * New drivers should use this.
         *
         */
        const struct drm_gem_object_funcs *funcs;
    
        /**
         * @lru_node:
         *
         * List node in a &drm_gem_lru.
         */
        struct list_head lru_node;
    
        /**
         * @lru:
         *
         * The current LRU list that the GEM object is on.
         */
        struct drm_gem_lru *lru;
};

其中:

  • refcount:表示对象引用计数,用于对GEM对象全生命周期的管理;
  • handle_count:表示该对象的句柄计数。每个句柄还持有一个引用,当handle_count降为0时,任何全局名称(例如flink命名空间中的id)都将被清除;
  • dev:该对象所属的DRM设备的指针;
  • filp:用作可互换的缓存对象的后备存储的共享内存文件节点;
  • vma_node:用于支持内存映射的对象的映射信息;
  • size:对象的大小,以字节为单位;
  • name:该对象的全局名称,从1开始。值为0表示无名称;
  • dma_buf:与此GEM对象关联的dma-buf
  • import_attach:支持此对象的dma-buf附件;
  • resv:与此GEM对象关联的保留对象的指针;
  • _resv:此GEM对象的保留对象;
  • funcs:可选的GEM对象函数。如果设置了此字段,则将使用它而不是对应的drm_driver GEM回调函数;
  • lru_node:在drm_gem_lru中的列表节点;
  • lruGEM对象当前所在的LRU列表;

1.2 struct drm_gem_object_funcs

struct drm_gem_object_funcs定义了与GEM对象相关的回调函数,用于管理和操作GEM对象;

struct drm_gem_object_funcs {
    void (*free)(struct drm_gem_object *obj);
    int (*open)(struct drm_gem_object *obj, struct drm_file *file);
    void (*close)(struct drm_gem_object *obj, struct drm_file *file);
    void (*print_info)(struct drm_printer *p, unsigned int indent, const struct drm_gem_object *obj);
    struct dma_buf *(*export)(struct drm_gem_object *obj, int flags);
    int (*pin)(struct drm_gem_object *obj);
    void (*unpin)(struct drm_gem_object *obj);
    struct sg_table *(*get_sg_table)(struct drm_gem_object *obj);
    int (*vmap)(struct drm_gem_object *obj, struct iosys_map *map);
    void (*vunmap)(struct drm_gem_object *obj, struct iosys_map *map);
    int (*mmap)(struct drm_gem_object *obj, struct vm_area_struct *vma);
    int (*evict)(struct drm_gem_object *obj);
    enum drm_gem_object_status (*status)(struct drm_gem_object *obj);
    const struct vm_operations_struct *vm_ops;
};

其中:

  • free:用于释放GEM对象及其相关资源的操作,必须实现;
  • open:创建GEM handle时(drm_gem_handle_create),回调该函数,可选;
  • close:释放GEM handle时(drm_gem_handle_delete),回调该函数,可选;
  • get_sg_table:用于获取bufferScatter-Gather表(SG表);
  • vmap:为buffer获取一个虚拟地址,会被drm_gem_dmabuf_vmap helper使用,可选;
  • vunmap: 释放由vmap返回的虚拟地址,会被drm_gem_dmabuf_vunmap helper使用,可选;
  • mmap:用于处理对GEM对象的mmap调用,并相应地设置vmavm_area_struct)结构,可选;
    • 此回调函数被drm_gem_mmap_objdrm_gem_prime_mmap两者使用;
    • 当存在mmap时,不会使用vm_ops,而是必须由mmap回调函数设置vma->vm_ops
  • vm_ops:与mmap一起使用的虚拟内存区域操作结构体,它包含了对应于虚拟内存区域操作的函数指针;对于GEM对象或其他需要通过mmap系统调用映射到用户空间的内核对象,必须实现适当的vm_ops结构体。

二、核心API

2.1 GEM初始化

GEM使用shmem来申请匿名页内存,drm_gem_object_init将会根据传入的size创建一个指定大小的指定大小的shmfs,并将这个shmfs file保存到struct drm_gem_objectfilp字段。

当图形硬件使用系统内存,这些内存就会作为对象的主存储直接使用,否则就会作为后备内存。

驱动负责调用shmem_read_mapping_page_gfp做实际物理页面的申请,初始化GEM对象时驱动可以决定申请页面,或者延迟到需要内存时再申请(需要内存时是指:用户态访问内存发生缺页中断,或是驱动需要启动DMA用到这段内存)。

在某些情况下,匿名可分页内存分配并不理想,特别是当硬件要求物理连续的系统内存时,这在嵌入式设备中经常发生。在这种情况下,驱动程序可以通过调用drm_gem_private_object_init来初始化GEM对象,而不是使用drm_gem_object_init,从而创建没有shmfs支持的私有GEM对象。

drm_gem_object_init函数定义在drivers/gpu/drm/drm_gem.c

/**
 * drm_gem_object_init - initialize an allocated shmem-backed GEM object
 * @dev: drm_device the object should be initialized for
 * @obj: drm_gem_object to initialize
 * @size: object size 对象的大小
 *
 * Initialize an already allocated GEM object of the specified size with
 * shmfs backing store.
 */
int drm_gem_object_init(struct drm_device *dev,
                        struct drm_gem_object *obj, size_t )
{
        struct file *filp;

        drm_gem_private_object_init(dev, obj, size);
 
        filp = shmem_file_setup("drm mm object", size, VM_NORESERVE);
        if (IS_ERR(filp))
                return PTR_ERR(filp);

        obj->filp = filp;

        return 0;
}

函数通过调用 drm_gem_private_object_init 进行对象初始化;

/**
 * drm_gem_private_object_init - initialize an allocated private GEM object
 * @dev: drm_device the object should be initialized for
 * @obj: drm_gem_object to initialize
 * @size: object size
 *
 * Initialize an already allocated GEM object of the specified size with
 * no GEM provided backing store. Instead the caller is responsible for
 * backing the object and handling it.
 */
void drm_gem_private_object_init(struct drm_device *dev,
                                 struct drm_gem_object *obj, size_t size)
{
        BUG_ON((size & (PAGE_SIZE - 1)) != 0);

    	// 初始化成员
        obj->dev = dev;
        obj->filp = NULL;

    	// 初始化对象引用计数为1
        kref_init(&obj->refcount);
        obj->handle_count = 0;
        obj->size = size;
        dma_resv_init(&obj->_resv);
        if (!obj->resv)
                obj->resv = &obj->_resv;

        drm_vma_node_reset(&obj->vma_node);
        INIT_LIST_HEAD(&obj->lru_node);
}

然后通过调用 shmem_file_setup 创建一个与该 GEM对象关联的shmem文件,并将文件指针赋值给 obj->filp

2.2 GEM对象命名

用户空间与内核之间的通信使用本地句柄(handle)、全局名称或者文件描述符来引用GEM对象,所有这些都是32bit数。

2.2.1 GEM handle

GEM handle只在特定的DRM文件中有效,应用程序通过驱动程序特定的ioctl获取GEM对象的handle,并可以在其他标准或驱动程序特定的ioctl中使用该handle引用GEM对象,关闭一个DRM文件句柄会释放其中所有的GEM,并解引用相关的GEM对象。

对于GEM handle,函数drm_gem_handle_create用于创建GEM对象的handle,这个函数接收三个参数:

  • file_privDRM file的私有数据;
  • objGEM对象;
  • handlep:将创建的handle返回给调用者的指针handlep

drm_gem_handle_create函数定义在drivers/gpu/drm/drm_gem.c

/**
 * drm_gem_handle_create_tail - internal functions to create a handle
 * @file_priv: drm file-private structure to register the handle for
 * @obj: object to register
 * @handlep: pointer to return the created handle to the caller
 *
 * This expects the &drm_device.object_name_lock to be held already and will
 * drop it before returning. Used to avoid races in establishing new handles
 * when importing an object from either an flink name or a dma-buf.
 *
 * Handles must be release again through drm_gem_handle_delete(). This is done
 * when userspace closes @file_priv for all attached handles, or through the
 * GEM_CLOSE ioctl for individual handles.
 */
int
drm_gem_handle_create_tail(struct drm_file *file_priv,
                           struct drm_gem_object *obj,
                           u32 *handlep)
{
        struct drm_device *dev = obj->dev;
        u32 handle;
        int ret;

        WARN_ON(!mutex_is_locked(&dev->object_name_lock));
        if (obj->handle_count++ == 0)
                drm_gem_object_get(obj);

        /*
         * Get the user-visible handle using idr.  Preload and perform
         * allocation under our spinlock.
         */
        idr_preload(GFP_KERNEL);
		// 获取自旋锁
        spin_lock(&file_priv->table_lock);   

		// 基于基数树分配一个唯一的id
        ret = idr_alloc(&file_priv->object_idr, obj, 1, 0, GFP_NOWAIT); 

		// 释放自旋锁
        spin_unlock(&file_priv->table_lock);   
        idr_preload_end();

        mutex_unlock(&dev->object_name_lock);
        if (ret < 0)
                goto err_unref;

        handle = ret;

        ret = drm_vma_node_allow(&obj->vma_node, file_priv);
        if (ret)
                goto err_remove;

        if (obj->funcs->open) {
				// 回调open函数
                ret = obj->funcs->open(obj, file_priv);
                if (ret)
                        goto err_revoke;
        }

        *handlep = handle; // 写回
        return 0;

err_revoke:
        drm_vma_node_revoke(&obj->vma_node, file_priv);
err_remove:
        spin_lock(&file_priv->table_lock);
        idr_remove(&file_priv->object_idr, handle);
        spin_unlock(&file_priv->table_lock);
err_unref:
        drm_gem_object_handle_put_unlocked(obj);
        return ret;
}

/**
 * drm_gem_handle_create - create a gem handle for an object
 * @file_priv: drm file-private structure to register the handle for
 * @obj: object to register
 * @handlep: pointer to return the created handle to the caller
 *
 * Create a handle for this object. This adds a handle reference to the object,
 * which includes a regular reference count. Callers will likely want to
 * dereference the object afterwards.
 *
 * Since this publishes @obj to userspace it must be fully set up by this point,
 * drivers must call this last in their buffer object creation callbacks.
 */
int drm_gem_handle_create(struct drm_file *file_priv,
                          struct drm_gem_object *obj,
                          u32 *handlep)
{
        mutex_lock(&obj->dev->object_name_lock);

        return drm_gem_handle_create_tail(file_priv, obj, handlep);
}

可以由drm_gem_object_lookup检索与handle关联的GEM对象。

handle不再需要时,驱动程序使用drm_gem_handle_delete进行删除。释放句柄并不直接销毁或释放GEM对象本身,它只是解除了句柄与GEM对象之间的关联。

为了避免泄漏GEM对象,驱动程序必须确保适当地丢弃自己所拥有的引用(比如在对象创建时获取的初始引用),而不需要特别考虑handle。例如,在实现dumb_create操作时,驱动程序必须在返回handle之前丢弃对GEM对象的初始引用。

2.2.2 GEM名称

GEM名称类似于句柄,但不仅限于DRM文件。它们可以在进程之间传递,用于全局引用GEM对象。应用程序需要通过ioctl的DRM_IOCTL_GEM_FLINKDRM_IOCTL_GEM_OPEN来将句柄转换为名称,以及将名称转换为句柄。这个转换由DRM core处理,不需要任何驱动程序特定的支持。

2.2.3 文件描述符

GEM也支持通过PRIME dma-buf文件描述符的缓存共享,基于GEM的驱动必须使用提供的辅助函数来实现exoprtingimporting。共享文件描述符比可以被猜测的全局名称更安全,因此是首选的缓存共享机制。通过GEM全局名称进行缓存共享仅在传统用户态支持,更进一步的说,PRIME由于其基于dma-buf,还允许跨设备缓存共享。

2.3 GEM对象的生命周期

所有的GEM对象都由GEM Core进行引用计数管理,在DRM子系统中,可以通过调用函数drm_gem_object_getdrm_gem_object_put来获取和释放对GEM对象的引用。

执行drm_gem_object_get调用者必须持有struct drm_devicestruct_mutex锁,而为了方便也提供了drm_gem_object_put_unlocked也可以不持锁操作。

当最后一个对GEM对象的引用释放时,GEM core调用struct drm_gem_object_funcs中的free操作,这一操作对于使能了GEM的驱动来说是必须,并且必须释放GEM对象和所有相关资源。

struct drm_gem_object_funcs中,void (*free)(struct drm_gem_object *obj)函数指针是用于释放GEM对象及其相关资源的操作。驱动程序需要实现这个函数,并在适当的时候调用drm_gem_object_release释放与GEM对象相关的资源。

2.4 GEM对象内存映射

linux kernel驱动中,实现mmap系统调用离不开两个关键步骤:

  • 物理内存的分配;
  • 建立物理内存到用户空间的映射关系。

这刚好对应了DRM中的dumb_createmmap操作,先说映射关系,在linux驱动中建立映射关系的方法主要有如下两种:

  • 一次性映射:在mmap回调函数中,一次性建立好整块内存的映射关系,通常以remap_pfn_range为代表;
  • Page Fault:在mmap先不建立映射关系,等上层触发缺页异常时,在fault中断处理函数中建立映射关系,缺哪块补哪块,通常以vm_insert_page为代表。

想再进一步学习mmap系统调用的相关知识,推荐大家阅读DRM 驱动mmap详解:(一)预备知识》《认真分析mmap:是什么 为什么 怎么用》

DRM中,GEM更倾向于通过特定于驱动程序的ioctl实现类似读/写的访问buffer,而不是将buffer映射到用户空间。然而,当需要对buffer进行随机访问时(例如执行软件渲染),直接访问对象可能更有效率。

不能直接使用mmap系统调用来映射GEM对象,因为它们没有自己的文件句柄。目前有两种共存的方法将GEM对象映射到用户空间:

(1) 第一种方法使用特定于驱动程序的ioctl来执行映射操作,底层调用do_mmap,这种方法经常被认为不可靠,在新的GEM驱动程序中似乎不被鼓励使用,因此这里不会描述它;

(2) 使用mmap系统调用对DRM文件句柄进行映射;

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)

DRM通过传递一个虚拟偏移量来标识要映射的GEM对象,该偏移量通过mmapoffset参数传递。在映射之前,GEM对象必须与虚拟偏移量关联起来。为此,驱动程序必须在对象上调用drm_gem_create_mmap_offset

分配了虚拟偏移量值后,驱动程序必须以特定于驱动程序的方式将该值传递给应用程序,然后可以将其用作mmapoffset参数。

GEM核心提供了一个辅助方法drm_gem_mmap来处理对象映射。该方法可以直接设置为mmap文件操作处理程序,它将根据偏移值查找GEM对象,并将VMA操作设置为struct drm_driver gem_vm_ops字段。

请注意:drm_gem_mmap不会将内存映射到用户空间,而是依赖于驱动程序提供的fault handler来逐个映射页面。

三、Rockchip gem驱动

这里我们介绍一下Rochchip DRM驱动中与gem object相关的实现,具体实现文件:

  • drivers/gpu/drm/rockchip/rockchip_drm_fb.c
  • drivers/gpu/drm/rockchip/rockchip_drm_gem.c
  • drivers/gpu/drm/rockchip/rockchip_drm_gem.h
  • drivers/gpu/drm/rockchip/rockchip_drm_fb.h

3.1 gem object创建及初始化

gem object的创建以及初始化是由rockchip_gem_dumb_create函数完成的,其定义在rockchip_drm_driver中;

static const struct drm_driver rockchip_drm_driver = {
        .driver_features        = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
        .dumb_create            = rockchip_gem_dumb_create,
		......
};

其中dumb_create配置为rockchip_gem_dumb_create,该函数用于分配物理内存dumb buffer,函数定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.c

/*
 * rockchip_gem_dumb_create - (struct drm_driver)->dumb_create callback
 * function
 *
 * This aligns the pitch and size arguments to the minimum required. wrap
 * this into your own function if you need bigger alignment.
 */
int rockchip_gem_dumb_create(struct drm_file *file_priv,
                             struct drm_device *dev,
                             struct drm_mode_create_dumb *args)
{
        struct rockchip_gem_object *rk_obj;
		
		// 宽度*每像素位数/8
        int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);  

        /*
         * align to 64 bytes since Mali requires it.
         */
        args->pitch = ALIGN(min_pitch, 64);
		
		// 宽*高*每像素位数/8,即得到总大小(单位字节)
        args->size = args->pitch * args->height;  

        rk_obj = rockchip_gem_create_with_handle(file_priv, dev, args->size,
                                                 &args->handle);

        return PTR_ERR_OR_ZERO(rk_obj);
}

这里主要实现函数是rockchip_gem_create_with_handle,定义如下;

/*
 * rockchip_gem_create_with_handle - allocate an object with the given
 * size and create a gem handle on it
 *
 * returns a struct rockchip_gem_object* on success or ERR_PTR values
 * on failure.
 */
static struct rockchip_gem_object *
rockchip_gem_create_with_handle(struct drm_file *file_priv,
                                struct drm_device *drm, unsigned int size,
                                unsigned int *handle)
{
        struct rockchip_gem_object *rk_obj;
        struct drm_gem_object *obj;
        bool is_framebuffer;
        int ret;

        is_framebuffer = drm->fb_helper && file_priv == drm->fb_helper->client.file;

        rk_obj = rockchip_gem_create_object(drm, size, is_framebuffer);
        if (IS_ERR(rk_obj))
                return ERR_CAST(rk_obj);

		// 获取GEM对象
        obj = &rk_obj->base; 

        /*
         * allocate a id of idr table where the obj is registered
         * and handle has the id what user can see.
         */
        ret = drm_gem_handle_create(file_priv, obj, handle); 
        if (ret)
                goto err_handle_create;

        /* drop reference from allocate - handle holds it now. */
        drm_gem_object_put(obj);

        return rk_obj;

err_handle_create:
        rockchip_gem_free_object(obj);

        return ERR_PTR(ret);
}

函数执行流程如下:

  • 调用rockchip_gem_create_object创建并初始化Rockchip驱动自定义的GEM对象struct rockchip_gem_objec;数据结构rockchip_gem_objectRockchip驱动自定义的GEM对象,内部包含struct drm_gem_object,定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.h
  • 调用drm_gem_handle_create创建GEM对象handle
  • 调用drm_gem_object_putGEM对象的引用计数-1;

rockchip_gem_create_with_handle函数调用栈:

rockchip_gem_create_with_handle(file_priv, dev, args->size,&args->handle)
    // #1 创建并初始化Rockchip驱动自定义的GEM对象
    rk_obj = rockchip_gem_create_object(drm, size, is_framebuffer) 
	    // #1.1 创建Rockchip驱动自定义的GEM对象 
    	rk_obj = rockchip_gem_alloc_object(drm, size)          
		    // GEM对象初始化
    		drm_gem_object_init(drm, obj, size)       
	    // #1.2 为Rockchip GEM对象分配DMA缓冲区,或者在IOMMU上分配内存
 	    rockchip_gem_alloc_buf(rk_obj, alloc_kmap)    
			rockchip_gem_alloc_dma(rk_obj, alloc_kmap)   
    			// 分配DMA缓冲区。分配的大小为obj->size字节,属性为rk_obj->dma_attrs
        		rk_obj->kvaddr = dma_alloc_attrs(drm->dev, obj->size,
                                         &rk_obj->dma_addr, GFP_KERNEL,
                                         rk_obj->dma_attrs);
    // 获取GEM对象 struct drm_gem_object
    obj = &rk_obj->base;
    // #2 创建GEM对象handle
    drm_gem_handle_create(file_priv, obj, handle)    
    // 3# 引用计数-1
    drm_gem_object_put(obj) 

rockchip_gem_alloc_dma函数执行后会创建了一块内存放在了rockchip gem object的对象里。

3.1.1 struct rockchip_gem_object

struct rockchip_gem_objectRockchip驱动扩展的gem object,定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.h

struct rockchip_gem_object {
        struct drm_gem_object base;
        unsigned int flags;

        void *kvaddr;
        dma_addr_t dma_addr;
        /* Used when IOMMU is disabled */
        unsigned long dma_attrs;

        /* Used when IOMMU is enabled */
        struct drm_mm_node mm;
        unsigned long num_pages;
        struct page **pages;
        struct sg_table *sgt;
        size_t size;
};

其中:

  • base:这是内核定义的gem object结构;
  • kvaddr:这个字段是一个指向虚拟地址的指针,它可能指向这个GEM对象在用户空间中的虚拟地址;
  • dma_addr:这个字段是设备内存地址,它可能被用于直接内存访问(DMA)操作;
  • dma_attrs:这个字段可能用于存储与DMA操作相关的属性;
  • num_pages:这个字段表示这个GEM对象占用的页面数量;
  • pages:这是一个指向struct page的指针数组,它可能用于存储这个GEM对象占用的所有页面的信息;
  • size:这个字段表示这个GEM对象的大小;
3.1.2 rockchip_gem_alloc_object

rockchip_gem_alloc_object用于创建Rockchip驱动自定义的GEM对象;

static const struct drm_gem_object_funcs rockchip_gem_object_funcs = {
		 // 释放GEM对象及其相关资源的操作
        .free = rockchip_gem_free_object,
        .get_sg_table = rockchip_gem_prime_get_sg_table,
        .vmap = rockchip_gem_prime_vmap,
        .vunmap = rockchip_gem_prime_vunmap,
        .mmap = rockchip_drm_gem_object_mmap,
        .vm_ops = &drm_gem_dma_vm_ops,
};

static struct rockchip_gem_object *
        rockchip_gem_alloc_object(struct drm_device *drm, unsigned int size)
{
        struct rockchip_gem_object *rk_obj;
        struct drm_gem_object *obj;

        size = round_up(size, PAGE_SIZE);

        rk_obj = kzalloc(sizeof(*rk_obj), GFP_KERNEL);
        if (!rk_obj)
                return ERR_PTR(-ENOMEM);

        obj = &rk_obj->base;

        obj->funcs = &rockchip_gem_object_funcs;
		
    	// 初始化gem object
        drm_gem_object_init(drm, obj, size);

        return rk_obj;
}

流程如此:

  • 调用kzalloc动态分配Rockchip驱动自定义的GEM对象 struct rockchip_gem_object
  • 然后设定GEM对象funcsrockchip_gem_object_funcs
  • 最后调用drm_gem_object_init初始化GEM对象,创建一个大小为sizeshmfs,并将这个shmfs file存放到struct drm_gem_objectfilp字段。
3.1.3 rockchip_gem_alloc_buf

rockchip_gem_alloc_buf函数用于为Rockchip GEM对象分配DMA缓冲区,或者在IOMMU上分配内存;

static int rockchip_gem_alloc_buf(struct rockchip_gem_object *rk_obj,
                                  bool alloc_kmap)
{
        struct drm_gem_object *obj = &rk_obj->base;
        struct drm_device *drm = obj->dev;
	    // 获取drm驱动私有数据
        struct rockchip_drm_private *private = drm->dev_private; 

        if (private->domain)
                // 为Rockchip GEM对象在IOMMU上分配内存
                return rockchip_gem_alloc_iommu(rk_obj, alloc_kmap);
        else
            	// 为Rockchip GEM对象分配DMA缓冲区
                return rockchip_gem_alloc_dma(rk_obj, alloc_kmap);
}

由于没有初始化private->domain,因此会执行rockchip_gem_alloc_dma

static int rockchip_gem_alloc_iommu(struct rockchip_gem_object *rk_obj,
                                    bool alloc_kmap)
{
        int ret;

		// 获取页面
        ret = rockchip_gem_get_pages(rk_obj);  
        if (ret < 0)
                return ret;

		// 将GEM对象映射到IOMMU
        ret = rockchip_gem_iommu_map(rk_obj); 
        if (ret < 0)
                goto err_free;

        if (alloc_kmap) {
                // 将获取到的页面映射到内核虚拟地址空间中
                rk_obj->kvaddr = vmap(rk_obj->pages, rk_obj->num_pages, VM_MAP,
                                      pgprot_writecombine(PAGE_KERNEL));
                if (!rk_obj->kvaddr) {
                        DRM_ERROR("failed to vmap() buffer\n");
                        ret = -ENOMEM;
                        goto err_unmap;
                }
        }

        return 0;

err_unmap:
    	// 解除IOMMU映射
        rockchip_gem_iommu_unmap(rk_obj);
err_free:
        // 释放页面
        rockchip_gem_put_pages(rk_obj);

        return ret;
}

static int rockchip_gem_alloc_dma(struct rockchip_gem_object *rk_obj,
                                  bool alloc_kmap)
{
        struct drm_gem_object *obj = &rk_obj->base;
        struct drm_device *drm = obj->dev;

        rk_obj->dma_attrs = DMA_ATTR_WRITE_COMBINE;

        if (!alloc_kmap)
                rk_obj->dma_attrs |= DMA_ATTR_NO_KERNEL_MAPPING;

        // 分配DMA缓冲区。分配的大小为obj->size字节,属性为rk_obj->dma_attrs
        rk_obj->kvaddr = dma_alloc_attrs(drm->dev, obj->size,
                                         &rk_obj->dma_addr, GFP_KERNEL,
                                         rk_obj->dma_attrs);
        if (!rk_obj->kvaddr) {
                DRM_ERROR("failed to allocate %zu byte dma buffer", obj->size);
                return -ENOMEM;
        }

        return 0;
}

3.2 framebuffer创建

介绍完了gem object的创建和初始化,我们知道drm framebuffer的实现依赖于底层内存管理器比如GEMTTM

那么struct drm_frmebuffer是怎么创建的呢?

这里我们需要回顾一下《Rockchip RK3399 - DRM驱动程序》文章中介绍的rockchip_drm_bind函数,在该函数执行中会进行模式配置的初始化,在jams及rockchip_drm_mode_config_init中有如下一行代码:

dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;

drm设备模式设置mode_config的回调函数funcs被设置为rockchip_drm_mode_config_funcs

static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
        .fb_create = rockchip_fb_create,
        .atomic_check = drm_atomic_helper_check,
        .atomic_commit = drm_atomic_helper_commit,
};

fb_create 回调接口用于创建framebuffer object,并绑定GEM对象。

rockchip_fb_create定义在drivers/gpu/drm/rockchip/rockchip_drm_fb.c

static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
        .destroy       = drm_gem_fb_destroy,
        .create_handle = drm_gem_fb_create_handle,
        .dirty         = drm_atomic_helper_dirtyfb,
};

static struct drm_framebuffer *
rockchip_fb_create(struct drm_device *dev, struct drm_file *file,
                   const struct drm_mode_fb_cmd2 *mode_cmd)
{
        struct drm_afbc_framebuffer *afbc_fb;
        const struct drm_format_info *info;
        int ret;

    	// 获取drm格式
        info = drm_get_format_info(dev, mode_cmd);
        if (!info)
                return ERR_PTR(-ENOMEM);

    	// 动态分配内存,指向struct drm_afbc_framebuffer
        afbc_fb = kzalloc(sizeof(*afbc_fb), GFP_KERNEL);
        if (!afbc_fb)
                return ERR_PTR(-ENOMEM);

    	// 初始化成员afbc_fb->base,struct drm_framebuffer类型;同时设置framebuffer的funcs为rockchip_drm_fb_funcs
        ret = drm_gem_fb_init_with_funcs(dev, &afbc_fb->base, file, mode_cmd,
                                         &rockchip_drm_fb_funcs);
        if (ret) {
                kfree(afbc_fb);
                return ERR_PTR(ret);
        }

    	// 如果支持afbc
        if (drm_is_afbc(mode_cmd->modifier[0])) {
                int ret, i;

                ret = drm_gem_fb_afbc_init(dev, mode_cmd, afbc_fb);
                if (ret) {
                        struct drm_gem_object **obj = afbc_fb->base.obj;

                        for (i = 0; i < info->num_planes; ++i)
                                drm_gem_object_put(obj[i]);

                        kfree(afbc_fb);
                        return ERR_PTR(ret);
                }
        }

    	// 返回framebuffer
        return &afbc_fb->base;
}

3.2.1 drm_gem_fb_init_with_funcs

drm_gem_fb_init_with_funcs是一个用于实现&drm_mode_config_funcs.fb_create回调函数的辅助函数。它适用于那些初始化framebuffer时同时提供自定义的framebuffer操作集合的驱动程序,drm_gem_fb_init_with_funcs定义在drivers/gpu/drm/drm_gem_framebuffer_helper.c:

函数参数说明:

  • devDRM设备结构体指针。
  • fbframebuffer对象指针。
  • file:保存了支持framebufferGEM句柄的DRM文件指针;
  • mode_cmd:用户空间framebuffer创建请求的元数据;
  • funcsframebuffer操作结合;
/**
 * drm_gem_fb_init_with_funcs() - Helper function for implementing
 *                                &drm_mode_config_funcs.fb_create
 *                                callback in cases when the driver
 *                                allocates a subclass of
 *                                struct drm_framebuffer
 * @dev: DRM device
 * @fb: framebuffer object
 * @file: DRM file that holds the GEM handle(s) backing the framebuffer
 * @mode_cmd: Metadata from the userspace framebuffer creation request
 * @funcs: vtable to be used for the new framebuffer object
 *
 * This function can be used to set &drm_framebuffer_funcs for drivers that need
 * custom framebuffer callbacks. Use drm_gem_fb_create() if you don't need to
 * change &drm_framebuffer_funcs. The function does buffer size validation.
 * The buffer size validation is for a general case, though, so users should
 * pay attention to the checks being appropriate for them or, at least,
 * non-conflicting.
 *
 * Returns:
 * Zero or a negative error code.
 */
int drm_gem_fb_init_with_funcs(struct drm_device *dev,
                               struct drm_framebuffer *fb,
                               struct drm_file *file,
                               const struct drm_mode_fb_cmd2 *mode_cmd,
                               const struct drm_framebuffer_funcs *funcs)
{
        const struct drm_format_info *info;
        struct drm_gem_object *objs[DRM_FORMAT_MAX_PLANES];
        unsigned int i;
        int ret;

	    // 获取DRM格式信息
        info = drm_get_format_info(dev, mode_cmd);
        if (!info) {
                drm_dbg_kms(dev, "Failed to get FB format info\n");
                return -EINVAL;
        }

	    // 遍历每一个color plane
        for (i = 0; i < info->num_planes; i++) {
                unsigned int width = mode_cmd->width / (i ? info->hsub : 1);
                unsigned int height = mode_cmd->height / (i ? info->vsub : 1);
                unsigned int min_size;

            	// 查找对应color plane的GEM对象
                objs[i] = drm_gem_object_lookup(file, mode_cmd->handles[i]);
                if (!objs[i]) {
                        drm_dbg_kms(dev, "Failed to lookup GEM object\n");
                        ret = -ENOENT;
                        goto err_gem_object_put;
                }

                min_size = (height - 1) * mode_cmd->pitches[i]
                         + drm_format_info_min_pitch(info, i, width)
                         + mode_cmd->offsets[i];

                if (objs[i]->size < min_size) {
                        drm_dbg_kms(dev,
                                    "GEM object size (%zu) smaller than minimum size (%u) for plane %d\n",
                                    objs[i]->size, min_size, i);
                        drm_gem_object_put(objs[i]);
                        ret = -EINVAL;
                        goto err_gem_object_put;
                }
        }

    	// 初始化framebuffer对象
        ret = drm_gem_fb_init(dev, fb, mode_cmd, objs, i, funcs);
        if (ret)
                goto err_gem_object_put;

        return 0;

err_gem_object_put:
        while (i > 0) {
                --i;
                drm_gem_object_put(objs[i]);
        }
        return ret;
}

函数主要流程如下:

  • 通过drm_get_format_info函数获取DRM格式信息,如果失败则返回错误;
  • 遍历每个color plane,计算出每个color plane的宽度、高度和最小尺寸;
  • 使用drm_gem_object_lookup函数查找对应color planeGEM对象,如果失败则返回错误;
  • 对比GEM对象的大小和最小尺寸,如果大小小于最小尺寸则返回错误;
  • 调用drm_gem_fb_init函数初始化framebuffer对象。
3.2.2 drm_gem_fb_init

drm_gem_fb_init函数用于初始化framebuffer对象,函数定义在drivers/gpu/drm/drm_gem_framebuffer_helper.c

static int
drm_gem_fb_init(struct drm_device *dev,
                 struct drm_framebuffer *fb,
                 const struct drm_mode_fb_cmd2 *mode_cmd,
                 struct drm_gem_object **obj, unsigned int num_planes,
                 const struct drm_framebuffer_funcs *funcs)
{
        unsigned int i;
        int ret;

        drm_helper_mode_fill_fb_struct(dev, fb, mode_cmd);

        for (i = 0; i < num_planes; i++)
                fb->obj[i] = obj[i];

        ret = drm_framebuffer_init(dev, fb, funcs);
        if (ret)
                drm_err(dev, "Failed to init framebuffer: %d\n", ret);

        return ret;
}

参考文章

[1] DRMGEM

[2] DRM 驱动mmap详解:(二)CMA Helper

[3] linux kernel DRM Internals

[4] linux gem initialization

posted @ 2023-10-20 22:59  大奥特曼打小怪兽  阅读(482)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步