/*
 * memory.h
 *
 * Copyright (C) 2015 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _MEMORY_H_
#define _MEMORY_H_

#include <common.h>
#include <interrupt.h>
#include <boot/multiboot.h>
#include <object.h>
#include <filesystem.h>
#include <sdk/avltree.h>
#include <sdk/memory.h>

#define PAGE_SIZE      4096
#define PAGE_PRESENT   (1 << 0)
#define PAGE_WRITABLE  (1 << 1)
#define PAGE_USERMODE  (1 << 2)
#define PAGE_ACCESSED  (1 << 5)
#define PAGE_DIRTY     (1 << 6)
#define PAGE_GLOBAL    (1 << 8)
#define PAGE_EVICTABLE (1 << 9)

#define PAGE_ERROR_PRESENT_FLAG  (1 << 0)
#define PAGE_ERROR_WRITE_FLAG    (1 << 1)
#define PAGE_ERROR_USERMODE_FLAG (1 << 2)

#define ADDR_TO_PDE(x)   ((x) >> 22)
#define ADDR_TO_PTE(x)   (((x) >> 12) & 0x3FF)
#define PAGE_ALIGN(x)    ((x) & 0xFFFFF000)
#define PAGE_ALIGN_UP(x) (((x) + 0xFFF) & 0xFFFFF000)
#define PAGE_OFFSET(x)   ((x) & 0x00000FFF)
#define PAGE_NUMBER(x)   ((x) >> 12)

#define KERNEL_AREA_START 0x80000000
#define KERNEL_AREA_END   0xFFFFFFFF
#define KERNEL_POOL_START 0x81000000
#define KERNEL_POOL_END   0xBFFFFFFF
#define MAPPING_START     0xC8000000
#define MAPPING_END       0xFFFFFFFF
#define USER_AREA_START   0x00000000
#define USER_AREA_END     0x7FFFFFFF
#define KERNEL_PAGE_START ADDR_TO_PDE(KERNEL_AREA_START)
#define KERNEL_PAGE_END   ADDR_TO_PDE(KERNEL_AREA_END)
#define USER_PAGE_START   ADDR_TO_PDE(USER_AREA_START)
#define USER_PAGE_END     ADDR_TO_PDE(USER_AREA_END)

#define PAGEDIR_SELF_ENTRY  768
#define PAGE_DIRECTORY_ADDR ((PAGEDIR_SELF_ENTRY << 22) + (PAGEDIR_SELF_ENTRY << 12))
#define PAGE_TABLE_ADDR     (PAGEDIR_SELF_ENTRY << 22)

#define INVALID_PAGE        ((void*)-1)
#define TOTAL_PAGES         1048576
#define MEM_STACK_VIRT_ADDR 0xC0400000
#define MEM_TREE_BLOCKS     0xC0800000
#define TEMPORARY_PAGES     256
#define TEMPORARY_ADDR      (0xC8000000 - TEMPORARY_PAGES * PAGE_SIZE)
#define EVICTION_THRESHOLD  128

#define INVALID_STORE_NUMBER        ((dword_t)-1)
#define PAGE_STORE_ENTRY_PRESENT    (1 << 31)

typedef struct memory_block memory_block_t;

typedef struct
{
    uintptr_t phys_addr;
    uintptr_t ref_count;
} page_t;

typedef struct
{
    list_entry_t link;
    void *page_directory;
    void *pool_address;
    uintptr_t pool_size;
    avl_tree_t by_addr_tree;
    avl_tree_t by_size_tree;
    lock_t lock;
    list_entry_t evictable_blocks;
    list_entry_t *evict_blk_ptr;
    uintptr_t evict_page_num;
    memory_stats_t stats;
} memory_address_space_t;

typedef struct
{
    list_entry_t link;
    void *physical;
    uintptr_t offset;
} shared_page_t;

typedef struct
{
    object_t header;
    dword_t flags;
    list_entry_t page_list;
    size_t size;
    file_instance_t *file; /* strong reference */
} memory_section_t;

struct memory_block
{
    avl_node_t by_addr_node;
    avl_node_t by_size_node;
    list_entry_t evict_link;
    uintptr_t address;
    size_t size;
    dword_t flags;
    memory_address_space_t *address_space;
    memory_section_t *section;
    qword_t section_offset;
};

typedef struct
{
    list_entry_t link;
    dword_t num_entries;
    dword_t max_entries;
    handle_t file_handle;
    dword_t *bitmap;
    list_entry_t entry_list;
} page_store_t;

typedef struct
{
    list_entry_t link;
    void *address;
    memory_address_space_t *address_space;
    dword_t number;
    void *physical;
} page_store_entry_t;

typedef enum
{
    PAGE_ERROR_NOTPRESENT,
    PAGE_ERROR_READONLY,
    PAGE_ERROR_UNPRIVILEGED
} page_error_t;

void set_page_directory(void *phys_addr);
void *get_page_directory();
void *get_physical_address(void *virtual);
dword_t map_memory_internal(void *physical, void *virtual, size_t size, dword_t page_flags);
void unmap_memory_internal(void *virtual, size_t size);
dword_t read_physical(void *physical, void *buffer, size_t size);
dword_t write_physical(void *physical, void *buffer, size_t size);
dword_t map_memory_in_address_space(memory_address_space_t *address_space,
                                    void *physical,
                                    void **virtual,
                                    uintptr_t size,
                                    dword_t block_flags);
dword_t unmap_memory_in_address_space(memory_address_space_t *address_space, void *virtual);
dword_t pin_memory(const void *virtual, void **pinned, size_t size, bool_t lock_contents);
dword_t map_memory(void *physical, void **virtual, size_t size, dword_t page_flags);
dword_t unmap_memory(void *virtual);
dword_t alloc_memory_in_address_space(
    memory_address_space_t *address_space,
    void **address,
    size_t size,
    dword_t block_flags,
    memory_section_t *section,
    qword_t section_offset);
dword_t free_memory_in_address_space(memory_address_space_t *address_space, void *address);
dword_t commit_pages(void *address, size_t size);
dword_t uncommit_pages(void *address, size_t size);
void *alloc_pool(void *address, size_t size, dword_t block_flags);
void free_pool(void *address);
dword_t create_address_space(void *base_address, dword_t page_count, memory_address_space_t *mem_space);
dword_t clone_address_space(memory_address_space_t *original, memory_address_space_t *clone);
void bump_address_space(memory_address_space_t *mem_space);
void delete_address_space(memory_address_space_t *mem_space);
bool_t memory_fault_handler(void *address, registers_t *regs);
void memory_init(multiboot_tag_mmap_t *mmap, uintptr_t lowest_physical);
void memory_cleanup(object_t *obj);

#endif
