IOMMU 和 ARM SMMU 简介

IOMMU 和 ARM SMMU 简介

一、IOMMU 简介

在计算中,输入输出内存管理单元 (IOMMU) 是一种内存管理单元 (MMU),它将支持直接内存访问(支持 DMA)的 I/O 总线连接到物理内存。与传统 MMU 一样,IOMMU 将设备可见的虚拟地址(也称为 I/O 虚拟地址,IOVA)映射到物理地址 (PA)。不同的平台具有不同的 IOMMU,例如 Intel IOMMU(PCI Express 显卡使用的图形地址重映射表 (GART))和 ARM 平台使用的系统内存管理单元 (SMMU)。

CPU 和设备按以下方式访问物理内存:

    +---------------------+
    |      Main Memory    |
    +---------------------+
               |
              pa
               |
         -------------
         |           |
    +--------+   +--------+
    | IOMMU  |   |  MMU   |
    +--------+   +--------+
         |           |
       iova          va
         |           |
    +--------+   +--------+
    | Device |   |  CPU   |
    +--------+   +--------+

与 DMA 相比,IOMMU 具有以下优势:

  • 可以分配较大的内存区域,而无需在物理内存中连续。IOMMU 将连续的虚拟地址 (VA) 映射到分段的 PA
  • 如果设备不支持足够长的内存地址以寻址整个物理内存,则仍可通过 IOMMU 寻址整个内存。例如,x86 计算机可以使用物理地址扩展 (PAE) 功能寻址超过 4 GB 的内存。但是普通的 32 位 PCI 设备无法寻址 4 GB 以上的内存。使用 IOMMU,设备可以寻址整个物理内存。
  • 内存受到保护,免受尝试 DMA 攻击的恶意设备和由于设备无法读取或写入映射的物理内存而尝试错误内存传输的故障设备的攻击。
  • 在虚拟化中,来宾操作系统可以使用并非专门用于虚拟化的硬件。显卡等更高性能的硬件使用 DMA 直接访问内存。在虚拟环境中,所有内存地址都由虚拟化软件(如 QEMU)重新映射,这会导致客户机操作系统无法使用 DMA 访问内存。IOMMU 处理此重新映射,允许驱动程序使用 DMA 访问来宾 OS 中的内存。
  • 在某些体系结构中,IOMMU 还以类似于地址重新映射的方式执行中断重新映射。
  • IOMMU 可以支持外设内存分页。使用 PCI-SIG PCIe 地址转换服务 (ATS) 页面请求接口 (PRI) 扩展的外设可以检测内存管理器服务的需求并发出信号。

与 DMA 相比,使用 IOMMU 的缺点包括额外的性能和内存开销。地址转换和页面错误处理会增加性能开销。此外,IOMMU 需要在内存中为 I/O 页表分配空间。在某些情况下,IOMMU 和 CPU 共享页表以避免此内存开销。例如,设备和 CPU 共享虚拟地址。

二、ARM SMMU 数据结构

SMMU 提供使用设备可见的 IOVA 访问物理内存的功能。在系统架构中,多个设备可能会使用 IOVA 通过 IOMMU 访问物理内存。IOMMU 需要区分不同的设备,因此每个设备都分配了一个 SID(Stream ID),该 ID 指向对应的流表表项 (STE)。所有 STE 都以数组的形式存在于内存中。SMMU 记录 STE 数组的起始地址。扫描设备时,OS 会为设备分配唯一的 SID。设备通过 IOMMU 访问内存的所有配置都会写入到 SID 对应的 STE 中。

Stream 表:

                  +-------+
strtab_base ----- | STE 0 |
                  | STE 1 |
StreamID[n:0] ->  | STE 2 |
                  | STE 3 |
                  +-------+

STE 存储从 IOVA 到 PA 的地址转换过程。为了适应虚拟化场景下的内存访问需求,SMMU 支持两个阶段的地址转换,其方式类似于扩展页表 (EPT)。阶段 1 将 VA 转换为中间物理地址 (IPA),阶段 2 将 IPA 转换为 PA。

STE:

Stream Table Entry (STE)
+-----------------------+
| Config | S1ContextPtr | -> CD -> Stage 1 translation tables
+-----------------------+
|  VMID  | S2TTB        | -> Stage 2 translation tables
+-----------------------+
| Other attributes,     |
| configuration         |
+-----------------------+

在非虚拟化场景下,如果设备使用 IOVA 通过 IOMMU 执行 DMA,则只需要进行阶段 1 的地址转换。由于多个设备可能使用一个设备,因此每个设备的 STE 还记录有关上下文描述符 (CD) 表的信息,S1ContextPtr 指向内存中 CD 表的基址。CD 表也是一个数组,并使用 SubstreamID(SSID 或 PASID)进行内存访问。PASID 是与进程关联的 ID,用于区分不同进程的 VA 空间。使用 PASID 找到 CD 条目后,将找到阶段 1 地址转换的 I/O 页表,并将其存储在 TTB0 和 TTB1 中。

光盘:

                  +-------+
S1ContextPtr ---- |  CD 0 |
                  |  CD 1 |
SubStreamID   ->  |  CD 2 |
                  |  CD 3 |
                  +-------+

Context Desctriptor (CD)
+-----------------------+
| Configuration | TTB0  |
+-----------------------+
|      ASID     | TTB1  |
+-----------------------+

应该注意的是,通常,该过程通过使用设备驱动程序启用设备执行 DMA 时使用的 IOVA,以便由内核模式驱动程序分配。当有多个进程时,内核模式 IOVA 映射到该进程的 VA 空间,即 DMA 期间,不同的进程实际上使用相同的 IOVA 空间。因此,只需要 CD 0。一般情况下,只需要将 I/O 页表的基址存储在 CD 0 中。但是,当设备的 I/O 地址空间和进程的 VA 空间需要统一时,例如访问共享虚拟地址 (SVA),则使用多个 CD 表项分别绑定不同进程的 VA 空间。

设备通过 SMMU 转换地址是一个复杂的过程。第一步是使用设备 SID 查找 STE。STE 记录是否需要绕过阶段 1 地址转换。旁通是指直接使用 PA 或 IPA。如果未执行旁路,则使用 SID 查找关联的 CD,该 CD 记录第 1 阶段地址转换的页表,以将 VA 转换为 IPA。如果在 STE 中配置了阶段 2 页表转换,则 IPA 将被转换为 PA。如果未配置第 2 阶段地址转换,则之前获取的 IPA 是 PA。

SMMU 地址转换流程如下:

                         VA
                          |
                    -----------
                    |         |
+---------------------+       |
| Stage 1 translation |    Bypass
|        VA->IPA      |       |
+---------------------+       |
                    |         |
                    -----------
                          |
                         IPA
                          |
                    -----------
                    |         |
+---------------------+       |
| Stage 2 translation |    Bypass
|        IPA->VA      |       |
+---------------------+       |
                    |         |
                    -----------
                          |
                         PA

三、ARM SMMUv3 初始化

所有与 IOMMU 相关的驱动程序都存储在内核的 drivers/iommu 目录下。最新的 SMMUv3 驱动程序是 。该结构管理内存中 SMMU 平台设备的关键信息。内核通过填写此结构来初始化 SMMU 设备。arm-smmu-v3.cstruct arm_smmu_device

/* An SMMUv3 instance */
struct arm_smmu_device {
	struct device			*dev;
	void __iomem			*base;
	u32				features;
	u32				options;

	struct arm_smmu_cmdq		cmdq;
	struct arm_smmu_evtq		evtq;
	struct arm_smmu_priq		priq;

	int				gerr_irq;
	int				combined_irq;
	u32				sync_nr;

	unsigned long			ias; /* IPA */
	unsigned long			oas; /* PA */
	unsigned long			pgsize_bitmap;

#define ARM_SMMU_MAX_ASIDS		(1 << 16)
	unsigned int			asid_bits;
	DECLARE_BITMAP(asid_map, ARM_SMMU_MAX_ASIDS);

#define ARM_SMMU_MAX_VMIDS		(1 << 16)
	unsigned int			vmid_bits;
	DECLARE_BITMAP(vmid_map, ARM_SMMU_MAX_VMIDS);

	unsigned int			ssid_bits;
	unsigned int			sid_bits;

