當(dāng)前位置:首頁 > 公眾號(hào)精選 > Linux閱碼場(chǎng)
[導(dǎo)讀]這是一篇指導(dǎo)驅(qū)動(dòng)工程師如何使用DMA API的文檔。

一、前言

這是一篇指導(dǎo)驅(qū)動(dòng)工程師如何使用DMA API的文檔,為了方便理解,文檔中給出了偽代碼的例程。另外一篇文檔dma-api.txt給出了相關(guān)API的簡(jiǎn)明描述,有興趣也可以看看那一篇,這兩份文檔在DMA API的描述方面是一致的。

 

二、從CPU角度看到的地址和從DMA控制器看到的地址有什么不同?

在DMA API中涉及好幾個(gè)地址的概念(物理地址、虛擬地址和總線地址),正確的理解這些地址是非常重要的。

內(nèi)核通常使用的地址是虛擬地址。我們調(diào)用kmalloc()、vmalloc()或者類似的接口返回的地址都是虛擬地址,保存在"void *"的變量中。

虛擬內(nèi)存系統(tǒng)(TLB、頁表等)將虛擬地址(程序角度)翻譯成物理地址(CPU角度),物理地址保存在“phys_addr_t”或“resource_size_t”的變量中。對(duì)于一個(gè)硬件設(shè)備上的寄存器等設(shè)備資源,內(nèi)核是按照物理地址來管理的。通過/proc/iomem,你可以看到這些和設(shè)備IO 相關(guān)的物理地址。當(dāng)然,驅(qū)動(dòng)并不能直接使用這些物理地址,必須首先通過ioremap()接口將這些物理地址映射到內(nèi)核虛擬地址空間上去。

I/O設(shè)備使用第三種地址:“總線地址”。如果設(shè)備在MMIO地址空間中有若干的寄存器,或者該設(shè)備足夠的智能,它可以通過DMA執(zhí)行讀寫系統(tǒng)內(nèi)存的操作,這些情況下,設(shè)備使用的地址就是總線地址。在某些系統(tǒng)中,總線地址與CPU物理地址相同,但一般來說它們不是。iommus和host bridge可以在物理地址和總線地址之間進(jìn)行映射。

從設(shè)備的角度來看,DMA控制器使用總線地址空間,不過可能僅限于總線空間的一個(gè)子集。例如:即便是一個(gè)系統(tǒng)支持64位地址內(nèi)存和64 位地址的PCI bar,但是DMA可以不使用全部的64 bit地址,通過IOMMU的映射,PCI設(shè)備上的DMA可以只使用32位DMA地址。

我們用下面這樣的系統(tǒng)結(jié)構(gòu)來說明各種地址的概念:

在PCI設(shè)備枚舉(初始化)過程中,內(nèi)核了解了所有的IO device及其對(duì)應(yīng)的MMIO地址空間(MMIO是物理地址空間的子集),并且也了解了是PCI主橋設(shè)備將這些PCI device和系統(tǒng)連接在一起。PCI設(shè)備會(huì)有BAR(base address register),表示自己在PCI總線上的地址,CPU并不能通過總線地址A(位于BAR范圍內(nèi))直接訪問總線上的PCI設(shè)備,PCI host bridge會(huì)在MMIO(即物理地址)和總線地址之間進(jìn)行mapping。因此,對(duì)于CPU,它實(shí)際上是可以通過B地址(位于MMIO地址空間)訪問PCI設(shè)備(反正PCI host bridge會(huì)進(jìn)行翻譯)。地址B的信息保存在struct resource變量中,并可以通過/proc/iomem開放給用戶空間。對(duì)于驅(qū)動(dòng)程序,它往往是通過ioremap()把物理地址B映射成虛擬地址C,這時(shí)候,驅(qū)動(dòng)程序就可以通過ioread32(C)來訪問PCI總線上的地址A了。

如果PCI設(shè)備支持DMA,那么在驅(qū)動(dòng)中我們可以通過kmalloc或者其他類似接口分配一個(gè)DMA buffer,并且返回了虛擬地址X,MMU將X地址映射成了物理地址Y,從而定位了DMA buffer在系統(tǒng)內(nèi)存中的位置。因此,驅(qū)動(dòng)可以通過訪問地址X來操作DMA buffer,但是PCI 設(shè)備并不能通過X地址來訪問DMA buffer,因?yàn)镸MU對(duì)設(shè)備不可見,而且系統(tǒng)內(nèi)存所在的系統(tǒng)總線和PCI總線屬于不同的地址空間。

在一些簡(jiǎn)單的系統(tǒng)中,設(shè)備可以通過DMA直接訪問物理地址Y,但是在大多數(shù)的系統(tǒng)中,有一個(gè)IOMMU的硬件block用來將DMA可訪問的總線地址翻譯成物理地址,也就是把上圖中的地址Z翻譯成Y。理解了這些底層硬件,你也就知道類似dma_map_single這樣的DMA API是在做什么了。驅(qū)動(dòng)在調(diào)用dma_map_single這樣的接口函數(shù)的時(shí)候會(huì)傳遞一個(gè)虛擬地址X,在這個(gè)函數(shù)中會(huì)設(shè)定IOMMU的頁表,將地址X映射到Z,并且將返回z這個(gè)總線地址。驅(qū)動(dòng)可以把Z這個(gè)總線地址設(shè)定到設(shè)備上的DMA相關(guān)的寄存器中。這樣,當(dāng)設(shè)備發(fā)起對(duì)地址Z開始的DMA操作的時(shí)候,IOMMU可以進(jìn)行地址映射,并將DMA操作定位到Y(jié)地址開始的DMA buffer。

