AMD GPU任务调度(1)—— 用户态分析

文章目录

  • 硬件基础
    • 命令发送模式
    • Ring Buffer
    • Buffer Type
  • 数据结构
    • agmdgpu驱动关系图
    • drm_radeon_cs_chunk
      • AMDGPU_CHUNK_ID_IB
      • AMDGPU_CHUNK_ID_FENCE
    • amdgpu_cs_context
    • radeon_cmdbuf_chunk
    • radeon_cmdbuf
    • amdgpu_ib
    • radeon_drm_cs
    • radeon驱动关系图
  • 流程
    • 流程图
    • 创建上下文
    • 填写命令
    • 下发命令

硬件基础

命令发送模式

  • GPU接收CPU发送的渲染命令,执行相应的计算,渲染命令在CPU和GPU之间传递,由CPU发送给GPU。AMD的GPU有两种命令发送方式,第一种是CPU通过直接写GPU的寄存器,发送相应的渲染命令,对于GPU来说,这种方式是CPU将命令push给自己,然后开始工作,被称为push模式;第二种是系统初始化时CPU在主存上分配一块内存,用作存放GPU的渲染命令,当CPU有渲染需求时就往这块内存写命令并通知GPU读取,这种方式对GPU来说,需要主动去主存上把渲染命令pull过来,然后开始工作,被称为pull模式。通常情况下AMD都建议使用pull模式,只有系统内存不适合pull模式时采用push模式。本文分析的是pull模式,这是linux AMD GPU驱动的工作方式。

Ring Buffer

  • pull方式下,系统初始化时CPU在主存上开辟一段内存空间,并与GPU“协商”,将这块内存空间作为存取渲染命令的空间。CPU和GPU都可以访问这段空间,其中CPU作为生产者负责写入渲染命令,GPU作为消费者负责读取渲染命令并执行。数据共享不可避免地会有同步问题,当数据空间只有很小一块时,如果生产者和消费者都在操作这块空间,为保证数据一致就必须要加锁了。为了避免这种情况,共享空间应该尽量大,并且避免生产者和消费者操作同一块空间。生产者在前准备好数据后,就往下一个空闲的空间写入,之前写入的空间要在消费者读取之后再重新填入内容。而消费者需要按照生产者的顺序读取数据。
  • Ring Buffer就是出于上述考虑设计的。Host驱动初始化时在系统上分配一大块Buffer,可以想象成开辟了一个数组空间,并告知GPU硬件这个地址。CPU和GPU同时维护4个状态,内存起始地址,内存大小,读偏移和写偏移。当Host驱动需要发送渲染请求时,就向这段Buffer空间写入渲染命令,更新CPU和GPU的写偏移地址,然后往Buffer的下一个空闲空间写数据。GPU硬件收到有渲染命令到达时从约定的Buffer读取渲染命令,并更新CPU和GPU的读偏移地址。GPU侧读数据和Host的写数据的方向相同。当整个Buffer空间满了之后,又从Buffer的起始处开始读写数据,又重复之前的动作。读写侧的这种行为,都是在反复的对一段内存空间来回写入和读出,循环操作,这个Buffer空间就像一个环一样,这个空间就是著名的Ring Buffer。
  • CPU和GPU的渲染命令传递就通过Ring Buffer来实现。整个过程如下图所示。下图截自AMD GPU手册。
    AMD GPU任务调度(1)—— 用户态分析_第1张图片

