關於DMA和它的仇家


[基礎知識]什么叫做DMA?
DMA=Direct Memory Access。這是一種通過硬件實現的數據傳輸機制。簡單的說,就是不在CPU的參與下完成數據的傳輸。
[/基礎知識]
不太明白?我舉個簡單的例子:
比如有個數組a,我希望把這個數組中的內容傳輸到另一個數組b中。我們假設這兩個數組都是一樣大。比如int a[10000];int b[10000];。
那么我可以這樣做:
[code=c]for(int x=0;x<sizeof(a)/sizeof(int);x++){
    b[x]=a[x];
}
[/code]
循環將數組中的每個元素進行傳遞。這是最簡單的一種方法,也是最容易理解的方法。
不過這種方法雖然簡單,效率可算不上高。如果你了解微機原理和匯編的話就明白了,b[x]=a[x];這句話並非像你看到的那樣,把a[x]中的元素值賦給b[x]。
那是怎么一個過程?
實際上是這樣的:首先a[x]中的元素值賦給某CPU中的寄存器,然后再將該寄存器的值賦給b[x]。
為什么會這樣?
這是因為a和b都是在內存中的,而CPU不允許內存直接進行數據傳輸。所以在這個過程中CPU必須參一腳當中介。
可想而知每賦值一次都要中介,效率就這么被降下去了。
既然問題是出在CPU當中介這個地方,那么有什么方法可以回避掉這個瓶頸呢?有。那就是DMA。
DMA是一種硬件設備。這種設備的工作原理是這樣的:
——首先CPU告訴DMA設備,要有一堆數據需要傳輸,為了效率而請它出馬。(DMA請求)
——DMA收到CPU的消息,開始准備。此時CPU把數據源地址、數據目標地址、傳輸數據量、傳輸模式等等參數告訴它。(DMA初始化)
——DMA初始化完,向CPU發送消息“借你的總線用一用,我要開始傳輸數據了!”(總線出借,DMA啟動)
——CPU收到消息后,暫時切斷自己與總線的聯系。DMA開始傳輸數據。(DMA數據)
——DMA傳輸完數據之后,向CPU發送消息“搞定了!總線還給你。”(總線歸還)
——CPU說:“干得好!老將出馬一個頂倆!辛苦了,你先歇着吧。”DMA設備停止。CPU該干啥干啥。
由於是硬件實現的,所以DMA的速度非常快。快到什么程度呢?在DS上,尤其是數據量非常大的時候,相比於CPU當中介,效率能夠提高一百萬倍以上。
由於DMA的速度是如此之快,所以大量的數據傳輸,一般都要求使用DMA。
那么,剛才那個例子就可以寫成:
[code=c]dmaCopy((void*)a,(void*)b,sizeof(a));
[/code]
但是DS是個很特殊的平台,在某些情況下,DMA不適用。有些內存區域,DMA是無法訪問的。那就是BIOS、TCM和Cache。

[基礎知識]BIOS是一塊被硬件保護的內存區域。這塊區域正常情況下是“讀寫保護”。也就是說,采用正常的方法是無法訪問這塊內存的。自然,DMA也無法訪問。直接讀取BIOS,讀出來的全是隨機的數據。
那么,我想DUMP BIOS,該怎么做呢?
這就需要一些技巧了。現在我先不說,到教程的最后我再把這個坑給填了。
[/基礎知識]
[基礎知識]TCM=Tightly Coupled Memory。這是一種高速緩存,據說是被直接集成在CPU芯片中。DS有兩種TCM,分別是ITCM(Instruction TCM)和DTCM(Data TCM)。不用解釋你們也能知道這兩種TCM是干啥用的。
[/基礎知識]
由於是高速緩存,所以這兩塊內存區域被當做特殊的用途。比如某些對時間要求非常嚴格的代碼,就可以被放到ITCM中執行。這可以有效地提高運行速度。某些需要頻繁存取的數據,也可以放到DTCM中以節省存取時間。
怎么樣把代碼放到ITCM中?有兩種方法。一種是使用gcc特有的“屬性標簽”,將指定代碼賦予“ITCM”屬性,此時該代碼會被載入ITCM中執行。還有一種方法是直接將.c源文件改成.itcm.c,此時源文件會被直接編譯成在ITCM中運行的目標文件。
而DTCM就方便得多了。雖然兩個TCM都是可映射的,也就是說,它們的地址並非固定,但是NDSLIB的lnkscript將這兩塊TCM映射到了0x01000000和0x0B000000上。既然已經有了固定地址,那么就可以很輕松地訪問了。不過,正如剛才所說的,這兩塊內存空間都是有特殊用途的,所以不建議直接訪問。相比於ITCM來說,DTCM更加重要。因為在這塊內存中,存在着一個非常重要的對象——棧。
“棧”這種東西我也不詳細解釋了。局部變量和函數調用的參數,就是靠棧進行傳遞的。
由於DMA無法訪問TCM,所以也就無法訪問棧。又由於局部變量是被開辟到棧中,所以DMA也無法對局部變量進行傳遞。舉個例子:
我要往主引擎的標准調色盤中填充隨機顏色。
下面的代碼就是錯誤的:
[code=c]void fillRandomColorToMainPalette(){
    u16 tmpPalette[256];
    dmaCopy((void*)tmpPalette,(void*)BG_PALETTE,sizeof(tmpPalette));
}
[/code]
原因很簡單,tmpPalette中的數據雖然是隨機的,但這個數組是局部變量,被開辟在棧中,DMA無法訪問。
下面則是正確方法:
[code=c]void fillRandomColorToMainPalette(){
    u16 tmpPalette[256];
    memcpy((void*)BG_PALETTE,(void*)tmpPalette,sizeof(tmpPalette));
}
[/code]
memcpy不是需要CPU參與嗎?那豈不是很慢?
是的。很慢,相比於DMA來說慢多了。不過,目前我們只能用它。教程的最后我將教你一種又快又安全的方法。

