xref: /linux/kernel/dma/remap.c (revision 3a39d672e7f48b8d6b91a09afa4b55352773b4b5)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2014 The Linux Foundation
4  */
5 #include <linux/dma-map-ops.h>
6 #include <linux/slab.h>
7 #include <linux/vmalloc.h>
8 
dma_common_find_pages(void * cpu_addr)9 struct page **dma_common_find_pages(void *cpu_addr)
10 {
11 	struct vm_struct *area = find_vm_area(cpu_addr);
12 
13 	if (!area || !(area->flags & VM_DMA_COHERENT))
14 		return NULL;
15 	WARN(area->flags != VM_DMA_COHERENT,
16 	     "unexpected flags in area: %p\n", cpu_addr);
17 	return area->pages;
18 }
19 
20 /*
21  * Remaps an array of PAGE_SIZE pages into another vm_area.
22  * Cannot be used in non-sleeping contexts
23  */
dma_common_pages_remap(struct page ** pages,size_t size,pgprot_t prot,const void * caller)24 void *dma_common_pages_remap(struct page **pages, size_t size,
25 			 pgprot_t prot, const void *caller)
26 {
27 	void *vaddr;
28 
29 	vaddr = vmap(pages, PAGE_ALIGN(size) >> PAGE_SHIFT,
30 		     VM_DMA_COHERENT, prot);
31 	if (vaddr)
32 		find_vm_area(vaddr)->pages = pages;
33 	return vaddr;
34 }
35 
36 /*
37  * Remaps an allocated contiguous region into another vm_area.
38  * Cannot be used in non-sleeping contexts
39  */
dma_common_contiguous_remap(struct page * page,size_t size,pgprot_t prot,const void * caller)40 void *dma_common_contiguous_remap(struct page *page, size_t size,
41 			pgprot_t prot, const void *caller)
42 {
43 	int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
44 	struct page **pages;
45 	void *vaddr;
46 	int i;
47 
48 	pages = kvmalloc_array(count, sizeof(struct page *), GFP_KERNEL);
49 	if (!pages)
50 		return NULL;
51 	for (i = 0; i < count; i++)
52 		pages[i] = nth_page(page, i);
53 	vaddr = vmap(pages, count, VM_DMA_COHERENT, prot);
54 	kvfree(pages);
55 
56 	return vaddr;
57 }
58 
59 /*
60  * Unmaps a range previously mapped by dma_common_*_remap
61  */
dma_common_free_remap(void * cpu_addr,size_t size)62 void dma_common_free_remap(void *cpu_addr, size_t size)
63 {
64 	struct vm_struct *area = find_vm_area(cpu_addr);
65 
66 	if (!area || !(area->flags & VM_DMA_COHERENT)) {
67 		WARN(1, "trying to free invalid coherent area: %p\n", cpu_addr);
68 		return;
69 	}
70 
71 	vunmap(cpu_addr);
72 }
73