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 <j...@nvidia.com>
---
 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, &params,
+                                     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


Reply via email to