Blame view

mm/frame_vector.c 6.43 KB
b24413180   Greg Kroah-Hartman   License cleanup: ...
1
  // SPDX-License-Identifier: GPL-2.0
8025e5ddf   Jan Kara   [media] mm: Provi...
2
3
4
5
6
7
8
9
  #include <linux/kernel.h>
  #include <linux/errno.h>
  #include <linux/err.h>
  #include <linux/mm.h>
  #include <linux/slab.h>
  #include <linux/vmalloc.h>
  #include <linux/pagemap.h>
  #include <linux/sched.h>
61f9ec1d8   Jonathan Corbet   mm: fix docbook c...
10
  /**
8025e5ddf   Jan Kara   [media] mm: Provi...
11
12
13
   * get_vaddr_frames() - map virtual addresses to pfns
   * @start:	starting user address
   * @nr_frames:	number of pages / pfns from start to map
7f23b3504   Lorenzo Stoakes   mm: replace get_v...
14
   * @gup_flags:	flags modifying lookup behaviour
8025e5ddf   Jan Kara   [media] mm: Provi...
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   * @vec:	structure which receives pages / pfns of the addresses mapped.
   *		It should have space for at least nr_frames entries.
   *
   * This function maps virtual addresses from @start and fills @vec structure
   * with page frame numbers or page pointers to corresponding pages (choice
   * depends on the type of the vma underlying the virtual address). If @start
   * belongs to a normal vma, the function grabs reference to each of the pages
   * to pin them in memory. If @start belongs to VM_IO | VM_PFNMAP vma, we don't
   * touch page structures and the caller must make sure pfns aren't reused for
   * anything else while he is using them.
   *
   * The function returns number of pages mapped which may be less than
   * @nr_frames. In particular we stop mapping if there are more vmas of
   * different type underlying the specified range of virtual addresses.
   * When the function isn't able to map a single page, it returns error.
   *
c1e8d7c6a   Michel Lespinasse   mmap locking API:...
31
   * This function takes care of grabbing mmap_lock as necessary.
8025e5ddf   Jan Kara   [media] mm: Provi...
32
33
   */
  int get_vaddr_frames(unsigned long start, unsigned int nr_frames,
7f23b3504   Lorenzo Stoakes   mm: replace get_v...
34
  		     unsigned int gup_flags, struct frame_vector *vec)
