code:
/*
 * Copyright (c) 2011-2017 Richard Braun.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * XXX This module is far from complete. It just provides the basic support
 * needed for kernel allocation.
 */

#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

#include <kern/init.h>
#include <kern/kmem.h>
#include <kern/list.h>
#include <kern/macros.h>
#include <kern/mutex.h>
#include <kern/panic.h>
#include <kern/rbtree.h>
#include <kern/shell.h>
#include <kern/task.h>
#include <machine/page.h>
#include <machine/pmap.h>
#include <vm/vm_adv.h>
#include <vm/vm_inherit.h>
#include <vm/vm_map.h>
#include <vm/vm_kmem.h>
#include <vm/vm_page.h>
#include <vm/vm_prot.h>

/*
 * Special threshold which disables the use of the free area cache address.
 */
#define VM_MAP_NO_FIND_CACHE (~(size_t)0)

/*
 * Mapping request.
 *
 * Most members are input parameters from a call to e.g. vm_map_enter(). The
 * start member is also an output argument. The next member is used internally
 * by the mapping functions.
 */
struct vm_map_request {
    uintptr_t start;
    size_t size;
    size_t align;
    int flags;
    struct vm_object *object;
    uint64_t offset;
    struct vm_map_entry *next;
};

static int vm_map_prepare(struct vm_map *map, uintptr_t start,
                          size_t size, size_t align, int flags,
                          struct vm_object *object, uint64_t offset,
                          struct vm_map_request *request);

static int vm_map_insert(struct vm_map *map, struct vm_map_entry *entry,
                         const struct vm_map_request *request);

static struct kmem_cache vm_map_entry_cache;
static struct kmem_cache vm_map_cache;

struct vm_map vm_map_kernel_map;

static struct vm_map_entry *
vm_map_entry_create(void)
{
    struct vm_map_entry *entry;

    entry = kmem_cache_alloc(&vm_map_entry_cache);

    /* TODO Handle error */
    if (entry == NULL) {
        panic("vm_map: can't create map entry");
    }

    return entry;
}

static void
vm_map_entry_destroy(struct vm_map_entry *entry)
{
    kmem_cache_free(&vm_map_entry_cache, entry);
}

static inline int
vm_map_entry_cmp_lookup(uintptr_t addr, const struct rbtree_node *node)
{
    struct vm_map_entry *entry;

    entry = rbtree_entry(node, struct vm_map_entry, tree_node);

    if (addr >= entry->end) {
        return 1;
    }

    if (addr >= entry->start) {
        return 0;
    }

    return -1;
}

static inline int
vm_map_entry_cmp_insert(const struct rbtree_node *a,
                        const struct rbtree_node *b)
{
    struct vm_map_entry *entry;

    entry = rbtree_entry(a, struct vm_map_entry, tree_node);
    return vm_map_entry_cmp_lookup(entry->start, b);
}

static bool
vm_map_request_valid(const struct vm_map_request *request)
{
    return ((request->object != NULL) || (request->offset == 0))
           && (vm_page_aligned(request->offset))
           && (vm_page_aligned(request->start))
           && (request->size > 0)
           && (vm_page_aligned(request->size))
           && ((request->start + request->size) > request->start)
           && ((request->align == 0) || (request->align >= PAGE_SIZE))
           && (ISP2(request->align))
           && ((VM_MAP_PROT(request->flags) & VM_MAP_MAXPROT(request->flags))
               == VM_MAP_PROT(request->flags))
           && (!(request->flags & VM_MAP_FIXED)
               || (request->align == 0)
               || P2ALIGNED(request->start, request->align));
}

/*
 * Look up an entry in a map.
 *
 * This function returns the entry which is closest to the given address
 * such that addr < entry->end (i.e. either containing or after the requested
 * address), or NULL if there is no such entry.
 */
static struct vm_map_entry *
vm_map_lookup_nearest(struct vm_map *map, uintptr_t addr)
{
    struct vm_map_entry *entry;
    struct rbtree_node *node;

    assert(vm_page_aligned(addr));

    entry = map->lookup_cache;

    if ((entry != NULL) && (addr >= entry->start) && (addr < entry->end)) {
        return entry;
    }

    node = rbtree_lookup_nearest(&map->entry_tree, addr,
                                 vm_map_entry_cmp_lookup, RBTREE_RIGHT);

    if (node != NULL) {
        entry = rbtree_entry(node, struct vm_map_entry, tree_node);
        assert(addr < entry->end);
        map->lookup_cache = entry;
        return entry;
    }

    return NULL;
}

static void
vm_map_reset_find_cache(struct vm_map *map)
{
    map->find_cache = 0;
    map->find_cache_threshold = VM_MAP_NO_FIND_CACHE;
}

static int
vm_map_find_fixed(struct vm_map *map, struct vm_map_request *request)
{
    struct vm_map_entry *next;
    uintptr_t start;
    size_t size;

    start = request->start;
    size = request->size;

    if ((start < map->start) || (start + size) > map->end) {
        return ENOMEM;
    }

    next = vm_map_lookup_nearest(map, start);

    if (next == NULL) {
        if ((map->end - start) < size) {
            return ENOMEM;
        }

        request->next = NULL;
        return 0;
    }

    if ((start >= next->start) || ((next->start - start) < size)) {
        return ENOMEM;
    }

    request->next = next;
    return 0;
}

static int
vm_map_find_avail(struct vm_map *map, struct vm_map_request *request)
{
    struct vm_map_entry *next;
    struct list *node;
    uintptr_t base, start;
    size_t size, align, space;
    int error;

    /* If there is a hint, try there */
    if (request->start != 0) {
        error = vm_map_find_fixed(map, request);

        if (!error) {
            return 0;
        }
    }

    size = request->size;
    align = request->align;

    if (size > map->find_cache_threshold) {
        base = map->find_cache;
    } else {
        base = map->start;

        /*
         * Searching from the map start means the area which size is the
         * threshold (or a smaller one) may be selected, making the threshold
         * invalid. Reset it.
         */
        map->find_cache_threshold = 0;
    }

retry:
    start = base;
    next = vm_map_lookup_nearest(map, start);

    for (;;) {
        assert(start <= map->end);

        if (align != 0) {
            start = P2ROUND(start, align);
        }

        /*
         * The end of the map has been reached, and no space could be found.
         * If the search didn't start at map->start, retry from there in case
         * space is available below the previous start address.
         */
        if ((map->end - start) < size) {
            if (base != map->start) {
                base = map->start;
                map->find_cache_threshold = 0;
                goto retry;
            }

            return ENOMEM;
        }

        if (next == NULL) {
            space = map->end - start;
        } else if (start >= next->start) {
            space = 0;
        } else {
            space = next->start - start;
        }

        if (space >= size) {
            map->find_cache = start + size;
            request->start = start;
            request->next = next;
            return 0;
        }

        if (space > map->find_cache_threshold) {
            map->find_cache_threshold = space;
        }

        start = next->end;
        node = list_next(&next->list_node);

        if (list_end(&map->entry_list, node)) {
            next = NULL;
        } else {
            next = list_entry(node, struct vm_map_entry, list_node);
        }
    }
}

static inline struct vm_map_entry *
vm_map_next(struct vm_map *map, struct vm_map_entry *entry)
{
    struct list *node;

    node = list_next(&entry->list_node);

    if (list_end(&map->entry_list, node)) {
        return NULL;
    } else {
        return list_entry(node, struct vm_map_entry, list_node);
    }
}

static void
vm_map_link(struct vm_map *map, struct vm_map_entry *entry,
            struct vm_map_entry *next)
{
    assert(entry->start < entry->end);

    if (next == NULL) {
        list_insert_tail(&map->entry_list, &entry->list_node);
    } else {
        list_insert_before(&entry->list_node, &next->list_node);
    }

    rbtree_insert(&map->entry_tree, &entry->tree_node, vm_map_entry_cmp_insert);
    map->nr_entries++;
}

static void
vm_map_unlink(struct vm_map *map, struct vm_map_entry *entry)
{
    assert(entry->start < entry->end);

    if (map->lookup_cache == entry) {
        map->lookup_cache = NULL;
    }

    list_remove(&entry->list_node);
    rbtree_remove(&map->entry_tree, &entry->tree_node);
    map->nr_entries--;
}

/*
 * Check mapping parameters, find a suitable area of virtual memory, and
 * prepare the mapping request for that region.
 */
static int
vm_map_prepare(struct vm_map *map, uintptr_t start,
               size_t size, size_t align, int flags,
               struct vm_object *object, uint64_t offset,
               struct vm_map_request *request)
{
    int error;

    request->start = start;
    request->size = size;
    request->align = align;
    request->flags = flags;
    request->object = object;
    request->offset = offset;
    assert(vm_map_request_valid(request));

    if (flags & VM_MAP_FIXED) {
        error = vm_map_find_fixed(map, request);
    } else {
        error = vm_map_find_avail(map, request);
    }

    return error;
}

/*
 * Merging functions.
 *
 * There is room for optimization (e.g. not reinserting entries when it is
 * known the tree doesn't need to be adjusted), but focus on correctness for
 * now.
 */

static inline int
vm_map_try_merge_compatible(const struct vm_map_request *request,
                            const struct vm_map_entry *entry)
{
    /* Only merge special kernel mappings for now */
    return (request->object == NULL)
           && (entry->object == NULL)
           && ((request->flags & VM_MAP_ENTRY_MASK)
               == (entry->flags & VM_MAP_ENTRY_MASK));
}

static struct vm_map_entry *
vm_map_try_merge_prev(struct vm_map *map, const struct vm_map_request *request,
                      struct vm_map_entry *entry)
{
    struct vm_map_entry *next;

    assert(entry != NULL);

    if (!vm_map_try_merge_compatible(request, entry)) {
        return NULL;
    }

    if (entry->end != request->start) {
        return NULL;
    }

    next = vm_map_next(map, entry);
    vm_map_unlink(map, entry);
    entry->end += request->size;
    vm_map_link(map, entry, next);
    return entry;
}

static struct vm_map_entry *
vm_map_try_merge_next(struct vm_map *map, const struct vm_map_request *request,
                      struct vm_map_entry *entry)
{
    struct vm_map_entry *next;
    uintptr_t end;

    assert(entry != NULL);

    if (!vm_map_try_merge_compatible(request, entry)) {
        return NULL;
    }

    end = request->start + request->size;

    if (end != entry->start) {
        return NULL;
    }

    next = vm_map_next(map, entry);
    vm_map_unlink(map, entry);
    entry->start = request->start;
    vm_map_link(map, entry, next);
    return entry;
}

static struct vm_map_entry *
vm_map_try_merge_near(struct vm_map *map, const struct vm_map_request *request,
                      struct vm_map_entry *first, struct vm_map_entry *second)
{
    struct vm_map_entry *entry;

    assert(first != NULL);
    assert(second != NULL);