[基礎知識]什么是Cache?
眾所周知CPU的速度非常快。當CPU訪問外設的時候,有些外設速度比較慢,響應CPU比較遲鈍。此時CPU要么等外設響應,要么繼續干它的活等外設的中斷信號。
但是有些外設是沒有中斷的。此時CPU就必須等了。最典型的例子就是內存。
當CPU訪問內存的時候,並非像你想象的那樣,CPU立刻就能訪問到它想訪問的內存空間,而是有一個“WaitState”的過程。想想看吧,每訪問一次內存都要等上幾個機器周期,這可不是個好事~~~尤其是,這個“幾”可不是簡單的一位數,有些時候甚至能達到3位數。
那么這個問題又該怎么解決呢?那就是Cache了。
Cache是集成在CPU內部的極高速的緩存。注意關鍵詞“極高速”。一般來說,它的訪問速度幾乎可以媲美CPU。這就意味着,CPU在訪問Cache的時候幾乎不會浪費多少時間。不過,速度的提升是用容量作為代價的。Cache的容量很小。在DS中,數據緩沖(Data Cache,簡寫為DC)只有4K,指令緩沖(Instruction Cache,簡寫為IC)只有8K。
[/基礎知識]
那么,我們把常用的數據放到Cache中,CPU在訪問的時候直接訪問Cache就行了,不用耗費時間去訪問內存了。
事實上CPU就是這么做的。在讀內存的時候,CPU首先讀Cache,看看有沒有它想要的數據的“副本”,有的話那就太好了,直接拿過去用。沒有的話就只好費點功夫去讀內存了。而在寫內存的時候,CPU直接寫到Cache中,而非直接寫到內存中。
哪尼?
確實是這樣子的。當Cache寫滿了之后,此時才將Cache中的數據更新到內存,同時清空Cache。就像寄信一樣,所有的信件會首先攢到郵局,到達一定數量之后才會送出去。
不過這又出現一個問題:假如Cache中有某個內存數據的“副本”,那么CPU在讀該內存的時候就會直接使用該副本而不用去讀內存。那萬一內存中的數據被改寫,此時CPU再讀該內存,讀出來的豈不是那個舊的副本而不是最新的內存數據?同樣,假如我想DMA一些數據,誰能保證此時內存中的數據就是最新的數據?
某人:那啥,我直接讀寫Cache得了。鳥內存真它奶奶的麻煩!
很可惜,Cache是完全的黑箱。你不知道它的地址。你也無法直接訪問它。
那該怎么辦呢?幸好NDSLIB為我們提供了一些函數:
[code=c]// 將整個Data Cache更新到內存
void DC_FlushAll()
// 將Data Cache中指定地址指定大小的區域更新到內存
void DC_FlushRange(const void *base, u32 size)

// 清空整個Data Cache
void DC_InvalidateAll()
// 清空Data Cache中指定地址指定大小的區域
void DC_InvalidateRange(const void *base, u32 size)

