驱动开发系列55 - Linux Graphics QXL显卡驱动代码分析(二)显存管理

一:概述

        前面介绍了当内核检测到匹配的PCI设备后,会调用 qxl_pci_probe 初始化设备,其中会调用qxl_device_init 来初始化设备,为QXL设备进行内存映射,资源分配,环形缓冲区初始化,IRQ注册等操作,本文展开说说这些细节,以及介绍下QXL的显存管理。

二:QXL设备初始化细节


int qxl_device_init(struct qxl_device *qdev,
		    struct pci_dev *pdev)
{
	int r, sb;

	pci_set_drvdata(pdev, &qdev->ddev);

	mutex_init(&qdev->gem.mutex);
	mutex_init(&qdev->update_area_mutex);
	mutex_init(&qdev->release_mutex);
	mutex_init(&qdev->surf_evict_mutex);
	qxl_gem_init(qdev);

	qdev->rom_base = pci_resource_start(pdev, 2);
	qdev->rom_size = pci_resource_len(pdev, 2);
	qdev->vram_base = pci_resource_start(pdev, 0);
	qdev->io_base = pci_resource_start(pdev, 3);

	qdev->vram_mapping = io_mapping_create_wc(qdev->vram_base, pci_resource_len(pdev, 0));
	if (!qdev->vram_mapping) {
		pr_err("Unable to create vram_mapping");
		return -ENOMEM;
	}

	if (pci_resource_len(pdev, 4) > 0) {
		/* 64bit surface bar present */
		sb = 4;
		qdev->surfaceram_base = pci_resource_start(pdev, sb);
		qdev->surfaceram_size = pci_resource_len(pdev, sb);
		qdev->surface_mapping =
			io_mapping_create_wc(qdev->surfaceram_base,
					     qdev->surfaceram_size);
	}
	if (qdev->surface_mapping == NULL) {
		/* 64bit surface bar not present (or mapping failed) */
		sb = 1;
		qdev->surfaceram_base = pci_resource_start(pdev, sb);
		qdev->surfaceram_size = pci_resource_len(pdev, sb);
		qdev->surface_mapping =
			io_mapping_create_wc(qdev->surfaceram_base,
					     qdev->surfaceram_size);
		if (!qdev->surface_mapping) {
			pr_err("Unable to create surface_mapping");
			r = -ENOMEM;
			goto vram_mapping_free;
		}
	}

	DRM_DEBUG_KMS("qxl: vram %llx-%llx(%dM %dk), surface %llx-%llx(%dM %dk, %s)\n",
		 (unsigned long long)qdev->vram_base,
		 (unsigned long long)pci_resource_end(pdev, 0),
		 (int)pci_resource_len(pdev, 0) / 1024 / 1024,
		 (int)pci_resource_len(pdev, 0) / 1024,
		 (unsigned long long)qdev->surfaceram_base,
		 (unsigned long long)pci_resource_end(pdev, sb),
		 (int)qdev->surfaceram_size / 1024 / 1024,
		 (int)qdev->surfaceram_size / 1024,
		 (sb == 4) ? "64bit" : "32bit");

	qdev->rom = ioremap_wc(qdev->rom_base, qdev->rom_size);
	if (!qdev->rom) {
		pr_err("Unable to ioremap ROM\n");
		r = -ENOMEM;
		goto surface_mapping_free;
	}

	if (!qxl_check_device(qdev)) {
		r = -ENODEV;
		goto rom_unmap;
	}

	r = qxl_bo_init(qdev);
	if (r) {
		DRM_ERROR("bo init failed %d\n", r);
		goto rom_unmap;
	}

	qdev->ram_header = ioremap_wc(qdev->vram_base +
				   qdev->rom->ram_header_offset,
				   sizeof(*qdev->ram_header));
	if (!qdev->ram_header) {
		DRM_ERROR("Unable to ioremap RAM header\n");
		r = -ENOMEM;
		goto bo_fini;
	}

	qdev->command_ring = qxl_ring_create(&(qdev->ram_header->cmd_ring_hdr),
					     sizeof(struct qxl_command),
					     QXL_COMMAND_RING_SIZE,
					     qdev->io_base + QXL_IO_NOTIFY_CMD,
					     &qdev->display_event);
	if (!qdev->command_ring) {
		DRM_ERROR("Unable to create command ring\n");
		r = -ENOMEM;
		goto ram_header_unmap;
	}

