使用通用裝置的動態 DMA 對映

作者:

James E.J. Bottomley <James.Bottomley@HansenPartnership.com>

本文件描述了 DMA API。有關 API 的更溫和的介紹(和實際示例),請參閱動態 DMA 對映指南

此 API 分為兩部分。第一部分描述了基本 API。第二部分描述了支援非一致性記憶體機器的擴充套件。除非您知道您的驅動程式絕對必須支援非一致性平臺(這通常只適用於舊平臺),否則您應該只使用第一部分中描述的 API。

第一部分 - dma_API

要獲取 dma_API,您必須 #include <linux/dma-mapping.h>。這提供了 dma_addr_t 和下面描述的介面。

dma_addr_t 可以儲存平臺上任何有效的 DMA 地址。它可以提供給裝置作為 DMA 源或目標使用。CPU 不能直接引用 dma_addr_t,因為其物理地址空間和 DMA 地址空間之間可能存在轉換。

第一部分a - 使用大型 DMA 一致性緩衝區

void *
dma_alloc_coherent(struct device *dev, size_t size,
                   dma_addr_t *dma_handle, gfp_t flag)

一致性記憶體是指裝置或處理器寫入後,處理器或裝置可以立即讀取的記憶體,無需擔心快取效應。(但是,您可能需要確保在告訴裝置讀取該記憶體之前重新整理處理器的寫入緩衝區。)

此例程分配一個大小為 <size> 位元組的一致性記憶體區域。

它返回指向已分配區域的指標(在處理器的虛擬地址空間中),如果分配失敗則返回 NULL。

它還返回一個 <dma_handle>,該控制代碼可以轉換為與匯流排寬度相同的無符號整數,並作為該區域的 DMA 地址基址提供給裝置。

注意:在某些平臺上,一致性記憶體可能成本高昂,並且最小分配長度可能與頁面一樣大,因此您應該儘可能整合您的一致性記憶體請求。最簡單的方法是使用 dma_pool 呼叫(見下文)。

標誌引數(僅限於 dma_alloc_coherent())允許呼叫者為分配指定 GFP_ 標誌(參見kmalloc())(實現可以選擇忽略影響返回記憶體位置的標誌,如 GFP_DMA)。

void
dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,
                  dma_addr_t dma_handle)

釋放您之前分配的一致性記憶體區域。dev、size 和 dma_handle 必須與傳遞給 dma_alloc_coherent() 的值相同。cpu_addr 必須是 dma_alloc_coherent() 返回的虛擬地址。

請注意,與它們的同級分配呼叫不同,這些例程只能在 IRQ 啟用時呼叫。

第一部分b - 使用小型 DMA 一致性緩衝區

要獲取 dma_API 的這一部分,您必須 #include <linux/dmapool.h>

許多驅動程式需要大量小型 DMA 一致性記憶體區域用於 DMA 描述符或 I/O 緩衝區。您可以使用 DMA 池,而不是使用 dma_alloc_coherent() 以頁面或更大的單位進行分配。它們的工作方式與 struct kmem_cache 非常相似,只是它們使用 DMA 一致性分配器,而不是 __get_free_pages()。此外,它們理解常見的硬體對齊約束,例如佇列頭需要對齊到 N 位元組邊界。

struct dma_pool *
dma_pool_create(const char *name, struct device *dev,
                size_t size, size_t align, size_t alloc);

dma_pool_create() 初始化一個 DMA 一致性緩衝區池,用於給定裝置。它必須在可以休眠的上下文下呼叫。

“name”用於診斷(類似於 struct kmem_cache 名稱);dev 和 size 類似於您傳遞給 dma_alloc_coherent() 的引數。裝置對此類資料的硬體對齊要求是“align”(以位元組表示,必須是 2 的冪)。如果您的裝置沒有跨邊界限制,則將 alloc 設為 0;傳遞 4096 表示從此池分配的記憶體不得跨越 4KB 邊界。

void *
dma_pool_zalloc(struct dma_pool *pool, gfp_t mem_flags,
                dma_addr_t *handle)

封裝了dma_pool_alloc(),如果分配嘗試成功,還會將返回的記憶體清零。

