heap_free(): Handle NULL pointers like free(), write documentation.
[bertos.git] / mware / heap.c
1 /*!
2  * \file
3  * <!--
4  * Copyright 2004 Develer S.r.l. (http://www.develer.com/)
5  * Copyright 1999,2000,2001 Bernardo Innocenti <bernie@develer.com>
6  * This file is part of DevLib - See devlib/README for information.
7  * -->
8  *
9  * \brief Heap subsystem (public interface).
10  *
11  * \version $Id$
12  *
13  * \author Bernardo Innocenti <bernie@develer.com>
14  */
15
16 /*#*
17  *#* $Log$
18  *#* Revision 1.6  2004/10/26 09:02:13  bernie
19  *#* heap_free(): Handle NULL pointers like free(), write documentation.
20  *#*
21  *#* Revision 1.5  2004/10/03 20:43:22  bernie
22  *#* Import changes from sc/firmware.
23  *#*
24  *#* Revision 1.1  2004/07/31 16:33:58  rasky
25  *#* Spostato lo heap da kern/ a mware/
26  *#*
27  *#* Revision 1.2  2004/06/03 11:27:09  bernie
28  *#* Add dual-license information.
29  *#*
30  *#* Revision 1.1  2004/05/23 17:27:00  bernie
31  *#* Import kern/ subdirectory.
32  *#*
33  *#*/
34
35 #include "heap.h"
36 #include <string.h>           // memset()
37 #include <macros.h>           // IS_POW2()
38 #include <debug.h>            // ASSERT()
39
40 /* NOTE: struct size must be a 2's power! */
41 typedef struct _MemChunk
42 {
43         struct _MemChunk *next;
44         size_t size;
45 } MemChunk;
46
47 STATIC_ASSERT(IS_POW2(sizeof(MemChunk)));
48
49 #define FREE_FILL_CODE     0xDEAD
50 #define ALLOC_FILL_CODE    0xBEEF
51
52 void heap_init(struct Heap* h, void* memory, size_t size)
53 {
54 #ifdef _DEBUG
55         memset(memory, FREE_FILL_CODE, size);
56 #endif
57
58         /* Initialize heap with a single big chunk */
59         h->FreeList = (MemChunk *)memory;
60         h->FreeList->next = NULL;
61         h->FreeList->size = size;
62 }
63
64
65 void *heap_allocmem(struct Heap* h, size_t size)
66 {
67         MemChunk *chunk, *prev;
68
69         /* Round size up to the allocation granularity */
70         size = ROUND2(size, sizeof(MemChunk));
71
72         /* Handle allocations of 0 bytes */
73         if (!size)
74                 size = sizeof(MemChunk);
75
76         /* Walk on the free list looking for any chunk big enough to
77          * fit the requested block size.
78          */
79         for (prev = (MemChunk *)&h->FreeList, chunk = h->FreeList;
80                 chunk;
81                 prev = chunk, chunk = chunk->next)
82         {
83                 if (chunk->size >= size)
84                 {
85                         if (chunk->size == size)
86                         {
87                                 /* Just remove this chunk from the free list */
88                                 prev->next = chunk->next;
89                                 #ifdef _DEBUG
90                                         memset(chunk, ALLOC_FILL_CODE, size);
91                                 #endif
92                                 return (void *)chunk;
93                         }
94                         else
95                         {
96                                 /* Allocate from the END of an existing chunk */
97                                 chunk->size -= size;
98                                 #ifdef _DEBUG
99                                         memset((uint8_t *)chunk + chunk->size, ALLOC_FILL_CODE, size);
100                                 #endif
101                                 return (void *)((uint8_t *)chunk + chunk->size);
102                         }
103                 }
104         }
105
106         return NULL; /* fail */
107 }
108
109
110 void heap_freemem(struct Heap* h, void *mem, size_t size)
111 {
112         MemChunk *prev;
113
114         ASSERT(mem);
115
116 #ifdef _DEBUG
117         memset(mem, FREE_FILL_CODE, size);
118 #endif
119
120         /* Round size up to the allocation granularity */
121         size = ROUND2(size, sizeof(MemChunk));
122
123         /* Handle allocations of 0 bytes */
124         if (!size)
125                 size = sizeof(MemChunk);
126
127         /* Special case: first chunk in the free list */
128         ASSERT((uint8_t*)mem != (uint8_t*)h->FreeList);
129         if (((uint8_t *)mem) < ((uint8_t *)h->FreeList))
130         {
131                 /* Insert memory block before the current free list head */
132                 prev = (MemChunk *)mem;
133                 prev->next = h->FreeList;
134                 prev->size = size;
135                 h->FreeList = prev;
136         }
137         else /* Normal case: not the first chunk in the free list */
138         {
139                 /*
140                  * Walk on the free list. Stop at the insertion point (when mem
141                  * is between prev and prev->next)
142                  */
143                 prev = h->FreeList;
144                 while (prev->next < (MemChunk *)mem && prev->next)
145                         prev = prev->next;
146
147                 /* Make sure mem is not *within* prev */
148                 ASSERT((uint8_t*)mem >= (uint8_t*)prev + prev->size);
149
150                 /* Should it be merged with previous block? */
151                 if (((uint8_t *)prev) + prev->size == ((uint8_t *)mem))
152                 {
153                         /* Yes */
154                         prev->size += size;
155                 }
156                 else /* not merged with previous chunk */
157                 {
158                         MemChunk *curr = (MemChunk*)mem;
159
160                         /* insert it after the previous node
161                          * and move the 'prev' pointer forward
162                          * for the following operations
163                          */
164                         curr->next = prev->next;
165                         curr->size = size;
166                         prev->next = curr;
167
168                         /* Adjust for the following test */
169                         prev = curr;
170                 }
171         }
172
173         /* Also merge with next chunk? */
174         if (((uint8_t *)prev) + prev->size == ((uint8_t *)prev->next))
175         {
176                 prev->size += prev->next->size;
177                 prev->next = prev->next->next;
178
179                 /* There should be only one merge opportunity, becuase we always merge on free */
180                 ASSERT((uint8_t*)prev + prev->size != (uint8_t*)prev->next);
181         }
182 }
183
184 #if CONFIG_HEAP_MALLOC
185
186 void *heap_malloc(struct Heap* h, size_t size)
187 {
188         size_t *mem;
189
190         size += sizeof(size_t);
191         if ((mem = (size_t*)heap_allocmem(h, size)))
192                 *mem++ = size;
193
194         return mem;
195 }
196
197 void *heap_calloc(struct Heap* h, size_t size)
198 {
199         void *mem;
200
201         if ((mem = heap_malloc(h, size)))
202                 memset(mem, 0, size);
203
204         return mem;
205 }
206
207 /*!
208  * Free a block of memory, determining its size automatically.
209  *
210  * \param h    Heap from which the block was allocated.
211  * \param mem  Pointer to a block of memory previously allocated with
212  *             either heap_malloc() or heap_calloc().
213  *
214  * \note If \a mem is a NULL pointer, no operation is performed.
215  *
216  * \note Freeing the same memory block twice has undefined behavior.
217  *
218  * \note This function works like the ANSI C free().
219  */
220 void heap_free(struct Heap *h, void *mem)
221 {
222         size_t *_mem = (size_t *)mem;
223
224         if (_mem)
225         {
226                 --_mem;
227                 heap_freemem(h, _mem, *_mem);
228         }
229 }
230
231 #endif /* CONFIG_HEAP_MALLOC */