www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 公眾號精選 > Linux閱碼場
[導讀]這是一篇指導驅動工程師如何使用DMA API的文檔。

一、前言

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

 

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

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

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

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

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

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

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

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

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

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

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

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

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

驅動想要使用DMA mapping framework的API,需要首先包含相關頭文件:

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

 

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

既然驅動想要使用DMA mapping framework提供的接口,我們首先需要知道的就是是否所有的系統(tǒng)內(nèi)存都是可以調(diào)用DMA API進行mapping?還是只有一部分?那么這些可以DMA控制器訪問系統(tǒng)內(nèi)存有什么特點?關于這一點,一直以來有一些不成文的規(guī)則,在本文中我們看看是否能夠將其全部記錄下來。

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

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

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

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

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

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

網(wǎng)絡子系統(tǒng)在分配buffer的時候會確保這一點的。

 

四、DMA尋址限制

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

是否有DMA尋址限制是和硬件設計相關,有時候標準總線協(xié)議也會規(guī)定這一點。例如:PCI-X規(guī)范規(guī)定,所有的PCI-X設備必須要支持64 bit的尋址。

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

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

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

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

1、用另外的mask

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

3、忽略這個設備的存在,不對其進行初始化

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

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

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

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

 

五、兩種類型的DMA mapping

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

Consistent DMA mapping有下面兩種特點:

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

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

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

一般使用Consistent DMA mapping的場景包括:

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

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

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

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

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

只有這樣才能保證在所有的平臺上,給設備驅動可以正常的工作。

此外,在有些平臺上,修改了DMA Consistent buffer后,你的驅動可能需要flush write buffer,以便讓device側感知到memory的變化。這個動作類似在PCI橋中的flush write buffer的動作。

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

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

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

一般使用streaming DMA mapping的場景包括:

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

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

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

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

 

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

1、分配并映射dma buffer

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

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

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

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

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

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

2、umap并釋放dma buffer

當驅動需要umap并釋放dma buffer的時候,需要調(diào)用下面的接口:

這個接口函數(shù)的dev、size參數(shù)上面已經(jīng)描述過了,而cpu_addr和dma_handle這兩個參數(shù)就是dma_alloc_coherent() 接口的那兩個地址返回值。需要強調(diào)的一點就是:和dma_alloc_coherent不同,dma_free_coherent不能在中斷上下文中調(diào)用。(因為在有些平臺上,free DMA的操作會引發(fā)TLB維護的操作(從而引發(fā)cpu core之間的通信),如果關閉了IRQ會鎖死在SMP IPI 的代碼中)。

3、dma pool

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

 

七、DMA操作方向

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

如果你知道的話,你應該盡可能的提供準確的DMA操作方向。

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

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

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

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

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

 

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

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

1、map/umap單個的dma buffer

map單個的dma buffer的示例如下:

umap單個的dma buffer可以使用下面的接口:

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

錯誤示例一:

錯誤示例二:

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

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

2、map/umap多個形成scatterlist的dma buffer

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

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

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

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

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

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

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

3、sync操作

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

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

或者:

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

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

或者:

以便device(也就是設備上的DMA控制器)可以看到cpu更新后的數(shù)據(jù)。此外,需要強調(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操作。

下面的例子給出了一個sync操作的示例:

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

所有的驅動代碼都需要遷移到DMA mapping framework的接口函數(shù)上來。目前內(nèi)核已經(jīng)計劃完全移除virt_to_bus() 和bus_to_virt() 這兩個函數(shù),因為它們已經(jīng)過時了。有些平臺由于不能正確的支持virt_to_bus() 和bus_to_virt(),因此根本就沒有提供這兩個接口。


九、錯誤處理

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

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

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

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

示例代碼一:

示例代碼二(如果我們在循環(huán)中mapping dma buffer,當在中間出錯的時候,一樣要unmap所有已經(jīng)映射的dma buffer):

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

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

 

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

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

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

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

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

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

上面的這些代碼基本是不需要解釋你就會明白的了。另外,我們對于dma address和len是分開處理的,因為在有些實現(xiàn)中,unmaping的操作僅僅需要dma address信息就夠了。

 

十一、平臺移植需要注意的問題

如果你僅僅是驅動工程師,并不負責將linux遷移到某個cpu arch上去,那么后面的內(nèi)容其實你可以忽略掉了。

1、Struct scatterlist的需求

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

2、ARCH_DMA_MINALIGN

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

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

 

十二、后記

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

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

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)翻譯文章,轉發(fā)請注明出處。蝸窩科技

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


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

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

9月2日消息,不造車的華為或將催生出更大的獨角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關鍵字: AWS AN BSP 數(shù)字化

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

關鍵字: 汽車 人工智能 智能驅動 BSP

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

關鍵字: 亞馬遜 解密 控制平面 BSP

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

關鍵字: 騰訊 編碼器 CPU

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

關鍵字: 華為 12nm EDA 半導體

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

關鍵字: 華為 12nm 手機 衛(wèi)星通信

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

關鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

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

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

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

關鍵字: BSP 信息技術
關閉
關閉