From: luochunsheng luochunsheng@huawei.com
hulk inclusion category: drivers/iommu bugzilla: 28210 CVE: NA ---------------------------------- arm64 cannot support virtualization when using the 3408iMR/3416iMRraid card. For solving the problem,we add two fuctions: 1.we prepare init bypass level2 entry for specific devices when smmu uses 2 level streamid entry. 2. we add smmu.bypassdev cmdline to allow SMMU bypass streams for some specific devices.
Signed-off-by: luochunshengluochunsheng@huawei.com Signed-off-by: Yang Yingliang yangyingliang@huawei.com --- drivers/iommu/Kconfig | 6 +++ drivers/iommu/arm-smmu-v3.c | 123 ++++++++++++++++++++++++++++++++++++++++++++ drivers/iommu/iommu.c | 15 +++++- include/linux/iommu.h | 6 +++ 4 files changed, 149 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index a677013..4f4132d 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -443,4 +443,10 @@ config QCOM_IOMMU help Support for IOMMU on certain Qualcomm SoCs.
+config SMMU_BYPASS_DEV + bool "SMMU bypass streams for some specific devices" + help + according smmu.bypassdev cmdline, SMMU performs attribute + transformation only,with no address translation. + endif # IOMMU_SUPPORT diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c index f6acfd1..e8f7bd3 100644 --- a/drivers/iommu/arm-smmu-v3.c +++ b/drivers/iommu/arm-smmu-v3.c @@ -369,6 +369,42 @@ MODULE_PARM_DESC(disable_bypass, "Disable bypass streams such that incoming transactions from devices that are not attached to an iommu domain will report an abort back to the device and will not be allowed to pass through the SMMU.");
+#ifdef CONFIG_SMMU_BYPASS_DEV +struct smmu_bypass_device { + unsigned short vendor; + unsigned short device; +}; +#define MAX_CMDLINE_SMMU_BYPASS_DEV 16 + +static struct smmu_bypass_device smmu_bypass_devices[MAX_CMDLINE_SMMU_BYPASS_DEV]; +static int smmu_bypass_devices_num; + +static int __init arm_smmu_bypass_dev_setup(char *str) +{ + unsigned short vendor; + unsigned short device; + int ret; + + if (!str) + return -EINVAL; + + ret = sscanf(str, "%hx:%hx", &vendor, &device); + if (ret != 2) + return -EINVAL; + + if (smmu_bypass_devices_num >= MAX_CMDLINE_SMMU_BYPASS_DEV) + return -ERANGE; + + smmu_bypass_devices[smmu_bypass_devices_num].vendor = vendor; + smmu_bypass_devices[smmu_bypass_devices_num].device = device; + smmu_bypass_devices_num++; + + return 0; +} + +__setup("smmu.bypassdev=", arm_smmu_bypass_dev_setup); +#endif + enum pri_resp { PRI_RESP_DENY = 0, PRI_RESP_FAIL = 1, @@ -2634,6 +2670,30 @@ static void arm_smmu_put_resv_regions(struct device *dev, kfree(entry); }
+#ifdef CONFIG_SMMU_BYPASS_DEV +static int arm_smmu_device_domain_type(struct device *dev, unsigned int *type) +{ + int i; + struct pci_dev *pdev; + + if (!dev_is_pci(dev)) + return -ERANGE; + + pdev = to_pci_dev(dev); + for (i = 0; i < smmu_bypass_devices_num; i++) { + if ((smmu_bypass_devices[i].vendor == pdev->vendor) + && (smmu_bypass_devices[i].device == pdev->device)) { + dev_info(dev, "device 0x%hx:0x%hx uses identity mapping.", + pdev->vendor, pdev->device); + *type = IOMMU_DOMAIN_IDENTITY; + return 0; + } + } + + return -ERANGE; +} +#endif + static struct iommu_ops arm_smmu_ops = { .capable = arm_smmu_capable, .domain_alloc = arm_smmu_domain_alloc, @@ -2660,6 +2720,9 @@ static void arm_smmu_put_resv_regions(struct device *dev, .of_xlate = arm_smmu_of_xlate, .get_resv_regions = arm_smmu_get_resv_regions, .put_resv_regions = arm_smmu_put_resv_regions, +#ifdef CONFIG_SMMU_BYPASS_DEV + .device_domain_type = arm_smmu_device_domain_type, +#endif .pgsize_bitmap = -1UL, /* Restricted during device attach */ };
@@ -2741,12 +2804,60 @@ static int arm_smmu_init_l1_strtab(struct arm_smmu_device *smmu) return 0; }
+#ifdef CONFIG_SMMU_BYPASS_DEV +static void arm_smmu_install_bypass_ste_for_dev(struct arm_smmu_device *smmu, + u32 sid) +{ + u64 val; + __le64 *step = arm_smmu_get_step_for_sid(smmu, sid); + + if (!step) + return; + + val = STRTAB_STE_0_V; + val |= FIELD_PREP(STRTAB_STE_0_CFG, STRTAB_STE_0_CFG_BYPASS); + step[0] = cpu_to_le64(val); + step[1] = cpu_to_le64(FIELD_PREP(STRTAB_STE_1_SHCFG, + STRTAB_STE_1_SHCFG_INCOMING)); + step[2] = 0; + +} + +static int arm_smmu_prepare_init_l2_strtab(struct device *dev, void *data) +{ + u32 sid; + int ret; + unsigned int type; + struct pci_dev *pdev; + struct arm_smmu_device *smmu = (struct arm_smmu_device *)data; + + if (arm_smmu_device_domain_type(dev, &type)) + return 0; + + pdev = to_pci_dev(dev); + sid = PCI_DEVID(pdev->bus->number, pdev->devfn); + if (!arm_smmu_sid_in_range(smmu, sid)) + return -ERANGE; + + ret = arm_smmu_init_l2_strtab(smmu, sid); + if (ret) + return ret; + + arm_smmu_install_bypass_ste_for_dev(smmu, sid); + + return 0; +} +#endif + 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; +#ifdef CONFIG_SMMU_BYPASS_DEV + int ret; +#endif
/* Calculate the L1 size, capped to the SIDSIZE. */ size = STRTAB_L1_SZ_SHIFT - (ilog2(STRTAB_L1_DESC_DWORDS) + 3); @@ -2776,7 +2887,19 @@ static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu) reg |= FIELD_PREP(STRTAB_BASE_CFG_SPLIT, STRTAB_SPLIT); cfg->strtab_base_cfg = reg;
+#ifdef CONFIG_SMMU_BYPASS_DEV + ret = arm_smmu_init_l1_strtab(smmu); + if (ret) + return ret; + + if (smmu_bypass_devices_num) { + ret = bus_for_each_dev(&pci_bus_type, NULL, (void *)smmu, + arm_smmu_prepare_init_l2_strtab); + } + return ret; +#else return arm_smmu_init_l1_strtab(smmu); +#endif }
static int arm_smmu_init_strtab_linear(struct arm_smmu_device *smmu) diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index b7b0105..361ceee 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -1269,7 +1269,9 @@ struct iommu_group *iommu_group_get_for_dev(struct device *dev) const struct iommu_ops *ops = dev->bus->iommu_ops; struct iommu_group *group; int ret; - +#ifdef CONFIG_SMMU_BYPASS_DEV + unsigned int type = iommu_def_domain_type; +#endif group = iommu_group_get(dev); if (group) return group; @@ -1291,8 +1293,19 @@ struct iommu_group *iommu_group_get_for_dev(struct device *dev) if (!group->default_domain) { struct iommu_domain *dom;
+#ifdef CONFIG_SMMU_BYPASS_DEV + /* direct allocate required default domain type for some specific devices. */ + if (ops->device_domain_type != NULL) { + if (ops->device_domain_type(dev, &type)) + type = iommu_def_domain_type; + } + + dom = __iommu_domain_alloc(dev->bus, type); + if (!dom && type != IOMMU_DOMAIN_DMA) { +#else dom = __iommu_domain_alloc(dev->bus, iommu_def_domain_type); if (!dom && iommu_def_domain_type != IOMMU_DOMAIN_DMA) { +#endif dev_warn(dev, "failed to allocate default IOMMU domain of type %u; falling back to IOMMU_DOMAIN_DMA", iommu_def_domain_type); diff --git a/include/linux/iommu.h b/include/linux/iommu.h index 7edde72..e6ffd42 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -362,6 +362,12 @@ struct iommu_ops { int (*page_response)(struct device *dev, struct page_response_msg *msg);
unsigned long pgsize_bitmap; + +#ifdef CONFIG_SMMU_BYPASS_DEV +#ifndef __GENKSYMS__ + int (*device_domain_type)(struct device *dev, unsigned int *type); +#endif +#endif };
/**