8025e5ddf   Jan Kara   [media] mm: Provi...
35
36
37
38
39
40
41
42
43
44
45
46
  {
  	struct mm_struct *mm = current->mm;
  	struct vm_area_struct *vma;
  	int ret = 0;
  	int err;
  	int locked;
  
  	if (nr_frames == 0)
  		return 0;
  
  	if (WARN_ON_ONCE(nr_frames > vec->nr_allocated))
  		nr_frames = vec->nr_allocated;
5d65e7a7d   Andrey Konovalov   mm: untag user po...
47
  	start = untagged_addr(start);
d8ed45c5d   Michel Lespinasse   mmap locking API:...
48
  	mmap_read_lock(mm);
8025e5ddf   Jan Kara   [media] mm: Provi...
49
50
51
52
53
54
  	locked = 1;
  	vma = find_vma_intersection(mm, start, start + 1);
  	if (!vma) {
  		ret = -EFAULT;
  		goto out;
  	}
b7f0554a5   Dan Williams   mm: fail get_vadd...
55
56
57
58
59
60
61
62
63
  
  	/*
  	 * While get_vaddr_frames() could be used for transient (kernel
  	 * controlled lifetime) pinning of memory pages all current
  	 * users establish long term (userspace controlled lifetime)
  	 * page pinning. Treat get_vaddr_frames() like
  	 * get_user_pages_longterm() and disallow it for filesystem-dax
  	 * mappings.
  	 */
1f704fd0d   Christophe JAILLET   mm/frame_vector.c...
64
65
66
67
  	if (vma_is_fsdax(vma)) {
  		ret = -EOPNOTSUPP;
  		goto out;
  	}
b7f0554a5   Dan Williams   mm: fail get_vadd...
68

8025e5ddf   Jan Kara   [media] mm: Provi...
69
70
71
  	if (!(vma->vm_flags & (VM_IO | VM_PFNMAP))) {
  		vec->got_ref = true;
  		vec->is_pfns = false;
55a650c35   John Hubbard   mm/gup: frame_vec...
72
  		ret = pin_user_pages_locked(start, nr_frames,
3b913179c   Lorenzo Stoakes   mm: replace get_u...
73
  			gup_flags, (struct page **)(vec->ptrs), &locked);
8025e5ddf   Jan Kara   [media] mm: Provi...
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
  		goto out;
  	}
  
  	vec->got_ref = false;
  	vec->is_pfns = true;
  	do {
  		unsigned long *nums = frame_vector_pfns(vec);
  
  		while (ret < nr_frames && start + PAGE_SIZE <= vma->vm_end) {
  			err = follow_pfn(vma, start, &nums[ret]);
  			if (err) {
  				if (ret == 0)
  					ret = err;
  				goto out;
  			}
  			start += PAGE_SIZE;
  			ret++;
  		}
  		/*
  		 * We stop if we have enough pages or if VMA doesn't completely
  		 * cover the tail page.
  		 */
  		if (ret >= nr_frames || start < vma->vm_end)
  			break;
  		vma = find_vma_intersection(mm, start, start + 1);
  	} while (vma && vma->vm_flags & (VM_IO | VM_PFNMAP));
  out:
  	if (locked)
d8ed45c5d   Michel Lespinasse   mmap locking API:...
102
  		mmap_read_unlock(mm);
8025e5ddf   Jan Kara   [media] mm: Provi...
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
  	if (!ret)
  		ret = -EFAULT;
  	if (ret > 0)
  		vec->nr_frames = ret;
  	return ret;
  }
  EXPORT_SYMBOL(get_vaddr_frames);
  
  /**
   * put_vaddr_frames() - drop references to pages if get_vaddr_frames() acquired
   *			them
   * @vec:	frame vector to put
   *
   * Drop references to pages if get_vaddr_frames() acquired them. We also
   * invalidate the frame vector so that it is prepared for the next call into
   * get_vaddr_frames().
   */
  void put_vaddr_frames(struct frame_vector *vec)
  {
8025e5ddf   Jan Kara   [media] mm: Provi...
122
123
124
125
126
127
128
129
130
131
132
133
  	struct page **pages;
  
  	if (!vec->got_ref)
  		goto out;
  	pages = frame_vector_pages(vec);
  	/*
  	 * frame_vector_pages() might needed to do a conversion when
  	 * get_vaddr_frames() got pages but vec was later converted to pfns.
  	 * But it shouldn't really fail to convert pfns back...
  	 */
  	if (WARN_ON(IS_ERR(pages)))
  		goto out;
55a650c35   John Hubbard   mm/gup: frame_vec...
134
135
  
  	unpin_user_pages(pages, vec->nr_frames);
8025e5ddf   Jan Kara   [media] mm: Provi...
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
  	vec->got_ref = false;
  out:
  	vec->nr_frames = 0;
  }
  EXPORT_SYMBOL(put_vaddr_frames);
  
  /**
   * frame_vector_to_pages - convert frame vector to contain page pointers
   * @vec:	frame vector to convert
   *
   * Convert @vec to contain array of page pointers.  If the conversion is
   * successful, return 0. Otherwise return an error. Note that we do not grab
   * page references for the page structures.
   */
  int frame_vector_to_pages(struct frame_vector *vec)
  {
  	int i;
  	unsigned long *nums;
  	struct page **pages;
  
  	if (!vec->is_pfns)
  		return 0;
  	nums = frame_vector_pfns(vec);
  	for (i = 0; i < vec->nr_frames; i++)
  		if (!pfn_valid(nums[i]))
  			return -EINVAL;
  	pages = (struct page **)nums;
  	for (i = 0; i < vec->nr_frames; i++)
  		pages[i] = pfn_to_page(nums[i]);
  	vec->is_pfns = false;
  	return 0;
  }
  EXPORT_SYMBOL(frame_vector_to_pages);
  
  /**
   * frame_vector_to_pfns - convert frame vector to contain pfns
   * @vec:	frame vector to convert
   *
   * Convert @vec to contain array of pfns.
   */
  void frame_vector_to_pfns(struct frame_vector *vec)
  {
  	int i;
  	unsigned long *nums;
  	struct page **pages;
  
  	if (vec->is_pfns)
  		return;
  	pages = (struct page **)(vec->ptrs);
  	nums = (unsigned long *)pages;
  	for (i = 0; i < vec->nr_frames; i++)
  		nums[i] = page_to_pfn(pages[i]);
  	vec->is_pfns = true;
  }
  EXPORT_SYMBOL(frame_vector_to_pfns);
  
  /**
   * frame_vector_create() - allocate & initialize structure for pinned pfns
   * @nr_frames:	number of pfns slots we should reserve
   *
   * Allocate and initialize struct pinned_pfns to be able to hold @nr_pfns
   * pfns.
   */
  struct frame_vector *frame_vector_create(unsigned int nr_frames)
  {
  	struct frame_vector *vec;
  	int size = sizeof(struct frame_vector) + sizeof(void *) * nr_frames;
  
  	if (WARN_ON_ONCE(nr_frames == 0))
  		return NULL;
  	/*
  	 * This is absurdly high. It's here just to avoid strange effects when
  	 * arithmetics overflows.
  	 */
  	if (WARN_ON_ONCE(nr_frames > INT_MAX / sizeof(void *) / 2))
  		return NULL;
  	/*
  	 * Avoid higher order allocations, use vmalloc instead. It should
  	 * be rare anyway.
  	 */
752ade68c   Michal Hocko   treewide: use kv[...
216
  	vec = kvmalloc(size, GFP_KERNEL);
8025e5ddf   Jan Kara   [media] mm: Provi...
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
  	if (!vec)
  		return NULL;
  	vec->nr_allocated = nr_frames;
  	vec->nr_frames = 0;
  	return vec;
  }
  EXPORT_SYMBOL(frame_vector_create);
  
  /**
   * frame_vector_destroy() - free memory allocated to carry frame vector
   * @vec:	Frame vector to free
   *
   * Free structure allocated by frame_vector_create() to carry frames.
   */
  void frame_vector_destroy(struct frame_vector *vec)
  {
  	/* Make sure put_vaddr_frames() got called properly... */
  	VM_BUG_ON(vec->nr_frames > 0);
  	kvfree(vec);
  }
  EXPORT_SYMBOL(frame_vector_destroy);