Buffer Type

  • AMD GPU提供了Ring Buffer的方式传递渲染命令,这里的Ring Buffer空间可以称作Primary Buffer。除此以外,GPU还提供了Indirect Buffer的方式传递渲染命令。
  • GPU中有四个寄存器,分别如下:
  1. CP_IB_BASE:存放Indirect Buffer的内存起始地址,由Host驱动负责写入,它指向的是一段存放了渲染命令的内存空间
  2. CP_IB_BUFSZ:存放Indirect Buffer的内存大小,由Host驱动负责写入,它存放的是CP_IB_BASE指向空间的大小
  3. CP_IB2_BASE:存放Indirect Buffer2的内存起始地址,由Host驱动负责写入,它指向的是一段存放了渲染命令的内存空间
  4. CP_IB2_BUFSZ:存放Indirect Buffer2的内存大小,由Host驱动负责写入,它存放的是CP_IB2_BASE指向空间的大小
  • 以上四个寄存器是Indirect Buffer传递命令的基础,我们知道GPU提供的渲染命令有4种,可以分为两类,一类是正常的渲染命令,另一类是写寄存器的命令,也就是说,CPU可以把写寄存器的命令作为内容写入到Ring Buffer上让GPU执行。
  • 对于AMD R5xx Family的GPU,Host驱动使用Indirect Buffer传递渲染命令大大致步骤如下:
  1. Host驱动准备一段内存空间并填入想要执行的渲染命令,将这段空间的地址IB_ADDR写入GPU的CP_IB_BASE寄存器。
  2. Host向Primary Buffer写入普通的渲染命令之后,如果想要让GPU从IB_ADDR读取渲染命令并执行,就添加写寄存器CP_IB_BUFSZ的命令,作为内容写入Primary Buffer。当GPU在Primary Buffer中读取到这样一个命令并执行相应的寄存器写动作时,硬件会触发GPU去IB_ADDR读取渲染命令并执行,当读取了CP_IB_BUFSZ长度的内容后,GPU会再返回Primary Buffer中读取命令继续执行。
  3. GPU从Indirect Buffer中读取渲染命令,如果在这段内存的最后,有另一条写CP_IB2_BUFSZ的命令,那么GPU会继续跳转到CP_IB2_BASE指向的内存读取渲染指令,执行完成之后再返回Primary Buffer。
  • 对于AMD R6xx/R7xx Family的GPU,Host驱动使用Indirect Buffer传递渲染命令的步骤大大简化,它专门提供一个操作码为0x32的type3类型的packet,用于触发GPU去Indirect Buffer处取渲染指令,packet的IT_BODY格式如下:
    AMD GPU任务调度(1)—— 用户态分析_第2张图片
  • GPU过Primary Buffer的指示,跳转到别处取渲染命令执行,Indirect Buffer因此得命令,整个过程如下图所示:
    AMD GPU任务调度(1)—— 用户态分析_第3张图片
  • AMD的GPU命令中,type0和type1都是写寄存器的命令,因此可以用作触发Indirect Buffer的内容,type3是普通的渲染命令。如上图所示,当GPU解析到cmd pkg 5时,假设这个命令就是一个写CP_IB_BUFSZ寄存器的命令,那么它就会触发GPU跳转到CP_IB_BASE指向的地方去渲染命令,然后执行。注意,上面的描述只针对AMD R5xx Family,对于R6xx/R7xx Family,直接从type3类型的packet中解析即可触发Indirect Buffer的行为。

数据结构

agmdgpu驱动关系图

AMD GPU任务调度(1)—— 用户态分析_第4张图片

drm_radeon_cs_chunk

  • 该结构体是用户态程序下发渲染命令到内核的桥梁,所有命令都放在这个结构体里面统一下发下去。
struct drm_amdgpu_cs_chunk {
    __u32       chunk_id;				/* 1 */
    __u32       length_dw;				/* 2 */
    __u64       chunk_data;				/* 3 */
};
1. 缓存类型,用于提示内核态应该怎样解析这条命令换成,当用户态驱动要下发渲染命令给内核时,它已经将渲染命令存放到了一段内存空间,这段内存空间可以有不同类型,可以有以下这些:
#define AMDGPU_CHUNK_ID_IB		0x01
#define AMDGPU_CHUNK_ID_FENCE		0x02
#define AMDGPU_CHUNK_ID_DEPENDENCIES	0x03
#define AMDGPU_CHUNK_ID_SYNCOBJ_IN      0x04
#define AMDGPU_CHUNK_ID_SYNCOBJ_OUT     0x05
#define AMDGPU_CHUNK_ID_BO_HANDLES      0x06
2. 命令长度,单位是dw,double word,4字节为单位
3. 命令内容指针,存放一条渲染命令,对于不同的数据,chunk_data指向的内容不一样。

AMDGPU_CHUNK_ID_IB

  • drm_amdgpu_cs_chunk_ib,该结构体对应最常用的IB缓存类型,chunk类型为AMDGPU_CHUNK_ID_IB,它描述了一个IB空间。