	qdev->cursor_ring = qxl_ring_create(
				&(qdev->ram_header->cursor_ring_hdr),
				sizeof(struct qxl_command),
				QXL_CURSOR_RING_SIZE,
				qdev->io_base + QXL_IO_NOTIFY_CURSOR,
				&qdev->cursor_event);

	if (!qdev->cursor_ring) {
		DRM_ERROR("Unable to create cursor ring\n");
		r = -ENOMEM;
		goto command_ring_free;
	}

	qdev->release_ring = qxl_ring_create(
				&(qdev->ram_header->release_ring_hdr),
				sizeof(uint64_t),
				QXL_RELEASE_RING_SIZE, 0,
				NULL);

	if (!qdev->release_ring) {
		DRM_ERROR("Unable to create release ring\n");
		r = -ENOMEM;
		goto cursor_ring_free;
	}

	idr_init_base(&qdev->release_idr, 1);
	spin_lock_init(&qdev->release_idr_lock);
	spin_lock_init(&qdev->release_lock);

	idr_init_base(&qdev->surf_id_idr, 1);
	spin_lock_init(&qdev->surf_id_idr_lock);

	mutex_init(&qdev->async_io_mutex);

	/* reset the device into a known state - no memslots, no primary
	 * created, no surfaces. */
	qxl_io_reset(qdev);

	/* must initialize irq before first async io - slot creation */
	r = qxl_irq_init(qdev);
	if (r) {
		DRM_ERROR("Unable to init qxl irq\n");
		goto release_ring_free;
	}

	/*
	 * Note that virtual is surface0. We rely on the single ioremap done
	 * before.
	 */
	setup_slot(qdev, &qdev->main_slot, 0, "main",
		   (unsigned long)qdev->vram_base,
		   (unsigned long)qdev->rom->ram_header_offset);
	setup_slot(qdev, &qdev->surfaces_slot, 1, "surfaces",
		   (unsigned long)qdev->surfaceram_base,
		   (unsigned long)qdev->surfaceram_size);

	INIT_WORK(&qdev->gc_work, qxl_gc_work);

	return 0;

release_ring_free:
	qxl_ring_free(qdev->release_ring);
cursor_ring_free:
	qxl_ring_free(qdev->cursor_ring);
command_ring_free:
	qxl_ring_free(qdev->command_ring);
ram_header_unmap:
	iounmap(qdev->ram_header);
bo_fini:
	qxl_bo_fini(qdev);
rom_unmap:
	iounmap(qdev->rom);
surface_mapping_free:
	io_mapping_free(qdev->surface_mapping);
vram_mapping_free:
	io_mapping_free(qdev->vram_mapping);
	return r;
}

这个代码做了以下几件事:

1. 将qdev绑定到PCI设备,即将设备相关的数据结构与PCI设备关联起来,供后续使用。

2. 初始化多个mutex 和 spinlock,并调用qxl_gem_init() 初始化GEM相关数据。

3. 获取QXL设备的ROM,VRAM,I/O,Surface RAM的物理地址和大小。

	qdev->rom_base = pci_resource_start(pdev, 2);
	qdev->rom_size = pci_resource_len(pdev, 2);
	qdev->vram_base = pci_resource_start(pdev, 0);
	qdev->io_base = pci_resource_start(pdev, 3);

        上面这些语句是从PCI配置空间中的BAR(Base Address Register)中读取地址信息,BAR是用于映射设备资源(如 ROM、VRAM、I/O 区域)的。

        那么这些BAR值是什么时候写进去的呢? 这些BAR值是在系统启动时,由BIOS/UEFI或操作系统内核写进去的。比如当系统上电时,BIOS/UEFI扫描所有PCI设备,它们声明了BAR,BIOS会为这些BAR分配内存地址,并写入BAR寄存器,系统引导后,Linux内核也会扫描PCI总线,并读取这些BAR的值,映射成资源表。虽然QXL是虚拟显卡设备,但在QEMU虚拟机中BAR的写入过程和上面的描述是一样的。 

4.  将物理地址映射到内核虚拟地址空间,以确保内核能够能访问这些区域。其中 vram_base 是QXL的存放通用GPU数据的显存区域,主要用于存储命令缓冲区、缓冲区对象;surfaceram_base 是QXL的存储图层数据的显存区域, 主要存放图像数据、纹理。rom_base是显卡的只读存储器地址区域,主要存放硬件描述结构,版本信息,设备初始化配置。

5. 初始化缓冲区对象;这个后面再详细介绍。

