AMD GPU 内核驱动分析(一)总览

您所在的位置:网站首页 amd显卡级别分类 AMD GPU 内核驱动分析(一)总览

AMD GPU 内核驱动分析(一)总览

2024-06-10 11:30| 来源: 网络整理| 查看: 265

和CPU相比,GPU中包含了大量的并行计算单元,适合处理像素,矩阵,坐标等大量同类型的数据,因此,很多LINUX上的应用程序为了能够利用GPU的加速功能,都试图和GPU直接打交道,因此,系统中可能有多个组件或者程序同时使用GPU,比如桌面系统中OpenGL的实现MESA。这样会带来一个问题,就是多个进程并发访问GPU,如果控制不好,势必会造成系统工作不稳定,为了解决这样的问题,LINUX内核开发者在内核中设计了DRM模块,所有访问GPU的操作都通过DRM统一管理,由DRM来统一协调对GPU的访问,所以,在Linux系统中,各类GPU驱动,包括NVIDIA,AMD等大厂的GPU驱动都是集成在DRM框架中的.以AMD GPU驱动为例,AMD GPU的内核驱动结构上是一个复杂的软件堆栈,负责管理和控制AMD图形硬件,以便应用程序可以与GPU进行通信并利用其图形处理能力,这些能力都是通过DRM提供的,DRM屏蔽了不同显卡厂家的硬件差异,以统一一致的接口向上层提供服务。

以下是AMD GPU内核驱动的主要组成部分:

硬件抽象层(HAL):

HAL是位于底层的部分,负责与GPU硬件之间的通信。它提供了一个抽象的接口,使上层的驱动和应用程序可以访问GPU的功能和寄存器,而不需要了解具体的硬件细节。

图形命令处理器(GCP):

GCP负责接收、解析和执行应用程序发送的图形命令。它将应用程序的图形请求翻译成GPU硬件可以理解的指令,以执行绘图操作和渲染任务。

AMDGPU内核驱动:

这是AMD GPU驱动的核心部分,运行在操作系统的内核空间。它与硬件通信,管理GPU资源(如显存和显卡寄存器),执行调度和任务分配,以确保多个任务在GPU上正确运行。

GPU用户空间驱动:

GPU用户空间驱动是运行在操作系统的用户空间的组件,它与AMDGPU内核驱动协同工作,提供对GPU的高级控制。它包括OpenGL和Vulkan驱动程序,以及OpenCL运行时库,允许应用程序与GPU进行图形和通用计算任务的交互。

AMDGPU-PRO:

AMDGPU-PRO是AMD的专业级GPU驱动,主要用于支持专业应用程序和工作负载,如CAD、3D建模和科学计算。它提供了更丰富的特性和支持,包括对专业图形API和库的更好的兼容性。

AMD ROCm:

AMD ROCm(Radeon Open Compute)是一个开源的GPU计算平台,旨在支持GPU加速的深度学习和高性能计算工作负载。它包括ROCm内核驱动和ROCm用户空间工具,为GPU计算提供了强大的支持。

总之,AMD GPU的内核驱动结构是一个多层次的系统,由硬件抽象层、内核驱动、用户空间驱动和专业级组件组成,以便应用程序可以有效地利用AMD GPU的图形和计算能力。这些组件共同协作,确保GPU能够执行各种图形和计算任务。

图中的箭头表示源模块会调用箭头指向的目标模块中的函数或者符号,这种调用通常和LINUXN内核中的框架驱动注册相对应,即由架构中低层次的模块向框架中高层次的模块注册操作句炳,所以实际的架构层次应该是上下颠倒的,也就是说,从架构上看,应该是DRM在上,而AMDGPU在最底层,只有扇入口,没有扇出口。

虽然不清楚N卡的官方驱动的实现细节,但作为对比,从模块拓扑来看,和AMD GPU应用架构很类似:

NVIDIA NOUVEAU开源驱动同样集成于DRM框架中,模块之间的依赖比AMD更加简洁一些,不过这可能意味着驱动的实现更加复杂。

单纯从核显的角度作对照,可能拿INTEL的核显作为对比最为合适,毕竟两者面对的是同一个市场,INTEL的核显I915驱动拓扑如下:

再看一个,下图是VIRTUALBOX中基于虚拟GPU的DRM框架,vmwgfx.ko是虚拟GPU的KMD.对比三张拓扑,可以看到DRM和GPU之间的关系大同小异,都是KMD将自身注册进入DRM驱动框架内部,UMD通过DRM框架调用KMD的实现(模块关联图架构上是从底层到上层的,如果从调用层次上看,应该把图上下颠倒)

同样作为虚拟机,vmware是直接基于vmwgfx提供KMD的角色:

小结:

显卡是DRM设备的后端:

所以,进一步抽象GPU和DRM框架的架构,建立它们之间的联系,似乎是这样的:

DRM以字符设备IOCTL命令的形式向用户态提供功能,IOCTL的实现有两种,一种是DRM通用的CMD,另一种是GPU specific实现,GPU Specific的CMD范围在(0x40-0xA9).

以锐龙R5600G核显VEGA7为例,其支持DX12,Vulkan,OpenCL,OpenGL等多种用户态编程接口,这些接口在GPU软件栈中的作用基本上相当于Posix在CPU侧的作用,是构成GPU生态系统(渲染,计算,多媒体)的基石,非常重要。

AMDKFD驱动配置编译架构分析

amdkfd目录最终会和amdgpu目录的源码一同编译,生成amdgpu.ko,AMD GPU驱动,主要是指amdgpu.ko.

和AMDGPU相关的配置:

AMDGPU驱动初始化

AMDGPU由多个硬件处理单元组成(IP核block),任何一条GPU的渲染命令,其最终都是发送到GPU的硬件单元上,硬件单元可以处理一些通用的渲染命令,也可能处理一些专有渲染命令,因此一条GPU渲染命令需要指明它发往的硬件单元。实际上,mesa驱动针对GPU的每个硬件单元都抽象了具体的结构,当硬件单元驱动想给自己对应的硬件发送GPU命令时,就需要设置硬件的类型。就是这里的ring_type。为什么叫ring_type呢,因为每个硬件处理单元和CPU之间时通过ring buffer传递渲染命令的,每个硬件单元上都有一个或者多个ring buffer,所以ring_type就是GPU硬件单元的类型。从DMESG LOG信息可以看到,AMDGPU初始化时,初始化了compute, gfx, sdma, jpeg_dec, vcn_enc,vcn_dec,kiq等ring.

注册的调用顺序为:

amdgpu_pci_probe->amdgpu_driver_load_kms->amdgpu_device_init->amdgpu_device_ip_early_init->amdgpu_discovery_set_ip_blocks->amdgpu_discovery_set_ih_ip_blocks->amdgpu_device_ip_block_add.

最终由amdgpu_device_ip_block_add完成设备的注册。然后会在如下调用堆栈中完成RING调度线程的初始化。

