1 /*
2  * Copyright (c) 2008-2009,2012-2015 Travis Geiselbrecht
3  * Copyright (c) 2009 Corey Tabaka
4  *
5  * Use of this source code is governed by a MIT-style
6  * license that can be found in the LICENSE file or at
7  * https://opensource.org/licenses/MIT
8  */
9 #include <lk/debug.h>
10 #include <lk/trace.h>
11 #include <assert.h>
12 #include <lk/err.h>
13 #include <lk/list.h>
14 #include <rand.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <kernel/mutex.h>
19 #include <lib/miniheap.h>
20 #include <lib/heap.h>
21 #include <lib/page_alloc.h>
22 
23 #define LOCAL_TRACE 0
24 
25 #define DEBUG_HEAP 0
26 #define ALLOC_FILL 0x99
27 #define FREE_FILL 0x77
28 #define PADDING_FILL 0x55
29 #define PADDING_SIZE 64
30 
31 // whether or not the heap will try to trim itself every time a free happens
32 #ifndef MINIHEAP_AUTOTRIM
33 #define MINIHEAP_AUTOTRIM 0
34 #endif
35 
36 #define HEAP_MAGIC (0x48454150)  // 'HEAP'
37 
38 struct free_heap_chunk {
39     struct list_node node;
40     size_t len;
41 };
42 
43 struct heap {
44     void *base;
45     size_t len;
46     size_t remaining;
47     size_t low_watermark;
48     mutex_t lock;
49     struct list_node free_list;
50 };
51 
52 // heap static vars
53 static struct heap theheap;
54 
55 // structure placed at the beginning every allocation
56 struct alloc_struct_begin {
57 #if LK_DEBUGLEVEL > 1
58     unsigned int magic;
59 #endif
60     void *ptr;
61     size_t size;
62 #if DEBUG_HEAP
63     void *padding_start;
64     size_t padding_size;
65 #endif
66 };
67 
68 static ssize_t heap_grow(size_t len);
69 
dump_free_chunk(struct free_heap_chunk * chunk)70 static void dump_free_chunk(struct free_heap_chunk *chunk) {
71     dprintf(INFO, "\t\tbase %p, end 0x%lx, len 0x%zx\n", chunk, (vaddr_t)chunk + chunk->len, chunk->len);
72 }
73 
miniheap_dump(void)74 void miniheap_dump(void) {
75     dprintf(INFO, "Heap dump (using miniheap):\n");
76     dprintf(INFO, "\tbase %p, len 0x%zx\n", theheap.base, theheap.len);
77     dprintf(INFO, "\tfree list:\n");
78 
79     mutex_acquire(&theheap.lock);
80 
81     struct free_heap_chunk *chunk;
82     list_for_every_entry(&theheap.free_list, chunk, struct free_heap_chunk, node) {
83         dump_free_chunk(chunk);
84     }
85     mutex_release(&theheap.lock);
86 
87 }
88 
89 // try to insert this free chunk into the free list, consuming the chunk by merging it with
90 // nearby ones if possible. Returns base of whatever chunk it became in the list.
heap_insert_free_chunk(struct free_heap_chunk * chunk)91 static struct free_heap_chunk *heap_insert_free_chunk(struct free_heap_chunk *chunk) {
92     vaddr_t chunk_end = (vaddr_t)chunk + chunk->len;
93 
94     LTRACEF("chunk ptr %p, size 0x%zx\n", chunk, chunk->len);
95 
96     struct free_heap_chunk *next_chunk;
97     struct free_heap_chunk *last_chunk;
98 
99     mutex_acquire(&theheap.lock);
100 
101     theheap.remaining += chunk->len;
102 
103     // walk through the list, finding the node to insert before
104     list_for_every_entry(&theheap.free_list, next_chunk, struct free_heap_chunk, node) {
105         if (chunk < next_chunk) {
106             DEBUG_ASSERT(chunk_end <= (vaddr_t)next_chunk);
107 
108             list_add_before(&next_chunk->node, &chunk->node);
109 
110             goto try_merge;
111         }
112     }
113 
114     // walked off the end of the list, add it at the tail
115     list_add_tail(&theheap.free_list, &chunk->node);
116 
117     // try to merge with the previous chunk
118 try_merge:
119     last_chunk = list_prev_type(&theheap.free_list, &chunk->node, struct free_heap_chunk, node);
120     if (last_chunk) {
121         if ((vaddr_t)last_chunk + last_chunk->len == (vaddr_t)chunk) {
122             // easy, just extend the previous chunk
123             last_chunk->len += chunk->len;
124 
125             // remove ourself from the list
126             list_delete(&chunk->node);
127 
128             // set the chunk pointer to the newly extended chunk, in case
129             // it needs to merge with the next chunk below
130             chunk = last_chunk;
131         }
132     }
133 
134     // try to merge with the next chunk
135     if (next_chunk) {
136         if ((vaddr_t)chunk + chunk->len == (vaddr_t)next_chunk) {
137             // extend our chunk
138             chunk->len += next_chunk->len;
139 
140             // remove them from the list
141             list_delete(&next_chunk->node);
142         }
143     }
144 
145     mutex_release(&theheap.lock);
146 
147     return chunk;
148 }
149 
heap_create_free_chunk(void * ptr,size_t len,bool allow_debug)150 static struct free_heap_chunk *heap_create_free_chunk(void *ptr, size_t len, bool allow_debug) {
151     DEBUG_ASSERT((len % sizeof(void *)) == 0); // size must be aligned on pointer boundary
152     DEBUG_ASSERT(len > sizeof(struct free_heap_chunk));
153 
154 #if DEBUG_HEAP
155     if (allow_debug)
156         memset(ptr, FREE_FILL, len);
157 #endif
158 
159     struct free_heap_chunk *chunk = (struct free_heap_chunk *)ptr;
160     chunk->len = len;
161 
162     return chunk;
163 }
164 
miniheap_alloc(size_t size,unsigned int alignment)165 void *miniheap_alloc(size_t size, unsigned int alignment) {
166     void *ptr;
167 #if DEBUG_HEAP
168     size_t original_size = size;
169 #endif
170 
171     LTRACEF("size %zd, align %d\n", size, alignment);
172 
173     // alignment must be power of 2
174     if (alignment & (alignment - 1))
175         return NULL;
176 
177     // we always put a size field + base pointer + magic in front of the allocation
178     size += sizeof(struct alloc_struct_begin);
179 #if DEBUG_HEAP
180     size += PADDING_SIZE;
181 #endif
182 
183     // make sure we allocate at least the size of a struct free_heap_chunk so that
184     // when we free it, we can create a struct free_heap_chunk struct and stick it
185     // in the spot
186     if (size < sizeof(struct free_heap_chunk))
187         size = sizeof(struct free_heap_chunk);
188 
189     // round up size to a multiple of native pointer size
190     size = ROUNDUP(size, sizeof(void *));
191 
192     // deal with nonzero alignments
193     if (alignment > 0) {
194         if (alignment < 16)
195             alignment = 16;
196 
197         // add alignment for worst case fit
198         size += alignment;
199     }
200 
201     int retry_count = 0;
202 retry:
203     mutex_acquire(&theheap.lock);
204 
205     // walk through the list
206     ptr = NULL;
207     struct free_heap_chunk *chunk;
208     list_for_every_entry(&theheap.free_list, chunk, struct free_heap_chunk, node) {
209         DEBUG_ASSERT((chunk->len % sizeof(void *)) == 0); // len should always be a multiple of pointer size
210 
211         // is it big enough to service our allocation?
212         if (chunk->len >= size) {
213             ptr = chunk;
214 
215             // remove it from the list
216             struct list_node *next_node = list_next(&theheap.free_list, &chunk->node);
217             list_delete(&chunk->node);
218 
219             if (chunk->len > size + sizeof(struct free_heap_chunk)) {
220                 // there's enough space in this chunk to create a new one after the allocation
221                 struct free_heap_chunk *newchunk = heap_create_free_chunk((uint8_t *)ptr + size, chunk->len - size, true);
222 
223                 // truncate this chunk
224                 chunk->len -= chunk->len - size;
225 
226                 // add the new one where chunk used to be
227                 if (next_node)
228                     list_add_before(next_node, &newchunk->node);
229                 else
230                     list_add_tail(&theheap.free_list, &newchunk->node);
231             }
232 
233             // the allocated size is actually the length of this chunk, not the size requested
234             DEBUG_ASSERT(chunk->len >= size);
235             size = chunk->len;
236 
237 #if DEBUG_HEAP
238             memset(ptr, ALLOC_FILL, size);
239 #endif
240 
241             ptr = (void *)((addr_t)ptr + sizeof(struct alloc_struct_begin));
242 
243             // align the output if requested
244             if (alignment > 0) {
245                 ptr = (void *)ROUNDUP((addr_t)ptr, (addr_t)alignment);
246             }
247 
248             struct alloc_struct_begin *as = (struct alloc_struct_begin *)ptr;
249             as--;
250 #if LK_DEBUGLEVEL > 1
251             as->magic = HEAP_MAGIC;
252 #endif
253             as->ptr = (void *)chunk;
254             as->size = size;
255             theheap.remaining -= size;
256 
257             if (theheap.remaining < theheap.low_watermark) {
258                 theheap.low_watermark = theheap.remaining;
259             }
260 #if DEBUG_HEAP
261             as->padding_start = ((uint8_t *)ptr + original_size);
262             as->padding_size = (((addr_t)chunk + size) - ((addr_t)ptr + original_size));
263 //          printf("padding start %p, size %u, chunk %p, size %u\n", as->padding_start, as->padding_size, chunk, size);
264 
265             memset(as->padding_start, PADDING_FILL, as->padding_size);
266 #endif
267 
268             break;
269         }
270     }
271 
272     mutex_release(&theheap.lock);
273 
274     /* try to grow the heap if we can */
275     if (ptr == NULL && retry_count == 0) {
276         ssize_t err = heap_grow(size);
277         if (err >= 0) {
278             retry_count++;
279             goto retry;
280         }
281     }
282 
283     LTRACEF("returning ptr %p\n", ptr);
284 
285     return ptr;
286 }
287 
miniheap_realloc(void * ptr,size_t size)288 void *miniheap_realloc(void *ptr, size_t size) {
289     /* slow implementation */
290     if (!ptr)
291         return miniheap_alloc(size, 0);
292     if (size == 0) {
293         miniheap_free(ptr);
294         return NULL;
295     }
296 
297     // XXX better implementation
298     void *p = miniheap_alloc(size, 0);
299     if (!p)
300         return NULL;
301 
302     memcpy(p, ptr, size); // XXX wrong
303     miniheap_free(ptr);
304 
305     return p;
306 }
307 
miniheap_free(void * ptr)308 void miniheap_free(void *ptr) {
309     if (!ptr)
310         return;
311 
312     LTRACEF("ptr %p\n", ptr);
313 
314     // check for the old allocation structure
315     struct alloc_struct_begin *as = (struct alloc_struct_begin *)ptr;
316     as--;
317 
318     DEBUG_ASSERT_COND(as->magic == HEAP_MAGIC);
319 
320 #if DEBUG_HEAP
321     {
322         uint i;
323         uint8_t *pad = (uint8_t *)as->padding_start;
324 
325         for (i = 0; i < as->padding_size; i++) {
326             if (pad[i] != PADDING_FILL) {
327                 printf("free at %p scribbled outside the lines:\n", ptr);
328                 hexdump(pad, as->padding_size);
329                 panic("die\n");
330             }
331         }
332     }
333 #endif
334 
335     LTRACEF("allocation was %zd bytes long at ptr %p\n", as->size, as->ptr);
336 
337     // looks good, create a free chunk and add it to the pool
338     heap_insert_free_chunk(heap_create_free_chunk(as->ptr, as->size, true));
339 
340 #if MINIHEAP_AUTOTRIM
341     miniheap_trim();
342 #endif
343 }
344 
miniheap_trim(void)345 void miniheap_trim(void) {
346     LTRACE_ENTRY;
347 
348     mutex_acquire(&theheap.lock);
349 
350     // walk through the list, finding free chunks that can be returned to the page allocator
351     struct free_heap_chunk *chunk;
352     struct free_heap_chunk *next_chunk;
353     list_for_every_entry_safe(&theheap.free_list, chunk, next_chunk, struct free_heap_chunk, node) {
354         LTRACEF("looking at chunk %p, len 0x%zx\n", chunk, chunk->len);
355 
356         uintptr_t start = (uintptr_t)chunk;
357         uintptr_t end = start + chunk->len;
358         DEBUG_ASSERT(end > start); // make sure it doesn't wrap the address space and has a positive len
359 
360         // compute the page aligned region in this free block (if any)
361         uintptr_t start_page = ROUNDUP(start, PAGE_SIZE);
362         uintptr_t end_page = ROUNDDOWN(end, PAGE_SIZE);
363         DEBUG_ASSERT(end_page <= end);
364         DEBUG_ASSERT(start_page >= start);
365 
366         LTRACEF("start page 0x%lx, end page 0x%lx\n", start_page, end_page);
367 
368 retry:
369         // see if the free block encompasses at least one page
370         if (unlikely(end_page > start_page)) {
371             LTRACEF("could trim: start 0x%lx, end 0x%lx\n", start_page, end_page);
372 
373             // cases where the start of the block is already page aligned
374             if (start_page == start) {
375                 // look for special case, we're going to completely remove the chunk
376                 if (end_page == end) {
377                     LTRACEF("special case, free chunk completely covers page(s)\n");
378                     list_delete(&chunk->node);
379                     goto free_chunk;
380                 }
381             } else {
382                 // start of block is not page aligned,
383                 // will there be enough space before the block if we trim?
384                 if (start_page - start < sizeof(struct free_heap_chunk)) {
385                     LTRACEF("not enough space for free chunk before\n");
386                     start_page += PAGE_SIZE;
387                     goto retry;
388                 }
389             }
390 
391             // do we need to split the free block and create a new block afterwards?
392             if (end_page < end) {
393                 size_t new_chunk_size = end - end_page;
394                 LTRACEF("will have to split, new chunk will be 0x%zx bytes long\n", new_chunk_size);
395 
396                 // if there's not enough space afterwards for a free chunk, we can't free the last page
397                 if (new_chunk_size < sizeof(struct free_heap_chunk)) {
398                     LTRACEF("not enough space for free chunk afterwards\n");
399                     end_page -= PAGE_SIZE;
400                     goto retry;
401                 }
402 
403                 // trim the new space off the end of the current chunk
404                 chunk->len -= new_chunk_size;
405                 end = end_page;
406 
407                 // create a new chunk after the one we're trimming
408                 struct free_heap_chunk *new_chunk = heap_create_free_chunk((void *)end_page, new_chunk_size, false);
409 
410                 // link it with the current block
411                 list_add_after(&chunk->node, &new_chunk->node);
412             }
413 
414             // check again to see if we are now completely covering a block
415             if (start_page == start && end_page == end) {
416                 LTRACEF("special case, after splitting off new chunk, free chunk completely covers page(s)\n");
417                 list_delete(&chunk->node);
418                 goto free_chunk;
419             }
420 
421             // trim the size of the block
422             chunk->len -= end_page - start_page;
423 
424 free_chunk:
425             // return it to the allocator
426             LTRACEF("returning %p size 0x%lx to the page allocator\n", (void *)start_page, end_page - start_page);
427             page_free((void *)start_page, (end_page - start_page) / PAGE_SIZE);
428 
429             // tweak accounting
430             theheap.remaining -= end_page - start_page;
431         }
432     }
433 
434     mutex_release(&theheap.lock);
435 }
436 
miniheap_get_stats(struct miniheap_stats * ptr)437 void miniheap_get_stats(struct miniheap_stats *ptr) {
438     struct free_heap_chunk *chunk;
439 
440     ptr->heap_start = theheap.base;
441     ptr->heap_len = theheap.len;
442     ptr->heap_free=0;
443     ptr->heap_max_chunk = 0;
444 
445     mutex_acquire(&theheap.lock);
446 
447     list_for_every_entry(&theheap.free_list, chunk, struct free_heap_chunk, node) {
448         ptr->heap_free += chunk->len;
449 
450         if (chunk->len > ptr->heap_max_chunk) {
451             ptr->heap_max_chunk = chunk->len;
452         }
453     }
454 
455     ptr->heap_low_watermark = theheap.low_watermark;
456 
457     mutex_release(&theheap.lock);
458 }
459 
heap_grow(size_t size)460 static ssize_t heap_grow(size_t size) {
461     size = ROUNDUP(size, PAGE_SIZE);
462     void *ptr = page_alloc(size / PAGE_SIZE, PAGE_ALLOC_ANY_ARENA);
463     if (!ptr) {
464         TRACEF("failed to grow kernel heap by 0x%zx bytes\n", size);
465         return ERR_NO_MEMORY;
466     }
467 
468     LTRACEF("growing heap by 0x%zx bytes, new ptr %p\n", size, ptr);
469 
470     heap_insert_free_chunk(heap_create_free_chunk(ptr, size, true));
471 
472     /* change the heap start and end variables */
473     if ((uintptr_t)ptr < (uintptr_t)theheap.base || theheap.base == 0)
474         theheap.base = ptr;
475 
476     uintptr_t endptr = (uintptr_t)ptr + size;
477     if (endptr > (uintptr_t)theheap.base + theheap.len) {
478         theheap.len = (uintptr_t)endptr - (uintptr_t)theheap.base;
479     }
480 
481     return size;
482 }
483 
miniheap_init(void * ptr,size_t len)484 void miniheap_init(void *ptr, size_t len) {
485     LTRACEF("ptr %p, len %zu\n", ptr, len);
486 
487     // create a mutex
488     mutex_init(&theheap.lock);
489 
490     // initialize the free list
491     list_initialize(&theheap.free_list);
492 
493     // align the buffer to a ptr
494     if (((uintptr_t)ptr % sizeof(uintptr_t)) > 0) {
495         uintptr_t aligned_ptr = ROUNDUP((uintptr_t)ptr, sizeof(uintptr_t));
496 
497         DEBUG_ASSERT((aligned_ptr - (uintptr_t)ptr) < len);
498 
499         len -= aligned_ptr - (uintptr_t)ptr;
500         ptr = (void *)aligned_ptr;
501 
502         LTRACEF("(aligned) ptr %p, len %zu\n", ptr, len);
503     }
504 
505     // set the heap range
506     theheap.base = ptr;
507     theheap.len = len;
508     theheap.remaining = 0; // will get set by heap_insert_free_chunk()
509     theheap.low_watermark = 0;
510 
511     // if passed a default range, use it
512     if (len > 0)
513         heap_insert_free_chunk(heap_create_free_chunk(ptr, len, true));
514 }
515 
516