6. 初始化命令缓冲区。

7. 设置main slot 和 surface slot 地址和大小;这个意思是说前面已经把vram显存和surface显存准备好了,现在来告诉QEMU虚拟GPU设备,你可以从这些地址读写数据了;

8. 重置设备状态和初始化中断处理。

9. 初始化异步工作项 gc_work, 它的作用是延迟执行异步任务( qxl_gc_work() ), 作用是将来某个时刻由内核调度器执行 qxl_gc_work()。 qxl_gc_work()的主要职责是回收不再使用的显存对象,释放命令缓冲区对象或surface对象,清理命令缓冲区的垃圾引用。

三:缓冲区对象初始化

        下面进一步说说缓冲区对象的初始化过程 qxl_bo_init,qxl_bo_init 调用 qxl_ttm_init, qxl_ttm_init 是QXL显卡驱动中用于初始化缓冲区对象的函数,它的作用是建立QXL显卡的内存管理系统,包括VRAM 和 Surface RAM两部分。 VRAM 是用于存放显示输出帧缓冲区,即我们最终会看到的画面,是“扫描输出”区域,Surface RAM 用来存放多个图形表面,即多个图层/窗口的缓存,比如Qt、GTK的窗口、合成器的临时缓冲等。

int qxl_ttm_init(struct qxl_device *qdev)
{
	int r;
	int num_io_pages; /* != rom->num_io_pages, we include surface0 */

	/* No others user of address space so set it to 0 */
	r = ttm_device_init(&qdev->mman.bdev, &qxl_bo_driver, NULL,
			    qdev->ddev.anon_inode->i_mapping,
			    qdev->ddev.vma_offset_manager,
			    false, false);
	if (r) {
		DRM_ERROR("failed initializing buffer object driver(%d).\n", r);
		return r;
	}
	/* NOTE: this includes the framebuffer (aka surface 0) */
	num_io_pages = qdev->rom->ram_header_offset / PAGE_SIZE;
	r = qxl_ttm_init_mem_type(qdev, TTM_PL_VRAM, num_io_pages);
	if (r) {
		DRM_ERROR("Failed initializing VRAM heap.\n");
		return r;
	}
	r = qxl_ttm_init_mem_type(qdev, TTM_PL_PRIV,
				  qdev->surfaceram_size / PAGE_SIZE);
	if (r) {
		DRM_ERROR("Failed initializing Surfaces heap.\n");
		return r;
	}
	DRM_INFO("qxl: %uM of VRAM memory size\n",
		 (unsigned int)qdev->vram_size / (1024 * 1024));
	DRM_INFO("qxl: %luM of IO pages memory ready (VRAM domain)\n",
		 ((unsigned int)num_io_pages * PAGE_SIZE) / (1024 * 1024));
	DRM_INFO("qxl: %uM of Surface memory size\n",
		 (unsigned int)qdev->surfaceram_size / (1024 * 1024));
	return 0;
}

        1. 这个代码首先调用 ttm_device_init 初始化显存管理系统,让显卡设备能用TTM框架管理自己的显存,并通过一组定制函数来自定义自己的行为(qxl_bo_driver);  

ttm_device_init(&qdev->mman.bdev, &qxl_bo_driver,
                NULL, qdev->ddev.anon_inode->i_mapping,
                qdev->ddev.vma_offset_manager,
                false, false);

        这个代码的意思是:我要开始用TTM管理QXL的显存了,相关操作安装qxl_bo_driver 来处理。

        TTM 是Linux DRM 子系统中的一个通用显存管理框架,它的主要职责是同意管理显存系统内存,对页表进行地址映射,管理图形缓冲区对象,支持缓冲区搬迁(显存和内存之间来回迁移),通过DMA等机制与内核其他子系统协作。    

static struct ttm_device_funcs qxl_bo_driver = {
	.ttm_tt_create = &qxl_ttm_tt_create,
	.ttm_tt_destroy = &qxl_ttm_backend_destroy,
	.eviction_valuable = ttm_bo_eviction_valuable,
	.evict_flags = &qxl_evict_flags,
	.move = &qxl_bo_move,
	.io_mem_reserve = &qxl_ttm_io_mem_reserve,
	.delete_mem_notify = &qxl_bo_delete_mem_notify,
};

        这个qxl_bo_driver 定义了QXL驱动如何管理TTM buffer object 的行为,起到类似“策略函数集合”的作用。其中:

        ttm_tt_create 是用来创建页表结构的,用于系统内存页管理。 

        ttm_tt_destroy 是用来销毁页表的。

        eviction_valuable 判断某BO是否值得被驱逐。 

        evict_flags 设置被驱逐 BO 的新内存属性,比如将其移动到系统内存

        move 实现显存与系统内存之间的移动逻辑

        io_mem_reserve 映射到I/O内存时调用,告诉内核如何将显存映射成可访问的地址

        delete_mem_notify BO被释放前的回调,用来清理QXL资源。

