src/kernel --> src/

This commit is contained in:
boreddevnl
2026-03-16 00:30:47 +01:00
parent 3da1496e4f
commit fc83d7941b
630 changed files with 2 additions and 2 deletions

525
src/mem/memory_manager.c Normal file
View File

@@ -0,0 +1,525 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "memory_manager.h"
#include <stdint.h>
#include "limine.h"
#include "platform.h"
// --- Internal State ---
// memory_pool is no longer a single pointer, as we now manage multiple regions.
// The block_list will contain all information about free and allocated regions.
static size_t memory_pool_size = 0;
static MemBlock block_list[MAX_ALLOCATIONS];
static int block_count = 0;
static size_t total_allocated = 0;
static size_t peak_allocated = 0;
static uint32_t allocation_counter = 0;
static bool initialized = false;
extern void serial_write(const char *str);
extern void serial_write_num(uint32_t n);
// --- Helper Functions ---
// Simple memset for internal use
void mem_memset(void *dest, int val, size_t len) {
uint8_t *ptr = (uint8_t *)dest;
while (len-- > 0) {
*ptr++ = (uint8_t)val;
}
}
void mem_memcpy(void *dest, const void *src, size_t len) {
uint8_t *d = (uint8_t *)dest;
const uint8_t *s = (const uint8_t *)src;
while (len-- > 0) {
*d++ = *s++;
}
}
// Simple memmove
static void mem_memmove(void *dest, const void *src, size_t len) {
uint8_t *d = (uint8_t *)dest;
const uint8_t *s = (const uint8_t *)src;
if (d < s) {
while (len--) *d++ = *s++;
} else {
d += len;
s += len;
while (len--) *(--d) = *(--s);
}
}
// Get current time in ticks (simple counter)
static uint32_t get_timestamp(void) {
static uint32_t tick = 0;
return tick++;
}
// Sorts the block list by address. This is crucial for efficient merging of free blocks.
static void sort_block_list() {
bool swapped;
for (int i = 0; i < block_count - 1; i++) {
swapped = false;
for (int j = 0; j < block_count - i - 1; j++) {
if ((uintptr_t)block_list[j].address > (uintptr_t)block_list[j + 1].address) {
MemBlock tmp = block_list[j];
block_list[j] = block_list[j + 1];
block_list[j + 1] = tmp;
swapped = true;
}
}
if (!swapped) break;
}
}
// Calculate fragmentation
static size_t calculate_fragmentation(void) {
size_t total_free = memory_pool_size - total_allocated;
if (total_free == 0) return 0;
size_t largest_free = 0;
for (int i = 0; i < block_count; i++) {
if (!block_list[i].allocated && block_list[i].size > largest_free) {
largest_free = block_list[i].size;
}
}
if (total_allocated == 0) return 0;
// Fragmentation = 1 - (Largest Free / Total Free)
size_t frag_percent = 100 - ((largest_free * 100) / total_free);
return frag_percent;
}
// --- Public API ---
void memory_manager_init_from_memmap(struct limine_memmap_response *memmap) {
if (initialized || !memmap) return;
// Clear metadata
mem_memset(block_list, 0, sizeof(block_list));
block_count = 0;
total_allocated = 0;
peak_allocated = 0;
allocation_counter = 0;
memory_pool_size = 0;
for (uint64_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 = entry->base;
uint64_t size = entry->length;
// Avoid low memory below 1MB which is used for boot/kernel structures
if (base < 0x100000) {
if (base + size <= 0x100000) {
continue; // Skip this low memory block entirely
}
uint64_t diff = 0x100000 - base;
base = 0x100000;
size -= diff;
}
if (size < 4096) continue; // Ignore small fragments
if (block_count >= MAX_ALLOCATIONS) {
serial_write("[MEM] WARN: Exceeded MAX_ALLOCATIONS while parsing memmap.\n");
break;
}
block_list[block_count].address = (void*)p2v(base);
block_list[block_count].size = size;
block_list[block_count].allocated = false;
block_list[block_count].allocation_id = 0;
block_count++;
memory_pool_size += size;
}
}
sort_block_list();
initialized = true;
serial_write("[MEM] Total usable memory: ");
serial_write_num(memory_pool_size / 1024 / 1024);
serial_write(" MB\n");
}
// Internal helper to insert a block at a specific index
static void insert_block_at(int idx, void* addr, size_t size, bool allocated, uint32_t id) {
if (block_count >= MAX_ALLOCATIONS) return;
for (int j = block_count; j > idx; j--) {
block_list[j] = block_list[j - 1];
}
block_list[idx].address = addr;
block_list[idx].size = size;
block_list[idx].allocated = allocated;
block_list[idx].allocation_id = id;
block_list[idx].timestamp = (allocated) ? get_timestamp() : 0;
block_count++;
}
// Internal helper to remove a block at a specific index
static void remove_block_at(int idx) {
if (idx < 0 || idx >= block_count) return;
for (int j = idx; j < block_count - 1; j++) {
block_list[j] = block_list[j + 1];
}
block_count--;
}
void* kmalloc_aligned(size_t size, size_t alignment) {
if (!initialized || size == 0) return NULL;
uint64_t rflags;
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
if (alignment == 0) alignment = 8;
size = (size + 7) & ~7ULL; // Ensure size is multiple of 8
for (int i = 0; i < block_count; i++) {
if (block_list[i].allocated) continue;
uintptr_t block_start = (uintptr_t)block_list[i].address;
size_t block_size = block_list[i].size;
uintptr_t aligned_addr = block_start;
if (aligned_addr % alignment != 0) {
aligned_addr = (aligned_addr + alignment - 1) & ~(alignment - 1);
}
size_t padding = aligned_addr - block_start;
if (block_size >= size + padding) {
// Check if we have enough slots for potential splits
int extra_needed = 0;
if (padding > 0) extra_needed++;
size_t remaining_size = block_size - (size + padding);
if (remaining_size > 0) extra_needed++;
if (block_count + extra_needed > MAX_ALLOCATIONS) {
continue;
}
void* ptr = (void*)aligned_addr;
uint32_t alloc_id = ++allocation_counter;
// We are splitting block_list[i].
// Possible outcomes:
// 1. [Padding (Free)] [Allocated] [Remaining (Free)]
// 2. [Padding (Free)] [Allocated]
// 3. [Allocated] [Remaining (Free)]
// 4. [Allocated]
// We'll modify block_list[i] and insert others as needed.
// To keep things simple and maintain sorted order, we update from right to left or carefully.
if (padding > 0 && remaining_size > 0) {
// Case 1: Split into 3
block_list[i].size = padding; // Padding stays at i
insert_block_at(i + 1, ptr, size, true, alloc_id);
insert_block_at(i + 2, (void*)(aligned_addr + size), remaining_size, false, 0);
} else if (padding > 0) {
// Case 2: Split into 2
block_list[i].size = padding;
insert_block_at(i + 1, ptr, size, true, alloc_id);
} else if (remaining_size > 0) {
// Case 3: Split into 2
block_list[i].address = ptr;
block_list[i].size = size;
block_list[i].allocated = true;
block_list[i].allocation_id = alloc_id;
block_list[i].timestamp = get_timestamp();
insert_block_at(i + 1, (void*)(aligned_addr + size), remaining_size, false, 0);
} else {
// Case 4: Perfect fit
block_list[i].allocated = true;
block_list[i].allocation_id = alloc_id;
block_list[i].timestamp = get_timestamp();
}
total_allocated += size;
if (total_allocated > peak_allocated) peak_allocated = total_allocated;
mem_memset(ptr, 0, size);
asm volatile("push %0; popfq" : : "r"(rflags));
return ptr;
}
}
asm volatile("push %0; popfq" : : "r"(rflags));
return NULL;
}
void* kmalloc(size_t size) {
return kmalloc_aligned(size, 8);
}
void kfree(void *ptr) {
if (ptr == NULL || !initialized) return;
uint64_t rflags;
asm volatile("pushfq; pop %0; cli" : "=r"(rflags));
int block_idx = -1;
for (int i = 0; i < block_count; i++) {
if (block_list[i].allocated && block_list[i].address == ptr) {
block_idx = i;
break;
}
}
if (block_idx == -1) {
asm volatile("push %0; popfq" : : "r"(rflags));
return;
}
total_allocated -= block_list[block_idx].size;
block_list[block_idx].allocated = false;
block_list[block_idx].allocation_id = 0;
// Merge with next block if possible
if (block_idx + 1 < block_count && !block_list[block_idx + 1].allocated) {
uintptr_t current_end = (uintptr_t)block_list[block_idx].address + block_list[block_idx].size;
uintptr_t next_start = (uintptr_t)block_list[block_idx + 1].address;
if (current_end == next_start) {
block_list[block_idx].size += block_list[block_idx + 1].size;
remove_block_at(block_idx + 1);
}
}
// Merge with previous block if possible
if (block_idx > 0 && !block_list[block_idx - 1].allocated) {
uintptr_t prev_end = (uintptr_t)block_list[block_idx - 1].address + block_list[block_idx - 1].size;
uintptr_t current_start = (uintptr_t)block_list[block_idx].address;
if (prev_end == current_start) {
block_list[block_idx - 1].size += block_list[block_idx].size;
remove_block_at(block_idx);
}
}
asm volatile("push %0; popfq" : : "r"(rflags));
}
void* krealloc(void *ptr, size_t new_size) {
if (new_size == 0) {
kfree(ptr);
return NULL;
}
if (ptr == NULL) {
return kmalloc(new_size);
}
for (int i = 0; i < block_count; i++) {
if (block_list[i].allocated && block_list[i].address == ptr) {
if (block_list[i].size >= new_size) {
return ptr;
}
void *new_ptr = kmalloc(new_size);
if (new_ptr == NULL) {
return NULL;
}
mem_memmove(new_ptr, ptr, block_list[i].size);
kfree(ptr);
return new_ptr;
}
}
return NULL;
}
MemStats memory_get_stats(void) {
MemStats stats;
mem_memset(&stats, 0, sizeof(MemStats));
stats.total_memory = memory_pool_size;
stats.used_memory = total_allocated;
stats.available_memory = memory_pool_size - total_allocated;
stats.allocated_blocks = 0;
stats.free_blocks = 0;
stats.largest_free_block = 0;
stats.smallest_free_block = memory_pool_size;
stats.peak_memory_used = peak_allocated;
// Count and analyze blocks
for (int i = 0; i < block_count; i++) {
if (block_list[i].allocated) {
stats.allocated_blocks++;
} else {
stats.free_blocks++;
if (block_list[i].size > stats.largest_free_block) {
stats.largest_free_block = block_list[i].size;
}
if (block_list[i].size < stats.smallest_free_block) {
stats.smallest_free_block = block_list[i].size;
}
}
}
if (stats.free_blocks == 0) {
stats.smallest_free_block = 0;
}
stats.fragmentation_percent = calculate_fragmentation();
return stats;
}
void memory_print_stats(void) {
MemStats stats = memory_get_stats();
// Use CLI write functions - declare as extern
extern void cmd_write(const char *str);
extern void cmd_write_int(int n);
extern void cmd_putchar(char c);
cmd_write("\n=== MEMORY STATISTICS ===\n");
cmd_write("Total Memory: ");
cmd_write_int(stats.total_memory / 1024);
cmd_write(" KB\n");
cmd_write("Used Memory: ");
cmd_write_int(stats.used_memory / 1024);
cmd_write(" KB\n");
cmd_write("Available Memory: ");
cmd_write_int(stats.available_memory / 1024);
cmd_write(" KB\n");
cmd_write("Allocated Blocks: ");
cmd_write_int(stats.allocated_blocks);
cmd_write("\n");
cmd_write("Free Blocks: ");
cmd_write_int(stats.free_blocks);
cmd_write("\n");
cmd_write("Largest Free: ");
cmd_write_int(stats.largest_free_block / 1024);
cmd_write(" KB\n");
cmd_write("Peak Usage: ");
cmd_write_int(stats.peak_memory_used / 1024);
cmd_write(" KB\n");
cmd_write("Fragmentation: ");
cmd_write_int(stats.fragmentation_percent);
cmd_write("%\n");
cmd_write("Usage: ");
int usage_percent = (stats.used_memory * 100) / stats.total_memory;
cmd_write_int(usage_percent);
cmd_write("%\n");
cmd_write("========================\n\n");
}
void memory_print_detailed(void) {
extern void cmd_write(const char *str);
extern void cmd_write_int(int n);
extern void cmd_putchar(char c);
cmd_write("\n=== DETAILED MEMORY BLOCKS ===\n");
cmd_write("ID Address Size Status\n");
cmd_write("------ -------- -------- --------\n");
for (int i = 0; i < block_count; i++) {
if (block_list[i].size == 0) continue;
// ID
cmd_write_int(block_list[i].allocation_id);
cmd_write(" ");
// Address (simplified hex output)
cmd_write("0x");
cmd_write_int((uintptr_t)block_list[i].address / 1024);
cmd_write(" ");
// Size
cmd_write_int(block_list[i].size / 1024);
cmd_write("KB ");
// Status
if (block_list[i].allocated) {
cmd_write("ALLOC\n");
} else {
cmd_write("FREE\n");
}
}
cmd_write("==============================\n\n");
}
void memory_validate(void) {
extern void cmd_write(const char *str);
extern void cmd_write_int(int n);
int errors = 0;
// Check for overlapping blocks
for (int i = 0; i < block_count; i++) {
for (int j = i + 1; j < block_count; j++) {
void *i_start = block_list[i].address;
void *i_end = (uint8_t *)i_start + block_list[i].size;
void *j_start = block_list[j].address;
void *j_end = (uint8_t *)j_start + block_list[j].size;
if (i_start < j_end && i_end > j_start) {
errors++;
cmd_write("ERROR: Overlapping blocks detected!\n");
}
}
}
if (errors == 0) {
cmd_write("Memory validation: OK\n");
} else {
cmd_write("Memory validation failed with ");
cmd_write_int(errors);
cmd_write(" errors\n");
}
}
void memory_dump_blocks(void) {
extern void cmd_write(const char *str);
extern void cmd_write_int(int n);
cmd_write("\nMemory block dump:\n");
cmd_write("Total blocks: ");
cmd_write_int(block_count);
cmd_write("\n");
memory_print_detailed();
}
size_t memory_get_peak_usage(void) {
return peak_allocated;
}
void memory_reset_peak(void) {
peak_allocated = total_allocated;
}
bool memory_is_valid_ptr(void *ptr) {
if (ptr == NULL) return false;
if (!initialized) {
return false;
}
// Check if it's an allocated block
for (int i = 0; i < block_count; i++) {
if (block_list[i].allocated && block_list[i].address == ptr) {
return true;
}
}
return false;
}

