summaryrefslogtreecommitdiffstats
path: root/firmware/buflib_malloc.c
blob: 0cd292f1e0e5fb7ae58449c16ae24604c6f8740e (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
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2023 Aidan MacDonald
 *
 * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

/*
 * Malloc backed buflib. This is intended for debugging rather than for
 * serious use - the buffer passed to the context is wasted, and memory
 * is acquired from malloc() instead. The main point is to make ASAN more
 * effective by isolating buflib allocations from each other.
 *
 * Currently this is a bare-minimum implementation, it doesn't even run
 * buflib callbacks since it never moves anything. It could later be
 * extended with stress-testing options, for example by randomly moving
 * allocations around.
 */

#include "buflib.h"
#include "panic.h"
#include <stdlib.h>

static struct buflib_malloc_handle *get_free_handle(struct buflib_context *ctx)
{
    struct buflib_malloc_handle *h;
    for (size_t i = 0; i < ctx->num_allocs; ++i)
    {
        h = &ctx->allocs[i];
        if (h->size == 0)
            return h;
    }

    ctx->num_allocs++;
    ctx->allocs = realloc(ctx->allocs, ctx->num_allocs * sizeof(*ctx->allocs));
    if (!ctx->allocs)
        panicf("buflib %p handle OOM", ctx);

    h = &ctx->allocs[ctx->num_allocs - 1];
    h->size = 0;
    return h;
}

static int get_handle_num(struct buflib_context *ctx,
                          struct buflib_malloc_handle *handle)
{
    return (handle - ctx->allocs) + 1;
}

static struct buflib_malloc_handle *get_handle(struct buflib_context *ctx,
                                               int handle)
{
    return &ctx->allocs[handle - 1];
}

struct buflib_callbacks buflib_ops_locked = {
    .move_callback = NULL,
    .shrink_callback = NULL,
    .sync_callback = NULL,
};

void buflib_init(struct buflib_context *ctx, void *buf, size_t size)
{
    ctx->allocs = NULL;
    ctx->num_allocs = 0;
    ctx->buf = buf;
    ctx->bufsize = size;
}

size_t buflib_available(struct buflib_context *ctx)
{
    return ctx->bufsize;
}

size_t buflib_allocatable(struct buflib_context *ctx)
{
    return ctx->bufsize;
}

bool buflib_context_relocate(struct buflib_context *ctx, void *buf)
{
    ctx->buf = buf;
    return true;
}

int buflib_alloc(struct buflib_context *ctx, size_t size)
{
    return buflib_alloc_ex(ctx, size, NULL);
}

int buflib_alloc_ex(struct buflib_context *ctx, size_t size,
                    struct buflib_callbacks *ops)
{
    struct buflib_malloc_handle *handle = get_free_handle(ctx);

    handle->data = malloc(size);
    handle->user = handle->data;
    handle->size = size;
    handle->pin_count = 0;
    handle->ops = ops;

    if (!handle->data)
        panicf("buflib %p data OOM", ctx);

    return get_handle_num(ctx, handle);
}

int buflib_alloc_maximum(struct buflib_context* ctx,
                         size_t *size, struct buflib_callbacks *ops)
{
    *size = ctx->bufsize;

    return buflib_alloc_ex(ctx, *size, ops);
}

bool buflib_shrink(struct buflib_context *ctx, int handle,
                   void *newstart, size_t new_size)
{
    struct buflib_malloc_handle *h = get_handle(ctx, handle);
    if (newstart < h->user || new_size > h->size - (newstart - h->user))
        return false;

    /* XXX: this might be allowed, but what would be the point... */
    if (new_size == 0)
    {
        panicf("weird shrink");
        return false;
    }

    /* due to buflib semantics we must not realloc */
    h->user = newstart;
    h->size = new_size;
    return true;
}

void buflib_pin(struct buflib_context *ctx, int handle)
{
    struct buflib_malloc_handle *h = get_handle(ctx, handle);

    h->pin_count++;
}

void buflib_unpin(struct buflib_context *ctx, int handle)
{
    struct buflib_malloc_handle *h = get_handle(ctx, handle);

    h->pin_count--;
}

unsigned buflib_pin_count(struct buflib_context *ctx, int handle)
{
    struct buflib_malloc_handle *h = get_handle(ctx, handle);

    return h->pin_count;
}

void _buflib_malloc_put_data_pinned(struct buflib_context *ctx, void *data)
{
    for (size_t i = 0; i < ctx->num_allocs; ++i)
    {
        if (ctx->allocs[i].user == data)
        {
            ctx->allocs[i].pin_count--;
            break;
        }
    }
}

int buflib_free(struct buflib_context *ctx, int handle)
{
    if (handle <= 0)
        return 0;

    struct buflib_malloc_handle *h = get_handle(ctx, handle);

    free(h->data);
    h->size = 0;

    return 0;
}

#ifdef BUFLIB_DEBUG_GET_DATA
void *buflib_get_data(struct buflib_context *ctx, int handle)
{
    /* kind of silly since it's better for ASAN to catch this but... */
    if (handle <= 0 || handle > ctx->num_allocs)
        panicf("buflib %p: invalid handle %d", ctx, handle);

    struct buflib_malloc_handle *h = get_handle(ctx, handle);
    if (h->user == NULL)
        panicf("buflib %p: handle %d use after free", ctx, handle);

    return h->user;
}
#endif

void *buflib_buffer_out(struct buflib_context *ctx, size_t *size)
{
    if (*size == 0)
        *size = ctx->bufsize;

    void *ret = ctx->buf;

    ctx->buf += *size;
    return ret;
}

void buflib_buffer_in(struct buflib_context *ctx, int size)
{
    ctx->buf -= size;
}

#ifdef BUFLIB_DEBUG_PRINT
int buflib_get_num_blocks(struct buflib_context *ctx)
{
    return ctx->num_allocs;
}

bool buflib_print_block_at(struct buflib_context *ctx, int block_num,
                           char *buf, size_t bufsize)
{
    if (block_num >= ctx->num_allocs)
    {
        if (bufsize > 0)
            *buf = '\0';
        return false;
    }

    struct buflib_malloc_handle *handle = &ctx->allocs[block_num];
    if (handle->data)
    {
        snprintf(buf, bufsize, "%03d addr:%8p length:%zu",
                 block_num, handle->data, handle->size);
    }
    else
    {
        snprintf(buf, bufsize, "%03d (unallocated)", block_num);
    }

    return true;
}
#endif

#ifdef BUFLIB_DEBUG_CHECK_VALID
void buflib_check_valid(struct buflib_context *ctx)
{
    (void)ctx;
}
#endif