dma的大小
从开机开始看起 bootmem_init --- dma_contiguous_reserve(arm64_dma_phys_limit):
if (size_cmdline != -1) {
这里是cmdline中使用cma=来指定起始地址和长度,比较自由任意
selected_size = size_cmdline;
selected_base = base_cmdline;
selected_limit = min_not_zero(limit_cmdline, limit);
if (base_cmdline + size_cmdline == limit_cmdline)
fixed = true;
} else {
这里则是听命系统分配了
#ifdef CONFIG_CMA_SIZE_SEL_MBYTES
selected_size = size_bytes;
#elif defined(CONFIG_CMA_SIZE_SEL_PERCENTAGE)
selected_size = cma_early_percent_memory();
#elif defined(CONFIG_CMA_SIZE_SEL_MIN)
selected_size = min(size_bytes, cma_early_percent_memory());
#elif defined(CONFIG_CMA_SIZE_SEL_MAX)
selected_size = max(size_bytes, cma_early_percent_memory());
#endif
}
默认 CONFIG_CMA_SIZE_SEL_MBYTES,为16M
起始地址 base=__memblock_find_range_top_down(start, end, size, align, nid, flags)
从memblock的top开始往下找一块size (16M)大小的 memory属性(非reserved)的物理块.
注意了,开始找的地方是从dma的边界往下(arm64_dma_phys_limit)。
这块物理块会记录在 cma_areas[cma_area_count] 中,然后 dma_contiguous_default_area 指向这块物理块。
看来 cma 才是大哥,dma是小小弟啊。
获取dma内存的gfp mask
dma_direct_optimal_gfp_mask:
if (*phys_limit <= DMA_BIT_MASK(zone_dma_bits))
return GFP_DMA;
if (*phys_limit <= DMA_BIT_MASK(32))
return GFP_DMA32;
不同的mask,对应从不同的zone获取内存。若内存不足,往上一级借(steal)。这里优先是dma zone
拿ARM64举例:ARM64中高版本默认开启了dma和dma32 zone
在zone_sizes_init函数中,定义了这两个dma zone 的边界。
dma32边界是32bit,dma边界是不超过32bit,具体多小可以由固件定义,固件不定义,默认dma边界为32bit。
注意了,这里的边界就是实际的内存物理地址区间中的32bit地址处,内存物理地址起始高于32bit,dma zone就没了
获取dma内存
一致dma映射
dma_alloc_coherent
从dma_contiguous_default_area记录的区域中获取内存
cma_alloc_aligned(dma_contiguous_default_area, size, gfp);
然后调用到alloc_contig_range,gfp变成了GFP_KERNEL
pfn = cma->base_pfn + (bitmap_no << cma->order_per_bit); // 空闲pfn
ret = alloc_contig_range(pfn, pfn + count, MIGRATE_CMA,
GFP_KERNEL | (no_warn ? __GFP_NOWARN : 0))
page = pfn_to_page(pfn)
分配dma/cma有一个核心流程alloc_contig_range,检查该pfn是不是正在被伙伴系统使用,如果是则要腾出来
归还给dma/cma用。通常dma zone作为最贵重的zone,除非内存不足或者执意有人用了GFP_DMA占用了预留的dma,还有
就是使用cma=放飞了自我。
可以发现,从dma_contiguous_default_area中申请page,和gfp mask没有任何关系。
那啥情况和gfp有关呢。
1 config DMA_CMA没有开,则优先从dma zone获取page。
2 dma_contiguous_default_area 没货了,则优先从dma zone获取page
__dma_direct_alloc_pages:
if (!page)
page = alloc_pages_node(node, gfp, get_order(size));
一致性dma映射,可支持重新映射虚拟地址到vmalloc空间
if (remap) {
//ret为vmalloc空间
ret = dma_common_contiguous_remap(page, size, prot,
__builtin_return_address(0));
} else {
// ret为线性映射空间
ret = page_address(page);
}
函数的返回值为申请到的物理页的虚拟地址,dma物理地址存放在参数中
*dma_handle = phys_to_dma_direct(dev, page_to_phys(page));
流式dma映射
#define dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, 0)
参数二:参与dma传输的虚拟地址buf
参数四:数据方向,系统会根据direction的值invalid 或者write back cache
返回的是dma地址
DMA_TO_DEVICE
urb->setup_dma = dma_map_single(
hcd->self.sysdev,
urb->setup_packet, //kmalloc
sizeof(struct usb_ctrlrequest),
DMA_TO_DEVICE);
DMA_FROM_DEVICE
urb->transfer_dma = dma_map_single(
hcd->self.sysdev,
urb->transfer_buffer, //kmalloc
urb->transfer_buffer_length,
DMA_FROM_DEVICE);
函数定义为
static inline dma_addr_t dma_map_single_attrs(struct device *dev, void *ptr,
size_t size, enum dma_data_direction dir, unsigned long attrs) {
return dma_map_page_attrs(dev, virt_to_page(ptr), offset_in_page(ptr),
size, dir, attrs)
}
这里最终会将ptr定位到一个具体的物理地址
phys = page_to_phys(virt_to_page(ptr)) + offset_in_page(ptr)
最后将64为的物理地址对应到32位的dma地址,phys_attr_t转化位dma_attr_t
dma_addr_t dma_addr = phys_to_dma(dev, phys)
对比一致性dma和流式dma:
一致性dma是从dma zone或者预留的dma/cma空间中获取
流式dma,使用kmalloc获取物理小内存,再转为dma地址
dma一致性(coherent)
bypass cache
1、使用一致性DMA映射
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag)
dma_free_coherent(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle)
2、O_SYNC标志:
映射物理内存时加上O_SYNC
open("/dev/mem", O_RDWR | O_SYNC)
手动刷cache
1、DMA向外设写入CPU数据,只需要刷一次
搬运前clean cache
2、DMA从外设读取数据给CPU使用,需要刷两次(前后各一次)
搬运前clean cache
搬运后ivalid cache
搬运前:
void dma_sync_single_for_device(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir);
void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, int nelems, enum dma_data_direction dir);
实现细节
if (!dev_is_dma_coherent(dev)) //使用于非dma一致性dev
arch_sync_dma_for_device(paddr, size, dir); 调用汇编cvac
搬运后:
void dma_sync_single_for_cpu(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir);
void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nelems, enum dma_data_direction dir);
if (!dev_is_dma_coherent(dev)) {
arch_sync_dma_for_cpu(paddr, size, dir);
arch_sync_dma_for_cpu_all();
}
if (dir == DMA_FROM_DEVICE)
arch_dma_mark_clean(paddr, size);
i