DMA介绍

DMA即Direct Memory Access的缩写。直接内存访问, 其目的就是将CPU从数据传输中解放出来,将数据传输直接下放到设备自己去处理。 目前PCI/PCIE的设备一般都是具有DMA功能,不需要单独的DMA控制器去处理。 做DMA的时候CPU告诉设备操作的内存地址在那个地方,以及长度是多少,其它的就不管了。

涉及内容有内存分配以及Cache一致性问题处理。

DMA分配

在DMA内存分配的时候需要考虑分配什么范围的地址是合适的。

这可从两个角度来看: 从设备的角度看,每个设备对DMA的地址长度都是有限制的, 早期的设备只能对24bit地址做dma,后面又出现了支持32bit,40bit,64bit地址的设备, 所以必须有一种方法表征设备DMA的寻址能力; 从CPU的角度看,CPU能够接收并正确译码设备发出的最大访存地址,需要表征CPU支持的最大DMA地址。

对于设备来说,在struct device{}结构体中有两个域来表征 *dma_mask, coherent_dma_mask, dma_mask用于表示该设备的DMA的寻址能力,coherent_dma_mask则用于在一致性DMA映射时对内存地址的要求 这两个域通过dma_set_mask() 和 dma_set_coherent_dma_mask()进行设置的,如果是PCI设备则可调用 pci_set_dma_mask() pci_set_consistent_dma_mask()

对于CPU来说,可以在设置struct dma_map_ops {}的时候, 设置set_dma_mask回调函数来限制DMA地址的请求范围

再看看内存管理系统对内存的分配,内存管理系统将所有的页框分为几个管理区,传统上会有DMA Zone, Normal Zone,如果配置了CONFIG_DMA32的还有一个DMA32 Zone,如果有HighMem的则有一个HighMem Zone。 这里我们只看Zone DMA和Zone DMA32。传统上我们通过GFP_DMA标志从DMA Zone区获取内存,来用于设备的DMA。 这个区域是比较小的 1 << 24,即16M大小。随着内存的扩大以及设备DMA地址的增长, 为了分配到更大的内存区域,Kernel定义GFP_DMA32来获取DMA内存,这样获取用于DMA的内存能够扩大到4G范围。

关于GFP_DMA32的可以查看这里GFP_DMA32,Kernel后面的发展可能会将DMA Zone或者DMA3 Zone取消掉 General DMA zone rework

Loongson3A上使用大内存情况下只有CONFIG_DMA32的标志,而GFP_DMA32在kmalloc()中是没有什么处理的, 所以完全会出现分配一个比32bit地址还大的情况。GFP_DMA32这个标志在__get_free_pages()中有处理。

龙芯在内存映射上在大于256M的内存处其物理地址都放到大于 0x100000000处了,即大于32bit的地址。这么大的地址对于很多设备来说, 都是不能够直接使用的,故使用了swiotlb方式, 将分配的不能DMA的地址与低端内存处一块内存绑定,使用低端这块内存做DMA。

关于内存的管理区可以通过/proc/zoneinfo获取到相应的信息。 DMA Zone是在slab初始化的时候创建的一个16M的高速缓冲区,可以通过kmalloc(size, GFP_DMA)获取到。 /proc/slabinfo中会列出

通用的内存分配接口

可以通过kmalloc()以及__get_free_pages()及其辅助函数来获取内存,然后通过virt_to_bus()将地址 转换为dma的地址。

一般不推荐使用这种方式。因为上面的分配方式可能会有一些问题,比如在只配置了DMA32的情况下, 如果传递GFP_DMA标志获取的内存会超过24bit的范围(龙芯上面使用大内存就有这个情况), 而且使用virt_to_bus()进行转换可能对支持MMIO的体系结构不起作用等。

另一种方式为启动的时候保留一部分内存,使用的时候通过ioremap的方式获取。 Android上面这种方面使用的比较多,比如为摄像头获取内存等

DMA通用接口

为了提高驱动程序的移植性,一般dma分配都是通过DMA通用层的方式进行, 它屏蔽了体系结构的一些差异,提供了统一的操作界面。 通过通用层进行缓冲区分配映射的方式有两种,一种为一致性DMA映射,一种为流式DMA映射。 前者包括了分配和映射两个功能,后者只用于映射,内存分配可以通过前面介绍的几种方式进行

一致性映射

下面一组函数实现了内存的分配和映射

void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag);
void dma_free_coherent(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle);
static inline void *pci_alloc_consistent(struct pci_dev *hwdev, size_t size,
            dma_addr_t *dma_handle)

在Linux上, 默认情况下认为设备DMA能够使用32bit的地址, 如果设备支持只支持其它长度地址,在调用分配映射函数之前需要通过 set_dma_mask()和dma_set_coherent_dma_mask()进行设置。

