ARM技术william hill官网
直播中

李娜

7年用户 1651经验值
私信 关注
[经验]

多个设备是否可以共用一个SMMU StreamID?

近年基于arm应用处理器的移动,infrastructure SoC平台都包含SMMUv3 IP(MMU-600,MMU-700),用于DMA mapping,VFIO,Shared Virtual Address(SVA)等场景。

v2-2fd7afc1e1898053f4671cd423bba909_hd.jpg

在这些使用场景中,SMMUv3是这样使用的呢?SMMUv3的硬件设计方面比较灵活,但是软件使用方面是有些限制的。本文不是介绍SMMUv3构架或IP,而是站在应用SMMU的角度的十问十答。

本文要求读者对SMMU构架,SMMU driver,VFIO,SVA有一定的理解。

1.jpg

2.jpg

Q1. 多个设备是否可以共用一个SMMU StreamID?

图片.png

0861980accb343328835f4e359cc63b0.png

SMMU硬件本身是可以支持多个设备共用一个StreamID的,从而使用同一地址映射关系。

对于PCI(非PCIe)设备,由于一条PCI bus上的PCI设备共享一个requester ID(BDF),而它被用作StreamID,因而这些PCI设备必须共用一个StreamID。

但是Linux SMMU driver的设计不允许多个设备共用一个StreamID (在同一条PCI上的设备除外),SMMU driver会在probe device时,通过device tree或是ACPI table获取该设备的StreamID,

bus_iommu_probe->probe_iommu_group->__iommu_probe_device->arm_smmu_probe_device->arm_smmu_insert_master

arm_smmu_insert_master将这些StreamID插入这个smmu的全局红黑树中记录。代码可以看出,如果要插入的Stream ID已存在红黑树中,就会出现错误,driver报告

stream n already in tree

利用arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid)来查找这个红黑树得到一个StreamID对应的master,处理SMMU event时可以得到这个event对应的device.

 

因此对应同一SMMU上的platform device和PCIe device,smmu driver不允许它们共用一个StreamID。这点SoC构架师需要特别注意。

Q2. 多个设备(多个StreamID)是否可以使用一个iommu group?

smmu driver为每个platform device创建一个单独的iommu group。

对于传统PCI/PCI-X设备,因为它们没有单独的PCIe Requester ID, 因此一条传统PCI总线或者PCI-X总线上的PCI设备都划分到同一个device group。对于PCIe设备,在设备通往PCIe树根节点的路径上,所有的PCIe downstream port和multi-function device都需要通过ACS特性,将peer-to-peer转发的功能关闭。若某个PCIe downstream port或multi-function未通过ACS特性关闭peer-to-peer特性,则下面的所有设备都必须归到同一个iommu group,否则该PCIe设备就可以独立成一个iommu group。

在DMA mapping使用场景里,为每个iommu group分配一个default iommu domain。

VFIO场景下,可以通过vifo_iommu_attach_group->iommu_attach_group来更改为一个新创建的iommu domain。

Q3. SMMU上的device使用的地址映射是否需要和CPU使用的地址映射一样?如何给使用SMMU的device分配ASID和VMID?

SMMU的页表格式和使用方式与CPU的页表非常类似,这是为了可以让device的IOMMU可以共享CPU的页表,因而SMMU的页表中也可以设置ASID和VMID。但是在实际软件使用中,DMA mapping和VFIO的使用场景,设备的IOMMU的页表并不与CPU共享,设备的IOVA-PA的映射是由smmu driver独立建立,与CPU使用的VA-PA映射本不相同。为设备地址转换设置的ASID和VMID主要是为了在IOMMU的TLB里区分不同设备对应的TLB。

一个SMMU上ASID分配是在attach device时,在Xaary里找到一个没使用的ASID,而VMID是通过一个bitmap找到一个没有使用的VMID,与CPU使用ASID和VMID空间无关。

