IB_ADDR
写入GPU的CP_IB_BASE寄存器。IB_ADDR
读取渲染命令并执行,就添加写寄存器CP_IB_BUFSZ的命令,作为内容写入Primary Buffer。当GPU在Primary Buffer中读取到这样一个命令并执行相应的寄存器写动作时,硬件会触发GPU去IB_ADDR
读取渲染命令并执行,当读取了CP_IB_BUFSZ长度的内容后,GPU会再返回Primary Buffer中读取命令继续执行。IT_BODY
格式如下:AMD R5xx Family
,对于R6xx/R7xx Family
,直接从type3类型的packet中解析即可触发Indirect Buffer的行为。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
,它描述了一个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
struct drm_amdgpu_cs_chunk_fence {
__u32 handle;
__u32 offset;
};
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类型的渲染命令缓存
为上层驱动提供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
暴露给上层硬件驱动的渲染命令缓冲对象,当上层驱动想要发送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. 当前使用的命令缓冲对象
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成员获取
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,填充命令并下发。
void amdgpu_cs_init_functions(struct amdgpu_screen_winsys *ws)
{
.......
ws->base.cs_create = amdgpu_cs_create;
ws->base.cs_flush = amdgpu_cs_flush;
......
}
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,对于整个命令提交上下文,上层的驱动命令不关注,因此只需要返回上层命令关注的缓存就可以了
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的空间大小
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,[待分析]
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下发渲染命令,这个是开源流程,我们可以分析清楚