64
src/mem/memory_manager.h Normal file
View File

@@ -0,0 +1,64 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef MEMORY_MANAGER_H
#define MEMORY_MANAGER_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "limine.h"
// Memory Manager Configuration
#define DEFAULT_POOL_SIZE (128 * 1024 * 1024) // 128MB default
#define MAX_ALLOCATIONS 262144 // Increased for larger pools
#define MAX_FRAGMENTATION_SLOTS 2048
// Allocation block metadata
typedef struct {
void *address;
size_t size;
bool allocated;
uint32_t allocation_id;
uint32_t timestamp;
} MemBlock;
// Memory statistics
typedef struct {
size_t total_memory;
size_t used_memory;
size_t available_memory;
size_t allocated_blocks;
size_t free_blocks;
size_t largest_free_block;
size_t smallest_free_block;
size_t fragmentation_percent;
size_t peak_memory_used;
} MemStats;
// Public API
void memory_manager_init_from_memmap(struct limine_memmap_response *memmap);
// Allocation/Deallocation
void* kmalloc(size_t size);
void* kmalloc_aligned(size_t size, size_t alignment);
void kfree(void *ptr);
void* krealloc(void *ptr, size_t new_size);
// Statistics and Information
MemStats memory_get_stats(void);
void memory_print_stats(void);
void memory_print_detailed(void);
void memory_validate(void);
void memory_dump_blocks(void);
// Internal utilities
size_t memory_get_peak_usage(void);
void memory_reset_peak(void);
bool memory_is_valid_ptr(void *ptr);
void mem_memset(void *dest, int val, size_t len);
void mem_memcpy(void *dest, const void *src, size_t len);
#endif // MEMORY_MANAGER_H

