【原创】Linux环境下的图形系统和AMD R600显卡编程(5)——AMD显卡显命令处理机制
通常通过读写设备寄存器对设备进行编程,在X86系统上,有专门的IO指令进行编程,在其他诸如MIPS、SPARC这类系统上,通过将设备的寄存器映射到内存地址空间直接使用读写内存的方式对设备进行编程。
Radeon显卡提供两种方式对硬件进行编程,一种称为“推模式”(push mode)即直接写寄存器的方式,另一种称为拉模式,这篇blog讨论拉模式,这也是驱动中使用的模式。
在拉模式下,驱动使用命令流(Command Stream)的形式进行对显卡编程:驱动程序将需要对显卡进行配置的一连串命令写入命令缓冲区,写完之后进入让出处理器,显卡按照命令写入的顺序执行这些命令,执行完成后触发中断通知驱动。CPU将这些命令放入一个称为命令环的环形缓冲区中,命令环是GTT内存中分出来的一片内存,驱动程序往命令环中填充命令,填充完后通知GPU命令已经写入命令,GPU的命令处理器CP(Command Processor)。上一篇博客即是通过ring环内存的使用来说明如何在系统中分配内存以及建立映射关系的。
驱动写入的命令流由命令处理器CP进行解析,具体来说,CP完成以下工作:
- 接收驱动程序的命令流。驱动程序将命令流先写入系统内存,然后由CP通过总线主设备访问方式进行获取,当前支持三种命令流,除了前面说的环形缓冲命令流,还有间接缓冲1命令流和间接缓冲2命令流;
- 解析命令流,将解析后的数据传输给图形控制器的其他模块,包括3D图形处理器、2D图形处理器、视频处理器。
命令环缓冲区
在拉模式下,驱动程序在系统内存中为命令流申请一块缓冲区。GPU会根据这些命令流去执行屏幕绘图等操作。这种命令缓冲区按照环形方式进行管理,是CPU和GPU 共享的一片系统主存,CPU负责写入命令包,GPU负责读取和解析命令包。因为CPU和GPU 看到的环形缓冲区状态必须是一致性,所以CPU和GPU都要共同维护和管理环形缓冲区的状态:基地址、长度、写指针和读指针。为了使Ring Buffer能够正常工作,CPU和GPU 必须维护这种状态的一致性。Ring Buffer基地址和大小是在系统第一次启动时已经初始化好的,之后一般也不会改变。当操作Ring Buffer时, 读指针和写指针的修改非常频繁。为了维护环形缓冲区的状态一致性,当写操作者(CPU)更新写指针时,它必须将写指针告诉GPU。同样的,当读操作者(GPU)更新读指针时,它必须将读指针告知CPU。无论是CPU还是GPU都是从低地址开始进行填写或抽取操作的,一旦到了环形缓冲区的结束处,又从环形缓冲区起始处继续。
图1
整个过程如图1示,左边的Host(CPU)和右边的GPU各自记录了命令环的起始地址,并各自保存了一份读写指针,CPU写之前首先查询读指针,确认有空闲空间之后写入内容并更新写指针,GPU读取了命令之后更新读指针。
间接缓冲
在系统主存中,除了环形缓冲区之外,CP还可以从间接缓冲1和间接缓冲2中获取命令包。这个过程是这样完成的:在主命令流中(ring buffer)有一个设置CP的间接缓冲1地址和大小的寄存器。写间接缓冲1的寄存器触发CP从提供的地址处取间接缓冲区1的命令流。主命令的最后一个命令包设置间接缓冲1地址和大小;然后CP开始从间接缓冲1中取数据。间接缓冲1的数命令流可能使用间接缓冲区2。和之前的过程一样,写间接缓冲1的寄存器触发CP从间接缓冲区2中获取新的命令流。间接缓冲1流中的最后一个包设置间接缓冲2的地址和大小。CP从间接缓冲2取命令直到全部去完;执行完间接缓冲区2的命令后返回到间接缓冲1的命令流。CP从间接缓冲1中取剩余的命令一直到间接缓冲1的末尾,返回到主命令流中。
这个过程有点类似函数调用。程序在运行过程中遇到函数调用,则会使用跳转指令跳到被调用函数入口,执行完函数后跳回到原来的程序位置继续执行。这的最大调用“深度”为2。
在Linux内核radeon驱动中有一个ring test过程用于验证ring buffer是否工作正常,如果ring test通过,那么GPU和CPU交互的部分已经配置正确,可以正常工作了。
Ring buffer机制几乎在所有类型的芯片上都是一样的,区别只是r600以后的芯片ring buffer GPU端读写指针的寄存器地址发生了变化。Linux内核驱动针对不同GPU核实现ring buffer机制以及ring test过程的代码几乎是完全相同的。
从内核中拿出ring test过程的代码:
2287 int r600_ring_test(struct radeon_device *rdev)
2288 {
2289 uint32_t scratch;
2290 uint32_t tmp = 0;
2291 unsigned i;
2292 int r;
2293
2294 r = radeon_scratch_get(rdev, &scratch);
2295 if (r) {
2296 DRM_ERROR("radeon: cp failed to get scratch reg (%d).\n", r);
2297 return r;
2298 }
2299 WREG32(scratch, 0xCAFEDEAD);
2300 r = radeon_ring_lock(rdev, 3);
2301 if (r) {
2302 DRM_ERROR("radeon: cp failed to lock ring (%d).\n", r);
2303 radeon_scratch_free(rdev, scratch);
2304 return r;
2305 }
2306 radeon_ring_write(rdev, PACKET3(PACKET3_SET_CONFIG_REG, 1));
2307 radeon_ring_write(rdev, ((scratch - PACKET3_SET_CONFIG_REG_OFFSET) >> 2));
2308 radeon_ring_write(rdev, 0xDEADBEEF);
2309 radeon_ring_unlock_commit(rdev);
2310 for (i = 0; i < rdev->usec_timeout; i++) {
2311 tmp = RREG32(scratch);
2312 if (tmp == 0xDEADBEEF)
2313 break;
2314 DRM_UDELAY(1);
2315 }
2316 if (i < rdev->usec_timeout) {
2317 DRM_INFO("ring test succeeded in %d usecs\n", i);
2318 } else {
2319 DRM_ERROR("radeon: ring test failed (scratch(0x%04X)=0x%08X)\n",
2320 scratch, tmp);
2321 r = -EINVAL;
2322 }
2323 radeon_scratch_free(rdev, scratch);
2324 return r;
2325 }
2294行获取一个可用的scratch寄存器,scratch寄存器是功能未定义的寄存器,由(驱动)软件定义其功能。
2299行使用mmio的方式直接向寄存器中写入值“0xCAFEDEAD”,此时该scratch寄存器的内容为0xCAFEDEAD。
2300行向内核驱动中的ring buffer机制申请3个dword(gpu命令都是以4字节为单位计的),同时由于会有多个程序并发访问ring buffer,这里还会对ring buffer加锁。
2306-2308行代码向刚才申请到的ring buffer内存中写入3个dword的命令,关于GPU命令在下一章会详细介绍,这里的命令的意思是向刚才的scratch寄存器中写入值“0xDEADBEEF”。
2309行提交命令,上面三行代码写的命令写入ring buffer后并不会被执行,直到调用radeon_ring_unlock_commit之后命令才会被执行。
2310-2314行是一个通过轮询的方式检查scratch寄存器的过程,如果上面的命令正常运行,那么scratch寄存器的值将会是“0xDEADBEEF”,否则命令没有正常运行,ring test 失败。
从上面的示例代码中可以看到,在radeon内核驱动使用了下面三个函数就可以操作ring buffer了:
API接口函数 | 功能 | 参数 |
radeon_ring_lock | 申请ring buffer内存并锁住ring buffer,如果ring buffer被用完,则更新CPU端的读指针 | N为申请的dwords数目 |
radeon_ring_write | 向ring buffer写入命令和命令参数,这里只更新CPU端的写指针 | |
radeon_ring_commit | 更新GPU端的写指针,释放ring buffer锁 |
需要提及的是scratch寄存器,scratch寄存器是GPU预留给软件使用的寄存器,r300以前的显卡只5个scratch寄存器,以后的显卡有7个寄存器,GPU本身并不依赖这些寄存器对其进行配置,软件可以自定义其功能。上面这段代码仅仅用于验证命令是否正确执行,然而后面的轮询过程却对我们有所启发:软件发送了命令之后什么时候直到命令被执行完成了?可以按照这里面的做法,在命令尾部再添加一条写scratch寄存器的命令(当然必须保证往scratch寄存器写入的值和scratch寄存器原来的值不一样),而后轮询该scratch寄存器,如果这个寄存器被写入了我们要求其写入的值,那么就可以确定命令已经执行完了。这里实际上定义了一个软硬件同步的机制,后面中断机制的章节会讨论驱动中fence机制的实现,fence机制是使用中断实现的,但是那里面使用了我们上面提到的思想。
经过上面描述之后,阅读ring buffer的实现代码应该不难读懂了。
Linux内核中完成ring test后,会有一个indirect buffer test过程。这个过程和ring test过程完成的操作一样,写scratch寄存器。
2660 int r600_ib_test(struct radeon_device *rdev)
2661 {
2662 struct radeon_ib *ib;
2663 uint32_t scratch;
2664 uint32_t tmp = 0;
2665 unsigned i;
2666 int r;
2667
2668 r = radeon_scratch_get(rdev, &scratch);
......
2673 WREG32(scratch, 0xCAFEDEAD);
2674 r = radeon_ib_get(rdev, &ib);
......
2679 ib->ptr[0] = PACKET3(PACKET3_SET_CONFIG_REG, 1);
2680 ib->ptr[1] = ((scratch - PACKET3_SET_CONFIG_REG_OFFSET) >> 2);
2681 ib->ptr[2] = 0xDEADBEEF;
2682 ib->ptr[3] = PACKET2(0);
2683 ib->ptr[4] = PACKET2(0);
2684 ib->ptr[5] = PACKET2(0);
2685 ib->ptr[6] = PACKET2(0);
2686 ib->ptr[7] = PACKET2(0);
2687 ib->ptr[8] = PACKET2(0);
2688 ib->ptr[9] = PACKET2(0);
2689 ib->ptr[10] = PACKET2(0);
2690 ib->ptr[11] = PACKET2(0);
2691 ib->ptr[12] = PACKET2(0);
2692 ib->ptr[13] = PACKET2(0);
2693 ib->ptr[14] = PACKET2(0);
2694 ib->ptr[15] = PACKET2(0);
2695 ib->length_dw = 16;
2696 r = radeon_ib_schedule(rdev, ib);
......
2703 r = radeon_fence_wait(ib->fence, false);
......
2708 for (i = 0; i < rdev->usec_timeout; i++) {
2709 tmp = RREG32(scratch);
2710 if (tmp == 0xDEADBEEF)
2711 break;
2712 DRM_UDELAY(1);
2713 }
.....
2721 radeon_scratch_free(rdev, scratch);
2722 radeon_ib_free(rdev, &ib);
2723 return r;
2724 }
2668-2673行的内容和ring test的过程一样。
2674行从系统中获取一个indirect buffer,ib->ptr中记录了indirect buffer在内存中的位置。
2679-2694向indirect buffer中填充命令和参数,这里填写的命令和参数与ring test 中填写的命令和参数是相同的,当然这里也有对齐要求。
2696 行将填写好的indirect buffer添加到调度队列中。
2703行涉及fence机制,在中断机制一节中我们将详细介绍。
同样读懂indirect buffer机制的代码也不会有太大困难。
Indirect buffer要能够正常运行,必须将其插入到ring buffer的代码中去,这就类似在汇编代码中插入"call xx"指令进行函数调用一样。radeon_ring_ib_execute函数添加的命令就相当于函数调用时使用的call指令。
下一篇将描述这些命令的格式,并给出一些例子。
参考资料:
这部分的描述的内容基本上来自“Radeon R5xx Acceleration”文档。
“Graphic Engine Resource Management”对命令的调度有一些改进,可以作为进一步学习的参考。
【原创】Linux环境下的图形系统和AMD R600显卡编程(5)——AMD显卡显命令处理机制的更多相关文章
- 【原创】Linux环境下的图形系统和AMD R600显卡编程(1)——Linux环境下的图形系统简介
Linux/Unix环境下最早的图形系统是Xorg图形系统,Xorg图形系统通过扩展的方式以适应显卡和桌面图形发展的需要,然而随着软硬件的发展,特别是嵌入式系统的发展,Xorg显得庞大而落后.开源社区 ...
- Linux环境下的图形系统和AMD R600显卡编程(1)——Linux环境下的图形系统简介
转:https://www.cnblogs.com/shoemaker/p/linux_graphics01.html Linux/Unix环境下最早的图形系统是Xorg图形系统,Xorg图形系统通过 ...
- 【原创】Linux环境下的图形系统和AMD R600显卡编程(2)——Framebuffer、DRM、EXA和Mesa简介【转】
转自:http://www.cnblogs.com/shoemaker/p/linux_graphics02.html 1. Framebuffer Framebuffer驱动提供基本的显示,fram ...
- 【原创】Linux环境下的图形系统和AMD R600显卡编程(6)——AMD显卡GPU命令格式
前面一篇blog里面描述了命令环缓冲区机制,在命令环机制下,驱动写入PM4(不知道为何会取这样一个名字)包格式的命令对显卡进行配置.这一篇blog将详细介绍命令包的格式. 当前定义了4中命令包,分别是 ...
- 【原创】Linux环境下的图形系统和AMD R600显卡编程(3)——AMD显卡简介
早期的显卡仅用于显示,后来显卡中加入了2D加速部件,这些部件用于做拷屏,画点,画线等操作.随着游戏.三维模拟以及科学计算可视化等需要,对3D的需求逐渐增加,早期图形绘制工作由CPU来完成,要达到真实感 ...
- 【原创】Linux环境下的图形系统和AMD R600显卡编程(9)——R600显卡的3D引擎和图形流水线
1. R600 3D引擎 R600核心是AMD一款非常重要的GPU核心,这个核心引入了统一处理器架构,其寄存器和指令集同以前的GPU 都完全不同,对其编程也有比较大的区别. 图1显示了R600 GPU ...
- Linux环境下的图形系统和AMD R600显卡编程(2)——Framebuffer、DRM、EXA和Mesa简介
转:https://www.cnblogs.com/shoemaker/p/linux_graphics02.html 1. Framebuffer Framebuffer驱动提供基本的显示,fram ...
- 【原创】Linux环境下的图形系统和AMD R600显卡编程(10)——R600显卡的3D引擎编程
3D图形处理流水线需要流经多个硬件单元才能得到最后的渲染结果,流水线上的所有的硬件单元必须被正确编程,才能得到正确的结果. 总体上看,从图形处理流水线的源头开始,需要准备好vertex和index,在 ...
- 【原创】Linux环境下的图形系统和AMD R600显卡编程(7)——AMD显卡的软件中断
CPU上处理的中断可以分成“硬件中断”和“软件中断”两类,比如网卡产生的中断称为硬件中断,而如果是软件使用诸如"int 0x10"(X86平台上)这样的指令产生中断称为软件中断,硬 ...
随机推荐
- B1016 部分A+B (15分)
B1016 部分A+B (15分) 输入格式: 输入在一行中依次给出 A.DA.B.DB,中间以空格分隔,其中 \(0<A,B<10^10\). 输出格式: 在一行中输出 PA+PB的值. ...
- [CodeForces - 296D]Greg and Graph(floyd)
Description 题意:给定一个有向图,一共有N个点,给邻接矩阵.依次去掉N个节点,每一次去掉一个节点的同时,将其直接与当前节点相连的边和当前节点连出的边都需要去除,输出N个数,表示去掉当前节点 ...
- 代码review的流程
以前我们一直都是如果要进行代码review的时候,要不我们就直接用idea来进行查看,根据不同的来查看 但是我们都是看代码的不同来进行来实现的,其实我们不需要这样,我们可以使用工具Phabricato ...
- 4x4矩阵键盘 扫描程序
一:不排除第四位异常处理 uchar JuzhenkeyScan() { // P3=0xfe; // temp=P3; // while(temp!=0xfe) // { // temp=P3; / ...
- mongo创建数据库和用户
1.linux安装mongo conf文件配置: 配置文件: dbpath=/home/data/mongodb/mongodb logpath=/home/data/logs/mongodb.log ...
- 《Cracking the Coding Interview》——第1章:数组和字符串——题目7
2014-03-18 01:55 题目:给定一个MxN矩阵,如果某个元素为0,则将对应的整行和整列置为0. 解法:单独挑出一行和一列作为标记数组.因为某元素为0就全部置为0,所以不论A[i][j]为0 ...
- h5布局之道(最终篇)
大家好,时隔一年多了,前几篇探讨的rem布局后来又有改进不过一直没有想起来更新博客,rem布局淘宝用的也比较早,有兴趣的可以看看淘宝的flexible ,我的用法比较简单,原来一样,废话不说了直接上代 ...
- Selenium+Python自动化之如何绕过登录验证码
一.使用Fiddler抓包 1.一般登陆网站成功后,会生成一个已登录状态的cookie,那么只需要直接把这个值拿到,用selenium进行addCookie操作即可. 2.可以先手动登录一次,然后抓取 ...
- python - web自动化测试 - 元素操作 - 窗口切换
# -*- coding:utf-8 -*- ''' @project: web学习 @author: Jimmy @file: 元素操作-切换.py @ide: PyCharm Community ...
- unity值得推荐的网址
免费字体下载网站:http://www.dafont.com/ 免费声音文件下载网站:http://freesound.org/ http://incompetech.com/mus ...