/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2023 by William Wilgus * * 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. * ****************************************************************************/ /* [chunk_alloc] allows arrays (or any data) to be allocated in smaller chunks */ #include "chunk_alloc.h" #include "panic.h" //#define LOGF_ENABLE #include "logf.h" #ifndef MAX #define MAX(a,b) ((a) > (b) ? (a) : (b)) #endif #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif /*Note on offsets returned * variable data will have variable offsets you need to * store these as [chunk_alloc] doesn't keep track * if you have a fixed size for each alloc you can do * offset / sizeof(data) or index * sizeof(data) to convert */ struct chunk_alloc { int handle; /* data handle of buflib allocated bytes */ size_t max_start_offset; /* start of last allocation */ }; #define CHUNK_ARRSZ(n) (sizeof(struct chunk_alloc) * n) static struct chunk_alloc* get_chunk_array(struct buflib_context *ctx, int handle) { return (struct chunk_alloc*)buflib_get_data_pinned(ctx, handle); } static void put_chunk_array(struct buflib_context *ctx, struct chunk_alloc *data) { buflib_put_data_pinned(ctx, data); } /* shrink or grow chunk allocation * chunks greater than max_chunks will be freed * new allocs will default to chunk_size * previous or current chunk < max_chunks will NOT be changed * Returns true on success false on failure */ bool chunk_realloc(struct chunk_alloc_header *hdr, size_t chunk_size, size_t max_chunks) { struct buflib_context *ctx = hdr->context; struct chunk_alloc *new_chunk = NULL; struct chunk_alloc *old_chunk; size_t min_chunk = 1; int new_handle = 0; if (max_chunks > hdr->count) /* need room for more chunks */ { new_handle = buflib_alloc(ctx, CHUNK_ARRSZ(max_chunks)); if (new_handle <= 0) { logf("%s Error OOM %ld chunks", __func__, max_chunks); return false; } new_chunk = get_chunk_array(ctx, new_handle); /* ensure all chunks data is zeroed, we depend on it */ memset(new_chunk, 0, CHUNK_ARRSZ(max_chunks)); } if (hdr->chunk_handle > 0) /* handle existing chunk */ { logf("%s %ld chunks => %ld chunks", __func__, hdr->count, max_chunks); old_chunk = get_chunk_array(ctx, hdr->chunk_handle); if (new_chunk != NULL) /* copy any valid old chunks to new */ { min_chunk = MIN(max_chunks, hdr->current + 1); logf("%s copying %ld chunks", __func__, min_chunk); memcpy(new_chunk, old_chunk, CHUNK_ARRSZ(min_chunk)); put_chunk_array(ctx, new_chunk); } /* free any chunks that no longer fit */ for (size_t i = max_chunks; i <= hdr->current; i++) { logf("%s discarding chunk[%ld]", __func__, i); buflib_free(ctx, old_chunk[i].handle); } put_chunk_array(ctx, old_chunk); if (max_chunks < hdr->count && max_chunks > 0) { logf("%s shrink existing chunk array", __func__); min_chunk = max_chunks; buflib_shrink(ctx, hdr->chunk_handle, old_chunk, CHUNK_ARRSZ(max_chunks)); new_handle = hdr->chunk_handle; } else { logf("%s free existing chunk array", __func__); buflib_free(ctx, hdr->chunk_handle); /* free old chunk array */ } hdr->current = min_chunk - 1; } else { logf("chunk_alloc_init %ld chunks", hdr->count); } hdr->cached_chunk.handle = 0; /* reset last chunk to force new lookup */ hdr->chunk_handle = new_handle; hdr->chunk_size = chunk_size; hdr->count = max_chunks; return true; } /* frees all allocations */ void chunk_alloc_free(struct chunk_alloc_header *hdr) { logf("%s freeing %ld chunks", __func__, hdr->count); chunk_realloc(hdr, 0, 0); } /* initialize chunk allocator * chunk_size specifies initial size of each chunk * a single allocation CAN be larger than this * max_chunks * chunk_size is the total expected size of the buffer * more data will not be allocated once all chunks used * Returns true on success or false on failure */ bool chunk_alloc_init(struct chunk_alloc_header *hdr, struct buflib_context *ctx, size_t chunk_size, size_t max_chunks) { /* initialize header */ memset(hdr, 0, sizeof(struct chunk_alloc_header)); hdr->context = ctx; return chunk_realloc(hdr, chunk_size, max_chunks); } /* shrink current chunk to size used */ static void finalize(struct chunk_alloc_header *hdr, struct chunk_alloc *chunk_array) { /* Note calling functions check if chunk_bytes_free > 0 */ size_t idx = hdr->current; if (idx >= hdr->count) return; int handle = chunk_array[idx].handle; struct buflib_context *ctx = hdr->context; hdr->chunk_bytes_total -= hdr->chunk_bytes_free; hdr->chunk_bytes_free = 0; buflib_shrink(ctx, handle, NULL, hdr->chunk_bytes_total); logf("%s shrink hdr idx[%ld] offset[%ld]: new size: %ld", __func__, idx, chunk_array[idx].max_start_offset, hdr->chunk_bytes_total); } /* shrink current chunk to size used */ void chunk_alloc_finalize(struct chunk_alloc_header *hdr) { if (hdr->chunk_bytes_free > 0) { struct chunk_alloc *chunk; chunk = get_chunk_array(hdr->context, hdr->chunk_handle); finalize(hdr, chunk); put_chunk_array(hdr->context, chunk); } } /* allocates from current chunk if size > bytes remaining * current chunk shrinks to size used and a new chunk is allocated * returns virtual offset on success or CHUNK_ALLOC_INVALID on error */ size_t chunk_alloc(struct chunk_alloc_header *hdr, size_t size) { size_t virtual_offset = CHUNK_ALLOC_INVALID; size_t idx = hdr->current; int handle = hdr->chunk_handle; struct buflib_context *ctx = hdr->context; struct chunk_alloc *chunk = get_chunk_array(ctx, handle); while (size > 0) { if (idx >= hdr->count) { logf("%s Error OOM -- out of chunks", __func__); break; /* Out of chunks */ } hdr->current = idx; if(chunk[idx].handle <= 0) /* need to make an new allocation */ { size_t new_alloc_size = MAX(size, hdr->chunk_size); chunk[idx].handle = buflib_alloc(ctx, new_alloc_size); if (chunk[idx].handle <= 0) { logf("%s Error OOM", __func__); break; /* OOM */ } hdr->chunk_bytes_total = new_alloc_size; hdr->chunk_bytes_free = new_alloc_size; chunk[idx].max_start_offset = (idx > 0 ? (chunk[idx - 1].max_start_offset) : 0); logf("%s New alloc hdr idx[%ld] offset[%ld]: available: %ld", __func__, idx, chunk[idx].max_start_offset, new_alloc_size); } if(size <= hdr->chunk_bytes_free) /* request will fit */ { virtual_offset = chunk[idx].max_start_offset; chunk[idx].max_start_offset += size; hdr->chunk_bytes_free -= size; if (hdr->cached_chunk.handle == chunk[idx].handle) hdr->cached_chunk.max_offset = chunk[idx].max_start_offset; /*logf("%s hdr idx[%ld] offset[%ld] size: %ld", __func__, idx, offset, size);*/ break; /* Success */ } else if (hdr->chunk_bytes_free > 0) /* shrink the current chunk */ { finalize(hdr, chunk); } idx++; } put_chunk_array(ctx, chunk); return virtual_offset; } /* retrieves chunk given virtual offset * returns actual offset */ static size_t chunk_get_at_offset(struct chunk_alloc_header *hdr, size_t offset) { if ((hdr->cached_chunk.handle > 0) && (offset >= hdr->cached_chunk.min_offset) && (offset < hdr->cached_chunk.max_offset)) { /* convert virtual offset to real internal offset */ return offset - hdr->cached_chunk.min_offset; } /* chunk isn't cached perform new lookup */ struct chunk_alloc *chunk = get_chunk_array(hdr->context, hdr->chunk_handle); logf("%s search for offset[%ld]", __func__, offset); for (size_t idx = hdr->current; idx < hdr->count; idx--) { size_t min_offset = (idx == 0 ? 0 : chunk[idx - 1].max_start_offset); if (offset < chunk[idx].max_start_offset && offset >= min_offset) { logf("%s found hdr idx[%ld] min offset[%ld] max offset[%ld]", __func__, idx, min_offset, chunk[idx].max_start_offset); /* store found chunk */ hdr->cached_chunk.handle = chunk[idx].handle; hdr->cached_chunk.max_offset = chunk[idx].max_start_offset; hdr->cached_chunk.min_offset = min_offset; put_chunk_array(hdr->context, chunk); /* convert virtual offset to real internal offset */ return offset - hdr->cached_chunk.min_offset; } } panicf("%s Error offset %d does not exist", __func__, (unsigned int)offset); return CHUNK_ALLOC_INVALID; } /* get data - buffer chunk can't be moved while pinned * multiple calls will up pin count so put should be called for each * Returns data at offset */ void* chunk_get_data(struct chunk_alloc_header *hdr, size_t offset) { size_t real = chunk_get_at_offset(hdr, offset); logf("%s offset: %ld real: %ld", __func__, offset, real); return buflib_get_data_pinned(hdr->context, hdr->cached_chunk.handle) + real; } /* release a pinned buffer, chunk can't be moved till pin count == 0 */ void chunk_put_data(struct chunk_alloc_header *hdr, void* data, size_t offset) { size_t real = chunk_get_at_offset(hdr, offset); buflib_put_data_pinned(hdr->context, data - real); }