    if ((first->end == request->start)
        && ((request->start + request->size) == second->start)
        && vm_map_try_merge_compatible(request, first)
        && vm_map_try_merge_compatible(request, second)) {
        struct vm_map_entry *next;

        next = vm_map_next(map, second);
        vm_map_unlink(map, first);
        vm_map_unlink(map, second);
        first->end = second->end;
        vm_map_entry_destroy(second);
        vm_map_link(map, first, next);
        return first;
    }

    entry = vm_map_try_merge_prev(map, request, first);

    if (entry != NULL) {
        return entry;
    }

    return vm_map_try_merge_next(map, request, second);
}

static struct vm_map_entry *
vm_map_try_merge(struct vm_map *map, const struct vm_map_request *request)
{
    struct vm_map_entry *entry, *prev;
    struct list *node;

    /* Statically allocated map entries must not be merged */
    assert(!(request->flags & VM_MAP_NOMERGE));

    if (request->next == NULL) {
        node = list_last(&map->entry_list);

        if (list_end(&map->entry_list, node)) {
            entry = NULL;
        } else {
            prev = list_entry(node, struct vm_map_entry, list_node);
            entry = vm_map_try_merge_prev(map, request, prev);
        }
    } else {
        node = list_prev(&request->next->list_node);

        if (list_end(&map->entry_list, node)) {
            entry = vm_map_try_merge_next(map, request, request->next);
        } else {
            prev = list_entry(node, struct vm_map_entry, list_node);
            entry = vm_map_try_merge_near(map, request, prev, request->next);
        }
    }

    return entry;
}

/*
 * Convert a prepared mapping request into an entry in the given map.
 *
 * If entry is NULL, a map entry is allocated for the mapping.
 */
static int
vm_map_insert(struct vm_map *map, struct vm_map_entry *entry,
              const struct vm_map_request *request)
{
    if (entry == NULL) {
        entry = vm_map_try_merge(map, request);

        if (entry != NULL) {
            goto out;
        }

        entry = vm_map_entry_create();
    }

    entry->start = request->start;
    entry->end = request->start + request->size;
    entry->object = request->object;
    entry->offset = request->offset;
    entry->flags = request->flags & VM_MAP_ENTRY_MASK;
    vm_map_link(map, entry, request->next);

out:
    map->size += request->size;
    return 0;
}

int
vm_map_enter(struct vm_map *map, uintptr_t *startp,
             size_t size, size_t align, int flags,
             struct vm_object *object, uint64_t offset)
{
    struct vm_map_request request;
    int error;

    mutex_lock(&map->lock);

    error = vm_map_prepare(map, *startp, size, align, flags, object, offset,
                           &request);

    if (error) {
        goto error_enter;
    }

    error = vm_map_insert(map, NULL, &request);

    if (error) {
        goto error_enter;
    }

    mutex_unlock(&map->lock);

    *startp = request.start;
    return 0;

error_enter:
    vm_map_reset_find_cache(map);
    mutex_unlock(&map->lock);
    return error;
}

static void
vm_map_split_entries(struct vm_map_entry *prev, struct vm_map_entry *next,
                     uintptr_t split_addr)
{
    uintptr_t delta;

    delta = split_addr - prev->start;
    prev->end = split_addr;
    next->start = split_addr;

    if (next->object != NULL) {
        next->offset += delta;
    }
}

static void
vm_map_clip_start(struct vm_map *map, struct vm_map_entry *entry,
                  uintptr_t start)
{
    struct vm_map_entry *new_entry, *next;

    if ((start <= entry->start) || (start >= entry->end)) {
        return;
    }

    next = vm_map_next(map, entry);
    vm_map_unlink(map, entry);
    new_entry = vm_map_entry_create();
    *new_entry = *entry;
    vm_map_split_entries(new_entry, entry, start);
    vm_map_link(map, entry, next);
    vm_map_link(map, new_entry, entry);
}

static void
vm_map_clip_end(struct vm_map *map, struct vm_map_entry *entry, uintptr_t end)
{
    struct vm_map_entry *new_entry, *next;

    if ((end <= entry->start) || (end >= entry->end)) {
        return;
    }

    next = vm_map_next(map, entry);
    vm_map_unlink(map, entry);
    new_entry = vm_map_entry_create();
    *new_entry = *entry;
    vm_map_split_entries(entry, new_entry, end);
    vm_map_link(map, entry, next);
    vm_map_link(map, new_entry, next);
}

void
vm_map_remove(struct vm_map *map, uintptr_t start, uintptr_t end)
{
    struct vm_map_entry *entry;
    struct list *node;

    assert(start >= map->start);
    assert(end <= map->end);
    assert(start < end);

    mutex_lock(&map->lock);

    entry = vm_map_lookup_nearest(map, start);

    if (entry == NULL) {
        goto out;
    }

    vm_map_clip_start(map, entry, start);

    while (entry->start < end) {
        vm_map_clip_end(map, entry, end);
        map->size -= entry->end - entry->start;
        node = list_next(&entry->list_node);
        vm_map_unlink(map, entry);

        /* TODO Defer destruction to shorten critical section */
        vm_map_entry_destroy(entry);

        if (list_end(&map->entry_list, node)) {
            break;
        }

        entry = list_entry(node, struct vm_map_entry, list_node);
    }

    vm_map_reset_find_cache(map);

out:
    mutex_unlock(&map->lock);
}

static void
vm_map_init(struct vm_map *map, struct pmap *pmap,
            uintptr_t start, uintptr_t end)
{
    assert(vm_page_aligned(start));
    assert(vm_page_aligned(end));
    assert(start < end);

    mutex_init(&map->lock);
    list_init(&map->entry_list);
    rbtree_init(&map->entry_tree);
    map->nr_entries = 0;
    map->start = start;
    map->end = end;
    map->size = 0;
    map->lookup_cache = NULL;
    vm_map_reset_find_cache(map);
    map->pmap = pmap;
}

#ifdef CONFIG_SHELL

static void
vm_map_shell_info(struct shell *shell, int argc, char **argv)
{
    const struct task *task;

    (void)shell;

    if (argc < 2) {
        goto error;
    } else {
        task = task_lookup(argv[1]);

        if (task == NULL) {
            goto error;
        }

        vm_map_info(task_get_vm_map(task));
    }

    return;

error:
    printf("vm_map: info: invalid arguments\n");
}

static struct shell_cmd vm_map_shell_cmds[] = {
    SHELL_CMD_INITIALIZER("vm_map_info", vm_map_shell_info,
        "vm_map_info <task_name>",
        "display information about a VM map"),
};

static int __init
vm_map_setup_shell(void)
{
    SHELL_REGISTER_CMDS(vm_map_shell_cmds, shell_get_main_cmd_set());
    return 0;
}

INIT_OP_DEFINE(vm_map_setup_shell,
               INIT_OP_DEP(mutex_setup, true),
               INIT_OP_DEP(printf_setup, true),
               INIT_OP_DEP(shell_setup, true),
               INIT_OP_DEP(task_setup, true),
               INIT_OP_DEP(vm_map_setup, true));

#endif /* CONFIG_SHELL */

static int __init
vm_map_bootstrap(void)
{
    vm_map_init(vm_map_get_kernel_map(), pmap_get_kernel_pmap(),
                PMAP_START_KMEM_ADDRESS, PMAP_END_KMEM_ADDRESS);
    kmem_cache_init(&vm_map_entry_cache, "vm_map_entry",
                    sizeof(struct vm_map_entry), 0, NULL,
                    KMEM_CACHE_PAGE_ONLY);
    return 0;
}

INIT_OP_DEFINE(vm_map_bootstrap,
               INIT_OP_DEP(kmem_bootstrap, true),
               INIT_OP_DEP(thread_bootstrap, true));

static int __init
vm_map_setup(void)
{
    kmem_cache_init(&vm_map_cache, "vm_map", sizeof(struct vm_map),
                    0, NULL, KMEM_CACHE_PAGE_ONLY);
    return 0;
}

INIT_OP_DEFINE(vm_map_setup,
               INIT_OP_DEP(pmap_setup, true),
               INIT_OP_DEP(printf_setup, true),
               INIT_OP_DEP(vm_map_bootstrap, true));

int
vm_map_create(struct vm_map **mapp)
{
    struct vm_map *map;
    struct pmap *pmap;
    int error;

    map = kmem_cache_alloc(&vm_map_cache);

    if (map == NULL) {
        error = ENOMEM;
        goto error_map;
    }

    error = pmap_create(&pmap);

    if (error) {
        goto error_pmap;
    }

    vm_map_init(map, pmap, PMAP_START_ADDRESS, PMAP_END_ADDRESS);
    *mapp = map;
    return 0;

error_pmap:
    kmem_cache_free(&vm_map_cache, map);
error_map:
    return error;
}

void
vm_map_info(struct vm_map *map)
{
    struct vm_map_entry *entry;
    const char *type, *name;

    if (map == vm_map_get_kernel_map()) {
        name = "kernel map";
    } else {
        name = "map";
    }

    mutex_lock(&map->lock);

    printf("vm_map: %s: %016lx-%016lx\n"
           "vm_map:      start             end          "
           "size     offset   flags    type\n", name,
           (unsigned long)map->start, (unsigned long)map->end);

    list_for_each_entry(&map->entry_list, entry, list_node) {
        if (entry->object == NULL) {
            type = "null";
        } else {
            type = "object";
        }

        printf("vm_map: %016lx %016lx %8luk %08llx %08x %s\n",
               (unsigned long)entry->start, (unsigned long)entry->end,
               (unsigned long)(entry->end - entry->start) >> 10,
               (unsigned long long)entry->offset, entry->flags, type);
    }

    printf("vm_map: total: %zuk\n", map->size >> 10);

    mutex_unlock(&map->lock);
}


code:
/*
 * Copyright (c) 2011-2017 Richard Braun.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Virtual memory map management.
 */

#ifndef VM_VM_MAP_H
#define VM_VM_MAP_H

#include <stdint.h>

#include <kern/init.h>
#include <kern/list.h>
#include <kern/mutex.h>
#include <kern/rbtree.h>
#include <machine/pmap.h>
#include <vm/vm_adv.h>
#include <vm/vm_inherit.h>
#include <vm/vm_prot.h>

/*
 * Mapping flags.
 *
 * Unless otherwise mentioned, these can also be used as map entry flags.
 */
#define VM_MAP_NOMERGE  0x10000
#define VM_MAP_FIXED    0x20000 /* Not an entry flag */

/*
 * Macro used to forge "packed" flags.
 */
#define VM_MAP_FLAGS(prot, maxprot, inherit, advice, mapflags)          \
    ((prot) | ((maxprot) << 4) | ((inherit) << 8) | ((advice) << 12)    \
     | (mapflags))

/*
 * Flags usable as map entry flags.
 *
 * Map entry flags also use the packed format.
 */
#define VM_MAP_ENTRY_MASK (VM_MAP_NOMERGE | 0xffff)

