summaryrefslogtreecommitdiff
path: root/firmware/malloc/bmalloc.c
blob: 470ee49840c28f600465a9cf83cfdf19c880a072 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 by Daniel Stenberg
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
/*****************************************************************************
 *
 * Big (best-fit) Memory Allocation
 *
 * Author:  Daniel Stenberg <daniel@haxx.se>
 *
 * Read THOUGHTS for theories and details on implementation.
 * 
 ****************************************************************************/

#include <stdio.h>
#include <stdlib.h>

#include "bysize.h"

#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

/* #define DEBUG_MALLOC */

#define BMEM_ALIGN 64 /* resolution */ 

#define BMEMERR_TOOSMALL -1

/* this struct will be stored in all CHUNKS and AREAS */
struct BlockInfo {
  struct BlockInfo *lower;  /* previous block in memory (lower address) */
  struct BlockInfo *higher; /* next block in memory (higher address) */
  unsigned long info;       /* 31 bits size: 1 bit free boolean */
#define INFO_FREE 1
#define INFO_SIZE (~ INFO_FREE) /* inverted FREE bit pattern */

  /* FREE+SIZE Could be written to use ordinary bitfields if using a smart
     (like gcc) compiler in a manner like:
     int size:31;
     int free:1;

     The 'higher' pointer COULD be removed completely if the size is used as
     an index to the higher one. This would then REQUIRE the entire memory
     pool to be contiguous and it needs a 'terminating' "node" or an extra
     flag that informs about the end of the list.
     */
};

/* the BLOCK list should be sorted in a lower to higher address order */
struct BlockInfo *blockHead=NULL; /* nothing from the start */

void bmalloc_status(void);


/***********************************************************************
 * 
 * remove_block()
 *
 * Remove the block from the address-sorted list.
 *
 ***********************************************************************/

static
void remove_block(struct BlockInfo *block)
{
  if(block->lower)
    block->lower->higher = block->higher;
  else
    blockHead = block->higher;
  if(block->higher)
    block->higher->lower = block->lower;
}

/****************************************************************************
 *
 * add_blocktolists()
 *
 * Adds the specified block at the specified place in the address-sorted
 * list and at the appropriate place in the size-sorted.
 *
 ***************************************************************************/
static
void add_blocktolists(struct BlockInfo *block,
		      struct BlockInfo *newblock,
		      size_t newsize)
{
  struct BlockInfo *temp; /* temporary storage variable */
  if(block) {
    /* `block' is now a lower address than 'newblock' */

    /*
     * Check if the new CHUNK is wall-to-wall with the lower addressed
     * one (if *that* is free)
     */
    if(block->info&INFO_FREE) {
      if((char *)block + (block->info&INFO_SIZE) == (char *)newblock) {
	/* yes sir, this is our lower address neighbour, enlarge that one
	   pick it out from the list and recursively add that chunk and
	   then we escape */

	/* remove from size-sorted list: */
	bmalloc_remove_chunksize((char*)block+sizeof(struct BlockInfo));

	block->info += newsize; /* newsize is an even number and thus the FREE
				   bit is untouched */

	remove_block(block); /* unlink the block address-wise */

	/* recursively check our lower friend(s) */
	add_blocktolists(block->lower, block, block->info&INFO_SIZE);
	return;
      }
    }

    temp = block->higher;

    block->higher = newblock;
    newblock->lower = block;
    newblock->higher = temp;
  }
  else {
    /* this block should preceed the heading one */
    temp = blockHead;

    /* check if this is our higher addressed neighbour */
    if((char *)newblock + newsize == (char *)temp) {

      /* yes, we are wall-to-wall with the higher CHUNK */
      if(temp->info&INFO_FREE) {
	/* and the neighbour is even free, remove that one and enlarge
	   ourselves, call add_blocktolists() recursively and then escape */

	remove_block(temp); /* unlink 'temp' from list */

	/* remove from size-sorted list: */
	bmalloc_remove_chunksize((char*)temp+sizeof(struct BlockInfo) );

	/* add the upper block's size on ourselves */
	newsize += temp->info&INFO_SIZE;

	/* add the new, bigger block */
	add_blocktolists(block, newblock, newsize);
	return;
      }
    }

    blockHead = newblock;
    newblock->higher = temp;
    newblock->lower  = NULL; /* there is no lower one */
  }

  newblock->info = newsize | INFO_FREE; /* we do assume size isn't using the
					   FREE bit */
  bmalloc_insert_bysize((char *)newblock+sizeof(struct BlockInfo), newsize);
}

/***********************************************************************
 *
 * findblockbyaddr()
 *
 * Find the block that is just before the input block in memory. Returns NULL
 * if none is.
 *
 ***********************************************************************/

static
struct BlockInfo *findblockbyaddr(struct BlockInfo *block)
{
  struct BlockInfo *test = blockHead;
  struct BlockInfo *lower = NULL;
  
  while(test && (test < block)) {
    lower = test;
    test = test->higher;
  }
  return lower;
}

/***********************************************************************
 *
 * bmalloc_add_pool()
 * 
 * This function should be the absolutely first function to call. It sets up
 * the memory bounds of the [first] CHUNK(s). It is possible to call this
 * function several times to add more CHUNKs to the pool of free memory. This
 * allows the bmalloc system to deal with non-contigous memory areas.
 *
 * Returns non-zero if an error occured. The memory was not added then.
 *
 ***********************************************************************/

