现在的位置: 首页 > 综合 > 正文

Linux DMA meory简述

2018年04月16日 ⁄ 综合 ⁄ 共 5775字 ⁄ 字号 评论关闭

首先,应该先读Documentation/DMA-API.txtDMA-mapping.txt.

1.       DMA memeory分类

一共有两种DMA memory

a.       Consistent/coherent

b.      non-consistent/no-coherent

第一类由底层硬件保证了内存的一致性,这样的内存当进行DMA时,不需要相关的invalidate函数操作。

第二类内存在进行DMA之前,必须做flush/invalidate等操作。

底层硬件如何实现,或者处理器通过内存映射,或者有额外的硬件支持,这些对于driver来说是透明的。

注意 coherentno-coherentcache是不同的概念,coherent也许是cached,也许是uncached,这是由底层硬件来实现。

 

2.       DMAmemory的操作函数

DMA-API有两类,

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

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

第一类是向处理器直接分配内存,第二类是通过pci的接口分配内存。对于mipsarm来说,这两类API是一样的,pci_
APImipsarm处理器中最后会走到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

对于mipsarm来说,pci.h又会包含如下语句

/* implement the pci_ DMA API in terms of the generic device dma_ one */

#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中实现了mipsDMA函数

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_COHERENTCONFIG_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_singlesync的作用,并且返回物理地址。

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的操作到了r4kcache操作上来。

5.       ARM上的DMA实现

因为不太懂ARM,这里只说一下arm cache的层次结构。

一般如果要sync一个bufferarm提供到外面的接口有

___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));

 

                /* FIXME: non-speculating: not required */

                /* don't bother invalidating if DMA to device */

                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_rangeinvalidate L2cache

然后需要dmac_unmap_area

 

这个函数在cacheflush.h中有定义,表示为cpu_cache.dma_unmap_area,这个文件中还定义 了很多其它cache的操作,因为arm有不同的型号,所以每一种 cache都不一样。

arch/arm/mm下面,cpu_cache在如下函数中定义

如果CPUARMV7的话,则在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)

抱歉!评论已关闭.