根據(jù)上面的描述我們可以得出這樣的結(jié)論:Linux可以使用動(dòng)態(tài)DMA 映射(dynamic DMA mapping)的方法,當(dāng)然,這需要一些來自驅(qū)動(dòng)的協(xié)助。所謂動(dòng)態(tài)DMA 映射是指只有在使用的時(shí)候,才建立DMA buffer虛擬地址到總線地址的映射,一旦DMA傳輸完畢,就將之前建立的映射關(guān)系銷毀。

雖然上面的例子使用IOMMU為例描述,不過本文隨后描述的API也可以在沒有IOMMU硬件的平臺(tái)上運(yùn)行。

順便說明一點(diǎn):DMA API適用于各種CPU arch,各種總線類型,DMA mapping framework已經(jīng)屏蔽了底層硬件的細(xì)節(jié)。對(duì)于驅(qū)動(dòng)工程師而言,你應(yīng)該使用通用的DMA API(例如dma_map_*() 接口函數(shù)),而不是和特定總線相關(guān)的API(例如pci_map_*() 接口函數(shù))。

驅(qū)動(dòng)想要使用DMA mapping framework的API,需要首先包含相關(guān)頭文件:

這個(gè)頭文件中定義了dma_addr_t這種數(shù)據(jù)類型,而這種類型的變量可以保存任何有效的DMA地址,不管是什么總線,什么樣的CPU arch。驅(qū)動(dòng)調(diào)用了DMA API之后,返回的DMA地址(總線地址)就是這種類型的。

 

三、什么樣的系統(tǒng)內(nèi)存可以被DMA控制器訪問到?

既然驅(qū)動(dòng)想要使用DMA mapping framework提供的接口,我們首先需要知道的就是是否所有的系統(tǒng)內(nèi)存都是可以調(diào)用DMA API進(jìn)行mapping?還是只有一部分?那么這些可以DMA控制器訪問系統(tǒng)內(nèi)存有什么特點(diǎn)?關(guān)于這一點(diǎn),一直以來有一些不成文的規(guī)則,在本文中我們看看是否能夠?qū)⑵淙坑涗浵聛怼?

如果驅(qū)動(dòng)是通過伙伴系統(tǒng)的接口(例如__get_free_page*())或者類似kmalloc() or kmem_cache_alloc()這樣的通用內(nèi)存分配的接口來分配DMA buffer,那么這些接口函數(shù)返回的虛擬地址可以直接用于DMA mapping接口API,并通過DMA操作在外設(shè)和dma buffer中交換數(shù)據(jù)。

使用vmalloc() 分配的DMA buffer可以直接使用嗎?最好不要這樣,雖然強(qiáng)行使用也沒有問題,但是終究是比較麻煩。首先,vmalloc分配的page frame是不連續(xù)的,如果底層硬件需要物理內(nèi)存連續(xù),那么vmalloc分配的內(nèi)存不能滿足硬件要求。即便是底層DMA硬件支持scatter-gather,vmalloc分配出來的內(nèi)存仍然存在其他問題。我們知道vmalloc分配的虛擬地址和對(duì)應(yīng)的物理地址沒有線性關(guān)系(kmalloc或者_(dá)_get_free_page*這樣的接口,其返回的虛擬地址和物理地址有一個(gè)固定偏移的關(guān)系),而在做DMA mapping的時(shí)候,需要知道物理地址,有線性關(guān)系的虛擬地址很容易可以獲取其物理地址,但是對(duì)于vmalloc分配的虛擬地址,我們需要遍歷頁表才可以找到其物理地址。

在驅(qū)動(dòng)中定義的全局變量可以用于DMA嗎?如果編譯到內(nèi)核,那么全局變量位于內(nèi)核的數(shù)據(jù)段或者bss段。在內(nèi)核初始化的時(shí)候,會(huì)建立kernel image mapping,因此全局變量所占據(jù)的內(nèi)存都是連續(xù)的,并且VA和PA是有固定偏移的線性關(guān)系,因此可以用于DMA操作。不過,在定義這些全局變量的DMA buffer的時(shí)候,我們要小心的進(jìn)行cacheline的對(duì)齊,并且要處理CPU和DMA controller之間的操作同步,以避免cache coherence問題。

如果驅(qū)動(dòng)編譯成模塊會(huì)怎么樣呢?這時(shí)候,驅(qū)動(dòng)中的全局定義的DMA buffer不在內(nèi)核的線性映射區(qū)域,其虛擬地址是在模塊加載的時(shí)候,通過vmalloc分配,因此這時(shí)候如果DMA buffer如果大于一個(gè)page frame,那么實(shí)際上我們也是無法保證其底層物理地址的連續(xù)性,也無法保證VA和PA的線性關(guān)系,這一點(diǎn)和編譯到內(nèi)核是不同的。

通過kmap接口返回的內(nèi)存可以做DMA buffer嗎?也不行,其原理類似vmalloc,這里就不贅述了。

