|  | // 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); | 
|  | 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, 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_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) | 
|  | { | 
|  | 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 struct iommu_device *fsl_pamu_probe_device(struct device *dev) | 
|  | { | 
|  | return &pamu_iommu; | 
|  | } | 
|  |  | 
|  | static void fsl_pamu_release_device(struct 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, | 
|  | .probe_device	= fsl_pamu_probe_device, | 
|  | .release_device	= fsl_pamu_release_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; | 
|  | } |