void *
dma_pool_alloc(struct dma_pool *pool, gfp_t gfp_flags,
               dma_addr_t *dma_handle);

這會從池中分配記憶體;返回的記憶體將滿足建立時指定的尺寸和對齊要求。傳遞 GFP_ATOMIC 以防止阻塞,或者如果允許(不在中斷中,不持有 SMP 鎖),則傳遞 GFP_KERNEL 以允許阻塞。與 dma_alloc_coherent() 類似,這會返回兩個值:CPU 可用的地址,以及池裝置可用的 DMA 地址。

void
dma_pool_free(struct dma_pool *pool, void *vaddr,
              dma_addr_t addr);

這會將記憶體放回池中。pool 是傳遞給dma_pool_alloc()的;CPU (vaddr) 和 DMA 地址是該例程分配要釋放的記憶體時返回的地址。

void
dma_pool_destroy(struct dma_pool *pool);

dma_pool_destroy() 釋放池的資源。它必須在可以休眠的上下文中呼叫。在銷燬池之前,請確保已將所有已分配的記憶體釋放回池中。

第一部分c - DMA 定址限制

int
dma_set_mask_and_coherent(struct device *dev, u64 mask)

檢查掩碼是否可能,如果可能,則更新裝置的流式和一致性 DMA 掩碼引數。

返回:如果成功則返回 0,否則返回負錯誤。

int
dma_set_mask(struct device *dev, u64 mask)

檢查掩碼是否可能,如果可能,則更新裝置引數。

返回:如果成功則返回 0,否則返回負錯誤。

int
dma_set_coherent_mask(struct device *dev, u64 mask)

檢查掩碼是否可能,如果可能,則更新裝置引數。

返回:如果成功則返回 0,否則返回負錯誤。

u64
dma_get_required_mask(struct device *dev)

此 API 返回平臺高效執行所需的掩碼。通常這意味著返回的掩碼是覆蓋所有記憶體所需的最小掩碼。檢查所需的掩碼使具有可變描述符大小的驅動程式有機會根據需要使用較小的描述符。

請求所需掩碼不會更改當前掩碼。如果您希望利用它,則應發出 dma_set_mask() 呼叫將掩碼設定為返回的值。

size_t
dma_max_mapping_size(struct device *dev);

返回裝置對映的最大大小。dma_map_single()、dma_map_page() 等對映函式的大小引數不應大於返回的值。

size_t
dma_opt_mapping_size(struct device *dev);

返回裝置對映的最大最佳化大小。

在某些情況下,對映較大的緩衝區可能需要更長時間。此外,對於高速率、短壽命的流式對映,對映所花費的前期時間可能佔總請求生命週期的相當一部分。因此,如果拆分較大的請求不會導致顯著的效能損失,則建議裝置驅動程式將總 DMA 流式對映長度限制為返回的值。

bool
dma_need_sync(struct device *dev, dma_addr_t dma_addr);

如果需要呼叫 dma_sync_single_for_{device,cpu} 來傳輸記憶體所有權,則返回 %true。如果可以跳過這些呼叫,則返回 %false。

unsigned long
dma_get_merge_boundary(struct device *dev);

返回 DMA 合併邊界。如果裝置不能合併任何 DMA 地址段,則函式返回 0。

第一部分d - 流式 DMA 對映

dma_addr_t
dma_map_single(struct device *dev, void *cpu_addr, size_t size,
               enum dma_data_direction direction)

對映一段處理器虛擬記憶體,以便裝置可以訪問它,並返回該記憶體的 DMA 地址。

這兩種 API 的方向都可以透過型別轉換自由轉換。然而,dma_API 使用強型別列舉器來表示其方向

DMA_NONE

無方向(用於除錯)

DMA_TO_DEVICE

資料從記憶體流向裝置

DMA_FROM_DEVICE

資料從裝置流向記憶體

DMA_BIDIRECTIONAL

方向未知

注意

機器中並非所有記憶體區域都可以透過此 API 對映。此外,連續的核心虛擬空間在物理記憶體中可能不是連續的。由於此 API 不提供任何分散/聚集能力,如果使用者嘗試對映非物理連續的記憶體,它將失敗。因此,要透過此 API 對映的記憶體應從保證物理連續的源(如 kmalloc)獲取。