塊設(shè)備使用的I/O buffer和網(wǎng)絡(luò)設(shè)備收發(fā)數(shù)據(jù)的buffer是如何確保其內(nèi)存是可以進(jìn)行DMA操作的呢?塊設(shè)備I/O子系統(tǒng)和

網(wǎng)絡(luò)子系統(tǒng)在分配buffer的時(shí)候會(huì)確保這一點(diǎn)的。

 

四、DMA尋址限制

你的設(shè)備有DMA尋址限制嗎?不同的硬件平臺(tái)有不同的配置方式,有的平臺(tái)沒有限制,外設(shè)可以訪問系統(tǒng)內(nèi)存的每一個(gè)Byte,有些則不可以。例如:系統(tǒng)總線有32個(gè)bit,而你的設(shè)備通過DMA只能驅(qū)動(dòng)低24位地址,在這種情況下,外設(shè)在發(fā)起DMA操作的時(shí)候,只能訪問16M以下的系統(tǒng)內(nèi)存。如果設(shè)備有DMA尋址的限制,那么驅(qū)動(dòng)需要將這個(gè)限制通知到內(nèi)核。如果驅(qū)動(dòng)不通知內(nèi)核,那么內(nèi)核缺省情況下認(rèn)為外設(shè)的DMA可以訪問所有的系統(tǒng)總線的32 bit地址線。對(duì)于64 bit平臺(tái),情況類似,不再贅述。

是否有DMA尋址限制是和硬件設(shè)計(jì)相關(guān),有時(shí)候標(biāo)準(zhǔn)總線協(xié)議也會(huì)規(guī)定這一點(diǎn)。例如:PCI-X規(guī)范規(guī)定,所有的PCI-X設(shè)備必須要支持64 bit的尋址。

如果有尋址限制,那么在該外設(shè)驅(qū)動(dòng)的probe函數(shù)中,你需要詢問內(nèi)核,看看是否有DMA controller可以支持這個(gè)外設(shè)的尋址限制。雖然有缺省的尋址限制的設(shè)定,不過最好還是在probe函數(shù)中進(jìn)行相關(guān)處理,至少這說明你已經(jīng)為你的外設(shè)考慮過尋址限制這事了。

一旦確定了設(shè)備DMA尋址限制之后,我們可以通過下面的接口進(jìn)行設(shè)定:

根據(jù)DMA buffer的特性,DMA操作有兩種:一種是streaming,DMA buffer是一次性的,用完就算。這種DMA buffer需要自己考慮cache一致性。另外一種是DMA buffer是cache coherent的,軟件實(shí)現(xiàn)上比較簡(jiǎn)單,更重要的是這種DMA buffer往往是靜態(tài)的、長(zhǎng)時(shí)間存在的。不同類型的DMA操作可能有有不同的尋址限制,也可能相同。如果相同,我們可以用上面這個(gè)接口設(shè)定streaming和coherent兩種DMA 操作的地址掩碼。如果不同,可以下面的接口進(jìn)行設(shè)定:

前者是設(shè)定streaming類型的DMA地址掩碼,后者是設(shè)定coherent類型的DMA地址掩碼。為了更好的理解這些接口,我們聊聊參數(shù)和返回值。dev指向該設(shè)備的struct device對(duì)象,一般來說,這個(gè)struct device對(duì)象應(yīng)該是嵌入在bus-specific 的實(shí)例中,例如對(duì)于PCI設(shè)備,有一個(gè)struct pci_dev的實(shí)例與之對(duì)應(yīng),而在這里需要傳入的dev參數(shù)則可以通過&pdev->dev得到(pdev指向struct pci_dev的實(shí)例)。mask表示你的設(shè)備支持的地址線信息。如果調(diào)用這些接口返回0,則說明一切OK,從該設(shè)備到指定mask的內(nèi)存的DMA操作是可以被系統(tǒng)支持的(包括DMA controller、bus layer等)。如果返回值非0,那么說明這樣的DMA尋址是不能正確完成的,如果強(qiáng)行這么做將會(huì)產(chǎn)生不可預(yù)知的后果。驅(qū)動(dòng)必須檢測(cè)返回值,如果不行,那么建議修改mask或者不使用DMA。也就是說,對(duì)上面接口調(diào)用失敗后,你有三個(gè)選擇:

1、用另外的mask

2、不使用DMA模式,采用普通I/O模式

3、忽略這個(gè)設(shè)備的存在,不對(duì)其進(jìn)行初始化

一個(gè)可以尋址32 bit的設(shè)備,其初始化的示例代碼如下:

另一個(gè)常見的場(chǎng)景是有64位尋址能力的設(shè)備。一般來說我們會(huì)首先嘗試設(shè)定64位的地址掩碼,但是這時(shí)候有可能會(huì)失敗,從而將掩碼降低為32位。內(nèi)核之所以會(huì)在設(shè)定64位掩碼的時(shí)候失敗,這并不是因?yàn)槠脚_(tái)不能進(jìn)行64位尋址,而僅僅是因?yàn)?2位尋址比64位尋址效率更高。例如,SPARC64 平臺(tái)上,PCI SAC尋址比DAC尋址性能更好。

下面的代碼描述了如何確定streaming類型DMA的地址掩碼:

