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在设计的时候考虑了更多跟虚拟化相关的实现, 针对虚拟化场景有一定的改进和优化。

    水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
  • 相关阅读:
    使用javap分析Java的字符串操作
    使用javap深入理解Java整型常量和整型变量的区别
    分享一个WebGL开发的网站-用JavaScript + WebGL开发3D模型
    Java动态代理之InvocationHandler最简单的入门教程
    Java实现 LeetCode 542 01 矩阵(暴力大法,正反便利)
    Java实现 LeetCode 542 01 矩阵(暴力大法,正反便利)
    Java实现 LeetCode 542 01 矩阵(暴力大法,正反便利)
    Java实现 LeetCode 541 反转字符串 II(暴力大法)
    Java实现 LeetCode 541 反转字符串 II(暴力大法)
    Java实现 LeetCode 541 反转字符串 II(暴力大法)
  • 原文地址:https://www.cnblogs.com/10087622blog/p/15479476.html
Copyright © 2011-2022 走看看