| /* |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License, version 2, as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| * |
| * Copyright (C) 2013 Freescale Semiconductor, Inc. |
| * Author: Varun Sethi <varun.sethi@freescale.com> |
| * |
| */ |
| |
| #define pr_fmt(fmt) "fsl-pamu-domain: %s: " fmt, __func__ |
| |
| #include <linux/init.h> |
| #include <linux/iommu.h> |
| #include <linux/notifier.h> |
| #include <linux/slab.h> |
| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/mm.h> |
| #include <linux/interrupt.h> |
| #include <linux/device.h> |
| #include <linux/of_platform.h> |
| #include <linux/bootmem.h> |
| #include <linux/err.h> |
| #include <asm/io.h> |
| #include <asm/bitops.h> |
| |
| #include <asm/pci-bridge.h> |
| #include <sysdev/fsl_pci.h> |
| |
| #include "fsl_pamu_domain.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); |
| |
| 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("PAMU 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("PAMU 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("PAMU 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("PAMU 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("%s: size too small or not a power of two\n", __func__); |
| return -EINVAL; |
| } |
| |
| /* iova must be page size aligned*/ |
| if (iova & (size - 1)) { |
| pr_debug("%s: address is not aligned with window size\n", __func__); |
| 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 = domain->priv; |
| |
| if ((iova < domain->geometry.aperture_start) || |
| iova > (domain->geometry.aperture_end)) |
| return 0; |
| |
| return get_phys_addr(dma_domain, iova); |
| } |
| |
| static int fsl_pamu_domain_has_cap(struct iommu_domain *domain, |
| unsigned long cap) |
| { |
| return cap == IOMMU_CAP_CACHE_COHERENCY; |
| } |
| |
| static void fsl_pamu_domain_destroy(struct iommu_domain *domain) |
| { |
| struct fsl_dma_domain *dma_domain = domain->priv; |
| |
| domain->priv = NULL; |
| |
| /* 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 int fsl_pamu_domain_init(struct iommu_domain *domain) |
| { |
| struct fsl_dma_domain *dma_domain; |
| |
| dma_domain = iommu_alloc_dma_domain(); |
| if (!dma_domain) { |
| pr_debug("dma_domain allocation failed\n"); |
| return -ENOMEM; |
| } |
| domain->priv = dma_domain; |
| dma_domain->iommu_domain = domain; |
| /* defaul geometry 64 GB i.e. maximum system address */ |
| domain->geometry.aperture_start = 0; |
| domain->geometry.aperture_end = (1ULL << 36) - 1; |
| domain->geometry.force_aperture = true; |
| |
| return 0; |
| } |
| |
| /* 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 = domain->priv; |
| 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 = domain->priv; |
| 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 %s\n", |
| liodn[i], dev->of_node->full_name); |
| 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 = domain->priv; |
| 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 %s\n", |
| dev->of_node->full_name); |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static void fsl_pamu_detach_device(struct iommu_domain *domain, |
| struct device *dev) |
| { |
| struct fsl_dma_domain *dma_domain = domain->priv; |
| 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 %s\n", |
| dev->of_node->full_name); |
| } |
| |
| static int configure_domain_geometry(struct iommu_domain *domain, void *data) |
| { |
| struct iommu_domain_geometry *geom_attr = data; |
| struct fsl_dma_domain *dma_domain = domain->priv; |
| 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_domain_attr(struct iommu_domain *domain, |
| enum iommu_attr attr_type, void *data) |
| { |
| struct fsl_dma_domain *dma_domain = domain->priv; |
| 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; |
| 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 = domain->priv; |
| int ret = 0; |
| |
| |
| switch (attr_type) { |
| case DOMAIN_ATTR_FSL_PAMU_STASH: |
| memcpy((struct pamu_stash_attribute *) 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; |
| 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*/ |
| if (version >= 0x204) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* 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_partioning; |
| struct iommu_group *group = NULL; |
| |
| pci_ctl = pci_bus_to_host(pdev->bus); |
| pci_endpt_partioning = check_pci_ctl_endpt_part(pci_ctl); |
| /* We can partition PCIe devices so assign device group to the device */ |
| if (pci_endpt_partioning) { |
| group = iommu_group_get_for_dev(&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 int fsl_pamu_add_device(struct device *dev) |
| { |
| struct iommu_group *group = ERR_PTR(-ENODEV); |
| struct pci_dev *pdev; |
| const u32 *prop; |
| int ret = 0, len; |
| |
| /* |
| * For platform devices we allocate a separate group for |
| * each of the devices. |
| */ |
| if (dev_is_pci(dev)) { |
| pdev = to_pci_dev(dev); |
| /* Don't create device groups for virtual PCI bridges */ |
| if (pdev->subordinate) |
| return 0; |
| |
| group = get_pci_device_group(pdev); |
| |
| } else { |
| prop = of_get_property(dev->of_node, "fsl,liodn", &len); |
| if (prop) |
| group = get_device_iommu_group(dev); |
| } |
| |
| if (IS_ERR(group)) |
| return PTR_ERR(group); |
| |
| /* |
| * Check if device has already been added to an iommu group. |
| * Group could have already been created for a PCI device in |
| * the iommu_group_get_for_dev path. |
| */ |
| if (!dev->iommu_group) |
| ret = iommu_group_add_device(group, dev); |
| |
| iommu_group_put(group); |
| return ret; |
| } |
| |
| static void fsl_pamu_remove_device(struct device *dev) |
| { |
| iommu_group_remove_device(dev); |
| } |
| |
| static int fsl_pamu_set_windows(struct iommu_domain *domain, u32 w_count) |
| { |
| struct fsl_dma_domain *dma_domain = domain->priv; |
| 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 = kzalloc(sizeof(struct dma_window) * |
| w_count, 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 u32 fsl_pamu_get_windows(struct iommu_domain *domain) |
| { |
| struct fsl_dma_domain *dma_domain = domain->priv; |
| |
| return dma_domain->win_cnt; |
| } |
| |
| static const struct iommu_ops fsl_pamu_ops = { |
| .domain_init = fsl_pamu_domain_init, |
| .domain_destroy = fsl_pamu_domain_destroy, |
| .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, |
| .domain_get_windows = fsl_pamu_get_windows, |
| .domain_set_windows = fsl_pamu_set_windows, |
| .iova_to_phys = fsl_pamu_iova_to_phys, |
| .domain_has_cap = fsl_pamu_domain_has_cap, |
| .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, |
| }; |
| |
| int pamu_domain_init(void) |
| { |
| int ret = 0; |
| |
| ret = iommu_init_mempool(); |
| if (ret) |
| return ret; |
| |
| bus_set_iommu(&platform_bus_type, &fsl_pamu_ops); |
| bus_set_iommu(&pci_bus_type, &fsl_pamu_ops); |
| |
| return ret; |
| } |