設(shè)定coherent 類型的DMA地址掩碼也是類似的,不再贅述。需要說明的是:coherent地址掩碼總是等于或者小于streaming地址掩碼,因此,一般來說,我們只要設(shè)定了streaming地址掩碼成功了,那么使用同樣的掩碼或者小一些的掩碼來設(shè)定coherent地址掩碼總是會(huì)成功,因此這時(shí)候我們一般就不檢查dma_set_coherent_mask的返回值了,當(dāng)然,有些設(shè)備很奇怪,只能使用coherent DMA,那么這種情況下,驅(qū)動(dòng)需要檢查dma_set_coherent_mask的返回值。

 

五、兩種類型的DMA mapping

1、一致性DMA映射(Consistent DMA mappings )

Consistent DMA mapping有下面兩種特點(diǎn):

(1)持續(xù)使用該DMA buffer(不是一次性的),因此Consistent DMA總是在初始化的時(shí)候進(jìn)行map,在shutdown的時(shí)候unmap。

(2)CPU和DMA controller在發(fā)起對(duì)DMA buffer的并行訪問的時(shí)候不需要考慮cache的影響,也就是說不需要軟件進(jìn)行cache操作,CPU和DMA controller都可以看到對(duì)方對(duì)DMA buffer的更新。實(shí)際上一致性DMA映射中的那個(gè)Consistent實(shí)際上可以稱為coherent,即cache coherent。

缺省情況下,coherent mask被設(shè)定為低32 bit(0xFFFFFFFF),即便缺省值是OK了,我們也建議你通過接口在驅(qū)動(dòng)中設(shè)定coherent mask。

一般使用Consistent DMA mapping的場(chǎng)景包括:

(1)網(wǎng)卡驅(qū)動(dòng)和網(wǎng)卡DMA控制器往往是通過一些內(nèi)存中的描述符(形成環(huán)或者鏈)進(jìn)行交互,這些保存描述符的memory一般采用Consistent DMA mapping。

(2)SCSI硬件適配器上的DMA可以主存中的一些數(shù)據(jù)結(jié)構(gòu)(mailbox command)進(jìn)行交互,這些保存mailbox command的memory一般采用Consistent DMA mapping。

(3)有些外設(shè)有能力執(zhí)行主存上的固件代碼(microcode),這些保存microcode的主存一般采用Consistent DMA mapping。

上面的這些例子有同樣的特性:CPU對(duì)memory的修改可以立刻被device感知到,反之亦然。一致性映射可以保證這一點(diǎn)。

需要注意的是:一致性的DMA映射并不意味著不需要memory barrier這樣的工具來保證memory order,CPU有可能為了性能而重排對(duì)consistent memory上內(nèi)存訪問指令。例如:如果在DMA consistent memory上有兩個(gè)word,分別是word0和word1,對(duì)于device一側(cè),必須保證word0先更新,然后才有對(duì)word1的更新,那么你需要這樣寫代碼:

只有這樣才能保證在所有的平臺(tái)上,給設(shè)備驅(qū)動(dòng)可以正常的工作。

此外,在有些平臺(tái)上,修改了DMA Consistent buffer后,你的驅(qū)動(dòng)可能需要flush write buffer,以便讓device側(cè)感知到memory的變化。這個(gè)動(dòng)作類似在PCI橋中的flush write buffer的動(dòng)作。

2、流式DMA映射(streaming DMA mapping)

流式DMA映射是一次性的,一般是需要進(jìn)行DMA傳輸?shù)臅r(shí)候才進(jìn)行mapping,一旦DMA傳輸完成,就立刻ummap(除非你使用dma_sync_*的接口,下面會(huì)描述)。并且硬件可以為順序化訪問進(jìn)行優(yōu)化。

這里的streaming可以被認(rèn)為是asynchronous,或者是不屬于coherent memory范圍的。

一般使用streaming DMA mapping的場(chǎng)景包括:

(1)網(wǎng)卡進(jìn)行數(shù)據(jù)傳輸使用的DMA buffer

(2)文件系統(tǒng)中的各種數(shù)據(jù)buffer,這些buffer中的數(shù)據(jù)最終到讀寫到SCSI設(shè)備上去,一般而言,驅(qū)動(dòng)會(huì)接受這些buffer,然后進(jìn)行streaming DMA mapping,之后和SCSI設(shè)備上的DMA進(jìn)行交互。

設(shè)計(jì)streaming DMA mapping這樣的接口是為了充分優(yōu)化硬件的性能,為了打到這個(gè)目標(biāo),在使用這些接口的時(shí)候,你必須清清楚楚的知道調(diào)用接口會(huì)發(fā)生什么。

無論哪種類型的DMA映射都有對(duì)齊的限制,這些限制來自底層的總線,當(dāng)然也有可能是某些總線上的設(shè)備有這樣的限制。此外,如果系統(tǒng)中的cache并不是DMA coherent的,而且底層的DMA buffer不合其他數(shù)據(jù)共享cacheline,這樣的系統(tǒng)將工作的更好。

 

六、如何使用coherent DMA mapping的接口?

1、分配并映射dma buffer

為了分配并映射一個(gè)較大(page大小或者類似)的coherent DMA memory,你需要調(diào)用下面的接口:

DMA操作總是會(huì)涉及具體設(shè)備上的DMA controller,而dev參數(shù)就是執(zhí)行該設(shè)備的struct device對(duì)象的。size參數(shù)指明了你想要分配的DMA Buffer的大小,byte為單位。dma_alloc_coherent這個(gè)接口也可以在中斷上下文調(diào)用,當(dāng)然,gfp參數(shù)要傳遞GFP_ATOMIC標(biāo)記,gfp是內(nèi)存分配的flag,dma_alloc_coherent僅僅是透?jìng)髟揻lag到內(nèi)存管理模塊。

