文章目录
- 硬件基础
- 命令发送模式
- Ring Buffer
- Buffer Type
- 用户态任务提交
- 数据结构
- 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
- amdgpu驱动关系图
- 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手册。
Buffer Type
- AMD GPU提供了Ring Buffer的方式传递渲染命令,这里的Ring Buffer空间可以称作Primary Buffer。除此以外,GPU还提供了Indirect Buffer的方式传递渲染命令。
- GPU中有四个寄存器,分别如下:
- CP_IB_BASE:存放Indirect Buffer的内存起始地址,由Host驱动负责写入,它指向的是一段存放了渲染命令的内存空间
- CP_IB_BUFSZ:存放Indirect Buffer的内存大小,由Host驱动负责写入,它存放的是CP_IB_BASE指向空间的大小
- CP_IB2_BASE:存放Indirect Buffer2的内存起始地址,由Host驱动负责写入,它指向的是一段存放了渲染命令的内存空间
- CP_IB2_BUFSZ:存放Indirect Buffer2的内存大小,由Host驱动负责写入,它存放的是CP_IB2_BASE指向空间的大小
- 以上四个寄存器是Indirect Buffer传递命令的基础,我们知道GPU提供的渲染命令有4种,可以分为两类,一类是正常的渲染命令,另一类是写寄存器的命令,也就是说,CPU可以把写寄存器的命令作为内容写入到Ring Buffer上让GPU执行。Host驱动使用Indirect Buffer传递渲染命令大大致步骤如下:
- Host驱动准备一段内存空间并填入想要执行的渲染命令,将这段空间的地址
IB_ADDR 写入GPU的CP_IB_BASE寄存器。 - Host向Primary Buffer写入普通的渲染命令之后,如果想要让GPU从
IB_ADDR 读取渲染命令并执行,就添加写寄存器CP_IB_BUFSZ的命令,作为内容写入Primary Buffer。当GPU在Primary Buffer中读取到这样一个命令并执行相应的寄存器写动作时,硬件会触发GPU去IB_ADDR 读取渲染命令并执行,当读取了CP_IB_BUFSZ长度的内容后,GPU会再返回Primary Buffer中读取命令继续执行。 - GPU从Indirect Buffer中读取渲染命令,如果在这段内存的最后,有另一条写CP_IB2_BUFSZ的命令,那么GPU会继续跳转到CP_IB2_BASE指向的内存读取渲染指令,执行完成之后再返回Primary Buffer。
- GPU过Primary Buffer的指示,跳转到别处取渲染命令执行,Indirect Buffer因此得命令,整个过程如下图所示:
- AMD的GPU命令中,type0和type1都是写寄存器的命令,因此可以用作触发Indirect Buffer的内容,type3是普通的渲染命令。如上图所示,当GPU解析到cmd pkg 5时,假设这个命令就是一个写CP_IB_BUFSZ寄存器的命令,那么它就会触发GPU跳转到CP_IB_BASE指向的地方去渲染命令,然后执行。
用户态任务提交
数据结构
drm_radeon_cs_chunk
- 该结构体是用户态程序下发渲染命令到内核的桥梁,所有命令都放在这个结构体里面同一下发下去。
1 2 3 4 5 | struct drm_amdgpu_cs_chunk { __u32 chunk_id; /* 1 */ __u32 length_dw; /* 2 */ __u64 chunk_data; /* 3 */ }; |
1 2 3 4 5 6 7 8 9 | 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类型为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | struct drm_amdgpu_cs_chunk_ib { __u32 _pad; /** AMDGPU_IB_FLAG_* */ __u32 flags; /** Virtual address to begin IB execution */ __u64 va_start; /** Size of submission */ __u32 ib_bytes; /** HW IP to submit to */ __u32 ip_type; /** HW IP index of the same type to submit to */ __u32 ip_instance; /** Ring index to submit to */ __u32 ring; }; |
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,它包含了各种结构类型的渲染命令缓存
1 2 3 4 5 6 7 8 9 | 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 2 | 1. 指向ib类型的chunk 2. 指向buffer object类型的渲染命令缓存 |
radeon_cmdbuf_chunk
为上层驱动提供GPU命令字的中间结构,它的核心成员是buf,指向上面radeon_cs_context结构体的buf成员
1 2 3 4 5 6 7 8 | 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 2 3 | 1. 上层应用每添加一个双字节命令,cdw就加1,记录当前buf空间已经添加的GPU命令字 2. 记录buf空间总大小 3. 指向真正存放渲染命令的内存空间,amdgpu_ib结构的ib_mapped成员,这是一个从内核分配的buffer object |
radeon_cmdbuf
暴露给上层硬件驱动的渲染命令缓冲对象,当上层驱动想要发送GPU命令时,它分配一个这样的结构体,然后往里面填入自己要发送的GPU命令。
1 2 3 4 5 6 7 8 | 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 | 1. 当前使用的命令缓冲对象 |
amdgpu_ib
1 2 3 4 5 6 7 8 9 | 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 2 3 4 5 6 | 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硬件单元渲染命令上下文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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,填充命令并下发。 |
amdgpu驱动关系图
radeon驱动关系图
- radeon驱动是amd GPU驱动的另外一个版本,上面没有介绍,保留这个图示备忘
流程
- 渲染命令的下发分为三步,首先硬件驱动创建一个命令提交上下文,其中包含buffer空间的分配,然后是往buffer里面填写渲染命令,最后ioctl命令下发到内核,其中命令的添加由上层驱动负责,上下文创建和命令的提交由amdgpu驱动负责,我们分别介绍这三步的主要动作。其中上下文创建和提交函数如下:
1 2 3 4 5 6 7 | void amdgpu_cs_init_functions(struct amdgpu_screen_winsys *ws) { ....... ws->base.cs_create = amdgpu_cs_create; ws->base.cs_flush = amdgpu_cs_flush; ...... } |
创建上下文
- 创建上下文的主要目的就是分配命令缓存,它返回给上层硬件驱动模块一个radeon_cmdbuf,驱动模块通过此cmdbuf填写命令,分配的命令缓存就是一块IB,对于amdgpu的驱动,它的命令缓存是放在Indrect Buffer中的,而在radeon驱动的实现中,它的命令缓存放在Primary Buffer中的。这里介绍amdgpu驱动,流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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 3 4 5 6 | 1. 分配一个命令提交的上下文,命令缓存关联这个上下文,最终返回这个上下文包含的命令缓存,初始化上下文的各个成员 2. 初始化上下文包含的两个基本的缓存IB,设置其类型 3. 初始化当前命令提交上下文和备用的命令提交上下文,如前所述,为了增强应用程序下发命令的能力,用户态驱动设计了两个命令提交上下文, 当前的上下文指针在两个上下文之间切换,当一个上下文被内核读取时可以用另外一个 4. 为上下文中的命令缓存分配空间,返回给上层驱动后,驱动可以往此空间填写命令 5. 返回上下文的cmdbuf,对于整个命令提交上下文,上层的驱动命令不关注,因此只需要返回上层命令关注的缓存就可以了 |
- 跟踪amdgpu_get_new_ib函数,分析具体的空间分配过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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); ...... } |
1 2 3 4 5 6 7 8 9 10 | 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 为例,简单介绍:
1 2 3 | si_emit_draw_packets radeon_emit(cs, PKT3(PKT3_INDEX_TYPE, 0, 0)) /* 1 */ cs->current.buf[cs->current.cdw++] = value; /* 2 */ |
1 2 3 4 | 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然后下发 |
下发命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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 2 3 4 5 6 7 8 | 1. 不同IP核上的渲染命令,对齐要求不同,按照要求补齐 2. 如果当前渲染命令长度超过最大长度,打印报错,但本次的命令需要继续提交 3. 获取current提交上下文,amdgpu_cs_context 4. 确保前一次提交已经完成,[待分析] 5. 锁保护,[待分析] 6. 在提交渲染命令前,将current和thread的CS互换,提交的时候通过thread CS下发命令 7. 将渲染命令所在的IB提交 8. 渲染命令提交后,重新分配IB,[待分析] |
- 最后分析提交IB动作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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); |
1 2 3 4 5 6 7 8 | 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驱动厂商还是有封装一些接口,比如这里的amdgpu_cs_submit_raw2。 |