int bmalloc_add_pool(void *start,
                     size_t size)
{
  struct BlockInfo *newblock = (struct BlockInfo *)start;
  struct BlockInfo *block;

  if(size < BMEM_ALIGN)
    return BMEMERR_TOOSMALL;

  block = findblockbyaddr( newblock );
  /* `block' is now a lower address than 'newblock' or NULL */

  if(size&1)
    size--; /* only add even sizes */

  add_blocktolists(block, newblock, size);

  return 0;
}


#ifdef DEBUG_VERBOSE
static void bmalloc_failed(size_t size)
{
  printf("*** " __FILE__ " Couldn't allocate %d bytes\n", size);
  bmalloc_status();
}
#else
#define bmalloc_failed(x)
#endif

void bmalloc_status(void)
{
#ifdef DEBUG_MALLOC
  struct BlockInfo *block = blockHead;
  long mem_free = 0;
  long mem_used = 0;

  printf("List of BLOCKS (in address order):\n");
  while(block) {
    printf("  START %p END %p SIZE %ld FLAG %s\n",
	   block,
           (char *)block+(block->info&INFO_SIZE),
           block->info&INFO_SIZE,
	   (block->info&INFO_FREE)?"free":"used");
    if(block->info&INFO_FREE)
      mem_free += block->info&INFO_SIZE;
    else
      mem_used += block->info&INFO_SIZE;
    block = block->higher;
  }
  printf(" Used mem: %ld , free mem: %ld (total %ld)\n",
         mem_used, mem_free, mem_used + mem_free);
  bmalloc_print_sizes();
#endif
}


void *bmalloc(size_t size)
{
  void *mem;

#ifdef DEBUG_VERBOSE
  {
    static int count=0;
    int realsize = size + sizeof(struct BlockInfo);
    if(realsize%4096)
      realsize = ((size / BMEM_ALIGN)+1) * BMEM_ALIGN;
    printf("%d bmalloc(%d) [%d]\n", count++, size, realsize);
  }
#endif

  size += sizeof(struct BlockInfo); /* add memory for our header */

  if(size&(BMEM_ALIGN-1)) /* a lot faster than %BMEM_ALIGN but this MUST be
			     changed if the BLOCKSIZE is not 2^X ! */
    size = ((size / BMEM_ALIGN)+1) * BMEM_ALIGN; /* align like this */

  /* get a CHUNK from the list with this size */
  mem = bmalloc_obtainbysize ( size );
  if(mem) {
    /* the memory block we have got is the "best-fit" and it is already
       un-linked from the free list */

    /* now do the math to get the proper block pointer */
    struct BlockInfo *block= (struct BlockInfo *)
      ((char *)mem - sizeof(struct BlockInfo));

    block->info &= ~INFO_FREE;
    /* not free anymore */

    if( size != (block->info&INFO_SIZE)) {
      /* split this chunk into two pieces and return the one that fits us */
      size_t othersize = (block->info&INFO_SIZE) - size;

      if(othersize > BMEM_ALIGN) { 
	/* prevent losing small pieces of memory due to weird alignments
	   of the memory pool */

	block->info = size;  /* set new size (leave FREE bit cleared) */
      
	/* Add the new chunk to the lists: */
	add_blocktolists(block->lower,
			 (struct BlockInfo *)((char *)block + size),
			 othersize );
      }
    }
    
    /* Return the memory our parent may use: */
    return (char *)block+sizeof(struct BlockInfo);
  }
  else {
    bmalloc_failed(size);
    return NULL; /* can't find any memory, fail hard */
  }

#ifdef DEBUG_VERBOSE
  bmalloc_status();
#endif
  return mem;
}

void bfree(void *ptr)
{
  struct BlockInfo *block = (struct BlockInfo *)
    ((char *)ptr - sizeof(struct BlockInfo));
  size_t size;

  /* setup our initial higher and lower pointers */
  struct BlockInfo *lower = block->lower;
  struct BlockInfo *higher = block->higher;

#ifdef DEBUG_VERBOSE
  static int freecount=0;
  printf("%d bfree(%p)\n", freecount++, ptr);
#endif
  /* bind together lower addressed FREE CHUNKS */
  if(lower && (lower->info&INFO_FREE) &&
     ((char *)lower + (lower->info&INFO_SIZE) == (char *)block)) {
    size = block->info&INFO_SIZE;      /* original size */
    
    /* remove from size-link: */
    bmalloc_remove_chunksize((char *)lower+sizeof(struct BlockInfo));

    remove_block(block);              /* unlink from address list */
    block = lower;                    /* new base area pointer */
    block->info += size;              /* append the new size (the FREE bit
					 will remain untouched) */

    lower = lower->lower;             /* new lower pointer */
  }
  /* bind together higher addressed FREE CHUNKS */
  if(higher && (higher->info&INFO_FREE) &&
     ((char *)block + (block->info&INFO_SIZE) == (char *)higher)) { 
    /* append higher size, the FREE bit won't be affected */
    block->info += (higher->info&INFO_SIZE);
    
    /* unlink from size list: */
    bmalloc_remove_chunksize(higher+sizeof(struct BlockInfo));
    remove_block(higher);             /* unlink from address list */
    higher = higher->higher;          /* the new higher link */
    block->higher = higher;           /* new higher link */
  }
  block->info &= ~INFO_FREE;   /* consider this FREE! */

  block->lower = lower;
  block->higher = higher;

  bmalloc_insert_bysize((char *)block+sizeof(struct BlockInfo),
                        block->info&INFO_SIZE);

#ifdef DEBUG_VERBOSE
  bmalloc_status();
#endif

}