aboutsummaryrefslogtreecommitdiffstats
path: root/extras/mini-os/lib/xmalloc.c
blob: 015cd31bb999f9f178cbe135e6e8373868023b47 (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
/* 
 ****************************************************************************
 * (C) 2005 - Grzegorz Milos - Intel Research Cambridge
 ****************************************************************************
 *
 *        File: xmaloc.c
 *      Author: Grzegorz Milos (gm281@cam.ac.uk)
 *              Samuel Thibault (samuel.thibault@eu.citrix.com)
 *     Changes: 
 *              
 *        Date: Aug 2005
 *              Jan 2008
 * 
 * Environment: Xen Minimal OS
 * Description: simple memory allocator
 *
 ****************************************************************************
 * Simple allocator for Mini-os.  If larger than a page, simply use the
 * page-order allocator.
 *
 * Copy of the allocator for Xen by Rusty Russell:
 * Copyright (C) 2005 Rusty Russell IBM Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <mini-os/os.h>
#include <mini-os/mm.h>
#include <mini-os/types.h>
#include <mini-os/lib.h>
#include <mini-os/list.h>
#include <mini-os/xmalloc.h>

#ifndef HAVE_LIBC
/* static spinlock_t freelist_lock = SPIN_LOCK_UNLOCKED; */

struct xmalloc_hdr
{
    /* Total including this hdr, unused padding and second hdr. */
    size_t size;
    MINIOS_TAILQ_ENTRY(struct xmalloc_hdr) freelist;
} __cacheline_aligned;

static MINIOS_TAILQ_HEAD(,struct xmalloc_hdr) freelist =
	MINIOS_TAILQ_HEAD_INITIALIZER(freelist);

/* Unused padding data between the two hdrs. */

struct xmalloc_pad
{
    /* Size including both hdrs. */
    size_t hdr_size;
};

/* Return size, increased to alignment with align. */
static inline size_t align_up(size_t size, size_t align)
{
    return (size + align - 1) & ~(align - 1);
}

static void maybe_split(struct xmalloc_hdr *hdr, size_t size, size_t block)
{
    struct xmalloc_hdr *extra;
    size_t leftover;
    size = align_up(size, __alignof__(struct xmalloc_hdr));
    size = align_up(size, __alignof__(struct xmalloc_pad));
    leftover = block - size;

    /* If enough is left to make a block, put it on free list. */
    if ( leftover >= (2 * (sizeof(struct xmalloc_hdr) + sizeof(struct xmalloc_pad))) )
    {
        extra = (struct xmalloc_hdr *)((unsigned long)hdr + size);
        extra->size = leftover;
        /* spin_lock_irqsave(&freelist_lock, flags); */
        MINIOS_TAILQ_INSERT_HEAD(&freelist, extra, freelist);
        /* spin_unlock_irqrestore(&freelist_lock, flags); */
    }
    else
    {
        size = block;
    }

    hdr->size = size;
}

static struct xmalloc_hdr *xmalloc_new_page(size_t size)
{
    struct xmalloc_hdr *hdr;
    /* unsigned long flags; */

    hdr = (struct xmalloc_hdr *)alloc_page();
    if ( hdr == NULL )
        return NULL;

    maybe_split(hdr, size, PAGE_SIZE);

    return hdr;
}

/* Big object?  Just use the page allocator. */
static void *xmalloc_whole_pages(size_t size, size_t align)
{
    struct xmalloc_hdr *hdr;
    struct xmalloc_pad *pad;
    unsigned int pageorder;
    void *ret;
    /* Room for headers */
    size_t hdr_size = sizeof(struct xmalloc_hdr) + sizeof(struct xmalloc_pad);
    /* Align for actual beginning of data */
    hdr_size = align_up(hdr_size, align);

    pageorder = get_order(hdr_size + size);

    hdr = (struct xmalloc_hdr *)alloc_pages(pageorder);
    if ( hdr == NULL )
        return NULL;

    hdr->size = (1UL << (pageorder + PAGE_SHIFT));

    ret = (char*)hdr + hdr_size;
    pad = (struct xmalloc_pad *) ret - 1;
    pad->hdr_size = hdr_size;
    return ret;
}

void *_xmalloc(size_t size, size_t align)
{
    struct xmalloc_hdr *i, *tmp, *hdr = NULL;
    uintptr_t data_begin;
    size_t hdr_size;
    /* unsigned long flags; */

    hdr_size = sizeof(struct xmalloc_hdr) + sizeof(struct xmalloc_pad);
    /* Align on headers requirements. */
    align = align_up(align, __alignof__(struct xmalloc_hdr));
    align = align_up(align, __alignof__(struct xmalloc_pad));

    /* For big allocs, give them whole pages. */
    if ( size + align_up(hdr_size, align) >= PAGE_SIZE )
        return xmalloc_whole_pages(size, align);

    /* Search free list. */
    /* spin_lock_irqsave(&freelist_lock, flags); */
    MINIOS_TAILQ_FOREACH_SAFE(i, &freelist, freelist, tmp)
    {
        data_begin = align_up((uintptr_t)i + hdr_size, align);

        if ( data_begin + size > (uintptr_t)i + i->size )
            continue;

        MINIOS_TAILQ_REMOVE(&freelist, i, freelist);
        /* spin_unlock_irqrestore(&freelist_lock, flags); */

        uintptr_t size_before = (data_begin - hdr_size) - (uintptr_t)i;

        if (size_before >= 2 * hdr_size) {
            /* Worth splitting the beginning */
            struct xmalloc_hdr *new_i = (void*)(data_begin - hdr_size);
            new_i->size = i->size - size_before;
            i->size = size_before;
            /* spin_lock_irqsave(&freelist_lock, flags); */
            MINIOS_TAILQ_INSERT_HEAD(&freelist, i, freelist);
            /* spin_unlock_irqrestore(&freelist_lock, flags); */
            i = new_i;
        }
        maybe_split(i, (data_begin + size) - (uintptr_t)i, i->size);
        hdr = i;
        break;
    }

    if (!hdr) {
        /* spin_unlock_irqrestore(&freelist_lock, flags); */

        /* Alloc a new page and return from that. */
        hdr = xmalloc_new_page(align_up(hdr_size, align) + size);
        if ( hdr == NULL )
            return NULL;
        data_begin = (uintptr_t)hdr + align_up(hdr_size, align);
    }

    struct xmalloc_pad *pad = (struct xmalloc_pad *) data_begin - 1;
    pad->hdr_size = data_begin - (uintptr_t)hdr;
    BUG_ON(data_begin % align);
    return (void*)data_begin;
}

void xfree(const void *p)
{
    /* unsigned long flags; */
    struct xmalloc_hdr *i, *tmp, *hdr;
    struct xmalloc_pad *pad;

    if ( p == NULL )
        return;

    pad = (struct xmalloc_pad *)p - 1;
    hdr = (struct xmalloc_hdr *)((char *)p - pad->hdr_size);

    /* Big allocs free directly. */
    if ( hdr->size >= PAGE_SIZE )
    {
        free_pages(hdr, get_order(hdr->size));
        return;
    }

    /* We know hdr will be on same page. */
    if(((long)p & PAGE_MASK) != ((long)hdr & PAGE_MASK))
    {
        printk("Header should be on the same page\n");
        *(int*)0=0;
    }

    /* Merge with other free block, or put in list. */
    /* spin_lock_irqsave(&freelist_lock, flags); */
    MINIOS_TAILQ_FOREACH_SAFE(i, &freelist, freelist, tmp)
    {
        unsigned long _i   = (unsigned long)i;
        unsigned long _hdr = (unsigned long)hdr;

        /* Do not merge across page boundaries. */
        if ( ((_i ^ _hdr) & PAGE_MASK) != 0 )
            continue;

        /* We follow this block?  Swallow it. */
        if ( (_i + i->size) == _hdr )
        {
            MINIOS_TAILQ_REMOVE(&freelist, i, freelist);
            i->size += hdr->size;
            hdr = i;
        }

        /* We precede this block? Swallow it. */
        if ( (_hdr + hdr->size) == _i )
        {
            MINIOS_TAILQ_REMOVE(&freelist, i, freelist);
            hdr->size += i->size;
        }
    }

    /* Did we merge an entire page? */
    if ( hdr->size == PAGE_SIZE )
    {
        if((((unsigned long)hdr) & (PAGE_SIZE-1)) != 0)
        {
            printk("Bug\n");
            *(int*)0=0;
        }
        free_page(hdr);
    }
    else
    {
        MINIOS_TAILQ_INSERT_HEAD(&freelist, hdr, freelist);
    }

    /* spin_unlock_irqrestore(&freelist_lock, flags); */
}

void *_realloc(void *ptr, size_t size)
{
    void *new;
    struct xmalloc_hdr *hdr;
    struct xmalloc_pad *pad;
    size_t old_data_size;

    if (ptr == NULL)
        return _xmalloc(size, DEFAULT_ALIGN);

    pad = (struct xmalloc_pad *)ptr - 1;
    hdr = (struct xmalloc_hdr *)((char*)ptr - pad->hdr_size);

    old_data_size = hdr->size - pad->hdr_size;
    if ( old_data_size >= size )
    {
	maybe_split(hdr, pad->hdr_size + size, hdr->size);
        return ptr;
    }
    
    new = _xmalloc(size, DEFAULT_ALIGN);
    if (new == NULL) 
        return NULL;

    memcpy(new, ptr, old_data_size);
    xfree(ptr);

    return new;
}
#endif

/*
 * Local variables:
 * mode: C
 * c-set-style: "BSD"
 * c-basic-offset: 4
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 */