struct drm_amdgpu_cs_chunk_ib {
	__u32 _pad;
	/** AMDGPU_IB_FLAG_* */
	__u32 flags;
	/** Virtual address to begin IB execution */
	__u64 va_start;								/* 1 */
	/** Size of submission */
	__u32 ib_bytes;								/* 2 */
	/** HW IP to submit to */
	__u32 ip_type;								/* 3 */
	/** HW IP index of the same type to submit to  */
	__u32 ip_instance;				
	/** Ring index to submit to */
	__u32 ring;									/* 4 */
};
1. IB空间的起始地址,它是虚拟的,该地址GPU是可以访问的
2. IB空间的大小
3. IB中的命令针对的是哪个IP核
4. 一个IP核上可能有多个ring buffer,这个成员指示IB提交到的是哪一个ring buffer

AMDGPU_CHUNK_ID_FENCE

struct drm_amdgpu_cs_chunk_fence {
__u32 handle;
__u32 offset;
};

amdgpu_cs_context

  • 该结构是drm_amdgpu_cs_chunk_ib的封装,drm_radeon_cs_chunk_ib的结构更接近内核侧,但上层驱动模块使用不方便,因此设计了drm_amdgpu_cs_chunk_ib,它包含了各种结构类型的渲染命令缓存
struct amdgpu_cs_context {
   struct drm_amdgpu_cs_chunk_ib ib[IB_NUM];		/* 1 */

   /* Buffers. */
   unsigned                    max_real_buffers;
   unsigned                    num_real_buffers;
   struct amdgpu_cs_buffer     *real_buffers;		/* 2 */
   ......
}
1. 指向ib类型的chunk
2. 指向buffer object类型的渲染命令缓存

radeon_cmdbuf_chunk

为上层驱动提供GPU命令字的中间结构,它的核心成员是buf,指向上面radeon_cs_context结构体的buf成员

struct radeon_cmdbuf_chunk {
   /* Number of used dwords. */
   unsigned cdw;   							/* 1 */
   /* Maximum number of dwords. */
   unsigned max_dw; 						/* 2 */
   /* The base pointer of the chunk. */
   uint32_t *buf;   						/* 3 */
};
1. 上层应用每添加一个双字节命令,cdw就加1,记录当前buf空间已经添加的GPU命令字
2. 记录buf空间总大小
3. 指向真正存放渲染命令的内存空间,amdgpu_ib结构的ib_mapped成员,这是一个从内核分配的buffer object

radeon_cmdbuf

暴露给上层硬件驱动的渲染命令缓冲对象,当上层驱动想要发送GPU命令时,它分配一个这样的结构体,然后往里面填入自己要发送的GPU命令。

struct radeon_cmdbuf {
   struct radeon_cmdbuf_chunk current;										/* 1 */
   struct radeon_cmdbuf_chunk *prev;
   unsigned num_prev; /* Number of previous chunks. */						
   unsigned max_prev; /* Space in array pointed to by prev. */
   unsigned prev_dw;  /* Total number of dwords in previous chunks. */
   ......
};
1. 当前使用的命令缓冲对象

amdgpu_ib

struct amdgpu_ib {
   struct radeon_cmdbuf base;								/* 1 */

   /* A buffer out of which new IBs are allocated. */
   struct pb_buffer        *big_ib_buffer;					/* 2 */
   uint8_t                 *ib_mapped;						/* 3 */
   unsigned                used_ib_space;					/* 4 */
   ......
};
1. 供上层驱动使用的cmdbuf,它实际上是一个指针,指向amdgpu_ib的ib_mapped区域,上层GPU的IP驱动不需要知道底层amdgpu驱动分配cmdbuf具
体形式,因此cmdbuf真正的空间在别处,base.current.buf实际上只是一个指针
2. 通过调用内核的GEM API申请的一大块内存,专门用作indirect_buffer,这段内存通过amdgpu_bo_create申请,存在于GTT中(Graphics Translation Table),这是可以被GPU直接访问的Host内存,申请这个内存之后,返回的是一个pb_buffer对象,需要映射之后才能获得访问该内存的地址。最终访
问这段地址空间的地址保存在ib_mapped中
3. big_ib_buffer对应内存的起始地址,当上层驱动要写入渲染命令时,最终写入到这个地址中,它被base.current.buf引用
4. big_ib_buffer内存空间中已经使用的容量,big_ib_buffer的总大小可以通过它的size成员获取

