AMD GPU任务调度(3) —— fence机制

8 篇文章 53 订阅

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个字段组成,如下:
  1. PREDICATE:待分析
  2. IT_OPCODE:操作码,用于描述具体的渲染命令类型或者特殊操作,比如Indirect Buffer和事件触发操作
  3. COUNT:描述IT_BODY的大小的字段,4字节为单位。它的值为IT_BODY长度减1。
  4. 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之后会根据这两个信息产生写内存事件或者中断。下面依次介绍每个字段含义:
  1. HEADER:packet包头部,IT_OPCODE为event_write_eop(0x47),COUNT为4。
  2. event initiator:GPU产生事件是写入VGT_EVENT_INITIATOR寄存器的值
  3. event type:事件类型,GPU提供了几种事件类型,包括Cache Flush,Cache Flush And Tnval,Bottom Of Pipe等,通常情况下选择第二种,在cach flush并且使无效之后产生事件
  4. address_lo/address_hi:事件产生时写入的内存地址
  5. interrupt select:选择以何种方式产生事件,包括Send Interrupt Only,Send Interrupt when Write Confirm is received from the MC
  6. data select:产生事件时发送的数据长度
  7. 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 */
}
  • 19
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
以下是一个示例的 amdgpu 堆栈调用信息: ``` [<ffff800004d1b670>] amdgpu_fence_wait+0xd0/0x278 [amdgpu] [<ffff800004ce5424>] ? amdgpu_vm_bo_update+0x54/0x54 [amdgpu] [<ffff800004d24fbb>] amdgpu_sched_run_job+0x6b/0x108 [amdgpu] [<ffff800004d2512e>] amdgpu_sched_main_thread+0x9e/0x1c8 [amdgpu] [<ffff800004ce5424>] ? amdgpu_vm_bo_update+0x54/0x54 [amdgpu] [<ffff800004ce5424>] ? amdgpu_vm_bo_update+0x54/0x54 [amdgpu] [<ffff800004ce5424>] ? amdgpu_vm_bo_update+0x54/0x54 [amdgpu] [<ffff800004d6c9a9>] kthread+0xf9/0x111 [<ffff80000480f9d1>] ret_from_fork+0x31/0x40 ``` 上述堆栈信息表示了一个 amdgpu 驱动程序的函数调用堆栈,最后一行是最先被调用的函数。下面是对每一行的解释: - 第一行 `[<ffff800004d1b670>] amdgpu_fence_wait+0xd0/0x278 [amdgpu]` 表示当前正在执行的函数是 `amdgpu_fence_wait`,它位于 amdgpu 驱动程序中,偏移量为 `0xd0`,函数大小为 `0x278`。 - 第二行 `[<ffff800004ce5424>] ? amdgpu_vm_bo_update+0x54/0x54 [amdgpu]` 表示当前函数被调用时,正在执行另一个函数 `amdgpu_vm_bo_update`,但是由于没有堆栈信息,所以无法确定它的位置和大小。 - 第三行 `[<ffff800004d24fbb>] amdgpu_sched_run_job+0x6b/0x108 [amdgpu]` 表示当前函数是 `amdgpu_sched_run_job`,它位于 amdgpu 驱动程序中,偏移量为 `0x6b`,函数大小为 `0x108`。 - 第四行 `[<ffff800004d2512e>] amdgpu_sched_main_thread+0x9e/0x1c8 [amdgpu]` 表示当前函数是 `amdgpu_sched_main_thread`,它位于 amdgpu 驱动程序中,偏移量为 `0x9e`,函数大小为 `0x1c8`。 - 第五至第七行显示了函数 `amdgpu_vm_bo_update` 的堆栈信息,但是由于没有调用其它函数,所以堆栈信息中只有它自己的信息。 - 第八行 `[<ffff800004d6c9a9>] kthread+0xf9/0x111` 表示当前正在执行的函数是 `kthread`,它位于内核中,偏移量为 `0xf9`,函数大小为 `0x111`。 - 第九行 `[<ffff80000480f9d1>] ret_from_fork+0x31/0x40` 表示当前正在执行的函数是 `ret_from_fork`,它位于内核中,偏移量为 `0x31`,函数大小为 `0x40`。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

享乐主

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值