杂谈 linux DMA内存

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值