DMA-API(map和unmap)调用流程分析(十一)
1.简介
当使用IOMMU且使能DMA-IOMMU中间层时,使用DMA API接口alloc、free、map、unmap内存时,底层都会调用到IOMMU API,最终调用到SMMUv3驱动,完成内存的map和unmap。调用流程如下图所示。
2.dma_map_single
从下图可以看出,dma_map_single
有三种映射内存的方式。第一种是使用direct方式映射内存。第二种使用iommu方式映射内存,第三种使用dma_map_ops
回调函数映射内存。本文只介绍前两种。
2.1.dma_direct_map_page
dma_direct_map_page
方式映射内存流程如下图所示。
- 如果满足下面的条件之一,则需要使用
swiotlb_map
映射内存。swiotlb_map
会在内部分配一个bounce buffer,然后将源数据拷贝到bounce buffer中,然后clean bounce buffer的cache,最后返回bounce buffer的DMA地址。- 强制使用swiotlb bounce,即
is_swiotlb_force_bounce
返回true
。 - 传入的地址DMA无法访问或者kmalloc分配的内存需要bounce buffer,并且设备拥有swiotlb内存池。
- 强制使用swiotlb bounce,即
- 如果不满足1的条件,说明传入的地址DMA可以访问,只需要clean cache即可,最后返回传入地址的DMA地址。
2.2.iommu_dma_map_page
如果使用IOMMU,则调用iommu_dma_map_page
函数映射内存。执行流程如下:
- 如果起始物理地址和长度没有按页对齐,IOMMU无法直接映射,则需要使用bounce page(会按页对齐),即走
swiotlb_tbl_map_single
的流程。 - clean需要映射内存的cache。
- 调用
__iommu_dma_map
映射内存,内部首先分配IOVA,然后调用SMMU驱动映射IOVA和物理内存。IOMMU映射内存的流程参考DMA-API(alloc和free)调用流程分析(十) 第2.2.节。
3.dma_unmap_single
从下图可以看出,dma_unmap_single
有三种unmap内存的方式。第一种是使用direct方式unmap内存。第二种使用iommu方式unmap内存,第三种使用dma_map_ops
回调函数方式unmap内存。本文只介绍前两种。dma_map_single
和dma_unmap_single
函数相互匹配,当使用direct方式map内存时,则必须使用direct方式unmap内存,IOMMU则类似。
3.1.dma_direct_unmap_page
如果dma_direct_map_page
函数使用swiotlb方式映射内存,则dma_direct_unmap_page
函数也要用swiotlb方式unmap内存,否则只需要invalidate cache。swiotlb方式unmap内存时,首先要invalidate cache,然后将swiotlb内存中的数据拷贝到源缓冲区中,最后释放swiotlb内存。
3.2.iommu_dma_unmap_page
iommu_dma_unmap_page
函数unmap内存的流程如下图所示,具体如下:
- 由于invalidate cache需要使用物理内存,因此需要调用
iommu_iova_to_phys
将IOVA转换成物理地址。 - invalidate cache。
- 调用IOMMU API unmap内存。流程可参考DMA-API(alloc和free)调用流程分析(十) 第3.2.节。
- 如使用swiotlb内存,则还需要将swiotlb bounce buffer中的数据拷贝到原缓冲中,然后释放swiotlb bounce buffer。
当使用IOMMU的情况下,调用iommu_iova_to_phys
函数将IOVA转换成物理地址。转换的功能最终在arm_lpae_iova_to_phys
函数中实现,其会从遍历pgd页表开始遍历,最终计算得到IOVA对应的物理地址。
[drivers/iommu/io-pgtable-arm.c]
static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,unsigned long iova)
{......do {/* Valid IOPTE pointer? */if (!ptep)return 0;/* 获取IOVA的第lvl级页表索引,加上ptep的基地址,就能得到对应页表项的地址 */ptep += ARM_LPAE_LVL_IDX(iova, lvl, data);pte = READ_ONCE(*ptep); /* 获取第lvl级页表的下一级页表 *//* Valid entry? */if (!pte)return 0;/* 如果是叶子页表,即最后一级页表,则表明找到iova的物理 */if (iopte_leaf(pte, lvl, data->iop.fmt))goto found_translation;/* 从页表项中提取下一级页表的地址 */ptep = iopte_deref(pte, data);} while (++lvl < ARM_LPAE_MAX_LEVELS); // 下一级页表/* Ran out of page tables to walk */return 0;
/* 找到最后一级页表地址 */
found_translation:/* ARM_LPAE_BLOCK_SIZE计算block/page描述符映射的内存大小* (ARM_LPAE_BLOCK_SIZE - 1)获取了block/page描述符映射内存大小的掩码* iova与验码计算,得到了物理地址的业内便宜*/iova &= (ARM_LPAE_BLOCK_SIZE(lvl, data) - 1);/* iopte_to_paddr获取最后一级页表映射的物理内存页基地址,加上偏移最终得到物理地址 */return iopte_to_paddr(pte, data) | iova;
}
4.dma_map_sg
从下图可以看出,dma_map_sg
有三种映射内存的方式。第一种是使用direct方式映射内存。第二种使用iommu方式映射内存,第三种使用dma_map_ops
回调函数映射内存。本文只介绍前两种。
4.1.dma_direct_map_sg
dma_direct_map_sg
函数的执行流程如下所示,其遍历scatterlist
数组中的每一个page,调用dma_direct_map_page
函数映射,具体可参考2.1节。
4.2.iommu_dma_map_sg
iommu_dma_map_sg
映射内存的流程如下图所示,具体步骤如下:
- 如果设备是不可信设备,或者
scatterlist
数组中有缓冲区没有按DMA要求的长度对齐,则走iommu_dma_map_sg_swiotlb
映射流程。映射完成后直接返回,不走后面的流程。每一个scatterlist
映射后的DMA地址(IOVA地址)保存在dma_address
中,因此整个scatterlist
数组映射后的IOVA地址并不连续。- 首先给第一个
scatterlist
设置SG_DMA_SWIOTLB
标志,表示scatterlist
使用swiotlb bounce buffer。 - 遍历每一个
scatterlist
,调用iommu_dma_map_page
进行映射,具体可参考2.2节。
- 首先给第一个
- 首先调用
iommu_dma_sync_sg_for_device
同步缓存,若需要swiotlb(长度没有对齐的情况第一步已经处理,这里通常不需要swiotlb),则还需要分配bounce buffer并且拷贝数据。然后遍历scatterlist
数组,计算需要分配的IOVA长度。接着分配IOVA,最后调用iommu_map_sg
映射scatterlist
数组,内部会针对每一个scatterlist
,调用__iommu_map
映射内存。整个scatterlist
数组映射后的IOVA地址连续。IOMMU映射内存的流程参考DMA-API(alloc和free)调用流程分析(十) 第2.2.节。
5.dma_unmap_sg
从下图可以看出,dma_unmap_sg
有三种unmap内存的方式。第一种是使用direct方式unmap内存。第二种使用iommu方式unmap内存,第三种使用dma_map_ops
回调函数unmap内存。本文只介绍前两种。
5.1.dma_direct_unmap_sg
dma_direct_unmap_sg
函数执行流程如下图所示,主要工作有:
- 如果DMA地址是总线地址(即
scatterlist
中的dma_flags
设置了SG_DMA_BUS_ADDRESS
标志),则不需要unmap,直接清除掉SG_DMA_BUS_ADDRESS
即可。pci_p2pdma缓冲区地址通常会设置SG_DMA_BUS_ADDRESS
标志。 - 否则需要走unmap流程。遍历每一个
scatterlist
,invalidate cache,如果使用swiotlb内存,还需要拷贝数据和释放swiotlb内存。
5.2.iommu_dma_unmap_sg
iommu_dma_unmap_sg
和iommu_dma_map_sg
流程刚好相反,执行流程如下:
- 第一个
scatterlist
如果设置了SG_DMA_SWIOTLB
标志,则说明使用swiotlb内存(IOVA不连续),则遍历每一个scatterlist
,调用iommu_dma_unmap_page
解除映射。完成之后直接返回。 - 遍历
scatterlist
数组,invalidate cache。 - 遍历
scatterlist
数组,计算IOVA的起始地址和结束地址(IOVA不连续)。 - 调用
__iommu_dma_unmap
函数unmap内存。流程可参考DMA-API(alloc和free)调用流程分析(十) 第3.2.节。
参考资料
- linux6.12 source code.
- Arm ® System Memory Management Unit Architecture Specification version 3.