157
src/mem/paging.c Normal file
View File

@@ -0,0 +1,157 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "paging.h"
#include "memory_manager.h"
#include "platform.h"
#include <stddef.h>
static uint64_t current_pml4_phys = 0;
// Get current CR3 value
static uint64_t read_cr3(void) {
uint64_t cr3;
asm volatile("mov %%cr3, %0" : "=r"(cr3));
return cr3;
}
// Set CR3 value
static void write_cr3(uint64_t cr3) {
asm volatile("mov %0, %%cr3" : : "r"(cr3));
}
// Helper to allocate a page table and clear it
static uint64_t alloc_page_table_phys(void) {
void *ptr = kmalloc_aligned(PAGE_SIZE, PAGE_SIZE);
if (!ptr) return 0;
page_table_t* table = (page_table_t*)ptr;
// Clear table
for (int i = 0; i < 512; i++) {
table->entries[i] = 0;
}
// Return the physical address of this table
return v2p((uint64_t)table);
}
void paging_init(void) {
current_pml4_phys = read_cr3() & PT_ADDR_MASK;
}
uint64_t paging_get_pml4_phys(void) {
return current_pml4_phys;
}
void paging_switch_directory(uint64_t pml4_phys) {
current_pml4_phys = pml4_phys;
write_cr3(pml4_phys);
}
void paging_map_page(uint64_t pml4_phys, uint64_t virtual_addr, uint64_t physical_addr, uint64_t flags) {
if (!pml4_phys) return;
page_table_t* pml4 = (page_table_t*)p2v(pml4_phys);
// Extract indices
uint64_t pml4_index = (virtual_addr >> 39) & 0x1FF;
uint64_t pdpt_index = (virtual_addr >> 30) & 0x1FF;
uint64_t pd_index = (virtual_addr >> 21) & 0x1FF;
uint64_t pt_index = (virtual_addr >> 12) & 0x1FF;
// Check PML4 entry
if (!(pml4->entries[pml4_index] & PT_PRESENT)) {
uint64_t new_table_phys = alloc_page_table_phys();
if (!new_table_phys) return; // Out of memory
pml4->entries[pml4_index] = new_table_phys | PT_PRESENT | PT_RW | PT_USER;
}
// Get PDPT
page_table_t* pdpt = (page_table_t*)p2v(pml4->entries[pml4_index] & PT_ADDR_MASK);
if (!(pdpt->entries[pdpt_index] & PT_PRESENT)) {
uint64_t new_table_phys = alloc_page_table_phys();
if (!new_table_phys) return;
pdpt->entries[pdpt_index] = new_table_phys | PT_PRESENT | PT_RW | PT_USER;
}
// Get PD
page_table_t* pd = (page_table_t*)p2v(pdpt->entries[pdpt_index] & PT_ADDR_MASK);
if (!(pd->entries[pd_index] & PT_PRESENT)) {
uint64_t new_table_phys = alloc_page_table_phys();
if (!new_table_phys) return;
pd->entries[pd_index] = new_table_phys | PT_PRESENT | PT_RW | PT_USER;
}
// Get PT
page_table_t* pt = (page_table_t*)p2v(pd->entries[pd_index] & PT_ADDR_MASK);
// Set entry in PT
pt->entries[pt_index] = (physical_addr & PT_ADDR_MASK) | flags;
// Flush TLB for this address
asm volatile("invlpg (%0)" : : "r"(virtual_addr) : "memory");
}
uint64_t paging_create_user_pml4_phys(void) {
// 1. Allocate a new physical PML4
uint64_t new_pml4_phys = alloc_page_table_phys();
if (!new_pml4_phys) return 0;
page_table_t* new_pml4 = (page_table_t*)p2v(new_pml4_phys);
// 2. Clone the higher-half kernel mappings from the active PML4
// In x86_64, indices 256-511 are the higher half.
uint64_t kernel_pml4_phys = paging_get_pml4_phys();
if (kernel_pml4_phys) {
page_table_t* kernel_pml4 = (page_table_t*)p2v(kernel_pml4_phys);
for (int i = 256; i < 512; i++) {
new_pml4->entries[i] = kernel_pml4->entries[i];
}
}
// The lower half (0-255) is left empty for the user process to use
return new_pml4_phys;
}
void paging_destroy_user_pml4_phys(uint64_t pml4_phys) {
if (!pml4_phys) return;
page_table_t* pml4 = (page_table_t*)p2v(pml4_phys);
// Only traverse lower half (user space, indices 0-255)
for (int pml4_idx = 0; pml4_idx < 256; pml4_idx++) {
if (pml4->entries[pml4_idx] & PT_PRESENT) {
page_table_t* pdpt = (page_table_t*)p2v(pml4->entries[pml4_idx] & PT_ADDR_MASK);
for (int pdpt_idx = 0; pdpt_idx < 512; pdpt_idx++) {
if (pdpt->entries[pdpt_idx] & PT_PRESENT) {
page_table_t* pd = (page_table_t*)p2v(pdpt->entries[pdpt_idx] & PT_ADDR_MASK);
for (int pd_idx = 0; pd_idx < 512; pd_idx++) {
if (pd->entries[pd_idx] & PT_PRESENT) {
page_table_t* pt = (page_table_t*)p2v(pd->entries[pd_idx] & PT_ADDR_MASK);
for (int pt_idx = 0; pt_idx < 512; pt_idx++) {
if (pt->entries[pt_idx] & PT_PRESENT) {
uint64_t phys_addr = pt->entries[pt_idx] & PT_ADDR_MASK;
extern void kfree(void* ptr);
kfree((void*)p2v(phys_addr));
}
}
extern void kfree(void* ptr);
kfree((void*)pt);
}
}
extern void kfree(void* ptr);
kfree((void*)pd);
}
}
extern void kfree(void* ptr);
kfree((void*)pdpt);
}
}
// Finally free the pml4 itself
extern void kfree(void* ptr);
kfree((void*)pml4);
}

42
src/mem/paging.h Normal file
View File

@@ -0,0 +1,42 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef PAGING_H
#define PAGING_H
#include <stdint.h>
#include <stdbool.h>
#define PAGE_SIZE 4096
// Page Table Entry Flags
#define PT_PRESENT (1ull << 0)
#define PT_RW (1ull << 1)
#define PT_USER (1ull << 2)
#define PT_WRITE_THROUGH (1ull << 3)
#define PT_CACHE_DISABLE (1ull << 4)
#define PT_ACCESSED (1ull << 5)
#define PT_DIRTY (1ull << 6)
#define PT_HUGE (1ull << 7)
#define PT_GLOBAL (1ull << 8)
#define PT_NX (1ull << 63)
#define PT_ADDR_MASK 0x000FFFFFFFFFF000ull
typedef struct {
uint64_t entries[512];
} __attribute__((aligned(PAGE_SIZE))) page_table_t;
uint64_t paging_get_pml4_phys(void);
void paging_map_page(uint64_t pml4_phys, uint64_t virtual_addr, uint64_t physical_addr, uint64_t flags);
uint64_t paging_create_user_pml4_phys(void);
void paging_switch_directory(uint64_t pml4_phys);
void paging_destroy_user_pml4_phys(uint64_t pml4_phys);
void paging_init(void);
#endif // PAGING_H

