mirror of
https://github.com/JannisHeydemann/BoredOS.git
synced 2026-05-30 02:16:58 +00:00
src/kernel --> src/
This commit is contained in:
525
src/mem/memory_manager.c
Normal file
525
src/mem/memory_manager.c
Normal 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
64
src/mem/memory_manager.h
Normal 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
157
src/mem/paging.c
Normal 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
42
src/mem/paging.h
Normal 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
604
src/mem/vm.c
Normal 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
111
src/mem/vm.h
Normal 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
|
||||
Reference in New Issue
Block a user