| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * |
| * Copyright (C) 2013 Freescale Semiconductor, Inc. |
| * Author: Varun Sethi <varun.sethi@freescale.com> |
| */ |
| |
| #define pr_fmt(fmt) "fsl-pamu-domain: %s: " fmt, __func__ |
| |
| #include "fsl_pamu_domain.h" |
| |
| #include <linux/platform_device.h> |
| #include <sysdev/fsl_pci.h> |
| |
| /* |
| * Global spinlock that needs to be held while |
| * configuring PAMU. |
| */ |
| static DEFINE_SPINLOCK(iommu_lock); |
| |
| static struct kmem_cache *fsl_pamu_domain_cache; |
| static struct kmem_cache *iommu_devinfo_cache; |
| static DEFINE_SPINLOCK(device_domain_lock); |
| |
| struct iommu_device pamu_iommu; /* IOMMU core code handle */ |
| |
| static struct fsl_dma_domain *to_fsl_dma_domain(struct iommu_domain *dom) |
| { |
| return container_of(dom, struct fsl_dma_domain, iommu_domain); |
| } |
| |
| static int __init iommu_init_mempool(void) |
| { |
| fsl_pamu_domain_cache = kmem_cache_create("fsl_pamu_domain", |
| sizeof(struct fsl_dma_domain), |
| 0, |
| SLAB_HWCACHE_ALIGN, |
| NULL); |
| if (!fsl_pamu_domain_cache) { |
| pr_debug("Couldn't create fsl iommu_domain cache\n"); |
| return -ENOMEM; |
| } |
| |
| iommu_devinfo_cache = kmem_cache_create("iommu_devinfo", |
| sizeof(struct device_domain_info), |
| 0, |
| SLAB_HWCACHE_ALIGN, |
| NULL); |
| if (!iommu_devinfo_cache) { |
| pr_debug("Couldn't create devinfo cache\n"); |
| kmem_cache_destroy(fsl_pamu_domain_cache); |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static int update_liodn_stash(int liodn, struct fsl_dma_domain *dma_domain, |
| u32 val) |
| { |
| int ret = 0; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&iommu_lock, flags); |
| ret = pamu_update_paace_stash(liodn, val); |
| if (ret) { |
| pr_debug("Failed to update SPAACE for liodn %d\n ", liodn); |
| spin_unlock_irqrestore(&iommu_lock, flags); |
| return ret; |
| } |
| |
| spin_unlock_irqrestore(&iommu_lock, flags); |
| |
| return ret; |
| } |
| |
| /* Set the geometry parameters for a LIODN */ |
| static int pamu_set_liodn(struct fsl_dma_domain *dma_domain, struct device *dev, |
| int liodn) |
| { |
| u32 omi_index = ~(u32)0; |
| unsigned long flags; |
| int ret; |
| |
| /* |
| * Configure the omi_index at the geometry setup time. |
| * This is a static value which depends on the type of |
| * device and would not change thereafter. |
| */ |
| get_ome_index(&omi_index, dev); |
| |
| spin_lock_irqsave(&iommu_lock, flags); |
| ret = pamu_disable_liodn(liodn); |
| if (ret) |
| goto out_unlock; |
| ret = pamu_config_ppaace(liodn, omi_index, dma_domain->stash_id, 0); |
| if (ret) |
| goto out_unlock; |
| ret = pamu_config_ppaace(liodn, ~(u32)0, dma_domain->stash_id, |
| PAACE_AP_PERMS_QUERY | PAACE_AP_PERMS_UPDATE); |
| out_unlock: |
| spin_unlock_irqrestore(&iommu_lock, flags); |
| if (ret) { |
| pr_debug("PAACE configuration failed for liodn %d\n", |
| liodn); |
| } |
| return ret; |
| } |
| |
| static void remove_device_ref(struct device_domain_info *info) |
| { |
| unsigned long flags; |
| |
| list_del(&info->link); |
| spin_lock_irqsave(&iommu_lock, flags); |
| pamu_disable_liodn(info->liodn); |
| spin_unlock_irqrestore(&iommu_lock, flags); |
| spin_lock_irqsave(&device_domain_lock, flags); |
| dev_iommu_priv_set(info->dev, NULL); |
| kmem_cache_free(iommu_devinfo_cache, info); |
| spin_unlock_irqrestore(&device_domain_lock, flags); |
| } |
| |
| static void detach_device(struct device *dev, struct fsl_dma_domain *dma_domain) |
| { |
| struct device_domain_info *info, *tmp; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dma_domain->domain_lock, flags); |
| /* Remove the device from the domain device list */ |
| list_for_each_entry_safe(info, tmp, &dma_domain->devices, link) { |
| if (!dev || (info->dev == dev)) |
| remove_device_ref(info); |
| } |
| spin_unlock_irqrestore(&dma_domain->domain_lock, flags); |
| } |
| |
| static void attach_device(struct fsl_dma_domain *dma_domain, int liodn, struct device *dev) |
| { |
| struct device_domain_info *info, *old_domain_info; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&device_domain_lock, flags); |
| /* |
| * Check here if the device is already attached to domain or not. |
| * If the device is already attached to a domain detach it. |
| */ |
| old_domain_info = dev_iommu_priv_get(dev); |
| if (old_domain_info && old_domain_info->domain != dma_domain) { |
| spin_unlock_irqrestore(&device_domain_lock, flags); |
| detach_device(dev, old_domain_info->domain); |
| spin_lock_irqsave(&device_domain_lock, flags); |
| } |
| |
| info = kmem_cache_zalloc(iommu_devinfo_cache, GFP_ATOMIC); |
| |
| info->dev = dev; |
| info->liodn = liodn; |
| info->domain = dma_domain; |
| |
| list_add(&info->link, &dma_domain->devices); |
| /* |
| * In case of devices with multiple LIODNs just store |
| * the info for the first LIODN as all |
| * LIODNs share the same domain |
| */ |
| if (!dev_iommu_priv_get(dev)) |
| dev_iommu_priv_set(dev, info); |
| spin_unlock_irqrestore(&device_domain_lock, flags); |
| } |
| |
| static phys_addr_t fsl_pamu_iova_to_phys(struct iommu_domain *domain, |
| dma_addr_t iova) |
| { |
| if (iova < domain->geometry.aperture_start || |
| iova > domain->geometry.aperture_end) |
| return 0; |
| return iova; |
| } |
| |
| static bool fsl_pamu_capable(struct device *dev, enum iommu_cap cap) |
| { |
| return cap == IOMMU_CAP_CACHE_COHERENCY; |
| } |
| |
| static void fsl_pamu_domain_free(struct iommu_domain *domain) |
| { |
| struct fsl_dma_domain *dma_domain = to_fsl_dma_domain(domain); |
| |
| /* remove all the devices from the device list */ |
| detach_device(NULL, dma_domain); |
| kmem_cache_free(fsl_pamu_domain_cache, dma_domain); |
| } |
| |
| static struct iommu_domain *fsl_pamu_domain_alloc(unsigned type) |
| { |
| struct fsl_dma_domain *dma_domain; |
| |
| if (type != IOMMU_DOMAIN_UNMANAGED) |
| return NULL; |
| |
| dma_domain = kmem_cache_zalloc(fsl_pamu_domain_cache, GFP_KERNEL); |
| if (!dma_domain) |
| return NULL; |
| |
| dma_domain->stash_id = ~(u32)0; |
| INIT_LIST_HEAD(&dma_domain->devices); |
| spin_lock_init(&dma_domain->domain_lock); |
| |
| /* default geometry 64 GB i.e. maximum system address */ |
| dma_domain->iommu_domain. geometry.aperture_start = 0; |
| dma_domain->iommu_domain.geometry.aperture_end = (1ULL << 36) - 1; |
| dma_domain->iommu_domain.geometry.force_aperture = true; |
| |
| return &dma_domain->iommu_domain; |
| } |
| |
| /* Update stash destination for all LIODNs associated with the domain */ |
| static int update_domain_stash(struct fsl_dma_domain *dma_domain, u32 val) |
| { |
| struct device_domain_info *info; |
| int ret = 0; |
| |
| list_for_each_entry(info, &dma_domain->devices, link) { |
| ret = update_liodn_stash(info->liodn, dma_domain, val); |
| if (ret) |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int fsl_pamu_attach_device(struct iommu_domain *domain, |
| struct device *dev) |
| { |
| struct fsl_dma_domain *dma_domain = to_fsl_dma_domain(domain); |
| unsigned long flags; |
| int len, ret = 0, i; |
| const u32 *liodn; |
| struct pci_dev *pdev = NULL; |
| struct pci_controller *pci_ctl; |
| |
| /* |
| * Use LIODN of the PCI controller while attaching a |
| * PCI device. |
| */ |
| if (dev_is_pci(dev)) { |
| pdev = to_pci_dev(dev); |
| pci_ctl = pci_bus_to_host(pdev->bus); |
| /* |
| * make dev point to pci controller device |
| * so we can get the LIODN programmed by |
| * u-boot. |
| */ |
| dev = pci_ctl->parent; |
| } |
| |
| liodn = of_get_property(dev->of_node, "fsl,liodn", &len); |
| if (!liodn) { |
| pr_debug("missing fsl,liodn property at %pOF\n", dev->of_node); |
| return -ENODEV; |
| } |
| |
| spin_lock_irqsave(&dma_domain->domain_lock, flags); |
| for (i = 0; i < len / sizeof(u32); i++) { |
| /* Ensure that LIODN value is valid */ |
| if (liodn[i] >= PAACE_NUMBER_ENTRIES) { |
| pr_debug("Invalid liodn %d, attach device failed for %pOF\n", |
| liodn[i], dev->of_node); |
| ret = -ENODEV; |
| break; |
| } |
| |
| attach_device(dma_domain, liodn[i], dev); |
| ret = pamu_set_liodn(dma_domain, dev, liodn[i]); |
| if (ret) |
| break; |
| ret = pamu_enable_liodn(liodn[i]); |
| if (ret) |
| break; |
| } |
| spin_unlock_irqrestore(&dma_domain->domain_lock, flags); |
| return ret; |
| } |
| |
| static void fsl_pamu_set_platform_dma(struct device *dev) |
| { |
| struct iommu_domain *domain = iommu_get_domain_for_dev(dev); |
| struct fsl_dma_domain *dma_domain = to_fsl_dma_domain(domain); |
| const u32 *prop; |
| int len; |
| struct pci_dev *pdev = NULL; |
| struct pci_controller *pci_ctl; |
| |
| /* |
| * Use LIODN of the PCI controller while detaching a |
| * PCI device. |
| */ |
| if (dev_is_pci(dev)) { |
| pdev = to_pci_dev(dev); |
| pci_ctl = pci_bus_to_host(pdev->bus); |
| /* |
| * make dev point to pci controller device |
| * so we can get the LIODN programmed by |
| * u-boot. |
| */ |
| dev = pci_ctl->parent; |
| } |
| |
| prop = of_get_property(dev->of_node, "fsl,liodn", &len); |
| if (prop) |
| detach_device(dev, dma_domain); |
| else |
| pr_debug("missing fsl,liodn property at %pOF\n", dev->of_node); |
| } |
| |
| /* Set the domain stash attribute */ |
| int fsl_pamu_configure_l1_stash(struct iommu_domain *domain, u32 cpu) |
| { |
| struct fsl_dma_domain *dma_domain = to_fsl_dma_domain(domain); |
| unsigned long flags; |
| int ret; |
| |
| spin_lock_irqsave(&dma_domain->domain_lock, flags); |
| dma_domain->stash_id = get_stash_id(PAMU_ATTR_CACHE_L1, cpu); |
| if (dma_domain->stash_id == ~(u32)0) { |
| pr_debug("Invalid stash attributes\n"); |
| spin_unlock_irqrestore(&dma_domain->domain_lock, flags); |
| return -EINVAL; |
| } |
| ret = update_domain_stash(dma_domain, dma_domain->stash_id); |
| spin_unlock_irqrestore(&dma_domain->domain_lock, flags); |
| |
| return ret; |
| } |
| |
| static bool check_pci_ctl_endpt_part(struct pci_controller *pci_ctl) |
| { |
| u32 version; |
| |
| /* Check the PCI controller version number by readding BRR1 register */ |
| version = in_be32(pci_ctl->cfg_addr + (PCI_FSL_BRR1 >> 2)); |
| version &= PCI_FSL_BRR1_VER; |
| /* If PCI controller version is >= 0x204 we can partition endpoints */ |
| return version >= 0x204; |
| } |
| |
| static struct iommu_group *fsl_pamu_device_group(struct device *dev) |
| { |
| struct iommu_group *group; |
| struct pci_dev *pdev; |
| |
| /* |
| * For platform devices we allocate a separate group for each of the |
| * devices. |
| */ |
| if (!dev_is_pci(dev)) |
| return generic_device_group(dev); |
| |
| /* |
| * We can partition PCIe devices so assign device group to the device |
| */ |
| pdev = to_pci_dev(dev); |
| if (check_pci_ctl_endpt_part(pci_bus_to_host(pdev->bus))) |
| return pci_device_group(&pdev->dev); |
| |
| /* |
| * All devices connected to the controller will share the same device |
| * group. |
| * |
| * Due to ordering between fsl_pamu_init() and fsl_pci_init() it is |
| * guaranteed that the pci_ctl->parent platform_device will have the |
| * iommu driver bound and will already have a group set. So we just |
| * re-use this group as the group for every device in the hose. |
| */ |
| group = iommu_group_get(pci_bus_to_host(pdev->bus)->parent); |
| if (WARN_ON(!group)) |
| return ERR_PTR(-EINVAL); |
| return group; |
| } |
| |
| static struct iommu_device *fsl_pamu_probe_device(struct device *dev) |
| { |
| int len; |
| |
| /* |
| * uboot must fill the fsl,liodn for platform devices to be supported by |
| * the iommu. |
| */ |
| if (!dev_is_pci(dev) && |
| !of_get_property(dev->of_node, "fsl,liodn", &len)) |
| return ERR_PTR(-ENODEV); |
| |
| return &pamu_iommu; |
| } |
| |
| static const struct iommu_ops fsl_pamu_ops = { |
| .capable = fsl_pamu_capable, |
| .domain_alloc = fsl_pamu_domain_alloc, |
| .probe_device = fsl_pamu_probe_device, |
| .device_group = fsl_pamu_device_group, |
| .set_platform_dma_ops = fsl_pamu_set_platform_dma, |
| .default_domain_ops = &(const struct iommu_domain_ops) { |
| .attach_dev = fsl_pamu_attach_device, |
| .iova_to_phys = fsl_pamu_iova_to_phys, |
| .free = fsl_pamu_domain_free, |
| } |
| }; |
| |
| int __init pamu_domain_init(void) |
| { |
| int ret = 0; |
| |
| ret = iommu_init_mempool(); |
| if (ret) |
| return ret; |
| |
| ret = iommu_device_sysfs_add(&pamu_iommu, NULL, NULL, "iommu0"); |
| if (ret) |
| return ret; |
| |
| ret = iommu_device_register(&pamu_iommu, &fsl_pamu_ops, NULL); |
| if (ret) { |
| iommu_device_sysfs_remove(&pamu_iommu); |
| pr_err("Can't register iommu device\n"); |
| } |
| |
| return ret; |
| } |