/*
 * @author  xamidev <xamidev@riseup.net>
 * @brief   Physical memory manager from freelist
 * @license GPL-3.0-only
 */

/*
pmm - Physical Memory Manager
will manage 4kb pages physically
it will probably need to get some info from Limine,
to see which pages are used by kernel/bootloader/mmio/fb etc.
*/

#include "config.h"
#include <mem/paging.h>
#include <limine.h>
#include <stddef.h>
#include <stdint.h>
#include <kernel.h>
#include <mem/utils.h>
#include <mem/pmm.h>

/*
First we'll have to discover the physical memory layout,
and for that we can use a Limine request.
*/

// Offset from Higher Half Direct Map
uint64_t hhdm_off;

static uintptr_t g_freelist = 0;

/*
 * pmm_alloc - Allocate a physical page
 *
 * This function allocates a single physical page (frame)
 * 
 * Return:
 * <addr> - Address for the allocated page
 */
uintptr_t pmm_alloc()
{
    if (!g_freelist) {
		panic(NULL, "PMM is out of memory!");		
	}
    uintptr_t addr = g_freelist;
    g_freelist = *(uintptr_t*) PHYS_TO_VIRT(g_freelist);
    return addr;
}

/*
 * pmm_free - Frees a memory page
 * @addr: Address to the page
 */
void pmm_free(uintptr_t addr)
{
    *(uintptr_t*) PHYS_TO_VIRT(addr) = g_freelist;
    g_freelist = addr;
}

/*
 * pmm_init_freelist - PMM freelist initialization
 *
 * This function marks the biggest memory region as
 * free, so we can use it in pmm_alloc.
 */
static void pmm_init_freelist(struct limine_memmap_response* memmap)
{
    uint64_t total_pages = 0;

    for (size_t i=0; i<memmap->entry_count; i++) {
        struct limine_memmap_entry* entry = memmap->entries[i];

        if (entry->type == LIMINE_MEMMAP_USABLE) {
            uint64_t base = ALIGN_UP(entry->base, PAGE_SIZE);
            uint64_t end = ALIGN_DOWN(entry->base + entry->length, PAGE_SIZE);

            if (end > PAGING_MAX_PHYS) {
                end = PAGING_MAX_PHYS;
            }

            // Region above PAGING_MAX_PHYS
            if (base >= end) continue;

            for (uint64_t addr = base; addr < end; addr += PAGE_SIZE) {
                pmm_free(addr);
                total_pages++;
            }
        }
    }

    DEBUG("%u frames in freelist, %u bytes available (%u MB)", total_pages, total_pages*PAGE_SIZE, total_pages*PAGE_SIZE/1000000);
}

/*
 * pmm_init - Physical memory manager initialization
 * @boot_ctx: Boot context structure
 *
 * This function prepares the PMM for use.
 * The PMM works with a freelist.
 */
void pmm_init(struct boot_context boot_ctx)
{
    hhdm_off = boot_ctx.hhdm->offset;
    //pmm_find_biggest_usable_region(boot_ctx.mmap, boot_ctx.hhdm);

    // Now we have biggest USABLE region,
    // so to populate the free list we just iterate through it
    pmm_init_freelist(boot_ctx.mmap);
}
