標籤:style blog http io ar os 使用 sp for
通常通過讀寫裝置寄存器對裝置進行編程,在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顯卡顯命令處理機制