mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-11-25 15:20:53 +07:00
iommu/arm-smmu: add support for multi-master iommu groups
Whilst the driver currently creates one IOMMU group per device, this will soon change when we start supporting non-transparent PCI bridges which require all upstream masters to be assigned to the same address space. This patch reworks our IOMMU group code so that we can easily support multi-master groups. The master configuration (streamids and smrs) is stored as private iommudata on the group, whilst the low-level attach/detach code is updated to avoid double alloc/free when dealing with multiple masters sharing the same SMMU configuration. This unifies device handling, regardless of whether the device sits on the platform or pci bus. Signed-off-by: Will Deacon <will.deacon@arm.com>
This commit is contained in:
parent
4cf740b0b6
commit
8f68f8e282
@ -431,17 +431,17 @@ static void parse_driver_options(struct arm_smmu_device *smmu)
|
|||||||
} while (arm_smmu_options[++i].opt);
|
} while (arm_smmu_options[++i].opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct device *dev_get_master_dev(struct device *dev)
|
static struct device_node *dev_get_dev_node(struct device *dev)
|
||||||
{
|
{
|
||||||
if (dev_is_pci(dev)) {
|
if (dev_is_pci(dev)) {
|
||||||
struct pci_bus *bus = to_pci_dev(dev)->bus;
|
struct pci_bus *bus = to_pci_dev(dev)->bus;
|
||||||
|
|
||||||
while (!pci_is_root_bus(bus))
|
while (!pci_is_root_bus(bus))
|
||||||
bus = bus->parent;
|
bus = bus->parent;
|
||||||
return bus->bridge->parent;
|
return bus->bridge->parent->of_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dev;
|
return dev->of_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct arm_smmu_master *find_smmu_master(struct arm_smmu_device *smmu,
|
static struct arm_smmu_master *find_smmu_master(struct arm_smmu_device *smmu,
|
||||||
@ -466,15 +466,17 @@ static struct arm_smmu_master *find_smmu_master(struct arm_smmu_device *smmu,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct arm_smmu_master_cfg *
|
static struct arm_smmu_master_cfg *
|
||||||
find_smmu_master_cfg(struct arm_smmu_device *smmu, struct device *dev)
|
find_smmu_master_cfg(struct device *dev)
|
||||||
{
|
{
|
||||||
struct arm_smmu_master *master;
|
struct arm_smmu_master_cfg *cfg = NULL;
|
||||||
|
struct iommu_group *group = iommu_group_get(dev);
|
||||||
|
|
||||||
if (dev_is_pci(dev))
|
if (group) {
|
||||||
return dev->archdata.iommu;
|
cfg = iommu_group_get_iommudata(group);
|
||||||
|
iommu_group_put(group);
|
||||||
|
}
|
||||||
|
|
||||||
master = find_smmu_master(smmu, dev->of_node);
|
return cfg;
|
||||||
return master ? &master->cfg : NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int insert_smmu_master(struct arm_smmu_device *smmu,
|
static int insert_smmu_master(struct arm_smmu_device *smmu,
|
||||||
@ -550,7 +552,7 @@ static struct arm_smmu_device *find_smmu_for_device(struct device *dev)
|
|||||||
{
|
{
|
||||||
struct arm_smmu_device *smmu;
|
struct arm_smmu_device *smmu;
|
||||||
struct arm_smmu_master *master = NULL;
|
struct arm_smmu_master *master = NULL;
|
||||||
struct device_node *dev_node = dev_get_master_dev(dev)->of_node;
|
struct device_node *dev_node = dev_get_dev_node(dev);
|
||||||
|
|
||||||
spin_lock(&arm_smmu_devices_lock);
|
spin_lock(&arm_smmu_devices_lock);
|
||||||
list_for_each_entry(smmu, &arm_smmu_devices, list) {
|
list_for_each_entry(smmu, &arm_smmu_devices, list) {
|
||||||
@ -1156,9 +1158,10 @@ static int arm_smmu_domain_add_master(struct arm_smmu_domain *smmu_domain,
|
|||||||
struct arm_smmu_device *smmu = smmu_domain->smmu;
|
struct arm_smmu_device *smmu = smmu_domain->smmu;
|
||||||
void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
|
void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
|
||||||
|
|
||||||
|
/* Devices in an IOMMU group may already be configured */
|
||||||
ret = arm_smmu_master_configure_smrs(smmu, cfg);
|
ret = arm_smmu_master_configure_smrs(smmu, cfg);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret == -EEXIST ? 0 : ret;
|
||||||
|
|
||||||
for (i = 0; i < cfg->num_streamids; ++i) {
|
for (i = 0; i < cfg->num_streamids; ++i) {
|
||||||
u32 idx, s2cr;
|
u32 idx, s2cr;
|
||||||
@ -1179,6 +1182,10 @@ static void arm_smmu_domain_remove_master(struct arm_smmu_domain *smmu_domain,
|
|||||||
struct arm_smmu_device *smmu = smmu_domain->smmu;
|
struct arm_smmu_device *smmu = smmu_domain->smmu;
|
||||||
void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
|
void __iomem *gr0_base = ARM_SMMU_GR0(smmu);
|
||||||
|
|
||||||
|
/* An IOMMU group is torn down by the first device to be removed */
|
||||||
|
if ((smmu->features & ARM_SMMU_FEAT_STREAM_MATCH) && !cfg->smrs)
|
||||||
|
return;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We *must* clear the S2CR first, because freeing the SMR means
|
* We *must* clear the S2CR first, because freeing the SMR means
|
||||||
* that it can be re-allocated immediately.
|
* that it can be re-allocated immediately.
|
||||||
@ -1200,7 +1207,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
|
|||||||
struct arm_smmu_device *smmu, *dom_smmu;
|
struct arm_smmu_device *smmu, *dom_smmu;
|
||||||
struct arm_smmu_master_cfg *cfg;
|
struct arm_smmu_master_cfg *cfg;
|
||||||
|
|
||||||
smmu = dev_get_master_dev(dev)->archdata.iommu;
|
smmu = find_smmu_for_device(dev);
|
||||||
if (!smmu) {
|
if (!smmu) {
|
||||||
dev_err(dev, "cannot attach to SMMU, is it on the same bus?\n");
|
dev_err(dev, "cannot attach to SMMU, is it on the same bus?\n");
|
||||||
return -ENXIO;
|
return -ENXIO;
|
||||||
@ -1228,7 +1235,7 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Looks ok, so add the device to the domain */
|
/* Looks ok, so add the device to the domain */
|
||||||
cfg = find_smmu_master_cfg(smmu_domain->smmu, dev);
|
cfg = find_smmu_master_cfg(dev);
|
||||||
if (!cfg)
|
if (!cfg)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
@ -1240,7 +1247,7 @@ static void arm_smmu_detach_dev(struct iommu_domain *domain, struct device *dev)
|
|||||||
struct arm_smmu_domain *smmu_domain = domain->priv;
|
struct arm_smmu_domain *smmu_domain = domain->priv;
|
||||||
struct arm_smmu_master_cfg *cfg;
|
struct arm_smmu_master_cfg *cfg;
|
||||||
|
|
||||||
cfg = find_smmu_master_cfg(smmu_domain->smmu, dev);
|
cfg = find_smmu_master_cfg(dev);
|
||||||
if (cfg)
|
if (cfg)
|
||||||
arm_smmu_domain_remove_master(smmu_domain, cfg);
|
arm_smmu_domain_remove_master(smmu_domain, cfg);
|
||||||
}
|
}
|
||||||
@ -1554,17 +1561,19 @@ static int __arm_smmu_get_pci_sid(struct pci_dev *pdev, u16 alias, void *data)
|
|||||||
return 0; /* Continue walking */
|
return 0; /* Continue walking */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void __arm_smmu_release_pci_iommudata(void *data)
|
||||||
|
{
|
||||||
|
kfree(data);
|
||||||
|
}
|
||||||
|
|
||||||
static int arm_smmu_add_device(struct device *dev)
|
static int arm_smmu_add_device(struct device *dev)
|
||||||
{
|
{
|
||||||
struct arm_smmu_device *smmu;
|
struct arm_smmu_device *smmu;
|
||||||
|
struct arm_smmu_master_cfg *cfg;
|
||||||
struct iommu_group *group;
|
struct iommu_group *group;
|
||||||
|
void (*releasefn)(void *) = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (dev->archdata.iommu) {
|
|
||||||
dev_warn(dev, "IOMMU driver already assigned to device\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
smmu = find_smmu_for_device(dev);
|
smmu = find_smmu_for_device(dev);
|
||||||
if (!smmu)
|
if (!smmu)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
@ -1576,7 +1585,6 @@ static int arm_smmu_add_device(struct device *dev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dev_is_pci(dev)) {
|
if (dev_is_pci(dev)) {
|
||||||
struct arm_smmu_master_cfg *cfg;
|
|
||||||
struct pci_dev *pdev = to_pci_dev(dev);
|
struct pci_dev *pdev = to_pci_dev(dev);
|
||||||
|
|
||||||
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
|
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
|
||||||
@ -1592,11 +1600,20 @@ static int arm_smmu_add_device(struct device *dev)
|
|||||||
*/
|
*/
|
||||||
pci_for_each_dma_alias(pdev, __arm_smmu_get_pci_sid,
|
pci_for_each_dma_alias(pdev, __arm_smmu_get_pci_sid,
|
||||||
&cfg->streamids[0]);
|
&cfg->streamids[0]);
|
||||||
dev->archdata.iommu = cfg;
|
releasefn = __arm_smmu_release_pci_iommudata;
|
||||||
} else {
|
} else {
|
||||||
dev->archdata.iommu = smmu;
|
struct arm_smmu_master *master;
|
||||||
|
|
||||||
|
master = find_smmu_master(smmu, dev->of_node);
|
||||||
|
if (!master) {
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto out_put_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg = &master->cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iommu_group_set_iommudata(group, cfg, releasefn);
|
||||||
ret = iommu_group_add_device(group, dev);
|
ret = iommu_group_add_device(group, dev);
|
||||||
|
|
||||||
out_put_group:
|
out_put_group:
|
||||||
@ -1606,10 +1623,6 @@ static int arm_smmu_add_device(struct device *dev)
|
|||||||
|
|
||||||
static void arm_smmu_remove_device(struct device *dev)
|
static void arm_smmu_remove_device(struct device *dev)
|
||||||
{
|
{
|
||||||
if (dev_is_pci(dev))
|
|
||||||
kfree(dev->archdata.iommu);
|
|
||||||
|
|
||||||
dev->archdata.iommu = NULL;
|
|
||||||
iommu_group_remove_device(dev);
|
iommu_group_remove_device(dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user