radeon_drm_cs

  • GPU硬件单元渲染命令上下文
struct amdgpu_cs {
   /* must be first because this is inherited */
   struct amdgpu_ib main;										/* 1 */
   ......
   enum ring_type ring_type;									/* 2 */
   ......	
   /* We flip between these two CS. While one is being consumed
    * by the kernel in another thread, the other one is being filled
    * by the pipe driver. */
   struct amdgpu_cs_context csc1;								/* 3 */
   struct amdgpu_cs_context csc2;
   /* The currently-used CS. */
   struct amdgpu_cs_context *csc;
   /* The CS being currently-owned by the other thread. */
   struct amdgpu_cs_context *cst;
   ......
}
1. 指向要交给GPU硬件单元处理的渲染命令缓冲区
2. GPU由多个硬件处理单元组成(IP核),任何一条GPU的渲染命令,其最终都是发送到GPU的硬件单元上,硬件单元可以处理一些通用的渲染
命令,也可能处理一些专有渲染命令,因此一条GPU渲染命令需要指明它发往的硬件单元。实际上,mesa驱动针对GPU的每个硬件单元都抽象了
具体的结构,当硬件单元驱动想给自己对应的硬件发送GPU命令时,就需要设置硬件的类型。就是这里的ring_type。为什么叫ring_type呢,因为
每个硬件处理单元和CPU之间时通过ring buffer传递渲染命令的,每个硬件单元上都有一个或者多个ring buffer,所以这里的ring_type就是GPU硬
件单元的类型。mesa中抽象的GPU硬件单元有以下几种:
enum ring_type {
    RING_GFX = 0,			/* 显示器单元 */
    RING_COMPUTE,			/* 计算单元 */
    RING_DMA,				/* DMA控制器 */
    RING_UVD,				/* Unified Video Decoder */
    RING_VCE,				......
    RING_UVD_ENC,
    RING_VCN_DEC,
    RING_VCN_ENC,
    RING_VCN_JPEG,
};
3. 为了提高驱动下发GPU命令的能力,amdgpu_cs定义了两个命令缓冲上下文,当要下发给内核的GPU渲染命令多时,可能一个上下文中的
一批命令还没有处理完。下一个上下文的GPU命令又来了,传统的情况下,由于GPU硬件在同一时间内只能处理一个上下文的命令,那么下一个
上下文的GPU命令必须等当前上下文命令执行完毕,才能下发。当前上下文命令执行完之后,驱动开始装填GPU命令,然后切换上下文,如果
GPU命令很多的话,占用了GPU上下文但是装填GPU命令的时间开销会增大。实际上这部分操作可以提前做,当前上下文的GPU命令还在执行
时,我们可以提前装填好命令到下一个要执行的GPU上下文。等到当前上下文的GPU命令执行完之后,我们直接切换指针,然后下发GPU命令,
这样就能提高驱动下发GPU命令的能力。
cs1,cs2,csc,cst这几个成员,就是实现这个功能的:csc表示当前使用的context,cst表示备用的context。初始化时csc->csc1,cst->csc2,
当上层驱动填写命令完毕发起flush时,如果发现当前context仍然有命令在处理,就会切换到备用的cst,填充命令并下发。

radeon驱动关系图

  • radeon驱动是amd GPU驱动的另外一个版本,上面没有介绍,保留这个图示备忘
    AMD GPU任务调度(1)—— 用户态分析_第5张图片

流程

  • 渲染命令的下发分为三步,首先硬件驱动创建一个命令提交上下文,其中包含buffer空间的分配,然后是往buffer里面填写渲染命令,最后ioctl命令下发到内核,其中命令的添加由上层驱动负责,上下文创建和命令的提交由amdgpu驱动负责,我们分别介绍这三步的主要动作。其中上下文创建和提交函数如下:
void amdgpu_cs_init_functions(struct amdgpu_screen_winsys *ws)
{  
	.......
	ws->base.cs_create = amdgpu_cs_create;
    ws->base.cs_flush = amdgpu_cs_flush;
    ......
}     

流程图

  • TODO