	struct arm_smmu_strtab_cfg	strtab_cfg;

	/* IOMMU core code handle */
	struct iommu_device		iommu;
};

加载驱动程序的入口是函数,该函数执行以下操作:arm_smmu_device_probe

  1. 从 DTS 的 SMMU 节点或 ACPI 的 SMMU 配置表中读取 SMMU 中断等属性。
  2. 用于从设备获取资源信息并重新映射 I/O。struct resource
  3. 探测 SMMU 的硬件功能。
  4. 初始化中断和事件队列。
  5. 创建 STE 表。
  6. 重置设备。
  7. 向 IOMMU 注册 SMMU。

1. 读取 DTS 的 SMMU 节点信息

该函数从 中读取属性并将属性记录在 中。此外,该函数还会检查 DMA 是否支持一致性。如果 DMA 支持一致性,则该函数将设置一致性特征。arm_smmu_device_dt_probesmmu->dev->of_nodesmmu->options

if (of_dma_is_coherent(dev->of_node))
      smmu->features |= ARM_SMMU_FEAT_COHERENCY;

2. 获取设备资源信息并执行 I/O 重映射

struct resource存储获取到的 SMMU 设备资源信息,包括 I/O 基址和 I/O 重映射后的基址。然后,可以使用 plus offset 读取和写入 SMMU 硬件寄存器。smmu->basesmmu->base

/* Base address */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ioaddr = res->start;
smmu->base = devm_ioremap_resource(dev, res);

3. 探测 SMMU 硬件特性

该函数通过读取 SMMU 寄存器来获取 SMMU 的硬件特性。arm_smmu_device_hw_probe

IDR0 寄存器:

reg = readl_relaxed(smmu->base + ARM_SMMU_IDR0);
  • 记录是否支持两级 STE 表和两级 CD 表。
  • 记录是否支持 PRI、ATS、SEV、MSI、HYP、STALL、STAGE 1 和 Stage 2。
  • 获取 ias length、asid_bits 和 vmid_bits。

IDR1 寄存器:

reg = readl_relaxed(smmu->base + ARM_SMMU_IDR1);
  • 获取 evtq 和 priq 队列的长度、ssid_bits 和 sid_bits。

IDR5 注册:

reg = readl_relaxed(smmu->base + ARM_SMMU_IDR5);
  • 获取 evtq stall 的最大个数。
  • 记录是否支持 VAX、oas length 和 pgsize_bitmap。

4. 初始化中断和事件队列

该函数初始化内存中的数据结构,包括 evtq、priq 和 cmdq 三个队列。SMMU 驱动程序使用 cmdq 队列向硬件发送命令,例如刷新 TLB 和写入 CD。事件队列由挂载到 SMMU 的平台设备用于向驱动程序发送异常消息。priq 队列具有与事件队列类似的功能,只是前者由挂载的 PCI 设备使用。event 和 pri 队列有自己的中断 ID 来通知异常事件。此外,gerror 中断 ID 用于报告不可恢复的严重错误,这些错误会被直接中断,而不会被添加到队列中。arm_smmu_init_structures

irq = platform_get_irq_byname(pdev, "combined");
if (irq > 0)
      smmu->combined_irq = irq;
else {
      irq = platform_get_irq_byname(pdev, "eventq");
      if (irq > 0)
            smmu->evtq.q.irq = irq;

      irq = platform_get_irq_byname(pdev, "priq");
      if (irq > 0)
            smmu->priq.q.irq = irq;

      irq = platform_get_irq_byname(pdev, "gerror");
      if (irq > 0)
            smmu->gerr_irq = irq;
}

/* Initialise in-memory data structures */
ret = arm_smmu_init_structures(smmu);

当驱动程序重置 SMMU 设备时,注册相应的事件处理。eventq 和 priq 注册一个内核线程以完成事件处理。对于不可恢复的错误,直接注册它完成了中断处理。arm_smmu_setup_unique_irqsarm_smmu_setup_unique_irqs

5. 创建 STE 表

