]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
Initial commit: AdrOS v0.1 (Multi-arch Kernel, PMM, VMM, Scheduler)
authortadryanom_bot <[email protected]>
Tue, 3 Feb 2026 01:17:57 +0000 (01:17 +0000)
committertadryanom_bot <[email protected]>
Tue, 3 Feb 2026 01:17:57 +0000 (01:17 +0000)
27 files changed:
BUILD_GUIDE.md [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.md [new file with mode: 0644]
include/idt.h [new file with mode: 0644]
include/io.h [new file with mode: 0644]
include/multiboot2.h [new file with mode: 0644]
include/pmm.h [new file with mode: 0644]
include/process.h [new file with mode: 0644]
include/uart_console.h [new file with mode: 0644]
include/vga_console.h [new file with mode: 0644]
include/vmm.h [new file with mode: 0644]
src/arch/arm/boot.S [new file with mode: 0644]
src/arch/arm/linker.ld [new file with mode: 0644]
src/arch/riscv/boot.S [new file with mode: 0644]
src/arch/riscv/linker.ld [new file with mode: 0644]
src/arch/x86/boot.S [new file with mode: 0644]
src/arch/x86/idt.c [new file with mode: 0644]
src/arch/x86/interrupts.S [new file with mode: 0644]
src/arch/x86/linker.ld [new file with mode: 0644]
src/arch/x86/multiboot_header.S [new file with mode: 0644]
src/arch/x86/process.S [new file with mode: 0644]
src/arch/x86/vmm.c [new file with mode: 0644]
src/drivers/uart_console.c [new file with mode: 0644]
src/drivers/vga_console.c [new file with mode: 0644]
src/kernel/main.c [new file with mode: 0644]
src/kernel/scheduler.c [new file with mode: 0644]
src/mm/pmm.c [new file with mode: 0644]

diff --git a/BUILD_GUIDE.md b/BUILD_GUIDE.md
new file mode 100644 (file)
index 0000000..8b069ca
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
index 0000000..3ae501d
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef IDT_H
+#define IDT_H
+
+#include <stdint.h>
+
+/* 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 (file)
index 0000000..f44840e
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef IO_H
+#define IO_H
+
+#include <stdint.h>
+
+/* 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 (file)
index 0000000..57148f5
--- /dev/null
@@ -0,0 +1,72 @@
+#ifndef MULTIBOOT2_H
+#define MULTIBOOT2_H
+
+#include <stdint.h>
+
+/*  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 (file)
index 0000000..83ba57d
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef PMM_H
+#define PMM_H
+
+#include <stdint.h>
+
+#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 (file)
index 0000000..c656d1c
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef PROCESS_H
+#define PROCESS_H
+
+#include <stdint.h>
+#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 (file)
index 0000000..cc22ac7
--- /dev/null
@@ -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 (file)
index 0000000..296bbcc
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef VGA_CONSOLE_H
+#define VGA_CONSOLE_H
+
+#include <stdint.h>
+
+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 (file)
index 0000000..cd97b9e
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef VMM_H
+#define VMM_H
+
+#include <stdint.h>
+
+/* 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 (file)
index 0000000..8c66e36
--- /dev/null
@@ -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 (file)
index 0000000..7042ae1
--- /dev/null
@@ -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 (file)
index 0000000..d29dff8
--- /dev/null
@@ -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 (file)
index 0000000..df0e989
--- /dev/null
@@ -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 (file)
index 0000000..c1c48a9
--- /dev/null
@@ -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 (file)
index 0000000..7217ae2
--- /dev/null
@@ -0,0 +1,177 @@
+#include "idt.h"
+#include "io.h"
+#include "uart_console.h"
+#include <stddef.h>
+
+#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; i<IDT_ENTRIES; i++) {
+        // Default to 0, non-present
+        idt[i].base_lo = 0;
+        idt[i].sel = 0;
+        idt[i].always0 = 0;
+        idt[i].flags = 0;
+        interrupt_handlers[i] = NULL;
+    }
+
+    // Remap PIC
+    pic_remap();
+
+    // Install Exception Handlers (0-31)
+    // 0x08 is our Kernel Code Segment
+    idt_set_gate(0, (uint32_t)isr0, 0x08, 0x8E);
+    idt_set_gate(1, (uint32_t)isr1, 0x08, 0x8E);
+    idt_set_gate(2, (uint32_t)isr2, 0x08, 0x8E);
+    idt_set_gate(3, (uint32_t)isr3, 0x08, 0x8E);
+    idt_set_gate(4, (uint32_t)isr4, 0x08, 0x8E);
+    idt_set_gate(5, (uint32_t)isr5, 0x08, 0x8E);
+    idt_set_gate(6, (uint32_t)isr6, 0x08, 0x8E);
+    idt_set_gate(7, (uint32_t)isr7, 0x08, 0x8E);
+    idt_set_gate(8, (uint32_t)isr8, 0x08, 0x8E);
+    idt_set_gate(9, (uint32_t)isr9, 0x08, 0x8E);
+    idt_set_gate(10, (uint32_t)isr10, 0x08, 0x8E);
+    idt_set_gate(11, (uint32_t)isr11, 0x08, 0x8E);
+    idt_set_gate(12, (uint32_t)isr12, 0x08, 0x8E);
+    idt_set_gate(13, (uint32_t)isr13, 0x08, 0x8E);
+    idt_set_gate(14, (uint32_t)isr14, 0x08, 0x8E); // Page Fault!
+    idt_set_gate(15, (uint32_t)isr15, 0x08, 0x8E);
+    idt_set_gate(16, (uint32_t)isr16, 0x08, 0x8E);
+    idt_set_gate(17, (uint32_t)isr17, 0x08, 0x8E);
+    idt_set_gate(18, (uint32_t)isr18, 0x08, 0x8E);
+    idt_set_gate(19, (uint32_t)isr19, 0x08, 0x8E);
+    idt_set_gate(20, (uint32_t)isr20, 0x08, 0x8E);
+    idt_set_gate(21, (uint32_t)isr21, 0x08, 0x8E);
+    idt_set_gate(22, (uint32_t)isr22, 0x08, 0x8E);
+    idt_set_gate(23, (uint32_t)isr23, 0x08, 0x8E);
+    idt_set_gate(24, (uint32_t)isr24, 0x08, 0x8E);
+    idt_set_gate(25, (uint32_t)isr25, 0x08, 0x8E);
+    idt_set_gate(26, (uint32_t)isr26, 0x08, 0x8E);
+    idt_set_gate(27, (uint32_t)isr27, 0x08, 0x8E);
+    idt_set_gate(28, (uint32_t)isr28, 0x08, 0x8E);
+    idt_set_gate(29, (uint32_t)isr29, 0x08, 0x8E);
+    idt_set_gate(30, (uint32_t)isr30, 0x08, 0x8E);
+    idt_set_gate(31, (uint32_t)isr31, 0x08, 0x8E);
+
+    // Install IRQ Handlers (32-47)
+    idt_set_gate(32, (uint32_t)irq0, 0x08, 0x8E); // Timer
+    idt_set_gate(33, (uint32_t)irq1, 0x08, 0x8E); // Keyboard
+    idt_set_gate(34, (uint32_t)irq2, 0x08, 0x8E);
+    idt_set_gate(35, (uint32_t)irq3, 0x08, 0x8E);
+    idt_set_gate(36, (uint32_t)irq4, 0x08, 0x8E);
+    idt_set_gate(37, (uint32_t)irq5, 0x08, 0x8E);
+    idt_set_gate(38, (uint32_t)irq6, 0x08, 0x8E);
+    idt_set_gate(39, (uint32_t)irq7, 0x08, 0x8E);
+    idt_set_gate(40, (uint32_t)irq8, 0x08, 0x8E);
+    idt_set_gate(41, (uint32_t)irq9, 0x08, 0x8E);
+    idt_set_gate(42, (uint32_t)irq10, 0x08, 0x8E);
+    idt_set_gate(43, (uint32_t)irq11, 0x08, 0x8E);
+    idt_set_gate(44, (uint32_t)irq12, 0x08, 0x8E);
+    idt_set_gate(45, (uint32_t)irq13, 0x08, 0x8E);
+    idt_set_gate(46, (uint32_t)irq14, 0x08, 0x8E);
+    idt_set_gate(47, (uint32_t)irq15, 0x08, 0x8E);
+
+    // Load IDT
+    __asm__ volatile("lidt %0" : : "m"(idtp));
+    
+    // Enable interrupts!
+    __asm__ volatile("sti");
+    
+    uart_print("[IDT] Interrupts Enabled!\n");
+}
+
+void register_interrupt_handler(uint8_t n, isr_handler_t handler) {
+    interrupt_handlers[n] = handler;
+}
+
+// The Main Handler called by assembly
+void isr_handler(struct registers* regs) {
+    // Check if we have a custom handler
+    if (interrupt_handlers[regs->int_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 (file)
index 0000000..7f99981
--- /dev/null
@@ -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 (file)
index 0000000..1a04ae9
--- /dev/null
@@ -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 (file)
index 0000000..b83c50f
--- /dev/null
@@ -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 (file)
index 0000000..51c62b7
--- /dev/null
@@ -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 (file)
index 0000000..fca2acb
--- /dev/null
@@ -0,0 +1,82 @@
+#include "vmm.h"
+#include "pmm.h"
+#include "uart_console.h"
+#include <stddef.h>
+
+/* 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 (file)
index 0000000..bc0146a
--- /dev/null
@@ -0,0 +1,72 @@
+#include <stdint.h>
+#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 (file)
index 0000000..812dd4c
--- /dev/null
@@ -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 (file)
index 0000000..98546c3
--- /dev/null
@@ -0,0 +1,107 @@
+#include <stdint.h>
+#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 (file)
index 0000000..0c3b9e4
--- /dev/null
@@ -0,0 +1,117 @@
+#include "process.h"
+#include "pmm.h"
+#include "vmm.h"
+#include "uart_console.h"
+#include <stddef.h>
+
+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 (file)
index 0000000..a9f3a81
--- /dev/null
@@ -0,0 +1,154 @@
+#include "pmm.h"
+#include "multiboot2.h"
+#include "uart_console.h"
+#include <stddef.h>
+
+// 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;
+}