diff options
Diffstat (limited to 'drivers/video/tegra/nvmap')
-rw-r--r-- | drivers/video/tegra/nvmap/Makefile | 7 | ||||
-rw-r--r-- | drivers/video/tegra/nvmap/nvmap.c | 871 | ||||
-rw-r--r-- | drivers/video/tegra/nvmap/nvmap.h | 271 | ||||
-rw-r--r-- | drivers/video/tegra/nvmap/nvmap_common.h | 38 | ||||
-rw-r--r-- | drivers/video/tegra/nvmap/nvmap_dev.c | 1436 | ||||
-rw-r--r-- | drivers/video/tegra/nvmap/nvmap_handle.c | 1020 | ||||
-rw-r--r-- | drivers/video/tegra/nvmap/nvmap_heap.c | 1113 | ||||
-rw-r--r-- | drivers/video/tegra/nvmap/nvmap_heap.h | 68 | ||||
-rw-r--r-- | drivers/video/tegra/nvmap/nvmap_ioctl.c | 749 | ||||
-rw-r--r-- | drivers/video/tegra/nvmap/nvmap_ioctl.h | 159 | ||||
-rw-r--r-- | drivers/video/tegra/nvmap/nvmap_mru.c | 187 | ||||
-rw-r--r-- | drivers/video/tegra/nvmap/nvmap_mru.h | 84 |
12 files changed, 6003 insertions, 0 deletions
diff --git a/drivers/video/tegra/nvmap/Makefile b/drivers/video/tegra/nvmap/Makefile new file mode 100644 index 00000000000..95d7f68836a --- /dev/null +++ b/drivers/video/tegra/nvmap/Makefile | |||
@@ -0,0 +1,7 @@ | |||
1 | GCOV_PROFILE := y | ||
2 | obj-y += nvmap.o | ||
3 | obj-y += nvmap_dev.o | ||
4 | obj-y += nvmap_handle.o | ||
5 | obj-y += nvmap_heap.o | ||
6 | obj-y += nvmap_ioctl.o | ||
7 | obj-${CONFIG_NVMAP_RECLAIM_UNPINNED_VM} += nvmap_mru.o \ No newline at end of file | ||
diff --git a/drivers/video/tegra/nvmap/nvmap.c b/drivers/video/tegra/nvmap/nvmap.c new file mode 100644 index 00000000000..b4b6241618d --- /dev/null +++ b/drivers/video/tegra/nvmap/nvmap.c | |||
@@ -0,0 +1,871 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/nvmap/nvmap.c | ||
3 | * | ||
4 | * Memory manager for Tegra GPU | ||
5 | * | ||
6 | * Copyright (c) 2009-2011, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | #include <linux/err.h> | ||
24 | #include <linux/highmem.h> | ||
25 | #include <linux/io.h> | ||
26 | #include <linux/rbtree.h> | ||
27 | #include <linux/vmalloc.h> | ||
28 | #include <linux/wait.h> | ||
29 | #include <linux/slab.h> | ||
30 | |||
31 | #include <asm/pgtable.h> | ||
32 | #include <asm/tlbflush.h> | ||
33 | |||
34 | #include <mach/iovmm.h> | ||
35 | #include <mach/nvmap.h> | ||
36 | |||
37 | #include "nvmap.h" | ||
38 | #include "nvmap_mru.h" | ||
39 | |||
40 | /* private nvmap_handle flag for pinning duplicate detection */ | ||
41 | #define NVMAP_HANDLE_VISITED (0x1ul << 31) | ||
42 | |||
43 | /* map the backing pages for a heap_pgalloc handle into its IOVMM area */ | ||
44 | static void map_iovmm_area(struct nvmap_handle *h) | ||
45 | { | ||
46 | tegra_iovmm_addr_t va; | ||
47 | unsigned long i; | ||
48 | |||
49 | BUG_ON(!h->heap_pgalloc || !h->pgalloc.area); | ||
50 | BUG_ON(h->size & ~PAGE_MASK); | ||
51 | WARN_ON(!h->pgalloc.dirty); | ||
52 | |||
53 | for (va = h->pgalloc.area->iovm_start, i = 0; | ||
54 | va < (h->pgalloc.area->iovm_start + h->size); | ||
55 | i++, va += PAGE_SIZE) { | ||
56 | BUG_ON(!pfn_valid(page_to_pfn(h->pgalloc.pages[i]))); | ||
57 | tegra_iovmm_vm_insert_pfn(h->pgalloc.area, va, | ||
58 | page_to_pfn(h->pgalloc.pages[i])); | ||
59 | } | ||
60 | h->pgalloc.dirty = false; | ||
61 | } | ||
62 | |||
63 | /* must be called inside nvmap_pin_lock, to ensure that an entire stream | ||
64 | * of pins will complete without racing with a second stream. handle should | ||
65 | * have nvmap_handle_get (or nvmap_validate_get) called before calling | ||
66 | * this function. */ | ||
67 | static int pin_locked(struct nvmap_client *client, struct nvmap_handle *h) | ||
68 | { | ||
69 | struct tegra_iovmm_area *area; | ||
70 | BUG_ON(!h->alloc); | ||
71 | |||
72 | nvmap_mru_lock(client->share); | ||
73 | if (atomic_inc_return(&h->pin) == 1) { | ||
74 | if (h->heap_pgalloc && !h->pgalloc.contig) { | ||
75 | area = nvmap_handle_iovmm_locked(client, h); | ||
76 | if (!area) { | ||
77 | /* no race here, inside the pin mutex */ | ||
78 | atomic_dec(&h->pin); | ||
79 | nvmap_mru_unlock(client->share); | ||
80 | return -ENOMEM; | ||
81 | } | ||
82 | if (area != h->pgalloc.area) | ||
83 | h->pgalloc.dirty = true; | ||
84 | h->pgalloc.area = area; | ||
85 | } | ||
86 | } | ||
87 | nvmap_mru_unlock(client->share); | ||
88 | return 0; | ||
89 | } | ||
90 | |||
91 | /* doesn't need to be called inside nvmap_pin_lock, since this will only | ||
92 | * expand the available VM area */ | ||
93 | static int handle_unpin(struct nvmap_client *client, | ||
94 | struct nvmap_handle *h, int free_vm) | ||
95 | { | ||
96 | int ret = 0; | ||
97 | nvmap_mru_lock(client->share); | ||
98 | |||
99 | if (atomic_read(&h->pin) == 0) { | ||
100 | nvmap_err(client, "%s unpinning unpinned handle %p\n", | ||
101 | current->group_leader->comm, h); | ||
102 | nvmap_mru_unlock(client->share); | ||
103 | return 0; | ||
104 | } | ||
105 | |||
106 | BUG_ON(!h->alloc); | ||
107 | |||
108 | if (!atomic_dec_return(&h->pin)) { | ||
109 | if (h->heap_pgalloc && h->pgalloc.area) { | ||
110 | /* if a secure handle is clean (i.e., mapped into | ||
111 | * IOVMM, it needs to be zapped on unpin. */ | ||
112 | if (h->secure && !h->pgalloc.dirty) { | ||
113 | tegra_iovmm_zap_vm(h->pgalloc.area); | ||
114 | h->pgalloc.dirty = true; | ||
115 | } | ||
116 | if (free_vm) { | ||
117 | tegra_iovmm_free_vm(h->pgalloc.area); | ||
118 | h->pgalloc.area = NULL; | ||
119 | } else | ||
120 | nvmap_mru_insert_locked(client->share, h); | ||
121 | ret = 1; | ||
122 | } | ||
123 | } | ||
124 | |||
125 | nvmap_mru_unlock(client->share); | ||
126 | nvmap_handle_put(h); | ||
127 | return ret; | ||
128 | } | ||
129 | |||
130 | static int pin_array_locked(struct nvmap_client *client, | ||
131 | struct nvmap_handle **h, int count) | ||
132 | { | ||
133 | int pinned; | ||
134 | int i; | ||
135 | int err = 0; | ||
136 | |||
137 | for (pinned = 0; pinned < count; pinned++) { | ||
138 | err = pin_locked(client, h[pinned]); | ||
139 | if (err) | ||
140 | break; | ||
141 | } | ||
142 | |||
143 | if (err) { | ||
144 | /* unpin pinned handles */ | ||
145 | for (i = 0; i < pinned; i++) { | ||
146 | /* inc ref counter, because | ||
147 | * handle_unpin decrements it */ | ||
148 | nvmap_handle_get(h[i]); | ||
149 | /* unpin handles and free vm */ | ||
150 | handle_unpin(client, h[i], true); | ||
151 | } | ||
152 | } | ||
153 | |||
154 | if (err && tegra_iovmm_get_max_free(client->share->iovmm) >= | ||
155 | client->iovm_limit) { | ||
156 | /* First attempt to pin in empty iovmm | ||
157 | * may still fail because of fragmentation caused by | ||
158 | * placing handles in MRU areas. After such failure | ||
159 | * all MRU gets cleaned and iovm space is freed. | ||
160 | * | ||
161 | * We have to do pinning again here since there might be is | ||
162 | * no more incoming pin_wait wakeup calls from unpin | ||
163 | * operations */ | ||
164 | for (pinned = 0; pinned < count; pinned++) { | ||
165 | err = pin_locked(client, h[pinned]); | ||
166 | if (err) | ||
167 | break; | ||
168 | } | ||
169 | if (err) { | ||
170 | pr_err("Pinning in empty iovmm failed!!!\n"); | ||
171 | BUG_ON(1); | ||
172 | } | ||
173 | } | ||
174 | return err; | ||
175 | } | ||
176 | |||
177 | static int wait_pin_array_locked(struct nvmap_client *client, | ||
178 | struct nvmap_handle **h, int count) | ||
179 | { | ||
180 | int ret = 0; | ||
181 | |||
182 | ret = pin_array_locked(client, h, count); | ||
183 | |||
184 | if (ret) { | ||
185 | ret = wait_event_interruptible(client->share->pin_wait, | ||
186 | !pin_array_locked(client, h, count)); | ||
187 | } | ||
188 | return ret ? -EINTR : 0; | ||
189 | } | ||
190 | |||
191 | static int handle_unpin_noref(struct nvmap_client *client, unsigned long id) | ||
192 | { | ||
193 | struct nvmap_handle *h; | ||
194 | int w; | ||
195 | |||
196 | h = nvmap_validate_get(client, id); | ||
197 | if (unlikely(!h)) { | ||
198 | nvmap_err(client, "%s attempting to unpin invalid handle %p\n", | ||
199 | current->group_leader->comm, (void *)id); | ||
200 | return 0; | ||
201 | } | ||
202 | |||
203 | nvmap_err(client, "%s unpinning unreferenced handle %p\n", | ||
204 | current->group_leader->comm, h); | ||
205 | WARN_ON(1); | ||
206 | |||
207 | w = handle_unpin(client, h, false); | ||
208 | nvmap_handle_put(h); | ||
209 | return w; | ||
210 | } | ||
211 | |||
212 | void nvmap_unpin_ids(struct nvmap_client *client, | ||
213 | unsigned int nr, const unsigned long *ids) | ||
214 | { | ||
215 | unsigned int i; | ||
216 | int do_wake = 0; | ||
217 | |||
218 | for (i = 0; i < nr; i++) { | ||
219 | struct nvmap_handle_ref *ref; | ||
220 | |||
221 | if (!ids[i]) | ||
222 | continue; | ||
223 | |||
224 | nvmap_ref_lock(client); | ||
225 | ref = _nvmap_validate_id_locked(client, ids[i]); | ||
226 | if (ref) { | ||
227 | struct nvmap_handle *h = ref->handle; | ||
228 | int e = atomic_add_unless(&ref->pin, -1, 0); | ||
229 | |||
230 | nvmap_ref_unlock(client); | ||
231 | |||
232 | if (!e) { | ||
233 | nvmap_err(client, "%s unpinning unpinned " | ||
234 | "handle %08lx\n", | ||
235 | current->group_leader->comm, ids[i]); | ||
236 | } else { | ||
237 | do_wake |= handle_unpin(client, h, false); | ||
238 | } | ||
239 | } else { | ||
240 | nvmap_ref_unlock(client); | ||
241 | if (client->super) | ||
242 | do_wake |= handle_unpin_noref(client, ids[i]); | ||
243 | else | ||
244 | nvmap_err(client, "%s unpinning invalid " | ||
245 | "handle %08lx\n", | ||
246 | current->group_leader->comm, ids[i]); | ||
247 | } | ||
248 | } | ||
249 | |||
250 | if (do_wake) | ||
251 | wake_up(&client->share->pin_wait); | ||
252 | } | ||
253 | |||
254 | /* pins a list of handle_ref objects; same conditions apply as to | ||
255 | * _nvmap_handle_pin, but also bumps the pin count of each handle_ref. */ | ||
256 | int nvmap_pin_ids(struct nvmap_client *client, | ||
257 | unsigned int nr, const unsigned long *ids) | ||
258 | { | ||
259 | int ret = 0; | ||
260 | unsigned int i; | ||
261 | struct nvmap_handle **h = (struct nvmap_handle **)ids; | ||
262 | struct nvmap_handle_ref *ref; | ||
263 | |||
264 | /* to optimize for the common case (client provided valid handle | ||
265 | * references and the pin succeeds), increment the handle_ref pin | ||
266 | * count during validation. in error cases, the tree will need to | ||
267 | * be re-walked, since the handle_ref is discarded so that an | ||
268 | * allocation isn't required. if a handle_ref is not found, | ||
269 | * locally validate that the caller has permission to pin the handle; | ||
270 | * handle_refs are not created in this case, so it is possible that | ||
271 | * if the caller crashes after pinning a global handle, the handle | ||
272 | * will be permanently leaked. */ | ||
273 | nvmap_ref_lock(client); | ||
274 | for (i = 0; i < nr && !ret; i++) { | ||
275 | ref = _nvmap_validate_id_locked(client, ids[i]); | ||
276 | if (ref) { | ||
277 | atomic_inc(&ref->pin); | ||
278 | nvmap_handle_get(h[i]); | ||
279 | } else { | ||
280 | struct nvmap_handle *verify; | ||
281 | nvmap_ref_unlock(client); | ||
282 | verify = nvmap_validate_get(client, ids[i]); | ||
283 | if (verify) | ||
284 | nvmap_warn(client, "%s pinning unreferenced " | ||
285 | "handle %p\n", | ||
286 | current->group_leader->comm, h[i]); | ||
287 | else | ||
288 | ret = -EPERM; | ||
289 | nvmap_ref_lock(client); | ||
290 | } | ||
291 | } | ||
292 | nvmap_ref_unlock(client); | ||
293 | |||
294 | nr = i; | ||
295 | |||
296 | if (ret) | ||
297 | goto out; | ||
298 | |||
299 | ret = mutex_lock_interruptible(&client->share->pin_lock); | ||
300 | if (WARN_ON(ret)) | ||
301 | goto out; | ||
302 | |||
303 | ret = wait_pin_array_locked(client, h, nr); | ||
304 | |||
305 | mutex_unlock(&client->share->pin_lock); | ||
306 | |||
307 | if (ret) { | ||
308 | ret = -EINTR; | ||
309 | } else { | ||
310 | for (i = 0; i < nr; i++) { | ||
311 | if (h[i]->heap_pgalloc && h[i]->pgalloc.dirty) | ||
312 | map_iovmm_area(h[i]); | ||
313 | } | ||
314 | } | ||
315 | |||
316 | out: | ||
317 | if (ret) { | ||
318 | nvmap_ref_lock(client); | ||
319 | for (i = 0; i < nr; i++) { | ||
320 | ref = _nvmap_validate_id_locked(client, ids[i]); | ||
321 | if (!ref) { | ||
322 | nvmap_warn(client, "%s freed handle %p " | ||
323 | "during pinning\n", | ||
324 | current->group_leader->comm, | ||
325 | (void *)ids[i]); | ||
326 | continue; | ||
327 | } | ||
328 | atomic_dec(&ref->pin); | ||
329 | } | ||
330 | nvmap_ref_unlock(client); | ||
331 | |||
332 | for (i = 0; i < nr; i++) | ||
333 | nvmap_handle_put(h[i]); | ||
334 | } | ||
335 | |||
336 | return ret; | ||
337 | } | ||
338 | |||
339 | static phys_addr_t handle_phys(struct nvmap_handle *h) | ||
340 | { | ||
341 | phys_addr_t addr; | ||
342 | |||
343 | if (h->heap_pgalloc && h->pgalloc.contig) { | ||
344 | addr = page_to_phys(h->pgalloc.pages[0]); | ||
345 | } else if (h->heap_pgalloc) { | ||
346 | BUG_ON(!h->pgalloc.area); | ||
347 | addr = h->pgalloc.area->iovm_start; | ||
348 | } else { | ||
349 | addr = h->carveout->base; | ||
350 | } | ||
351 | |||
352 | return addr; | ||
353 | } | ||
354 | |||
355 | /* stores the physical address (+offset) of each handle relocation entry | ||
356 | * into its output location. see nvmap_pin_array for more details. | ||
357 | * | ||
358 | * each entry in arr (i.e., each relocation request) specifies two handles: | ||
359 | * the handle to pin (pin), and the handle where the address of pin should be | ||
360 | * written (patch). in pseudocode, this loop basically looks like: | ||
361 | * | ||
362 | * for (i = 0; i < nr; i++) { | ||
363 | * (pin, pin_offset, patch, patch_offset) = arr[i]; | ||
364 | * patch[patch_offset] = address_of(pin) + pin_offset; | ||
365 | * } | ||
366 | */ | ||
367 | static int nvmap_reloc_pin_array(struct nvmap_client *client, | ||
368 | const struct nvmap_pinarray_elem *arr, | ||
369 | int nr, struct nvmap_handle *gather) | ||
370 | { | ||
371 | struct nvmap_handle *last_patch = NULL; | ||
372 | unsigned int last_pfn = 0; | ||
373 | pte_t **pte; | ||
374 | void *addr; | ||
375 | int i; | ||
376 | |||
377 | pte = nvmap_alloc_pte(client->dev, &addr); | ||
378 | if (IS_ERR(pte)) | ||
379 | return PTR_ERR(pte); | ||
380 | |||
381 | for (i = 0; i < nr; i++) { | ||
382 | struct nvmap_handle *patch; | ||
383 | struct nvmap_handle *pin; | ||
384 | phys_addr_t reloc_addr; | ||
385 | phys_addr_t phys; | ||
386 | unsigned int pfn; | ||
387 | |||
388 | /* all of the handles are validated and get'ted prior to | ||
389 | * calling this function, so casting is safe here */ | ||
390 | pin = (struct nvmap_handle *)arr[i].pin_mem; | ||
391 | |||
392 | if (arr[i].patch_mem == (unsigned long)last_patch) { | ||
393 | patch = last_patch; | ||
394 | } else if (arr[i].patch_mem == (unsigned long)gather) { | ||
395 | patch = gather; | ||
396 | } else { | ||
397 | if (last_patch) | ||
398 | nvmap_handle_put(last_patch); | ||
399 | |||
400 | patch = nvmap_get_handle_id(client, arr[i].patch_mem); | ||
401 | if (!patch) { | ||
402 | nvmap_free_pte(client->dev, pte); | ||
403 | return -EPERM; | ||
404 | } | ||
405 | last_patch = patch; | ||
406 | } | ||
407 | |||
408 | if (patch->heap_pgalloc) { | ||
409 | unsigned int page = arr[i].patch_offset >> PAGE_SHIFT; | ||
410 | phys = page_to_phys(patch->pgalloc.pages[page]); | ||
411 | phys += (arr[i].patch_offset & ~PAGE_MASK); | ||
412 | } else { | ||
413 | phys = patch->carveout->base + arr[i].patch_offset; | ||
414 | } | ||
415 | |||
416 | pfn = __phys_to_pfn(phys); | ||
417 | if (pfn != last_pfn) { | ||
418 | pgprot_t prot = nvmap_pgprot(patch, pgprot_kernel); | ||
419 | phys_addr_t kaddr = (phys_addr_t)addr; | ||
420 | set_pte_at(&init_mm, kaddr, *pte, pfn_pte(pfn, prot)); | ||
421 | flush_tlb_kernel_page(kaddr); | ||
422 | last_pfn = pfn; | ||
423 | } | ||
424 | |||
425 | reloc_addr = handle_phys(pin) + arr[i].pin_offset; | ||
426 | reloc_addr >>= arr[i].reloc_shift; | ||
427 | __raw_writel(reloc_addr, addr + (phys & ~PAGE_MASK)); | ||
428 | } | ||
429 | |||
430 | nvmap_free_pte(client->dev, pte); | ||
431 | |||
432 | if (last_patch) | ||
433 | nvmap_handle_put(last_patch); | ||
434 | |||
435 | wmb(); | ||
436 | |||
437 | return 0; | ||
438 | } | ||
439 | |||
440 | static int nvmap_validate_get_pin_array(struct nvmap_client *client, | ||
441 | const struct nvmap_pinarray_elem *arr, | ||
442 | int nr, struct nvmap_handle **h) | ||
443 | { | ||
444 | int i; | ||
445 | int ret = 0; | ||
446 | int count = 0; | ||
447 | |||
448 | nvmap_ref_lock(client); | ||
449 | |||
450 | for (i = 0; i < nr; i++) { | ||
451 | struct nvmap_handle_ref *ref; | ||
452 | |||
453 | if (need_resched()) { | ||
454 | nvmap_ref_unlock(client); | ||
455 | schedule(); | ||
456 | nvmap_ref_lock(client); | ||
457 | } | ||
458 | |||
459 | ref = _nvmap_validate_id_locked(client, arr[i].pin_mem); | ||
460 | |||
461 | if (!ref) | ||
462 | nvmap_warn(client, "falied to validate id\n"); | ||
463 | else if (!ref->handle) | ||
464 | nvmap_warn(client, "id had no associated handle\n"); | ||
465 | else if (!ref->handle->alloc) | ||
466 | nvmap_warn(client, "handle had no allocation\n"); | ||
467 | |||
468 | if (!ref || !ref->handle || !ref->handle->alloc) { | ||
469 | ret = -EPERM; | ||
470 | break; | ||
471 | } | ||
472 | |||
473 | /* a handle may be referenced multiple times in arr, but | ||
474 | * it will only be pinned once; this ensures that the | ||
475 | * minimum number of sync-queue slots in the host driver | ||
476 | * are dedicated to storing unpin lists, which allows | ||
477 | * for greater parallelism between the CPU and graphics | ||
478 | * processor */ | ||
479 | if (ref->handle->flags & NVMAP_HANDLE_VISITED) | ||
480 | continue; | ||
481 | |||
482 | ref->handle->flags |= NVMAP_HANDLE_VISITED; | ||
483 | |||
484 | h[count] = nvmap_handle_get(ref->handle); | ||
485 | BUG_ON(!h[count]); | ||
486 | count++; | ||
487 | } | ||
488 | |||
489 | nvmap_ref_unlock(client); | ||
490 | |||
491 | if (ret) { | ||
492 | for (i = 0; i < count; i++) { | ||
493 | h[i]->flags &= ~NVMAP_HANDLE_VISITED; | ||
494 | nvmap_handle_put(h[i]); | ||
495 | } | ||
496 | } | ||
497 | |||
498 | return ret ?: count; | ||
499 | } | ||
500 | |||
501 | /* a typical mechanism host1x clients use for using the Tegra graphics | ||
502 | * processor is to build a command buffer which contains relocatable | ||
503 | * memory handle commands, and rely on the kernel to convert these in-place | ||
504 | * to addresses which are understood by the GPU hardware. | ||
505 | * | ||
506 | * this is implemented by having clients provide a sideband array | ||
507 | * of relocatable handles (+ offsets) and the location in the command | ||
508 | * buffer handle to patch with the GPU address when the client submits | ||
509 | * its command buffer to the host1x driver. | ||
510 | * | ||
511 | * the host driver also uses this relocation mechanism internally to | ||
512 | * relocate the client's (unpinned) command buffers into host-addressable | ||
513 | * memory. | ||
514 | * | ||
515 | * @client: nvmap_client which should be used for validation; should be | ||
516 | * owned by the process which is submitting command buffers | ||
517 | * @gather: special handle for relocated command buffer outputs used | ||
518 | * internally by the host driver. if this handle is encountered | ||
519 | * as an output handle in the relocation array, it is assumed | ||
520 | * to be a known-good output and is not validated. | ||
521 | * @arr: array of ((relocatable handle, offset), (output handle, offset)) | ||
522 | * tuples. | ||
523 | * @nr: number of entries in arr | ||
524 | * @unique_arr: list of nvmap_handle objects which were pinned by | ||
525 | * nvmap_pin_array. must be unpinned by the caller after the | ||
526 | * command buffers referenced in gather have completed. | ||
527 | */ | ||
528 | int nvmap_pin_array(struct nvmap_client *client, struct nvmap_handle *gather, | ||
529 | const struct nvmap_pinarray_elem *arr, int nr, | ||
530 | struct nvmap_handle **unique_arr) | ||
531 | { | ||
532 | int count = 0; | ||
533 | int ret = 0; | ||
534 | int i; | ||
535 | |||
536 | if (mutex_lock_interruptible(&client->share->pin_lock)) { | ||
537 | nvmap_warn(client, "%s interrupted when acquiring pin lock\n", | ||
538 | current->group_leader->comm); | ||
539 | return -EINTR; | ||
540 | } | ||
541 | |||
542 | count = nvmap_validate_get_pin_array(client, arr, nr, unique_arr); | ||
543 | if (count < 0) { | ||
544 | mutex_unlock(&client->share->pin_lock); | ||
545 | nvmap_warn(client, "failed to validate pin array\n"); | ||
546 | return count; | ||
547 | } | ||
548 | |||
549 | for (i = 0; i < count; i++) | ||
550 | unique_arr[i]->flags &= ~NVMAP_HANDLE_VISITED; | ||
551 | |||
552 | ret = wait_pin_array_locked(client, unique_arr, count); | ||
553 | |||
554 | mutex_unlock(&client->share->pin_lock); | ||
555 | |||
556 | if (!ret) | ||
557 | ret = nvmap_reloc_pin_array(client, arr, nr, gather); | ||
558 | |||
559 | if (WARN_ON(ret)) { | ||
560 | for (i = 0; i < count; i++) | ||
561 | nvmap_handle_put(unique_arr[i]); | ||
562 | return ret; | ||
563 | } else { | ||
564 | for (i = 0; i < count; i++) { | ||
565 | if (unique_arr[i]->heap_pgalloc && | ||
566 | unique_arr[i]->pgalloc.dirty) | ||
567 | map_iovmm_area(unique_arr[i]); | ||
568 | } | ||
569 | } | ||
570 | |||
571 | return count; | ||
572 | } | ||
573 | |||
574 | phys_addr_t nvmap_pin(struct nvmap_client *client, | ||
575 | struct nvmap_handle_ref *ref) | ||
576 | { | ||
577 | struct nvmap_handle *h; | ||
578 | phys_addr_t phys; | ||
579 | int ret = 0; | ||
580 | |||
581 | h = nvmap_handle_get(ref->handle); | ||
582 | if (WARN_ON(!h)) | ||
583 | return -EINVAL; | ||
584 | |||
585 | atomic_inc(&ref->pin); | ||
586 | |||
587 | if (WARN_ON(mutex_lock_interruptible(&client->share->pin_lock))) { | ||
588 | ret = -EINTR; | ||
589 | } else { | ||
590 | ret = wait_pin_array_locked(client, &h, 1); | ||
591 | mutex_unlock(&client->share->pin_lock); | ||
592 | } | ||
593 | |||
594 | if (ret) { | ||
595 | atomic_dec(&ref->pin); | ||
596 | nvmap_handle_put(h); | ||
597 | } else { | ||
598 | if (h->heap_pgalloc && h->pgalloc.dirty) | ||
599 | map_iovmm_area(h); | ||
600 | phys = handle_phys(h); | ||
601 | } | ||
602 | |||
603 | return ret ?: phys; | ||
604 | } | ||
605 | |||
606 | phys_addr_t nvmap_handle_address(struct nvmap_client *c, unsigned long id) | ||
607 | { | ||
608 | struct nvmap_handle *h; | ||
609 | phys_addr_t phys; | ||
610 | |||
611 | h = nvmap_get_handle_id(c, id); | ||
612 | if (!h) | ||
613 | return -EPERM; | ||
614 | mutex_lock(&h->lock); | ||
615 | phys = handle_phys(h); | ||
616 | mutex_unlock(&h->lock); | ||
617 | nvmap_handle_put(h); | ||
618 | |||
619 | return phys; | ||
620 | } | ||
621 | |||
622 | void nvmap_unpin(struct nvmap_client *client, struct nvmap_handle_ref *ref) | ||
623 | { | ||
624 | if (!ref) | ||
625 | return; | ||
626 | |||
627 | atomic_dec(&ref->pin); | ||
628 | if (handle_unpin(client, ref->handle, false)) | ||
629 | wake_up(&client->share->pin_wait); | ||
630 | } | ||
631 | |||
632 | void nvmap_unpin_handles(struct nvmap_client *client, | ||
633 | struct nvmap_handle **h, int nr) | ||
634 | { | ||
635 | int i; | ||
636 | int do_wake = 0; | ||
637 | |||
638 | for (i = 0; i < nr; i++) { | ||
639 | if (WARN_ON(!h[i])) | ||
640 | continue; | ||
641 | do_wake |= handle_unpin(client, h[i], false); | ||
642 | } | ||
643 | |||
644 | if (do_wake) | ||
645 | wake_up(&client->share->pin_wait); | ||
646 | } | ||
647 | |||
648 | void *nvmap_mmap(struct nvmap_handle_ref *ref) | ||
649 | { | ||
650 | struct nvmap_handle *h; | ||
651 | pgprot_t prot; | ||
652 | unsigned long adj_size; | ||
653 | unsigned long offs; | ||
654 | struct vm_struct *v; | ||
655 | void *p; | ||
656 | |||
657 | h = nvmap_handle_get(ref->handle); | ||
658 | if (!h) | ||
659 | return NULL; | ||
660 | |||
661 | prot = nvmap_pgprot(h, pgprot_kernel); | ||
662 | |||
663 | if (h->heap_pgalloc) | ||
664 | return vm_map_ram(h->pgalloc.pages, h->size >> PAGE_SHIFT, | ||
665 | -1, prot); | ||
666 | |||
667 | /* carveout - explicitly map the pfns into a vmalloc area */ | ||
668 | |||
669 | nvmap_usecount_inc(h); | ||
670 | |||
671 | adj_size = h->carveout->base & ~PAGE_MASK; | ||
672 | adj_size += h->size; | ||
673 | adj_size = PAGE_ALIGN(adj_size); | ||
674 | |||
675 | v = alloc_vm_area(adj_size); | ||
676 | if (!v) { | ||
677 | nvmap_usecount_dec(h); | ||
678 | nvmap_handle_put(h); | ||
679 | return NULL; | ||
680 | } | ||
681 | |||
682 | p = v->addr + (h->carveout->base & ~PAGE_MASK); | ||
683 | |||
684 | for (offs = 0; offs < adj_size; offs += PAGE_SIZE) { | ||
685 | unsigned long addr = (unsigned long) v->addr + offs; | ||
686 | unsigned int pfn; | ||
687 | pgd_t *pgd; | ||
688 | pud_t *pud; | ||
689 | pmd_t *pmd; | ||
690 | pte_t *pte; | ||
691 | |||
692 | pfn = __phys_to_pfn(h->carveout->base + offs); | ||
693 | pgd = pgd_offset_k(addr); | ||
694 | pud = pud_alloc(&init_mm, pgd, addr); | ||
695 | if (!pud) | ||
696 | break; | ||
697 | pmd = pmd_alloc(&init_mm, pud, addr); | ||
698 | if (!pmd) | ||
699 | break; | ||
700 | pte = pte_alloc_kernel(pmd, addr); | ||
701 | if (!pte) | ||
702 | break; | ||
703 | set_pte_at(&init_mm, addr, pte, pfn_pte(pfn, prot)); | ||
704 | flush_tlb_kernel_page(addr); | ||
705 | } | ||
706 | |||
707 | if (offs != adj_size) { | ||
708 | free_vm_area(v); | ||
709 | nvmap_usecount_dec(h); | ||
710 | nvmap_handle_put(h); | ||
711 | return NULL; | ||
712 | } | ||
713 | |||
714 | /* leave the handle ref count incremented by 1, so that | ||
715 | * the handle will not be freed while the kernel mapping exists. | ||
716 | * nvmap_handle_put will be called by unmapping this address */ | ||
717 | return p; | ||
718 | } | ||
719 | |||
720 | void nvmap_munmap(struct nvmap_handle_ref *ref, void *addr) | ||
721 | { | ||
722 | struct nvmap_handle *h; | ||
723 | |||
724 | if (!ref) | ||
725 | return; | ||
726 | |||
727 | h = ref->handle; | ||
728 | |||
729 | if (h->heap_pgalloc) { | ||
730 | vm_unmap_ram(addr, h->size >> PAGE_SHIFT); | ||
731 | } else { | ||
732 | struct vm_struct *vm; | ||
733 | addr -= (h->carveout->base & ~PAGE_MASK); | ||
734 | vm = remove_vm_area(addr); | ||
735 | BUG_ON(!vm); | ||
736 | kfree(vm); | ||
737 | nvmap_usecount_dec(h); | ||
738 | } | ||
739 | nvmap_handle_put(h); | ||
740 | } | ||
741 | |||
742 | struct nvmap_handle_ref *nvmap_alloc(struct nvmap_client *client, size_t size, | ||
743 | size_t align, unsigned int flags, | ||
744 | unsigned int heap_mask) | ||
745 | { | ||
746 | const unsigned int default_heap = (NVMAP_HEAP_SYSMEM | | ||
747 | NVMAP_HEAP_CARVEOUT_GENERIC); | ||
748 | struct nvmap_handle_ref *r = NULL; | ||
749 | int err; | ||
750 | |||
751 | if (heap_mask == 0) | ||
752 | heap_mask = default_heap; | ||
753 | |||
754 | r = nvmap_create_handle(client, size); | ||
755 | if (IS_ERR(r)) | ||
756 | return r; | ||
757 | |||
758 | err = nvmap_alloc_handle_id(client, nvmap_ref_to_id(r), | ||
759 | heap_mask, align, flags); | ||
760 | |||
761 | if (err) { | ||
762 | nvmap_free_handle_id(client, nvmap_ref_to_id(r)); | ||
763 | return ERR_PTR(err); | ||
764 | } | ||
765 | |||
766 | return r; | ||
767 | } | ||
768 | |||
769 | /* allocates memory with specifed iovm_start address. */ | ||
770 | struct nvmap_handle_ref *nvmap_alloc_iovm(struct nvmap_client *client, | ||
771 | size_t size, size_t align, unsigned int flags, unsigned int iovm_start) | ||
772 | { | ||
773 | int err; | ||
774 | struct nvmap_handle *h; | ||
775 | struct nvmap_handle_ref *r; | ||
776 | const unsigned int default_heap = NVMAP_HEAP_IOVMM; | ||
777 | |||
778 | /* size need to be more than one page. | ||
779 | * otherwise heap preference would change to system heap. | ||
780 | */ | ||
781 | if (size <= PAGE_SIZE) | ||
782 | size = PAGE_SIZE << 1; | ||
783 | r = nvmap_create_handle(client, size); | ||
784 | if (IS_ERR_OR_NULL(r)) | ||
785 | return r; | ||
786 | |||
787 | h = r->handle; | ||
788 | h->pgalloc.iovm_addr = iovm_start; | ||
789 | err = nvmap_alloc_handle_id(client, nvmap_ref_to_id(r), | ||
790 | default_heap, align, flags); | ||
791 | if (err) | ||
792 | goto fail; | ||
793 | |||
794 | err = mutex_lock_interruptible(&client->share->pin_lock); | ||
795 | if (WARN_ON(err)) | ||
796 | goto fail; | ||
797 | err = pin_locked(client, h); | ||
798 | mutex_unlock(&client->share->pin_lock); | ||
799 | if (err) | ||
800 | goto fail; | ||
801 | return r; | ||
802 | |||
803 | fail: | ||
804 | nvmap_free_handle_id(client, nvmap_ref_to_id(r)); | ||
805 | return ERR_PTR(err); | ||
806 | } | ||
807 | |||
808 | void nvmap_free_iovm(struct nvmap_client *client, struct nvmap_handle_ref *r) | ||
809 | { | ||
810 | unsigned long ref_id = nvmap_ref_to_id(r); | ||
811 | |||
812 | nvmap_unpin_ids(client, 1, &ref_id); | ||
813 | nvmap_free_handle_id(client, ref_id); | ||
814 | } | ||
815 | |||
816 | void nvmap_free(struct nvmap_client *client, struct nvmap_handle_ref *r) | ||
817 | { | ||
818 | if (!r) | ||
819 | return; | ||
820 | |||
821 | nvmap_free_handle_id(client, nvmap_ref_to_id(r)); | ||
822 | } | ||
823 | |||
824 | /* | ||
825 | * create a mapping to the user's buffer and write it | ||
826 | * (uses similar logic from nvmap_reloc_pin_array to map the cmdbuf) | ||
827 | */ | ||
828 | int nvmap_patch_word(struct nvmap_client *client, | ||
829 | struct nvmap_handle *patch, | ||
830 | u32 patch_offset, u32 patch_value) | ||
831 | { | ||
832 | phys_addr_t phys; | ||
833 | unsigned long kaddr; | ||
834 | unsigned int pfn; | ||
835 | void *addr; | ||
836 | pte_t **pte; | ||
837 | pgprot_t prot; | ||
838 | |||
839 | if (patch_offset >= patch->size) { | ||
840 | nvmap_warn(client, "read/write outside of handle\n"); | ||
841 | return -EFAULT; | ||
842 | } | ||
843 | |||
844 | pte = nvmap_alloc_pte(client->dev, &addr); | ||
845 | if (IS_ERR(pte)) | ||
846 | return PTR_ERR(pte); | ||
847 | |||
848 | /* derive physaddr of cmdbuf WAIT to patch */ | ||
849 | if (patch->heap_pgalloc) { | ||
850 | unsigned int page = patch_offset >> PAGE_SHIFT; | ||
851 | phys = page_to_phys(patch->pgalloc.pages[page]); | ||
852 | phys += (patch_offset & ~PAGE_MASK); | ||
853 | } else { | ||
854 | phys = patch->carveout->base + patch_offset; | ||
855 | } | ||
856 | |||
857 | pfn = __phys_to_pfn(phys); | ||
858 | prot = nvmap_pgprot(patch, pgprot_kernel); | ||
859 | kaddr = (unsigned long)addr; | ||
860 | |||
861 | /* write PTE, so addr points to cmdbuf PFN */ | ||
862 | set_pte_at(&init_mm, kaddr, *pte, pfn_pte(pfn, prot)); | ||
863 | flush_tlb_kernel_page(kaddr); | ||
864 | |||
865 | /* write patch_value to addr + page offset */ | ||
866 | __raw_writel(patch_value, addr + (phys & ~PAGE_MASK)); | ||
867 | |||
868 | nvmap_free_pte(client->dev, pte); | ||
869 | wmb(); | ||
870 | return 0; | ||
871 | } | ||
diff --git a/drivers/video/tegra/nvmap/nvmap.h b/drivers/video/tegra/nvmap/nvmap.h new file mode 100644 index 00000000000..44a0d86b603 --- /dev/null +++ b/drivers/video/tegra/nvmap/nvmap.h | |||
@@ -0,0 +1,271 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/nvmap/nvmap.h | ||
3 | * | ||
4 | * GPU memory management driver for Tegra | ||
5 | * | ||
6 | * Copyright (c) 2010-2012, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | *' | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | #ifndef __VIDEO_TEGRA_NVMAP_NVMAP_H | ||
24 | #define __VIDEO_TEGRA_NVMAP_NVMAP_H | ||
25 | |||
26 | #include <linux/list.h> | ||
27 | #include <linux/mm.h> | ||
28 | #include <linux/mutex.h> | ||
29 | #include <linux/rbtree.h> | ||
30 | #include <linux/sched.h> | ||
31 | #include <linux/wait.h> | ||
32 | #include <linux/atomic.h> | ||
33 | #include <mach/nvmap.h> | ||
34 | #include "nvmap_heap.h" | ||
35 | |||
36 | struct nvmap_device; | ||
37 | struct page; | ||
38 | struct tegra_iovmm_area; | ||
39 | |||
40 | #if defined(CONFIG_TEGRA_NVMAP) | ||
41 | #define nvmap_err(_client, _fmt, ...) \ | ||
42 | dev_err(nvmap_client_to_device(_client), \ | ||
43 | "%s: "_fmt, __func__, ##__VA_ARGS__) | ||
44 | |||
45 | #define nvmap_warn(_client, _fmt, ...) \ | ||
46 | dev_warn(nvmap_client_to_device(_client), \ | ||
47 | "%s: "_fmt, __func__, ##__VA_ARGS__) | ||
48 | |||
49 | #define nvmap_debug(_client, _fmt, ...) \ | ||
50 | dev_dbg(nvmap_client_to_device(_client), \ | ||
51 | "%s: "_fmt, __func__, ##__VA_ARGS__) | ||
52 | |||
53 | #define nvmap_ref_to_id(_ref) ((unsigned long)(_ref)->handle) | ||
54 | |||
55 | /* handles allocated using shared system memory (either IOVMM- or high-order | ||
56 | * page allocations */ | ||
57 | struct nvmap_pgalloc { | ||
58 | struct page **pages; | ||
59 | struct tegra_iovmm_area *area; | ||
60 | struct list_head mru_list; /* MRU entry for IOVMM reclamation */ | ||
61 | bool contig; /* contiguous system memory */ | ||
62 | bool dirty; /* area is invalid and needs mapping */ | ||
63 | u32 iovm_addr; /* is non-zero, if client need specific iova mapping */ | ||
64 | }; | ||
65 | |||
66 | struct nvmap_handle { | ||
67 | struct rb_node node; /* entry on global handle tree */ | ||
68 | atomic_t ref; /* reference count (i.e., # of duplications) */ | ||
69 | atomic_t pin; /* pin count */ | ||
70 | unsigned int usecount; /* how often is used */ | ||
71 | unsigned long flags; | ||
72 | size_t size; /* padded (as-allocated) size */ | ||
73 | size_t orig_size; /* original (as-requested) size */ | ||
74 | size_t align; | ||
75 | struct nvmap_client *owner; | ||
76 | struct nvmap_device *dev; | ||
77 | union { | ||
78 | struct nvmap_pgalloc pgalloc; | ||
79 | struct nvmap_heap_block *carveout; | ||
80 | }; | ||
81 | bool global; /* handle may be duplicated by other clients */ | ||
82 | bool secure; /* zap IOVMM area on unpin */ | ||
83 | bool heap_pgalloc; /* handle is page allocated (sysmem / iovmm) */ | ||
84 | bool alloc; /* handle has memory allocated */ | ||
85 | unsigned int userflags; /* flags passed from userspace */ | ||
86 | struct mutex lock; | ||
87 | }; | ||
88 | |||
89 | #define NVMAP_DEFAULT_PAGE_POOL_SIZE 8192 | ||
90 | #define NVMAP_UC_POOL NVMAP_HANDLE_UNCACHEABLE | ||
91 | #define NVMAP_WC_POOL NVMAP_HANDLE_WRITE_COMBINE | ||
92 | #define NVMAP_IWB_POOL NVMAP_HANDLE_INNER_CACHEABLE | ||
93 | #define NVMAP_WB_POOL NVMAP_HANDLE_CACHEABLE | ||
94 | #define NVMAP_NUM_POOLS (NVMAP_HANDLE_CACHEABLE + 1) | ||
95 | |||
96 | struct nvmap_page_pool { | ||
97 | struct mutex lock; | ||
98 | int npages; | ||
99 | struct page **page_array; | ||
100 | struct page **shrink_array; | ||
101 | int max_pages; | ||
102 | int flags; | ||
103 | }; | ||
104 | |||
105 | int nvmap_page_pool_init(struct nvmap_page_pool *pool, int flags); | ||
106 | |||
107 | struct nvmap_share { | ||
108 | struct tegra_iovmm_client *iovmm; | ||
109 | wait_queue_head_t pin_wait; | ||
110 | struct mutex pin_lock; | ||
111 | union { | ||
112 | struct nvmap_page_pool pools[NVMAP_NUM_POOLS]; | ||
113 | struct { | ||
114 | struct nvmap_page_pool uc_pool; | ||
115 | struct nvmap_page_pool wc_pool; | ||
116 | struct nvmap_page_pool iwb_pool; | ||
117 | struct nvmap_page_pool wb_pool; | ||
118 | }; | ||
119 | }; | ||
120 | #ifdef CONFIG_NVMAP_RECLAIM_UNPINNED_VM | ||
121 | struct mutex mru_lock; | ||
122 | struct list_head *mru_lists; | ||
123 | int nr_mru; | ||
124 | #endif | ||
125 | }; | ||
126 | |||
127 | struct nvmap_carveout_commit { | ||
128 | size_t commit; | ||
129 | struct list_head list; | ||
130 | }; | ||
131 | |||
132 | struct nvmap_client { | ||
133 | const char *name; | ||
134 | struct nvmap_device *dev; | ||
135 | struct nvmap_share *share; | ||
136 | struct rb_root handle_refs; | ||
137 | atomic_t iovm_commit; | ||
138 | size_t iovm_limit; | ||
139 | struct mutex ref_lock; | ||
140 | bool super; | ||
141 | atomic_t count; | ||
142 | struct task_struct *task; | ||
143 | struct list_head list; | ||
144 | struct nvmap_carveout_commit carveout_commit[0]; | ||
145 | }; | ||
146 | |||
147 | struct nvmap_vma_priv { | ||
148 | struct nvmap_handle *handle; | ||
149 | size_t offs; | ||
150 | atomic_t count; /* number of processes cloning the VMA */ | ||
151 | }; | ||
152 | |||
153 | static inline void nvmap_ref_lock(struct nvmap_client *priv) | ||
154 | { | ||
155 | mutex_lock(&priv->ref_lock); | ||
156 | } | ||
157 | |||
158 | static inline void nvmap_ref_unlock(struct nvmap_client *priv) | ||
159 | { | ||
160 | mutex_unlock(&priv->ref_lock); | ||
161 | } | ||
162 | #endif /* CONFIG_TEGRA_NVMAP */ | ||
163 | |||
164 | struct device *nvmap_client_to_device(struct nvmap_client *client); | ||
165 | |||
166 | pte_t **nvmap_alloc_pte(struct nvmap_device *dev, void **vaddr); | ||
167 | |||
168 | pte_t **nvmap_alloc_pte_irq(struct nvmap_device *dev, void **vaddr); | ||
169 | |||
170 | void nvmap_free_pte(struct nvmap_device *dev, pte_t **pte); | ||
171 | |||
172 | void nvmap_usecount_inc(struct nvmap_handle *h); | ||
173 | void nvmap_usecount_dec(struct nvmap_handle *h); | ||
174 | |||
175 | struct nvmap_heap_block *nvmap_carveout_alloc(struct nvmap_client *dev, | ||
176 | struct nvmap_handle *handle, | ||
177 | unsigned long type); | ||
178 | |||
179 | unsigned long nvmap_carveout_usage(struct nvmap_client *c, | ||
180 | struct nvmap_heap_block *b); | ||
181 | |||
182 | struct nvmap_carveout_node; | ||
183 | void nvmap_carveout_commit_add(struct nvmap_client *client, | ||
184 | struct nvmap_carveout_node *node, size_t len); | ||
185 | |||
186 | void nvmap_carveout_commit_subtract(struct nvmap_client *client, | ||
187 | struct nvmap_carveout_node *node, | ||
188 | size_t len); | ||
189 | |||
190 | struct nvmap_share *nvmap_get_share_from_dev(struct nvmap_device *dev); | ||
191 | |||
192 | struct nvmap_handle *nvmap_validate_get(struct nvmap_client *client, | ||
193 | unsigned long handle); | ||
194 | |||
195 | struct nvmap_handle_ref *_nvmap_validate_id_locked(struct nvmap_client *priv, | ||
196 | unsigned long id); | ||
197 | |||
198 | struct nvmap_handle *nvmap_get_handle_id(struct nvmap_client *client, | ||
199 | unsigned long id); | ||
200 | |||
201 | struct nvmap_handle_ref *nvmap_create_handle(struct nvmap_client *client, | ||
202 | size_t size); | ||
203 | |||
204 | struct nvmap_handle_ref *nvmap_duplicate_handle_id(struct nvmap_client *client, | ||
205 | unsigned long id); | ||
206 | |||
207 | int nvmap_alloc_handle_id(struct nvmap_client *client, | ||
208 | unsigned long id, unsigned int heap_mask, | ||
209 | size_t align, unsigned int flags); | ||
210 | |||
211 | void nvmap_free_handle_id(struct nvmap_client *c, unsigned long id); | ||
212 | |||
213 | int nvmap_pin_ids(struct nvmap_client *client, | ||
214 | unsigned int nr, const unsigned long *ids); | ||
215 | |||
216 | void nvmap_unpin_ids(struct nvmap_client *priv, | ||
217 | unsigned int nr, const unsigned long *ids); | ||
218 | |||
219 | void _nvmap_handle_free(struct nvmap_handle *h); | ||
220 | |||
221 | int nvmap_handle_remove(struct nvmap_device *dev, struct nvmap_handle *h); | ||
222 | |||
223 | void nvmap_handle_add(struct nvmap_device *dev, struct nvmap_handle *h); | ||
224 | |||
225 | #if defined(CONFIG_TEGRA_NVMAP) | ||
226 | static inline struct nvmap_handle *nvmap_handle_get(struct nvmap_handle *h) | ||
227 | { | ||
228 | if (unlikely(atomic_inc_return(&h->ref) <= 1)) { | ||
229 | pr_err("%s: %s getting a freed handle\n", | ||
230 | __func__, current->group_leader->comm); | ||
231 | if (atomic_read(&h->ref) <= 0) | ||
232 | return NULL; | ||
233 | } | ||
234 | return h; | ||
235 | } | ||
236 | |||
237 | static inline void nvmap_handle_put(struct nvmap_handle *h) | ||
238 | { | ||
239 | int cnt = atomic_dec_return(&h->ref); | ||
240 | |||
241 | if (WARN_ON(cnt < 0)) { | ||
242 | pr_err("%s: %s put to negative references\n", | ||
243 | __func__, current->comm); | ||
244 | } else if (cnt == 0) | ||
245 | _nvmap_handle_free(h); | ||
246 | } | ||
247 | |||
248 | static inline pgprot_t nvmap_pgprot(struct nvmap_handle *h, pgprot_t prot) | ||
249 | { | ||
250 | if (h->flags == NVMAP_HANDLE_UNCACHEABLE) | ||
251 | return pgprot_noncached(prot); | ||
252 | else if (h->flags == NVMAP_HANDLE_WRITE_COMBINE) | ||
253 | return pgprot_writecombine(prot); | ||
254 | else if (h->flags == NVMAP_HANDLE_INNER_CACHEABLE) | ||
255 | return pgprot_inner_writeback(prot); | ||
256 | return prot; | ||
257 | } | ||
258 | #else /* CONFIG_TEGRA_NVMAP */ | ||
259 | struct nvmap_handle *nvmap_handle_get(struct nvmap_handle *h); | ||
260 | void nvmap_handle_put(struct nvmap_handle *h); | ||
261 | pgprot_t nvmap_pgprot(struct nvmap_handle *h, pgprot_t prot); | ||
262 | #endif /* !CONFIG_TEGRA_NVMAP */ | ||
263 | |||
264 | int is_nvmap_vma(struct vm_area_struct *vma); | ||
265 | |||
266 | struct nvmap_handle_ref *nvmap_alloc_iovm(struct nvmap_client *client, | ||
267 | size_t size, size_t align, unsigned int flags, unsigned int iova_start); | ||
268 | |||
269 | void nvmap_free_iovm(struct nvmap_client *client, struct nvmap_handle_ref *r); | ||
270 | |||
271 | #endif | ||
diff --git a/drivers/video/tegra/nvmap/nvmap_common.h b/drivers/video/tegra/nvmap/nvmap_common.h new file mode 100644 index 00000000000..6da010720bb --- /dev/null +++ b/drivers/video/tegra/nvmap/nvmap_common.h | |||
@@ -0,0 +1,38 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/nvmap/nvmap_common.h | ||
3 | * | ||
4 | * GPU memory management driver for Tegra | ||
5 | * | ||
6 | * Copyright (c) 2011, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | *' | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | extern void v7_flush_kern_cache_all(void *); | ||
24 | extern void v7_clean_kern_cache_all(void *); | ||
25 | |||
26 | #define FLUSH_CLEAN_BY_SET_WAY_THRESHOLD (8 * PAGE_SIZE) | ||
27 | |||
28 | static inline void inner_flush_cache_all(void) | ||
29 | { | ||
30 | on_each_cpu(v7_flush_kern_cache_all, NULL, 1); | ||
31 | } | ||
32 | |||
33 | static inline void inner_clean_cache_all(void) | ||
34 | { | ||
35 | on_each_cpu(v7_clean_kern_cache_all, NULL, 1); | ||
36 | } | ||
37 | |||
38 | extern void __flush_dcache_page(struct address_space *, struct page *); | ||
diff --git a/drivers/video/tegra/nvmap/nvmap_dev.c b/drivers/video/tegra/nvmap/nvmap_dev.c new file mode 100644 index 00000000000..f84f38c93aa --- /dev/null +++ b/drivers/video/tegra/nvmap/nvmap_dev.c | |||
@@ -0,0 +1,1436 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/nvmap/nvmap_dev.c | ||
3 | * | ||
4 | * User-space interface to nvmap | ||
5 | * | ||
6 | * Copyright (c) 2011-2012, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | #include <linux/backing-dev.h> | ||
24 | #include <linux/bitmap.h> | ||
25 | #include <linux/debugfs.h> | ||
26 | #include <linux/delay.h> | ||
27 | #include <linux/kernel.h> | ||
28 | #include <linux/miscdevice.h> | ||
29 | #include <linux/mm.h> | ||
30 | #include <linux/oom.h> | ||
31 | #include <linux/platform_device.h> | ||
32 | #include <linux/seq_file.h> | ||
33 | #include <linux/slab.h> | ||
34 | #include <linux/spinlock.h> | ||
35 | #include <linux/uaccess.h> | ||
36 | #include <linux/vmalloc.h> | ||
37 | |||
38 | #include <asm/cacheflush.h> | ||
39 | #include <asm/tlbflush.h> | ||
40 | |||
41 | #include <mach/iovmm.h> | ||
42 | #include <mach/nvmap.h> | ||
43 | |||
44 | #include "nvmap.h" | ||
45 | #include "nvmap_ioctl.h" | ||
46 | #include "nvmap_mru.h" | ||
47 | #include "nvmap_common.h" | ||
48 | |||
49 | #define NVMAP_NUM_PTES 64 | ||
50 | #define NVMAP_CARVEOUT_KILLER_RETRY_TIME 100 /* msecs */ | ||
51 | |||
52 | #ifdef CONFIG_NVMAP_CARVEOUT_KILLER | ||
53 | static bool carveout_killer = true; | ||
54 | #else | ||
55 | static bool carveout_killer; | ||
56 | #endif | ||
57 | module_param(carveout_killer, bool, 0640); | ||
58 | |||
59 | struct nvmap_carveout_node { | ||
60 | unsigned int heap_bit; | ||
61 | struct nvmap_heap *carveout; | ||
62 | int index; | ||
63 | struct list_head clients; | ||
64 | spinlock_t clients_lock; | ||
65 | }; | ||
66 | |||
67 | struct nvmap_device { | ||
68 | struct vm_struct *vm_rgn; | ||
69 | pte_t *ptes[NVMAP_NUM_PTES]; | ||
70 | unsigned long ptebits[NVMAP_NUM_PTES / BITS_PER_LONG]; | ||
71 | unsigned int lastpte; | ||
72 | spinlock_t ptelock; | ||
73 | |||
74 | struct rb_root handles; | ||
75 | spinlock_t handle_lock; | ||
76 | wait_queue_head_t pte_wait; | ||
77 | struct miscdevice dev_super; | ||
78 | struct miscdevice dev_user; | ||
79 | struct nvmap_carveout_node *heaps; | ||
80 | int nr_carveouts; | ||
81 | struct nvmap_share iovmm_master; | ||
82 | struct list_head clients; | ||
83 | spinlock_t clients_lock; | ||
84 | }; | ||
85 | |||
86 | struct nvmap_device *nvmap_dev; | ||
87 | |||
88 | static struct backing_dev_info nvmap_bdi = { | ||
89 | .ra_pages = 0, | ||
90 | .capabilities = (BDI_CAP_NO_ACCT_AND_WRITEBACK | | ||
91 | BDI_CAP_READ_MAP | BDI_CAP_WRITE_MAP), | ||
92 | }; | ||
93 | |||
94 | static int nvmap_open(struct inode *inode, struct file *filp); | ||
95 | static int nvmap_release(struct inode *inode, struct file *filp); | ||
96 | static long nvmap_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); | ||
97 | static int nvmap_map(struct file *filp, struct vm_area_struct *vma); | ||
98 | static void nvmap_vma_open(struct vm_area_struct *vma); | ||
99 | static void nvmap_vma_close(struct vm_area_struct *vma); | ||
100 | static int nvmap_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf); | ||
101 | |||
102 | static const struct file_operations nvmap_user_fops = { | ||
103 | .owner = THIS_MODULE, | ||
104 | .open = nvmap_open, | ||
105 | .release = nvmap_release, | ||
106 | .unlocked_ioctl = nvmap_ioctl, | ||
107 | .mmap = nvmap_map, | ||
108 | }; | ||
109 | |||
110 | static const struct file_operations nvmap_super_fops = { | ||
111 | .owner = THIS_MODULE, | ||
112 | .open = nvmap_open, | ||
113 | .release = nvmap_release, | ||
114 | .unlocked_ioctl = nvmap_ioctl, | ||
115 | .mmap = nvmap_map, | ||
116 | }; | ||
117 | |||
118 | static struct vm_operations_struct nvmap_vma_ops = { | ||
119 | .open = nvmap_vma_open, | ||
120 | .close = nvmap_vma_close, | ||
121 | .fault = nvmap_vma_fault, | ||
122 | }; | ||
123 | |||
124 | int is_nvmap_vma(struct vm_area_struct *vma) | ||
125 | { | ||
126 | return vma->vm_ops == &nvmap_vma_ops; | ||
127 | } | ||
128 | |||
129 | struct device *nvmap_client_to_device(struct nvmap_client *client) | ||
130 | { | ||
131 | if (client->super) | ||
132 | return client->dev->dev_super.this_device; | ||
133 | else | ||
134 | return client->dev->dev_user.this_device; | ||
135 | } | ||
136 | |||
137 | struct nvmap_share *nvmap_get_share_from_dev(struct nvmap_device *dev) | ||
138 | { | ||
139 | return &dev->iovmm_master; | ||
140 | } | ||
141 | |||
142 | /* allocates a PTE for the caller's use; returns the PTE pointer or | ||
143 | * a negative errno. may be called from IRQs */ | ||
144 | pte_t **nvmap_alloc_pte_irq(struct nvmap_device *dev, void **vaddr) | ||
145 | { | ||
146 | unsigned long flags; | ||
147 | unsigned long bit; | ||
148 | |||
149 | spin_lock_irqsave(&dev->ptelock, flags); | ||
150 | bit = find_next_zero_bit(dev->ptebits, NVMAP_NUM_PTES, dev->lastpte); | ||
151 | if (bit == NVMAP_NUM_PTES) { | ||
152 | bit = find_first_zero_bit(dev->ptebits, dev->lastpte); | ||
153 | if (bit == dev->lastpte) | ||
154 | bit = NVMAP_NUM_PTES; | ||
155 | } | ||
156 | |||
157 | if (bit == NVMAP_NUM_PTES) { | ||
158 | spin_unlock_irqrestore(&dev->ptelock, flags); | ||
159 | return ERR_PTR(-ENOMEM); | ||
160 | } | ||
161 | |||
162 | dev->lastpte = bit; | ||
163 | set_bit(bit, dev->ptebits); | ||
164 | spin_unlock_irqrestore(&dev->ptelock, flags); | ||
165 | |||
166 | *vaddr = dev->vm_rgn->addr + bit * PAGE_SIZE; | ||
167 | return &(dev->ptes[bit]); | ||
168 | } | ||
169 | |||
170 | /* allocates a PTE for the caller's use; returns the PTE pointer or | ||
171 | * a negative errno. must be called from sleepable contexts */ | ||
172 | pte_t **nvmap_alloc_pte(struct nvmap_device *dev, void **vaddr) | ||
173 | { | ||
174 | int ret; | ||
175 | pte_t **pte; | ||
176 | ret = wait_event_interruptible(dev->pte_wait, | ||
177 | !IS_ERR(pte = nvmap_alloc_pte_irq(dev, vaddr))); | ||
178 | |||
179 | if (ret == -ERESTARTSYS) | ||
180 | return ERR_PTR(-EINTR); | ||
181 | |||
182 | return pte; | ||
183 | } | ||
184 | |||
185 | /* frees a PTE */ | ||
186 | void nvmap_free_pte(struct nvmap_device *dev, pte_t **pte) | ||
187 | { | ||
188 | unsigned long addr; | ||
189 | unsigned int bit = pte - dev->ptes; | ||
190 | unsigned long flags; | ||
191 | |||
192 | if (WARN_ON(bit >= NVMAP_NUM_PTES)) | ||
193 | return; | ||
194 | |||
195 | addr = (unsigned long)dev->vm_rgn->addr + bit * PAGE_SIZE; | ||
196 | set_pte_at(&init_mm, addr, *pte, 0); | ||
197 | |||
198 | spin_lock_irqsave(&dev->ptelock, flags); | ||
199 | clear_bit(bit, dev->ptebits); | ||
200 | spin_unlock_irqrestore(&dev->ptelock, flags); | ||
201 | wake_up(&dev->pte_wait); | ||
202 | } | ||
203 | |||
204 | /* verifies that the handle ref value "ref" is a valid handle ref for the | ||
205 | * file. caller must hold the file's ref_lock prior to calling this function */ | ||
206 | struct nvmap_handle_ref *_nvmap_validate_id_locked(struct nvmap_client *c, | ||
207 | unsigned long id) | ||
208 | { | ||
209 | struct rb_node *n = c->handle_refs.rb_node; | ||
210 | |||
211 | while (n) { | ||
212 | struct nvmap_handle_ref *ref; | ||
213 | ref = rb_entry(n, struct nvmap_handle_ref, node); | ||
214 | if ((unsigned long)ref->handle == id) | ||
215 | return ref; | ||
216 | else if (id > (unsigned long)ref->handle) | ||
217 | n = n->rb_right; | ||
218 | else | ||
219 | n = n->rb_left; | ||
220 | } | ||
221 | |||
222 | return NULL; | ||
223 | } | ||
224 | |||
225 | struct nvmap_handle *nvmap_get_handle_id(struct nvmap_client *client, | ||
226 | unsigned long id) | ||
227 | { | ||
228 | struct nvmap_handle_ref *ref; | ||
229 | struct nvmap_handle *h = NULL; | ||
230 | |||
231 | nvmap_ref_lock(client); | ||
232 | ref = _nvmap_validate_id_locked(client, id); | ||
233 | if (ref) | ||
234 | h = ref->handle; | ||
235 | if (h) | ||
236 | h = nvmap_handle_get(h); | ||
237 | nvmap_ref_unlock(client); | ||
238 | return h; | ||
239 | } | ||
240 | |||
241 | unsigned long nvmap_carveout_usage(struct nvmap_client *c, | ||
242 | struct nvmap_heap_block *b) | ||
243 | { | ||
244 | struct nvmap_heap *h = nvmap_block_to_heap(b); | ||
245 | struct nvmap_carveout_node *n; | ||
246 | int i; | ||
247 | |||
248 | for (i = 0; i < c->dev->nr_carveouts; i++) { | ||
249 | n = &c->dev->heaps[i]; | ||
250 | if (n->carveout == h) | ||
251 | return n->heap_bit; | ||
252 | } | ||
253 | return 0; | ||
254 | } | ||
255 | |||
256 | /* | ||
257 | * This routine is used to flush the carveout memory from cache. | ||
258 | * Why cache flush is needed for carveout? Consider the case, where a piece of | ||
259 | * carveout is allocated as cached and released. After this, if the same memory is | ||
260 | * allocated for uncached request and the memory is not flushed out from cache. | ||
261 | * In this case, the client might pass this to H/W engine and it could start modify | ||
262 | * the memory. As this was cached earlier, it might have some portion of it in cache. | ||
263 | * During cpu request to read/write other memory, the cached portion of this memory | ||
264 | * might get flushed back to main memory and would cause corruptions, if it happens | ||
265 | * after H/W writes data to memory. | ||
266 | * | ||
267 | * But flushing out the memory blindly on each carveout allocation is redundant. | ||
268 | * | ||
269 | * In order to optimize the carveout buffer cache flushes, the following | ||
270 | * strategy is used. | ||
271 | * | ||
272 | * The whole Carveout is flushed out from cache during its initialization. | ||
273 | * During allocation, carveout buffers are not flused from cache. | ||
274 | * During deallocation, carveout buffers are flushed, if they were allocated as cached. | ||
275 | * if they were allocated as uncached/writecombined, no cache flush is needed. | ||
276 | * Just draining store buffers is enough. | ||
277 | */ | ||
278 | int nvmap_flush_heap_block(struct nvmap_client *client, | ||
279 | struct nvmap_heap_block *block, size_t len, unsigned int prot) | ||
280 | { | ||
281 | pte_t **pte; | ||
282 | void *addr; | ||
283 | phys_addr_t kaddr; | ||
284 | phys_addr_t phys = block->base; | ||
285 | phys_addr_t end = block->base + len; | ||
286 | |||
287 | if (prot == NVMAP_HANDLE_UNCACHEABLE || prot == NVMAP_HANDLE_WRITE_COMBINE) | ||
288 | goto out; | ||
289 | |||
290 | if (len >= FLUSH_CLEAN_BY_SET_WAY_THRESHOLD) { | ||
291 | inner_flush_cache_all(); | ||
292 | if (prot != NVMAP_HANDLE_INNER_CACHEABLE) | ||
293 | outer_flush_range(block->base, block->base + len); | ||
294 | goto out; | ||
295 | } | ||
296 | |||
297 | pte = nvmap_alloc_pte((client ? client->dev : nvmap_dev), &addr); | ||
298 | if (IS_ERR(pte)) | ||
299 | return PTR_ERR(pte); | ||
300 | |||
301 | kaddr = (phys_addr_t)addr; | ||
302 | |||
303 | while (phys < end) { | ||
304 | phys_addr_t next = (phys + PAGE_SIZE) & PAGE_MASK; | ||
305 | unsigned long pfn = __phys_to_pfn(phys); | ||
306 | void *base = (void *)kaddr + (phys & ~PAGE_MASK); | ||
307 | |||
308 | next = min(next, end); | ||
309 | set_pte_at(&init_mm, kaddr, *pte, pfn_pte(pfn, pgprot_kernel)); | ||
310 | flush_tlb_kernel_page(kaddr); | ||
311 | __cpuc_flush_dcache_area(base, next - phys); | ||
312 | phys = next; | ||
313 | } | ||
314 | |||
315 | if (prot != NVMAP_HANDLE_INNER_CACHEABLE) | ||
316 | outer_flush_range(block->base, block->base + len); | ||
317 | |||
318 | nvmap_free_pte((client ? client->dev : nvmap_dev), pte); | ||
319 | out: | ||
320 | wmb(); | ||
321 | return 0; | ||
322 | } | ||
323 | |||
324 | void nvmap_carveout_commit_add(struct nvmap_client *client, | ||
325 | struct nvmap_carveout_node *node, | ||
326 | size_t len) | ||
327 | { | ||
328 | unsigned long flags; | ||
329 | |||
330 | nvmap_ref_lock(client); | ||
331 | spin_lock_irqsave(&node->clients_lock, flags); | ||
332 | BUG_ON(list_empty(&client->carveout_commit[node->index].list) && | ||
333 | client->carveout_commit[node->index].commit != 0); | ||
334 | |||
335 | client->carveout_commit[node->index].commit += len; | ||
336 | /* if this client isn't already on the list of nodes for this heap, | ||
337 | add it */ | ||
338 | if (list_empty(&client->carveout_commit[node->index].list)) { | ||
339 | list_add(&client->carveout_commit[node->index].list, | ||
340 | &node->clients); | ||
341 | } | ||
342 | spin_unlock_irqrestore(&node->clients_lock, flags); | ||
343 | nvmap_ref_unlock(client); | ||
344 | } | ||
345 | |||
346 | void nvmap_carveout_commit_subtract(struct nvmap_client *client, | ||
347 | struct nvmap_carveout_node *node, | ||
348 | size_t len) | ||
349 | { | ||
350 | unsigned long flags; | ||
351 | |||
352 | if (!client) | ||
353 | return; | ||
354 | |||
355 | spin_lock_irqsave(&node->clients_lock, flags); | ||
356 | BUG_ON(client->carveout_commit[node->index].commit < len); | ||
357 | client->carveout_commit[node->index].commit -= len; | ||
358 | /* if no more allocation in this carveout for this node, delete it */ | ||
359 | if (!client->carveout_commit[node->index].commit) | ||
360 | list_del_init(&client->carveout_commit[node->index].list); | ||
361 | spin_unlock_irqrestore(&node->clients_lock, flags); | ||
362 | } | ||
363 | |||
364 | static struct nvmap_client *get_client_from_carveout_commit( | ||
365 | struct nvmap_carveout_node *node, struct nvmap_carveout_commit *commit) | ||
366 | { | ||
367 | struct nvmap_carveout_commit *first_commit = commit - node->index; | ||
368 | return (void *)first_commit - offsetof(struct nvmap_client, | ||
369 | carveout_commit); | ||
370 | } | ||
371 | |||
372 | static DECLARE_WAIT_QUEUE_HEAD(wait_reclaim); | ||
373 | static int wait_count; | ||
374 | bool nvmap_shrink_carveout(struct nvmap_carveout_node *node) | ||
375 | { | ||
376 | struct nvmap_carveout_commit *commit; | ||
377 | size_t selected_size = 0; | ||
378 | int selected_oom_adj = OOM_ADJUST_MIN; | ||
379 | struct task_struct *selected_task = NULL; | ||
380 | unsigned long flags; | ||
381 | bool wait = false; | ||
382 | int current_oom_adj = OOM_ADJUST_MIN; | ||
383 | |||
384 | task_lock(current); | ||
385 | if (current->signal) | ||
386 | current_oom_adj = current->signal->oom_adj; | ||
387 | task_unlock(current); | ||
388 | |||
389 | spin_lock_irqsave(&node->clients_lock, flags); | ||
390 | /* find the task with the smallest oom_adj (lowest priority) | ||
391 | * and largest carveout allocation -- ignore kernel allocations, | ||
392 | * there's no way to handle them */ | ||
393 | list_for_each_entry(commit, &node->clients, list) { | ||
394 | struct nvmap_client *client = | ||
395 | get_client_from_carveout_commit(node, commit); | ||
396 | size_t size = commit->commit; | ||
397 | struct task_struct *task = client->task; | ||
398 | struct signal_struct *sig; | ||
399 | |||
400 | if (!task) | ||
401 | continue; | ||
402 | |||
403 | task_lock(task); | ||
404 | sig = task->signal; | ||
405 | if (!task->mm || !sig) | ||
406 | goto end; | ||
407 | /* don't try to kill current */ | ||
408 | if (task == current->group_leader) | ||
409 | goto end; | ||
410 | /* don't try to kill higher priority tasks */ | ||
411 | if (sig->oom_adj < current_oom_adj) | ||
412 | goto end; | ||
413 | if (sig->oom_adj < selected_oom_adj) | ||
414 | goto end; | ||
415 | if (sig->oom_adj == selected_oom_adj && | ||
416 | size <= selected_size) | ||
417 | goto end; | ||
418 | selected_oom_adj = sig->oom_adj; | ||
419 | selected_size = size; | ||
420 | selected_task = task; | ||
421 | end: | ||
422 | task_unlock(task); | ||
423 | } | ||
424 | if (selected_task) { | ||
425 | wait = true; | ||
426 | if (fatal_signal_pending(selected_task)) { | ||
427 | pr_warning("carveout_killer: process %d dying " | ||
428 | "slowly\n", selected_task->pid); | ||
429 | goto out; | ||
430 | } | ||
431 | pr_info("carveout_killer: killing process %d with oom_adj %d " | ||
432 | "to reclaim %d (for process with oom_adj %d)\n", | ||
433 | selected_task->pid, selected_oom_adj, | ||
434 | selected_size, current_oom_adj); | ||
435 | force_sig(SIGKILL, selected_task); | ||
436 | } | ||
437 | out: | ||
438 | spin_unlock_irqrestore(&node->clients_lock, flags); | ||
439 | return wait; | ||
440 | } | ||
441 | |||
442 | static | ||
443 | struct nvmap_heap_block *do_nvmap_carveout_alloc(struct nvmap_client *client, | ||
444 | struct nvmap_handle *handle, | ||
445 | unsigned long type) | ||
446 | { | ||
447 | struct nvmap_carveout_node *co_heap; | ||
448 | struct nvmap_device *dev = client->dev; | ||
449 | int i; | ||
450 | |||
451 | for (i = 0; i < dev->nr_carveouts; i++) { | ||
452 | struct nvmap_heap_block *block; | ||
453 | co_heap = &dev->heaps[i]; | ||
454 | |||
455 | if (!(co_heap->heap_bit & type)) | ||
456 | continue; | ||
457 | |||
458 | block = nvmap_heap_alloc(co_heap->carveout, handle); | ||
459 | if (block) | ||
460 | return block; | ||
461 | } | ||
462 | return NULL; | ||
463 | } | ||
464 | |||
465 | static bool nvmap_carveout_freed(int count) | ||
466 | { | ||
467 | smp_rmb(); | ||
468 | return count != wait_count; | ||
469 | } | ||
470 | |||
471 | struct nvmap_heap_block *nvmap_carveout_alloc(struct nvmap_client *client, | ||
472 | struct nvmap_handle *handle, | ||
473 | unsigned long type) | ||
474 | { | ||
475 | struct nvmap_heap_block *block; | ||
476 | struct nvmap_carveout_node *co_heap; | ||
477 | struct nvmap_device *dev = client->dev; | ||
478 | int i; | ||
479 | unsigned long end = jiffies + | ||
480 | msecs_to_jiffies(NVMAP_CARVEOUT_KILLER_RETRY_TIME); | ||
481 | int count = 0; | ||
482 | |||
483 | do { | ||
484 | block = do_nvmap_carveout_alloc(client, handle, type); | ||
485 | if (!carveout_killer) | ||
486 | return block; | ||
487 | |||
488 | if (block) | ||
489 | return block; | ||
490 | |||
491 | if (!count++) { | ||
492 | char task_comm[TASK_COMM_LEN]; | ||
493 | if (client->task) | ||
494 | get_task_comm(task_comm, client->task); | ||
495 | else | ||
496 | task_comm[0] = 0; | ||
497 | pr_info("%s: failed to allocate %u bytes for " | ||
498 | "process %s, firing carveout " | ||
499 | "killer!\n", __func__, handle->size, task_comm); | ||
500 | |||
501 | } else { | ||
502 | pr_info("%s: still can't allocate %u bytes, " | ||
503 | "attempt %d!\n", __func__, handle->size, count); | ||
504 | } | ||
505 | |||
506 | /* shrink carveouts that matter and try again */ | ||
507 | for (i = 0; i < dev->nr_carveouts; i++) { | ||
508 | int count; | ||
509 | co_heap = &dev->heaps[i]; | ||
510 | |||
511 | if (!(co_heap->heap_bit & type)) | ||
512 | continue; | ||
513 | |||
514 | count = wait_count; | ||
515 | /* indicates we didn't find anything to kill, | ||
516 | might as well stop trying */ | ||
517 | if (!nvmap_shrink_carveout(co_heap)) | ||
518 | return NULL; | ||
519 | |||
520 | if (time_is_after_jiffies(end)) | ||
521 | wait_event_interruptible_timeout(wait_reclaim, | ||
522 | nvmap_carveout_freed(count), | ||
523 | end - jiffies); | ||
524 | } | ||
525 | } while (time_is_after_jiffies(end)); | ||
526 | |||
527 | if (time_is_before_jiffies(end)) | ||
528 | pr_info("carveout_killer: timeout expired without " | ||
529 | "allocation succeeding.\n"); | ||
530 | |||
531 | return NULL; | ||
532 | } | ||
533 | |||
534 | /* remove a handle from the device's tree of all handles; called | ||
535 | * when freeing handles. */ | ||
536 | int nvmap_handle_remove(struct nvmap_device *dev, struct nvmap_handle *h) | ||
537 | { | ||
538 | spin_lock(&dev->handle_lock); | ||
539 | |||
540 | /* re-test inside the spinlock if the handle really has no clients; | ||
541 | * only remove the handle if it is unreferenced */ | ||
542 | if (atomic_add_return(0, &h->ref) > 0) { | ||
543 | spin_unlock(&dev->handle_lock); | ||
544 | return -EBUSY; | ||
545 | } | ||
546 | smp_rmb(); | ||
547 | BUG_ON(atomic_read(&h->ref) < 0); | ||
548 | BUG_ON(atomic_read(&h->pin) != 0); | ||
549 | |||
550 | rb_erase(&h->node, &dev->handles); | ||
551 | |||
552 | spin_unlock(&dev->handle_lock); | ||
553 | return 0; | ||
554 | } | ||
555 | |||
556 | /* adds a newly-created handle to the device master tree */ | ||
557 | void nvmap_handle_add(struct nvmap_device *dev, struct nvmap_handle *h) | ||
558 | { | ||
559 | struct rb_node **p; | ||
560 | struct rb_node *parent = NULL; | ||
561 | |||
562 | spin_lock(&dev->handle_lock); | ||
563 | p = &dev->handles.rb_node; | ||
564 | while (*p) { | ||
565 | struct nvmap_handle *b; | ||
566 | |||
567 | parent = *p; | ||
568 | b = rb_entry(parent, struct nvmap_handle, node); | ||
569 | if (h > b) | ||
570 | p = &parent->rb_right; | ||
571 | else | ||
572 | p = &parent->rb_left; | ||
573 | } | ||
574 | rb_link_node(&h->node, parent, p); | ||
575 | rb_insert_color(&h->node, &dev->handles); | ||
576 | spin_unlock(&dev->handle_lock); | ||
577 | } | ||
578 | |||
579 | /* validates that a handle is in the device master tree, and that the | ||
580 | * client has permission to access it */ | ||
581 | struct nvmap_handle *nvmap_validate_get(struct nvmap_client *client, | ||
582 | unsigned long id) | ||
583 | { | ||
584 | struct nvmap_handle *h = NULL; | ||
585 | struct rb_node *n; | ||
586 | |||
587 | spin_lock(&client->dev->handle_lock); | ||
588 | |||
589 | n = client->dev->handles.rb_node; | ||
590 | |||
591 | while (n) { | ||
592 | h = rb_entry(n, struct nvmap_handle, node); | ||
593 | if ((unsigned long)h == id) { | ||
594 | if (client->super || h->global || (h->owner == client)) | ||
595 | h = nvmap_handle_get(h); | ||
596 | else | ||
597 | h = NULL; | ||
598 | spin_unlock(&client->dev->handle_lock); | ||
599 | return h; | ||
600 | } | ||
601 | if (id > (unsigned long)h) | ||
602 | n = n->rb_right; | ||
603 | else | ||
604 | n = n->rb_left; | ||
605 | } | ||
606 | spin_unlock(&client->dev->handle_lock); | ||
607 | return NULL; | ||
608 | } | ||
609 | |||
610 | struct nvmap_client *nvmap_create_client(struct nvmap_device *dev, | ||
611 | const char *name) | ||
612 | { | ||
613 | struct nvmap_client *client; | ||
614 | struct task_struct *task; | ||
615 | int i; | ||
616 | |||
617 | if (WARN_ON(!dev)) | ||
618 | return NULL; | ||
619 | |||
620 | client = kzalloc(sizeof(*client) + (sizeof(struct nvmap_carveout_commit) | ||
621 | * dev->nr_carveouts), GFP_KERNEL); | ||
622 | if (!client) | ||
623 | return NULL; | ||
624 | |||
625 | client->name = name; | ||
626 | client->super = true; | ||
627 | client->dev = dev; | ||
628 | /* TODO: allocate unique IOVMM client for each nvmap client */ | ||
629 | client->share = &dev->iovmm_master; | ||
630 | client->handle_refs = RB_ROOT; | ||
631 | |||
632 | atomic_set(&client->iovm_commit, 0); | ||
633 | |||
634 | client->iovm_limit = nvmap_mru_vm_size(client->share->iovmm); | ||
635 | |||
636 | for (i = 0; i < dev->nr_carveouts; i++) { | ||
637 | INIT_LIST_HEAD(&client->carveout_commit[i].list); | ||
638 | client->carveout_commit[i].commit = 0; | ||
639 | } | ||
640 | |||
641 | get_task_struct(current->group_leader); | ||
642 | task_lock(current->group_leader); | ||
643 | /* don't bother to store task struct for kernel threads, | ||
644 | they can't be killed anyway */ | ||
645 | if (current->flags & PF_KTHREAD) { | ||
646 | put_task_struct(current->group_leader); | ||
647 | task = NULL; | ||
648 | } else { | ||
649 | task = current->group_leader; | ||
650 | } | ||
651 | task_unlock(current->group_leader); | ||
652 | client->task = task; | ||
653 | |||
654 | mutex_init(&client->ref_lock); | ||
655 | atomic_set(&client->count, 1); | ||
656 | |||
657 | spin_lock(&dev->clients_lock); | ||
658 | list_add(&client->list, &dev->clients); | ||
659 | spin_unlock(&dev->clients_lock); | ||
660 | return client; | ||
661 | } | ||
662 | |||
663 | static void destroy_client(struct nvmap_client *client) | ||
664 | { | ||
665 | struct rb_node *n; | ||
666 | int i; | ||
667 | |||
668 | if (!client) | ||
669 | return; | ||
670 | |||
671 | |||
672 | while ((n = rb_first(&client->handle_refs))) { | ||
673 | struct nvmap_handle_ref *ref; | ||
674 | int pins, dupes; | ||
675 | |||
676 | ref = rb_entry(n, struct nvmap_handle_ref, node); | ||
677 | rb_erase(&ref->node, &client->handle_refs); | ||
678 | |||
679 | smp_rmb(); | ||
680 | pins = atomic_read(&ref->pin); | ||
681 | |||
682 | if (ref->handle->owner == client) | ||
683 | ref->handle->owner = NULL; | ||
684 | |||
685 | while (pins--) | ||
686 | nvmap_unpin_handles(client, &ref->handle, 1); | ||
687 | |||
688 | dupes = atomic_read(&ref->dupes); | ||
689 | while (dupes--) | ||
690 | nvmap_handle_put(ref->handle); | ||
691 | |||
692 | kfree(ref); | ||
693 | } | ||
694 | |||
695 | if (carveout_killer) { | ||
696 | wait_count++; | ||
697 | smp_wmb(); | ||
698 | wake_up_all(&wait_reclaim); | ||
699 | } | ||
700 | |||
701 | for (i = 0; i < client->dev->nr_carveouts; i++) | ||
702 | list_del(&client->carveout_commit[i].list); | ||
703 | |||
704 | if (client->task) | ||
705 | put_task_struct(client->task); | ||
706 | |||
707 | spin_lock(&client->dev->clients_lock); | ||
708 | list_del(&client->list); | ||
709 | spin_unlock(&client->dev->clients_lock); | ||
710 | kfree(client); | ||
711 | } | ||
712 | |||
713 | struct nvmap_client *nvmap_client_get(struct nvmap_client *client) | ||
714 | { | ||
715 | if (WARN_ON(!client)) | ||
716 | return NULL; | ||
717 | |||
718 | if (WARN_ON(!atomic_add_unless(&client->count, 1, 0))) | ||
719 | return NULL; | ||
720 | |||
721 | return client; | ||
722 | } | ||
723 | |||
724 | struct nvmap_client *nvmap_client_get_file(int fd) | ||
725 | { | ||
726 | struct nvmap_client *client = ERR_PTR(-EFAULT); | ||
727 | struct file *f = fget(fd); | ||
728 | if (!f) | ||
729 | return ERR_PTR(-EINVAL); | ||
730 | |||
731 | if ((f->f_op == &nvmap_user_fops) || (f->f_op == &nvmap_super_fops)) { | ||
732 | client = f->private_data; | ||
733 | atomic_inc(&client->count); | ||
734 | } | ||
735 | |||
736 | fput(f); | ||
737 | return client; | ||
738 | } | ||
739 | |||
740 | void nvmap_client_put(struct nvmap_client *client) | ||
741 | { | ||
742 | if (!client) | ||
743 | return; | ||
744 | |||
745 | if (!atomic_dec_return(&client->count)) | ||
746 | destroy_client(client); | ||
747 | } | ||
748 | |||
749 | static int nvmap_open(struct inode *inode, struct file *filp) | ||
750 | { | ||
751 | struct miscdevice *miscdev = filp->private_data; | ||
752 | struct nvmap_device *dev = dev_get_drvdata(miscdev->parent); | ||
753 | struct nvmap_client *priv; | ||
754 | int ret; | ||
755 | |||
756 | ret = nonseekable_open(inode, filp); | ||
757 | if (unlikely(ret)) | ||
758 | return ret; | ||
759 | |||
760 | BUG_ON(dev != nvmap_dev); | ||
761 | priv = nvmap_create_client(dev, "user"); | ||
762 | if (!priv) | ||
763 | return -ENOMEM; | ||
764 | |||
765 | priv->super = (filp->f_op == &nvmap_super_fops); | ||
766 | |||
767 | filp->f_mapping->backing_dev_info = &nvmap_bdi; | ||
768 | |||
769 | filp->private_data = priv; | ||
770 | return 0; | ||
771 | } | ||
772 | |||
773 | static int nvmap_release(struct inode *inode, struct file *filp) | ||
774 | { | ||
775 | nvmap_client_put(filp->private_data); | ||
776 | return 0; | ||
777 | } | ||
778 | |||
779 | static int nvmap_map(struct file *filp, struct vm_area_struct *vma) | ||
780 | { | ||
781 | struct nvmap_vma_priv *priv; | ||
782 | |||
783 | /* after NVMAP_IOC_MMAP, the handle that is mapped by this VMA | ||
784 | * will be stored in vm_private_data and faulted in. until the | ||
785 | * ioctl is made, the VMA is mapped no-access */ | ||
786 | vma->vm_private_data = NULL; | ||
787 | |||
788 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | ||
789 | if (!priv) | ||
790 | return -ENOMEM; | ||
791 | |||
792 | priv->offs = 0; | ||
793 | priv->handle = NULL; | ||
794 | atomic_set(&priv->count, 1); | ||
795 | |||
796 | vma->vm_flags |= VM_SHARED; | ||
797 | vma->vm_flags |= (VM_IO | VM_DONTEXPAND | VM_MIXEDMAP | VM_RESERVED); | ||
798 | vma->vm_ops = &nvmap_vma_ops; | ||
799 | vma->vm_private_data = priv; | ||
800 | |||
801 | return 0; | ||
802 | } | ||
803 | |||
804 | static long nvmap_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | ||
805 | { | ||
806 | int err = 0; | ||
807 | void __user *uarg = (void __user *)arg; | ||
808 | |||
809 | if (_IOC_TYPE(cmd) != NVMAP_IOC_MAGIC) | ||
810 | return -ENOTTY; | ||
811 | |||
812 | if (_IOC_NR(cmd) > NVMAP_IOC_MAXNR) | ||
813 | return -ENOTTY; | ||
814 | |||
815 | if (_IOC_DIR(cmd) & _IOC_READ) | ||
816 | err = !access_ok(VERIFY_WRITE, uarg, _IOC_SIZE(cmd)); | ||
817 | if (_IOC_DIR(cmd) & _IOC_WRITE) | ||
818 | err = !access_ok(VERIFY_READ, uarg, _IOC_SIZE(cmd)); | ||
819 | |||
820 | if (err) | ||
821 | return -EFAULT; | ||
822 | |||
823 | switch (cmd) { | ||
824 | case NVMAP_IOC_CLAIM: | ||
825 | nvmap_warn(filp->private_data, "preserved handles not" | ||
826 | "supported\n"); | ||
827 | err = -ENODEV; | ||
828 | break; | ||
829 | case NVMAP_IOC_CREATE: | ||
830 | case NVMAP_IOC_FROM_ID: | ||
831 | err = nvmap_ioctl_create(filp, cmd, uarg); | ||
832 | break; | ||
833 | |||
834 | case NVMAP_IOC_GET_ID: | ||
835 | err = nvmap_ioctl_getid(filp, uarg); | ||
836 | break; | ||
837 | |||
838 | case NVMAP_IOC_PARAM: | ||
839 | err = nvmap_ioctl_get_param(filp, uarg); | ||
840 | break; | ||
841 | |||
842 | case NVMAP_IOC_UNPIN_MULT: | ||
843 | case NVMAP_IOC_PIN_MULT: | ||
844 | err = nvmap_ioctl_pinop(filp, cmd == NVMAP_IOC_PIN_MULT, uarg); | ||
845 | break; | ||
846 | |||
847 | case NVMAP_IOC_ALLOC: | ||
848 | err = nvmap_ioctl_alloc(filp, uarg); | ||
849 | break; | ||
850 | |||
851 | case NVMAP_IOC_FREE: | ||
852 | err = nvmap_ioctl_free(filp, arg); | ||
853 | break; | ||
854 | |||
855 | case NVMAP_IOC_MMAP: | ||
856 | err = nvmap_map_into_caller_ptr(filp, uarg); | ||
857 | break; | ||
858 | |||
859 | case NVMAP_IOC_WRITE: | ||
860 | case NVMAP_IOC_READ: | ||
861 | err = nvmap_ioctl_rw_handle(filp, cmd == NVMAP_IOC_READ, uarg); | ||
862 | break; | ||
863 | |||
864 | case NVMAP_IOC_CACHE: | ||
865 | err = nvmap_ioctl_cache_maint(filp, uarg); | ||
866 | break; | ||
867 | |||
868 | default: | ||
869 | return -ENOTTY; | ||
870 | } | ||
871 | return err; | ||
872 | } | ||
873 | |||
874 | /* to ensure that the backing store for the VMA isn't freed while a fork'd | ||
875 | * reference still exists, nvmap_vma_open increments the reference count on | ||
876 | * the handle, and nvmap_vma_close decrements it. alternatively, we could | ||
877 | * disallow copying of the vma, or behave like pmem and zap the pages. FIXME. | ||
878 | */ | ||
879 | static void nvmap_vma_open(struct vm_area_struct *vma) | ||
880 | { | ||
881 | struct nvmap_vma_priv *priv; | ||
882 | |||
883 | priv = vma->vm_private_data; | ||
884 | |||
885 | BUG_ON(!priv); | ||
886 | |||
887 | atomic_inc(&priv->count); | ||
888 | } | ||
889 | |||
890 | static void nvmap_vma_close(struct vm_area_struct *vma) | ||
891 | { | ||
892 | struct nvmap_vma_priv *priv = vma->vm_private_data; | ||
893 | |||
894 | if (priv) { | ||
895 | if (priv->handle) { | ||
896 | nvmap_usecount_dec(priv->handle); | ||
897 | BUG_ON(priv->handle->usecount < 0); | ||
898 | } | ||
899 | if (!atomic_dec_return(&priv->count)) { | ||
900 | if (priv->handle) | ||
901 | nvmap_handle_put(priv->handle); | ||
902 | kfree(priv); | ||
903 | } | ||
904 | } | ||
905 | vma->vm_private_data = NULL; | ||
906 | } | ||
907 | |||
908 | static int nvmap_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf) | ||
909 | { | ||
910 | struct nvmap_vma_priv *priv; | ||
911 | unsigned long offs; | ||
912 | |||
913 | offs = (unsigned long)(vmf->virtual_address - vma->vm_start); | ||
914 | priv = vma->vm_private_data; | ||
915 | if (!priv || !priv->handle || !priv->handle->alloc) | ||
916 | return VM_FAULT_SIGBUS; | ||
917 | |||
918 | offs += priv->offs; | ||
919 | /* if the VMA was split for some reason, vm_pgoff will be the VMA's | ||
920 | * offset from the original VMA */ | ||
921 | offs += (vma->vm_pgoff << PAGE_SHIFT); | ||
922 | |||
923 | if (offs >= priv->handle->size) | ||
924 | return VM_FAULT_SIGBUS; | ||
925 | |||
926 | if (!priv->handle->heap_pgalloc) { | ||
927 | unsigned long pfn; | ||
928 | BUG_ON(priv->handle->carveout->base & ~PAGE_MASK); | ||
929 | pfn = ((priv->handle->carveout->base + offs) >> PAGE_SHIFT); | ||
930 | vm_insert_pfn(vma, (unsigned long)vmf->virtual_address, pfn); | ||
931 | return VM_FAULT_NOPAGE; | ||
932 | } else { | ||
933 | struct page *page; | ||
934 | offs >>= PAGE_SHIFT; | ||
935 | page = priv->handle->pgalloc.pages[offs]; | ||
936 | if (page) | ||
937 | get_page(page); | ||
938 | vmf->page = page; | ||
939 | return (page) ? 0 : VM_FAULT_SIGBUS; | ||
940 | } | ||
941 | } | ||
942 | |||
943 | static ssize_t attr_show_usage(struct device *dev, | ||
944 | struct device_attribute *attr, char *buf) | ||
945 | { | ||
946 | struct nvmap_carveout_node *node = nvmap_heap_device_to_arg(dev); | ||
947 | |||
948 | return sprintf(buf, "%08x\n", node->heap_bit); | ||
949 | } | ||
950 | |||
951 | static struct device_attribute heap_attr_show_usage = | ||
952 | __ATTR(usage, S_IRUGO, attr_show_usage, NULL); | ||
953 | |||
954 | static struct attribute *heap_extra_attrs[] = { | ||
955 | &heap_attr_show_usage.attr, | ||
956 | NULL, | ||
957 | }; | ||
958 | |||
959 | static struct attribute_group heap_extra_attr_group = { | ||
960 | .attrs = heap_extra_attrs, | ||
961 | }; | ||
962 | |||
963 | static void client_stringify(struct nvmap_client *client, struct seq_file *s) | ||
964 | { | ||
965 | char task_comm[TASK_COMM_LEN]; | ||
966 | if (!client->task) { | ||
967 | seq_printf(s, "%-18s %18s %8u", client->name, "kernel", 0); | ||
968 | return; | ||
969 | } | ||
970 | get_task_comm(task_comm, client->task); | ||
971 | seq_printf(s, "%-18s %18s %8u", client->name, task_comm, | ||
972 | client->task->pid); | ||
973 | } | ||
974 | |||
975 | static void allocations_stringify(struct nvmap_client *client, | ||
976 | struct seq_file *s) | ||
977 | { | ||
978 | unsigned long base = 0; | ||
979 | struct rb_node *n = rb_first(&client->handle_refs); | ||
980 | |||
981 | for (; n != NULL; n = rb_next(n)) { | ||
982 | struct nvmap_handle_ref *ref = | ||
983 | rb_entry(n, struct nvmap_handle_ref, node); | ||
984 | struct nvmap_handle *handle = ref->handle; | ||
985 | if (handle->alloc && !handle->heap_pgalloc) { | ||
986 | seq_printf(s, "%-18s %-18s %8lx %10u %8x\n", "", "", | ||
987 | (unsigned long)(handle->carveout->base), | ||
988 | handle->size, handle->userflags); | ||
989 | } else if (handle->alloc && handle->heap_pgalloc) { | ||
990 | seq_printf(s, "%-18s %-18s %8lx %10u %8x\n", "", "", | ||
991 | base, handle->size, handle->userflags); | ||
992 | } | ||
993 | } | ||
994 | } | ||
995 | |||
996 | static int nvmap_debug_allocations_show(struct seq_file *s, void *unused) | ||
997 | { | ||
998 | struct nvmap_carveout_node *node = s->private; | ||
999 | struct nvmap_carveout_commit *commit; | ||
1000 | unsigned long flags; | ||
1001 | unsigned int total = 0; | ||
1002 | |||
1003 | spin_lock_irqsave(&node->clients_lock, flags); | ||
1004 | seq_printf(s, "%-18s %18s %8s %10s %8s\n", "CLIENT", "PROCESS", "PID", | ||
1005 | "SIZE", "FLAGS"); | ||
1006 | seq_printf(s, "%-18s %18s %8s %10s\n", "", "", | ||
1007 | "BASE", "SIZE"); | ||
1008 | list_for_each_entry(commit, &node->clients, list) { | ||
1009 | struct nvmap_client *client = | ||
1010 | get_client_from_carveout_commit(node, commit); | ||
1011 | client_stringify(client, s); | ||
1012 | seq_printf(s, " %10u\n", commit->commit); | ||
1013 | allocations_stringify(client, s); | ||
1014 | seq_printf(s, "\n"); | ||
1015 | total += commit->commit; | ||
1016 | } | ||
1017 | seq_printf(s, "%-18s %-18s %8u %10u\n", "total", "", 0, total); | ||
1018 | spin_unlock_irqrestore(&node->clients_lock, flags); | ||
1019 | |||
1020 | return 0; | ||
1021 | } | ||
1022 | |||
1023 | static int nvmap_debug_allocations_open(struct inode *inode, struct file *file) | ||
1024 | { | ||
1025 | return single_open(file, nvmap_debug_allocations_show, | ||
1026 | inode->i_private); | ||
1027 | } | ||
1028 | |||
1029 | static const struct file_operations debug_allocations_fops = { | ||
1030 | .open = nvmap_debug_allocations_open, | ||
1031 | .read = seq_read, | ||
1032 | .llseek = seq_lseek, | ||
1033 | .release = single_release, | ||
1034 | }; | ||
1035 | |||
1036 | static int nvmap_debug_clients_show(struct seq_file *s, void *unused) | ||
1037 | { | ||
1038 | struct nvmap_carveout_node *node = s->private; | ||
1039 | struct nvmap_carveout_commit *commit; | ||
1040 | unsigned long flags; | ||
1041 | unsigned int total = 0; | ||
1042 | |||
1043 | spin_lock_irqsave(&node->clients_lock, flags); | ||
1044 | seq_printf(s, "%-18s %18s %8s %10s\n", "CLIENT", "PROCESS", "PID", | ||
1045 | "SIZE"); | ||
1046 | list_for_each_entry(commit, &node->clients, list) { | ||
1047 | struct nvmap_client *client = | ||
1048 | get_client_from_carveout_commit(node, commit); | ||
1049 | client_stringify(client, s); | ||
1050 | seq_printf(s, " %10u\n", commit->commit); | ||
1051 | total += commit->commit; | ||
1052 | } | ||
1053 | seq_printf(s, "%-18s %18s %8u %10u\n", "total", "", 0, total); | ||
1054 | spin_unlock_irqrestore(&node->clients_lock, flags); | ||
1055 | |||
1056 | return 0; | ||
1057 | } | ||
1058 | |||
1059 | static int nvmap_debug_clients_open(struct inode *inode, struct file *file) | ||
1060 | { | ||
1061 | return single_open(file, nvmap_debug_clients_show, inode->i_private); | ||
1062 | } | ||
1063 | |||
1064 | static const struct file_operations debug_clients_fops = { | ||
1065 | .open = nvmap_debug_clients_open, | ||
1066 | .read = seq_read, | ||
1067 | .llseek = seq_lseek, | ||
1068 | .release = single_release, | ||
1069 | }; | ||
1070 | |||
1071 | static int nvmap_debug_iovmm_clients_show(struct seq_file *s, void *unused) | ||
1072 | { | ||
1073 | unsigned long flags; | ||
1074 | unsigned int total = 0; | ||
1075 | struct nvmap_client *client; | ||
1076 | struct nvmap_device *dev = s->private; | ||
1077 | |||
1078 | spin_lock_irqsave(&dev->clients_lock, flags); | ||
1079 | seq_printf(s, "%-18s %18s %8s %10s\n", "CLIENT", "PROCESS", "PID", | ||
1080 | "SIZE"); | ||
1081 | list_for_each_entry(client, &dev->clients, list) { | ||
1082 | client_stringify(client, s); | ||
1083 | seq_printf(s, " %10u\n", atomic_read(&client->iovm_commit)); | ||
1084 | total += atomic_read(&client->iovm_commit); | ||
1085 | } | ||
1086 | seq_printf(s, "%-18s %18s %8u %10u\n", "total", "", 0, total); | ||
1087 | spin_unlock_irqrestore(&dev->clients_lock, flags); | ||
1088 | |||
1089 | return 0; | ||
1090 | } | ||
1091 | |||
1092 | static int nvmap_debug_iovmm_clients_open(struct inode *inode, | ||
1093 | struct file *file) | ||
1094 | { | ||
1095 | return single_open(file, nvmap_debug_iovmm_clients_show, | ||
1096 | inode->i_private); | ||
1097 | } | ||
1098 | |||
1099 | static const struct file_operations debug_iovmm_clients_fops = { | ||
1100 | .open = nvmap_debug_iovmm_clients_open, | ||
1101 | .read = seq_read, | ||
1102 | .llseek = seq_lseek, | ||
1103 | .release = single_release, | ||
1104 | }; | ||
1105 | |||
1106 | static int nvmap_debug_iovmm_allocations_show(struct seq_file *s, void *unused) | ||
1107 | { | ||
1108 | unsigned long flags; | ||
1109 | unsigned int total = 0; | ||
1110 | struct nvmap_client *client; | ||
1111 | struct nvmap_device *dev = s->private; | ||
1112 | |||
1113 | spin_lock_irqsave(&dev->clients_lock, flags); | ||
1114 | seq_printf(s, "%-18s %18s %8s %10s\n", "CLIENT", "PROCESS", "PID", | ||
1115 | "SIZE"); | ||
1116 | seq_printf(s, "%-18s %18s %8s %10s\n", "", "", | ||
1117 | "BASE", "SIZE"); | ||
1118 | list_for_each_entry(client, &dev->clients, list) { | ||
1119 | client_stringify(client, s); | ||
1120 | seq_printf(s, " %10u\n", atomic_read(&client->iovm_commit)); | ||
1121 | allocations_stringify(client, s); | ||
1122 | seq_printf(s, "\n"); | ||
1123 | total += atomic_read(&client->iovm_commit); | ||
1124 | } | ||
1125 | seq_printf(s, "%-18s %-18s %8u %10u\n", "total", "", 0, total); | ||
1126 | spin_unlock_irqrestore(&dev->clients_lock, flags); | ||
1127 | |||
1128 | return 0; | ||
1129 | } | ||
1130 | |||
1131 | static int nvmap_debug_iovmm_allocations_open(struct inode *inode, | ||
1132 | struct file *file) | ||
1133 | { | ||
1134 | return single_open(file, nvmap_debug_iovmm_allocations_show, | ||
1135 | inode->i_private); | ||
1136 | } | ||
1137 | |||
1138 | static const struct file_operations debug_iovmm_allocations_fops = { | ||
1139 | .open = nvmap_debug_iovmm_allocations_open, | ||
1140 | .read = seq_read, | ||
1141 | .llseek = seq_lseek, | ||
1142 | .release = single_release, | ||
1143 | }; | ||
1144 | |||
1145 | static int nvmap_probe(struct platform_device *pdev) | ||
1146 | { | ||
1147 | struct nvmap_platform_data *plat = pdev->dev.platform_data; | ||
1148 | struct nvmap_device *dev; | ||
1149 | struct dentry *nvmap_debug_root; | ||
1150 | unsigned int i; | ||
1151 | int e; | ||
1152 | |||
1153 | if (!plat) { | ||
1154 | dev_err(&pdev->dev, "no platform data?\n"); | ||
1155 | return -ENODEV; | ||
1156 | } | ||
1157 | |||
1158 | if (WARN_ON(nvmap_dev != NULL)) { | ||
1159 | dev_err(&pdev->dev, "only one nvmap device may be present\n"); | ||
1160 | return -ENODEV; | ||
1161 | } | ||
1162 | |||
1163 | dev = kzalloc(sizeof(*dev), GFP_KERNEL); | ||
1164 | if (!dev) { | ||
1165 | dev_err(&pdev->dev, "out of memory for device\n"); | ||
1166 | return -ENOMEM; | ||
1167 | } | ||
1168 | |||
1169 | dev->dev_user.minor = MISC_DYNAMIC_MINOR; | ||
1170 | dev->dev_user.name = "nvmap"; | ||
1171 | dev->dev_user.fops = &nvmap_user_fops; | ||
1172 | dev->dev_user.parent = &pdev->dev; | ||
1173 | |||
1174 | dev->dev_super.minor = MISC_DYNAMIC_MINOR; | ||
1175 | dev->dev_super.name = "knvmap"; | ||
1176 | dev->dev_super.fops = &nvmap_super_fops; | ||
1177 | dev->dev_super.parent = &pdev->dev; | ||
1178 | |||
1179 | dev->handles = RB_ROOT; | ||
1180 | |||
1181 | init_waitqueue_head(&dev->pte_wait); | ||
1182 | |||
1183 | init_waitqueue_head(&dev->iovmm_master.pin_wait); | ||
1184 | mutex_init(&dev->iovmm_master.pin_lock); | ||
1185 | for (i = 0; i < NVMAP_NUM_POOLS; i++) | ||
1186 | nvmap_page_pool_init(&dev->iovmm_master.pools[i], i); | ||
1187 | |||
1188 | dev->iovmm_master.iovmm = | ||
1189 | tegra_iovmm_alloc_client(dev_name(&pdev->dev), NULL, | ||
1190 | &(dev->dev_user)); | ||
1191 | #ifdef CONFIG_TEGRA_IOVMM | ||
1192 | if (!dev->iovmm_master.iovmm) { | ||
1193 | e = PTR_ERR(dev->iovmm_master.iovmm); | ||
1194 | dev_err(&pdev->dev, "couldn't create iovmm client\n"); | ||
1195 | goto fail; | ||
1196 | } | ||
1197 | #endif | ||
1198 | dev->vm_rgn = alloc_vm_area(NVMAP_NUM_PTES * PAGE_SIZE); | ||
1199 | if (!dev->vm_rgn) { | ||
1200 | e = -ENOMEM; | ||
1201 | dev_err(&pdev->dev, "couldn't allocate remapping region\n"); | ||
1202 | goto fail; | ||
1203 | } | ||
1204 | e = nvmap_mru_init(&dev->iovmm_master); | ||
1205 | if (e) { | ||
1206 | dev_err(&pdev->dev, "couldn't initialize MRU lists\n"); | ||
1207 | goto fail; | ||
1208 | } | ||
1209 | |||
1210 | spin_lock_init(&dev->ptelock); | ||
1211 | spin_lock_init(&dev->handle_lock); | ||
1212 | INIT_LIST_HEAD(&dev->clients); | ||
1213 | spin_lock_init(&dev->clients_lock); | ||
1214 | |||
1215 | for (i = 0; i < NVMAP_NUM_PTES; i++) { | ||
1216 | unsigned long addr; | ||
1217 | pgd_t *pgd; | ||
1218 | pud_t *pud; | ||
1219 | pmd_t *pmd; | ||
1220 | |||
1221 | addr = (unsigned long)dev->vm_rgn->addr + (i * PAGE_SIZE); | ||
1222 | pgd = pgd_offset_k(addr); | ||
1223 | pud = pud_alloc(&init_mm, pgd, addr); | ||
1224 | if (!pud) { | ||
1225 | e = -ENOMEM; | ||
1226 | dev_err(&pdev->dev, "couldn't allocate page tables\n"); | ||
1227 | goto fail; | ||
1228 | } | ||
1229 | pmd = pmd_alloc(&init_mm, pud, addr); | ||
1230 | if (!pmd) { | ||
1231 | e = -ENOMEM; | ||
1232 | dev_err(&pdev->dev, "couldn't allocate page tables\n"); | ||
1233 | goto fail; | ||
1234 | } | ||
1235 | dev->ptes[i] = pte_alloc_kernel(pmd, addr); | ||
1236 | if (!dev->ptes[i]) { | ||
1237 | e = -ENOMEM; | ||
1238 | dev_err(&pdev->dev, "couldn't allocate page tables\n"); | ||
1239 | goto fail; | ||
1240 | } | ||
1241 | } | ||
1242 | |||
1243 | e = misc_register(&dev->dev_user); | ||
1244 | if (e) { | ||
1245 | dev_err(&pdev->dev, "unable to register miscdevice %s\n", | ||
1246 | dev->dev_user.name); | ||
1247 | goto fail; | ||
1248 | } | ||
1249 | |||
1250 | e = misc_register(&dev->dev_super); | ||
1251 | if (e) { | ||
1252 | dev_err(&pdev->dev, "unable to register miscdevice %s\n", | ||
1253 | dev->dev_super.name); | ||
1254 | goto fail; | ||
1255 | } | ||
1256 | |||
1257 | dev->nr_carveouts = 0; | ||
1258 | dev->heaps = kzalloc(sizeof(struct nvmap_carveout_node) * | ||
1259 | plat->nr_carveouts, GFP_KERNEL); | ||
1260 | if (!dev->heaps) { | ||
1261 | e = -ENOMEM; | ||
1262 | dev_err(&pdev->dev, "couldn't allocate carveout memory\n"); | ||
1263 | goto fail; | ||
1264 | } | ||
1265 | |||
1266 | nvmap_debug_root = debugfs_create_dir("nvmap", NULL); | ||
1267 | if (IS_ERR_OR_NULL(nvmap_debug_root)) | ||
1268 | dev_err(&pdev->dev, "couldn't create debug files\n"); | ||
1269 | |||
1270 | for (i = 0; i < plat->nr_carveouts; i++) { | ||
1271 | struct nvmap_carveout_node *node = &dev->heaps[dev->nr_carveouts]; | ||
1272 | const struct nvmap_platform_carveout *co = &plat->carveouts[i]; | ||
1273 | if (!co->size) | ||
1274 | continue; | ||
1275 | node->carveout = nvmap_heap_create(dev->dev_user.this_device, | ||
1276 | co->name, co->base, co->size, | ||
1277 | co->buddy_size, node); | ||
1278 | if (!node->carveout) { | ||
1279 | e = -ENOMEM; | ||
1280 | dev_err(&pdev->dev, "couldn't create %s\n", co->name); | ||
1281 | goto fail_heaps; | ||
1282 | } | ||
1283 | node->index = dev->nr_carveouts; | ||
1284 | dev->nr_carveouts++; | ||
1285 | spin_lock_init(&node->clients_lock); | ||
1286 | INIT_LIST_HEAD(&node->clients); | ||
1287 | node->heap_bit = co->usage_mask; | ||
1288 | if (nvmap_heap_create_group(node->carveout, | ||
1289 | &heap_extra_attr_group)) | ||
1290 | dev_warn(&pdev->dev, "couldn't add extra attributes\n"); | ||
1291 | |||
1292 | dev_info(&pdev->dev, "created carveout %s (%uKiB)\n", | ||
1293 | co->name, co->size / 1024); | ||
1294 | |||
1295 | if (!IS_ERR_OR_NULL(nvmap_debug_root)) { | ||
1296 | struct dentry *heap_root = | ||
1297 | debugfs_create_dir(co->name, nvmap_debug_root); | ||
1298 | if (!IS_ERR_OR_NULL(heap_root)) { | ||
1299 | debugfs_create_file("clients", 0664, heap_root, | ||
1300 | node, &debug_clients_fops); | ||
1301 | debugfs_create_file("allocations", 0664, | ||
1302 | heap_root, node, &debug_allocations_fops); | ||
1303 | } | ||
1304 | } | ||
1305 | } | ||
1306 | if (!IS_ERR_OR_NULL(nvmap_debug_root)) { | ||
1307 | struct dentry *iovmm_root = | ||
1308 | debugfs_create_dir("iovmm", nvmap_debug_root); | ||
1309 | if (!IS_ERR_OR_NULL(iovmm_root)) { | ||
1310 | debugfs_create_file("clients", 0664, iovmm_root, | ||
1311 | dev, &debug_iovmm_clients_fops); | ||
1312 | debugfs_create_file("allocations", 0664, iovmm_root, | ||
1313 | dev, &debug_iovmm_allocations_fops); | ||
1314 | for (i = 0; i < NVMAP_NUM_POOLS; i++) { | ||
1315 | char name[40]; | ||
1316 | char *memtype_string[] = {"uc", "wc", | ||
1317 | "iwb", "wb"}; | ||
1318 | sprintf(name, "%s_page_pool_available_pages", | ||
1319 | memtype_string[i]); | ||
1320 | debugfs_create_u32(name, S_IRUGO|S_IWUSR, | ||
1321 | iovmm_root, | ||
1322 | &dev->iovmm_master.pools[i].npages); | ||
1323 | } | ||
1324 | } | ||
1325 | } | ||
1326 | |||
1327 | platform_set_drvdata(pdev, dev); | ||
1328 | nvmap_dev = dev; | ||
1329 | |||
1330 | return 0; | ||
1331 | fail_heaps: | ||
1332 | for (i = 0; i < dev->nr_carveouts; i++) { | ||
1333 | struct nvmap_carveout_node *node = &dev->heaps[i]; | ||
1334 | nvmap_heap_remove_group(node->carveout, &heap_extra_attr_group); | ||
1335 | nvmap_heap_destroy(node->carveout); | ||
1336 | } | ||
1337 | fail: | ||
1338 | kfree(dev->heaps); | ||
1339 | nvmap_mru_destroy(&dev->iovmm_master); | ||
1340 | if (dev->dev_super.minor != MISC_DYNAMIC_MINOR) | ||
1341 | misc_deregister(&dev->dev_super); | ||
1342 | if (dev->dev_user.minor != MISC_DYNAMIC_MINOR) | ||
1343 | misc_deregister(&dev->dev_user); | ||
1344 | if (!IS_ERR_OR_NULL(dev->iovmm_master.iovmm)) | ||
1345 | tegra_iovmm_free_client(dev->iovmm_master.iovmm); | ||
1346 | if (dev->vm_rgn) | ||
1347 | free_vm_area(dev->vm_rgn); | ||
1348 | kfree(dev); | ||
1349 | nvmap_dev = NULL; | ||
1350 | return e; | ||
1351 | } | ||
1352 | |||
1353 | static int nvmap_remove(struct platform_device *pdev) | ||
1354 | { | ||
1355 | struct nvmap_device *dev = platform_get_drvdata(pdev); | ||
1356 | struct rb_node *n; | ||
1357 | struct nvmap_handle *h; | ||
1358 | int i; | ||
1359 | |||
1360 | misc_deregister(&dev->dev_super); | ||
1361 | misc_deregister(&dev->dev_user); | ||
1362 | |||
1363 | while ((n = rb_first(&dev->handles))) { | ||
1364 | h = rb_entry(n, struct nvmap_handle, node); | ||
1365 | rb_erase(&h->node, &dev->handles); | ||
1366 | kfree(h); | ||
1367 | } | ||
1368 | |||
1369 | if (!IS_ERR_OR_NULL(dev->iovmm_master.iovmm)) | ||
1370 | tegra_iovmm_free_client(dev->iovmm_master.iovmm); | ||
1371 | |||
1372 | nvmap_mru_destroy(&dev->iovmm_master); | ||
1373 | |||
1374 | for (i = 0; i < dev->nr_carveouts; i++) { | ||
1375 | struct nvmap_carveout_node *node = &dev->heaps[i]; | ||
1376 | nvmap_heap_remove_group(node->carveout, &heap_extra_attr_group); | ||
1377 | nvmap_heap_destroy(node->carveout); | ||
1378 | } | ||
1379 | kfree(dev->heaps); | ||
1380 | |||
1381 | free_vm_area(dev->vm_rgn); | ||
1382 | kfree(dev); | ||
1383 | nvmap_dev = NULL; | ||
1384 | return 0; | ||
1385 | } | ||
1386 | |||
1387 | static int nvmap_suspend(struct platform_device *pdev, pm_message_t state) | ||
1388 | { | ||
1389 | return 0; | ||
1390 | } | ||
1391 | |||
1392 | static int nvmap_resume(struct platform_device *pdev) | ||
1393 | { | ||
1394 | return 0; | ||
1395 | } | ||
1396 | |||
1397 | static struct platform_driver nvmap_driver = { | ||
1398 | .probe = nvmap_probe, | ||
1399 | .remove = nvmap_remove, | ||
1400 | .suspend = nvmap_suspend, | ||
1401 | .resume = nvmap_resume, | ||
1402 | |||
1403 | .driver = { | ||
1404 | .name = "tegra-nvmap", | ||
1405 | .owner = THIS_MODULE, | ||
1406 | }, | ||
1407 | }; | ||
1408 | |||
1409 | static int __init nvmap_init_driver(void) | ||
1410 | { | ||
1411 | int e; | ||
1412 | |||
1413 | nvmap_dev = NULL; | ||
1414 | |||
1415 | e = nvmap_heap_init(); | ||
1416 | if (e) | ||
1417 | goto fail; | ||
1418 | |||
1419 | e = platform_driver_register(&nvmap_driver); | ||
1420 | if (e) { | ||
1421 | nvmap_heap_deinit(); | ||
1422 | goto fail; | ||
1423 | } | ||
1424 | |||
1425 | fail: | ||
1426 | return e; | ||
1427 | } | ||
1428 | fs_initcall(nvmap_init_driver); | ||
1429 | |||
1430 | static void __exit nvmap_exit_driver(void) | ||
1431 | { | ||
1432 | platform_driver_unregister(&nvmap_driver); | ||
1433 | nvmap_heap_deinit(); | ||
1434 | nvmap_dev = NULL; | ||
1435 | } | ||
1436 | module_exit(nvmap_exit_driver); | ||
diff --git a/drivers/video/tegra/nvmap/nvmap_handle.c b/drivers/video/tegra/nvmap/nvmap_handle.c new file mode 100644 index 00000000000..539b7ce9801 --- /dev/null +++ b/drivers/video/tegra/nvmap/nvmap_handle.c | |||
@@ -0,0 +1,1020 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/nvmap/nvmap_handle.c | ||
3 | * | ||
4 | * Handle allocation and freeing routines for nvmap | ||
5 | * | ||
6 | * Copyright (c) 2009-2012, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | #define pr_fmt(fmt) "%s: " fmt, __func__ | ||
24 | |||
25 | #include <linux/err.h> | ||
26 | #include <linux/kernel.h> | ||
27 | #include <linux/list.h> | ||
28 | #include <linux/mm.h> | ||
29 | #include <linux/rbtree.h> | ||
30 | #include <linux/slab.h> | ||
31 | #include <linux/vmalloc.h> | ||
32 | #include <linux/fs.h> | ||
33 | |||
34 | #include <asm/cacheflush.h> | ||
35 | #include <asm/outercache.h> | ||
36 | #include <asm/pgtable.h> | ||
37 | |||
38 | #include <mach/iovmm.h> | ||
39 | #include <mach/nvmap.h> | ||
40 | |||
41 | #include <linux/vmstat.h> | ||
42 | #include <linux/swap.h> | ||
43 | #include <linux/shrinker.h> | ||
44 | #include <linux/moduleparam.h> | ||
45 | |||
46 | #include "nvmap.h" | ||
47 | #include "nvmap_mru.h" | ||
48 | #include "nvmap_common.h" | ||
49 | |||
50 | #define PRINT_CARVEOUT_CONVERSION 0 | ||
51 | #if PRINT_CARVEOUT_CONVERSION | ||
52 | #define PR_INFO pr_info | ||
53 | #else | ||
54 | #define PR_INFO(...) | ||
55 | #endif | ||
56 | |||
57 | #define NVMAP_SECURE_HEAPS (NVMAP_HEAP_CARVEOUT_IRAM | NVMAP_HEAP_IOVMM | \ | ||
58 | NVMAP_HEAP_CARVEOUT_VPR) | ||
59 | #ifdef CONFIG_NVMAP_HIGHMEM_ONLY | ||
60 | #define GFP_NVMAP (__GFP_HIGHMEM | __GFP_NOWARN) | ||
61 | #else | ||
62 | #define GFP_NVMAP (GFP_KERNEL | __GFP_HIGHMEM | __GFP_NOWARN) | ||
63 | #endif | ||
64 | /* handles may be arbitrarily large (16+MiB), and any handle allocated from | ||
65 | * the kernel (i.e., not a carveout handle) includes its array of pages. to | ||
66 | * preserve kmalloc space, if the array of pages exceeds PAGELIST_VMALLOC_MIN, | ||
67 | * the array is allocated using vmalloc. */ | ||
68 | #define PAGELIST_VMALLOC_MIN (PAGE_SIZE * 2) | ||
69 | #define NVMAP_TEST_PAGE_POOL_SHRINKER 1 | ||
70 | static bool enable_pp = 1; | ||
71 | static int pool_size[NVMAP_NUM_POOLS]; | ||
72 | |||
73 | static char *s_memtype_str[] = { | ||
74 | "uc", | ||
75 | "wc", | ||
76 | "iwb", | ||
77 | "wb", | ||
78 | }; | ||
79 | |||
80 | static inline void nvmap_page_pool_lock(struct nvmap_page_pool *pool) | ||
81 | { | ||
82 | mutex_lock(&pool->lock); | ||
83 | } | ||
84 | |||
85 | static inline void nvmap_page_pool_unlock(struct nvmap_page_pool *pool) | ||
86 | { | ||
87 | mutex_unlock(&pool->lock); | ||
88 | } | ||
89 | |||
90 | static struct page *nvmap_page_pool_alloc_locked(struct nvmap_page_pool *pool) | ||
91 | { | ||
92 | struct page *page = NULL; | ||
93 | |||
94 | if (pool->npages > 0) | ||
95 | page = pool->page_array[--pool->npages]; | ||
96 | return page; | ||
97 | } | ||
98 | |||
99 | static struct page *nvmap_page_pool_alloc(struct nvmap_page_pool *pool) | ||
100 | { | ||
101 | struct page *page = NULL; | ||
102 | |||
103 | if (pool) { | ||
104 | nvmap_page_pool_lock(pool); | ||
105 | page = nvmap_page_pool_alloc_locked(pool); | ||
106 | nvmap_page_pool_unlock(pool); | ||
107 | } | ||
108 | return page; | ||
109 | } | ||
110 | |||
111 | static bool nvmap_page_pool_release_locked(struct nvmap_page_pool *pool, | ||
112 | struct page *page) | ||
113 | { | ||
114 | int ret = false; | ||
115 | |||
116 | if (enable_pp && pool->npages < pool->max_pages) { | ||
117 | pool->page_array[pool->npages++] = page; | ||
118 | ret = true; | ||
119 | } | ||
120 | return ret; | ||
121 | } | ||
122 | |||
123 | static bool nvmap_page_pool_release(struct nvmap_page_pool *pool, | ||
124 | struct page *page) | ||
125 | { | ||
126 | int ret = false; | ||
127 | |||
128 | if (pool) { | ||
129 | nvmap_page_pool_lock(pool); | ||
130 | ret = nvmap_page_pool_release_locked(pool, page); | ||
131 | nvmap_page_pool_unlock(pool); | ||
132 | } | ||
133 | return ret; | ||
134 | } | ||
135 | |||
136 | static int nvmap_page_pool_get_available_count(struct nvmap_page_pool *pool) | ||
137 | { | ||
138 | return pool->npages; | ||
139 | } | ||
140 | |||
141 | static int nvmap_page_pool_free(struct nvmap_page_pool *pool, int nr_free) | ||
142 | { | ||
143 | int i = nr_free; | ||
144 | int idx = 0; | ||
145 | struct page *page; | ||
146 | |||
147 | if (!nr_free) | ||
148 | return nr_free; | ||
149 | nvmap_page_pool_lock(pool); | ||
150 | while (i) { | ||
151 | page = nvmap_page_pool_alloc_locked(pool); | ||
152 | if (!page) | ||
153 | break; | ||
154 | pool->shrink_array[idx++] = page; | ||
155 | i--; | ||
156 | } | ||
157 | |||
158 | if (idx) | ||
159 | set_pages_array_wb(pool->shrink_array, idx); | ||
160 | while (idx--) | ||
161 | __free_page(pool->shrink_array[idx]); | ||
162 | nvmap_page_pool_unlock(pool); | ||
163 | return i; | ||
164 | } | ||
165 | |||
166 | static int nvmap_page_pool_get_unused_pages(void) | ||
167 | { | ||
168 | unsigned int i; | ||
169 | int total = 0; | ||
170 | struct nvmap_share *share = nvmap_get_share_from_dev(nvmap_dev); | ||
171 | |||
172 | for (i = 0; i < NVMAP_NUM_POOLS; i++) | ||
173 | total += nvmap_page_pool_get_available_count(&share->pools[i]); | ||
174 | |||
175 | return total; | ||
176 | } | ||
177 | |||
178 | static void nvmap_page_pool_resize(struct nvmap_page_pool *pool, int size) | ||
179 | { | ||
180 | int available_pages; | ||
181 | int pages_to_release = 0; | ||
182 | struct page **page_array = NULL; | ||
183 | struct page **shrink_array = NULL; | ||
184 | |||
185 | if (size == pool->max_pages) | ||
186 | return; | ||
187 | repeat: | ||
188 | nvmap_page_pool_free(pool, pages_to_release); | ||
189 | nvmap_page_pool_lock(pool); | ||
190 | available_pages = nvmap_page_pool_get_available_count(pool); | ||
191 | if (available_pages > size) { | ||
192 | nvmap_page_pool_unlock(pool); | ||
193 | pages_to_release = available_pages - size; | ||
194 | goto repeat; | ||
195 | } | ||
196 | |||
197 | if (size == 0) { | ||
198 | vfree(pool->page_array); | ||
199 | vfree(pool->shrink_array); | ||
200 | pool->page_array = pool->shrink_array = NULL; | ||
201 | goto out; | ||
202 | } | ||
203 | |||
204 | page_array = vmalloc(sizeof(struct page *) * size); | ||
205 | shrink_array = vmalloc(sizeof(struct page *) * size); | ||
206 | if (!page_array || !shrink_array) | ||
207 | goto fail; | ||
208 | |||
209 | memcpy(page_array, pool->page_array, | ||
210 | pool->npages * sizeof(struct page *)); | ||
211 | vfree(pool->page_array); | ||
212 | vfree(pool->shrink_array); | ||
213 | pool->page_array = page_array; | ||
214 | pool->shrink_array = shrink_array; | ||
215 | out: | ||
216 | pr_debug("%s pool resized to %d from %d pages", | ||
217 | s_memtype_str[pool->flags], size, pool->max_pages); | ||
218 | pool->max_pages = size; | ||
219 | goto exit; | ||
220 | fail: | ||
221 | vfree(page_array); | ||
222 | vfree(shrink_array); | ||
223 | pr_err("failed"); | ||
224 | exit: | ||
225 | nvmap_page_pool_unlock(pool); | ||
226 | } | ||
227 | |||
228 | static int nvmap_page_pool_shrink(struct shrinker *shrinker, | ||
229 | struct shrink_control *sc) | ||
230 | { | ||
231 | unsigned int i; | ||
232 | unsigned int pool_offset; | ||
233 | struct nvmap_page_pool *pool; | ||
234 | int shrink_pages = sc->nr_to_scan; | ||
235 | static atomic_t start_pool = ATOMIC_INIT(-1); | ||
236 | struct nvmap_share *share = nvmap_get_share_from_dev(nvmap_dev); | ||
237 | |||
238 | if (!shrink_pages) | ||
239 | goto out; | ||
240 | |||
241 | pr_debug("sh_pages=%d", shrink_pages); | ||
242 | |||
243 | for (i = 0; i < NVMAP_NUM_POOLS && shrink_pages; i++) { | ||
244 | pool_offset = atomic_add_return(1, &start_pool) % | ||
245 | NVMAP_NUM_POOLS; | ||
246 | pool = &share->pools[pool_offset]; | ||
247 | shrink_pages = nvmap_page_pool_free(pool, shrink_pages); | ||
248 | } | ||
249 | out: | ||
250 | return nvmap_page_pool_get_unused_pages(); | ||
251 | } | ||
252 | |||
253 | static struct shrinker nvmap_page_pool_shrinker = { | ||
254 | .shrink = nvmap_page_pool_shrink, | ||
255 | .seeks = 1, | ||
256 | }; | ||
257 | |||
258 | static void shrink_page_pools(int *total_pages, int *available_pages) | ||
259 | { | ||
260 | struct shrink_control sc; | ||
261 | |||
262 | sc.gfp_mask = GFP_KERNEL; | ||
263 | sc.nr_to_scan = 0; | ||
264 | *total_pages = nvmap_page_pool_shrink(NULL, &sc); | ||
265 | sc.nr_to_scan = *total_pages * 2; | ||
266 | *available_pages = nvmap_page_pool_shrink(NULL, &sc); | ||
267 | } | ||
268 | |||
269 | #if NVMAP_TEST_PAGE_POOL_SHRINKER | ||
270 | static bool shrink_pp; | ||
271 | static int shrink_set(const char *arg, const struct kernel_param *kp) | ||
272 | { | ||
273 | int cpu = smp_processor_id(); | ||
274 | unsigned long long t1, t2; | ||
275 | int total_pages, available_pages; | ||
276 | |||
277 | param_set_bool(arg, kp); | ||
278 | |||
279 | if (shrink_pp) { | ||
280 | t1 = cpu_clock(cpu); | ||
281 | shrink_page_pools(&total_pages, &available_pages); | ||
282 | t2 = cpu_clock(cpu); | ||
283 | pr_info("shrink page pools: time=%lldns, " | ||
284 | "total_pages_released=%d, free_pages_available=%d", | ||
285 | t2-t1, total_pages, available_pages); | ||
286 | } | ||
287 | return 0; | ||
288 | } | ||
289 | |||
290 | static int shrink_get(char *buff, const struct kernel_param *kp) | ||
291 | { | ||
292 | return param_get_bool(buff, kp); | ||
293 | } | ||
294 | |||
295 | static struct kernel_param_ops shrink_ops = { | ||
296 | .get = shrink_get, | ||
297 | .set = shrink_set, | ||
298 | }; | ||
299 | |||
300 | module_param_cb(shrink_page_pools, &shrink_ops, &shrink_pp, 0644); | ||
301 | #endif | ||
302 | |||
303 | static int enable_pp_set(const char *arg, const struct kernel_param *kp) | ||
304 | { | ||
305 | int total_pages, available_pages; | ||
306 | |||
307 | param_set_bool(arg, kp); | ||
308 | |||
309 | if (!enable_pp) { | ||
310 | shrink_page_pools(&total_pages, &available_pages); | ||
311 | pr_info("disabled page pools and released pages, " | ||
312 | "total_pages_released=%d, free_pages_available=%d", | ||
313 | total_pages, available_pages); | ||
314 | } | ||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | static int enable_pp_get(char *buff, const struct kernel_param *kp) | ||
319 | { | ||
320 | return param_get_int(buff, kp); | ||
321 | } | ||
322 | |||
323 | static struct kernel_param_ops enable_pp_ops = { | ||
324 | .get = enable_pp_get, | ||
325 | .set = enable_pp_set, | ||
326 | }; | ||
327 | |||
328 | module_param_cb(enable_page_pools, &enable_pp_ops, &enable_pp, 0644); | ||
329 | |||
330 | #define POOL_SIZE_SET(m, i) \ | ||
331 | static int pool_size_##m##_set(const char *arg, const struct kernel_param *kp) \ | ||
332 | { \ | ||
333 | struct nvmap_share *share = nvmap_get_share_from_dev(nvmap_dev); \ | ||
334 | param_set_int(arg, kp); \ | ||
335 | nvmap_page_pool_resize(&share->pools[i], pool_size[i]); \ | ||
336 | return 0; \ | ||
337 | } | ||
338 | |||
339 | #define POOL_SIZE_GET(m) \ | ||
340 | static int pool_size_##m##_get(char *buff, const struct kernel_param *kp) \ | ||
341 | { \ | ||
342 | return param_get_int(buff, kp); \ | ||
343 | } | ||
344 | |||
345 | #define POOL_SIZE_OPS(m) \ | ||
346 | static struct kernel_param_ops pool_size_##m##_ops = { \ | ||
347 | .get = pool_size_##m##_get, \ | ||
348 | .set = pool_size_##m##_set, \ | ||
349 | }; | ||
350 | |||
351 | #define POOL_SIZE_MOUDLE_PARAM_CB(m, i) \ | ||
352 | module_param_cb(m##_pool_size, &pool_size_##m##_ops, &pool_size[i], 0644) | ||
353 | |||
354 | POOL_SIZE_SET(uc, NVMAP_HANDLE_UNCACHEABLE); | ||
355 | POOL_SIZE_GET(uc); | ||
356 | POOL_SIZE_OPS(uc); | ||
357 | POOL_SIZE_MOUDLE_PARAM_CB(uc, NVMAP_HANDLE_UNCACHEABLE); | ||
358 | |||
359 | POOL_SIZE_SET(wc, NVMAP_HANDLE_WRITE_COMBINE); | ||
360 | POOL_SIZE_GET(wc); | ||
361 | POOL_SIZE_OPS(wc); | ||
362 | POOL_SIZE_MOUDLE_PARAM_CB(wc, NVMAP_HANDLE_WRITE_COMBINE); | ||
363 | |||
364 | POOL_SIZE_SET(iwb, NVMAP_HANDLE_INNER_CACHEABLE); | ||
365 | POOL_SIZE_GET(iwb); | ||
366 | POOL_SIZE_OPS(iwb); | ||
367 | POOL_SIZE_MOUDLE_PARAM_CB(iwb, NVMAP_HANDLE_INNER_CACHEABLE); | ||
368 | |||
369 | POOL_SIZE_SET(wb, NVMAP_HANDLE_CACHEABLE); | ||
370 | POOL_SIZE_GET(wb); | ||
371 | POOL_SIZE_OPS(wb); | ||
372 | POOL_SIZE_MOUDLE_PARAM_CB(wb, NVMAP_HANDLE_CACHEABLE); | ||
373 | |||
374 | int nvmap_page_pool_init(struct nvmap_page_pool *pool, int flags) | ||
375 | { | ||
376 | struct page *page; | ||
377 | int i; | ||
378 | static int reg = 1; | ||
379 | struct sysinfo info; | ||
380 | typedef int (*set_pages_array) (struct page **pages, int addrinarray); | ||
381 | set_pages_array s_cpa[] = { | ||
382 | set_pages_array_uc, | ||
383 | set_pages_array_wc, | ||
384 | set_pages_array_iwb, | ||
385 | set_pages_array_wb | ||
386 | }; | ||
387 | |||
388 | BUG_ON(flags >= NVMAP_NUM_POOLS); | ||
389 | memset(pool, 0x0, sizeof(*pool)); | ||
390 | mutex_init(&pool->lock); | ||
391 | pool->flags = flags; | ||
392 | |||
393 | /* No default pool for cached memory. */ | ||
394 | if (flags == NVMAP_HANDLE_CACHEABLE) | ||
395 | return 0; | ||
396 | |||
397 | si_meminfo(&info); | ||
398 | if (!pool_size[flags]) { | ||
399 | /* Use 3/8th of total ram for page pools. | ||
400 | * 1/8th for uc, 1/8th for wc and 1/8th for iwb. | ||
401 | */ | ||
402 | pool->max_pages = info.totalram >> 3; | ||
403 | } | ||
404 | if (pool->max_pages <= 0 || pool->max_pages >= info.totalram) | ||
405 | pool->max_pages = NVMAP_DEFAULT_PAGE_POOL_SIZE; | ||
406 | pool_size[flags] = pool->max_pages; | ||
407 | pr_info("nvmap %s page pool size=%d pages", | ||
408 | s_memtype_str[flags], pool->max_pages); | ||
409 | pool->page_array = vmalloc(sizeof(void *) * pool->max_pages); | ||
410 | pool->shrink_array = vmalloc(sizeof(struct page *) * pool->max_pages); | ||
411 | if (!pool->page_array || !pool->shrink_array) | ||
412 | goto fail; | ||
413 | |||
414 | if (reg) { | ||
415 | reg = 0; | ||
416 | register_shrinker(&nvmap_page_pool_shrinker); | ||
417 | } | ||
418 | |||
419 | nvmap_page_pool_lock(pool); | ||
420 | for (i = 0; i < pool->max_pages; i++) { | ||
421 | page = alloc_page(GFP_NVMAP); | ||
422 | if (!page) | ||
423 | goto do_cpa; | ||
424 | if (!nvmap_page_pool_release_locked(pool, page)) { | ||
425 | __free_page(page); | ||
426 | goto do_cpa; | ||
427 | } | ||
428 | } | ||
429 | do_cpa: | ||
430 | (*s_cpa[flags])(pool->page_array, pool->npages); | ||
431 | nvmap_page_pool_unlock(pool); | ||
432 | return 0; | ||
433 | fail: | ||
434 | pool->max_pages = 0; | ||
435 | vfree(pool->shrink_array); | ||
436 | vfree(pool->page_array); | ||
437 | return -ENOMEM; | ||
438 | } | ||
439 | |||
440 | static inline void *altalloc(size_t len) | ||
441 | { | ||
442 | if (len >= PAGELIST_VMALLOC_MIN) | ||
443 | return vmalloc(len); | ||
444 | else | ||
445 | return kmalloc(len, GFP_KERNEL); | ||
446 | } | ||
447 | |||
448 | static inline void altfree(void *ptr, size_t len) | ||
449 | { | ||
450 | if (!ptr) | ||
451 | return; | ||
452 | |||
453 | if (len >= PAGELIST_VMALLOC_MIN) | ||
454 | vfree(ptr); | ||
455 | else | ||
456 | kfree(ptr); | ||
457 | } | ||
458 | |||
459 | void _nvmap_handle_free(struct nvmap_handle *h) | ||
460 | { | ||
461 | struct nvmap_share *share = nvmap_get_share_from_dev(h->dev); | ||
462 | unsigned int i, nr_page, page_index = 0; | ||
463 | struct nvmap_page_pool *pool = NULL; | ||
464 | |||
465 | if (nvmap_handle_remove(h->dev, h) != 0) | ||
466 | return; | ||
467 | |||
468 | if (!h->alloc) | ||
469 | goto out; | ||
470 | |||
471 | if (!h->heap_pgalloc) { | ||
472 | nvmap_usecount_inc(h); | ||
473 | nvmap_heap_free(h->carveout); | ||
474 | goto out; | ||
475 | } | ||
476 | |||
477 | nr_page = DIV_ROUND_UP(h->size, PAGE_SIZE); | ||
478 | |||
479 | BUG_ON(h->size & ~PAGE_MASK); | ||
480 | BUG_ON(!h->pgalloc.pages); | ||
481 | |||
482 | nvmap_mru_remove(share, h); | ||
483 | |||
484 | if (h->flags < NVMAP_NUM_POOLS) | ||
485 | pool = &share->pools[h->flags]; | ||
486 | |||
487 | while (page_index < nr_page) { | ||
488 | if (!nvmap_page_pool_release(pool, | ||
489 | h->pgalloc.pages[page_index])) | ||
490 | break; | ||
491 | page_index++; | ||
492 | } | ||
493 | |||
494 | if (page_index == nr_page) | ||
495 | goto skip_attr_restore; | ||
496 | |||
497 | /* Restore page attributes. */ | ||
498 | if (h->flags == NVMAP_HANDLE_WRITE_COMBINE || | ||
499 | h->flags == NVMAP_HANDLE_UNCACHEABLE || | ||
500 | h->flags == NVMAP_HANDLE_INNER_CACHEABLE) | ||
501 | set_pages_array_wb(&h->pgalloc.pages[page_index], | ||
502 | nr_page - page_index); | ||
503 | |||
504 | skip_attr_restore: | ||
505 | if (h->pgalloc.area) | ||
506 | tegra_iovmm_free_vm(h->pgalloc.area); | ||
507 | |||
508 | for (i = page_index; i < nr_page; i++) | ||
509 | __free_page(h->pgalloc.pages[i]); | ||
510 | |||
511 | altfree(h->pgalloc.pages, nr_page * sizeof(struct page *)); | ||
512 | |||
513 | out: | ||
514 | kfree(h); | ||
515 | } | ||
516 | |||
517 | static struct page *nvmap_alloc_pages_exact(gfp_t gfp, size_t size) | ||
518 | { | ||
519 | struct page *page, *p, *e; | ||
520 | unsigned int order; | ||
521 | |||
522 | size = PAGE_ALIGN(size); | ||
523 | order = get_order(size); | ||
524 | page = alloc_pages(gfp, order); | ||
525 | |||
526 | if (!page) | ||
527 | return NULL; | ||
528 | |||
529 | split_page(page, order); | ||
530 | e = page + (1 << order); | ||
531 | for (p = page + (size >> PAGE_SHIFT); p < e; p++) | ||
532 | __free_page(p); | ||
533 | |||
534 | return page; | ||
535 | } | ||
536 | |||
537 | static int handle_page_alloc(struct nvmap_client *client, | ||
538 | struct nvmap_handle *h, bool contiguous) | ||
539 | { | ||
540 | size_t size = PAGE_ALIGN(h->size); | ||
541 | struct nvmap_share *share = nvmap_get_share_from_dev(h->dev); | ||
542 | unsigned int nr_page = size >> PAGE_SHIFT; | ||
543 | pgprot_t prot; | ||
544 | unsigned int i = 0, page_index = 0; | ||
545 | struct page **pages; | ||
546 | struct nvmap_page_pool *pool = NULL; | ||
547 | |||
548 | pages = altalloc(nr_page * sizeof(*pages)); | ||
549 | if (!pages) | ||
550 | return -ENOMEM; | ||
551 | |||
552 | prot = nvmap_pgprot(h, pgprot_kernel); | ||
553 | |||
554 | h->pgalloc.area = NULL; | ||
555 | if (contiguous) { | ||
556 | struct page *page; | ||
557 | page = nvmap_alloc_pages_exact(GFP_NVMAP, size); | ||
558 | if (!page) | ||
559 | goto fail; | ||
560 | |||
561 | for (i = 0; i < nr_page; i++) | ||
562 | pages[i] = nth_page(page, i); | ||
563 | |||
564 | } else { | ||
565 | if (h->flags < NVMAP_NUM_POOLS) | ||
566 | pool = &share->pools[h->flags]; | ||
567 | |||
568 | for (i = 0; i < nr_page; i++) { | ||
569 | /* Get pages from pool, if available. */ | ||
570 | pages[i] = nvmap_page_pool_alloc(pool); | ||
571 | if (!pages[i]) | ||
572 | break; | ||
573 | page_index++; | ||
574 | } | ||
575 | |||
576 | for (; i < nr_page; i++) { | ||
577 | pages[i] = nvmap_alloc_pages_exact(GFP_NVMAP, | ||
578 | PAGE_SIZE); | ||
579 | if (!pages[i]) | ||
580 | goto fail; | ||
581 | } | ||
582 | |||
583 | #ifndef CONFIG_NVMAP_RECLAIM_UNPINNED_VM | ||
584 | h->pgalloc.area = tegra_iovmm_create_vm(client->share->iovmm, | ||
585 | NULL, size, h->align, prot, | ||
586 | h->pgalloc.iovm_addr); | ||
587 | if (!h->pgalloc.area) | ||
588 | goto fail; | ||
589 | |||
590 | h->pgalloc.dirty = true; | ||
591 | #endif | ||
592 | } | ||
593 | |||
594 | if (nr_page == page_index) | ||
595 | goto skip_attr_change; | ||
596 | |||
597 | /* Update the pages mapping in kernel page table. */ | ||
598 | if (h->flags == NVMAP_HANDLE_WRITE_COMBINE) | ||
599 | set_pages_array_wc(&pages[page_index], | ||
600 | nr_page - page_index); | ||
601 | else if (h->flags == NVMAP_HANDLE_UNCACHEABLE) | ||
602 | set_pages_array_uc(&pages[page_index], | ||
603 | nr_page - page_index); | ||
604 | else if (h->flags == NVMAP_HANDLE_INNER_CACHEABLE) | ||
605 | set_pages_array_iwb(&pages[page_index], | ||
606 | nr_page - page_index); | ||
607 | |||
608 | skip_attr_change: | ||
609 | h->size = size; | ||
610 | h->pgalloc.pages = pages; | ||
611 | h->pgalloc.contig = contiguous; | ||
612 | INIT_LIST_HEAD(&h->pgalloc.mru_list); | ||
613 | return 0; | ||
614 | |||
615 | fail: | ||
616 | while (i--) { | ||
617 | set_pages_array_wb(&pages[i], 1); | ||
618 | __free_page(pages[i]); | ||
619 | } | ||
620 | altfree(pages, nr_page * sizeof(*pages)); | ||
621 | wmb(); | ||
622 | return -ENOMEM; | ||
623 | } | ||
624 | |||
625 | static void alloc_handle(struct nvmap_client *client, | ||
626 | struct nvmap_handle *h, unsigned int type) | ||
627 | { | ||
628 | BUG_ON(type & (type - 1)); | ||
629 | |||
630 | #ifdef CONFIG_NVMAP_CONVERT_CARVEOUT_TO_IOVMM | ||
631 | #define __NVMAP_HEAP_CARVEOUT (NVMAP_HEAP_CARVEOUT_IRAM | NVMAP_HEAP_CARVEOUT_VPR) | ||
632 | #define __NVMAP_HEAP_IOVMM (NVMAP_HEAP_IOVMM | NVMAP_HEAP_CARVEOUT_GENERIC) | ||
633 | if (type & NVMAP_HEAP_CARVEOUT_GENERIC) { | ||
634 | #ifdef CONFIG_NVMAP_ALLOW_SYSMEM | ||
635 | if (h->size <= PAGE_SIZE) { | ||
636 | PR_INFO("###CARVEOUT CONVERTED TO SYSMEM " | ||
637 | "0x%x bytes %s(%d)###\n", | ||
638 | h->size, current->comm, current->pid); | ||
639 | goto sysheap; | ||
640 | } | ||
641 | #endif | ||
642 | PR_INFO("###CARVEOUT CONVERTED TO IOVM " | ||
643 | "0x%x bytes %s(%d)###\n", | ||
644 | h->size, current->comm, current->pid); | ||
645 | } | ||
646 | #else | ||
647 | #define __NVMAP_HEAP_CARVEOUT NVMAP_HEAP_CARVEOUT_MASK | ||
648 | #define __NVMAP_HEAP_IOVMM NVMAP_HEAP_IOVMM | ||
649 | #endif | ||
650 | |||
651 | if (type & __NVMAP_HEAP_CARVEOUT) { | ||
652 | struct nvmap_heap_block *b; | ||
653 | #ifdef CONFIG_NVMAP_CONVERT_CARVEOUT_TO_IOVMM | ||
654 | PR_INFO("###IRAM REQUEST RETAINED " | ||
655 | "0x%x bytes %s(%d)###\n", | ||
656 | h->size, current->comm, current->pid); | ||
657 | #endif | ||
658 | /* Protect handle from relocation */ | ||
659 | nvmap_usecount_inc(h); | ||
660 | |||
661 | b = nvmap_carveout_alloc(client, h, type); | ||
662 | if (b) { | ||
663 | h->heap_pgalloc = false; | ||
664 | h->alloc = true; | ||
665 | nvmap_carveout_commit_add(client, | ||
666 | nvmap_heap_to_arg(nvmap_block_to_heap(b)), | ||
667 | h->size); | ||
668 | } | ||
669 | nvmap_usecount_dec(h); | ||
670 | |||
671 | } else if (type & __NVMAP_HEAP_IOVMM) { | ||
672 | size_t reserved = PAGE_ALIGN(h->size); | ||
673 | int commit = 0; | ||
674 | int ret; | ||
675 | |||
676 | /* increment the committed IOVM space prior to allocation | ||
677 | * to avoid race conditions with other threads simultaneously | ||
678 | * allocating. */ | ||
679 | commit = atomic_add_return(reserved, | ||
680 | &client->iovm_commit); | ||
681 | |||
682 | if (commit < client->iovm_limit) | ||
683 | ret = handle_page_alloc(client, h, false); | ||
684 | else | ||
685 | ret = -ENOMEM; | ||
686 | |||
687 | if (!ret) { | ||
688 | h->heap_pgalloc = true; | ||
689 | h->alloc = true; | ||
690 | } else { | ||
691 | atomic_sub(reserved, &client->iovm_commit); | ||
692 | } | ||
693 | |||
694 | } else if (type & NVMAP_HEAP_SYSMEM) { | ||
695 | #if defined(CONFIG_NVMAP_CONVERT_CARVEOUT_TO_IOVMM) && \ | ||
696 | defined(CONFIG_NVMAP_ALLOW_SYSMEM) | ||
697 | sysheap: | ||
698 | #endif | ||
699 | if (handle_page_alloc(client, h, true) == 0) { | ||
700 | BUG_ON(!h->pgalloc.contig); | ||
701 | h->heap_pgalloc = true; | ||
702 | h->alloc = true; | ||
703 | } | ||
704 | } | ||
705 | } | ||
706 | |||
707 | /* small allocations will try to allocate from generic OS memory before | ||
708 | * any of the limited heaps, to increase the effective memory for graphics | ||
709 | * allocations, and to reduce fragmentation of the graphics heaps with | ||
710 | * sub-page splinters */ | ||
711 | static const unsigned int heap_policy_small[] = { | ||
712 | NVMAP_HEAP_CARVEOUT_VPR, | ||
713 | NVMAP_HEAP_CARVEOUT_IRAM, | ||
714 | #ifdef CONFIG_NVMAP_ALLOW_SYSMEM | ||
715 | NVMAP_HEAP_SYSMEM, | ||
716 | #endif | ||
717 | NVMAP_HEAP_CARVEOUT_MASK, | ||
718 | NVMAP_HEAP_IOVMM, | ||
719 | 0, | ||
720 | }; | ||
721 | |||
722 | static const unsigned int heap_policy_large[] = { | ||
723 | NVMAP_HEAP_CARVEOUT_VPR, | ||
724 | NVMAP_HEAP_CARVEOUT_IRAM, | ||
725 | NVMAP_HEAP_IOVMM, | ||
726 | NVMAP_HEAP_CARVEOUT_MASK, | ||
727 | #ifdef CONFIG_NVMAP_ALLOW_SYSMEM | ||
728 | NVMAP_HEAP_SYSMEM, | ||
729 | #endif | ||
730 | 0, | ||
731 | }; | ||
732 | |||
733 | /* Do not override single page policy if there is not much space to | ||
734 | avoid invoking system oom killer. */ | ||
735 | #define NVMAP_SMALL_POLICY_SYSMEM_THRESHOLD 50000000 | ||
736 | |||
737 | int nvmap_alloc_handle_id(struct nvmap_client *client, | ||
738 | unsigned long id, unsigned int heap_mask, | ||
739 | size_t align, unsigned int flags) | ||
740 | { | ||
741 | struct nvmap_handle *h = NULL; | ||
742 | const unsigned int *alloc_policy; | ||
743 | int nr_page; | ||
744 | int err = -ENOMEM; | ||
745 | |||
746 | h = nvmap_get_handle_id(client, id); | ||
747 | |||
748 | if (!h) | ||
749 | return -EINVAL; | ||
750 | |||
751 | if (h->alloc) | ||
752 | goto out; | ||
753 | |||
754 | h->userflags = flags; | ||
755 | nr_page = ((h->size + PAGE_SIZE - 1) >> PAGE_SHIFT); | ||
756 | h->secure = !!(flags & NVMAP_HANDLE_SECURE); | ||
757 | h->flags = (flags & NVMAP_HANDLE_CACHE_FLAG); | ||
758 | h->align = max_t(size_t, align, L1_CACHE_BYTES); | ||
759 | |||
760 | #ifndef CONFIG_TEGRA_IOVMM | ||
761 | if (heap_mask & NVMAP_HEAP_IOVMM) { | ||
762 | heap_mask &= NVMAP_HEAP_IOVMM; | ||
763 | heap_mask |= NVMAP_HEAP_CARVEOUT_GENERIC; | ||
764 | } | ||
765 | #endif | ||
766 | #ifndef CONFIG_NVMAP_CONVERT_CARVEOUT_TO_IOVMM | ||
767 | #ifdef CONFIG_NVMAP_ALLOW_SYSMEM | ||
768 | /* Allow single pages allocations in system memory to save | ||
769 | * carveout space and avoid extra iovm mappings */ | ||
770 | if (nr_page == 1) { | ||
771 | if (heap_mask & NVMAP_HEAP_IOVMM) | ||
772 | heap_mask |= NVMAP_HEAP_SYSMEM; | ||
773 | else if (heap_mask & NVMAP_HEAP_CARVEOUT_GENERIC) { | ||
774 | /* Calculate size of free physical pages | ||
775 | * managed by kernel */ | ||
776 | unsigned long freeMem = | ||
777 | (global_page_state(NR_FREE_PAGES) + | ||
778 | global_page_state(NR_FILE_PAGES) - | ||
779 | total_swapcache_pages) << PAGE_SHIFT; | ||
780 | |||
781 | if (freeMem > NVMAP_SMALL_POLICY_SYSMEM_THRESHOLD) | ||
782 | heap_mask |= NVMAP_HEAP_SYSMEM; | ||
783 | } | ||
784 | } | ||
785 | #endif | ||
786 | |||
787 | /* This restriction is deprecated as alignments greater than | ||
788 | PAGE_SIZE are now correctly handled, but it is retained for | ||
789 | AP20 compatibility. */ | ||
790 | if (h->align > PAGE_SIZE) | ||
791 | heap_mask &= NVMAP_HEAP_CARVEOUT_MASK; | ||
792 | #endif | ||
793 | /* secure allocations can only be served from secure heaps */ | ||
794 | if (h->secure) | ||
795 | heap_mask &= NVMAP_SECURE_HEAPS; | ||
796 | |||
797 | if (!heap_mask) { | ||
798 | err = -EINVAL; | ||
799 | goto out; | ||
800 | } | ||
801 | |||
802 | alloc_policy = (nr_page == 1) ? heap_policy_small : heap_policy_large; | ||
803 | |||
804 | while (!h->alloc && *alloc_policy) { | ||
805 | unsigned int heap_type; | ||
806 | |||
807 | heap_type = *alloc_policy++; | ||
808 | heap_type &= heap_mask; | ||
809 | |||
810 | if (!heap_type) | ||
811 | continue; | ||
812 | |||
813 | heap_mask &= ~heap_type; | ||
814 | |||
815 | while (heap_type && !h->alloc) { | ||
816 | unsigned int heap; | ||
817 | |||
818 | /* iterate possible heaps MSB-to-LSB, since higher- | ||
819 | * priority carveouts will have higher usage masks */ | ||
820 | heap = 1 << __fls(heap_type); | ||
821 | alloc_handle(client, h, heap); | ||
822 | heap_type &= ~heap; | ||
823 | } | ||
824 | } | ||
825 | |||
826 | out: | ||
827 | err = (h->alloc) ? 0 : err; | ||
828 | nvmap_handle_put(h); | ||
829 | return err; | ||
830 | } | ||
831 | |||
832 | void nvmap_free_handle_id(struct nvmap_client *client, unsigned long id) | ||
833 | { | ||
834 | struct nvmap_handle_ref *ref; | ||
835 | struct nvmap_handle *h; | ||
836 | int pins; | ||
837 | |||
838 | nvmap_ref_lock(client); | ||
839 | |||
840 | ref = _nvmap_validate_id_locked(client, id); | ||
841 | if (!ref) { | ||
842 | nvmap_ref_unlock(client); | ||
843 | return; | ||
844 | } | ||
845 | |||
846 | BUG_ON(!ref->handle); | ||
847 | h = ref->handle; | ||
848 | |||
849 | if (atomic_dec_return(&ref->dupes)) { | ||
850 | nvmap_ref_unlock(client); | ||
851 | goto out; | ||
852 | } | ||
853 | |||
854 | smp_rmb(); | ||
855 | pins = atomic_read(&ref->pin); | ||
856 | rb_erase(&ref->node, &client->handle_refs); | ||
857 | |||
858 | if (h->alloc && h->heap_pgalloc && !h->pgalloc.contig) | ||
859 | atomic_sub(h->size, &client->iovm_commit); | ||
860 | |||
861 | if (h->alloc && !h->heap_pgalloc) { | ||
862 | mutex_lock(&h->lock); | ||
863 | nvmap_carveout_commit_subtract(client, | ||
864 | nvmap_heap_to_arg(nvmap_block_to_heap(h->carveout)), | ||
865 | h->size); | ||
866 | mutex_unlock(&h->lock); | ||
867 | } | ||
868 | |||
869 | nvmap_ref_unlock(client); | ||
870 | |||
871 | if (pins) | ||
872 | nvmap_err(client, "%s freeing pinned handle %p\n", | ||
873 | current->group_leader->comm, h); | ||
874 | |||
875 | while (pins--) | ||
876 | nvmap_unpin_handles(client, &ref->handle, 1); | ||
877 | |||
878 | if (h->owner == client) | ||
879 | h->owner = NULL; | ||
880 | |||
881 | kfree(ref); | ||
882 | |||
883 | out: | ||
884 | BUG_ON(!atomic_read(&h->ref)); | ||
885 | nvmap_handle_put(h); | ||
886 | } | ||
887 | |||
888 | static void add_handle_ref(struct nvmap_client *client, | ||
889 | struct nvmap_handle_ref *ref) | ||
890 | { | ||
891 | struct rb_node **p, *parent = NULL; | ||
892 | |||
893 | nvmap_ref_lock(client); | ||
894 | p = &client->handle_refs.rb_node; | ||
895 | while (*p) { | ||
896 | struct nvmap_handle_ref *node; | ||
897 | parent = *p; | ||
898 | node = rb_entry(parent, struct nvmap_handle_ref, node); | ||
899 | if (ref->handle > node->handle) | ||
900 | p = &parent->rb_right; | ||
901 | else | ||
902 | p = &parent->rb_left; | ||
903 | } | ||
904 | rb_link_node(&ref->node, parent, p); | ||
905 | rb_insert_color(&ref->node, &client->handle_refs); | ||
906 | nvmap_ref_unlock(client); | ||
907 | } | ||
908 | |||
909 | struct nvmap_handle_ref *nvmap_create_handle(struct nvmap_client *client, | ||
910 | size_t size) | ||
911 | { | ||
912 | struct nvmap_handle *h; | ||
913 | struct nvmap_handle_ref *ref = NULL; | ||
914 | |||
915 | if (!client) | ||
916 | return ERR_PTR(-EINVAL); | ||
917 | |||
918 | if (!size) | ||
919 | return ERR_PTR(-EINVAL); | ||
920 | |||
921 | h = kzalloc(sizeof(*h), GFP_KERNEL); | ||
922 | if (!h) | ||
923 | return ERR_PTR(-ENOMEM); | ||
924 | |||
925 | ref = kzalloc(sizeof(*ref), GFP_KERNEL); | ||
926 | if (!ref) { | ||
927 | kfree(h); | ||
928 | return ERR_PTR(-ENOMEM); | ||
929 | } | ||
930 | |||
931 | atomic_set(&h->ref, 1); | ||
932 | atomic_set(&h->pin, 0); | ||
933 | h->owner = client; | ||
934 | h->dev = client->dev; | ||
935 | BUG_ON(!h->owner); | ||
936 | h->size = h->orig_size = size; | ||
937 | h->flags = NVMAP_HANDLE_WRITE_COMBINE; | ||
938 | mutex_init(&h->lock); | ||
939 | |||
940 | nvmap_handle_add(client->dev, h); | ||
941 | |||
942 | atomic_set(&ref->dupes, 1); | ||
943 | ref->handle = h; | ||
944 | atomic_set(&ref->pin, 0); | ||
945 | add_handle_ref(client, ref); | ||
946 | return ref; | ||
947 | } | ||
948 | |||
949 | struct nvmap_handle_ref *nvmap_duplicate_handle_id(struct nvmap_client *client, | ||
950 | unsigned long id) | ||
951 | { | ||
952 | struct nvmap_handle_ref *ref = NULL; | ||
953 | struct nvmap_handle *h = NULL; | ||
954 | |||
955 | BUG_ON(!client || client->dev != nvmap_dev); | ||
956 | /* on success, the reference count for the handle should be | ||
957 | * incremented, so the success paths will not call nvmap_handle_put */ | ||
958 | h = nvmap_validate_get(client, id); | ||
959 | |||
960 | if (!h) { | ||
961 | nvmap_debug(client, "%s duplicate handle failed\n", | ||
962 | current->group_leader->comm); | ||
963 | return ERR_PTR(-EPERM); | ||
964 | } | ||
965 | |||
966 | if (!h->alloc) { | ||
967 | nvmap_err(client, "%s duplicating unallocated handle\n", | ||
968 | current->group_leader->comm); | ||
969 | nvmap_handle_put(h); | ||
970 | return ERR_PTR(-EINVAL); | ||
971 | } | ||
972 | |||
973 | nvmap_ref_lock(client); | ||
974 | ref = _nvmap_validate_id_locked(client, (unsigned long)h); | ||
975 | |||
976 | if (ref) { | ||
977 | /* handle already duplicated in client; just increment | ||
978 | * the reference count rather than re-duplicating it */ | ||
979 | atomic_inc(&ref->dupes); | ||
980 | nvmap_ref_unlock(client); | ||
981 | return ref; | ||
982 | } | ||
983 | |||
984 | nvmap_ref_unlock(client); | ||
985 | |||
986 | /* verify that adding this handle to the process' access list | ||
987 | * won't exceed the IOVM limit */ | ||
988 | if (h->heap_pgalloc && !h->pgalloc.contig) { | ||
989 | int oc; | ||
990 | oc = atomic_add_return(h->size, &client->iovm_commit); | ||
991 | if (oc > client->iovm_limit && !client->super) { | ||
992 | atomic_sub(h->size, &client->iovm_commit); | ||
993 | nvmap_handle_put(h); | ||
994 | nvmap_err(client, "duplicating %p in %s over-commits" | ||
995 | " IOVMM space\n", (void *)id, | ||
996 | current->group_leader->comm); | ||
997 | return ERR_PTR(-ENOMEM); | ||
998 | } | ||
999 | } | ||
1000 | |||
1001 | ref = kzalloc(sizeof(*ref), GFP_KERNEL); | ||
1002 | if (!ref) { | ||
1003 | nvmap_handle_put(h); | ||
1004 | return ERR_PTR(-ENOMEM); | ||
1005 | } | ||
1006 | |||
1007 | if (!h->heap_pgalloc) { | ||
1008 | mutex_lock(&h->lock); | ||
1009 | nvmap_carveout_commit_add(client, | ||
1010 | nvmap_heap_to_arg(nvmap_block_to_heap(h->carveout)), | ||
1011 | h->size); | ||
1012 | mutex_unlock(&h->lock); | ||
1013 | } | ||
1014 | |||
1015 | atomic_set(&ref->dupes, 1); | ||
1016 | ref->handle = h; | ||
1017 | atomic_set(&ref->pin, 0); | ||
1018 | add_handle_ref(client, ref); | ||
1019 | return ref; | ||
1020 | } | ||
diff --git a/drivers/video/tegra/nvmap/nvmap_heap.c b/drivers/video/tegra/nvmap/nvmap_heap.c new file mode 100644 index 00000000000..7474f31534f --- /dev/null +++ b/drivers/video/tegra/nvmap/nvmap_heap.c | |||
@@ -0,0 +1,1113 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/nvmap/nvmap_heap.c | ||
3 | * | ||
4 | * GPU heap allocator. | ||
5 | * | ||
6 | * Copyright (c) 2011, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | #include <linux/device.h> | ||
24 | #include <linux/kernel.h> | ||
25 | #include <linux/list.h> | ||
26 | #include <linux/mm.h> | ||
27 | #include <linux/mutex.h> | ||
28 | #include <linux/slab.h> | ||
29 | #include <linux/err.h> | ||
30 | |||
31 | #include <mach/nvmap.h> | ||
32 | #include "nvmap.h" | ||
33 | #include "nvmap_heap.h" | ||
34 | #include "nvmap_common.h" | ||
35 | |||
36 | #include <asm/tlbflush.h> | ||
37 | #include <asm/cacheflush.h> | ||
38 | |||
39 | /* | ||
40 | * "carveouts" are platform-defined regions of physically contiguous memory | ||
41 | * which are not managed by the OS. a platform may specify multiple carveouts, | ||
42 | * for either small special-purpose memory regions (like IRAM on Tegra SoCs) | ||
43 | * or reserved regions of main system memory. | ||
44 | * | ||
45 | * the carveout allocator returns allocations which are physically contiguous. | ||
46 | * to reduce external fragmentation, the allocation algorithm implemented in | ||
47 | * this file employs 3 strategies for keeping allocations of similar size | ||
48 | * grouped together inside the larger heap: the "small", "normal" and "huge" | ||
49 | * strategies. the size thresholds (in bytes) for determining which strategy | ||
50 | * to employ should be provided by the platform for each heap. it is possible | ||
51 | * for a platform to define a heap where only the "normal" strategy is used. | ||
52 | * | ||
53 | * o "normal" allocations use an address-order first-fit allocator (called | ||
54 | * BOTTOM_UP in the code below). each allocation is rounded up to be | ||
55 | * an integer multiple of the "small" allocation size. | ||
56 | * | ||
57 | * o "huge" allocations use an address-order last-fit allocator (called | ||
58 | * TOP_DOWN in the code below). like "normal" allocations, each allocation | ||
59 | * is rounded up to be an integer multiple of the "small" allocation size. | ||
60 | * | ||
61 | * o "small" allocations are treated differently: the heap manager maintains | ||
62 | * a pool of "small"-sized blocks internally from which allocations less | ||
63 | * than 1/2 of the "small" size are buddy-allocated. if a "small" allocation | ||
64 | * is requested and none of the buddy sub-heaps is able to service it, | ||
65 | * the heap manager will try to allocate a new buddy-heap. | ||
66 | * | ||
67 | * this allocator is intended to keep "splinters" colocated in the carveout, | ||
68 | * and to ensure that the minimum free block size in the carveout (i.e., the | ||
69 | * "small" threshold) is still a meaningful size. | ||
70 | * | ||
71 | */ | ||
72 | |||
73 | #define MAX_BUDDY_NR 128 /* maximum buddies in a buddy allocator */ | ||
74 | |||
75 | enum direction { | ||
76 | TOP_DOWN, | ||
77 | BOTTOM_UP | ||
78 | }; | ||
79 | |||
80 | enum block_type { | ||
81 | BLOCK_FIRST_FIT, /* block was allocated directly from the heap */ | ||
82 | BLOCK_BUDDY, /* block was allocated from a buddy sub-heap */ | ||
83 | BLOCK_EMPTY, | ||
84 | }; | ||
85 | |||
86 | struct heap_stat { | ||
87 | size_t free; /* total free size */ | ||
88 | size_t free_largest; /* largest free block */ | ||
89 | size_t free_count; /* number of free blocks */ | ||
90 | size_t total; /* total size */ | ||
91 | size_t largest; /* largest unique block */ | ||
92 | size_t count; /* total number of blocks */ | ||
93 | /* fast compaction attempt counter */ | ||
94 | unsigned int compaction_count_fast; | ||
95 | /* full compaction attempt counter */ | ||
96 | unsigned int compaction_count_full; | ||
97 | }; | ||
98 | |||
99 | struct buddy_heap; | ||
100 | |||
101 | struct buddy_block { | ||
102 | struct nvmap_heap_block block; | ||
103 | struct buddy_heap *heap; | ||
104 | }; | ||
105 | |||
106 | struct list_block { | ||
107 | struct nvmap_heap_block block; | ||
108 | struct list_head all_list; | ||
109 | unsigned int mem_prot; | ||
110 | unsigned long orig_addr; | ||
111 | size_t size; | ||
112 | size_t align; | ||
113 | struct nvmap_heap *heap; | ||
114 | struct list_head free_list; | ||
115 | }; | ||
116 | |||
117 | struct combo_block { | ||
118 | union { | ||
119 | struct list_block lb; | ||
120 | struct buddy_block bb; | ||
121 | }; | ||
122 | }; | ||
123 | |||
124 | struct buddy_bits { | ||
125 | unsigned int alloc:1; | ||
126 | unsigned int order:7; /* log2(MAX_BUDDY_NR); */ | ||
127 | }; | ||
128 | |||
129 | struct buddy_heap { | ||
130 | struct list_block *heap_base; | ||
131 | unsigned int nr_buddies; | ||
132 | struct list_head buddy_list; | ||
133 | struct buddy_bits bitmap[MAX_BUDDY_NR]; | ||
134 | }; | ||
135 | |||
136 | struct nvmap_heap { | ||
137 | struct list_head all_list; | ||
138 | struct list_head free_list; | ||
139 | struct mutex lock; | ||
140 | struct list_head buddy_list; | ||
141 | unsigned int min_buddy_shift; | ||
142 | unsigned int buddy_heap_size; | ||
143 | unsigned int small_alloc; | ||
144 | const char *name; | ||
145 | void *arg; | ||
146 | struct device dev; | ||
147 | }; | ||
148 | |||
149 | static struct kmem_cache *buddy_heap_cache; | ||
150 | static struct kmem_cache *block_cache; | ||
151 | |||
152 | static inline struct nvmap_heap *parent_of(struct buddy_heap *heap) | ||
153 | { | ||
154 | return heap->heap_base->heap; | ||
155 | } | ||
156 | |||
157 | static inline unsigned int order_of(size_t len, size_t min_shift) | ||
158 | { | ||
159 | len = 2 * DIV_ROUND_UP(len, (1 << min_shift)) - 1; | ||
160 | return fls(len)-1; | ||
161 | } | ||
162 | |||
163 | /* returns the free size in bytes of the buddy heap; must be called while | ||
164 | * holding the parent heap's lock. */ | ||
165 | static void buddy_stat(struct buddy_heap *heap, struct heap_stat *stat) | ||
166 | { | ||
167 | unsigned int index; | ||
168 | unsigned int shift = parent_of(heap)->min_buddy_shift; | ||
169 | |||
170 | for (index = 0; index < heap->nr_buddies; | ||
171 | index += (1 << heap->bitmap[index].order)) { | ||
172 | size_t curr = 1 << (heap->bitmap[index].order + shift); | ||
173 | |||
174 | stat->largest = max(stat->largest, curr); | ||
175 | stat->total += curr; | ||
176 | stat->count++; | ||
177 | |||
178 | if (!heap->bitmap[index].alloc) { | ||
179 | stat->free += curr; | ||
180 | stat->free_largest = max(stat->free_largest, curr); | ||
181 | stat->free_count++; | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | |||
186 | /* returns the free size of the heap (including any free blocks in any | ||
187 | * buddy-heap suballocators; must be called while holding the parent | ||
188 | * heap's lock. */ | ||
189 | static unsigned long heap_stat(struct nvmap_heap *heap, struct heap_stat *stat) | ||
190 | { | ||
191 | struct buddy_heap *bh; | ||
192 | struct list_block *l = NULL; | ||
193 | unsigned long base = -1ul; | ||
194 | |||
195 | memset(stat, 0, sizeof(*stat)); | ||
196 | mutex_lock(&heap->lock); | ||
197 | list_for_each_entry(l, &heap->all_list, all_list) { | ||
198 | stat->total += l->size; | ||
199 | stat->largest = max(l->size, stat->largest); | ||
200 | stat->count++; | ||
201 | base = min(base, l->orig_addr); | ||
202 | } | ||
203 | |||
204 | list_for_each_entry(bh, &heap->buddy_list, buddy_list) { | ||
205 | buddy_stat(bh, stat); | ||
206 | /* the total counts are double-counted for buddy heaps | ||
207 | * since the blocks allocated for buddy heaps exist in the | ||
208 | * all_list; subtract out the doubly-added stats */ | ||
209 | stat->total -= bh->heap_base->size; | ||
210 | stat->count--; | ||
211 | } | ||
212 | |||
213 | list_for_each_entry(l, &heap->free_list, free_list) { | ||
214 | stat->free += l->size; | ||
215 | stat->free_count++; | ||
216 | stat->free_largest = max(l->size, stat->free_largest); | ||
217 | } | ||
218 | mutex_unlock(&heap->lock); | ||
219 | |||
220 | return base; | ||
221 | } | ||
222 | |||
223 | static ssize_t heap_name_show(struct device *dev, | ||
224 | struct device_attribute *attr, char *buf); | ||
225 | |||
226 | static ssize_t heap_stat_show(struct device *dev, | ||
227 | struct device_attribute *attr, char *buf); | ||
228 | |||
229 | static struct device_attribute heap_stat_total_max = | ||
230 | __ATTR(total_max, S_IRUGO, heap_stat_show, NULL); | ||
231 | |||
232 | static struct device_attribute heap_stat_total_count = | ||
233 | __ATTR(total_count, S_IRUGO, heap_stat_show, NULL); | ||
234 | |||
235 | static struct device_attribute heap_stat_total_size = | ||
236 | __ATTR(total_size, S_IRUGO, heap_stat_show, NULL); | ||
237 | |||
238 | static struct device_attribute heap_stat_free_max = | ||
239 | __ATTR(free_max, S_IRUGO, heap_stat_show, NULL); | ||
240 | |||
241 | static struct device_attribute heap_stat_free_count = | ||
242 | __ATTR(free_count, S_IRUGO, heap_stat_show, NULL); | ||
243 | |||
244 | static struct device_attribute heap_stat_free_size = | ||
245 | __ATTR(free_size, S_IRUGO, heap_stat_show, NULL); | ||
246 | |||
247 | static struct device_attribute heap_stat_base = | ||
248 | __ATTR(base, S_IRUGO, heap_stat_show, NULL); | ||
249 | |||
250 | static struct device_attribute heap_attr_name = | ||
251 | __ATTR(name, S_IRUGO, heap_name_show, NULL); | ||
252 | |||
253 | static struct attribute *heap_stat_attrs[] = { | ||
254 | &heap_stat_total_max.attr, | ||
255 | &heap_stat_total_count.attr, | ||
256 | &heap_stat_total_size.attr, | ||
257 | &heap_stat_free_max.attr, | ||
258 | &heap_stat_free_count.attr, | ||
259 | &heap_stat_free_size.attr, | ||
260 | &heap_stat_base.attr, | ||
261 | &heap_attr_name.attr, | ||
262 | NULL, | ||
263 | }; | ||
264 | |||
265 | static struct attribute_group heap_stat_attr_group = { | ||
266 | .attrs = heap_stat_attrs, | ||
267 | }; | ||
268 | |||
269 | static ssize_t heap_name_show(struct device *dev, | ||
270 | struct device_attribute *attr, char *buf) | ||
271 | { | ||
272 | |||
273 | struct nvmap_heap *heap = container_of(dev, struct nvmap_heap, dev); | ||
274 | return sprintf(buf, "%s\n", heap->name); | ||
275 | } | ||
276 | |||
277 | static ssize_t heap_stat_show(struct device *dev, | ||
278 | struct device_attribute *attr, char *buf) | ||
279 | { | ||
280 | struct nvmap_heap *heap = container_of(dev, struct nvmap_heap, dev); | ||
281 | struct heap_stat stat; | ||
282 | unsigned long base; | ||
283 | |||
284 | base = heap_stat(heap, &stat); | ||
285 | |||
286 | if (attr == &heap_stat_total_max) | ||
287 | return sprintf(buf, "%u\n", stat.largest); | ||
288 | else if (attr == &heap_stat_total_count) | ||
289 | return sprintf(buf, "%u\n", stat.count); | ||
290 | else if (attr == &heap_stat_total_size) | ||
291 | return sprintf(buf, "%u\n", stat.total); | ||
292 | else if (attr == &heap_stat_free_max) | ||
293 | return sprintf(buf, "%u\n", stat.free_largest); | ||
294 | else if (attr == &heap_stat_free_count) | ||
295 | return sprintf(buf, "%u\n", stat.free_count); | ||
296 | else if (attr == &heap_stat_free_size) | ||
297 | return sprintf(buf, "%u\n", stat.free); | ||
298 | else if (attr == &heap_stat_base) | ||
299 | return sprintf(buf, "%08lx\n", base); | ||
300 | else | ||
301 | return -EINVAL; | ||
302 | } | ||
303 | #ifndef CONFIG_NVMAP_CARVEOUT_COMPACTOR | ||
304 | static struct nvmap_heap_block *buddy_alloc(struct buddy_heap *heap, | ||
305 | size_t size, size_t align, | ||
306 | unsigned int mem_prot) | ||
307 | { | ||
308 | unsigned int index = 0; | ||
309 | unsigned int min_shift = parent_of(heap)->min_buddy_shift; | ||
310 | unsigned int order = order_of(size, min_shift); | ||
311 | unsigned int align_mask; | ||
312 | unsigned int best = heap->nr_buddies; | ||
313 | struct buddy_block *b; | ||
314 | |||
315 | if (heap->heap_base->mem_prot != mem_prot) | ||
316 | return NULL; | ||
317 | |||
318 | align = max(align, (size_t)(1 << min_shift)); | ||
319 | align_mask = (align >> min_shift) - 1; | ||
320 | |||
321 | for (index = 0; index < heap->nr_buddies; | ||
322 | index += (1 << heap->bitmap[index].order)) { | ||
323 | |||
324 | if (heap->bitmap[index].alloc || (index & align_mask) || | ||
325 | (heap->bitmap[index].order < order)) | ||
326 | continue; | ||
327 | |||
328 | if (best == heap->nr_buddies || | ||
329 | heap->bitmap[index].order < heap->bitmap[best].order) | ||
330 | best = index; | ||
331 | |||
332 | if (heap->bitmap[best].order == order) | ||
333 | break; | ||
334 | } | ||
335 | |||
336 | if (best == heap->nr_buddies) | ||
337 | return NULL; | ||
338 | |||
339 | b = kmem_cache_zalloc(block_cache, GFP_KERNEL); | ||
340 | if (!b) | ||
341 | return NULL; | ||
342 | |||
343 | while (heap->bitmap[best].order != order) { | ||
344 | unsigned int buddy; | ||
345 | heap->bitmap[best].order--; | ||
346 | buddy = best ^ (1 << heap->bitmap[best].order); | ||
347 | heap->bitmap[buddy].order = heap->bitmap[best].order; | ||
348 | heap->bitmap[buddy].alloc = 0; | ||
349 | } | ||
350 | heap->bitmap[best].alloc = 1; | ||
351 | b->block.base = heap->heap_base->block.base + (best << min_shift); | ||
352 | b->heap = heap; | ||
353 | b->block.type = BLOCK_BUDDY; | ||
354 | return &b->block; | ||
355 | } | ||
356 | #endif | ||
357 | |||
358 | static struct buddy_heap *do_buddy_free(struct nvmap_heap_block *block) | ||
359 | { | ||
360 | struct buddy_block *b = container_of(block, struct buddy_block, block); | ||
361 | struct buddy_heap *h = b->heap; | ||
362 | unsigned int min_shift = parent_of(h)->min_buddy_shift; | ||
363 | unsigned int index; | ||
364 | |||
365 | index = (block->base - h->heap_base->block.base) >> min_shift; | ||
366 | h->bitmap[index].alloc = 0; | ||
367 | |||
368 | for (;;) { | ||
369 | unsigned int buddy = index ^ (1 << h->bitmap[index].order); | ||
370 | if (buddy >= h->nr_buddies || h->bitmap[buddy].alloc || | ||
371 | h->bitmap[buddy].order != h->bitmap[index].order) | ||
372 | break; | ||
373 | |||
374 | h->bitmap[buddy].order++; | ||
375 | h->bitmap[index].order++; | ||
376 | index = min(buddy, index); | ||
377 | } | ||
378 | |||
379 | kmem_cache_free(block_cache, b); | ||
380 | if ((1 << h->bitmap[0].order) == h->nr_buddies) | ||
381 | return h; | ||
382 | |||
383 | return NULL; | ||
384 | } | ||
385 | |||
386 | |||
387 | /* | ||
388 | * base_max limits position of allocated chunk in memory. | ||
389 | * if base_max is 0 then there is no such limitation. | ||
390 | */ | ||
391 | static struct nvmap_heap_block *do_heap_alloc(struct nvmap_heap *heap, | ||
392 | size_t len, size_t align, | ||
393 | unsigned int mem_prot, | ||
394 | unsigned long base_max) | ||
395 | { | ||
396 | struct list_block *b = NULL; | ||
397 | struct list_block *i = NULL; | ||
398 | struct list_block *rem = NULL; | ||
399 | unsigned long fix_base; | ||
400 | enum direction dir; | ||
401 | |||
402 | /* since pages are only mappable with one cache attribute, | ||
403 | * and most allocations from carveout heaps are DMA coherent | ||
404 | * (i.e., non-cacheable), round cacheable allocations up to | ||
405 | * a page boundary to ensure that the physical pages will | ||
406 | * only be mapped one way. */ | ||
407 | if (mem_prot == NVMAP_HANDLE_CACHEABLE || | ||
408 | mem_prot == NVMAP_HANDLE_INNER_CACHEABLE) { | ||
409 | align = max_t(size_t, align, PAGE_SIZE); | ||
410 | len = PAGE_ALIGN(len); | ||
411 | } | ||
412 | |||
413 | #ifdef CONFIG_NVMAP_CARVEOUT_COMPACTOR | ||
414 | dir = BOTTOM_UP; | ||
415 | #else | ||
416 | dir = (len <= heap->small_alloc) ? BOTTOM_UP : TOP_DOWN; | ||
417 | #endif | ||
418 | |||
419 | if (dir == BOTTOM_UP) { | ||
420 | list_for_each_entry(i, &heap->free_list, free_list) { | ||
421 | size_t fix_size; | ||
422 | fix_base = ALIGN(i->block.base, align); | ||
423 | fix_size = i->size - (fix_base - i->block.base); | ||
424 | |||
425 | /* needed for compaction. relocated chunk | ||
426 | * should never go up */ | ||
427 | if (base_max && fix_base > base_max) | ||
428 | break; | ||
429 | |||
430 | if (fix_size >= len) { | ||
431 | b = i; | ||
432 | break; | ||
433 | } | ||
434 | } | ||
435 | } else { | ||
436 | list_for_each_entry_reverse(i, &heap->free_list, free_list) { | ||
437 | if (i->size >= len) { | ||
438 | fix_base = i->block.base + i->size - len; | ||
439 | fix_base &= ~(align-1); | ||
440 | if (fix_base >= i->block.base) { | ||
441 | b = i; | ||
442 | break; | ||
443 | } | ||
444 | } | ||
445 | } | ||
446 | } | ||
447 | |||
448 | if (!b) | ||
449 | return NULL; | ||
450 | |||
451 | if (dir == BOTTOM_UP) | ||
452 | b->block.type = BLOCK_FIRST_FIT; | ||
453 | |||
454 | /* split free block */ | ||
455 | if (b->block.base != fix_base) { | ||
456 | /* insert a new free block before allocated */ | ||
457 | rem = kmem_cache_zalloc(block_cache, GFP_KERNEL); | ||
458 | if (!rem) { | ||
459 | b->orig_addr = b->block.base; | ||
460 | b->block.base = fix_base; | ||
461 | b->size -= (b->block.base - b->orig_addr); | ||
462 | goto out; | ||
463 | } | ||
464 | |||
465 | rem->block.type = BLOCK_EMPTY; | ||
466 | rem->block.base = b->block.base; | ||
467 | rem->orig_addr = rem->block.base; | ||
468 | rem->size = fix_base - rem->block.base; | ||
469 | b->block.base = fix_base; | ||
470 | b->orig_addr = fix_base; | ||
471 | b->size -= rem->size; | ||
472 | list_add_tail(&rem->all_list, &b->all_list); | ||
473 | list_add_tail(&rem->free_list, &b->free_list); | ||
474 | } | ||
475 | |||
476 | b->orig_addr = b->block.base; | ||
477 | |||
478 | if (b->size > len) { | ||
479 | /* insert a new free block after allocated */ | ||
480 | rem = kmem_cache_zalloc(block_cache, GFP_KERNEL); | ||
481 | if (!rem) | ||
482 | goto out; | ||
483 | |||
484 | rem->block.type = BLOCK_EMPTY; | ||
485 | rem->block.base = b->block.base + len; | ||
486 | rem->size = b->size - len; | ||
487 | BUG_ON(rem->size > b->size); | ||
488 | rem->orig_addr = rem->block.base; | ||
489 | b->size = len; | ||
490 | list_add(&rem->all_list, &b->all_list); | ||
491 | list_add(&rem->free_list, &b->free_list); | ||
492 | } | ||
493 | |||
494 | out: | ||
495 | list_del(&b->free_list); | ||
496 | b->heap = heap; | ||
497 | b->mem_prot = mem_prot; | ||
498 | b->align = align; | ||
499 | return &b->block; | ||
500 | } | ||
501 | |||
502 | #ifdef DEBUG_FREE_LIST | ||
503 | static void freelist_debug(struct nvmap_heap *heap, const char *title, | ||
504 | struct list_block *token) | ||
505 | { | ||
506 | int i; | ||
507 | struct list_block *n; | ||
508 | |||
509 | dev_debug(&heap->dev, "%s\n", title); | ||
510 | i = 0; | ||
511 | list_for_each_entry(n, &heap->free_list, free_list) { | ||
512 | dev_debug(&heap->dev, "\t%d [%p..%p]%s\n", i, (void *)n->orig_addr, | ||
513 | (void *)(n->orig_addr + n->size), | ||
514 | (n == token) ? "<--" : ""); | ||
515 | i++; | ||
516 | } | ||
517 | } | ||
518 | #else | ||
519 | #define freelist_debug(_heap, _title, _token) do { } while (0) | ||
520 | #endif | ||
521 | |||
522 | static struct list_block *do_heap_free(struct nvmap_heap_block *block) | ||
523 | { | ||
524 | struct list_block *b = container_of(block, struct list_block, block); | ||
525 | struct list_block *n = NULL; | ||
526 | struct nvmap_heap *heap = b->heap; | ||
527 | |||
528 | BUG_ON(b->block.base > b->orig_addr); | ||
529 | b->size += (b->block.base - b->orig_addr); | ||
530 | b->block.base = b->orig_addr; | ||
531 | |||
532 | freelist_debug(heap, "free list before", b); | ||
533 | |||
534 | /* Find position of first free block to the right of freed one */ | ||
535 | list_for_each_entry(n, &heap->free_list, free_list) { | ||
536 | if (n->block.base > b->block.base) | ||
537 | break; | ||
538 | } | ||
539 | |||
540 | /* Add freed block before found free one */ | ||
541 | list_add_tail(&b->free_list, &n->free_list); | ||
542 | BUG_ON(list_empty(&b->all_list)); | ||
543 | |||
544 | freelist_debug(heap, "free list pre-merge", b); | ||
545 | |||
546 | /* merge freed block with next if they connect | ||
547 | * freed block becomes bigger, next one is destroyed */ | ||
548 | if (!list_is_last(&b->free_list, &heap->free_list)) { | ||
549 | n = list_first_entry(&b->free_list, struct list_block, free_list); | ||
550 | if (n->block.base == b->block.base + b->size) { | ||
551 | list_del(&n->all_list); | ||
552 | list_del(&n->free_list); | ||
553 | BUG_ON(b->orig_addr >= n->orig_addr); | ||
554 | b->size += n->size; | ||
555 | kmem_cache_free(block_cache, n); | ||
556 | } | ||
557 | } | ||
558 | |||
559 | /* merge freed block with prev if they connect | ||
560 | * previous free block becomes bigger, freed one is destroyed */ | ||
561 | if (b->free_list.prev != &heap->free_list) { | ||
562 | n = list_entry(b->free_list.prev, struct list_block, free_list); | ||
563 | if (n->block.base + n->size == b->block.base) { | ||
564 | list_del(&b->all_list); | ||
565 | list_del(&b->free_list); | ||
566 | BUG_ON(n->orig_addr >= b->orig_addr); | ||
567 | n->size += b->size; | ||
568 | kmem_cache_free(block_cache, b); | ||
569 | b = n; | ||
570 | } | ||
571 | } | ||
572 | |||
573 | freelist_debug(heap, "free list after", b); | ||
574 | b->block.type = BLOCK_EMPTY; | ||
575 | return b; | ||
576 | } | ||
577 | |||
578 | #ifndef CONFIG_NVMAP_CARVEOUT_COMPACTOR | ||
579 | |||
580 | static struct nvmap_heap_block *do_buddy_alloc(struct nvmap_heap *h, | ||
581 | size_t len, size_t align, | ||
582 | unsigned int mem_prot) | ||
583 | { | ||
584 | struct buddy_heap *bh; | ||
585 | struct nvmap_heap_block *b = NULL; | ||
586 | |||
587 | list_for_each_entry(bh, &h->buddy_list, buddy_list) { | ||
588 | b = buddy_alloc(bh, len, align, mem_prot); | ||
589 | if (b) | ||
590 | return b; | ||
591 | } | ||
592 | |||
593 | /* no buddy heaps could service this allocation: try to create a new | ||
594 | * buddy heap instead */ | ||
595 | bh = kmem_cache_zalloc(buddy_heap_cache, GFP_KERNEL); | ||
596 | if (!bh) | ||
597 | return NULL; | ||
598 | |||
599 | b = do_heap_alloc(h, h->buddy_heap_size, | ||
600 | h->buddy_heap_size, mem_prot, 0); | ||
601 | if (!b) { | ||
602 | kmem_cache_free(buddy_heap_cache, bh); | ||
603 | return NULL; | ||
604 | } | ||
605 | |||
606 | bh->heap_base = container_of(b, struct list_block, block); | ||
607 | bh->nr_buddies = h->buddy_heap_size >> h->min_buddy_shift; | ||
608 | bh->bitmap[0].alloc = 0; | ||
609 | bh->bitmap[0].order = order_of(h->buddy_heap_size, h->min_buddy_shift); | ||
610 | list_add_tail(&bh->buddy_list, &h->buddy_list); | ||
611 | return buddy_alloc(bh, len, align, mem_prot); | ||
612 | } | ||
613 | |||
614 | #endif | ||
615 | |||
616 | #ifdef CONFIG_NVMAP_CARVEOUT_COMPACTOR | ||
617 | |||
618 | static int do_heap_copy_listblock(struct nvmap_device *dev, | ||
619 | unsigned long dst_base, unsigned long src_base, size_t len) | ||
620 | { | ||
621 | pte_t **pte_src = NULL; | ||
622 | pte_t **pte_dst = NULL; | ||
623 | void *addr_src = NULL; | ||
624 | void *addr_dst = NULL; | ||
625 | unsigned long kaddr_src; | ||
626 | unsigned long kaddr_dst; | ||
627 | unsigned long phys_src = src_base; | ||
628 | unsigned long phys_dst = dst_base; | ||
629 | unsigned long pfn_src; | ||
630 | unsigned long pfn_dst; | ||
631 | int error = 0; | ||
632 | |||
633 | pgprot_t prot = pgprot_writecombine(pgprot_kernel); | ||
634 | |||
635 | int page; | ||
636 | |||
637 | pte_src = nvmap_alloc_pte(dev, &addr_src); | ||
638 | if (IS_ERR(pte_src)) { | ||
639 | pr_err("Error when allocating pte_src\n"); | ||
640 | pte_src = NULL; | ||
641 | error = -1; | ||
642 | goto fail; | ||
643 | } | ||
644 | |||
645 | pte_dst = nvmap_alloc_pte(dev, &addr_dst); | ||
646 | if (IS_ERR(pte_dst)) { | ||
647 | pr_err("Error while allocating pte_dst\n"); | ||
648 | pte_dst = NULL; | ||
649 | error = -1; | ||
650 | goto fail; | ||
651 | } | ||
652 | |||
653 | kaddr_src = (unsigned long)addr_src; | ||
654 | kaddr_dst = (unsigned long)addr_dst; | ||
655 | |||
656 | BUG_ON(phys_dst > phys_src); | ||
657 | BUG_ON((phys_src & PAGE_MASK) != phys_src); | ||
658 | BUG_ON((phys_dst & PAGE_MASK) != phys_dst); | ||
659 | BUG_ON((len & PAGE_MASK) != len); | ||
660 | |||
661 | for (page = 0; page < (len >> PAGE_SHIFT) ; page++) { | ||
662 | |||
663 | pfn_src = __phys_to_pfn(phys_src) + page; | ||
664 | pfn_dst = __phys_to_pfn(phys_dst) + page; | ||
665 | |||
666 | set_pte_at(&init_mm, kaddr_src, *pte_src, | ||
667 | pfn_pte(pfn_src, prot)); | ||
668 | flush_tlb_kernel_page(kaddr_src); | ||
669 | |||
670 | set_pte_at(&init_mm, kaddr_dst, *pte_dst, | ||
671 | pfn_pte(pfn_dst, prot)); | ||
672 | flush_tlb_kernel_page(kaddr_dst); | ||
673 | |||
674 | memcpy(addr_dst, addr_src, PAGE_SIZE); | ||
675 | } | ||
676 | |||
677 | fail: | ||
678 | if (pte_src) | ||
679 | nvmap_free_pte(dev, pte_src); | ||
680 | if (pte_dst) | ||
681 | nvmap_free_pte(dev, pte_dst); | ||
682 | return error; | ||
683 | } | ||
684 | |||
685 | |||
686 | static struct nvmap_heap_block *do_heap_relocate_listblock( | ||
687 | struct list_block *block, bool fast) | ||
688 | { | ||
689 | struct nvmap_heap_block *heap_block = &block->block; | ||
690 | struct nvmap_heap_block *heap_block_new = NULL; | ||
691 | struct nvmap_heap *heap = block->heap; | ||
692 | struct nvmap_handle *handle = heap_block->handle; | ||
693 | unsigned long src_base = heap_block->base; | ||
694 | unsigned long dst_base; | ||
695 | size_t src_size = block->size; | ||
696 | size_t src_align = block->align; | ||
697 | unsigned int src_prot = block->mem_prot; | ||
698 | int error = 0; | ||
699 | struct nvmap_share *share; | ||
700 | |||
701 | if (!handle) { | ||
702 | pr_err("INVALID HANDLE!\n"); | ||
703 | return NULL; | ||
704 | } | ||
705 | |||
706 | mutex_lock(&handle->lock); | ||
707 | |||
708 | share = nvmap_get_share_from_dev(handle->dev); | ||
709 | |||
710 | /* TODO: It is possible to use only handle lock and no share | ||
711 | * pin_lock, but then we'll need to lock every handle during | ||
712 | * each pinning operation. Need to estimate performance impact | ||
713 | * if we decide to simplify locking this way. */ | ||
714 | mutex_lock(&share->pin_lock); | ||
715 | |||
716 | /* abort if block is pinned */ | ||
717 | if (atomic_read(&handle->pin)) | ||
718 | goto fail; | ||
719 | /* abort if block is mapped */ | ||
720 | if (handle->usecount) | ||
721 | goto fail; | ||
722 | |||
723 | if (fast) { | ||
724 | /* Fast compaction path - first allocate, then free. */ | ||
725 | heap_block_new = do_heap_alloc(heap, src_size, src_align, | ||
726 | src_prot, src_base); | ||
727 | if (heap_block_new) | ||
728 | do_heap_free(heap_block); | ||
729 | else | ||
730 | goto fail; | ||
731 | } else { | ||
732 | /* Full compaction path, first free, then allocate | ||
733 | * It is slower but provide best compaction results */ | ||
734 | do_heap_free(heap_block); | ||
735 | heap_block_new = do_heap_alloc(heap, src_size, src_align, | ||
736 | src_prot, src_base); | ||
737 | /* Allocation should always succeed*/ | ||
738 | BUG_ON(!heap_block_new); | ||
739 | } | ||
740 | |||
741 | /* update handle */ | ||
742 | handle->carveout = heap_block_new; | ||
743 | heap_block_new->handle = handle; | ||
744 | |||
745 | /* copy source data to new block location */ | ||
746 | dst_base = heap_block_new->base; | ||
747 | |||
748 | /* new allocation should always go lower addresses */ | ||
749 | BUG_ON(dst_base >= src_base); | ||
750 | |||
751 | error = do_heap_copy_listblock(handle->dev, | ||
752 | dst_base, src_base, src_size); | ||
753 | BUG_ON(error); | ||
754 | |||
755 | fail: | ||
756 | mutex_unlock(&share->pin_lock); | ||
757 | mutex_unlock(&handle->lock); | ||
758 | return heap_block_new; | ||
759 | } | ||
760 | |||
761 | static void nvmap_heap_compact(struct nvmap_heap *heap, | ||
762 | size_t requested_size, bool fast) | ||
763 | { | ||
764 | struct list_block *block_current = NULL; | ||
765 | struct list_block *block_prev = NULL; | ||
766 | struct list_block *block_next = NULL; | ||
767 | |||
768 | struct list_head *ptr, *ptr_prev, *ptr_next; | ||
769 | int relocation_count = 0; | ||
770 | |||
771 | ptr = heap->all_list.next; | ||
772 | |||
773 | /* walk through all blocks */ | ||
774 | while (ptr != &heap->all_list) { | ||
775 | block_current = list_entry(ptr, struct list_block, all_list); | ||
776 | |||
777 | ptr_prev = ptr->prev; | ||
778 | ptr_next = ptr->next; | ||
779 | |||
780 | if (block_current->block.type != BLOCK_EMPTY) { | ||
781 | ptr = ptr_next; | ||
782 | continue; | ||
783 | } | ||
784 | |||
785 | if (fast && block_current->size >= requested_size) | ||
786 | break; | ||
787 | |||
788 | /* relocate prev block */ | ||
789 | if (ptr_prev != &heap->all_list) { | ||
790 | |||
791 | block_prev = list_entry(ptr_prev, | ||
792 | struct list_block, all_list); | ||
793 | |||
794 | BUG_ON(block_prev->block.type != BLOCK_FIRST_FIT); | ||
795 | |||
796 | if (do_heap_relocate_listblock(block_prev, true)) { | ||
797 | |||
798 | /* After relocation current free block can be | ||
799 | * destroyed when it is merged with previous | ||
800 | * free block. Updated pointer to new free | ||
801 | * block can be obtained from the next block */ | ||
802 | relocation_count++; | ||
803 | ptr = ptr_next->prev; | ||
804 | continue; | ||
805 | } | ||
806 | } | ||
807 | |||
808 | if (ptr_next != &heap->all_list) { | ||
809 | |||
810 | block_next = list_entry(ptr_next, | ||
811 | struct list_block, all_list); | ||
812 | |||
813 | BUG_ON(block_next->block.type != BLOCK_FIRST_FIT); | ||
814 | |||
815 | if (do_heap_relocate_listblock(block_next, fast)) { | ||
816 | ptr = ptr_prev->next; | ||
817 | relocation_count++; | ||
818 | continue; | ||
819 | } | ||
820 | } | ||
821 | ptr = ptr_next; | ||
822 | } | ||
823 | pr_err("Relocated %d chunks\n", relocation_count); | ||
824 | } | ||
825 | #endif | ||
826 | |||
827 | void nvmap_usecount_inc(struct nvmap_handle *h) | ||
828 | { | ||
829 | if (h->alloc && !h->heap_pgalloc) { | ||
830 | mutex_lock(&h->lock); | ||
831 | h->usecount++; | ||
832 | mutex_unlock(&h->lock); | ||
833 | } else { | ||
834 | h->usecount++; | ||
835 | } | ||
836 | } | ||
837 | |||
838 | |||
839 | void nvmap_usecount_dec(struct nvmap_handle *h) | ||
840 | { | ||
841 | h->usecount--; | ||
842 | } | ||
843 | |||
844 | /* nvmap_heap_alloc: allocates a block of memory of len bytes, aligned to | ||
845 | * align bytes. */ | ||
846 | struct nvmap_heap_block *nvmap_heap_alloc(struct nvmap_heap *h, | ||
847 | struct nvmap_handle *handle) | ||
848 | { | ||
849 | struct nvmap_heap_block *b; | ||
850 | size_t len = handle->size; | ||
851 | size_t align = handle->align; | ||
852 | unsigned int prot = handle->flags; | ||
853 | |||
854 | mutex_lock(&h->lock); | ||
855 | |||
856 | #ifdef CONFIG_NVMAP_CARVEOUT_COMPACTOR | ||
857 | /* Align to page size */ | ||
858 | align = ALIGN(align, PAGE_SIZE); | ||
859 | len = ALIGN(len, PAGE_SIZE); | ||
860 | b = do_heap_alloc(h, len, align, prot, 0); | ||
861 | if (!b) { | ||
862 | pr_err("Compaction triggered!\n"); | ||
863 | nvmap_heap_compact(h, len, true); | ||
864 | b = do_heap_alloc(h, len, align, prot, 0); | ||
865 | if (!b) { | ||
866 | pr_err("Full compaction triggered!\n"); | ||
867 | nvmap_heap_compact(h, len, false); | ||
868 | b = do_heap_alloc(h, len, align, prot, 0); | ||
869 | } | ||
870 | } | ||
871 | #else | ||
872 | if (len <= h->buddy_heap_size / 2) { | ||
873 | b = do_buddy_alloc(h, len, align, prot); | ||
874 | } else { | ||
875 | if (h->buddy_heap_size) | ||
876 | len = ALIGN(len, h->buddy_heap_size); | ||
877 | align = max(align, (size_t)L1_CACHE_BYTES); | ||
878 | b = do_heap_alloc(h, len, align, prot, 0); | ||
879 | } | ||
880 | #endif | ||
881 | |||
882 | if (b) { | ||
883 | b->handle = handle; | ||
884 | handle->carveout = b; | ||
885 | } | ||
886 | mutex_unlock(&h->lock); | ||
887 | return b; | ||
888 | } | ||
889 | |||
890 | struct nvmap_heap *nvmap_block_to_heap(struct nvmap_heap_block *b) | ||
891 | { | ||
892 | if (b->type == BLOCK_BUDDY) { | ||
893 | struct buddy_block *bb; | ||
894 | bb = container_of(b, struct buddy_block, block); | ||
895 | return parent_of(bb->heap); | ||
896 | } else { | ||
897 | struct list_block *lb; | ||
898 | lb = container_of(b, struct list_block, block); | ||
899 | return lb->heap; | ||
900 | } | ||
901 | } | ||
902 | |||
903 | /* nvmap_heap_free: frees block b*/ | ||
904 | void nvmap_heap_free(struct nvmap_heap_block *b) | ||
905 | { | ||
906 | struct buddy_heap *bh = NULL; | ||
907 | struct nvmap_heap *h = nvmap_block_to_heap(b); | ||
908 | struct list_block *lb; | ||
909 | |||
910 | mutex_lock(&h->lock); | ||
911 | if (b->type == BLOCK_BUDDY) | ||
912 | bh = do_buddy_free(b); | ||
913 | else { | ||
914 | lb = container_of(b, struct list_block, block); | ||
915 | nvmap_flush_heap_block(NULL, b, lb->size, lb->mem_prot); | ||
916 | do_heap_free(b); | ||
917 | } | ||
918 | |||
919 | if (bh) { | ||
920 | list_del(&bh->buddy_list); | ||
921 | mutex_unlock(&h->lock); | ||
922 | nvmap_heap_free(&bh->heap_base->block); | ||
923 | kmem_cache_free(buddy_heap_cache, bh); | ||
924 | } else | ||
925 | mutex_unlock(&h->lock); | ||
926 | } | ||
927 | |||
928 | |||
929 | static void heap_release(struct device *heap) | ||
930 | { | ||
931 | } | ||
932 | |||
933 | /* nvmap_heap_create: create a heap object of len bytes, starting from | ||
934 | * address base. | ||
935 | * | ||
936 | * if buddy_size is >= NVMAP_HEAP_MIN_BUDDY_SIZE, then allocations <= 1/2 | ||
937 | * of the buddy heap size will use a buddy sub-allocator, where each buddy | ||
938 | * heap is buddy_size bytes (should be a power of 2). all other allocations | ||
939 | * will be rounded up to be a multiple of buddy_size bytes. | ||
940 | */ | ||
941 | struct nvmap_heap *nvmap_heap_create(struct device *parent, const char *name, | ||
942 | phys_addr_t base, size_t len, | ||
943 | size_t buddy_size, void *arg) | ||
944 | { | ||
945 | struct nvmap_heap *h = NULL; | ||
946 | struct list_block *l = NULL; | ||
947 | |||
948 | if (WARN_ON(buddy_size && buddy_size < NVMAP_HEAP_MIN_BUDDY_SIZE)) { | ||
949 | dev_warn(parent, "%s: buddy_size %u too small\n", __func__, | ||
950 | buddy_size); | ||
951 | buddy_size = 0; | ||
952 | } else if (WARN_ON(buddy_size >= len)) { | ||
953 | dev_warn(parent, "%s: buddy_size %u too large\n", __func__, | ||
954 | buddy_size); | ||
955 | buddy_size = 0; | ||
956 | } else if (WARN_ON(buddy_size & (buddy_size - 1))) { | ||
957 | dev_warn(parent, "%s: buddy_size %u not a power of 2\n", | ||
958 | __func__, buddy_size); | ||
959 | buddy_size = 1 << (ilog2(buddy_size) + 1); | ||
960 | } | ||
961 | |||
962 | if (WARN_ON(buddy_size && (base & (buddy_size - 1)))) { | ||
963 | unsigned long orig = base; | ||
964 | dev_warn(parent, "%s: base address %p not aligned to " | ||
965 | "buddy_size %u\n", __func__, (void *)base, buddy_size); | ||
966 | base = ALIGN(base, buddy_size); | ||
967 | len -= (base - orig); | ||
968 | } | ||
969 | |||
970 | if (WARN_ON(buddy_size && (len & (buddy_size - 1)))) { | ||
971 | dev_warn(parent, "%s: length %u not aligned to " | ||
972 | "buddy_size %u\n", __func__, len, buddy_size); | ||
973 | len &= ~(buddy_size - 1); | ||
974 | } | ||
975 | |||
976 | h = kzalloc(sizeof(*h), GFP_KERNEL); | ||
977 | if (!h) { | ||
978 | dev_err(parent, "%s: out of memory\n", __func__); | ||
979 | goto fail_alloc; | ||
980 | } | ||
981 | |||
982 | l = kmem_cache_zalloc(block_cache, GFP_KERNEL); | ||
983 | if (!l) { | ||
984 | dev_err(parent, "%s: out of memory\n", __func__); | ||
985 | goto fail_alloc; | ||
986 | } | ||
987 | |||
988 | dev_set_name(&h->dev, "heap-%s", name); | ||
989 | h->name = name; | ||
990 | h->arg = arg; | ||
991 | h->dev.parent = parent; | ||
992 | h->dev.driver = NULL; | ||
993 | h->dev.release = heap_release; | ||
994 | if (device_register(&h->dev)) { | ||
995 | dev_err(parent, "%s: failed to register %s\n", __func__, | ||
996 | dev_name(&h->dev)); | ||
997 | goto fail_alloc; | ||
998 | } | ||
999 | if (sysfs_create_group(&h->dev.kobj, &heap_stat_attr_group)) { | ||
1000 | dev_err(&h->dev, "%s: failed to create attributes\n", __func__); | ||
1001 | goto fail_register; | ||
1002 | } | ||
1003 | h->small_alloc = max(2 * buddy_size, len / 256); | ||
1004 | h->buddy_heap_size = buddy_size; | ||
1005 | if (buddy_size) | ||
1006 | h->min_buddy_shift = ilog2(buddy_size / MAX_BUDDY_NR); | ||
1007 | INIT_LIST_HEAD(&h->free_list); | ||
1008 | INIT_LIST_HEAD(&h->buddy_list); | ||
1009 | INIT_LIST_HEAD(&h->all_list); | ||
1010 | mutex_init(&h->lock); | ||
1011 | l->block.base = base; | ||
1012 | l->block.type = BLOCK_EMPTY; | ||
1013 | l->size = len; | ||
1014 | l->orig_addr = base; | ||
1015 | list_add_tail(&l->free_list, &h->free_list); | ||
1016 | list_add_tail(&l->all_list, &h->all_list); | ||
1017 | |||
1018 | inner_flush_cache_all(); | ||
1019 | outer_flush_range(base, base + len); | ||
1020 | wmb(); | ||
1021 | return h; | ||
1022 | |||
1023 | fail_register: | ||
1024 | device_unregister(&h->dev); | ||
1025 | fail_alloc: | ||
1026 | if (l) | ||
1027 | kmem_cache_free(block_cache, l); | ||
1028 | kfree(h); | ||
1029 | return NULL; | ||
1030 | } | ||
1031 | |||
1032 | void *nvmap_heap_device_to_arg(struct device *dev) | ||
1033 | { | ||
1034 | struct nvmap_heap *heap = container_of(dev, struct nvmap_heap, dev); | ||
1035 | return heap->arg; | ||
1036 | } | ||
1037 | |||
1038 | void *nvmap_heap_to_arg(struct nvmap_heap *heap) | ||
1039 | { | ||
1040 | return heap->arg; | ||
1041 | } | ||
1042 | |||
1043 | /* nvmap_heap_destroy: frees all resources in heap */ | ||
1044 | void nvmap_heap_destroy(struct nvmap_heap *heap) | ||
1045 | { | ||
1046 | WARN_ON(!list_empty(&heap->buddy_list)); | ||
1047 | |||
1048 | sysfs_remove_group(&heap->dev.kobj, &heap_stat_attr_group); | ||
1049 | device_unregister(&heap->dev); | ||
1050 | |||
1051 | while (!list_empty(&heap->buddy_list)) { | ||
1052 | struct buddy_heap *b; | ||
1053 | b = list_first_entry(&heap->buddy_list, struct buddy_heap, | ||
1054 | buddy_list); | ||
1055 | list_del(&heap->buddy_list); | ||
1056 | nvmap_heap_free(&b->heap_base->block); | ||
1057 | kmem_cache_free(buddy_heap_cache, b); | ||
1058 | } | ||
1059 | |||
1060 | WARN_ON(!list_is_singular(&heap->all_list)); | ||
1061 | while (!list_empty(&heap->all_list)) { | ||
1062 | struct list_block *l; | ||
1063 | l = list_first_entry(&heap->all_list, struct list_block, | ||
1064 | all_list); | ||
1065 | list_del(&l->all_list); | ||
1066 | kmem_cache_free(block_cache, l); | ||
1067 | } | ||
1068 | |||
1069 | kfree(heap); | ||
1070 | } | ||
1071 | |||
1072 | /* nvmap_heap_create_group: adds the attribute_group grp to the heap kobject */ | ||
1073 | int nvmap_heap_create_group(struct nvmap_heap *heap, | ||
1074 | const struct attribute_group *grp) | ||
1075 | { | ||
1076 | return sysfs_create_group(&heap->dev.kobj, grp); | ||
1077 | } | ||
1078 | |||
1079 | /* nvmap_heap_remove_group: removes the attribute_group grp */ | ||
1080 | void nvmap_heap_remove_group(struct nvmap_heap *heap, | ||
1081 | const struct attribute_group *grp) | ||
1082 | { | ||
1083 | sysfs_remove_group(&heap->dev.kobj, grp); | ||
1084 | } | ||
1085 | |||
1086 | int nvmap_heap_init(void) | ||
1087 | { | ||
1088 | BUG_ON(buddy_heap_cache != NULL); | ||
1089 | buddy_heap_cache = KMEM_CACHE(buddy_heap, 0); | ||
1090 | if (!buddy_heap_cache) { | ||
1091 | pr_err("%s: unable to create buddy heap cache\n", __func__); | ||
1092 | return -ENOMEM; | ||
1093 | } | ||
1094 | |||
1095 | block_cache = KMEM_CACHE(combo_block, 0); | ||
1096 | if (!block_cache) { | ||
1097 | kmem_cache_destroy(buddy_heap_cache); | ||
1098 | pr_err("%s: unable to create block cache\n", __func__); | ||
1099 | return -ENOMEM; | ||
1100 | } | ||
1101 | return 0; | ||
1102 | } | ||
1103 | |||
1104 | void nvmap_heap_deinit(void) | ||
1105 | { | ||
1106 | if (buddy_heap_cache) | ||
1107 | kmem_cache_destroy(buddy_heap_cache); | ||
1108 | if (block_cache) | ||
1109 | kmem_cache_destroy(block_cache); | ||
1110 | |||
1111 | block_cache = NULL; | ||
1112 | buddy_heap_cache = NULL; | ||
1113 | } | ||
diff --git a/drivers/video/tegra/nvmap/nvmap_heap.h b/drivers/video/tegra/nvmap/nvmap_heap.h new file mode 100644 index 00000000000..158a1fa3d33 --- /dev/null +++ b/drivers/video/tegra/nvmap/nvmap_heap.h | |||
@@ -0,0 +1,68 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/nvmap/nvmap_heap.h | ||
3 | * | ||
4 | * GPU heap allocator. | ||
5 | * | ||
6 | * Copyright (c) 2010-2011, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | #ifndef __NVMAP_HEAP_H | ||
24 | #define __NVMAP_HEAP_H | ||
25 | |||
26 | struct device; | ||
27 | struct nvmap_heap; | ||
28 | struct attribute_group; | ||
29 | |||
30 | struct nvmap_heap_block { | ||
31 | phys_addr_t base; | ||
32 | unsigned int type; | ||
33 | struct nvmap_handle *handle; | ||
34 | }; | ||
35 | |||
36 | #define NVMAP_HEAP_MIN_BUDDY_SIZE 8192 | ||
37 | |||
38 | struct nvmap_heap *nvmap_heap_create(struct device *parent, const char *name, | ||
39 | phys_addr_t base, size_t len, | ||
40 | unsigned int buddy_size, void *arg); | ||
41 | |||
42 | void nvmap_heap_destroy(struct nvmap_heap *heap); | ||
43 | |||
44 | void *nvmap_heap_device_to_arg(struct device *dev); | ||
45 | |||
46 | void *nvmap_heap_to_arg(struct nvmap_heap *heap); | ||
47 | |||
48 | struct nvmap_heap_block *nvmap_heap_alloc(struct nvmap_heap *heap, | ||
49 | struct nvmap_handle *handle); | ||
50 | |||
51 | struct nvmap_heap *nvmap_block_to_heap(struct nvmap_heap_block *b); | ||
52 | |||
53 | void nvmap_heap_free(struct nvmap_heap_block *block); | ||
54 | |||
55 | int nvmap_heap_create_group(struct nvmap_heap *heap, | ||
56 | const struct attribute_group *grp); | ||
57 | |||
58 | void nvmap_heap_remove_group(struct nvmap_heap *heap, | ||
59 | const struct attribute_group *grp); | ||
60 | |||
61 | int __init nvmap_heap_init(void); | ||
62 | |||
63 | void nvmap_heap_deinit(void); | ||
64 | |||
65 | int nvmap_flush_heap_block(struct nvmap_client *client, | ||
66 | struct nvmap_heap_block *block, size_t len, unsigned int prot); | ||
67 | |||
68 | #endif | ||
diff --git a/drivers/video/tegra/nvmap/nvmap_ioctl.c b/drivers/video/tegra/nvmap/nvmap_ioctl.c new file mode 100644 index 00000000000..58bc71d5046 --- /dev/null +++ b/drivers/video/tegra/nvmap/nvmap_ioctl.c | |||
@@ -0,0 +1,749 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/nvmap/nvmap_ioctl.c | ||
3 | * | ||
4 | * User-space interface to nvmap | ||
5 | * | ||
6 | * Copyright (c) 2011, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | #include <linux/dma-mapping.h> | ||
24 | #include <linux/fs.h> | ||
25 | #include <linux/kernel.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <linux/uaccess.h> | ||
28 | |||
29 | #include <asm/cacheflush.h> | ||
30 | #include <asm/outercache.h> | ||
31 | #include <asm/tlbflush.h> | ||
32 | |||
33 | #include <mach/iovmm.h> | ||
34 | #include <mach/nvmap.h> | ||
35 | |||
36 | #include "nvmap_ioctl.h" | ||
37 | #include "nvmap.h" | ||
38 | #include "nvmap_common.h" | ||
39 | |||
40 | static ssize_t rw_handle(struct nvmap_client *client, struct nvmap_handle *h, | ||
41 | int is_read, unsigned long h_offs, | ||
42 | unsigned long sys_addr, unsigned long h_stride, | ||
43 | unsigned long sys_stride, unsigned long elem_size, | ||
44 | unsigned long count); | ||
45 | |||
46 | static int cache_maint(struct nvmap_client *client, struct nvmap_handle *h, | ||
47 | unsigned long start, unsigned long end, unsigned int op); | ||
48 | |||
49 | |||
50 | int nvmap_ioctl_pinop(struct file *filp, bool is_pin, void __user *arg) | ||
51 | { | ||
52 | struct nvmap_pin_handle op; | ||
53 | struct nvmap_handle *h; | ||
54 | unsigned long on_stack[16]; | ||
55 | unsigned long *refs; | ||
56 | unsigned long __user *output; | ||
57 | unsigned int i; | ||
58 | int err = 0; | ||
59 | |||
60 | if (copy_from_user(&op, arg, sizeof(op))) | ||
61 | return -EFAULT; | ||
62 | |||
63 | if (!op.count) | ||
64 | return -EINVAL; | ||
65 | |||
66 | if (op.count > 1) { | ||
67 | size_t bytes = op.count * sizeof(unsigned long *); | ||
68 | |||
69 | if (op.count > ARRAY_SIZE(on_stack)) | ||
70 | refs = kmalloc(op.count * sizeof(*refs), GFP_KERNEL); | ||
71 | else | ||
72 | refs = on_stack; | ||
73 | |||
74 | if (!refs) | ||
75 | return -ENOMEM; | ||
76 | |||
77 | if (copy_from_user(refs, (void *)op.handles, bytes)) { | ||
78 | err = -EFAULT; | ||
79 | goto out; | ||
80 | } | ||
81 | } else { | ||
82 | refs = on_stack; | ||
83 | on_stack[0] = (unsigned long)op.handles; | ||
84 | } | ||
85 | |||
86 | if (is_pin) | ||
87 | err = nvmap_pin_ids(filp->private_data, op.count, refs); | ||
88 | else | ||
89 | nvmap_unpin_ids(filp->private_data, op.count, refs); | ||
90 | |||
91 | /* skip the output stage on unpin */ | ||
92 | if (err || !is_pin) | ||
93 | goto out; | ||
94 | |||
95 | /* it is guaranteed that if nvmap_pin_ids returns 0 that | ||
96 | * all of the handle_ref objects are valid, so dereferencing | ||
97 | * directly here is safe */ | ||
98 | if (op.count > 1) | ||
99 | output = (unsigned long __user *)op.addr; | ||
100 | else { | ||
101 | struct nvmap_pin_handle __user *tmp = arg; | ||
102 | output = (unsigned long __user *)&(tmp->addr); | ||
103 | } | ||
104 | |||
105 | if (!output) | ||
106 | goto out; | ||
107 | |||
108 | for (i = 0; i < op.count && !err; i++) { | ||
109 | unsigned long addr; | ||
110 | |||
111 | h = (struct nvmap_handle *)refs[i]; | ||
112 | |||
113 | if (h->heap_pgalloc && h->pgalloc.contig) | ||
114 | addr = page_to_phys(h->pgalloc.pages[0]); | ||
115 | else if (h->heap_pgalloc) | ||
116 | addr = h->pgalloc.area->iovm_start; | ||
117 | else | ||
118 | addr = h->carveout->base; | ||
119 | |||
120 | err = put_user(addr, &output[i]); | ||
121 | } | ||
122 | |||
123 | if (err) | ||
124 | nvmap_unpin_ids(filp->private_data, op.count, refs); | ||
125 | |||
126 | out: | ||
127 | if (refs != on_stack) | ||
128 | kfree(refs); | ||
129 | |||
130 | return err; | ||
131 | } | ||
132 | |||
133 | int nvmap_ioctl_getid(struct file *filp, void __user *arg) | ||
134 | { | ||
135 | struct nvmap_client *client = filp->private_data; | ||
136 | struct nvmap_create_handle op; | ||
137 | struct nvmap_handle *h = NULL; | ||
138 | |||
139 | if (copy_from_user(&op, arg, sizeof(op))) | ||
140 | return -EFAULT; | ||
141 | |||
142 | if (!op.handle) | ||
143 | return -EINVAL; | ||
144 | |||
145 | h = nvmap_get_handle_id(client, op.handle); | ||
146 | |||
147 | if (!h) | ||
148 | return -EPERM; | ||
149 | |||
150 | op.id = (__u32)h; | ||
151 | if (client == h->owner) | ||
152 | h->global = true; | ||
153 | |||
154 | nvmap_handle_put(h); | ||
155 | |||
156 | return copy_to_user(arg, &op, sizeof(op)) ? -EFAULT : 0; | ||
157 | } | ||
158 | |||
159 | int nvmap_ioctl_alloc(struct file *filp, void __user *arg) | ||
160 | { | ||
161 | struct nvmap_alloc_handle op; | ||
162 | struct nvmap_client *client = filp->private_data; | ||
163 | |||
164 | if (copy_from_user(&op, arg, sizeof(op))) | ||
165 | return -EFAULT; | ||
166 | |||
167 | if (!op.handle) | ||
168 | return -EINVAL; | ||
169 | |||
170 | if (op.align & (op.align - 1)) | ||
171 | return -EINVAL; | ||
172 | |||
173 | /* user-space handles are aligned to page boundaries, to prevent | ||
174 | * data leakage. */ | ||
175 | op.align = max_t(size_t, op.align, PAGE_SIZE); | ||
176 | |||
177 | return nvmap_alloc_handle_id(client, op.handle, op.heap_mask, | ||
178 | op.align, op.flags); | ||
179 | } | ||
180 | |||
181 | int nvmap_ioctl_create(struct file *filp, unsigned int cmd, void __user *arg) | ||
182 | { | ||
183 | struct nvmap_create_handle op; | ||
184 | struct nvmap_handle_ref *ref = NULL; | ||
185 | struct nvmap_client *client = filp->private_data; | ||
186 | int err = 0; | ||
187 | |||
188 | if (copy_from_user(&op, arg, sizeof(op))) | ||
189 | return -EFAULT; | ||
190 | |||
191 | if (!client) | ||
192 | return -ENODEV; | ||
193 | |||
194 | if (cmd == NVMAP_IOC_CREATE) { | ||
195 | ref = nvmap_create_handle(client, PAGE_ALIGN(op.size)); | ||
196 | if (!IS_ERR(ref)) | ||
197 | ref->handle->orig_size = op.size; | ||
198 | } else if (cmd == NVMAP_IOC_FROM_ID) { | ||
199 | ref = nvmap_duplicate_handle_id(client, op.id); | ||
200 | } else { | ||
201 | return -EINVAL; | ||
202 | } | ||
203 | |||
204 | if (IS_ERR(ref)) | ||
205 | return PTR_ERR(ref); | ||
206 | |||
207 | op.handle = nvmap_ref_to_id(ref); | ||
208 | if (copy_to_user(arg, &op, sizeof(op))) { | ||
209 | err = -EFAULT; | ||
210 | nvmap_free_handle_id(client, op.handle); | ||
211 | } | ||
212 | |||
213 | return err; | ||
214 | } | ||
215 | |||
216 | int nvmap_map_into_caller_ptr(struct file *filp, void __user *arg) | ||
217 | { | ||
218 | struct nvmap_client *client = filp->private_data; | ||
219 | struct nvmap_map_caller op; | ||
220 | struct nvmap_vma_priv *vpriv; | ||
221 | struct vm_area_struct *vma; | ||
222 | struct nvmap_handle *h = NULL; | ||
223 | unsigned int cache_flags; | ||
224 | int err = 0; | ||
225 | |||
226 | if (copy_from_user(&op, arg, sizeof(op))) | ||
227 | return -EFAULT; | ||
228 | |||
229 | if (!op.handle) | ||
230 | return -EINVAL; | ||
231 | |||
232 | h = nvmap_get_handle_id(client, op.handle); | ||
233 | |||
234 | if (!h) | ||
235 | return -EPERM; | ||
236 | |||
237 | down_read(¤t->mm->mmap_sem); | ||
238 | |||
239 | vma = find_vma(current->mm, op.addr); | ||
240 | if (!vma || !vma->vm_private_data) { | ||
241 | err = -ENOMEM; | ||
242 | goto out; | ||
243 | } | ||
244 | |||
245 | if (op.offset & ~PAGE_MASK) { | ||
246 | err = -EFAULT; | ||
247 | goto out; | ||
248 | } | ||
249 | |||
250 | if ((op.offset + op.length) > h->size) { | ||
251 | err = -EADDRNOTAVAIL; | ||
252 | goto out; | ||
253 | } | ||
254 | |||
255 | vpriv = vma->vm_private_data; | ||
256 | BUG_ON(!vpriv); | ||
257 | |||
258 | /* the VMA must exactly match the requested mapping operation, and the | ||
259 | * VMA that is targetted must have been created by this driver | ||
260 | */ | ||
261 | if ((vma->vm_start != op.addr) || !is_nvmap_vma(vma) || | ||
262 | (vma->vm_end-vma->vm_start != op.length)) { | ||
263 | err = -EPERM; | ||
264 | goto out; | ||
265 | } | ||
266 | |||
267 | /* verify that each mmap() system call creates a unique VMA */ | ||
268 | |||
269 | if (vpriv->handle && (h == vpriv->handle)) { | ||
270 | goto out; | ||
271 | } else if (vpriv->handle) { | ||
272 | err = -EADDRNOTAVAIL; | ||
273 | goto out; | ||
274 | } | ||
275 | |||
276 | nvmap_usecount_inc(h); | ||
277 | |||
278 | if (!h->heap_pgalloc && (h->carveout->base & ~PAGE_MASK)) { | ||
279 | nvmap_usecount_dec(h); | ||
280 | err = -EFAULT; | ||
281 | goto out; | ||
282 | } | ||
283 | |||
284 | vpriv->handle = h; | ||
285 | vpriv->offs = op.offset; | ||
286 | |||
287 | cache_flags = op.flags & NVMAP_HANDLE_CACHE_FLAG; | ||
288 | if ((cache_flags == NVMAP_HANDLE_INNER_CACHEABLE || | ||
289 | cache_flags == NVMAP_HANDLE_CACHEABLE) && | ||
290 | (h->flags == NVMAP_HANDLE_UNCACHEABLE || | ||
291 | h->flags == NVMAP_HANDLE_WRITE_COMBINE)) { | ||
292 | if (h->size & ~PAGE_MASK) { | ||
293 | pr_err("\n%s:attempt to convert a buffer from uc/wc to" | ||
294 | " wb, whose size is not a multiple of page size." | ||
295 | " request ignored.\n", __func__); | ||
296 | } else { | ||
297 | unsigned int nr_page = h->size >> PAGE_SHIFT; | ||
298 | wmb(); | ||
299 | /* override allocation time cache coherency attributes. */ | ||
300 | h->flags &= ~NVMAP_HANDLE_CACHE_FLAG; | ||
301 | h->flags |= cache_flags; | ||
302 | |||
303 | /* Update page attributes, if the memory is allocated | ||
304 | * from system heap pages. | ||
305 | */ | ||
306 | if (cache_flags == NVMAP_HANDLE_INNER_CACHEABLE && | ||
307 | h->heap_pgalloc) | ||
308 | set_pages_array_iwb(h->pgalloc.pages, nr_page); | ||
309 | else if (h->heap_pgalloc) | ||
310 | set_pages_array_wb(h->pgalloc.pages, nr_page); | ||
311 | } | ||
312 | } | ||
313 | vma->vm_page_prot = nvmap_pgprot(h, vma->vm_page_prot); | ||
314 | |||
315 | out: | ||
316 | up_read(¤t->mm->mmap_sem); | ||
317 | |||
318 | if (err) | ||
319 | nvmap_handle_put(h); | ||
320 | return err; | ||
321 | } | ||
322 | |||
323 | int nvmap_ioctl_get_param(struct file *filp, void __user* arg) | ||
324 | { | ||
325 | struct nvmap_handle_param op; | ||
326 | struct nvmap_client *client = filp->private_data; | ||
327 | struct nvmap_handle *h; | ||
328 | int err = 0; | ||
329 | |||
330 | if (copy_from_user(&op, arg, sizeof(op))) | ||
331 | return -EFAULT; | ||
332 | |||
333 | h = nvmap_get_handle_id(client, op.handle); | ||
334 | if (!h) | ||
335 | return -EINVAL; | ||
336 | |||
337 | switch (op.param) { | ||
338 | case NVMAP_HANDLE_PARAM_SIZE: | ||
339 | op.result = h->orig_size; | ||
340 | break; | ||
341 | case NVMAP_HANDLE_PARAM_ALIGNMENT: | ||
342 | mutex_lock(&h->lock); | ||
343 | if (!h->alloc) | ||
344 | op.result = 0; | ||
345 | else if (h->heap_pgalloc) | ||
346 | op.result = PAGE_SIZE; | ||
347 | else if (h->carveout->base) | ||
348 | op.result = (h->carveout->base & -h->carveout->base); | ||
349 | else | ||
350 | op.result = SZ_4M; | ||
351 | mutex_unlock(&h->lock); | ||
352 | break; | ||
353 | case NVMAP_HANDLE_PARAM_BASE: | ||
354 | if (WARN_ON(!h->alloc || !atomic_add_return(0, &h->pin))) | ||
355 | op.result = -1ul; | ||
356 | else if (!h->heap_pgalloc) { | ||
357 | mutex_lock(&h->lock); | ||
358 | op.result = h->carveout->base; | ||
359 | mutex_unlock(&h->lock); | ||
360 | } else if (h->pgalloc.contig) | ||
361 | op.result = page_to_phys(h->pgalloc.pages[0]); | ||
362 | else if (h->pgalloc.area) | ||
363 | op.result = h->pgalloc.area->iovm_start; | ||
364 | else | ||
365 | op.result = -1ul; | ||
366 | break; | ||
367 | case NVMAP_HANDLE_PARAM_HEAP: | ||
368 | if (!h->alloc) | ||
369 | op.result = 0; | ||
370 | else if (!h->heap_pgalloc) { | ||
371 | mutex_lock(&h->lock); | ||
372 | op.result = nvmap_carveout_usage(client, h->carveout); | ||
373 | mutex_unlock(&h->lock); | ||
374 | } else if (h->pgalloc.contig) | ||
375 | op.result = NVMAP_HEAP_SYSMEM; | ||
376 | else | ||
377 | op.result = NVMAP_HEAP_IOVMM; | ||
378 | break; | ||
379 | default: | ||
380 | err = -EINVAL; | ||
381 | break; | ||
382 | } | ||
383 | |||
384 | if (!err && copy_to_user(arg, &op, sizeof(op))) | ||
385 | err = -EFAULT; | ||
386 | |||
387 | nvmap_handle_put(h); | ||
388 | return err; | ||
389 | } | ||
390 | |||
391 | int nvmap_ioctl_rw_handle(struct file *filp, int is_read, void __user* arg) | ||
392 | { | ||
393 | struct nvmap_client *client = filp->private_data; | ||
394 | struct nvmap_rw_handle __user *uarg = arg; | ||
395 | struct nvmap_rw_handle op; | ||
396 | struct nvmap_handle *h; | ||
397 | ssize_t copied; | ||
398 | int err = 0; | ||
399 | |||
400 | if (copy_from_user(&op, arg, sizeof(op))) | ||
401 | return -EFAULT; | ||
402 | |||
403 | if (!op.handle || !op.addr || !op.count || !op.elem_size) | ||
404 | return -EINVAL; | ||
405 | |||
406 | h = nvmap_get_handle_id(client, op.handle); | ||
407 | if (!h) | ||
408 | return -EPERM; | ||
409 | |||
410 | nvmap_usecount_inc(h); | ||
411 | |||
412 | copied = rw_handle(client, h, is_read, op.offset, | ||
413 | (unsigned long)op.addr, op.hmem_stride, | ||
414 | op.user_stride, op.elem_size, op.count); | ||
415 | |||
416 | if (copied < 0) { | ||
417 | err = copied; | ||
418 | copied = 0; | ||
419 | } else if (copied < (op.count * op.elem_size)) | ||
420 | err = -EINTR; | ||
421 | |||
422 | __put_user(copied, &uarg->count); | ||
423 | |||
424 | nvmap_usecount_dec(h); | ||
425 | |||
426 | nvmap_handle_put(h); | ||
427 | |||
428 | return err; | ||
429 | } | ||
430 | |||
431 | int nvmap_ioctl_cache_maint(struct file *filp, void __user *arg) | ||
432 | { | ||
433 | struct nvmap_client *client = filp->private_data; | ||
434 | struct nvmap_cache_op op; | ||
435 | struct vm_area_struct *vma; | ||
436 | struct nvmap_vma_priv *vpriv; | ||
437 | unsigned long start; | ||
438 | unsigned long end; | ||
439 | int err = 0; | ||
440 | |||
441 | if (copy_from_user(&op, arg, sizeof(op))) | ||
442 | return -EFAULT; | ||
443 | |||
444 | if (!op.handle || !op.addr || op.op < NVMAP_CACHE_OP_WB || | ||
445 | op.op > NVMAP_CACHE_OP_WB_INV) | ||
446 | return -EINVAL; | ||
447 | |||
448 | down_read(¤t->mm->mmap_sem); | ||
449 | |||
450 | vma = find_vma(current->active_mm, (unsigned long)op.addr); | ||
451 | if (!vma || !is_nvmap_vma(vma) || | ||
452 | (unsigned long)op.addr + op.len > vma->vm_end) { | ||
453 | err = -EADDRNOTAVAIL; | ||
454 | goto out; | ||
455 | } | ||
456 | |||
457 | vpriv = (struct nvmap_vma_priv *)vma->vm_private_data; | ||
458 | |||
459 | if ((unsigned long)vpriv->handle != op.handle) { | ||
460 | err = -EFAULT; | ||
461 | goto out; | ||
462 | } | ||
463 | |||
464 | start = (unsigned long)op.addr - vma->vm_start; | ||
465 | end = start + op.len; | ||
466 | |||
467 | err = cache_maint(client, vpriv->handle, start, end, op.op); | ||
468 | out: | ||
469 | up_read(¤t->mm->mmap_sem); | ||
470 | return err; | ||
471 | } | ||
472 | |||
473 | int nvmap_ioctl_free(struct file *filp, unsigned long arg) | ||
474 | { | ||
475 | struct nvmap_client *client = filp->private_data; | ||
476 | |||
477 | if (!arg) | ||
478 | return 0; | ||
479 | |||
480 | nvmap_free_handle_id(client, arg); | ||
481 | return 0; | ||
482 | } | ||
483 | |||
484 | static void inner_cache_maint(unsigned int op, void *vaddr, size_t size) | ||
485 | { | ||
486 | if (op == NVMAP_CACHE_OP_WB_INV) | ||
487 | dmac_flush_range(vaddr, vaddr + size); | ||
488 | else if (op == NVMAP_CACHE_OP_INV) | ||
489 | dmac_map_area(vaddr, size, DMA_FROM_DEVICE); | ||
490 | else | ||
491 | dmac_map_area(vaddr, size, DMA_TO_DEVICE); | ||
492 | } | ||
493 | |||
494 | static void outer_cache_maint(unsigned int op, unsigned long paddr, size_t size) | ||
495 | { | ||
496 | if (op == NVMAP_CACHE_OP_WB_INV) | ||
497 | outer_flush_range(paddr, paddr + size); | ||
498 | else if (op == NVMAP_CACHE_OP_INV) | ||
499 | outer_inv_range(paddr, paddr + size); | ||
500 | else | ||
501 | outer_clean_range(paddr, paddr + size); | ||
502 | } | ||
503 | |||
504 | static void heap_page_cache_maint(struct nvmap_client *client, | ||
505 | struct nvmap_handle *h, unsigned long start, unsigned long end, | ||
506 | unsigned int op, bool inner, bool outer, pte_t **pte, | ||
507 | unsigned long kaddr, pgprot_t prot) | ||
508 | { | ||
509 | struct page *page; | ||
510 | unsigned long paddr; | ||
511 | unsigned long next; | ||
512 | unsigned long off; | ||
513 | size_t size; | ||
514 | |||
515 | while (start < end) { | ||
516 | page = h->pgalloc.pages[start >> PAGE_SHIFT]; | ||
517 | next = min(((start + PAGE_SIZE) & PAGE_MASK), end); | ||
518 | off = start & ~PAGE_MASK; | ||
519 | size = next - start; | ||
520 | paddr = page_to_phys(page) + off; | ||
521 | |||
522 | if (inner) { | ||
523 | void *vaddr = (void *)kaddr + off; | ||
524 | BUG_ON(!pte); | ||
525 | BUG_ON(!kaddr); | ||
526 | set_pte_at(&init_mm, kaddr, *pte, | ||
527 | pfn_pte(__phys_to_pfn(paddr), prot)); | ||
528 | flush_tlb_kernel_page(kaddr); | ||
529 | inner_cache_maint(op, vaddr, size); | ||
530 | } | ||
531 | |||
532 | if (outer) | ||
533 | outer_cache_maint(op, paddr, size); | ||
534 | start = next; | ||
535 | } | ||
536 | } | ||
537 | |||
538 | static bool fast_cache_maint(struct nvmap_client *client, struct nvmap_handle *h, | ||
539 | unsigned long start, unsigned long end, unsigned int op) | ||
540 | { | ||
541 | int ret = false; | ||
542 | |||
543 | if ((op == NVMAP_CACHE_OP_INV) || | ||
544 | ((end - start) < FLUSH_CLEAN_BY_SET_WAY_THRESHOLD)) | ||
545 | goto out; | ||
546 | |||
547 | if (op == NVMAP_CACHE_OP_WB_INV) | ||
548 | inner_flush_cache_all(); | ||
549 | else if (op == NVMAP_CACHE_OP_WB) | ||
550 | inner_clean_cache_all(); | ||
551 | |||
552 | if (h->heap_pgalloc && (h->flags != NVMAP_HANDLE_INNER_CACHEABLE)) { | ||
553 | heap_page_cache_maint(client, h, start, end, op, | ||
554 | false, true, NULL, 0, 0); | ||
555 | } else if (h->flags != NVMAP_HANDLE_INNER_CACHEABLE) { | ||
556 | start += h->carveout->base; | ||
557 | end += h->carveout->base; | ||
558 | outer_cache_maint(op, start, end - start); | ||
559 | } | ||
560 | ret = true; | ||
561 | out: | ||
562 | return ret; | ||
563 | } | ||
564 | |||
565 | static int cache_maint(struct nvmap_client *client, struct nvmap_handle *h, | ||
566 | unsigned long start, unsigned long end, unsigned int op) | ||
567 | { | ||
568 | pgprot_t prot; | ||
569 | pte_t **pte = NULL; | ||
570 | unsigned long kaddr; | ||
571 | unsigned long loop; | ||
572 | int err = 0; | ||
573 | |||
574 | h = nvmap_handle_get(h); | ||
575 | if (!h) | ||
576 | return -EFAULT; | ||
577 | |||
578 | if (!h->alloc) { | ||
579 | err = -EFAULT; | ||
580 | goto out; | ||
581 | } | ||
582 | |||
583 | wmb(); | ||
584 | if (h->flags == NVMAP_HANDLE_UNCACHEABLE || | ||
585 | h->flags == NVMAP_HANDLE_WRITE_COMBINE || start == end) | ||
586 | goto out; | ||
587 | |||
588 | if (fast_cache_maint(client, h, start, end, op)) | ||
589 | goto out; | ||
590 | |||
591 | prot = nvmap_pgprot(h, pgprot_kernel); | ||
592 | pte = nvmap_alloc_pte(client->dev, (void **)&kaddr); | ||
593 | if (IS_ERR(pte)) { | ||
594 | err = PTR_ERR(pte); | ||
595 | pte = NULL; | ||
596 | goto out; | ||
597 | } | ||
598 | |||
599 | if (h->heap_pgalloc) { | ||
600 | heap_page_cache_maint(client, h, start, end, op, true, | ||
601 | (h->flags == NVMAP_HANDLE_INNER_CACHEABLE) ? false : true, | ||
602 | pte, kaddr, prot); | ||
603 | goto out; | ||
604 | } | ||
605 | |||
606 | if (start > h->size || end > h->size) { | ||
607 | nvmap_warn(client, "cache maintenance outside handle\n"); | ||
608 | return -EINVAL; | ||
609 | } | ||
610 | |||
611 | /* lock carveout from relocation by mapcount */ | ||
612 | nvmap_usecount_inc(h); | ||
613 | |||
614 | start += h->carveout->base; | ||
615 | end += h->carveout->base; | ||
616 | |||
617 | loop = start; | ||
618 | |||
619 | while (loop < end) { | ||
620 | unsigned long next = (loop + PAGE_SIZE) & PAGE_MASK; | ||
621 | void *base = (void *)kaddr + (loop & ~PAGE_MASK); | ||
622 | next = min(next, end); | ||
623 | |||
624 | set_pte_at(&init_mm, kaddr, *pte, | ||
625 | pfn_pte(__phys_to_pfn(loop), prot)); | ||
626 | flush_tlb_kernel_page(kaddr); | ||
627 | |||
628 | inner_cache_maint(op, base, next - loop); | ||
629 | loop = next; | ||
630 | } | ||
631 | |||
632 | if (h->flags != NVMAP_HANDLE_INNER_CACHEABLE) | ||
633 | outer_cache_maint(op, start, end - start); | ||
634 | |||
635 | /* unlock carveout */ | ||
636 | nvmap_usecount_dec(h); | ||
637 | |||
638 | out: | ||
639 | if (pte) | ||
640 | nvmap_free_pte(client->dev, pte); | ||
641 | nvmap_handle_put(h); | ||
642 | return err; | ||
643 | } | ||
644 | |||
645 | static int rw_handle_page(struct nvmap_handle *h, int is_read, | ||
646 | phys_addr_t start, unsigned long rw_addr, | ||
647 | unsigned long bytes, unsigned long kaddr, pte_t *pte) | ||
648 | { | ||
649 | pgprot_t prot = nvmap_pgprot(h, pgprot_kernel); | ||
650 | unsigned long end = start + bytes; | ||
651 | int err = 0; | ||
652 | |||
653 | while (!err && start < end) { | ||
654 | struct page *page = NULL; | ||
655 | phys_addr_t phys; | ||
656 | size_t count; | ||
657 | void *src; | ||
658 | |||
659 | if (!h->heap_pgalloc) { | ||
660 | phys = h->carveout->base + start; | ||
661 | } else { | ||
662 | page = h->pgalloc.pages[start >> PAGE_SHIFT]; | ||
663 | BUG_ON(!page); | ||
664 | get_page(page); | ||
665 | phys = page_to_phys(page) + (start & ~PAGE_MASK); | ||
666 | } | ||
667 | |||
668 | set_pte_at(&init_mm, kaddr, pte, | ||
669 | pfn_pte(__phys_to_pfn(phys), prot)); | ||
670 | flush_tlb_kernel_page(kaddr); | ||
671 | |||
672 | src = (void *)kaddr + (phys & ~PAGE_MASK); | ||
673 | phys = PAGE_SIZE - (phys & ~PAGE_MASK); | ||
674 | count = min_t(size_t, end - start, phys); | ||
675 | |||
676 | if (is_read) | ||
677 | err = copy_to_user((void *)rw_addr, src, count); | ||
678 | else | ||
679 | err = copy_from_user(src, (void *)rw_addr, count); | ||
680 | |||
681 | if (err) | ||
682 | err = -EFAULT; | ||
683 | |||
684 | rw_addr += count; | ||
685 | start += count; | ||
686 | |||
687 | if (page) | ||
688 | put_page(page); | ||
689 | } | ||
690 | |||
691 | return err; | ||
692 | } | ||
693 | |||
694 | static ssize_t rw_handle(struct nvmap_client *client, struct nvmap_handle *h, | ||
695 | int is_read, unsigned long h_offs, | ||
696 | unsigned long sys_addr, unsigned long h_stride, | ||
697 | unsigned long sys_stride, unsigned long elem_size, | ||
698 | unsigned long count) | ||
699 | { | ||
700 | ssize_t copied = 0; | ||
701 | pte_t **pte; | ||
702 | void *addr; | ||
703 | int ret = 0; | ||
704 | |||
705 | if (!elem_size) | ||
706 | return -EINVAL; | ||
707 | |||
708 | if (!h->alloc) | ||
709 | return -EFAULT; | ||
710 | |||
711 | if (elem_size == h_stride && elem_size == sys_stride) { | ||
712 | elem_size *= count; | ||
713 | h_stride = elem_size; | ||
714 | sys_stride = elem_size; | ||
715 | count = 1; | ||
716 | } | ||
717 | |||
718 | pte = nvmap_alloc_pte(client->dev, &addr); | ||
719 | if (IS_ERR(pte)) | ||
720 | return PTR_ERR(pte); | ||
721 | |||
722 | while (count--) { | ||
723 | if (h_offs + elem_size > h->size) { | ||
724 | nvmap_warn(client, "read/write outside of handle\n"); | ||
725 | ret = -EFAULT; | ||
726 | break; | ||
727 | } | ||
728 | if (is_read) | ||
729 | cache_maint(client, h, h_offs, | ||
730 | h_offs + elem_size, NVMAP_CACHE_OP_INV); | ||
731 | |||
732 | ret = rw_handle_page(h, is_read, h_offs, sys_addr, | ||
733 | elem_size, (unsigned long)addr, *pte); | ||
734 | |||
735 | if (ret) | ||
736 | break; | ||
737 | |||
738 | if (!is_read) | ||
739 | cache_maint(client, h, h_offs, | ||
740 | h_offs + elem_size, NVMAP_CACHE_OP_WB); | ||
741 | |||
742 | copied += elem_size; | ||
743 | sys_addr += sys_stride; | ||
744 | h_offs += h_stride; | ||
745 | } | ||
746 | |||
747 | nvmap_free_pte(client->dev, pte); | ||
748 | return ret ?: copied; | ||
749 | } | ||
diff --git a/drivers/video/tegra/nvmap/nvmap_ioctl.h b/drivers/video/tegra/nvmap/nvmap_ioctl.h new file mode 100644 index 00000000000..c802cd4dd7a --- /dev/null +++ b/drivers/video/tegra/nvmap/nvmap_ioctl.h | |||
@@ -0,0 +1,159 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/nvmap/nvmap_ioctl.h | ||
3 | * | ||
4 | * ioctl declarations for nvmap | ||
5 | * | ||
6 | * Copyright (c) 2010, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | #ifndef __VIDEO_TEGRA_NVMAP_IOCTL_H | ||
24 | #define __VIDEO_TEGRA_NVMAP_IOCTL_H | ||
25 | |||
26 | #include <linux/ioctl.h> | ||
27 | #include <linux/file.h> | ||
28 | |||
29 | #include <mach/nvmap.h> | ||
30 | |||
31 | enum { | ||
32 | NVMAP_HANDLE_PARAM_SIZE = 1, | ||
33 | NVMAP_HANDLE_PARAM_ALIGNMENT, | ||
34 | NVMAP_HANDLE_PARAM_BASE, | ||
35 | NVMAP_HANDLE_PARAM_HEAP, | ||
36 | }; | ||
37 | |||
38 | enum { | ||
39 | NVMAP_CACHE_OP_WB = 0, | ||
40 | NVMAP_CACHE_OP_INV, | ||
41 | NVMAP_CACHE_OP_WB_INV, | ||
42 | }; | ||
43 | |||
44 | |||
45 | struct nvmap_create_handle { | ||
46 | union { | ||
47 | __u32 key; /* ClaimPreservedHandle */ | ||
48 | __u32 id; /* FromId */ | ||
49 | __u32 size; /* CreateHandle */ | ||
50 | }; | ||
51 | __u32 handle; | ||
52 | }; | ||
53 | |||
54 | struct nvmap_alloc_handle { | ||
55 | __u32 handle; | ||
56 | __u32 heap_mask; | ||
57 | __u32 flags; | ||
58 | __u32 align; | ||
59 | }; | ||
60 | |||
61 | struct nvmap_map_caller { | ||
62 | __u32 handle; /* hmem */ | ||
63 | __u32 offset; /* offset into hmem; should be page-aligned */ | ||
64 | __u32 length; /* number of bytes to map */ | ||
65 | __u32 flags; | ||
66 | unsigned long addr; /* user pointer */ | ||
67 | }; | ||
68 | |||
69 | struct nvmap_rw_handle { | ||
70 | unsigned long addr; /* user pointer */ | ||
71 | __u32 handle; /* hmem */ | ||
72 | __u32 offset; /* offset into hmem */ | ||
73 | __u32 elem_size; /* individual atom size */ | ||
74 | __u32 hmem_stride; /* delta in bytes between atoms in hmem */ | ||
75 | __u32 user_stride; /* delta in bytes between atoms in user */ | ||
76 | __u32 count; /* number of atoms to copy */ | ||
77 | }; | ||
78 | |||
79 | struct nvmap_pin_handle { | ||
80 | unsigned long handles; /* array of handles to pin/unpin */ | ||
81 | unsigned long addr; /* array of addresses to return */ | ||
82 | __u32 count; /* number of entries in handles */ | ||
83 | }; | ||
84 | |||
85 | struct nvmap_handle_param { | ||
86 | __u32 handle; | ||
87 | __u32 param; | ||
88 | unsigned long result; | ||
89 | }; | ||
90 | |||
91 | struct nvmap_cache_op { | ||
92 | unsigned long addr; | ||
93 | __u32 handle; | ||
94 | __u32 len; | ||
95 | __s32 op; | ||
96 | }; | ||
97 | |||
98 | #define NVMAP_IOC_MAGIC 'N' | ||
99 | |||
100 | /* Creates a new memory handle. On input, the argument is the size of the new | ||
101 | * handle; on return, the argument is the name of the new handle | ||
102 | */ | ||
103 | #define NVMAP_IOC_CREATE _IOWR(NVMAP_IOC_MAGIC, 0, struct nvmap_create_handle) | ||
104 | #define NVMAP_IOC_CLAIM _IOWR(NVMAP_IOC_MAGIC, 1, struct nvmap_create_handle) | ||
105 | #define NVMAP_IOC_FROM_ID _IOWR(NVMAP_IOC_MAGIC, 2, struct nvmap_create_handle) | ||
106 | |||
107 | /* Actually allocates memory for the specified handle */ | ||
108 | #define NVMAP_IOC_ALLOC _IOW(NVMAP_IOC_MAGIC, 3, struct nvmap_alloc_handle) | ||
109 | |||
110 | /* Frees a memory handle, unpinning any pinned pages and unmapping any mappings | ||
111 | */ | ||
112 | #define NVMAP_IOC_FREE _IO(NVMAP_IOC_MAGIC, 4) | ||
113 | |||
114 | /* Maps the region of the specified handle into a user-provided virtual address | ||
115 | * that was previously created via an mmap syscall on this fd */ | ||
116 | #define NVMAP_IOC_MMAP _IOWR(NVMAP_IOC_MAGIC, 5, struct nvmap_map_caller) | ||
117 | |||
118 | /* Reads/writes data (possibly strided) from a user-provided buffer into the | ||
119 | * hmem at the specified offset */ | ||
120 | #define NVMAP_IOC_WRITE _IOW(NVMAP_IOC_MAGIC, 6, struct nvmap_rw_handle) | ||
121 | #define NVMAP_IOC_READ _IOW(NVMAP_IOC_MAGIC, 7, struct nvmap_rw_handle) | ||
122 | |||
123 | #define NVMAP_IOC_PARAM _IOWR(NVMAP_IOC_MAGIC, 8, struct nvmap_handle_param) | ||
124 | |||
125 | /* Pins a list of memory handles into IO-addressable memory (either IOVMM | ||
126 | * space or physical memory, depending on the allocation), and returns the | ||
127 | * address. Handles may be pinned recursively. */ | ||
128 | #define NVMAP_IOC_PIN_MULT _IOWR(NVMAP_IOC_MAGIC, 10, struct nvmap_pin_handle) | ||
129 | #define NVMAP_IOC_UNPIN_MULT _IOW(NVMAP_IOC_MAGIC, 11, struct nvmap_pin_handle) | ||
130 | |||
131 | #define NVMAP_IOC_CACHE _IOW(NVMAP_IOC_MAGIC, 12, struct nvmap_cache_op) | ||
132 | |||
133 | /* Returns a global ID usable to allow a remote process to create a handle | ||
134 | * reference to the same handle */ | ||
135 | #define NVMAP_IOC_GET_ID _IOWR(NVMAP_IOC_MAGIC, 13, struct nvmap_create_handle) | ||
136 | |||
137 | #define NVMAP_IOC_MAXNR (_IOC_NR(NVMAP_IOC_GET_ID)) | ||
138 | |||
139 | int nvmap_ioctl_pinop(struct file *filp, bool is_pin, void __user *arg); | ||
140 | |||
141 | int nvmap_ioctl_get_param(struct file *filp, void __user* arg); | ||
142 | |||
143 | int nvmap_ioctl_getid(struct file *filp, void __user *arg); | ||
144 | |||
145 | int nvmap_ioctl_alloc(struct file *filp, void __user *arg); | ||
146 | |||
147 | int nvmap_ioctl_free(struct file *filp, unsigned long arg); | ||
148 | |||
149 | int nvmap_ioctl_create(struct file *filp, unsigned int cmd, void __user *arg); | ||
150 | |||
151 | int nvmap_map_into_caller_ptr(struct file *filp, void __user *arg); | ||
152 | |||
153 | int nvmap_ioctl_cache_maint(struct file *filp, void __user *arg); | ||
154 | |||
155 | int nvmap_ioctl_rw_handle(struct file *filp, int is_read, void __user* arg); | ||
156 | |||
157 | |||
158 | |||
159 | #endif | ||
diff --git a/drivers/video/tegra/nvmap/nvmap_mru.c b/drivers/video/tegra/nvmap/nvmap_mru.c new file mode 100644 index 00000000000..f54d44923eb --- /dev/null +++ b/drivers/video/tegra/nvmap/nvmap_mru.c | |||
@@ -0,0 +1,187 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/nvmap/nvmap_mru.c | ||
3 | * | ||
4 | * IOVMM virtualization support for nvmap | ||
5 | * | ||
6 | * Copyright (c) 2009-2011, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | #include <linux/list.h> | ||
24 | #include <linux/slab.h> | ||
25 | |||
26 | #include <asm/pgtable.h> | ||
27 | |||
28 | #include <mach/iovmm.h> | ||
29 | |||
30 | #include "nvmap.h" | ||
31 | #include "nvmap_mru.h" | ||
32 | |||
33 | /* if IOVMM reclamation is enabled (CONFIG_NVMAP_RECLAIM_UNPINNED_VM), | ||
34 | * unpinned handles are placed onto a most-recently-used eviction list; | ||
35 | * multiple lists are maintained, segmented by size (sizes were chosen to | ||
36 | * roughly correspond with common sizes for graphics surfaces). | ||
37 | * | ||
38 | * if a handle is located on the MRU list, then the code below may | ||
39 | * steal its IOVMM area at any time to satisfy a pin operation if no | ||
40 | * free IOVMM space is available | ||
41 | */ | ||
42 | |||
43 | static const size_t mru_cutoff[] = { | ||
44 | 262144, 393216, 786432, 1048576, 1572864 | ||
45 | }; | ||
46 | |||
47 | static inline struct list_head *mru_list(struct nvmap_share *share, size_t size) | ||
48 | { | ||
49 | unsigned int i; | ||
50 | |||
51 | BUG_ON(!share->mru_lists); | ||
52 | for (i = 0; i < ARRAY_SIZE(mru_cutoff); i++) | ||
53 | if (size <= mru_cutoff[i]) | ||
54 | break; | ||
55 | |||
56 | return &share->mru_lists[i]; | ||
57 | } | ||
58 | |||
59 | size_t nvmap_mru_vm_size(struct tegra_iovmm_client *iovmm) | ||
60 | { | ||
61 | size_t vm_size = tegra_iovmm_get_vm_size(iovmm); | ||
62 | return (vm_size >> 2) * 3; | ||
63 | } | ||
64 | |||
65 | /* nvmap_mru_vma_lock should be acquired by the caller before calling this */ | ||
66 | void nvmap_mru_insert_locked(struct nvmap_share *share, struct nvmap_handle *h) | ||
67 | { | ||
68 | size_t len = h->pgalloc.area->iovm_length; | ||
69 | list_add(&h->pgalloc.mru_list, mru_list(share, len)); | ||
70 | } | ||
71 | |||
72 | void nvmap_mru_remove(struct nvmap_share *s, struct nvmap_handle *h) | ||
73 | { | ||
74 | nvmap_mru_lock(s); | ||
75 | if (!list_empty(&h->pgalloc.mru_list)) | ||
76 | list_del(&h->pgalloc.mru_list); | ||
77 | nvmap_mru_unlock(s); | ||
78 | INIT_LIST_HEAD(&h->pgalloc.mru_list); | ||
79 | } | ||
80 | |||
81 | /* returns a tegra_iovmm_area for a handle. if the handle already has | ||
82 | * an iovmm_area allocated, the handle is simply removed from its MRU list | ||
83 | * and the existing iovmm_area is returned. | ||
84 | * | ||
85 | * if no existing allocation exists, try to allocate a new IOVMM area. | ||
86 | * | ||
87 | * if a new area can not be allocated, try to re-use the most-recently-unpinned | ||
88 | * handle's allocation. | ||
89 | * | ||
90 | * and if that fails, iteratively evict handles from the MRU lists and free | ||
91 | * their allocations, until the new allocation succeeds. | ||
92 | */ | ||
93 | struct tegra_iovmm_area *nvmap_handle_iovmm_locked(struct nvmap_client *c, | ||
94 | struct nvmap_handle *h) | ||
95 | { | ||
96 | struct list_head *mru; | ||
97 | struct nvmap_handle *evict = NULL; | ||
98 | struct tegra_iovmm_area *vm = NULL; | ||
99 | unsigned int i, idx; | ||
100 | pgprot_t prot; | ||
101 | |||
102 | BUG_ON(!h || !c || !c->share); | ||
103 | |||
104 | prot = nvmap_pgprot(h, pgprot_kernel); | ||
105 | |||
106 | if (h->pgalloc.area) { | ||
107 | BUG_ON(list_empty(&h->pgalloc.mru_list)); | ||
108 | list_del(&h->pgalloc.mru_list); | ||
109 | INIT_LIST_HEAD(&h->pgalloc.mru_list); | ||
110 | return h->pgalloc.area; | ||
111 | } | ||
112 | |||
113 | vm = tegra_iovmm_create_vm(c->share->iovmm, NULL, | ||
114 | h->size, h->align, prot, | ||
115 | h->pgalloc.iovm_addr); | ||
116 | |||
117 | if (vm) { | ||
118 | INIT_LIST_HEAD(&h->pgalloc.mru_list); | ||
119 | return vm; | ||
120 | } | ||
121 | /* if client is looking for specific iovm address, return from here. */ | ||
122 | if ((vm == NULL) && (h->pgalloc.iovm_addr != 0)) | ||
123 | return NULL; | ||
124 | /* attempt to re-use the most recently unpinned IOVMM area in the | ||
125 | * same size bin as the current handle. If that fails, iteratively | ||
126 | * evict handles (starting from the current bin) until an allocation | ||
127 | * succeeds or no more areas can be evicted */ | ||
128 | mru = mru_list(c->share, h->size); | ||
129 | if (!list_empty(mru)) | ||
130 | evict = list_first_entry(mru, struct nvmap_handle, | ||
131 | pgalloc.mru_list); | ||
132 | |||
133 | if (evict && evict->pgalloc.area->iovm_length >= h->size) { | ||
134 | list_del(&evict->pgalloc.mru_list); | ||
135 | vm = evict->pgalloc.area; | ||
136 | evict->pgalloc.area = NULL; | ||
137 | INIT_LIST_HEAD(&evict->pgalloc.mru_list); | ||
138 | return vm; | ||
139 | } | ||
140 | |||
141 | idx = mru - c->share->mru_lists; | ||
142 | |||
143 | for (i = 0; i < c->share->nr_mru && !vm; i++, idx++) { | ||
144 | if (idx >= c->share->nr_mru) | ||
145 | idx = 0; | ||
146 | mru = &c->share->mru_lists[idx]; | ||
147 | while (!list_empty(mru) && !vm) { | ||
148 | evict = list_first_entry(mru, struct nvmap_handle, | ||
149 | pgalloc.mru_list); | ||
150 | |||
151 | BUG_ON(atomic_read(&evict->pin) != 0); | ||
152 | BUG_ON(!evict->pgalloc.area); | ||
153 | list_del(&evict->pgalloc.mru_list); | ||
154 | INIT_LIST_HEAD(&evict->pgalloc.mru_list); | ||
155 | tegra_iovmm_free_vm(evict->pgalloc.area); | ||
156 | evict->pgalloc.area = NULL; | ||
157 | vm = tegra_iovmm_create_vm(c->share->iovmm, | ||
158 | NULL, h->size, h->align, | ||
159 | prot, h->pgalloc.iovm_addr); | ||
160 | } | ||
161 | } | ||
162 | return vm; | ||
163 | } | ||
164 | |||
165 | int nvmap_mru_init(struct nvmap_share *share) | ||
166 | { | ||
167 | int i; | ||
168 | mutex_init(&share->mru_lock); | ||
169 | share->nr_mru = ARRAY_SIZE(mru_cutoff) + 1; | ||
170 | |||
171 | share->mru_lists = kzalloc(sizeof(struct list_head) * share->nr_mru, | ||
172 | GFP_KERNEL); | ||
173 | |||
174 | if (!share->mru_lists) | ||
175 | return -ENOMEM; | ||
176 | |||
177 | for (i = 0; i < share->nr_mru; i++) | ||
178 | INIT_LIST_HEAD(&share->mru_lists[i]); | ||
179 | |||
180 | return 0; | ||
181 | } | ||
182 | |||
183 | void nvmap_mru_destroy(struct nvmap_share *share) | ||
184 | { | ||
185 | kfree(share->mru_lists); | ||
186 | share->mru_lists = NULL; | ||
187 | } | ||
diff --git a/drivers/video/tegra/nvmap/nvmap_mru.h b/drivers/video/tegra/nvmap/nvmap_mru.h new file mode 100644 index 00000000000..6c94630bc3e --- /dev/null +++ b/drivers/video/tegra/nvmap/nvmap_mru.h | |||
@@ -0,0 +1,84 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/nvmap_mru.c | ||
3 | * | ||
4 | * IOVMM virtualization support for nvmap | ||
5 | * | ||
6 | * Copyright (c) 2009-2010, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
16 | * more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License along | ||
19 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
21 | */ | ||
22 | |||
23 | |||
24 | #ifndef __VIDEO_TEGRA_NVMAP_MRU_H | ||
25 | #define __VIDEO_TEGRA_NVMAP_MRU_H | ||
26 | |||
27 | #include <linux/spinlock.h> | ||
28 | |||
29 | #include "nvmap.h" | ||
30 | |||
31 | struct tegra_iovmm_area; | ||
32 | struct tegra_iovmm_client; | ||
33 | |||
34 | #ifdef CONFIG_NVMAP_RECLAIM_UNPINNED_VM | ||
35 | |||
36 | static inline void nvmap_mru_lock(struct nvmap_share *share) | ||
37 | { | ||
38 | mutex_lock(&share->mru_lock); | ||
39 | } | ||
40 | |||
41 | static inline void nvmap_mru_unlock(struct nvmap_share *share) | ||
42 | { | ||
43 | mutex_unlock(&share->mru_lock); | ||
44 | } | ||
45 | |||
46 | int nvmap_mru_init(struct nvmap_share *share); | ||
47 | |||
48 | void nvmap_mru_destroy(struct nvmap_share *share); | ||
49 | |||
50 | size_t nvmap_mru_vm_size(struct tegra_iovmm_client *iovmm); | ||
51 | |||
52 | void nvmap_mru_insert_locked(struct nvmap_share *share, struct nvmap_handle *h); | ||
53 | |||
54 | void nvmap_mru_remove(struct nvmap_share *s, struct nvmap_handle *h); | ||
55 | |||
56 | struct tegra_iovmm_area *nvmap_handle_iovmm_locked(struct nvmap_client *c, | ||
57 | struct nvmap_handle *h); | ||
58 | |||
59 | #else | ||
60 | |||
61 | #define nvmap_mru_lock(_s) do { } while (0) | ||
62 | #define nvmap_mru_unlock(_s) do { } while (0) | ||
63 | #define nvmap_mru_init(_s) 0 | ||
64 | #define nvmap_mru_destroy(_s) do { } while (0) | ||
65 | #define nvmap_mru_vm_size(_a) tegra_iovmm_get_vm_size(_a) | ||
66 | |||
67 | static inline void nvmap_mru_insert_locked(struct nvmap_share *share, | ||
68 | struct nvmap_handle *h) | ||
69 | { } | ||
70 | |||
71 | static inline void nvmap_mru_remove(struct nvmap_share *s, | ||
72 | struct nvmap_handle *h) | ||
73 | { } | ||
74 | |||
75 | static inline struct tegra_iovmm_area *nvmap_handle_iovmm_locked(struct nvmap_client *c, | ||
76 | struct nvmap_handle *h) | ||
77 | { | ||
78 | BUG_ON(!h->pgalloc.area); | ||
79 | return h->pgalloc.area; | ||
80 | } | ||
81 | |||
82 | #endif | ||
83 | |||
84 | #endif | ||