最近在调试PCIe的行情加速卡的驱动,其中使用DMA在CPU和FPGA间传输数据。最开始使用的是低16M的DMA ZONE的内存,用slab分配器的kmalloc分配获取。但由于最新的需求,需要使用的内存远远超过16M,这样再使用DMA ZONE区域的内存就不够了,那就只能使用DMA32区域的内存来进行DMA传输了。
在我使用的调试机器上,DMA32区域的内存情况如下:
由上图可知DMA32 ZONE为16M~4G,高于4G的内存为Normal ZONE。
如果使用DMA32 ZONE的内存,不能够使用slab分配器,否则会panic。
__cache_allocàcache_alloc_refillàcache_grow函数:
BUG_ON(flags & GFP_SLAB_BUG_MASK); |
如果设置了GFP_SLAB_BUG_MASK标志,那么就会直接bug_on。GFP_SLAB_BUG_MASK标志的定义如下:
/* Do not use these with a slab allocator */ #define GFP_SLAB_BUG_MASK (__GFP_DMA32|__GFP_HIGHMEM|~__GFP_BITS_MASK) |
因为我们需要设置__GFP_DMA32标志,因此在使用DMA32ZONE的内存时候,不能使用slab分配器来分配内存。
因此使用buddy分配器来分配DMA32ZONE的内存供DMA传输使用。这个时候需要注意一个问题:我们的设置是否能够在DMA32 ZONE分配的内存上执行DMA?
之前使用的低16M的DMA ZONE的内存,设置的设备的寻址能力为24bit,现在使用的是16M~4G的DMA32 ZONE,设备需要能在32位地址上执行DMA,调用dma_set_mask设置32bit的寻址能力。
如果不设置32bit的寻址能力,那么在流式DMA映射的时候就会报错。报错是swiotlb_full函数打印出来的。
printk(KERN_ERR "DMA: Out of SW-IOMMU space for %zu bytes at " "device %s\n", size, dev ? dev_name(dev) : "?"); |
下面分析下DMA映射的相关代码swiotlb_map_page函数实现:
/* * 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; |
此函数中首先调用dma_capable函数检查映射的地址范围是否在设备允许的寻址能力范围内。
static inline bool dma_capable(struct device *dev, dma_addr_t addr, size_t size) { if (!dev->dma_mask) return 0;
return addr + size - 1 <= *dev->dma_mask; } |
此函数首先检查有没有设置设备的寻址掩码,调用dma_set_mask函数底层实现就是设置*dev->dma_mask = mask,如果设置DMA_BIT_MASK(32),那么mask就是0xffffffff。任何4G范围内的地址在此检查条件下都能通过。