mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-11 21:14:07 +08:00
a61127c213
Based on 1 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms and conditions of the gnu general public license version 2 as published by the free software foundation this program is distributed in the hope it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details you should have received a copy of the gnu general public license along with this program if not write to the free software foundation inc 51 franklin st fifth floor boston ma 02110 1301 usa extracted by the scancode license scanner the SPDX license identifier GPL-2.0-only has been chosen to replace the boilerplate/reference in 111 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Alexios Zavras <alexios.zavras@intel.com> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190530000436.567572064@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
228 lines
5.8 KiB
C
228 lines
5.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* OF helpers for IOMMU
|
|
*
|
|
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/export.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/limits.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_iommu.h>
|
|
#include <linux/of_pci.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/fsl/mc.h>
|
|
|
|
#define NO_IOMMU 1
|
|
|
|
/**
|
|
* of_get_dma_window - Parse *dma-window property and returns 0 if found.
|
|
*
|
|
* @dn: device node
|
|
* @prefix: prefix for property name if any
|
|
* @index: index to start to parse
|
|
* @busno: Returns busno if supported. Otherwise pass NULL
|
|
* @addr: Returns address that DMA starts
|
|
* @size: Returns the range that DMA can handle
|
|
*
|
|
* This supports different formats flexibly. "prefix" can be
|
|
* configured if any. "busno" and "index" are optionally
|
|
* specified. Set 0(or NULL) if not used.
|
|
*/
|
|
int of_get_dma_window(struct device_node *dn, const char *prefix, int index,
|
|
unsigned long *busno, dma_addr_t *addr, size_t *size)
|
|
{
|
|
const __be32 *dma_window, *end;
|
|
int bytes, cur_index = 0;
|
|
char propname[NAME_MAX], addrname[NAME_MAX], sizename[NAME_MAX];
|
|
|
|
if (!dn || !addr || !size)
|
|
return -EINVAL;
|
|
|
|
if (!prefix)
|
|
prefix = "";
|
|
|
|
snprintf(propname, sizeof(propname), "%sdma-window", prefix);
|
|
snprintf(addrname, sizeof(addrname), "%s#dma-address-cells", prefix);
|
|
snprintf(sizename, sizeof(sizename), "%s#dma-size-cells", prefix);
|
|
|
|
dma_window = of_get_property(dn, propname, &bytes);
|
|
if (!dma_window)
|
|
return -ENODEV;
|
|
end = dma_window + bytes / sizeof(*dma_window);
|
|
|
|
while (dma_window < end) {
|
|
u32 cells;
|
|
const void *prop;
|
|
|
|
/* busno is one cell if supported */
|
|
if (busno)
|
|
*busno = be32_to_cpup(dma_window++);
|
|
|
|
prop = of_get_property(dn, addrname, NULL);
|
|
if (!prop)
|
|
prop = of_get_property(dn, "#address-cells", NULL);
|
|
|
|
cells = prop ? be32_to_cpup(prop) : of_n_addr_cells(dn);
|
|
if (!cells)
|
|
return -EINVAL;
|
|
*addr = of_read_number(dma_window, cells);
|
|
dma_window += cells;
|
|
|
|
prop = of_get_property(dn, sizename, NULL);
|
|
cells = prop ? be32_to_cpup(prop) : of_n_size_cells(dn);
|
|
if (!cells)
|
|
return -EINVAL;
|
|
*size = of_read_number(dma_window, cells);
|
|
dma_window += cells;
|
|
|
|
if (cur_index++ == index)
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(of_get_dma_window);
|
|
|
|
static int of_iommu_xlate(struct device *dev,
|
|
struct of_phandle_args *iommu_spec)
|
|
{
|
|
const struct iommu_ops *ops;
|
|
struct fwnode_handle *fwnode = &iommu_spec->np->fwnode;
|
|
int err;
|
|
|
|
ops = iommu_ops_from_fwnode(fwnode);
|
|
if ((ops && !ops->of_xlate) ||
|
|
!of_device_is_available(iommu_spec->np))
|
|
return NO_IOMMU;
|
|
|
|
err = iommu_fwspec_init(dev, &iommu_spec->np->fwnode, ops);
|
|
if (err)
|
|
return err;
|
|
/*
|
|
* The otherwise-empty fwspec handily serves to indicate the specific
|
|
* IOMMU device we're waiting for, which will be useful if we ever get
|
|
* a proper probe-ordering dependency mechanism in future.
|
|
*/
|
|
if (!ops)
|
|
return driver_deferred_probe_check_state(dev);
|
|
|
|
return ops->of_xlate(dev, iommu_spec);
|
|
}
|
|
|
|
struct of_pci_iommu_alias_info {
|
|
struct device *dev;
|
|
struct device_node *np;
|
|
};
|
|
|
|
static int of_pci_iommu_init(struct pci_dev *pdev, u16 alias, void *data)
|
|
{
|
|
struct of_pci_iommu_alias_info *info = data;
|
|
struct of_phandle_args iommu_spec = { .args_count = 1 };
|
|
int err;
|
|
|
|
err = of_map_rid(info->np, alias, "iommu-map", "iommu-map-mask",
|
|
&iommu_spec.np, iommu_spec.args);
|
|
if (err)
|
|
return err == -ENODEV ? NO_IOMMU : err;
|
|
|
|
err = of_iommu_xlate(info->dev, &iommu_spec);
|
|
of_node_put(iommu_spec.np);
|
|
return err;
|
|
}
|
|
|
|
static int of_fsl_mc_iommu_init(struct fsl_mc_device *mc_dev,
|
|
struct device_node *master_np)
|
|
{
|
|
struct of_phandle_args iommu_spec = { .args_count = 1 };
|
|
int err;
|
|
|
|
err = of_map_rid(master_np, mc_dev->icid, "iommu-map",
|
|
"iommu-map-mask", &iommu_spec.np,
|
|
iommu_spec.args);
|
|
if (err)
|
|
return err == -ENODEV ? NO_IOMMU : err;
|
|
|
|
err = of_iommu_xlate(&mc_dev->dev, &iommu_spec);
|
|
of_node_put(iommu_spec.np);
|
|
return err;
|
|
}
|
|
|
|
const struct iommu_ops *of_iommu_configure(struct device *dev,
|
|
struct device_node *master_np)
|
|
{
|
|
const struct iommu_ops *ops = NULL;
|
|
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
|
|
int err = NO_IOMMU;
|
|
|
|
if (!master_np)
|
|
return NULL;
|
|
|
|
if (fwspec) {
|
|
if (fwspec->ops)
|
|
return fwspec->ops;
|
|
|
|
/* In the deferred case, start again from scratch */
|
|
iommu_fwspec_free(dev);
|
|
}
|
|
|
|
/*
|
|
* We don't currently walk up the tree looking for a parent IOMMU.
|
|
* See the `Notes:' section of
|
|
* Documentation/devicetree/bindings/iommu/iommu.txt
|
|
*/
|
|
if (dev_is_pci(dev)) {
|
|
struct of_pci_iommu_alias_info info = {
|
|
.dev = dev,
|
|
.np = master_np,
|
|
};
|
|
|
|
err = pci_for_each_dma_alias(to_pci_dev(dev),
|
|
of_pci_iommu_init, &info);
|
|
} else if (dev_is_fsl_mc(dev)) {
|
|
err = of_fsl_mc_iommu_init(to_fsl_mc_device(dev), master_np);
|
|
} else {
|
|
struct of_phandle_args iommu_spec;
|
|
int idx = 0;
|
|
|
|
while (!of_parse_phandle_with_args(master_np, "iommus",
|
|
"#iommu-cells",
|
|
idx, &iommu_spec)) {
|
|
err = of_iommu_xlate(dev, &iommu_spec);
|
|
of_node_put(iommu_spec.np);
|
|
idx++;
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Two success conditions can be represented by non-negative err here:
|
|
* >0 : there is no IOMMU, or one was unavailable for non-fatal reasons
|
|
* 0 : we found an IOMMU, and dev->fwspec is initialised appropriately
|
|
* <0 : any actual error
|
|
*/
|
|
if (!err) {
|
|
/* The fwspec pointer changed, read it again */
|
|
fwspec = dev_iommu_fwspec_get(dev);
|
|
ops = fwspec->ops;
|
|
}
|
|
/*
|
|
* If we have reason to believe the IOMMU driver missed the initial
|
|
* probe for dev, replay it to get things in order.
|
|
*/
|
|
if (!err && dev->bus && !device_iommu_mapped(dev))
|
|
err = iommu_probe_device(dev);
|
|
|
|
/* Ignore all other errors apart from EPROBE_DEFER */
|
|
if (err == -EPROBE_DEFER) {
|
|
ops = ERR_PTR(err);
|
|
} else if (err < 0) {
|
|
dev_dbg(dev, "Adding to IOMMU failed: %d\n", err);
|
|
ops = NULL;
|
|
}
|
|
|
|
return ops;
|
|
}
|