Untitled

 avatar
unknown
c_cpp
a year ago
14 kB
19
Indexable
/*
    Copyright (C) 2024-25 Amar Djulovic

    This file is part of floppaOS.

    floppaOS 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.

    floppaOS 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.
*/

#include "vmm.h"
#include "pmm.h"
#include "paging.h"
#include "utils.h"
#include "../lib/logging.h"
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#define KERNEL_VADDR_BASE  0xC0000000
#define USER_SPACE_END     0xBFFFFFFF
#define GNU_INLINE      __attribute__((always_inline))
#define PAGE_ALIGN(addr)   (((addr) + 0xFFF) & ~0xFFF)
#define RECURSIVE_PD      ((PDE*)0xFFFFF000)
#define RECURSIVE_PT(i)   ((PTE*)(0xFFC00000 + ((i) << 12)))
typedef struct vm_region {
    uintptr_t start;
    uintptr_t end;
    PageAttributes attrs;
    struct vm_region* next;
} vm_region_t;

static vm_region_t* vm_regions = NULL;
uintptr_t kernel_heap_start;
uintptr_t kernel_heap_end;

extern PDE pd[PAGE_DIRECTORY_SIZE];
extern PDE kernel_pd[PAGE_DIRECTORY_SIZE];
extern uint8_t _kernel_start;
extern uint8_t _kernel_end;

static inline GNU_INLINE uint32_t _pd_index(uintptr_t addr) {
    return (addr >> 22) & 0x3FF;
}

static inline GNU_INLINE uint32_t _pt_index(uintptr_t addr) {
    return (addr >> 12) & 0x3FF;
}

static inline GNU_INLINE uintptr_t _align_down(uintptr_t addr) {
    return addr & ~(PAGE_SIZE - 1);
}

static inline GNU_INLINE uintptr_t _align_up(uintptr_t addr) {
    return PAGE_ALIGN(addr);
}

static void _zero_pt(PTE* pt) {
    flop_memset(pt, 0, PAGE_TABLE_SIZE * sizeof(PTE));
}


static PTE* _alloc_pt(void) {
    void* pt = pmm_alloc_page();
    if (!pt) return NULL;
    return (PTE*)pt;
}

static int _pd_lvl(PTE* tables[2], uint32_t indices[2], PageAttributes attrs) {
    if (!tables[0][indices[0]].present) {
        PTE* new_pt = (PTE*)pmm_alloc_page();
        if (!new_pt) return -1;
        _zero_pt((PTE*)((uintptr_t)new_pt));

        PageAttributes new_attrs = {
            .present = 1,
            .rw = 1,
            .user = attrs.user,
            .frame_addr = ((uintptr_t)new_pt) >> 12
        };
        SET_PF((PTE*)&tables[0][indices[0]], new_attrs);
    }
    tables[1] = RECURSIVE_PT(indices[0]);
    return 0;
}

static void _pt_lvl(PTE* tables[2], uint32_t indices[2], uintptr_t paddr, PageAttributes attrs) {
    if (tables[1][indices[1]].present) {
        pmm_free_page((void*)(tables[1][indices[1]].frame_addr << 12));
    }
    attrs.frame_addr = paddr >> 12;
    SET_PF(&tables[1][indices[1]], attrs);
}

int map_page(uintptr_t vaddr, uintptr_t paddr, PageAttributes attrs) {
    vaddr = _align_down(vaddr);
    paddr = _align_down(paddr);
    if (vaddr == 0) return -1;

    uint32_t indices[2] = {_pd_index(vaddr), _pt_index(vaddr)};
    PTE* tables[2] = {RECURSIVE_PD, NULL};
    for (int i = 0; i < 2; i++) { // walk page table levels
        switch (i) {
            case 0:
                _pd_lvl(tables, indices, attrs);
            case 1:
                _pt_lvl(tables, indices, paddr, attrs);
        }
    }

    asm volatile ("invlpg (%0)" : : "a" (vaddr));
    return 0;
}