需要注意的是dma_alloc_coherent分配的內(nèi)存的起始地址和size都是對(duì)齊在page上(類似__get_free_pages的感覺,當(dāng)然__get_free_pages接受的size參數(shù)是page order),如果你的驅(qū)動(dòng)不需要那么大的DMA buffer,那么可以選擇dma_pool接口,下面會(huì)進(jìn)一步描述。

如果傳入非空的dev參數(shù),即使驅(qū)動(dòng)調(diào)用了掩碼設(shè)置接口函數(shù)設(shè)定了DMA mask,說明該設(shè)備可以訪問大于32-bit地址空間的地址,一致性DMA映射的接口函數(shù)也一般會(huì)默認(rèn)的返回一個(gè)32-bit可尋址的DMA buffer地址。要知道dma mask和coherent dma mask是不同的,除非驅(qū)動(dòng)顯示的調(diào)用dma_set_coherent_mask()接口來修改coherent dma mask,例如大小大于32-bit地址,dma_alloc_coherent接口函數(shù)才會(huì)返回大于32-bit地址空間的地址。dma pool接口也是如此。

dma_alloc_coherent函數(shù)返回兩個(gè)值,一個(gè)是從CPU角度訪問DMA buffer的虛擬地址,另外一個(gè)是從設(shè)備(DMA controller)角度看到的bus address:dma_handle,驅(qū)動(dòng)可以將這個(gè)bus address傳遞給HW。

即便是請(qǐng)求的DMA buffer的大小小于PAGE SIZE,dma_alloc_coherent返回的cpu虛擬地址和DMA總線地址都保證對(duì)齊在最小的PAGE_SIZE上,這個(gè)特性確保了分配的DMA buffer有這樣的特性:如果page size是64K,即便是驅(qū)動(dòng)分配一個(gè)小于或者等于64K的dma buffer,那么DMA buffer不會(huì)越過64K的邊界。

2、umap并釋放dma buffer

當(dāng)驅(qū)動(dòng)需要umap并釋放dma buffer的時(shí)候,需要調(diào)用下面的接口:

這個(gè)接口函數(shù)的dev、size參數(shù)上面已經(jīng)描述過了,而cpu_addr和dma_handle這兩個(gè)參數(shù)就是dma_alloc_coherent() 接口的那兩個(gè)地址返回值。需要強(qiáng)調(diào)的一點(diǎn)就是:和dma_alloc_coherent不同,dma_free_coherent不能在中斷上下文中調(diào)用。(因?yàn)樵谟行┢脚_(tái)上,free DMA的操作會(huì)引發(fā)TLB維護(hù)的操作(從而引發(fā)cpu core之間的通信),如果關(guān)閉了IRQ會(huì)鎖死在SMP IPI 的代碼中)。

3、dma pool

如果你的驅(qū)動(dòng)需非常多的小的dma buffer,那么dma pool是最適合你的機(jī)制。這個(gè)概念類似kmem_cache,__get_free_pages往往獲取的是連續(xù)的page frame,而kmem_cache是批發(fā)了一大批page frame,然后自己“零售”。dma pool就是通過dma_alloc_coherent接口獲取大塊一致性的DMA內(nèi)存,然后驅(qū)動(dòng)可以調(diào)用dma_pool_alloc從那個(gè)大塊DMA內(nèi)存中分一個(gè)小塊的dma buffer供自己使用。具體接口描述就不說了,大家可以自行閱讀。

 

七、DMA操作方向

由于下面的章節(jié)會(huì)用到DMA操作方向這個(gè)概念,因此我們先簡(jiǎn)單的描述一下,DMA操作方向定義如下:

如果你知道的話,你應(yīng)該盡可能的提供準(zhǔn)確的DMA操作方向。

DMA_TO_DEVICE表示“從內(nèi)存(dma buffer)到設(shè)備”,而 DMA_FROM_DEVICE表示“從設(shè)備到內(nèi)存(dma buffer)”,上面的這些字符定義了數(shù)據(jù)在DMA操作中的移動(dòng)方向。

雖然我們強(qiáng)烈要求驅(qū)動(dòng)在知道DMA傳輸方向的適合,精確的指明是DMA_TO_DEVICE或者DMA_FROM_DEVICE,然而,如果你確實(shí)是不知道具體的操作方向,那么設(shè)定為DMA_BIDIRECTIONAL也是可以的,表示DMA操作可以執(zhí)行任何一個(gè)方向的的數(shù)據(jù)搬移。你的平臺(tái)需要保證這一點(diǎn)可以讓DMA正常工作,當(dāng)然,這也有可能會(huì)引入一些性能上的額外開銷。

DMA_NONE主要是用于調(diào)試。在驅(qū)動(dòng)知道精確的DMA方向之前,可以把它保存在DMA控制數(shù)據(jù)結(jié)構(gòu)中,在dma方向設(shè)定有問題的適合,你可以跟蹤dma方向的設(shè)置情況,以便定位問題所在。

