/* * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only 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. */ #include #include #include #include #include #include #include #include #include #include #include #include "ion_priv.h" #include #include #include #include struct ion_iommu_heap { struct ion_heap heap; unsigned int has_outer_cache; }; /* * We will attempt to allocate high-order pages and store those in an * sg_list. However, some APIs expect an array of struct page * where * each page is of size PAGE_SIZE. We use this extra structure to * carry around an array of such pages (derived from the high-order * pages with nth_page). */ struct ion_iommu_priv_data { struct page **pages; int nrpages; unsigned long size; }; static const unsigned int orders[] = {8, 4, 0}; static const int num_orders = ARRAY_SIZE(orders); struct page_info { struct page *page; unsigned int order; struct list_head list; }; static unsigned int order_to_size(int order) { return PAGE_SIZE << order; } static struct page_info *alloc_largest_available(unsigned long size, unsigned int max_order) { struct page *page; struct page_info *info; int i; for (i = 0; i < num_orders; i++) { gfp_t gfp; if (size < order_to_size(orders[i])) continue; if (max_order < orders[i]) continue; gfp = __GFP_HIGHMEM; if (orders[i]) { gfp |= __GFP_COMP | __GFP_NORETRY | __GFP_NO_KSWAPD | __GFP_NOWARN; } else { gfp |= GFP_KERNEL; } gfp |= __GFP_ZERO; page = alloc_pages(gfp, orders[i]); if (!page) continue; info = kmalloc(sizeof(struct page_info), GFP_KERNEL); info->page = page; info->order = orders[i]; return info; } return NULL; } static int ion_iommu_heap_allocate(struct ion_heap *heap, struct ion_buffer *buffer, unsigned long size, unsigned long align, unsigned long flags) { int ret, i; struct list_head pages_list; struct page_info *info, *tmp_info; struct ion_iommu_priv_data *data = NULL; if (msm_use_iommu()) { struct scatterlist *sg; struct sg_table *table; int j; unsigned int num_large_pages = 0; long size_remaining = PAGE_ALIGN(size); unsigned int max_order = orders[0]; data = kmalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; INIT_LIST_HEAD(&pages_list); while (size_remaining > 0) { info = alloc_largest_available(size_remaining, max_order); if (!info) { ret = -ENOMEM; goto err_free_data; } list_add_tail(&info->list, &pages_list); size_remaining -= order_to_size(info->order); max_order = info->order; num_large_pages++; } data->size = PFN_ALIGN(size); data->nrpages = data->size >> PAGE_SHIFT; data->pages = kzalloc(sizeof(struct page *)*data->nrpages, GFP_KERNEL); if (!data->pages) { ret = -ENOMEM; goto err_free_data; } table = buffer->sg_table = kzalloc(sizeof(struct sg_table), GFP_KERNEL); if (!table) { ret = -ENOMEM; goto err1; } ret = sg_alloc_table(table, num_large_pages, GFP_KERNEL); if (ret) goto err2; i = 0; sg = table->sgl; list_for_each_entry_safe(info, tmp_info, &pages_list, list) { struct page *page = info->page; sg_set_page(sg, page, order_to_size(info->order), 0); sg_dma_address(sg) = sg_phys(sg); sg = sg_next(sg); for (j = 0; j < (1 << info->order); ++j) data->pages[i++] = nth_page(page, j); list_del(&info->list); kfree(info); } if (!ION_IS_CACHED(flags)) dma_sync_sg_for_device(NULL, table->sgl, table->nents, DMA_BIDIRECTIONAL); buffer->priv_virt = data; return 0; } else { return -ENOMEM; } sg_free_table(buffer->sg_table); err2: kfree(buffer->sg_table); buffer->sg_table = 0; err1: kfree(data->pages); err_free_data: kfree(data); list_for_each_entry_safe(info, tmp_info, &pages_list, list) { if (info->page) __free_pages(info->page, info->order); list_del(&info->list); kfree(info); } return ret; } static void ion_iommu_heap_free(struct ion_buffer *buffer) { int i; struct scatterlist *sg; struct sg_table *table = buffer->sg_table; struct ion_iommu_priv_data *data = buffer->priv_virt; if (!table) return; if (!data) return; for_each_sg(table->sgl, sg, table->nents, i) __free_pages(sg_page(sg), get_order(sg_dma_len(sg))); sg_free_table(table); kfree(table); table = 0; kfree(data->pages); kfree(data); } void *ion_iommu_heap_map_kernel(struct ion_heap *heap, struct ion_buffer *buffer) { struct ion_iommu_priv_data *data = buffer->priv_virt; pgprot_t page_prot = PAGE_KERNEL; if (!data) return NULL; if (!ION_IS_CACHED(buffer->flags)) page_prot = pgprot_writecombine(page_prot); buffer->vaddr = vmap(data->pages, data->nrpages, VM_IOREMAP, page_prot); return buffer->vaddr; } void ion_iommu_heap_unmap_kernel(struct ion_heap *heap, struct ion_buffer *buffer) { if (!buffer->vaddr) return; vunmap(buffer->vaddr); buffer->vaddr = NULL; } int ion_iommu_heap_map_user(struct ion_heap *heap, struct ion_buffer *buffer, struct vm_area_struct *vma) { struct sg_table *table = buffer->sg_table; unsigned long addr = vma->vm_start; unsigned long offset = vma->vm_pgoff * PAGE_SIZE; struct scatterlist *sg; int i; if (!ION_IS_CACHED(buffer->flags)) vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); for_each_sg(table->sgl, sg, table->nents, i) { struct page *page = sg_page(sg); unsigned long remainder = vma->vm_end - addr; unsigned long len = sg_dma_len(sg); if (offset >= sg_dma_len(sg)) { offset -= sg_dma_len(sg); continue; } else if (offset) { page += offset / PAGE_SIZE; len = sg_dma_len(sg) - offset; offset = 0; } len = min(len, remainder); remap_pfn_range(vma, addr, page_to_pfn(page), len, vma->vm_page_prot); addr += len; if (addr >= vma->vm_end) return 0; } return 0; } int ion_iommu_heap_map_iommu(struct ion_buffer *buffer, struct ion_iommu_map *data, unsigned int domain_num, unsigned int partition_num, unsigned long align, unsigned long iova_length, unsigned long flags) { struct iommu_domain *domain; int ret = 0; unsigned long extra; int prot = IOMMU_WRITE | IOMMU_READ; prot |= ION_IS_CACHED(flags) ? IOMMU_CACHE : 0; BUG_ON(!msm_use_iommu()); data->mapped_size = iova_length; extra = iova_length - buffer->size; ret = msm_allocate_iova_address(domain_num, partition_num, data->mapped_size, align, &data->iova_addr); if (ret) goto out; domain = msm_get_iommu_domain(domain_num); if (!domain) { ret = -ENOMEM; goto out1; } ret = iommu_map_range(domain, data->iova_addr, buffer->sg_table->sgl, buffer->size, prot); if (ret) { pr_err("%s: could not map %lx in domain %p\n", __func__, data->iova_addr, domain); goto out1; } if (extra) { unsigned long extra_iova_addr = data->iova_addr + buffer->size; ret = msm_iommu_map_extra(domain, extra_iova_addr, extra, SZ_4K, prot); if (ret) goto out2; } return ret; out2: iommu_unmap_range(domain, data->iova_addr, buffer->size); out1: msm_free_iova_address(data->iova_addr, domain_num, partition_num, buffer->size); out: return ret; } void ion_iommu_heap_unmap_iommu(struct ion_iommu_map *data) { unsigned int domain_num; unsigned int partition_num; struct iommu_domain *domain; BUG_ON(!msm_use_iommu()); domain_num = iommu_map_domain(data); partition_num = iommu_map_partition(data); domain = msm_get_iommu_domain(domain_num); if (!domain) { WARN(1, "Could not get domain %d. Corruption?\n", domain_num); return; } iommu_unmap_range(domain, data->iova_addr, data->mapped_size); msm_free_iova_address(data->iova_addr, domain_num, partition_num, data->mapped_size); return; } static int ion_iommu_cache_ops(struct ion_heap *heap, struct ion_buffer *buffer, void *vaddr, unsigned int offset, unsigned int length, unsigned int cmd) { void (*outer_cache_op)(phys_addr_t, phys_addr_t); struct ion_iommu_heap *iommu_heap = container_of(heap, struct ion_iommu_heap, heap); switch (cmd) { case ION_IOC_CLEAN_CACHES: dmac_clean_range(vaddr, vaddr + length); outer_cache_op = outer_clean_range; break; case ION_IOC_INV_CACHES: dmac_inv_range(vaddr, vaddr + length); outer_cache_op = outer_inv_range; break; case ION_IOC_CLEAN_INV_CACHES: dmac_flush_range(vaddr, vaddr + length); outer_cache_op = outer_flush_range; break; default: return -EINVAL; } if (iommu_heap->has_outer_cache) { unsigned long pstart; unsigned int i; struct ion_iommu_priv_data *data = buffer->priv_virt; if (!data) return -ENOMEM; for (i = 0; i < data->nrpages; ++i) { pstart = page_to_phys(data->pages[i]); outer_cache_op(pstart, pstart + PAGE_SIZE); } } return 0; } static struct sg_table *ion_iommu_heap_map_dma(struct ion_heap *heap, struct ion_buffer *buffer) { return buffer->sg_table; } static void ion_iommu_heap_unmap_dma(struct ion_heap *heap, struct ion_buffer *buffer) { } static struct ion_heap_ops iommu_heap_ops = { .allocate = ion_iommu_heap_allocate, .free = ion_iommu_heap_free, .map_user = ion_iommu_heap_map_user, .map_kernel = ion_iommu_heap_map_kernel, .unmap_kernel = ion_iommu_heap_unmap_kernel, .map_iommu = ion_iommu_heap_map_iommu, .unmap_iommu = ion_iommu_heap_unmap_iommu, .cache_op = ion_iommu_cache_ops, .map_dma = ion_iommu_heap_map_dma, .unmap_dma = ion_iommu_heap_unmap_dma, }; struct ion_heap *ion_iommu_heap_create(struct ion_platform_heap *heap_data) { struct ion_iommu_heap *iommu_heap; iommu_heap = kzalloc(sizeof(struct ion_iommu_heap), GFP_KERNEL); if (!iommu_heap) return ERR_PTR(-ENOMEM); iommu_heap->heap.ops = &iommu_heap_ops; iommu_heap->heap.type = ION_HEAP_TYPE_IOMMU; iommu_heap->has_outer_cache = heap_data->has_outer_cache; return &iommu_heap->heap; } void ion_iommu_heap_destroy(struct ion_heap *heap) { struct ion_iommu_heap *iommu_heap = container_of(heap, struct ion_iommu_heap, heap); kfree(iommu_heap); iommu_heap = NULL; }