static struct ttm_tt *qxl_ttm_tt_create(struct ttm_buffer_object *bo,
					uint32_t page_flags)
{
	struct ttm_tt *ttm;

	ttm = kzalloc(sizeof(struct ttm_tt), GFP_KERNEL);
	if (ttm == NULL)
		return NULL;
	if (ttm_tt_init(ttm, bo, page_flags, ttm_cached, 0)) {
		kfree(ttm);
		return NULL;
	}
	return ttm;
}

        这个代码会创建一张页表(ttm_tt), 映射这块bo的内存区域,然后内核后面就能通过ttm_tt找到每一页。


static int qxl_bo_move(struct ttm_buffer_object *bo, bool evict,
		       struct ttm_operation_ctx *ctx,
		       struct ttm_resource *new_mem,
		       struct ttm_place *hop)
{
	struct ttm_resource *old_mem = bo->resource;
	int ret;

	if (!old_mem) {
		if (new_mem->mem_type != TTM_PL_SYSTEM) {
			hop->mem_type = TTM_PL_SYSTEM;
			hop->flags = TTM_PL_FLAG_TEMPORARY;
			return -EMULTIHOP;
		}

		ttm_bo_move_null(bo, new_mem);
		return 0;
	}

	qxl_bo_move_notify(bo, new_mem);

	ret = ttm_bo_wait_ctx(bo, ctx);
	if (ret)
		return ret;

	if (old_mem->mem_type == TTM_PL_SYSTEM && bo->ttm == NULL) {
		ttm_bo_move_null(bo, new_mem);
		return 0;
	}
	return ttm_bo_move_memcpy(bo, ctx, new_mem);
}

        这个代码用于将图形缓冲区(bo)迁移到另一个内存位置,根据当前状态选择合适的方式来迁移,比如复制内容,置为NULL。

  2. 计算VRAM的页数,以及初始化TTM的VRAM (framebuffer + command buffer)和 Surface RAM。

三:注册DRM驱动细节

        回忆在之前的 qxl_pci_probe 函数里, 会注册一个DRM驱动,来定义驱动的能力,回调函数。 下面来详细说说这个DRM驱动。

static int
qxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
    ....	 

	qdev = devm_drm_dev_alloc(&pdev->dev, &qxl_driver,
				  struct qxl_device, ddev);
	if (IS_ERR(qdev)) {
		pr_err("Unable to init drm dev");
		return -ENOMEM;
	}
    ....
}
static struct drm_driver qxl_driver = {
	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_CURSOR_HOTSPOT,

	.dumb_create = qxl_mode_dumb_create,
	.dumb_map_offset = drm_gem_ttm_dumb_map_offset,
#if defined(CONFIG_DEBUG_FS)
	.debugfs_init = qxl_debugfs_init,
#endif
	.gem_prime_import_sg_table = qxl_gem_prime_import_sg_table,
	DRM_FBDEV_TTM_DRIVER_OPS,
	.fops = &qxl_fops,
	.ioctls = qxl_ioctls,
	.num_ioctls = ARRAY_SIZE(qxl_ioctls),
	.name = DRIVER_NAME,
	.desc = DRIVER_DESC,
	.major = 0,
	.minor = 1,
	.patchlevel = 0,

