GPU fence
- AMD GPU的fence机制用来产生一个GPU的事件,用于CPU与GPU之间数据同步。当CPU发送一个fence到GPU某个IP的Ring Buffer后,GPU IP硬件会刷新该Ring上的cache。fence机制可以用来保证用户态程序下发的渲染命令被顺序执行,从而保证上层应用程序渲染相关数据的一致性。实现fence机制的硬件基础是AMD GPU提供的
EOP(end-of-pipe event)
渲染命令,它和普通的渲染命令使用方法相同,作为渲染命令提交到Ring Buffer,然后被GPU执行。
command format
- CPU与GPU通过Ring Buffer实现渲染命令的提交,渲染命令作为一个packet被提交到Ring Buffer上,AMD规定packet的格式,它被分为两部分,Header和IT_BODY(information body),EOP渲染命令也有如下通用格式,如下图所示:

- packet的HEADER最高2bit代表packet的类型,AMD将packet分为了3类,分别是0,2,3。每类packet的头部除了高2bit代表类型以外其它字段与packet类型相关,其中type-0和type-2是用作写寄存器的packet,type-3用做发送渲染命令以及一些特殊操作的命令,比如IB和Fence操作。type-3 packet的格式如下:

- HEADER由4个字段组成,如下:
- PREDICATE:待分析
- IT_OPCODE:操作码,用于描述具体的渲染命令类型或者特殊操作,比如Indirect Buffer和事件触发操作
- COUNT:描述IT_BODY的大小的字段,4字节为单位。它的值为IT_BODY长度减1。
- TYPE:packet类型,这里是3
- AMD驱动中有如下宏定义type-3类型的packet:
#define PACKET_TYPE3 3
#define PACKET3(op, n) ((PACKET_TYPE3 << 30) | (((op) & 0xFF) << 8) | ((n) & 0x3FFF) << 16)
#define PACKET3_EVENT_WRITE_EOP 0x47
/* 组装一个操作码为PACKET3_EVENT_WRITE_EOP的type-3类型的HEADER,并设置其IT_BODY长度为5 DWORD */
PACKET3(PACKET3_EVENT_WRITE_EOP, 4)
EOP event
- EOP(end-of-pipe)作为一个特殊命令,它的主要功能是设置GPU,在flush cache之后,往CPU可以访问的一个内存地址处写入时间戳或者EOP packet中定义的序列号,然后发送中断通知CPU flush动作完成,CPU在中断处理例程中实现fence完成后的相关软件处理,通知所有感兴趣的上层应用。通过这样的方式,CPU可以确认渲染命令是否已经完成,从而实现同步。AMD GPU定义了一个type-3 packet专门用于实现fence机制,packet格式如下:

- fence的packet包主要用来指示GPU在end-of-pipe之后应该在哪个内存空间,写入什么值,因此packet中有两个最主要的信息是序列号和地址,GPU在end-of-pipe之后会根据这两个信息产生写内存事件或者中断。下面依次介绍每个字段含义:
- HEADER:packet包头部,IT_OPCODE为
event_write_eop(0x47)
,COUNT为4。 - event initiator:GPU产生事件是写入
VGT_EVENT_INITIATOR
寄存器的值 - event type:事件类型,GPU提供了几种事件类型,包括Cache Flush,Cache Flush And Tnval,Bottom Of Pipe等,通常情况下选择第二种,在cach flush并且使无效之后产生事件
- address_lo/address_hi:事件产生时写入的内存地址
- interrupt select:选择以何种方式产生事件,包括Send Interrupt Only,Send Interrupt when Write Confirm is received from the MC
- data select:产生事件时发送的数据长度
- data_lo/data_hi:事件产生时要写的数据
- AMD fence机制的常用场景是每次CPU提交渲染命令之后,发送一个fence到GPU,同时启动一个定时器每隔一段事件查询一次EOP中给出的内存地址是否被填入了指定的数据。之后,就可以处理其它事情了。在定时器处理例程中如果查询到内存地址被填入指定数据,说明渲染命令已经处理完成。CPU根据这个信息,就可以继续提交新的渲染命令或者处理一些同步相关事情了。下面是amdgpu驱动中一段填充fence信息到Ring Buffer的操作:
amdgpu_ring_write(ring, PACKET3(PACKET3_EVENT_WRITE_EOP, 4)); /* 1 */
amdgpu_ring_write(ring, (EOP_TCL1_ACTION_EN | /* 2 */
EOP_TC_ACTION_EN |
EOP_TC_WB_ACTION_EN |
EVENT_TYPE(CACHE_FLUSH_AND_INV_TS_EVENT) |
EVENT_INDEX(5)));
amdgpu_ring_write(ring, addr & 0xfffffffc); /* 3 */
amdgpu_ring_write(ring, (upper_32_bits(addr) & 0xffff) | /* 4 */
DATA_SEL(write64bit ? 2 : 1) | INT_SEL(int_sel ? 2 : 0));
amdgpu_ring_write(ring, lower_32_bits(seq)); /* 5 */
amdgpu_ring_write(ring, upper_32_bits(seq));
1. 组装HEADER,将IT_OPCODE和COUNT填入HEADER中
2. 设置event的类型
3. 写入内存地址,因为内存地址是双字为单位,4字节对齐的,因此低2位会设置为0
4. 写入余下的内存地址,同时设置数据发送的位数已经中断产生方式
5. 填入要往内存地址写入的数据
DMA fence
- 设想这样一个场景,用户态应用往Ring Buffer提交渲染命令,GPU设备着手处理,渲染命令提交完成后返回到用户态空间,GPU异步执行渲染操作。此时用户态应用继续处理其它事务,它下发ioctl命令让显示设备输出刚刚发给GPU渲染的那一帧画面,通常的做法是,这个ioctl命令陷入内核态等待GPU渲染完成,获取渲染buffer的地址,然后将其设置为显示设备的扫描地址,最后返回用户态空间。整个过程可以归纳为向GPU发送渲染数据,然后让显示设备输出,更高效的做法是在内核空间一次性将这两个事情做完,这样就可以节省一次内核态与用户态上下文切换的开销。出现这种问题的本质是内核态缺乏一种机制,没法让两个硬件单元(GPU和display)同步数据,因此只能返回用户态,让用户态程序来负责这两个事情,间接完成数据同步。
- dma-fence的设计就是用于解决这类问题,它用来实现多个硬件之间共享buffer的同步,同样是上面的场景,当用户态应用往Ring Buffer提交渲染命令后,将GPU处理的渲染buffer共享给显示设备,同时将一个dma-fence关联到这个buffer上并往dma-fence添加一个回调函数,当GPU处理完buffer之后,触发buffer关联的dma-fence上的回调,回调函数中,就可以将渲染buffer的地址设置为显示设备的扫描地址,完成原本用户态应用负责的事情。
- dma fence有三个状态,一是初始化后的默认状态;二是使能状态(in-fence),通常在注册fence回调函数的时候被设置成这个状态;三是完成状态(out-fence),通常硬件的fence完成后在中断处理中设置。如下:
enum dma_fence_flag_bits {
DMA_FENCE_FLAG_SIGNALED_BIT, /* 1 */
DMA_FENCE_FLAG_TIMESTAMP_BIT,
DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, /* 2 */
DMA_FENCE_FLAG_USER_BITS, /* must always be last member */
};
1. out-fence状态,标记着fence完成。GPU发送EOP中断后,CPU响应该中断,并将fence的flag设置为这个状态。
2. in-fence状态,标记渲染命令处于fence状态。CPU发送渲染命令之后,使能fence时将flag标记为此状态。
数据结构
dma-fence
struct dma_fence {
const struct dma_fence_ops *ops; /* 1 */
union {
struct list_head cb_list; /* 2 */
/* @cb_list replaced by @timestamp on dma_fence_signal() */
ktime_t timestamp;
/* @timestamp replaced by @rcu on dma_fence_release() */
struct rcu_head rcu;
};
u64 context; /* 3 */
u64 seqno; /* 4 */
unsigned long flags; /* 5 */
struct kref refcount; /* 6 */
}
1. 定义dma-fence等待,释放,完成等操作
2. 维护该dma-fence完成后需要触发的回调函数
3. fence所在上下文,同一个上下文可以有多个fence,通过序列号seqno区分,每个fence有唯一一个序列号不同。在不同上下文中fence序列号可以相
同,fence序列号只在同一个上下文起到区分不同fence的作用。对于amdgpu这种应用场景,一个fence上下文就是一个Ring Buffer,一个fence通常关联
到这个Ring Buffer上的一个任务
4. fence关联的序号,当amdgpu驱动提交一个任务到Ring Buffer上时,就增加这个序列号并分配一个dma-fence关联到该任务,用于跟踪任务是否完成
5. 用于标记该fence的状态,当fence完成时会被标记为DMA_FENCE_FLAG_SIGNALED_BIT,表示fence已经完成,其它对该fence感兴趣的模块会据此执行相应的事务
6. 该fence的引用计数,可以有多个硬件模块或者软件驱动引用同一个fence
amdgpu-fence
- amdgpu驱动使用内核提供的dma-fence机制实现Host驱动和GPU的同步,amdgpu驱动将dma-fence附加到一个GPU IP的Ring Buffer上,因此封装了一个amdgpu_fence结构体用于将dma-fence与Ring Buffer关联起来。注意,一个Ring Buffer上可以包含若干个fence,因此多个amdgpu_fence的ring成员可能指向同一个环。
struct amdgpu_fence {
struct dma_fence base; /* Ring Buffer上的dma-fence */
/* RB, DMA, etc. */
struct amdgpu_ring *ring; /* amdgpu fence所在的Ring Buffer,amdgpu fence与amdgpu ring是多对一的关系 */
};
dma_fence_ops
- 内核的dma-fence只提供框架,可以让内核其它模块定制自己的实现,包括如何等待fence完成,fence完成时如何触发相应的回调,fence如何被释放等等,这个定制接口通过定义如下结构体
dma_fence_ops
来实现
struct dma_fence_ops {
bool (*enable_signaling)(struct dma_fence *fence); /* 使能fence,fence初始化后到完成之前,都处于使能状态 */
bool (*signaled)(struct dma_fence *fence); /* 向fence发信号,标记fence完成,通常这是在硬件fence完成时中断处理中实现 */
signed long (*wait)(struct dma_fence *fence, /* 等待fence完成 */
bool intr, signed long timeout);
void (*release)(struct dma_fence *fence); /* 释放fence */
......
}
/* amdgpu 驱动定制的fence接口 */
static const struct dma_fence_ops amdgpu_fence_ops = {
.get_driver_name = amdgpu_fence_get_driver_name,
.get_timeline_name = amdgpu_fence_get_timeline_name,
.enable_signaling = amdgpu_fence_enable_signaling,
.release = amdgpu_fence_release,
};
amdgpu_fence_driver
struct amdgpu_fence_driver {
uint64_t gpu_addr; /* 1 */
volatile uint32_t *cpu_addr; /* 2 */
/* sync_seq is protected by ring emission lock */
uint32_t sync_seq; /* 3 */
atomic_t last_seq; /* 4 */
struct amdgpu_irq_src *irq_src; /* 5 */
unsigned irq_type;
struct timer_list fallback_timer; /* 6 */
unsigned num_fences_mask; /* 7 */
struct dma_fence **fences; /* 8 */
};
1. GPU可访问的内存地址,该地址是GPU产生fence事件时将序列号写入的地址,GPU写gpu_addr地址,CPU从cpu_addr地址读出值
2. fence机制中gpu地址对应的cpu地址,驱动侧通过读取这个地址的内容,与序列号比较,如果相同,说明GPU fence事件产生
3. GPU往gpu_addr写入的序列号,单调递增。初始值为0,CPU每执行一个job,就会往GPU ring上发送一个fence,序列号就加1。fence的序列号用来
区分不同job。假设CPU往ring buffer上先后放了两个job,此时产生了GPU产生了end-of-pipe中断,CPU的中断处理函数例程中可以通过序列号区分是哪
个job对应的渲染命令被GPU处理完成。
4. 从cpu_addr中取出的GPU写入的序列号
5. 待分析
6. 定时器,用于每隔一段事件查询是否有fence被通知(signaled)
7. fence个数掩码,用于查询fences数组中的dma_fence,使其不越界
8. fences数组,调度器每提交一个job,相应地会增加fence的序列号,同时会生成一个dma_fence对象放到fences数组中。
API
- 对于amdgpu来说,一个Ring Buffer就是一个共享buffer,它上面可以关联dma-fence,当GPU执行完Ring Buffer上的渲染命令时,通过附加在buffer上面的dma-fence,就可以通知其它硬件模块并触发相应回调函数
dma_fence_add_callback
- fence被发送后,发送fence的线程就可以转而去处理其它事务,当fence完成时会触发相应的回调函数,一个dmp-fence可以注册多个回调函数,但一个回调函数只能注册到一个dmap-fence上,即dma-fence与callback时一对多的关系。amdgpu驱动中fence的回调函数注册在
dma_fence_add_callback
中实现,如下:
int dma_fence_add_callback(struct dma_fence *fence, struct dma_fence_cb *cb,
dma_fence_func_t func)
{
if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags)) { /* 1 */
INIT_LIST_HEAD(&cb->node);
return -ENOENT;
}
if (__dma_fence_enable_signaling(fence)) { /* 2 */
cb->func = func;
list_add_tail(&cb->node, &fence->cb_list);
}
......
}
1. 检测fence是否已经完成,如果已经完成,直接返回,调用者跳过回调函数的注册,直接执行就可以了
2. 判断fence是否注册了触发fence完成的函数,如果有,满足条件,将回调函数添加到dma-fence的回调函数链表中。
dma_fence_signal
dma_fence_signal
dma_fence_signal_locked
int dma_fence_signal_locked(struct dma_fence *fence)
{
if (unlikely(test_and_set_bit(DMA_FENCE_FLAG_SIGNALED_BIT, /* 1 */
&fence->flags)))
return -EINVAL;
list_for_each_entry_safe(cur, tmp, &cb_list, node) { /* 2 */
INIT_LIST_HEAD(&cur->node);
cur->func(fence, cur);
}
......
}
1. 设置fence状态标记为SIGNEALED,其余接口通过查询此状态标记检查fence是否处于out-fence状态
2. 对fence感兴趣的其它模块通过向cb_list添加回调函数,可以让fence在完成后执行该回调。这里就是fence完成后的回调
API
amdgpu_fence_driver_init_ring
- 初始化一个GPU IP上Buffer Ring的fence_driver,每个Buffer Ring上维护这一个fence driver,它用来记录提交到Ring Buffer上的job的完成情况。为什么要用这个才能记录?因为CPU提交渲染到Ring Buffer让GPU执行是异步,只能通过fence机制来确认job的完成情况。fence driver为上层渲染命令的同步提供底层基础,否则上层是无法知道渲染命令是否完成,当上层应用下发的渲染命令前后有依赖时,无法保证顺序
int amdgpu_fence_driver_init_ring(struct amdgpu_ring *ring,
unsigned num_hw_submission)
{
ring->fence_drv.sync_seq = 0; /* 1 */
atomic_set(&ring->fence_drv.last_seq, 0);
ring->fence_drv.initialized = false; /* 2 */
timer_setup(&ring->fence_drv.fallback_timer, amdgpu_fence_fallback, 0); /* 3 */
ring->fence_drv.num_fences_mask = num_hw_submission * 2 - 1; /* 4 */
ring->fence_drv.fences = kcalloc(num_hw_submission * 2, sizeof(void *), /* 5 */
GFP_KERNEL);
......
}
1. 将当前序列号和上一次的序列号初始化为0,sync_seq在真正发送fence的时候会加1,因此sync_seq从1开始,last_seq从0开始。
2. fence driver是否启动,这里设置为false,fence driver在启动start ring接口中设置为true
3. 设置定时器,该定时器用于查询是否有完成的fence。除了GPU IP核中断例程中会处理完成的fence外,这里注册的定时器回调函数也会周期性查询fence是否完成。
4. 设置fence个数的掩码,amd gpu调度器中允许同时有num_hw_submission个任务提交到Ring Buffer。调度器每提交一个任务,至少要发送一个fence用于跟踪当前任务是否完成。同时在提交任务之前,可能刷GPU的缓存,这时也会发送一个fence用于跟踪当前缓存是否刷新完成,因此一个任务最多可能会有2个fence发送。所以设置的fence个数掩码为num_hw_submission * 2 - 1。注意,num_hw_submission是2的幂级数。
5. 为fence driver的fences结构体分配空间,个数为num_hw_submission的2倍
amdgpu_fence_driver_start_ring
- fence driver的初始化主要完成基本成员的赋初值,但关键的gpu和cpu地址没有设置,这个设置在start ring中完成。
int amdgpu_fence_driver_start_ring(struct amdgpu_ring *ring,
struct amdgpu_irq_src *irq_src,
unsigned irq_type)
{
ring->fence_drv.cpu_addr = &adev->wb.wb[ring->fence_offs]; /* 1 */
ring->fence_drv.gpu_addr = adev->wb.gpu_addr + (ring->fence_offs * 4);
amdgpu_fence_write(ring, atomic_read(&ring->fence_drv.last_seq)); /* 2 */
amdgpu_irq_get(adev, irq_src, irq_type);
ring->fence_drv.irq_src = irq_src;
ring->fence_drv.irq_type = irq_type;
ring->fence_drv.initialized = true; /* 3 */
......
}
1. 设置fence driver的gpu和cpu地址
2. 将last_seq序列号写入fence driver的cpu地址中,由于last_seq初始值为0,因此cpu地址中读出的序列号为0,在提交任务时,当前序列号sync_seq会
先加1再发送fence,中断处理函数和定时器函数中通过判断sync_seq与last_seq是否相等,来判断是否有任务在执行或者有flush正在刷新
3. 将fence driver的初始化设置为true
amdgpu_fence_emit
- 向GPU发送一个fence,具体实现是往Ring Buffer上填入fence内容。真正地发送在设置Ring Buffer写偏移时触发。
int amdgpu_fence_emit(struct amdgpu_ring *ring, struct dma_fence **f,
unsigned flags)
{
struct amdgpu_fence *fence;
fence = kmem_cache_alloc(amdgpu_fence_slab, GFP_KERNEL); /* 1 */
seq = ++ring->fence_drv.sync_seq; /* 2 */
fence->ring = ring; /* 3 */
dma_fence_init(&fence->base, &amdgpu_fence_ops,
&ring->fence_drv.lock,
adev->fence_context + ring->idx,
seq);
amdgpu_ring_emit_fence(ring, ring->fence_drv.gpu_addr, /* 4 */
seq, flags | AMDGPU_FENCE_FLAG_INT);
ptr = &ring->fence_drv.fences[seq & ring->fence_drv.num_fences_mask]; /* 5 */
if (unlikely(rcu_dereference_protected(*ptr, 1))) {
struct dma_fence *old;
old = dma_fence_get_rcu_safe(ptr);
dma_fence_wait(old, false); /* 6 */
dma_fence_put(old); /* 7 */
}
rcu_assign_pointer(*ptr, dma_fence_get(&fence->base)); /* 8 */
......
}
1. 为amdgpu-fence结构体分配内存
2. 增加序列号,每发送一次fence,序列号自增一次
3. 设置fence所在的环
4. 往Ring Buffer中填入fence信息,即之前介绍的EOP packet
5. 取出序列号在fence数组中对应元素,判断是否为空,如果为空,将该元素指向新分配的fence。通常情况下,fence数组对应位置不会存在fence,如果存在,我们认为它是之前未完成的fence,我们等待fence完成,然后再释放老的fence。
6. 等到老的fence完成
7. 完成后释放老的fence,如果是最后一个fence的引用被释放,则删除老的fence
8. 设置fence数组对应元素指向即将发送的新fence。
amdgpu_fence_process
- 当GPU完成渲染任务后,会触发EOP中断,amdgpu在中断处理函数中会比较EOP packet中填入的序列号和GPU往GPU地址空间写入的值是否相同,如果相同,说明GPU完成了渲染操作或者完成的flush cache的操作,过程如下:
bool amdgpu_fence_process(struct amdgpu_ring *ring)
{
struct amdgpu_fence_driver *drv = &ring->fence_drv;
uint32_t seq, last_seq;
do {
last_seq = atomic_read(&ring->fence_drv.last_seq); /* 1 */
seq = amdgpu_fence_read(ring);
} while (atomic_cmpxchg(&drv->last_seq, last_seq, seq) != last_seq); /* 2 */
if (unlikely(seq == last_seq)) /* 3 */
return false;
last_seq &= drv->num_fences_mask; /* 4 */
seq &= drv->num_fences_mask;
do {
struct dma_fence *fence, **ptr;
++last_seq; /* 5 */
last_seq &= drv->num_fences_mask;
ptr = &drv->fences[last_seq];
/* There is always exactly one thread signaling this fence slot */
fence = rcu_dereference_protected(*ptr, 1);
RCU_INIT_POINTER(*ptr, NULL);
dma_fence_signal(fence); /* 6 */
dma_fence_put(fence);
} while (last_seq != seq);
......
}
1. 取出上一次fence的序列号last_seq和这一次fence的序列号seq
2. 用这一次fence的序列号更新上次任务的序列号,原子操作
3. 如果两次fence的序列号相等,说明当前没有fence完成,直接返回false,表示fence还处在未完成阶段
4. 计算序号在fences数组中的索引
5. 逐次增加last_seq,从fences数组中取出对应的dma-fence,更新fence的状态,最终触发fence上的回调,直到last_seq等于seq。在fence完成的回调
处理中,多个fence可能合并一次性处理。
6. 更新fence的状态为完成(signaled)
7. 释放fence
应用举例
- GPU调度器通过fence机制为上层应用提供了不同硬件之间数据的同步方法,GPU调度器本身也通过fence机制跟踪任务的完成情况。下面通过分析GPU调度器执行任务的过程,进一步理解fence机制
调度任务
- 调度器按从高到低的顺序,依次从对应的运行队列中选择合适的调度实体,取出调度实体的任务,将任务关联IB包含的渲染命令提交到Ring Buffer上。调度任务的执行在
drm_sched_main
函数中实现,GPU调度器利用fence跟踪渲染任务是否执行完,还可以利用fence机制注册回调函数,让GPU渲染任务执行完成后触发任务相关的收尾工作。处理其中fence相关的操作如下:
static int drm_sched_main(void *param)
{
while (!kthread_should_stop()) {
......
fence = sched->ops->run_job(sched_job); /* 1 */
if (!IS_ERR_OR_NULL(fence)) {
r = dma_fence_add_callback(fence, &sched_job->cb, /* 2 */
drm_sched_process_job);
if (r == -ENOENT)
drm_sched_process_job(fence, &sched_job->cb); /* 3 */
dma_fence_put(fence); /* 4 */
}
......
}
}
1. 执行渲染任务,该动作的实质是将渲染命令放到Ring Buffer上,然后让GPU异步处理,run_job返回一个fence,上层通过此fence注册回调函数
2. 往fence上注册回调函数,当该fence完成时,执行drm_sched_process_job函数
3. 如果此时fence已经完成,说明GPU已经将渲染任务完成,不再需要注册回调,直接运行回调函数即可
4. 释放fence
中断注册
- 渲染任务完成后,GPU硬件模块产生中断,CPU响应中断后触发中断服务例程,理论上上面的回调函数源头是中断服务例程触发的。我们跟踪一下GPU中断服务例程的注册,以GFX模块为例。中断注册在amdgpu驱动中完成,大致流程如下:
amdgpu_init
pci_register_driver(&amdgpu_kms_pci_driver)
amdgpu_kms_pci_driver.probe <=> amdgpu_pci_probe
amdgpu_driver_load_kms
amdgpu_device_init
amdgpu_device_ip_early_init
vi_set_ip_blocks
case CHIP_TONGA:
amdgpu_device_ip_block_add(adev, &gfx_v8_0_ip_block)
const struct amdgpu_ip_block_version gfx_v8_0_ip_block =
{
......
.funcs = &gfx_v8_0_ip_funcs,
};
static const struct amd_ip_funcs gfx_v8_0_ip_funcs = {
.name = "gfx_v8_0",
.early_init = gfx_v8_0_early_init,
......
}
gfx_v8_0_early_init
gfx_v8_0_set_irq_funcs(adev);
adev->gfx.eop_irq.funcs = &gfx_v8_0_eop_irq_funcs;
static const struct amdgpu_irq_src_funcs gfx_v8_0_eop_irq_funcs = {
.set = gfx_v8_0_set_eop_interrupt_state, /* 1 */
.process = gfx_v8_0_eop_irq, /* 2 */
};
1. 设置中断状态的操作,和具体的硬件编程接口有关对于amdgpu来说,主要时使能中断和禁止中断这两个功能
2. EOP中断例程处理函数
中断处理
- GPU的中断处理通过drm_driver注册,drm_driver提供统一的中断框架用于实现GPU中断处理例程的注册,内核公共的中断处理函数最终会调用到drm_driver的中断处理函数irq_handler,如下:
static struct drm_driver kms_driver = {
.irq_handler = amdgpu_irq_handler,
......
}
- 当GPU硬件产生中断并提交到CPU后,CPU根据中断向量表跳转到对应的服务例程,最终调用
amdgpu_irq_handler
,看下其具体流程:
amdgpu_irq_handler
amdgpu_ih_process
amdgpu_irq_dispatch
src->funcs->process(adev, src, &entry) <=> gfx_v8_0_eop_irq
amdgpu_fence_process
dma_fence_signal
dma_fence_signal_locked
触发回调
- 从上面的流程中可以看到,渲染任务执行完了会触发
drm_sched_process_job
回调,该函数会将调度相关的fence设置为完成,从而链式地触发其它监听模块的回调,具体如下:
static void drm_sched_process_job(struct dma_fence *f, struct dma_fence_cb *cb)
{
struct drm_sched_job *s_job = container_of(cb, struct drm_sched_job, cb);
struct drm_sched_fence *s_fence = s_job->s_fence; /* 获取job关联的dma-fence */
dma_fence_get(&s_fence->finished); /* 引用finished dma-fence */
drm_sched_fence_finished(s_fence); /* 通知监听finished dma-fence的其它模块,job已经处理完成,可以触发对应回调了 */
dma_fence_put(&s_fence->finished); /* 释放finished dma-fence */
}