IOMMU(一)-简单介绍
DMA
DMA全称Direct Memory Access,CPU访问外设内存很慢,如果由CPU给外设大量搬运数据,CPU会大量空转等待搬运数据完成,所以发明出DMA engine,把搬运数据的任务由DMA engine来完成,CPU只要告诉DMA engine从什么地方开始搬运多大数据就行了,然后就可能干其它有意义的工作,DMA engine搬运完数据就打断CPU说搬运完了,接着搬运哪的数据手动多大。
CPU访问内存用virtual_address,然后通过MMU转换成physical_address,外设访问内存用的bus_address,由bus把bus_address定位到物理内存上,不同的bus可能处理方法还不一样,但在pci下,目前bus_address就是phiscal_address。DMA内存由CPU分配,CPU需要把virtual_address转换成bus_address,然后DMA engine进行DMA操作。DMA_ZONE表示DMA可使用的内存范围,现在x86_64下一般设备所有内存都可用,所以说DMA_ZONE和bus_address为了兼容而保留其实不用特殊考虑。
DMA内存分配和回收,CPU分配内存,设备做DMA操作,然后CPU回收内存,这些内存是等下次DMA继续用还是一次就回收用到的api也不一样。
cache一致性,由体系保证,如果体系不能保证则只能禁止CPU对做DMA的内存缓存了。
cache aligned,由提供内存者保证,不aligned一些外设可能搞不定。
内核态DMA驱动和DMA API
一般外设都自带DMA功能,DMA只是外设的数据传输通道,外设的功能各不一样,但DMA传输数据通道功能都一样,所以内核就有了DMA API,其它外设驱动只要调用内核DMA API就可以搞定DMA相关的功能了,内存映射/数据对齐/缓存一致性等都由内核DMA API搞定。
dma_ops = &nommu_dma_ops;
i40e_alloc_mapped_page
└─dma_map_page
└─nommu_map_page
用户态DMA驱动
外设通过DMA把数据放到内核,用户态再系统调用把数据手动到用户态,开销很大,所以想着外设直接把数据手动到用户态,可用户态用的都是虚拟地址,第一个问题就是得把虚拟地址转换成物理地址,用/proc/self/pagemap正好可以获取虚拟地址对应的物理地址。第二个问题是怎么保证虚拟地址对应的物理地址一定存在于内存中并且固定在内存中的同一个物理地址,虚拟地址一定有对应的物理地址好说,可以直接把page的ref加1,并且强行给page写个0数据,但虚拟地址固定对应到一个物理地址就难说了,假如进程给一个虚拟地址找了一个page让设备给这个page DMA写数据,同时kernel开始了page migration或者same page merge,把进程的虚拟地址对应的物理设置成其它page,但设备DMA写的page还是原来的page,这样导致进程访问的数据就不是设备定到内存中的数据,但这种概率很小啊。总之hugepage能满足大部分特性。
IOMMU
类同于MMU,对DMA做地址翻译,用来解决DMA的安全性问题,DMA API同时肩负起了设置IOMMU的职责。
dma_ops = &intel_dma_ops;
i40e_alloc_mapped_page
└─dma_map_page
└─intel_map_page
└─__intel_map_single
├─if(iommu_no_mapping) return paddr;
├─intel_alloc_iova
└─domain_pfn_mapping
内核用IOMMU的好处,限制了设备DMA可写的范围,设备不成随便从物理内存读写了。其实IOMMU更大的用处在于用户态驱动,如DPDK和qemu,用于qemu passthrough能更好的理解IOMMU的作用,guest发起DMA时设置的地址是guest_phy_addr,qemu拿到guest DMA的内存段开始地址guest_dma_phy_addr_start转换成自己的host_dma_virt_addr,然后把两个地址和DMA段的长度len通知vfio建立map,vfio找从host_dma_phy_addr开始的len长度的连续物理内存,host_dma_virt_addr映射到host_dma_phy_addr,然后pin住,让qemu的虚拟地址始终有物理内存对应并且对应到固定物理内存。vfio再给iommu建立表项,如果DMA要和guest_phy_addr交换数据,就和host_dma_phy_addr交换数据,iommu中有个iova,其实这个iova在这儿就是guest_phy_addr。dpdk中有--iova ,相比较于qemu这儿就是小菜一碟。
kvm device passthrough老方法
kvm_iommu_map_pages
└─iommu_map
└─intel_iommu_map(domain->ops->map)
└─domain_pfn_mapping
qemu用vfio实现device passthrough新方法
vfio_iommu_type1_ioctl