/*
 * Buddy Allocator
 *
 * Copyright (C) 2009-2011 Udo Steinberg <udo@hypervisor.org>
 * Economic rights: Technische Universitaet Dresden (Germany)
 *
 * Copyright (C) 2012 Udo Steinberg, Intel Corporation.
 *
 * This file is part of the NOVA microhypervisor.
 *
 * NOVA is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * NOVA 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 General Public License version 2 for more details.
 */

#pragma once

#include "extern.hpp"
#include "memory.hpp"
#include "spinlock.hpp"
#include "list.hpp"
#include "quota.hpp"

class Buddy : public List<Buddy>
{
    private:
        class Block
        {
            public:
                Block *         prev;
                Block *         next;
                unsigned short  ord;
                unsigned short  tag;

                enum {
                    Used  = 0,
                    Free  = 1
                };
        };

        Spinlock        lock    { };
        signed long     max_idx { 0 };
        signed long     min_idx { 0 };
        mword           base    { 0 };
        mword           order   { 0 };
        Block *         index   { nullptr };
        Block *         head    { nullptr };

        static Buddy * list;

        ALWAYS_INLINE
        inline signed long block_to_index (Block *b)
        {
            return b - index;
        }

        ALWAYS_INLINE
        inline Block *index_to_block (signed long i)
        {
            return index + i;
        }

        ALWAYS_INLINE
        inline signed long page_to_index (mword l_addr)
        {
            return l_addr / PAGE_SIZE - base / PAGE_SIZE;
        }

        ALWAYS_INLINE
        inline mword index_to_page (signed long i)
        {
            return base + i * PAGE_SIZE;
        }

        ALWAYS_INLINE
        inline mword virt_to_phys (mword virt)
        {
            return virt - reinterpret_cast<mword>(&OFFSET);
        }

        ALWAYS_INLINE
        inline mword phys_to_virt (mword phys)
        {
            return phys + reinterpret_cast<mword>(&OFFSET);
        }

    public:
        enum Fill
        {
            NOFILL,
            FILL_0,
            FILL_1
        };

        static Buddy allocator;

        INIT
        Buddy (mword phys, mword virt, mword f_addr, size_t size);

        static void *alloc (unsigned short ord, Quota &quota, Fill fill);

        static void free (mword addr, Quota &quota);

     private:

        void *_alloc (unsigned short ord, Quota &quota, Fill fill);

        void _free (mword addr, Quota &quota);

     public:

        ALWAYS_INLINE
        static inline void *phys_to_ptr (Paddr phys)
        {
            return reinterpret_cast<void *>(allocator.phys_to_virt (static_cast<mword>(phys)));
        }

        ALWAYS_INLINE
        static inline mword ptr_to_phys (void *virt)
        {
            return allocator.virt_to_phys (reinterpret_cast<mword>(virt));
        }

        ALWAYS_INLINE
        static inline void *operator new (size_t, Quota &quota) { return Buddy::allocator.alloc (0, quota, Buddy::FILL_0); }
};
