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