int unmap_page(uintptr_t vaddr) {
    vaddr = _align_down(vaddr);
    uint32_t pd_idx = _pd_index(vaddr);
    uint32_t pt_idx = _pt_index(vaddr);

    if (!RECURSIVE_PD[pd_idx].present) return -1;
    PTE* pt = RECURSIVE_PT(pd_idx);

    if (!pt[pt_idx].present) return -1;
    pmm_free_page((void*)(pt[pt_idx].frame_addr << 12));
    PageAttributes zero = {0};
    SET_PF(&pt[pt_idx], zero);
    bool pt_empty = true;
    for (int i = 0; i < PAGE_TABLE_SIZE; i++) {
        if (pt[i].present) {
            pt_empty = false;
            break;
        }
    }
    if (pt_empty) {
        pmm_free_page((void*)(RECURSIVE_PD[pd_idx].table_addr << 12));
        PageAttributes zero_pd = {0};
        SET_PF((PTE*)&RECURSIVE_PD[pd_idx], zero_pd);
    }

    asm volatile ("invlpg (%0)" : : "a" (vaddr));
    return 0;
}


int unmap_range(uintptr_t vaddr, size_t size) {
    size_t pages = _align_up(size) / PAGE_SIZE;

    vm_region_t *cur = vm_regions, *prev = NULL;

    while (cur) {
        // check if the range is within the current region
        if (cur->start == vaddr && cur->end == vaddr + size) {

            if (prev) {
                prev->next = cur->next;

            } else {
                vm_regions = cur->next;
            }
            break;
        }

        // move to next region
        prev = cur;
        cur = cur->next;
    }

    for (size_t i = 0; i < pages; i++) {
        uintptr_t va = vaddr + i * PAGE_SIZE;
        if (unmap_page(va) < 0) {
            // todo: handle error
            return -1;
        }
    }
    return 0;
}

int map_range(uintptr_t vaddr, uintptr_t paddr, size_t size, PageAttributes attrs) {
    size_t pages = _align_up(size) / PAGE_SIZE;

    vm_region_t* r = _make_region(vaddr, vaddr + size, attrs);
    if (!r) {
        return -1;
    }
    r->next = vm_regions;
    vm_regions = r;

    for (size_t i = 0; i < pages; i++) {
        uintptr_t va = vaddr + i * PAGE_SIZE;
        uintptr_t pa = paddr + i * PAGE_SIZE;
        if (map_page(va, pa, attrs) < 0) {
            // rollback if failed 
            // prevent mem leaks
            // todo: actual error handling
            for (size_t j = 0; j < i; j++) {
                uintptr_t undo_va = vaddr + j * PAGE_SIZE;
                unmap_page(undo_va);
            }
            vm_regions = r->next;
            return -1;
        }
    }
    return 0;
}

static vm_region_t* _make_region(uintptr_t start, uintptr_t end, PageAttributes attrs) {
    vm_region_t* r = pmm_alloc_page();
    if (!r) return NULL;
    r->start = start;
    r->end = end;
    r->attrs = attrs;
    r->next = NULL;
    return r;
}

static void _clear_pd(void) {
    for (int i = 0; i < PAGE_DIRECTORY_SIZE; i++) {
        PageAttributes zero = {
            0
        };
        SET_PF((PTE*)&pd[i], zero);
    }
}

static void _id_map(void) {
    for (uintptr_t addr = 0; addr < 0x100000; addr += PAGE_SIZE) {
        PageAttributes id_map_pg_attrs= { 
            .present = 1, 
            .rw = 1, 
            .user = 0, 
            .frame_addr = addr >> 12 };
        map_page(addr, addr, id_map_pg_attrs);
    }
    // todo: check if low memory is mapped correctly
}

static void _map_kernel(uintptr_t pstart, size_t size) {
    for (uintptr_t i = 0; i < size; i += PAGE_SIZE) {
        uintptr_t vaddr = KERNEL_VADDR_BASE + i;
        uintptr_t paddr = pstart + i;
        PageAttributes k_pg_attrs = { 
            .present = 1, 
            .rw = 1, 
            .user = 0, 
            .frame_addr = paddr >> 12 };
        map_page(vaddr, paddr, k_pg_attrs);
    }
}

static void _init_heap(uintptr_t kernel_size) {
    // place heap right after the kernel size in the kernel virtual address space
    
    kernel_heap_start = KERNEL_VADDR_BASE + kernel_size;
    kernel_heap_end = kernel_heap_start;
}

