intel gpu 分配显存和映射

1,190 阅读9分钟

intel 用户态驱动创建的一个buffer,这个buffer 包含的信息 大小,地址,map等 我们通常用一个bo来表示这些信息。bo的创建有多种方式:

  1. 从外部倒入一个bo
  2. 创建一个bo
  3. 从用户指定的系统内存地址创建一个bo

外部倒入

从fd 倒入bo

handle是每个gem context 拥有的,不能跨进程传输。要跨进程传输可以将handle和fd联系起来。 fd 可以进程通信,android 大部分buffer 都是通过binder 传递fd。 gem 都是通过handle 去查找一个bo,要通过fd 转换成一个bo,首先要将fd 转换成handle。drm提供了 drmPrimeFDToHandle 接口,获得fd 对应的handle。对应的handle 转fd函数是drmPrimeHandleToFD。

从name 倒入bo

GEM name在用途上类似于句柄,但不是 DRM 文件的本地名称。 它们可以在进程之间传递以全局引用 GEM 对象。 名称不能直接用于引用 DRM API 中的对象,应用程序必须分别使用 DRM_IOCTL_GEM_FLINK 和 DRM_IOCTL_GEM_OPEN ioctl 将句柄转换为name,并将name转换为句柄。 转换由 DRM 核心处理,没有任何特定于驱动程序的支持

分配一个bo

内存类型注册:

intel gpu 驱动分为system 内存和local 内存。

enum intel_memory_type {
	INTEL_MEMORY_SYSTEM = I915_MEMORY_CLASS_SYSTEM,
	INTEL_MEMORY_LOCAL = I915_MEMORY_CLASS_DEVICE,
	INTEL_MEMORY_STOLEN_SYSTEM,
	INTEL_MEMORY_STOLEN_LOCAL,
	INTEL_MEMORY_MOCK,
};