此外,記憶體的 DMA 地址必須在裝置的 dma_mask 範圍內(dma_mask 是裝置可定址區域的位掩碼,即,如果記憶體的 DMA 地址與 dma_mask 進行 AND 運算後仍等於 DMA 地址,則裝置可以對該記憶體執行 DMA)。為確保 kmalloc 分配的記憶體位於 dma_mask 範圍內,驅動程式可以指定各種平臺相關標誌來限制分配的 DMA 地址範圍(例如,在 x86 上,GFP_DMA 保證在可用 DMA 地址的前 16MB 範圍內,這是 ISA 裝置所要求的)。

另請注意,如果平臺具有 IOMMU(將 I/O DMA 地址對映到物理記憶體地址的裝置),則上述關於物理連續性和 dma_mask 的約束可能不適用。但是,為了可移植性,裝置驅動程式編寫者不得假設存在這樣的 IOMMU。

警告

記憶體一致性以稱為快取行寬度的粒度執行。為了使此 API 對映的記憶體正常工作,對映區域必須正好從快取行邊界開始並在快取行邊界結束(以防止兩個單獨對映的區域共享單個快取行)。由於快取行大小在編譯時可能未知,API 不會強制執行此要求。因此,建議不特別注意在執行時確定快取行大小的驅動程式編寫者僅對映在頁面邊界開始和結束的虛擬區域(這些區域也保證是快取行邊界)。

DMA_TO_DEVICE 同步必須在軟體對記憶體區域進行最後修改之後,並在將其交給裝置之前完成。一旦使用此原語,此原語覆蓋的記憶體應被裝置視為只讀。如果裝置可能在任何時候寫入它,則它應該是 DMA_BIDIRECTIONAL(見下文)。

DMA_FROM_DEVICE 同步必須在驅動程式訪問可能被裝置更改的資料之前完成。此記憶體應被驅動程式視為只讀。如果驅動程式在任何時候需要寫入它,則它應該是 DMA_BIDIRECTIONAL(見下文)。

DMA_BIDIRECTIONAL 需要特殊處理:這意味著驅動程式不確定記憶體是否在交給裝置之前被修改,也不確定裝置是否也會修改它。因此,您必須始終對雙向記憶體進行兩次同步:一次在記憶體交給裝置之前(以確保所有記憶體更改都從處理器中重新整理),另一次在資料被裝置使用後可能被訪問之前(以確保任何處理器快取行都用裝置可能已更改的資料進行更新)。

void
dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,
                 enum dma_data_direction direction)

解除對映先前對映的區域。傳入的所有引數必須與對映 API 傳入(並返回)的引數相同。

dma_addr_t
dma_map_page(struct device *dev, struct page *page,
             unsigned long offset, size_t size,
             enum dma_data_direction direction)

void
dma_unmap_page(struct device *dev, dma_addr_t dma_address, size_t size,
               enum dma_data_direction direction)

用於頁面對映和解除對映的 API。其他對映 API 的所有注意事項和警告也適用於此處。此外,儘管提供了 <offset> 和 <size> 引數來執行部分頁面對映,但建議您除非真正瞭解快取寬度,否則永遠不要使用它們。

dma_addr_t
dma_map_resource(struct device *dev, phys_addr_t phys_addr, size_t size,
                 enum dma_data_direction dir, unsigned long attrs)

void
dma_unmap_resource(struct device *dev, dma_addr_t addr, size_t size,
                   enum dma_data_direction dir, unsigned long attrs)

用於 MMIO 資源對映和解除對映的 API。其他對映 API 的所有注意事項和警告也適用於此處。該 API 僅應用於對映裝置 MMIO 資源,不允許對映 RAM。

int
dma_mapping_error(struct device *dev, dma_addr_t dma_addr)

在某些情況下,dma_map_single()、dma_map_page() 和 dma_map_resource() 將無法建立對映。驅動程式可以透過使用 dma_mapping_error() 測試返回的 DMA 地址來檢查這些錯誤。非零返回值表示無法建立對映,驅動程式應採取適當措施(例如,減少當前 DMA 對映使用或延遲並稍後重試)。

