Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Varun Sethi | 4143 | 89.85% | 4 | 20.00% |
Joerg Roedel | 219 | 4.75% | 6 | 30.00% |
Robin Murphy | 212 | 4.60% | 2 | 10.00% |
Emil Medve | 15 | 0.33% | 1 | 5.00% |
Yijing Wang | 9 | 0.20% | 1 | 5.00% |
Colin Ian King | 3 | 0.07% | 1 | 5.00% |
Chi Pham | 3 | 0.07% | 1 | 5.00% |
Rob Herring | 3 | 0.07% | 1 | 5.00% |
Thomas Gleixner | 2 | 0.04% | 1 | 5.00% |
Thierry Reding | 1 | 0.02% | 1 | 5.00% |
Alex Williamson | 1 | 0.02% | 1 | 5.00% |
Total | 4611 | 20 |
// 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 <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 phys_addr_t get_phys_addr(struct fsl_dma_domain *dma_domain, dma_addr_t iova) { u32 win_cnt = dma_domain->win_cnt; struct dma_window *win_ptr = &dma_domain->win_arr[0]; struct iommu_domain_geometry *geom; geom = &dma_domain->iommu_domain.geometry; if (!win_cnt || !dma_domain->geom_size) { pr_debug("Number of windows/geometry not configured for the domain\n"); return 0; } if (win_cnt > 1) { u64 subwin_size; dma_addr_t subwin_iova; u32 wnd; subwin_size = dma_domain->geom_size >> ilog2(win_cnt); subwin_iova = iova & ~(subwin_size - 1); wnd = (subwin_iova - geom->aperture_start) >> ilog2(subwin_size); win_ptr = &dma_domain->win_arr[wnd]; } if (win_ptr->valid) return win_ptr->paddr + (iova & (win_ptr->size - 1)); return 0; } static int map_subwins(int liodn, struct fsl_dma_domain *dma_domain) { struct dma_window *sub_win_ptr = &dma_domain->win_arr[0]; int i, ret; unsigned long rpn, flags; for (i = 0; i < dma_domain->win_cnt; i++) { if (sub_win_ptr[i].valid) { rpn = sub_win_ptr[i].paddr >> PAMU_PAGE_SHIFT; spin_lock_irqsave(&iommu_lock, flags); ret = pamu_config_spaace(liodn, dma_domain->win_cnt, i, sub_win_ptr[i].size, ~(u32)0, rpn, dma_domain->snoop_id, dma_domain->stash_id, (i > 0) ? 1 : 0, sub_win_ptr[i].prot); spin_unlock_irqrestore(&iommu_lock, flags); if (ret) { pr_debug("SPAACE configuration failed for liodn %d\n", liodn); return ret; } } } return ret; } static int map_win(int liodn, struct fsl_dma_domain *dma_domain) { int ret; struct dma_window *wnd = &dma_domain->win_arr[0]; phys_addr_t wnd_addr = dma_domain->iommu_domain.geometry.aperture_start; unsigned long flags; spin_lock_irqsave(&iommu_lock, flags); ret = pamu_config_ppaace(liodn, wnd_addr, wnd->size, ~(u32)0, wnd->paddr >> PAMU_PAGE_SHIFT, dma_domain->snoop_id, dma_domain->stash_id, 0, wnd->prot); spin_unlock_irqrestore(&iommu_lock, flags); if (ret) pr_debug("PAACE configuration failed for liodn %d\n", liodn); return ret; } /* Map the DMA window corresponding to the LIODN */ static int map_liodn(int liodn, struct fsl_dma_domain *dma_domain) { if (dma_domain->win_cnt > 1) return map_subwins(liodn, dma_domain); else return map_win(liodn, dma_domain); } /* Update window/subwindow mapping for the LIODN */ static int update_liodn(int liodn, struct fsl_dma_domain *dma_domain, u32 wnd_nr) { int ret; struct dma_window *wnd = &dma_domain->win_arr[wnd_nr]; unsigned long flags; spin_lock_irqsave(&iommu_lock, flags); if (dma_domain->win_cnt > 1) { ret = pamu_config_spaace(liodn, dma_domain->win_cnt, wnd_nr, wnd->size, ~(u32)0, wnd->paddr >> PAMU_PAGE_SHIFT, dma_domain->snoop_id, dma_domain->stash_id, (wnd_nr > 0) ? 1 : 0, wnd->prot); if (ret) pr_debug("Subwindow reconfiguration failed for liodn %d\n", liodn); } else { phys_addr_t wnd_addr; wnd_addr = dma_domain->iommu_domain.geometry.aperture_start; ret = pamu_config_ppaace(liodn, wnd_addr, wnd->size, ~(u32)0, wnd->paddr >> PAMU_PAGE_SHIFT, dma_domain->snoop_id, dma_domain->stash_id, 0, wnd->prot); if (ret) pr_debug("Window reconfiguration failed for liodn %d\n", liodn); } spin_unlock_irqrestore(&iommu_lock, flags); return ret; } static int update_liodn_stash(int liodn, struct fsl_dma_domain *dma_domain, u32 val) { int ret = 0, i; unsigned long flags; spin_lock_irqsave(&iommu_lock, flags); if (!dma_domain->win_arr) { pr_debug("Windows not configured, stash destination update failed for liodn %d\n", liodn); spin_unlock_irqrestore(&iommu_lock, flags); return -EINVAL; } for (i = 0; i < dma_domain->win_cnt; i++) { ret = pamu_update_paace_stash(liodn, i, val); if (ret) { pr_debug("Failed to update SPAACE %d field for liodn %d\n ", i, 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(int liodn, struct device *dev, struct fsl_dma_domain *dma_domain, struct iommu_domain_geometry *geom_attr, u32 win_cnt) { phys_addr_t window_addr, window_size; phys_addr_t subwin_size; int ret = 0, i; u32 omi_index = ~(u32)0; unsigned long flags; /* * 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); window_addr = geom_attr->aperture_start; window_size = dma_domain->geom_size; spin_lock_irqsave(&iommu_lock, flags); ret = pamu_disable_liodn(liodn); if (!ret) ret = pamu_config_ppaace(liodn, window_addr, window_size, omi_index, 0, dma_domain->snoop_id, dma_domain->stash_id, win_cnt, 0); spin_unlock_irqrestore(&iommu_lock, flags); if (ret) { pr_debug("PAACE configuration failed for liodn %d, win_cnt =%d\n", liodn, win_cnt); return ret; } if (win_cnt > 1) { subwin_size = window_size >> ilog2(win_cnt); for (i = 0; i < win_cnt; i++) { spin_lock_irqsave(&iommu_lock, flags); ret = pamu_disable_spaace(liodn, i); if (!ret) ret = pamu_config_spaace(liodn, win_cnt, i, subwin_size, omi_index, 0, dma_domain->snoop_id, dma_domain->stash_id, 0, 0); spin_unlock_irqrestore(&iommu_lock, flags); if (ret) { pr_debug("SPAACE configuration failed for liodn %d\n", liodn); return ret; } } } return ret; } static int check_size(u64 size, dma_addr_t iova) { /* * Size must be a power of two and at least be equal * to PAMU page size. */ if ((size & (size - 1)) || size < PAMU_PAGE_SIZE) { pr_debug("Size too small or not a power of two\n"); return -EINVAL; } /* iova must be page size aligned */ if (iova & (size - 1)) { pr_debug("Address is not aligned with window size\n"); return -EINVAL; } return 0; } static struct fsl_dma_domain *iommu_alloc_dma_domain(void) { struct fsl_dma_domain *domain; domain = kmem_cache_zalloc(fsl_pamu_domain_cache, GFP_KERNEL); if (!domain) return NULL; domain->stash_id = ~(u32)0; domain->snoop_id = ~(u32)0; domain->win_cnt = pamu_get_max_subwin_cnt(); domain->geom_size = 0; INIT_LIST_HEAD(&domain->devices); spin_lock_init(&domain->domain_lock); return domain; } static void remove_device_ref(struct device_domain_info *info, u32 win_cnt) { unsigned long flags; list_del(&info->link); spin_lock_irqsave(&iommu_lock, flags); if (win_cnt > 1) pamu_free_subwins(info->liodn); pamu_disable_liodn(info->liodn); spin_unlock_irqrestore(&iommu_lock, flags); spin_lock_irqsave(&device_domain_lock, flags); info->dev->archdata.iommu_domain = 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, dma_domain->win_cnt); } 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->archdata.iommu_domain; 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->archdata.iommu_domain) dev->archdata.iommu_domain = 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) { struct fsl_dma_domain *dma_domain = to_fsl_dma_domain(domain); if (iova < domain->geometry.aperture_start || iova > domain->geometry.aperture_end) return 0; return get_phys_addr(dma_domain, iova); } static bool fsl_pamu_capable(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); dma_domain->enabled = 0; dma_domain->mapped = 0; 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 = iommu_alloc_dma_domain(); if (!dma_domain) { pr_debug("dma_domain allocation failed\n"); return NULL; } /* defaul 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; } /* Configure geometry settings for all LIODNs associated with domain */ static int pamu_set_domain_geometry(struct fsl_dma_domain *dma_domain, struct iommu_domain_geometry *geom_attr, u32 win_cnt) { struct device_domain_info *info; int ret = 0; list_for_each_entry(info, &dma_domain->devices, link) { ret = pamu_set_liodn(info->liodn, info->dev, dma_domain, geom_attr, win_cnt); if (ret) break; } return ret; } /* 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; } /* Update domain mappings for all LIODNs associated with the domain */ static int update_domain_mapping(struct fsl_dma_domain *dma_domain, u32 wnd_nr) { struct device_domain_info *info; int ret = 0; list_for_each_entry(info, &dma_domain->devices, link) { ret = update_liodn(info->liodn, dma_domain, wnd_nr); if (ret) break; } return ret; } static int disable_domain_win(struct fsl_dma_domain *dma_domain, u32 wnd_nr) { struct device_domain_info *info; int ret = 0; list_for_each_entry(info, &dma_domain->devices, link) { if (dma_domain->win_cnt == 1 && dma_domain->enabled) { ret = pamu_disable_liodn(info->liodn); if (!ret) dma_domain->enabled = 0; } else { ret = pamu_disable_spaace(info->liodn, wnd_nr); } } return ret; } static void fsl_pamu_window_disable(struct iommu_domain *domain, u32 wnd_nr) { struct fsl_dma_domain *dma_domain = to_fsl_dma_domain(domain); unsigned long flags; int ret; spin_lock_irqsave(&dma_domain->domain_lock, flags); if (!dma_domain->win_arr) { pr_debug("Number of windows not configured\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return; } if (wnd_nr >= dma_domain->win_cnt) { pr_debug("Invalid window index\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return; } if (dma_domain->win_arr[wnd_nr].valid) { ret = disable_domain_win(dma_domain, wnd_nr); if (!ret) { dma_domain->win_arr[wnd_nr].valid = 0; dma_domain->mapped--; } } spin_unlock_irqrestore(&dma_domain->domain_lock, flags); } static int fsl_pamu_window_enable(struct iommu_domain *domain, u32 wnd_nr, phys_addr_t paddr, u64 size, int prot) { struct fsl_dma_domain *dma_domain = to_fsl_dma_domain(domain); struct dma_window *wnd; int pamu_prot = 0; int ret; unsigned long flags; u64 win_size; if (prot & IOMMU_READ) pamu_prot |= PAACE_AP_PERMS_QUERY; if (prot & IOMMU_WRITE) pamu_prot |= PAACE_AP_PERMS_UPDATE; spin_lock_irqsave(&dma_domain->domain_lock, flags); if (!dma_domain->win_arr) { pr_debug("Number of windows not configured\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return -ENODEV; } if (wnd_nr >= dma_domain->win_cnt) { pr_debug("Invalid window index\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return -EINVAL; } win_size = dma_domain->geom_size >> ilog2(dma_domain->win_cnt); if (size > win_size) { pr_debug("Invalid window size\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return -EINVAL; } if (dma_domain->win_cnt == 1) { if (dma_domain->enabled) { pr_debug("Disable the window before updating the mapping\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return -EBUSY; } ret = check_size(size, domain->geometry.aperture_start); if (ret) { pr_debug("Aperture start not aligned to the size\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return -EINVAL; } } wnd = &dma_domain->win_arr[wnd_nr]; if (!wnd->valid) { wnd->paddr = paddr; wnd->size = size; wnd->prot = pamu_prot; ret = update_domain_mapping(dma_domain, wnd_nr); if (!ret) { wnd->valid = 1; dma_domain->mapped++; } } else { pr_debug("Disable the window before updating the mapping\n"); ret = -EBUSY; } spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return ret; } /* * Attach the LIODN to the DMA domain and configure the geometry * and window mappings. */ static int handle_attach_device(struct fsl_dma_domain *dma_domain, struct device *dev, const u32 *liodn, int num) { unsigned long flags; struct iommu_domain *domain = &dma_domain->iommu_domain; int ret = 0; int i; spin_lock_irqsave(&dma_domain->domain_lock, flags); for (i = 0; i < num; 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 = -EINVAL; break; } attach_device(dma_domain, liodn[i], dev); /* * Check if geometry has already been configured * for the domain. If yes, set the geometry for * the LIODN. */ if (dma_domain->win_arr) { u32 win_cnt = dma_domain->win_cnt > 1 ? dma_domain->win_cnt : 0; ret = pamu_set_liodn(liodn[i], dev, dma_domain, &domain->geometry, win_cnt); if (ret) break; if (dma_domain->mapped) { /* * Create window/subwindow mapping for * the LIODN. */ ret = map_liodn(liodn[i], dma_domain); if (ret) break; } } } spin_unlock_irqrestore(&dma_domain->domain_lock, flags); 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); const u32 *liodn; u32 liodn_cnt; int len, ret = 0; 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) { liodn_cnt = len / sizeof(u32); ret = handle_attach_device(dma_domain, dev, liodn, liodn_cnt); } else { pr_debug("missing fsl,liodn property at %pOF\n", dev->of_node); ret = -EINVAL; } return ret; } static void fsl_pamu_detach_device(struct iommu_domain *domain, struct device *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); } static int configure_domain_geometry(struct iommu_domain *domain, void *data) { struct iommu_domain_geometry *geom_attr = data; struct fsl_dma_domain *dma_domain = to_fsl_dma_domain(domain); dma_addr_t geom_size; unsigned long flags; geom_size = geom_attr->aperture_end - geom_attr->aperture_start + 1; /* * Sanity check the geometry size. Also, we do not support * DMA outside of the geometry. */ if (check_size(geom_size, geom_attr->aperture_start) || !geom_attr->force_aperture) { pr_debug("Invalid PAMU geometry attributes\n"); return -EINVAL; } spin_lock_irqsave(&dma_domain->domain_lock, flags); if (dma_domain->enabled) { pr_debug("Can't set geometry attributes as domain is active\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return -EBUSY; } /* Copy the domain geometry information */ memcpy(&domain->geometry, geom_attr, sizeof(struct iommu_domain_geometry)); dma_domain->geom_size = geom_size; spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return 0; } /* Set the domain stash attribute */ static int configure_domain_stash(struct fsl_dma_domain *dma_domain, void *data) { struct pamu_stash_attribute *stash_attr = data; unsigned long flags; int ret; spin_lock_irqsave(&dma_domain->domain_lock, flags); memcpy(&dma_domain->dma_stash, stash_attr, sizeof(struct pamu_stash_attribute)); dma_domain->stash_id = get_stash_id(stash_attr->cache, stash_attr->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; } /* Configure domain dma state i.e. enable/disable DMA */ static int configure_domain_dma_state(struct fsl_dma_domain *dma_domain, bool enable) { struct device_domain_info *info; unsigned long flags; int ret; spin_lock_irqsave(&dma_domain->domain_lock, flags); if (enable && !dma_domain->mapped) { pr_debug("Can't enable DMA domain without valid mapping\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return -ENODEV; } dma_domain->enabled = enable; list_for_each_entry(info, &dma_domain->devices, link) { ret = (enable) ? pamu_enable_liodn(info->liodn) : pamu_disable_liodn(info->liodn); if (ret) pr_debug("Unable to set dma state for liodn %d", info->liodn); } spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return 0; } static int fsl_pamu_set_windows(struct iommu_domain *domain, u32 w_count) { struct fsl_dma_domain *dma_domain = to_fsl_dma_domain(domain); unsigned long flags; int ret; spin_lock_irqsave(&dma_domain->domain_lock, flags); /* Ensure domain is inactive i.e. DMA should be disabled for the domain */ if (dma_domain->enabled) { pr_debug("Can't set geometry attributes as domain is active\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return -EBUSY; } /* Ensure that the geometry has been set for the domain */ if (!dma_domain->geom_size) { pr_debug("Please configure geometry before setting the number of windows\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return -EINVAL; } /* * Ensure we have valid window count i.e. it should be less than * maximum permissible limit and should be a power of two. */ if (w_count > pamu_get_max_subwin_cnt() || !is_power_of_2(w_count)) { pr_debug("Invalid window count\n"); spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return -EINVAL; } ret = pamu_set_domain_geometry(dma_domain, &domain->geometry, w_count > 1 ? w_count : 0); if (!ret) { kfree(dma_domain->win_arr); dma_domain->win_arr = kcalloc(w_count, sizeof(*dma_domain->win_arr), GFP_ATOMIC); if (!dma_domain->win_arr) { spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return -ENOMEM; } dma_domain->win_cnt = w_count; } spin_unlock_irqrestore(&dma_domain->domain_lock, flags); return ret; } static int fsl_pamu_set_domain_attr(struct iommu_domain *domain, enum iommu_attr attr_type, void *data) { struct fsl_dma_domain *dma_domain = to_fsl_dma_domain(domain); int ret = 0; switch (attr_type) { case DOMAIN_ATTR_GEOMETRY: ret = configure_domain_geometry(domain, data); break; case DOMAIN_ATTR_FSL_PAMU_STASH: ret = configure_domain_stash(dma_domain, data); break; case DOMAIN_ATTR_FSL_PAMU_ENABLE: ret = configure_domain_dma_state(dma_domain, *(int *)data); break; case DOMAIN_ATTR_WINDOWS: ret = fsl_pamu_set_windows(domain, *(u32 *)data); break; default: pr_debug("Unsupported attribute type\n"); ret = -EINVAL; break; } return ret; } static int fsl_pamu_get_domain_attr(struct iommu_domain *domain, enum iommu_attr attr_type, void *data) { struct fsl_dma_domain *dma_domain = to_fsl_dma_domain(domain); int ret = 0; switch (attr_type) { case DOMAIN_ATTR_FSL_PAMU_STASH: memcpy(data, &dma_domain->dma_stash, sizeof(struct pamu_stash_attribute)); break; case DOMAIN_ATTR_FSL_PAMU_ENABLE: *(int *)data = dma_domain->enabled; break; case DOMAIN_ATTR_FSL_PAMUV1: *(int *)data = DOMAIN_ATTR_FSL_PAMUV1; break; case DOMAIN_ATTR_WINDOWS: *(u32 *)data = dma_domain->win_cnt; break; default: pr_debug("Unsupported attribute type\n"); ret = -EINVAL; break; } return ret; } static struct iommu_group *get_device_iommu_group(struct device *dev) { struct iommu_group *group; group = iommu_group_get(dev); if (!group) group = iommu_group_alloc(); return group; } 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; } /* Get iommu group information from peer devices or devices on the parent bus */ static struct iommu_group *get_shared_pci_device_group(struct pci_dev *pdev) { struct pci_dev *tmp; struct iommu_group *group; struct pci_bus *bus = pdev->bus; /* * Traverese the pci bus device list to get * the shared iommu group. */ while (bus) { list_for_each_entry(tmp, &bus->devices, bus_list) { if (tmp == pdev) continue; group = iommu_group_get(&tmp->dev); if (group) return group; } bus = bus->parent; } return NULL; } static struct iommu_group *get_pci_device_group(struct pci_dev *pdev) { struct pci_controller *pci_ctl; bool pci_endpt_partitioning; struct iommu_group *group = NULL; pci_ctl = pci_bus_to_host(pdev->bus); pci_endpt_partitioning = check_pci_ctl_endpt_part(pci_ctl); /* We can partition PCIe devices so assign device group to the device */ if (pci_endpt_partitioning) { group = pci_device_group(&pdev->dev); /* * PCIe controller is not a paritionable entity * free the controller device iommu_group. */ if (pci_ctl->parent->iommu_group) iommu_group_remove_device(pci_ctl->parent); } else { /* * All devices connected to the controller will share the * PCI controllers device group. If this is the first * device to be probed for the pci controller, copy the * device group information from the PCI controller device * node and remove the PCI controller iommu group. * For subsequent devices, the iommu group information can * be obtained from sibling devices (i.e. from the bus_devices * link list). */ if (pci_ctl->parent->iommu_group) { group = get_device_iommu_group(pci_ctl->parent); iommu_group_remove_device(pci_ctl->parent); } else { group = get_shared_pci_device_group(pdev); } } if (!group) group = ERR_PTR(-ENODEV); return group; } static struct iommu_group *fsl_pamu_device_group(struct device *dev) { struct iommu_group *group = ERR_PTR(-ENODEV); int len; /* * For platform devices we allocate a separate group for * each of the devices. */ if (dev_is_pci(dev)) group = get_pci_device_group(to_pci_dev(dev)); else if (of_get_property(dev->of_node, "fsl,liodn", &len)) group = get_device_iommu_group(dev); return group; } static int fsl_pamu_add_device(struct device *dev) { struct iommu_group *group; group = iommu_group_get_for_dev(dev); if (IS_ERR(group)) return PTR_ERR(group); iommu_group_put(group); iommu_device_link(&pamu_iommu, dev); return 0; } static void fsl_pamu_remove_device(struct device *dev) { iommu_device_unlink(&pamu_iommu, dev); iommu_group_remove_device(dev); } static const struct iommu_ops fsl_pamu_ops = { .capable = fsl_pamu_capable, .domain_alloc = fsl_pamu_domain_alloc, .domain_free = fsl_pamu_domain_free, .attach_dev = fsl_pamu_attach_device, .detach_dev = fsl_pamu_detach_device, .domain_window_enable = fsl_pamu_window_enable, .domain_window_disable = fsl_pamu_window_disable, .iova_to_phys = fsl_pamu_iova_to_phys, .domain_set_attr = fsl_pamu_set_domain_attr, .domain_get_attr = fsl_pamu_get_domain_attr, .add_device = fsl_pamu_add_device, .remove_device = fsl_pamu_remove_device, .device_group = fsl_pamu_device_group, }; 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; iommu_device_set_ops(&pamu_iommu, &fsl_pamu_ops); ret = iommu_device_register(&pamu_iommu); if (ret) { iommu_device_sysfs_remove(&pamu_iommu); pr_err("Can't register iommu device\n"); return ret; } bus_set_iommu(&platform_bus_type, &fsl_pamu_ops); bus_set_iommu(&pci_bus_type, &fsl_pamu_ops); return ret; }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1