blob: 9ac859b39d79f30538340a566f1f127227157d21 [file] [log] [blame]
/* Copyright (c) 2009 The Regents of the University of California.
* See the COPYRIGHT files at the top of this source tree for full
* license information.
*
* Barret Rhoden <brho@cs.berkeley.edu>
* Kevin Klues <klueska@cs.berkeley.edu>
*/
#include <ros/common.h>
#include <error.h>
#include <pmap.h>
#include <kmalloc.h>
#include <stdio.h>
#include <slab.h>
#include <assert.h>
#define kmallocdebug(args...) //printk(args)
//List of physical pages used by kmalloc
static spinlock_t pages_list_lock = SPINLOCK_INITIALIZER;
static page_list_t pages_list;
struct kmem_cache *kmalloc_caches[NUM_KMALLOC_CACHES];
static void __kfree_release(struct kref *kref);
void kmalloc_init(void)
{
char kc_name[KMC_NAME_SZ];
/* we want at least a 16 byte alignment of the tag so that the bufs
* kmalloc returns are 16 byte aligned. we used to check the actual
* size == 16, since we adjusted the KMALLOC_SMALLEST based on that. */
static_assert(ALIGNED(sizeof(struct kmalloc_tag), 16));
/* build caches of common sizes. this size will later include the tag
* and the actual returned buffer. */
size_t ksize = KMALLOC_SMALLEST;
for (int i = 0; i < NUM_KMALLOC_CACHES; i++) {
snprintf(kc_name, KMC_NAME_SZ, "kmalloc_%d", ksize);
kmalloc_caches[i] = kmem_cache_create(kc_name, ksize,
KMALLOC_ALIGNMENT, 0,
NULL, 0, 0, NULL);
ksize <<= 1;
}
}
void *kmalloc(size_t size, int flags)
{
// reserve space for bookkeeping and preserve alignment
size_t ksize = size + sizeof(struct kmalloc_tag);
void *buf;
int cache_id;
// determine cache to pull from
if (ksize <= KMALLOC_SMALLEST)
cache_id = 0;
else
cache_id = LOG2_UP(ksize) - LOG2_UP(KMALLOC_SMALLEST);
// if we don't have a cache to handle it, alloc cont pages
if (cache_id >= NUM_KMALLOC_CACHES) {
/* The arena allocator will round up too, but we want to know in
* advance so that krealloc can avoid extra allocations. */
size_t amt_alloc = ROUNDUP(size + sizeof(struct kmalloc_tag),
PGSIZE);
buf = kpages_alloc(amt_alloc, flags);
if (!buf)
panic("Kmalloc failed! Handle me!");
// fill in the kmalloc tag
struct kmalloc_tag *tag = buf;
tag->flags = KMALLOC_TAG_PAGES;
tag->amt_alloc = amt_alloc;
tag->canary = KMALLOC_CANARY;
kref_init(&tag->kref, __kfree_release, 1);
return buf + sizeof(struct kmalloc_tag);
}
// else, alloc from the appropriate cache
buf = kmem_cache_alloc(kmalloc_caches[cache_id], flags);
if (!buf)
panic("Kmalloc failed! Handle me!");
// store a pointer to the buffers kmem_cache in it's bookkeeping space
struct kmalloc_tag *tag = buf;
tag->flags = KMALLOC_TAG_CACHE;
tag->my_cache = kmalloc_caches[cache_id];
tag->canary = KMALLOC_CANARY;
kref_init(&tag->kref, __kfree_release, 1);
return buf + sizeof(struct kmalloc_tag);
}
void *kzmalloc(size_t size, int flags)
{
void *v = kmalloc(size, flags);
if (!v)
return v;
memset(v, 0, size);
return v;
}
void *kmalloc_align(size_t size, int flags, size_t align)
{
void *addr, *retaddr;
int *tag_flags, offset;
/* alignment requests must be a multiple of long, even though we only
* need int in the current code. */
assert(ALIGNED(align, sizeof(long)));
/* must fit in the space reserved for the offset amount, which is at
* most 'align'. */
assert(align < (1 << (32 - KMALLOC_ALIGN_SHIFT)));
assert(IS_PWR2(align));
addr = kmalloc(size + align, flags);
if (!addr)
return 0;
if (ALIGNED(addr, align))
return addr;
retaddr = ROUNDUP(addr, align);
offset = retaddr - addr;
assert(offset < align);
/* we might not have room for a full tag. we might have only 8 bytes.
* but we'll at least have room for the flags part. */
tag_flags = (int*)(retaddr - sizeof(int));
*tag_flags = (offset << KMALLOC_ALIGN_SHIFT) | KMALLOC_TAG_UNALIGN;
return retaddr;
}
void *kzmalloc_align(size_t size, int flags, size_t align)
{
void *v = kmalloc_align(size, flags, align);
if (!v)
return v;
memset(v, 0, size);
return v;
}
static struct kmalloc_tag *__get_km_tag(void *buf)
{
struct kmalloc_tag *tag =
(struct kmalloc_tag*)(buf - sizeof(struct kmalloc_tag));
if (tag->canary != KMALLOC_CANARY){
printk("kmalloc bad canary: %08lx@%p, buf %p, expected %08lx\n",
tag->canary, &tag->canary, buf, KMALLOC_CANARY);
hexdump((void *)(buf - sizeof(struct kmalloc_tag)), 256);
panic("Bad canary");
}
return tag;
}
/* If we kmalloc_aligned, the buf we got back (and are now trying to perform
* some operation on) might not be the original, underlying, unaligned buf.
*
* This returns the underlying, unaligned buf, or 0 if the buf was not realigned
* in the first place. */
static void *__get_unaligned_orig_buf(void *buf)
{
int *tag_flags = (int*)(buf - sizeof(int));
if ((*tag_flags & KMALLOC_FLAG_MASK) == KMALLOC_TAG_UNALIGN)
return (buf - (*tag_flags >> KMALLOC_ALIGN_SHIFT));
else
return 0;
}
void *krealloc(void* buf, size_t size, int flags)
{
void *nbuf;
size_t osize = 0;
struct kmalloc_tag *tag;
if (buf){
if (__get_unaligned_orig_buf(buf))
panic("krealloc of a kmalloc_align not supported");
tag = __get_km_tag(buf);
/* whatever we got from either a slab or the page allocator is
* meant for both the buf+size as well as the kmalloc tag */
if ((tag->flags & KMALLOC_FLAG_MASK) == KMALLOC_TAG_CACHE) {
osize = tag->my_cache->obj_size
- sizeof(struct kmalloc_tag);
} else if ((tag->flags & KMALLOC_FLAG_MASK)
== KMALLOC_TAG_PAGES) {
osize = tag->amt_alloc - sizeof(struct kmalloc_tag);
} else {
panic("Probably a bad tag, flags %p\n", tag->flags);
}
if (osize >= size)
return buf;
}
nbuf = kmalloc(size, flags);
/* would be more interesting to user error(...) here. */
/* but in any event, NEVER destroy buf! */
if (! nbuf)
return NULL;
if (osize)
memmove(nbuf, buf, osize);
if (buf)
kfree(buf);
return nbuf;
}
/* Grabs a reference on a buffer. Release with kfree().
*
* Note that a krealloc on a buffer with ref > 1 that needs a new, underlying
* buffer will result in two buffers existing. In this case, the krealloc is a
* kmalloc and a kfree, but that kfree does not completely free since the
* original ref > 1. */
void kmalloc_incref(void *buf)
{
void *orig_buf = __get_unaligned_orig_buf(buf);
buf = orig_buf ? orig_buf : buf;
/* if we want a smaller tag, we can extract the code from kref and
* manually set the release method in kfree. */
kref_get(&__get_km_tag(buf)->kref, 1);
}
int kmalloc_refcnt(void *buf)
{
void *orig_buf = __get_unaligned_orig_buf(buf);
buf = orig_buf ? orig_buf : buf;
return kref_refcnt(&__get_km_tag(buf)->kref);
}
static void __kfree_release(struct kref *kref)
{
struct kmalloc_tag *tag = container_of(kref, struct kmalloc_tag, kref);
if ((tag->flags & KMALLOC_FLAG_MASK) == KMALLOC_TAG_CACHE)
kmem_cache_free(tag->my_cache, tag);
else if ((tag->flags & KMALLOC_FLAG_MASK) == KMALLOC_TAG_PAGES)
kpages_free(tag, tag->amt_alloc);
else
panic("Bad flag 0x%x in %s", tag->flags, __FUNCTION__);
}
void kfree(void *buf)
{
void *orig_buf;
if (buf == NULL)
return;
orig_buf = __get_unaligned_orig_buf(buf);
buf = orig_buf ? orig_buf : buf;
kref_put(&__get_km_tag(buf)->kref);
}
void kmalloc_canary_check(char *str)
{
struct kmalloc_tag *tag;
if (!debug_canary)
return;
tag = (struct kmalloc_tag*)(debug_canary - sizeof(struct kmalloc_tag));
if (tag->canary != KMALLOC_CANARY)
panic("\t\t KMALLOC CANARY CHECK FAILED %s\n", str);
}
struct sized_alloc *sized_kzmalloc(size_t size, int flags)
{
struct sized_alloc *sza;
sza = kzmalloc(sizeof(struct sized_alloc) + size, flags);
if (!sza)
return NULL;
sza->buf = sza + 1;
sza->size = size;
return sza;
}
void sza_printf(struct sized_alloc *sza, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
sza->sofar += vsnprintf(sza->buf + sza->sofar, sza->size - sza->sofar,
fmt, ap);
va_end(ap);
}