zoukankan      html  css  js  c++  java
  • iommu分析之smmu v3的实现

    smmu 除了完成 iommu 的统一的ops 之外,有自己独特的一些地方。
    1、Stream Table
    Stream Table是存在内存中的一张表,在SMMU设备初始化的时候由驱动程序创建好。
    Stream Table支持2种格式,Linear Stream Table 和 2-level Stream Table, Linear Stream Table就是将整个Stream Table在内存中线性展开为一个数组,
    优点是索引方便快捷,缺点是当平台上外设较少的时候浪费连续的内存空间。 2-level Stream Table则是将Stream Table拆成2级去索引,优点是更加节省内存。
    但是请注意的是,Stream Table 的2-level的构造和 iommu支持的两个阶段的翻译不是一个概念,
    SMMU支持2阶段地址翻译,这和内存虚拟化场景下MMU支持2阶段地址翻译类似, 第一阶段的地址翻译被用做进程(software entity)之间的隔离或者OS内的DMA隔离,
    第二阶段的地址翻译被用来做DMA重映射,即将Guest发起的DMA映射到Guest的地址空间内。
    第一阶段主要处理的是VA到IPA,第二阶段是IPA到PA

    2、Stream Table的查找属于smmu的第一阶段查找么?
    答案是肯定的,主要从看的角度来说。
    我们经常表述的SMMU支持两阶段地址翻译,是指va到gpa为第一阶段,而gpa到hpa是第二阶段,
    而stream table的查找是 属于前置查找,当然它可以实现为线性的,也可以实现为 2-level查找。
    从术语上说,它都不属于前面所说的一阶段查找的一部分,但是它的结果又决定了stage1的行为。

    3、Stream Table 是怎么申请内存的?
    smmuv3对Stream Table申请内存的实现如下:

    static int arm_smmu_init_strtab(struct arm_smmu_device *smmu)
    {
    	u64 reg;
    	int ret;
    
    	if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB)//二级表模式,类似于二级mmu页表来理解
    		ret = arm_smmu_init_strtab_2lvl(smmu);
    	else
    		ret = arm_smmu_init_strtab_linear(smmu);//否则是线性模式,所谓线性就是可以理解为一维数组
    ....
    

    如果是线性查找,则

    static int arm_smmu_init_strtab_linear(struct arm_smmu_device *smmu)
    {
    	void *strtab;
    	u64 reg;
    	u32 size;
    	struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
    
    	size = (1 << smmu->sid_bits) * (STRTAB_STE_DWORDS << 3);//caq:ste为64字节,8<<3为64
    	strtab = dmam_alloc_coherent(smmu->dev, size, &cfg->strtab_dma,
    				     GFP_KERNEL | __GFP_ZERO);//条目数*64,然后申请内存,**注意这里是一次性申请完**,和二级不一样
    	if (!strtab) {
    		dev_err(smmu->dev,
    			"failed to allocate linear stream table (%u bytes)\n",
    			size);
    		return -ENOMEM;
    	}
    	cfg->strtab = strtab;//caq:申请的内存地址记录下来,stream table的基地址
    	cfg->num_l1_ents = 1 << smmu->sid_bits;//条目数
    
    	/* Configure strtab_base_cfg for a linear table covering all SIDs */
    	reg  = FIELD_PREP(STRTAB_BASE_CFG_FMT, STRTAB_BASE_CFG_FMT_LINEAR);//caq:配置stream table(STRTAB_BASE_CFG)的log2size, ste的entry数目是2 ^ log2size
    	reg |= FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, smmu->sid_bits);
    	cfg->strtab_base_cfg = reg;//
    
    	arm_smmu_init_bypass_stes(strtab, cfg->num_l1_ents);
    	return 0;
    }
    

    如果是2-level查找,则

    //caq:二级表的初始化,查找的索引为SID,也就是streamid
    static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu)
    {
    	void *strtab;
    	u64 reg;
    	u32 size, l1size;
    	struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
    
    	/* Calculate the L1 size, capped to the SIDSIZE. */
    	size = STRTAB_L1_SZ_SHIFT - (ilog2(STRTAB_L1_DESC_DWORDS) + 3);
    	size = min(size, smmu->sid_bits - STRTAB_SPLIT);
    	cfg->num_l1_ents = 1 << size;//caq:计算有多少一级条目
    
    	size += STRTAB_SPLIT;
    	if (size < smmu->sid_bits)
    		dev_warn(smmu->dev,
    			 "2-level strtab only covers %u/%u bits of SID\n",
    			 size, smmu->sid_bits);
    
    	l1size = cfg->num_l1_ents * (STRTAB_L1_DESC_DWORDS << 3);//一级表*8,就是一个指针大小
    	strtab = dmam_alloc_coherent(smmu->dev, l1size, &cfg->strtab_dma,
    				     GFP_KERNEL | __GFP_ZERO);//caq:先申请一级表大小,请注意,看起来二级表是通过pagefault
    	if (!strtab) {
    		dev_err(smmu->dev,
    			"failed to allocate l1 stream table (%u bytes)\n",
    			size);
    		return -ENOMEM;
    	}
    	cfg->strtab = strtab;//caq:记录一级表位置,也就是stream table的基地址,是一个指针数组
    
    	/* Configure strtab_base_cfg for 2 levels */
    	reg  = FIELD_PREP(STRTAB_BASE_CFG_FMT, STRTAB_BASE_CFG_FMT_2LVL);//caq:fmt为2level
    	reg |= FIELD_PREP(STRTAB_BASE_CFG_LOG2SIZE, size);
    	reg |= FIELD_PREP(STRTAB_BASE_CFG_SPLIT, STRTAB_SPLIT);
    	cfg->strtab_base_cfg = reg;//caq:要按照二级查找的模式构造这个值,最终会将这个值刷到对应的寄存器
    
    	return arm_smmu_init_l1_strtab(smmu);
    } 
    
    

    那么,streamtable什么时候使用线性查找,什么时候使用二阶查找呢?

    #define IDR1_SSIDSIZE			GENMASK(10, 6)//硬件支持substreamID的bit数,0表示不支持substreamid
    #define IDR1_SIDSIZE			GENMASK(5, 0)//硬件支持streamID的bit数,0表示支持一个stream
    	/* SID/SSID sizes */
    	smmu->ssid_bits = FIELD_GET(IDR1_SSIDSIZE, reg);//caq:从硬件上支持的位数
    	smmu->sid_bits = FIELD_GET(IDR1_SIDSIZE, reg);//caq:从bit看支持64,但spec里面就是20bit,
    	/*
    	 * If the SMMU supports fewer bits than would fill a single L2 stream
    	 * table, use a linear table instead.
    	 */
    	if (smmu->sid_bits <= STRTAB_SPLIT)//caq:如果小于8bit,则没必要用二阶表
    		smmu->features &= ~ARM_SMMU_FEAT_2_LVL_STRTAB;//caq:直接用线性表
    
    #define STRTAB_L1_SZ_SHIFT		20//caq:20位的streamid,按照高 STRTAB_SPLIT 位来拆分
    #define STRTAB_SPLIT			8//caq:20位的streamid,高8位用来查找ste,低12位用来查找二层真正的ste entry
    
    

    如果硬件支持的streamid的bit小于8,则只使用线性查找。

    3、streamid,substreamid到底代表啥?
    一个smmu可以有多个设备连着,他们的页表除非共用,否则肯定不一样,SMMU 用stream id作区分,注意,此时区分的是设备。
    【StreamID去索引Stream Table中的STE(Stream Table Entry)】
    一个设备有多个进程会使用,所以smmu单元也要支持多页表,smmu使用substream id区分多进程的io页表。

    同样x86上也有类似的区分机制,不同的是x86是使用Request ID来区分的,Request ID默认是PCI设备分配到的BDF号。
    看SMMUv3 Spec,又有说明:对于PCI设备StreamID就是PCI设备的RequestID, 两者其实就是同一个东西。
    只是一个是从SMMU的角度去看就成为StreamID,从PCIe的角度去看就称之为RequestID。 同时,一个设备可能被多个进程使用,
    多个进程有多个页表,设备需要对其进行区分,SMMU使用SubstreamID来对其进行表示。 SubstreamID的概念和PCIe PASID是等效的,
    这只不过又是在ARM上的另外一种称呼而已。 SubstreamID最大支持20bit和PCIe PASID的最大宽度是一致的。

    设备master发起一笔DMA请求,请求的总线信息中带有streamid、subtreamid、iova(也就是大家理解的address)等参数,
    smmu硬件收到该请求会自动通过streamid检索到STE表(目前我们N2是采用二级STE表),再通过substreamid检索到这个STE表指向的CD表
    (就是context description表),从CD表中找到页表基地址,开始页表查找过程,找到IOVA对应的物理地址,再对这个物理地址发起一次DMA请求。
    由于smmu是为master服务的,可以称其为秘书,这个master可以是一个pcie的设备,也可能是其他设备。

    todo。。。。。。

    同X86上一样,ARM上的设备直通关键也是要解决DMA重映射和直通设备中断投递的问题。 但和X86上不一样的是,

    ARMv8-A上使用的是SMMU v3.1来处理设备的DMA重映射,这个在smmuv3的驱动代码就能看出,但是中断的话,

    由于arm进入服务器领域较晚,所以arm迭代的支持虚拟化的中断是在GICv3中断控制器来完成的,

    并没有像x86那样使用一个 **虚拟的 irq_chip **来完成。

    SMMUv3和GICv3在设计的时候考虑了更多跟虚拟化相关的实现, 针对虚拟化场景有一定的改进和优化。

    水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
  • 相关阅读:
    SQLiteDatabase 源码
    SQLiteOpenHelper 源码
    Java同步机制总结--synchronized
    [Swift A]
    [Swift A]-问号&感叹号
    [Swift A]
    [Swift A]
    android 屏幕适配
    2014年度加班时间
    nodejs初学-----helloworld
  • 原文地址:https://www.cnblogs.com/10087622blog/p/15479476.html
Copyright © 2011-2022 走看看