blob: cb0112e23d92f863ae77406c21e385900d97ef38
1 | /* |
2 | * vmm.h |
3 | * |
4 | * memory allocator for VPU |
5 | * |
6 | * Copyright (C) 2006 - 2013 CHIPS&MEDIA INC. |
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 | */ |
19 | |
20 | #ifndef __CNM_VIDEO_MEMORY_MANAGEMENT_H__ |
21 | #define __CNM_VIDEO_MEMORY_MANAGEMENT_H__ |
22 | |
23 | #define VMEM_PAGE_SIZE (16 * 1024) |
24 | #define MAKE_KEY(_a, _b) (((vmem_key_t)_a) << 32 | _b) |
25 | #define KEY_TO_VALUE(_key) (_key >> 32) |
26 | |
27 | #define VMEM_P_ALLOC(_x) vmalloc(_x) |
28 | #define VMEM_P_FREE(_x) vfree(_x) |
29 | |
30 | #define VMEM_ASSERT \ |
31 | pr_info("VMEM_ASSERT at %s:%d\n", __FILE__, __LINE__) |
32 | |
33 | |
34 | #define VMEM_HEIGHT(_tree) (_tree == NULL ? -1 : _tree->height) |
35 | |
36 | #define MAX(_a, _b) (_a >= _b ? _a : _b) |
37 | |
38 | struct avl_node_t; |
39 | #define vmem_key_t unsigned long long |
40 | |
41 | struct vmem_info_t { |
42 | ulong total_pages; |
43 | ulong alloc_pages; |
44 | ulong free_pages; |
45 | ulong page_size; |
46 | }; |
47 | |
48 | struct page_t { |
49 | s32 pageno; |
50 | ulong addr; |
51 | s32 used; |
52 | s32 alloc_pages; |
53 | s32 first_pageno; |
54 | }; |
55 | |
56 | struct avl_node_t { |
57 | vmem_key_t key; |
58 | s32 height; |
59 | struct page_t *page; |
60 | struct avl_node_t *left; |
61 | struct avl_node_t *right; |
62 | }; |
63 | |
64 | struct video_mm_t { |
65 | struct avl_node_t *free_tree; |
66 | struct avl_node_t *alloc_tree; |
67 | struct page_t *page_list; |
68 | s32 num_pages; |
69 | ulong base_addr; |
70 | ulong mem_size; |
71 | s32 free_page_count; |
72 | s32 alloc_page_count; |
73 | }; |
74 | |
75 | enum rotation_dir_t { |
76 | LEFT, |
77 | RIGHT |
78 | }; |
79 | |
80 | struct avl_node_data_t { |
81 | s32 key; |
82 | struct page_t *page; |
83 | }; |
84 | |
85 | static struct avl_node_t *make_avl_node( |
86 | vmem_key_t key, |
87 | struct page_t *page) |
88 | { |
89 | struct avl_node_t *node = |
90 | (struct avl_node_t *)VMEM_P_ALLOC(sizeof(struct avl_node_t)); |
91 | node->key = key; |
92 | node->page = page; |
93 | node->height = 0; |
94 | node->left = NULL; |
95 | node->right = NULL; |
96 | return node; |
97 | } |
98 | |
99 | static s32 get_balance_factor(struct avl_node_t *tree) |
100 | { |
101 | s32 factor = 0; |
102 | if (tree) |
103 | factor = VMEM_HEIGHT(tree->right) - VMEM_HEIGHT(tree->left); |
104 | return factor; |
105 | } |
106 | |
107 | /* |
108 | * Left Rotation |
109 | * |
110 | * A B |
111 | * \ / \ |
112 | * B => A C |
113 | * / \ \ |
114 | * D C D |
115 | * |
116 | */ |
117 | static struct avl_node_t *rotation_left(struct avl_node_t *tree) |
118 | { |
119 | struct avl_node_t *rchild; |
120 | struct avl_node_t *lchild; |
121 | |
122 | if (tree == NULL) |
123 | return NULL; |
124 | |
125 | rchild = tree->right; |
126 | if (rchild == NULL) |
127 | return tree; |
128 | |
129 | lchild = rchild->left; |
130 | rchild->left = tree; |
131 | tree->right = lchild; |
132 | |
133 | tree->height = |
134 | MAX(VMEM_HEIGHT(tree->left), VMEM_HEIGHT(tree->right)) + 1; |
135 | rchild->height = |
136 | MAX(VMEM_HEIGHT(rchild->left), VMEM_HEIGHT(rchild->right)) + 1; |
137 | return rchild; |
138 | } |
139 | |
140 | |
141 | /* |
142 | * Reft Rotation |
143 | * |
144 | * A B |
145 | * \ / \ |
146 | * B => D A |
147 | * / \ / |
148 | * D C C |
149 | * |
150 | */ |
151 | static struct avl_node_t *rotation_right(struct avl_node_t *tree) |
152 | { |
153 | struct avl_node_t *rchild; |
154 | struct avl_node_t *lchild; |
155 | |
156 | if (tree == NULL) |
157 | return NULL; |
158 | |
159 | lchild = tree->left; |
160 | if (lchild == NULL) |
161 | return NULL; |
162 | |
163 | rchild = lchild->right; |
164 | lchild->right = tree; |
165 | tree->left = rchild; |
166 | |
167 | tree->height = |
168 | MAX(VMEM_HEIGHT(tree->left), |
169 | VMEM_HEIGHT(tree->right)) + 1; |
170 | lchild->height = |
171 | MAX(VMEM_HEIGHT(lchild->left), |
172 | VMEM_HEIGHT(lchild->right)) + 1; |
173 | return lchild; |
174 | } |
175 | |
176 | static struct avl_node_t *do_balance(struct avl_node_t *tree) |
177 | { |
178 | s32 bfactor = 0, child_bfactor; |
179 | bfactor = get_balance_factor(tree); |
180 | if (bfactor >= 2) { |
181 | child_bfactor = get_balance_factor(tree->right); |
182 | if (child_bfactor == 1 || child_bfactor == 0) { |
183 | tree = rotation_left(tree); |
184 | } else if (child_bfactor == -1) { |
185 | tree->right = rotation_right(tree->right); |
186 | tree = rotation_left(tree); |
187 | } else { |
188 | pr_info( |
189 | "invalid balancing factor: %d\n", |
190 | child_bfactor); |
191 | VMEM_ASSERT; |
192 | return NULL; |
193 | } |
194 | } else if (bfactor <= -2) { |
195 | child_bfactor = get_balance_factor(tree->left); |
196 | if (child_bfactor == -1 || child_bfactor == 0) { |
197 | tree = rotation_right(tree); |
198 | } else if (child_bfactor == 1) { |
199 | tree->left = rotation_left(tree->left); |
200 | tree = rotation_right(tree); |
201 | } else { |
202 | pr_info( |
203 | "invalid balancing factor: %d\n", |
204 | child_bfactor); |
205 | VMEM_ASSERT; |
206 | return NULL; |
207 | } |
208 | } |
209 | return tree; |
210 | } |
211 | |
212 | static struct avl_node_t *unlink_end_node( |
213 | struct avl_node_t *tree, |
214 | s32 dir, |
215 | struct avl_node_t **found_node) |
216 | { |
217 | struct avl_node_t *node; |
218 | *found_node = NULL; |
219 | |
220 | if (tree == NULL) |
221 | return NULL; |
222 | |
223 | if (dir == LEFT) { |
224 | if (tree->left == NULL) { |
225 | *found_node = tree; |
226 | return NULL; |
227 | } |
228 | } else { |
229 | if (tree->right == NULL) { |
230 | *found_node = tree; |
231 | return NULL; |
232 | } |
233 | } |
234 | |
235 | if (dir == LEFT) { |
236 | node = tree->left; |
237 | tree->left = unlink_end_node(tree->left, LEFT, found_node); |
238 | if (tree->left == NULL) { |
239 | tree->left = (*found_node)->right; |
240 | (*found_node)->left = NULL; |
241 | (*found_node)->right = NULL; |
242 | } |
243 | } else { |
244 | node = tree->right; |
245 | tree->right = unlink_end_node(tree->right, RIGHT, found_node); |
246 | if (tree->right == NULL) { |
247 | tree->right = (*found_node)->left; |
248 | (*found_node)->left = NULL; |
249 | (*found_node)->right = NULL; |
250 | } |
251 | } |
252 | tree->height = |
253 | MAX(VMEM_HEIGHT(tree->left), VMEM_HEIGHT(tree->right)) + 1; |
254 | return do_balance(tree); |
255 | } |
256 | |
257 | |
258 | static struct avl_node_t *avltree_insert( |
259 | struct avl_node_t *tree, |
260 | vmem_key_t key, |
261 | struct page_t *page) |
262 | { |
263 | if (tree == NULL) { |
264 | tree = make_avl_node(key, page); |
265 | } else { |
266 | if (key >= tree->key) |
267 | tree->right = |
268 | avltree_insert(tree->right, key, page); |
269 | else |
270 | tree->left = |
271 | avltree_insert(tree->left, key, page); |
272 | } |
273 | tree = do_balance(tree); |
274 | tree->height = |
275 | MAX(VMEM_HEIGHT(tree->left), VMEM_HEIGHT(tree->right)) + 1; |
276 | return tree; |
277 | } |
278 | |
279 | static struct avl_node_t *do_unlink(struct avl_node_t *tree) |
280 | { |
281 | struct avl_node_t *node; |
282 | struct avl_node_t *end_node; |
283 | node = unlink_end_node(tree->right, LEFT, &end_node); |
284 | if (node) { |
285 | tree->right = node; |
286 | } else { |
287 | node = |
288 | unlink_end_node(tree->left, RIGHT, &end_node); |
289 | if (node) |
290 | tree->left = node; |
291 | } |
292 | |
293 | if (node == NULL) { |
294 | node = tree->right ? tree->right : tree->left; |
295 | end_node = node; |
296 | } |
297 | |
298 | if (end_node) { |
299 | end_node->left = |
300 | (tree->left != end_node) ? |
301 | tree->left : end_node->left; |
302 | end_node->right = |
303 | (tree->right != end_node) ? |
304 | tree->right : end_node->right; |
305 | end_node->height = |
306 | MAX(VMEM_HEIGHT(end_node->left), |
307 | VMEM_HEIGHT(end_node->right)) + 1; |
308 | } |
309 | tree = end_node; |
310 | return tree; |
311 | } |
312 | |
313 | static struct avl_node_t *avltree_remove( |
314 | struct avl_node_t *tree, |
315 | struct avl_node_t **found_node, |
316 | vmem_key_t key) |
317 | { |
318 | *found_node = NULL; |
319 | if (tree == NULL) { |
320 | pr_info("failed to find key %d\n", (s32)key); |
321 | return NULL; |
322 | } |
323 | |
324 | if (key == tree->key) { |
325 | *found_node = tree; |
326 | tree = do_unlink(tree); |
327 | } else if (key > tree->key) { |
328 | tree->right = |
329 | avltree_remove(tree->right, found_node, key); |
330 | } else { |
331 | tree->left = |
332 | avltree_remove(tree->left, found_node, key); |
333 | } |
334 | |
335 | if (tree) |
336 | tree->height = |
337 | MAX(VMEM_HEIGHT(tree->left), |
338 | VMEM_HEIGHT(tree->right)) + 1; |
339 | |
340 | tree = do_balance(tree); |
341 | return tree; |
342 | } |
343 | |
344 | void avltree_free(struct avl_node_t *tree) |
345 | { |
346 | if (tree == NULL) |
347 | return; |
348 | if (tree->left == NULL && tree->right == NULL) { |
349 | VMEM_P_FREE(tree); |
350 | return; |
351 | } |
352 | |
353 | avltree_free(tree->left); |
354 | tree->left = NULL; |
355 | avltree_free(tree->right); |
356 | tree->right = NULL; |
357 | VMEM_P_FREE(tree); |
358 | } |
359 | |
360 | static struct avl_node_t *remove_approx_value( |
361 | struct avl_node_t *tree, |
362 | struct avl_node_t **found, |
363 | vmem_key_t key) |
364 | { |
365 | *found = NULL; |
366 | if (tree == NULL) |
367 | return NULL; |
368 | |
369 | if (key == tree->key) { |
370 | *found = tree; |
371 | tree = do_unlink(tree); |
372 | } else if (key > tree->key) { |
373 | tree->right = remove_approx_value(tree->right, found, key); |
374 | } else { |
375 | tree->left = remove_approx_value(tree->left, found, key); |
376 | if (*found == NULL) { |
377 | *found = tree; |
378 | tree = do_unlink(tree); |
379 | } |
380 | } |
381 | if (tree) |
382 | tree->height = |
383 | MAX(VMEM_HEIGHT(tree->left), |
384 | VMEM_HEIGHT(tree->right)) + 1; |
385 | tree = do_balance(tree); |
386 | return tree; |
387 | } |
388 | |
389 | static void set_blocks_free( |
390 | struct video_mm_t *mm, |
391 | s32 pageno, |
392 | s32 npages) |
393 | { |
394 | s32 last_pageno = pageno + npages - 1; |
395 | s32 i; |
396 | struct page_t *page; |
397 | struct page_t *last_page; |
398 | |
399 | if (npages == 0) |
400 | VMEM_ASSERT; |
401 | |
402 | if (last_pageno >= mm->num_pages) { |
403 | pr_info( |
404 | "set_blocks_free: invalid last page number: %d\n", |
405 | last_pageno); |
406 | VMEM_ASSERT; |
407 | return; |
408 | } |
409 | |
410 | for (i = pageno; i <= last_pageno; i++) { |
411 | mm->page_list[i].used = 0; |
412 | mm->page_list[i].alloc_pages = 0; |
413 | mm->page_list[i].first_pageno = -1; |
414 | } |
415 | |
416 | page = &mm->page_list[pageno]; |
417 | page->alloc_pages = npages; |
418 | last_page = &mm->page_list[last_pageno]; |
419 | last_page->first_pageno = pageno; |
420 | mm->free_tree = |
421 | avltree_insert(mm->free_tree, MAKE_KEY(npages, pageno), page); |
422 | } |
423 | |
424 | static void set_blocks_alloc( |
425 | struct video_mm_t *mm, |
426 | s32 pageno, |
427 | s32 npages) |
428 | { |
429 | s32 last_pageno = pageno + npages - 1; |
430 | s32 i; |
431 | struct page_t *page; |
432 | struct page_t *last_page; |
433 | |
434 | if (last_pageno >= mm->num_pages) { |
435 | pr_info( |
436 | "set_blocks_free: invalid last page number: %d\n", |
437 | last_pageno); |
438 | VMEM_ASSERT; |
439 | return; |
440 | } |
441 | |
442 | for (i = pageno; i <= last_pageno; i++) { |
443 | mm->page_list[i].used = 1; |
444 | mm->page_list[i].alloc_pages = 0; |
445 | mm->page_list[i].first_pageno = -1; |
446 | } |
447 | |
448 | page = &mm->page_list[pageno]; |
449 | page->alloc_pages = npages; |
450 | last_page = &mm->page_list[last_pageno]; |
451 | last_page->first_pageno = pageno; |
452 | mm->alloc_tree = |
453 | avltree_insert(mm->alloc_tree, MAKE_KEY(page->addr, 0), page); |
454 | } |
455 | |
456 | |
457 | s32 vmem_init(struct video_mm_t *mm, ulong addr, ulong size) |
458 | { |
459 | s32 i; |
460 | |
461 | if (NULL == mm) |
462 | return -1; |
463 | |
464 | mm->base_addr = (addr + (VMEM_PAGE_SIZE - 1)) |
465 | & ~(VMEM_PAGE_SIZE - 1); |
466 | mm->mem_size = size & ~VMEM_PAGE_SIZE; |
467 | mm->num_pages = mm->mem_size / VMEM_PAGE_SIZE; |
468 | mm->free_tree = NULL; |
469 | mm->alloc_tree = NULL; |
470 | mm->free_page_count = mm->num_pages; |
471 | mm->alloc_page_count = 0; |
472 | mm->page_list = |
473 | (struct page_t *)VMEM_P_ALLOC( |
474 | mm->num_pages * sizeof(struct page_t)); |
475 | if (mm->page_list == NULL) { |
476 | pr_err("%s:%d failed to kmalloc(%ld)\n", |
477 | __func__, __LINE__, |
478 | mm->num_pages * sizeof(struct page_t)); |
479 | return -1; |
480 | } |
481 | |
482 | for (i = 0; i < mm->num_pages; i++) { |
483 | mm->page_list[i].pageno = i; |
484 | mm->page_list[i].addr = |
485 | mm->base_addr + i * VMEM_PAGE_SIZE; |
486 | mm->page_list[i].alloc_pages = 0; |
487 | mm->page_list[i].used = 0; |
488 | mm->page_list[i].first_pageno = -1; |
489 | } |
490 | set_blocks_free(mm, 0, mm->num_pages); |
491 | return 0; |
492 | } |
493 | |
494 | s32 vmem_exit(struct video_mm_t *mm) |
495 | { |
496 | if (mm == NULL) { |
497 | pr_info("vmem_exit: invalid handle\n"); |
498 | return -1; |
499 | } |
500 | |
501 | if (mm->free_tree) |
502 | avltree_free(mm->free_tree); |
503 | if (mm->alloc_tree) |
504 | avltree_free(mm->alloc_tree); |
505 | |
506 | if (mm->page_list) { |
507 | VMEM_P_FREE(mm->page_list); |
508 | mm->page_list = NULL; |
509 | } |
510 | |
511 | mm->base_addr = 0; |
512 | mm->mem_size = 0; |
513 | mm->num_pages = 0; |
514 | mm->page_list = NULL; |
515 | mm->free_tree = NULL; |
516 | mm->alloc_tree = NULL; |
517 | mm->free_page_count = 0; |
518 | mm->alloc_page_count = 0; |
519 | return 0; |
520 | } |
521 | |
522 | ulong vmem_alloc(struct video_mm_t *mm, s32 size, ulong pid) |
523 | { |
524 | struct avl_node_t *node; |
525 | struct page_t *free_page; |
526 | s32 npages, free_size; |
527 | s32 alloc_pageno; |
528 | ulong ptr; |
529 | |
530 | if (mm == NULL) { |
531 | pr_info("vmem_alloc: invalid handle\n"); |
532 | return -1; |
533 | } |
534 | |
535 | if (size <= 0) |
536 | return -1; |
537 | |
538 | npages = (size + VMEM_PAGE_SIZE - 1) / VMEM_PAGE_SIZE; |
539 | mm->free_tree = remove_approx_value(mm->free_tree, |
540 | &node, MAKE_KEY(npages, 0)); |
541 | |
542 | if (node == NULL) |
543 | return -1; |
544 | |
545 | free_page = node->page; |
546 | free_size = KEY_TO_VALUE(node->key); |
547 | alloc_pageno = free_page->pageno; |
548 | set_blocks_alloc(mm, alloc_pageno, npages); |
549 | if (npages != free_size) { |
550 | s32 free_pageno = alloc_pageno + npages; |
551 | set_blocks_free(mm, free_pageno, (free_size-npages)); |
552 | } |
553 | VMEM_P_FREE(node); |
554 | |
555 | ptr = mm->page_list[alloc_pageno].addr; |
556 | mm->alloc_page_count += npages; |
557 | mm->free_page_count -= npages; |
558 | return ptr; |
559 | } |
560 | |
561 | s32 vmem_free(struct video_mm_t *mm, ulong ptr, ulong pid) |
562 | { |
563 | ulong addr; |
564 | struct avl_node_t *found; |
565 | struct page_t *page; |
566 | s32 pageno, prev_free_pageno, next_free_pageno; |
567 | s32 prev_size, next_size; |
568 | s32 merge_page_no, merge_page_size, free_page_size; |
569 | |
570 | if (mm == NULL) { |
571 | pr_info("vmem_free: invalid handle\n"); |
572 | return -1; |
573 | } |
574 | |
575 | addr = ptr; |
576 | mm->alloc_tree = avltree_remove(mm->alloc_tree, &found, |
577 | MAKE_KEY(addr, 0)); |
578 | |
579 | if (found == NULL) { |
580 | pr_info("vmem_free: 0x%08x not found\n", (s32)addr); |
581 | VMEM_ASSERT; |
582 | return -1; |
583 | } |
584 | |
585 | /* find previous free block */ |
586 | page = found->page; |
587 | pageno = page->pageno; |
588 | free_page_size = page->alloc_pages; |
589 | prev_free_pageno = pageno - 1; |
590 | prev_size = -1; |
591 | if (prev_free_pageno >= 0) { |
592 | if (mm->page_list[prev_free_pageno].used == 0) { |
593 | prev_free_pageno = |
594 | mm->page_list[prev_free_pageno].first_pageno; |
595 | prev_size = |
596 | mm->page_list[prev_free_pageno].alloc_pages; |
597 | } |
598 | } |
599 | |
600 | /* find next free block */ |
601 | next_free_pageno = pageno + page->alloc_pages; |
602 | next_free_pageno = |
603 | (next_free_pageno == mm->num_pages) ? -1 : next_free_pageno; |
604 | next_size = -1; |
605 | if (next_free_pageno >= 0) { |
606 | if (mm->page_list[next_free_pageno].used == 0) { |
607 | next_size = |
608 | mm->page_list[next_free_pageno].alloc_pages; |
609 | } |
610 | } |
611 | VMEM_P_FREE(found); |
612 | |
613 | /* merge */ |
614 | merge_page_no = page->pageno; |
615 | merge_page_size = page->alloc_pages; |
616 | if (prev_size >= 0) { |
617 | mm->free_tree = avltree_remove(mm->free_tree, &found, |
618 | MAKE_KEY(prev_size, prev_free_pageno)); |
619 | if (found == NULL) { |
620 | VMEM_ASSERT; |
621 | return -1; |
622 | } |
623 | merge_page_no = found->page->pageno; |
624 | merge_page_size += found->page->alloc_pages; |
625 | VMEM_P_FREE(found); |
626 | } |
627 | if (next_size >= 0) { |
628 | mm->free_tree = avltree_remove(mm->free_tree, &found, |
629 | MAKE_KEY(next_size, next_free_pageno)); |
630 | if (found == NULL) { |
631 | VMEM_ASSERT; |
632 | return -1; |
633 | } |
634 | merge_page_size += found->page->alloc_pages; |
635 | VMEM_P_FREE(found); |
636 | } |
637 | page->alloc_pages = 0; |
638 | page->first_pageno = -1; |
639 | set_blocks_free(mm, merge_page_no, merge_page_size); |
640 | mm->alloc_page_count -= free_page_size; |
641 | mm->free_page_count += free_page_size; |
642 | return 0; |
643 | } |
644 | |
645 | s32 vmem_get_info(struct video_mm_t *mm, struct vmem_info_t *info) |
646 | { |
647 | if (mm == NULL) { |
648 | pr_info("vmem_get_info: invalid handle\n"); |
649 | return -1; |
650 | } |
651 | |
652 | if (info == NULL) |
653 | return -1; |
654 | |
655 | info->total_pages = mm->num_pages; |
656 | info->alloc_pages = mm->alloc_page_count; |
657 | info->free_pages = mm->free_page_count; |
658 | info->page_size = VMEM_PAGE_SIZE; |
659 | return 0; |
660 | } |
661 | #endif /* __CNM_VIDEO_MEMORY_MANAGEMENT_H__ */ |
662 |