除了潛在的平臺(tái)相關(guān)的性能優(yōu)化之外,精確地指定DMA操作方向還有另外一個(gè)優(yōu)點(diǎn)就是方便調(diào)試。有些平臺(tái)實(shí)際上在創(chuàng)建DMA mapping的時(shí)候,頁表(指將bus地址映射到物理地址的頁表)中有一個(gè)寫權(quán)限布爾值,這個(gè)值非常類似于用戶程序地址空間中的頁保護(hù)。當(dāng)DMA控制器硬件檢測(cè)到違反權(quán)限設(shè)置時(shí)(這時(shí)候dma buffer設(shè)定的是MA_TO_DEVICE類型,實(shí)際上DMA controller只能是讀dma buffer),這樣的平臺(tái)可以將錯(cuò)誤寫入內(nèi)核日志,從而方便了debug。

只有streaming mappings才會(huì)指明DMA操作方向,一致性DMA映射隱含的DMA操作方向是DMA_BIDIRECTIONAL。我們舉一個(gè)streaming mappings的例子:在網(wǎng)卡驅(qū)動(dòng)中,如果要發(fā)送數(shù)據(jù),那么在map/umap的時(shí)候需要指明DMA_TO_DEVICE的操作方向,而在接受數(shù)據(jù)包的時(shí)候,map/umap需要指明DMA操作方向是DMA_FROM_DEVICE。

 

八、如何使用streaming DMA mapping的接口?

streaming DMA mapping的接口函數(shù)可以在中斷上下文中調(diào)用。streaming DMA mapping有兩個(gè)版本的接口函數(shù),一個(gè)是用來map/umap單個(gè)的dma buffer,另外一個(gè)是用來map/umap形成scatterlist的多個(gè)dma buffer。

1、map/umap單個(gè)的dma buffer

map單個(gè)的dma buffer的示例如下:

umap單個(gè)的dma buffer可以使用下面的接口:

當(dāng)調(diào)用dma_map_single()返回錯(cuò)誤的時(shí)候,你應(yīng)當(dāng)調(diào)用dma_mapping_error()來處理錯(cuò)誤。雖然并不是所有的DMA mapping實(shí)現(xiàn)都支持dma_mapping_error這個(gè)接口(調(diào)用dma_mapping_error函數(shù)實(shí)際上會(huì)調(diào)用底層dma_map_ops操作函數(shù)集中的mapping_error成員函數(shù)),但是調(diào)用它來進(jìn)行出錯(cuò)處理仍然是一個(gè)好的做法。這樣做的好處是可以確保DMA mapping代碼在所有DMA實(shí)現(xiàn)中都能正常工作,而不需要依賴底層實(shí)現(xiàn)的細(xì)節(jié)。沒有檢查錯(cuò)誤就使用返回的地址可能會(huì)導(dǎo)致程序失敗,可能會(huì)產(chǎn)生kernel panic或者悄悄的損壞你有用的數(shù)據(jù)。下面列舉了一些不正確的方法來檢查DMA mapping錯(cuò)誤,之所以是錯(cuò)誤的方法是因?yàn)檫@些代碼對(duì)底層的DMA實(shí)現(xiàn)進(jìn)行了假設(shè)。順便說的是雖然這里是使用dma_map_single作為示例,實(shí)際上也是適用于dma_map_page()的。

錯(cuò)誤示例一:

錯(cuò)誤示例二:

當(dāng)DMA傳輸完成的時(shí)候,程序應(yīng)該調(diào)用dma_unmap_single()函數(shù)umap dma buffer。例如:在DMA完成傳輸后會(huì)通過中斷通知CPU,而在interrupt handler中可以調(diào)用dma_unmap_single()函數(shù)。dma_map_single函數(shù)在進(jìn)行DMA mapping的時(shí)候使用的是CPU指針(虛擬地址),這樣就導(dǎo)致該函數(shù)有一個(gè)弊端:不能使用HIGHMEM memory進(jìn)行mapping。鑒于此,map/unmap接口提供了另外一個(gè)類似的接口,這個(gè)接口不使用CPU指針,而是使用page和page offset來進(jìn)行DMA mapping:

在上面的代碼中,offset表示一個(gè)指定page內(nèi)的頁內(nèi)偏移(以Byte為單位)。和dma_map_single接口函數(shù)一樣,調(diào)用dma_map_page()返回錯(cuò)誤后需要調(diào)用dma_mapping_error() 來進(jìn)行錯(cuò)誤處理,上面都已經(jīng)描述了,這里不再贅述。當(dāng)DMA傳輸完成的時(shí)候,程序應(yīng)該調(diào)用dma_unmap_page()函數(shù)umap dma buffer。例如:在DMA完成傳輸后會(huì)通過中斷通知CPU,而在interrupt handler中可以調(diào)用dma_unmap_page()函數(shù)。

2、map/umap多個(gè)形成scatterlist的dma buffer

在scatterlist的情況下,你要映射的對(duì)象是分散的若干段DMA buffer,示例代碼如下:

上面的代碼中nents說明了sglist中條目的數(shù)量(即map多少段dma buffer)。

具體DMA映射的實(shí)現(xiàn)是自由的,它可以把scatterlist 中的若干段連續(xù)的DMA buffer映射成一個(gè)大塊的,連續(xù)的bus address region。例如:如果DMA mapping是以PAGE_SIZE為粒度進(jìn)行映射,那么那些分散的一塊塊的dma buffer可以被映射到一個(gè)對(duì)齊在PAGE_SIZE,然后各個(gè)dma buffer依次首尾相接的一個(gè)大的總線地址區(qū)域上。這樣做的好處就是對(duì)于那些不支持(或者支持有限)scatter-gather 的DMA controller,仍然可以通過mapping來實(shí)現(xiàn)。dma_map_sg調(diào)用識(shí)別的時(shí)候返回0,當(dāng)調(diào)用成功的時(shí)候,返回成功mapping的數(shù)目。

一旦調(diào)用成功,你需要調(diào)用for_each_sg來遍歷所有成功映射的mappings(這個(gè)數(shù)目可能會(huì)小于nents)并且使用sg_dma_address() 和 sg_dma_len() 這兩個(gè)宏來得到mapping后的dma地址和長(zhǎng)度。

umap多個(gè)形成scatterlist的dma buffer是通過下面的接口實(shí)現(xiàn)的:

再次強(qiáng)調(diào),調(diào)用dma_unmap_sg的時(shí)候要確保DMA操作已經(jīng)完成。另外,傳遞給dma_unmap_sg的nents參數(shù)需要等于傳遞給dma_map_sg的nents參數(shù),而不是該函數(shù)返回的count。

由于DMA地址空間是共享資源,每一次dma_map_{single,sg}() 的調(diào)用都需要有其對(duì)應(yīng)的dma_unmap_{single,sg}(),如果你總是分配dma地址資源而不回收,那么系統(tǒng)將會(huì)由于DMA address被用盡而陷入不可用的狀態(tài)。

3、sync操作

如果你需要多次訪問同一個(gè)streaming DMA buffer,并且在DMA傳輸之間讀寫DMA Buffer上的數(shù)據(jù),這時(shí)候你需要小心進(jìn)行DMA buffer的sync操作,以便CPU和設(shè)備(DMA controller)可以看到最新的、正確的數(shù)據(jù)。

首先用dma_map_{single,sg}()進(jìn)行映射,在完成DMA傳輸之后,用:

或者:

來完成sync的操作,以便CPU可以看到最新的數(shù)據(jù)。

如果,CPU操作了DMA buffer的數(shù)據(jù),然后你又想把控制權(quán)交給設(shè)備上的DMA 控制器,讓DMA controller訪問DMA buffer,這時(shí)候,在真正讓HW(指DMA控制器)去訪問DMA buffer之前,你需要調(diào)用:

或者:

以便device(也就是設(shè)備上的DMA控制器)可以看到cpu更新后的數(shù)據(jù)。此外,需要強(qiáng)調(diào)的是:傳遞給dma_sync_sg_for_cpu() 和 dma_sync_sg_for_device()的ents參數(shù)需要等于傳遞給dma_map_sg的nents參數(shù),而不是該函數(shù)返回的count。

在完成最后依次DMA傳輸之后,你需要調(diào)用DMA unmap函數(shù)dma_unmap_{single,sg}()。如果在第一次dma_map_*() 調(diào)用和dma_unmap_*()之間,你從來都沒有碰過DMA buffer中的數(shù)據(jù),那么你根本不需要調(diào)用dma_sync_*() 這樣的sync操作。

下面的例子給出了一個(gè)sync操作的示例:

當(dāng)使用了這套DMA mapping接口后,驅(qū)動(dòng)不應(yīng)該再使用virt_to_bus() 這個(gè)接口了,當(dāng)然bus_to_virt()也不行。不過,如果你的驅(qū)動(dòng)使用了這些接口怎么辦呢?其實(shí)這套新的DMA mapping接口沒有和virt_to_bus、bus_to_virt()一一對(duì)應(yīng)的接口,因此,為了讓你的程序能工作,你需要對(duì)驅(qū)動(dòng)程序進(jìn)行小小的修改:你必須要保存從dma_alloc_coherent()、dma_pool_alloc()以及dma_map_single()接口函數(shù)返回的dma address(對(duì)于dma_map_sg()這個(gè)接口,dma地址保存在scatterlist 中,當(dāng)然這需要硬件支持dynamic DMA mapping ),并把這個(gè)dma address保存在驅(qū)動(dòng)的數(shù)據(jù)結(jié)構(gòu)中,并且同時(shí)/或者保存在硬件的寄存器中。

所有的驅(qū)動(dòng)代碼都需要遷移到DMA mapping framework的接口函數(shù)上來。目前內(nèi)核已經(jīng)計(jì)劃完全移除virt_to_bus() 和bus_to_virt() 這兩個(gè)函數(shù),因?yàn)樗鼈円呀?jīng)過時(shí)了。有些平臺(tái)由于不能正確的支持virt_to_bus() 和bus_to_virt(),因此根本就沒有提供這兩個(gè)接口。


九、錯(cuò)誤處理

DMA地址空間在某些CPU架構(gòu)上是有限的,因此分配并mapping可能會(huì)產(chǎn)生錯(cuò)誤,我們可以通過下面的方法來判定是否發(fā)生了錯(cuò)誤:

(1)檢查是否dma_alloc_coherent() 返回了NULL或者dma_map_sg 返回0

(2)檢查dma_map_single和dma_map_page返回了dma address(通過dma_mapping_error函數(shù))