// 清空整個Instruction Cache
void IC_InvalidateAll()
// 清空Instruction Cache中指定地址指定大小的區域
void IC_InvalidateRange(const void *base, u32 size)
[/code]
那么,什么時候使用這些函數呢?
在DMA之前,我需要保證數據源內存中的數據是最新的。所以此時需要Flush,從而使DC中的副本能夠更新到內存中。
在DMA之后,我需要保證DC中的副本和內存中的數據是相同的。但是NDSLIB沒有更新DC的函數,所以沒辦法,我們只能把DC中的副本殺掉。此時如果CPU訪問內存,由於DC中沒有副本,所以就只能直接從內存訪問並將訪問到的值作為DC中副本了。所以此時需要Invalidate。
至於是用All還是Range,很明顯,All的話肯定最保險,但肯定也會更花時間。所以如果你有把握,那就用Range以節省時間;沒把握就用All求穩妥。

注意,模擬器在模擬Cache上相當不完善。根據我的觀察,三大著名模擬器——NO$GBA、desmume和ideas,都無法正確模擬Cache。所以如果你發現在模擬器上圖像正常而到了真機上出現破碎、混亂、顏色異常等等問題,設法把你的DMA函數前后加上DC_Flush...(...);和DC_Invalidate...(...);。

[填坑]正如我所說的,DMA非常之快。一般情況下的數據傳輸,靠它沒問題。不過有些場合DMA無法訪問,我又想要快,那該怎么辦呢?有請swiCopy和swiFastCopy!
[code=c]#define COPY_MODE_HWORD  (0)
#define COPY_MODE_WORD  (1<<26)
#define COPY_MODE_COPY  (0)
#define COPY_MODE_FILL  (1<<24)
void swiCopy(const void * source, void * dest, int flags);
void swiFastCopy(const void * source, void * dest, int flags);
[/code]
這兩個函數是所謂的“BIOS軟中斷”,又稱“系統調用”。你可以把他看成是GBA/DS特有的函數。
這兩個函數非常神奇,在GBA中,它們比DMA更快!不過很可惜,在DS中則一點都不快,甚至比memcpy還慢。
這兩個函數沒有區域限制。不管是BIOS還是內存,不管是ITCM還是DTCM,都能直接訪問。
現在我們來改寫一下那個隨機調色盤的例子:
[code=c]void fillRandomColorToMainPalette(){
    u16 tmpPalette[256];
    swiCopy((void*)tmpPalette,(void*)BG_PALETTE,COPY_MODE_WORD|COPY_MODE_COPY|(sizeof(tmpPalette)>>2));
}
[/code]
注意最后一個參數。它的單位是WORD,即4字節。因此數據大小需要除以4以轉換成WORD。
swiFastCopy則比swiCopy更快。你若是追求更高的速度也可以換成swiFastCopy。不過注意,這個函數只能傳輸半字寬度,也就是2字節的數據,因此不能使用COPY_MODE_WORD這個模式。但是傳輸數據大小依然以WORD為單位。
[/填坑]
最后我們把坑填了吧。這是DUMP ARM9的BIOS的函數:
[code=c]
#define BIOS_ADDRESS 0xFFFF0000
#define BIOS_SIZE 32768
void dumpARM9BIOS(){
    void* tmpBuffer=calloc(BIOS_SIZE,1);
    swiCopy((void*)BIOS_ADDRESS,(void*)tmpBuffer,COPY_MODE_WORD|COPY_MODE_COPY|(BIOS_SIZE>>2));
    DC_FlushAll();
    FILE* f=fopen("arm9.bios","wb+");
    if(f!=NULL)fwrite(tmpBuffer,BIOS_SIZE,1,f);
    fclose(f);
    free(tmpBuffer);
}
[/code]

yuuhimesama 發表於 2009-10-3 15:50

坐下慢慢看。。
話說R4沒有開啟DMA模式,哎。。。

gbayp 發表於 2009-10-9 18:50

牛B{:5_174:}

wjzj5886 發表於 2009-11-12 21:33

技術貼支持 雖然不懂函數 但是基本原理看懂了

5826659 發表於 2009-11-18 15:34

掌機類別的開發都要掌握匯編的吧···唉····

imcome 發表於 2009-11-29 00:24

有收獲,不錯,呵呵

明日青蛙 發表於 2010-1-17 19:01

懂了一點~~~

明日青蛙 發表於 2010-1-17 19:01

呵呵~~~以后寫代碼直接用dmacopy了~~~原來memcopy也不快啊~~~

17625 發表於 2010-2-23 14:49

Great!
Thanks a lot,

幻の上帝 發表於 2010-7-13 11:11

雷叔還是把錯誤改一下吧...有些童鞋可能通過Google什么的找到這里。。。
[b]NDS上效率一般swiCopy<swiFastCopy<memcpy<dmaCopy...
[/b]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM