从底层向上理解GPU(GPU驱动的初始化过程)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jiangbo1017/article/details/50757169

背景

这一系列的总结本来应该伴随着项目及时的整理的,但是对于显卡驱动而言,本身能够参考的资料就非常的少,只能自己从内核代码中去不对揣摩推敲。项目的目的其实非常的简单粗暴,为什么这么说呢,因为要做的工作包含在嵌入式设备上实现一个2D硬件加速器,能够支持Mesa开源3D图形库,egl,DLX以及DRM模块。最后达到一个类桌面环境下的基于硬件加速的3D应用开发环境和显示平台。本篇文章是依据GPU内核代码,也就是DRM代码来从底层介绍显卡驱动的初始化过程,显卡类型是AMD的radeon r600以后的系列显卡。

基本的过程就是驱动载入,硬件初始化,设置硬件独立的模块(如内存管理器),设置显示(分辨率等);


radeon_driver_load_kms()

这个函数是在radeon_kms.c文件中定义的,也是所有和GPU初始化相关的内容的起始点。分别调用radeon_device_init()来初始化非显示设备的硬件,而调用radeon_modeset_init()来初始化显示设备相关的硬件(CRTC,connector, encoder等)。

radeon_device_init()

驱动初始化主要的工作都是由radeon_device_init()来完成,这个函数是在radeon_device.c中定义。首先我们会初始化一大堆的驱动需要使用的结构。然后调用radeon_asic_init(). 这个函数用于设置电路相关的一些函数指针,比如睡眠/恢复调用,硬件重置,设置和处理中断请求,设置和获取时钟等等。通用代码就会通过这些注册的函数指针来调用这些和电路相关的函数获得相应的功能。例如,使能和处理一中断在RV100和在RV700上是不一样的。由于不同generation的显卡不断的变化,对于多个asic families会有多个不同的处理方式。这也就让我们需要相容以及匹配针对不同的芯片编程相关的函数。例如,R1XX和R3xx芯片使用相同的中断处理模式,但是他们有不同的初始化路径(r100_init()和r300_init());

为驱动设置DMA掩码

这个步骤是让内核能够知道这个显卡能够处理什么样的大小的地址空间。对于radeon系列,这个被用于GPU访问图形缓冲,graphic buffer是存储在系统内存上,GPU通过GART表来访问这块系统内存。AGP以及更老的片上GART机制限制在32位。新的一些片上GART机制有更大的地址空间。

设置MMIO

PCI/PCIE/AGP设备都是通过被称为BARs(基地址寄存器)来编程的。这些映射提供了对片上寄存器,帧缓冲,以及显存上的资源的访问。通过寄存器来设置GPU,如果你想访问这些寄存器,你将需要映射这个寄存器的基地址。如果你想要向framebuffer写入(用于显示到你屏幕上的数据),你将需要映射这个framebuffer的基地址。在这里我们映射寄存器基地址,这个映射的寄存器将用于让驱动能够配置显卡(GPU).

vga_client_register()

vga_client_register被调用,这个函数的用途超出了本篇博文讨论的范围。就是提供了一个基本的方式用于存在多个VGA设备的PCI总线上提供对VGA的限制。

radeon_init()

其实就是在radeon.h上的一个宏定义,引用了在之前通过radeon_asic_init()初始化过的电路相关初始化调用。这个电路相关的初始化函数被调用。对于RV100,就是定义在r100.c中的r100_init(),对于RV770,就是rv770_init().

对于radeon_device_init()完成上面说的这些已经很多了。接下来我们看看和电路特定的初始化函数到底做了什么。
他们遵循同样的模式,尽管一些电路可能做得多或者少,取决于他们的功能。我们看看在r100.c中的r100_init()。

首先,我们初始化debugfs。这是一个内核调试架构,这里不讨论。然后我们调用r100_vga_render_disable,关闭显卡的vga引擎。这个vga引擎提供了VGA的兼容性,由于我们将直接对显卡进行编程,所以我们关闭了它。
接着,我们通过定义在radeon_device.c的radeon_scratch_init()来设置GPU的划痕寄存器。这些被CP(command processor,命令处理器)所使用的划痕寄存器(scratch register)用来标识图形绘制的事件。通常他们是被我们称为fence的东西所使用的。对其中一个划痕寄存器的写入的操作可以被添加到命令流中,然后发送给GPU。当执行到这个命令的时候,GPU 就会将特定的值写入到这个划痕寄存器中。驱动然后就可以去检查划痕寄存器的值来决定这个fence是否发生。例如,如果你想知道GPU是否已将完成了对一个buffer的渲染工作,那么你将渲染命令后面插入一个fence.然后你就可以检查划痕寄存器的值来决定这个fence是否已经通过,也就是说渲染已经完成。

radeon_get_bios()

从PCI ROM BAR中载入显卡的bios. 这个显卡的BIOS包含有数据和命令表格。数据表定义了类似于显卡上的connectors的数量和类型,以及这些connector是如何与encoder,GPIO寄存器相互映射的,DDC所使用的位域以及其他i2c总线,笔记本的LVDS屏幕信息,显示以及PLL引擎的限制等等。命令表是用来初始化硬件(正常应该是有系统BIOS在启动时来完成,但是需要类似于像睡眠/恢复以及初始化二级显卡的东西),并且在用ATOM bios的系统上,命令表是被用来设置显示并更改像引擎以及内存时钟这样的东西。

初始化bios 划痕寄存器

