/**
 * \file
 * \brief Simple slab allocator.
 *
 * This file implements a simple slab allocator. It allocates blocks of a fixed
 * size from a pool of contiguous memory regions ("slabs").
 */

/*
 * 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/slab.h>
#include <aos/static_assert.h>

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

STATIC_ASSERT_SIZEOF(struct block_head, SLAB_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 refill_func Pointer to function to call when out of memory (or NULL)
 */
void slab_init(struct slab_allocator *slabs, size_t blocksize,
               slab_refill_func_t refill_func)
{
    slabs->slabs = NULL;
    slabs->blocksize = SLAB_REAL_BLOCKSIZE(blocksize);
    slabs->refill_func = refill_func;
}


/**
 * \brief Add memory (a new slab) to a slab allocator
 *
 * \param slabs Pointer to slab allocator instance
 * \param buf Pointer to start of memory region
 * \param buflen Size of memory region (in bytes)
 */
void slab_grow(struct slab_allocator *slabs, void *buf, size_t buflen)
{
    /* setup slab_head structure at top of buffer */
    assert(buflen > sizeof(struct slab_head));
    struct slab_head *head = buf;
    buflen -= sizeof(struct slab_head);
    buf = (char *)buf + sizeof(struct slab_head);

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

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

    /* enqueue slab in list of slabs */
    head->next = slabs->slabs;
    slabs->slabs = head;
}

/**
 * \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 memory)
 */
void *slab_alloc(struct slab_allocator *slabs)
{
    errval_t err;
    /* find a slab with free blocks */
    struct slab_head *sh;
    for (sh = slabs->slabs; sh != NULL && sh->free == 0; sh = sh->next);

    if (sh == NULL) {
        /* out of memory. try refill function if we have one */
        if (!slabs->refill_func) {
            return NULL;
        } else {
            err = slabs->refill_func(slabs);
            if (err_is_fail(err)) {
                DEBUG_ERR(err, "slab refill_func failed");
                return NULL;
            }
            for (sh = slabs->slabs; sh != NULL && sh->free == 0; sh = sh->next);
            if (sh == NULL) {
                return NULL;
            }
        }
    }

    /* dequeue top block from freelist */
    assert(sh != NULL);
    struct block_head *bh = sh->blocks;
    assert(bh != NULL);
    sh->blocks = bh->next;
    sh->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 slab_free(struct slab_allocator *slabs, void *block)
{
    if (block == NULL) {
        return;
    }

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

    /* find matching slab */
    struct slab_head *sh;
    size_t blocksize = slabs->blocksize;
    for (sh = slabs->slabs; sh != NULL; sh = sh->next) {
        /* check if block falls inside this slab */
        uintptr_t slab_limit = (uintptr_t)sh + sizeof(struct slab_head)
                               + blocksize * sh->total;
        if ((uintptr_t)bh > (uintptr_t)sh && (uintptr_t)bh < slab_limit) {
            break;
        }
    }
    assert(sh != NULL);

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

/**
 * \brief Returns the count of free blocks in the allocator
 *
 * \param slabs Pointer to slab allocator instance
 *
 * \returns Free block count
 */
size_t slab_freecount(struct slab_allocator *slabs)
{
    size_t ret = 0;

    for (struct slab_head *sh = slabs->slabs; sh != NULL; sh = sh->next) {
        ret += sh->free;
    }

    return ret;
}

/**
 * \brief General-purpose slab refill
 *
 * Allocates and maps a number of memory pages to the slab allocator.
 *
 * \param slabs Pointer to slab allocator instance
 * \param bytes (Minimum) amount of memory to map
 */
errval_t slab_refill_pages(struct slab_allocator *slabs, size_t bytes)
{
    errval_t err;
    // Hint: you can't just use malloc here...
    // Hint: For M1, just use the fixed mapping funcionality, however you may want to replace
    //       the fixed mapping later to avoid conflicts.

    struct capref frame;
    err = slot_alloc(&frame);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_SLOT_ALLOC);
    }

    err = slab_refill_no_pagefault(slabs, frame, bytes);
    if (err_is_fail(err)) {
        slot_free(frame);
        return err;
    }

    return SYS_ERR_OK;
}


/**
 * @brief refills the slab allocator without causing a page fault
 *
 * @param slabs     the slab allocator to be refilled
 * @param frame     an empty slot to hold the frame capability
 * @param minbytes  the minimum about of bytes to refill
 *
 * @return SYS_ERR_OK on success, errval on failure
 *
 * Note, the frame here is an empty slot that can be used as storage for a frame.
 */
errval_t slab_refill_no_pagefault(struct slab_allocator *slabs, struct capref frame,
                                  size_t minbytes)
{
    // Refill the slot allocator without causing a page fault
    // Hint: you can't just use malloc here...
    errval_t err;
    size_t alloc_bytes;
    err = frame_create(frame, minbytes, &alloc_bytes);
    if (err_is_fail(err)) return err_push(err, LIB_ERR_FRAME_ALLOC);

    struct paging_state *paging_state = get_current_paging_state();
    void *vaddr = NULL;
    err = paging_map_frame_attr(
        paging_state, &vaddr,
        alloc_bytes, frame, VREGION_FLAGS_READ_WRITE
    );
    if (err_is_fail(err)) {
        cap_delete(frame);
        return err;
    }

    slab_grow(slabs, vaddr, alloc_bytes);

    return SYS_ERR_OK;
}

/**
 * \brief General-purpose implementation of a slab allocate/refill function
 *
 * Allocates and maps a single page (FIXME: make configurable) and adds it
 * to the allocator.
 *
 * \param slabs Pointer to slab allocator instance
 */
errval_t slab_default_refill(struct slab_allocator *slabs)
{
    return slab_refill_pages(slabs, BASE_PAGE_SIZE);
}
