Merge branch 'for-next/iommu/tegra-smmu' into for-next/iommu/core

Tegra SMMU updates for 5.11: a complete redesign of the probing logic,
support for PCI devices and cleanup work.

* for-next/iommu/tegra-smmu:
  iommu/tegra-smmu: Add PCI support
  iommu/tegra-smmu: Rework tegra_smmu_probe_device()
  iommu/tegra-smmu: Use fwspec in tegra_smmu_(de)attach_dev
  iommu/tegra-smmu: Expand mutex protection range
  iommu/tegra-smmu: Unwrap tegra_smmu_group_get
This commit is contained in:
Will Deacon 2020-12-08 15:10:09 +00:00
Родитель a5f12de3ec 541f29bb06
Коммит c5257e39a4
1 изменённых файлов: 88 добавлений и 152 удалений

Просмотреть файл

@ -10,6 +10,7 @@
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
@ -256,26 +257,19 @@ static int tegra_smmu_alloc_asid(struct tegra_smmu *smmu, unsigned int *idp)
{
unsigned long id;
mutex_lock(&smmu->lock);
id = find_first_zero_bit(smmu->asids, smmu->soc->num_asids);
if (id >= smmu->soc->num_asids) {
mutex_unlock(&smmu->lock);
if (id >= smmu->soc->num_asids)
return -ENOSPC;
}
set_bit(id, smmu->asids);
*idp = id;
mutex_unlock(&smmu->lock);
return 0;
}
static void tegra_smmu_free_asid(struct tegra_smmu *smmu, unsigned int id)
{
mutex_lock(&smmu->lock);
clear_bit(id, smmu->asids);
mutex_unlock(&smmu->lock);
}
static bool tegra_smmu_capable(enum iommu_cap cap)
@ -420,17 +414,21 @@ static int tegra_smmu_as_prepare(struct tegra_smmu *smmu,
struct tegra_smmu_as *as)
{
u32 value;
int err;
int err = 0;
mutex_lock(&smmu->lock);
if (as->use_count > 0) {
as->use_count++;
return 0;
goto unlock;
}
as->pd_dma = dma_map_page(smmu->dev, as->pd, 0, SMMU_SIZE_PD,
DMA_TO_DEVICE);
if (dma_mapping_error(smmu->dev, as->pd_dma))
return -ENOMEM;
if (dma_mapping_error(smmu->dev, as->pd_dma)) {
err = -ENOMEM;
goto unlock;
}
/* We can't handle 64-bit DMA addresses */
if (!smmu_dma_addr_valid(smmu, as->pd_dma)) {
@ -453,83 +451,84 @@ static int tegra_smmu_as_prepare(struct tegra_smmu *smmu,
as->smmu = smmu;
as->use_count++;
mutex_unlock(&smmu->lock);
return 0;
err_unmap:
dma_unmap_page(smmu->dev, as->pd_dma, SMMU_SIZE_PD, DMA_TO_DEVICE);
unlock:
mutex_unlock(&smmu->lock);
return err;
}
static void tegra_smmu_as_unprepare(struct tegra_smmu *smmu,
struct tegra_smmu_as *as)
{
if (--as->use_count > 0)
mutex_lock(&smmu->lock);
if (--as->use_count > 0) {
mutex_unlock(&smmu->lock);
return;
}
tegra_smmu_free_asid(smmu, as->id);
dma_unmap_page(smmu->dev, as->pd_dma, SMMU_SIZE_PD, DMA_TO_DEVICE);
as->smmu = NULL;
mutex_unlock(&smmu->lock);
}
static int tegra_smmu_attach_dev(struct iommu_domain *domain,
struct device *dev)
{
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
struct tegra_smmu *smmu = dev_iommu_priv_get(dev);
struct tegra_smmu_as *as = to_smmu_as(domain);
struct device_node *np = dev->of_node;
struct of_phandle_args args;
unsigned int index = 0;
int err = 0;
unsigned int index;
int err;
while (!of_parse_phandle_with_args(np, "iommus", "#iommu-cells", index,
&args)) {
unsigned int swgroup = args.args[0];
if (args.np != smmu->dev->of_node) {
of_node_put(args.np);
continue;
}
of_node_put(args.np);
if (!fwspec)
return -ENOENT;
for (index = 0; index < fwspec->num_ids; index++) {
err = tegra_smmu_as_prepare(smmu, as);
if (err < 0)
return err;
if (err)
goto disable;
tegra_smmu_enable(smmu, swgroup, as->id);
index++;
tegra_smmu_enable(smmu, fwspec->ids[index], as->id);
}
if (index == 0)
return -ENODEV;
return 0;
disable:
while (index--) {
tegra_smmu_disable(smmu, fwspec->ids[index], as->id);
tegra_smmu_as_unprepare(smmu, as);
}
return err;
}
static void tegra_smmu_detach_dev(struct iommu_domain *domain, struct device *dev)
{
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
struct tegra_smmu_as *as = to_smmu_as(domain);
struct device_node *np = dev->of_node;
struct tegra_smmu *smmu = as->smmu;
struct of_phandle_args args;
unsigned int index = 0;
unsigned int index;
while (!of_parse_phandle_with_args(np, "iommus", "#iommu-cells", index,
&args)) {
unsigned int swgroup = args.args[0];
if (!fwspec)
return;
if (args.np != smmu->dev->of_node) {
of_node_put(args.np);
continue;
}
of_node_put(args.np);
tegra_smmu_disable(smmu, swgroup, as->id);
for (index = 0; index < fwspec->num_ids; index++) {
tegra_smmu_disable(smmu, fwspec->ids[index], as->id);
tegra_smmu_as_unprepare(smmu, as);
index++;
}
}
@ -799,75 +798,9 @@ static phys_addr_t tegra_smmu_iova_to_phys(struct iommu_domain *domain,
return SMMU_PFN_PHYS(pfn) + SMMU_OFFSET_IN_PAGE(iova);
}
static struct tegra_smmu *tegra_smmu_find(struct device_node *np)
{
struct platform_device *pdev;
struct tegra_mc *mc;
pdev = of_find_device_by_node(np);
if (!pdev)
return NULL;
mc = platform_get_drvdata(pdev);
if (!mc)
return NULL;
return mc->smmu;
}
static int tegra_smmu_configure(struct tegra_smmu *smmu, struct device *dev,
struct of_phandle_args *args)
{
const struct iommu_ops *ops = smmu->iommu.ops;
int err;
err = iommu_fwspec_init(dev, &dev->of_node->fwnode, ops);
if (err < 0) {
dev_err(dev, "failed to initialize fwspec: %d\n", err);
return err;
}
err = ops->of_xlate(dev, args);
if (err < 0) {
dev_err(dev, "failed to parse SW group ID: %d\n", err);
iommu_fwspec_free(dev);
return err;
}
return 0;
}
static struct iommu_device *tegra_smmu_probe_device(struct device *dev)
{
struct device_node *np = dev->of_node;
struct tegra_smmu *smmu = NULL;
struct of_phandle_args args;
unsigned int index = 0;
int err;
while (of_parse_phandle_with_args(np, "iommus", "#iommu-cells", index,
&args) == 0) {
smmu = tegra_smmu_find(args.np);
if (smmu) {
err = tegra_smmu_configure(smmu, dev, &args);
of_node_put(args.np);
if (err < 0)
return ERR_PTR(err);
/*
* Only a single IOMMU master interface is currently
* supported by the Linux kernel, so abort after the
* first match.
*/
dev_iommu_priv_set(dev, smmu);
break;
}
of_node_put(args.np);
index++;
}
struct tegra_smmu *smmu = dev_iommu_priv_get(dev);
if (!smmu)
return ERR_PTR(-ENODEV);
@ -875,10 +808,7 @@ static struct iommu_device *tegra_smmu_probe_device(struct device *dev)
return &smmu->iommu;
}
static void tegra_smmu_release_device(struct device *dev)
{
dev_iommu_priv_set(dev, NULL);
}
static void tegra_smmu_release_device(struct device *dev) {}
static const struct tegra_smmu_group_soc *
tegra_smmu_find_group(struct tegra_smmu *smmu, unsigned int swgroup)
@ -903,10 +833,12 @@ static void tegra_smmu_group_release(void *iommu_data)
mutex_unlock(&smmu->lock);
}
static struct iommu_group *tegra_smmu_group_get(struct tegra_smmu *smmu,
unsigned int swgroup)
static struct iommu_group *tegra_smmu_device_group(struct device *dev)
{
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
struct tegra_smmu *smmu = dev_iommu_priv_get(dev);
const struct tegra_smmu_group_soc *soc;
unsigned int swgroup = fwspec->ids[0];
struct tegra_smmu_group *group;
struct iommu_group *grp;
@ -934,7 +866,11 @@ static struct iommu_group *tegra_smmu_group_get(struct tegra_smmu *smmu,
group->smmu = smmu;
group->soc = soc;
group->group = iommu_group_alloc();
if (dev_is_pci(dev))
group->group = pci_device_group(dev);
else
group->group = generic_device_group(dev);
if (IS_ERR(group->group)) {
devm_kfree(smmu->dev, group);
mutex_unlock(&smmu->lock);
@ -950,24 +886,24 @@ static struct iommu_group *tegra_smmu_group_get(struct tegra_smmu *smmu,
return group->group;
}
static struct iommu_group *tegra_smmu_device_group(struct device *dev)
{
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
struct tegra_smmu *smmu = dev_iommu_priv_get(dev);
struct iommu_group *group;
group = tegra_smmu_group_get(smmu, fwspec->ids[0]);
if (!group)
group = generic_device_group(dev);
return group;
}
static int tegra_smmu_of_xlate(struct device *dev,
struct of_phandle_args *args)
{
struct platform_device *iommu_pdev = of_find_device_by_node(args->np);
struct tegra_mc *mc = platform_get_drvdata(iommu_pdev);
u32 id = args->args[0];
/*
* Note: we are here releasing the reference of &iommu_pdev->dev, which
* is mc->dev. Although some functions in tegra_smmu_ops may keep using
* its private data beyond this point, it's still safe to do so because
* the SMMU parent device is the same as the MC, so the reference count
* isn't strictly necessary.
*/
put_device(&iommu_pdev->dev);
dev_iommu_priv_set(dev, mc->smmu);
return iommu_fwspec_add_ids(dev, &id, 1);
}
@ -1092,16 +1028,6 @@ struct tegra_smmu *tegra_smmu_probe(struct device *dev,
if (!smmu)
return ERR_PTR(-ENOMEM);
/*
* This is a bit of a hack. Ideally we'd want to simply return this
* value. However the IOMMU registration process will attempt to add
* all devices to the IOMMU when bus_set_iommu() is called. In order
* not to rely on global variables to track the IOMMU instance, we
* set it here so that it can be looked up from the .probe_device()
* callback via the IOMMU device's .drvdata field.
*/
mc->smmu = smmu;
size = BITS_TO_LONGS(soc->num_asids) * sizeof(long);
smmu->asids = devm_kzalloc(dev, size, GFP_KERNEL);
@ -1154,22 +1080,32 @@ struct tegra_smmu *tegra_smmu_probe(struct device *dev,
iommu_device_set_fwnode(&smmu->iommu, dev->fwnode);
err = iommu_device_register(&smmu->iommu);
if (err) {
iommu_device_sysfs_remove(&smmu->iommu);
return ERR_PTR(err);
}
if (err)
goto remove_sysfs;
err = bus_set_iommu(&platform_bus_type, &tegra_smmu_ops);
if (err < 0) {
iommu_device_unregister(&smmu->iommu);
iommu_device_sysfs_remove(&smmu->iommu);
return ERR_PTR(err);
}
if (err < 0)
goto unregister;
#ifdef CONFIG_PCI
err = bus_set_iommu(&pci_bus_type, &tegra_smmu_ops);
if (err < 0)
goto unset_platform_bus;
#endif
if (IS_ENABLED(CONFIG_DEBUG_FS))
tegra_smmu_debugfs_init(smmu);
return smmu;
unset_platform_bus: __maybe_unused;
bus_set_iommu(&platform_bus_type, NULL);
unregister:
iommu_device_unregister(&smmu->iommu);
remove_sysfs:
iommu_device_sysfs_remove(&smmu->iommu);
return ERR_PTR(err);
}
void tegra_smmu_remove(struct tegra_smmu *smmu)