两种内存都注册成intel region. system 内存注册i915_gem_ttm_system_setup 显存注册 intel_gt_setup_lmem。 对于i915 驱动无论哪种内存方式都是用intel_memory_region_create 注册一个memory 的区域

 intel_memory_region_create(struct drm_i915_private *i915,
			   resource_size_t start,
			   resource_size_t size,
			   resource_size_t min_page_size,
			   resource_size_t io_start,
			   resource_size_t io_size,
			   u16 type,
			   u16 instance,
			   const struct intel_memory_region_ops *ops)
{
	struct intel_memory_region *mem;
	int err;

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

	mem->i915 = i915;
	mem->region = (struct resource)DEFINE_RES_MEM(start, size);
	mem->io_start = io_start; //显存和system 内存这里有区别
	mem->io_size = io_size;
	mem->min_page_size = min_page_size; //最小物理page size 如果使用4K 页面这个就是4K 使用64K 这个就是64K
	mem->ops = ops;//初始化obj 相关的函数,不通的buffer 使用不同的管理方式 这里有不同
        mem->total = size;
	mem->type = type;
	mem->instance = instance;
        
	if (ops->init) {
		err = ops->init(mem); //调用ops 的初始化

注册完成后 用户态就可以指定从那个region 分配一个buffer 作为bo。

显存注册

独立显卡都是以pci设备插到电脑中。setup_lmem 函数从pci 设备的bar 中读取bar基地址和显存size 创建一个memory region结构。

static struct intel_memory_region *setup_lmem(struct intel_gt *gt) {
....

io_start = pci_resource_start(pdev, GEN12_LMEM_BAR); //获取pci bar 基地址
io_size = min(pci_resource_len(pdev, GEN12_LMEM_BAR), lmem_size);
if (!io_size)
        return ERR_PTR(-EIO);

min_page_size = HAS_64K_PAGES(i915) ? I915_GTT_PAGE_SIZE_64K :
                                        I915_GTT_PAGE_SIZE_4K;
mem = intel_memory_region_create(i915, //注册一个mem region,记录下起始地址,操作函数
                                 0,
                                 lmem_size,
                                 min_page_size,
                                 io_start,
                                 io_size,
                                 INTEL_MEMORY_LOCAL,
                                 0,
                                 &intel_region_lmem_ops);
  
static const struct intel_memory_region_ops intel_region_lmem_ops = {
	.init = region_lmem_init,
	.release = region_lmem_release,
	.init_object = __i915_gem_ttm_object_init,
};


intel_memory_region_create 创建region 时候调用init 函数(region_lmem_init )初始化pci显存的地址成虚拟地址。初始化struct io_mapping 结构。region_lmem_init->intel_region_ttm_init 完成obj 对应的ttm 伙伴系统的初始化。新的驱动使用ttm 伙伴系统管理物理page。

    ret = i915_ttm_buddy_man_init(bdev, mem_type, false,
				  resource_size(&mem->region),//显存的大小
				  mem->io_size,//bar 空间和显存大小 最小的那个
				  mem->min_page_size, PAGE_SIZE);

初始化ttm 伙伴系统ttm_resource_manager

int i915_ttm_buddy_man_init(struct ttm_device *bdev,
			    unsigned int type, bool use_tt,
			    u64 size, u64 visible_size, u64 default_page_size,
			    u64 chunk_size)
{
//size 是显存大小 visible_size实际可以用的显存大小 受限pci 读取bar 限制 可能比实际显存小。
	struct ttm_resource_manager *man;
	struct i915_ttm_buddy_manager *bman;
	int err;

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

	err = drm_buddy_init(&bman->mm, size, chunk_size);//初始化buddy 系统
        // 根据size 分配成很多block,加入到free_list连表中维护,分配的时候从free_list连表中获取
	if (err)
		goto err_free_bman;

	mutex_init(&bman->lock);
	INIT_LIST_HEAD(&bman->reserved);
	GEM_BUG_ON(default_page_size < chunk_size);
	bman->default_page_size = default_page_size; //gpu 使用的page size
	bman->visible_size = visible_size >> PAGE_SHIFT; //有效的显存大小
	bman->visible_avail = bman->visible_size;

	man = &bman->manager;//初始化一个ttm_resource_manager
	man->use_tt = use_tt;
	man->func = &i915_ttm_buddy_manager_func;//i915实现的ttm伙伴系统分配释放等函数,ttm resource 管理会回调这个ops
	ttm_resource_manager_init(man, bdev, bman->mm.size >> PAGE_SHIFT);//初始化ttm_resource_manager 保存了合格manager 包含多少个page

	ttm_resource_manager_set_used(man, true);
	ttm_set_driver_manager(bdev, type, man);//将type 和ttm manager 绑定

	return 0;
}

drm buddy 根据size 大小, 算出了最小的order 然后将每个order 分配成一个block, 每个block 都让到 free_list连表中维护

int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
{
   ....
	mm->size = size; // 给drm_buddy 传输 整个region size 的大小
	mm->avail = size;
	mm->chunk_size = chunk_size;
	mm->max_order = ilog2(size) - ilog2(chunk_size);

	BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER);

	mm->free_list = kmalloc_array(mm->max_order + 1,
				      sizeof(struct list_head),
				      GFP_KERNEL);
	if (!mm->free_list)
		return -ENOMEM;

	for (i = 0; i <= mm->max_order; ++i)
		INIT_LIST_HEAD(&mm->free_list[i]);

	mm->n_roots = hweight64(size);

	mm->roots = kmalloc_array(mm->n_roots,
				  sizeof(struct drm_buddy_block *),
				  GFP_KERNEL);
	if (!mm->roots)
		goto out_free_list;

	offset = 0;
	i = 0;

	/*
	 * Split into power-of-two blocks, in case we are given a size that is
	 * not itself a power-of-two.
	 */
	do {
		struct drm_buddy_block *root;
		unsigned int order;
		u64 root_size;

		root_size = rounddown_pow_of_two(size);//根据size 不断开放 选择最后最小的order
		order = ilog2(root_size) - ilog2(chunk_size);

		root = drm_block_alloc(mm, NULL, order, offset);//分配一个block 包含 offset(其实的page number 和order)
		if (!root)
			goto out_free_roots;

		mark_free(mm, root);//加入到free_list中,后面分配的时候从这里获取

		BUG_ON(i > mm->max_order);
		BUG_ON(drm_buddy_block_size(mm, root) < chunk_size);

		mm->roots[i] = root;

		offset += root_size;
		size -= root_size;
		i++;
	} while (size);

	return 0;

}

系统内存注册

struct intel_memory_region *
i915_gem_ttm_system_setup(struct drm_i915_private *i915,
			  u16 type, u16 instance)
{
	struct intel_memory_region *mr;

	mr = intel_memory_region_create(i915, 0,
					totalram_pages() << PAGE_SHIFT,
					PAGE_SIZE, 0, 0,
					type, instance,
					&ttm_system_region_ops);
	if (IS_ERR(mr))
		return mr;

	intel_memory_region_set_name(mr, "system-ttm");
	return mr;
}

static const struct intel_memory_region_ops ttm_system_region_ops = {
	.init_object = __i915_gem_ttm_object_init,
	.release = intel_region_ttm_fini,
};

常见的显存和系统内存 region 都保存到i915->mm.regions中。 mesa 通过DRM_I915_QUERY_MEMORY_REGIONS 命令从内核中获取当前的mm.regions,并且再分配bo 的时候指定是system 还是local memory。

用户态分配

1.mesa 下发命令

mesa 根据配置的参数 调用DRM_IOCTL_I915_GEM_CREATE_EXT调用__i915_gem_object_create_user_ext分配内核中一个obj。

static struct drm_i915_gem_object *
__i915_gem_object_create_user_ext(struct drm_i915_private *i915, u64 size,
				  struct intel_memory_region **placements,
				  unsigned int n_placements,
				  unsigned int ext_flags)
{
	struct intel_memory_region *mr = placements[0];
        ......
	obj = i915_gem_object_alloc();
	if (!obj)
		return ERR_PTR(-ENOMEM);

	/*
	 * I915_BO_ALLOC_USER will make sure the object is cleared before
	 * any user access.
	 */
	flags = I915_BO_ALLOC_USER; //指定这个bo 是用户态分配的 不是kernel 分配的

	ret = mr->ops->init_object(mr, obj, I915_BO_INVALID_OFFSET, size, 0, flags);
        ....
	return obj;

2.初始化obj 状态信息

如果mesa 指定的是local memory或者system memory 的region 则init_object调用__i915_gem_ttm_object_init 来初始化一个ttm obj和ttm_resource

int __i915_gem_ttm_object_init(struct intel_memory_region *mem,
			       struct drm_i915_gem_object *obj,
			       resource_size_t offset,
			       resource_size_t size,
			       resource_size_t page_size,
			       unsigned int flags)
{
	.....
	drm_gem_private_object_init(&i915->drm, &obj->base, size);
	i915_gem_object_init(obj, &i915_gem_ttm_obj_ops, &lock_class, flags);//初始化obj ops

	obj->bo_offset = offset;

	/* Don't put on a region list until we're either locked or fully initialized.*/
	obj->mm.region = mem;
	INIT_LIST_HEAD(&obj->mm.region_link);
        .......
	obj->base.vma_node.driver_private = i915_gem_to_ttm(obj);
        .......

	ret = ttm_bo_init_reserved(&i915->bdev, i915_gem_to_ttm(obj), bo_type,
				   &i915_sys_placement, page_size >> PAGE_SHIFT,
				   &ctx, NULL, NULL, i915_ttm_bo_destroy);//
                                  分配一个ttm_buffer_object ,初始化struct ttm_resource
         .......
      }
      
  /* ttm_bo_init_reserved 有个很奇怪的地方是对应的是显存 local memory
   但是分配一个obj 对应的ttm_resource 指定的ttm_place 是临时创建的一个TTM_PL_SYSTEM,
   lpfn fpfn 都是0.
   ttm_resource_alloc会根据指定的内存类型 找到之前注册的memory region 区域ttm place8?
   
   /**
 * struct ttm_place
 *
 * @fpfn:	first valid page frame number to put the object
 * @lpfn:	last valid page frame number to put the object
 * @mem_type:	One of TTM_PL_* where the resource should be allocated from.
 * @flags:	memory domain and caching flags for the object
 *
 * Structure indicating a possible place to put an object.
 */
struct ttm_place {
	unsigned	fpfn;
	unsigned	lpfn;
	uint32_t	mem_type;
	uint32_t	flags;
};
   
                                  
int ttm_bo_init_reserved(struct ttm_device *bdev, struct ttm_buffer_object *bo,
			 enum ttm_bo_type type, struct ttm_placement *placement,
			 uint32_t alignment, struct ttm_operation_ctx *ctx,
			 struct sg_table *sg, struct dma_resv *resv,
			 void (*destroy) (struct ttm_buffer_object *))
{
	static const struct ttm_place sys_mem = { .mem_type = TTM_PL_SYSTEM };
........

	ret = ttm_resource_alloc(bo, &sys_mem, &bo->resource);
  .......
                                  

ttm_resource_alloc调用 i915_ttm_buddy_man_alloc 函数完成ttm_resource 分配和初始化。

static int i915_ttm_buddy_man_alloc(struct ttm_resource_manager *man,
				    struct ttm_buffer_object *bo,
				    const struct ttm_place *place,
				    struct ttm_resource **res)
{
	struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
	struct i915_ttm_buddy_resource *bman_res;
	struct drm_buddy *mm = &bman->mm;
	unsigned long n_pages, lpfn;
	u64 min_page_size;
	u64 size;
	int err;

	lpfn = place->lpfn;//这里place 是前面使用TTM_PL_SYSTEM 创建的 lpfn是0
	if (!lpfn)
		lpfn = man->size; //走这个从ttm_resource_manager获取size(size是这
                //manager包含了多少个page, 显存这里是读取了显存大小 然后算出有多少个page)
            // i915_ttm_buddy_man_init 函数初始化了man->size。lpfn指向了最后一个page number

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

	ttm_resource_init(bo, place, &bman_res->base);//初始化ttm_resource
	INIT_LIST_HEAD(&bman_res->blocks);
	bman_res->mm = mm;

	if (place->flags & TTM_PL_FLAG_TOPDOWN)
		bman_res->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;

	if (place->fpfn || lpfn != man->size)
		bman_res->flags |= DRM_BUDDY_RANGE_ALLOCATION;

	GEM_BUG_ON(!bman_res->base.num_pages);
	size = bman_res->base.num_pages << PAGE_SHIFT; //bo 对应的buffer 大小

	min_page_size = bman->default_page_size;
	if (bo->page_alignment)
		min_page_size = bo->page_alignment << PAGE_SHIFT;

     .......
	err = drm_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
				     (u64)lpfn << PAGE_SHIFT,
				     (u64)n_pages << PAGE_SHIFT,
				     min_page_size,
				     &bman_res->blocks,//drm buddy 分配的block 会加入到这个连表中
				     bman_res->flags);/*类似于伙伴系统 根据n_pages 算出
               order 然后从drm buddy 中的free_list连表中获取block。 block 包含了start page number 和end page number。这里并没有直接分配物理page, 这是记录了page number。
               根据这个number 在i915 get_pages 时候真正获取page*/
     ...........

	*res = &bman_res->base;
	return 0;
    ............
    
}
int drm_buddy_alloc_blocks(struct drm_buddy *mm,
			   u64 start, u64 end, u64 size,
			   u64 min_page_size,
			   struct list_head *blocks,
			   unsigned long flags)
{
     ......

	pages = size >> ilog2(mm->chunk_size);
	order = fls(pages) - 1;
	min_order = ilog2(min_page_size) - ilog2(mm->chunk_size);

	do {
		order = min(order, (unsigned int)fls(pages) - 1);
		BUG_ON(order > mm->max_order);
		BUG_ON(order < min_order);

		do {
			if (flags & DRM_BUDDY_RANGE_ALLOCATION)
				/* Allocate traversing within the range */
				block = alloc_range_bias(mm, start, end, order);
			else
				/* Allocate from freelist */
				block = alloc_from_freelist(mm, order, flags);//从free list 连表中获取

			if (!IS_ERR(block))
				break;

			if (order-- == min_order) {
				err = -ENOSPC;
				goto err_free;
			}
		} while (1);

		mark_allocated(block);
		mm->avail -= drm_buddy_block_size(mm, block);
		kmemleak_update_trace(block);
		list_add_tail(&block->link, &allocated);

		pages -= BIT(order);

		if (!pages)
			break;
	} while (1);

	list_splice_tail(&allocated, blocks);//最后将分配的block 所在的连表加入到i915_ttm_buddy_resource中的blocks 连表中。
	return 0;

}
    

以上过程并没有真正分配page,只是分配好了obj结构,初始化了i915_ttm_buddy_resource,这个初始化了i915_ttm_buddy_resource中的block 保存了obj 要使用的page 的page number。 真正需要使用物理存的时候根据这个page number 去分配出来调用get_pages 获取真正的pages。

由ttm 维护pages 相关操作函数

static const struct drm_i915_gem_object_ops i915_gem_ttm_obj_ops = {
	.name = "i915_gem_object_ttm",
	.flags = I915_GEM_OBJECT_IS_SHRINKABLE |
		 I915_GEM_OBJECT_SELF_MANAGED_SHRINK_LIST,

	.get_pages = i915_ttm_get_pages,
	.put_pages = i915_ttm_put_pages,
	.truncate = i915_ttm_truncate,
	.shrink = i915_ttm_shrink,

	.adjust_lru = i915_ttm_adjust_lru,
	.delayed_free = i915_ttm_delayed_free,
	.migrate = i915_ttm_migrate,

	.mmap_offset = i915_ttm_mmap_offset,
	.unmap_virtual = i915_ttm_unmap_virtual,
	.mmap_ops = &vm_ops_ttm,
};
static int __i915_ttm_get_pages(struct drm_i915_gem_object *obj,
				struct ttm_placement *placement)
{
    ........
	if (!i915_gem_object_has_pages(obj)) { //判断obj 是不是使用system 如果使用local 获取散列表
		struct i915_refct_sgt *rsgt =
			i915_ttm_resource_get_st(obj, bo->resource); //获取物理pages 散列表
                .........
		obj->mm.rsgt = rsgt;
		__i915_gem_object_set_pages(obj, &rsgt->table,
					    i915_sg_dma_sizes(rsgt->table.sgl));
                             //将散列表设置给obj,对应的获取这个sg 散列表的函数是__i915_gem_object_get_sg
	}

	GEM_BUG_ON(bo->ttm && ((obj->base.size >> PAGE_SHIFT) < bo->ttm->num_pages));
	i915_ttm_adjust_lru(obj);
	return ret;
}

i915_rsgt_from_buddy_resource 函数根据drm buddy 算出需要的sg 散列表, region_start 这显存的region 是0. 这里算出来只是相对偏移。 最后真正的地址是region iomap 存放的pci bar 开始地址 +偏移。

struct i915_refct_sgt *i915_rsgt_from_buddy_resource(struct ttm_resource *res,
						     u64 region_start,
						     u32 page_alignment)
{
    struct list_head *blocks = &bman_res->blocks; //在obj init 时候从ttm buddy管理中分配block 加入到了blocks中
    ....
    
	st = &rsgt->table;
	if (sg_alloc_table(st, res->num_pages, GFP_KERNEL)) {
		i915_refct_sgt_put(rsgt);
		return ERR_PTR(-ENOMEM);
	}

	sg = st->sgl;
	st->nents = 0;
	prev_end = (resource_size_t)-1;

	list_for_each_entry(block, blocks, link) { // 获取每个drm_buddy_block 然后算出offset 这里的offset 就是page number
            u64 block_size, offset;

            block_size = min_t(u64, size, drm_buddy_block_size(mm, block));
            offset = drm_buddy_block_offset(block);

            while (block_size) {
                    u64 len;

                    if (offset != prev_end || sg->length >= max_segment) {
                            if (st->nents)
                                    sg = __sg_next(sg);

                            sg_dma_address(sg) = region_start + offset;
                            GEM_BUG_ON(!IS_ALIGNED(sg_dma_address(sg),
                                                   page_alignment));
                            sg_dma_len(sg) = 0;
                            sg->length = 0;
                            st->nents++;
                    }

                    len = min_t(u64, block_size, max_segment - sg->length);
                    sg->length += len;
                    sg_dma_len(sg) += len;

                    offset += len;
                    block_size -= len;

                    prev_end = offset;
            }
	}

	sg_mark_end(sg);
	i915_sg_trim(st);

	return rsgt;
}

用户态map 一个bo(local memory)

mesa 中map 一个obj 是通过gem fd 加offset来map

   /* And map it */
   void *map = mmap(0, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED,
                    bufmgr->fd, mmap_arg.offset);
   if (map == MAP_FAILED) {

mmap 会调用 i915_gem_mmap

if (obj->ops->mmap_ops) { //使用ttm 作为obj 管理的都有mmap ops
    vma->vm_page_prot = pgprot_decrypted(vm_get_page_prot(vma->vm_flags));
    vma->vm_ops = obj->ops->mmap_ops;
    vma->vm_private_data = node->driver_private; //ttm bo
    return 0;
}

vma->vm_private_data = mmo;

switch (mmo->mmap_type) { //没有使用ttm 的仍然使用旧的方式
case I915_MMAP_TYPE_WC:
        vma->vm_page_prot =
                pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
        vma->vm_ops = &vm_ops_cpu;
        break;

case I915_MMAP_TYPE_FIXED:
        GEM_WARN_ON(1);
        fallthrough;
case I915_MMAP_TYPE_WB:
        vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
        vma->vm_ops = &vm_ops_cpu;
        break;

case I915_MMAP_TYPE_UC:
        vma->vm_page_prot =
                pgprot_noncached(vm_get_page_prot(vma->vm_flags));
        vma->vm_ops = &vm_ops_cpu;
        break;

case I915_MMAP_TYPE_GTT:
        vma->vm_page_prot =
                pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
        vma->vm_ops = &vm_ops_gtt;
        break;
}
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);

i915 ttm 管理的buffer实现了mmap_ops 函数

static const struct vm_operations_struct vm_ops_ttm = {
	.fault = vm_fault_ttm,
	.access = vm_access_ttm,
	.open = ttm_vm_open,
	.close = ttm_vm_close,
};

map 读写没有映射的page 会发生缺页异常调用fault

static vm_fault_t vm_fault_ttm(struct vm_fault *vmf)
{
	struct vm_area_struct *area = vmf->vma;
	struct ttm_buffer_object *bo = area->vm_private_data;
	struct drm_device *dev = bo->base.dev;
	struct drm_i915_gem_object *obj;
	intel_wakeref_t wakeref = 0;
	vm_fault_t ret;
	int idx;

	obj = i915_ttm_to_gem(bo);
.........

	if (drm_dev_enter(dev, &idx)) {
		ret = ttm_bo_vm_fault_reserved(vmf, vmf->vma->vm_page_prot,
					       TTM_BO_VM_NUM_PREFAULT); //local memory
		drm_dev_exit(idx);
	} else {
		ret = ttm_bo_vm_dummy_page(vmf, vmf->vma->vm_page_prot); 
	}

    ......
}

vm_fault_ttm->ttm_bo_io_mem_pfn->i915_ttm_io_mem_pfn

static unsigned long i915_ttm_io_mem_pfn(struct ttm_buffer_object *bo,
					 unsigned long page_offset)
{
	struct drm_i915_gem_object *obj = i915_ttm_to_gem(bo);
	struct scatterlist *sg;
	unsigned long base;
	unsigned int ofs;

	GEM_BUG_ON(!obj);
	GEM_WARN_ON(bo->ttm);

	base = obj->mm.region->iomap.base - obj->mm.region->region.start;//pic bar 读取io start 地址
	sg = __i915_gem_object_get_sg(obj, &obj->ttm.get_io_page, page_offset, &ofs, true); //从物理散列表获取offet 就是前面i915_rsgt_from_buddy_resource 保存的相对地址

	return ((base + sg_dma_address(sg)) >> PAGE_SHIFT) + ofs;
}

内核态map 一个bo

i915_gem_object_pin_map->i915_gem_object_map_pfn 函数实现了内存映射一个obj 访问 同样是iomap + 物理机散列表sg 组合