/*
 * Macros used to extract specific properties out of packed flags.
 */
#define VM_MAP_PROT(flags)      ((flags) & 0xf)
#define VM_MAP_MAXPROT(flags)   (((flags) & 0xf0) >> 4)
#define VM_MAP_INHERIT(flags)   (((flags) & 0xf00) >> 8)
#define VM_MAP_ADVICE(flags)    (((flags) & 0xf000) >> 12)

/*
 * Memory range descriptor.
 */
struct vm_map_entry {
    struct list list_node;
    struct rbtree_node tree_node;
    uintptr_t start;
    uintptr_t end;
    struct vm_object *object;
    uint64_t offset;
    int flags;
};

/*
 * Memory map.
 */
struct vm_map {
    struct mutex lock;
    struct list entry_list;
    struct rbtree entry_tree;
    unsigned int nr_entries;
    uintptr_t start;
    uintptr_t end;
    size_t size;
    struct vm_map_entry *lookup_cache;
    uintptr_t find_cache;
    size_t find_cache_threshold;
    struct pmap *pmap;
};

static inline struct vm_map *
vm_map_get_kernel_map(void)
{
    extern struct vm_map vm_map_kernel_map;

    return &vm_map_kernel_map;
}

/*
 * Create a virtual mapping.
 */
int vm_map_enter(struct vm_map *map, uintptr_t *startp,
                 size_t size, size_t align, int flags,
                 struct vm_object *object, uint64_t offset);

/*
 * Remove mappings from start to end.
 */
void vm_map_remove(struct vm_map *map, uintptr_t start, uintptr_t end);

/*
 * Create a VM map.
 */
int vm_map_create(struct vm_map **mapp);

/*
 * Display information about a memory map.
 */
void vm_map_info(struct vm_map *map);

/*
 * This init operation provides :
 *  - kernel mapping operations
 */
INIT_OP_DECLARE(vm_map_bootstrap);

/*
 * This init operation provides :
 *  - VM map creation
 *  - module fully initialized
 */
INIT_OP_DECLARE(vm_map_setup);

#endif /* VM_VM_MAP_H */

license: mit


code:
/*
 * Copyright (c) 2017 Richard Braun.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * This implementation is based on the paper "A lockless pagecache in Linux"
 * by Nick Piggin. It allows looking up pages without contention on VM objects.
 */

#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>

#include <kern/init.h>
#include <kern/mutex.h>
#include <kern/rcu.h>
#include <kern/rdxtree.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <machine/page.h>

struct vm_object vm_object_kernel_object;

static int __init
vm_object_setup(void)
{
    return 0;
}

INIT_OP_DEFINE(vm_object_setup,
               INIT_OP_DEP(mutex_setup, true),
               INIT_OP_DEP(rdxtree_setup, true),
               INIT_OP_DEP(vm_page_setup, true));

void __init
vm_object_init(struct vm_object *object, uint64_t size)
{
    assert(vm_page_aligned(size));

    mutex_init(&object->lock);
    rdxtree_init(&object->pages, 0);
    object->size = size;
    object->nr_pages = 0;
}

int
vm_object_insert(struct vm_object *object, struct vm_page *page,
                 uint64_t offset)
{
    int error;

    assert(vm_page_aligned(offset));

    /*
     * The page may have no references. Add one before publishing
     * so that concurrent lookups succeed.
     */
    vm_page_ref(page);

    mutex_lock(&object->lock);

    if (offset >= object->size) {
        error = EINVAL;
        goto error;
    }

    error = rdxtree_insert(&object->pages, vm_page_btop(offset), page);

    if (error) {
        goto error;
    }

    vm_page_link(page, object, offset);
    object->nr_pages++;
    assert(object->nr_pages != 0);

    mutex_unlock(&object->lock);

    return 0;

error:
    mutex_unlock(&object->lock);

    vm_page_unref(page);

    return error;
}

void
vm_object_remove(struct vm_object *object, uint64_t start, uint64_t end)
{
    struct vm_page *page;
    uint64_t offset;

    assert(vm_page_aligned(start));
    assert(vm_page_aligned(end));
    assert(start <= end);

    mutex_lock(&object->lock);

    for (offset = start; offset < end; offset += PAGE_SIZE) {
        page = rdxtree_remove(&object->pages, vm_page_btop(offset));

        if (page == NULL) {
            continue;
        }

        vm_page_unlink(page);
        vm_page_unref(page);
        assert(object->nr_pages != 0);
        object->nr_pages--;
    }

    mutex_unlock(&object->lock);
}

struct vm_page *
vm_object_lookup(struct vm_object *object, uint64_t offset)
{
    struct vm_page *page;
    int error;

    rcu_read_enter();

    do {
        page = rdxtree_lookup(&object->pages, vm_page_btop(offset));

        if (page == NULL) {
            break;
        }

        error = vm_page_tryref(page);
    } while (error);

    rcu_read_leave();

    return page;
}


code:
/*
 * Copyright (c) 2017 Richard Braun.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Virtual memory object.
 *
 * The purpose of VM objects is to track pages that are resident in
 * physical memory. They collectively form the page cache.
 */

#ifndef VM_OBJECT_H
#define VM_OBJECT_H

#include <stdint.h>

#include <kern/init.h>
#include <kern/rdxtree.h>
#include <vm/vm_object_types.h>
#include <vm/vm_page.h>

struct vm_object;

static inline struct vm_object *
vm_object_get_kernel_object(void)
{
    extern struct vm_object vm_object_kernel_object;

    return &vm_object_kernel_object;
}

/*
 * Initialize a VM object.
 */
void vm_object_init(struct vm_object *object, uint64_t size);

/*
 * Insert a page into a VM object.
 *
 * The offset must be page-aligned.
 *
 * The page becomes managed, and gains a reference. If successful,
 * the reference is kept. Otherwise it's dropped. If the page had
 * no references on entry, and a failure occurs, the page is freed.
 */
int vm_object_insert(struct vm_object *object, struct vm_page *page,
                     uint64_t offset);

/*
 * Remove pages from a VM object.
 *
 * The range boundaries must be page-aligned.
 *
 * Holes in the given range are silently skipped. Pages that are removed
 * become unmanaged and lose a reference.
 */
void vm_object_remove(struct vm_object *object, uint64_t start, uint64_t end);

/*
 * Look up a page in a VM object.
 *
 * The offset must be page-aligned.
 *
 * If successful, the returned page gains a reference. Note that, if a valid
 * page is returned, it may already have been removed from the object, or
 * moved at a different offset.
 */
struct vm_page * vm_object_lookup(struct vm_object *object, uint64_t offset);

/*
 * This init operation provides :
 *  - module fully initialized
 */
INIT_OP_DECLARE(vm_object_setup);

#endif /* VM_OBJECT_H */


code:
/*
 * Copyright (c) 2017 Richard Braun.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Isolated type definition used to avoid inclusion circular dependencies.
 */

#ifndef VM_OBJECT_TYPES_H
#define VM_OBJECT_TYPES_H

#include <stdint.h>

#include <kern/mutex.h>
#include <kern/rdxtree.h>

struct vm_object {
    struct mutex lock;
    struct rdxtree pages;
    uint64_t size;
    unsigned long nr_pages;
};

#endif /* VM_OBJECT_TYPES_H */


code:
/*
 * Copyright (c) 2010-2017 Richard Braun.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * This implementation uses the binary buddy system to manage its heap.
 * Descriptions of the buddy system can be found in the following works :
 * - "UNIX Internals: The New Frontiers", by Uresh Vahalia.
 * - "Dynamic Storage Allocation: A Survey and Critical Review",
 *    by Paul R. Wilson, Mark S. Johnstone, Michael Neely, and David Boles.
 *
 * In addition, this allocator uses per-CPU pools of pages for order 0
 * (i.e. single page) allocations. These pools act as caches (but are named
 * differently to avoid confusion with CPU caches) that reduce contention on
 * multiprocessor systems. When a pool is empty and cannot provide a page,
 * it is filled by transferring multiple pages from the backend buddy system.
 * The symmetric case is handled likewise.
 */

#include <assert.h>
#include <stdalign.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <kern/init.h>
#include <kern/list.h>
#include <kern/log.h>
#include <kern/macros.h>
#include <kern/mutex.h>
#include <kern/panic.h>
#include <kern/printf.h>
#include <kern/shell.h>
#include <kern/thread.h>
#include <machine/boot.h>
#include <machine/cpu.h>
#include <machine/page.h>
#include <machine/pmem.h>
#include <machine/types.h>
#include <vm/vm_page.h>

/*
 * Number of free block lists per zone.
 */
#define VM_PAGE_NR_FREE_LISTS 11

/*
 * The size of a CPU pool is computed by dividing the number of pages in its
 * containing zone by this value.
 */
#define VM_PAGE_CPU_POOL_RATIO 1024

/*
 * Maximum number of pages in a CPU pool.
 */
#define VM_PAGE_CPU_POOL_MAX_SIZE 128

/*
 * The transfer size of a CPU pool is computed by dividing the pool size by
 * this value.
 */
#define VM_PAGE_CPU_POOL_TRANSFER_RATIO 2

/*
 * Per-processor cache of pages.
 */
struct vm_page_cpu_pool {
    alignas(CPU_L1_SIZE) struct mutex lock;
    int size;
    int transfer_size;
    int nr_pages;
    struct list pages;
};

/*
 * Special order value for pages that aren't in a free list. Such pages are
 * either allocated, or part of a free block of pages but not the head page.
 */
#define VM_PAGE_ORDER_UNLISTED ((unsigned short)-1)

/*
 * Doubly-linked list of free blocks.
 */
struct vm_page_free_list {
    unsigned long size;
    struct list blocks;
};

/*
 * Zone name buffer size.
 */
#define VM_PAGE_NAME_SIZE 16

/*
 * Zone of contiguous memory.
 */
struct vm_page_zone {
    struct vm_page_cpu_pool cpu_pools[CONFIG_MAX_CPUS];

    phys_addr_t start;
    phys_addr_t end;
    struct vm_page *pages;
    struct vm_page *pages_end;
    struct mutex lock;
    struct vm_page_free_list free_lists[VM_PAGE_NR_FREE_LISTS];
    unsigned long nr_free_pages;
};

/*
 * Bootstrap information about a zone.
 */
struct vm_page_boot_zone {
    phys_addr_t start;
    phys_addr_t end;
    bool heap_present;
    phys_addr_t avail_start;
    phys_addr_t avail_end;
};

static int vm_page_is_ready __read_mostly;