创建上下文

  • 创建上下文的主要目的就是分配命令缓存,它返回给上层硬件驱动模块一个radeon_cmdbuf,驱动模块通过此cmdbuf填写命令,分配的命令缓存就是一块IB,对于amdgpu的驱动,它的命令缓存是放在Indrect Buffer中的,而在radeon驱动的实现中,它的命令缓存放在Primary Buffer中的。这里介绍amdgpu驱动,流程如下:
static struct radeon_cmdbuf *
amdgpu_cs_create(struct radeon_winsys_ctx *rwctx,
                 enum ring_type ring_type,
                 void (*flush)(void *ctx, unsigned flags,
                               struct pipe_fence_handle **fence),
                 void *flush_ctx,
                 bool stop_exec_on_failure)
{
   struct amdgpu_ctx *ctx = (struct amdgpu_ctx*)rwctx;
   struct amdgpu_cs *cs;

   cs = CALLOC_STRUCT(amdgpu_cs);								/* 1 */
   util_queue_fence_init(&cs->flush_completed);
   cs->ctx = ctx;
   cs->flush_cs = flush;
   cs->flush_data = flush_ctx;
   cs->ring_type = ring_type;
   cs->stop_exec_on_failure = stop_exec_on_failure;

   cs->main.ib_type = IB_MAIN;									/* 2 */					
   cs->compute_ib.ib_type = IB_PARALLEL_COMPUTE;

   amdgpu_init_cs_context(ctx->ws, &cs->csc1, ring_type)
   amdgpu_init_cs_context(ctx->ws, &cs->csc2, ring_type)

   /* Set the first submission context as current. */		
   cs->csc = &cs->csc1;											/* 3 */
   cs->cst = &cs->csc2;

   amdgpu_get_new_ib(ctx->ws, cs, IB_MAIN)						/* 4 */

   return &cs->main.base;										/* 5 */
}
1. 分配一个命令提交的上下文,命令缓存关联这个上下文,最终返回这个上下文包含的命令缓存,初始化上下文的各个成员
2. 初始化上下文包含的两个基本的缓存IB,设置其类型
3. 初始化当前命令提交上下文和备用的命令提交上下文,如前所述,为了增强应用程序下发命令的能力,用户态驱动设计了两个命令提交上下文,
当前的上下文指针在两个上下文之间切换,当一个上下文被内核读取时可以用另外一个
4. 为上下文中的命令缓存分配空间,返回给上层驱动后,驱动可以往此空间填写命令
5. 返回上下文的cmdbuf,对于整个命令提交上下文,上层的驱动命令不关注,因此只需要返回上层命令关注的缓存就可以了
  • 跟踪amdgpu_get_new_ib函数,分析具体的空间分配过程
static bool amdgpu_get_new_ib(struct amdgpu_winsys *ws, struct amdgpu_cs *cs,
                              enum ib_type ib_type)
{
   /* Small IBs are better than big IBs, because the GPU goes idle quicker
    * and there is less waiting for buffers and fences. Proof:
    *   http://www.phoronix.com/scan.php?page=article&item=mesa-111-si&num=1
    */
   /* This is the minimum size of a contiguous IB. */
   unsigned ib_size = 4 * 1024 * 4;											/* 6 */

   switch (ib_type) {														/* 7 */
   case IB_PARALLEL_COMPUTE:
      ib = &cs->compute_ib;
      break;
   case IB_MAIN:
      ib = &cs->main;
      break;
   }

   ib->max_ib_size = ib->max_ib_size - ib->max_ib_size / 32;				/* 8 */
   ib->base.prev_dw = 0;
   ib->base.num_prev = 0;
   ib->base.current.cdw = 0;
   ib->base.current.buf = NULL;