但在shared virtual address (SVA)使用场景中,设备的页表会被设置为共享一个CPU进程的VA-PA映射,因而共享该CPU进程的ASID和VMID。

Q4. SMMU如何处理TLBI Broadcast?

在硬件设计上,一般会SMMU和CPU放在同一个inner shareable domain,比如MMU-700(基于SMMUv3的实现)的TCU总线接口时ACE-Lite+DVM,它通过coherent interconnct与CPU连接。这样的设计让SMP系统更加高效:一个CPU上执行的TLBI操作,可以被硬件broadcast到其他CPU(TLBI broadcast),也可以broadcast到SMMU,让这些共享页表的master 更高效地维护它们TLB里面的内容一致性。

但如上所述,DMA mapping和VFIO使用场景中,设备的ASID和VMID与CPU并不共享。设备的SMMU TLB中具有同样ASID,VMID的TLB项不应该受到CPU发过来的TLBI broadcast的影响。实际上,SMMU driver会设置CD.ASET,

[47] ASET
ASID Set.
Selects type for ASID, between sets shared and non-shared with PE ASIDs. This flag affects broadcast TLB invalidation participation and the scope within which Global TLB entries are matched:

0b0: ASID in shared set: This ASID, and address space described by TTB0 and TTB1, are shared with that of a process on the PE. All matching broadcast invalidation messages will invalidate TLB entries created from this context (where supported and globally enabled), keeping SMMU and PE address spaces synchronized.

0b1: ASID in non-shared set: TLB entries created from this context are not expected to be invalidated by some broadcast invalidations

因而从CPU发来的带ASID值的TLBI广播(DVM)不会invalidate SMMU TLB中带同样ASID的TLB项。 

在arm_smmu_write_ctx_desc中,

val = cd->tcr |
#ifdef __BIG_ENDIAN
            CTXDESC_CD_0_ENDI |
#endif
            CTXDESC_CD_0_R | CTXDESC_CD_0_A |
            (cd->mm ? 0 : CTXDESC_CD_0_ASET) |
            CTXDESC_CD_0_AA64 |
            FIELD_PREP(CTXDESC_CD_0_ASID, cd->asid) |
            CTXDESC_CD_0_V;

由代码可知,只有在SVA场景下(cd->mm不为空),才会清除CD.ASET, 共享CPU的ASID,从CPU发来的带ASID值的TLBI广播(DVM)会invalidate SMMU TLB中带同样ASID的TLB项。

这点对验证DVM是否可以在系统中是否正常工作需要注意。

Q5. SMMU上device使用stage 1还是stage 2页表?

DMA mapping场景下,使用IOMMU_DOMAIN_DMA domain type,它对应到SMMU的smmu_domain->stage。

if (domain->type == IOMMU_DOMAIN_IDENTITY) {
        smmu_domain->stage = ARM_SMMU_DOMAIN_BYPASS;
        return 0;
    }

    /* Restrict the stage to what we can actually support */
    if (!(smmu->features & ARM_SMMU_FEAT_TRANS_S1))
        smmu_domain->stage = ARM_SMMU_DOMAIN_S2;
    if (!(smmu->features & ARM_SMMU_FEAT_TRANS_S2))
        smmu_domain->stage = ARM_SMMU_DOMAIN_S1;