在radeon_combios_init()中调用radeon_combios_initialize_bios_scratch_regs().这些寄存器是一种系统上的固件与图形驱动直接交流状态的方式。他们包含像连接输出,无论驱动还是固件都将处理像lid或者模式更改这样的事件,等等。

radeon_boot_test_post_card()

检查系统的bios是否被载入到显卡并启动。这个操作被用来决定显卡是否需要驱动通过bios 命令表来进行初始化还是说系统bios已经完成了这个工作。

radeon_get_clock_info()

从bios表格中获取PLL( phase locked loop,用于生成时钟)信息。这个包括显示PLL,引擎以及内存的PLL,用于生成最终时钟的PLL的引用时钟。

radeon_pm_init()

初始化芯片上的电源管理特性。

初始化MC(memory controller)

r100_mc_init().GPU有和CPU类似的自己的地址空间。 在这个地址空间你可以映射VRAM和GART.在片上的模块(2D, 3D引擎,显示控制等等)可以通过GPU的地址空间访问这些数据资源。VRAM被映射到一个偏移,GART在另外一个偏移。如果你想从位于GART内存中读取一个纹理数据,将指向GPU地址空间中的GART中的一定偏移,得到纹理的基地址。如果你想将显存中的一个buffer显示到显示器上,你将把你的一个crtc的基地址指向GPU地址空间中的显存的一个地址。内存控制器初始化函数决定了在GPU地址空间中,多少显存用于VRAM,多少用于GART.

radeon_fence_driver_init()

初始化用于fence的一些通用代码。前面已经对fence的使用描述过了。

radeon_irq_kms_init()

初始化用于中断请求的一些通用代码。

radeon_bo_init()

初始化内存管理器。memory manager.

r100_pci_gart_init()

设置板上的GART机制以及radeon_agp_init()来初始化AGP GART.这样就使得GPU能够访问到系统内存中的buffer。由于系统内存是分页的,大部分的分配是不连续的。GART提供了一个方式使得很多分开的物理页通过使用地址重映射之后看起来就像是连续的一块内存,并且你只需要将GPU的AGP基地址提供被北桥芯片就可以。板上GART在不不支持AGP的系统中也提供了同样的功能。

r100_set_safe_registers()

这个函数设置用户空间命令缓存允许访问的一个寄存器列表。当用户态驱动,如DDX(2d),mesa(3d)给GPU发送命令的时候,drm模块就会检查这些命令缓存,防止对未授权的寄存器或者内存的访问。

r100_startup()

使用所有通过r100_init()设置的内容来编码硬件。这是一个单独的函数,所有当从睡眠状态恢复是也会被调用,这种情况下当前的 硬件配置就需要保存。VRAM和GART的设置是在r100_mc_program()和r100_pci_gart_enable中编码的;irqs是在r100_irq_set()中设置的。

r100_cp_init()

初始化CP(command processor),并设置环形缓存(ring buffer). CP是芯片上的一部分,负责给GPU提供加速命令。是从ring buffer中读取命令,驱动(CPU)向ring buffer中发送命令,而GPU负责从中读取并处理命令。除了命令,你也可以向存储在GPU地址空间中其他位置的的命令缓冲中写入指针,被称为间接缓冲。例如,3D驱动可能会发送一个命令缓存给DRM;在检查之后,drm将会持有一个跟着一个fence,位于ring上的命令缓冲的指针。当CP获得ring上的这个指针的时候,它就会读取指向的命令缓冲,并处理其中的命令,然后返回到刚刚离开ring的位置。引用了这个命令缓冲的buffer将会被锁住,直到这个fence通过之后,由于在执行这些命令的时候GPU正在访问他们。

r100_wb_init()

初始化回写的划痕寄存器,这是一个特征,让GPU更新在GART内存中划痕寄存器的拷贝值。这也允许驱动(运行在cpu)访问这些寄存器的内容,而不用通过MMIO寄存器(需要通过总线)来读取他们.

r100_ib_init

初始化间接缓冲,用于像3D驱动着的用户态驱动给CP发送命令。

显示部分的初始化

radeon_modeset_init().首先我们设置显示的限制以及模式。然后我们设置输出的一些特性(radeon_modeset_create_props()),这些在X运行的时候通过xrandr properties都会被暴露出来。

在radeon_crtc_init中初始化CRTC,crtc也被称为显示控制器,是芯片上的一个模块,提供显示频率,决定一个特定的显示器指向帧缓冲中哪部分。一个CRTC提供一个独立的“head“。大部分的radeon电路都有两个CRTC,新的evergreen芯片有六个。

radeon_setup_enc_conn()设置connectors以及encoder基于显卡bios数据表的映射。encoder就是译码器,负责将数字信号转换为模拟信号输出给connectors,而connectors连接的是display,也就是显示设备。一个encoder可以被绑定到一个或者多个connectors。这个映射是重要的,因为你需要知道哪些encoder正在使用,以及绑定到了哪些,使得能够正确的显示。

radeon_hpd_init()是一个宏,指向的是电路相关的函数,为数字显示器初始化HPD(hot plug detect)硬件。HPD让你在显示连接上或者断开的时候能够获得一个中断。当出现这个中断的时候,驱动将采取合理的操作,并生成一个事件,使得用户态程序能够监听得到。应用程序就可以显示一个信息,问用户将要如何处理等等。

radeon_fbdev_init()

设置drm内核中的fb接口。这个在drm之上提供一个内核fb接口,用于终端或者其他的内核fb应用。

当驱动被卸载的时候,整个过程就是反过来;这次所有的*_fini()函数会被调用来卸载这个驱动。

写了好久,给自己以后参考,也希望对学习GPU内核的朋友有帮助。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页