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:
Коммит
c5257e39a4
|
@ -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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче