/*
 *  pmm.c - POST(Power On Self Test) Memory Manager
 *  according to the specification described in
 *  http://www.phoenix.com/NR/rdonlyres/873A00CF-33AC-4775-B77E-08E7B9754993/0/specspmm101.pdf
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 *  Copyright (C) 2009 FUJITSU LIMITED
 *
 *  Author: Kouya Shimura <kouya@jp.fujitsu.com>
 */

/*
 * Algorithm:
 *
 * This is not a fast storage allocator but simple one.  There is no
 * segregated management by block size and it does nothing special for
 * avoiding the fragmentation.
 *
 * The allocation algorithm is a first-fit. All memory blocks are
 * managed by linear single linked list in order of the address.
 * (i.e. There is no backward pointer) It searches the first available
 * equal or larger block from the head (lowest address) of memory
 * heap. The larger block is splitted into two blocks unless one side
 * becomes too small.
 * 
 * For de-allocation, the specified block is just marked as available
 * and it does nothing else. Thus, the fragmentation will occur. The
 * collection of continuous available blocks are done on the search
 * phase of another block allocation.
 *
 * The following is an abstract of this algorithm. The actual code
 * looks complicated on account of alignment and checking the handle.
 *
 *     static memblk_t *
 *     alloc(heap_t *heap, uint32_t size)
 *     {
 *         static memblk_t *mb;
 *         for_each_memblk(heap, mb) // search memory blocks
 *             if (memblk_is_avail(mb))
 *             {
 *                 collect_avail_memblks(heap, mb);
 *                 if (size <= memblk_bufsize(mb))
 *                 {
 *                     split_memblk(mb, size);
 *                     set_inuse(mb);
 *                     return mb;
 *                 }
 *             }
 *         return NULL;
 *     }
 */

#include <stdint.h>
#include <stddef.h>
#include <../hvmloader/config.h>
#include <../hvmloader/e820.h>
#include "util.h"

#define DEBUG_PMM 0

#define __stringify(a) #a
#define stringify(a) __stringify(a)

#define ASSERT(_expr, _action)                                  \
    if (!(_expr)) {                                             \
        printf("ASSERTION FAIL: %s %s:%d %s()\n",               \
               stringify(_expr), __FILE__, __LINE__, __func__); \
        _action;                                                \
    } else

#if DEBUG_PMM
# define PMM_DEBUG(format, p...) printf("PMM " format, ##p)
#else
# define PMM_DEBUG(format, p...)
#endif

struct pmmAllocArgs {
    uint16_t function;
    uint32_t length;
    uint32_t handle;
    uint16_t flags;
} __attribute__ ((packed));

struct pmmFindArgs {
    uint16_t function;
    uint32_t handle;
} __attribute__ ((packed));

struct pmmDeallocateArgs {
    uint16_t function;
    uint32_t buffer;
} __attribute__ ((packed));

#define PMM_FUNCTION_ALLOCATE   0
#define PMM_FUNCTION_FIND       1         
#define PMM_FUNCTION_DEALLOC    2

#define PARAGRAPH_LENGTH        16  // unit of length

#define PMM_HANDLE_ANONYMOUS    0xffffffff

#define PMM_FLAGS_MEMORY_TYPE_MASK      0x0003
#define PMM_FLAGS_MEMORY_INVALID        0
#define PMM_FLAGS_MEMORY_CONVENTIONAL   1  // 0 to 1MB
#define PMM_FLAGS_MEMORY_EXTENDED       2  // 1MB to 4GB
#define PMM_FLAGS_MEMORY_ANY            3  // whichever is available
#define PMM_FLAGS_ALIGINMENT            0x0004

/* Error code */
#define PMM_ENOMEM      (0)     // Out of memory, duplicate handle
#define PMM_EINVAL      (-1)    // Invalid argument

#define ALIGN_UP(addr, size)    (((addr)+((size)-1))&(~((size)-1)))
#define ALIGN_DOWN(addr, size)  ((addr)&(~((size)-1)))

typedef struct memblk {
    uint32_t magic;      // inuse or available
    struct memblk *next; // points the very next of this memblk
    uint32_t handle;     // identifier of this block
    uint32_t __fill;     // for 16byte alignment, not used
    uint8_t buffer[0];
} memblk_t;

typedef struct heap {
    memblk_t *head;     // start address of heap
    memblk_t *end;      // end address of heap
} heap_t;

#define HEAP_NOT_INITIALIZED    (memblk_t *)-1
#define HEAP_ALIGNMENT          16

/*
 * PMM handles two memory heaps, the caller chooses either.
 *
 * - conventional memroy (below 1MB)
 *    In HVM, the area is fixed. 0x00010000-0x0007FFFF
 *    (from SCRATCH_PHYSICAL_ADDRESS to HYPERCALL_PHYSICAL_ADDRESS)
 *
 * - extended memory (start at 1MB, below 4GB)
 *    In HVM, the area starts at memory address 0x00100000.
 *    The end address is variable. We read low RAM address from e820 table.
 *
 * The following struct must be located in the data segment since bss
 * in 32bitbios doesn't be relocated.
 */
static struct {
    heap_t heap;     // conventional memory
    heap_t ext_heap; // extended memory
} pmm_data = { {HEAP_NOT_INITIALIZED, NULL}, {NULL, NULL} };

/* These values are private use, not a spec in PMM */
#define MEMBLK_MAGIC_INUSE   0x2A4D4D50  // 'PMM*'
#define MEMBLK_MAGIC_AVAIL   0x5F4D4D50  // 'PMM_'

#define memblk_is_inuse(_mb)  ((_mb)->magic == MEMBLK_MAGIC_INUSE)
#define memblk_is_avail(_mb)  ((_mb)->magic == MEMBLK_MAGIC_AVAIL)

static void set_inuse(memblk_t *mb, uint32_t handle)
{
    mb->magic = MEMBLK_MAGIC_INUSE;
    mb->handle = handle;
}

static void set_avail(memblk_t *mb)
{
    mb->magic = MEMBLK_MAGIC_AVAIL;
    mb->handle = PMM_HANDLE_ANONYMOUS;
}

#define MEMBLK_HEADER_SIZE   ((int)(&((memblk_t *)0)->buffer))
#define MIN_MEMBLK_SIZE      (MEMBLK_HEADER_SIZE + PARAGRAPH_LENGTH)

#define memblk_size(_mb)     ((void *)((_mb)->next) - (void *)(_mb))
#define memblk_buffer(_mb)   ((uint32_t)(&(_mb)->buffer))
#define memblk_bufsize(_mb)  (memblk_size(_mb) - MEMBLK_HEADER_SIZE)

#define buffer_memblk(_buf)  (memblk_t *)((_buf) - MEMBLK_HEADER_SIZE)

#define memblk_loop_mbondition(_h, _mb) \
    (((_mb) < (_h)->end) && (/* avoid infinite loop */ (_mb) < (_mb)->next))

#define for_each_memblk(_h, _mb)                \
    for ((_mb) = (_h)->head;                    \
         memblk_loop_mbondition(_h, _mb);       \
         (_mb) = (_mb)->next)

#define for_remain_memblk(_h, _mb)              \
    for (;                                      \
         memblk_loop_mbondition(_h, _mb);       \
         (_mb) = (_mb)->next)

/*
 *                                       <-size->
 *    +==================+======+       +========+========+======+
 *    |      avail       |      |       | avail  | avail  |      |
 *    |      memblk      |memblk|...    | memblk | memblk |memblk|...
 *    +==================+======+   =>  +========+========+======+
 *    ^ |                ^ |    ^         |      ^ |      ^ |    ^
 *    | |next            | |next|         |next  | |next  | |next|
 *    | \________________/ \____/         \______/ \______/ \____/
 *    |                                          ^
 *    |                                          |
 *    mb                                         +- sb(return value)
 */
static memblk_t *
split_memblk(memblk_t *mb, uint32_t size)
{
    memblk_t *sb = (void *)memblk_buffer(mb) + size;

    /* Only split if the remaining fragment is big enough. */
    if ( (memblk_bufsize(mb) - size) < MIN_MEMBLK_SIZE)
        return mb;

    sb->next = mb->next;
    set_avail(sb);

    mb->next = sb;
    return sb;
}

/*
 *    +======+======+======+======+       +=================+======+
 *    |avail |avail |avail |inuse |       |      avail      |inuse |   
 *    |memblk|memblk|memblk|memblk|...    |      memblk     |memblk|...
 *    +======+======+======+======+   =>  +=================+======+
 *    ^ |    ^ |    ^ |    ^ |    ^         |               ^ |    ^
 *    | |next| |next| |next| |next|         |next           | |next|
 *    | \____/ \____/ \____/ \____/         \_______________/ \____/
 *    |
 *    mb
 */
static void
collect_avail_memblks(heap_t *heap, memblk_t *mb)
{
    memblk_t *nb = mb->next;

    for_remain_memblk ( heap, nb )
        if ( memblk_is_inuse(nb) )
            break;
    mb->next = nb;
}

static void
pmm_init_heap(heap_t *heap, uint32_t from_addr, uint32_t to_addr)
{
    memblk_t *mb = (memblk_t *)ALIGN_UP(from_addr, HEAP_ALIGNMENT);

    mb->next = (memblk_t *)ALIGN_DOWN(to_addr, HEAP_ALIGNMENT);
    set_avail(mb);

    heap->head = mb;
    heap->end = mb->next;
}

static void
pmm_initalize(void)
{
    int i, e820_nr = *E820_NR;
    struct e820entry *e820 = E820;

    /* Extended memory: RAM below 4GB, 0x100000-0xXXXXXXXX */
    for ( i = 0; i < e820_nr; i++ )
    {
        if ( (e820[i].type == E820_RAM) && (e820[i].addr >= 0x00100000) )
        {
            pmm_init_heap(&pmm_data.ext_heap, e820[i].addr, 
                          e820[i].addr + e820[i].size);
            break;
        }
    }

    /* convectional memory: RAM below 1MB, 0x10000-0x7FFFF */
    pmm_init_heap(&pmm_data.heap, SCRATCH_PHYSICAL_ADDRESS,
                  HYPERCALL_PHYSICAL_ADDRESS);
}

static uint32_t
pmm_max_avail_length(heap_t *heap)
{
    memblk_t *mb;
    uint32_t size, max = 0;

    for_each_memblk ( heap, mb )
    {
        if ( !memblk_is_avail(mb) )
            continue;
        collect_avail_memblks(heap, mb);
        size = memblk_bufsize(mb);
        if ( size > max )
            max = size;
    }

    return (max / PARAGRAPH_LENGTH);
}

static memblk_t *
first_fit(heap_t *heap, uint32_t size, uint32_t handle, uint32_t flags)
{
    memblk_t *mb;
    int32_t align = 0;

    if ( flags & PMM_FLAGS_ALIGINMENT )
        align = ((size ^ (size - 1)) >> 1) + 1;

    for_each_memblk ( heap, mb )
    {
        if ( memblk_is_avail(mb) )
        {
            collect_avail_memblks(heap, mb);

            if ( align )
            {
                uint32_t addr = memblk_buffer(mb);
                uint32_t offset = ALIGN_UP(addr, align) - addr;

                if ( offset > 0 )
                {
                    ASSERT(offset >= MEMBLK_HEADER_SIZE, continue);

                    if ( (offset + size) > memblk_bufsize(mb) )
                        continue;

                    mb = split_memblk(mb, offset - MEMBLK_HEADER_SIZE);
                    return mb;
                }
            }

            if ( size <= memblk_bufsize(mb) )
                return mb;
        }
        else
        {
            ASSERT(memblk_is_inuse(mb), return NULL);

            /* Duplication check for handle. */
            if ( (handle != PMM_HANDLE_ANONYMOUS) && (mb->handle == handle) )
                return NULL;
        }
    }

    return NULL;
}

static memblk_t *
pmm_find_handle(heap_t *heap, uint32_t handle)
{
    memblk_t *mb;

    if ( handle == PMM_HANDLE_ANONYMOUS )
        return NULL;

    for_each_memblk ( heap, mb )
        if ( mb->handle == handle )
            return mb;

    return NULL;
}

/*
 * allocate a memory block of the specified type and size, and returns
 * the address of the memory block.
 *
 * A client-specified identifier to be associated with the allocated
 * memory block. A handle of 0xFFFFFFFF indicates that no identifier
 * should be associated with the block. Such a memory block is known
 * as an "anonymous" memory block and cannot be found using the
 * pmmFind function. If a specified handle for a requested memory
 * block is already used in a currently allocated memory block, the
 * error value of 0x00000000 is returned
 *
 * If length is 0x00000000, no memory is allocated and the value
 * returned is the size of the largest memory block available for the
 * memory type specified in the flags parameter. The alignment bit in
 * the flags register is ignored when calculating the largest memory
 * block available.
 *
 * If a specified handle for a requested memory block is already used
 * in a currently allocated memory block, the error value of
 * 0x00000000 is returned.
 * 
 * A return value of 0x00000000 indicates that an error occurred and
 * no memory has been allocated. 
 */