604
src/mem/vm.c Normal file
View File

@@ -0,0 +1,604 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "vm.h"
#include "cmd.h"
#include "memory_manager.h"
#include "graphics.h"
#include "wm.h"
#include "fat32.h"
#include "rtc.h"
#include "ps2.h"
#include "kutils.h"
#include "io.h"
// --- Scancode Map (Set 1) ---
static char vm_scancode_map[128] = {
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0,
'\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*',
0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
// VM State
static int stack[VM_STACK_SIZE];
static unsigned long int rand_next = 1;
static int sp = 0;
static uint8_t memory[VM_MEMORY_SIZE]; // 64KB Linear RAM
static int vm_heap_ptr = 8192;
// --- Graphics Overlay Support ---
typedef struct {
int x, y, w, h;
uint32_t color;
} VMRect;
#define MAX_VM_RECTS 256
static VMRect vm_rects[MAX_VM_RECTS];
static int vm_rect_count = 0;
static void vm_paint_overlay(void) {
for (int i = 0; i < vm_rect_count; i++) {
draw_rect(vm_rects[i].x, vm_rects[i].y, vm_rects[i].w, vm_rects[i].h, vm_rects[i].color);
}
}
// Helper to access memory as int
static int mem_read32(int addr) {
if (addr < 0 || addr > VM_MEMORY_SIZE - 4) return 0;
int val = 0;
val |= memory[addr];
val |= memory[addr+1] << 8;
val |= memory[addr+2] << 16;
val |= memory[addr+3] << 24;
return val;
}
static void mem_write32(int addr, int val) {
if (addr < 0 || addr > VM_MEMORY_SIZE - 4) return;
memory[addr] = val & 0xFF;
memory[addr+1] = (val >> 8) & 0xFF;
memory[addr+2] = (val >> 16) & 0xFF;
memory[addr+3] = (val >> 24) & 0xFF;
}
static void vm_reset(void) {
sp = 0;
k_memset(memory, 0, VM_MEMORY_SIZE);
vm_heap_ptr = 8192;
}
static void push(int val) {
if (sp < VM_STACK_SIZE) {
stack[sp++] = val;
} else {
cmd_write("VM Error: Stack Overflow\n");
}
}
static int pop(void) {
if (sp > 0) {
return stack[--sp];
}
cmd_write("VM Error: Stack Underflow\n");
return 0;
}
// Syscall Implementations
static void vm_syscall(int id) {
switch (id) {
case VM_SYS_EXIT:
// Handled by return code in main loop usually, but here just do nothing or treat as halt
push(0);
break;
case VM_SYS_PRINT_INT:
cmd_write_int(pop());
push(0);
break;
case VM_SYS_PRINT_CHAR: {
char c = (char)pop();
char s[2] = {c, 0};
cmd_write(s);
push(0);
break;
}
case VM_SYS_PRINT_STR: {
int addr = pop();
if (addr >= 0 && addr < VM_MEMORY_SIZE) {
cmd_write((char*)&memory[addr]);
}
push(0);
break;
}
case VM_SYS_NL:
cmd_write("\n");
push(0);
break;
case VM_SYS_CLS:
cmd_screen_clear();
push(0);
break;
case VM_SYS_GETCHAR: {
int c = 0;
// Blocking read for a valid key press
while (1) {
if ((inb(0x64) & 1)) { // Data available
uint8_t sc = inb(0x60);
if (!(sc & 0x80)) { // Key press
if (sc < 128) {
c = vm_scancode_map[sc];
if (c) break;
}
}
}
}
push(c);
break;
}
case VM_SYS_KB_HIT:
// Simple check if data is waiting in keyboard controller
push((inb(0x64) & 1) ? 1 : 0);
break;
case VM_SYS_STRLEN: {
int addr = pop();
if (addr >= 0 && addr < VM_MEMORY_SIZE) {
push(k_strlen((char*)&memory[addr]));
} else push(0);
break;
}
case VM_SYS_STRCMP: {
int a2 = pop();
int a1 = pop();
if (a1 >= 0 && a1 < VM_MEMORY_SIZE && a2 >= 0 && a2 < VM_MEMORY_SIZE) {
push(k_strcmp((char*)&memory[a1], (char*)&memory[a2]));
} else push(0);
break;
}
case VM_SYS_STRCPY: {
int src = pop();
int dest = pop();
if (dest >= 0 && dest < VM_MEMORY_SIZE && src >= 0 && src < VM_MEMORY_SIZE) {
k_strcpy((char*)&memory[dest], (char*)&memory[src]);
push(dest);
} else push(0);
break;
}
case VM_SYS_STRCAT: {
// Not implemented in cli_utils
pop(); pop(); push(0);
break;
}
case VM_SYS_MEMSET: {
int n = pop();
int val = pop();
int ptr = pop();
if (ptr >= 0 && ptr + n <= VM_MEMORY_SIZE) {
k_memset(&memory[ptr], val, n);
push(ptr);
} else push(0);
break;
}
case VM_SYS_MEMCPY: {
int n = pop();
int src = pop();
int dest = pop();
if (dest >= 0 && dest+n <= VM_MEMORY_SIZE && src >= 0 && src+n <= VM_MEMORY_SIZE) {
for(int i=0; i<n; i++) memory[dest+i] = memory[src+i];
push(dest);
} else push(0);
break;
}
// Simplified Heap (using top of memory growing down?)
// For now, static allocation or mapped.
// Dummy malloc that returns an index into memory starting at 1024
case VM_SYS_MALLOC: {
int size = pop();
int res = vm_heap_ptr;
vm_heap_ptr += size;
if (vm_heap_ptr >= VM_MEMORY_SIZE) {
push(0); // OOM
} else {
push(res);
}
break;
}
case VM_SYS_FREE:
pop(); // No-op
push(0);
break;
case VM_SYS_RAND: {
rand_next = rand_next * 1103515245 + 12345;
push((unsigned int)(rand_next/65536) % 32768);
break;
}
case VM_SYS_SRAND: {
rand_next = pop();
push(0);
break;
}
case VM_SYS_ABS: {
int x = pop();
push(x < 0 ? -x : x);
break;
}
case VM_SYS_MIN: {
int b = pop();
int a = pop();
push(a < b ? a : b);
break;
}
case VM_SYS_MAX: {
int b = pop();
int a = pop();
push(a > b ? a : b);
break;
}
case VM_SYS_POW: {
int exp = pop();
int base = pop();
int res = 1;
for(int i=0; i<exp; i++) res *= base;
push(res);
break;
}
case VM_SYS_SQRT: {
int n = pop();
int res = 0;
while ((res*res) <= n) res++;
push(res - 1);
break;
}
case VM_SYS_SLEEP:
k_sleep(pop());
push(0);
break;
// File IO - Not supported yet as FILE* cannot be easily passed to VM
case VM_SYS_FOPEN: pop(); pop(); push(0); break;
case VM_SYS_FCLOSE: pop(); push(0); break;
case VM_SYS_FREAD: pop(); pop(); pop(); pop(); push(0); break;
case VM_SYS_FWRITE: pop(); pop(); pop(); pop(); push(0); break;
case VM_SYS_FSEEK: pop(); pop(); pop(); push(0); break;
case VM_SYS_REMOVE: pop(); push(0); break;
case VM_SYS_DRAW_PIXEL: {
int color = pop();
int y = pop();
int x = pop();
put_pixel(x, y, color);
push(0);
break;
}
case VM_SYS_DRAW_RECT: {
int color = pop();
int h = pop();
int w = pop();
int y = pop();
int x = pop();
// Store for overlay
if (vm_rect_count < MAX_VM_RECTS) {
vm_rects[vm_rect_count].x = x;
vm_rects[vm_rect_count].y = y;
vm_rects[vm_rect_count].w = w;
vm_rects[vm_rect_count].h = h;
vm_rects[vm_rect_count].color = color;
vm_rect_count++;
}
// Trigger repaint
wm_mark_dirty(x, y, w, h);
push(0);
break;
}
case VM_SYS_GET_WIDTH: push(get_screen_width()); break;
case VM_SYS_GET_HEIGHT: push(get_screen_height()); break;
case VM_SYS_ATOI: {
int addr = pop();
if (addr >= 0 && addr < VM_MEMORY_SIZE) {
push(k_atoi((char*)&memory[addr]));
} else push(0);
break;
}
case VM_SYS_ITOA: {
int addr = pop();
int val = pop();
if (addr >= 0 && addr < VM_MEMORY_SIZE) {
k_itoa(val, (char*)&memory[addr]);
}
push(0);
break;
}
case VM_SYS_PEEK: push(mem_read32(pop())); break;
case VM_SYS_POKE: {
int val = pop();
int addr = pop();
mem_write32(addr, val);
push(0);
break;
}
case VM_SYS_EXEC: pop(); push(-1); break;
case VM_SYS_SYSTEM: pop(); push(-1); break;
// --- New Builtins ---
case VM_SYS_ISALNUM: {
int c = pop();
push(((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')));
break;
}
case VM_SYS_ISALPHA: {
int c = pop();
push(((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')));
break;
}
case VM_SYS_ISDIGIT: {
int c = pop();
push((c >= '0' && c <= '9'));
break;
}
case VM_SYS_TOLOWER: {
int c = pop();
if (c >= 'A' && c <= 'Z') push(c + 32);
else push(c);
break;
}
case VM_SYS_TOUPPER: {
int c = pop();
if (c >= 'a' && c <= 'z') push(c - 32);
else push(c);
break;
}
case VM_SYS_STRNCPY: {
int n = pop();
int src = pop();
int dest = pop();
if (dest >= 0 && dest+n <= VM_MEMORY_SIZE && src >= 0 && src+n <= VM_MEMORY_SIZE) {
char *d = (char*)&memory[dest];
char *s = (char*)&memory[src];
int i;
for (i = 0; i < n && s[i] != '\0'; i++) d[i] = s[i];
for ( ; i < n; i++) d[i] = '\0';
push(dest);
} else push(0);
break;
}
case VM_SYS_STRNCAT: {
int n = pop();
int src = pop();
int dest = pop();
if (dest >= 0 && dest < VM_MEMORY_SIZE && src >= 0 && src < VM_MEMORY_SIZE) {
char *d = (char*)&memory[dest];
char *s = (char*)&memory[src];
int d_len = 0; while (d[d_len]) d_len++;
int i;
for (i = 0; i < n && s[i] != '\0'; i++) {
if (dest + d_len + i < VM_MEMORY_SIZE) d[d_len + i] = s[i];
}
if (dest + d_len + i < VM_MEMORY_SIZE) d[d_len + i] = '\0';
push(dest);
} else push(0);
break;
}
case VM_SYS_STRNCMP: {
int n = pop();
int s2 = pop();
int s1 = pop();
if (s1 >= 0 && s1 < VM_MEMORY_SIZE && s2 >= 0 && s2 < VM_MEMORY_SIZE) {
char *p1 = (char*)&memory[s1];
char *p2 = (char*)&memory[s2];
int res = 0;
for (int i = 0; i < n; i++) {
if (p1[i] != p2[i] || p1[i] == '\0' || p2[i] == '\0') {
res = (unsigned char)p1[i] - (unsigned char)p2[i];
break;
}
}
push(res);
} else push(0);
break;
}
case VM_SYS_STRSTR: {
int needle = pop();
int haystack = pop();
if (haystack >= 0 && haystack < VM_MEMORY_SIZE && needle >= 0 && needle < VM_MEMORY_SIZE) {
char *h = (char*)&memory[haystack];
char *n = (char*)&memory[needle];
int h_len = 0; while(h[h_len]) h_len++;
int n_len = 0; while(n[n_len]) n_len++;
int found_idx = -1;
for (int i = 0; i <= h_len - n_len; i++) {
bool match = true;
for (int j = 0; j < n_len; j++) {
if (h[i+j] != n[j]) { match = false; break; }
}
if (match) { found_idx = haystack + i; break; }
}
push(found_idx != -1 ? found_idx : 0); // Return ptr or null (0)
} else push(0);
break;
}
case VM_SYS_STRRCHR: {
int c = pop();
int s = pop();
if (s >= 0 && s < VM_MEMORY_SIZE) {
char *str = (char*)&memory[s];
int len = 0; while(str[len]) len++;
int found_idx = 0;
for (int i = len; i >= 0; i--) {
if (str[i] == c) {
found_idx = s + i;
break;
}
}
push(found_idx);
} else push(0);
break;
}
case VM_SYS_MEMMOVE: {
int n = pop();
int src = pop();
int dest = pop();
if (dest >= 0 && dest+n <= VM_MEMORY_SIZE && src >= 0 && src+n <= VM_MEMORY_SIZE) {
uint8_t *d = &memory[dest];
uint8_t *s = &memory[src];
if (d < s) {
for (int i = 0; i < n; i++) d[i] = s[i];
} else {
for (int i = n - 1; i >= 0; i--) d[i] = s[i];
}
push(dest);
} else push(0);
break;
}
default:
cmd_write("VM: Unknown Syscall\n");
break;
}
}
int vm_exec(const uint8_t *code, int code_size) {
if (code_size < 8) return -1;
for (int i = 0; i < 7; i++) {
if (code[i] != VM_MAGIC[i]) return -1;
}
vm_reset();
vm_rect_count = 0;
wm_custom_paint_hook = vm_paint_overlay;
// Safety check
if (code_size > VM_MEMORY_SIZE) {
cmd_write("VM Error: Binary too large\n");
wm_custom_paint_hook = NULL;
return -1;
}
// Load program into memory at address 0
k_memset(memory, 0, VM_MEMORY_SIZE);
for(int i=0; i<code_size; i++) memory[i] = code[i];
int pc = 8; // Skip header
while (pc < code_size) {
uint8_t op = memory[pc++];
switch (op) {
case OP_HALT:
wm_custom_paint_hook = NULL;
return 0;
case OP_IMM: {
int val = 0;
val |= memory[pc++];
val |= memory[pc++] << 8;
val |= memory[pc++] << 16;
val |= memory[pc++] << 24;
push(val);
break;
}
case OP_LOAD: { // Read 32-bit int from absolute address
int addr = 0;
addr |= memory[pc++];
addr |= memory[pc++] << 8;
addr |= memory[pc++] << 16;
addr |= memory[pc++] << 24;
push(mem_read32(addr));
break;
}
case OP_STORE: {
int addr = 0;
addr |= memory[pc++];
addr |= memory[pc++] << 8;
addr |= memory[pc++] << 16;
addr |= memory[pc++] << 24;
mem_write32(addr, pop());
break;
}
case OP_LOAD8: { // Read byte
int addr = 0;
addr |= memory[pc++];
addr |= memory[pc++] << 8;
addr |= memory[pc++] << 16;
addr |= memory[pc++] << 24;
if (addr >= 0 && addr < VM_MEMORY_SIZE) push(memory[addr]);
else push(0);
break;
}
case OP_STORE8: {
int addr = 0;
addr |= memory[pc++];
addr |= memory[pc++] << 8;
addr |= memory[pc++] << 16;
addr |= memory[pc++] << 24;
int val = pop();
if (addr >= 0 && addr < VM_MEMORY_SIZE) memory[addr] = (uint8_t)val;
break;
}
case OP_ADD: push(pop() + pop()); break;
case OP_SUB: { int b=pop(); int a=pop(); push(a-b); } break;
case OP_MUL: push(pop() * pop()); break;
case OP_DIV: { int b=pop(); int a=pop(); push(b==0?0:a/b); } break;
case OP_PRINT: cmd_write_int(pop()); cmd_write("\n"); break;
case OP_PRITC: { char c=(char)pop(); char s[2]={c,0}; cmd_write(s); } break;
case OP_JMP: {
int addr = 0;
addr |= memory[pc++];
addr |= memory[pc++] << 8;
addr |= memory[pc++] << 16;
addr |= memory[pc++] << 24;
pc = addr;
break;
}
case OP_JZ: {
int addr = 0;
addr |= memory[pc++];
addr |= memory[pc++] << 8;
addr |= memory[pc++] << 16;
addr |= memory[pc++] << 24;
if (pop() == 0) pc = addr;
break;
}
case OP_EQ: push(pop() == pop()); break;
case OP_NEQ: push(pop() != pop()); break;
case OP_LT: { int b=pop(); int a=pop(); push(a<b); } break;
case OP_GT: { int b=pop(); int a=pop(); push(a>b); } break;
case OP_LE: { int b=pop(); int a=pop(); push(a<=b); } break;
case OP_GE: { int b=pop(); int a=pop(); push(a>=b); } break;
case OP_SYSCALL: {
int id = 0;
id |= memory[pc++];
id |= memory[pc++] << 8;
id |= memory[pc++] << 16;
id |= memory[pc++] << 24;
vm_syscall(id);
break;
}
case OP_PUSH_PTR: {
// Push immediate value (pointer)
// This is same as IMM but semantically distinct for future use
int val = 0;
val |= memory[pc++];
val |= memory[pc++] << 8;
val |= memory[pc++] << 16;
val |= memory[pc++] << 24;
push(val);
break;
}
case OP_POP:
pop();
break;
default:
wm_custom_paint_hook = NULL;
return -1;
}
}
wm_custom_paint_hook = NULL;
return 0;
}

111
src/mem/vm.h Normal file
View File

@@ -0,0 +1,111 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef VM_H
#define VM_H
#include <stdint.h>
#include <stdbool.h>
// Simple Stack-Based VM
// Header: "BORDEXE" (7 bytes) + Version (1 byte)
#define VM_MAGIC "BORDEXE"
#define VM_STACK_SIZE 256
#define VM_MEMORY_SIZE (64 * 1024) // 64KB
typedef enum {
OP_HALT = 0,
OP_IMM, // Push immediate (int32)
OP_LOAD, // Load from memory (addr) - int32
OP_STORE, // Store to memory (addr) - int32
OP_ADD, // +
OP_SUB, // -
OP_MUL, // *
OP_DIV, // /
OP_PRINT, // Deprecated
OP_PRITC, // Deprecated
OP_JMP, // Jump (addr)
OP_JZ, // Jump if zero
OP_EQ, // ==
OP_NEQ, // !=
OP_LT, // <
OP_GT, // >
OP_LE, // <=
OP_GE, // >=
OP_SYSCALL, // Call system function (id)
OP_LOAD8, // Load byte
OP_STORE8, // Store byte
OP_PUSH_PTR, // Push pointer to data segment (relative to start of mem)
OP_POP // Pop and discard top of stack
} OpCode;
// Syscall IDs
typedef enum {
VM_SYS_EXIT = 0,
VM_SYS_PRINT_INT,
VM_SYS_PRINT_CHAR,
VM_SYS_PRINT_STR,
VM_SYS_NL,
VM_SYS_CLS,
VM_SYS_GETCHAR,
VM_SYS_STRLEN,
VM_SYS_STRCMP,
VM_SYS_STRCPY,
VM_SYS_STRCAT,
VM_SYS_MEMSET,
VM_SYS_MEMCPY,
VM_SYS_MALLOC,
VM_SYS_FREE,
VM_SYS_RAND,
VM_SYS_SRAND,
VM_SYS_ABS,
VM_SYS_MIN,
VM_SYS_MAX,
VM_SYS_POW,
VM_SYS_SQRT,
VM_SYS_SLEEP,
VM_SYS_FOPEN,
VM_SYS_FCLOSE,
VM_SYS_FREAD,
VM_SYS_FWRITE,
VM_SYS_FSEEK,
VM_SYS_REMOVE,
VM_SYS_DRAW_PIXEL,
VM_SYS_DRAW_RECT,
VM_SYS_DRAW_LINE,
VM_SYS_DRAW_TEXT,
VM_SYS_GET_WIDTH,
VM_SYS_GET_HEIGHT,
VM_SYS_GET_TIME,
VM_SYS_KB_HIT,
VM_SYS_MOUSE_X,
VM_SYS_MOUSE_Y,
VM_SYS_MOUSE_STATE,
VM_SYS_PLAY_SOUND,
VM_SYS_ATOI,
VM_SYS_ITOA,
VM_SYS_PEEK,
VM_SYS_POKE,
VM_SYS_EXEC,
VM_SYS_SYSTEM,
VM_SYS_STRCHR,
VM_SYS_MEMCMP,
VM_SYS_GET_DATE,
// New Builtins
VM_SYS_ISALNUM,
VM_SYS_ISALPHA,
VM_SYS_ISDIGIT,
VM_SYS_TOLOWER,
VM_SYS_TOUPPER,
VM_SYS_STRNCPY,
VM_SYS_STRNCAT,
VM_SYS_STRNCMP,
VM_SYS_STRSTR,
VM_SYS_STRRCHR,
VM_SYS_MEMMOVE
} SyscallID;
int vm_exec(const uint8_t *code, int code_size);
#endif