blob: 2fcdff6f0cc20883261674749a12b1601c613bc4 [file] [log] [blame]
/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "qdf_flex_mem.h"
#include "qdf_list.h"
#include "qdf_lock.h"
#include "qdf_mem.h"
#include "qdf_module.h"
#include "qdf_talloc.h"
#include "qdf_trace.h"
#include "qdf_util.h"
static struct qdf_flex_mem_segment *
qdf_flex_mem_seg_alloc(struct qdf_flex_mem_pool *pool)
{
struct qdf_flex_mem_segment *seg;
size_t total_size = sizeof(struct qdf_flex_mem_segment) +
pool->item_size * QDF_FM_BITMAP_BITS;
seg = qdf_talloc(pool, total_size);
if (!seg)
return NULL;
seg->dynamic = true;
seg->bytes = (uint8_t *)(seg + 1);
seg->used_bitmap = 0;
qdf_list_insert_back(&pool->seg_list, &seg->node);
return seg;
}
void qdf_flex_mem_init(struct qdf_flex_mem_pool *pool)
{
int i;
qdf_spinlock_create(&pool->lock);
for (i = 0; i < pool->reduction_limit; i++)
qdf_flex_mem_seg_alloc(pool);
}
qdf_export_symbol(qdf_flex_mem_init);
void qdf_flex_mem_deinit(struct qdf_flex_mem_pool *pool)
{
struct qdf_flex_mem_segment *seg, *next;
qdf_spinlock_destroy(&pool->lock);
qdf_list_for_each_del(&pool->seg_list, seg, next, node) {
QDF_BUG(!seg->used_bitmap);
if (seg->used_bitmap)
continue;
qdf_list_remove_node(&pool->seg_list, &seg->node);
if (seg->dynamic)
qdf_tfree(seg);
}
}
qdf_export_symbol(qdf_flex_mem_deinit);
static void *__qdf_flex_mem_alloc(struct qdf_flex_mem_pool *pool)
{
struct qdf_flex_mem_segment *seg;
qdf_list_for_each(&pool->seg_list, seg, node) {
int index;
void *ptr;
index = qdf_ffz(seg->used_bitmap);
if (index < 0)
continue;
QDF_BUG(index < QDF_FM_BITMAP_BITS);
seg->used_bitmap ^= (QDF_FM_BITMAP)1 << index;
ptr = &seg->bytes[index * pool->item_size];
qdf_mem_zero(ptr, pool->item_size);
return ptr;
}
seg = qdf_flex_mem_seg_alloc(pool);
if (!seg)
return NULL;
seg->used_bitmap = 1;
return seg->bytes;
}
void *qdf_flex_mem_alloc(struct qdf_flex_mem_pool *pool)
{
void *ptr;
QDF_BUG(pool);
if (!pool)
return NULL;
qdf_spin_lock_bh(&pool->lock);
ptr = __qdf_flex_mem_alloc(pool);
qdf_spin_unlock_bh(&pool->lock);
return ptr;
}
qdf_export_symbol(qdf_flex_mem_alloc);
static void qdf_flex_mem_seg_free(struct qdf_flex_mem_pool *pool,
struct qdf_flex_mem_segment *seg)
{
if (!seg->dynamic)
return;
if (qdf_list_size(&pool->seg_list) <= pool->reduction_limit)
return;
qdf_list_remove_node(&pool->seg_list, &seg->node);
qdf_tfree(seg);
}
static void __qdf_flex_mem_free(struct qdf_flex_mem_pool *pool, void *ptr)
{
struct qdf_flex_mem_segment *seg;
void *low_addr;
void *high_addr;
unsigned long index;
qdf_list_for_each(&pool->seg_list, seg, node) {
low_addr = seg->bytes;
high_addr = low_addr + pool->item_size * QDF_FM_BITMAP_BITS;
if (ptr < low_addr || ptr > high_addr)
continue;
index = (ptr - low_addr) / pool->item_size;
QDF_BUG(index < QDF_FM_BITMAP_BITS);
seg->used_bitmap ^= (QDF_FM_BITMAP)1 << index;
if (!seg->used_bitmap)
qdf_flex_mem_seg_free(pool, seg);
return;
}
QDF_DEBUG_PANIC("Failed to find pointer in segment pool");
}
void qdf_flex_mem_free(struct qdf_flex_mem_pool *pool, void *ptr)
{
QDF_BUG(pool);
if (!pool)
return;
QDF_BUG(ptr);
if (!ptr)
return;
qdf_spin_lock_bh(&pool->lock);
__qdf_flex_mem_free(pool, ptr);
qdf_spin_unlock_bh(&pool->lock);
}
qdf_export_symbol(qdf_flex_mem_free);