1 /*
2 * Copyright © 2018 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24 #include <stdlib.h>
25 #include <inttypes.h>
26
27 #include "util/macros.h"
28 #include "util/u_math.h"
29 #include "util/vma.h"
30
31 struct util_vma_hole {
32 struct list_head link;
33 uint64_t offset;
34 uint64_t size;
35 };
36
37 #define util_vma_foreach_hole(_hole, _heap) \
38 list_for_each_entry(struct util_vma_hole, _hole, &(_heap)->holes, link)
39
40 #define util_vma_foreach_hole_safe(_hole, _heap) \
41 list_for_each_entry_safe(struct util_vma_hole, _hole, &(_heap)->holes, link)
42
43 #define util_vma_foreach_hole_safe_rev(_hole, _heap) \
44 list_for_each_entry_safe_rev(struct util_vma_hole, _hole, &(_heap)->holes, link)
45
46 void
util_vma_heap_init(struct util_vma_heap * heap,uint64_t start,uint64_t size)47 util_vma_heap_init(struct util_vma_heap *heap,
48 uint64_t start, uint64_t size)
49 {
50 list_inithead(&heap->holes);
51 heap->free_size = 0;
52 if (size > 0)
53 util_vma_heap_free(heap, start, size);
54
55 /* Default to using high addresses */
56 heap->alloc_high = true;
57
58 /* Default to not having a nospan alignment */
59 heap->nospan_shift = 0;
60 }
61
62 void
util_vma_heap_finish(struct util_vma_heap * heap)63 util_vma_heap_finish(struct util_vma_heap *heap)
64 {
65 util_vma_foreach_hole_safe(hole, heap)
66 free(hole);
67 }
68
69 #ifndef NDEBUG
70 static void
util_vma_heap_validate(struct util_vma_heap * heap)71 util_vma_heap_validate(struct util_vma_heap *heap)
72 {
73 uint64_t free_size = 0;
74 uint64_t prev_offset = 0;
75 util_vma_foreach_hole(hole, heap) {
76 assert(hole->offset > 0);
77 assert(hole->size > 0);
78
79 free_size += hole->size;
80
81 if (&hole->link == heap->holes.next) {
82 /* This must be the top-most hole. Assert that, if it overflows, it
83 * overflows to 0, i.e. 2^64.
84 */
85 assert(hole->size + hole->offset == 0 ||
86 hole->size + hole->offset > hole->offset);
87 } else {
88 /* This is not the top-most hole so it must not overflow and, in
89 * fact, must be strictly lower than the top-most hole. If
90 * hole->size + hole->offset == prev_offset, then we failed to join
91 * holes during a util_vma_heap_free.
92 */
93 assert(hole->size + hole->offset > hole->offset &&
94 hole->size + hole->offset < prev_offset);
95 }
96 prev_offset = hole->offset;
97 }
98
99 assert(free_size == heap->free_size);
100 }
101 #else
102 #define util_vma_heap_validate(heap)
103 #endif
104
105 static void
util_vma_hole_alloc(struct util_vma_heap * heap,struct util_vma_hole * hole,uint64_t offset,uint64_t size)106 util_vma_hole_alloc(struct util_vma_heap *heap,
107 struct util_vma_hole *hole,
108 uint64_t offset, uint64_t size)
109 {
110 assert(hole->offset <= offset);
111 assert(hole->size >= offset - hole->offset + size);
112
113 if (offset == hole->offset && size == hole->size) {
114 /* Just get rid of the hole. */
115 list_del(&hole->link);
116 free(hole);
117 goto done;
118 }
119
120 assert(offset - hole->offset <= hole->size - size);
121 uint64_t waste = (hole->size - size) - (offset - hole->offset);
122 if (waste == 0) {
123 /* We allocated at the top. Shrink the hole down. */
124 hole->size -= size;
125 goto done;
126 }
127
128 if (offset == hole->offset) {
129 /* We allocated at the bottom. Shrink the hole up. */
130 hole->offset += size;
131 hole->size -= size;
132 goto done;
133 }
134
135 /* We allocated in the middle. We need to split the old hole into two
136 * holes, one high and one low.
137 */
138 struct util_vma_hole *high_hole = calloc(1, sizeof(*hole));
139 high_hole->offset = offset + size;
140 high_hole->size = waste;
141
142 /* Adjust the hole to be the amount of space left at he bottom of the
143 * original hole.
144 */
145 hole->size = offset - hole->offset;
146
147 /* Place the new hole before the old hole so that the list is in order
148 * from high to low.
149 */
150 list_addtail(&high_hole->link, &hole->link);
151
152 done:
153 heap->free_size -= size;
154 }
155
156 uint64_t
util_vma_heap_alloc(struct util_vma_heap * heap,uint64_t size,uint64_t alignment)157 util_vma_heap_alloc(struct util_vma_heap *heap,
158 uint64_t size, uint64_t alignment)
159 {
160 /* The caller is expected to reject zero-size allocations */
161 assert(size > 0);
162 assert(alignment > 0);
163
164 util_vma_heap_validate(heap);
165
166 /* The requested alignment should not be stronger than the block/nospan
167 * alignment.
168 */
169 if (heap->nospan_shift) {
170 assert(ALIGN(BITFIELD64_BIT(heap->nospan_shift), alignment) ==
171 BITFIELD64_BIT(heap->nospan_shift));
172 }
173
174 if (heap->alloc_high) {
175 util_vma_foreach_hole_safe(hole, heap) {
176 if (size > hole->size)
177 continue;
178
179 /* Compute the offset as the highest address where a chunk of the
180 * given size can be without going over the top of the hole.
181 *
182 * This calculation is known to not overflow because we know that
183 * hole->size + hole->offset can only overflow to 0 and size > 0.
184 */
185 uint64_t offset = (hole->size - size) + hole->offset;
186
187 if (heap->nospan_shift) {
188 uint64_t end = offset + size - 1;
189 if ((end >> heap->nospan_shift) != (offset >> heap->nospan_shift)) {
190 /* can we shift the offset down and still fit in the current hole? */
191 end &= ~BITFIELD64_MASK(heap->nospan_shift);
192 assert(end >= size);
193 offset -= size;
194 if (offset < hole->offset)
195 continue;
196 }
197 }
198
199 /* Align the offset. We align down and not up because we are
200 * allocating from the top of the hole and not the bottom.
201 */
202 offset = (offset / alignment) * alignment;
203
204 if (offset < hole->offset)
205 continue;
206
207 util_vma_hole_alloc(heap, hole, offset, size);
208 util_vma_heap_validate(heap);
209 return offset;
210 }
211 } else {
212 util_vma_foreach_hole_safe_rev(hole, heap) {
213 if (size > hole->size)
214 continue;
215
216 uint64_t offset = hole->offset;
217
218 /* Align the offset */
219 uint64_t misalign = offset % alignment;
220 if (misalign) {
221 uint64_t pad = alignment - misalign;
222 if (pad > hole->size - size)
223 continue;
224
225 offset += pad;
226 }
227
228 if (heap->nospan_shift) {
229 uint64_t end = offset + size - 1;
230 if ((end >> heap->nospan_shift) != (offset >> heap->nospan_shift)) {
231 /* can we shift the offset up and still fit in the current hole? */
232 offset = end & ~BITFIELD64_MASK(heap->nospan_shift);
233 if ((offset + size) > (hole->offset + hole->size))
234 continue;
235 }
236 }
237
238 util_vma_hole_alloc(heap, hole, offset, size);
239 util_vma_heap_validate(heap);
240 return offset;
241 }
242 }
243
244 /* Failed to allocate */
245 return 0;
246 }
247
248 bool
util_vma_heap_alloc_addr(struct util_vma_heap * heap,uint64_t offset,uint64_t size)249 util_vma_heap_alloc_addr(struct util_vma_heap *heap,
250 uint64_t offset, uint64_t size)
251 {
252 /* An offset of 0 is reserved for allocation failure. It is not a valid
253 * address and cannot be allocated.
254 */
255 assert(offset > 0);
256
257 /* Allocating something with a size of 0 is also not valid. */
258 assert(size > 0);
259
260 /* It's possible for offset + size to wrap around if we touch the top of
261 * the 64-bit address space, but we cannot go any higher than 2^64.
262 */
263 assert(offset + size == 0 || offset + size > offset);
264
265 /* Find the hole if one exists. */
266 util_vma_foreach_hole_safe(hole, heap) {
267 if (hole->offset > offset)
268 continue;
269
270 /* Holes are ordered high-to-low so the first hole we find with
271 * hole->offset <= is our hole. If it's not big enough to contain the
272 * requested range, then the allocation fails.
273 */
274 assert(hole->offset <= offset);
275 if (hole->size < offset - hole->offset + size)
276 return false;
277
278 util_vma_hole_alloc(heap, hole, offset, size);
279 return true;
280 }
281
282 /* We didn't find a suitable hole */
283 return false;
284 }
285
286 void
util_vma_heap_free(struct util_vma_heap * heap,uint64_t offset,uint64_t size)287 util_vma_heap_free(struct util_vma_heap *heap,
288 uint64_t offset, uint64_t size)
289 {
290 /* An offset of 0 is reserved for allocation failure. It is not a valid
291 * address and cannot be freed.
292 */
293 assert(offset > 0);
294
295 /* Freeing something with a size of 0 is also not valid. */
296 assert(size > 0);
297
298 /* It's possible for offset + size to wrap around if we touch the top of
299 * the 64-bit address space, but we cannot go any higher than 2^64.
300 */
301 assert(offset + size == 0 || offset + size > offset);
302
303 util_vma_heap_validate(heap);
304
305 /* Find immediately higher and lower holes if they exist. */
306 struct util_vma_hole *high_hole = NULL, *low_hole = NULL;
307 util_vma_foreach_hole(hole, heap) {
308 if (hole->offset <= offset) {
309 low_hole = hole;
310 break;
311 }
312 high_hole = hole;
313 }
314
315 if (high_hole)
316 assert(offset + size <= high_hole->offset);
317 bool high_adjacent = high_hole && offset + size == high_hole->offset;
318
319 if (low_hole) {
320 assert(low_hole->offset + low_hole->size > low_hole->offset);
321 assert(low_hole->offset + low_hole->size <= offset);
322 }
323 bool low_adjacent = low_hole && low_hole->offset + low_hole->size == offset;
324
325 if (low_adjacent && high_adjacent) {
326 /* Merge the two holes */
327 low_hole->size += size + high_hole->size;
328 list_del(&high_hole->link);
329 free(high_hole);
330 } else if (low_adjacent) {
331 /* Merge into the low hole */
332 low_hole->size += size;
333 } else if (high_adjacent) {
334 /* Merge into the high hole */
335 high_hole->offset = offset;
336 high_hole->size += size;
337 } else {
338 /* Neither hole is adjacent; make a new one */
339 struct util_vma_hole *hole = calloc(1, sizeof(*hole));
340
341 hole->offset = offset;
342 hole->size = size;
343
344 /* Add it after the high hole so we maintain high-to-low ordering */
345 if (high_hole)
346 list_add(&hole->link, &high_hole->link);
347 else
348 list_add(&hole->link, &heap->holes);
349 }
350
351 heap->free_size += size;
352 util_vma_heap_validate(heap);
353 }
354
355 void
util_vma_heap_print(struct util_vma_heap * heap,FILE * fp,const char * tab,uint64_t total_size)356 util_vma_heap_print(struct util_vma_heap *heap, FILE *fp,
357 const char *tab, uint64_t total_size)
358 {
359 fprintf(fp, "%sutil_vma_heap:\n", tab);
360
361 uint64_t total_free = 0;
362 util_vma_foreach_hole(hole, heap) {
363 fprintf(fp, "%s hole: offset = %"PRIu64" (0x%"PRIx64"), "
364 "size = %"PRIu64" (0x%"PRIx64")\n",
365 tab, hole->offset, hole->offset, hole->size, hole->size);
366 total_free += hole->size;
367 }
368 assert(total_free <= total_size);
369 assert(total_free == heap->free_size);
370 fprintf(fp, "%s%"PRIu64"B (0x%"PRIx64") free (%.2f%% full)\n",
371 tab, total_free, total_free,
372 ((double)(total_size - total_free) / (double)total_size) * 100);
373 }
374