具体的实现细节为:

static void *loongson_dma_alloc_coherent(struct device *dev, size_t size,
                dma_addr_t *dma_handle, gfp_t gfp, struct dma_attrs *attrs)
{
    void *ret;

    if (dma_alloc_from_coherent(dev, size, dma_handle, &ret))
        return ret;

    /* ignore region specifiers */
    gfp &= ~(__GFP_DMA | __GFP_DMA32 | __GFP_HIGHMEM);

#ifdef CONFIG_ZONE_DMA
    if (dev == NULL)
        gfp |= __GFP_DMA;
    else if (dev->coherent_dma_mask <= DMA_BIT_MASK(24))
        gfp |= __GFP_DMA;
    else
#endif
#ifdef CONFIG_ZONE_DMA32
    if (dev->coherent_dma_mask <= DMA_BIT_MASK(32))
        gfp |= __GFP_DMA32;
    else
#endif
    ;
    gfp |= __GFP_NORETRY;

    ret = swiotlb_alloc_coherent(dev, size, dma_handle, gfp);
    mb();
    return ret;
}

dma_alloc_from_coherent() 是从为设备保留的内存处分配的,一般不会使用。 在配置CONFIG_DMA32的情况下,如果设备的coherent_dma_mask小于等于32bit,则使用GFP_DMA32标志, 如果大于32bit,则使用默认的方式。

void *
swiotlb_alloc_coherent(struct device *hwdev, size_t size,
               dma_addr_t *dma_handle, gfp_t flags)
{
    dma_addr_t dev_addr;
    void *ret;
    int order = get_order(size);
    u64 dma_mask = DMA_BIT_MASK(32);

    if (hwdev && hwdev->coherent_dma_mask)
        dma_mask = hwdev->coherent_dma_mask;

    ret = (void *)__get_free_pages(flags, order);
    if (ret && swiotlb_virt_to_bus(hwdev, ret) + size - 1 > dma_mask) {
        /*
        * The allocated memory isn't reachable by the device.
        */
        free_pages((unsigned long) ret, order);
        ret = NULL;
    }
    if (!ret) {
        /*
        * We are either out of memory or the device can't DMA to
        * GFP_DMA memory; fall back on map_single(), which
        * will grab memory from the lowest available address range.
        */
        ret = map_single(hwdev, 0, size, DMA_FROM_DEVICE);
        if (!ret)
            return NULL;
    }

    memset(ret, 0, size);
    dev_addr = swiotlb_virt_to_bus(hwdev, ret);

    /* Confirm address can be DMA'd by device */
    if (dev_addr + size - 1 > dma_mask) {
        printk("hwdev DMA mask = 0x%016Lx, dev_addr = 0x%016Lx\n",
               (unsigned long long)dma_mask,
               (unsigned long long)dev_addr);

        /* DMA_TO_DEVICE to avoid memcpy in unmap_single */
        swiotlb_tbl_unmap_single(hwdev, ret, size, DMA_TO_DEVICE);
        return NULL;
    }
    *dma_handle = dev_addr;
    return ret;
}
EXPORT_SYMBOL(swiotlb_alloc_coherent);

__get_free_pages() 传递GFP_DMA32,则在低256M获取所需内存,如果没有则从Normal中获取DMA内存。 如果无法获取到,则使用map_single(),从保留的64M处获取内存。

分配小于一页的dma空间可以通过所谓的DMA池进行管理

dma_pool_create()
dma_pool_alloc()
dma_pool_free()
dma_pool_destroy()

流式映射

通过通用的内存分配接口获取内存,然后通过dma_map_single()/dma_map_sg()的方式获取到一个设备支持的dma地址。 映射时必须分清楚是到设备还是来自设备,因为两者会使用到不同的cache刷新方式。 如果是到设备的,则将cache刷新到内存,如果是来自设备,则将对应cache无效。

具体实现:

static dma_addr_t loongson_dma_map_page(struct device *dev, struct page *page,
                                unsigned long offset, size_t size,
                                enum dma_data_direction dir,
                                struct dma_attrs *attrs)
{
        dma_addr_t daddr = swiotlb_map_page(dev, page, offset, size,
                                        dir, attrs);
        mb();
        return daddr;
}

直接调用lib/swiotlb.c中swiotlb_map_page()

dma_addr_t swiotlb_map_page(struct device *dev, struct page *page,
                unsigned long offset, size_t size,
                enum dma_data_direction dir,
                struct dma_attrs *attrs)
{
    phys_addr_t phys = page_to_phys(page) + offset;
    dma_addr_t dev_addr = phys_to_dma(dev, phys);
    void *map;