/*
 * Zone table.
 *
 * The system supports a maximum of 4 zones :
 *  - DMA: suitable for DMA
 *  - DMA32: suitable for DMA when devices support 32-bits addressing
 *  - DIRECTMAP: direct physical mapping, allows direct access from
 *    the kernel with a simple offset translation
 *  - HIGHMEM: must be mapped before it can be accessed
 *
 * Zones are ordered by priority, 0 being the lowest priority. Their
 * relative priorities are DMA < DMA32 < DIRECTMAP < HIGHMEM. Some zones
 * may actually be aliases for others, e.g. if DMA is always possible from
 * the direct physical mapping, DMA and DMA32 are aliases for DIRECTMAP,
 * in which case the zone table contains DIRECTMAP and HIGHMEM only.
 */
static struct vm_page_zone vm_page_zones[PMEM_MAX_ZONES];

/*
 * Bootstrap zone table.
 */
static struct vm_page_boot_zone vm_page_boot_zones[PMEM_MAX_ZONES]
    __initdata;

/*
 * Number of loaded zones.
 */
static unsigned int vm_page_zones_size __read_mostly;

static void __init
vm_page_init(struct vm_page *page, unsigned short zone_index, phys_addr_t pa)
{
    memset(page, 0, sizeof(*page));
    page->type = VM_PAGE_RESERVED;
    page->zone_index = zone_index;
    page->order = VM_PAGE_ORDER_UNLISTED;
    page->phys_addr = pa;

    page->nr_refs = 0;

    page->object = NULL;
}

void
vm_page_set_type(struct vm_page *page, unsigned int order, unsigned short type)
{
    unsigned int i, nr_pages;

    nr_pages = 1 << order;

    for (i = 0; i < nr_pages; i++) {
        page[i].type = type;
    }
}

static void __init
vm_page_free_list_init(struct vm_page_free_list *free_list)
{
    free_list->size = 0;
    list_init(&free_list->blocks);
}

static inline void
vm_page_free_list_insert(struct vm_page_free_list *free_list,
                         struct vm_page *page)
{
    assert(page->order == VM_PAGE_ORDER_UNLISTED);

    free_list->size++;
    list_insert_head(&free_list->blocks, &page->node);
}

static inline void
vm_page_free_list_remove(struct vm_page_free_list *free_list,
                         struct vm_page *page)
{
    assert(page->order != VM_PAGE_ORDER_UNLISTED);

    free_list->size--;
    list_remove(&page->node);
}

static struct vm_page *
vm_page_zone_alloc_from_buddy(struct vm_page_zone *zone, unsigned int order)
{
    struct vm_page_free_list *free_list = free_list;
    struct vm_page *page, *buddy;
    unsigned int i;

    assert(order < VM_PAGE_NR_FREE_LISTS);

    for (i = order; i < VM_PAGE_NR_FREE_LISTS; i++) {
        free_list = &zone->free_lists[i];

        if (free_list->size != 0) {
            break;
        }
    }

    if (i == VM_PAGE_NR_FREE_LISTS) {
        return NULL;
    }

    page = list_first_entry(&free_list->blocks, struct vm_page, node);
    vm_page_free_list_remove(free_list, page);
    page->order = VM_PAGE_ORDER_UNLISTED;

    while (i > order) {
        i--;
        buddy = &page[1 << i];
        vm_page_free_list_insert(&zone->free_lists[i], buddy);
        buddy->order = i;
    }

    zone->nr_free_pages -= (1 << order);
    return page;
}

static void
vm_page_zone_free_to_buddy(struct vm_page_zone *zone, struct vm_page *page,
                           unsigned int order)
{
    struct vm_page *buddy;
    phys_addr_t pa, buddy_pa;
    unsigned int nr_pages;

    assert(page >= zone->pages);
    assert(page < zone->pages_end);
    assert(page->order == VM_PAGE_ORDER_UNLISTED);
    assert(order < VM_PAGE_NR_FREE_LISTS);

    nr_pages = (1 << order);
    pa = page->phys_addr;

    while (order < (VM_PAGE_NR_FREE_LISTS - 1)) {
        buddy_pa = pa ^ vm_page_ptob(1 << order);

        if ((buddy_pa < zone->start) || (buddy_pa >= zone->end)) {
            break;
        }

        buddy = &zone->pages[vm_page_btop(buddy_pa - zone->start)];

        if (buddy->order != order) {
            break;
        }

        vm_page_free_list_remove(&zone->free_lists[order], buddy);
        buddy->order = VM_PAGE_ORDER_UNLISTED;
        order++;
        pa &= -vm_page_ptob(1 << order);
        page = &zone->pages[vm_page_btop(pa - zone->start)];
    }

    vm_page_free_list_insert(&zone->free_lists[order], page);
    page->order = order;
    zone->nr_free_pages += nr_pages;
}

static void __init
vm_page_cpu_pool_init(struct vm_page_cpu_pool *cpu_pool, int size)
{
    mutex_init(&cpu_pool->lock);
    cpu_pool->size = size;
    cpu_pool->transfer_size = (size + VM_PAGE_CPU_POOL_TRANSFER_RATIO - 1)
                              / VM_PAGE_CPU_POOL_TRANSFER_RATIO;
    cpu_pool->nr_pages = 0;
    list_init(&cpu_pool->pages);
}

static inline struct vm_page_cpu_pool *
vm_page_cpu_pool_get(struct vm_page_zone *zone)
{
    return &zone->cpu_pools[cpu_id()];
}

static inline struct vm_page *
vm_page_cpu_pool_pop(struct vm_page_cpu_pool *cpu_pool)
{
    struct vm_page *page;

    assert(cpu_pool->nr_pages != 0);
    cpu_pool->nr_pages--;
    page = list_first_entry(&cpu_pool->pages, struct vm_page, node);
    list_remove(&page->node);
    return page;
}

static inline void
vm_page_cpu_pool_push(struct vm_page_cpu_pool *cpu_pool, struct vm_page *page)
{
    assert(cpu_pool->nr_pages < cpu_pool->size);
    cpu_pool->nr_pages++;
    list_insert_head(&cpu_pool->pages, &page->node);
}

static int
vm_page_cpu_pool_fill(struct vm_page_cpu_pool *cpu_pool,
                      struct vm_page_zone *zone)
{
    struct vm_page *page;
    int i;

    assert(cpu_pool->nr_pages == 0);

    mutex_lock(&zone->lock);

    for (i = 0; i < cpu_pool->transfer_size; i++) {
        page = vm_page_zone_alloc_from_buddy(zone, 0);

        if (page == NULL) {
            break;
        }

        vm_page_cpu_pool_push(cpu_pool, page);
    }

    mutex_unlock(&zone->lock);

    return i;
}

static void
vm_page_cpu_pool_drain(struct vm_page_cpu_pool *cpu_pool,
                       struct vm_page_zone *zone)
{
    struct vm_page *page;
    int i;

    assert(cpu_pool->nr_pages == cpu_pool->size);

    mutex_lock(&zone->lock);

    for (i = cpu_pool->transfer_size; i > 0; i--) {
        page = vm_page_cpu_pool_pop(cpu_pool);
        vm_page_zone_free_to_buddy(zone, page, 0);
    }

    mutex_unlock(&zone->lock);
}

static phys_addr_t __init
vm_page_zone_size(struct vm_page_zone *zone)
{
    return zone->end - zone->start;
}

static int __init
vm_page_zone_compute_pool_size(struct vm_page_zone *zone)
{
    phys_addr_t size;

    size = vm_page_btop(vm_page_zone_size(zone)) / VM_PAGE_CPU_POOL_RATIO;

    if (size == 0) {
        size = 1;
    } else if (size > VM_PAGE_CPU_POOL_MAX_SIZE) {
        size = VM_PAGE_CPU_POOL_MAX_SIZE;
    }

    return size;
}

static void __init
vm_page_zone_init(struct vm_page_zone *zone, phys_addr_t start, phys_addr_t end,
                  struct vm_page *pages)
{
    phys_addr_t pa;
    int pool_size;
    unsigned int i;

    zone->start = start;
    zone->end = end;
    pool_size = vm_page_zone_compute_pool_size(zone);

    for (i = 0; i < ARRAY_SIZE(zone->cpu_pools); i++) {
        vm_page_cpu_pool_init(&zone->cpu_pools[i], pool_size);
    }

    zone->pages = pages;
    zone->pages_end = pages + vm_page_btop(vm_page_zone_size(zone));
    mutex_init(&zone->lock);

    for (i = 0; i < ARRAY_SIZE(zone->free_lists); i++) {
        vm_page_free_list_init(&zone->free_lists[i]);
    }

    zone->nr_free_pages = 0;
    i = zone - vm_page_zones;

    for (pa = zone->start; pa < zone->end; pa += PAGE_SIZE) {
        vm_page_init(&pages[vm_page_btop(pa - zone->start)], i, pa);
    }
}

static struct vm_page *
vm_page_zone_alloc(struct vm_page_zone *zone, unsigned int order,
                   unsigned short type)
{
    struct vm_page_cpu_pool *cpu_pool;
    struct vm_page *page;
    int filled;

    assert(order < VM_PAGE_NR_FREE_LISTS);

    if (order == 0) {
        thread_pin();
        cpu_pool = vm_page_cpu_pool_get(zone);
        mutex_lock(&cpu_pool->lock);

        if (cpu_pool->nr_pages == 0) {
            filled = vm_page_cpu_pool_fill(cpu_pool, zone);

            if (!filled) {
                mutex_unlock(&cpu_pool->lock);
                thread_unpin();
                return NULL;
            }
        }

        page = vm_page_cpu_pool_pop(cpu_pool);
        mutex_unlock(&cpu_pool->lock);
        thread_unpin();
    } else {
        mutex_lock(&zone->lock);
        page = vm_page_zone_alloc_from_buddy(zone, order);
        mutex_unlock(&zone->lock);

        if (page == NULL) {
            return NULL;
        }
    }

    assert(page->type == VM_PAGE_FREE);
    vm_page_set_type(page, order, type);
    return page;
}

static void
vm_page_zone_free(struct vm_page_zone *zone, struct vm_page *page,
                  unsigned int order)
{
    struct vm_page_cpu_pool *cpu_pool;

    assert(page->type != VM_PAGE_FREE);
    assert(order < VM_PAGE_NR_FREE_LISTS);

    vm_page_set_type(page, order, VM_PAGE_FREE);

    if (order == 0) {
        thread_pin();
        cpu_pool = vm_page_cpu_pool_get(zone);
        mutex_lock(&cpu_pool->lock);

        if (cpu_pool->nr_pages == cpu_pool->size) {
            vm_page_cpu_pool_drain(cpu_pool, zone);
        }

        vm_page_cpu_pool_push(cpu_pool, page);
        mutex_unlock(&cpu_pool->lock);
        thread_unpin();
    } else {
        mutex_lock(&zone->lock);
        vm_page_zone_free_to_buddy(zone, page, order);
        mutex_unlock(&zone->lock);
    }
}

void __init
vm_page_load(unsigned int zone_index, phys_addr_t start, phys_addr_t end)
{
    struct vm_page_boot_zone *zone;

    assert(zone_index < ARRAY_SIZE(vm_page_boot_zones));
    assert(vm_page_aligned(start));
    assert(vm_page_aligned(end));
    assert(start < end);
    assert(vm_page_zones_size < ARRAY_SIZE(vm_page_boot_zones));

    zone = &vm_page_boot_zones[zone_index];
    zone->start = start;
    zone->end = end;
    zone->heap_present = false;

    log_debug("vm_page: load: %s: %llx:%llx",
              vm_page_zone_name(zone_index),
              (unsigned long long)start, (unsigned long long)end);

    vm_page_zones_size++;
}

void
vm_page_load_heap(unsigned int zone_index, phys_addr_t start, phys_addr_t end)
{
    struct vm_page_boot_zone *zone;

    assert(zone_index < ARRAY_SIZE(vm_page_boot_zones));
    assert(vm_page_aligned(start));
    assert(vm_page_aligned(end));

    zone = &vm_page_boot_zones[zone_index];

    assert(zone->start <= start);
    assert(end <= zone-> end);

    zone->avail_start = start;
    zone->avail_end = end;
    zone->heap_present = true;

    log_debug("vm_page: heap: %s: %llx:%llx",
              vm_page_zone_name(zone_index),
              (unsigned long long)start, (unsigned long long)end);
}

int
vm_page_ready(void)
{
    return vm_page_is_ready;
}

static unsigned int
vm_page_select_alloc_zone(unsigned int selector)
{
    unsigned int zone_index;

    switch (selector) {
    case VM_PAGE_SEL_DMA:
        zone_index = PMEM_ZONE_DMA;
        break;
    case VM_PAGE_SEL_DMA32:
        zone_index = PMEM_ZONE_DMA32;
        break;
    case VM_PAGE_SEL_DIRECTMAP:
        zone_index = PMEM_ZONE_DIRECTMAP;
        break;
    case VM_PAGE_SEL_HIGHMEM:
        zone_index = PMEM_ZONE_HIGHMEM;
        break;
    default:
        panic("vm_page: invalid selector");
    }

    return MIN(vm_page_zones_size - 1, zone_index);
}

static int __init
vm_page_boot_zone_loaded(const struct vm_page_boot_zone *zone)
{
    return (zone->end != 0);
}

static void __init
vm_page_check_boot_zones(void)
{
    unsigned int i;
    int expect_loaded;

    if (vm_page_zones_size == 0) {
        panic("vm_page: no physical memory loaded");
    }

    for (i = 0; i < ARRAY_SIZE(vm_page_boot_zones); i++) {
        expect_loaded = (i < vm_page_zones_size);

        if (vm_page_boot_zone_loaded(&vm_page_boot_zones[i]) == expect_loaded) {
            continue;
        }

        panic("vm_page: invalid boot zone table");
    }
}

static phys_addr_t __init
vm_page_boot_zone_size(struct vm_page_boot_zone *zone)
{
    return zone->end - zone->start;
}

static phys_addr_t __init
vm_page_boot_zone_avail_size(struct vm_page_boot_zone *zone)
{
    return zone->avail_end - zone->avail_start;
}

static void * __init
vm_page_bootalloc(size_t size)
{
    struct vm_page_boot_zone *zone;
    phys_addr_t pa;
    unsigned int i;

    for (i = vm_page_select_alloc_zone(VM_PAGE_SEL_DIRECTMAP);
         i < vm_page_zones_size;
         i--) {
        zone = &vm_page_boot_zones[i];

        if (!zone->heap_present) {
            continue;
        }

        if (size <= vm_page_boot_zone_avail_size(zone)) {
            pa = zone->avail_start;
            zone->avail_start += vm_page_round(size);
            return (void *)vm_page_direct_va(pa);
        }
    }

    panic("vm_page: no physical memory available");
}

static void
vm_page_info_common(int (*print_fn)(const char *format, ...))
{
    struct vm_page_zone *zone;
    unsigned long pages;
    unsigned int i;

    for (i = 0; i < vm_page_zones_size; i++) {
        zone = &vm_page_zones[i];
        pages = (unsigned long)(zone->pages_end - zone->pages);
        print_fn("vm_page: %s: pages: %lu (%luM), free: %lu (%luM)\n",
                 vm_page_zone_name(i), pages, pages >> (20 - PAGE_SHIFT),
                 zone->nr_free_pages, zone->nr_free_pages >> (20 - PAGE_SHIFT));
    }
}

#ifdef CONFIG_SHELL

static void
vm_page_info(void)
{
    vm_page_info_common(printf);
}

static void
vm_page_shell_info(struct shell *shell, int argc, char **argv)
{
    (void)shell;
    (void)argc;
    (void)argv;
    vm_page_info();
}

static struct shell_cmd vm_page_shell_cmds[] = {
    SHELL_CMD_INITIALIZER("vm_page_info", vm_page_shell_info,
        "vm_page_info",
        "display information about physical memory"),
};

static int __init
vm_page_setup_shell(void)
{
    SHELL_REGISTER_CMDS(vm_page_shell_cmds, shell_get_main_cmd_set());
    return 0;
}

INIT_OP_DEFINE(vm_page_setup_shell,
               INIT_OP_DEP(printf_setup, true),
               INIT_OP_DEP(shell_setup, true),
               INIT_OP_DEP(vm_page_setup, true));

#endif /* CONFIG_SHELL */

static int __init
vm_page_setup(void)
{
    struct vm_page_boot_zone *boot_zone;
    struct vm_page_zone *zone;
    struct vm_page *table, *page, *end;
    size_t nr_pages, table_size;
    uintptr_t va;
    unsigned int i;
    phys_addr_t pa;

    vm_page_check_boot_zones();

    /*
     * Compute the page table size.
     */
    nr_pages = 0;

    for (i = 0; i < vm_page_zones_size; i++) {
        nr_pages += vm_page_btop(vm_page_boot_zone_size(&vm_page_boot_zones[i]));
    }

    table_size = vm_page_round(nr_pages * sizeof(struct vm_page));
    log_info("vm_page: page table size: %zu entries (%zuk)", nr_pages,
             table_size >> 10);
    table = vm_page_bootalloc(table_size);
    va = (uintptr_t)table;

    /*
     * Initialize the zones, associating them to the page table. When
     * the zones are initialized, all their pages are set allocated.
     * Pages are then released, which populates the free lists.
     */
    for (i = 0; i < vm_page_zones_size; i++) {
        zone = &vm_page_zones[i];
        boot_zone = &vm_page_boot_zones[i];
        vm_page_zone_init(zone, boot_zone->start, boot_zone->end, table);
        page = zone->pages + vm_page_btop(boot_zone->avail_start
                                          - boot_zone->start);
        end = zone->pages + vm_page_btop(boot_zone->avail_end
                                         - boot_zone->start);

        while (page < end) {
            page->type = VM_PAGE_FREE;
            vm_page_zone_free_to_buddy(zone, page, 0);
            page++;
        }

        table += vm_page_btop(vm_page_zone_size(zone));
    }

    while (va < (uintptr_t)table) {
        pa = vm_page_direct_pa(va);
        page = vm_page_lookup(pa);
        assert((page != NULL) && (page->type == VM_PAGE_RESERVED));
        page->type = VM_PAGE_TABLE;
        va += PAGE_SIZE;
    }

    vm_page_is_ready = 1;

    return 0;
}

INIT_OP_DEFINE(vm_page_setup,
               INIT_OP_DEP(boot_load_vm_page_zones, true),
               INIT_OP_DEP(log_setup, true),
               INIT_OP_DEP(printf_setup, true));

/* TODO Rename to avoid confusion with "managed pages" */
void __init
vm_page_manage(struct vm_page *page)
{
    assert(page->zone_index < ARRAY_SIZE(vm_page_zones));
    assert(page->type == VM_PAGE_RESERVED);

    vm_page_set_type(page, 0, VM_PAGE_FREE);
    vm_page_zone_free_to_buddy(&vm_page_zones[page->zone_index], page, 0);
}

struct vm_page *
vm_page_lookup(phys_addr_t pa)
{
    struct vm_page_zone *zone;
    unsigned int i;

    for (i = 0; i < vm_page_zones_size; i++) {
        zone = &vm_page_zones[i];

        if ((pa >= zone->start) && (pa < zone->end)) {
            return &zone->pages[vm_page_btop(pa - zone->start)];
        }
    }

    return NULL;
}

static bool
vm_page_block_referenced(const struct vm_page *page, unsigned int order)
{
    unsigned int i, nr_pages;

    nr_pages = 1 << order;

    for (i = 0; i < nr_pages; i++) {
        if (vm_page_referenced(&page[i])) {
            return true;
        }
    }

    return false;
}

struct vm_page *
vm_page_alloc(unsigned int order, unsigned int selector, unsigned short type)
{
    struct vm_page *page;
    unsigned int i;

    for (i = vm_page_select_alloc_zone(selector); i < vm_page_zones_size; i--) {
        page = vm_page_zone_alloc(&vm_page_zones[i], order, type);

        if (page != NULL) {
            assert(!vm_page_block_referenced(page, order));
            return page;
        }
    }

    return NULL;
}

void
vm_page_free(struct vm_page *page, unsigned int order)
{
    assert(page->zone_index < ARRAY_SIZE(vm_page_zones));
    assert(!vm_page_block_referenced(page, order));

    vm_page_zone_free(&vm_page_zones[page->zone_index], page, order);
}

const char *
vm_page_zone_name(unsigned int zone_index)
{
    /* Don't use a switch statement since zones can be aliased */
    if (zone_index == PMEM_ZONE_HIGHMEM) {
        return "HIGHMEM";
    } else if (zone_index == PMEM_ZONE_DIRECTMAP) {
        return "DIRECTMAP";
    } else if (zone_index == PMEM_ZONE_DMA32) {
        return "DMA32";
    } else if (zone_index == PMEM_ZONE_DMA) {
        return "DMA";
    } else {
        panic("vm_page: invalid zone index");
    }
}

void
vm_page_log_info(void)
{
    vm_page_info_common(log_info);
}


code:
/*
 * Copyright (c) 2010-2017 Richard Braun.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Physical page management.
 *
 * A page is said to be managed if it's linked to a VM object, in which
 * case there is at least one reference to it.
 */

#ifndef VM_VM_PAGE_H
#define VM_VM_PAGE_H

#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include <kern/atomic.h>
#include <kern/init.h>
#include <kern/list.h>
#include <kern/log2.h>
#include <kern/macros.h>
#include <machine/page.h>
#include <machine/pmap.h>
#include <machine/pmem.h>
#include <machine/types.h>
#include <vm/vm_object_types.h>

/*
 * Byte/page conversion and rounding macros (not inline functions to
 * be easily usable on both virtual and physical addresses, which may not
 * have the same type size).
 */
#define vm_page_btop(bytes)     ((bytes) >> PAGE_SHIFT)
#define vm_page_ptob(pages)     ((pages) << PAGE_SHIFT)
#define vm_page_trunc(bytes)    P2ALIGN(bytes, PAGE_SIZE)
#define vm_page_round(bytes)    P2ROUND(bytes, PAGE_SIZE)
#define vm_page_end(bytes)      P2END(bytes, PAGE_SIZE)
#define vm_page_aligned(bytes)  P2ALIGNED(bytes, PAGE_SIZE)

/*
 * Zone selectors.
 *
 * Selector-to-zone-list translation table :
 * DMA          DMA
 * DMA32        DMA32 DMA
 * DIRECTMAP    DIRECTMAP DMA32 DMA
 * HIGHMEM      HIGHMEM DIRECTMAP DMA32 DMA
 */
#define VM_PAGE_SEL_DMA         0
#define VM_PAGE_SEL_DMA32       1
#define VM_PAGE_SEL_DIRECTMAP   2
#define VM_PAGE_SEL_HIGHMEM     3

/*
 * Page usage types.
 */
#define VM_PAGE_FREE        0   /* Page unused */
#define VM_PAGE_RESERVED    1   /* Page reserved at boot time */
#define VM_PAGE_TABLE       2   /* Page is part of the page table */
#define VM_PAGE_PMAP        3   /* Page stores pmap-specific data */
#define VM_PAGE_KMEM        4   /* Page is a direct-mapped kmem slab */
#define VM_PAGE_OBJECT      5   /* Page is part of a VM object */
#define VM_PAGE_KERNEL      6   /* Type for generic kernel allocations */

/*
 * Physical page descriptor.
 */
struct vm_page {
    struct list node;
    unsigned short type;
    unsigned short zone_index;
    unsigned short order;
    phys_addr_t phys_addr;
    void *priv;

    unsigned int nr_refs;

    /* VM object back reference */
    struct vm_object *object;
    uint64_t offset;
};

static inline unsigned short
vm_page_type(const struct vm_page *page)
{
    return page->type;
}

void vm_page_set_type(struct vm_page *page, unsigned int order,
                      unsigned short type);

static inline unsigned int
vm_page_order(size_t size)
{
    return log2_order(vm_page_btop(vm_page_round(size)));
}

static inline phys_addr_t
vm_page_to_pa(const struct vm_page *page)
{
    return page->phys_addr;
}

static inline uintptr_t
vm_page_direct_va(phys_addr_t pa)
{
    assert(pa < PMEM_DIRECTMAP_LIMIT);
    return ((uintptr_t)pa + PMAP_START_DIRECTMAP_ADDRESS);
}

static inline phys_addr_t
vm_page_direct_pa(uintptr_t va)
{
    assert(va >= PMAP_START_DIRECTMAP_ADDRESS);
    assert(va < PMAP_END_DIRECTMAP_ADDRESS);
    return (va - PMAP_START_DIRECTMAP_ADDRESS);
}

static inline void *
vm_page_direct_ptr(const struct vm_page *page)
{
    return (void *)vm_page_direct_va(vm_page_to_pa(page));
}

/*
 * Associate private data with a page.
 */
static inline void
vm_page_set_priv(struct vm_page *page, void *priv)
{
    page->priv = priv;
}

static inline void *
vm_page_get_priv(const struct vm_page *page)
{
    return page->priv;
}

static inline void
vm_page_link(struct vm_page *page, struct vm_object *object, uint64_t offset)
{
    assert(object != NULL);
    page->object = object;
    page->offset = offset;
}

static inline void
vm_page_unlink(struct vm_page *page)
{
    assert(page->object != NULL);
    page->object = NULL;
}

/*
 * Load physical memory into the vm_page module at boot time.
 *
 * All addresses must be page-aligned. Zones can be loaded in any order.
 */
void vm_page_load(unsigned int zone_index, phys_addr_t start, phys_addr_t end);

/*
 * Load available physical memory into the vm_page module at boot time.
 *
 * The zone referred to must have been loaded with vm_page_load
 * before loading its heap.
 */
void vm_page_load_heap(unsigned int zone_index, phys_addr_t start,
                       phys_addr_t end);

/*
 * Return true if the vm_page module is completely initialized, false
 * otherwise, in which case only vm_page_bootalloc() can be used for
 * allocations.
 */
int vm_page_ready(void);

/*
 * Make the given page managed by the vm_page module.
 *
 * If additional memory can be made usable after the VM system is initialized,
 * it should be reported through this function.
 */
void vm_page_manage(struct vm_page *page);

/*
 * Return the page descriptor for the given physical address.
 */
struct vm_page * vm_page_lookup(phys_addr_t pa);

/*
 * Allocate a block of 2^order physical pages.
 *
 * The selector is used to determine the zones from which allocation can
 * be attempted.
 *
 * If successful, the returned pages have no references.
 */
struct vm_page * vm_page_alloc(unsigned int order, unsigned int selector,
                               unsigned short type);

/*
 * Release a block of 2^order physical pages.
 *
 * The pages must have no references.
 */
void vm_page_free(struct vm_page *page, unsigned int order);

/*
 * Return the name of the given zone.
 */
const char * vm_page_zone_name(unsigned int zone_index);

/*
 * Log information about physical pages.
 */
void vm_page_log_info(void);

static inline bool
vm_page_referenced(const struct vm_page *page)
{
    return atomic_load(&page->nr_refs, ATOMIC_RELAXED) != 0;
}

static inline void
vm_page_ref(struct vm_page *page)
{
    unsigned int nr_refs;

    nr_refs = atomic_fetch_add(&page->nr_refs, 1, ATOMIC_RELAXED);
    assert(nr_refs != (unsigned int)-1);
}

static inline void
vm_page_unref(struct vm_page *page)
{
    unsigned int nr_refs;

    nr_refs = atomic_fetch_sub(&page->nr_refs, 1, ATOMIC_ACQ_REL);
    assert(nr_refs != 0);

    if (nr_refs == 1) {
        vm_page_free(page, 0);
    }
}

static inline int
vm_page_tryref(struct vm_page *page)
{
    unsigned int nr_refs, prev;

    do {
        nr_refs = atomic_load(&page->nr_refs, ATOMIC_RELAXED);

        if (nr_refs == 0) {
            return EAGAIN;
        }

        prev = atomic_cas(&page->nr_refs, nr_refs,
                          nr_refs + 1, ATOMIC_ACQUIRE);
    } while (prev != nr_refs);

    return 0;
}

/*
 * This init operation provides :
 *  - module fully initialized
 */
INIT_OP_DECLARE(vm_page_setup);

#endif /* VM_VM_PAGE_H */


code:
/*
 * Copyright (c) 2010, 2012 Richard Braun.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef VM_VM_PROT_H
#define VM_VM_PROT_H

/*
 * Protection flags.
 */
#define VM_PROT_NONE    0
#define VM_PROT_READ    1
#define VM_PROT_WRITE   2
#define VM_PROT_EXECUTE 4
#define VM_PROT_DEFAULT (VM_PROT_READ | VM_PROT_WRITE)
#define VM_PROT_ALL     (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE)

#endif /* VM_VM_PROT_H */


code:
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------

*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
*.wav
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash

CMakeLists.txt
CMakeCache.txt
cmake_install.cmke
/CMakeFiles

# qtcreator generated files
*.pro.user*
*.txt.user*

# xemacs temporary files
*.flc

# Vim temporary files
.*.swp

# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*

#QT Creator generated files
*.cflags
*.config
*.cxxflags
*.files
*.includes
*.user
*.creator

#KDevelop generated files
*.kdev4

# MinGW generated files
*.Debug
*.Release

# Python byte code
*.pyc

# Binaries
# --------
*.dll
*.exe

#MakeFile generated files
build/

#other
*.log

./simpleAMEapp/res
./simpleAMEapp/simpleAMEapp

repo_name: assblast/AME

path: .gitignore

license: unknown


code:
CC=g++

CCTARGET=build/

