summaryrefslogtreecommitdiffstats
path: root/firmware/include/buflib.h
blob: 32a5a6abe03b494d0cb883e2be9a6e2a205a6f59 (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
387
388
389
/**************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2009 Andrew Mahone
 * Copyright (C) 2011 Thomas Martitz
 * 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.
 *
 ****************************************************************************/
#ifndef _BUFLIB_H_
#define _BUFLIB_H_

#include "config.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

/* Add extra checks to buflib_get_data to catch bad handles */
//#define BUFLIB_DEBUG_GET_DATA

/* Support integrity check */
//#define BUFLIB_DEBUG_CHECK_VALID

/* Support debug printing of memory blocks */
//#define BUFLIB_DEBUG_PRINT

/* Defined by the backend header. */
struct buflib_context;

/* Buflib callback return codes. */
#define BUFLIB_CB_OK            0
#define BUFLIB_CB_CANNOT_MOVE   1
#define BUFLIB_CB_CANNOT_SHRINK 1

/* Buflib shrink hints. */
#define BUFLIB_SHRINK_SIZE_MASK (~BUFLIB_SHRINK_POS_MASK)
#define BUFLIB_SHRINK_POS_FRONT (1u<<31)
#define BUFLIB_SHRINK_POS_BACK  (1u<<30)
#define BUFLIB_SHRINK_POS_MASK  (BUFLIB_SHRINK_POS_FRONT|BUFLIB_SHRINK_POS_BACK)

/**
 * Callbacks run by buflib to manage an allocation.
 */
struct buflib_callbacks
{
    /**
     * \brief Called when buflib wants to move the buffer
     * \param handle    Handle being moved
     * \param current   Current address of the buffer
     * \param new       New address the buffer would have after moving
     * \return BUFLIB_CB_OK - Allow the buffer to be moved.
     * \return BUFLIB_CB_CANNOT_MOVE - Do not allow the buffer to be moved.
     *
     * This callback allows you to fix up any pointers that might
     * be pointing to the buffer before it is moved. The task of
     * actually moving the buffer contents is performed by buflib
     * after the move callback returns, if movement is allowed.
     *
     * Care must be taken to ensure that the buffer is not accessed
     * from outside the move callback until the move is complete. If
     * this is a concern, eg. due to multi-threaded access, then you
     * must implement a sync_callback() and guard any access to the
     * buffer with a lock.
     *
     * If the move callback is NULL then buflib will never move
     * the allocation, as if you returned BUFLIB_CB_CANNOT_MOVE.
     */
    int (*move_callback)(int handle, void* current, void* new);

    /**
     * \brief Called when buflib wants to shrink the buffer
     * \param handle    Handle to shrink
     * \param hints     Hints regarding the shrink request
     * \param start     Current address of the buffer
     * \param size      Current size of the buffer as seen by buflib.
     *                  This may be rounded up compared to the nominal
     *                  allocation size due to alignment requirements.
     * \return BUFLIB_CB_OK - Was able to shrink the buffer.
     * \return BUFLIB_CB_CANNOT_SHRINK - Buffer cannot shrink.
     *
     * This callback is run by buflib when it runs out of memory
     * and starts a compaction run. Buflib will not actually try
     * to shrink or move memory, you must do that yourself and
     * call buflib_shrink() to report the new start address and
     * size of the buffer.
     *
     * If the shrink callback is NULL then buflib will regard the
     * buffer as non-shrinkable.
     */
    int (*shrink_callback)(int handle, unsigned hints,
                           void *start, size_t size);

    /**
     * \brief Called before and after attempting to move the buffer
     * \param handle    Handle being moved
     * \param lock      True to lock, false to unlock
     *
     * The purpose of this callback is to block access to the buffer
     * from other threads while a buffer is being moved, using a lock
     * such as a mutex.
     *
     * It is called with `sync_callback(handle, true)` before running
     * the move callback and `sync_callback(handle, false)` after the
     * move is complete, regardless of whether the buffer was actually
     * moved or not.
     */
    void (*sync_callback)(int handle, bool lock);
};

/**
 * A set of all NULL callbacks for use with allocations that need to stay
 * locked in RAM and not moved or shrunk. These type of allocations should
 * be avoided as much as possible to avoid memory fragmentation but it can
 * suitable for short-lived allocations.
 *
 * \note Use of this is discouraged. Prefer to use normal moveable
 *       allocations and pin them.
 */
extern struct buflib_callbacks buflib_ops_locked;

/**
 * \brief Intialize a buflib context
 * \param ctx       Context to initialize
 * \param buf       Buffer which will be used as the context's memory pool
 * \param size      Size of the buffer
 */
void buflib_init(struct buflib_context *ctx, void *buf, size_t size);

/**
 * Returns the amount of unallocated bytes. It does not mean this amount
 * can be actually allocated because they might not be contiguous.
 */
size_t buflib_available(struct buflib_context *ctx);

/**
 * Returns the size of the largest possible contiguous allocation, given
 * the current state of the memory pool. A larger allocation may still
 * succeed if compaction is able to create a larger contiguous area.
 */
size_t buflib_allocatable(struct buflib_context *ctx);

/**
 * \brief Relocate the buflib memory pool to a new address
 * \param ctx       Context to relocate
 * \param buf       New memory pool address
 * \return True if relocation should proceed, false if it cannot.
 *
 * Updates all pointers inside the buflib context to point to a new pool
 * address. You must call this function before moving the pool and move
 * the data manually afterwards only if this function returns true.
 *
 * This is intended from a move_callback() in buflib-on-buflib scenarios,
 * where the memory pool of the "inner" buflib is allocated from an "outer"
 * buflib.
 *
 * \warning This does not run any move callbacks, so it is not safe to
 *          use if any allocations require them.
 */
bool buflib_context_relocate(struct buflib_context *ctx, void *buf);

/**
 * \brief Allocate memory from buflib
 * \param ctx       Context to allocate from
 * \param size      Allocation size
 * \return Handle for the allocation (> 0) or a negative value on error
 *
 * This is the same as calling buflib_alloc_ex() with a NULL callbacks
 * struct. The resulting allocation can be moved by buflib; use pinning
 * if you need to prevent moves.
 *
 * Note that zero is not a valid handle, and will never be returned by
 * this function. However, this may change, and you should treat a zero
 * or negative return value as an allocation failure.
 */
int buflib_alloc(struct buflib_context *ctx, size_t size);

/**
 * \brief Allocate memory from buflib with custom buffer ops
 * \param ctx       Context to allocate from
 * \param size      Allocation size
 * \param ops       Pointer to ops struct or NULL if no ops are needed.
 * \return Handle for the allocation (> 0) or a negative value on error.
 *
 * Use this if you need to pass custom callbacks for responding to buflib
 * move or shrink operations. Passing a NULL ops pointer means the buffer
 * can be moved by buflib at any time.
 *
 * Note that zero is not a valid handle, and will never be returned by
 * this function. However, this may change, and you should treat a zero
 * or negative return value as an allocation failure.
 */
int buflib_alloc_ex(struct buflib_context *ctx, size_t size,
                    struct buflib_callbacks *ops);

/**
 * \brief Attempt a maximum size allocation
 * \param ctx       Context to allocate from
 * \param size      Size of the allocation will be written here on success.
 * \param ops       Pointer to ops struct or NULL if no ops are needed.
 * \return Handle for the allocation (> 0) or a negative value on error.
 *
 * Buflib will attempt to compact and shrink other allocations as much as
 * possible and then allocate the largest contigous free area. Since this
 * will consume effectively *all* available memory, future allocations are
 * likely to fail.
 *
 * \note There is rarely any justification to use this with the core_alloc
 *       context due to the impact it has on the entire system. You should
 *       change your code if you think you need this. Of course, if you are
 *       using a private buflib context then this warning does not apply.
 */
int buflib_alloc_maximum(struct buflib_context *ctx,
                         size_t *size, struct buflib_callbacks *ops);

/**
 * \brief Reduce the size of a buflib allocation
 * \param ctx       Buflib context of the allocation
 * \param handle    Handle identifying the allocation
 * \param newstart  New start address. Must be within the current bounds
 *                  of the allocation, as returned by buflib_get_data().
 * \param new_size  New size of the buffer.
 * \return True if shrinking was successful; otherwise, returns false and
 *         does not modify the allocation.
 *
 * Shrinking always succeeds provided the new allocation is contained
 * within the current allocation. A failure is always a programming
 * error, so you need not check for it and in the future the failure
 * case may be changed to a panic or undefined behavior with no return
 * code.
 *
 * The new start address and size need not have any particular alignment,
 * however buflib cannot work with unaligned addresses so there is rarely
 * any purpose to creating unaligned allocations.
 *
 * Shrinking is typically done from a shrink_callback(), but can be done
 * at any time if you want to reduce the size of a buflib allocation.
 */
bool buflib_shrink(struct buflib_context *ctx, int handle,
                   void *newstart, size_t new_size);

/**
 * \brief Increment an allocation's pin count
 * \param ctx       Buflib context of the allocation
 * \param handle    Handle identifying the allocation
 *
 * The pin count acts like a reference count. Buflib will not attempt to
 * move any buffer with a positive pin count, nor invoke any move or sync
 * callbacks. Hence, when pinned, it is safe to hold pointers to a buffer
 * across yields or use them for I/O.
 *
 * Note that shrink callbacks can still be invoked for pinned handles.
 */
void buflib_pin(struct buflib_context *ctx, int handle);

/**
 * \brief Decrement an allocation's pin count
 * \param ctx       Buflib context of the allocation
 * \param handle    Handle identifying the allocation
 */
void buflib_unpin(struct buflib_context *ctx, int handle);

/**
 * \brief Return the pin count of an allocation
 * \param ctx       Buflib context of the allocation
 * \param handle    Handle identifying the allocation
 * \return Current pin count; zero means the handle is not pinned.
 */
unsigned buflib_pin_count(struct buflib_context *ctx, int handle);

/**
 * \brief Free an allocation and return its memory to the pool
 * \param ctx       Buflib context of the allocation
 * \param handle    Handle identifying the allocation
 * \return Always returns zero (zero is not a valid handle, so this can
 *         be used to invalidate the variable containing the handle).
 */
int buflib_free(struct buflib_context *context, int handle);

/**
 * \brief Get a pointer to the buffer for an allocation
 * \param ctx       Buflib context of the allocation
 * \param handle    Handle identifying the allocation
 * \return Pointer to the allocation's memory.
 *
 * Note that buflib can move allocations in order to free up space when
 * making new allocations. For this reason, it's unsafe to hold a pointer
 * to a buffer across a yield() or any other operation that can cause a
 * context switch. This includes any function that may block, and even
 * some functions that might not block -- eg. if a low priority thread
 * acquires a mutex, calling mutex_unlock() may trigger a context switch
 * to a higher-priority thread.
 *
 * buflib_get_data() is a very cheap operation, however, costing only
 * a few pointer lookups. Don't hesitate to use it extensively.
 *
 * If you need to hold a pointer across a possible context switch, pin
 * the handle with buflib_pin() to prevent the buffer from being moved.
 * This is required when doing I/O into buflib allocations, for example.
 */
#ifdef BUFLIB_DEBUG_GET_DATA
void *buflib_get_data(struct buflib_context *ctx, int handle);
#else
static inline void *buflib_get_data(struct buflib_context *ctx, int handle);
#endif

/**
 * \brief Shift allocations up to free space at the start of the pool
 * \param ctx       Context to operate on
 * \param size      Indicates number of bytes to free up, or 0 to free
 *                  up as much as possible. On return, the actual number
 *                  of bytes freed is written here.
 * \return Pointer to the start of the free area
 *
 * If `*size` is non-zero, the actual amount of space freed up might
 * be less than `*size`.
 *
 * \warning This will move data around in the pool without calling any
 *          move callbacks!
 * \warning This function is deprecated and will eventually be removed.
 */
void* buflib_buffer_out(struct buflib_context *ctx, size_t *size);

/**
 * \brief Shift allocations down into free space below the pool
 * \param ctx       Context to operate on
 * \param size      Number of bytes to add to the pool.
 *
 * This operation should only be used to return memory that was previously
 * taken from the pool with buflib_buffer_out(), by passing the same size
 * that you got from that function.
 *
 * \warning This will move data around in the pool without calling any
 *          move callbacks!
 * \warning This function is deprecated and will eventually be removed.
 */
void buflib_buffer_in(struct buflib_context *ctx, int size);

#ifdef BUFLIB_DEBUG_PRINT
/**
 * Return the number of blocks in the buffer, allocated or unallocated.
 *
 * Only available if BUFLIB_DEBUG_PRINT is defined.
 */
int buflib_get_num_blocks(struct buflib_context *ctx);

/**
 * Write a string describing the block at index block_num to the
 * provided buffer. The buffer will always be null terminated and
 * there is no provision to detect truncation. (A 40-byte buffer
 * is enough to contain any returned string.)
 *
 * Returns false if the block index is out of bounds, and writes
 * an empty string.
 *
 * Only available if BUFLIB_DEBUG_PRINT is defined.
 */
bool buflib_print_block_at(struct buflib_context *ctx, int block_num,
                           char *buf, size_t bufsize);
#endif

#ifdef BUFLIB_DEBUG_CHECK_VALID
/**
 * Check integrity of given buflib context
 */
void buflib_check_valid(struct buflib_context *ctx);
#endif

#if CONFIG_BUFLIB_BACKEND == BUFLIB_BACKEND_MEMPOOL
#include "buflib_mempool.h"
#endif

#ifndef BUFLIB_ALLOC_OVERHEAD
# define BUFLIB_ALLOC_OVERHEAD 0
#endif

#endif /* _BUFLIB_H_ */