Type: fix Change-Id: If59a66aae658dd35dbcb4987ab00c306b3c6e2e2 Signed-off-by: Damjan Marion <damarion@cisco.com>
362 lines
9.5 KiB
C
362 lines
9.5 KiB
C
/*
|
|
* Copyright (c) 2018 Cisco and/or its affiliates.
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <vppinfra/valloc.h>
|
|
|
|
/** @file
|
|
@brief Simple first-fit virtual space allocator
|
|
*/
|
|
|
|
/** Add a chunk of memory to a virtual allocation arena
|
|
@param vam - clib_valloc_main_t * pointer to the allocation arena
|
|
@param template - clib_valloc_chunk_t * pointer to a template chunk which
|
|
describes the virtual address range to add
|
|
|
|
@note only the baseva and size member of the template chunk are significant
|
|
It's perfectly OK for the new chunk to be discontinuous with previous
|
|
chunks, the chunk fusion algorithm won't merge them.
|
|
*/
|
|
|
|
__clib_export void
|
|
clib_valloc_add_chunk (clib_valloc_main_t *vam, clib_valloc_chunk_t *template)
|
|
{
|
|
clib_valloc_chunk_t *ch, *new_ch;
|
|
u32 index;
|
|
|
|
ASSERT (vam->flags & CLIB_VALLOC_INITIALIZED);
|
|
|
|
clib_spinlock_lock_if_init (&vam->lock);
|
|
|
|
/* Add at the beginning, or at the end... */
|
|
index = vam->first_index;
|
|
|
|
/*
|
|
* Make sure we're not trying to add an overlapping chunk..
|
|
* It's worth checking, because someone will eventually do that.
|
|
*/
|
|
if (CLIB_DEBUG > 0 && index != ~0)
|
|
{
|
|
while (index != ~0)
|
|
{
|
|
ch = pool_elt_at_index (vam->chunks, index);
|
|
ASSERT (template->baseva < ch->baseva || template->baseva >=
|
|
(ch->baseva + ch->size));
|
|
ASSERT (template->baseva + template->size < ch->baseva ||
|
|
template->baseva + template->size >=
|
|
(ch->baseva + ch->size));
|
|
index = ch->next;
|
|
}
|
|
index = vam->first_index;
|
|
}
|
|
|
|
if (index != ~0)
|
|
ch = pool_elt_at_index (vam->chunks, index);
|
|
|
|
if (index == ~0 || template->baseva < ch->baseva)
|
|
{
|
|
pool_get (vam->chunks, new_ch);
|
|
clib_memset (new_ch, 0, sizeof (*new_ch));
|
|
|
|
if (index != ~0)
|
|
{
|
|
ch = pool_elt_at_index (vam->chunks, index);
|
|
|
|
new_ch->next = index;
|
|
new_ch->prev = ~0;
|
|
ch->prev = new_ch - vam->chunks;
|
|
}
|
|
else
|
|
{
|
|
new_ch->next = new_ch->prev = ~0;
|
|
}
|
|
|
|
new_ch->baseva = template->baseva;
|
|
new_ch->size = template->size;
|
|
|
|
vam->first_index = new_ch - vam->chunks;
|
|
|
|
hash_set (vam->chunk_index_by_baseva, new_ch->baseva, vam->first_index);
|
|
}
|
|
else
|
|
{
|
|
/* Walk to the end of the chunk chain */
|
|
while (index != ~0)
|
|
{
|
|
ch = pool_elt_at_index (vam->chunks, index);
|
|
index = ch->next;
|
|
}
|
|
/* we want the last chunk index */
|
|
index = ch - vam->chunks;
|
|
|
|
pool_get (vam->chunks, new_ch);
|
|
clib_memset (new_ch, 0, sizeof (*new_ch));
|
|
|
|
ch = pool_elt_at_index (vam->chunks, index);
|
|
|
|
new_ch->next = ~0;
|
|
new_ch->prev = index;
|
|
ch->next = new_ch - vam->chunks;
|
|
|
|
new_ch->baseva = template->baseva;
|
|
new_ch->size = template->size;
|
|
|
|
hash_set (vam->chunk_index_by_baseva, new_ch->baseva,
|
|
new_ch - vam->chunks);
|
|
}
|
|
|
|
clib_spinlock_unlock_if_init (&vam->lock);
|
|
}
|
|
|
|
/** Initialize a virtual memory allocation arena
|
|
@param vam - clib_valloc_main_t * pointer to the arena to initialize
|
|
@param template - clib_valloc_chunk_t * pointer to a template chunk which
|
|
describes the initial virtual address range
|
|
*/
|
|
__clib_export void
|
|
clib_valloc_init (clib_valloc_main_t * vam, clib_valloc_chunk_t * template,
|
|
int need_lock)
|
|
{
|
|
ASSERT (template && template->baseva && template->size);
|
|
clib_memset (vam, 0, sizeof (*vam));
|
|
if (need_lock)
|
|
clib_spinlock_init (&vam->lock);
|
|
|
|
vam->chunk_index_by_baseva = hash_create (0, sizeof (uword));
|
|
vam->first_index = ~0;
|
|
vam->flags |= CLIB_VALLOC_INITIALIZED;
|
|
|
|
clib_valloc_add_chunk (vam, template);
|
|
}
|
|
|
|
/** Allocate virtual space
|
|
@param vam - clib_valloc_main_t * pointer to the allocation arena
|
|
@param size - u64 number of bytes to allocate
|
|
@os_out_of_memory_on_failure - 1=> panic on allocation failure
|
|
@return uword allocated space, 0=> failure
|
|
*/
|
|
__clib_export uword
|
|
clib_valloc_alloc (clib_valloc_main_t * vam, uword size,
|
|
int os_out_of_memory_on_failure)
|
|
{
|
|
clib_valloc_chunk_t *ch, *new_ch, *next_ch;
|
|
u32 index;
|
|
|
|
clib_spinlock_lock_if_init (&vam->lock);
|
|
|
|
index = vam->first_index;
|
|
|
|
while (index != ~0)
|
|
{
|
|
ch = pool_elt_at_index (vam->chunks, index);
|
|
/* If the chunk is free... */
|
|
if ((ch->flags & CLIB_VALLOC_BUSY) == 0)
|
|
{
|
|
/* Too small? */
|
|
if (ch->size < size)
|
|
goto next_chunk;
|
|
/* Exact match? */
|
|
if (ch->size == size)
|
|
{
|
|
ch->flags |= CLIB_VALLOC_BUSY;
|
|
clib_spinlock_unlock_if_init (&vam->lock);
|
|
return ch->baseva;
|
|
}
|
|
/*
|
|
* The current free chunk is larger than necessary, split the block.
|
|
*/
|
|
pool_get (vam->chunks, new_ch);
|
|
/* ch might have just moved */
|
|
ch = pool_elt_at_index (vam->chunks, index);
|
|
clib_memset (new_ch, 0, sizeof (*new_ch));
|
|
new_ch->next = new_ch->prev = ~0;
|
|
new_ch->baseva = ch->baseva + size;
|
|
new_ch->size = ch->size - size;
|
|
ch->size = size;
|
|
|
|
/* Insert into doubly-linked list */
|
|
new_ch->next = ch->next;
|
|
new_ch->prev = ch - vam->chunks;
|
|
|
|
if (ch->next != ~0)
|
|
{
|
|
next_ch = pool_elt_at_index (vam->chunks, ch->next);
|
|
next_ch->prev = new_ch - vam->chunks;
|
|
}
|
|
ch->next = new_ch - vam->chunks;
|
|
|
|
hash_set (vam->chunk_index_by_baseva, new_ch->baseva,
|
|
new_ch - vam->chunks);
|
|
|
|
ch->flags |= CLIB_VALLOC_BUSY;
|
|
clib_spinlock_unlock_if_init (&vam->lock);
|
|
return ch->baseva;
|
|
}
|
|
|
|
next_chunk:
|
|
index = ch->next;
|
|
}
|
|
clib_spinlock_unlock_if_init (&vam->lock);
|
|
|
|
if (os_out_of_memory_on_failure)
|
|
os_out_of_memory ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** Free virtual space
|
|
@param vam - clib_valloc_main_t * pointer to the allocation arena
|
|
@param baseva - uword base virtual address of the returned space
|
|
@return uword - size of the freed virtual chunk
|
|
@note the size is returned since we know it / in case the caller
|
|
doesn't memorize chunk sizes
|
|
*/
|
|
__clib_export uword
|
|
clib_valloc_free (clib_valloc_main_t * vam, uword baseva)
|
|
{
|
|
clib_valloc_chunk_t *ch, *prev_ch, *next_ch, *n2_ch;
|
|
u32 index;
|
|
uword return_size = 0;
|
|
uword *p;
|
|
|
|
clib_spinlock_lock_if_init (&vam->lock);
|
|
|
|
p = hash_get (vam->chunk_index_by_baseva, baseva);
|
|
|
|
/* Check even in production images */
|
|
if (p == 0)
|
|
os_panic ();
|
|
|
|
index = p[0];
|
|
|
|
ch = pool_elt_at_index (vam->chunks, index);
|
|
|
|
return_size = ch->size;
|
|
|
|
ASSERT (ch->baseva == baseva);
|
|
ASSERT ((ch->flags & CLIB_VALLOC_BUSY) != 0);
|
|
|
|
ch->flags &= ~CLIB_VALLOC_BUSY;
|
|
|
|
/* combine with previous entry? */
|
|
if (ch->prev != ~0)
|
|
{
|
|
prev_ch = pool_elt_at_index (vam->chunks, ch->prev);
|
|
/*
|
|
* Previous item must be free as well, and
|
|
* tangent to this block.
|
|
*/
|
|
if ((prev_ch->flags & CLIB_VALLOC_BUSY) == 0
|
|
&& ((prev_ch->baseva + prev_ch->size) == ch->baseva))
|
|
{
|
|
hash_unset (vam->chunk_index_by_baseva, baseva);
|
|
prev_ch->size += ch->size;
|
|
prev_ch->next = ch->next;
|
|
if (ch->next != ~0)
|
|
{
|
|
next_ch = pool_elt_at_index (vam->chunks, ch->next);
|
|
next_ch->prev = ch->prev;
|
|
}
|
|
ASSERT (ch - vam->chunks != vam->first_index);
|
|
clib_memset (ch, 0xfe, sizeof (*ch));
|
|
pool_put (vam->chunks, ch);
|
|
/* See about combining with next elt */
|
|
ch = prev_ch;
|
|
}
|
|
}
|
|
|
|
/* Combine with next entry? */
|
|
if (ch->next != ~0)
|
|
{
|
|
next_ch = pool_elt_at_index (vam->chunks, ch->next);
|
|
|
|
if ((next_ch->flags & CLIB_VALLOC_BUSY) == 0
|
|
&& ((ch->baseva + ch->size) == next_ch->baseva))
|
|
{
|
|
hash_unset (vam->chunk_index_by_baseva, next_ch->baseva);
|
|
ch->size += next_ch->size;
|
|
ch->next = next_ch->next;
|
|
if (ch->next != ~0)
|
|
{
|
|
n2_ch = pool_elt_at_index (vam->chunks, next_ch->next);
|
|
n2_ch->prev = ch - vam->chunks;
|
|
}
|
|
ASSERT (next_ch - vam->chunks != vam->first_index);
|
|
clib_memset (next_ch, 0xfe, sizeof (*ch));
|
|
pool_put (vam->chunks, next_ch);
|
|
}
|
|
}
|
|
|
|
clib_spinlock_unlock_if_init (&vam->lock);
|
|
return return_size;
|
|
}
|
|
|
|
/** format a virtual allocation arena (varargs)
|
|
@param vam - clib_valloc_main_t pointer to the arena to format
|
|
@param verbose - int - verbosity level
|
|
@return u8 vector
|
|
*/
|
|
__clib_export u8 *
|
|
format_valloc (u8 *s, va_list *va)
|
|
{
|
|
clib_valloc_main_t *vam = va_arg (*va, clib_valloc_main_t *);
|
|
int verbose = va_arg (*va, int);
|
|
clib_valloc_chunk_t *ch;
|
|
u32 index;
|
|
uword *p;
|
|
|
|
clib_spinlock_lock_if_init (&vam->lock);
|
|
|
|
s = format (s, "%d chunks, first index %d\n",
|
|
pool_elts (vam->chunks), vam->first_index);
|
|
|
|
if (verbose)
|
|
{
|
|
index = vam->first_index;
|
|
while (index != ~0)
|
|
{
|
|
ch = pool_elt_at_index (vam->chunks, index);
|
|
|
|
s = format (s, "[%d] base %llx size %llx (%lld) prev %d %s\n",
|
|
index, ch->baseva, ch->size, ch->size, ch->prev,
|
|
(ch->flags & CLIB_VALLOC_BUSY) ? "busy" : "free");
|
|
|
|
p = hash_get (vam->chunk_index_by_baseva, ch->baseva);
|
|
if (p == 0)
|
|
{
|
|
s = format (s, " BUG: baseva not in hash table!\n");
|
|
}
|
|
else if (p[0] != index)
|
|
{
|
|
s = format (s, " BUG: baseva in hash table %d not %d!\n",
|
|
p[0], index);
|
|
}
|
|
index = ch->next;
|
|
}
|
|
}
|
|
|
|
clib_spinlock_unlock_if_init (&vam->lock);
|
|
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
* fd.io coding-style-patch-verification: ON
|
|
*
|
|
* Local Variables:
|
|
* eval: (c-set-style "gnu")
|
|
* End:
|
|
*/
|