CCSRC= \
	ame/source/render/output/*.cpp \
	ame/source/render/enviroment/*.cpp \
	ame/source/render/UI/text/*.cpp \
	ame/source/mesh/*.cpp \
	ame/source/app/*.cpp \
	ame/source/app/modules/thirdParty/*.cpp \
	ame/source/app/modules/Asound/AsoundOAL.cpp \

CCINCLUDE=-I./ame/source/ -I/usr/include/freetype2

CCVERSION= -std=c++17

CCLINKS=-ldl -lvulkan -lpthread -lglfw -lGL -lopenal -lalut -lfreetype

CCFLAGS=$(CCSRC) $(CCINCLUDE) $(CCLINKS) $(CCVERSION) -fPIC

all:
	@sh dirScript.sh
	@mkdir build
	$(CC) $(CCFLAGS) -c
	@mv ./*.o $(CCTARGET)

path: Makefile


code:
#ifndef _AME_H
#define _AME_H

#include <app/ameTypes.h>
#include <app/modules/Asound.h>
#include <mesh/transform.h>
#include <mesh/object.h>
#include <render/output/window.h>
#include <render/output/vkrender.h>
#include <render/output/OGLRender.h>
#include <render/output/shader.h>
#include <render/output/texture.h>
#include <render/output/material.h>
#include <render/enviroment/camera.h>
#include <render/UI/text/text.h>

#include <glm/glm.hpp>

#endif // _AME_H


code:
#include <app/Application.h>

Application::Application () {
    ameVersionMajor = 1;
    ameVersionMinor = 0;
    ameversionCertificate = 0;
}


code:
#ifndef _APPLICATION_H
#define _APPLICATION_H

#include <string>
#include <app/Atime.h>
#include <app/modules/Ainput.h>

enum OGLContext {
    OGLC_GLFW, OGLC_QT
};
struct OGLTools {
    OGLContext OpenGLContext; //kind of context (GLFW or Qt)
    void ( *CreateQtOGLContext ) (); //called in Qt app for creation openGL Qt context
    void ( *glSwapBuffers ) ();
    bool ( *glWindowShouldClose ) ();
};

struct Settings {
    bool fullscreen = false;
    uint32_t windowWidth;
    uint32_t windowHeight;
    uint8_t windowMonitor;
    bool windowResizeble;
    std::string windowName;
    unsigned int MSAA;
    bool textBlending = true;
};

struct Application {
    Application ();

    std::string appName;
    std::string companyName;

    uint8_t appVersionMajor;
    uint8_t appVersionMinor;
    uint8_t appVersionCertificate;

    uint8_t ameVersionMajor;
    uint8_t ameVersionMinor;
    uint8_t ameversionCertificate;

    bool running = true;

    Settings settings;
    Atime time;
    Ainput input;

    OGLTools openGLTools; //used in OGLRender class to controling openGL contexts

    void (*update)(); //link to function, called then every update window surface
    void (*mousePositionCallback)();
    void (*keyboardCallback)();
};

#endif // APPLICATION_H


code:
#include <app/Atime.h>
#include <ctime>

void Atime::wait ( unsigned int ms ) {

    time_t thisTime = clock ();
    while ( clock () < thisTime + ( ms * 1000 ) ) {}
}

int Atime::getCurrentTime () {

    return clock ();
}


code:
#ifndef AME_TIME_H
#define AME_TIME_H

#include <ctime>

struct Atime {
    void wait ( unsigned int ms );
    int getCurrentTime ();
};
#endif


code:
#include <app/ameTypes.h>

ame::vec2::vec2 () {
    this -> x = 0;
    this -> y = 0;
}
ame::vec2::vec2 ( double x, double y ) {
    this -> x = x;
    this -> y = y;
}

ame::vec3::vec3 ( double x, double y, double z ) {
    this -> x = x;
    this -> y = y;
    this -> z = z;
}
ame::vec3::vec3 () {
    this -> x = 0;
    this -> y = 0;
    this -> z = 0;
}

ame::vec4::vec4 () {
    this -> x = 0;
    this -> y = 0;
    this -> z = 0;
    this -> t = 0;
}
ame::vec4::vec4 (double x, double y, double z, double t) {
    this -> x = x;
    this -> y = y;
    this -> z = z;
    this -> t = t;
}


code:
#ifndef AMETYPES_H
#define AMETYPES_H

namespace ame {

    struct vec2 {
        vec2 ();
        vec2 ( double, double );

        double x, y;
    };

    struct vec3 {
        vec3 ();
        vec3 ( double, double, double );

        double x, y, z;
    };

    struct vec4 {
        vec4 ();
        vec4 ( double, double, double, double );

        double x, y, z, t;
    };

};

#endif // AMETYPES_H


code:
#ifndef AINPUT_H
#define AINPUT_H

enum MouseKey {
    MOUSE_LEFT_BUTTON, MOUSE_RIGHT_BUTTON, MOUSE_MIDDLE_BUTTON
};
enum Action {
    PRESSED, CLICKED, UNPRESSED
};

struct Akeyboard {
    int key;
    int scancode;
    Action action;
    int mods;
};

struct Amouse {
    double posX;
    double posY;

    double lastX, lastY;     //предыдущие координаты мыши
    double offsetX, offsetY; //смещение мыши
    double sensitivity;      //чувствительность

    MouseKey key;
    Action action;
};

struct Ainput {
    Akeyboard keyboard;
    Amouse mouse;
};

#endif // AINPUT_H


code:
#pragma once

#include <string>
#include <fstream>

#include <AL/al.h>
#include <AL/alc.h>
#include <AL/alut.h>

struct AudioObjectAtttributes {

    float position [2];
    float velocity [2];
    float orientation [5];
};

struct Listener {
    AudioObjectAtttributes attributes;
};

struct Source {
    AudioObjectAtttributes attributes;
    ALuint buffer;
    ALuint source;

    ALsizei size, freq;
    ALenum format;
    ALvoid *data;
    ALboolean loop = AL_FALSE;
    ALenum state;
};

class Asound {
public:
    Asound ( std::string filename, Source&, Listener& );

    void play ();
    void pause ();

    ~Asound ();
private:

    ALCdevice *device;
    ALCcontext *context;

    Listener* listener;
    Source* source;
};


code:
#include <string>
#include <iostream>

#include <app/modules/Asound.h>

Asound::Asound ( std::string filename, Source &source, Listener &listener ) {

    //initiliazing OpenAL
    this -> device = alcOpenDevice ( NULL );
    this -> context = alcCreateContext ( this -> device, NULL );
    this -> source = &source;
    this -> listener = &listener;

    //check for errors
    if ( !this -> device ) {
        std::cerr << "Error open device!" << std::endl;
    }
    if ( !alcMakeContextCurrent ( this -> context ) ) {
        std::cerr << "Error creating context!" << std::endl;
    }

    //configuring listener
    alListenerfv ( AL_POSITION, listener.attributes.position );
    alListenerfv ( AL_ORIENTATION, listener.attributes.orientation );
    alListenerfv ( AL_VELOCITY, listener.attributes.velocity );

    //source generation
    alGenSources ( (ALuint) 1, &source.source );

    alSourcef ( this -> source -> source, AL_PITCH, 1 );
    alSourcef ( this -> source -> source, AL_GAIN, 1 );
    alSourcefv ( this -> source -> source, AL_POSITION, source.attributes.position );
    alSourcefv ( this -> source -> source, AL_VELOCITY, source.attributes.velocity );
    alSourcei ( this -> source -> source, AL_LOOPING, AL_FALSE );

    //buffer generation
    alGenBuffers ( (ALuint) 1, &this -> source -> buffer );

    //loading an audio stream to a buffer
    alutLoadWAVFile ( (ALbyte*) filename.c_str (),
                      &source.format,
                      &source.data,
                      &source.size,
                      &source.freq,
                      &source.loop
                    );

    alBufferData ( source.buffer, source.format, source.data, source.size, source.freq );

    //binding source to a buffer
    alSourcei ( this -> source -> source, AL_BUFFER, this -> source -> buffer );
}

void Asound::play () {

    alSourcePlay ( this -> source -> source );

    alGetSourcei ( this -> source -> source, AL_SOURCE_STATE, &this -> source -> state );

    while ( this -> source -> state == AL_PLAYING ) {
        alGetSourcei ( this -> source -> source, AL_SOURCE_STATE, &this -> source -> state );
    }
}

void Asound::pause () {
    //
}

Asound::~Asound () {
    //clean up context
    alDeleteSources ( 1, &this -> source -> source );
    alDeleteBuffers ( 1, &this -> source -> buffer );
    device = alcGetContextsDevice ( this -> context );
    alcMakeContextCurrent ( NULL );
    alcDestroyContext ( this -> context );
    alcCloseDevice ( this -> device );
}


code:
// Writed by Romiro27 (Ivan Rekunov)

#include <fstream>
#include <vector>
#include <array>
#include <thread>
#include <mutex>

#ifndef _LOGGER_H
#define _LOGGER_H

namespace Logs {
    class FileSync
    {
        public:
            FileSync(const std::string& file_name, std::size_t buffer_size = 128u) : m_buffer(buffer_size), m_output(file_name), m_offset(0u)
            {
                if(!m_output.is_open())
                {
                    throw std::runtime_error("LOGGER_ERROR: Log file wasn't created!");
                }
            }

            void write(const std::string& data)
            {
                const std::size_t data_size = data.size();

                std::lock_guard<std::mutex> lock(m_mutex);

                if(data_size + m_offset >= m_buffer.size())
                {
                    record();
                }

                std::copy(begin(data), end(data), begin(m_buffer) + m_offset);

                m_offset += data_size;
            }

            ~FileSync()
            {
                record();
            }

        private:
            void record()
            {
                if(m_offset != 0u)
                {
                    m_output.write(m_buffer.data(), m_offset);
                    m_offset = 0u;
                }
            }

        private:
            std::ofstream m_output;
            std::vector<char> m_buffer;
            std::size_t m_offset;
            std::mutex m_mutex;
    };

    class Logger
    {
        public:
            enum Level
            {
                TRACE_LEVEL,
                DEBUG_LEVEL,
                INFO_LEVEL,
                WARN_LEVEL,
                ERROR_LEVEL,
                FATAL_LEVEL
            };

            static inline std::array<std::string, Level::FATAL_LEVEL + 1u> LevelStr =
            {
                "TRACE", "DEBUG",
                "INFO",  "WARNING",
                "ERROR", "FATAL"
            };

        public:
            int count = 0;

            Logger(const std::string& file_name) : m_sync(file_name)
            {

            }

            void log(Level level, const char* source, std::string message)
            {	count++;
                const std::string formated_message = std::to_string(count) + " [" + LevelStr[level] + "] - " + source + " - " + message + "\n";
                m_sync.write(formated_message);
            }

        private:
            FileSync m_sync;

    };
}

inline Logs::Logger& getGlobalLogger()
{
    static Logs::Logger logger("./data.log");

    return logger;
}

#define STR_(x) #x
#define STR(x) STR_(x)

#define LOG_TRACE(message) getGlobalLogger().log(Logs::Logger::Level::TRACE_LEVEL, __FILE__ ":" STR(__LINE__), (message))
#define LOG_DEBUG(message) getGlobalLogger().log(Logs::Logger::Level::DEBUG_LEVEL, __FILE__ ":" STR(__LINE__), (message))
#define LOG_INFO(message) getGlobalLogger().log(Logs::Logger::Level::INFO_LEVEL, __FILE__ ":" STR(__LINE__), (message))
#define LOG_WARN(message) getGlobalLogger().log(Logs::Logger::Level::WARN_LEVEL, __FILE__ ":" STR(__LINE__), (message))
#define LOG_ERROR(message) getGlobalLogger().log(Logs::Logger::Level::ERROR_LEVEL, __FILE__ ":" STR(__LINE__), (message))
#define LOG_FATAL(message) getGlobalLogger().log(Logs::Logger::Level::FATAL_LEVEL, __FILE__ ":" STR(__LINE__), (message))

#endif // LOGGER_H


code:
#include <fstream>
#include <iostream>
#include <algorithm>
#include <map>

#include <GLES3/gl3.h>

#include <app/modules/thirdParty/OBJLoader.h>

static bool CompareOBJIndexPtr(const OBJIndex* a, const OBJIndex* b);
static inline unsigned int FindNextChar(unsigned int start, const char* str, unsigned int length, char token);
static inline unsigned int ParseOBJIndexValue(const std::string& token, unsigned int start, unsigned int end);
static inline float ParseOBJFloatValue(const std::string& token, unsigned int start, unsigned int end);
static inline std::vector<std::string> SplitString(const std::string &s, char delim);

OBJModel::OBJModel(const std::string& fileName)
{
	hasUVs = false;
	hasNormals = false;
    std::ifstream file;
    file.open(fileName.c_str());

    std::string line;
    if(file.is_open())
    {
        while(file.good())
        {
            getline(file, line);

            unsigned int lineLength = line.length();

            if(lineLength < 2)
                continue;

            const char* lineCStr = line.c_str();

            switch(lineCStr[0])
            {
                case 'v':
                    if(lineCStr[1] == 't')
                        this->UVs.push_back(ParseOBJVec2(line));
                    else if(lineCStr[1] == 'n')
                        this->normals.push_back(ParseOBJVec3(line));
                    else if(lineCStr[1] == ' ' || lineCStr[1] == '\t')
                        this->vertices.push_back(ParseOBJVec3(line));
                break;
                case 'f':
                    CreateOBJFace(line);
                break;
                default: break;
            };
        }
    }
    else
    {
        std::cerr << "Unable to load mesh: " << fileName << std::endl;
    }
}

void Model::calculateNormals ()
{
    for(unsigned int i = 0; i < indices.size(); i += 3)
    {
        int i0 = indices[i];
        int i1 = indices[i + 1];
        int i2 = indices[i + 2];

        glm::vec3 v1 = positions[i1] - positions[i0];
        glm::vec3 v2 = positions[i2] - positions[i0];

        glm::vec3 normal = glm::normalize(glm::cross(v1, v2));

        normals[i0] += normal;
        normals[i1] += normal;
        normals[i2] += normal;
    }

    for(unsigned int i = 0; i < positions.size(); i++)
        normals[i] = glm::normalize(normals[i]);
}

Model OBJModel::ToIndexedModel ()
{
    Model result;
    Model normalModel;

    unsigned int numIndices = OBJIndices.size();

    std::vector<OBJIndex*> indexLookup;

    for(unsigned int i = 0; i < numIndices; i++)
        indexLookup.push_back(&OBJIndices[i]);

    std::sort(indexLookup.begin(), indexLookup.end(), CompareOBJIndexPtr);

    std::map<OBJIndex, unsigned int> normalModelIndexMap;
    std::map<unsigned int, unsigned int> indexMap;

    for(unsigned int i = 0; i < numIndices; i++)
    {
        OBJIndex* currentIndex = &OBJIndices[i];

        glm::vec3 currentPosition = vertices[currentIndex->vertexIndex];
        glm::vec2 currentTexCoord;
        glm::vec3 currentNormal;

        if(hasUVs)
            currentTexCoord = UVs[currentIndex->uvIndex];
        else
            currentTexCoord = glm::vec2(0,0);

        if(hasNormals)
            currentNormal = normals[currentIndex->normalIndex];
        else
            currentNormal = glm::vec3(0,0,0);

        unsigned int normalModelIndex;
        unsigned int resultModelIndex;

        //Create model to properly generate normals on
        std::map<OBJIndex, unsigned int>::iterator it = normalModelIndexMap.find(*currentIndex);
        if(it == normalModelIndexMap.end())
        {
            normalModelIndex = normalModel.positions.size();

            normalModelIndexMap.insert(std::pair<OBJIndex, unsigned int>(*currentIndex, normalModelIndex));
            normalModel.positions.push_back(currentPosition);
            normalModel.textureCoordinates.push_back(currentTexCoord);
            normalModel.normals.push_back(currentNormal);
        }
        else
            normalModelIndex = it->second;

        //Create model which properly separates texture coordinates
        unsigned int previousVertexLocation = FindLastVertexIndex(indexLookup, currentIndex, result);

        if(previousVertexLocation == (unsigned int)-1)
        {
            resultModelIndex = result.positions.size();

            result.positions.push_back(currentPosition);
            result.textureCoordinates.push_back(currentTexCoord);
            result.normals.push_back(currentNormal);
        }
        else
            resultModelIndex = previousVertexLocation;

        normalModel.indices.push_back(normalModelIndex);
        result.indices.push_back(resultModelIndex);
        indexMap.insert(std::pair<unsigned int, unsigned int>(resultModelIndex, normalModelIndex));
    }

    if(!hasNormals)
    {
        normalModel.calculateNormals ();

        for(unsigned int i = 0; i < result.positions.size(); i++)
            result.normals[i] = normalModel.normals[indexMap[i]];
    }

    return result;
};

unsigned int OBJModel::FindLastVertexIndex(const std::vector<OBJIndex*>& indexLookup, const OBJIndex* currentIndex, const Model& result)
{
    unsigned int start = 0;
    unsigned int end = indexLookup.size();
    unsigned int current = (end - start) / 2 + start;
    unsigned int previous = start;

    while(current != previous)
    {
        OBJIndex* testIndex = indexLookup[current];

        if(testIndex->vertexIndex == currentIndex->vertexIndex)
        {
            unsigned int countStart = current;

            for(unsigned int i = 0; i < current; i++)
            {
                OBJIndex* possibleIndex = indexLookup[current - i];

                if(possibleIndex == currentIndex)
                    continue;

                if(possibleIndex->vertexIndex != currentIndex->vertexIndex)
                    break;

                countStart--;
            }

            for(unsigned int i = countStart; i < indexLookup.size() - countStart; i++)
            {
                OBJIndex* possibleIndex = indexLookup[current + i];

                if(possibleIndex == currentIndex)
                    continue;

                if(possibleIndex->vertexIndex != currentIndex->vertexIndex)
                    break;
                else if((!hasUVs || possibleIndex->uvIndex == currentIndex->uvIndex)
                    && (!hasNormals || possibleIndex->normalIndex == currentIndex->normalIndex))
                {
                    glm::vec3 currentPosition = vertices[currentIndex->vertexIndex];
                    glm::vec2 currentTexCoord;
                    glm::vec3 currentNormal;

                    if(hasUVs)
                        currentTexCoord = UVs[currentIndex->uvIndex];
                    else
                        currentTexCoord = glm::vec2(0,0);

                    if(hasNormals)
                        currentNormal = normals[currentIndex->normalIndex];
                    else
                        currentNormal = glm::vec3(0,0,0);

                    for(unsigned int j = 0; j < result.positions.size(); j++)
                    {
                        if(currentPosition == result.positions[j]
                            && ((!hasUVs || currentTexCoord == result.textureCoordinates[j])
                            && (!hasNormals || currentNormal == result.normals[j])))
                        {
                            return j;
                        }
                    }
                }
            }

            return -1;
        }
        else
        {
            if(testIndex->vertexIndex < currentIndex->vertexIndex)
                start = current;
            else
                end = current;
        }

        previous = current;
        current = (end - start) / 2 + start;
    }

    return -1;
}

void OBJModel::CreateOBJFace(const std::string& line)
{
    std::vector<std::string> tokens = SplitString(line, ' ');

    this->OBJIndices.push_back(ParseOBJIndex(tokens[1], &this->hasUVs, &this->hasNormals));
    this->OBJIndices.push_back(ParseOBJIndex(tokens[2], &this->hasUVs, &this->hasNormals));
    this->OBJIndices.push_back(ParseOBJIndex(tokens[3], &this->hasUVs, &this->hasNormals));

    if((int)tokens.size() > 4)
    {
        this->OBJIndices.push_back(ParseOBJIndex(tokens[1], &this->hasUVs, &this->hasNormals));
        this->OBJIndices.push_back(ParseOBJIndex(tokens[3], &this->hasUVs, &this->hasNormals));
        this->OBJIndices.push_back(ParseOBJIndex(tokens[4], &this->hasUVs, &this->hasNormals));
    }
}

OBJIndex OBJModel::ParseOBJIndex(const std::string& token, bool* hasUVs, bool* hasNormals)
{
    unsigned int tokenLength = token.length();
    const char* tokenString = token.c_str();

    unsigned int vertIndexStart = 0;
    unsigned int vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, '/');

    OBJIndex result;
    result.vertexIndex = ParseOBJIndexValue(token, vertIndexStart, vertIndexEnd);
    result.uvIndex = 0;
    result.normalIndex = 0;

    if(vertIndexEnd >= tokenLength)
        return result;

    vertIndexStart = vertIndexEnd + 1;
    vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, '/');

    result.uvIndex = ParseOBJIndexValue(token, vertIndexStart, vertIndexEnd);
    *hasUVs = true;

    if(vertIndexEnd >= tokenLength)
        return result;

    vertIndexStart = vertIndexEnd + 1;
    vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, '/');

    result.normalIndex = ParseOBJIndexValue(token, vertIndexStart, vertIndexEnd);
    *hasNormals = true;

    return result;
}

glm::vec3 OBJModel::ParseOBJVec3(const std::string& line)
{
    unsigned int tokenLength = line.length();
    const char* tokenString = line.c_str();

    unsigned int vertIndexStart = 2;

    while(vertIndexStart < tokenLength)
    {
        if(tokenString[vertIndexStart] != ' ')
            break;
        vertIndexStart++;
    }

    unsigned int vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' ');

    float x = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd);

    vertIndexStart = vertIndexEnd + 1;
    vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' ');

    float y = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd);

    vertIndexStart = vertIndexEnd + 1;
    vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' ');

    float z = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd);

    return glm::vec3(x,y,z);

    //glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str()))
}

glm::vec2 OBJModel::ParseOBJVec2(const std::string& line)
{
    unsigned int tokenLength = line.length();
    const char* tokenString = line.c_str();

    unsigned int vertIndexStart = 3;

    while(vertIndexStart < tokenLength)
    {
        if(tokenString[vertIndexStart] != ' ')
            break;
        vertIndexStart++;
    }

    unsigned int vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' ');

    float x = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd);

    vertIndexStart = vertIndexEnd + 1;
    vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' ');

    float y = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd);

    return glm::vec2(x,y);
}

static bool CompareOBJIndexPtr(const OBJIndex* a, const OBJIndex* b)
{
    return a->vertexIndex < b->vertexIndex;
}

static inline unsigned int FindNextChar(unsigned int start, const char* str, unsigned int length, char token)
{
    unsigned int result = start;
    while(result < length)
    {
        result++;
        if(str[result] == token)
            break;
    }

    return result;
}

static inline unsigned int ParseOBJIndexValue(const std::string& token, unsigned int start, unsigned int end)
{
    return atoi(token.substr(start, end - start).c_str()) - 1;
}

static inline float ParseOBJFloatValue(const std::string& token, unsigned int start, unsigned int end)
{
    return atof(token.substr(start, end - start).c_str());
}

static inline std::vector<std::string> SplitString(const std::string &s, char delim)
{
    std::vector<std::string> elems;

    const char* cstr = s.c_str();
    unsigned int strLength = s.length();
    unsigned int start = 0;
    unsigned int end = 0;

    while(end <= strLength)
    {
        while(end <= strLength)
        {
            if(cstr[end] == delim)
                break;
            end++;
        }

        elems.push_back(s.substr(start, end - start));
        start = end + 1;
        end = start;
    }

    return elems;
}


code:
#ifndef OBJLOADER_H
#define OBJLOADER_H

#include <vector>
#include <string>
#include <glm/glm.hpp>

#include <mesh/mesh.h>

struct OBJIndex {

    unsigned int vertexIndex;
    unsigned int uvIndex;
    unsigned int normalIndex;

    bool operator<(const OBJIndex& r) const { return vertexIndex < r.vertexIndex; }
};

class OBJModel {
public:

    std::vector<OBJIndex> OBJIndices;
    std::vector<glm::vec3> vertices;
    std::vector<glm::vec2> UVs;
    std::vector<glm::vec3> normals;
    bool hasUVs;
    bool hasNormals;

    OBJModel(const std::string& fileName);

    Model ToIndexedModel();

private:

    unsigned int FindLastVertexIndex(const std::vector<OBJIndex*>& indexLookup, const OBJIndex* currentIndex, const Model& result);
    void CreateOBJFace(const std::string& line);

    glm::vec2 ParseOBJVec2(const std::string& line);
    glm::vec3 ParseOBJVec3(const std::string& line);
    OBJIndex ParseOBJIndex(const std::string& token, bool* hasUVs, bool* hasNormals);
};

#endif