int vmm_init(void) {

    // get kernel end and start from linker.ld
    uintptr_t pstart = (uintptr_t)&_kernel_start; 
    uintptr_t pend   = (uintptr_t)&_kernel_end;
    uintptr_t ksize  = PAGE_ALIGN(pend - pstart); 
    _clear_pd();
    _id_map();
    _map_kernel(pstart, ksize);
    _init_heap(ksize); 
    _flush_tlb();
    log("vmm: initialized\n", GREEN);
    return 0;
}

uintptr_t virt_to_phys(uintptr_t vaddr) {
    vaddr = _align_down(vaddr);
    uint32_t pd_idx = _pd_index(vaddr);
    uint32_t pt_idx = _pt_index(vaddr);

    if (!RECURSIVE_PD[pd_idx].present) return 0;
    PTE* pt = RECURSIVE_PT(pd_idx);
    if (!pt[pt_idx].present) return 0;

    return (pt[pt_idx].frame_addr << 12) | (vaddr & 0xFFF);
}

uintptr_t phys_to_virt(uintptr_t paddr) {
    paddr = _align_down(paddr);
    for (uint32_t pd_idx = 0; pd_idx < PAGE_DIRECTORY_SIZE; pd_idx++) {
        if (!RECURSIVE_PD[pd_idx].present) continue;
        PTE* pt = RECURSIVE_PT(pd_idx);
        for (uint32_t pt_idx = 0; pt_idx < PAGE_TABLE_SIZE; pt_idx++) {
            if (!pt[pt_idx].present) continue;
            if ((pt[pt_idx].frame_addr << 12) == paddr) {
                return (pd_idx << 22) | (pt_idx << 12) | (paddr & 0xFFF);
            }
        }
    }
    return 0;
}
int map_kernel_space(uintptr_t vaddr, size_t size) {
    if (vaddr < KERNEL_VADDR_BASE) {
        return -1;
    }

    PageAttributes attrs = {
        .present = 1,
        .rw = 1,
        .user = 0
    };

    uintptr_t phys_addr = (uintptr_t)pmm_alloc_pages(0, size / PAGE_SIZE);
    return map_range(vaddr, phys_addr, size, attrs);
}

