mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 08:19:28 +00:00
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. Signed-off-by: Will Deacon <will.deacon@arm.com>
This commit is contained in:
parent
f1d8454869
commit
03edb2264c
1 changed files with 58 additions and 36 deletions
|
@ -1330,61 +1330,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)) {
|
||||
struct pci_dev *pdev = to_pci_dev(dev);
|
||||
|
||||
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;
|
||||
/*
|
||||
* 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,
|
||||
&cfg->streamids[0]);
|
||||
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);
|
||||
|
|
Loading…
Reference in a new issue