可以根据 SMMU 配置创建两级或线性 STE 表。与线性 STE 表相比,两级 STE 表不需要在开始时创建所有 STE,而只需分配第一级的目录表项。对于线性 STE 表,使用 DMA 根据 和 的值分配内存中的连续区域。基址记录在配置中,并且所有 STE 都设置为默认旁路模式。sid_bitsSTE

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);
	strtab = dmam_alloc_coherent(smmu->dev, size, &cfg->strtab_dma,
				     GFP_KERNEL | __GFP_ZERO);

	cfg->strtab = strtab;
	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);
	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;
}

6. 重置设备

该函数将重置设备。该函数根据获取到的器件寄存器,将队列内存属性等信息写入控制寄存器 CR1 和 CR2,将 STE 表的基址和配置信息写入STRTAB_BASE寄存器,并将内存中三个队列的基址、队列头和队列尾写入相应的寄存器。然后,reset 函数通过调用 来注册中断事件处理。arm_smmu_device_resetarm_smmu_setup_irqs

7. 向 IOMMU 注册 SMMU

不同平台上的 IOMMU 设备在 Linux 内核中具有统一的 IOMMU 接口。在 SMMU 初始化期间,设备节点将在 sys 目录中注册,并注册到设备、系统 PCI 总线和平台设备总线。这样,当使用 IOMMU 公网接口时,会调用扫描 SMMU 提供的功能。有关详细信息,请参阅 中提供的各种 IOMMU 接口的实现。smmu->iommuarm_smmu_opsarm_smmu_ops

iommu_device_sysfs_add(&smmu->iommu, dev, NULL,
			     "smmu3.%pa", &ioaddr);

iommu_device_set_ops(&smmu->iommu, &arm_smmu_ops);
iommu_device_set_fwnode(&smmu->iommu, dev->fwnode);

iommu_device_register(&smmu->iommu);

bus_set_iommu(&pci_bus_type, &arm_smmu_ops);
bus_set_iommu(&platform_bus_type, &arm_smmu_ops);

三、IOMMU 和 DMA

IOMMU 的主要功能之一是防止器件在 DMA 模式下访问存储器时直接使用 PA。因此,生成了 IOVA。在分配内存时,首先在 I/O 地址空间中分配一个 IOVA,然后在 IOMMU 管理的页表中建立 IOVA 和 PA 之间的映射。当外围设备执行 DMA 时,只需要使用 IOVA。dma_allocdma_alloc

当调用 series 函数以分配内存时,将调用该函数。该函数分配 IOVA 和实际物理内存,用于建立 IOVA 与物理内存之间的映射关系,即定位设备对应的 STE,定位 CD(一般为 CD 0),在内存中定位对应的页表,并将 IOVA 与物理内存之间的映射写入页表。ARM SMMU 相关的页表操作在 io-pgtable.c 中完成。dma_allociommu_dma_allociommu_dma_allociommu_map

iommu_dma_alloc
      pages = __iommu_dma_alloc_pages(count, alloc_sizes >> PAGE_SHIFT, gfp);
      iova = iommu_dma_alloc_iova(domain, size, dev->coherent_dma_mask, dev);

      iommu_map_sg(domain, iova, sgt.sgl, sgt.orig_nents, prot

可以通过多种方式绕过 IOMMU。Linux 提供 iommu.passthrough 模式。您可以将 DMA 配置为不使用 IOMMU,而是使用软件输入输出转换后备缓冲区 (SWIOTLB) 技术来访问内存。此外,SMMUv3 驱动程序提供了绕过 SMMU 的参数。除此之外,您无需在 ACPI 或 DTS 中配置 SMMU 节点,以便在加载系统时不会探测相应的 SMMU。

四、总结

IOMMU 的主要功能是提供从 IOVA 到 PA 的映射,以便设备访问物理内存。这样,设备就不直接使用 PA 来访问内存,这是安全的。ARM SMMUv3 作为 IOMMU 的具体实现,为 IOMMU 提供了一个接口。IOMMU 相关操作包括设备执行的 DMA 操作,以及通过 VFIO 透传安全地暴露给用户模式的设备硬件功能,用于建立设备可识别的 IOVA 与实际 PA 之间的映射。

五、引用

  • https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit
  • https://developer.arm.com/documentation/ihi0070/latest
  • https://kernel.taobao.org/2020/06/ARM-SMMU-and-IOMMU/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值