   /* Allocate a new buffer for IBs if the current buffer is all used. */
   if (!ib->big_ib_buffer ||												/* 9 */
       ib->used_ib_space + ib_size > ib->big_ib_buffer->size) {
      if (!amdgpu_ib_new_buffer(ws, ib, cs->ring_type))
         return false;
   }
   ......
   amdgpu_cs_add_buffer(&cs->main.base, ib->big_ib_buffer,					/* 10 */
                        RADEON_USAGE_READ, 0, RADEON_PRIO_IB1);
   ib->base.current.buf = (uint32_t*)(ib->ib_mapped + ib->used_ib_space);	/* 11 */
   ib_size = ib->big_ib_buffer->size - ib->used_ib_space;					/* 12 */
   ib->base.current.max_dw = ib_size / 4 - amdgpu_cs_epilog_dws(cs);
   ......
}
6. 设置默认分配的空间大小为16K
7. 命令缓存上下文结构体amdgpu_cs中有两个ib成员,分别是main核parallel compute,解析调用者是要为哪个ib分配空间
8. 初始化ib的结构体
9. 从之前的结构体分析中,我们知道,ib中有一个结构体big_ib_buffer,它在ib初始化的时候像内核申请一大块内存,然后慢慢细分慢慢用,这样避免了
 多次系统调用,这里我们获取ib就是从这个空间中查看有无空闲的空间,如果有直接使用,如果没有或者big_ib_buffer根本还没有申请,那么就通过
 GEM系统调用从内核先分配大块内存,这里是上下文的初始化,显示是第一次,因此big_ib_buffer肯定不存在需要从内核分配。但下一次获取ib时就可
 以使用现的了。
 10. 往结构体radeon_cmdbuf中添加分配好的big_ib_buffer,这样就将用户使用的radeon_cmdbuf设置指向到我们分配的内存中了
 11. 设置radeon_cmdbuf的指针,指向big_ib_buffer空间的起始地址
 12. 根据big_ib_buffer的大小,设置radeon_cmdbuf的空间大小

填写命令

  • 上层硬件模块拥有cmdbuf之后,如果想要填写渲染命令,直接往cmdbuf中写入渲染命令就可以了,这里我们以一个上层的GPU IP驱动函数si_emit_draw_packets为例,简单介绍:
si_emit_draw_packets
	radeon_emit(cs, PKT3(PKT3_INDEX_TYPE, 0, 0))									/* 1 */
		cs->current.buf[cs->current.cdw++] = value;									/* 2 */
1. 当硬件驱动模块要往中写入渲染命令时,直接调用radeon_emit就可以了,从这里看,硬件驱动想要内核发送一个type 3类型的渲染命令,当然
这个渲染命令只是用户用户态和内核态交流,只需要内核的IB解析器理解就可以了,IB解析器理解之后再转换成真正的渲染命令
2. 往buf中填写渲染命令,从amdgpu_cs创建的流程分析,amdgpu_cs_context会初始化它的两个IB成员,然后通过amdgpu_get_new_ib为Main 
IB分配空间,完成之后,就会让cmdbuf中的buf指针指向这个Main IB的空间,所以,上层应用写入的渲染命令,最终会放到Main IB然后下发

下发命令

static int amdgpu_cs_flush(struct radeon_cmdbuf *rcs,
                           unsigned flags,
                           struct pipe_fence_handle **fence)
{
   struct amdgpu_cs *cs = amdgpu_cs(rcs);

   switch (cs->ring_type) {
   case RING_DMA:																/* 1 */
      /* pad DMA ring to 8 DWs */
      if (ws->info.chip_class <= GFX6) {
         while (rcs->current.cdw & 7)
            radeon_emit(rcs, 0xf0000000); /* NOP packet */
      }
      break;
   case RING_GFX:
   case RING_COMPUTE:
   ......
   }

   if (rcs->current.cdw > rcs->current.max_dw) {								/* 2 */
      fprintf(stderr, "amdgpu: command stream overflowed\n");
   }