int map_user_space(uintptr_t vaddr, size_t size) {
    if (vaddr >= USER_SPACE_END) {
        return -1;
    }

    PageAttributes attrs = {
        .present = 1,
        .rw = 1,
        .user = 1
    };

    uintptr_t phys_addr = (uintptr_t)pmm_alloc_pages(0, size / PAGE_SIZE);
    return map_range(vaddr, phys_addr, size, attrs);
}
int vmm_is_mapped(uintptr_t vaddr) {
    uint32_t pd_idx = _pd_index(vaddr);
    uint32_t pt_idx = _pt_index(vaddr);

    if (!kernel_pd[pd_idx].present) return 0;

    PTE* pt = (PTE*)(kernel_pd[pd_idx].table_addr << 12);
    return pt[pt_idx].present;
}
int vmm_protect(uintptr_t vaddr, size_t size, PageAttributes new_attrs) {
    size_t pages = _align_up(size) / PAGE_SIZE;

    for (size_t i = 0; i < pages; i++) {
        uintptr_t va = vaddr + i * PAGE_SIZE;
        uint32_t pd_idx = _pd_index(va);
        uint32_t pt_idx = _pt_index(va);

        if (!kernel_pd[pd_idx].present)
            return -1;

        PTE* pt = (PTE*)(kernel_pd[pd_idx].table_addr << 12);
        if (!pt[pt_idx].present)
            return -1;

        new_attrs.frame_addr = pt[pt_idx].frame_addr;
        SET_PF(&pt[pt_idx], new_attrs);
    }

    _flush_tlb();
    return 0;
}
void vmm_dump_mappings(void) {
    vm_region_t* cur = vm_regions;
    while (cur) {
        log_step("vmm: region", CYAN);
        log_addr("  start", cur->start);
        log_addr("  end", cur->end);
        log("  attrs: ", WHITE);
        if (cur->attrs.user) log("user ", GREEN);
        if (cur->attrs.rw)   log("rw ", GREEN);
        if (cur->attrs.present) log("present ", GREEN);
        log("\n", WHITE);
        cur = cur->next;
    }
}
uintptr_t vmm_find_free_region(uintptr_t min, size_t size) {
    uintptr_t vaddr = min;
    while (vaddr + size < USER_SPACE_END) {
        if (!vmm_is_mapped(vaddr)) return vaddr;
        vaddr += PAGE_SIZE;
    }
    return 0;
}
PDE* vmm_clone_page_directory(void) {
    PDE* new_pd = (PDE*)pmm_alloc_page();
    if (!new_pd)
        return NULL;

    for (uint32_t i = 0; i < PAGE_DIRECTORY_SIZE; i++) {
        if ((i << 22) >= KERNEL_VADDR_BASE) {
            new_pd[i] = kernel_pd[i];
            continue;
        }

        if (!kernel_pd[i].present)
            continue;

        PTE* src_pt = (PTE*)(kernel_pd[i].table_addr << 12);
        PTE* new_pt = (PTE*)pmm_alloc_page();
        if (!new_pt)
            return NULL;

        _zero_pt(new_pt);

        for (uint32_t j = 0; j < PAGE_TABLE_SIZE; j++) {
            if (!src_pt[j].present)
                continue;

            void* new_frame = pmm_alloc_page();
            if (!new_frame)
                return NULL;

            void* old_frame = (void*)(src_pt[j].frame_addr << 12);
            flop_memcpy(new_frame, old_frame, PAGE_SIZE);

            PageAttributes attrs = {
                .present = src_pt[j].present,
                .rw = src_pt[j].rw,
                .user = src_pt[j].user,
                .frame_addr = ((uintptr_t)new_frame) >> 12
            };

            SET_PF(&new_pt[j], attrs);
        }

        PageAttributes pt_attrs = {
            .present = 1,
            .rw = 1,
            .user = 1,
            .frame_addr = ((uintptr_t)new_pt) >> 12
        };

        SET_PF((PTE*)&new_pd[i], pt_attrs);
    }

    return new_pd;
}

static PageAttributes pd_entry_get_attrs(PDE* pd, uint32_t pd_idx) {
    PageAttributes attrs = {0};
    if (!pd[pd_idx].present)
        return attrs;
    attrs.present = pd[pd_idx].present;
    attrs.rw = pd[pd_idx].rw;
    attrs.user = pd[pd_idx].user;
    attrs.frame_addr = pd[pd_idx].table_addr;
    return attrs;
}

PageAttributes vmm_pd_attrs_for_vaddr(uintptr_t vaddr) {
    uint32_t pd_idx = _pd_index(vaddr);
    return pd_entry_get_attrs(kernel_pd, pd_idx);
}

// get page attributes for a pt entry 
static PageAttributes pt_entry_get_attrs(PDE* pd, uint32_t pd_idx, uint32_t pt_idx) {
    PageAttributes attrs = {0};
    if (!pd[pd_idx].present)
        return attrs;
    PTE* pt = (PTE*)(pd[pd_idx].table_addr << 12);
    if (!pt[pt_idx].present)
        return attrs;
    attrs.present = pt[pt_idx].present;
    attrs.rw = pt[pt_idx].rw;
    attrs.user = pt[pt_idx].user;
    attrs.frame_addr = pt[pt_idx].frame_addr;
    return attrs;
}

PageAttributes vmm_pt_attrs_for_vaddr(uintptr_t vaddr) {
    uint32_t pd_idx = _pd_index(vaddr);
    uint32_t pt_idx = _pt_index(vaddr);
    return pt_entry_get_attrs(kernel_pd, pd_idx, pt_idx);
}

// Find region containing vaddr
static vm_region_t* find_region(uintptr_t vaddr) {
    vm_region_t* cur = vm_regions;
    while (cur) {
        if (vaddr >= cur->start && vaddr < cur->end)
            return cur;
        cur = cur->next;
    }
    return NULL;
}

// Get region attributes for vaddr
PageAttributes vaddr_fetch_attrs(uintptr_t vaddr) {
    vm_region_t* r = find_region(vaddr);
    if (!r) {
        PageAttributes zero = {0};
        return zero;
    }
    return r->attrs;
}
Editor is loading...
Leave a Comment