IOMMU就是所谓的SMMU,它可以将不连续的物理内存组织成一个连续的虚拟内存(这对于很多驱动来说很有意义)。IOMMU把虚拟内存转换成物理内存的逻辑与CPU的MMU一样。
通常,Linux很难分配好几MB大小的连续的物理内存(比如5MB或者10MB)。因为内存在使用当中都被会被碎片化,很难找到这么大且连续的物理内存大小。所以以前通常会在linux内核启动之前,把需要用到的几十MB或者上百MB的内存从初始化的物理内存中挖出去。但这么挖出去之后,会造成内存的浪费,因为这么挖出去的内存无论设备用或者不用都不能被Linux内核所分配使用。
IOMMU既可以让Linux内核不用预留很大的连续的物理内存,又可以保证让Linux内核使用一般的API分配到连续的虚拟内存(不管物理内存是连续的还是不连续的)。
下面来看一下IOMMU在整个架构中的位置。
这个根据芯片厂家的芯片型号有很大关系,但这里就说一下高通平台的几个MSM平台的芯片,比如MSM8960的IOMMU。这些芯片的几个多媒体子系统(multimedia subsystem)都有自己独立的IOMMU,如下图所示。(图中红色的部分为IOMMU)。MSM8960中有12个IOMMU。

voi虚拟化 虚拟化iommu有什么用_voi虚拟化

从上面图中可以看到,IOMMU位于 multimedia core 和 multimedia FABRIC(MMSS FABRIC) 中间,完成地址转换工作。如果IOMMU不用的,就直接bypass。(这和CPU的MMU都一样)
MSM8960上面说了有12个 IOMMU,每个IOMMU都用于特定的 multimedia core。下面来看一下一个IOMMU部分的详细说明。

voi虚拟化 虚拟化iommu有什么用_voi虚拟化_02

上面图中,最上面的Multimedia Core为当前IOMMU的使用者。每个IOMMU的使用者,都有一个或者两个Machine ID(MID),这些ID或者是固定的或者是使用者自己生成的??。
中间的 SMMU_M2VCBMT这个是一个标记(nomenclature)?,可以通过找到相应的Virtual Machine ID(VMID)和相应的context bank number。比如JPEG decoder的IOMMU的VMID和context bank number 可以配置在SMMU_JPEGD_M2VCBRn 寄存器(?)中。
从上图中可以看到,MSM8960的每个IOMMU有两个context buffer(通常是两个,但GFX3D IOMMU有三个context buffer)。
每个MID即使在使用同一个IOMMU,但可以有不同的page table。这给了IOMMU的使用者很大的自由度,比如JPEG decoder可以使用不同的page table去读或者去写。
context bank和VMID值都是由Secure Root of Trust(SRoT)在启动的时候设置并且被锁住的。每个context bank有自己的Translation Table Base Register(TTBR),这个寄存器就像是ARM寄存器中保存一级页表基地址的协处理器中的寄存器一样保存页表的基地址。IOMMU使用的页表的格式也是和ARM架构中的页表的格式是哟模一样的。

IOMMU的Security相关:
 The VMID is needed for security. It determines on whose behalf the request is made. The context bank index is
 used to de termine which context bank and which page table is used to service this request, but the VMID is
 then needed to send the translated request to the rest of the system. If non secure apps are controlling the
 client and the IOMMU, the nonsecure apps VMID must be programmed to reflect this so the IOMMU
 cannot be used to access memory that does not belong to the nonsecure apps . When the TZ is controlling
 the IOMMU, e.g., in secure playback , the VMID must be reprogrammed to allow access to the secured
 memory.
 DomainIOMMU中Domain代表虚拟地址的范围。每个Domain在IOMMU map中都有自己的虚拟地址范围。在MSM8960中一共定义了四种IOMMU Domain。这表示每个IOMMU的context bank,必须要map到四个Domain中的一个所代表的虚拟地址范围当中。
 enum {
 VIDEO_DOMAIN,
 CAMERA_DOMAIN,
 DISPLAY_READ_DOMAIN,
 DISPLAY_WRITE_DOMAIN,
 ROTATOR_SRC_DOMAIN,
 ROTATOR_DST_DOMAIN,
 MAX_DOMAINS
 };


一个ion client在配置IOMMU把一段地址从虚拟地址转换成物理地址的时候(就像设置mmu一样),必须要指定一个Domain。例如MDP驱动在做上述的事情的时候,就必须指定DISPLAY_DOMAIN。

int ion_map_iommu(struct ion_client *client, struct ion_handle *handle,
 int domain_num, int partition_num, unsigned long align,
 unsigned long iova_length, ion_phys_addr_t *iova,
 unsigned long *buffer_size,
 unsigned long flags, unsigned long iommu_flags)
 调用ion_map_iommu()函数的例子,可以看msm_smem.c文件中的get_device_address()函数里,
 调用ion_map_iommu()之前的log,显示如下:
 <6>[ 40.004803] [0:TimedEventQueue: 818] Calling ion_map_iommu - domain: 5, partition: 1
 <6>[ 40.012398] [0:TimedEventQueue: 818] Calling ion_map_iommu - domain: 5, partition: 1
 Partitions

Partition是在一个Domain中,虚拟地址的一个窗口。所以不同的partition表示一个在一个4GB大小的虚拟地址空间中的不同的窗口。一个ION Client在请求虚拟地址->物理地址的映射的时候,Client必须指定partition以指定属于哪个虚拟地址空间。
目前有三种partition。每个部分都有如下虚拟地址窗口(开始地址以及大小)。

VIDEO_FIRMWARE_POOL
 Virtual addr start = SZ_128K
 Size of virtual address window = SZ_16M to SZ_128K
 VIDEO_MAIN_POOL
 Virtual addr start = SZ_16M
 Size of virtual address window = SZ_256M to SZ_16M
 GEN_POOL
 Virtual addr start = SZ_256M
 Size of virtual address window = SZ_2G to SZ_256M
 比如,MSM8960的video core,从video core角度看,其firmware下载地址必须小于16MB,这个是其硬件决定的。下面的代码就是保证要求的虚拟地址范围满足其需求的一个例子。上面的三个partition中,只有VIDEO_FIRMWARE_POOL的地址范围在16MB以下,所以传如的partition也是VIDEO_FIRMWARE_POOL。
 ion_map_iommu(ddl_context- >video_ion_client, addr ->alloc_handle, VIDEO_DOMAI N,
 VIDEO_FIRMWARE_POOL, SZ_4K, 0, &iova, &buffer_size, UNCACHED, 0);
 这几个Partition的定义如下:
 enum {
 VIDEO_FIRMWARE_POOL,
 VIDEO_MAIN_POOL,
 GEN_POOL,
 };
 IOMMU Kernel APIs
 Ion APIs

前面说过,IOMMU和CPU的MMU一样,都是用于虚拟地址和物理地址之间的转换的。只不过IOMMU是服务于multimedia core。一般IOMMU提供了ion_map_iommu()函数和ion_unmap_iommu()函数完成配置IOMMU已完成虚拟地址到物理地址的转换工作,就像配置MMU一样。
ion_map_iommu()函数的第一个和第二个参数分别是ion clients和ion handle。想要把ion handle表示的ion buffer用IOMMU map起来,就必须要在调用ion_map_iommu()函数之前申请ion buffer。IOMMU在申请这个ion buffer的时候所使用的接口为ion_alloc()函数,且使用的ion heap id是 ION_IOMMU_HEAP_ID。这个在高通平台已经定义成了ION_SYSTEM_HEAP_ID。(msm_ion.h 中 #define ION_IOMMU_HEAP_ID ION_SYSTEM_HEAP_ID )。以前讲过ion memory相关的内容,ION_SYSTEM_HEAP_ID对应的ion heap type定义在了msm89xx-ion.dtsi文件中,根据ion heap type就可以看出来起使用的ion_heap_ops到底是什么。
这里直接讲了,ION_IOMMU_HEAP_ID对应的ion_heap_ops就是

static struct ion_heap_ops system_heap_ops = {
 .allocate = ion_system_heap_allocate,
 .free = ion_system_heap_free,
 .map_dma = ion_system_heap_map_dma,
 .unmap_dma = ion_system_heap_unmap_dma,
 .map_kernel = ion_heap_map_kernel,
 .unmap_kernel = ion_heap_unmap_kernel,
 .map_user = ion_heap_map_user,
 .shrink = ion_system_heap_shrink,
 };

IOMMU相关的内存分配,在ion_alloc()函数调用的时候传ION_SYSTEM_HEAP_ID或者ION_IOMMU_HEAP_ID就会调用上面的alloc对应的函数。在ion_system_heap_allocate()函数分配完内存之后,其信息就会保存到相应的ion_buffer类型的变量中。

struct ion_buffer {
 …
 size_t size;
 union {
 void *priv_virt;
 ion_phys_addr_t priv_phys;
 };
 …
 int kmap_cnt; //number of times the buffer is mapped to the kernel
 void *vaddr; //上面kmap_cnt如果不是0,vaddr就表示map到内核的虚拟地址!!
 int dmap_cnt; //number of times the buffer is mapped for dma
 struct sg_table *sg_table; //the sg table for the buffer if dmap_cnt is not zero!!
 struct page **pages;
 struct list_head vmas;
 }
 ion_buffer保存着所有分配到的物理内存的page个数,内存大小。ion_buffer后面会被用来配置IOMMU和CPU的MMU。
 system_heap_ops中的map_kernel,应该是用来把分配到的内存再map给kernel的。因为有时候CPU通过MMU也是需要访问这些内存以拷贝一些数据给相应的multimedia core的。
 kernel\drivers\iommu\msm_iommu-v1.c
 kernel\drivers\iommu\msm_iommu_sec.c
 kernel\drivers\iommu\msm_iommu_mapping.c
 iommu_ops!!
 ion_map_iommu() : 配置IOMMU,完成地址转换(就像配置MMU一样)。