    BUG_ON(dir == DMA_NONE);
    /*
    * If the address happens to be in the device's DMA window,
    * we can safely return the device addr and not worry about bounce
    * buffering it.
    */
    if (dma_capable(dev, dev_addr, size) && !swiotlb_force)
        return dev_addr;

    /*
    * Oh well, have to allocate and map a bounce buffer.
    */
    map = map_single(dev, phys, size, dir);
    if (!map) {
        swiotlb_full(dev, size, dir, 1);
        map = io_tlb_overflow_buffer;
    }

    dev_addr = swiotlb_virt_to_bus(dev, map);

    /*
    * Ensure that the address returned is DMA'ble
    */
    if (!dma_capable(dev, dev_addr, size)) {
        swiotlb_tbl_unmap_single(dev, map, size, dir);
        dev_addr = swiotlb_virt_to_bus(dev, io_tlb_overflow_buffer);
    }

    return dev_addr;
}
EXPORT_SYMBOL_GPL(swiotlb_map_page);

这个函数中使用dma_mask,判断分配的地址是否满足设备要求,如果超出设备dma范围,则 使用保留的64M处内存做DMA,在map_single()中会将原分配内存记录到新分配的内存索引处, 在调用dma_sync_single_for_cpu()或者对应的dma_unmap_single()才知道将低端64M内存的数据拷贝 到什么地方去。

在没有使用swiotlb情况下,这两者会有性能上的差别,前者可能没有后者好。 因为MIPS上面如果Cache的一致性没有硬件维护,则会将地址转换到Uncache区域,势必会影响效率, 但是不用uncache方式又不知道多久对cache进行刷新,而后者就没有这个问题,因为做dma的时候会标志数据从那里来的, 而且cpu要访问dma内存,必须借助sync后才能开始,这样就能够知道什么时候刷cache了,也就是这种方式我们能够使用 cache的地址进行访问,且需要时才对chache进行刷新操作,也就能够提高访问速度了。 当然如果硬件支持Cache一致性,这两者应该是没有什么区别的。启用了swiotlb的话,后者没有前者性能好了。

如果设备支持分散聚集I/O,则可以通过几个小的缓冲区来实现大块连续缓存区的目的。这个也是属于流式映射中一种

回弹缓冲

在系统启动的时候,会在低256M中保留64M做DMA。这64M就是用于实现swiotlb的, 在一致性映射情况下,其实不会直接使用到这块的,因为_get_free_pages() 在GFP_DMA32的情况下会从低256M获取内存。主要是流式映射可能会用到,如果通用方式分配的地址不能用于DMA的 话,则会在这64M的地方获取一段内存,这段内存给设备用于DMA,DMA完成后,在CPU要访问DMA数据之前, 需要使用unmap或sync方式将64M内存处的数据 拷贝到原来内存地址处。

这64M的分配维护方法为: 将这64M按照2K大小进行分片,然后以128片为最大能分配内存段。将其分离到一个链表中进行管理。

index  slabs

0 ---> 128
1 ---> 127
2 ---> 126
.   .
.   .
.   .
128 ---> 1
129 ---> 128
130 ---> 127
.   .
.   .
.   .
256 ---> 1
.   .
.   .
.   .

分配时需要确定需要内存大小,通过大小计算出需要多少片,然后使用一个值来表征内存分配的对齐要求, 如果分配的内存小于一个页面,则对齐在一个片的边缘,如果申请的内存大于一个PAGE,则将其对齐到一个页面。 然后通过对链表的索引取齐,确定从哪个索引处开始搜索没有使用的空闲片。比如,需要126 * 2K = 252K 大小内存, 对齐的内存片段为64K/2K = 32。对初始的io_tlb_index(初始值为0)进行32对齐,则获取的index为0, 从链表位置0开始搜索,确定零处的值128 > 126,则将 [0,125]的值设为0,表征这些 内存已经被占用。因为没有比0更小的项了,故不会做其他索引处值的更新了。使用index + nslot更新io_tlb_index,即 对io_tlb_index赋值为126。然后返回地址swiotlb_start_phys + 0 << 11 为DMA的地址。 通过计算能够确定,在这种情况最大的dma分配地址为128 * 2K = 256K大小。

Cache一致性问题

在MIPS上,如果硬件不支持Cache一致性,在做一致性映射的时候则会将CPU访问的内存地址转换为uncahed段的, 这样能够避免cache一致性的问题。 Loongson3A实现了Cache一致性,Loongson2F/2E没有做硬件cache一致性的保证。

原来遇到的一个关于wifi的问题,原来开始用kzalloc分配地址OK,但是通过dma_pool分配地址就不行了。



blog comments powered by Disqus