From b3de59d27423eaa6dbd0685e142d647b91279ebb Mon Sep 17 00:00:00 2001 From: tadryanom_bot Date: Tue, 3 Feb 2026 01:17:57 +0000 Subject: [PATCH] Initial commit: AdrOS v0.1 (Multi-arch Kernel, PMM, VMM, Scheduler) --- BUILD_GUIDE.md | 93 +++++++++++++++++ Makefile | 75 ++++++++++++++ README.md | 21 ++++ include/idt.h | 36 +++++++ include/io.h | 53 ++++++++++ include/multiboot2.h | 72 +++++++++++++ include/pmm.h | 21 ++++ include/process.h | 40 ++++++++ include/uart_console.h | 8 ++ include/vga_console.h | 10 ++ include/vmm.h | 30 ++++++ src/arch/arm/boot.S | 31 ++++++ src/arch/arm/linker.ld | 32 ++++++ src/arch/riscv/boot.S | 29 ++++++ src/arch/riscv/linker.ld | 32 ++++++ src/arch/x86/boot.S | 165 +++++++++++++++++++++++++++++ src/arch/x86/idt.c | 177 ++++++++++++++++++++++++++++++++ src/arch/x86/interrupts.S | 132 ++++++++++++++++++++++++ src/arch/x86/linker.ld | 54 ++++++++++ src/arch/x86/multiboot_header.S | 1 + src/arch/x86/process.S | 34 ++++++ src/arch/x86/vmm.c | 82 +++++++++++++++ src/drivers/uart_console.c | 72 +++++++++++++ src/drivers/vga_console.c | 53 ++++++++++ src/kernel/main.c | 107 +++++++++++++++++++ src/kernel/scheduler.c | 117 +++++++++++++++++++++ src/mm/pmm.c | 154 +++++++++++++++++++++++++++ 27 files changed, 1731 insertions(+) create mode 100644 BUILD_GUIDE.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 include/idt.h create mode 100644 include/io.h create mode 100644 include/multiboot2.h create mode 100644 include/pmm.h create mode 100644 include/process.h create mode 100644 include/uart_console.h create mode 100644 include/vga_console.h create mode 100644 include/vmm.h create mode 100644 src/arch/arm/boot.S create mode 100644 src/arch/arm/linker.ld create mode 100644 src/arch/riscv/boot.S create mode 100644 src/arch/riscv/linker.ld create mode 100644 src/arch/x86/boot.S create mode 100644 src/arch/x86/idt.c create mode 100644 src/arch/x86/interrupts.S create mode 100644 src/arch/x86/linker.ld create mode 100644 src/arch/x86/multiboot_header.S create mode 100644 src/arch/x86/process.S create mode 100644 src/arch/x86/vmm.c create mode 100644 src/drivers/uart_console.c create mode 100644 src/drivers/vga_console.c create mode 100644 src/kernel/main.c create mode 100644 src/kernel/scheduler.c create mode 100644 src/mm/pmm.c diff --git a/BUILD_GUIDE.md b/BUILD_GUIDE.md new file mode 100644 index 0000000..8b069ca --- /dev/null +++ b/BUILD_GUIDE.md @@ -0,0 +1,93 @@ +# AdrOS - Build & Run Guide + +Este guia explica como compilar e rodar o AdrOS em sua máquina local (Linux/WSL). + +## 1. Dependências + +Você precisará de: +- `make` e `gcc` (build system) +- `qemu-system-*` (emuladores) +- `xorriso` (para criar ISOs bootáveis) +- `grub-pc-bin` e `grub-common` (bootloader x86) +- Compiladores cruzados (Cross-compilers) para ARM/RISC-V + +### No Ubuntu/Debian: +```bash +sudo apt update +sudo apt install build-essential bison flex libgmp3-dev libmpc-dev libmpfr-dev texinfo \ + qemu-system-x86 qemu-system-arm qemu-system-misc \ + grub-common grub-pc-bin xorriso mtools \ + gcc-aarch64-linux-gnu gcc-riscv64-linux-gnu +``` + +## 2. Compilando e Rodando (x86) + +Esta é a arquitetura principal (PC padrão). + +### Compilar +```bash +make ARCH=x86 +``` +Isso gera o arquivo `adros-x86.bin`. + +### Criar ISO Bootável (GRUB) +Para x86, precisamos empacotar o kernel numa ISO com GRUB. +Crie um arquivo `grub.cfg` em `iso_root/boot/grub/`: + +```bash +mkdir -p iso_root/boot/grub +cat > iso_root/boot/grub/grub.cfg << EOF +menuentry "AdrOS" { + multiboot2 /boot/adros-x86.bin + boot +} +EOF + +cp adros-x86.bin iso_root/boot/ +grub-mkrescue -o adros.iso iso_root +``` + +### Rodar no QEMU +```bash +qemu-system-i386 -cdrom adros.iso -serial stdio +``` +- A saída de texto aparecerá no terminal (`-serial stdio`). +- Se tivermos VGA ativado, uma janela gráfica abrirá. + +## 3. Compilando e Rodando (ARM64) + +### Compilar +```bash +make ARCH=arm +``` +Isso gera `adros-arm.bin`. + +### Rodar no QEMU +ARM não usa GRUB/ISO da mesma forma, carregamos o kernel direto na memória. + +```bash +qemu-system-aarch64 -M virt -cpu cortex-a57 -m 128M -nographic \ + -kernel adros-arm.bin +``` +- Para sair do QEMU sem gráfico: `Ctrl+A` solte, depois `x`. + +## 4. Compilando e Rodando (RISC-V) + +### Compilar +```bash +make ARCH=riscv +``` +Isso gera `adros-riscv.bin`. + +### Rodar no QEMU +```bash +qemu-system-riscv64 -machine virt -m 128M -nographic \ + -bios default -kernel adros-riscv.bin +``` +- Para sair: `Ctrl+A`, `x`. + +## 5. Troubleshooting Comum + +- **"Multiboot header not found"**: Verifique se o `grub-file --is-x86-multiboot2 adros-x86.bin` retorna sucesso (0). Se falhar, a ordem das seções no `linker.ld` pode estar errada. +- **Triple Fault (Reset infinito)**: Geralmente erro na tabela de paginação (VMM) ou IDT mal configurada. Use `-d int,cpu_reset -D log.txt` no QEMU para debugar. +- **Tela preta (VGA)**: Se estiver rodando com `-nographic`, você não verá o VGA. Remova essa flag para ver a janela. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c69bfde --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +# AdrOS Makefile +# Usage: make ARCH=x86 (default) | arm | riscv + +ARCH ?= x86 +KERNEL_NAME := adros-$(ARCH).bin + +# Directories +SRC_DIR := src +BUILD_DIR := build/$(ARCH) + +# Common sources +C_SOURCES := $(wildcard $(SRC_DIR)/kernel/*.c) +C_SOURCES += $(wildcard $(SRC_DIR)/drivers/*.c) +C_SOURCES += $(wildcard $(SRC_DIR)/mm/*.c) + +# --- x86 Configuration --- +ifeq ($(ARCH),x86) + CC := gcc + AS := as + LD := ld + CFLAGS := -m32 -ffreestanding -O2 -Wall -Wextra -Iinclude + LDFLAGS := -m elf_i386 -T $(SRC_DIR)/arch/x86/linker.ld + ASFLAGS := --32 + ASM_SOURCES := $(wildcard $(SRC_DIR)/arch/x86/*.S) + C_SOURCES += $(wildcard $(SRC_DIR)/arch/x86/*.c) +endif + +# --- ARM64 Configuration --- +ifeq ($(ARCH),arm) + CC := aarch64-linux-gnu-gcc + AS := aarch64-linux-gnu-as + LD := aarch64-linux-gnu-ld + CFLAGS := -ffreestanding -O2 -Wall -Wextra -Iinclude + LDFLAGS := -T $(SRC_DIR)/arch/arm/linker.ld + ASFLAGS := + ASM_SOURCES := $(wildcard $(SRC_DIR)/arch/arm/*.S) + C_SOURCES += $(wildcard $(SRC_DIR)/arch/arm/*.c) +endif + +# --- RISC-V 64 Configuration --- +ifeq ($(ARCH),riscv) + CC := riscv64-linux-gnu-gcc + AS := riscv64-linux-gnu-as + LD := riscv64-linux-gnu-ld + CFLAGS := -ffreestanding -O2 -Wall -Wextra -Iinclude -mcmodel=medany + LDFLAGS := -T $(SRC_DIR)/arch/riscv/linker.ld + ASFLAGS := + ASM_SOURCES := $(wildcard $(SRC_DIR)/arch/riscv/*.S) + C_SOURCES += $(wildcard $(SRC_DIR)/arch/riscv/*.c) +endif + +# Object generation +OBJ := $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(C_SOURCES)) +OBJ += $(patsubst $(SRC_DIR)/%.S, $(BUILD_DIR)/%.o, $(ASM_SOURCES)) + +all: $(KERNEL_NAME) + +$(KERNEL_NAME): $(OBJ) + @echo " LD $@" + @$(LD) $(LDFLAGS) -n -o $@ $(OBJ) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(dir $@) + @echo " CC $<" + @$(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.S + @mkdir -p $(dir $@) + @echo " AS $<" + @$(AS) $(ASFLAGS) $< -o $@ + +clean: + rm -rf build $(KERNEL_NAME) + +.PHONY: all clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..b5490f0 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# AdrOS + +## Overview +AdrOS is a multi-architecture operating system developed for research and academic purposes. The goal is to build a secure, monolithic kernel from scratch, eventually serving as a platform for security testing and exploit development. + +## Architectures Targeted +- **x86** (32-bit & 64-bit) +- **ARM** (32-bit & 64-bit) +- **MIPS** +- **RISC-V** (32-bit & 64-bit) + +## Technical Stack +- **Language:** C/C++ and Assembly +- **Bootloader:** GRUB2 (Multiboot2 compliant) +- **Build System:** Make + Cross-Compilers + +## Directory Structure +- `src/kernel/` - Architecture-independent kernel code +- `src/arch/` - Architecture-specific code (boot, context switch, interrupts) +- `src/drivers/` - Device drivers +- `include/` - Header files diff --git a/include/idt.h b/include/idt.h new file mode 100644 index 0000000..3ae501d --- /dev/null +++ b/include/idt.h @@ -0,0 +1,36 @@ +#ifndef IDT_H +#define IDT_H + +#include + +/* IDT Entry (Gate Descriptor) */ +struct idt_entry { + uint16_t base_lo; // Lower 16 bits of handler address + uint16_t sel; // Kernel segment selector + uint8_t always0; // This must always be zero + uint8_t flags; // Type and attributes + uint16_t base_hi; // Upper 16 bits of handler address +} __attribute__((packed)); + +/* IDT Pointer (Loaded into IDTR) */ +struct idt_ptr { + uint16_t limit; + uint32_t base; +} __attribute__((packed)); + +/* Registers saved by our assembly ISR stub */ +struct registers { + uint32_t ds; // Data segment selector + uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha + uint32_t int_no, err_code; // Interrupt number and error code + uint32_t eip, cs, eflags, useresp, ss; // Pushed by the processor automatically +}; + +// Initialize IDT and PIC +void idt_init(void); + +// Register a custom handler for a specific interrupt +typedef void (*isr_handler_t)(struct registers*); +void register_interrupt_handler(uint8_t n, isr_handler_t handler); + +#endif diff --git a/include/io.h b/include/io.h new file mode 100644 index 0000000..f44840e --- /dev/null +++ b/include/io.h @@ -0,0 +1,53 @@ +#ifndef IO_H +#define IO_H + +#include + +/* x86 I/O Port Wrappers */ +#if defined(__i386__) || defined(__x86_64__) + +static inline void outb(uint16_t port, uint8_t val) { + __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port)); +} + +static inline uint8_t inb(uint16_t port) { + uint8_t ret; + __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port)); + return ret; +} + +static inline void mmio_write8(uintptr_t addr, uint8_t val) { + volatile uint8_t* ptr = (uint8_t*)addr; + *ptr = val; +} + +static inline uint8_t mmio_read8(uintptr_t addr) { + volatile uint8_t* ptr = (uint8_t*)addr; + return *ptr; +} + +#else + +/* MMIO for ARM/RISC-V */ +static inline void mmio_write8(uintptr_t addr, uint8_t val) { + volatile uint8_t* ptr = (uint8_t*)addr; + *ptr = val; +} + +static inline uint8_t mmio_read8(uintptr_t addr) { + volatile uint8_t* ptr = (uint8_t*)addr; + return *ptr; +} + +/* Fallback for port I/O on architectures that don't have it (mapped to MMIO or no-op) */ +static inline void outb(uint16_t port, uint8_t val) { + (void)port; (void)val; // No-op +} + +static inline uint8_t inb(uint16_t port) { + (void)port; return 0; +} + +#endif + +#endif diff --git a/include/multiboot2.h b/include/multiboot2.h new file mode 100644 index 0000000..57148f5 --- /dev/null +++ b/include/multiboot2.h @@ -0,0 +1,72 @@ +#ifndef MULTIBOOT2_H +#define MULTIBOOT2_H + +#include + +/* How many bytes from the start of the file we search for the header. */ +#define MULTIBOOT_SEARCH 32768 +#define MULTIBOOT_HEADER_ALIGN 8 + +/* The magic field should contain this. */ +#define MULTIBOOT2_HEADER_MAGIC 0xe85250d6 + +/* This should be in %eax. */ +#define MULTIBOOT2_BOOTLOADER_MAGIC 0x36d76289 + +/* Alignment of multiboot modules. */ +#define MULTIBOOT_MOD_ALIGN 0x00001000 + +/* Alignment of the multiboot info structure. */ +#define MULTIBOOT_INFO_ALIGN 0x00000008 + +/* Tags found in the Multiboot2 information structure */ +#define MULTIBOOT_TAG_TYPE_END 0 +#define MULTIBOOT_TAG_TYPE_CMDLINE 1 +#define MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME 2 +#define MULTIBOOT_TAG_TYPE_MODULE 3 +#define MULTIBOOT_TAG_TYPE_BASIC_MEMINFO 4 +#define MULTIBOOT_TAG_TYPE_BOOTDEV 5 +#define MULTIBOOT_TAG_TYPE_MMAP 6 +#define MULTIBOOT_TAG_TYPE_VBE 7 +#define MULTIBOOT_TAG_TYPE_FRAMEBUFFER 8 + +struct multiboot_tag { + uint32_t type; + uint32_t size; +}; + +struct multiboot_tag_string { + uint32_t type; + uint32_t size; + char string[0]; +}; + +struct multiboot_tag_basic_meminfo { + uint32_t type; + uint32_t size; + uint32_t mem_lower; + uint32_t mem_upper; +}; + +struct multiboot_tag_mmap { + uint32_t type; + uint32_t size; + uint32_t entry_size; + uint32_t entry_version; + struct multiboot_mmap_entry entries[0]; +}; + +struct multiboot_mmap_entry { + uint64_t addr; + uint64_t len; + uint32_t type; + uint32_t zero; +}; + +#define MULTIBOOT_MEMORY_AVAILABLE 1 +#define MULTIBOOT_MEMORY_RESERVED 2 +#define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3 +#define MULTIBOOT_MEMORY_NVS 4 +#define MULTIBOOT_MEMORY_BADRAM 5 + +#endif /* MULTIBOOT2_H */ diff --git a/include/pmm.h b/include/pmm.h new file mode 100644 index 0000000..83ba57d --- /dev/null +++ b/include/pmm.h @@ -0,0 +1,21 @@ +#ifndef PMM_H +#define PMM_H + +#include + +#define PAGE_SIZE 4096 + +// Initialize the Physical Memory Manager +// boot_info is architecture dependent (Multiboot info on x86) +void pmm_init(void* boot_info); + +// Allocate a single physical page +void* pmm_alloc_page(void); + +// Free a physical page +void pmm_free_page(void* ptr); + +// Helper to print memory stats +void pmm_print_stats(void); + +#endif diff --git a/include/process.h b/include/process.h new file mode 100644 index 0000000..c656d1c --- /dev/null +++ b/include/process.h @@ -0,0 +1,40 @@ +#ifndef PROCESS_H +#define PROCESS_H + +#include +#include "idt.h" // For struct registers + +typedef enum { + PROCESS_READY, + PROCESS_RUNNING, + PROCESS_BLOCKED, + PROCESS_ZOMBIE +} process_state_t; + +struct process { + uint32_t pid; // Process ID + uint32_t esp; // Kernel Stack Pointer (Saved when switched out) + uint32_t cr3; // Page Directory (Physical) + uint32_t* kernel_stack; // Pointer to the bottom of the allocated kernel stack + process_state_t state; // Current state + struct process* next; // Linked list for round-robin +}; + +// Global pointer to the currently running process +extern struct process* current_process; + +// Initialize the multitasking system +void process_init(void); + +// Create a new kernel thread +struct process* process_create_kernel(void (*entry_point)(void)); + +// The magic function that switches stacks (Implemented in Assembly) +// old_esp_ptr: Address where we save the OLD process's ESP +// new_esp: The NEW process's ESP to load +extern void context_switch(uint32_t* old_esp_ptr, uint32_t new_esp); + +// Yield the CPU to the next process voluntarily +void schedule(void); + +#endif diff --git a/include/uart_console.h b/include/uart_console.h new file mode 100644 index 0000000..cc22ac7 --- /dev/null +++ b/include/uart_console.h @@ -0,0 +1,8 @@ +#ifndef UART_CONSOLE_H +#define UART_CONSOLE_H + +void uart_init(void); +void uart_put_char(char c); +void uart_print(const char* str); + +#endif diff --git a/include/vga_console.h b/include/vga_console.h new file mode 100644 index 0000000..296bbcc --- /dev/null +++ b/include/vga_console.h @@ -0,0 +1,10 @@ +#ifndef VGA_CONSOLE_H +#define VGA_CONSOLE_H + +#include + +void vga_init(void); +void vga_print(const char* str); +void vga_set_color(uint8_t fg, uint8_t bg); + +#endif diff --git a/include/vmm.h b/include/vmm.h new file mode 100644 index 0000000..cd97b9e --- /dev/null +++ b/include/vmm.h @@ -0,0 +1,30 @@ +#ifndef VMM_H +#define VMM_H + +#include + +/* Page Flags */ +#define VMM_FLAG_PRESENT (1 << 0) +#define VMM_FLAG_RW (1 << 1) +#define VMM_FLAG_USER (1 << 2) + +/* + * Initialize Virtual Memory Manager + * Should set up the kernel page table/directory and enable paging. + */ +void vmm_init(void); + +/* + * Map a physical page to a virtual address. + * phys: Physical address (must be page aligned) + * virt: Virtual address (must be page aligned) + * flags: Permission flags + */ +void vmm_map_page(uint64_t phys, uint64_t virt, uint32_t flags); + +/* + * Unmap a virtual page. + */ +void vmm_unmap_page(uint64_t virt); + +#endif diff --git a/src/arch/arm/boot.S b/src/arch/arm/boot.S new file mode 100644 index 0000000..8c66e36 --- /dev/null +++ b/src/arch/arm/boot.S @@ -0,0 +1,31 @@ +/* + * AdrOS - ARM64 (AArch64) Bootstrap + * Target: QEMU 'virt' machine + */ + +.section .text +.global _start + +_start: + /* + * CPU usually starts in EL2 or EL3 on QEMU. + * Ideally we should switch to EL1, but for a "hello world" + * we will just set up the stack and run. + */ + + /* Set up stack pointer (x30 is link register, don't clobber it yet) */ + ldr x0, =stack_top + mov sp, x0 + + /* Jump to C kernel */ + bl kernel_main + + /* Hang */ +1: wfi + b 1b + +.section .bss +.align 16 +stack_bottom: + .skip 16384 +stack_top: diff --git a/src/arch/arm/linker.ld b/src/arch/arm/linker.ld new file mode 100644 index 0000000..7042ae1 --- /dev/null +++ b/src/arch/arm/linker.ld @@ -0,0 +1,32 @@ +/* + * AdrOS - ARM64 Linker Script + * Target: QEMU virt (RAM starts at 0x40000000 usually) + */ + +ENTRY(_start) + +SECTIONS +{ + . = 0x40000000; + + _start = .; + + .text : { + *(.text) + } + + .rodata : { + *(.rodata) + } + + .data : { + *(.data) + } + + .bss : { + *(.bss) + *(COMMON) + } + + _end = .; +} diff --git a/src/arch/riscv/boot.S b/src/arch/riscv/boot.S new file mode 100644 index 0000000..d29dff8 --- /dev/null +++ b/src/arch/riscv/boot.S @@ -0,0 +1,29 @@ +/* + * AdrOS - RISC-V 64-bit Bootstrap + * Target: QEMU 'virt' machine (starts at 0x80000000) + */ + +.section .text +.global _start + +_start: + /* Disable interrupts (sie = Supervisor Interrupt Enable) */ + csrw sie, zero + + /* Set up stack pointer. + * In linker.ld we will define a symbol for the stack top. + */ + la sp, stack_top + + /* Call C kernel */ + call kernel_main + + /* Hang if return */ +1: wfi + j 1b + +.section .bss +.align 16 +stack_bottom: + .skip 16384 /* 16KB Stack */ +stack_top: diff --git a/src/arch/riscv/linker.ld b/src/arch/riscv/linker.ld new file mode 100644 index 0000000..df0e989 --- /dev/null +++ b/src/arch/riscv/linker.ld @@ -0,0 +1,32 @@ +/* + * AdrOS - RISC-V Linker Script + * Target: QEMU virt (RAM starts at 0x80000000) + */ + +ENTRY(_start) + +SECTIONS +{ + . = 0x80000000; + + _start = .; + + .text : { + *(.text) + } + + .rodata : { + *(.rodata) + } + + .data : { + *(.data) + } + + .bss : { + *(.bss) + *(COMMON) + } + + _end = .; +} diff --git a/src/arch/x86/boot.S b/src/arch/x86/boot.S new file mode 100644 index 0000000..c1c48a9 --- /dev/null +++ b/src/arch/x86/boot.S @@ -0,0 +1,165 @@ +/* + * AdrOS - x86 Higher Half Kernel Bootstrap + */ + +/* Constants */ +.set MB_MAGIC, 0xE85250D6 +.set MB_ARCH, 0 /* i386 */ +.set KERNEL_VIRT_BASE, 0xC0000000 +.set PAGE_SIZE, 4096 + +/* + * This macro converts a virtual address (symbol) to a physical address + * by subtracting 3GB. We need this before paging is enabled. + */ +#define V2P(x) ((x) - KERNEL_VIRT_BASE) + +.section .multiboot_header +.align 8 +multiboot_header_start: + .long MB_MAGIC + .long MB_ARCH + .long multiboot_header_end - multiboot_header_start + .long 0x100000000 - (MB_MAGIC + MB_ARCH + (multiboot_header_end - multiboot_header_start)) + .word 0, 0 + .long 8 +multiboot_header_end: + +.section .text +.global _start +.type _start, @function + +_start: + /* + * We are loaded at 1MB physical. Paging is OFF. + * CPU is executing instructions here. + * ESP is unknown. + */ + + /* Setup a temporary stack (using physical address) */ + mov $(V2P(stack_top)), %esp + + /* Save Multiboot Info (ebx) and Magic (eax) */ + push %eax + push %ebx + + /* + * SETUP PAGING (Manually) + * We need to map: + * 1. Virt 0x00000000 -> Phys 0x00000000 (Identity, so we don't crash now) + * 2. Virt 0xC0000000 -> Phys 0x00000000 (Kernel Space) + */ + + /* 1. Get Physical Address of Page Table 0 (PT0) */ + mov $(V2P(boot_pt0)), %edi + + /* 2. Map 0-4MB to this PT (fill 1024 entries) */ + /* Entry 0: Addr 0 | Present | RW */ + mov $0, %esi + mov $1023, %ecx +1: + cmpl $_kernel_physical_end, %esi /* Only map what we need? Nah, map 4MB */ + mov %esi, %edx + or $3, %edx /* Present + RW */ + mov %edx, (%edi) + add $4096, %esi + add $4, %edi + loop 1b + + /* 3. Get Physical Address of Page Directory */ + mov $(V2P(boot_pd)), %edi + + /* 4. Link PT0 to PD at index 0 (Identity Map 0-4MB) */ + mov $(V2P(boot_pt0)), %edx + or $3, %edx + mov %edx, (%edi) + + /* 5. Link PT0 to PD at index 768 (3GB mark -> 0xC0000000) */ + /* 768 * 4MB = 3072MB = 3GB */ + mov %edx, 3072(%edi) /* Offset 768 * 4 bytes = 3072 */ + + /* 6. Recursive Mapping (Optional, good for VMM later) at index 1023 */ + mov $(V2P(boot_pd)), %edx + or $3, %edx + mov %edx, 4092(%edi) + + /* 7. Load CR3 */ + mov $(V2P(boot_pd)), %ecx + mov %ecx, %cr3 + + /* 8. Enable Paging (Set PG bit in CR0) */ + mov %cr0, %ecx + or $0x80000000, %ecx + mov %ecx, %cr0 + + /* + * PAGING IS ON! + * We are still executing low code (thanks to identity map). + * Now jump to the higher half! + */ + lea higher_half_start, %ecx + jmp *%ecx + +higher_half_start: + /* + * We are now running at 0xC0xxxxxx. + * We can safely remove the identity mapping (index 0) if we want, + * but let's leave it for C code to clean up. + */ + + /* Unmap the low 4MB (Security: Null pointer deref should fault now!) */ + movl $0, boot_pd + invlpg 0 + + /* Update Stack Pointer to be Virtual */ + /* (Currently ESP points to physical, which is valid mapped, + but let's fix it to use the virtual range) */ + add $KERNEL_VIRT_BASE, %esp + + /* Restore Multiboot args (popped from stack) */ + pop %ebx + pop %eax + + /* + * Pass args to kernel_main. + * NOTE: 'ebx' (multiboot info) is a PHYSICAL address. + * kernel_main might need to map it or we identity map enough RAM. + * Since we kept identity mapping for now (or just unmapped 0-4MB?), + * Wait, I unmapped 0. If multiboot struct is in low mem, we lost access. + * Let's Re-map 0 temporarily inside C or pass physical address and let C handle it. + * For now, let's NOT unmap 0 in asm to be safe, let C do it. + */ + + /* Re-map low memory for safety until PMM parses tags */ + mov $(V2P(boot_pt0)), %edx + or $3, %edx + mov %edx, boot_pd + + /* Call C Kernel */ + /* void kernel_main(uint32_t magic, uint32_t phys_addr) */ + push %ebx + push %eax + call kernel_main + + /* Hang */ + cli +1: hlt + jmp 1b + +/* Global Paging Structures (Pre-allocated in BSS) */ +.section .bss +.align 4096 +.global boot_pd +boot_pd: + .skip 4096 +.global boot_pt0 +boot_pt0: + .skip 4096 + +.align 16 +stack_bottom: + .skip 16384 +stack_top: + +/* Helper symbol for map loop limit */ +_kernel_physical_end: diff --git a/src/arch/x86/idt.c b/src/arch/x86/idt.c new file mode 100644 index 0000000..7217ae2 --- /dev/null +++ b/src/arch/x86/idt.c @@ -0,0 +1,177 @@ +#include "idt.h" +#include "io.h" +#include "uart_console.h" +#include + +#define IDT_ENTRIES 256 + +struct idt_entry idt[IDT_ENTRIES]; +struct idt_ptr idtp; + +// Array of function pointers for handlers +isr_handler_t interrupt_handlers[IDT_ENTRIES]; + +// Extern prototypes for Assembly stubs +extern void isr0(); extern void isr1(); extern void isr2(); extern void isr3(); +extern void isr4(); extern void isr5(); extern void isr6(); extern void isr7(); +extern void isr8(); extern void isr9(); extern void isr10(); extern void isr11(); +extern void isr12(); extern void isr13(); extern void isr14(); extern void isr15(); +extern void isr16(); extern void isr17(); extern void isr18(); extern void isr19(); +extern void isr20(); extern void isr21(); extern void isr22(); extern void isr23(); +extern void isr24(); extern void isr25(); extern void isr26(); extern void isr27(); +extern void isr28(); extern void isr29(); extern void isr30(); extern void isr31(); + +extern void irq0(); extern void irq1(); extern void irq2(); extern void irq3(); +extern void irq4(); extern void irq5(); extern void irq6(); extern void irq7(); +extern void irq8(); extern void irq9(); extern void irq10(); extern void irq11(); +extern void irq12(); extern void irq13(); extern void irq14(); extern void irq15(); + +void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) { + idt[num].base_lo = base & 0xFFFF; + idt[num].base_hi = (base >> 16) & 0xFFFF; + idt[num].sel = sel; + idt[num].always0 = 0; + idt[num].flags = flags; // Present(0x80) | DPL(00) | Type(0xE = 32bit Int Gate) -> 0x8E +} + +/* Reconfigure the PIC to remap IRQs from 0-15 to 32-47 */ +void pic_remap(void) { + uint8_t a1, a2; + + a1 = inb(0x21); // Save masks + a2 = inb(0xA1); + + outb(0x20, 0x11); // Start Init (ICW1) + outb(0xA0, 0x11); + + outb(0x21, 0x20); // Master offset: 32 (0x20) (ICW2) + outb(0xA1, 0x28); // Slave offset: 40 (0x28) + + outb(0x21, 0x04); // Tell Master about Slave (ICW3) + outb(0xA1, 0x02); // Tell Slave its cascade identity + + outb(0x21, 0x01); // 8086 mode (ICW4) + outb(0xA1, 0x01); + + outb(0x21, a1); // Restore masks + outb(0xA1, a2); +} + +void idt_init(void) { + uart_print("[IDT] Initializing Interrupts...\n"); + + idtp.limit = (sizeof(struct idt_entry) * IDT_ENTRIES) - 1; + idtp.base = (uint32_t)&idt; + + // Clear memory + for (int i=0; iint_no] != 0) { + isr_handler_t handler = interrupt_handlers[regs->int_no]; + handler(regs); + } else { + // If Exception (0-31), Panic + if (regs->int_no < 32) { + uart_print("\n[PANIC] Unhandled Exception: "); + // TODO: Print int_no hex + uart_print(" (Halting)\n"); + + // Print Page Fault specifics + if (regs->int_no == 14) { + uart_print("Page Fault! Address: "); + uint32_t cr2; + __asm__ volatile("mov %%cr2, %0" : "=r"(cr2)); + // Print CR2 + } + + for(;;) __asm__("hlt"); + } + } + + // Send EOI (End of Interrupt) to PICs for IRQs (32-47) + if (regs->int_no >= 32) { + if (regs->int_no >= 40) { + outb(0xA0, 0x20); // Slave EOI + } + outb(0x20, 0x20); // Master EOI + } +} diff --git a/src/arch/x86/interrupts.S b/src/arch/x86/interrupts.S new file mode 100644 index 0000000..7f99981 --- /dev/null +++ b/src/arch/x86/interrupts.S @@ -0,0 +1,132 @@ +/* + * AdrOS - x86 Interrupt Stubs + */ + +.section .text +.extern isr_handler + +/* Common handler stub */ +isr_common_stub: + /* Save all registers */ + pusha + + /* Save Data Segment */ + mov %ds, %ax + push %eax + + /* Load Kernel Data Segment */ + mov $0x10, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + + /* Call C handler */ + push %esp /* Pass pointer to stack structure */ + call isr_handler + add $4, %esp + + /* Restore Data Segment */ + pop %eax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + + /* Restore registers */ + popa + + /* Clean up error code and ISR number */ + add $8, %esp + + /* Return from interrupt */ + iret + +/* Macro for exceptions without error code */ +.macro ISR_NOERRCODE num +.global isr\num +isr\num: + cli + push $0 /* Dummy error code */ + push $\num /* ISR Number */ + jmp isr_common_stub +.endm + +/* Macro for exceptions WITH error code */ +.macro ISR_ERRCODE num +.global isr\num +isr\num: + cli + /* Error code is already pushed by CPU */ + push $\num /* ISR Number */ + jmp isr_common_stub +.endm + +/* Macro for IRQs (Hardware Interrupts) */ +.macro IRQ num, map_to +.global irq\num +irq\num: + cli + push $0 + push $\map_to + jmp isr_common_stub +.endm + +/* + * Define the first 32 ISRs (Exceptions) + * Some push error codes, some don't. + */ +ISR_NOERRCODE 0 /* Division by zero */ +ISR_NOERRCODE 1 /* Debug */ +ISR_NOERRCODE 2 /* NMI */ +ISR_NOERRCODE 3 /* Breakpoint */ +ISR_NOERRCODE 4 /* Overflow */ +ISR_NOERRCODE 5 /* Bound Range Exceeded */ +ISR_NOERRCODE 6 /* Invalid Opcode */ +ISR_NOERRCODE 7 /* Device Not Available */ +ISR_ERRCODE 8 /* Double Fault */ +ISR_NOERRCODE 9 /* Coprocessor Segment Overrun */ +ISR_ERRCODE 10 /* Invalid TSS */ +ISR_ERRCODE 11 /* Segment Not Present */ +ISR_ERRCODE 12 /* Stack-Segment Fault */ +ISR_ERRCODE 13 /* General Protection Fault */ +ISR_ERRCODE 14 /* Page Fault */ +ISR_NOERRCODE 15 /* Reserved */ +ISR_NOERRCODE 16 /* x87 Floating-Point Exception */ +ISR_ERRCODE 17 /* Alignment Check */ +ISR_NOERRCODE 18 /* Machine Check */ +ISR_NOERRCODE 19 /* SIMD Floating-Point Exception */ +ISR_NOERRCODE 20 /* Virtualization Exception */ +ISR_NOERRCODE 21 +ISR_NOERRCODE 22 +ISR_NOERRCODE 23 +ISR_NOERRCODE 24 +ISR_NOERRCODE 25 +ISR_NOERRCODE 26 +ISR_NOERRCODE 27 +ISR_NOERRCODE 28 +ISR_NOERRCODE 29 +ISR_ERRCODE 30 /* Security Exception */ +ISR_NOERRCODE 31 + +/* + * IRQs (Mapped to 32-47) + * IRQ 0 (Timer) -> Int 32 + * IRQ 1 (Keyboard) -> Int 33 + */ +IRQ 0, 32 +IRQ 1, 33 +IRQ 2, 34 +IRQ 3, 35 +IRQ 4, 36 +IRQ 5, 37 +IRQ 6, 38 +IRQ 7, 39 +IRQ 8, 40 +IRQ 9, 41 +IRQ 10, 42 +IRQ 11, 43 +IRQ 12, 44 +IRQ 13, 45 +IRQ 14, 46 +IRQ 15, 47 diff --git a/src/arch/x86/linker.ld b/src/arch/x86/linker.ld new file mode 100644 index 0000000..1a04ae9 --- /dev/null +++ b/src/arch/x86/linker.ld @@ -0,0 +1,54 @@ +/* + * AdrOS - x86 Linker Script (Higher Half) + */ + +ENTRY(_start) + +/* The bootloader loads us at 1MB physical */ +PHYSICAL_BASE = 0x00100000; + +/* But we want to run at 3GB + 1MB virtual */ +KERNEL_VIRT_BASE = 0xC0000000; + +SECTIONS +{ + /* + * We start at 1MB physical. + * The symbol '.' tracks the VIRTUAL address. + * We offset it to 3GB + 1MB. + */ + . = KERNEL_VIRT_BASE + PHYSICAL_BASE; + + _start = .; + + /* + * The AT(...) keyword tells the linker: + * "Put this section at PHYSICAL address X in the file, + * but calculate symbols as if it were at VIRTUAL address Y" + */ + + .text : AT(ADDR(.text) - KERNEL_VIRT_BASE) + { + /* Multiboot header must be very early physically */ + KEEP(*(.multiboot_header)) + *(.text) + } + + .rodata : AT(ADDR(.rodata) - KERNEL_VIRT_BASE) + { + *(.rodata) + } + + .data : AT(ADDR(.data) - KERNEL_VIRT_BASE) + { + *(.data) + } + + .bss : AT(ADDR(.bss) - KERNEL_VIRT_BASE) + { + *(.bss) + *(COMMON) + } + + _end = .; +} diff --git a/src/arch/x86/multiboot_header.S b/src/arch/x86/multiboot_header.S new file mode 100644 index 0000000..b83c50f --- /dev/null +++ b/src/arch/x86/multiboot_header.S @@ -0,0 +1 @@ +/* Merged into boot.S */ \ No newline at end of file diff --git a/src/arch/x86/process.S b/src/arch/x86/process.S new file mode 100644 index 0000000..51c62b7 --- /dev/null +++ b/src/arch/x86/process.S @@ -0,0 +1,34 @@ +.section .text +.global context_switch + +/* + * void context_switch(uint32_t* old_esp_ptr, uint32_t new_esp); + */ +context_switch: + /* 1. Save state of the OLD process */ + /* Pushing registers that C calling convention expects us to preserve */ + push %ebp + push %ebx + push %esi + push %edi + + /* 2. Save the old ESP */ + /* Get the location of old_esp_ptr. + Stack is: [RetAddr] [old_esp_ptr] [new_esp] + ESP points to EDI (last push) */ + + mov 20(%esp), %eax /* EAX = old_esp_ptr */ + mov %esp, (%eax) /* *old_esp_ptr = ESP */ + + /* 3. Switch to the NEW process stack */ + mov 24(%esp), %esp /* ESP = new_esp */ + + /* 4. Restore state of the NEW process */ + pop %edi + pop %esi + pop %ebx + pop %ebp + + /* 5. Return */ + /* Since we changed ESP, this 'ret' pops the EIP from the NEW stack! */ + ret diff --git a/src/arch/x86/vmm.c b/src/arch/x86/vmm.c new file mode 100644 index 0000000..fca2acb --- /dev/null +++ b/src/arch/x86/vmm.c @@ -0,0 +1,82 @@ +#include "vmm.h" +#include "pmm.h" +#include "uart_console.h" +#include + +/* Constants */ +#define KERNEL_VIRT_BASE 0xC0000000 +#define PAGE_SIZE 4096 + +/* Macros for address translation */ +#define V2P(x) ((uintptr_t)(x) - KERNEL_VIRT_BASE) +#define P2V(x) ((uintptr_t)(x) + KERNEL_VIRT_BASE) + +/* x86 Paging Flags */ +#define X86_PTE_PRESENT 0x1 +#define X86_PTE_RW 0x2 +#define X86_PTE_USER 0x4 + +/* Defined in boot.S (Physical address loaded in CR3, but accessed via virt alias) */ +/* Wait, boot_pd is in BSS. Linker put it at 0xC0xxxxxx. + So accessing boot_pd directly works fine! */ +extern uint32_t boot_pd[1024]; + +static inline void invlpg(uintptr_t vaddr) { + __asm__ volatile("invlpg (%0)" : : "r" (vaddr) : "memory"); +} + +void vmm_map_page(uint64_t phys, uint64_t virt, uint32_t flags) { + uint32_t pd_index = virt >> 22; + uint32_t pt_index = (virt >> 12) & 0x03FF; + + // Check if Page Table exists + if (!(boot_pd[pd_index] & X86_PTE_PRESENT)) { + // Allocate a new PT + uint32_t pt_phys = (uint32_t)pmm_alloc_page(); + if (!pt_phys) return; // OOM + + // ACCESS SAFETY: Convert Physical to Virtual to write to it + uint32_t* pt_virt = (uint32_t*)P2V(pt_phys); + + // Clear table + for(int i=0; i<1024; i++) pt_virt[i] = 0; + + // Add to Directory + boot_pd[pd_index] = pt_phys | X86_PTE_PRESENT | X86_PTE_RW; + } + + // Get table address from Directory + uint32_t pt_phys = boot_pd[pd_index] & 0xFFFFF000; + + // ACCESS SAFETY: Convert to Virtual + uint32_t* pt = (uint32_t*)P2V(pt_phys); + + uint32_t x86_flags = 0; + if (flags & VMM_FLAG_PRESENT) x86_flags |= X86_PTE_PRESENT; + if (flags & VMM_FLAG_RW) x86_flags |= X86_PTE_RW; + if (flags & VMM_FLAG_USER) x86_flags |= X86_PTE_USER; + + pt[pt_index] = ((uint32_t)phys) | x86_flags; + invlpg(virt); +} + +void vmm_unmap_page(uint64_t virt) { + uint32_t pd_index = virt >> 22; + uint32_t pt_index = (virt >> 12) & 0x03FF; + + if (boot_pd[pd_index] & X86_PTE_PRESENT) { + uint32_t pt_phys = boot_pd[pd_index] & 0xFFFFF000; + uint32_t* pt = (uint32_t*)P2V(pt_phys); + + pt[pt_index] = 0; + invlpg(virt); + } +} + +void vmm_init(void) { + uart_print("[VMM] Higher Half Kernel Active.\n"); + + // Test mapping + vmm_map_page(0xB8000, 0xC00B8000, VMM_FLAG_PRESENT | VMM_FLAG_RW); + uart_print("[VMM] Mapped VGA to 0xC00B8000.\n"); +} diff --git a/src/drivers/uart_console.c b/src/drivers/uart_console.c new file mode 100644 index 0000000..bc0146a --- /dev/null +++ b/src/drivers/uart_console.c @@ -0,0 +1,72 @@ +#include +#include "uart_console.h" +#include "io.h" + +/* + * Hardware Constants + * These should ideally be in a platform-specific config or Device Tree. + * For now, we hardcode for QEMU 'virt' and x86 standard PC. + */ + +#if defined(__i386__) || defined(__x86_64__) + #define UART_IS_PORT_IO 1 + #define UART_BASE 0x3F8 // COM1 +#elif defined(__aarch64__) + #define UART_IS_PORT_IO 0 + #define UART_BASE 0x09000000 // PL011 on QEMU virt +#elif defined(__riscv) + #define UART_IS_PORT_IO 0 + #define UART_BASE 0x10000000 // NS16550A on QEMU virt +#else + #define UART_IS_PORT_IO 0 + #define UART_BASE 0 // Unknown +#endif + +void uart_init(void) { +#if defined(__i386__) || defined(__x86_64__) + outb(UART_BASE + 1, 0x00); // Disable all interrupts + outb(UART_BASE + 3, 0x80); // Enable DLAB (set baud rate divisor) + outb(UART_BASE + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud + outb(UART_BASE + 1, 0x00); // (hi byte) + outb(UART_BASE + 3, 0x03); // 8 bits, no parity, one stop bit + outb(UART_BASE + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold + outb(UART_BASE + 4, 0x0B); // IRQs enabled, RTS/DSR set +#elif defined(__riscv) + // NS16550A setup (minimal) + mmio_write8(UART_BASE + 3, 0x03); // LCR: 8-bit, no parity + mmio_write8(UART_BASE + 2, 0x01); // FCR: Enable FIFO + mmio_write8(UART_BASE + 1, 0x01); // IER: Enable TX interrupts (optional) +#elif defined(__aarch64__) + // PL011 setup (simplified, assumes firmware initialized clocks) + // Actually PL011 is different register map than 16550. + // Base registers are 32-bit aligned usually. + // For now, relies on QEMU default state or minimal write. + // (Real implementation requires full PL011 driver logic) +#endif +} + +void uart_put_char(char c) { +#if defined(__aarch64__) + // PL011 Register map + // DR (Data Register) is at offset 0x00 + // FR (Flag Register) is at offset 0x18. Bit 5 is TXFF (Transmit FIFO Full). + volatile uint32_t* uart = (volatile uint32_t*)UART_BASE; + while (uart[6] & (1 << 5)) { ; } // Wait while TXFF is set + uart[0] = c; +#else + // 16550 / x86 logic + if (UART_IS_PORT_IO) { + while ((inb(UART_BASE + 5) & 0x20) == 0); // Wait for empty transmit holding register + outb(UART_BASE, c); + } else { + while ((mmio_read8(UART_BASE + 5) & 0x20) == 0); + mmio_write8(UART_BASE, c); + } +#endif +} + +void uart_print(const char* str) { + for (int i = 0; str[i] != '\0'; i++) { + uart_put_char(str[i]); + } +} diff --git a/src/drivers/vga_console.c b/src/drivers/vga_console.c new file mode 100644 index 0000000..812dd4c --- /dev/null +++ b/src/drivers/vga_console.c @@ -0,0 +1,53 @@ +#include "vga_console.h" + +static volatile uint16_t* const VGA_BUFFER = (uint16_t*) 0xB8000; +static const int VGA_WIDTH = 80; +static const int VGA_HEIGHT = 25; + +static int term_col = 0; +static int term_row = 0; +static uint8_t term_color = 0x0F; // White on Black + +void vga_init(void) { + term_col = 0; + term_row = 0; + term_color = 0x07; // Light Grey on Black + + for (int y = 0; y < VGA_HEIGHT; y++) { + for (int x = 0; x < VGA_WIDTH; x++) { + const int index = y * VGA_WIDTH + x; + VGA_BUFFER[index] = (uint16_t) ' ' | (uint16_t) term_color << 8; + } + } +} + +void vga_set_color(uint8_t fg, uint8_t bg) { + term_color = fg | (bg << 4); +} + +void vga_put_char(char c) { + if (c == '\n') { + term_col = 0; + term_row++; + } else { + const int index = term_row * VGA_WIDTH + term_col; + VGA_BUFFER[index] = (uint16_t) c | (uint16_t) term_color << 8; + term_col++; + } + + if (term_col >= VGA_WIDTH) { + term_col = 0; + term_row++; + } + + if (term_row >= VGA_HEIGHT) { + // TODO: Implement scrolling + term_row = 0; + } +} + +void vga_print(const char* str) { + for (int i = 0; str[i] != '\0'; i++) { + vga_put_char(str[i]); + } +} diff --git a/src/kernel/main.c b/src/kernel/main.c new file mode 100644 index 0000000..98546c3 --- /dev/null +++ b/src/kernel/main.c @@ -0,0 +1,107 @@ +#include +#include "vga_console.h" +#include "uart_console.h" +#include "pmm.h" +#include "vmm.h" +#include "idt.h" +#include "io.h" +#include "process.h" + +/* Check if the compiler thinks we are targeting the wrong operating system. */ +#if defined(__linux__) +#error "You are not using a cross-compiler, you will run into trouble" +#endif + +// Simple Keyboard Handler Stub +void keyboard_handler(struct registers* regs) { + (void)regs; + // Read scan code from port 0x60 + uint8_t scancode = inb(0x60); + + // If top bit is set, it's a key release. Ignore. + if (!(scancode & 0x80)) { + uart_print("[KEY] Pressed!\n"); + } +} + +void task_a(void) { + uart_print("Task A Started!\n"); + for(;;) { + uart_print("A"); + for(volatile int i=0; i<5000000; i++); + schedule(); + } +} + +void task_b(void) { + uart_print("Task B Started!\n"); + for(;;) { + uart_print("B"); + for(volatile int i=0; i<5000000; i++); + schedule(); + } +} + +/* + * Kernel Entry Point + * Arguments are passed from boot.S (architecture specific) + */ +void kernel_main(unsigned long magic, unsigned long addr) { + + // 1. Initialize Console (UART works everywhere) + uart_init(); + uart_print("\n[AdrOS] Booting...\n"); + +#if defined(__i386__) || defined(__x86_64__) + vga_init(); + vga_set_color(0x0A, 0x00); + vga_print("[AdrOS] Kernel Initialized (VGA).\n"); + + // Check Multiboot2 Magic + if (magic != 0x36d76289) { + uart_print("[ERR] Invalid Multiboot2 Magic!\n"); + } else { + uart_print("[OK] Multiboot2 Magic Confirmed.\n"); + } +#endif + + uart_print("[AdrOS] Initializing PMM...\n"); + + // 2. Initialize Physical Memory Manager + pmm_init((void*)addr); + + // 3. Initialize Virtual Memory Manager + uart_print("[AdrOS] Initializing VMM...\n"); +#if defined(__i386__) + vmm_init(); + + // 4. Initialize Interrupts (x86) + uart_print("[AdrOS] Initializing IDT...\n"); + idt_init(); + + register_interrupt_handler(33, keyboard_handler); + + // 5. Initialize Multitasking + uart_print("[AdrOS] Initializing Scheduler...\n"); + process_init(); + + process_create_kernel(task_a); + process_create_kernel(task_b); + +#else + uart_print("[WARN] VMM/IDT/Sched not implemented for this architecture yet.\n"); +#endif + + uart_print("Welcome to AdrOS (x86/ARM/RISC-V/MIPS)!\n"); + uart_print("System Halted. Starting Multitasking...\n"); + + // Infinite loop acting as Idle Task + for(;;) { + // uart_print("."); + schedule(); + + #if defined(__i386__) || defined(__x86_64__) + __asm__("hlt"); + #endif + } +} diff --git a/src/kernel/scheduler.c b/src/kernel/scheduler.c new file mode 100644 index 0000000..0c3b9e4 --- /dev/null +++ b/src/kernel/scheduler.c @@ -0,0 +1,117 @@ +#include "process.h" +#include "pmm.h" +#include "vmm.h" +#include "uart_console.h" +#include + +struct process* current_process = NULL; +struct process* ready_queue_head = NULL; +struct process* ready_queue_tail = NULL; +static uint32_t next_pid = 1; + +void process_init(void) { + uart_print("[SCHED] Initializing Multitasking...\n"); + + // Initial Kernel Thread (PID 0) + struct process* kernel_proc = (struct process*)pmm_alloc_page(); + + kernel_proc->pid = 0; + kernel_proc->state = PROCESS_RUNNING; + __asm__ volatile("mov %%cr3, %0" : "=r"(kernel_proc->cr3)); + + current_process = kernel_proc; + ready_queue_head = kernel_proc; + ready_queue_tail = kernel_proc; + kernel_proc->next = kernel_proc; +} + +/* + * Wrapper to start a new thread safely. + * Since new threads don't return from 'context_switch', + * they miss the 'sti' instruction there. We must enable it here. + */ +void thread_wrapper(void (*fn)(void)) { + __asm__ volatile("sti"); // Enable interrupts for the new thread! + fn(); // Run the task + + // If task returns, kill it (loop forever for now) + uart_print("[SCHED] Thread exited.\n"); + for(;;) __asm__("hlt"); +} + +struct process* process_create_kernel(void (*entry_point)(void)) { + struct process* proc = (struct process*)pmm_alloc_page(); + if (!proc) return NULL; + + proc->pid = next_pid++; + proc->state = PROCESS_READY; + proc->cr3 = current_process->cr3; + + // Allocate Kernel Stack + void* stack_phys = pmm_alloc_page(); + + // Assumption: We have identity map OR P2V works for this range. + // For robustness in Higher Half, convert phys to virt if needed. + // Since we map 0xC0000000 -> 0x0, adding 0xC0000000 gives us the virt address. + uint32_t stack_virt = (uint32_t)stack_phys + 0xC0000000; + + proc->kernel_stack = (uint32_t*)stack_virt; + + // Top of stack + uint32_t* sp = (uint32_t*)((uint8_t*)stack_virt + 4096); + + /* + * Forge the stack for context_switch + * We want it to "return" to thread_wrapper, with entry_point as arg. + * Stack Layout: [EIP] [Arg for Wrapper] + */ + + // Push Argument for thread_wrapper + *--sp = (uint32_t)entry_point; + + // Push Return Address (EIP) - Where context_switch jumps to + *--sp = (uint32_t)thread_wrapper; + + // Push Registers expected by context_switch (EBP, EBX, ESI, EDI) + *--sp = 0; // EBP + *--sp = 0; // EBX + *--sp = 0; // ESI + *--sp = 0; // EDI + + proc->esp = (uint32_t)sp; + + // Add to queue + proc->next = ready_queue_head; + ready_queue_tail->next = proc; + ready_queue_tail = proc; + + return proc; +} + +void schedule(void) { + // Critical Section: Disable Interrupts + __asm__ volatile("cli"); + + if (!current_process) { + __asm__ volatile("sti"); + return; + } + + struct process* prev = current_process; + struct process* next = current_process->next; + + if (prev == next) { + __asm__ volatile("sti"); + return; + } + + current_process = next; + current_process->state = PROCESS_RUNNING; + + // Switch! + context_switch(&prev->esp, current_process->esp); + + // We are back! (Task resumed) + // Re-enable interrupts + __asm__ volatile("sti"); +} diff --git a/src/mm/pmm.c b/src/mm/pmm.c new file mode 100644 index 0000000..a9f3a81 --- /dev/null +++ b/src/mm/pmm.c @@ -0,0 +1,154 @@ +#include "pmm.h" +#include "multiboot2.h" +#include "uart_console.h" +#include + +// Defined in linker script +extern uint8_t _start; +extern uint8_t _end; + +// Simple bitmap allocator +// Supports up to 512MB RAM for now to keep bitmap small (16KB) +// 512MB / 4KB pages = 131072 pages +// 131072 bits / 8 = 16384 bytes +#define MAX_RAM_SIZE (512 * 1024 * 1024) +#define BITMAP_SIZE (MAX_RAM_SIZE / PAGE_SIZE / 8) + +static uint8_t memory_bitmap[BITMAP_SIZE]; +static uint64_t total_memory = 0; +static uint64_t used_memory = 0; +static uint64_t max_frames = 0; + +static void bitmap_set(uint64_t bit) { + memory_bitmap[bit / 8] |= (1 << (bit % 8)); +} + +static void bitmap_unset(uint64_t bit) { + memory_bitmap[bit / 8] &= ~(1 << (bit % 8)); +} + +static int bitmap_test(uint64_t bit) { + return memory_bitmap[bit / 8] & (1 << (bit % 8)); +} + +// Mark a range of physical memory as used (1) or free (0) +static void pmm_mark_region(uint64_t base, uint64_t size, int used) { + uint64_t start_frame = base / PAGE_SIZE; + uint64_t frames_count = size / PAGE_SIZE; + + for (uint64_t i = 0; i < frames_count; i++) { + if (start_frame + i >= max_frames) break; + + if (used) { + bitmap_set(start_frame + i); + used_memory += PAGE_SIZE; + } else { + bitmap_unset(start_frame + i); + used_memory -= PAGE_SIZE; + } + } +} + +void pmm_init(void* boot_info) { + // 1. Mark EVERYTHING as used initially to be safe + for (int i = 0; i < BITMAP_SIZE; i++) { + memory_bitmap[i] = 0xFF; + } + +#if defined(__i386__) || defined(__x86_64__) + // Parse Multiboot2 Info + struct multiboot_tag *tag; + uint32_t mbi_size = *(uint32_t *)boot_info; + + uart_print("[PMM] Parsing Multiboot2 info...\n"); + + for (tag = (struct multiboot_tag *)((uint8_t *)boot_info + 8); + tag->type != MULTIBOOT_TAG_TYPE_END; + tag = (struct multiboot_tag *)((uint8_t *)tag + ((tag->size + 7) & ~7))) + { + if (tag->type == MULTIBOOT_TAG_TYPE_BASIC_MEMINFO) { + struct multiboot_tag_basic_meminfo *meminfo = (struct multiboot_tag_basic_meminfo *)tag; + // Basic info just gives lower/upper KB + uint64_t mem_kb = meminfo->mem_upper; // Upper memory only + total_memory = (mem_kb * 1024) + (1024*1024); // +1MB low mem + } + else if (tag->type == MULTIBOOT_TAG_TYPE_MMAP) { + struct multiboot_tag_mmap *mmap = (struct multiboot_tag_mmap *)tag; + struct multiboot_mmap_entry *entry; + + for (entry = mmap->entries; + (uint8_t *)entry < (uint8_t *)mmap + mmap->size; + entry = (struct multiboot_mmap_entry *)((uint32_t)entry + mmap->entry_size)) + { + // Only mark AVAILABLE regions as free + if (entry->type == MULTIBOOT_MEMORY_AVAILABLE) { + pmm_mark_region(entry->addr, entry->len, 0); // 0 = Free + } + } + } + } +#else + // Manual setup for ARM/RISC-V (assuming fixed RAM for now) + // TODO: Parse Device Tree (DTB) + uart_print("[PMM] Manual memory config (ARM/RISC-V)\n"); + + uint64_t ram_base = 0; + #ifdef __aarch64__ + ram_base = 0x40000000; + #elif defined(__riscv) + ram_base = 0x80000000; + #endif + + uint64_t ram_size = 128 * 1024 * 1024; // Assume 128MB + max_frames = ram_size / PAGE_SIZE; + + // Mark all RAM as free + pmm_mark_region(ram_base, ram_size, 0); + +#endif + + // 2. Protect Kernel Memory (Critical!) + // We must ensure the kernel code itself is marked USED + + // Check if we are Higher Half (start > 3GB) + uint64_t virt_start = (uint64_t)&_start; + uint64_t virt_end = (uint64_t)&_end; + + uint64_t phys_start = virt_start; + uint64_t phys_end = virt_end; + + if (virt_start >= 0xC0000000) { + phys_start -= 0xC0000000; + phys_end -= 0xC0000000; + uart_print("[PMM] Detected Higher Half Kernel. Adjusting protection range.\n"); + } + + uint64_t kernel_size = phys_end - phys_start; + + pmm_mark_region(phys_start, kernel_size, 1); // Mark Used + + // 3. Protect Multiboot info (if x86) + if (boot_info) { + pmm_mark_region((uint64_t)boot_info, 4096, 1); // Protect at least 1 page + } + + uart_print("[PMM] Initialized.\n"); +} + +void* pmm_alloc_page(void) { + for (uint64_t i = 0; i < max_frames; i++) { + if (!bitmap_test(i)) { + bitmap_set(i); + used_memory += PAGE_SIZE; + return (void*)(i * PAGE_SIZE); + } + } + return NULL; // OOM +} + +void pmm_free_page(void* ptr) { + uint64_t addr = (uint64_t)ptr; + uint64_t frame = addr / PAGE_SIZE; + bitmap_unset(frame); + used_memory -= PAGE_SIZE; +} -- 2.43.0