This API is basically the same as iommu_of_get_single_iommu(), except that it will try to parse the ACPI IORT table if it is available. The ACPI IORT table can return a flags value to indicate IOMMU_FWSPEC_PCI_RC_ATS, return this through an output flags pointer. Signed-off-by: Jason Gunthorpe <jgg@xxxxxxxxxx> --- drivers/acpi/arm64/iort.c | 3 +- drivers/acpi/scan.c | 1 + drivers/iommu/Makefile | 1 + drivers/iommu/iommu.c | 3 ++ drivers/iommu/iort_iommu.c | 98 ++++++++++++++++++++++++++++++++++++ include/linux/acpi_iort.h | 1 + include/linux/iommu-driver.h | 41 +++++++++++++++ 7 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 drivers/iommu/iort_iommu.c diff --git a/drivers/acpi/arm64/iort.c b/drivers/acpi/arm64/iort.c index 798c0b344f4be8..6b2d50cc9ac180 100644 --- a/drivers/acpi/arm64/iort.c +++ b/drivers/acpi/arm64/iort.c @@ -79,8 +79,7 @@ static inline int iort_set_fwnode(struct acpi_iort_node *iort_node, * * Returns: fwnode_handle pointer on success, NULL on failure */ -static inline struct fwnode_handle *iort_get_fwnode( - struct acpi_iort_node *node) +struct fwnode_handle *iort_get_fwnode(struct acpi_iort_node *node) { struct iort_fwnode *curr; struct fwnode_handle *fwnode = NULL; diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 9ec01196573b6e..eb7406cdc9a464 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1571,6 +1571,7 @@ static int acpi_iommu_configure_id(struct device *dev, const u32 *id_in) struct iommu_probe_info pinf = { .dev = dev, .is_dma_configure = true, + .acpi_map_id = id_in, .is_acpi = true, }; diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index 9c35b106cecb2e..ebf6c151a97746 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_IOMMU_IO_PGTABLE_LPAE) += io-pgtable-arm.o obj-$(CONFIG_IOMMU_IO_PGTABLE_DART) += io-pgtable-dart.o obj-$(CONFIG_IOMMU_IOVA) += iova.o obj-$(CONFIG_OF_IOMMU) += of_iommu.o +obj-$(CONFIG_ACPI_IORT) += iort_iommu.o obj-$(CONFIG_ACPI_VIOT) += viot_iommu.o obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o obj-$(CONFIG_IPMMU_VMSA) += ipmmu-vmsa.o diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index caf14a53ed1952..7468a64778931b 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -3030,6 +3030,9 @@ iommu_device_from_fwnode_pinf(struct iommu_probe_info *pinf, if (!pinf->num_ids) pinf->cached_single_iommu = true; + if (pinf->is_acpi) + pinf->acpi_fwnode = fwnode; + if (!iommu || iommu->fwnode != fwnode) { iommu = iommu_device_from_fwnode(fwnode); if (!iommu) diff --git a/drivers/iommu/iort_iommu.c b/drivers/iommu/iort_iommu.c new file mode 100644 index 00000000000000..9a997b0fd5d5f1 --- /dev/null +++ b/drivers/iommu/iort_iommu.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES + */ +#include <linux/acpi_iort.h> +#include <acpi/actbl2.h> + +#include <linux/iommu.h> +#include <linux/iommu-driver.h> + +struct parse_info { + struct iommu_probe_info *pinf; + const struct iommu_ops *ops; + u32 *ids; +}; + +static bool iort_iommu_driver_enabled(struct iommu_probe_info *pinf, u8 type) +{ + switch (type) { + case ACPI_IORT_NODE_SMMU_V3: + return IS_ENABLED(CONFIG_ARM_SMMU_V3); + case ACPI_IORT_NODE_SMMU: + return IS_ENABLED(CONFIG_ARM_SMMU); + default: + dev_warn(pinf->dev, + FW_WARN + "IORT node type %u does not describe an SMMU\n", + type); + return false; + } +} + +static int parse_single_iommu(struct acpi_iort_node *iort_iommu, u32 streamid, + void *_info) +{ + struct parse_info *info = _info; + struct iommu_probe_info *pinf = info->pinf; + struct fwnode_handle *fwnode; + struct iommu_device *iommu; + + fwnode = iort_get_fwnode(iort_iommu); + if (!fwnode) + return -ENODEV; + + iommu = iommu_device_from_fwnode_pinf(pinf, info->ops, fwnode); + if (IS_ERR(iommu)) { + if (iommu == ERR_PTR(-EPROBE_DEFER) && + !iort_iommu_driver_enabled(pinf, iort_iommu->type)) + return -ENODEV; + return PTR_ERR(iommu); + } + iommu_fw_cache_id(pinf, streamid); + return 0; +} + +static int parse_read_ids(struct acpi_iort_node *iommu, u32 streamid, + void *_info) +{ + struct parse_info *info = _info; + + *info->ids = streamid; + (*info->ids)++; + return 0; +} + +static int iort_get_u32_ids(struct iommu_probe_info *pinf, u32 *ids) +{ + struct parse_info info = { .pinf = pinf, .ids = ids }; + struct iort_params params; + + return iort_iommu_for_each_id(pinf->dev, pinf->acpi_map_id, ¶ms, + parse_read_ids, &info); +} + +struct iommu_device * +__iommu_iort_get_single_iommu(struct iommu_probe_info *pinf, + const struct iommu_ops *ops, + struct iort_params *params) +{ + struct parse_info info = { .pinf = pinf, .ops = ops }; + struct iort_params unused_params; + int err; + + if (!pinf->is_dma_configure || !pinf->is_acpi) + return ERR_PTR(-ENODEV); + + if (!params) + params = &unused_params; + + iommu_fw_clear_cache(pinf); + err = iort_iommu_for_each_id(pinf->dev, pinf->acpi_map_id, params, + parse_single_iommu, &info); + if (err) + return ERR_PTR(err); + pinf->get_u32_ids = iort_get_u32_ids; + return iommu_fw_finish_get_single(pinf); +} +EXPORT_SYMBOL(__iommu_iort_get_single_iommu); diff --git a/include/linux/acpi_iort.h b/include/linux/acpi_iort.h index 13f0cefb930693..bacba2a76c3acb 100644 --- a/include/linux/acpi_iort.h +++ b/include/linux/acpi_iort.h @@ -40,6 +40,7 @@ typedef int (*iort_for_each_fn)(struct acpi_iort_node *iommu, u32 streamid, int iort_iommu_for_each_id(struct device *dev, const u32 *id_in, struct iort_params *params, iort_for_each_fn fn, void *info); +struct fwnode_handle *iort_get_fwnode(struct acpi_iort_node *node); #ifdef CONFIG_ACPI_IORT u32 iort_msi_map_id(struct device *dev, u32 id); diff --git a/include/linux/iommu-driver.h b/include/linux/iommu-driver.h index ce0ba1f35bb5dc..c4e133cdef2c78 100644 --- a/include/linux/iommu-driver.h +++ b/include/linux/iommu-driver.h @@ -19,6 +19,7 @@ struct of_phandle_args; struct fwnode_handle; struct iommu_device; +struct iort_params; struct iommu_ops; /* @@ -39,7 +40,9 @@ struct iommu_probe_info { struct list_head *deferred_group_list; struct iommu_device *cached_iommu; struct device_node *of_master_np; + struct fwnode_handle *acpi_fwnode; const u32 *of_map_id; + const u32 *acpi_map_id; int (*get_u32_ids)(struct iommu_probe_info *pinf, u32 *ids); unsigned int num_ids; u32 cached_ids[8]; @@ -63,6 +66,21 @@ iommu_device_from_fwnode_pinf(struct iommu_probe_info *pinf, struct fwnode_handle *fwnode); struct iommu_device *iommu_fw_finish_get_single(struct iommu_probe_info *pinf); +/** + * iommu_fw_acpi_fwnode - Get an ACPI fwnode_handle + * @pinf: The iommu_probe_info + * + * Return the ACPI version of the fwnode describing the iommu data that is + * associated with the device being probed. + */ +static inline struct fwnode_handle * +iommu_fw_acpi_fwnode(struct iommu_probe_info *pinf) +{ + if (!pinf->is_acpi) + return NULL; + return pinf->acpi_fwnode; +} + typedef int (*iommu_of_xlate_fn)(struct iommu_device *iommu, struct of_phandle_args *args, void *priv); void iommu_of_allow_bus_probe(struct iommu_probe_info *pinf); @@ -213,4 +231,27 @@ __iommu_viot_get_single_iommu(struct iommu_probe_info *pinf, __iommu_of_get_single_iommu(pinf, ops, -1)), \ drv_struct, member) +#if IS_ENABLED(CONFIG_ACPI_IORT) +struct iommu_device * +__iommu_iort_get_single_iommu(struct iommu_probe_info *pinf, + const struct iommu_ops *ops, + struct iort_params *params); +#else +static inline struct iommu_device * +__iommu_iort_get_single_iommu(struct iommu_probe_info *pinf, + const struct iommu_ops *ops, + struct iort_params *params) +{ + return ERR_PTR(-ENODEV); +} +#endif +#define iommu_iort_get_single_iommu(pinf, ops, params, drv_struct, member) \ + ({ \ + memset(params, 0, sizeof(*(params))); \ + container_of_err(__iommu_first(__iommu_iort_get_single_iommu( \ + pinf, ops, params), \ + __iommu_of_get_single_iommu( \ + pinf, ops, -1)), \ + drv_struct, member) \ + }) #endif -- 2.42.0