int
dma_map_sg(struct device *dev, struct scatterlist *sg,
           int nents, enum dma_data_direction direction)

返回:對映的 DMA 地址段數(如果分散/聚集列表的某些元素在物理或虛擬上相鄰且 IOMMU 將它們對映為單個條目,則此數字可能短於傳入的 <nents>)。

請注意,sg 一旦被對映,就不能再次對映。對映過程允許破壞 sg 中的資訊。

與其他對映介面一樣,dma_map_sg() 可能會失敗。當它失敗時,返回 0,驅動程式必須採取適當的行動。驅動程式採取行動至關重要,對於塊裝置驅動程式,中止請求甚至 oops 也比什麼都不做並損壞檔案系統要好。

對於分散列表,您可以像這樣使用結果對映

int i, count = dma_map_sg(dev, sglist, nents, direction);
struct scatterlist *sg;

for_each_sg(sglist, sg, count, i) {
        hw_address[i] = sg_dma_address(sg);
        hw_len[i] = sg_dma_len(sg);
}

其中 nents 是 sglist 中的條目數。

實現可以自由地將幾個連續的 sglist 條目合併為一個(例如,使用 IOMMU,或者如果幾個頁面碰巧在物理上連續),並返回它將它們對映到的實際 sg 條目數。失敗時返回 0。

然後,您應該迴圈計數次(注意:這可以少於 nents 次),並使用 sg_dma_address() 和 sg_dma_len() 宏,而您之前訪問的是 sg->address 和 sg->length,如上所示。

void
dma_unmap_sg(struct device *dev, struct scatterlist *sg,
             int nents, enum dma_data_direction direction)

解除對映先前對映的分散/聚集列表。所有引數必須與傳入分散/聚集對映 API 的引數相同。

注意:<nents> 必須是您傳入的數字,而不是返回的 DMA 地址條目數。

void
dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle,
                        size_t size,
                        enum dma_data_direction direction)

void
dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle,
                           size_t size,
                           enum dma_data_direction direction)

void
dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
                    int nents,
                    enum dma_data_direction direction)

void
dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
                       int nents,
                       enum dma_data_direction direction)

同步 CPU 和裝置的單個連續或分散/聚集對映。對於 sync_sg API,所有引數必須與傳遞給 sg 對映 API 的引數相同。對於 sync_single API,您可以使用與傳遞給單個對映 API 的引數不完全相同的 dma_handle 和 size 引數來執行部分同步。

注意

您必須這樣做

  • 在讀取裝置透過 DMA 寫入的值之前(使用 DMA_FROM_DEVICE 方向)

  • 在寫入將使用 DMA 寫入裝置的值之後(使用 DMA_TO_DEVICE 方向)

  • 如果記憶體是 DMA_BIDIRECTIONAL,則在將記憶體交給裝置之前之後

另請參見 dma_map_single()。

dma_addr_t
dma_map_single_attrs(struct device *dev, void *cpu_addr, size_t size,
                     enum dma_data_direction dir,
                     unsigned long attrs)

void
dma_unmap_single_attrs(struct device *dev, dma_addr_t dma_addr,
                       size_t size, enum dma_data_direction dir,
                       unsigned long attrs)

int
dma_map_sg_attrs(struct device *dev, struct scatterlist *sgl,
                 int nents, enum dma_data_direction dir,
                 unsigned long attrs)

void
dma_unmap_sg_attrs(struct device *dev, struct scatterlist *sgl,
                   int nents, enum dma_data_direction dir,
                   unsigned long attrs)

上述四個函式與不帶 _attrs 字尾的對應函式類似,只是它們傳遞一個可選的 dma_attrs。

DMA 屬性的解釋是特定於體系結構的,每個屬性都應在DMA 屬性中記錄。

如果 dma_attrs 為 0,則每個函式的語義與不帶 _attrs 字尾的相應函式的語義相同。因此,dma_map_single_attrs() 通常可以替換 dma_map_single() 等。

作為 *_attrs 函式用法的示例,以下是如何在為 DMA 對映記憶體時傳遞屬性 DMA_ATTR_FOO 的示例:

#include <linux/dma-mapping.h>
/* DMA_ATTR_FOO should be defined in linux/dma-mapping.h and
* documented in Documentation/core-api/dma-attributes.rst */
...

        unsigned long attr;
        attr |= DMA_ATTR_FOO;
        ....
        n = dma_map_sg_attrs(dev, sg, nents, DMA_TO_DEVICE, attr);
        ....

關心 DMA_ATTR_FOO 的架構會在其對映和解除對映例程的實現中檢查其存在,例如:

void whizco_dma_map_sg_attrs(struct device *dev, dma_addr_t dma_addr,
                             size_t size, enum dma_data_direction dir,
                             unsigned long attrs)
{
        ....
        if (attrs & DMA_ATTR_FOO)
                /* twizzle the frobnozzle */
        ....
}

第一部分e - 基於 IOVA 的 DMA 對映

這些 API 在使用 IOMMU 時允許非常高效的對映。它們是可選路徑,需要額外的程式碼,並且僅建議用於 DMA 對映效能或儲存 DMA 地址的空間使用量很重要的驅動程式。上一節中的所有考慮因素也適用於此處。

bool dma_iova_try_alloc(struct device *dev, struct dma_iova_state *state,
            phys_addr_t phys, size_t size);

用於嘗試為對映操作分配 IOVA 空間。如果它返回 false,則此 API 不能用於給定裝置,應使用正常的流式 DMA 對映 API。struct dma_iova_state 由驅動程式分配,並且必須保留直到解除對映。

static inline bool dma_use_iova(struct dma_iova_state *state)

在呼叫 dma_iova_try_alloc 後,驅動程式可以使用它來檢查是否使用了基於 IOVA 的 API。這在解除對映路徑中很有用。

int dma_iova_link(struct device *dev, struct dma_iova_state *state,
            phys_addr_t phys, size_t offset, size_t size,
            enum dma_data_direction dir, unsigned long attrs);

用於將範圍連結到先前分配的 IOVA。除第一次呼叫 dma_iova_link 之外,給定狀態的所有後續呼叫的起始必須對齊到 dma_get_merge_boundary() 返回的 DMA 合併邊界,並且除最後一個範圍之外的所有範圍的大小也必須對齊到 DMA 合併邊界。

int dma_iova_sync(struct device *dev, struct dma_iova_state *state,
            size_t offset, size_t size);

必須呼叫以同步 IOMMU 頁表,用於一個或多個 dma_iova_link() 呼叫對映的 IOVA 範圍。

對於使用一次性對映的驅動程式,可以透過呼叫以下函式解除對映所有範圍並釋放 IOVA:

void dma_iova_destroy(struct device *dev, struct dma_iova_state *state,
             size_t mapped_len, enum dma_data_direction dir,
             unsigned long attrs);

或者,驅動程式可以透過解除對映和對映單獨的區域來動態管理 IOVA 空間。在這種情況下,

void dma_iova_unlink(struct device *dev, struct dma_iova_state *state,
            size_t offset, size_t size, enum dma_data_direction dir,
            unsigned long attrs);

用於解除對映先前對映的範圍,並且

void dma_iova_free(struct device *dev, struct dma_iova_state *state);

用於釋放 IOVA 空間。在呼叫 dma_iova_free() 之前,所有區域都必須已使用 dma_iova_unlink() 解除對映。

第二部分 - 非一致性 DMA 分配

這些 API 允許分配保證可透過傳入裝置進行 DMA 定址的頁面,但需要對核心與裝置之間的記憶體所有權進行顯式管理。

如果您不理解處理器和 I/O 裝置之間快取行一致性的工作原理,則不應使用此 API 的這部分。

struct page *
dma_alloc_pages(struct device *dev, size_t size, dma_addr_t *dma_handle,
                enum dma_data_direction dir, gfp_t gfp)

此例程分配一個大小為 <size> 位元組的非一致性記憶體區域。它返回指向該區域第一個 struct page 的指標,如果分配失敗則返回 NULL。返回的 struct page 可用於 struct page 適合的所有操作。

