18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * OF helpers for IOMMU
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/export.h>
98c2ecf20Sopenharmony_ci#include <linux/iommu.h>
108c2ecf20Sopenharmony_ci#include <linux/limits.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/msi.h>
138c2ecf20Sopenharmony_ci#include <linux/of.h>
148c2ecf20Sopenharmony_ci#include <linux/of_iommu.h>
158c2ecf20Sopenharmony_ci#include <linux/of_pci.h>
168c2ecf20Sopenharmony_ci#include <linux/pci.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci#include <linux/fsl/mc.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define NO_IOMMU	1
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci/**
238c2ecf20Sopenharmony_ci * of_get_dma_window - Parse *dma-window property and returns 0 if found.
248c2ecf20Sopenharmony_ci *
258c2ecf20Sopenharmony_ci * @dn: device node
268c2ecf20Sopenharmony_ci * @prefix: prefix for property name if any
278c2ecf20Sopenharmony_ci * @index: index to start to parse
288c2ecf20Sopenharmony_ci * @busno: Returns busno if supported. Otherwise pass NULL
298c2ecf20Sopenharmony_ci * @addr: Returns address that DMA starts
308c2ecf20Sopenharmony_ci * @size: Returns the range that DMA can handle
318c2ecf20Sopenharmony_ci *
328c2ecf20Sopenharmony_ci * This supports different formats flexibly. "prefix" can be
338c2ecf20Sopenharmony_ci * configured if any. "busno" and "index" are optionally
348c2ecf20Sopenharmony_ci * specified. Set 0(or NULL) if not used.
358c2ecf20Sopenharmony_ci */
368c2ecf20Sopenharmony_ciint of_get_dma_window(struct device_node *dn, const char *prefix, int index,
378c2ecf20Sopenharmony_ci		      unsigned long *busno, dma_addr_t *addr, size_t *size)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	const __be32 *dma_window, *end;
408c2ecf20Sopenharmony_ci	int bytes, cur_index = 0;
418c2ecf20Sopenharmony_ci	char propname[NAME_MAX], addrname[NAME_MAX], sizename[NAME_MAX];
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	if (!dn || !addr || !size)
448c2ecf20Sopenharmony_ci		return -EINVAL;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	if (!prefix)
478c2ecf20Sopenharmony_ci		prefix = "";
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	snprintf(propname, sizeof(propname), "%sdma-window", prefix);
508c2ecf20Sopenharmony_ci	snprintf(addrname, sizeof(addrname), "%s#dma-address-cells", prefix);
518c2ecf20Sopenharmony_ci	snprintf(sizename, sizeof(sizename), "%s#dma-size-cells", prefix);
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	dma_window = of_get_property(dn, propname, &bytes);
548c2ecf20Sopenharmony_ci	if (!dma_window)
558c2ecf20Sopenharmony_ci		return -ENODEV;
568c2ecf20Sopenharmony_ci	end = dma_window + bytes / sizeof(*dma_window);
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	while (dma_window < end) {
598c2ecf20Sopenharmony_ci		u32 cells;
608c2ecf20Sopenharmony_ci		const void *prop;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci		/* busno is one cell if supported */
638c2ecf20Sopenharmony_ci		if (busno)
648c2ecf20Sopenharmony_ci			*busno = be32_to_cpup(dma_window++);
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci		prop = of_get_property(dn, addrname, NULL);
678c2ecf20Sopenharmony_ci		if (!prop)
688c2ecf20Sopenharmony_ci			prop = of_get_property(dn, "#address-cells", NULL);
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci		cells = prop ? be32_to_cpup(prop) : of_n_addr_cells(dn);
718c2ecf20Sopenharmony_ci		if (!cells)
728c2ecf20Sopenharmony_ci			return -EINVAL;
738c2ecf20Sopenharmony_ci		*addr = of_read_number(dma_window, cells);
748c2ecf20Sopenharmony_ci		dma_window += cells;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci		prop = of_get_property(dn, sizename, NULL);
778c2ecf20Sopenharmony_ci		cells = prop ? be32_to_cpup(prop) : of_n_size_cells(dn);
788c2ecf20Sopenharmony_ci		if (!cells)
798c2ecf20Sopenharmony_ci			return -EINVAL;
808c2ecf20Sopenharmony_ci		*size = of_read_number(dma_window, cells);
818c2ecf20Sopenharmony_ci		dma_window += cells;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci		if (cur_index++ == index)
848c2ecf20Sopenharmony_ci			break;
858c2ecf20Sopenharmony_ci	}
868c2ecf20Sopenharmony_ci	return 0;
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(of_get_dma_window);
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic int of_iommu_xlate(struct device *dev,
918c2ecf20Sopenharmony_ci			  struct of_phandle_args *iommu_spec)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	const struct iommu_ops *ops;
948c2ecf20Sopenharmony_ci	struct fwnode_handle *fwnode = &iommu_spec->np->fwnode;
958c2ecf20Sopenharmony_ci	int ret;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	ops = iommu_ops_from_fwnode(fwnode);
988c2ecf20Sopenharmony_ci	if ((ops && !ops->of_xlate) ||
998c2ecf20Sopenharmony_ci	    !of_device_is_available(iommu_spec->np))
1008c2ecf20Sopenharmony_ci		return NO_IOMMU;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	ret = iommu_fwspec_init(dev, &iommu_spec->np->fwnode, ops);
1038c2ecf20Sopenharmony_ci	if (ret)
1048c2ecf20Sopenharmony_ci		return ret;
1058c2ecf20Sopenharmony_ci	/*
1068c2ecf20Sopenharmony_ci	 * The otherwise-empty fwspec handily serves to indicate the specific
1078c2ecf20Sopenharmony_ci	 * IOMMU device we're waiting for, which will be useful if we ever get
1088c2ecf20Sopenharmony_ci	 * a proper probe-ordering dependency mechanism in future.
1098c2ecf20Sopenharmony_ci	 */
1108c2ecf20Sopenharmony_ci	if (!ops)
1118c2ecf20Sopenharmony_ci		return driver_deferred_probe_check_state(dev);
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	if (!try_module_get(ops->owner))
1148c2ecf20Sopenharmony_ci		return -ENODEV;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	ret = ops->of_xlate(dev, iommu_spec);
1178c2ecf20Sopenharmony_ci	module_put(ops->owner);
1188c2ecf20Sopenharmony_ci	return ret;
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic int of_iommu_configure_dev_id(struct device_node *master_np,
1228c2ecf20Sopenharmony_ci				     struct device *dev,
1238c2ecf20Sopenharmony_ci				     const u32 *id)
1248c2ecf20Sopenharmony_ci{
1258c2ecf20Sopenharmony_ci	struct of_phandle_args iommu_spec = { .args_count = 1 };
1268c2ecf20Sopenharmony_ci	int err;
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	err = of_map_id(master_np, *id, "iommu-map",
1298c2ecf20Sopenharmony_ci			 "iommu-map-mask", &iommu_spec.np,
1308c2ecf20Sopenharmony_ci			 iommu_spec.args);
1318c2ecf20Sopenharmony_ci	if (err)
1328c2ecf20Sopenharmony_ci		return err == -ENODEV ? NO_IOMMU : err;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	err = of_iommu_xlate(dev, &iommu_spec);
1358c2ecf20Sopenharmony_ci	of_node_put(iommu_spec.np);
1368c2ecf20Sopenharmony_ci	return err;
1378c2ecf20Sopenharmony_ci}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_cistatic int of_iommu_configure_dev(struct device_node *master_np,
1408c2ecf20Sopenharmony_ci				  struct device *dev)
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	struct of_phandle_args iommu_spec;
1438c2ecf20Sopenharmony_ci	int err = NO_IOMMU, idx = 0;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	while (!of_parse_phandle_with_args(master_np, "iommus",
1468c2ecf20Sopenharmony_ci					   "#iommu-cells",
1478c2ecf20Sopenharmony_ci					   idx, &iommu_spec)) {
1488c2ecf20Sopenharmony_ci		err = of_iommu_xlate(dev, &iommu_spec);
1498c2ecf20Sopenharmony_ci		of_node_put(iommu_spec.np);
1508c2ecf20Sopenharmony_ci		idx++;
1518c2ecf20Sopenharmony_ci		if (err)
1528c2ecf20Sopenharmony_ci			break;
1538c2ecf20Sopenharmony_ci	}
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	return err;
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_cistruct of_pci_iommu_alias_info {
1598c2ecf20Sopenharmony_ci	struct device *dev;
1608c2ecf20Sopenharmony_ci	struct device_node *np;
1618c2ecf20Sopenharmony_ci};
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_cistatic int of_pci_iommu_init(struct pci_dev *pdev, u16 alias, void *data)
1648c2ecf20Sopenharmony_ci{
1658c2ecf20Sopenharmony_ci	struct of_pci_iommu_alias_info *info = data;
1668c2ecf20Sopenharmony_ci	u32 input_id = alias;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	return of_iommu_configure_dev_id(info->np, info->dev, &input_id);
1698c2ecf20Sopenharmony_ci}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_cistatic int of_iommu_configure_device(struct device_node *master_np,
1728c2ecf20Sopenharmony_ci				     struct device *dev, const u32 *id)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	return (id) ? of_iommu_configure_dev_id(master_np, dev, id) :
1758c2ecf20Sopenharmony_ci		      of_iommu_configure_dev(master_np, dev);
1768c2ecf20Sopenharmony_ci}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ciconst struct iommu_ops *of_iommu_configure(struct device *dev,
1798c2ecf20Sopenharmony_ci					   struct device_node *master_np,
1808c2ecf20Sopenharmony_ci					   const u32 *id)
1818c2ecf20Sopenharmony_ci{
1828c2ecf20Sopenharmony_ci	const struct iommu_ops *ops = NULL;
1838c2ecf20Sopenharmony_ci	struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
1848c2ecf20Sopenharmony_ci	int err = NO_IOMMU;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	if (!master_np)
1878c2ecf20Sopenharmony_ci		return NULL;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	if (fwspec) {
1908c2ecf20Sopenharmony_ci		if (fwspec->ops)
1918c2ecf20Sopenharmony_ci			return fwspec->ops;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci		/* In the deferred case, start again from scratch */
1948c2ecf20Sopenharmony_ci		iommu_fwspec_free(dev);
1958c2ecf20Sopenharmony_ci	}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	/*
1988c2ecf20Sopenharmony_ci	 * We don't currently walk up the tree looking for a parent IOMMU.
1998c2ecf20Sopenharmony_ci	 * See the `Notes:' section of
2008c2ecf20Sopenharmony_ci	 * Documentation/devicetree/bindings/iommu/iommu.txt
2018c2ecf20Sopenharmony_ci	 */
2028c2ecf20Sopenharmony_ci	if (dev_is_pci(dev)) {
2038c2ecf20Sopenharmony_ci		struct of_pci_iommu_alias_info info = {
2048c2ecf20Sopenharmony_ci			.dev = dev,
2058c2ecf20Sopenharmony_ci			.np = master_np,
2068c2ecf20Sopenharmony_ci		};
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci		pci_request_acs();
2098c2ecf20Sopenharmony_ci		err = pci_for_each_dma_alias(to_pci_dev(dev),
2108c2ecf20Sopenharmony_ci					     of_pci_iommu_init, &info);
2118c2ecf20Sopenharmony_ci	} else {
2128c2ecf20Sopenharmony_ci		err = of_iommu_configure_device(master_np, dev, id);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci		fwspec = dev_iommu_fwspec_get(dev);
2158c2ecf20Sopenharmony_ci		if (!err && fwspec)
2168c2ecf20Sopenharmony_ci			of_property_read_u32(master_np, "pasid-num-bits",
2178c2ecf20Sopenharmony_ci					     &fwspec->num_pasid_bits);
2188c2ecf20Sopenharmony_ci	}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	/*
2218c2ecf20Sopenharmony_ci	 * Two success conditions can be represented by non-negative err here:
2228c2ecf20Sopenharmony_ci	 * >0 : there is no IOMMU, or one was unavailable for non-fatal reasons
2238c2ecf20Sopenharmony_ci	 *  0 : we found an IOMMU, and dev->fwspec is initialised appropriately
2248c2ecf20Sopenharmony_ci	 * <0 : any actual error
2258c2ecf20Sopenharmony_ci	 */
2268c2ecf20Sopenharmony_ci	if (!err) {
2278c2ecf20Sopenharmony_ci		/* The fwspec pointer changed, read it again */
2288c2ecf20Sopenharmony_ci		fwspec = dev_iommu_fwspec_get(dev);
2298c2ecf20Sopenharmony_ci		ops    = fwspec->ops;
2308c2ecf20Sopenharmony_ci	}
2318c2ecf20Sopenharmony_ci	/*
2328c2ecf20Sopenharmony_ci	 * If we have reason to believe the IOMMU driver missed the initial
2338c2ecf20Sopenharmony_ci	 * probe for dev, replay it to get things in order.
2348c2ecf20Sopenharmony_ci	 */
2358c2ecf20Sopenharmony_ci	if (!err && dev->bus && !device_iommu_mapped(dev))
2368c2ecf20Sopenharmony_ci		err = iommu_probe_device(dev);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	/* Ignore all other errors apart from EPROBE_DEFER */
2398c2ecf20Sopenharmony_ci	if (err == -EPROBE_DEFER) {
2408c2ecf20Sopenharmony_ci		ops = ERR_PTR(err);
2418c2ecf20Sopenharmony_ci	} else if (err < 0) {
2428c2ecf20Sopenharmony_ci		dev_dbg(dev, "Adding to IOMMU failed: %d\n", err);
2438c2ecf20Sopenharmony_ci		ops = NULL;
2448c2ecf20Sopenharmony_ci	}
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	return ops;
2478c2ecf20Sopenharmony_ci}
248