(3)當(dāng)在mapping多個(gè)page的時(shí)候,如果中間發(fā)生了mapping error,那么需要對(duì)那些已經(jīng)mapped的page進(jìn)行unmap的操作。下面的示例代碼用dma_map_single函數(shù),對(duì)于dma_map_page也一樣適用。

示例代碼一:

示例代碼二(如果我們?cè)谘h(huán)中mapping dma buffer,當(dāng)在中間出錯(cuò)的時(shí)候,一樣要unmap所有已經(jīng)映射的dma buffer):

如果在網(wǎng)卡驅(qū)動(dòng)的tx回調(diào)函數(shù)(例如ndo_start_xmit)中出現(xiàn)了DMA mapping失敗,那么驅(qū)動(dòng)必須調(diào)用dev_kfree_skb() 來是否socket buffer并返回NETDEV_TX_OK 。這表示這個(gè)socket buffer由于錯(cuò)誤而丟棄掉了。

如果在SCSI driver的queue command回調(diào)函數(shù)中出現(xiàn)了DMA mapping失敗,那么驅(qū)動(dòng)必須返回SCSI_MLQUEUE_HOST_BUSY 。這意味著SCSI子系統(tǒng)稍后會(huì)再次重傳該command給driver。

 

十、優(yōu)化數(shù)據(jù)結(jié)構(gòu)

在很多的平臺(tái)上,dma_unmap_{single,page}()其實(shí)什么也沒有做,是空函數(shù)。因此,跟蹤映射的dma address及其長(zhǎng)度基本上就是浪費(fèi)內(nèi)存空間。為了方便驅(qū)動(dòng)工程師編寫代碼方便,我們提供了幾個(gè)實(shí)用工具(宏定義),如果沒有它們,驅(qū)動(dòng)程序中將充分ifdef或者類似的一些“work around”。下面我們并不是一個(gè)個(gè)的介紹這些宏定義,而是給出一些示例代碼,驅(qū)動(dòng)工程師可以照葫蘆畫瓢。

1、DEFINE_DMA_UNMAP_{ADDR,LEN}。在DMA buffer數(shù)據(jù)結(jié)構(gòu)中使用這個(gè)宏定義,具體例子如下:

根據(jù)CONFIG_NEED_DMA_MAP_STATE的配置不同,DEFINE_DMA_UNMAP_{ADDR,LEN}可能是定義相關(guān)的dma address和長(zhǎng)度的成員,也可能是空。

2、dma_unmap_{addr,len}_set()。使用該宏定義來賦值,具體例子如下:

3、dma_unmap_{addr,len}(),使用該宏來訪問變量。

上面的這些代碼基本是不需要解釋你就會(huì)明白的了。另外,我們對(duì)于dma address和len是分開處理的,因?yàn)樵谟行?shí)現(xiàn)中,unmaping的操作僅僅需要dma address信息就夠了。

 

十一、平臺(tái)移植需要注意的問題

如果你僅僅是驅(qū)動(dòng)工程師,并不負(fù)責(zé)將linux遷移到某個(gè)cpu arch上去,那么后面的內(nèi)容其實(shí)你可以忽略掉了。

1、Struct scatterlist的需求

如果cpu arch支持IOMMU(包括軟件模擬的IOMMU),那么你需要打開CONFIG_NEED_SG_DMA_LENGTH 這個(gè)內(nèi)核選項(xiàng)。

2、ARCH_DMA_MINALIGN

CPU體系結(jié)構(gòu)相關(guān)的代碼必須要要保證kmalloc分配的buffer是DMA-safe的(kmalloc分配的buffer也是有可能用于DMA buffer),驅(qū)動(dòng)和內(nèi)核子系統(tǒng)的正確運(yùn)行都是依賴這個(gè)條件的。如果一個(gè)cpu arch不是全面支持DMA-coherent的(例如硬件并不保證cpu cache中的數(shù)據(jù)等于main memory中的數(shù)據(jù)),那么必須定義ARCH_DMA_MINALIGN。而通過這個(gè)宏定義,kmalloc分配的buffer可以保證對(duì)齊在ARCH_DMA_MINALIGN上,從而保證了kmalloc分配的DMA Buffer不會(huì)和其他的buffer共享一個(gè)cacheline。想要了解具體的實(shí)例可以參考arch/arm/include/asm/cache.h。

另外,請(qǐng)注意:ARCH_DMA_MINALIGN 是DMA buffer的對(duì)齊約束,你不需要擔(dān)心CPU ARCH的數(shù)據(jù)對(duì)齊約束(例如,有些CPU arch要求有些數(shù)據(jù)對(duì)象需要64-bit對(duì)齊)。

 

十二、后記

如果沒有來自廣大人民群眾的反饋和建議,這份文檔(包括DMA API本身)可能會(huì)顯得過時(shí),陳舊。

此外,對(duì)這份文檔有幫助的人如下(沒有按照什么特別的順序):

Russell King
Leo Dagum
Ralf Baechle
Grant Grundler
Jay Estabrook
Thomas Sailer
Andrea Arcangeli
Jens Axboe
David Mosberger-Tang davidm@hpl.hp.com

 

備注:本文基本上是內(nèi)核文檔DMA-API-HOWTO.txt的翻譯,如果有興趣可以參考原文。

原創(chuàng)翻譯文章,轉(zhuǎn)發(fā)請(qǐng)注明出處。蝸窩科技

http://www.wowotech.net/memory_management/DMA-Mapping-api.html


免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