它還返回一個 <dma_handle>,該控制代碼可以轉換為與匯流排寬度相同的無符號整數,並作為該區域的 DMA 地址基址提供給裝置。

dir 引數指定裝置是否讀寫資料,詳情請參見 dma_map_single()。

gfp 引數允許呼叫者為分配指定 GFP_ 標誌(參見kmalloc()),但會拒絕用於指定記憶體區域的標誌,例如 GFP_DMA 或 GFP_HIGHMEM。

在將記憶體交給裝置之前,需要呼叫 dma_sync_single_for_device(),在讀取裝置寫入的記憶體之前,需要呼叫 dma_sync_single_for_cpu(),就像重新使用的流式 DMA 對映一樣。

void
dma_free_pages(struct device *dev, size_t size, struct page *page,
                dma_addr_t dma_handle, enum dma_data_direction dir)

釋放先前使用 dma_alloc_pages() 分配的記憶體區域。dev、size、dma_handle 和 dir 必須與傳遞給 dma_alloc_pages() 的引數相同。page 必須是 dma_alloc_pages() 返回的指標。

int
dma_mmap_pages(struct device *dev, struct vm_area_struct *vma,
               size_t size, struct page *page)

將 dma_alloc_pages() 返回的分配對映到使用者地址空間。dev 和 size 必須與傳遞給 dma_alloc_pages() 的引數相同。page 必須是 dma_alloc_pages() 返回的指標。

void *
dma_alloc_noncoherent(struct device *dev, size_t size,
                dma_addr_t *dma_handle, enum dma_data_direction dir,
                gfp_t gfp)

此例程是 dma_alloc_pages 的便捷包裝,它返回已分配記憶體的核心虛擬地址,而不是頁面結構。

void
dma_free_noncoherent(struct device *dev, size_t size, void *cpu_addr,
                dma_addr_t dma_handle, enum dma_data_direction dir)

釋放先前使用 dma_alloc_noncoherent() 分配的記憶體區域。dev、size、dma_handle 和 dir 必須與傳遞給 dma_alloc_noncoherent() 的引數相同。cpu_addr 必須是 dma_alloc_noncoherent() 返回的虛擬地址。

struct sg_table *
dma_alloc_noncontiguous(struct device *dev, size_t size,
                        enum dma_data_direction dir, gfp_t gfp,
                        unsigned long attrs);

此例程分配 <size> 位元組的非一致性且可能非連續的記憶體。它返回指向描述已分配和 DMA 對映記憶體的 struct sg_table 的指標,如果分配失敗則返回 NULL。返回的記憶體可用於 struct page 對映到 scatterlist 中適合的用途。

返回的 sg_table 保證有一個單一的 DMA 對映段,由 sgt->nents 指示,但它可能有多個 CPU 端段,由 sgt->orig_nents 指示。

dir 引數指定裝置是否讀寫資料,詳情請參見 dma_map_single()。

gfp 引數允許呼叫者為分配指定 GFP_ 標誌(參見kmalloc()),但會拒絕用於指定記憶體區域的標誌,例如 GFP_DMA 或 GFP_HIGHMEM。

attrs 引數必須為 0 或 DMA_ATTR_ALLOC_SINGLE_PAGES。

在將記憶體交給裝置之前,需要呼叫 dma_sync_sgtable_for_device(),在讀取裝置寫入的記憶體之前,需要呼叫 dma_sync_sgtable_for_cpu(),就像重新使用的流式 DMA 對映一樣。

void
dma_free_noncontiguous(struct device *dev, size_t size,
                       struct sg_table *sgt,
                       enum dma_data_direction dir)

釋放先前使用 dma_alloc_noncontiguous() 分配的記憶體。dev、size 和 dir 必須與傳遞給 dma_alloc_noncontiguous() 的引數相同。sgt 必須是 dma_alloc_noncontiguous() 返回的指標。

void *
dma_vmap_noncontiguous(struct device *dev, size_t size,
        struct sg_table *sgt)

返回使用 dma_alloc_noncontiguous() 返回的分配的連續核心對映。dev 和 size 必須與傳遞給 dma_alloc_noncontiguous() 的引數相同。sgt 必須是 dma_alloc_noncontiguous() 返回的指標。

一旦使用此函式映射了非連續分配,就必須使用 flush_kernel_vmap_range() 和 invalidate_kernel_vmap_range() API 來管理核心對映、裝置和使用者空間對映(如果有)之間的一致性。

void
dma_vunmap_noncontiguous(struct device *dev, void *vaddr)

解除對映 dma_vmap_noncontiguous() 返回的核心對映。dev 必須與傳遞給 dma_alloc_noncontiguous() 的裝置相同。vaddr 必須是 dma_vmap_noncontiguous() 返回的指標。

int
dma_mmap_noncontiguous(struct device *dev, struct vm_area_struct *vma,
                       size_t size, struct sg_table *sgt)

將 dma_alloc_noncontiguous() 返回的分配對映到使用者地址空間。dev 和 size 必須與傳遞給 dma_alloc_noncontiguous() 的引數相同。sgt 必須是 dma_alloc_noncontiguous() 返回的指標。

int
dma_get_cache_alignment(void)

返回處理器快取對齊。這是您在對映記憶體或執行部分重新整理時必須遵守的絕對最小對齊寬度。

注意

此 API 可能會返回一個大於實際快取行的數字,但它將保證一個或多個快取行完全符合此呼叫返回的寬度。它也將始終是 2 的冪,以便於對齊。

第三部分 - 除錯驅動程式對 DMA-API 的使用

如上所述的 DMA-API 存在一些限制。例如,DMA 地址必須以相同的大小用對應的函式釋放。隨著硬體 IOMMU 的出現,驅動程式不違反這些限制變得越來越重要。在最壞的情況下,這種違反可能導致資料損壞,甚至檔案系統被破壞。

為了除錯驅動程式並發現 DMA-API 使用中的錯誤,可以將檢查程式碼編譯到核心中,這將告知開發人員這些違規行為。如果您的體系結構支援,您可以在核心配置中選擇“啟用 DMA-API 使用除錯”選項。啟用此選項會對效能產生影響。請勿在生產核心中啟用它。

如果您啟動,生成的核心將包含一些關於為哪個裝置分配了哪些 DMA 記憶體的簿記程式碼。如果此程式碼檢測到錯誤,它會將警告訊息和一些詳細資訊列印到您的核心日誌中。示例警告訊息可能如下所示

WARNING: at /data2/repos/linux-2.6-iommu/lib/dma-debug.c:448
        check_unmap+0x203/0x490()
Hardware name:
forcedeth 0000:00:08.0: DMA-API: device driver frees DMA memory with wrong
        function [device address=0x00000000640444be] [size=66 bytes] [mapped as
single] [unmapped as page]
Modules linked in: nfsd exportfs bridge stp llc r8169
Pid: 0, comm: swapper Tainted: G        W  2.6.28-dmatest-09289-g8bb99c0 #1
Call Trace:
<IRQ>  [<ffffffff80240b22>] warn_slowpath+0xf2/0x130
[<ffffffff80647b70>] _spin_unlock+0x10/0x30
[<ffffffff80537e75>] usb_hcd_link_urb_to_ep+0x75/0xc0
[<ffffffff80647c22>] _spin_unlock_irqrestore+0x12/0x40
[<ffffffff8055347f>] ohci_urb_enqueue+0x19f/0x7c0
[<ffffffff80252f96>] queue_work+0x56/0x60
[<ffffffff80237e10>] enqueue_task_fair+0x20/0x50
[<ffffffff80539279>] usb_hcd_submit_urb+0x379/0xbc0
[<ffffffff803b78c3>] cpumask_next_and+0x23/0x40
[<ffffffff80235177>] find_busiest_group+0x207/0x8a0
[<ffffffff8064784f>] _spin_lock_irqsave+0x1f/0x50
[<ffffffff803c7ea3>] check_unmap+0x203/0x490
[<ffffffff803c8259>] debug_dma_unmap_page+0x49/0x50
[<ffffffff80485f26>] nv_tx_done_optimized+0xc6/0x2c0
[<ffffffff80486c13>] nv_nic_irq_optimized+0x73/0x2b0
[<ffffffff8026df84>] handle_IRQ_event+0x34/0x70
[<ffffffff8026ffe9>] handle_edge_irq+0xc9/0x150
[<ffffffff8020e3ab>] do_IRQ+0xcb/0x1c0
[<ffffffff8020c093>] ret_from_intr+0x0/0xa
<EOI> <4>---[ end trace f6435a98e2a38c0e ]---

驅動程式開發人員可以找到驅動程式和裝置,包括導致此警告的 DMA-API 呼叫的堆疊跟蹤。

預設情況下,只有第一個錯誤才會導致警告訊息。所有其他錯誤只會靜默計數。此限制的存在是為了防止程式碼淹沒您的核心日誌。為了支援除錯裝置驅動程式,可以透過 debugfs 停用此功能。有關詳細資訊,請參閱下面的 debugfs 介面文件。

DMA-API 除錯程式碼的 debugfs 目錄名為 dma-api/。在此目錄中,目前可以找到以下檔案

dma-api/all_errors

此檔案包含一個數值。如果此值不等於零,除錯程式碼會將找到的每個錯誤都列印到核心日誌中。使用此選項時請小心,因為它很容易淹沒您的日誌。

dma-api/disabled

如果除錯程式碼被停用,此只讀檔案包含字元“Y”。這可能發生在記憶體不足或啟動時被停用時。

dma-api/dump

此只讀檔案包含當前 DMA 對映。

dma-api/error_count

此檔案是隻讀的,顯示發現的錯誤總數。

dma-api/num_errors

此檔案中的數字顯示在停止之前將列印到核心日誌的警告數量。此數字在系統啟動時初始化為一,並可以透過寫入此檔案進行設定。

dma-api/min_free_entries

此只讀檔案可用於獲取分配器曾經見過的最小可用 dma_debug_entries 數量。如果此值降至零,程式碼將嘗試增加 nr_total_entries 進行補償。

dma-api/num_free_entries

分配器中當前可用 dma_debug_entries 的數量。

dma-api/nr_total_entries

分配器中 dma_debug_entries 的總數,包括可用和已使用的。

dma-api/driver_filter

您可以將驅動程式的名稱寫入此檔案,以將除錯輸出限制為來自該特定驅動程式的請求。將空字串寫入此檔案可停用過濾器並再次檢視所有錯誤。

如果此程式碼編譯到您的核心中,則預設情況下會啟用它。如果您無論如何都想不帶簿記啟動,可以提供“dma_debug=off”作為啟動引數。這將停用 DMA-API 除錯。請注意,您無法在執行時再次啟用它。您必須重新啟動才能這樣做。

如果您只想檢視特定裝置驅動程式的除錯訊息,可以指定 dma_debug_driver=<drivername> 引數。這將在啟動時啟用驅動程式過濾器。之後,除錯程式碼將只打印該驅動程式的錯誤。此過濾器可以稍後使用 debugfs 停用或更改。

當代碼在執行時自行停用時,很可能是因為它用完了 dma_debug_entries 並且無法按需分配更多。啟動時預分配了 65536 個條目——如果這對您來說太低,請使用“dma_debug_entries=<您期望的數字>”啟動以覆蓋預設值。請注意,程式碼是分批分配條目的,因此預分配條目的確切數量可能大於實際請求的數量。程式碼每次動態分配與最初預分配的條目一樣多的條目時,都會列印到核心日誌。這表明可能需要更大的預分配大小,或者如果這種情況持續發生,則表明驅動程式可能正在洩漏對映。

void
debug_dma_mapping_error(struct device *dev, dma_addr_t dma_addr);

dma-debug 介面 debug_dma_mapping_error() 用於除錯那些未能檢查 dma_map_single() 和 dma_map_page() 介面返回地址的 DMA 對映錯誤的驅動程式。此介面清除由 debug_dma_map_page() 設定的標誌,以指示驅動程式已呼叫 dma_mapping_error()。當驅動程式解除對映時,debug_dma_unmap() 會檢查該標誌,如果該標誌仍處於設定狀態,則會列印警告訊息,其中包含導致解除對映的呼叫跟蹤。此介面可以從 dma_mapping_error() 例程呼叫,以啟用 DMA 對映錯誤檢查除錯。