天天看點

Linux DMA meory簡述

首先,應該先讀 Documentation/ DMA-API.txt和DMA-mapping.txt.

1.       DMA memeory分類

一共有兩種DMA memory,

a.       Consistent/coherent

b.      non-consistent/no-coherent

第一類由底層硬體保證了記憶體的一緻性,這樣的記憶體當進行DMA時,不需要相關的invalidate函數操作。

第二類記憶體在進行DMA之前,必須做flush/invalidate等操作。

底層硬體如何實作,或者處理器通過記憶體映射,或者有額外的硬體支援,這些對于driver來說是透明的。

注意 coherent和no-coherent和cache是不同的概念,coherent也許是cached,也許是uncached,這是由底層硬體來實作。

2.       DMA對memory的操作函數

DMA-API有兩類,

第一類是dma_ API, 它必須包含#include <linux/dma-mapping.h>

第二類是pci_ API, 它必須包含#include <linux/pci.h>

第一類是向處理器直接配置設定記憶體,第二類是通過pci的接口配置設定記憶體。對于mips和arm來說,這兩類API是一樣的,pci_ API在mips和arm處理器中最後會走到dma_ API

是以這裡隻提及DMA-API

dma_alloc_coherent

配置設定coherent記憶體。

一般來說,coherent記憶體是比較昂貴,或者代價比較高一些,是以隻能小塊的記憶體使用coherent,比如典型的descriptor。

dma_pool_create

這個用來配置設定一個小塊coherent的記憶體池子。dma_alloc_coherent是以頁為機關配置設定。

dma_sync_single_for_device

dma_sync_single_for_cpu

并不太确定這兩個sync是啥意思,很重要的兩個函數,在mips上的實作也不同,一個是做了flush的動作,一個是沒做

dma_map_single

flush這段記憶體,并傳回實體位址,當配置設定buffer的時候,通常會使用這個函數傳回實體位址。

3.       DMA的層次結構

3.1   對于DMA_ API來說,從<linux/dma-mapping.h>開始,這裡定義了一些公共的API,然後在這個頭檔案中會

#ifdef CONFIG_HAS_DMA

#include <asm/dma-mapping.h>

#else

#include <asm-generic/dma-mapping-broken.h>

#endif

一般我們會定義CONFIG_HAS_DMA,是以會包含體系相關的dma-mapping.h。

對于mips來說,是arch/mips/include/asm/dma-mapping.h

對于arm來說,是arch/arm/include/asm/dma-mapping.h

在這兩個頭檔案中,可以直接調用到各個arch中的dma mapping的實作,也可以在封裝一層,

再包含#include dma-maping-common.h,然後這裡通過dma_map_ops函數指針調到各個arch的dma mapping實作。

體系相關的dma-mapping.h中聲明了其它DMA_API。它在哪裡實作了,

對于mips來說,它在arch/mips/mm/dma-default.c

對于arm來說,它在arch/arm/mm/dma-mapping.c

對于PCI_ API來說,從<linux/pci.h>開始。

這個頭檔案會包含體系相關的<asm/pci.h>

對于mips來說,是arch/mips/include/asm/pci.h

對于arm來說,是arch/arm/include/asm/pci.h

對于mips和arm來說,pci.h又會包含如下語句

#include <asm-generic/pci-dma-compat.h>

然後在pci-dma-compat.h裡,開始包含#include <linux/dma-mapping.h>,并且把PCI_向DMA_做了轉換,這就把PCI_和DMA_聯系上了。

4.       MIPS上的DMA實作

在dma-default.c中實作了mips的DMA函數

dma_alloc_coherent

        void *ret;

        gfp = massage_gfp_flags(dev, gfp);

        ret = (void *) __get_free_pages(gfp, get_order(size));

        if (ret) {

                        memset(ret, 0, size);

                        *dma_handle = plat_map_dma_mem(dev, ret, size);

                        if (!plat_device_is_coherent(dev)) {

                                        dma_cache_wback_inv((unsigned long) ret, size);

                                        ret = UNCAC_ADDR(ret);

                        }

        }

        return ret;

}

從這個函數可以看出,mips配置設定一個頁面,清空,傳回實體位址,然後判斷這個device是否是coherent,因為我們前面說過可以有額外的硬體來支援coherent。

plat_device_is_coherent

{

#ifdef CONFIG_DMA_COHERENT

        return 1;

#endif

#ifdef CONFIG_DMA_NONCOHERENT

        return 0;

#endif

}

