| /* linux/drivers/iommu/exynos_iovmm.c |
| * |
| * Copyright (c) 2011-2012 Samsung Electronics Co., Ltd. |
| * http://www.samsung.com |
| * |
| * 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. |
| */ |
| |
| #ifdef CONFIG_EXYNOS_IOMMU_DEBUG |
| #define DEBUG |
| #endif |
| |
| #include <linux/kernel.h> |
| #include <linux/hardirq.h> |
| #include <linux/slab.h> |
| #include <linux/scatterlist.h> |
| #include <linux/err.h> |
| #include <linux/debugfs.h> |
| #include <linux/delay.h> |
| |
| #include <linux/exynos_iovmm.h> |
| |
| #include "exynos-iommu.h" |
| |
| #define sg_physically_continuous(sg) (sg_next(sg) == NULL) |
| |
| /* alloc_iovm_region - Allocate IO virtual memory region |
| * vmm: virtual memory allocator |
| * size: total size to allocate vm region from @vmm. |
| * align: alignment constraints of the allocated virtual address |
| * max_align: maximum alignment of allocated virtual address. allocated address |
| * does not need to satisfy larger alignment than max_align. |
| * section_offset: page size-aligned offset of iova start address within an 1MB |
| * boundary. The caller of alloc_iovm_region will obtain the |
| * allocated iova + section_offset. This is provided just for the |
| * physically contiguous memory. |
| * page_offset: must be smaller than PAGE_SIZE. Just a valut to be added to the |
| * allocated virtual address. This does not effect to the allocaded size |
| * and address. |
| * |
| * This function returns allocated IO virtual address that satisfies the given |
| * constraints: the caller will get the allocated virtual address plus |
| * (section_offset + page_offset). Returns 0 if this function is not able |
| * to allocate IO virtual memory. |
| */ |
| static dma_addr_t alloc_iovm_region(struct exynos_iovmm *vmm, size_t size, |
| size_t section_offset, |
| off_t page_offset) |
| { |
| u32 index = 0; |
| u32 vstart; |
| u32 vsize; |
| unsigned long end, i; |
| struct exynos_vm_region *region; |
| size_t align = SZ_1M; |
| |
| BUG_ON(page_offset >= PAGE_SIZE); |
| |
| /* To avoid allocating prefetched iovm region */ |
| vsize = (ALIGN(size + SZ_128K, SZ_128K) + section_offset) >> PAGE_SHIFT; |
| align >>= PAGE_SHIFT; |
| section_offset >>= PAGE_SHIFT; |
| |
| spin_lock(&vmm->bitmap_lock); |
| again: |
| index = find_next_zero_bit(vmm->vm_map, |
| IOVM_NUM_PAGES(vmm->iovm_size), index); |
| |
| if (align) { |
| index = ALIGN(index, align); |
| if (index >= IOVM_NUM_PAGES(vmm->iovm_size)) { |
| spin_unlock(&vmm->bitmap_lock); |
| return 0; |
| } |
| |
| if (test_bit(index, vmm->vm_map)) |
| goto again; |
| } |
| |
| end = index + vsize; |
| |
| if (end >= IOVM_NUM_PAGES(vmm->iovm_size)) { |
| spin_unlock(&vmm->bitmap_lock); |
| return 0; |
| } |
| |
| i = find_next_bit(vmm->vm_map, end, index); |
| if (i < end) { |
| index = i + 1; |
| goto again; |
| } |
| |
| bitmap_set(vmm->vm_map, index, vsize); |
| |
| spin_unlock(&vmm->bitmap_lock); |
| |
| vstart = (index << PAGE_SHIFT) + vmm->iova_start + page_offset; |
| |
| region = kmalloc(sizeof(*region), GFP_KERNEL); |
| if (unlikely(!region)) { |
| spin_lock(&vmm->bitmap_lock); |
| bitmap_clear(vmm->vm_map, index, vsize); |
| spin_unlock(&vmm->bitmap_lock); |
| return 0; |
| } |
| |
| INIT_LIST_HEAD(®ion->node); |
| region->start = vstart; |
| region->size = vsize << PAGE_SHIFT; |
| region->dummy_size = region->size - size; |
| region->section_off = (unsigned int)(section_offset << PAGE_SHIFT); |
| |
| spin_lock(&vmm->vmlist_lock); |
| list_add_tail(®ion->node, &vmm->regions_list); |
| vmm->allocated_size += region->size; |
| vmm->num_areas++; |
| vmm->num_map++; |
| spin_unlock(&vmm->vmlist_lock); |
| |
| return region->start + region->section_off; |
| } |
| |
| struct exynos_vm_region *find_iovm_region(struct exynos_iovmm *vmm, |
| dma_addr_t iova) |
| { |
| struct exynos_vm_region *region; |
| |
| spin_lock(&vmm->vmlist_lock); |
| |
| list_for_each_entry(region, &vmm->regions_list, node) { |
| if (region->start <= iova && |
| (region->start + region->size) > iova) { |
| spin_unlock(&vmm->vmlist_lock); |
| return region; |
| } |
| } |
| |
| spin_unlock(&vmm->vmlist_lock); |
| |
| return NULL; |
| } |
| |
| static struct exynos_vm_region *remove_iovm_region(struct exynos_iovmm *vmm, |
| dma_addr_t iova) |
| { |
| struct exynos_vm_region *region; |
| |
| spin_lock(&vmm->vmlist_lock); |
| |
| list_for_each_entry(region, &vmm->regions_list, node) { |
| if (region->start + region->section_off == iova) { |
| list_del(®ion->node); |
| vmm->allocated_size -= region->size; |
| vmm->num_areas--; |
| vmm->num_unmap++; |
| spin_unlock(&vmm->vmlist_lock); |
| return region; |
| } |
| } |
| |
| spin_unlock(&vmm->vmlist_lock); |
| |
| return NULL; |
| } |
| |
| static void free_iovm_region(struct exynos_iovmm *vmm, |
| struct exynos_vm_region *region) |
| { |
| if (!region) |
| return; |
| |
| spin_lock(&vmm->bitmap_lock); |
| bitmap_clear(vmm->vm_map, |
| (region->start - vmm->iova_start) >> PAGE_SHIFT, |
| region->size >> PAGE_SHIFT); |
| spin_unlock(&vmm->bitmap_lock); |
| |
| SYSMMU_EVENT_LOG_IOVMM_UNMAP(IOVMM_TO_LOG(vmm), |
| region->start, region->start + region->size); |
| |
| kfree(region); |
| } |
| |
| static dma_addr_t add_iovm_region(struct exynos_iovmm *vmm, |
| dma_addr_t start, size_t size) |
| { |
| struct exynos_vm_region *region, *pos; |
| |
| region = kzalloc(sizeof(*region), GFP_KERNEL); |
| if (!region) |
| return 0; |
| |
| INIT_LIST_HEAD(®ion->node); |
| region->start = start; |
| region->size = (u32)size; |
| |
| spin_lock(&vmm->vmlist_lock); |
| |
| list_for_each_entry(pos, &vmm->regions_list, node) { |
| if ((start < (pos->start + pos->size)) && |
| ((start + size) > pos->start)) { |
| spin_unlock(&vmm->vmlist_lock); |
| kfree(region); |
| return 0; |
| } |
| } |
| |
| list_add(®ion->node, &vmm->regions_list); |
| |
| spin_unlock(&vmm->vmlist_lock); |
| |
| return start; |
| } |
| |
| static void show_iovm_regions(struct exynos_iovmm *vmm) |
| { |
| struct exynos_vm_region *pos; |
| |
| pr_err("LISTING IOVMM REGIONS...\n"); |
| spin_lock(&vmm->vmlist_lock); |
| list_for_each_entry(pos, &vmm->regions_list, node) { |
| pr_err("REGION: %#x (SIZE: %#x, +[%#x, %#x])\n", |
| pos->start, pos->size, |
| pos->section_off, pos->dummy_size); |
| } |
| spin_unlock(&vmm->vmlist_lock); |
| pr_err("END OF LISTING IOVMM REGIONS...\n"); |
| } |
| |
| int iovmm_activate(struct device *dev) |
| { |
| struct exynos_iovmm *vmm = exynos_get_iovmm(dev); |
| |
| if (!vmm) { |
| dev_err(dev, "%s: IOVMM not found\n", __func__); |
| return -EINVAL; |
| } |
| |
| return iommu_attach_device(vmm->domain, dev); |
| } |
| |
| void iovmm_deactivate(struct device *dev) |
| { |
| struct exynos_iovmm *vmm = exynos_get_iovmm(dev); |
| |
| if (!vmm) { |
| dev_err(dev, "%s: IOVMM not found\n", __func__); |
| return; |
| } |
| |
| iommu_detach_device(vmm->domain, dev); |
| } |
| |
| struct iommu_domain *get_domain_from_dev(struct device *dev) |
| { |
| struct exynos_iovmm *vmm = exynos_get_iovmm(dev); |
| |
| if (!vmm) { |
| dev_err(dev, "%s: IOVMM not found\n", __func__); |
| return NULL; |
| } |
| |
| return vmm->domain; |
| } |
| |
| /* iovmm_map - allocate and map IO virtual memory for the given device |
| * dev: device that has IO virtual address space managed by IOVMM |
| * sg: list of physically contiguous memory chunks. The preceding chunk needs to |
| * be larger than the following chunks in sg for efficient mapping and |
| * performance. If elements of sg are more than one, physical address of |
| * each chunk needs to be aligned by its size for efficent mapping and TLB |
| * utilization. |
| * offset: offset in bytes to be mapped and accessed by dev. |
| * size: size in bytes to be mapped and accessed by dev. |
| * |
| * This function allocates IO virtual memory for the given device and maps the |
| * given physical memory conveyed by sg into the allocated IO memory region. |
| * Returns allocated IO virtual address if it allocates and maps successfull. |
| * Otherwise, minus error number. Caller must check if the return value of this |
| * function with IS_ERR_VALUE(). |
| */ |
| dma_addr_t iovmm_map(struct device *dev, struct scatterlist *sg, off_t offset, |
| size_t size, enum dma_data_direction direction, int prot) |
| { |
| off_t start_off; |
| dma_addr_t addr, start = 0; |
| size_t mapped_size = 0; |
| struct exynos_iovmm *vmm = exynos_get_iovmm(dev); |
| size_t section_offset = 0; /* section offset of contig. mem */ |
| int ret = 0; |
| int idx; |
| struct scatterlist *tsg; |
| struct exynos_vm_region *region; |
| |
| if (vmm == NULL) { |
| dev_err(dev, "%s: IOVMM not found\n", __func__); |
| return -EINVAL; |
| } |
| |
| for (; (sg != NULL) && (sg->length < offset); sg = sg_next(sg)) |
| offset -= sg->length; |
| |
| if (sg == NULL) { |
| dev_err(dev, "IOVMM: invalid offset to %s.\n", __func__); |
| return -EINVAL; |
| } |
| |
| tsg = sg; |
| |
| start_off = offset_in_page(sg_phys(sg) + offset); |
| size = PAGE_ALIGN(size + start_off); |
| |
| if (sg_physically_continuous(sg)) { |
| size_t aligned_pad_size; |
| phys_addr_t phys = page_to_phys(sg_page(sg)); |
| section_offset = phys & (~SECT_MASK); |
| aligned_pad_size = ALIGN(phys, SECT_SIZE) - phys; |
| if ((sg->length - aligned_pad_size) < SECT_SIZE) { |
| aligned_pad_size = ALIGN(phys, LPAGE_SIZE) - phys; |
| if ((sg->length - aligned_pad_size) >= LPAGE_SIZE) |
| section_offset = phys & (~LPAGE_MASK); |
| else |
| section_offset = 0; |
| } |
| } |
| start = alloc_iovm_region(vmm, size, section_offset, start_off); |
| if (!start) { |
| spin_lock(&vmm->vmlist_lock); |
| dev_err(dev, "%s: Not enough IOVM space to allocate %#zx\n", |
| __func__, size); |
| dev_err(dev, "%s: Total %#zx, Allocated %#zx , Chunks %d\n", |
| __func__, vmm->iovm_size, |
| vmm->allocated_size, vmm->num_areas); |
| spin_unlock(&vmm->vmlist_lock); |
| ret = -ENOMEM; |
| goto err_map_nomem; |
| } |
| |
| addr = start - start_off; |
| |
| do { |
| phys_addr_t phys; |
| size_t len; |
| |
| phys = sg_phys(sg); |
| len = sg->length; |
| |
| /* if back to back sg entries are contiguous consolidate them */ |
| while (sg_next(sg) && |
| sg_phys(sg) + sg->length == sg_phys(sg_next(sg))) { |
| len += sg_next(sg)->length; |
| sg = sg_next(sg); |
| } |
| |
| if (offset > 0) { |
| len -= offset; |
| phys += offset; |
| offset = 0; |
| } |
| |
| if (offset_in_page(phys)) { |
| len += offset_in_page(phys); |
| phys = round_down(phys, PAGE_SIZE); |
| } |
| |
| len = PAGE_ALIGN(len); |
| |
| if (len > (size - mapped_size)) |
| len = size - mapped_size; |
| |
| ret = iommu_map(vmm->domain, addr, phys, len, prot); |
| if (ret) { |
| dev_err(dev, "iommu_map failed w/ err: %d\n", ret); |
| break; |
| } |
| |
| addr += len; |
| mapped_size += len; |
| } while ((sg = sg_next(sg)) && (mapped_size < size)); |
| |
| BUG_ON(mapped_size > size); |
| |
| if (mapped_size < size) { |
| dev_err(dev, "mapped_size(%#zx) is smaller than size(%#zx)\n", |
| mapped_size, size); |
| if (!ret) { |
| dev_err(dev, "ret: %d\n", ret); |
| ret = -EINVAL; |
| } |
| goto err_map_map; |
| } |
| |
| region = find_iovm_region(vmm, start); |
| BUG_ON(!region); |
| |
| /* |
| * If pretched SLPD is a fault SLPD in zero_l2_table, FLPD cache |
| * or prefetch buffer caches the address of zero_l2_table. |
| * This function replaces the zero_l2_table with new L2 page |
| * table to write valid mappings. |
| * Accessing the valid area may cause page fault since FLPD |
| * cache may still caches zero_l2_table for the valid area |
| * instead of new L2 page table that have the mapping |
| * information of the valid area |
| * Thus any replacement of zero_l2_table with other valid L2 |
| * page table must involve FLPD cache invalidation if the System |
| * MMU have prefetch feature and FLPD cache (version 3.3). |
| * FLPD cache invalidation is performed with TLB invalidation |
| * by VPN without blocking. It is safe to invalidate TLB without |
| * blocking because the target address of TLB invalidation is |
| * not currently mapped. |
| */ |
| |
| /* TODO: for sysmmu v6, remove it later */ |
| exynos_sysmmu_tlb_invalidate(vmm->domain, region->start, region->size); |
| |
| dev_dbg(dev, "IOVMM: Allocated VM region @ %#x/%#x bytes.\n", |
| (unsigned int)start, (unsigned int)size); |
| |
| SYSMMU_EVENT_LOG_IOVMM_MAP(IOVMM_TO_LOG(vmm), start, start + size, |
| region->size - size); |
| |
| return start; |
| |
| err_map_map: |
| iommu_unmap(vmm->domain, start - start_off, mapped_size); |
| free_iovm_region(vmm, remove_iovm_region(vmm, start)); |
| |
| dev_err(dev, |
| "Failed(%d) to map IOVMM REGION %pa (SIZE: %#zx, mapped: %#zx)\n", |
| ret, &start, size, mapped_size); |
| idx = 0; |
| do { |
| pr_err("SGLIST[%d].size = %#x\n", idx++, tsg->length); |
| } while ((tsg = sg_next(tsg))); |
| |
| show_iovm_regions(vmm); |
| |
| err_map_nomem: |
| dev_dbg(dev, |
| "IOVMM: Failed to allocated VM region for %#x bytes.\n", |
| (unsigned int)size); |
| return (dma_addr_t)ret; |
| } |
| |
| void iovmm_unmap(struct device *dev, dma_addr_t iova) |
| { |
| struct exynos_iovmm *vmm = exynos_get_iovmm(dev); |
| struct exynos_vm_region *region; |
| size_t unmap_size; |
| |
| /* This function must not be called in IRQ handlers */ |
| BUG_ON(in_irq()); |
| |
| if (vmm == NULL) { |
| dev_err(dev, "%s: IOVMM not found\n", __func__); |
| return; |
| } |
| |
| region = remove_iovm_region(vmm, iova); |
| if (region) { |
| u32 start = region->start + region->section_off; |
| u32 size = region->size - region->dummy_size; |
| |
| /* clear page offset */ |
| if (WARN_ON(start != iova)) { |
| dev_err(dev, "IOVMM: " |
| "iova %pa and region %#x(+%#x)@%#x(-%#x) mismatch\n", |
| &iova, region->size, region->dummy_size, |
| region->start, region->section_off); |
| show_iovm_regions(vmm); |
| /* reinsert iovm region */ |
| add_iovm_region(vmm, region->start, region->size); |
| kfree(region); |
| return; |
| } |
| unmap_size = iommu_unmap(vmm->domain, start & SPAGE_MASK, size); |
| if (unlikely(unmap_size != size)) { |
| dev_err(dev, |
| "Failed to unmap REGION of %#x:\n", start); |
| dev_err(dev, "(SIZE: %#x, iova: %pa, unmapped: %#zx)\n", |
| size, &iova, unmap_size); |
| show_iovm_regions(vmm); |
| kfree(region); |
| BUG(); |
| return; |
| } |
| |
| exynos_sysmmu_tlb_invalidate(vmm->domain, region->start, region->size); |
| |
| /* TODO: for sysmmu v6, remove it later */ |
| /* 60us is required to guarantee that PTW ends itself */ |
| udelay(60); |
| |
| free_iovm_region(vmm, region); |
| |
| dev_dbg(dev, "IOVMM: Unmapped %#x bytes from %#x.\n", |
| (unsigned int)unmap_size, (unsigned int)iova); |
| } else { |
| dev_err(dev, "IOVMM: No IOVM region %pa to free.\n", &iova); |
| } |
| } |
| |
| /* |
| * NOTE: |
| * exynos_iovmm_map_userptr() should be called under current->mm.mmap_sem held. |
| */ |
| dma_addr_t exynos_iovmm_map_userptr(struct device *dev, unsigned long vaddr, |
| size_t size, int prot) |
| { |
| struct exynos_iovmm *vmm = exynos_get_iovmm(dev); |
| unsigned long eaddr = vaddr + size; |
| off_t offset = offset_in_page(vaddr); |
| int ret = -EINVAL; |
| struct vm_area_struct *vma; |
| dma_addr_t start; |
| struct exynos_vm_region *region; |
| |
| vma = find_vma(current->mm, vaddr); |
| if (!vma || (vaddr < vma->vm_start)) { |
| dev_err(dev, "%s: invalid address %#lx\n", __func__, vaddr); |
| goto err; |
| } |
| |
| if (!!(vma->vm_flags & VM_PFNMAP)) |
| prot |= IOMMU_PFNMAP; |
| |
| /* non-cached user mapping should be treated as non-shareable mapping */ |
| if ((pgprot_val(pgprot_writecombine(vma->vm_page_prot)) == |
| pgprot_val(vma->vm_page_prot)) || |
| (pgprot_val(pgprot_noncached(vma->vm_page_prot)) == |
| pgprot_val(vma->vm_page_prot))) |
| prot &= ~IOMMU_CACHE; |
| |
| while (eaddr > vma->vm_end) { |
| if (!!(vma->vm_flags & VM_PFNMAP)) { |
| dev_err(dev, "%s: non-linear pfnmap is not supported\n", |
| __func__); |
| goto err; |
| } |
| |
| if ((vma->vm_next == NULL) || |
| (vma->vm_end != vma->vm_next->vm_start)) { |
| dev_err(dev, "%s: invalid size %zu\n", __func__, size); |
| goto err; |
| } |
| |
| vma = vma->vm_next; |
| } |
| |
| size = PAGE_ALIGN(size + offset); |
| start = alloc_iovm_region(vmm, size, 0, offset); |
| if (!start) { |
| spin_lock(&vmm->vmlist_lock); |
| dev_err(dev, "%s: Not enough IOVM space to allocate %#zx\n", |
| __func__, size); |
| dev_err(dev, "%s: Total %#zx, Allocated %#zx , Chunks %d\n", |
| __func__, vmm->iovm_size, |
| vmm->allocated_size, vmm->num_areas); |
| spin_unlock(&vmm->vmlist_lock); |
| ret = -ENOMEM; |
| goto err; |
| } |
| |
| ret = exynos_iommu_map_userptr(vmm->domain, vaddr - offset, |
| start - offset, size, prot); |
| if (ret < 0) |
| goto err_map; |
| |
| region = find_iovm_region(vmm, start); |
| BUG_ON(!region); |
| |
| SYSMMU_EVENT_LOG_IOVMM_MAP(IOVMM_TO_LOG(vmm), start, start + size, |
| region->size - size); |
| return start; |
| err_map: |
| free_iovm_region(vmm, remove_iovm_region(vmm, start)); |
| err: |
| return (dma_addr_t)ret; |
| } |
| |
| void exynos_iovmm_unmap_userptr(struct device *dev, dma_addr_t iova) |
| { |
| struct exynos_iovmm *vmm = exynos_get_iovmm(dev); |
| struct exynos_vm_region *region; |
| |
| region = remove_iovm_region(vmm, iova); |
| if (region) { |
| u32 start = region->start + region->section_off; |
| u32 size = region->size - region->dummy_size; |
| |
| /* clear page offset */ |
| if (WARN_ON(start != iova)) { |
| dev_err(dev, "IOVMM: " |
| "iova %pa and region %#x(+%#x)@%#x(-%#x) mismatch\n", |
| &iova, region->size, region->dummy_size, |
| region->start, region->section_off); |
| show_iovm_regions(vmm); |
| /* reinsert iovm region */ |
| add_iovm_region(vmm, region->start, region->size); |
| kfree(region); |
| return; |
| } |
| |
| exynos_iommu_unmap_userptr(vmm->domain, |
| start & SPAGE_MASK, size); |
| |
| free_iovm_region(vmm, region); |
| } else { |
| dev_err(dev, "IOVMM: No IOVM region %pa to free.\n", &iova); |
| } |
| } |
| |
| int iovmm_map_oto(struct device *dev, phys_addr_t phys, size_t size) |
| { |
| struct exynos_iovmm *vmm = exynos_get_iovmm(dev); |
| int ret; |
| |
| BUG_ON(!IS_ALIGNED(phys, PAGE_SIZE)); |
| BUG_ON(!IS_ALIGNED(size, PAGE_SIZE)); |
| |
| if (vmm == NULL) { |
| dev_err(dev, "%s: IOVMM not found\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (WARN_ON((phys < vmm->iova_start) || |
| (phys + size) >= (vmm->iova_start + vmm->iovm_size))) { |
| dev_err(dev, |
| "Unable to create one to one mapping for %#zx @ %pa\n", |
| size, &phys); |
| return -EINVAL; |
| } |
| |
| if (!add_iovm_region(vmm, (dma_addr_t)phys, size)) |
| return -EADDRINUSE; |
| |
| ret = iommu_map(vmm->domain, (dma_addr_t)phys, phys, size, 0); |
| if (ret < 0) |
| free_iovm_region(vmm, |
| remove_iovm_region(vmm, (dma_addr_t)phys)); |
| |
| return ret; |
| } |
| |
| void iovmm_unmap_oto(struct device *dev, phys_addr_t phys) |
| { |
| struct exynos_iovmm *vmm = exynos_get_iovmm(dev); |
| struct exynos_vm_region *region; |
| size_t unmap_size; |
| |
| /* This function must not be called in IRQ handlers */ |
| BUG_ON(in_irq()); |
| BUG_ON(!IS_ALIGNED(phys, PAGE_SIZE)); |
| |
| if (vmm == NULL) { |
| dev_err(dev, "%s: IOVMM not found\n", __func__); |
| return; |
| } |
| |
| region = remove_iovm_region(vmm, (dma_addr_t)phys); |
| if (region) { |
| unmap_size = iommu_unmap(vmm->domain, (dma_addr_t)phys, |
| region->size); |
| WARN_ON(unmap_size != region->size); |
| |
| exynos_sysmmu_tlb_invalidate(vmm->domain, (dma_addr_t)phys, |
| region->size); |
| |
| free_iovm_region(vmm, region); |
| |
| dev_dbg(dev, "IOVMM: Unmapped %#zx bytes from %pa.\n", |
| unmap_size, &phys); |
| } |
| } |
| |
| static struct dentry *exynos_iovmm_debugfs_root; |
| static struct dentry *exynos_iommu_debugfs_root; |
| |
| static int exynos_iovmm_create_debugfs(void) |
| { |
| exynos_iovmm_debugfs_root = debugfs_create_dir("iovmm", NULL); |
| if (!exynos_iovmm_debugfs_root) |
| pr_err("IOVMM: Failed to create debugfs entry\n"); |
| else |
| pr_info("IOVMM: Created debugfs entry at debugfs/iovmm\n"); |
| |
| exynos_iommu_debugfs_root = debugfs_create_dir("iommu", NULL); |
| if (!exynos_iommu_debugfs_root) |
| pr_err("IOMMU: Failed to create debugfs entry\n"); |
| else |
| pr_info("IOMMU: Created debugfs entry at debugfs/iommu\n"); |
| |
| return 0; |
| } |
| core_initcall(exynos_iovmm_create_debugfs); |
| |
| static int iovmm_debug_show(struct seq_file *s, void *unused) |
| { |
| struct exynos_iovmm *vmm = s->private; |
| |
| seq_printf(s, "%10.s %10.s %10.s %6.s\n", |
| "VASTART", "SIZE", "FREE", "CHUNKS"); |
| seq_puts(s, "---------------------------------------------\n"); |
| |
| spin_lock(&vmm->vmlist_lock); |
| seq_printf(s, " %#x %#10zx %#10zx %d\n", |
| vmm->iova_start, vmm->iovm_size, |
| vmm->iovm_size - vmm->allocated_size, |
| vmm->num_areas); |
| seq_puts(s, "---------------------------------------------\n"); |
| seq_printf(s, "Total number of mappings : %d\n", vmm->num_map); |
| seq_printf(s, "Total number of unmappings: %d\n", vmm->num_unmap); |
| spin_unlock(&vmm->vmlist_lock); |
| |
| return 0; |
| } |
| |
| static int iovmm_debug_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, iovmm_debug_show, inode->i_private); |
| } |
| |
| static ssize_t iovmm_debug_write(struct file *filp, const char __user *p, |
| size_t len, loff_t *off) |
| { |
| struct seq_file *s = filp->private_data; |
| struct exynos_iovmm *vmm = s->private; |
| /* clears the map count in IOVMM */ |
| spin_lock(&vmm->vmlist_lock); |
| vmm->num_map = 0; |
| vmm->num_unmap = 0; |
| spin_unlock(&vmm->vmlist_lock); |
| return len; |
| } |
| |
| static const struct file_operations iovmm_debug_fops = { |
| .open = iovmm_debug_open, |
| .read = seq_read, |
| .write = iovmm_debug_write, |
| .llseek = seq_lseek, |
| .release = single_release, |
| }; |
| |
| static void iovmm_register_debugfs(struct exynos_iovmm *vmm) |
| { |
| if (!exynos_iovmm_debugfs_root) |
| return; |
| |
| debugfs_create_file(vmm->domain_name, 0664, |
| exynos_iovmm_debugfs_root, vmm, &iovmm_debug_fops); |
| } |
| |
| struct exynos_iovmm *exynos_create_single_iovmm(const char *name, |
| unsigned int start, unsigned int end) |
| { |
| struct exynos_iovmm *vmm; |
| int ret = 0; |
| |
| vmm = kzalloc(sizeof(*vmm), GFP_KERNEL); |
| if (!vmm) { |
| ret = -ENOMEM; |
| goto err_alloc_vmm; |
| } |
| |
| vmm->iovm_size = (size_t)(end - start); |
| vmm->iova_start = start; |
| vmm->vm_map = kzalloc(IOVM_BITMAP_SIZE(vmm->iovm_size), GFP_KERNEL); |
| if (!vmm->vm_map) { |
| ret = -ENOMEM; |
| goto err_setup_domain; |
| } |
| |
| vmm->domain = iommu_domain_alloc(&platform_bus_type); |
| if (!vmm->domain) { |
| ret = -ENOMEM; |
| goto err_setup_domain; |
| } |
| |
| ret = exynos_iommu_init_event_log(IOVMM_TO_LOG(vmm), IOVMM_LOG_LEN); |
| if (!ret) { |
| iovmm_add_log_to_debugfs(exynos_iovmm_debugfs_root, |
| IOVMM_TO_LOG(vmm), name); |
| iommu_add_log_to_debugfs(exynos_iommu_debugfs_root, |
| IOMMU_TO_LOG(vmm->domain), name); |
| } else { |
| goto err_init_event_log; |
| } |
| |
| spin_lock_init(&vmm->vmlist_lock); |
| spin_lock_init(&vmm->bitmap_lock); |
| |
| INIT_LIST_HEAD(&vmm->regions_list); |
| |
| vmm->domain_name = name; |
| |
| iovmm_register_debugfs(vmm); |
| |
| pr_debug("%s IOVMM: Created %#zx B IOVMM from %#x.\n", |
| name, vmm->iovm_size, vmm->iova_start); |
| return vmm; |
| |
| err_init_event_log: |
| iommu_domain_free(vmm->domain); |
| err_setup_domain: |
| kfree(vmm); |
| err_alloc_vmm: |
| pr_err("%s IOVMM: Failed to create IOVMM (%d)\n", name, ret); |
| |
| return ERR_PTR(ret); |
| } |
| |
| void iovmm_set_fault_handler(struct device *dev, |
| iommu_fault_handler_t handler, void *token) |
| { |
| int ret; |
| |
| ret = exynos_iommu_add_fault_handler(dev, handler, token); |
| if (ret) |
| dev_err(dev, "Failed to add fault handler\n"); |
| } |