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。但arm內有很多platform類設備
這類設備沒有bdf號,所以arm抽象成StreamID來區分設備也比較合理。
同時,一個設備可能被多個進程使用,
多個進程有多個頁表,設備需要對其進行區分,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在設計的時候考慮了更多跟虛擬化相關的實現, 針對虛擬化場景有一定的改進和優化。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM