diff mbox series

[08/30] iommu/of: Add iommu_of_get_single_iommu()

Message ID 8-v1-f82a05539a64+5042-iommu_fwspec_p2_jgg@nvidia.com
State Handled Elsewhere
Headers show
Series Make a new API for drivers to use to get their FW | expand

Commit Message

Jason Gunthorpe Nov. 30, 2023, 1:10 a.m. UTC
This function can be called by drivers in their probe function to return a
single iommu_device instance associated with the current probe.

All drivers need a way to get the iommu_device instance the FW says the
device should be using. Wrap the function with a macro that does the
container_of().

The driver indicates what instances it accepts by passing in its ops.

num_cells is provided to validate that the args are correctly sized.

This function is all that is required by drivers that only support a
single IOMMU instance and no IDs data. Driver's should follow a typical
pattern in their probe_device:

	iommu = iommu_of_get_single_iommu(pinf, &rk_iommu_ops, -1,
					  struct rk_iommu, iommu);
	if (IS_ERR(iommu)) return ERR_CAST(iommu);

	data = kzalloc(sizeof(*data), GFP_KERNEL);
	if (!data) return ERR_PTR(-ENOMEM);
	[..]
	dev_iommu_priv_set(dev, data);
        return &iommu->iommu;

Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
---
 drivers/acpi/scan.c          |  1 +
 drivers/iommu/iommu.c        | 52 ++++++++++++++++++++++
 drivers/iommu/of_iommu.c     | 59 +++++++++++++++++++++++++
 include/linux/iommu-driver.h | 85 ++++++++++++++++++++++++++++++++++++
 4 files changed, 197 insertions(+)
diff mbox series

Patch

diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 9c13df632aa5e0..de36299c3b75bf 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -1570,6 +1570,7 @@  static int acpi_iommu_configure_id(struct device *dev, const u32 *id_in)
 	const struct iommu_ops *ops;
 	struct iommu_probe_info pinf = {
 		.dev = dev,
+		.is_dma_configure = true,
 	};
 
 	/* Serialise to make dev->iommu stable under our potential fwspec */
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 45e6543748fd46..ca411ad14c1182 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -3015,6 +3015,58 @@  struct iommu_device *iommu_device_from_fwnode(struct fwnode_handle *fwnode)
 	return NULL;
 }
 
