/* * * (C) COPYRIGHT ARM Limited. All rights reserved. * * This program is free software and is provided to you under the terms of the * GNU General Public License version 2 as published by the Free Software * Foundation, and any use by you of this program is subject to the terms * of such GNU licence. * * A copy of the licence is included with the program, and can also be obtained * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include #include #include #include /* kernel module definitions */ #include /* file system operations */ #include /* character device definitions */ #include /* request_mem_region */ #include /* memory mananger definitions */ #include #include /*kmap*/ #include /* is_compat_task */ #include #include #include static void umpp_vm_close(struct vm_area_struct *vma) { umpp_cpu_mapping * mapping; umpp_session * session; ump_dd_handle handle; mapping = (umpp_cpu_mapping*)vma->vm_private_data; UMP_ASSERT(mapping); session = mapping->session; handle = mapping->handle; umpp_dd_remove_cpu_mapping(mapping->handle, mapping); /* will free the mapping object */ ump_dd_release(handle); } static const struct vm_operations_struct umpp_vm_ops = { .close = umpp_vm_close }; int umpp_phys_commit(umpp_allocation * alloc) { uint64_t i; /* round up to a page boundary */ alloc->size = (alloc->size + PAGE_SIZE - 1) & ~((uint64_t)PAGE_SIZE-1) ; /* calculate number of pages */ alloc->blocksCount = alloc->size >> PAGE_SHIFT; if( (sizeof(ump_dd_physical_block_64) * alloc->blocksCount) > ((size_t)-1)) { printk(KERN_WARNING "UMP: umpp_phys_commit - trying to allocate more than possible\n"); return -ENOMEM; } alloc->block_array = kmalloc(sizeof(ump_dd_physical_block_64) * alloc->blocksCount, __GFP_HARDWALL | GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN); if (NULL == alloc->block_array) { return -ENOMEM; } for (i = 0; i < alloc->blocksCount; i++) { void * mp; struct page * page = alloc_page(GFP_HIGHUSER | __GFP_NORETRY | __GFP_NOWARN | __GFP_COLD); if (NULL == page) { break; } alloc->block_array[i].addr = page_to_pfn(page) << PAGE_SHIFT; alloc->block_array[i].size = PAGE_SIZE; mp = kmap(page); if (NULL == mp) { __free_page(page); break; } memset(mp, 0x00, PAGE_SIZE); /* instead of __GFP_ZERO, so we can do cache maintenance */ ump_sync_to_memory(PFN_PHYS(page_to_pfn(page)), mp, PAGE_SIZE); kunmap(page); } if (i == alloc->blocksCount) { return 0; } else { uint64_t j; for (j = 0; j < i; j++) { struct page * page; page = pfn_to_page(alloc->block_array[j].addr >> PAGE_SHIFT); __free_page(page); } kfree(alloc->block_array); return -ENOMEM; } } void umpp_phys_free(umpp_allocation * alloc) { uint64_t i; for (i = 0; i < alloc->blocksCount; i++) { __free_page(pfn_to_page(alloc->block_array[i].addr >> PAGE_SHIFT)); } kfree(alloc->block_array); } int umpp_linux_mmap(struct file * filp, struct vm_area_struct * vma) { ump_secure_id id; ump_dd_handle h; size_t offset; int err = -EINVAL; size_t length = vma->vm_end - vma->vm_start; umpp_cpu_mapping * map = NULL; umpp_session *session = filp->private_data; if ( 0 == length ) { return -EINVAL; } map = kzalloc(sizeof(*map), GFP_KERNEL); if (NULL == map) { WARN_ON(1); err = -ENOMEM; goto out; } /* unpack our arg */ #if defined CONFIG_64BIT && CONFIG_64BIT if (is_compat_task()) { #endif id = vma->vm_pgoff >> UMP_LINUX_OFFSET_BITS_32; offset = vma->vm_pgoff & UMP_LINUX_OFFSET_MASK_32; #if defined CONFIG_64BIT && CONFIG_64BIT } else { id = vma->vm_pgoff >> UMP_LINUX_OFFSET_BITS_64; offset = vma->vm_pgoff & UMP_LINUX_OFFSET_MASK_64; } #endif h = ump_dd_from_secure_id(id); if (UMP_DD_INVALID_MEMORY_HANDLE != h) { uint64_t i; uint64_t block_idx; uint64_t block_offset; uint64_t paddr; umpp_allocation * alloc; uint64_t last_byte; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,7,0)) vma->vm_flags |= VM_DONTCOPY | VM_DONTEXPAND | VM_IO | VM_MIXEDMAP | VM_DONTDUMP; #else vma->vm_flags |= VM_DONTCOPY | VM_DONTEXPAND | VM_RESERVED | VM_IO | VM_MIXEDMAP; #endif vma->vm_ops = &umpp_vm_ops; vma->vm_private_data = map; alloc = (umpp_allocation*)h; if( (alloc->flags & UMP_CONSTRAINT_UNCACHED) != 0) { /* cache disabled flag set, disable caching for cpu mappings */ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); } last_byte = length + (offset << PAGE_SHIFT) - 1; if (last_byte >= alloc->size || last_byte < (offset << PAGE_SHIFT)) { goto err_out; } if (umpp_dd_find_start_block(alloc, offset << PAGE_SHIFT, &block_idx, &block_offset)) { goto err_out; } paddr = alloc->block_array[block_idx].addr + block_offset; for (i = 0; i < (length >> PAGE_SHIFT); i++) { /* check if we've overrrun the current block, if so move to the next block */ if (paddr >= (alloc->block_array[block_idx].addr + alloc->block_array[block_idx].size)) { block_idx++; UMP_ASSERT(block_idx < alloc->blocksCount); paddr = alloc->block_array[block_idx].addr; } err = vm_insert_mixed(vma, vma->vm_start + (i << PAGE_SHIFT), paddr >> PAGE_SHIFT); paddr += PAGE_SIZE; } map->vaddr_start = (void*)vma->vm_start; map->nr_pages = length >> PAGE_SHIFT; map->page_off = offset; map->handle = h; map->session = session; umpp_dd_add_cpu_mapping(h, map); return 0; err_out: ump_dd_release(h); } kfree(map); out: return err; }