blob: 8a5a72f4cc885cfa4507bb6449af0eeba92c4afa [file]
/**
* Copyright (C) Advanced Micro Devices, Inc. All rights reserved.
*
* You may not use this software and documentation (if any) (collectively, the
* "Materials") except in compliance with the terms and conditions of the
* Software License Agreement included with the Materials or otherwise as set
* forth in writing and signed by you and an authorized signatory of AMD.
*
* If you do not have a copy of the Software License Agreement, contact your AMD
* representative for a copy. You agree that you will not reverse engineer or
* decompile the Materials, in whole or in part, except as allowed by applicable
* law.
*
* THE MATERIALS ARE DISTRIBUTED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
* REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
*/
#include "dc_memory_pool.h"
#include <linux/atomic.h>
enum {
DC_MEMORY_POOL_SENTINEL = -1,
DC_MEMORY_POOL_ACQUIRED = -2,
DC_MEMORY_POOL_RELEASED = -3,
// Correct autoincrement for negative numbers
DC_MEMORY_POOL_ENUM_SIZE_IMPL,
DC_MEMORY_POOL_ENUM_SIZE = 1 - DC_MEMORY_POOL_ENUM_SIZE_IMPL,
};
// Align everything to size of page to avoid false sharing
struct dc_memory_pool_page {
__aligned(PAGE_SIZE) char _dummy[PAGE_SIZE];
};
// Generation counter protects against ABA-problem
union index_t {
struct {
int32_t value;
uint32_t generation;
} s;
int64_t raw;
};
struct dc_memory_pool {
// Values and pointers constant after initialization, can share page
__aligned(PAGE_SIZE) size_t size;
size_t capacity;
void *unaligned_pool;
void *unaligned_memory;
struct dc_memory_pool_page *memory;
atomic_t *free_list;
// Updated every operation, use separate page to avoid false sharing
__aligned(PAGE_SIZE) atomic64_t free_head; // index_t
char _reserved2[PAGE_SIZE - sizeof(atomic64_t)];
};
static_assert(
sizeof(struct dc_memory_pool) == 2 * PAGE_SIZE,
"dc_memory_pool size should be 2 pages"
);
static_assert(
offsetof(struct dc_memory_pool, free_head) == PAGE_SIZE,
"dc_memory_pool free_head should be aligned to page"
);
static size_t divide_ceiling(size_t x, size_t d)
{
return (x + d - 1) / d;
}
static size_t round_up_to_multiple(size_t x, size_t m)
{
return divide_ceiling(x, m) * m;
}
static size_t size_in_pages(size_t x)
{
return divide_ceiling(x, PAGE_SIZE);
}
static void *page_align_up(void *p)
{
intptr_t i = (intptr_t)p;
i = (intptr_t)round_up_to_multiple((size_t)i, PAGE_SIZE);
return (void *)i;
}
__must_check struct dc_memory_pool *dc_memory_pool_create(size_t size,
size_t capacity)
{
if (!size || !capacity)
return NULL;
// Limited by split between size and generation in index_t
if (capacity >= (uint32_t)-DC_MEMORY_POOL_ENUM_SIZE)
return NULL;
// uint32_t because that's what alloc functions take
const uint32_t block_pages = (uint32_t)size_in_pages(size);
const uint32_t lines = (uint32_t)capacity * block_pages;
const uint32_t padded_struct_size =
sizeof(struct dc_memory_pool) + PAGE_SIZE;
void *unaligned_pool = kzalloc(padded_struct_size, GFP_KERNEL);
if (!unaligned_pool)
return NULL;
struct dc_memory_pool *pool = page_align_up(unaligned_pool);
// Compound literals create temporary in debug driver, exceeding stack size
pool->size = size;
pool->capacity = capacity;
pool->unaligned_pool = unaligned_pool;
pool->unaligned_memory = kcalloc(lines + 1, PAGE_SIZE, GFP_KERNEL);
pool->memory = page_align_up(pool->unaligned_memory);
pool->free_list = kcalloc((uint32_t)capacity, sizeof(atomic_t), GFP_KERNEL);
pool->free_head = (atomic64_t)ATOMIC_INIT(0);
if (!pool->unaligned_memory || !pool->free_list) {
dc_memory_pool_destroy(pool);
return NULL;
}
for (int32_t i = 0; i < (int32_t)capacity; i++)
atomic_set_release(&pool->free_list[i], i + 1);
atomic_set_release(&pool->free_list[capacity - 1],
DC_MEMORY_POOL_SENTINEL);
return pool;
}
void dc_memory_pool_destroy(struct dc_memory_pool *pool)
{
if (!pool)
return;
kfree(pool->free_list);
kfree(pool->unaligned_memory);
kfree(pool->unaligned_pool);
}
static int32_t dc_memory_pool_get_index(const struct dc_memory_pool *pool,
const void *memory)
{
if (!pool || !memory) {
ASSERT(false);
return DC_MEMORY_POOL_SENTINEL;
}
// Direct pointer arithmetic would be UB if called by false `owns()`
if ((uintptr_t)memory < (uintptr_t)pool->memory)
return DC_MEMORY_POOL_SENTINEL;
const uintptr_t distance = (uintptr_t)memory - (uintptr_t)pool->memory;
const size_t block_size = size_in_pages(pool->size) * PAGE_SIZE;
const size_t i = (size_t)distance / block_size;
if (distance % (uintptr_t)block_size != 0)
return DC_MEMORY_POOL_SENTINEL;
if (i >= pool->capacity)
return DC_MEMORY_POOL_SENTINEL;
return (int32_t)i;
}
static void *dc_memory_pool_get_page(const struct dc_memory_pool *pool,
int32_t index)
{
if (!pool) {
ASSERT(false);
return NULL;
}
if (index < 0 || index >= (int32_t)pool->capacity) {
ASSERT(false);
return NULL;
}
return &pool->memory[(size_t)index * size_in_pages(pool->size)];
}
__must_check void *dc_memory_pool_acquire(struct dc_memory_pool *pool)
{
if (!pool) {
ASSERT(false);
return NULL;
}
// CAS-atomic `out = head; head = head->next;`
union index_t old_head = {
.raw = atomic64_read_acquire(&pool->free_head),
};
union index_t new_head = {
.raw = 0,
};
int32_t i = 0;
do {
i = old_head.s.value;
if (i == DC_MEMORY_POOL_SENTINEL)
return NULL;
new_head = (union index_t){
.s.value = atomic_read_acquire(&pool->free_list[i]),
.s.generation = old_head.s.generation + 1,
};
} while (!atomic64_try_cmpxchg(&pool->free_head, &old_head.raw,
new_head.raw));
atomic_set_release(&pool->free_list[i], DC_MEMORY_POOL_ACQUIRED);
return dc_memory_pool_get_page(pool, i);
}
void dc_memory_pool_release(struct dc_memory_pool *pool, void *memory)
{
if (!dc_memory_pool_owns(pool, memory)) {
// Likely acquired in different pool or (racing?) double free
ASSERT(false);
return;
}
const int32_t i = dc_memory_pool_get_index(pool, memory);
if (atomic_xchg(&pool->free_list[i], DC_MEMORY_POOL_RELEASED) !=
DC_MEMORY_POOL_ACQUIRED) {
// Double free from two racing threads, use krefs to sync
ASSERT(false);
return;
}
// CAS-atomic `in->next = head; head = in;`
union index_t old_head = {
.raw = atomic64_read_acquire(&pool->free_head),
};
union index_t new_head = {
.raw = 0,
};
do {
atomic_set_release(&pool->free_list[i], old_head.s.value);
new_head = (union index_t){
.s.value = i,
.s.generation = old_head.s.generation + 1,
};
} while (!atomic64_try_cmpxchg(&pool->free_head, &old_head.raw,
new_head.raw));
}
__must_check bool dc_memory_pool_owns(const struct dc_memory_pool *pool,
const void *memory)
{
const int32_t i = dc_memory_pool_get_index(pool, memory);
if (i < 0)
return false;
if (atomic_read_acquire(&pool->free_list[i]) != DC_MEMORY_POOL_ACQUIRED)
return false;
return true;
}
__must_check size_t dc_memory_pool_size(const struct dc_memory_pool *pool)
{
ASSERT(pool);
return pool->size;
}
__must_check size_t dc_memory_pool_capacity(const struct dc_memory_pool *pool)
{
ASSERT(pool);
return pool->capacity;
}