+/*
+ * Helper for FW interfaces to parse the fwnode into an iommu_driver. This
+ * caches past search results to avoid re-searching the linked list and computes
+ * if the FW is describing a single or multi-instance ID list.
+ */
+struct iommu_device *
+iommu_device_from_fwnode_pinf(struct iommu_probe_info *pinf,
+			      const struct iommu_ops *ops,
+			      struct fwnode_handle *fwnode)
+{
+	struct iommu_device *iommu = pinf->cached_iommu;
+
+	if (!pinf->num_ids)
+		pinf->cached_single_iommu = true;
+
+	if (!iommu || iommu->fwnode != fwnode) {
+		iommu = iommu_device_from_fwnode(fwnode);
+		if (!iommu)
+			return ERR_PTR(
+				driver_deferred_probe_check_state(pinf->dev));
+		pinf->cached_iommu = iommu;
+		if (pinf->num_ids)
+			pinf->cached_single_iommu = false;
+	}
+
+	/* NULL ops is used for the -EPROBE_DEFER check, match everything */
+	if (ops && iommu->ops != ops) {
+		if (!pinf->num_ids)
+			return ERR_PTR(-ENODEV);
+		dev_err(pinf->dev,
+			FW_BUG
+			"One device in the FW has iommu's with different Linux drivers, expecting %ps FW wants %ps.",
+			ops, iommu->ops);
+		return ERR_PTR(-EINVAL);
+	}
+	return iommu;
+}
+
+struct iommu_device *iommu_fw_finish_get_single(struct iommu_probe_info *pinf)
+{
+	if (WARN_ON(!pinf->num_ids || !pinf->cached_iommu))
+		return ERR_PTR(-EINVAL);
+	if (!pinf->cached_single_iommu) {
+		dev_err(pinf->dev,
+			FW_BUG
+			"The iommu driver %ps expects only one iommu instance, the FW has more.\n",
+			pinf->cached_iommu->ops);
+		return ERR_PTR(-EINVAL);
+	}
+	return pinf->cached_iommu;
+}
+
 int iommu_fwspec_init(struct device *dev, struct fwnode_handle *iommu_fwnode,
 		      const struct iommu_ops *ops)
 {
diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c
index 20266a8edd5c71..37af32a6bc84e5 100644
--- a/drivers/iommu/of_iommu.c
+++ b/drivers/iommu/of_iommu.c
@@ -138,6 +138,9 @@  int of_iommu_configure(struct device *dev, struct device_node *master_np,
 {
 	struct iommu_probe_info pinf = {
 		.dev = dev,
+		.of_master_np = master_np,
+		.of_map_id = id,
+		.is_dma_configure = true,
 	};
 	struct iommu_fwspec *fwspec;
 	int err;
@@ -277,3 +280,59 @@  void of_iommu_get_resv_regions(struct device *dev, struct list_head *list)
 #endif
 }
 EXPORT_SYMBOL(of_iommu_get_resv_regions);
+
+struct parse_info {
+	struct iommu_probe_info *pinf;
+	const struct iommu_ops *ops;
+	int num_cells;
+};
+
+static struct iommu_device *parse_iommu(struct parse_info *info,
+					struct of_phandle_args *iommu_spec)
+{
+	if (!of_device_is_available(iommu_spec->np))
+		return ERR_PTR(-ENODEV);
+
+	if (info->num_cells != -1 && iommu_spec->args_count != info->num_cells) {
+		dev_err(info->pinf->dev,
+			FW_BUG
+			"Driver %ps expects number of cells %u but DT has %u\n",
+			info->ops, info->num_cells, iommu_spec->args_count);
+		return ERR_PTR(-EINVAL);
+	}
+	return iommu_device_from_fwnode_pinf(info->pinf, info->ops,
+					     &iommu_spec->np->fwnode);
+}
+
+static int parse_single_iommu(struct of_phandle_args *iommu_spec, void *_info)
+{
+	struct parse_info *info = _info;
+	struct iommu_device *iommu;
+
+	iommu = parse_iommu(info, iommu_spec);
+	if (IS_ERR(iommu))
+		return PTR_ERR(iommu);
+	info->pinf->num_ids++;
+	return 0;
+}
+
+struct iommu_device *__iommu_of_get_single_iommu(struct iommu_probe_info *pinf,
+						 const struct iommu_ops *ops,
+						 int num_cells)
+{
+	struct parse_info info = { .pinf = pinf,
+				   .ops = ops,
+				   .num_cells = num_cells };
+	int err;
+
+	if (!pinf->is_dma_configure || !pinf->of_master_np)
+		return ERR_PTR(-ENODEV);
+
+	iommu_fw_clear_cache(pinf);
+	err = of_iommu_for_each_id(pinf->dev, pinf->of_master_np,
+				   pinf->of_map_id, parse_single_iommu, &info);
+	if (err)
+		return ERR_PTR(err);
+	return iommu_fw_finish_get_single(pinf);
+}
+EXPORT_SYMBOL_GPL(__iommu_of_get_single_iommu);
diff --git a/include/linux/iommu-driver.h b/include/linux/iommu-driver.h
index c572620d3069b4..597998a62b0dd6 100644
--- a/include/linux/iommu-driver.h
+++ b/include/linux/iommu-driver.h
@@ -13,25 +13,110 @@ 
 #endif
 
 #include <linux/types.h>
+#include <linux/err.h>
+#include <linux/slab.h>
 
+struct of_phandle_args;
 struct fwnode_handle;
+struct iommu_device;
+struct iommu_ops;
+
+/*
+ * FIXME this is sort of like container_of_safe() that was removed, do we want
+ * to put it in the common header?
+ */
+#define container_of_err(ptr, type, member)                       \
+	({                                                        \
+		void *__mptr = (void *)(ptr);                     \
+								  \
+		(offsetof(type, member) != 0 && IS_ERR(__mptr)) ? \
+			(type *)ERR_CAST(__mptr) :                \
+			container_of(ptr, type, member);          \
+	})
 
 struct iommu_probe_info {
 	struct device *dev;
 	struct list_head *deferred_group_list;
+	struct iommu_device *cached_iommu;
+	struct device_node *of_master_np;
+	const u32 *of_map_id;
+	unsigned int num_ids;
 	bool defer_setup : 1;
+	bool is_dma_configure : 1;
+	bool cached_single_iommu : 1;
 };
 
+static inline void iommu_fw_clear_cache(struct iommu_probe_info *pinf)
+{
+	pinf->num_ids = 0;
+	pinf->cached_single_iommu = true;
+}
+
 int iommu_probe_device_pinf(struct iommu_probe_info *pinf);
 struct iommu_device *iommu_device_from_fwnode(struct fwnode_handle *fwnode);
+struct iommu_device *
+iommu_device_from_fwnode_pinf(struct iommu_probe_info *pinf,
+			      const struct iommu_ops *ops,
+			      struct fwnode_handle *fwnode);
+struct iommu_device *iommu_fw_finish_get_single(struct iommu_probe_info *pinf);
 
 #if IS_ENABLED(CONFIG_OF_IOMMU)
 void of_iommu_get_resv_regions(struct device *dev, struct list_head *list);
+
+struct iommu_device *__iommu_of_get_single_iommu(struct iommu_probe_info *pinf,
+						 const struct iommu_ops *ops,
+						 int num_cells);
 #else
 static inline void of_iommu_get_resv_regions(struct device *dev,
 					     struct list_head *list)
 {
 }
+static inline
+struct iommu_device *__iommu_of_get_single_iommu(struct iommu_probe_info *pinf,
+						 const struct iommu_ops *ops,
+						 int num_cells)
+{
+	return ERR_PTR(-ENODEV);
+}
 #endif
 
+/**
+ * iommu_of_get_single_iommu - Return the driver's iommu instance
+ * @pinf: The iommu_probe_info
+ * @ops: The ops the iommu instance must have
+ * @num_cells: #iommu-cells value to enforce, -1 is no check
+ * @drv_struct: The driver struct containing the struct iommu_device
+ * @member: The name of the iommu_device member
+ *
+ * Parse the OF table describing the iommus and return a pointer to the driver's
+ * iommu_device struct that the OF table points to. Check that the OF table is
+ * well formed with a single iommu for all the entries and that the table refers
+ * to this iommu driver. Integrates a container_of() to simplify all users.
+ */
+#define iommu_of_get_single_iommu(pinf, ops, num_cells, drv_struct, member)  \
+	container_of_err(__iommu_of_get_single_iommu(pinf, ops, num_cells), \
+			  drv_struct, member)
+
+/**
+ * iommu_of_num_ids - Return the number of iommu associations the FW has
+ * @pinf: The iommu_probe_info
+ *
+ * For drivers using iommu_of_get_single_iommu() this will return the number
+ * of ids associated with the iommu instance. For other cases this will return
+ * the sum of all ids across all instances. Returns >= 1.
+ */
+static inline unsigned int iommu_of_num_ids(struct iommu_probe_info *pinf)
+{
+	return pinf->num_ids;
+}
+
+/*
+ * Used temporarily to indicate drivers that have moved to the new probe method.
+ */
+static inline int iommu_dummy_of_xlate(struct device *dev,
+				       struct of_phandle_args *args)
+{
+	return 0;
+}
+
 #endif