	.release = qxl_drm_release,
};

         首先这个DRM驱动支持GEM,即显存管理框架,用于buffer对象(BO)的分配、映射管理。并且支持模式设置(modesetting),意味着它可以设置分辨率、刷新率,是KMS的前提。 同时驱动支持原子操作,即显示配置的更改可以打包成一个事务进行提交,不会产生中间态,确保一致性。支持硬件游标的热点位置设置,即可指定光标图像的“热点”,例如鼠标箭头的尖端,而不是总以左上角为基准。 

        其次这个DRM驱动支持以下回调函数:

        dump_create: 创建dump buffer的函数,用于没有 GPU 加速 的 framebuffer。

        dump_map_offset: 为dump buffer映射提供 offset, 通常使用通用的 drm_gem_ttm_dump_map_offset。 

        debugfs_init: 在debugfs中初始化调试接口,仅在内核开启CONFIG_DEBUG_FS时编译。

        gem_prime_import_sg_table: 用于DMA-BUF导入,支持跨设备 buffer 共享。 

        DRM_FBDEV_TTM_DRIVERS_OPS: 宏定义,为使用 TTM 管理显存的 fbdev 提供默认实现。

        fops: 文件操作函数合集,比如 open,release,mmap等。

        ioctls / num_ioctls : 自定义 IOCTL 命令数组及数量。 

        release:当DRM device 被释放时调用的回调函数。

四: 缓冲区对象的创建

        前面注册了DRM驱动,其中DRM驱动的一个功能是用于buffer对象(BO)的分配、映射管理。下面看看怎么做的。

int qxl_gem_object_create(struct qxl_device *qdev, int size,
			  int alignment, int initial_domain,
			  bool discardable, bool kernel,
			  struct qxl_surface *surf,
			  struct drm_gem_object **obj)
{
	struct qxl_bo *qbo;
	int r;

	*obj = NULL;
	/* At least align on page size */
	if (alignment < PAGE_SIZE)
		alignment = PAGE_SIZE;
	r = qxl_bo_create(qdev, size, kernel, false, initial_domain, 0, surf, &qbo);
	if (r) {
		if (r != -ERESTARTSYS)
			DRM_ERROR(
			"Failed to allocate GEM object (%d, %d, %u, %d)\n",
				  size, initial_domain, alignment, r);
		return r;
	}
	*obj = &qbo->tbo.base;

	mutex_lock(&qdev->gem.mutex);
	list_add_tail(&qbo->list, &qdev->gem.objects);
	mutex_unlock(&qdev->gem.mutex);

	return 0;
}

        这个函数的主要功能是:在QXL显存设备上分配一块图形内存,并封装为一个GEM对象,提供用户或内核使用。其中核心实现是 qxl_bo_create 函数,下面来看一下:


int qxl_bo_create(struct qxl_device *qdev, unsigned long size,
		  bool kernel, bool pinned, u32 domain, u32 priority,
		  struct qxl_surface *surf,
		  struct qxl_bo **bo_ptr)
{
	struct ttm_operation_ctx ctx = { !kernel, false };
	struct qxl_bo *bo;
	enum ttm_bo_type type;
	int r;

	if (kernel)
		type = ttm_bo_type_kernel;
	else
		type = ttm_bo_type_device;
	*bo_ptr = NULL;
	bo = kzalloc(sizeof(struct qxl_bo), GFP_KERNEL);
	if (bo == NULL)
		return -ENOMEM;
	size = roundup(size, PAGE_SIZE);
	r = drm_gem_object_init(&qdev->ddev, &bo->tbo.base, size);
	if (unlikely(r)) {
		kfree(bo);
		return r;
	}
	bo->tbo.base.funcs = &qxl_object_funcs;
	bo->type = domain;
	bo->surface_id = 0;
	INIT_LIST_HEAD(&bo->list);

	if (surf)
		bo->surf = *surf;

	qxl_ttm_placement_from_domain(bo, domain);

	bo->tbo.priority = priority;
	r = ttm_bo_init_reserved(&qdev->mman.bdev, &bo->tbo, type,
				 &bo->placement, 0, &ctx, NULL, NULL,
				 &qxl_ttm_bo_destroy);
	if (unlikely(r != 0)) {
		if (r != -ERESTARTSYS)
			dev_err(qdev->ddev.dev,
				"object_init failed for (%lu, 0x%08X)\n",
				size, domain);
		return r;
	}
	if (pinned)
		ttm_bo_pin(&bo->tbo);
	ttm_bo_unreserve(&bo->tbo);
	*bo_ptr = bo;
	return 0;
}

        这个函数实现了QXL驱动中用于创建图形内存对象的核心功能, 它通过TTM内存管理框架创建一个buffer,对应一个qxl_bo 对象,同时初始化底层的drm_gem_object 和 ttm_buffer_object, 最后返回这个buffer。

        这个函数首先分配 qxl_bo 结构体内存,这个结构体定义如下:

struct qxl_bo {
	struct ttm_buffer_object	tbo;

