Fix cross compilation (e.g. on Darwin). Following changes to make.tmpl,
[AROS.git] / arch / all-pc / boot / grub2-aros / grub-core / kern / mm.c
blob1c3d02388f09d08787bd596b626c15cabad623ff
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 /* Exclude last 4K to avoid overflows. */
121 /* If addr + 0x1000 overflows then whole region is in excluded zone. */
122 if ((grub_addr_t) addr > ~((grub_addr_t) 0x1000))
123 return;
125 /* If addr + 0x1000 + size overflows then decrease size. */
126 if (((grub_addr_t) addr + 0x1000) > ~(grub_addr_t) size)
127 size = ((grub_addr_t) -0x1000) - (grub_addr_t) addr;
129 for (p = &grub_mm_base, q = *p; q; p = &(q->next), q = *p)
130 if ((grub_uint8_t *) addr + size + q->pre_size == (grub_uint8_t *) q)
132 r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);
133 *r = *q;
134 r->pre_size += size;
136 if (r->pre_size >> GRUB_MM_ALIGN_LOG2)
138 h = (grub_mm_header_t) (r + 1);
139 h->size = (r->pre_size >> GRUB_MM_ALIGN_LOG2);
140 h->magic = GRUB_MM_ALLOC_MAGIC;
141 r->size += h->size << GRUB_MM_ALIGN_LOG2;
142 r->pre_size &= (GRUB_MM_ALIGN - 1);
143 *p = r;
144 grub_free (h + 1);
146 *p = r;
147 return;
150 /* Allocate a region from the head. */
151 r = (grub_mm_region_t) ALIGN_UP ((grub_addr_t) addr, GRUB_MM_ALIGN);
153 /* If this region is too small, ignore it. */
154 if (size < GRUB_MM_ALIGN + (char *) r - (char *) addr + sizeof (*r))
155 return;
157 size -= (char *) r - (char *) addr + sizeof (*r);
159 h = (grub_mm_header_t) (r + 1);
160 h->next = h;
161 h->magic = GRUB_MM_FREE_MAGIC;
162 h->size = (size >> GRUB_MM_ALIGN_LOG2);
164 r->first = h;
165 r->pre_size = (grub_addr_t) r - (grub_addr_t) addr;
166 r->size = (h->size << GRUB_MM_ALIGN_LOG2);
168 /* Find where to insert this region. Put a smaller one before bigger ones,
169 to prevent fragmentation. */
170 for (p = &grub_mm_base, q = *p; q; p = &(q->next), q = *p)
171 if (q->size > r->size)
172 break;
174 *p = r;
175 r->next = q;
178 /* Allocate the number of units N with the alignment ALIGN from the ring
179 buffer starting from *FIRST. ALIGN must be a power of two. Both N and
180 ALIGN are in units of GRUB_MM_ALIGN. Return a non-NULL if successful,
181 otherwise return NULL. */
182 static void *
183 grub_real_malloc (grub_mm_header_t *first, grub_size_t n, grub_size_t align)
185 grub_mm_header_t p, q;
187 /* When everything is allocated side effect is that *first will have alloc
188 magic marked, meaning that there is no room in this region. */
189 if ((*first)->magic == GRUB_MM_ALLOC_MAGIC)
190 return 0;
192 /* Try to search free slot for allocation in this memory region. */
193 for (q = *first, p = q->next; ; q = p, p = p->next)
195 grub_off_t extra;
197 extra = ((grub_addr_t) (p + 1) >> GRUB_MM_ALIGN_LOG2) & (align - 1);
198 if (extra)
199 extra = align - extra;
201 if (! p)
202 grub_fatal ("null in the ring");
204 if (p->magic != GRUB_MM_FREE_MAGIC)
205 grub_fatal ("free magic is broken at %p: 0x%x", p, p->magic);
207 if (p->size >= n + extra)
209 extra += (p->size - extra - n) & (~(align - 1));
210 if (extra == 0 && p->size == n)
212 /* There is no special alignment requirement and memory block
213 is complete match.
215 1. Just mark memory block as allocated and remove it from
216 free list.
218 Result:
219 +---------------+ previous block's next
220 | alloc, size=n | |
221 +---------------+ v
223 q->next = p->next;
225 else if (align == 1 || p->size == n + extra)
227 /* There might be alignment requirement, when taking it into
228 account memory block fits in.
230 1. Allocate new area at end of memory block.
231 2. Reduce size of available blocks from original node.
232 3. Mark new area as allocated and "remove" it from free
233 list.
235 Result:
236 +---------------+
237 | free, size-=n | next --+
238 +---------------+ |
239 | alloc, size=n | |
240 +---------------+ v
243 p->size -= n;
244 p += p->size;
246 else if (extra == 0)
248 grub_mm_header_t r;
250 r = p + extra + n;
251 r->magic = GRUB_MM_FREE_MAGIC;
252 r->size = p->size - extra - n;
253 r->next = p->next;
254 q->next = r;
256 if (q == p)
258 q = r;
259 r->next = r;
262 else
264 /* There is alignment requirement and there is room in memory
265 block. Split memory block to three pieces.
267 1. Create new memory block right after section being
268 allocated. Mark it as free.
269 2. Add new memory block to free chain.
270 3. Mark current memory block having only extra blocks.
271 4. Advance to aligned block and mark that as allocated and
272 "remove" it from free list.
274 Result:
275 +------------------------------+
276 | free, size=extra | next --+
277 +------------------------------+ |
278 | alloc, size=n | |
279 +------------------------------+ |
280 | free, size=orig.size-extra-n | <------+, next --+
281 +------------------------------+ v
283 grub_mm_header_t r;
285 r = p + extra + n;
286 r->magic = GRUB_MM_FREE_MAGIC;
287 r->size = p->size - extra - n;
288 r->next = p;
290 p->size = extra;
291 q->next = r;
292 p += extra;
295 p->magic = GRUB_MM_ALLOC_MAGIC;
296 p->size = n;
298 /* Mark find as a start marker for next allocation to fasten it.
299 This will have side effect of fragmenting memory as small
300 pieces before this will be un-used. */
301 /* So do it only for chunks under 64K. */
302 if (n < (0x8000 >> GRUB_MM_ALIGN_LOG2)
303 || *first == p)
304 *first = q;
306 return p + 1;
309 /* Search was completed without result. */
310 if (p == *first)
311 break;
314 return 0;
317 /* Allocate SIZE bytes with the alignment ALIGN and return the pointer. */
318 void *
319 grub_memalign (grub_size_t align, grub_size_t size)
321 grub_mm_region_t r;
322 grub_size_t n = ((size + GRUB_MM_ALIGN - 1) >> GRUB_MM_ALIGN_LOG2) + 1;
323 int count = 0;
325 if (!grub_mm_base)
326 goto fail;
328 align = (align >> GRUB_MM_ALIGN_LOG2);
329 if (align == 0)
330 align = 1;
332 again:
334 for (r = grub_mm_base; r; r = r->next)
336 void *p;
338 p = grub_real_malloc (&(r->first), n, align);
339 if (p)
340 return p;
343 /* If failed, increase free memory somehow. */
344 switch (count)
346 case 0:
347 /* Invalidate disk caches. */
348 grub_disk_cache_invalidate_all ();
349 count++;
350 goto again;
352 #if 0
353 case 1:
354 /* Unload unneeded modules. */
355 grub_dl_unload_unneeded ();
356 count++;
357 goto again;
358 #endif
360 default:
361 break;
364 fail:
365 grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
366 return 0;
369 /* Allocate SIZE bytes and return the pointer. */
370 void *
371 grub_malloc (grub_size_t size)
373 return grub_memalign (0, size);
376 /* Allocate SIZE bytes, clear them and return the pointer. */
377 void *
378 grub_zalloc (grub_size_t size)
380 void *ret;
382 ret = grub_memalign (0, size);
383 if (ret)
384 grub_memset (ret, 0, size);
386 return ret;
389 /* Deallocate the pointer PTR. */
390 void
391 grub_free (void *ptr)
393 grub_mm_header_t p;
394 grub_mm_region_t r;
396 if (! ptr)
397 return;
399 get_header_from_pointer (ptr, &p, &r);
401 if (r->first->magic == GRUB_MM_ALLOC_MAGIC)
403 p->magic = GRUB_MM_FREE_MAGIC;
404 r->first = p->next = p;
406 else
408 grub_mm_header_t q, s;
410 #if 0
411 q = r->first;
414 grub_printf ("%s:%d: q=%p, q->size=0x%x, q->magic=0x%x\n",
415 GRUB_FILE, __LINE__, q, q->size, q->magic);
416 q = q->next;
418 while (q != r->first);
419 #endif
421 for (s = r->first, q = s->next; q <= p || q->next >= p; s = q, q = s->next)
423 if (q->magic != GRUB_MM_FREE_MAGIC)
424 grub_fatal ("free magic is broken at %p: 0x%x", q, q->magic);
426 if (q <= q->next && (q > p || q->next < p))
427 break;
430 p->magic = GRUB_MM_FREE_MAGIC;
431 p->next = q->next;
432 q->next = p;
434 if (p->next + p->next->size == p)
436 p->magic = 0;
438 p->next->size += p->size;
439 q->next = p->next;
440 p = p->next;
443 r->first = q;
445 if (q == p + p->size)
447 q->magic = 0;
448 p->size += q->size;
449 if (q == s)
450 s = p;
451 s->next = p;
452 q = s;
455 r->first = q;
459 /* Reallocate SIZE bytes and return the pointer. The contents will be
460 the same as that of PTR. */
461 void *
462 grub_realloc (void *ptr, grub_size_t size)
464 grub_mm_header_t p;
465 grub_mm_region_t r;
466 void *q;
467 grub_size_t n;
469 if (! ptr)
470 return grub_malloc (size);
472 if (! size)
474 grub_free (ptr);
475 return 0;
478 /* FIXME: Not optimal. */
479 n = ((size + GRUB_MM_ALIGN - 1) >> GRUB_MM_ALIGN_LOG2) + 1;
480 get_header_from_pointer (ptr, &p, &r);
482 if (p->size >= n)
483 return ptr;
485 q = grub_malloc (size);
486 if (! q)
487 return q;
489 /* We've already checked that p->size < n. */
490 grub_memcpy (q, ptr, p->size << GRUB_MM_ALIGN_LOG2);
491 grub_free (ptr);
492 return q;
495 #ifdef MM_DEBUG
496 int grub_mm_debug = 0;
498 void
499 grub_mm_dump_free (void)
501 grub_mm_region_t r;
503 for (r = grub_mm_base; r; r = r->next)
505 grub_mm_header_t p;
507 /* Follow the free list. */
508 p = r->first;
511 if (p->magic != GRUB_MM_FREE_MAGIC)
512 grub_fatal ("free magic is broken at %p: 0x%x", p, p->magic);
514 grub_printf ("F:%p:%u:%p\n",
515 p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2, p->next);
516 p = p->next;
518 while (p != r->first);
521 grub_printf ("\n");
524 void
525 grub_mm_dump (unsigned lineno)
527 grub_mm_region_t r;
529 grub_printf ("called at line %u\n", lineno);
530 for (r = grub_mm_base; r; r = r->next)
532 grub_mm_header_t p;
534 for (p = (grub_mm_header_t) ALIGN_UP ((grub_addr_t) (r + 1),
535 GRUB_MM_ALIGN);
536 (grub_addr_t) p < (grub_addr_t) (r+1) + r->size;
537 p++)
539 switch (p->magic)
541 case GRUB_MM_FREE_MAGIC:
542 grub_printf ("F:%p:%u:%p\n",
543 p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2, p->next);
544 break;
545 case GRUB_MM_ALLOC_MAGIC:
546 grub_printf ("A:%p:%u\n", p, (unsigned int) p->size << GRUB_MM_ALIGN_LOG2);
547 break;
552 grub_printf ("\n");
555 void *
556 grub_debug_malloc (const char *file, int line, grub_size_t size)
558 void *ptr;
560 if (grub_mm_debug)
561 grub_printf ("%s:%d: malloc (0x%" PRIxGRUB_SIZE ") = ", file, line, size);
562 ptr = grub_malloc (size);
563 if (grub_mm_debug)
564 grub_printf ("%p\n", ptr);
565 return ptr;
568 void *
569 grub_debug_zalloc (const char *file, int line, grub_size_t size)
571 void *ptr;
573 if (grub_mm_debug)
574 grub_printf ("%s:%d: zalloc (0x%" PRIxGRUB_SIZE ") = ", file, line, size);
575 ptr = grub_zalloc (size);
576 if (grub_mm_debug)
577 grub_printf ("%p\n", ptr);
578 return ptr;
581 void
582 grub_debug_free (const char *file, int line, void *ptr)
584 if (grub_mm_debug)
585 grub_printf ("%s:%d: free (%p)\n", file, line, ptr);
586 grub_free (ptr);
589 void *
590 grub_debug_realloc (const char *file, int line, void *ptr, grub_size_t size)
592 if (grub_mm_debug)
593 grub_printf ("%s:%d: realloc (%p, 0x%" PRIxGRUB_SIZE ") = ", file, line, ptr, size);
594 ptr = grub_realloc (ptr, size);
595 if (grub_mm_debug)
596 grub_printf ("%p\n", ptr);
597 return ptr;
600 void *
601 grub_debug_memalign (const char *file, int line, grub_size_t align,
602 grub_size_t size)
604 void *ptr;
606 if (grub_mm_debug)
607 grub_printf ("%s:%d: memalign (0x%" PRIxGRUB_SIZE ", 0x%" PRIxGRUB_SIZE
608 ") = ", file, line, align, size);
609 ptr = grub_memalign (align, size);
610 if (grub_mm_debug)
611 grub_printf ("%p\n", ptr);
612 return ptr;
615 #endif /* MM_DEBUG */