   /* If the CS is not empty or overflowed.... */								
   if (likely(radeon_emitted(&cs->main.base, 0) &&
       cs->main.base.current.cdw <= cs->main.base.current.max_dw &&
       !debug_get_option_noop())) {
      struct amdgpu_cs_context *cur = cs->csc;									/* 3 */
      amdgpu_cs_sync_flush(rcs);												/* 4 */

      /* Prepare buffers.
       *
       * This fence must be held until the submission is queued to ensure
       * that the order of fence dependency updates matches the order of
       * submissions.
       */
      simple_mtx_lock(&ws->bo_fence_lock);										/* 5 */
      amdgpu_add_fence_dependencies_bo_lists(cs);

      /* Swap command streams. "cst" is going to be submitted. */
      cs->csc = cs->cst;														/* 6 */
      cs->cst = cur;

      /* Submit. */																/* 7 */
      util_queue_add_job(&ws->cs_queue, cs, &cs->flush_completed,
                         amdgpu_cs_submit_ib, NULL, 0);
      /* The submission has been queued, unlock the fence now. */
      simple_mtx_unlock(&ws->bo_fence_lock);									

   amdgpu_get_new_ib(ws, cs, IB_MAIN);											/* 8 */
   ......
}
1. 不同IP核上的渲染命令,对齐要求不同,按照要求补齐
2. 如果当前渲染命令长度超过最大长度,打印报错,但本次的命令需要继续提交
3. 获取current提交上下文,amdgpu_cs_context
4. 确保前一次提交已经完成,[待分析]
5. 锁保护,[待分析]
6. 在提交渲染命令前,将current和thread的CS互换,提交的时候通过thread CS下发命令
7. 将渲染命令所在的IB提交
8. 渲染命令提交后,重新分配IB,[待分析]
  • 最后分析提交IB动作
void amdgpu_cs_submit_ib(void *job, int thread_index)
{
   struct amdgpu_cs *acs = (struct amdgpu_cs*)job;
   struct amdgpu_cs_context *cs = acs->cst;													/* 9 */
   struct drm_amdgpu_bo_list_in bo_list_in; 
   ......                  
   struct drm_amdgpu_bo_list_entry *list =													/* 10 */
         alloca((cs->num_real_buffers + 2) * sizeof(struct drm_amdgpu_bo_list_entry));

   unsigned num_handles = 0;
   for (i = 0; i < cs->num_real_buffers; ++i) {												/* 11 */
      struct amdgpu_cs_buffer *buffer = &cs->real_buffers[i];								
      list[num_handles].bo_handle = buffer->bo->u.real.kms_handle;							
      list[num_handles].bo_priority = (util_last_bit(buffer->u.real.priority_usage) - 1) / 2;
      ++num_handles;																		
   }
   ......
   /* Standard path passing the buffer list via the CS ioctl. */				
   bo_list_in.operation = ~0;																/* 12 */
   bo_list_in.list_handle = ~0;
   bo_list_in.bo_number = num_handles;
   bo_list_in.bo_info_size = sizeof(struct drm_amdgpu_bo_list_entry);
   bo_list_in.bo_info_ptr = (uint64_t)(uintptr_t)list;
   ......
   struct drm_amdgpu_cs_chunk chunks[6];
   chunks[num_chunks].chunk_id = AMDGPU_CHUNK_ID_BO_HANDLES;								/* 13 */
   chunks[num_chunks].length_dw = sizeof(struct drm_amdgpu_bo_list_in) / 4;
   chunks[num_chunks].chunk_data = (uintptr_t)&bo_list_in;
   num_chunks++;
   ......
   amdgpu_cs_submit_raw2(ws->dev, acs->ctx->ctx, bo_list,									/* 14 */
                         num_chunks, chunks, &seq_no);
9. 获取thread提交上下文,声明一个bo_list_in的对象,用户装填渲染命令
10. 创建链表list,用来存放提交上下中的缓存,个数是num_real_buffers+2个
11. 从amdgpu_cs_context上下文中依次取出所有的real_buffer,然后放到list中,这里的主要动作就是传递bo_handle,可以把它想象成是fd,内
核通过这个handle可以访问到对应内存
12. 初始化bo_list_in并将其指向之前由list组织起来的real_buffer中
13. 将所有搜集的数据放到drm_amdgpu_cs_chunk中,这是最终传递渲染命令给内核时用到的数据结构
14. 通过amdgpu_cs_submit_raw2函数提交所有数据结构到内核,这个函数是amdgpu驱动用户态共享库中的函数,它最终会通过内核提供的ioctl
接口下发渲染命令。从这里可以看出,GPU驱动厂商还是有封装一些GPU接口,比如这里的amdgpu_cs_submit_raw2。我们可以从驱动厂商提供的共享库/usr/lib64/dri/radeonsi_dri.so中查到这个函数符号,可以确认这个接口是由该共享库提供的,至于接口的内部具体实现就不得而知了,但这不影响我们的驱动分析,因为不管这个函数怎么实现的,它最终会调用内核的ioctl命令字DRM_IOCTL_AMDGPU_CS下发渲染命令,这个是开源流程,我们可以分析清楚

你可能感兴趣的:(GPU)