iommu/arm-smmu: handle multi-alias IOMMU groups for PCI devices

IOMMU groups for PCI devices can correspond to multiple DMA aliases due
to things like ACS and PCI quirks.

This patch extends the ARM SMMU ->add_device callback so that we
consider all of the DMA aliases for a PCI IOMMU group, rather than
creating a separate group for each Requester ID.

Change-Id: I3b8e81e17447cc0bbd0f9d299b5494c390372002
Signed-off-by: Will Deacon <will.deacon@arm.com>
This commit is contained in:
Will Deacon 2015-01-19 14:27:33 +00:00 committed by David Keitel
parent 922d32b8b4
commit 81eb6eff75

View file

@ -2562,65 +2562,83 @@ 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_pci_device(struct pci_dev *pdev)
{
struct arm_smmu_device *smmu;
struct arm_smmu_master_cfg *cfg;
int i, ret;
u16 sid;
struct iommu_group *group;
void (*releasefn)(void *) = NULL;
int ret;
struct arm_smmu_master_cfg *cfg;
smmu = find_smmu_for_device(dev);
if (!smmu)
return -ENODEV;
group = iommu_group_alloc();
if (IS_ERR(group)) {
dev_err(dev, "Failed to allocate IOMMU group\n");
group = iommu_group_get_for_dev(&pdev->dev);
if (IS_ERR(group))
return PTR_ERR(group);
}
if (dev_is_pci(dev)) {
u32 sid;
int tmp;
cfg = iommu_group_get_iommudata(group);
if (!cfg) {
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
if (!cfg) {
ret = -ENOMEM;
goto out_put_group;
}
cfg->num_streamids = 1;
ret = msm_pcie_configure_sid(dev, &sid, &tmp);
if (ret) {
dev_err(dev,
"Couldn't configure SID through PCI-e driver: %d\n",
ret);
kfree(cfg);
goto out_put_group;
}
cfg->streamids[0] = sid;
releasefn = __arm_smmu_release_pci_iommudata;
} else {
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,
__arm_smmu_release_pci_iommudata);
}
iommu_group_set_iommudata(group, cfg, releasefn);
ret = iommu_group_add_device(group, dev);
if (cfg->num_streamids >= MAX_MASTER_STREAMIDS) {
ret = -ENOSPC;
goto out_put_group;
}
/*
* Assume Stream ID == Requester ID for now.
* We need a way to describe the ID mappings in FDT.
*/
pci_for_each_dma_alias(pdev, __arm_smmu_get_pci_sid, &sid);
for (i = 0; i < cfg->num_streamids; ++i)
if (cfg->streamids[i] == sid)
break;
/* Avoid duplicate SIDs, as this can lead to SMR conflicts */
if (i == cfg->num_streamids)
cfg->streamids[cfg->num_streamids++] = sid;
return 0;
out_put_group:
iommu_group_put(group);
return ret;
}
static int arm_smmu_add_platform_device(struct device *dev)
{
struct iommu_group *group;
struct arm_smmu_master *master;
struct arm_smmu_device *smmu = find_smmu_for_device(dev);
if (!smmu)
return -ENODEV;
master = find_smmu_master(smmu, dev->of_node);
if (!master)
return -ENODEV;
/* No automatic group creation for platform devices */
group = iommu_group_alloc();
if (IS_ERR(group))
return PTR_ERR(group);
iommu_group_set_iommudata(group, &master->cfg, NULL);
return iommu_group_add_device(group, dev);
}
static int arm_smmu_add_device(struct device *dev)
{
if (dev_is_pci(dev))
return arm_smmu_add_pci_device(to_pci_dev(dev));
return arm_smmu_add_platform_device(dev);
}
static void arm_smmu_remove_device(struct device *dev)
{
iommu_group_remove_device(dev);