static uint32_t
pmmAllocate(uint32_t length, uint32_t handle, uint16_t flags)
{
    heap_t *heap;
    memblk_t *mb;
    uint32_t size;

    switch ( flags & PMM_FLAGS_MEMORY_TYPE_MASK )
    {
    case PMM_FLAGS_MEMORY_CONVENTIONAL:
        heap = &pmm_data.heap;
        break;

    case PMM_FLAGS_MEMORY_EXTENDED:
    case PMM_FLAGS_MEMORY_ANY: /* XXX: ignore conventional memory for now */
        heap = &pmm_data.ext_heap;
        break;

    default:
        return PMM_EINVAL;
    }

    /* return the largest memory block available */
    if ( length == 0 )
        return pmm_max_avail_length(heap);

    size = length * PARAGRAPH_LENGTH;
    mb = first_fit(heap, size, handle, flags);

    if ( mb == NULL )
        return PMM_ENOMEM;

    /* duplication check for handle */
    if ( handle != PMM_HANDLE_ANONYMOUS )
    {
        memblk_t *nb = mb->next;

        for_remain_memblk(heap, nb)
            if (nb->handle == handle)
                return PMM_ENOMEM;
    }

    split_memblk(mb, size);
    set_inuse(mb, handle);

    return memblk_buffer(mb);
}

/*
 * returns the address of the memory block associated with the
 * specified handle.  
 *
 * A return value of 0x00000000 indicates that the handle does not
 * correspond to a currently allocated memory block.
 */
static uint32_t
pmmFind(uint32_t handle)
{
    memblk_t *mb;

    if ( handle == PMM_HANDLE_ANONYMOUS )
        return 0;

    mb = pmm_find_handle(&pmm_data.heap, handle);
    if ( mb == NULL )
        mb = pmm_find_handle(&pmm_data.ext_heap, handle);

    return mb ? memblk_buffer(mb) : 0;
}

/* 
 * frees the specified memory block that was previously allocated by
 * pmmAllocate.
 *
 * If the memory block was deallocated correctly, the return value is
 * 0x00000000. If there was an error, the return value is non-zero.
 */
static uint32_t
pmmDeallocate(uint32_t buffer)
{
    memblk_t *mb = buffer_memblk(buffer);

    if ( !memblk_is_inuse(mb) )
        return PMM_EINVAL;

    set_avail(mb);
    return 0;
}


union pmm_args {
    uint16_t function;
    struct pmmAllocArgs alloc;
    struct pmmFindArgs find;
    struct pmmDeallocateArgs dealloc;
} __attribute__ ((packed));

/*
 * entry function of all PMM services.
 *
 * Values returned to the caller are placed in the DX:AX register
 * pair. The flags and all registers, other than DX and AX, are
 * preserved across calls to PMM services.
 */
uint32_t
pmm(void *argp)
{
    union pmm_args *ap = argp;
    uint32_t ret = PMM_EINVAL;

    if ( pmm_data.heap.head == HEAP_NOT_INITIALIZED )
        pmm_initalize();

    switch ( ap->function )
    {
    case PMM_FUNCTION_ALLOCATE:
        ret = pmmAllocate(ap->alloc.length, ap->alloc.handle, ap->alloc.flags);
        PMM_DEBUG("Alloc length=%x handle=%x flags=%x ret=%x\n", 
                  ap->alloc.length, ap->alloc.handle, ap->alloc.flags, ret);
        break;

    case PMM_FUNCTION_FIND:
        ret = pmmFind(ap->find.handle);
        PMM_DEBUG("Find handle=%x ret=%x\n", ap->find.handle, ret);
        break;

    case PMM_FUNCTION_DEALLOC:
        ret = pmmDeallocate(ap->dealloc.buffer);
        PMM_DEBUG("Dealloc buffer=%x ret=%x\n", ap->dealloc.buffer, ret);
        break;

    default:
        PMM_DEBUG("Invalid function:%d\n", ap->function);
        break;
    }

    return ret;
}