對于mips來講,它依據CONFIG_DMA_COHERENT和CONFIG_DMA_NONCOHERENT

定義。它隻是提供了這樣一個函數給所有的平台。

一般我們會定義CONFIG_DMA_NONCOHERENT。

是以這裡,mips是如何操作這coherent函數的呢?它把這一頁先write-inv, 然後傳回SEG1位址,其實就是uncache,這樣确實保證了coherent,但效率會變低。

dma_addr_t dma_map_single(struct device *dev, void *ptr, size_t size,

        enum dma_data_direction direction)

{

        unsigned long addr = (unsigned long) ptr;

        if (!plat_device_is_coherent(dev))

                        __dma_sync(addr, size, direction);

        return plat_map_dma_mem(dev, ptr, size);

}

Dma_map_single有sync的作用,并且傳回實體位址。

static inline void __dma_sync(unsigned long addr, size_t size,

        enum dma_data_direction direction)

{

        switch (direction) {

        case DMA_TO_DEVICE:

                        dma_cache_wback(addr, size);

                        break;

        case DMA_FROM_DEVICE:

                        dma_cache_inv(addr, size);

                        break;

        case DMA_BIDIRECTIONAL:

                        dma_cache_wback_inv(addr, size);

                        break;

        default:

                        BUG();

        }

}

對于寫device,我們需要wback,對于從device讀,我們需要invalidate,對于雙向,我們需要invalidate+wback。

這個涉及到cache的操作了,具體如何操作,與cache的類型相關。

在arch/mips/include/asm/io.h中

#ifdef CONFIG_DMA_NONCOHERENT

#define dma_cache_wback_inv(start, size) _dma_cache_wback_inv(start, size)

#define dma_cache_wback(start, size)                         _dma_cache_wback(start, size)

#define dma_cache_inv(start, size)                                _dma_cache_inv(start, size)

而_dma_cache_wback_inv這些底層函數的指派則是在系統起來,初始化cache時指派的,mips有多種類型,對于24K,74K等,它會使用原先的R4k cache,這裡我們要選擇CONFIG_CSRC_R4K=y。

在r4k_cache_init函數中

                        _dma_cache_wback_inv              = r4k_dma_cache_wback_inv;

                        _dma_cache_wback        = r4k_dma_cache_wback_inv;

                        _dma_cache_inv                           = r4k_dma_cache_inv;

是以最後DMA的操作到了r4k的cache操作上來。

5.       ARM上的DMA實作

因為不太懂ARM,這裡隻說一下arm cache的層次結構。

一般如果要sync一個buffer,arm提供到外面的接口有

___dma_single_cpu_to_dev

___dma_single_dev_to_cpu

我不知道這兩個的差別,這個要留到以後分析。

___dma_single_dev_to_cpu

{

                BUG_ON(!virt_addr_valid(kaddr) || !virt_addr_valid(kaddr + size - 1));

                if (dir != DMA_TO_DEVICE) {

                                unsigned long paddr = __pa(kaddr);

                                outer_inv_range(paddr, paddr + size);

                }

                dmac_unmap_area(kaddr, size, dir);

}

define dmac_unmap_area                                          cpu_cache.dma_unmap_area

如果有需要還要使用outer_inv_range來invalidate L2的cache。

然後需要dmac_unmap_area

這個函數在cacheflush.h中有定義,表示為cpu_cache.dma_unmap_area,這個檔案中還定義 了很多其它cache的操作,因為arm有不同的型号,是以每一種 cache都不一樣。

在arch/arm/mm下面,cpu_cache在如下函數中定義

如果CPU是ARMV7的話,則在cache-v7.S最後兩句,定義了這個結構

        @ define struct cpu_cache_fns (see <asm/cacheflush.h> and proc-macros.S)

        define_cache_functions v7

而define_cache_functions  v7則是在proc-macros.S中定義

ENTRY(\name\()_cache_fns)

        .long   \name\()_flush_icache_all

其實就是 v7_flush_icache_all等。

而v7_flush_icache_all這些函數的定義 則是在cache-v7.S中

ENTRY(v7_dma_unmap_area)

        add     r1, r1, r0

        teq     r2, #DMA_TO_DEVICE

        bne     v7_dma_inv_range

        mov     pc, lr

ENDPROC(v7_dma_unmap_area)

繼續閱讀