/**
 * \file
 * \brief Very simple slab allocator.
 *
 * This file implements a simple slab allocator. It allocates blocks of a fixed
 * size from one contiguous memory region.
 */

/*
 * Copyright (c) 2008, 2009, 2010, ETH Zurich.
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached LICENSE file.
 * If you do not find this file, copies can be found by writing to:
 * ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
 */

#include <aos/aos.h>
#include <aos/simpleslab.h>
#include <aos/static_assert.h>

struct simpleblock_head {
    struct simpleblock_head *next;///< Pointer to next block in free list
};

STATIC_ASSERT_SIZEOF(struct simpleblock_head, SIMPLESLAB_BLOCK_HDRSIZE);

/**
 * \brief Initialise a new slab allocator
 *
 * \param slabs Pointer to slab allocator instance, to be filled-in
 * \param blocksize Size of blocks to be allocated by this allocator
 * \param buf Buffer from which to allocate blocks
 * \param buflen Size of buf
 */
void simpleslab_init(struct simpleslab_allocator *slabs, size_t blocksize, void *buf, size_t buflen)
{
    assert(blocksize >= sizeof(struct simpleblock_head));

    /* calculate number of blocks in buffer */
    assert(buflen / blocksize <= UINT32_MAX);
    slabs->free = slabs->total = buflen / blocksize;
    assert(slabs->total > 0);
    assert(buf != NULL);

    slabs->start = buf;
    slabs->end = buf + buflen;

    /* enqueue blocks in freelist */
    struct simpleblock_head *bh = slabs->blocks = buf;
    for (uint32_t i = slabs->total; i > 1; i--) {
        buf = (char *)buf + blocksize;
        bh->next = buf;
        bh = buf;
    }
    bh->next = NULL;
}

/**
 * \brief Allocate a new block from the slab allocator
 *
 * \param slabs Pointer to slab allocator instance
 *
 * \returns Pointer to block on success, NULL on error (out of blocks)
 */
void *simpleslab_alloc(struct simpleslab_allocator *slabs)
{
    struct simpleblock_head *bh = slabs->blocks;
    if (bh == NULL) return NULL;
    slabs->blocks = bh->next;
    slabs->free--;

    return bh;
}

/**
 * \brief Free a block to the slab allocator
 *
 * \param slabs Pointer to slab allocator instance
 * \param block Pointer to block previously returned by #slab_alloc
 */
void simpleslab_free(struct simpleslab_allocator *slabs, void *block)
{
    assert(block != NULL);
    assert(slabs->start <= block && block < slabs->end);

    struct simpleblock_head *bh = (struct simpleblock_head *)block;

    /* re-enqueue in slab's free list */
    bh->next = slabs->blocks;
    slabs->blocks = bh;
    slabs->free++;
    assert(slabs->free <= slabs->total);
}
