Upgraded GRUB2 to 2.00 release.
[AROS.git] / arch / all-pc / boot / grub2-aros / grub-core / kern / mm.c
blob9f08e05bf5827b662abcdec34bacbe9297cf6eb8
1 /* mm.c - functions for memory manager */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2002,2005,2007,2008,2009 Free Software Foundation, Inc.
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
21 The design of this memory manager.
23 This is a simple implementation of malloc with a few extensions. These are
24 the extensions:
26 - memalign is implemented efficiently.
28 - multiple regions may be used as free space. They may not be
29 contiguous.
31 Regions are managed by a singly linked list, and the meta information is
32 stored in the beginning of each region. Space after the meta information
33 is used to allocate memory.
35 The memory space is used as cells instead of bytes for simplicity. This
36 is important for some CPUs which may not access multiple bytes at a time
37 when the first byte is not aligned at a certain boundary (typically,
38 4-byte or 8-byte). The size of each cell is equal to the size of struct
39 grub_mm_header, so the header of each allocated/free block fits into one
40 cell precisely. One cell is 16 bytes on 32-bit platforms and 32 bytes
41 on 64-bit platforms.
43 There are two types of blocks: allocated blocks and free blocks.
45 In allocated blocks, the header of each block has only its size. Note that
46 this size is based on cells but not on bytes. The header is located right
47 before the returned pointer, that is, the header resides at the previous
48 cell.
50 Free blocks constitutes a ring, using a singly linked list. The first free
51 block is pointed to by the meta information of a region. The allocator
52 attempts to pick up the second block instead of the first one. This is
53 a typical optimization against defragmentation, and makes the
54 implementation a bit easier.
56 For safety, both allocated blocks and free ones are marked by magic
57 numbers. Whenever anything unexpected is detected, GRUB aborts the
58 operation.
61 #include <config.h>
62 #include <grub/mm.h>
63 #include <grub/misc.h>
64 #include <grub/err.h>
65 #include <grub/types.h>
66 #include <grub/disk.h>
67 #include <grub/dl.h>
68 #include <grub/i18n.h>
69 #include <grub/mm_private.h>
71 #ifdef MM_DEBUG
72 # undef grub_malloc
73 # undef grub_zalloc
74 # undef grub_realloc
75 # undef grub_free
76 # undef grub_memalign
77 #endif
81 grub_mm_region_t grub_mm_base;
83 /* Get a header from the pointer PTR, and set *P and *R to a pointer
84 to the header and a pointer to its region, respectively. PTR must
85 be allocated. */
86 static void
87 get_header_from_pointer (void *ptr, grub_mm_header_t *p, grub_mm_region_t *r)
89 if ((grub_addr_t) ptr & (GRUB_MM_ALIGN - 1))
90 grub_fatal ("unaligned pointer %p", ptr);
92 for (*r = grub_mm_base; *r; *r = (*r)->next)
93 if ((grub_addr_t) ptr > (grub_addr_t) ((*r) + 1)
94 && (grub_addr_t) ptr <= (grub_addr_t) ((*r) + 1) + (*r)->size)
95 break;
97 if (! *r)
98 grub_fatal ("out of range pointer %p", ptr);
100 *p = (grub_mm_header_t) ptr - 1;
101 if ((*p)->magic == GRUB_MM_FREE_MAGIC)
102 grub_fatal ("double free at %p", *p);
103 if ((*p)->magic != GRUB_MM_ALLOC_MAGIC)
104 grub_fatal ("alloc magic is broken at %p: %lx", *p,
105 (unsigned long) (*p)->magic);
108 /* Initialize a region starting from ADDR and whose size is SIZE,
109 to use it as free space. */
110 void
111 grub_mm_init_region (void *addr, grub_size_t size)
113 grub_mm_header_t h;
114 grub_mm_region_t r, *p, q;
116 #if 0
117 grub_printf ("Using memory for heap: start=%p, end=%p\n", addr, addr + (unsigned int) size);
118 #endif
120 /* Allocate a region from the head. */
121 r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);
122 size -= (char *) r - (char *) addr + sizeof (*r);
124 /* If this region is too small, ignore it. */
125 if (size < GRUB_MM_ALIGN)
126 return;
128 h = (grub_mm_header_t) (r + 1);
129 h->next = h;
130 h->magic = GRUB_MM_FREE_MAGIC;
131 h->size = (size >> GRUB_MM_ALIGN_LOG2);
133 r->first = h;
134 r->pre_size = (grub_addr_t) r - (grub_addr_t) addr;
135 r->size = (h->size << GRUB_MM_ALIGN_LOG2);
137 /* Find where to insert this region. Put a smaller one before bigger ones,
138 to prevent fragmentation. */
139 for (p = &grub_mm_base, q = *p; q; p = &(q->next), q = *p)
140 if (q->size > r->size)
141 break;
143 *p = r;
144 r->next = q;
147 /* Allocate the number of units N with the alignment ALIGN from the ring
148 buffer starting from *FIRST. ALIGN must be a power of two. Both N and
149 ALIGN are in units of GRUB_MM_ALIGN. Return a non-NULL if successful,
150 otherwise return NULL. */
151 static void *
152 grub_real_malloc (grub_mm_header_t *first, grub_size_t n, grub_size_t align)
154 grub_mm_header_t p, q;
156 /* When everything is allocated side effect is that *first will have alloc
157 magic marked, meaning that there is no room in this region. */
158 if ((*first)->magic == GRUB_MM_ALLOC_MAGIC)
159 return 0;
161 /* Try to search free slot for allocation in this memory region. */
162 for (q = *first, p = q->next; ; q = p, p = p->next)
164 grub_off_t extra;
166 extra = ((grub_addr_t) (p + 1) >> GRUB_MM_ALIGN_LOG2) % align;
167 if (extra)
168 extra = align - extra;
170 if (! p)
171 grub_fatal ("null in the ring");
173 if (p->magic != GRUB_MM_FREE_MAGIC)
174 grub_fatal ("free magic is broken at %p: 0x%x", p, p->magic);
176 if (p->size >= n + extra)
178 extra += (p->size - extra - n) & (~(align - 1));
179 if (extra == 0 && p->size == n)
181 /* There is no special alignment requirement and memory block
182 is complete match.
184 1. Just mark memory block as allocated and remove it from
185 free list.
187 Result:
188 +---------------+ previous block's next
189 | alloc, size=n | |
190 +---------------+ v
192 q->next = p->next;
194 else if (align == 1 || p->size == n + extra)
196 /* There might be alignment requirement, when taking it into
197 account memory block fits in.
199 1. Allocate new area at end of memory block.
200 2. Reduce size of available blocks from original node.
201 3. Mark new area as allocated and "remove" it from free
202 list.
204 Result:
205 +---------------+
206 | free, size-=n | next --+
207 +---------------+ |
208 | alloc, size=n | |
209 +---------------+ v
212 p->size -= n;
213 p += p->size;
215 else if (extra == 0)
217 grub_mm_header_t r;
219 r = p + extra + n;
220 r->magic = GRUB_MM_FREE_MAGIC;
221 r->size = p->size - extra - n;
222 r->next = p->next;
223 q->next = r;
225 if (q == p)
227 q = r;
228 r->next = r;
231 else
233 /* There is alignment requirement and there is room in memory
234 block. Split memory block to three pieces.
236 1. Create new memory block right after section being
237 allocated. Mark it as free.
238 2. Add new memory block to free chain.
239 3. Mark current memory block having only extra blocks.
240 4. Advance to aligned block and mark that as allocated and
241 "remove" it from free list.
243 Result:
244 +------------------------------+
245 | free, size=extra | next --+
246 +------------------------------+ |
247 | alloc, size=n | |
248 +------------------------------+ |
249 | free, size=orig.size-extra-n | <------+, next --+
250 +------------------------------+ v
252 grub_mm_header_t r;
254 r = p + extra + n;
255 r->magic = GRUB_MM_FREE_MAGIC;
256 r->size = p->size - extra - n;
257 r->next = p;
259 p->size = extra;
260 q->next = r;
261 p += extra;
264 p->magic = GRUB_MM_ALLOC_MAGIC;
265 p->size = n;
267 /* Mark find as a start marker for next allocation to fasten it.
268 This will have side effect of fragmenting memory as small
269 pieces before this will be un-used. */
270 *first = q;
272 return p + 1;
275 /* Search was completed without result. */
276 if (p == *first)
277 break;
280 return 0;
283 /* Allocate SIZE bytes with the alignment ALIGN and return the pointer. */
284 void *
285 grub_memalign (grub_size_t align, grub_size_t size)
287 grub_mm_region_t r;
288 grub_size_t n = ((size + GRUB_MM_ALIGN - 1) >> GRUB_MM_ALIGN_LOG2) + 1;
289 int count = 0;
291 if (!grub_mm_base)
292 goto fail;
294 align = (align >> GRUB_MM_ALIGN_LOG2);
295 if (align == 0)
296 align = 1;
298 again:
300 for (r = grub_mm_base; r; r = r->next)
302 void *p;
304 p = grub_real_malloc (&(r->first), n, align);
305 if (p)
306 return p;
309 /* If failed, increase free memory somehow. */
310 switch (count)
312 case 0:
313 /* Invalidate disk caches. */
314 grub_disk_cache_invalidate_all ();
315 count++;
316 goto again;
318 #if 0
319 case 1:
320 /* Unload unneeded modules. */
321 grub_dl_unload_unneeded ();
322 count++;
323 goto again;
324 #endif
326 default:
327 break;
330 fail:
331 grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
332 return 0;
335 /* Allocate SIZE bytes and return the pointer. */
336 void *
337 grub_malloc (grub_size_t size)
339 return grub_memalign (0, size);
342 /* Allocate SIZE bytes, clear them and return the pointer. */
343 void *
344 grub_zalloc (grub_size_t size)
346 void *ret;
348 ret = grub_memalign (0, size);
349 if (ret)
350 grub_memset (ret, 0, size);
352 return ret;
355 /* Deallocate the pointer PTR. */
356 void
357 grub_free (void *ptr)
359 grub_mm_header_t p;
360 grub_mm_region_t r;
362 if (! ptr)
363 return;
365 get_header_from_pointer (ptr, &p, &r);
367 if (r->first->magic == GRUB_MM_ALLOC_MAGIC)
369 p->magic = GRUB_MM_FREE_MAGIC;
370 r->first = p->next = p;
372 else
374 grub_mm_header_t q, s;
376 #if 0
377 q = r->first;
380 grub_printf ("%s:%d: q=%p, q->size=0x%x, q->magic=0x%x\n",
381 GRUB_FILE, __LINE__, q, q->size, q->magic);
382 q = q->next;
384 while (q != r->first);
385 #endif
387 for (s = r->first, q = s->next; q <= p || q->next >= p; s = q, q = s->next)
389 if (q->magic != GRUB_MM_FREE_MAGIC)
390 grub_fatal ("free magic is broken at %p: 0x%x", q, q->magic);
392 if (q <= q->next && (q > p || q->next < p))
393 break;
396 p->magic = GRUB_MM_FREE_MAGIC;
397 p->next = q->next;
398 q->next = p;
400 if (p->next + p->next->size == p)
402 p->magic = 0;
404 p->next->size += p->size;
405 q->next = p->next;
406 p = p->next;
409 r->first = q;
411 if (q == p + p->size)
413 q->magic = 0;
414 p->size += q->size;
415 if (q == s)
416 s = p;
417 s->next = p;
418 q = s;
421 r->first = q;
425 /* Reallocate SIZE bytes and return the pointer. The contents will be
426 the same as that of PTR. */
427 void *
428 grub_realloc (void *ptr, grub_size_t size)
430 grub_mm_header_t p;
431 grub_mm_region_t r;
432 void *q;
433 grub_size_t n;
435 if (! ptr)
436 return grub_malloc (size);
438 if (! size)
440 grub_free (ptr);
441 return 0;
444 /* FIXME: Not optimal. */
445 n = ((size + GRUB_MM_ALIGN - 1) >> GRUB_MM_ALIGN_LOG2) + 1;
446 get_header_from_pointer (ptr, &p, &r);
448 if (p->size >= n)
449 return ptr;
451 q = grub_malloc (size);
452 if (! q)
453 return q;
455 grub_memcpy (q, ptr, size);
456 grub_free (ptr);
457 return q;
460 #ifdef MM_DEBUG
461 int grub_mm_debug = 0;
463 void
464 grub_mm_dump_free (void)
466 grub_mm_region_t r;
468 for (r = grub_mm_base; r; r = r->next)
470 grub_mm_header_t p;
472 /* Follow the free list. */
473 p = r->first;
476 if (p->magic != GRUB_MM_FREE_MAGIC)
477 grub_fatal ("free magic is broken at %p: 0x%x", p, p->magic);
479 grub_printf ("F:%p:%u:%p\n",
480 p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2, p->next);
481 p = p->next;
483 while (p != r->first);
486 grub_printf ("\n");
489 void
490 grub_mm_dump (unsigned lineno)
492 grub_mm_region_t r;
494 grub_printf ("called at line %u\n", lineno);
495 for (r = grub_mm_base; r; r = r->next)
497 grub_mm_header_t p;
499 for (p = (grub_mm_header_t) ALIGN_UP ((grub_addr_t) (r + 1),
500 GRUB_MM_ALIGN);
501 (grub_addr_t) p < (grub_addr_t) (r+1) + r->size;
502 p++)
504 switch (p->magic)
506 case GRUB_MM_FREE_MAGIC:
507 grub_printf ("F:%p:%u:%p\n",
508 p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2, p->next);
509 break;
510 case GRUB_MM_ALLOC_MAGIC:
511 grub_printf ("A:%p:%u\n", p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2);
512 break;
517 grub_printf ("\n");
520 void *
521 grub_debug_malloc (const char *file, int line, grub_size_t size)
523 void *ptr;
525 if (grub_mm_debug)
526 grub_printf ("%s:%d: malloc (0x%" PRIxGRUB_SIZE ") = ", file, line, size);
527 ptr = grub_malloc (size);
528 if (grub_mm_debug)
529 grub_printf ("%p\n", ptr);
530 return ptr;
533 void *
534 grub_debug_zalloc (const char *file, int line, grub_size_t size)
536 void *ptr;
538 if (grub_mm_debug)
539 grub_printf ("%s:%d: zalloc (0x%" PRIxGRUB_SIZE ") = ", file, line, size);
540 ptr = grub_zalloc (size);
541 if (grub_mm_debug)
542 grub_printf ("%p\n", ptr);
543 return ptr;
546 void
547 grub_debug_free (const char *file, int line, void *ptr)
549 if (grub_mm_debug)
550 grub_printf ("%s:%d: free (%p)\n", file, line, ptr);
551 grub_free (ptr);
554 void *
555 grub_debug_realloc (const char *file, int line, void *ptr, grub_size_t size)
557 if (grub_mm_debug)
558 grub_printf ("%s:%d: realloc (%p, 0x%" PRIxGRUB_SIZE ") = ", file, line, ptr, size);
559 ptr = grub_realloc (ptr, size);
560 if (grub_mm_debug)
561 grub_printf ("%p\n", ptr);
562 return ptr;
565 void *
566 grub_debug_memalign (const char *file, int line, grub_size_t align,
567 grub_size_t size)
569 void *ptr;
571 if (grub_mm_debug)
572 grub_printf ("%s:%d: memalign (0x%" PRIxGRUB_SIZE ", 0x%" PRIxGRUB_SIZE
573 ") = ", file, line, align, size);
574 ptr = grub_memalign (align, size);
575 if (grub_mm_debug)
576 grub_printf ("%p\n", ptr);
577 return ptr;
580 #endif /* MM_DEBUG */