433 lines
10 KiB
C
433 lines
10 KiB
C
/*
|
|
* memrar_allocator 1.0: An allocator for Intel RAR.
|
|
*
|
|
* Copyright (C) 2010 Intel Corporation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of version 2 of the GNU General
|
|
* Public License as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be
|
|
* useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program; if not, write to the Free
|
|
* Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
* The full GNU General Public License is included in this
|
|
* distribution in the file called COPYING.
|
|
*
|
|
*
|
|
* ------------------------------------------------------------------
|
|
*
|
|
* This simple allocator implementation provides a
|
|
* malloc()/free()-like interface for reserving space within a
|
|
* previously reserved block of memory. It is not specific to
|
|
* any hardware, nor is it coupled with the lower level paging
|
|
* mechanism.
|
|
*
|
|
* The primary goal of this implementation is to provide a means
|
|
* to partition an arbitrary block of memory without actually
|
|
* accessing the memory or incurring any hardware side-effects
|
|
* (e.g. paging). It is, in effect, a bookkeeping mechanism for
|
|
* buffers.
|
|
*/
|
|
|
|
|
|
#include "memrar_allocator.h"
|
|
#include <linux/slab.h>
|
|
#include <linux/bug.h>
|
|
#include <linux/kernel.h>
|
|
|
|
|
|
struct memrar_allocator *memrar_create_allocator(unsigned long base,
|
|
size_t capacity,
|
|
size_t block_size)
|
|
{
|
|
struct memrar_allocator *allocator = NULL;
|
|
struct memrar_address_ranges *first_node = NULL;
|
|
|
|
/*
|
|
* Make sure the base address is aligned on a block_size
|
|
* boundary.
|
|
*
|
|
* @todo Is this necessary?
|
|
*/
|
|
/* base = ALIGN(base, block_size); */
|
|
|
|
/* Validate parameters.
|
|
*
|
|
* Make sure we can allocate the entire memory space. Zero
|
|
* capacity or block size are obviously invalid.
|
|
*/
|
|
if (base == 0
|
|
|| capacity == 0
|
|
|| block_size == 0
|
|
|| ULONG_MAX - capacity < base
|
|
|| capacity < block_size)
|
|
return allocator;
|
|
|
|
/*
|
|
* There isn't much point in creating a memory allocator that
|
|
* is only capable of holding one block but we'll allow it,
|
|
* and issue a diagnostic.
|
|
*/
|
|
WARN(capacity < block_size * 2,
|
|
"memrar: Only one block available to allocator.\n");
|
|
|
|
allocator = kmalloc(sizeof(*allocator), GFP_KERNEL);
|
|
|
|
if (allocator == NULL)
|
|
return allocator;
|
|
|
|
mutex_init(&allocator->lock);
|
|
allocator->base = base;
|
|
|
|
/* Round the capacity down to a multiple of block_size. */
|
|
allocator->capacity = (capacity / block_size) * block_size;
|
|
|
|
allocator->block_size = block_size;
|
|
|
|
allocator->largest_free_area = allocator->capacity;
|
|
|
|
/* Initialize the handle and free lists. */
|
|
INIT_LIST_HEAD(&allocator->allocated_list.list);
|
|
INIT_LIST_HEAD(&allocator->free_list.list);
|
|
|
|
first_node = kmalloc(sizeof(*first_node), GFP_KERNEL);
|
|
if (first_node == NULL) {
|
|
kfree(allocator);
|
|
allocator = NULL;
|
|
} else {
|
|
/* Full range of blocks is available. */
|
|
first_node->range.begin = base;
|
|
first_node->range.end = base + allocator->capacity;
|
|
list_add(&first_node->list,
|
|
&allocator->free_list.list);
|
|
}
|
|
|
|
return allocator;
|
|
}
|
|
|
|
void memrar_destroy_allocator(struct memrar_allocator *allocator)
|
|
{
|
|
/*
|
|
* Assume that the memory allocator lock isn't held at this
|
|
* point in time. Caller must ensure that.
|
|
*/
|
|
|
|
struct memrar_address_ranges *pos = NULL;
|
|
struct memrar_address_ranges *n = NULL;
|
|
|
|
if (allocator == NULL)
|
|
return;
|
|
|
|
mutex_lock(&allocator->lock);
|
|
|
|
/* Reclaim free list resources. */
|
|
list_for_each_entry_safe(pos,
|
|
n,
|
|
&allocator->free_list.list,
|
|
list) {
|
|
list_del(&pos->list);
|
|
kfree(pos);
|
|
}
|
|
|
|
mutex_unlock(&allocator->lock);
|
|
|
|
kfree(allocator);
|
|
}
|
|
|
|
unsigned long memrar_allocator_alloc(struct memrar_allocator *allocator,
|
|
size_t size)
|
|
{
|
|
struct memrar_address_ranges *pos = NULL;
|
|
|
|
size_t num_blocks;
|
|
unsigned long reserved_bytes;
|
|
|
|
/*
|
|
* Address of allocated buffer. We assume that zero is not a
|
|
* valid address.
|
|
*/
|
|
unsigned long addr = 0;
|
|
|
|
if (allocator == NULL || size == 0)
|
|
return addr;
|
|
|
|
/* Reserve enough blocks to hold the amount of bytes requested. */
|
|
num_blocks = DIV_ROUND_UP(size, allocator->block_size);
|
|
|
|
reserved_bytes = num_blocks * allocator->block_size;
|
|
|
|
mutex_lock(&allocator->lock);
|
|
|
|
if (reserved_bytes > allocator->largest_free_area) {
|
|
mutex_unlock(&allocator->lock);
|
|
return addr;
|
|
}
|
|
|
|
/*
|
|
* Iterate through the free list to find a suitably sized
|
|
* range of free contiguous memory blocks.
|
|
*
|
|
* We also take the opportunity to reset the size of the
|
|
* largest free area size statistic.
|
|
*/
|
|
list_for_each_entry(pos, &allocator->free_list.list, list) {
|
|
struct memrar_address_range * const fr = &pos->range;
|
|
size_t const curr_size = fr->end - fr->begin;
|
|
|
|
if (curr_size >= reserved_bytes && addr == 0) {
|
|
struct memrar_address_range *range = NULL;
|
|
struct memrar_address_ranges * const new_node =
|
|
kmalloc(sizeof(*new_node), GFP_KERNEL);
|
|
|
|
if (new_node == NULL)
|
|
break;
|
|
|
|
list_add(&new_node->list,
|
|
&allocator->allocated_list.list);
|
|
|
|
/*
|
|
* Carve out area of memory from end of free
|
|
* range.
|
|
*/
|
|
range = &new_node->range;
|
|
range->end = fr->end;
|
|
fr->end -= reserved_bytes;
|
|
range->begin = fr->end;
|
|
addr = range->begin;
|
|
|
|
/*
|
|
* Check if largest area has decreased in
|
|
* size. We'll need to continue scanning for
|
|
* the next largest area if it has.
|
|
*/
|
|
if (curr_size == allocator->largest_free_area)
|
|
allocator->largest_free_area -=
|
|
reserved_bytes;
|
|
else
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Reset largest free area size statistic as needed,
|
|
* but only if we've actually allocated memory.
|
|
*/
|
|
if (addr != 0
|
|
&& curr_size > allocator->largest_free_area) {
|
|
allocator->largest_free_area = curr_size;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&allocator->lock);
|
|
|
|
return addr;
|
|
}
|
|
|
|
long memrar_allocator_free(struct memrar_allocator *allocator,
|
|
unsigned long addr)
|
|
{
|
|
struct list_head *pos = NULL;
|
|
struct list_head *tmp = NULL;
|
|
struct list_head *dst = NULL;
|
|
|
|
struct memrar_address_ranges *allocated = NULL;
|
|
struct memrar_address_range const *handle = NULL;
|
|
|
|
unsigned long old_end = 0;
|
|
unsigned long new_chunk_size = 0;
|
|
|
|
if (allocator == NULL)
|
|
return -EINVAL;
|
|
|
|
if (addr == 0)
|
|
return 0; /* Ignore "free(0)". */
|
|
|
|
mutex_lock(&allocator->lock);
|
|
|
|
/* Find the corresponding handle. */
|
|
list_for_each_entry(allocated,
|
|
&allocator->allocated_list.list,
|
|
list) {
|
|
if (allocated->range.begin == addr) {
|
|
handle = &allocated->range;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* No such buffer created by this allocator. */
|
|
if (handle == NULL) {
|
|
mutex_unlock(&allocator->lock);
|
|
return -EFAULT;
|
|
}
|
|
|
|
/*
|
|
* Coalesce adjacent chunks of memory if possible.
|
|
*
|
|
* @note This isn't full blown coalescing since we're only
|
|
* coalescing at most three chunks of memory.
|
|
*/
|
|
list_for_each_safe(pos, tmp, &allocator->free_list.list) {
|
|
/* @todo O(n) performance. Optimize. */
|
|
|
|
struct memrar_address_range * const chunk =
|
|
&list_entry(pos,
|
|
struct memrar_address_ranges,
|
|
list)->range;
|
|
|
|
/* Extend size of existing free adjacent chunk. */
|
|
if (chunk->end == handle->begin) {
|
|
/*
|
|
* Chunk "less than" than the one we're
|
|
* freeing is adjacent.
|
|
*
|
|
* Before:
|
|
*
|
|
* +-----+------+
|
|
* |chunk|handle|
|
|
* +-----+------+
|
|
*
|
|
* After:
|
|
*
|
|
* +------------+
|
|
* | chunk |
|
|
* +------------+
|
|
*/
|
|
|
|
struct memrar_address_ranges const * const next =
|
|
list_entry(pos->next,
|
|
struct memrar_address_ranges,
|
|
list);
|
|
|
|
chunk->end = handle->end;
|
|
|
|
/*
|
|
* Now check if next free chunk is adjacent to
|
|
* the current extended free chunk.
|
|
*
|
|
* Before:
|
|
*
|
|
* +------------+----+
|
|
* | chunk |next|
|
|
* +------------+----+
|
|
*
|
|
* After:
|
|
*
|
|
* +-----------------+
|
|
* | chunk |
|
|
* +-----------------+
|
|
*/
|
|
if (!list_is_singular(pos)
|
|
&& chunk->end == next->range.begin) {
|
|
chunk->end = next->range.end;
|
|
list_del(pos->next);
|
|
kfree(next);
|
|
}
|
|
|
|
list_del(&allocated->list);
|
|
|
|
new_chunk_size = chunk->end - chunk->begin;
|
|
|
|
goto exit_memrar_free;
|
|
|
|
} else if (handle->end == chunk->begin) {
|
|
/*
|
|
* Chunk "greater than" than the one we're
|
|
* freeing is adjacent.
|
|
*
|
|
* +------+-----+
|
|
* |handle|chunk|
|
|
* +------+-----+
|
|
*
|
|
* After:
|
|
*
|
|
* +------------+
|
|
* | chunk |
|
|
* +------------+
|
|
*/
|
|
|
|
struct memrar_address_ranges const * const prev =
|
|
list_entry(pos->prev,
|
|
struct memrar_address_ranges,
|
|
list);
|
|
|
|
chunk->begin = handle->begin;
|
|
|
|
/*
|
|
* Now check if previous free chunk is
|
|
* adjacent to the current extended free
|
|
* chunk.
|
|
*
|
|
*
|
|
* Before:
|
|
*
|
|
* +----+------------+
|
|
* |prev| chunk |
|
|
* +----+------------+
|
|
*
|
|
* After:
|
|
*
|
|
* +-----------------+
|
|
* | chunk |
|
|
* +-----------------+
|
|
*/
|
|
if (!list_is_singular(pos)
|
|
&& prev->range.end == chunk->begin) {
|
|
chunk->begin = prev->range.begin;
|
|
list_del(pos->prev);
|
|
kfree(prev);
|
|
}
|
|
|
|
list_del(&allocated->list);
|
|
|
|
new_chunk_size = chunk->end - chunk->begin;
|
|
|
|
goto exit_memrar_free;
|
|
|
|
} else if (chunk->end < handle->begin
|
|
&& chunk->end > old_end) {
|
|
/* Keep track of where the entry could be
|
|
* potentially moved from the "allocated" list
|
|
* to the "free" list if coalescing doesn't
|
|
* occur, making sure the "free" list remains
|
|
* sorted.
|
|
*/
|
|
old_end = chunk->end;
|
|
dst = pos;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Nothing to coalesce.
|
|
*
|
|
* Move the entry from the "allocated" list to the "free"
|
|
* list.
|
|
*/
|
|
list_move(&allocated->list, dst);
|
|
new_chunk_size = handle->end - handle->begin;
|
|
allocated = NULL;
|
|
|
|
exit_memrar_free:
|
|
|
|
if (new_chunk_size > allocator->largest_free_area)
|
|
allocator->largest_free_area = new_chunk_size;
|
|
|
|
mutex_unlock(&allocator->lock);
|
|
|
|
kfree(allocated);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Local Variables:
|
|
c-file-style: "linux"
|
|
End:
|
|
*/
|