root/src/add-ons/kernel/drivers/graphics/common/memory_manager.c
/*
 * Copyright 2006, Haiku Inc.
 * Copyright 2002, Thomas Kurschel.
 *
 * Distributed under the terms of the MIT license.
 */

/**     Memory manager used for graphics mem
 *
 *      It has the following features
 *      - doesn't access memory to be managed
 *      - memory block's owner is identified by tag,
 *        tag is verified during free, and all memory
 *        belonging to one tag can be freed at once
 *      - multi-threading save
 */

#include "memory_manager.h"

#include <KernelExport.h>
#include <stdlib.h>


//#define TRACE_MEMORY_MANAGER
#ifdef TRACE_MEMORY_MANAGER
#       define TRACE(x) dprintf x
#else
#       define TRACE(x) ;
#endif


// allocated memory block
typedef struct mem_block {
        struct mem_block *prev, *next;
        uint32 base;
        uint32 size;
        void *tag;
        bool allocated;
} mem_block;

// memory heap
struct mem_info {
        mem_block *first;
        area_id heap_area;
        uint32 block_size;
        sem_id lock;
        mem_block *heap;
        mem_block *unused;
        uint32 heap_entries;
};


/** merge "block" with successor */

static void
merge(mem_info *mem, mem_block *block)
{
        mem_block *next = block->next;

        block->size += next->size;

        if (next->next)
                next->next->prev = block;

        block->next = next->next;
        next->next = mem->unused;
        mem->unused = next;
}


/** free memory block including merge */

static mem_block *
freeblock(mem_info *mem, mem_block *block)
{
        mem_block *prev, *next;

        block->allocated = false;
        prev = block->prev;

        if (prev && !prev->allocated) {
                block = prev;
                merge(mem, prev);
        }

        next = block->next;

        if (next && !next->allocated)
                merge(mem, block);

        return block;
}


//      #pragma mark -


/** Init memory manager.
 *
 *      \param start start of address space
 *      \param length length of address space
 *      \param blockSize - granularity
 *      \param heapEntries - maximum number of blocks
 */

mem_info *
mem_init(const char* name, uint32 start, uint32 length,
        uint32 blockSize, uint32 heapEntries)
{
        mem_block *first;
        mem_info *mem;
        uint i;
        uint32 size;

        TRACE(("mem_init(name=%s, start=%lx, length=%lx, blockSize=%lx, heapEntries=%ld)\n",
                name, start, length, blockSize, heapEntries));

        mem = malloc(sizeof(*mem));
        if (mem == NULL)
                goto err1;

        mem->block_size = blockSize;
        mem->heap_entries = heapEntries;
        mem->lock = create_sem(1, name);
        if (mem->lock < 0)
                goto err2;

        // align size to B_PAGE_SIZE
        size = heapEntries * sizeof(mem_block);
        size = (size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);

        mem->heap_area = create_area(name, (void **)&mem->heap,
                B_ANY_ADDRESS, size, B_FULL_LOCK, B_READ_AREA | B_WRITE_AREA);

        if (mem->heap_area < 0 || mem->heap == NULL)
                goto err3;

        for (i = 1; i < heapEntries; ++i) {
                mem->heap[i - 1].next = &mem->heap[i];
        }

        mem->heap[heapEntries - 1].next = NULL;
        mem->unused = &mem->heap[1];

        first = &mem->heap[0];
        mem->first = first;

        first->base = start;
        first->size = length;
        first->prev = first->next = NULL;
        first->allocated = false;
        
        return mem;

err3:
        delete_sem(mem->lock);
err2:
        free(mem);
err1:
        return NULL;
}


/** destroy heap */

void
mem_destroy(mem_info *mem)
{
        TRACE(("mem_destroy(mem %p)\n", mem));

        delete_area(mem->heap_area);
        delete_sem(mem->lock);
        free(mem);
}


/** Allocate memory block
 *
 *      \param mem heap handle
 *      \param size size in bytes
 *      \param tag owner tag
 *
 *      \param blockID - returns block id
 *      \param offset - returns start address of block
 */

status_t
mem_alloc(mem_info *mem, uint32 size, void *tag, uint32 *blockID, uint32 *offset)
{
        mem_block *current, *newEntry;
        status_t status;

        TRACE(("mem_alloc(mem %p, size=%lx, tag=%p)\n", mem, size, tag));

        status = acquire_sem(mem->lock);
        if (status != B_OK)
                return status;

        // we assume block_size is power of two 
        size = (size + mem->block_size - 1) & ~(mem->block_size - 1);

        // simple first fit     
        for (current = mem->first; current; current = current->next) {
                if (!current->allocated && current->size >= size)
                        break;
        }

        if (current == NULL) {
                TRACE(("mem_alloc: out of memory\n"));
                goto err;
        }

        if (size != current->size) {
                newEntry = mem->unused;

                if (newEntry == NULL) {
                        TRACE(("mem_alloc: out of blocks\n"));
                        goto err;
                }

                mem->unused = newEntry->next;

                newEntry->next = current->next;
                newEntry->prev = current;       
                newEntry->allocated = false;
                newEntry->base = current->base + size;
                newEntry->size = current->size - size;

                if (current->next)
                        current->next->prev = newEntry;

                current->next = newEntry;
                current->size = size;
        }

        current->allocated = true;
        current->tag = tag;

        *blockID = current - mem->heap + 1;
        *offset = current->base;

        release_sem(mem->lock);

        TRACE(("mem_alloc(block_id=%ld, offset=%lx)\n", *blockID, *offset));
        return B_OK;

err:
        release_sem(mem->lock);
        return B_NO_MEMORY;
}


/** Free memory
 *      \param mem heap handle
 *      \param blockID block id
 *      \param tag owner tag (must match tag passed to mem_alloc())
 */

status_t
mem_free(mem_info *mem, uint32 blockID, void *tag)
{       
        mem_block *block;
        status_t status;

        TRACE(("mem_free(mem %p, blockID=%ld, tag=%p)\n", mem, blockID, tag));

        status = acquire_sem(mem->lock);
        if (status != B_OK)
                return status;

        --blockID;

        if (blockID >= mem->heap_entries) {
                TRACE(("mem_free: invalid ID %lu\n", blockID));
                goto err;
        }

        block = &mem->heap[blockID];
        if (!block->allocated || block->tag != tag) {
                TRACE(("mem_free: not owner\n"));
                goto err;
        }

        freeblock(mem, block);

        release_sem(mem->lock);
        return B_OK;

err:
        release_sem(mem->lock);
        return B_BAD_VALUE;
}


/** Free all memory belonging to owner "tag" */

status_t
mem_freetag(mem_info *mem, void *tag)
{
        mem_block *current;
        status_t status;

        TRACE(("mem_freetag(mem %p, tag=%p)\n", mem, tag));

        status = acquire_sem(mem->lock);
        if (status != B_OK)
                return status;
        
        for (current = mem->first; current; current = current->next) {
                if (current->allocated && current->tag == tag)
                        current = freeblock(mem, current);
        }

        release_sem(mem->lock);
        return B_OK;
}