diff --git a/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_cs.c b/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_cs.c index 0ad6ae220d..7679ccf13a 100644 --- a/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_cs.c +++ b/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_cs.c @@ -84,70 +84,168 @@ struct RedoxCsWaitWire { uint64_t completed_seqno; /* response: highest completed seqno */ }; -/* Acquire a batch BO of the given size from the ring-buffer pool, - * or allocate a fresh one. Returns NULL on allocation failure. +/* ---- Batch BO pool ----------------------------------------------------- */ + +/* The pool keeps one BO per power-of-2 size class, covering + * cls 0: 4 KiB (4096 = 1 << 12) + * cls 1: 8 KiB (8192 = 1 << 13) + * ... + * cls 7: 512 KiB (524288 = 1 << 19) * - * The pool keeps one BO per power-of-2 size class. Larger requests - * are always freshly allocated (the pool only caches sizes 4 KiB to - * 512 KiB). On release, the BO is recycled back to its size class - * slot only if the slot is empty. + * Requests smaller than 4 KiB are clamped up to class 0 so that tiny + * batches still benefit from recycling. Requests larger than 512 KiB + * are always freshly allocated (never pooled) and destroyed on release. + * + * Every BO placed in a pool slot is sized to the FULL class capacity + * (4 KiB << cls), never the exact request size. This guarantees any + * cached entry can serve any request that maps to that class — the + * root cause of the previous heap-overflow bug was caching a + * request-sized BO and later serving it to a larger request in the + * same class. + */ + +/* 4 KiB = 1 << REDOX_DRM_BATCH_POOL_BASE_BITS. */ +#define REDOX_DRM_BATCH_POOL_BASE_BITS 12 + +/* Compute the batch pool size class for byte_count. + * + * Returns a class index in [0, REDOX_DRM_BATCH_POOL_SIZE_CLASSES-1] + * for poolable sizes, or REDOX_DRM_BATCH_POOL_SIZE_CLASSES if the + * request exceeds the largest class (512 KiB) and must be freshly + * allocated without pooling. + * + * Requests of 0..4096 bytes map to class 0. + */ +static unsigned +batch_bo_size_class(size_t byte_count) +{ + /* Clamp sub-4-KiB requests up to the smallest class. byte_count + * is guaranteed > 0 by all callers, but a 0 argument is handled + * safely (returns class 0). */ + if (byte_count <= (size_t)4096) + return 0; + + /* ceil(log2(byte_count)): the number of bits needed to represent + * (byte_count - 1), which is the bit-position of the highest set + * bit of (byte_count - 1) plus one. This is the smallest power + * of two >= byte_count. */ + unsigned bits = 0; + size_t v = byte_count - 1; + while (v > 0) { + bits++; + v >>= 1; + } + + if (bits < REDOX_DRM_BATCH_POOL_BASE_BITS) + return 0; + return bits - REDOX_DRM_BATCH_POOL_BASE_BITS; +} + +/* Capacity (in bytes) of a given size class. */ +static size_t +batch_bo_class_capacity(unsigned cls) +{ + return (size_t)4096 << cls; +} + +/* Acquire a batch BO of at least byte_count bytes from the ring-buffer + * pool, or allocate a fresh one. Returns NULL on allocation failure. + * + * Pool-served BOs are sized to the full class capacity (4 KiB << cls). + * Oversized requests (> 512 KiB) bypass the pool entirely and get an + * exact-sized fresh allocation. */ static struct pipe_resource * batch_bo_acquire(struct redox_drm_winsys *rws, size_t byte_count) { - if (byte_count == 0 || !rws) - return NULL; + if (byte_count == 0 || !rws) + return NULL; - if (byte_count > (1U << (REDOX_DRM_BATCH_POOL_SIZE_CLASSES - 1 + 12))) - return NULL; + unsigned cls = batch_bo_size_class(byte_count); - unsigned cls = 0; - size_t sz = byte_count - 1; - while (sz > 0) { - cls++; - sz >>= 1; - } - if (cls >= 12) - cls = 12; - if (cls >= REDOX_DRM_BATCH_POOL_SIZE_CLASSES) - return NULL; + /* Requests exceeding the largest pool class (512 KiB) always get + * a fresh, exact-sized allocation — they are never pooled. */ + if (cls >= REDOX_DRM_BATCH_POOL_SIZE_CLASSES) { + struct pipe_resource templ = {0}; + templ.width0 = (uint32_t)byte_count; + templ.height0 = 1; + templ.depth0 = 1; + templ.target = PIPE_BUFFER; + return redox_drm_bo_create(rws, &templ); + } - if (rws->batch_pool[cls]) { - struct pipe_resource *pr = rws->batch_pool[cls]; - rws->batch_pool[cls] = NULL; - return pr; - } + size_t capacity = batch_bo_class_capacity(cls); - struct pipe_resource templ = {0}; - templ.width0 = byte_count; - templ.height0 = 1; - templ.depth0 = 1; - templ.target = PIPE_BUFFER; - return redox_drm_bo_create(rws, &templ); + /* Try the per-class cache slot under the pool lock. */ + struct pipe_resource *cached = NULL; + mtx_lock(&rws->batch_pool_mutex); + if (rws->batch_pool[cls]) { + cached = rws->batch_pool[cls]; + rws->batch_pool[cls] = NULL; + } + mtx_unlock(&rws->batch_pool_mutex); + + if (cached) { + /* Defensive: verify the cached BO can actually hold the + * request. A correctly maintained pool always satisfies + * this, but corruption or a logic error must never lead to + * an undersized write (heap overflow). */ + if (cached->width0 >= byte_count) + return cached; + + debug_printf("redox: batch pool: undersized cached BO " + "(width0=%u, need=%zu) — destroying\n", + cached->width0, byte_count); + redox_drm_bo_destroy(rws, cached); + /* Fall through to allocate a fresh BO. */ + } + + /* Allocate a fresh BO at the full class capacity so the recycled + * entry can serve any smaller request that maps to this class. */ + struct pipe_resource templ = {0}; + templ.width0 = (uint32_t)capacity; + templ.height0 = 1; + templ.depth0 = 1; + templ.target = PIPE_BUFFER; + return redox_drm_bo_create(rws, &templ); } +/* Release a batch BO back to the ring-buffer pool, or destroy it if it + * cannot be cached. + * + * Only BOs whose width0 exactly matches a class capacity (4 KiB << cls) + * are recyclable. Oversized fresh allocations and BOs with non-standard + * widths are destroyed rather than leaked. + */ static void batch_bo_release(struct redox_drm_winsys *rws, struct pipe_resource *pr) { - if (!pr || !rws) - return; + if (!pr || !rws) + return; - size_t sz = pr->width0; - if (sz == 0 || sz > (1U << (REDOX_DRM_BATCH_POOL_SIZE_CLASSES - 1 + 12))) - return; - unsigned cls = 0; - size_t s = sz - 1; - while (s > 0) { - cls++; - s >>= 1; - } - if (cls >= 12) - cls = 12; - if (cls >= REDOX_DRM_BATCH_POOL_SIZE_CLASSES) - return; - if (rws->batch_pool[cls]) - return; - rws->batch_pool[cls] = pr; + unsigned cls = batch_bo_size_class(pr->width0); + + /* Only BOs whose width0 matches the full class capacity are + * recyclable. Oversized fresh allocations (cls >= SIZE_CLASSES) + * and BOs whose width0 doesn't align to a class boundary are + * destroyed instead of being cached — caching them would risk + * undersized serves on the next acquire. */ + if (cls >= REDOX_DRM_BATCH_POOL_SIZE_CLASSES || + pr->width0 != batch_bo_class_capacity(cls)) { + redox_drm_bo_destroy(rws, pr); + return; + } + + mtx_lock(&rws->batch_pool_mutex); + if (rws->batch_pool[cls]) { + /* Slot already occupied — destroy the redundant BO rather + * than leaking it. */ + mtx_unlock(&rws->batch_pool_mutex); + redox_drm_bo_destroy(rws, pr); + return; + } + rws->batch_pool[cls] = pr; + mtx_unlock(&rws->batch_pool_mutex); } uint64_t diff --git a/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_winsys.c b/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_winsys.c index c987533977..492a1613a1 100644 --- a/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_winsys.c +++ b/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_winsys.c @@ -401,6 +401,8 @@ redox_drm_initialize(struct redox_drm_winsys *rws) for (unsigned i = 0; i < REDOX_DRM_BATCH_POOL_SIZE_CLASSES; i++) rws->batch_pool[i] = NULL; + (void) mtx_init(&rws->batch_pool_mutex, mtx_plain); + rws->cs = redox_drm_cs_create(rws); if (!rws->cs) { debug_printf("redox: failed to create cs state\n"); @@ -415,14 +417,16 @@ redox_drm_destroy(struct redox_drm_winsys *rws) if (!rws) return; - for (unsigned i = 0; i < REDOX_DRM_BATCH_POOL_SIZE_CLASSES; i++) { - if (rws->batch_pool[i]) { - redox_drm_bo_destroy(rws, rws->batch_pool[i]); - rws->batch_pool[i] = NULL; - } - } + for (unsigned i = 0; i < REDOX_DRM_BATCH_POOL_SIZE_CLASSES; i++) { + if (rws->batch_pool[i]) { + redox_drm_bo_destroy(rws, rws->batch_pool[i]); + rws->batch_pool[i] = NULL; + } + } - if (rws->cs) { + mtx_destroy(&rws->batch_pool_mutex); + + if (rws->cs) { redox_drm_cs_destroy(rws->cs); rws->cs = NULL; } diff --git a/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_winsys.h b/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_winsys.h index 8d97d138d4..64ff75242f 100644 --- a/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_winsys.h +++ b/local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/redox_drm_winsys.h @@ -11,6 +11,8 @@ #include "util/u_inlines.h" #include "winsys/sw_winsys.h" +#include "c11/threads.h" /* mtx_t for batch_pool_mutex */ + struct redox_drm_cs; struct redox_drm_surface; struct pipe_resource; @@ -32,6 +34,12 @@ struct redox_drm_winsys { */ struct redox_drm_cs *cs; + /* Protects batch_pool[] against concurrent acquire/release from + * multiple pipe_contexts sharing this winsys. Mesa drivers may + * create several contexts per screen, and each can flush (submit) + * independently from different threads. */ + mtx_t batch_pool_mutex; + /* Batch BO ring-buffer pool. batch_pool[i] caches the largest * recently-recycled BO whose size class == i. Acquire pulls from * the pool if a matching size class is cached; release puts a BO