	/* Protected by gem.mutex */
	struct list_head		list;
	/* Protected by tbo.reserved */
	struct ttm_place		placements[3];
	struct ttm_placement		placement;
	struct iosys_map		map;
	void				*kptr;
	unsigned int                    map_count;
	int                             type;

	/* Constant after initialization */
	unsigned int is_primary:1; /* is this now a primary surface */
	unsigned int is_dumb:1;
	struct qxl_bo *shadow;
	unsigned int hw_surf_alloc:1;
	struct qxl_surface surf;
	uint32_t surface_id;
	struct qxl_release *surf_create;
};

         这个结构体是QXL DRM 驱动中缓冲对象结构体,是显存管理的核心,其中ttm_buffer_object 是描述这个对象当前在哪个内存区。 base 是内嵌的drm_gem_object,供用户空间访问。 ttm_place 是表示当前BO可被放置的内存区域。 iosys_map 封装了内核对该BO的访问映射信息。kptr 是内核虚拟地址,用于读写显存。

      下面再回到 qxl_bo_create  函数中,在初始化完qxl_bo之后,这个函数会初始化drm_gem_object, 作用是让gem对象挂到 qxl_bo->tbo.base 上。 然后设置操作这个bo对象的回调函数。   接着调用 ttm_bo_init_reserved,它是TTM框架中最核心的内存资源初始化接口,它会把bo注册进TTM管理系统,为bo分配页表,并将它安置到合适的内存区域,准备好GPU访问所需的地址映射,并确保在bo在资源紧张时能被swap-out, move等。

五:其他缓冲区对象操作     

        ttm_bo_pin 用于将bo固定在一个内存位置上,防止其被移动或换出,使用场景是GPU framebuffer 等必须驻留在显存/某特定区域。或者驱动不允许bo在使用过程中被搬迁(如DMA正在传输)。

        ttm_bo_vmap 用与将bo映射到内核虚拟地址空间,使得内核可以通过CPU直接访问这块显存或系统内存。使用场景是CPU想读写某个BO时。

        这两个函数实现细节会在后面的ttm文章中详细介绍。qxl_bo 操作目前只是在它基础上做了封装。

       

六:备注:

一:TTM 和 GEM 以及GTT 的区别:

1. GEM 是高层统一抽象,用户用户空间与内核之间传递 buffer。

2. TTM 更底层,用于内核内部的显存和BO管理机制。

3. GTT 是Graphics Translation Table(图形地址重映射表),它的实际数据存在系统内存中,由GPU通过地址映射机制访问,就像GPU自己的“页表”,GTT把一块系统内存“虚拟”成GPU可访问的地址。

七:总结:


        以下是qxl显存管理用到的函数总结:

       0. ttm_device_init 用来初始化ttm设备; ttm_range_man_init 用来初始化内存类型区域。 ttm_device_funcs 是实现显存与系统内存之间的统一管理用的,是TTM的钩子函数。

       1. drm_gem_object 是用户态和内核态之间共享显存的桥梁,比如我们在用户态创建一个用于渲染的framebuffer 或 texture,它在内核中就可能对应一个 drm_gem_objet. 

       2. ttm_buffer_object 前面说了 drm_gem_object 是封装用户可见的抽象对象,那么对应内部就是ttm_buffer_object了,它负责处理页表映射,VRAM/GTT切换等复杂细节。

       3. drm_gem_object_init 是用来初始化一个GEM对象的。

       4. drm_gem_object_funcs 是在drm_gem_object_init 初始化时传进去的回调函数,用来操作一个GEM对象的(打开,释放,关闭,固定,映射等)。

       5. ttm_bo_init_reserved 是用来初始化一个 ttm_buffer_object 的 

       6. ttm_bo_pin 是用来固定一个 ttm_buffer_object 的

       7. ttm_bo_unreserve 是用来解除缓冲区的独占,可以允许其他进程或组件访问。

       8. drm_gem_handle_create 是用来为GEM对象创建一个句柄,方便用户空间使用。

       9.  drm_gem_object_get,  drm_gem_object_put 是增加/减少引用技术。

     10.  drm_gem_ttm_dumb_map_offset 是获取GEM对象的 mmap 偏移量。

     11.  ttm_bo_vmap 用于将 ttm_buffer_object 对象映射到内核虚拟地址空间。 

     12.  ttm_bo_vunmap 用于取消之前通过 ttm_bo_vmap 建立的内核虚拟地址映射。

     13.  drm_gem_object_lookup 是根据句柄查找GEM对象。

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑不溜秋的

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值