```​

if (smmu_domain) {

switch (smmu_domain->stage) {

case ARM_SMMU_DOMAIN_S1:

s1_cfg = &smmu_domain->s1_cfg;

break;

case ARM_SMMU_DOMAIN_S2:

case ARM_SMMU_DOMAIN_NESTED:

s2_cfg = &smmu_domain->s2_cfg;

break;

default:

break;

}

在SMMU硬件实现同时支持stage 1和stage 2的情况下,将使用stage 1。

在VFIO使用场景中,如果设置ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU)则使用stage1;如果设置ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_NESTING_IOMMU)则使用stage 2, 这样可以让出stage 1给虚拟机的软件用。

##### Q6. 一个设备有多个Stream ID会如何呢?

如果一个device在device tree, ACPI table中指定了多个StreamID, 那么arm SMMU driver为这些Stream ID使用一样的STE和CD值,即使用同样的translation。

[https://elixir.bootlin.com/li...](https://aijishu.com/link?target=https%3A%2F%2Felixir.bootlin.com%2Flinux%2Fv5.15.11%2Fsource%2Fdrivers%2Fiommu%2Farm%2Farm-smmu-v3%2Farm-smmu-v3.c%23L2255)

static void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master)

{

int i, j;

struct arm_smmu_device *smmu = master->smmu;

for (i = 0; i < master->num_streams; ++i) {
    u32 sid = master->streams[i].id;
    __le64 *step = arm_smmu_get_step_for_sid(smmu, sid);

    /* Bridged PCI devices may end up with duplicated IDs */
    for (j = 0; j < i; j++)
        if (master->streams[j].id == sid)
            break;
    if (j < i)
        continue;

    arm_smmu_write_strtab_ent(master, sid, step);
}

}

##### Q7. 如何告知smmu driver设备使用的StreamID?

以device tree为例,smmu node信息如下

smmu: iommu@2b400000 {

compatible = "arm,smmu-v3";

reg = <0x0 0x2b400000 0x0 0x100000>;

interrupts = <GIC_SPI 74 IRQ_TYPE_EDGE_RISING>,

<GIC_SPI 79 IRQ_TYPE_EDGE_RISING>,

<GIC_SPI 75 IRQ_TYPE_EDGE_RISING>,

<GIC_SPI 77 IRQ_TYPE_EDGE_RISING>;

interrupt-names = "eventq", "gerror", "priq", "cmdq-sync";

dma-coherent;

#iommu-cells = <1>;

msi-parent = <&its 0x10000>;

};

Platform device 的StreamID设置方式为

master@1 {

/* device has Stream ID 42 in the IOMMU */

iommus = <&{/smmu} 42>;

};

master@2 {
    /* device has Stream IDs 23 and 24 in the IOMMU */
    iommus = <&{/smmu} 23>, <&{/smmu} 24>;
};
of_iommu_configure->of_iommu_configure_device->of_iommu_configure_dev从iommus,#iommu-cells中获得stream ID。

[https://elixir.bootlin.com/li...](https://aijishu.com/link?target=https%3A%2F%2Felixir.bootlin.com%2Flinux%2Fv5.15.11%2Fsource%2Fdrivers%2Fiommu%2Fof_iommu.c%23L77)

while (!of_parse_phandle_with_args(master_np, "iommus",

"#iommu-cells",

idx, &iommu_spec)) {

err = of_iommu_xlate(dev, &iommu_spec);

对于PCIe设备,硬件设计上会将PCIe的requester ID(Bus:Device:Fucntion, BDF)用作产生StreamID。在PCIe Root Complex的device tree node上的iommu-map,iommu-map-mask指定硬件设计PCIe requester ID 与StreamID的对应关系:
iommu-map用于指定requester ID与StreamID的映射关系:

iommu-map = <rid-base,iommu,iommu-base,length>

rid-base是PCIe Requester ID base, iommu是指定用哪个IOMMU,iommu-base是对应的Stream ID base,Length是Requester ID/StreamID 的个数。对应关系是:

StreamID = RequesterID - rid-base + iommu-base

iommu-map-mask用于mask那些RequesterID bit:

iommu-map-mask=

iommu-map-mask是一个可选的属性,如有iommu-map-mask,对应关系为:

StreamID = (RequesterID &mask) - rid-base + iommu-base

但如上所述,在arm SMMUv3 driver上,如果使用iommu-map-mask可能导致多个PCIe Device Requester ID 得到同一个StreamID,会出现错误,SMMU driver报告stream ‘num’ already in tree

一个例子:

PCIe Root Complex:

pci: pci@40000000 {

#address-cells = <0x3>;

#size-cells = <0x2>;

#interrupt-cells = <0x1>;

compatible = "pci-host-ecam-generic";

device_type = "pci";

bus-range = <0x0 0x1>;

reg = <0x0 0x40000000 0x0 0x10000000>;

ranges = <0x2000000 0x0 0x50000000 0x0 0x50000000 0x0 0x10000000>;

interrupt-map-mask = <0x0 0x0 0x0 0x7>;
    msi-map = <0x0 &its 0x0 0x10000>;
    iommu-map = <0x0 &smmu 0x0 0x10000>;

    dma-coherent;
};
这个过程是pci_for_each_dma_alias( .., of_pci_iommu_init, …) ->of_pci_iommu_init ->of_iommu_configure_dev_id 从iommu-map, iommu-map-mask中得到BDF对应的streamid

err = of_map_id(master_np, *id, "iommu-map",

"iommu-map-mask", &iommu_spec.np,

iommu_spec.args);

if (err)

return err == -ENODEV ? NO_IOMMU : err;

err = of_iommu_xlate(dev, &iommu_spec);
##### Q8. 如何处理软件上没有设置iommu但硬件上连接smmu的情况?设置了iommu的设备使用什么default iommu domain?

系统硬件设计上,为了系统安全或是可能的软件使用场景,除CPU之外的其他很多master都会接在SMMU上。但是如果没有在device tree中指定”iommus”或是“iommu-map”,那么arm SMMU driver会如何设置SMMU处理来自这个device的传输地址转换呢?是bypass SMMU还是被SMMU abort这个传输呢?

它取决于“arm-smmu.disable_bypass” kernel boot cmdline和smmu kernel module的“disable_bypass” 参数,如果disable_bypass被设置,那么smmu abort这些传输,否则bypass这些传输。

对于那些在device tree中指定”iommus”或是“iommu-map”的device,default iommu domain到底是IOMMU_DOMAIN_DMA(需要经过stage1转换),IOMMU_DOMAIN_IDENTITY(bypass SMMU)取决于CONFIG_IOMMU_DEFAULT_PASSTHROUGH和“iommu.passthrough” kernel boot cmdline。“iommu.passthrough=1”让device bypass SMMU地址转换,也就是identity-mapped,让Device可以访问DMA-mapped 页。iommu.passthrough可以override CONFIG_IOMMU_DEFAULT_PASSTHROUGH。

##### Q9. SMMU使用什么page size和页表格式?

为了方便页表管理和可能的与CPU共享页表的情形,SMMU driver尽量采用与Linux kernel一样的page size。当SMMU硬件不支持CPU使用的page size时,

static void arm_lpae_restrict_pgsizes(struct io_pgtable_cfg *cfg)

{

unsigned long granule, page_sizes;

unsigned int max_addr_bits = 48;

/*
 * We need to restrict the supported page sizes to match the
 * translation regime for a particular granule. Aim to match
 * the CPU page size if possible, otherwise prefer smaller sizes.
 * While we're at it, restrict the block sizes to match the
 * chosen granule.
 */
if (cfg->pgsize_bitmap & PAGE_SIZE)
    granule = PAGE_SIZE;
##### Q10. 使用2 level STE/CD table还是linear table?

SMMU driver会检查SMMU硬件是否支持2 level的STE和CD。如不支持,则只能使用linear STE和CD table。如支持,则STE table使用2 level table, split bit被设置为,

#define STRTAB_SPLIT 8

但如果SMMU硬件支持的StreamID bit <=8, 那么使用linear table。
CD table 使用2 level table, split bit被设置为,

#define CTXDESC_SPLIT 10

但如果SMMU硬件支持的SubstreamID bit <=10, 那么使用linear table。





原作者:修志龙_ZenonXiu

更多回帖

发帖
×
20
完善资料,
赚取积分