...->amdgpu_device_init->amdgpu_device_ip_init->amdgpu_ring_init->amdgpu_device_init_schedulers->drm_sched_init->kthread_run(drm_sched_main,...

AMDGPU驱动会为每个RING创建一个内核线程,专门负责对这个RING上的COMMAND PACKET进行调度。

AMDGPU对应的DRM驱动结构体drm_driver为amdgpu_kms_driver对象,定义在文件amdgpu_drv.c中,在启用AMDGPU系统上,这个结构体即是DRM框架的后端。

2791 static const struct drm_driver amdgpu_kms_driver = { 2792 .driver_features = 2793 ¦ DRIVER_ATOMIC | 2794 ¦ DRIVER_GEM | 2795 ¦ DRIVER_RENDER | DRIVER_MODESET | DRIVER_SYNCOBJ | 2796 ¦ DRIVER_SYNCOBJ_TIMELINE, 2797 .open = amdgpu_driver_open_kms, 2798 .postclose = amdgpu_driver_postclose_kms, 2799 .lastclose = amdgpu_driver_lastclose_kms, 2800 .ioctls = amdgpu_ioctls_kms, 2801 .num_ioctls = ARRAY_SIZE(amdgpu_ioctls_kms), 2802 .dumb_create = amdgpu_mode_dumb_create, 2803 .dumb_map_offset = amdgpu_mode_dumb_mmap, 2804 .fops = &amdgpu_driver_kms_fops, 2805 .release = &amdgpu_driver_release_kms, 2806 2807 .prime_handle_to_fd = drm_gem_prime_handle_to_fd, 2808 .prime_fd_to_handle = drm_gem_prime_fd_to_handle, 2809 .gem_prime_import = amdgpu_gem_prime_import, 2810 .gem_prime_mmap = drm_gem_prime_mmap, 2811 2812 .name = DRIVER_NAME, 2813 .desc = DRIVER_DESC, 2814 .date = DRIVER_DATE, 2815 .major = KMS_DRIVER_MAJOR, 2816 .minor = KMS_DRIVER_MINOR, 2817 .patchlevel = KMS_DRIVER_PATCHLEVEL, 2818 };

DRM Driver initialization  process are launched by AMD GPU PCIE Driver probe procedure.

AMDKFD驱动

AMD的amdkfd 可以理解为在 DRM 子系统中提供了 CPU 与 GPU 沟通的快速通道,使得两者可以平等的访问内存资源而无需额外拷贝。AMDKFD驱动在内核中通过CONFIG_HSA_AMD配置项使能/关闭,关闭后整个KFD的注册全部为空函数,系统自然也就不会创建/dev/kfd设备节点。

KFD和GPU的渲染,多媒体功能都隐藏在内核DRM框架后面,两个是并行独立的模块,KFD的存在目的是为了给HPC计算场景提供一个快速访问GPU的通道,对于DRM渲染功能来说,并不是必不可少的,所以当关闭CONFIG_HSA_AMD后,并不影响正常的DRM渲染功能。

具体分析可以参考这篇对HSA架构的分析:

AMD HSA 异构计算架构和AMD-KFD内核驱动&NVIDIA内核驱动_papaofdoudou的博客-CSDN博客

AMDGPU固件

amdgpu固件分布在/lib/firmware/amdgpu,/lib/firmware/amd两个目录, GPU驱动加载时通过内核API request_firmware装载进GPU。

AMDGPU驱动初始化流程

GPU工作context

1.pasid是 per file的,不是per进程的.

2.每个struct file下可以创建多个context,共享同一个PASID(drm设备open时分配的).

3.pasid在不同的struct file是不同的,便于细粒度控制不同的打开上下文。

释放PASID堆栈,发生在文件关闭的时候:

前面截图中,线程分配了CTXID 1,2,3,4,退出时只释放了1,2,3。 4实际上是在这里释放的:

gpu_sched原理

gpu 的调度策略如下图所示:

1.每个context定义一个三维的entity表,作为某个指定IP Block的某个RING(比如GFX支持四个RING,而且VCN支持2个RING)下某个JOB的容器。

2.驱动初始化时,为每个ring创建一个调度线程和四个优先级调度队列,全系统唯一。

3.entity是JOB的容器,JOB中包含具体的由GPU执行的某个操作的渲染指令或者解码指令,而entity代表了用户的操作ring buffer,它和某个RING上的某个优先级队列管理结构对应。

4.不同上下文的entity如果分配在同一个IP BLOCK的同一个RING上的同优先级,则通过优先级队列中struct drm_sched_rq->rb_tree_root红黑数管理,管理值为最久等待时间oldest_job_waiting,通过JOB提交时调用ktime_get获得。

调度策略

entity是job的容器,drm_sched_main 调度线程在选择可以运行的entity的时候,涉及到一个调度策略问题,在6.X内核之前,从sched_rq中选择可以运行JOB的entity使用的是优先级+roundrobin调度策略,首先安按照sched_rq 3/2/1/0 的优先级顺序选择一个最高优先级的sched running queue,之后,在当前选择的sched_rq里面根据round robin选择一个可以运行的entity投入运行。这种方式下,sched_rq是通过list_head链表管理之下的所有entity的。

下图表示和调度相关的几个关键数据结构之间的关系:

1.struct amdgpu_ring和struct drm_gpu_scheduler是一对一的关系,每个GPU ring对应一个内核调度对象,对应一个数据结构。

2.每个RING(调度对象)包含四个struct drm_sched_rq对象。

3.每个struct drm_sched_rq对象包含了多个entity对象,每个entity对应用户空间某个命令队列。

4.每个entity对象连接起多个struct drm_sched_job对象,代表应用的某笔渲染命令。

5.entity对象会反向指向其所在的struct drm_sched_rq对象,同时通过struct drm_gpu_scheduler        **sched_list指针数组间接引用了其可以运行于其上的调度器对象,意味着某个entity可以选择投入到多个相同性质的ring中的任意一个去执行,比如有多个渲染ring,计算ring,则相应队列可以选择任何一个ring执行,为复杂均衡提供了一种可能。

而6.X内核之后,调度策略增加了一种模式,就是FIFO,这种方式是通过红黑树对entity进行管理,上图中绘制的就是这种方式。 为了兼容老的RR方式,AMDGPU模块定义了drm_sched_policy变量进行控制,在FIFO这种方式下,红黑树使用JOB投入时间作为键值进树。直到调度器将时间窗口内的JOB全部取完后,spcs queue未空,下面的FIRST逻辑再次运行时,彩才会切换entity在红黑数中的位置。

简而言之,调度器在选择entity的时候,会判断drm_sched_policy的配置,根据调度策略的不同选择使用FIFO还是round robin策略。

AMDGPU对FIFO调度的支持是在6.0内核开始的,commit id为:08fb97de03aa2205c6791301bd83a095abc1949c, comment里面详细解释了针对的问题和修改思路,可以参考:

清华linux-stable镜像仓库:  

https://mirrors.tuna.tsinghua.edu.cn/git/linux-stable.git  参考资料

https://www.x.org/docs/AMD/old/R5xx_Acceleration_v1.5.pdf

https://blog.csdn.net/tugouxp/article/details/131619250?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22131619250%22%2C%22source%22%3A%22tugouxp%22%7D

dma-fence简析以及使用demo_papaofdoudou的博客-CSDN博客

AMD GPU虚拟化_amd显卡虚拟化_shuaifeng.zhang的博客-CSDN博客

结束


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3