最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

没有IOMMU的DMA操作

旗下网站admin39浏览0评论

没有IOMMU的DMA操作

没有IOMMU的DMA操作

我们知道DMA通常需要访问连续的物理内存,除非设备支持iommu,当设备不支持iommu的话可以用以下方式:

在内核启动时为设备保留内存

将MMU内嵌到设备中,如GPU

这里GPU MMU的方式算是个例外,不在本篇文章讨论范围内。

我们知道DMA映射有两种方式,一种是一致性映射 dma_alloc_coherent,一种是流式映射 dma_map_single (dma_map_sg可以映射多个dma buffer)。

一致性映射 dma_alloc_coherent

dma_alloc_coherent会调用dma_alloc_attrs:

static inline void *dma_alloc_attrs(struct device *dev, size_t size,           dma_addr_t *dma_handle, gfp_t flag,           unsigned long attrs){ const struct dma_map_ops *ops = get_dma_ops(dev); void *cpu_addr; BUG_ON(!ops); if (dma_alloc_from_dev_coherent(dev, size, dma_handle, &cpu_addr))  return cpu_addr; if (!arch_dma_alloc_attrs(&dev, &flag))  return NULL; if (!ops->alloc)  return NULL; cpu_addr = ops->alloc(dev, size, dma_handle, flag, attrs); debug_dma_alloc_coherent(dev, size, *dma_handle, cpu_addr); return cpu_addr;}

ops->alloc对应的回调有两个注册,分别是swiotlb和iommu:

static struct dma_map_ops swiotlb_dma_ops = { .alloc = __dma_alloc, //dma_alloc_attrs .free = __dma_free, .mmap = __swiotlb_mmap, .get_sgtable = __swiotlb_get_sgtable, .map_page = __swiotlb_map_page, //dma_map_single .unmap_page = __swiotlb_unmap_page, .map_sg = __swiotlb_map_sg_attrs, //dma_map_sg .unmap_sg = __swiotlb_unmap_sg_attrs, .sync_single_for_cpu = __swiotlb_sync_single_for_cpu, .sync_single_for_device = __swiotlb_sync_single_for_device, .sync_sg_for_cpu = __swiotlb_sync_sg_for_cpu, .sync_sg_for_device = __swiotlb_sync_sg_for_device, .dma_supported = __swiotlb_dma_supported, .mapping_error = __swiotlb_dma_mapping_error,};static struct dma_map_ops iommu_dma_ops = { .alloc = __iommu_alloc_attrs, .free = __iommu_free_attrs, .mmap = __iommu_mmap_attrs, .get_sgtable = __iommu_get_sgtable, .map_page = __iommu_map_page, .unmap_page = __iommu_unmap_page, .map_sg = __iommu_map_sg_attrs, .unmap_sg = __iommu_unmap_sg_attrs, .sync_single_for_cpu = __iommu_sync_single_for_cpu, .sync_single_for_device = __iommu_sync_single_for_device, .sync_sg_for_cpu = __iommu_sync_sg_for_cpu, .sync_sg_for_device = __iommu_sync_sg_for_device, .map_resource = iommu_dma_map_resource, .unmap_resource = iommu_dma_unmap_resource, .mapping_error = iommu_dma_mapping_error,};

非iommu的话即调用__dma_alloc:

static void *__dma_alloc(struct device *dev, size_t size,    dma_addr_t *dma_handle, gfp_t flags,    unsigned long attrs){  ...... size = PAGE_ALIGN(size); if (!coherent && !gfpflags_allow_blocking(flags)) {    ......  //coherent_pool  void *addr = __alloc_from_pool(size, &page, flags);   if (addr)   *dma_handle = phys_to_dma(dev, page_to_phys(page));  return addr; } //cma or buddy or swiotlb ptr = __dma_alloc_coherent(dev, size, dma_handle, flags, attrs); if (!ptr)  goto no_mem; ...... return coherent_ptr;}

其中__alloc_from_pool用来分配 coherent_pool 的内存,__dma_alloc_coherent用来分配 cma 或者 buddy 或者 swiotlb的内存。(实际方案中一般通过memblock的方式划分coherent pool,cma,swiotlb这三种reserved mem)

其分配流程如下图所示:

from nxp community by eric chen

相关解释:

为了下面流式映射更好的理解,这里再详细讲下 swiotlb 的分配过程。

__dma_alloc_coherent->swiotlb_alloc_coherent->map_single

static phys_addr_tmap_single(struct device *hwdev, phys_addr_t phys, size_t size,    enum dma_data_direction dir, unsigned long attrs){ dma_addr_t start_dma_addr; if (swiotlb_force == SWIOTLB_NO_FORCE) {  dev_warn_ratelimited(hwdev, "Cannot do DMA to address %pa\n",         &phys);  return SWIOTLB_MAP_ERROR; } start_dma_addr = swiotlb_phys_to_dma(hwdev, io_tlb_start); return swiotlb_tbl_map_single(hwdev, start_dma_addr, phys, size,          dir, attrs);}phys_addr_t swiotlb_tbl_map_single(struct device *hwdev,       dma_addr_t tbl_dma_addr,       phys_addr_t orig_addr, size_t size,       enum dma_data_direction dir,       unsigned long attrs){  if (io_tlb_list[index] >= nslots) {   int count = 0;   for (i = index; i < (int) (index + nslots); i++)    io_tlb_list[i] = 0;   for (i = index - 1; (OFFSET(i, IO_TLB_SEGSIZE) != IO_TLB_SEGSIZE - 1) && io_tlb_list[i]; i--)    io_tlb_list[i] = ++count;   tlb_addr = io_tlb_start + (index << IO_TLB_SHIFT);   /*    * Update the indices to avoid searching in the next    * round.    */   io_tlb_index = ((index + nslots) < io_tlb_nslabs     ? (index + nslots) : 0);   goto found;  }    ......    return tlb_addr;}

申请bounce buffer并且返回虚拟地址,出去再转为dma地址

系统启动的时候就做好了slots和swiotlb内存的映射,这里根据slot可以返回其地址。

至此,dma_alloc_coherent的分配流程就完成了。我们可以看出虽然申请api都是dma_alloc_coherent函数,但是后台的实现有很多种,并且和是否是dma zone也没什么必然关系,本质上只是一块0x0000_0000到0xFFFF_FFFF范围内的连续内存。

流式映射 dma_map_single

因为DMA受32位访问的限制,所以只能访问0x0000_0000到0xFFFF_FFFF地址空间的内存,再加上DMA需要访问连续的物理内存,故coherent pool,cma,buddy,swiotlb必须保证在0x0000_0000~0xFFFF_FFFF以内的连续物理空间。 这些没毛病。

但是如果一个64位系统的话,CPU访问内存完全是可以大于0xFFFF_FFFF范围的。比如一个内存的基地址是0x80000000,内存大小是4G,则内存的物理地址范围是0x8000_0000~0x18000_0000。由于DMA寻址范围为0x0000_0000~0xFFFF_FFFF,如果CPU把数据放在0x10000_0000~0x18000_0000这段空间,DMA就无法访问了。

怎么解决上面的问题?

此时swiotlb就登上了历史舞台。

from nxp community by eric chen

swiotlb做的工作如上图所示,主要通过map_single从swiotlb里找到一块buffer叫做Bounce Buffer,然后把CPU访问的Data Buffer与Bounce Buffer映射起来,最后通过swiotlb_bounce把这两个buffer中的数据做个同步(memcpy)。

下面我们通过代码把上面的过程梳理一下。

物理页映射

dma_map_single->dma_map_single_attrs->(ops->map_page)->__swiotlb_map_page-> swiotlb_map_page-> map_single

dma_addr_t swiotlb_map_page(struct device *dev, struct page *page,       unsigned long offset, size_t size,       enum dma_data_direction dir,       unsigned long attrs){ //根据页号获取物理地址,进而获得DMA地址 phys_addr_t map, phys = page_to_phys(page) + offset; dma_addr_t dev_addr = phys_to_dma(dev, phys); 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.  */ //判断DMA的寻址能力是否能够覆盖上一步得到的物理地址,如果能的话,直接返回物理地址,否则采用swiotlb机制分配内存。 if (dma_capable(dev, dev_addr, size) && swiotlb_force != SWIOTLB_FORCE)  return dev_addr; trace_swiotlb_bounced(dev, dev_addr, size, swiotlb_force); /* Oh well, have to allocate and map a bounce buffer. */ //用swiotlb机制分配内存 map = map_single(dev, phys, size, dir, attrs); //判断调用swiotlb机制分配的内存物理地址是否在DMA寻址能力范围内,如果在的话直接返回,否则直接返回备用地址 if (map == SWIOTLB_MAP_ERROR) {  swiotlb_full(dev, size, dir, 1);  return swiotlb_phys_to_dma(dev, io_tlb_overflow_buffer); } dev_addr = swiotlb_phys_to_dma(dev, map); /* Ensure that the address returned is DMA'ble */ if (dma_capable(dev, dev_addr, size))  return dev_addr; attrs |= DMA_ATTR_SKIP_CPU_SYNC; swiotlb_tbl_unmap_single(dev, map, size, dir, attrs); return swiotlb_phys_to_dma(dev, io_tlb_overflow_buffer);}

CPU访问的内存转为DMA地址

判断DMA的寻址能力是否能够覆盖上一步得到的地址,如果能的话,直接返回地址,否则采用swiotlb机制分配内存。

通过map_single用swiotlb机制分配内存,详情见上面

至此,CPU对应的Data Buffer和DMA对应的Bounce Buffer就映射起来了

数据同步

dma_sync_single_for_device

dma_sync_single_for_device    _swiotlb_sync_single_for_device        swiotlb_sync_single_for_device          swiotlb_sync_single(..., SYNC_FOR_DEVICE)            swiotlb_tbl_sync_single              swiotlb_bounce(..., DMA_TO_DEVICE)                memcpy(vaddr, buffer + offset, sz) //将数据从Data Buffer处拷贝到Bounce Buffer        __dma_map_area            ENTRY(__dma_map_area)              cmp     w2, #DMA_FROM_DEVICE              b.eq    __dma_inv_area //invalid就是使cache中内容无效,下次使用时需要从内存中重新读取              b       __dma_clean_area //把cache中内容刷到内存中            ENDPIPROC(__dma_map_area)

dma_sync_single_for_cpu

dma_sync_single_for_cpu    __swiotlb_sync_single_for_cpu        __dma_unmap_area            ENTRY(__dma_unmap_area)              cmp     w2, #DMA_TO_DEVICE              b.ne    __dma_inv_area //invalid就是使cache中内容无效,下次使用时需要从内存中重新读取              ret            ENDPIPROC(__dma_unmap_area)        swiotlb_sync_single_for_cpu          swiotlb_sync_single(..., SYNC_FOR_CPU)            swiotlb_tbl_sync_single              swiotlb_bounce(..., DMA_FROM_DEVICE)                memcpy(buffer + offset, vaddr, sz) //将数据从Bounce Buffer处拷贝到Data Buffer

5T技术资源大放送!包括但不限于:C/C++,Arm, Linux,Android,人工智能,单片机,树莓派,等等。在公众号内回复「peter」,即可免费获取!!

 记得点击分享、赞和在看,给我充点儿电吧

没有IOMMU的DMA操作

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论