收藏 分销(赏)

GPU内核代码分析.doc

上传人:pc****0 文档编号:9009062 上传时间:2025-03-11 格式:DOC 页数:31 大小:547KB 下载积分:10 金币
下载 相关 举报
GPU内核代码分析.doc_第1页
第1页 / 共31页
GPU内核代码分析.doc_第2页
第2页 / 共31页


点击查看更多>>
资源描述
基于片上系统的radeon rs780显卡驱动设计 1 平台相关信息 4 2 RS780图形加速相关技术 5 2.1 显存空间分配和管理 5 2.1.1 显存空间分配示例 5 2.1.2 显存空间分配管理机制 7 2.2 微码加载 11 2.3 命令处理器和缓冲区 11 2.3.1 命令处理器 11 2.3.2 环形缓冲区 13 2.3.3 间接缓冲区 14 3 pm4包 17 4 CPU与GPU 的fence同步 24 5 模式设置和测试 26 6 GPU驱动代码分析 27 6.1 PCI空间资源访问 27 6.2 GPU初始化和启动流程分析 28 6.3 GPU实现代码详细分析 29 6.3.1 显存空间初始化 29 6.3.2 显存控制器和输出模式 30 6.3.3 命令控制器设置 31 平台相关信息 在实现中我们需要注意的是龙芯页面大小是16K,GPU页表项大小为8B,每个GART页表项对应一个物理页。另外,系统可以将128M VRAM空间和IO空间全部映射给CPU,因此对CPU而言,可以实现对显存的的管理和操作。GPU详细信息请参考ATI的《R6xx_R7xx_3D.pdf》等文档。 1 RS780图形加速相关技术 rs780显卡驱动的设计(参考DRM/radeon)中,主要涉及到显存空间分配和管理、微码加载、CP&ring buffer、pm4包、CPU与GPU fence同步和模式设置及测试等相关技术。 1.1 显存空间分配和管理 2.1.1 显存空间分配示例 经过对系统信息的获取和分析,我们得知GPU的VRAM地址空间范围:从0X40000000到0X48000000;同时,还可以使用GART机制将系统内存空间映射为GPU显存,在我们的设计中仅仅使用了128M,作为测试验证使用(而radeon显卡一般需要分配不少于512M)。并通过PCI BAR0和BAR2寄存器,将PCI设备(此系统上GPU为PCI设备) memory和IO空间映射出来,以供CPU访问。显存分配结构图-2.1.1。 图-2.1.1 显存分配结构 现对图-2.1.1做详细分析,rs780 radeon 显卡是32位GPU,可以访存空间是4G。硬件设计上,VRAM大小是128M,地址范围:0X40000000~0X47FFFFFF;从0X48000000之上,是我们映射的128M系统主存空间,称为GTT主存空间。这样以来,我们就可以根据使用需求来对显存做具体划分,比如,我们在GTT主存上为ring buffer分配空间,地址范围:0X48004000~0X48103FFF。VRAM的分配较为简单(在此没对显存做管理,仅是最简单的使用方式),直接指定地址就可以,比如, rdev.zone[FB_ZONE].size = ALIGN( 8 << 20UL, PAGE_SIZE); rdev.zone[FB_ZONE].gpu = rdev.zone[VRAM_ZONE].gpu; rdev.zone[FB_ZONE].cpu = rdev.zone[VRAM_ZONE].cpu; 这样,只要我们自己做合理的划分使用,就可以保证访问显存的合理合法性。另一方面,在显存的使用中比较麻烦的是GTT主存部分。这里要将系统主存映射为显存空间,然后才能使用。这其中涉及到分页机制的使用,而且在我们的系统上,CPU和GPU页大小并不一致,CPU页为16K,GPU页为4K(第一节中有介绍),具体关系见图-2.2.2。 图-2.1.2 系统内存和显存映射关系 在这样的一个过程,首先要分配连续的系统主存页,然后使用页表机制,将分配的空间映射到GTT主存空间。其中一个主要的函数是static inline void set_gpu_page(uint64_t dma_addr, uint32_t index),用于实现填写页表项的过程。 static inline void set_gpu_page(uint64_t dma_addr, uint32_t index) { void __iomem *ptr = (void *)rdev.zone[GTT_TABLE_ZONE].cpu; dma_addr &= 0xfffffffffffff000ULL; dma_addr |= R600_PTE_VALID | R600_PTE_SYSTEM | R600_PTE_SNOOPED; dma_addr |=R600_PTE_READABLE |R600_PTE_WRITEABLE; writeq(dma_addr, ((void __iomem *)ptr) + (index * 8)); } 将dma_addr地址写到以ptr为基址,每次向index索引(偏移)位置写8B(页表项大小)。现以为ring buffer分配1M连续空间分配为例。 rdev.cp_dma_addr = dma_addr; tmp_addr = rdev.zone[RING_ZONE].gpu; for (j = 0; j < rdev.zone[RING_ZONE].size/PAGE_SIZE; j++) { tmp_dma_addr = dma_addr + j * PAGE_SIZE; for(i = 0;i < PAGE_SIZE/GPU_PAGE_SIZE; i++) { set_gpu_page(tmp_dma_addr + i * GPU_PAGE_SIZE, (tmp_addr - rdev.zone[GTT_ZONE].gpu)>>12UL); tmp_addr += GPU_PAGE_SIZE; } } 外循环是一次16K,内循环是16K分4(PAGE_SIZE/GPU_PAGE_SIZE)次填写页表项。页表项和页表的对应关系见图-2.3。 图-2.1.3 页表项和内存页关系 具体实现,见gpu_virtual_location()和相关的其他函数。 2.1.2 显存空间分配管理机制 1 现在的显存管理主要针对两种基本的内存类型: 固定内存 对内存管理而言,它是PCI空间。这种内存通常是卡上的VRAM或AGP内存。例如,对AGP而言,X server能分配128M内存空间并告诉内存管理这个空间是固定分配给X server使用的;而在我们示例系统中是VRAM。 转换表内存 通过动态地分配系统内存页绑定到转换表,从而GPU能够访问系统内存页,比如,AGP aperture。 这两种类型的内存是cache一致或non-cache一致的。一般而言,普通硬件仅仅支持non-cache-coherent内存,但是它有以下缺点: 读数据很慢,因为处理器不预先从内存中去数据。一般速度只有10MB/s。 从cache-coherent到no-cache-coherent内存的转换有较长的延时,因为会发生一个全局CPU cache和TLB flush。这将影响转换表内存的性能,但这是可以被优化的。一般而言,这个延时等于读一个4KB的non-cache-coherent内存或写256KB写一致的AGP内存。然而,延时不受从cache-coherent到non-cache-coherent页转换数目的限制。同时,最近的dri-devel列表所述,CPU可以映射固定内存或不映射。 2 基于显存管理的实现,需要解决下列问题: 内存资源要对所有的用户都是可用的 不应该使用可用内存之外的空间 仅仅需要维护一个内存buffer拷贝 当我们想让内存buffer工作或GPU正在其上运行时,buffer不应该莫名其妙的消失 从显存读取数据不应该慢 内存管理应该快速,那么它是否应该在内核中 Buffer应该是可共享的,例如,X server可以在DRI clients上画图 以下针对上述问题做分配讲解: 2.1 内存资源对所有用户可用 这是一个在内核中内存管理的情况。所有内存buffer对内核是可见的并且可以释放它们,将其作为其他内存类型或回收为系统主存。X server 能够为不同的固定内存和转换表内存域设置最多7个内存管理。一般实现是VRAM一个显存管理,per-bound AGP内存一个内存管理,转换表AGP一个内存管理。客户决定是buffer是哪种内存类型并且DRM驱动设备决定什么时候分配和释放。比如,如果VRAM拷贝操作比AGP快,或对于系统转换表内存,如果支持快速PCI分散/聚集DMA, VRAM可能被释放。 2.2 不能使用可用内存外的空间 在上述内存类型中,转换表内存是很丰富的。如果我们需要更多的转换表内存,我们可以从系统主存中分配页表,作为一个buffer并分配给转换表使用。如果转换表空间不够用(如,通过读AGP aperture),就应该释放其他转换表buffer,甚至其他用户的转换表buffer。只要我们不改变缓存策略,释放是很快的。 如果我们需要更多的VRAM空间,我们可以释放无用的VRAM buffer作为转换表内存使用(这是通过DMA或快速块传送实现的)。因此,又有许多可以使用的转换表。 转换表内存使用的页必须是内核页,但我们不能分太多的内核页给转换表。因为如果分太多的页给转换表,在某些情况下,可能致使系统不可用,而任何DRI用户能够做各自的事情。因此,应该设置一个限制,应该分配多少内核页只为转换表用。当超过了这个限制,对标准的交互系统就要暂时释放一些不使用的页。不幸的是,这在内核中并不是容易实现的方式。可交换的页属于用户空间进程并且不应该有其他内核内存子系统知道这个引用。内核开发的人员这样做的目的是让X server 映射一个大的匿名区域,告诉显存管理这个区域。Linux mmap()操作不会分配任何页,因此不会浪费内存空间。当我们需要释放暂时不使用的buffer页时,可以简单地让X server映射它们,标记放在哪里然后释放它们。当我们需要时,内核可以把它们作为第二存储(系统主存)交换出去。通过告诉内核X server页在用户空间的地址,这就比较容易得到反馈。 2.3 CPU仅仅应该维护一个主存buffer的单个拷贝 通过释放拷贝的buffer内容可以达到这个目标,因为拷贝操作是很快的。转换表主存甚至不需要这样做,因为释放仅仅是从转换表中解除绑定。 2.4 当CPU或GPU要使用内存buffer时,它不能无缘无故的消失 GPU 为了保证GPU正在使用的一个buffer不会被回收,在我们发送命令给GPU之前,我们要validate该buffer。这样可以确保buffer放在我们想放的位置并且不会被释放。然后,我们采用fence对象和该buffer发生联系。fence对象在GPU命令流中,当GPU完成所有的命令处理时fence对象发送信号给CPU,fence对象发送信号后,如果需要才会安全地回收缓冲。许多buffer能够分配同样的fence对象而且fence对象能够由用户空间访问。一个驱动可以实现多种类型的fence对象,根据buffer的内容(video/3D)和可能的硬件完成的flush操作。 在我们需要等到一个fence对象终止的情况下,一个fence对象实现最简单的形式是仅仅等待GPU空闲。一个轻量级较为先进的形式是增加一个序列数的blit操作给命令流并且等待这个序列数被传送。而在I915DRM驱动使用命令流用户IRQ来实现一个更加精巧的实现,对不同的命令buffer和buffer的不同fence类型在被释放之前需要一个读/写flush操作。 CPU 我们怎样确保当CPU正在写一个buffer的时候该buffer不会被释放,又怎样保证当一个buffer被释放时能够从驱动中找到它呢?最简单的方式是我们不必关心它。只管放心去写或从buffer中读。 这是主存管理的一个关键特性。它允许一个特定的性能优化类,并使驱动写操作更加容易。这种技巧使用的是CPU页表机制。在一个buffer被释放或移动,所有的用户空间页表引用它的进程都被杀死。当CPU试图访问buffer中一页时,我们简单地更新页表项为buffer中该页的当前位置。如果它不在我们映射的VRAM中,就将它块传送到映射的VRAM中。试图从buffer中读或写的进程不会发现这种情况,但当buffer需要移动到映射的VRAM上可能会阻塞片刻。 当前转换表内存的实现是这样的:当一个buffer需要被释放时,它的用户空间页表会被杀死。然后,取消绑定。如果再次有效,就从新绑定。如果CPU访问时候发现buffer没有被绑定就首先从non-cache-coherent转换为cache-coherent,然后用户页表项会被更新指向该buffer。这种转换比较慢但是不会经常发生。 2.5从显存读取数据不应该很慢 由于处理器不会从non-cache-coherent中预先取数据,因此读数据就非常慢。通常只有10MB/s。以下是加速的方式: 将转换表内存从aperture中移出并确保cache一致性。通常读一到二个页的操作会有一个延时因此区域读应该对性能有较大提升。 硬件协助cache一致性内存拷贝。比如一个PCI DMA 分发/聚集操作或一个硬件块传送来cache一致转换表内存。 对大的buffer,第一个方法会更好些但是对小的数据第二种较好。DRI驱动能够根据用户空间需求实现一个合适的策略。 2.6内存管理应该高效,是否应该在kernel中 可以不放在内核中。现在的i915 tex驱动是一个例证,它显示事情可以做的很好。它当前仅仅实现了non-cache-coherent的转换表内存,因此当buffer被创建或销毁时会带来cache策略变化延时,但增加的内核IOCTL负担非常小。 2.6.1 子分配器 当移植i915 tex驱动时,创建和销毁批量buffer就比较明显,cache一致性策略变化延时就很大并对性能有很大影响。因此,用一个大的内存管理buffer创建一个批量buffer池,该内存管理buffer是在用户空间由固定大小的批量buffer的一个子分配器来管理的。 这也可能是获得纹理内存实现最高性能的一种方式。内存在DRM中以大块的形式被分配的,分配后的空间也是由用户空间来管理的。DRM内存管理页表机制使这种方式成为可能,因为DRI驱动不会通知并不需要关心一个内核块是否释放。 子分配器也从内存使用者的角度获益。因为页表机制,内核DRM buffer大小需要页大小(4KB)对齐。如果一个用户使用很多小的纹理,子分配器是一种很节约内存的方式。 2.7 buffer应该是共享的 所有的DRM buffer都有一个唯一的标示,这是在buffer被创建时产生的,创建的进程标示它们是否应该被共享。通常,共享的buffer有back-buffer和depth-buffer或绘图重定向。 当一个用户想和其他用户共享一个buffer时,它会发送标示符给其他用户,并且其他用户调用libdrm函数来增加buffer被引用次数并获取信息。仅当该buffer没有被引用时buffer才会被释放。 1.2 微码加载 RS780显卡微码的加载分为两个部分,atombios和microcode。atombios部分主要做模式设置和GPU最原始的初始化工作,由于CPU中的BIOS做了部分类似的工作,在此GPU驱动程序设计中没有加载atombios。在此主要介绍用于设置命令控制器的微码:microengine(ME)和prefetch parser(PFP)。 对ME和PFP的加载分成了两个阶段, 1.3 命令处理器和缓冲区 2.3.1 命令处理器 CP是面向图形控制器的可编程的专用计算引擎,主要是处理环形缓冲和间接线性缓冲的命令流,即获取和翻译PROMO4命令流,然后将顶点索引存入索引缓冲,并将索引缓冲基地址发送给VGT(Vertex grouper/Tesselator);VGT启动DMA从索引缓冲中获取顶点索引,然后将顶点索引发送给SPI(Shader Pipe Interpolator—插值器);同时,VGT还会将图元连接信息发送给图元组装器PA。 顶点处理 所有着色过程全部在统一着色模块(aka着色硬核)中处理,着色模块包括序列器模块SQ和着色流水线模块SP。每个着色器程序都可以访问一组通用寄存器GPRs(General Purpose Registers),这些通用寄存器在运行时进行动态分配。SPI用顶点基地址等参数对通用寄存器进行设置。然后,针对SQ,SPI会启动顶点着色器运行。着色器要做的第一件事是获取所需的顶点,然后交给应用顶点着色器;着色器运行后的结果存入着色器输出缓冲SX。如果存在几何着色器GS,顶点着色器VS就相当于GS的输出着色器。 像素处理 顶点处理完成后,几何数据就传回给图元组装器PA,进行图元组装和计算设置。PA的输出结果进入扫描转换器SC,进行扫描转换。SC会检查深度缓冲DB中像素点的有效性。产生的像素点(位置和质心)发往SPI,然后到着色器核心进行像素处理。像素着色器应用程序(包括纹理获取,ALU计算,Scratch RAM读写)运行。最后,着色器内存交换单元SMX充当了scratch内存访问之前的读写缓冲。一旦完成,像素点的位置和颜色就通过SX发往DB,最终到达CB进行渲染最后阶段的处理。 最后渲染 像素发送给DB和CB做最后处理,该过程包括alpha测试、深度测试和blending/分辨率设置。对于深度和混合而言,可以存在多个表面类型(16b,24b,32b,1-z(24)等)。另外,像素裁剪后的数据和颜色被缓存到DB中并且DB支持它们所有的压缩。图形管道线结构图见图-2.3.1.1。 图-2.3.1.1 图形管道线结构 图形控制器中的CP主要完成以下任务: 1) 接收驱动程序的命令流。驱动程序要么将命令流先写入系统内存,然后由CP通过总线主设备访问方式进行获取;要么就直接通过PCI设备访问方式将命令流写给CP。当前支持三种命令流:环形缓冲命令流、间接缓冲1命令流和间接缓冲2命令流。 2) 解析命令流,将解析后的数据传输给图形控制器的其他内部特征模块,内部特征模块包括3D图形处理器、2D图形处理器、视频处理器或者MPEG解码器。每时钟间隔内,数据传输可以是32位、64位、96位或者128位;其中64位、96位和128位数据传输必须在向量写模式下进行。而且,向量写模式只有在命令流处于拉模式下才有效。推模式下只能进行32位的数据传输。 3) CP内部有两通用DMA引擎,一个用于图形用户接口相关任务,一个专门用于视频采集任务。DMA引擎要求窗口源地址和目的地址都必须进行字节对齐。 接受驱动程序的命令流有两种方式,对GPU的CP而言,称为“推模式”和“拉模式”。推模式就是可编程IO模式,即驱动程序通过PCI总线访问直接操作GPU;通过这种方式,驱动程序可以将寄存器写序列或命令包序列直接发送给GPU。 1) 寄存器写序列。设置图形控制器的处理引擎状态,然后启动引擎。一般说来,通过某个寄存器的写可以触发引擎。这种情况常常用于调试。 2) 命令包序列。以压缩方式将命令信息传输给GPU;然后,GPU中的智能控制器将命令包转换成对其他处理引擎的寄存器写。 在拉模式下,CPU先将命令信息写入系统主存;然后GPU使用总线主设备访问方式,读取系统主存。因此拉模式需要解决的重要问题是,CPU和GPU如何管理和访问系统主存中的共享缓冲。 推模式的命令缓冲受限与GPU片上存储空间;而拉模式就不存在这样的限制。 推模式在某些整体系统性能上也有自己的优点,例如访存带宽比较少。另外,实现GPU片上命令缓冲溢出到帧缓冲技术,也可以缓解推模式的缓冲受限状况;但是,这也要求帧缓冲带宽能满足命令缓冲读写的需求。 CP在推拉两种模式之间的切换必须谨慎处理。切换常常不能成功;一般说来,整机系统在复位过程就选择好了模式(拉模式会被程序员优先选择),在整个系统运行过程中不进行模式切换。 2.3.2 环形缓冲区 在拉模式下,驱动程序在系统内存中为命令包申请一块缓冲区。GPU会根据这些命令包去执行屏幕绘图等操作。这种命令缓冲区按照环形方式进行管理,是CPU和GPU共享的一片系统主存,CPU负责写入命令包,GPU负责读取和解析命令包。因为CPU和GPU看到的环形缓冲区状态必须是一致性,所以CPU和GPU都要共同维护和管理环形缓冲区的状态:基地址、长度、写指针和读指针。为了使Ring Buffer能够正常工作,CPU和CPU必须维护这种状态的一致性。Ring Buffer基地址和大小是在系统第一次启动时已经初始化好的,之后一般也不会改变。一个简单的工作是初始化这种状态的读指针和写指针的拷贝。另一方面,当操作Ring Buffer时, 读指针和写指针的修改非常频繁;为了维护环形缓冲区的状态一致性,当写操作者(CPU)更新写指针时,它必须发这个值给图像控制器写指针的读拷贝。同样的,当读操作者(GPU)更新读指针时,它必须发这个值给读指针的写拷贝。CPU填写命令包、递增写指针后将写指针内容发送给GPU;当GPU抽取命令包、递增读指针后将读指针内容发送给CPU。无论是CPU还是GPU都是从低地址开始进行填写或抽取操作的,一旦到了环形缓冲区的结束处,又从环形缓冲区起始处继续;同时,GPU从队列头取CPU写入的命令包,一旦到达队列尾,又从头开始。 状态 说明 初始化值 运行动态变化 基地址 环形缓冲区起始地址,包括两种形式:内核虚地址,供CPU使用;PCI地址,供GPU使用。 驱动申请缓冲区时就固定了 不变 长度 环形缓冲区长度。 不变 写指针 CPU将要写入的单元索引 零 递增变化 读指针 GPU正在读取的单元索引 递增变化 在拉模式下,ring buffer操作及其控制结构图见图-2.3.1.1。 2.3.3 间接缓冲区 在系统主存中,除了环形缓冲区之外,CP还可以从间接缓冲1和间接缓冲2从获取命令包。这个过程是这样完成的:在主命令流中(ring buffer)有一个设置CP的间接缓冲1地址和大小的寄存器。写间接缓冲1大小的寄存器触发CP从提供的地址取新的命令流。从主命令流解析最后一个命令包来设置间接缓冲1地址和大小的寄存器;然后CP开始从间接缓冲1中取数据。间接缓冲1的数据流可能要设置CP的间接缓冲2地址和大小的寄存器。和之前的过程一样,写间接缓冲1大小的寄存器触发CP从提供的地址中获取新的命令流。从间接缓冲1流中解析最后一个包来设置间接缓冲2地址和大小的寄存器。CP从间接缓冲2取一定数量的数据一直到间接缓冲末尾;然后从间接缓冲1返回它的命令包解析。CP从间接缓冲1中取数据一直到间接缓冲1的末尾,然后从主命流(ring buffer)返回它的命令包解析。 与间接缓冲1/2相关的寄存器描述如下: 寄存器名称 位域描述 默认值 CP_IB2_BASE IB2_BASE [31:2] None 间接缓冲2在PCI内存空间的基地址。只能双字访问该寄存器。 CP_IB2_BUFSZ IB2_BUFSZ [22:0] 0x0 间接缓冲2长度,单位:双字。只能双字访问该寄存器。当写寄存器就导致从间接缓冲2中获取命令包。 CP_IB_BASE IB_BASE [31:2] None 间接缓冲1在PCI内存空间的基地址。只能双字访问该寄存器。 CP_IB_BUFSZ IB_BUFSZ [22:0] 0x0 间接缓冲1长度,单位:双字。只能双字访问该寄存器。当写寄存器就导致从间接缓冲1中获取命令包。 当CP从环形缓冲区中读取到一个用于设置间接缓冲1的地址和长度寄存器的命令包时,CP首先执行CP_IB_BASE寄存器写操作,然后紧接着执行写CP_IB_BUFSZ寄存器操作,然后就从间接缓冲1获取命令包;直到取完间接缓冲1的命令包,执行完毕后,返回环形缓冲区。 当CP从间接缓冲区1中读取到一个用于设置间接缓冲2的地址和长度寄存器的命令包时,CP首先执行CP_IB2_BASE寄存器写操作,然后紧接着执行写CP_IB2_BUFSZ寄存器操作,然后就从间接缓冲2获取命令包;直到取完间接缓冲2的命令包,执行完毕后,返回间接缓冲1。 注意:驱动使用间接缓冲发送命令包,必须注意以下几个规则: 1) CP_IB_BUFSZ或CP_IB2_BUFSZ寄存器的写,必须是类型0或类型1的命令包中最后一个寄存器写操作。 2) 在CP_IB_BUFSZ或CP_IB2_BUFSZ设置之前,CP_IB_BASE或CP_IB2_BASE寄存器必须用正确的值进行设置。 在推模式下,要用间接缓冲1/2长度来填写CP_IB_BUFSZ或CP_IB2_BUFSZ寄存器。在命令队列填充到CP之前,必须谨慎处理这两个寄存器的写操作。 2 pm4包 PM4 命令包 ATI显卡可以运行在PM4模式下,在这种模式下,不需要直接向寄存器中写数据执行绘图操作,而是在系统内存中准备PM4格式的命令包并让硬件(显卡的微引擎)执行绘图命令。 1型当前定义了4种命令包,分别是0型/1型/2型和3型命令包,命令包由两部分组成,第一部分是命令包头,第二部分是命令包主体,命令包头为请求GPU执行的具体操作,命令主体为执行该操作需要的数据。 1. 命令包格式 0型命令包用于写连续N个寄存器。 [插入R5xx Acceleration v1.5.pdf 29-33页内容] 1型命令包用于写两个寄存器,部分寄存器使命令无法访问到,这个时候需要使用0型命令 [插入R5xx Acceleration v1.5.pdf 29-33页内容] 2型命令包是一个空命令包,用于填充对其命令 [插入R5xx Acceleration v1.5.pdf 29-33页内容] 3型命令包主要执行绘图命令,图形的主要功能都是通过这个包实现的。 [插入R5xx Acceleration v1.5.pdf 29-33页内容] 2. 命令包使用示例 通过几个例子来说明命令包的使用方法 2.1 0型命令包 radeon_ring_write为向命令环中写入4字节数据。 ./drivers/gpu/drm/radeon/r600.c r600_fence_ring_emit函数有如下语句: radeon_ring_write(rdev, PACKET0(CP_INT_STATUS, 0)); radeon_ring_write(rdev, RB_INT_STAT); PACKET0定义如下: #define PACKET0(reg, n) ((PACKET_TYPE0 << 30) | \ 包类型 0型命令包 (((reg) >> 2) & 0xFFFF) | \ 寄存器偏移基地址 ((n) & 0x3FFF) << 16) 要写的寄存器书目 所有类型的数据包31-30bit为包类型标识符,0型数据包的类型标识符为0,其30位为PACKET_TYPE0(0x0),29-16bit为命令写的寄存器数量-1((n) & 0x3FFF) << 16),上面例子只写一个寄存器,其值为0。12-0bit ((reg) >> 2) & 0xFFFF)为第一个寄存器偏移地址的第14-2bit,由于使用0型包可以访问的所有寄存器都是4字节的,寄存器地址都是4字节对其的,所以低2位为0。 2.2 1型命令包 由于1型数据包可以用0型数据包代替而且1型数据包并不能够访问到所有寄存器,在linux-2.6.32.20的内核radeon驱动中并没有使用1型命令包。 2.3 2型命令包 2型命令包不做任何操作,仅用于填充用,填充ring buffer的时候有对齐要求,2.6.32.20内核radeon驱动对齐要求是16个dword(16×4字节),在命令没有16 dword对齐的时候,就需要使用2型命令包填充。 drivers/gpu/drm/radeon/radeon_ring.c radeon_ring_commit函数用于通知GPU从ring buffer中取数据并执行,该函数包含如下代码: count_dw_pad = (rdev->cp.align_mask + 1) - (rdev->cp.wptr & rdev->cp.align_mask); for (i = 0; i < count_dw_pad; i++) { radeon_ring_write(rdev, 2 << 30); } 第一句用于计算对齐命令需要的dword数目,后面的for循环用于填充2型命令。2型命令仅有个命令头部,并且只有31-30bit有效。 2.4 3型命令包 3型命令是主要的命令包,涵盖了寄存器设置/绘图命令/同步等主要操作。 以下是一个使用3型命令包设置寄存器的例子,这段代码来自drivers/gpu/drm/radeon/r600.c 的r600_ib_test函数: ib->ptr[0] = PACKET3(PACKET3_SET_CONFIG_REG, 1); ib->ptr[1] = ((scratch - PACKET3_SET_CONFIG_REG_OFFSET) >> 2); ib->ptr[2] = 0xDEADBEEF; ib->ptr[3] = PACKET2(0); ib->ptr[4] = PACKET2(0); ...... ib->ptr[15] = PACKET2(0); ib->length_dw = 16; 这段代码使用了indirect buffer,但是填充的命令和ring buffer中填充的命令是一样的。 #define PACKET3(op, n) ((PACKET_TYPE3 << 30) | \ (((op) & 0xFF) << 8) | \ ((n) & 0x3FFF) << 16) 3型命令头部包含了操作码op和数据数目(以dword计)。上面例子中PACKET3(PACKET3_SET_CONFIG_REG, 1) PACKET3_SET_CONFIG_REG表明这次命令包用于设置寄存器,1表明后面有2个dword数据,分别是(scratch - PACKET3_SET_CONFIG_REG_OFFSET) >> 2(寄存器地址)和0xDEADBEEF(往寄存器中写的值)。后面是用于对齐用的2型包。 下面使用一个更加复杂的命令包来说明3型包的使用,下面的这个命令包用于执行一个简单的2D操作。r600显卡是ATI推出的第一款使用统一着色器的GPU,r600及其以后的显卡不包含单独的2D单元,而是使用3D部件执行2D操作。为了简单起见,这里我们使用r500显卡上的填充矩形的命令包。 radeon_ring_write(rdev, PACKET3(PACKET3_PAINT_MULTI, 6)); radeon_ring_write(rdev, RADEON_GMC_DST_PITCH_OFFSET_CNTL | RADEON_GMC_DST_CLIPPING | // important RADEON_GMC_BRUSH_SOLID_COLOR | // 13 << 4 (RADEON_COLOR_FORMAT_ARGB8888 << 8) | // << 8 RADEON_GMC_SRC_DATATYPE_COLOR | // 4 << 12 RADEON_ROP3_P | // << 16 RADEON_GMC_CLR_CMP_CNTL_DIS); // 1 << 28 radeon_ring_write(rdev, ((pitch / 64) << 22) | (fb_offset>>10)); radeon_ring_write(rdev, 0 | (0 << 16)); // SC_TOP_LEFT radeon_ring_write(rdev, (fb_w -1) | ((fb_h -1) << 16)); // SC_BOT_RITE radeon_ring_write(rdev, color); // this is color radeon_ring_write(rdev, (x << 16) | y); radeon_ring_write(rdev, (w << 16) | h); radeon_ring_write(rdev, PACKET0(RADEON_DSTCACHE_CTLSTAT, 0)); radeon_ring_write(rdev, RADEON_RB2D_DC_FLUSH_ALL); radeon_ring_write(rdev, PACKET0(RADEON_WAIT_UNTIL, 0)); radeon_ring_write(rdev, RADEON_WAIT_2D_IDLECLEAN| RADEON_WAIT_HOST_IDLECLEAN| RADEON_WAIT_DMA_GUI_IDLE); 3 型包根据他们IT_OPCODE的不同,其IT_BODY差别很大,如果IT_OPCODE的最高位为1(通常是2D绘图命令),那么PACKET还需要加入GUI control。R500上的2D绘图命令有如下格式: HEADER GUI_CONTROL SETUP_BODY DATA_BLOCK 其中Header部分对应3型命令包的头,GUI_CONTROL/SETUP_BODY共同构成了当前绘图环境的配置,这两部分加上DATA_BLOCK共同构成了3型包的IT_BODY部分。 上面的代码第一句表明该命令包执行的是矩形绘制(PAINT_MULTI可以同时绘制多个矩形,这里我们只绘制了一个矩形)。 第二句对应GUI_CONTROL,GUI_CONTROL为32bit,内容为当前绘制环境的标志,下表给出了代码中使用的一些标志(如果是blit操作,除了表中的DSTxx参数外,还需要设置对应的SRCxx参数),关于这些标志更详细的信息可以参考“R5xx Acceleration v1.5.pdf”35-36页相关内容。 bit Field name Description 1 DST_PITCH_OFFSET 绘图目标区域的PITCH值和该区域在GPU虚拟地址空间中的偏移,如果该为被置为1,则需要在SETUP_BODY中指定该参数。 3 DST_CLIPPING 设置绘图区域的裁剪参数,如果该位置为1,则需要在SETUP_BODY中设置SC_TOP_LEFT和SC_BOTTOM_RIGHT参数。 7:4 BRUSH_TYPE 绘图时使用的brush类型,brush类型需要根据这里给出的类型在SETUP_BODY中填brush包,不同的BRUSH_TYPE对应的brush包不同 11:8 DST_TYPE 绘图目标区域的像素类型: 1 :- (reserved) 2 :- 8 bpp pseudoc
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传

当前位置:首页 > 包罗万象 > 大杂烩

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:0574-28810668  投诉电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服