xref: /kvm-unit-tests/lib/alloc_page.c (revision 5aca024ecf2c01430d0993df439374c46c6f2a29)
1 /*
2  * This work is licensed under the terms of the GNU LGPL, version 2.
3  *
4  * This is a simple allocator that provides contiguous physical addresses
5  * with page granularity.
6  */
7 #include "libcflat.h"
8 #include <asm/page.h>
9 #include <asm/io.h>
10 #include <asm/spinlock.h>
11 
12 static struct spinlock lock;
13 static void *freelist = 0;
14 
15 void free_pages(void *mem, unsigned long size)
16 {
17 	void *old_freelist;
18 	void *end;
19 
20 	assert_msg((unsigned long) mem % PAGE_SIZE == 0,
21 		   "mem not page aligned: %p", mem);
22 
23 	assert_msg(size % PAGE_SIZE == 0, "size not page aligned: %#lx", size);
24 
25 	assert_msg(size == 0 || mem + size > mem,
26 		   "mem + size overflow: %p + %#lx", mem, size);
27 
28 	if (size == 0) {
29 		freelist = NULL;
30 		return;
31 	}
32 
33 	spin_lock(&lock);
34 	old_freelist = freelist;
35 	freelist = mem;
36 	end = mem + size;
37 	while (mem + PAGE_SIZE != end) {
38 		*(void **)mem = (mem + PAGE_SIZE);
39 		mem += PAGE_SIZE;
40 	}
41 
42 	*(void **)mem = old_freelist;
43 	spin_unlock(&lock);
44 }
45 
46 void *alloc_page()
47 {
48 	void *p;
49 
50 	if (!freelist)
51 		return 0;
52 
53 	spin_lock(&lock);
54 	p = freelist;
55 	freelist = *(void **)freelist;
56 	spin_unlock(&lock);
57 
58 	return p;
59 }
60 
61 /*
62  * Allocates (1 << order) physically contiguous and naturally aligned pages.
63  * Returns NULL if there's no memory left.
64  */
65 void *alloc_pages(unsigned long order)
66 {
67 	/* Generic list traversal. */
68 	void *prev;
69 	void *curr = NULL;
70 	void *next = freelist;
71 
72 	/* Looking for a run of length (1 << order). */
73 	unsigned long run = 0;
74 	const unsigned long n = 1ul << order;
75 	const unsigned long align_mask = (n << PAGE_SHIFT) - 1;
76 	void *run_start = NULL;
77 	void *run_prev = NULL;
78 	unsigned long run_next_pa = 0;
79 	unsigned long pa;
80 
81 	assert(order < sizeof(unsigned long) * 8);
82 
83 	spin_lock(&lock);
84 	for (;;) {
85 		prev = curr;
86 		curr = next;
87 
88 		if (!curr) {
89 			run_start = NULL;
90 			break;
91 		}
92 
93 		next = *((void **) curr);
94 		pa = virt_to_phys(curr);
95 
96 		if (run == 0) {
97 			if (!(pa & align_mask)) {
98 				run_start = curr;
99 				run_prev = prev;
100 				run_next_pa = pa + PAGE_SIZE;
101 				run = 1;
102 			}
103 		} else if (pa == run_next_pa) {
104 			run_next_pa += PAGE_SIZE;
105 			run += 1;
106 		} else {
107 			run = 0;
108 		}
109 
110 		if (run == n) {
111 			if (run_prev)
112 				*((void **) run_prev) = next;
113 			else
114 				freelist = next;
115 			break;
116 		}
117 	}
118 	spin_unlock(&lock);
119 	return run_start;
120 }
121 
122 
123 void free_page(void *page)
124 {
125 	spin_lock(&lock);
126 	*(void **)page = freelist;
127 	freelist = page;
128 	spin_unlock(&lock);
129 }
130 
131