Compare commits
8 Commits
71e2ae482a
...
attempt-1
| Author | SHA1 | Date | |
|---|---|---|---|
| cd0e1cafae | |||
| f42d65267b | |||
| 6d9ab26423 | |||
| e8fa7e1f28 | |||
| 1cf39a70cf | |||
| bbd7d3725b | |||
| f3ef92be4f | |||
| 34e24c9979 |
1
.github/copilot-instructions.md
vendored
1
.github/copilot-instructions.md
vendored
@@ -1 +0,0 @@
|
|||||||
See `README.md` for a general overview on how to work and for a list of tasks to perform.
|
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,9 +1,3 @@
|
|||||||
build/
|
build/
|
||||||
release/
|
|
||||||
*.img
|
|
||||||
*.iso
|
*.iso
|
||||||
debug_grub/
|
*.img
|
||||||
*_output.txt
|
|
||||||
snippet.*
|
|
||||||
qemu.log
|
|
||||||
iso_output.txt
|
|
||||||
|
|||||||
@@ -1,40 +1,9 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
project(ClaudeOS C ASM)
|
project(ClaudeOS C ASM)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
# We are building a kernel, so we don't want standard libraries
|
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffreestanding -m32 -fno-pie -fno-pic -fno-builtin -fno-stack-protector -mno-sse -mno-mmx -g -O2 -Wall -Wextra")
|
|
||||||
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -m32 -fno-pie -fno-pic")
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -m32 -nostdlib -no-pie")
|
|
||||||
|
|
||||||
# Define build output directory
|
|
||||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
|
||||||
|
|
||||||
|
# Kernel
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
||||||
# Create output directories
|
|
||||||
file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/release)
|
|
||||||
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/isodir/boot/grub)
|
|
||||||
|
|
||||||
# Create grub.cfg for ISO
|
|
||||||
file(WRITE ${CMAKE_BINARY_DIR}/isodir/boot/grub/grub.cfg "set timeout=0\nset default=0\nsearch --set=root --file /boot/kernel.bin\nmenuentry \"ClaudeOS\" { multiboot2 /boot/kernel.bin }")
|
|
||||||
|
|
||||||
|
|
||||||
# ISO Generation
|
|
||||||
add_custom_target(iso ALL
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/kernel ${CMAKE_BINARY_DIR}/isodir/boot/kernel.bin
|
|
||||||
COMMAND grub-mkrescue -o ${CMAKE_SOURCE_DIR}/release/claude-os.iso ${CMAKE_BINARY_DIR}/isodir
|
|
||||||
DEPENDS kernel
|
|
||||||
COMMENT "Generating bootable ISO image"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test target
|
|
||||||
add_custom_target(test_images
|
|
||||||
COMMAND sh ${CMAKE_SOURCE_DIR}/test_images.sh
|
|
||||||
DEPENDS iso
|
|
||||||
COMMENT "Testing generated images in QEMU"
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
|
|||||||
7
PROMPT.md
Normal file
7
PROMPT.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Prompt
|
||||||
|
|
||||||
|
The following prompt was given to GitHub Copilot (Claude Sonnet 4.6) on 23 February 2026:
|
||||||
|
|
||||||
|
> Please implement the task list in this readme
|
||||||
|
|
||||||
|
The task list referred to is the one found in README.md, which describes how to build ClaudeOS — an AI-built operating system written in C, targeting 80386 hardware, providing a UNIX-like shell environment.
|
||||||
24
README.md
24
README.md
@@ -37,19 +37,17 @@ The git commit should describe what is done and why certain choices were made.
|
|||||||
The title of the commit should always end with (AI), to indicate that it was written by an AI.
|
The title of the commit should always end with (AI), to indicate that it was written by an AI.
|
||||||
Once a task is completed, it should be checked off.
|
Once a task is completed, it should be checked off.
|
||||||
|
|
||||||
- [x] Create directory structure
|
- [ ] Create directory structure
|
||||||
- [x] Create initial build system
|
- [ ] Create initial build system
|
||||||
- [x] Setup a simple kernel that writes `Hello, world` to Qemu debug port
|
- [ ] Setup a simple kernel that writes `Hello, world` to Qemu debug port
|
||||||
- [x] Update the build system to create both ISO and Floppy images. Verify these work using a test script. The standard CMake build target should automatically generate both images. (Only ISO supported for now)
|
- [ ] Update the build system to create both ISO and Floppy images. Verify these work using a test script.
|
||||||
- [x] Update the kernel to correctly setup the GDT
|
- [ ] Update the kernel to correctly setup the GDT
|
||||||
- [x] Create an interrupt handler.
|
- [ ] Create a physical memory allocator and mapper. The kernel should live in the upper last gigabyte of virtual memory. It should support different zones (e.g.: `SUB_16M`, `DEFAULT`, ...) These zones describe the region of memory that memory should be allocated in. If it is not possible to allocate in that region (because it is full, or has 0 capacity to begin with), it should fallback to another zone.
|
||||||
- [x] Implement a PIC handler
|
- [ ] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
|
||||||
- [x] Create a physical memory allocator and mapper. The kernel should live in the upper last gigabyte of virtual memory. It should support different zones (e.g.: `SUB_16M`, `DEFAULT`, ...) These zones describe the region of memory that memory should be allocated in. If it is not possible to allocate in that region (because it is full, or has 0 capacity to begin with), it should fallback to another zone.
|
- [ ] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them.
|
||||||
- [x] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
|
- [ ] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
|
||||||
- [x] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them.
|
- [ ] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
|
||||||
- [x] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
|
- [ ] Create subsystem for loading new processes in Ring 3.
|
||||||
- [x] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
|
|
||||||
- [x] Create subsystem for loading new processes in Ring 3.
|
|
||||||
- [ ] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
|
- [ ] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
|
||||||
- [ ] Write a VFS subsystem.
|
- [ ] Write a VFS subsystem.
|
||||||
- [ ] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer.
|
- [ ] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer.
|
||||||
|
|||||||
0
apps/.gitkeep
Normal file
0
apps/.gitkeep
Normal file
59
cmake/toolchain-i386-clang.cmake
Normal file
59
cmake/toolchain-i386-clang.cmake
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Cross-compilation toolchain for i386-elf using Clang + LLD.
|
||||||
|
#
|
||||||
|
# This targets a bare-metal i386 (80386) environment with no host OS
|
||||||
|
# libraries. lld is used as the linker because the macOS system ld does
|
||||||
|
# not support the ELF32 output format required for a GRUB-bootable kernel.
|
||||||
|
|
||||||
|
set(CMAKE_SYSTEM_NAME Generic)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR i386)
|
||||||
|
|
||||||
|
# Use Clang for both C and assembly.
|
||||||
|
set(CMAKE_C_COMPILER clang)
|
||||||
|
set(CMAKE_ASM_COMPILER clang)
|
||||||
|
|
||||||
|
# Prevent CMake from trying to run the cross-compiled test binaries.
|
||||||
|
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||||
|
|
||||||
|
# Target triple and architecture flags shared by all compilation steps.
|
||||||
|
set(CROSS_TARGET "i386-elf")
|
||||||
|
set(CROSS_ARCH_FLAGS "-target ${CROSS_TARGET} -m32 -march=i386 -mtune=i386")
|
||||||
|
|
||||||
|
# C compiler flags:
|
||||||
|
# - ffreestanding : no hosted C runtime assumptions
|
||||||
|
# - fno-stack-protector : no __stack_chk_fail dependency
|
||||||
|
# - fno-builtin : avoid implicit replacements of standard functions
|
||||||
|
set(CMAKE_C_FLAGS_INIT
|
||||||
|
"${CROSS_ARCH_FLAGS} -ffreestanding -fno-stack-protector -fno-builtin -Wall -Wextra"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assembler flags: only the flags the assembler front-end actually accepts.
|
||||||
|
# C-only flags like -ffreestanding, -fno-stack-protector, etc. must NOT be
|
||||||
|
# passed here — clang treats them as unused and errors out with -Werror.
|
||||||
|
# -Wno-unused-command-line-argument suppresses CMake's auto-added -MD/-MT/-MF
|
||||||
|
# dependency-tracking flags that clang ignores in pure assembler mode.
|
||||||
|
set(CMAKE_ASM_FLAGS_INIT
|
||||||
|
"-target ${CROSS_TARGET} -m32 -march=i386 -Wno-unused-command-line-argument"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Linking:
|
||||||
|
# On aarch64 Alpine the clang driver delegates link steps to the host gcc,
|
||||||
|
# which does not support -m32 and therefore fails. Bypass the driver entirely
|
||||||
|
# and call ld.lld directly. The CMake rule variables below replace the normal
|
||||||
|
# "compile-with-compiler-driver" link invocation.
|
||||||
|
#
|
||||||
|
# Rule variables use these placeholders:
|
||||||
|
# <TARGET> — output binary path
|
||||||
|
# <OBJECTS> — all compiled object files
|
||||||
|
# <LINK_FLAGS> — extra flags from target_link_options / CMAKE_EXE_LINKER_FLAGS
|
||||||
|
# <LINK_LIBRARIES> — libraries to link (empty for a freestanding kernel)
|
||||||
|
set(CMAKE_LINKER "ld.lld")
|
||||||
|
set(CMAKE_C_LINK_EXECUTABLE
|
||||||
|
"ld.lld -m elf_i386 <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
|
||||||
|
set(CMAKE_ASM_LINK_EXECUTABLE
|
||||||
|
"ld.lld -m elf_i386 <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
|
||||||
|
|
||||||
|
# Keep CMake from accidentally picking up host system headers/libraries.
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||||
0
docs/.gitkeep
Normal file
0
docs/.gitkeep
Normal file
@@ -1,62 +0,0 @@
|
|||||||
# Interrupt Subsystem
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The interrupt subsystem handles both CPU exceptions (faults, traps, aborts) and hardware interrupts (IRQs) from external devices. It consists of three cooperating components:
|
|
||||||
|
|
||||||
1. **IDT (Interrupt Descriptor Table)** — Maps interrupt vectors 0–255 to handler entry points.
|
|
||||||
2. **ISR (Interrupt Service Routines)** — Assembly stubs and a C dispatcher that routes interrupts.
|
|
||||||
3. **PIC (Programmable Interrupt Controller)** — Manages the 8259A PIC chips that multiplex hardware IRQs onto CPU interrupt lines.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Interrupt Vector Layout
|
|
||||||
|
|
||||||
| Vector Range | Purpose |
|
|
||||||
|---|---|
|
|
||||||
| 0–31 | CPU exceptions (Division by Zero, Page Fault, GPF, etc.) |
|
|
||||||
| 32–47 | Hardware IRQs (remapped from default 0–15) |
|
|
||||||
| 48–255 | Available for software interrupts / future use |
|
|
||||||
|
|
||||||
### Flow of an Interrupt
|
|
||||||
|
|
||||||
1. CPU or device raises an interrupt.
|
|
||||||
2. CPU looks up the vector in the IDT and jumps to the assembly stub.
|
|
||||||
3. The stub pushes a dummy error code (if the CPU didn't push one), the interrupt number, and all general-purpose registers onto the stack.
|
|
||||||
4. The stub loads the kernel data segment (0x10) and calls `isr_handler()` in C.
|
|
||||||
5. For hardware interrupts (vectors 32–47), `isr_handler` sends an End-of-Interrupt (EOI) to the PIC.
|
|
||||||
6. For CPU exceptions (vectors 0–31), the handler prints the exception name and halts.
|
|
||||||
7. On return, the stub restores all registers and executes `iret`.
|
|
||||||
|
|
||||||
### PIC Remapping
|
|
||||||
|
|
||||||
The 8259A PIC ships with IRQ 0–7 mapped to vectors 8–15, which collide with CPU exceptions. During initialization, we remap:
|
|
||||||
|
|
||||||
- **Master PIC** (IRQ 0–7) → vectors 32–39
|
|
||||||
- **Slave PIC** (IRQ 8–15) → vectors 40–47
|
|
||||||
|
|
||||||
The PIC is initialized in cascade mode with ICW4 (8086 mode). Original IRQ masks are saved and restored after remapping.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- `src/idt.c` / `src/idt.h` — IDT setup and gate registration.
|
|
||||||
- `src/interrupts.S` — Assembly stubs for ISRs 0–31 and IRQs 0–15.
|
|
||||||
- `src/isr.c` / `src/isr.h` — C interrupt dispatcher and `registers_t` structure.
|
|
||||||
- `src/pic.c` / `src/pic.h` — PIC initialization, EOI, mask/unmask.
|
|
||||||
- `src/port_io.h` — Inline `inb`, `outb`, `io_wait` helpers.
|
|
||||||
|
|
||||||
## Register Save Frame
|
|
||||||
|
|
||||||
When an interrupt fires, the following is pushed onto the stack (from high to low address):
|
|
||||||
|
|
||||||
```
|
|
||||||
ss, useresp (only on privilege change)
|
|
||||||
eflags
|
|
||||||
cs, eip (pushed by CPU)
|
|
||||||
err_code (pushed by CPU or stub as 0)
|
|
||||||
int_no (pushed by stub)
|
|
||||||
eax..edi (pushed by pusha)
|
|
||||||
ds (pushed by stub)
|
|
||||||
```
|
|
||||||
|
|
||||||
This matches the `registers_t` struct in `isr.h`.
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
# Kernel Memory Allocator (kmalloc)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The kernel memory allocator provides `kmalloc()` and `kfree()` for dynamic memory allocation within the kernel. It uses the paging subsystem to obtain physical memory on demand.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Free-List Allocator
|
|
||||||
|
|
||||||
The allocator maintains a singly-linked free list of available memory blocks, ordered by address. Each block carries an inline header:
|
|
||||||
|
|
||||||
```c
|
|
||||||
typedef struct block_header {
|
|
||||||
uint32_t size; // Usable bytes (excludes header)
|
|
||||||
uint32_t magic; // 0xCAFEBABE for integrity checking
|
|
||||||
struct block_header *next; // Next free block (free list only)
|
|
||||||
uint8_t is_free; // 1 = free, 0 = allocated
|
|
||||||
} block_header_t;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Allocation Strategy
|
|
||||||
|
|
||||||
1. **First-fit search:** Walk the free list for the first block with `size >= requested`.
|
|
||||||
2. **Block splitting:** If the found block is significantly larger than needed, it is split into the allocated portion and a new free block.
|
|
||||||
3. **Page expansion:** If no suitable block exists, a new 4 KiB page is obtained from `paging_alloc_page()`.
|
|
||||||
|
|
||||||
All allocations are 8-byte aligned. Minimum block size is 16 bytes to limit fragmentation.
|
|
||||||
|
|
||||||
### Deallocation
|
|
||||||
|
|
||||||
1. The block header is located by subtracting `sizeof(block_header_t)` from the user pointer.
|
|
||||||
2. The block's magic number is verified to detect corruption.
|
|
||||||
3. The block is inserted back into the free list in address order.
|
|
||||||
4. **Coalescing:** Adjacent free blocks are merged to reduce fragmentation.
|
|
||||||
|
|
||||||
### Integrity Checks
|
|
||||||
|
|
||||||
- A magic value (`0xCAFEBABE`) in each block header detects heap corruption.
|
|
||||||
- Double-free is detected by checking the `is_free` flag.
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
```c
|
|
||||||
void init_kmalloc(void);
|
|
||||||
void *kmalloc(size_t size);
|
|
||||||
void kfree(void *ptr);
|
|
||||||
void *kcalloc(size_t count, size_t size);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- `src/kmalloc.c` / `src/kmalloc.h` — Allocator implementation.
|
|
||||||
- `src/string.c` — Freestanding `memset`, `memcpy`, `strlen`, etc.
|
|
||||||
- `src/paging.c` — Provides physical page backing.
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
- Maximum single allocation is ~4080 bytes (one page minus header).
|
|
||||||
- No multi-page allocations for large objects.
|
|
||||||
- Free virtual addresses are not reused after `paging_free_page()`.
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# Paging Subsystem
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The paging subsystem manages virtual memory using the x86 two-level paging scheme (no PAE). It provides identity mapping for all physical memory and a kernel heap region for dynamic virtual page allocation.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Page Table Structure
|
|
||||||
|
|
||||||
x86 32-bit paging uses two levels:
|
|
||||||
|
|
||||||
| Level | Entries | Each Entry Maps | Total Coverage |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Page Directory | 1024 | 4 MiB (one page table) | 4 GiB |
|
|
||||||
| Page Table | 1024 | 4 KiB (one page) | 4 MiB |
|
|
||||||
|
|
||||||
Each entry is a 32-bit value containing a 20-bit physical page frame number and 12 bits of flags.
|
|
||||||
|
|
||||||
### Identity Mapping
|
|
||||||
|
|
||||||
During initialization, all detected physical memory is identity-mapped (virtual address = physical address). This is done in two phases:
|
|
||||||
|
|
||||||
1. **Static mapping (first 16 MiB):** Four page tables are statically allocated in BSS. This avoids a chicken-and-egg problem since the PMM bitmap itself resides in this region.
|
|
||||||
|
|
||||||
2. **Dynamic mapping (above 16 MiB):** Additional page tables are allocated from the PMM *before* paging is enabled (so physical addresses are still directly accessible). These cover all remaining detected physical memory.
|
|
||||||
|
|
||||||
### Kernel Heap
|
|
||||||
|
|
||||||
The kernel heap region occupies virtual addresses `0xD0000000` through `0xF0000000` (768 MiB).
|
|
||||||
|
|
||||||
When `paging_alloc_page()` is called:
|
|
||||||
1. A physical page is allocated from the PMM.
|
|
||||||
2. A page table entry is created mapping the next free virtual address to the physical page.
|
|
||||||
3. The virtual address is returned.
|
|
||||||
|
|
||||||
When `paging_free_page()` is called:
|
|
||||||
1. The physical address is looked up via the page table entry.
|
|
||||||
2. The virtual mapping is removed.
|
|
||||||
3. The physical page is returned to the PMM.
|
|
||||||
|
|
||||||
### TLB Management
|
|
||||||
|
|
||||||
- Single-page invalidations use `invlpg`.
|
|
||||||
- Full TLB flushes use CR3 reload.
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
```c
|
|
||||||
void init_paging(void);
|
|
||||||
void paging_map_page(uint32_t vaddr, uint32_t paddr, uint32_t flags);
|
|
||||||
void paging_unmap_page(uint32_t vaddr);
|
|
||||||
void *paging_alloc_page(void);
|
|
||||||
void paging_free_page(void *vaddr);
|
|
||||||
uint32_t paging_get_physical(uint32_t vaddr);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Flags
|
|
||||||
|
|
||||||
| Flag | Value | Meaning |
|
|
||||||
|---|---|---|
|
|
||||||
| `PAGE_PRESENT` | 0x001 | Page is present in memory |
|
|
||||||
| `PAGE_WRITE` | 0x002 | Page is writable |
|
|
||||||
| `PAGE_USER` | 0x004 | Page is user-accessible (ring 3) |
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- `src/paging.c` / `src/paging.h` — Implementation and API.
|
|
||||||
- `src/pmm.c` / `src/pmm.h` — Physical page allocation backing.
|
|
||||||
|
|
||||||
## Design Decisions
|
|
||||||
|
|
||||||
- **No higher-half kernel yet:** The kernel runs at its physical load address (1 MiB) with identity mapping. Higher-half mapping (0xC0000000) can be added later without changing the paging API.
|
|
||||||
- **Static + dynamic page tables:** The first 16 MiB uses BSS-allocated tables to bootstrap, while memory above 16 MiB uses PMM-allocated tables. This keeps BSS usage bounded at ~16 KiB regardless of total RAM.
|
|
||||||
- **Sequential heap allocation:** The heap grows upward linearly. No free-list reuse of freed virtual addresses is implemented yet.
|
|
||||||
63
docs/pmm.md
63
docs/pmm.md
@@ -1,63 +0,0 @@
|
|||||||
# Physical Memory Manager (PMM)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The PMM manages physical page frames using a bitmap allocator. It tracks which 4 KiB pages of physical RAM are free or in use, and supports allocating pages from different memory zones.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Bitmap Allocator
|
|
||||||
|
|
||||||
Each bit in a global bitmap corresponds to one 4 KiB physical page frame:
|
|
||||||
- **Bit = 1** → page is in use (allocated or reserved)
|
|
||||||
- **Bit = 0** → page is free
|
|
||||||
|
|
||||||
The bitmap covers the entire 32-bit physical address space (up to 4 GiB), requiring 128 KiB of storage in BSS.
|
|
||||||
|
|
||||||
### Memory Zones
|
|
||||||
|
|
||||||
The allocator supports zone-based allocation to satisfy constraints from different subsystems:
|
|
||||||
|
|
||||||
| Zone | Range | Purpose |
|
|
||||||
|---|---|---|
|
|
||||||
| `PMM_ZONE_DMA` | 0 – 16 MiB | ISA DMA-compatible memory |
|
|
||||||
| `PMM_ZONE_NORMAL` | 16 MiB – 4 GiB | General-purpose allocation |
|
|
||||||
|
|
||||||
When `PMM_ZONE_NORMAL` is requested but no pages are available above 16 MiB (e.g., on systems with less than 16 MiB of RAM), the allocator falls back to `PMM_ZONE_DMA`.
|
|
||||||
|
|
||||||
Page 0 (address 0x00000000) is always marked as used to prevent returning NULL as a valid physical address.
|
|
||||||
|
|
||||||
### Initialization
|
|
||||||
|
|
||||||
1. The entire bitmap is initialized to "all used" (0xFFFFFFFF).
|
|
||||||
2. The Multiboot2 memory map is parsed to discover available RAM regions.
|
|
||||||
3. Available regions are marked as free in the bitmap.
|
|
||||||
4. The kernel's own memory (between `_kernel_start` and `_kernel_end` linker symbols) is re-marked as used.
|
|
||||||
5. The Multiboot2 info structure itself is marked as used.
|
|
||||||
|
|
||||||
### Linker Symbols
|
|
||||||
|
|
||||||
The linker script exports `_kernel_start` and `_kernel_end` symbols so the PMM knows which physical pages the kernel occupies and must not allocate.
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
```c
|
|
||||||
void init_pmm(uint32_t multiboot_addr);
|
|
||||||
phys_addr_t pmm_alloc_page(pmm_zone_t zone);
|
|
||||||
void pmm_free_page(phys_addr_t addr);
|
|
||||||
```
|
|
||||||
|
|
||||||
- `init_pmm` — Parse Multiboot2 info and build the free-page bitmap.
|
|
||||||
- `pmm_alloc_page` — Allocate a single 4 KiB page from the specified zone. Returns 0 on OOM.
|
|
||||||
- `pmm_free_page` — Return a page to the free pool.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- `src/pmm.c` / `src/pmm.h` — Allocator implementation and zone definitions.
|
|
||||||
- `src/linker.ld` — Exports `_kernel_start` / `_kernel_end`.
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
- First-fit allocation (linear scan) — O(n) per allocation.
|
|
||||||
- No per-zone free counts or statistics yet.
|
|
||||||
- Does not handle memory hot-plug or ACPI reclaim regions.
|
|
||||||
109
docs/process.md
109
docs/process.md
@@ -1,109 +0,0 @@
|
|||||||
# Process Subsystem
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The process subsystem enables user-mode (Ring 3) process execution on ClaudeOS.
|
|
||||||
It provides process creation, context switching via the timer interrupt, and
|
|
||||||
system calls via `INT 0x80`.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Ring Transition
|
|
||||||
|
|
||||||
x86 protected mode uses privilege rings 0–3. The kernel runs in Ring 0 (full
|
|
||||||
hardware access) and user processes run in Ring 3 (restricted). The GDT
|
|
||||||
defines segment descriptors for both:
|
|
||||||
|
|
||||||
| GDT Entry | Selector | Purpose | DPL |
|
|
||||||
|-----------|----------|-----------------|-----|
|
|
||||||
| 0 | 0x00 | Null | – |
|
|
||||||
| 1 | 0x08 | Kernel Code | 0 |
|
|
||||||
| 2 | 0x10 | Kernel Data | 0 |
|
|
||||||
| 3 | 0x18 | User Code | 3 |
|
|
||||||
| 4 | 0x20 | User Data | 3 |
|
|
||||||
| 5 | 0x28 | TSS | 0 |
|
|
||||||
|
|
||||||
User-mode selectors include RPL=3: code = 0x1B, data = 0x23.
|
|
||||||
|
|
||||||
### Task State Segment (TSS)
|
|
||||||
|
|
||||||
The TSS (`tss.c`) stores the kernel stack pointer (SS0:ESP0) used when the CPU
|
|
||||||
transitions from Ring 3 to Ring 0 on an interrupt. Before running each process,
|
|
||||||
the scheduler updates TSS.ESP0 to that process's kernel stack top.
|
|
||||||
|
|
||||||
### Memory Layout
|
|
||||||
|
|
||||||
Each process gets its own page directory, cloned from the kernel's:
|
|
||||||
|
|
||||||
```
|
|
||||||
0x00000000 – 0x07FFFFFF : Identity-mapped (kernel/device access)
|
|
||||||
0x08048000 – ... : User code (loaded from binary image)
|
|
||||||
0xBFFF7000 – 0xBFFFF000 : User stack (2 pages, grows downward)
|
|
||||||
0xD0000000 – 0xF0000000 : Kernel heap (shared across all processes)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Process Control Block
|
|
||||||
|
|
||||||
```c
|
|
||||||
typedef struct process {
|
|
||||||
uint32_t pid;
|
|
||||||
process_state_t state; // UNUSED, READY, RUNNING, BLOCKED, ZOMBIE
|
|
||||||
registers_t saved_regs; // Full interrupt frame
|
|
||||||
uint32_t kernel_stack; // Base of per-process kernel stack
|
|
||||||
uint32_t kernel_stack_top; // TSS ESP0 value
|
|
||||||
uint32_t page_directory; // Physical address of page directory
|
|
||||||
uint32_t user_stack; // User stack virtual address
|
|
||||||
uint32_t entry_point; // User code entry point
|
|
||||||
int32_t exit_code; // Set on exit
|
|
||||||
uint32_t parent_pid;
|
|
||||||
char name[32];
|
|
||||||
} process_t;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Context Switching
|
|
||||||
|
|
||||||
Context switching uses the interrupt frame directly:
|
|
||||||
|
|
||||||
1. Timer IRQ (or `INT 0x80` for SYS_YIELD) fires
|
|
||||||
2. CPU pushes SS/ESP/EFLAGS/CS/EIP onto the process's kernel stack
|
|
||||||
3. ISR stub pushes the rest (pusha + DS) forming a `registers_t`
|
|
||||||
4. `schedule_tick()` is called with a pointer to these registers
|
|
||||||
5. Current process's registers are saved into its PCB
|
|
||||||
6. Next READY process's saved registers are written over the interrupt frame
|
|
||||||
7. TSS.ESP0 is updated, CR3 is switched to the new page directory
|
|
||||||
8. ISR stub restores the (now different) registers and `iret` enters the new
|
|
||||||
process in user mode
|
|
||||||
|
|
||||||
This avoids separate context-switch assembly — the existing ISR stub handles
|
|
||||||
everything.
|
|
||||||
|
|
||||||
## System Calls
|
|
||||||
|
|
||||||
System calls use `INT 0x80` with the call number in EAX:
|
|
||||||
|
|
||||||
| Number | Name | Arguments |
|
|
||||||
|--------|-------------|------------------------------|
|
|
||||||
| 0 | SYS_EXIT | EBX = exit code |
|
|
||||||
| 1 | SYS_WRITE | EBX = fd, ECX = buf, EDX = len |
|
|
||||||
| 2 | SYS_READ | (not implemented) |
|
|
||||||
| 3 | SYS_FORK | (returns child PID/0) |
|
|
||||||
| 4 | SYS_GETPID | (returns PID in EAX) |
|
|
||||||
| 5 | SYS_YIELD | (voluntary preemption) |
|
|
||||||
| 6 | SYS_WAITPID | EBX = child PID |
|
|
||||||
| 7 | SYS_EXEC | (not implemented) |
|
|
||||||
|
|
||||||
The INT 0x80 IDT gate has DPL=3 (flags 0xEE) so user-mode code can invoke it.
|
|
||||||
|
|
||||||
## Initial Process Entry
|
|
||||||
|
|
||||||
`process_run_first()` performs the initial transition to user mode using an
|
|
||||||
`iret` instruction that sets up Ring 3 segment selectors, the user stack
|
|
||||||
pointer, and the entry point. This is a one-way transition — the function
|
|
||||||
does not return.
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
- `tss.h` / `tss.c` — TSS structure and initialization
|
|
||||||
- `process.h` / `process.c` — Process table, creation, scheduling, exit, fork
|
|
||||||
- `syscall.h` / `syscall.c` — System call dispatch and handlers
|
|
||||||
- `interrupts.S` — Assembly stubs: `isr128` (INT 0x80), `tss_flush`, `enter_usermode`
|
|
||||||
0
libs/.gitkeep
Normal file
0
libs/.gitkeep
Normal file
0
src/.gitkeep
Normal file
0
src/.gitkeep
Normal file
@@ -1,29 +1,33 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
set(KERNEL_SOURCES
|
||||||
|
boot/boot.s
|
||||||
add_executable(kernel
|
|
||||||
boot.S
|
|
||||||
gdt_flush.S
|
|
||||||
gdt.c
|
|
||||||
idt.c
|
|
||||||
isr.c
|
|
||||||
pic.c
|
|
||||||
pmm.c
|
|
||||||
paging.c
|
|
||||||
kmalloc.c
|
|
||||||
string.c
|
|
||||||
driver.c
|
|
||||||
vga.c
|
|
||||||
tss.c
|
|
||||||
process.c
|
|
||||||
syscall.c
|
|
||||||
interrupts.S
|
|
||||||
kernel.c
|
kernel.c
|
||||||
|
debugport.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use our custom linker script
|
add_executable(kernel ${KERNEL_SOURCES})
|
||||||
target_link_options(kernel PRIVATE -T ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld)
|
|
||||||
|
|
||||||
target_include_directories(kernel PRIVATE
|
target_include_directories(kernel PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/vendor
|
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
${CMAKE_SOURCE_DIR}/include # If created later
|
)
|
||||||
|
|
||||||
|
target_compile_options(kernel PRIVATE
|
||||||
|
# C-only flags — not valid for the assembler front-end.
|
||||||
|
$<$<COMPILE_LANGUAGE:C>:-Werror>
|
||||||
|
$<$<COMPILE_LANGUAGE:C>:-ffreestanding>
|
||||||
|
$<$<COMPILE_LANGUAGE:C>:-fno-stack-protector>
|
||||||
|
$<$<COMPILE_LANGUAGE:C>:-fno-exceptions>
|
||||||
|
# Suppress the harmless "argument unused" warnings that clang emits
|
||||||
|
# when CMake passes -MD/-MT/-MF to the assembler.
|
||||||
|
$<$<COMPILE_LANGUAGE:ASM>:-Wno-unused-command-line-argument>
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(kernel PROPERTIES
|
||||||
|
OUTPUT_NAME "claudeos.elf"
|
||||||
|
SUFFIX ""
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_options(kernel PRIVATE
|
||||||
|
# Pass the linker script directly to ld.lld (CMAKE_C_LINK_EXECUTABLE
|
||||||
|
# calls ld.lld directly, so these are raw ld flags, not clang driver flags).
|
||||||
|
-T ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld
|
||||||
)
|
)
|
||||||
|
|||||||
65
src/boot.S
65
src/boot.S
@@ -1,65 +0,0 @@
|
|||||||
#define ASM_FILE
|
|
||||||
#include <multiboot2.h>
|
|
||||||
|
|
||||||
/* Multiboot 1 header constants */
|
|
||||||
.set ALIGN, 1<<0 /* align loaded modules on page boundaries */
|
|
||||||
.set MEMINFO, 1<<1 /* provide memory map */
|
|
||||||
.set FLAGS, ALIGN | MEMINFO /* this is the Multiboot 'flag' field */
|
|
||||||
.set MAGIC, 0x1BADB002 /* 'magic number' lets bootloader find the header */
|
|
||||||
.set CHECKSUM, -(MAGIC + FLAGS) /* checksum of above, to prove we are multiboot */
|
|
||||||
|
|
||||||
.section .multiboot
|
|
||||||
.align 4
|
|
||||||
multiboot1_header:
|
|
||||||
.long MAGIC
|
|
||||||
.long FLAGS
|
|
||||||
.long CHECKSUM
|
|
||||||
|
|
||||||
.align 8
|
|
||||||
multiboot_header:
|
|
||||||
/* magic */
|
|
||||||
.long MULTIBOOT2_HEADER_MAGIC
|
|
||||||
/* architecture: 0 (protected mode i386) */
|
|
||||||
.long MULTIBOOT_ARCHITECTURE_I386
|
|
||||||
/* header length */
|
|
||||||
.long multiboot_header_end - multiboot_header
|
|
||||||
/* checksum */
|
|
||||||
.long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
|
|
||||||
|
|
||||||
/* Tags here */
|
|
||||||
|
|
||||||
/* End tag */
|
|
||||||
.short MULTIBOOT_HEADER_TAG_END
|
|
||||||
.short 0
|
|
||||||
.long 8
|
|
||||||
multiboot_header_end:
|
|
||||||
|
|
||||||
.section .bss
|
|
||||||
.align 16
|
|
||||||
stack_bottom:
|
|
||||||
.skip 16384 # 16 KiB
|
|
||||||
stack_top:
|
|
||||||
|
|
||||||
.section .text
|
|
||||||
.global _start
|
|
||||||
.type _start, @function
|
|
||||||
_start:
|
|
||||||
/* Set up stack */
|
|
||||||
mov $stack_top, %esp
|
|
||||||
|
|
||||||
/* Reset EFLAGS */
|
|
||||||
push $0
|
|
||||||
popf
|
|
||||||
|
|
||||||
/* Push magic and multiboot info pointer onto stack for kernel_main */
|
|
||||||
/* Multiboot2 puts magic in EAX, pointer in EBX */
|
|
||||||
push %ebx
|
|
||||||
push %eax
|
|
||||||
|
|
||||||
call kernel_main
|
|
||||||
|
|
||||||
cli
|
|
||||||
1: hlt
|
|
||||||
jmp 1b
|
|
||||||
|
|
||||||
.size _start, . - _start
|
|
||||||
92
src/boot/boot.s
Normal file
92
src/boot/boot.s
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* boot.s — Multiboot-compliant bootstrap for ClaudeOS.
|
||||||
|
*
|
||||||
|
* This file is the very first code executed by GRUB after the kernel is
|
||||||
|
* loaded. Its responsibilities are:
|
||||||
|
*
|
||||||
|
* 1. Provide a valid Multiboot header so that GRUB recognises the image.
|
||||||
|
* 2. Zero the BSS segment (the C runtime requires it to start at zero).
|
||||||
|
* 3. Set up a small temporary stack.
|
||||||
|
* 4. Forward the Multiboot magic value and info-structure pointer to
|
||||||
|
* kernel_main() as its first and second arguments.
|
||||||
|
* 5. Halt the CPU if kernel_main() ever returns (it should not).
|
||||||
|
*
|
||||||
|
* Multiboot flags used:
|
||||||
|
* Bit 0 — 4 KiB-align all boot modules.
|
||||||
|
* Bit 1 — Include memory map in the Multiboot info structure.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------
|
||||||
|
* Multiboot header
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
.set MULTIBOOT_MAGIC, 0x1BADB002
|
||||||
|
.set MULTIBOOT_FLAGS, (1 << 0) | (1 << 1)
|
||||||
|
.set MULTIBOOT_CHECKSUM, -(MULTIBOOT_MAGIC + MULTIBOOT_FLAGS)
|
||||||
|
|
||||||
|
.section .multiboot, "a"
|
||||||
|
.align 4
|
||||||
|
.long MULTIBOOT_MAGIC
|
||||||
|
.long MULTIBOOT_FLAGS
|
||||||
|
.long MULTIBOOT_CHECKSUM
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------
|
||||||
|
* Bootstrap stack — 16 KiB, 16-byte aligned (required by the i386 ABI).
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
.section .bss
|
||||||
|
.align 16
|
||||||
|
stack_bottom:
|
||||||
|
.skip 16384
|
||||||
|
stack_top:
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------
|
||||||
|
* Kernel entry point
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
.section .text
|
||||||
|
.global _start
|
||||||
|
.type _start, @function
|
||||||
|
_start:
|
||||||
|
/* Set up the temporary kernel stack. */
|
||||||
|
mov $stack_top, %esp
|
||||||
|
|
||||||
|
/* Preserve the Multiboot magic before the BSS-clearing loop clobbers EAX.
|
||||||
|
*
|
||||||
|
* Registers on entry (Multiboot spec §3.2):
|
||||||
|
* eax — 0x2BADB002 (magic)
|
||||||
|
* ebx — multiboot_info_t* (physical address of info structure)
|
||||||
|
*
|
||||||
|
* 'rep stosb' only modifies EAX, EDI and ECX, so EBX survives intact.
|
||||||
|
* We park EAX in ESI (callee-saved, not used by the BSS loop). */
|
||||||
|
mov %eax, %esi /* save Multiboot magic */
|
||||||
|
|
||||||
|
/* Zero the BSS segment.
|
||||||
|
* The C standard requires that all static storage is zero-initialised.
|
||||||
|
* The linker script exports _bss_start and _bss_end for this purpose. */
|
||||||
|
mov $_bss_start, %edi
|
||||||
|
mov $_bss_end, %ecx
|
||||||
|
sub %edi, %ecx /* byte count */
|
||||||
|
xor %eax, %eax
|
||||||
|
rep stosb
|
||||||
|
|
||||||
|
/* Restore magic; EBX (info pointer) is still intact. */
|
||||||
|
mov %esi, %eax
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Call kernel_main(uint32_t multiboot_magic, uint32_t multiboot_info).
|
||||||
|
*
|
||||||
|
* The Multiboot specification passes:
|
||||||
|
* eax — 0x2BADB002 (bootloader magic)
|
||||||
|
* ebx — physical address of the multiboot_info_t structure
|
||||||
|
*
|
||||||
|
* We push them in reverse order to match the C calling convention.
|
||||||
|
*/
|
||||||
|
push %ebx /* arg1: multiboot_info pointer */
|
||||||
|
push %eax /* arg0: multiboot magic */
|
||||||
|
call kernel_main
|
||||||
|
|
||||||
|
/* kernel_main should never return; halt the machine if it does. */
|
||||||
|
.L_halt:
|
||||||
|
cli
|
||||||
|
hlt
|
||||||
|
jmp .L_halt
|
||||||
|
|
||||||
|
.size _start, . - _start
|
||||||
39
src/debugport.c
Normal file
39
src/debugport.c
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @file debugport.c
|
||||||
|
* @brief QEMU/Bochs debug port (I/O port 0xE9) implementation.
|
||||||
|
*
|
||||||
|
* Writing a byte to I/O port 0xE9 causes QEMU (when run with
|
||||||
|
* `-debugcon stdio`) to echo that byte verbatim to the host's stdout.
|
||||||
|
* This requires no peripheral initialisation and is available from the
|
||||||
|
* very first instruction after the bootloader hands control to the kernel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <debugport.h>
|
||||||
|
#include <io.h>
|
||||||
|
|
||||||
|
/** I/O port address of the QEMU/Bochs debug console. */
|
||||||
|
#define DEBUG_PORT 0xE9U
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a single character to the QEMU debug port.
|
||||||
|
*
|
||||||
|
* @param c Character to transmit.
|
||||||
|
*/
|
||||||
|
void debugport_putc(char c)
|
||||||
|
{
|
||||||
|
outb((uint16_t)DEBUG_PORT, (uint8_t)c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a NUL-terminated string to the QEMU debug port.
|
||||||
|
*
|
||||||
|
* Each character is transmitted individually via debugport_putc().
|
||||||
|
*
|
||||||
|
* @param s Pointer to the NUL-terminated string. Must not be NULL.
|
||||||
|
*/
|
||||||
|
void debugport_puts(const char *s)
|
||||||
|
{
|
||||||
|
while (*s) {
|
||||||
|
debugport_putc(*s++);
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/driver.c
72
src/driver.c
@@ -1,72 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file driver.c
|
|
||||||
* @brief Driver framework implementation.
|
|
||||||
*
|
|
||||||
* Iterates over all driver descriptors placed in the .drivers linker section
|
|
||||||
* by the REGISTER_DRIVER macro. Each driver is probed and, if the probe
|
|
||||||
* succeeds, initialized.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "driver.h"
|
|
||||||
#include "port_io.h"
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
/* Debug print helpers defined in kernel.c */
|
|
||||||
extern void offset_print(const char *str);
|
|
||||||
extern void print_hex(uint32_t val);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Linker-provided symbols marking the start and end of the .drivers section.
|
|
||||||
* Each entry is a pointer to a driver_t.
|
|
||||||
*/
|
|
||||||
extern const driver_t *__drivers_start[];
|
|
||||||
extern const driver_t *__drivers_end[];
|
|
||||||
|
|
||||||
void init_drivers(void) {
|
|
||||||
const driver_t **drv;
|
|
||||||
int loaded = 0;
|
|
||||||
int skipped = 0;
|
|
||||||
int failed = 0;
|
|
||||||
|
|
||||||
offset_print(" DRIVERS: scanning registered drivers...\n");
|
|
||||||
|
|
||||||
for (drv = __drivers_start; drv < __drivers_end; drv++) {
|
|
||||||
const driver_t *d = *drv;
|
|
||||||
if (!d || !d->name) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset_print(" DRIVERS: probing ");
|
|
||||||
offset_print(d->name);
|
|
||||||
offset_print("... ");
|
|
||||||
|
|
||||||
/* Run probe function if provided */
|
|
||||||
if (d->probe) {
|
|
||||||
driver_probe_result_t result = d->probe();
|
|
||||||
if (result == DRIVER_PROBE_NOT_FOUND) {
|
|
||||||
offset_print("not found\n");
|
|
||||||
skipped++;
|
|
||||||
continue;
|
|
||||||
} else if (result == DRIVER_PROBE_ERROR) {
|
|
||||||
offset_print("probe error\n");
|
|
||||||
failed++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Run init function */
|
|
||||||
if (d->init) {
|
|
||||||
int ret = d->init();
|
|
||||||
if (ret != 0) {
|
|
||||||
offset_print("init failed\n");
|
|
||||||
failed++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset_print("loaded\n");
|
|
||||||
loaded++;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset_print(" DRIVERS: done\n");
|
|
||||||
}
|
|
||||||
56
src/driver.h
56
src/driver.h
@@ -1,56 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file driver.h
|
|
||||||
* @brief Kernel driver architecture.
|
|
||||||
*
|
|
||||||
* Provides a simple framework for registering and initializing kernel drivers.
|
|
||||||
* Each driver provides a probe function that returns whether it should load,
|
|
||||||
* and an init function that performs the actual initialization.
|
|
||||||
*
|
|
||||||
* Drivers are registered at compile time using the REGISTER_DRIVER macro,
|
|
||||||
* which places driver descriptors in a special linker section. The kernel
|
|
||||||
* iterates over all registered drivers during boot.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef DRIVER_H
|
|
||||||
#define DRIVER_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/** Driver probe result codes. */
|
|
||||||
typedef enum {
|
|
||||||
DRIVER_PROBE_OK = 0, /**< Driver should be loaded. */
|
|
||||||
DRIVER_PROBE_NOT_FOUND = 1, /**< Hardware not found, skip this driver. */
|
|
||||||
DRIVER_PROBE_ERROR = 2 /**< Error during probing. */
|
|
||||||
} driver_probe_result_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Driver descriptor structure.
|
|
||||||
*
|
|
||||||
* Each driver provides a name, a probe function (to test if hardware is
|
|
||||||
* present), and an init function (to set up the driver).
|
|
||||||
*/
|
|
||||||
typedef struct driver {
|
|
||||||
const char *name; /**< Human-readable driver name. */
|
|
||||||
driver_probe_result_t (*probe)(void); /**< Probe function. Returns DRIVER_PROBE_OK if driver should load. */
|
|
||||||
int (*init)(void); /**< Init function. Returns 0 on success, non-zero on failure. */
|
|
||||||
} driver_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a driver.
|
|
||||||
*
|
|
||||||
* Places the driver descriptor in the .drivers linker section so it
|
|
||||||
* is automatically discovered during boot.
|
|
||||||
*/
|
|
||||||
#define REGISTER_DRIVER(drv) \
|
|
||||||
static const driver_t * __attribute__((used, section(".drivers"))) \
|
|
||||||
_driver_##drv = &(drv)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize all registered drivers.
|
|
||||||
*
|
|
||||||
* Iterates over the .drivers section, probes each driver, and initializes
|
|
||||||
* those that respond positively to probing.
|
|
||||||
*/
|
|
||||||
void init_drivers(void);
|
|
||||||
|
|
||||||
#endif /* DRIVER_H */
|
|
||||||
68
src/gdt.c
68
src/gdt.c
@@ -1,68 +0,0 @@
|
|||||||
#include "gdt.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/* GDT Pointer Structure */
|
|
||||||
struct gdt_ptr gp;
|
|
||||||
/* GDT entries: 0=null, 1=kcode, 2=kdata, 3=ucode, 4=udata, 5=tss */
|
|
||||||
struct gdt_entry gdt[6];
|
|
||||||
|
|
||||||
extern void gdt_flush(uint32_t);
|
|
||||||
|
|
||||||
/* Setup a descriptor in the Global Descriptor Table */
|
|
||||||
void gdt_set_gate(int32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran)
|
|
||||||
{
|
|
||||||
/* Setup the descriptor base address */
|
|
||||||
gdt[num].base_low = (base & 0xFFFF);
|
|
||||||
gdt[num].base_middle = (base >> 16) & 0xFF;
|
|
||||||
gdt[num].base_high = (base >> 24) & 0xFF;
|
|
||||||
|
|
||||||
/* Setup the descriptor limits */
|
|
||||||
gdt[num].limit_low = (limit & 0xFFFF);
|
|
||||||
gdt[num].granularity = ((limit >> 16) & 0x0F);
|
|
||||||
|
|
||||||
/* Finally, set up the granularity and access flags */
|
|
||||||
gdt[num].granularity |= (gran & 0xF0);
|
|
||||||
gdt[num].access = access;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Should be called by main. This will setup the special GDT
|
|
||||||
* pointer, set up the first 3 entries in our GDT, and then
|
|
||||||
* call gdt_flush() in our assembler file in order to tell
|
|
||||||
* the processor where the new GDT is and update the
|
|
||||||
* internal registers */
|
|
||||||
void init_gdt()
|
|
||||||
{
|
|
||||||
/* Setup the GDT pointer and limit */
|
|
||||||
gp.limit = (sizeof(struct gdt_entry) * 6) - 1;
|
|
||||||
gp.base = (uint32_t)&gdt;
|
|
||||||
|
|
||||||
/* Our NULL descriptor */
|
|
||||||
gdt_set_gate(0, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
/* The second entry is our Code Segment. The base address
|
|
||||||
* is 0, the limit is 4GBytes, it uses 4KByte granularity,
|
|
||||||
* uses 32-bit opcodes, and is a Code Segment descriptor.
|
|
||||||
* Please check the table above in the tutorial in order
|
|
||||||
* to see exactly what each value means */
|
|
||||||
/* 0x9A = 1001 1010
|
|
||||||
P=1, DPL=00, S=1 (Code/Data), Type=1010 (Code, Exec/Read) */
|
|
||||||
/* 0xCF = 1100 1111
|
|
||||||
G=1 (4k), DB=1 (32bit), L=0, AVL=0, LimitHigh=0xF */
|
|
||||||
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
|
|
||||||
|
|
||||||
/* The third entry is our Data Segment. It's EXACTLY the
|
|
||||||
* same as our code segment, but the descriptor type in
|
|
||||||
* this entry's access byte says it's a Data Segment */
|
|
||||||
/* 0x92 = 1001 0010
|
|
||||||
P=1, DPL=00, S=1, Type=0010 (Data, Read/Write) */
|
|
||||||
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
|
|
||||||
|
|
||||||
/* User mode code segment */
|
|
||||||
gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF);
|
|
||||||
|
|
||||||
/* User mode data segment */
|
|
||||||
gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF);
|
|
||||||
|
|
||||||
/* Flush out the old GDT and install the new changes! */
|
|
||||||
gdt_flush((uint32_t)&gp);
|
|
||||||
}
|
|
||||||
28
src/gdt.h
28
src/gdt.h
@@ -1,28 +0,0 @@
|
|||||||
#ifndef GDT_H
|
|
||||||
#define GDT_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/* GDT Entry Structure */
|
|
||||||
struct gdt_entry {
|
|
||||||
uint16_t limit_low;
|
|
||||||
uint16_t base_low;
|
|
||||||
uint8_t base_middle;
|
|
||||||
uint8_t access;
|
|
||||||
uint8_t granularity;
|
|
||||||
uint8_t base_high;
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
/* GDT Pointer Structure */
|
|
||||||
struct gdt_ptr {
|
|
||||||
uint16_t limit;
|
|
||||||
uint32_t base;
|
|
||||||
} __attribute__((packed, aligned(1)));
|
|
||||||
|
|
||||||
/* Initialize GDT */
|
|
||||||
void init_gdt(void);
|
|
||||||
|
|
||||||
/* Set a GDT gate (also used by TSS setup) */
|
|
||||||
void gdt_set_gate(int32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran);
|
|
||||||
|
|
||||||
#endif // GDT_H
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
.section .text
|
|
||||||
.global gdt_flush
|
|
||||||
.type gdt_flush, @function
|
|
||||||
gdt_flush:
|
|
||||||
mov 4(%esp), %eax
|
|
||||||
lgdt (%eax)
|
|
||||||
|
|
||||||
mov $0x10, %ax
|
|
||||||
mov %ax, %ds
|
|
||||||
mov %ax, %es
|
|
||||||
mov %ax, %fs
|
|
||||||
mov %ax, %gs
|
|
||||||
mov %ax, %ss
|
|
||||||
|
|
||||||
ljmp $0x08, $flush
|
|
||||||
|
|
||||||
flush:
|
|
||||||
ret
|
|
||||||
141
src/idt.c
141
src/idt.c
@@ -1,141 +0,0 @@
|
|||||||
#include "idt.h"
|
|
||||||
#include <string.h> // For memset
|
|
||||||
|
|
||||||
// The IDT itself
|
|
||||||
idt_entry_t idt[256];
|
|
||||||
idt_ptr_t idt_ptr;
|
|
||||||
|
|
||||||
// Helper to set a gate in the IDT
|
|
||||||
static void set_idt_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;
|
|
||||||
// flags: 0x8E = 10001110 (Present, Ring0, 32-bit Interrupt Gate)
|
|
||||||
idt[num].flags = flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public version for other subsystems (e.g., syscall INT 0x80)
|
|
||||||
void set_idt_gate_from_c(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) {
|
|
||||||
set_idt_gate(num, base, sel, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exception Handlers (ISRs)
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Hardware Interrupt Handlers (IRQs)
|
|
||||||
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 init_idt() {
|
|
||||||
// 1. Set up the IDT pointer
|
|
||||||
idt_ptr.limit = sizeof(idt_entry_t) * 256 - 1;
|
|
||||||
idt_ptr.base = (uint32_t)&idt;
|
|
||||||
|
|
||||||
// 2. Clear the IDT
|
|
||||||
memset(&idt, 0, sizeof(idt_entry_t) * 256);
|
|
||||||
|
|
||||||
// 3. Set the ISRs (Exceptions 0-31)
|
|
||||||
// Code Selector is 0x08 usually (defined in GDT)
|
|
||||||
// Flags: 0x8E = Present(1), DPL(00), Storage(0), GateType(1110 = 32-bit Int)
|
|
||||||
set_idt_gate( 0, (uint32_t)isr0, 0x08, 0x8E);
|
|
||||||
set_idt_gate( 1, (uint32_t)isr1, 0x08, 0x8E);
|
|
||||||
set_idt_gate( 2, (uint32_t)isr2, 0x08, 0x8E);
|
|
||||||
set_idt_gate( 3, (uint32_t)isr3, 0x08, 0x8E);
|
|
||||||
set_idt_gate( 4, (uint32_t)isr4, 0x08, 0x8E);
|
|
||||||
set_idt_gate( 5, (uint32_t)isr5, 0x08, 0x8E);
|
|
||||||
set_idt_gate( 6, (uint32_t)isr6, 0x08, 0x8E);
|
|
||||||
set_idt_gate( 7, (uint32_t)isr7, 0x08, 0x8E);
|
|
||||||
set_idt_gate( 8, (uint32_t)isr8, 0x08, 0x8E);
|
|
||||||
set_idt_gate( 9, (uint32_t)isr9, 0x08, 0x8E);
|
|
||||||
set_idt_gate(10, (uint32_t)isr10, 0x08, 0x8E);
|
|
||||||
set_idt_gate(11, (uint32_t)isr11, 0x08, 0x8E);
|
|
||||||
set_idt_gate(12, (uint32_t)isr12, 0x08, 0x8E);
|
|
||||||
set_idt_gate(13, (uint32_t)isr13, 0x08, 0x8E);
|
|
||||||
set_idt_gate(14, (uint32_t)isr14, 0x08, 0x8E);
|
|
||||||
set_idt_gate(15, (uint32_t)isr15, 0x08, 0x8E);
|
|
||||||
set_idt_gate(16, (uint32_t)isr16, 0x08, 0x8E);
|
|
||||||
set_idt_gate(17, (uint32_t)isr17, 0x08, 0x8E);
|
|
||||||
set_idt_gate(18, (uint32_t)isr18, 0x08, 0x8E);
|
|
||||||
set_idt_gate(19, (uint32_t)isr19, 0x08, 0x8E);
|
|
||||||
set_idt_gate(20, (uint32_t)isr20, 0x08, 0x8E);
|
|
||||||
set_idt_gate(21, (uint32_t)isr21, 0x08, 0x8E);
|
|
||||||
set_idt_gate(22, (uint32_t)isr22, 0x08, 0x8E);
|
|
||||||
set_idt_gate(23, (uint32_t)isr23, 0x08, 0x8E);
|
|
||||||
set_idt_gate(24, (uint32_t)isr24, 0x08, 0x8E);
|
|
||||||
set_idt_gate(25, (uint32_t)isr25, 0x08, 0x8E);
|
|
||||||
set_idt_gate(26, (uint32_t)isr26, 0x08, 0x8E);
|
|
||||||
set_idt_gate(27, (uint32_t)isr27, 0x08, 0x8E);
|
|
||||||
set_idt_gate(28, (uint32_t)isr28, 0x08, 0x8E);
|
|
||||||
set_idt_gate(29, (uint32_t)isr29, 0x08, 0x8E);
|
|
||||||
set_idt_gate(30, (uint32_t)isr30, 0x08, 0x8E);
|
|
||||||
set_idt_gate(31, (uint32_t)isr31, 0x08, 0x8E);
|
|
||||||
|
|
||||||
// 4. Set the IRQs (Remapped to 32-47)
|
|
||||||
set_idt_gate(32, (uint32_t)irq0, 0x08, 0x8E);
|
|
||||||
set_idt_gate(33, (uint32_t)irq1, 0x08, 0x8E);
|
|
||||||
set_idt_gate(34, (uint32_t)irq2, 0x08, 0x8E);
|
|
||||||
set_idt_gate(35, (uint32_t)irq3, 0x08, 0x8E);
|
|
||||||
set_idt_gate(36, (uint32_t)irq4, 0x08, 0x8E);
|
|
||||||
set_idt_gate(37, (uint32_t)irq5, 0x08, 0x8E);
|
|
||||||
set_idt_gate(38, (uint32_t)irq6, 0x08, 0x8E);
|
|
||||||
set_idt_gate(39, (uint32_t)irq7, 0x08, 0x8E);
|
|
||||||
set_idt_gate(40, (uint32_t)irq8, 0x08, 0x8E);
|
|
||||||
set_idt_gate(41, (uint32_t)irq9, 0x08, 0x8E);
|
|
||||||
set_idt_gate(42, (uint32_t)irq10, 0x08, 0x8E);
|
|
||||||
set_idt_gate(43, (uint32_t)irq11, 0x08, 0x8E);
|
|
||||||
set_idt_gate(44, (uint32_t)irq12, 0x08, 0x8E);
|
|
||||||
set_idt_gate(45, (uint32_t)irq13, 0x08, 0x8E);
|
|
||||||
set_idt_gate(46, (uint32_t)irq14, 0x08, 0x8E);
|
|
||||||
set_idt_gate(47, (uint32_t)irq15, 0x08, 0x8E);
|
|
||||||
|
|
||||||
// 5. Load the IDT using assembly instruction 'lidt'
|
|
||||||
// We can use inline assembly or a helper function.
|
|
||||||
// Assuming lid is available via inline asm or similar to gdt_flush
|
|
||||||
__asm__ volatile("lidt (%0)" : : "r" (&idt_ptr));
|
|
||||||
}
|
|
||||||
33
src/idt.h
33
src/idt.h
@@ -1,33 +0,0 @@
|
|||||||
#ifndef IDT_H
|
|
||||||
#define IDT_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/* A struct describing an interrupt gate. */
|
|
||||||
struct idt_entry_struct {
|
|
||||||
uint16_t base_lo; // The lower 16 bits of the address to jump to when this interrupt fires.
|
|
||||||
uint16_t sel; // Kernel segment selector.
|
|
||||||
uint8_t always0; // This must always be zero.
|
|
||||||
uint8_t flags; // More flags. See documentation.
|
|
||||||
uint16_t base_hi; // The upper 16 bits of the address to jump to.
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
typedef struct idt_entry_struct idt_entry_t;
|
|
||||||
|
|
||||||
/* A struct describing a pointer to an array of interrupt handlers.
|
|
||||||
* This is in a format suitable for giving to 'lidt'. */
|
|
||||||
struct idt_ptr_struct {
|
|
||||||
uint16_t limit;
|
|
||||||
uint32_t base; // The address of the first element in our idt_entry_t array.
|
|
||||||
} __attribute__((packed));
|
|
||||||
|
|
||||||
typedef struct idt_ptr_struct idt_ptr_t;
|
|
||||||
|
|
||||||
// These extern directives let us access the addresses of our ASM ISR handlers.
|
|
||||||
extern void isr0();
|
|
||||||
extern void isr1();
|
|
||||||
// ... we will add more later ...
|
|
||||||
|
|
||||||
void init_idt();
|
|
||||||
|
|
||||||
#endif
|
|
||||||
34
src/include/debugport.h
Normal file
34
src/include/debugport.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* @file debugport.h
|
||||||
|
* @brief QEMU/Bochs debug port (I/O port 0xE9) output interface.
|
||||||
|
*
|
||||||
|
* Port 0xE9 is a de-facto standard "debug port" supported by both Bochs and
|
||||||
|
* QEMU (when launched with `-debugcon stdio`). Any byte written to this port
|
||||||
|
* is immediately forwarded to the host's standard output, making it the
|
||||||
|
* simplest possible kernel logging mechanism — no interrupt handling, VGA, or
|
||||||
|
* serial initialisation is required.
|
||||||
|
*
|
||||||
|
* Usage in QEMU:
|
||||||
|
* qemu-system-i386 -debugcon stdio ...
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a single character to the QEMU debug port.
|
||||||
|
*
|
||||||
|
* Issues a single OUT instruction to port 0xE9.
|
||||||
|
*
|
||||||
|
* @param c Character to transmit.
|
||||||
|
*/
|
||||||
|
void debugport_putc(char c);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a NUL-terminated string to the QEMU debug port.
|
||||||
|
*
|
||||||
|
* Iterates over every character in @p s and calls debugport_putc() for each
|
||||||
|
* one. The terminating NUL byte is not transmitted.
|
||||||
|
*
|
||||||
|
* @param s Pointer to the NUL-terminated string. Must not be NULL.
|
||||||
|
*/
|
||||||
|
void debugport_puts(const char *s);
|
||||||
41
src/include/io.h
Normal file
41
src/include/io.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* @file io.h
|
||||||
|
* @brief Low-level x86 port I/O helpers.
|
||||||
|
*
|
||||||
|
* Provides thin wrappers around the x86 IN/OUT instructions so that C code
|
||||||
|
* throughout the kernel can read from and write to I/O ports without
|
||||||
|
* resorting to inline assembly at every call site.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write an 8-bit value to an I/O port.
|
||||||
|
*
|
||||||
|
* Issues an x86 OUT instruction. Execution halts until the peripheral
|
||||||
|
* acknowledges the write (the CPU inserts appropriate bus wait-states).
|
||||||
|
*
|
||||||
|
* @param port 16-bit I/O port address.
|
||||||
|
* @param value 8-bit value to send.
|
||||||
|
*/
|
||||||
|
static inline void outb(uint16_t port, uint8_t value)
|
||||||
|
{
|
||||||
|
__asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read an 8-bit value from an I/O port.
|
||||||
|
*
|
||||||
|
* Issues an x86 IN instruction.
|
||||||
|
*
|
||||||
|
* @param port 16-bit I/O port address.
|
||||||
|
* @return The 8-bit value returned by the peripheral.
|
||||||
|
*/
|
||||||
|
static inline uint8_t inb(uint16_t port)
|
||||||
|
{
|
||||||
|
uint8_t value;
|
||||||
|
__asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
176
src/interrupts.S
176
src/interrupts.S
@@ -1,176 +0,0 @@
|
|||||||
.section .text
|
|
||||||
.global idt_load
|
|
||||||
.type idt_load, @function
|
|
||||||
idt_load:
|
|
||||||
mov 4(%esp), %eax
|
|
||||||
lidt (%eax)
|
|
||||||
ret
|
|
||||||
|
|
||||||
/* Common ISR stub */
|
|
||||||
isr_common_stub:
|
|
||||||
pusha
|
|
||||||
|
|
||||||
/* Save segment registers */
|
|
||||||
mov %ds, %ax
|
|
||||||
push %eax
|
|
||||||
|
|
||||||
/* Load kernel data segment */
|
|
||||||
mov $0x10, %ax
|
|
||||||
mov %ax, %ds
|
|
||||||
mov %ax, %es
|
|
||||||
mov %ax, %fs
|
|
||||||
mov %ax, %gs
|
|
||||||
|
|
||||||
/* Push pointer to stack structure as argument for C handler */
|
|
||||||
push %esp
|
|
||||||
call isr_handler
|
|
||||||
add $4, %esp /* Clean up pushed pointer */
|
|
||||||
|
|
||||||
/* Restore segment registers */
|
|
||||||
pop %eax
|
|
||||||
mov %eax, %ds
|
|
||||||
mov %eax, %es
|
|
||||||
mov %eax, %fs
|
|
||||||
mov %eax, %gs
|
|
||||||
|
|
||||||
popa
|
|
||||||
add $8, %esp /* Cleans up error code and ISR number */
|
|
||||||
iret
|
|
||||||
|
|
||||||
/* Macro for exceptions with NO Error Code */
|
|
||||||
.macro ISR_NOERRCODE num
|
|
||||||
.global isr\num
|
|
||||||
.type isr\num, @function
|
|
||||||
isr\num:
|
|
||||||
cli
|
|
||||||
push $0
|
|
||||||
push $\num
|
|
||||||
jmp isr_common_stub
|
|
||||||
.endm
|
|
||||||
|
|
||||||
/* Macro for exceptions WITH Error Code */
|
|
||||||
.macro ISR_ERRCODE num
|
|
||||||
.global isr\num
|
|
||||||
.type isr\num, @function
|
|
||||||
isr\num:
|
|
||||||
cli
|
|
||||||
push $\num
|
|
||||||
jmp isr_common_stub
|
|
||||||
.endm
|
|
||||||
|
|
||||||
ISR_NOERRCODE 0
|
|
||||||
ISR_NOERRCODE 1
|
|
||||||
ISR_NOERRCODE 2
|
|
||||||
ISR_NOERRCODE 3
|
|
||||||
ISR_NOERRCODE 4
|
|
||||||
ISR_NOERRCODE 5
|
|
||||||
ISR_NOERRCODE 6
|
|
||||||
ISR_NOERRCODE 7
|
|
||||||
ISR_ERRCODE 8
|
|
||||||
ISR_NOERRCODE 9
|
|
||||||
ISR_ERRCODE 10
|
|
||||||
ISR_ERRCODE 11
|
|
||||||
ISR_ERRCODE 12
|
|
||||||
ISR_ERRCODE 13
|
|
||||||
ISR_ERRCODE 14
|
|
||||||
ISR_NOERRCODE 15
|
|
||||||
ISR_NOERRCODE 16
|
|
||||||
ISR_ERRCODE 17
|
|
||||||
ISR_NOERRCODE 18
|
|
||||||
ISR_NOERRCODE 19
|
|
||||||
ISR_NOERRCODE 20
|
|
||||||
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
|
|
||||||
ISR_NOERRCODE 31
|
|
||||||
/* Macro for IRQs (Hardware Interrupts) */
|
|
||||||
.macro ISR_IRQ num, idt_index
|
|
||||||
.global irq\num
|
|
||||||
.type irq\num, @function
|
|
||||||
irq\num:
|
|
||||||
cli
|
|
||||||
push $0
|
|
||||||
push $\idt_index
|
|
||||||
jmp isr_common_stub
|
|
||||||
.endm
|
|
||||||
|
|
||||||
/* Hardware Interrupts */
|
|
||||||
ISR_IRQ 0, 32
|
|
||||||
ISR_IRQ 1, 33
|
|
||||||
ISR_IRQ 2, 34
|
|
||||||
ISR_IRQ 3, 35
|
|
||||||
ISR_IRQ 4, 36
|
|
||||||
ISR_IRQ 5, 37
|
|
||||||
ISR_IRQ 6, 38
|
|
||||||
ISR_IRQ 7, 39
|
|
||||||
ISR_IRQ 8, 40
|
|
||||||
ISR_IRQ 9, 41
|
|
||||||
ISR_IRQ 10, 42
|
|
||||||
ISR_IRQ 11, 43
|
|
||||||
ISR_IRQ 12, 44
|
|
||||||
ISR_IRQ 13, 45
|
|
||||||
ISR_IRQ 14, 46
|
|
||||||
ISR_IRQ 15, 47
|
|
||||||
|
|
||||||
/*
|
|
||||||
* INT 0x80 - System call entry point.
|
|
||||||
* Uses the same isr_common_stub so the register layout matches registers_t.
|
|
||||||
*/
|
|
||||||
.global isr128
|
|
||||||
.type isr128, @function
|
|
||||||
isr128:
|
|
||||||
cli
|
|
||||||
push $0 /* Fake error code */
|
|
||||||
push $0x80 /* Interrupt number 128 */
|
|
||||||
jmp isr_common_stub
|
|
||||||
|
|
||||||
/*
|
|
||||||
* tss_flush - Load the Task Register with the TSS selector.
|
|
||||||
* TSS is GDT entry 5, selector = 5*8 = 0x28. With RPL=0: 0x28.
|
|
||||||
*/
|
|
||||||
.global tss_flush
|
|
||||||
.type tss_flush, @function
|
|
||||||
tss_flush:
|
|
||||||
mov $0x28, %ax
|
|
||||||
ltr %ax
|
|
||||||
ret
|
|
||||||
|
|
||||||
/*
|
|
||||||
* enter_usermode - Switch to Ring 3 user mode via iret.
|
|
||||||
* void enter_usermode(uint32_t eip, uint32_t esp);
|
|
||||||
*
|
|
||||||
* Builds an iret frame on the stack:
|
|
||||||
* SS = 0x23 (user data)
|
|
||||||
* ESP = user stack pointer
|
|
||||||
* EFLAGS = IF=1
|
|
||||||
* CS = 0x1B (user code)
|
|
||||||
* EIP = user entry point
|
|
||||||
*/
|
|
||||||
.global enter_usermode
|
|
||||||
.type enter_usermode, @function
|
|
||||||
enter_usermode:
|
|
||||||
mov 4(%esp), %ecx /* user EIP */
|
|
||||||
mov 8(%esp), %edx /* user ESP */
|
|
||||||
|
|
||||||
/* Set data segment registers to user data segment */
|
|
||||||
mov $0x23, %ax
|
|
||||||
mov %ax, %ds
|
|
||||||
mov %ax, %es
|
|
||||||
mov %ax, %fs
|
|
||||||
mov %ax, %gs
|
|
||||||
|
|
||||||
/* Build iret frame */
|
|
||||||
push $0x23 /* SS (user data) */
|
|
||||||
push %edx /* ESP (user stack) */
|
|
||||||
pushf /* EFLAGS */
|
|
||||||
orl $0x200, (%esp) /* Ensure IF (Interrupt Flag) is set */
|
|
||||||
push $0x1B /* CS (user code) */
|
|
||||||
push %ecx /* EIP (entry point) */
|
|
||||||
iret
|
|
||||||
87
src/isr.c
87
src/isr.c
@@ -1,87 +0,0 @@
|
|||||||
#include "isr.h"
|
|
||||||
#include "pic.h"
|
|
||||||
#include "process.h"
|
|
||||||
#include "syscall.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/* Forward declaration for kernel panic or similar */
|
|
||||||
void offset_print(const char *str);
|
|
||||||
void print_hex(uint32_t val);
|
|
||||||
|
|
||||||
/* Exception messages */
|
|
||||||
char *exception_messages[] = {
|
|
||||||
"Division By Zero",
|
|
||||||
"Debug",
|
|
||||||
"Non Maskable Interrupt",
|
|
||||||
"Breakpoint",
|
|
||||||
"Into Detected Overflow",
|
|
||||||
"Out of Bounds",
|
|
||||||
"Invalid Opcode",
|
|
||||||
"No Coprocessor",
|
|
||||||
"Double Fault",
|
|
||||||
"Coprocessor Segment Overrun",
|
|
||||||
"Bad TSS",
|
|
||||||
"Segment Not Present",
|
|
||||||
"Stack Fault",
|
|
||||||
"General Protection Fault",
|
|
||||||
"Page Fault",
|
|
||||||
"Unknown Interrupt",
|
|
||||||
"Coprocessor Fault",
|
|
||||||
"Alignment Check",
|
|
||||||
"Machine Check",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved",
|
|
||||||
"Reserved"
|
|
||||||
};
|
|
||||||
|
|
||||||
void isr_handler(registers_t *regs)
|
|
||||||
{
|
|
||||||
/* System call (INT 0x80) */
|
|
||||||
if (regs->int_no == 0x80) {
|
|
||||||
syscall_handler(regs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hardware interrupts (IRQs 0-15, mapped to vectors 32-47) */
|
|
||||||
if (regs->int_no >= 32 && regs->int_no < 48) {
|
|
||||||
/* Send EOI to PIC (IRQ number 0-15) */
|
|
||||||
pic_send_eoi(regs->int_no - 32);
|
|
||||||
|
|
||||||
if (regs->int_no == 32) {
|
|
||||||
/* Timer tick - invoke scheduler */
|
|
||||||
schedule_tick(regs);
|
|
||||||
} else if (regs->int_no == 33) {
|
|
||||||
/* Keyboard */
|
|
||||||
offset_print("Keyboard IRQ!\n");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CPU exceptions (vectors 0-31) */
|
|
||||||
offset_print("received interrupt: ");
|
|
||||||
print_hex(regs->int_no);
|
|
||||||
offset_print("\n");
|
|
||||||
|
|
||||||
if (regs->int_no < 32)
|
|
||||||
{
|
|
||||||
offset_print(exception_messages[regs->int_no]);
|
|
||||||
offset_print(" Exception. System Halted!\n");
|
|
||||||
offset_print(" EIP: ");
|
|
||||||
print_hex(regs->eip);
|
|
||||||
offset_print(" CS: ");
|
|
||||||
print_hex(regs->cs);
|
|
||||||
offset_print(" ERR: ");
|
|
||||||
print_hex(regs->err_code);
|
|
||||||
for (;;) ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
18
src/isr.h
18
src/isr.h
@@ -1,18 +0,0 @@
|
|||||||
#ifndef ISR_H
|
|
||||||
#define ISR_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef 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 (if applicable)
|
|
||||||
uint32_t eip, cs, eflags, useresp, ss; // Pushed by the processor automatically
|
|
||||||
} registers_t;
|
|
||||||
|
|
||||||
typedef void (*isr_t)(registers_t*);
|
|
||||||
|
|
||||||
void isr_handler(registers_t* regs);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
0
src/it/.gitkeep
Normal file
0
src/it/.gitkeep
Normal file
203
src/kernel.c
203
src/kernel.c
@@ -1,168 +1,45 @@
|
|||||||
#include <multiboot2.h>
|
/**
|
||||||
|
* @file kernel.c
|
||||||
|
* @brief Kernel entry point.
|
||||||
|
*
|
||||||
|
* Called by the Multiboot bootstrap (boot/boot.s) after the BSS has been
|
||||||
|
* zeroed and a temporary stack has been established. The bootloader passes
|
||||||
|
* the Multiboot magic value and a pointer to the Multiboot info structure.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <debugport.h>
|
||||||
#include "gdt.h"
|
|
||||||
#include "idt.h"
|
|
||||||
#include "pic.h"
|
|
||||||
#include "port_io.h"
|
|
||||||
#include "pmm.h"
|
|
||||||
#include "paging.h"
|
|
||||||
#include "kmalloc.h"
|
|
||||||
#include "driver.h"
|
|
||||||
#include "vga.h"
|
|
||||||
#include "tss.h"
|
|
||||||
#include "syscall.h"
|
|
||||||
#include "process.h"
|
|
||||||
|
|
||||||
void offset_print(const char *str)
|
/** Magic value the bootloader stores in EAX before jumping to _start. */
|
||||||
|
#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002U
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Main kernel entry point.
|
||||||
|
*
|
||||||
|
* Validates the Multiboot magic so we know the bootloader handed us a proper
|
||||||
|
* info structure, then writes a greeting to the QEMU debug port so the host
|
||||||
|
* can confirm the kernel reached C code successfully.
|
||||||
|
*
|
||||||
|
* @param multiboot_magic 0x2BADB002 when booted by a Multiboot-compliant
|
||||||
|
* bootloader (e.g. GRUB).
|
||||||
|
* @param multiboot_info Physical address of the multiboot_info_t structure
|
||||||
|
* provided by the bootloader.
|
||||||
|
*/
|
||||||
|
void kernel_main(uint32_t multiboot_magic, uint32_t multiboot_info)
|
||||||
{
|
{
|
||||||
while (*str) {
|
(void)multiboot_info;
|
||||||
outb(0xE9, *str);
|
|
||||||
str++;
|
/* Announce ourselves on the QEMU debug port.
|
||||||
|
* QEMU must be launched with -debugcon stdio for this to appear. */
|
||||||
|
if (multiboot_magic == MULTIBOOT_BOOTLOADER_MAGIC) {
|
||||||
|
debugport_puts("Hello, World!\n");
|
||||||
|
debugport_puts("ClaudeOS kernel booted successfully.\n");
|
||||||
|
} else {
|
||||||
|
debugport_puts("ERROR: bad Multiboot magic — not booted by GRUB?\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spin forever. Subsequent commits will add real work here. */
|
||||||
|
for (;;) {
|
||||||
|
__asm__ volatile("hlt");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_hex(uint32_t val)
|
|
||||||
{
|
|
||||||
const char *hex = "0123456789ABCDEF";
|
|
||||||
outb(0xE9, '0');
|
|
||||||
outb(0xE9, 'x');
|
|
||||||
for (int i = 28; i >= 0; i -= 4) {
|
|
||||||
outb(0xE9, hex[(val >> i) & 0xF]);
|
|
||||||
}
|
|
||||||
outb(0xE9, '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
void kernel_main(uint32_t magic, uint32_t addr) {
|
|
||||||
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) {
|
|
||||||
offset_print("Invalid magic number: ");
|
|
||||||
print_hex(magic);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset_print("Booting...\n");
|
|
||||||
|
|
||||||
init_gdt();
|
|
||||||
offset_print("GDT initialized\n");
|
|
||||||
|
|
||||||
init_idt();
|
|
||||||
offset_print("IDT initialized\n");
|
|
||||||
|
|
||||||
init_pic();
|
|
||||||
offset_print("PIC initialized\n");
|
|
||||||
|
|
||||||
init_pmm(addr);
|
|
||||||
offset_print("PMM initialized\n");
|
|
||||||
|
|
||||||
init_paging();
|
|
||||||
offset_print("Paging initialized\n");
|
|
||||||
|
|
||||||
/* Test paging: allocate a page and write to it */
|
|
||||||
void *test_page = paging_alloc_page();
|
|
||||||
if (test_page) {
|
|
||||||
offset_print("Allocated virtual page at: ");
|
|
||||||
print_hex((uint32_t)test_page);
|
|
||||||
*((volatile uint32_t *)test_page) = 0xDEADBEEF;
|
|
||||||
offset_print("Virtual page write/read OK\n");
|
|
||||||
paging_free_page(test_page);
|
|
||||||
} else {
|
|
||||||
offset_print("FAILED to allocate virtual page\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
init_kmalloc();
|
|
||||||
offset_print("Memory allocator initialized\n");
|
|
||||||
|
|
||||||
init_tss();
|
|
||||||
offset_print("TSS initialized\n");
|
|
||||||
|
|
||||||
init_syscalls();
|
|
||||||
offset_print("Syscalls initialized\n");
|
|
||||||
|
|
||||||
init_process();
|
|
||||||
offset_print("Process subsystem initialized\n");
|
|
||||||
|
|
||||||
init_drivers();
|
|
||||||
offset_print("Drivers initialized\n");
|
|
||||||
|
|
||||||
/* Show memory statistics and boot progress on VGA */
|
|
||||||
vga_show_mem_stats();
|
|
||||||
vga_puts("Boot complete.\n\n");
|
|
||||||
|
|
||||||
/* Test kmalloc/kfree */
|
|
||||||
uint32_t *test_alloc = (uint32_t *)kmalloc(64);
|
|
||||||
if (test_alloc) {
|
|
||||||
*test_alloc = 42;
|
|
||||||
offset_print("kmalloc(64) = ");
|
|
||||||
print_hex((uint32_t)test_alloc);
|
|
||||||
kfree(test_alloc);
|
|
||||||
offset_print("kfree OK\n");
|
|
||||||
} else {
|
|
||||||
offset_print("FAILED to kmalloc\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Create a minimal test user-mode program.
|
|
||||||
* This is flat binary machine code that calls SYS_WRITE then SYS_EXIT.
|
|
||||||
*
|
|
||||||
* The program writes "Hello from Ring 3!\n" to stdout (fd=1) via INT 0x80,
|
|
||||||
* then exits with code 42.
|
|
||||||
*
|
|
||||||
* Assembly (i386):
|
|
||||||
* ; SYS_WRITE(1, msg, 19)
|
|
||||||
* mov eax, 1 ; SYS_WRITE
|
|
||||||
* mov ebx, 1 ; fd = stdout
|
|
||||||
* call next ; get EIP for position-independent addressing
|
|
||||||
* next:
|
|
||||||
* pop ecx ; ECX = address of 'next'
|
|
||||||
* add ecx, 25 ; ECX = address of message string (offset to msg)
|
|
||||||
* mov edx, 19 ; len = 19
|
|
||||||
* int 0x80
|
|
||||||
* ; SYS_EXIT(42)
|
|
||||||
* mov eax, 0 ; SYS_EXIT
|
|
||||||
* mov ebx, 42 ; code = 42
|
|
||||||
* int 0x80
|
|
||||||
* ; loop forever (shouldn't reach here)
|
|
||||||
* jmp $
|
|
||||||
* msg:
|
|
||||||
* db "Hello from Ring 3!", 10
|
|
||||||
*/
|
|
||||||
static const uint8_t user_program[] = {
|
|
||||||
0xB8, 0x01, 0x00, 0x00, 0x00, /* mov eax, 1 (SYS_WRITE) */
|
|
||||||
0xBB, 0x01, 0x00, 0x00, 0x00, /* mov ebx, 1 (stdout) */
|
|
||||||
0xE8, 0x00, 0x00, 0x00, 0x00, /* call next (push EIP) */
|
|
||||||
/* next: offset 15 */
|
|
||||||
0x59, /* pop ecx */
|
|
||||||
0x83, 0xC1, 0x19, /* add ecx, 25 (offset from 'next' to msg) */
|
|
||||||
0xBA, 0x13, 0x00, 0x00, 0x00, /* mov edx, 19 (length) */
|
|
||||||
0xCD, 0x80, /* int 0x80 */
|
|
||||||
/* SYS_EXIT(42): offset 26 */
|
|
||||||
0xB8, 0x00, 0x00, 0x00, 0x00, /* mov eax, 0 (SYS_EXIT) */
|
|
||||||
0xBB, 0x2A, 0x00, 0x00, 0x00, /* mov ebx, 42 (exit code) */
|
|
||||||
0xCD, 0x80, /* int 0x80 */
|
|
||||||
0xEB, 0xFE, /* jmp $ (infinite loop safety) */
|
|
||||||
/* msg: offset 40 */
|
|
||||||
'H','e','l','l','o',' ','f','r','o','m',' ',
|
|
||||||
'R','i','n','g',' ','3','!','\n'
|
|
||||||
};
|
|
||||||
|
|
||||||
int32_t pid = process_create("init", user_program, sizeof(user_program));
|
|
||||||
if (pid > 0) {
|
|
||||||
offset_print("Created init process, pid=");
|
|
||||||
print_hex((uint32_t)pid);
|
|
||||||
|
|
||||||
/* Enable interrupts before entering user mode */
|
|
||||||
asm volatile("sti");
|
|
||||||
offset_print("Interrupts enabled\n");
|
|
||||||
|
|
||||||
/* Enter user mode - does not return */
|
|
||||||
process_run_first();
|
|
||||||
} else {
|
|
||||||
offset_print("FAILED to create init process\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Enable interrupts */
|
|
||||||
asm volatile("sti");
|
|
||||||
offset_print("Interrupts enabled\n");
|
|
||||||
|
|
||||||
offset_print("Hello, world\n");
|
|
||||||
}
|
|
||||||
|
|||||||
253
src/kmalloc.c
253
src/kmalloc.c
@@ -1,253 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file kmalloc.c
|
|
||||||
* @brief Kernel memory allocator implementation.
|
|
||||||
*
|
|
||||||
* A simple first-fit free-list allocator. Memory is obtained from the paging
|
|
||||||
* subsystem in 4 KiB page increments. The allocator maintains a linked list
|
|
||||||
* of free blocks. On allocation, the first block large enough is split if
|
|
||||||
* needed. On free, adjacent blocks are coalesced to reduce fragmentation.
|
|
||||||
*
|
|
||||||
* The allocator metadata (block headers) are stored inline at the beginning
|
|
||||||
* of each block, so the minimum allocation overhead is sizeof(block_header_t).
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "kmalloc.h"
|
|
||||||
#include "paging.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/* Debug print helpers defined in kernel.c */
|
|
||||||
extern void offset_print(const char *str);
|
|
||||||
extern void print_hex(uint32_t val);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Block header stored at the start of every allocated or free block.
|
|
||||||
* The usable memory starts immediately after this header.
|
|
||||||
*/
|
|
||||||
typedef struct block_header {
|
|
||||||
uint32_t size; /**< Size of the usable area (excludes header). */
|
|
||||||
uint32_t magic; /**< Magic number for integrity checking. */
|
|
||||||
struct block_header *next; /**< Next block in the free list (free blocks only). */
|
|
||||||
uint8_t is_free; /**< 1 if block is free, 0 if allocated. */
|
|
||||||
} block_header_t;
|
|
||||||
|
|
||||||
/** Magic value to detect heap corruption. */
|
|
||||||
#define BLOCK_MAGIC 0xCAFEBABE
|
|
||||||
|
|
||||||
/** Minimum block size to avoid excessive fragmentation. */
|
|
||||||
#define MIN_BLOCK_SIZE 16
|
|
||||||
|
|
||||||
/** Alignment for all allocations (8-byte aligned). */
|
|
||||||
#define ALIGNMENT 8
|
|
||||||
|
|
||||||
/** Round up to alignment boundary. */
|
|
||||||
#define ALIGN_UP(x, a) (((x) + (a) - 1) & ~((a) - 1))
|
|
||||||
|
|
||||||
/** Head of the free list. */
|
|
||||||
static block_header_t *free_list = NULL;
|
|
||||||
|
|
||||||
/** Number of pages currently allocated for the heap. */
|
|
||||||
static uint32_t heap_pages = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request a new page from the paging subsystem and add it to the free list.
|
|
||||||
*
|
|
||||||
* @return Pointer to the new block header, or NULL on failure.
|
|
||||||
*/
|
|
||||||
static block_header_t *request_page(void) {
|
|
||||||
void *page = paging_alloc_page();
|
|
||||||
if (!page) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
heap_pages++;
|
|
||||||
|
|
||||||
block_header_t *block = (block_header_t *)page;
|
|
||||||
block->size = 4096 - sizeof(block_header_t);
|
|
||||||
block->magic = BLOCK_MAGIC;
|
|
||||||
block->next = NULL;
|
|
||||||
block->is_free = 1;
|
|
||||||
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a block into the free list, maintaining address order.
|
|
||||||
* Then attempt to coalesce with adjacent blocks.
|
|
||||||
*
|
|
||||||
* @param block The block to insert.
|
|
||||||
*/
|
|
||||||
static void insert_free_block(block_header_t *block) {
|
|
||||||
block->is_free = 1;
|
|
||||||
|
|
||||||
/* Find insertion point (maintain address order for coalescing) */
|
|
||||||
block_header_t *prev = NULL;
|
|
||||||
block_header_t *curr = free_list;
|
|
||||||
|
|
||||||
while (curr && curr < block) {
|
|
||||||
prev = curr;
|
|
||||||
curr = curr->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Insert between prev and curr */
|
|
||||||
block->next = curr;
|
|
||||||
if (prev) {
|
|
||||||
prev->next = block;
|
|
||||||
} else {
|
|
||||||
free_list = block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Coalesce with next block if adjacent */
|
|
||||||
if (block->next) {
|
|
||||||
uint8_t *block_end = (uint8_t *)block + sizeof(block_header_t) + block->size;
|
|
||||||
if (block_end == (uint8_t *)block->next) {
|
|
||||||
block->size += sizeof(block_header_t) + block->next->size;
|
|
||||||
block->next = block->next->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Coalesce with previous block if adjacent */
|
|
||||||
if (prev) {
|
|
||||||
uint8_t *prev_end = (uint8_t *)prev + sizeof(block_header_t) + prev->size;
|
|
||||||
if (prev_end == (uint8_t *)block) {
|
|
||||||
prev->size += sizeof(block_header_t) + block->size;
|
|
||||||
prev->next = block->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split a block if it is large enough to hold the requested size
|
|
||||||
* plus a new block header and minimum block.
|
|
||||||
*
|
|
||||||
* @param block The block to potentially split.
|
|
||||||
* @param size The requested allocation size (aligned).
|
|
||||||
*/
|
|
||||||
static void split_block(block_header_t *block, uint32_t size) {
|
|
||||||
uint32_t remaining = block->size - size - sizeof(block_header_t);
|
|
||||||
|
|
||||||
if (remaining >= MIN_BLOCK_SIZE) {
|
|
||||||
block_header_t *new_block = (block_header_t *)((uint8_t *)block + sizeof(block_header_t) + size);
|
|
||||||
new_block->size = remaining;
|
|
||||||
new_block->magic = BLOCK_MAGIC;
|
|
||||||
new_block->is_free = 1;
|
|
||||||
new_block->next = block->next;
|
|
||||||
|
|
||||||
block->size = size;
|
|
||||||
block->next = new_block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_kmalloc(void) {
|
|
||||||
/* Allocate the initial heap page */
|
|
||||||
block_header_t *initial = request_page();
|
|
||||||
if (!initial) {
|
|
||||||
offset_print(" KMALLOC: FATAL - could not allocate initial heap page\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
free_list = initial;
|
|
||||||
offset_print(" KMALLOC: initialized with 1 page\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void *kmalloc(size_t size) {
|
|
||||||
if (size == 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Align the requested size */
|
|
||||||
size = ALIGN_UP(size, ALIGNMENT);
|
|
||||||
|
|
||||||
/* First-fit search through free list */
|
|
||||||
block_header_t *prev = NULL;
|
|
||||||
block_header_t *curr = free_list;
|
|
||||||
|
|
||||||
while (curr) {
|
|
||||||
if (curr->magic != BLOCK_MAGIC) {
|
|
||||||
offset_print(" KMALLOC: HEAP CORRUPTION detected!\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curr->is_free && curr->size >= size) {
|
|
||||||
/* Found a suitable block */
|
|
||||||
split_block(curr, size);
|
|
||||||
|
|
||||||
/* Remove from free list */
|
|
||||||
if (prev) {
|
|
||||||
prev->next = curr->next;
|
|
||||||
} else {
|
|
||||||
free_list = curr->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
curr->is_free = 0;
|
|
||||||
curr->next = NULL;
|
|
||||||
|
|
||||||
/* Return pointer past the header */
|
|
||||||
return (void *)((uint8_t *)curr + sizeof(block_header_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
prev = curr;
|
|
||||||
curr = curr->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* No suitable block found; request a new page */
|
|
||||||
block_header_t *new_block = request_page();
|
|
||||||
if (!new_block) {
|
|
||||||
offset_print(" KMALLOC: out of memory\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the new page is large enough, use it directly */
|
|
||||||
if (new_block->size >= size) {
|
|
||||||
split_block(new_block, size);
|
|
||||||
|
|
||||||
/* If there's a remainder, add it to the free list */
|
|
||||||
if (new_block->next && new_block->next->is_free) {
|
|
||||||
insert_free_block(new_block->next);
|
|
||||||
}
|
|
||||||
|
|
||||||
new_block->is_free = 0;
|
|
||||||
new_block->next = NULL;
|
|
||||||
return (void *)((uint8_t *)new_block + sizeof(block_header_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page too small (shouldn't happen for reasonable sizes) */
|
|
||||||
offset_print(" KMALLOC: requested size too large for single page\n");
|
|
||||||
insert_free_block(new_block);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void kfree(void *ptr) {
|
|
||||||
if (!ptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get the block header */
|
|
||||||
block_header_t *block = (block_header_t *)((uint8_t *)ptr - sizeof(block_header_t));
|
|
||||||
|
|
||||||
if (block->magic != BLOCK_MAGIC) {
|
|
||||||
offset_print(" KMALLOC: kfree() invalid pointer or corruption!\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block->is_free) {
|
|
||||||
offset_print(" KMALLOC: kfree() double free detected!\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
insert_free_block(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *kcalloc(size_t count, size_t size) {
|
|
||||||
size_t total = count * size;
|
|
||||||
|
|
||||||
/* Overflow check */
|
|
||||||
if (count != 0 && total / count != size) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *ptr = kmalloc(total);
|
|
||||||
if (ptr) {
|
|
||||||
memset(ptr, 0, total);
|
|
||||||
}
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file kmalloc.h
|
|
||||||
* @brief Kernel memory allocator.
|
|
||||||
*
|
|
||||||
* Provides malloc/free for the kernel. Internally uses the paging subsystem
|
|
||||||
* to ensure returned addresses are backed by physical RAM. Uses a simple
|
|
||||||
* first-fit free-list allocator with block splitting and coalescing.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef KMALLOC_H
|
|
||||||
#define KMALLOC_H
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the kernel memory allocator.
|
|
||||||
*
|
|
||||||
* Must be called after init_paging(). Allocates the initial heap pages.
|
|
||||||
*/
|
|
||||||
void init_kmalloc(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocate a block of kernel memory.
|
|
||||||
*
|
|
||||||
* @param size Number of bytes to allocate.
|
|
||||||
* @return Pointer to the allocated memory, or NULL on failure.
|
|
||||||
*/
|
|
||||||
void *kmalloc(size_t size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Free a previously allocated block of kernel memory.
|
|
||||||
*
|
|
||||||
* @param ptr Pointer returned by kmalloc. NULL is safely ignored.
|
|
||||||
*/
|
|
||||||
void kfree(void *ptr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocate a block of zero-initialized kernel memory.
|
|
||||||
*
|
|
||||||
* @param count Number of elements.
|
|
||||||
* @param size Size of each element in bytes.
|
|
||||||
* @return Pointer to the allocated and zeroed memory, or NULL on failure.
|
|
||||||
*/
|
|
||||||
void *kcalloc(size_t count, size_t size);
|
|
||||||
|
|
||||||
#endif /* KMALLOC_H */
|
|
||||||
@@ -1,40 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Linker script for the ClaudeOS kernel.
|
||||||
|
*
|
||||||
|
* The kernel is loaded by GRUB at physical address 0x00100000 (1 MiB).
|
||||||
|
* All kernel sections follow immediately after the multiboot header so that
|
||||||
|
* GRUB can locate it within the first 8 KiB of the ELF image as required by
|
||||||
|
* the Multiboot specification.
|
||||||
|
*
|
||||||
|
* Symbol _bss_start / _bss_end are exported so the bootstrap can zero the
|
||||||
|
* BSS segment before jumping to C code. _kernel_end marks the first byte
|
||||||
|
* after the kernel image, which the physical memory allocator will use as
|
||||||
|
* its starting address.
|
||||||
|
*/
|
||||||
|
|
||||||
|
OUTPUT_FORMAT(elf32-i386)
|
||||||
|
OUTPUT_ARCH(i386)
|
||||||
ENTRY(_start)
|
ENTRY(_start)
|
||||||
|
|
||||||
SECTIONS
|
SECTIONS
|
||||||
{
|
{
|
||||||
. = 1M;
|
. = 0x00100000;
|
||||||
|
|
||||||
_kernel_start = .;
|
/* The multiboot header must appear within the first 8 KiB. */
|
||||||
|
.multiboot ALIGN(4) :
|
||||||
|
{
|
||||||
|
KEEP(*(.multiboot))
|
||||||
|
}
|
||||||
|
|
||||||
.text BLOCK(4K) : ALIGN(4K)
|
.text ALIGN(4096) :
|
||||||
{
|
{
|
||||||
KEEP(*(.multiboot))
|
*(.text*)
|
||||||
*(.text)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.rodata BLOCK(4K) : ALIGN(4K)
|
.rodata ALIGN(4096) :
|
||||||
{
|
{
|
||||||
*(.rodata)
|
*(.rodata*)
|
||||||
*(.rodata.*)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.drivers BLOCK(4K) : ALIGN(4K)
|
.data ALIGN(4096) :
|
||||||
{
|
{
|
||||||
__drivers_start = .;
|
*(.data*)
|
||||||
KEEP(*(.drivers))
|
}
|
||||||
__drivers_end = .;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data BLOCK(4K) : ALIGN(4K)
|
.bss ALIGN(4096) :
|
||||||
{
|
{
|
||||||
*(.data)
|
_bss_start = .;
|
||||||
}
|
*(COMMON)
|
||||||
|
*(.bss*)
|
||||||
.bss BLOCK(4K) : ALIGN(4K)
|
_bss_end = .;
|
||||||
{
|
}
|
||||||
*(COMMON)
|
|
||||||
*(.bss)
|
|
||||||
}
|
|
||||||
|
|
||||||
_kernel_end = .;
|
_kernel_end = .;
|
||||||
}
|
}
|
||||||
|
|||||||
326
src/paging.c
326
src/paging.c
@@ -1,326 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file paging.c
|
|
||||||
* @brief Virtual memory paging subsystem implementation.
|
|
||||||
*
|
|
||||||
* Implements two-level x86 paging (page directory + page tables) with 4 KiB
|
|
||||||
* pages. At initialization, all detected physical memory is identity-mapped
|
|
||||||
* so that physical addresses equal virtual addresses. Drivers and the kernel
|
|
||||||
* can then allocate additional virtual pages as needed.
|
|
||||||
*
|
|
||||||
* The kernel heap region starts at KERNEL_HEAP_START (0xD0000000) and grows
|
|
||||||
* upward as pages are requested through paging_alloc_page().
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "paging.h"
|
|
||||||
#include "pmm.h"
|
|
||||||
#include "port_io.h"
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/* Debug print helpers defined in kernel.c */
|
|
||||||
extern void offset_print(const char *str);
|
|
||||||
extern void print_hex(uint32_t val);
|
|
||||||
|
|
||||||
/** Kernel heap starts at 0xD0000000 (above the 0xC0000000 higher-half region). */
|
|
||||||
#define KERNEL_HEAP_START 0xD0000000
|
|
||||||
/** Kernel heap ends at 0xF0000000 (768 MiB of virtual space for kernel heap). */
|
|
||||||
#define KERNEL_HEAP_END 0xF0000000
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The page directory. Must be page-aligned (4 KiB).
|
|
||||||
* Each entry either points to a page table or is zero (not present).
|
|
||||||
*/
|
|
||||||
static uint32_t page_directory[PAGE_ENTRIES] __attribute__((aligned(4096)));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Storage for page tables. We pre-allocate enough for identity mapping.
|
|
||||||
* For a system with up to 4 GiB, we'd need 1024 page tables, but we
|
|
||||||
* only use these for the first 16 MiB during early boot. Additional page
|
|
||||||
* tables are allocated from the PMM as needed.
|
|
||||||
*
|
|
||||||
* The first 16 MiB must be statically allocated because the PMM bitmap
|
|
||||||
* itself lives in BSS within this region.
|
|
||||||
*/
|
|
||||||
#define STATIC_PT_COUNT 4
|
|
||||||
static uint32_t static_page_tables[STATIC_PT_COUNT][PAGE_ENTRIES] __attribute__((aligned(4096)));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamically allocated page tables for memory above 16 MiB.
|
|
||||||
* Before paging is enabled, we allocate these from the PMM and store
|
|
||||||
* their physical addresses here so we can access them after paging.
|
|
||||||
*/
|
|
||||||
#define MAX_DYNAMIC_PT 256
|
|
||||||
static uint32_t *dynamic_page_tables[MAX_DYNAMIC_PT];
|
|
||||||
static uint32_t dynamic_pt_count = 0;
|
|
||||||
|
|
||||||
/** Next virtual address to hand out from the kernel heap. */
|
|
||||||
static uint32_t heap_next = KERNEL_HEAP_START;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush a single TLB entry for the given virtual address.
|
|
||||||
*
|
|
||||||
* @param vaddr The virtual address whose TLB entry to invalidate.
|
|
||||||
*/
|
|
||||||
static inline void tlb_flush_single(uint32_t vaddr) {
|
|
||||||
__asm__ volatile("invlpg (%0)" : : "r"(vaddr) : "memory");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reload CR3 to flush the entire TLB.
|
|
||||||
*/
|
|
||||||
static inline void tlb_flush_all(void) {
|
|
||||||
uint32_t cr3;
|
|
||||||
__asm__ volatile("mov %%cr3, %0" : "=r"(cr3));
|
|
||||||
__asm__ volatile("mov %0, %%cr3" : : "r"(cr3) : "memory");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a page table for a given page directory index.
|
|
||||||
*
|
|
||||||
* If the page directory entry is not present, allocate a new page table
|
|
||||||
* from the PMM and install it.
|
|
||||||
*
|
|
||||||
* @param pd_idx Page directory index (0–1023).
|
|
||||||
* @param create If non-zero, create the page table if it doesn't exist.
|
|
||||||
* @return Pointer to the page table, or NULL if not present and !create.
|
|
||||||
*/
|
|
||||||
static uint32_t *get_page_table(uint32_t pd_idx, int create) {
|
|
||||||
if (page_directory[pd_idx] & PAGE_PRESENT) {
|
|
||||||
return (uint32_t *)(page_directory[pd_idx] & 0xFFFFF000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!create) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate a new page table from the PMM */
|
|
||||||
phys_addr_t pt_phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
|
||||||
if (pt_phys == 0) {
|
|
||||||
offset_print(" PAGING: FATAL - could not allocate page table\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Zero the new page table */
|
|
||||||
memset((void *)pt_phys, 0, 4096);
|
|
||||||
|
|
||||||
/* Install it in the page directory */
|
|
||||||
page_directory[pd_idx] = pt_phys | PAGE_PRESENT | PAGE_WRITE;
|
|
||||||
|
|
||||||
return (uint32_t *)pt_phys;
|
|
||||||
}
|
|
||||||
|
|
||||||
void paging_map_page(uint32_t vaddr, uint32_t paddr, uint32_t flags) {
|
|
||||||
uint32_t pd_idx = PD_INDEX(vaddr);
|
|
||||||
uint32_t pt_idx = PT_INDEX(vaddr);
|
|
||||||
|
|
||||||
uint32_t *pt = get_page_table(pd_idx, 1);
|
|
||||||
if (!pt) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pt[pt_idx] = (paddr & 0xFFFFF000) | (flags & 0xFFF);
|
|
||||||
tlb_flush_single(vaddr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void paging_unmap_page(uint32_t vaddr) {
|
|
||||||
uint32_t pd_idx = PD_INDEX(vaddr);
|
|
||||||
uint32_t pt_idx = PT_INDEX(vaddr);
|
|
||||||
|
|
||||||
uint32_t *pt = get_page_table(pd_idx, 0);
|
|
||||||
if (!pt) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pt[pt_idx] = 0;
|
|
||||||
tlb_flush_single(vaddr);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t paging_get_physical(uint32_t vaddr) {
|
|
||||||
uint32_t pd_idx = PD_INDEX(vaddr);
|
|
||||||
uint32_t pt_idx = PT_INDEX(vaddr);
|
|
||||||
|
|
||||||
uint32_t *pt = get_page_table(pd_idx, 0);
|
|
||||||
if (!pt) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(pt[pt_idx] & PAGE_PRESENT)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (pt[pt_idx] & 0xFFFFF000) | (vaddr & 0xFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *paging_alloc_page(void) {
|
|
||||||
if (heap_next >= KERNEL_HEAP_END) {
|
|
||||||
offset_print(" PAGING: kernel heap exhausted\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate a physical page */
|
|
||||||
phys_addr_t paddr = pmm_alloc_page(PMM_ZONE_NORMAL);
|
|
||||||
if (paddr == 0) {
|
|
||||||
offset_print(" PAGING: out of physical memory\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Map it into the kernel heap */
|
|
||||||
uint32_t vaddr = heap_next;
|
|
||||||
paging_map_page(vaddr, paddr, PAGE_PRESENT | PAGE_WRITE);
|
|
||||||
heap_next += 4096;
|
|
||||||
|
|
||||||
return (void *)vaddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void paging_free_page(void *vaddr) {
|
|
||||||
uint32_t va = (uint32_t)vaddr;
|
|
||||||
|
|
||||||
/* Look up the physical address before unmapping */
|
|
||||||
uint32_t paddr = paging_get_physical(va);
|
|
||||||
if (paddr == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Unmap the virtual page */
|
|
||||||
paging_unmap_page(va);
|
|
||||||
|
|
||||||
/* Return the physical page to the PMM */
|
|
||||||
pmm_free_page(paddr & 0xFFFFF000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_paging(void) {
|
|
||||||
/* 1. Zero the page directory */
|
|
||||||
memset(page_directory, 0, sizeof(page_directory));
|
|
||||||
|
|
||||||
/* 2. Identity map the first 16 MiB using static page tables.
|
|
||||||
* This covers the kernel (loaded at 1 MiB), the PMM bitmap (in BSS),
|
|
||||||
* the stack, and typical BIOS/device regions.
|
|
||||||
* Each page table maps 4 MiB (1024 entries × 4 KiB).
|
|
||||||
*/
|
|
||||||
for (uint32_t i = 0; i < STATIC_PT_COUNT; i++) {
|
|
||||||
memset(static_page_tables[i], 0, sizeof(static_page_tables[i]));
|
|
||||||
|
|
||||||
for (uint32_t j = 0; j < PAGE_ENTRIES; j++) {
|
|
||||||
uint32_t paddr = (i * PAGE_ENTRIES + j) * 4096;
|
|
||||||
static_page_tables[i][j] = paddr | PAGE_PRESENT | PAGE_WRITE;
|
|
||||||
}
|
|
||||||
|
|
||||||
page_directory[i] = (uint32_t)static_page_tables[i] | PAGE_PRESENT | PAGE_WRITE;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset_print(" PAGING: identity mapped first 16 MiB\n");
|
|
||||||
|
|
||||||
/* 3. Identity map memory above 16 MiB using dynamically allocated page
|
|
||||||
* tables. We do this BEFORE enabling paging, so physical addresses
|
|
||||||
* are still directly accessible.
|
|
||||||
*
|
|
||||||
* mem_upper is in KiB and starts at 1 MiB, so total memory is
|
|
||||||
* approximately (mem_upper + 1024) KiB.
|
|
||||||
*/
|
|
||||||
uint32_t mem_kb = pmm_get_memory_size() + 1024; /* total memory in KiB */
|
|
||||||
uint32_t total_bytes = mem_kb * 1024;
|
|
||||||
uint32_t pd_entries_needed = (total_bytes + (4 * 1024 * 1024 - 1)) / (4 * 1024 * 1024);
|
|
||||||
|
|
||||||
if (pd_entries_needed > PAGE_ENTRIES) {
|
|
||||||
pd_entries_needed = PAGE_ENTRIES;
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic_pt_count = 0;
|
|
||||||
for (uint32_t i = STATIC_PT_COUNT; i < pd_entries_needed; i++) {
|
|
||||||
if (dynamic_pt_count >= MAX_DYNAMIC_PT) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate a page for this page table from the DMA zone,
|
|
||||||
* since we need it to be accessible before paging is enabled
|
|
||||||
* (i.e., within the first 16 MiB identity map won't help for
|
|
||||||
* the page table itself, but we haven't enabled paging yet so
|
|
||||||
* ALL physical memory is accessible). */
|
|
||||||
phys_addr_t pt_phys = pmm_alloc_page(PMM_ZONE_DMA);
|
|
||||||
if (pt_phys == 0) {
|
|
||||||
pt_phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
|
||||||
}
|
|
||||||
if (pt_phys == 0) {
|
|
||||||
offset_print(" PAGING: WARNING - could not alloc page table\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t *pt = (uint32_t *)pt_phys;
|
|
||||||
dynamic_page_tables[dynamic_pt_count++] = pt;
|
|
||||||
|
|
||||||
/* Fill the page table with identity mappings */
|
|
||||||
for (uint32_t j = 0; j < PAGE_ENTRIES; j++) {
|
|
||||||
uint32_t paddr = (i * PAGE_ENTRIES + j) * 4096;
|
|
||||||
pt[j] = paddr | PAGE_PRESENT | PAGE_WRITE;
|
|
||||||
}
|
|
||||||
|
|
||||||
page_directory[i] = pt_phys | PAGE_PRESENT | PAGE_WRITE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dynamic_pt_count > 0) {
|
|
||||||
offset_print(" PAGING: identity mapped ");
|
|
||||||
print_hex(pd_entries_needed * 4);
|
|
||||||
offset_print(" PAGING: MiB total using ");
|
|
||||||
print_hex(dynamic_pt_count);
|
|
||||||
offset_print(" PAGING: additional page tables\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 4. Load the page directory into CR3 */
|
|
||||||
__asm__ volatile("mov %0, %%cr3" : : "r"(page_directory) : "memory");
|
|
||||||
|
|
||||||
/* 5. Enable paging by setting bit 31 (PG) of CR0 */
|
|
||||||
uint32_t cr0;
|
|
||||||
__asm__ volatile("mov %%cr0, %0" : "=r"(cr0));
|
|
||||||
cr0 |= 0x80000000;
|
|
||||||
__asm__ volatile("mov %0, %%cr0" : : "r"(cr0) : "memory");
|
|
||||||
|
|
||||||
offset_print(" PAGING: enabled\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t paging_get_directory_phys(void) {
|
|
||||||
return (uint32_t)page_directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t paging_clone_directory(void) {
|
|
||||||
/* Allocate a new page for the directory */
|
|
||||||
phys_addr_t new_dir_phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
|
||||||
if (new_dir_phys == 0) {
|
|
||||||
offset_print(" PAGING: cannot allocate page directory\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t *new_dir = (uint32_t *)new_dir_phys;
|
|
||||||
|
|
||||||
/* Copy all entries from the kernel page directory.
|
|
||||||
* This shares the kernel-space mappings (identity map, kernel heap)
|
|
||||||
* with the new process. User-space mappings will be added separately. */
|
|
||||||
memcpy(new_dir, page_directory, 4096);
|
|
||||||
|
|
||||||
return new_dir_phys;
|
|
||||||
}
|
|
||||||
|
|
||||||
void paging_map_page_in(uint32_t *pd, uint32_t vaddr, uint32_t paddr, uint32_t flags) {
|
|
||||||
uint32_t pd_idx = PD_INDEX(vaddr);
|
|
||||||
uint32_t pt_idx = PT_INDEX(vaddr);
|
|
||||||
|
|
||||||
uint32_t *pt;
|
|
||||||
if (pd[pd_idx] & PAGE_PRESENT) {
|
|
||||||
pt = (uint32_t *)(pd[pd_idx] & 0xFFFFF000);
|
|
||||||
} else {
|
|
||||||
/* Allocate a new page table */
|
|
||||||
phys_addr_t pt_phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
|
||||||
if (pt_phys == 0) {
|
|
||||||
offset_print(" PAGING: cannot allocate page table for process\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
memset((void *)pt_phys, 0, 4096);
|
|
||||||
pd[pd_idx] = pt_phys | PAGE_PRESENT | PAGE_WRITE | PAGE_USER;
|
|
||||||
pt = (uint32_t *)pt_phys;
|
|
||||||
}
|
|
||||||
|
|
||||||
pt[pt_idx] = (paddr & 0xFFFFF000) | (flags & 0xFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
void paging_switch_directory(uint32_t phys_addr) {
|
|
||||||
__asm__ volatile("mov %0, %%cr3" : : "r"(phys_addr) : "memory");
|
|
||||||
}
|
|
||||||
118
src/paging.h
118
src/paging.h
@@ -1,118 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file paging.h
|
|
||||||
* @brief Virtual memory paging subsystem.
|
|
||||||
*
|
|
||||||
* Provides page directory and page table management for the x86 two-level
|
|
||||||
* paging scheme (no PAE). Allows mapping and unmapping individual 4 KiB pages,
|
|
||||||
* as well as allocating virtual pages backed by physical memory.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PAGING_H
|
|
||||||
#define PAGING_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/** Page table entry flags. */
|
|
||||||
#define PAGE_PRESENT 0x001 /**< Page is present in memory. */
|
|
||||||
#define PAGE_WRITE 0x002 /**< Page is writable. */
|
|
||||||
#define PAGE_USER 0x004 /**< Page is accessible from ring 3. */
|
|
||||||
#define PAGE_WRITETHROUGH 0x008 /**< Write-through caching. */
|
|
||||||
#define PAGE_NOCACHE 0x010 /**< Disable caching for this page. */
|
|
||||||
#define PAGE_ACCESSED 0x020 /**< CPU has read from this page. */
|
|
||||||
#define PAGE_DIRTY 0x040 /**< CPU has written to this page. */
|
|
||||||
#define PAGE_SIZE_4MB 0x080 /**< 4 MiB page (page directory only). */
|
|
||||||
|
|
||||||
/** Number of entries in a page directory or page table. */
|
|
||||||
#define PAGE_ENTRIES 1024
|
|
||||||
|
|
||||||
/** Extract the page directory index from a virtual address. */
|
|
||||||
#define PD_INDEX(vaddr) (((uint32_t)(vaddr) >> 22) & 0x3FF)
|
|
||||||
|
|
||||||
/** Extract the page table index from a virtual address. */
|
|
||||||
#define PT_INDEX(vaddr) (((uint32_t)(vaddr) >> 12) & 0x3FF)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the paging subsystem.
|
|
||||||
*
|
|
||||||
* Sets up a page directory, identity-maps all detected physical memory,
|
|
||||||
* and enables paging by writing to CR3 and CR0.
|
|
||||||
*/
|
|
||||||
void init_paging(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map a virtual address to a physical address with the given flags.
|
|
||||||
*
|
|
||||||
* If no page table exists for the virtual address range, one is allocated
|
|
||||||
* from the PMM automatically.
|
|
||||||
*
|
|
||||||
* @param vaddr Virtual address (must be page-aligned).
|
|
||||||
* @param paddr Physical address (must be page-aligned).
|
|
||||||
* @param flags Page flags (PAGE_PRESENT | PAGE_WRITE | ...).
|
|
||||||
*/
|
|
||||||
void paging_map_page(uint32_t vaddr, uint32_t paddr, uint32_t flags);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unmap a virtual address, freeing the mapping but not the physical page.
|
|
||||||
*
|
|
||||||
* @param vaddr Virtual address to unmap (must be page-aligned).
|
|
||||||
*/
|
|
||||||
void paging_unmap_page(uint32_t vaddr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocate a new virtual page backed by physical memory.
|
|
||||||
*
|
|
||||||
* Finds a free virtual address, allocates a physical page from the PMM,
|
|
||||||
* and creates a mapping.
|
|
||||||
*
|
|
||||||
* @return Virtual address of the allocated page, or NULL on failure.
|
|
||||||
*/
|
|
||||||
void *paging_alloc_page(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Free a virtual page, unmapping it and returning the physical page to the PMM.
|
|
||||||
*
|
|
||||||
* @param vaddr Virtual address of the page to free.
|
|
||||||
*/
|
|
||||||
void paging_free_page(void *vaddr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up the physical address mapped to a virtual address.
|
|
||||||
*
|
|
||||||
* @param vaddr Virtual address to translate.
|
|
||||||
* @return Physical address, or 0 if the page is not mapped.
|
|
||||||
*/
|
|
||||||
uint32_t paging_get_physical(uint32_t vaddr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the physical address of the kernel page directory.
|
|
||||||
*
|
|
||||||
* @return Physical address of the page directory.
|
|
||||||
*/
|
|
||||||
uint32_t paging_get_directory_phys(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clone the kernel page directory for a new process.
|
|
||||||
* Copies all kernel-space entries; user-space entries are empty.
|
|
||||||
*
|
|
||||||
* @return Physical address of the new page directory, or 0 on failure.
|
|
||||||
*/
|
|
||||||
uint32_t paging_clone_directory(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map a page in a specific page directory (not necessarily the active one).
|
|
||||||
*
|
|
||||||
* @param pd Pointer to the page directory (virtual/identity-mapped address).
|
|
||||||
* @param vaddr Virtual address to map (page-aligned).
|
|
||||||
* @param paddr Physical address to map to (page-aligned).
|
|
||||||
* @param flags Page flags.
|
|
||||||
*/
|
|
||||||
void paging_map_page_in(uint32_t *pd, uint32_t vaddr, uint32_t paddr, uint32_t flags);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch the active page directory.
|
|
||||||
*
|
|
||||||
* @param phys_addr Physical address of the page directory.
|
|
||||||
*/
|
|
||||||
void paging_switch_directory(uint32_t phys_addr);
|
|
||||||
|
|
||||||
#endif /* PAGING_H */
|
|
||||||
95
src/pic.c
95
src/pic.c
@@ -1,95 +0,0 @@
|
|||||||
#include "pic.h"
|
|
||||||
#include "port_io.h"
|
|
||||||
|
|
||||||
#define PIC1_COMMAND 0x20
|
|
||||||
#define PIC1_DATA 0x21
|
|
||||||
#define PIC2_COMMAND 0xA0
|
|
||||||
#define PIC2_DATA 0xA1
|
|
||||||
|
|
||||||
#define ICW1_ICW4 0x01 /* ICW4 (not) needed */
|
|
||||||
#define ICW1_SINGLE 0x02 /* Single (cascade) mode */
|
|
||||||
#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */
|
|
||||||
#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */
|
|
||||||
#define ICW1_INIT 0x10 /* Initialization - required! */
|
|
||||||
|
|
||||||
#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */
|
|
||||||
#define ICW4_AUTO 0x02 /* Auto (normal) EOI */
|
|
||||||
#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */
|
|
||||||
#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */
|
|
||||||
#define ICW4_SFNM 0x10 /* Special fully nested (not) */
|
|
||||||
|
|
||||||
#define PIC_EOI 0x20
|
|
||||||
|
|
||||||
void pic_send_eoi(uint8_t irq)
|
|
||||||
{
|
|
||||||
if(irq >= 8)
|
|
||||||
outb(PIC2_COMMAND, PIC_EOI);
|
|
||||||
|
|
||||||
outb(PIC1_COMMAND, PIC_EOI);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
reinitialize the PIC controllers, giving them specified vector offsets
|
|
||||||
rather than 8h and 70h, as configured by default
|
|
||||||
*/
|
|
||||||
#define PIC1_OFFSET 0x20
|
|
||||||
#define PIC2_OFFSET 0x28
|
|
||||||
|
|
||||||
void init_pic(void)
|
|
||||||
{
|
|
||||||
unsigned char a1, a2;
|
|
||||||
|
|
||||||
a1 = inb(PIC1_DATA); // save masks
|
|
||||||
a2 = inb(PIC2_DATA);
|
|
||||||
|
|
||||||
outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); // starts the initialization sequence (in cascade mode)
|
|
||||||
io_wait();
|
|
||||||
outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4);
|
|
||||||
io_wait();
|
|
||||||
|
|
||||||
outb(PIC1_DATA, PIC1_OFFSET); // ICW2: Master PIC vector offset
|
|
||||||
io_wait();
|
|
||||||
outb(PIC2_DATA, PIC2_OFFSET); // ICW2: Slave PIC vector offset
|
|
||||||
io_wait();
|
|
||||||
|
|
||||||
outb(PIC1_DATA, 4); // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100)
|
|
||||||
io_wait();
|
|
||||||
outb(PIC2_DATA, 2); // ICW3: tell Slave PIC its cascade identity (0000 0010)
|
|
||||||
io_wait();
|
|
||||||
|
|
||||||
outb(PIC1_DATA, ICW4_8086);
|
|
||||||
io_wait();
|
|
||||||
outb(PIC2_DATA, ICW4_8086);
|
|
||||||
io_wait();
|
|
||||||
|
|
||||||
outb(PIC1_DATA, a1); // restore saved masks.
|
|
||||||
outb(PIC2_DATA, a2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pic_clear_mask(uint8_t irq) {
|
|
||||||
uint16_t port;
|
|
||||||
uint8_t value;
|
|
||||||
|
|
||||||
if(irq < 8) {
|
|
||||||
port = PIC1_DATA;
|
|
||||||
} else {
|
|
||||||
port = PIC2_DATA;
|
|
||||||
irq -= 8;
|
|
||||||
}
|
|
||||||
value = inb(port) & ~(1 << irq);
|
|
||||||
outb(port, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pic_set_mask(uint8_t irq) {
|
|
||||||
uint16_t port;
|
|
||||||
uint8_t value;
|
|
||||||
|
|
||||||
if(irq < 8) {
|
|
||||||
port = PIC1_DATA;
|
|
||||||
} else {
|
|
||||||
port = PIC2_DATA;
|
|
||||||
irq -= 8;
|
|
||||||
}
|
|
||||||
value = inb(port) | (1 << irq);
|
|
||||||
outb(port, value);
|
|
||||||
}
|
|
||||||
11
src/pic.h
11
src/pic.h
@@ -1,11 +0,0 @@
|
|||||||
#ifndef PIC_H
|
|
||||||
#define PIC_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
void init_pic(void);
|
|
||||||
void pic_send_eoi(uint8_t irq);
|
|
||||||
void pic_clear_mask(uint8_t irq);
|
|
||||||
void pic_set_mask(uint8_t irq);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
165
src/pmm.c
165
src/pmm.c
@@ -1,165 +0,0 @@
|
|||||||
#include "pmm.h"
|
|
||||||
#include "port_io.h"
|
|
||||||
#include <multiboot2.h>
|
|
||||||
|
|
||||||
extern uint32_t _kernel_start;
|
|
||||||
extern uint32_t _kernel_end;
|
|
||||||
|
|
||||||
/* Printing helpers (defined in kernel.c) */
|
|
||||||
extern void offset_print(const char *str);
|
|
||||||
extern void print_hex(uint32_t val);
|
|
||||||
|
|
||||||
#define MAX_PHYSICAL_MEMORY 0xFFFFFFFF // 4GB
|
|
||||||
#define TOTAL_FRAMES (MAX_PHYSICAL_MEMORY / PAGE_SIZE + 1)
|
|
||||||
#define FRAMES_PER_INDEX 32
|
|
||||||
#define BITMAP_SIZE ((TOTAL_FRAMES + FRAMES_PER_INDEX - 1) / FRAMES_PER_INDEX)
|
|
||||||
|
|
||||||
static uint32_t memory_bitmap[BITMAP_SIZE];
|
|
||||||
static uint32_t memory_size = 0;
|
|
||||||
// static uint32_t used_frames = 0; // Unused for now
|
|
||||||
|
|
||||||
static void set_frame(phys_addr_t frame_addr) {
|
|
||||||
uint32_t frame = frame_addr / PAGE_SIZE;
|
|
||||||
uint32_t idx = frame / FRAMES_PER_INDEX;
|
|
||||||
uint32_t off = frame % FRAMES_PER_INDEX;
|
|
||||||
memory_bitmap[idx] |= (1 << off);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void clear_frame(phys_addr_t frame_addr) {
|
|
||||||
uint32_t frame = frame_addr / PAGE_SIZE;
|
|
||||||
uint32_t idx = frame / FRAMES_PER_INDEX;
|
|
||||||
uint32_t off = frame % FRAMES_PER_INDEX;
|
|
||||||
memory_bitmap[idx] &= ~(1 << off);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t first_free_frame(size_t start_frame, size_t end_frame) {
|
|
||||||
for (size_t i = start_frame; i < end_frame; i++) {
|
|
||||||
uint32_t idx = i / FRAMES_PER_INDEX;
|
|
||||||
uint32_t off = i % FRAMES_PER_INDEX;
|
|
||||||
if (!(memory_bitmap[idx] & (1 << off))) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (uint32_t)-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_pmm(uint32_t multiboot_addr) {
|
|
||||||
struct multiboot_tag *tag;
|
|
||||||
uint32_t addr = multiboot_addr;
|
|
||||||
|
|
||||||
/* Initialize all memory as used (1) initially */
|
|
||||||
for (uint32_t i = 0; i < BITMAP_SIZE; i++) {
|
|
||||||
memory_bitmap[i] = 0xFFFFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addr & 7) {
|
|
||||||
offset_print(" PMM: multiboot alignment error\n");
|
|
||||||
return; // Alignment issue
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t size = *(uint32_t *)addr;
|
|
||||||
|
|
||||||
uint32_t regions_found = 0;
|
|
||||||
for (tag = (struct multiboot_tag *)(addr + 8);
|
|
||||||
tag->type != MULTIBOOT_TAG_TYPE_END;
|
|
||||||
tag = (struct multiboot_tag *)((multiboot_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;
|
|
||||||
memory_size = meminfo->mem_upper;
|
|
||||||
offset_print(" PMM: mem_upper = ");
|
|
||||||
print_hex(memory_size);
|
|
||||||
} else if (tag->type == MULTIBOOT_TAG_TYPE_MMAP) {
|
|
||||||
multiboot_memory_map_t *mmap;
|
|
||||||
struct multiboot_tag_mmap *tag_mmap = (struct multiboot_tag_mmap *)tag;
|
|
||||||
|
|
||||||
for (mmap = tag_mmap->entries;
|
|
||||||
(multiboot_uint8_t *)mmap < (multiboot_uint8_t *)tag + tag->size;
|
|
||||||
mmap = (multiboot_memory_map_t *)((unsigned long)mmap + tag_mmap->entry_size)) {
|
|
||||||
|
|
||||||
if (mmap->type == MULTIBOOT_MEMORY_AVAILABLE) {
|
|
||||||
uint64_t start = mmap->addr;
|
|
||||||
uint64_t len = mmap->len;
|
|
||||||
uint64_t end = start + len;
|
|
||||||
|
|
||||||
regions_found++;
|
|
||||||
|
|
||||||
if (start % PAGE_SIZE != 0) {
|
|
||||||
start = (start + PAGE_SIZE) & ~(PAGE_SIZE - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint64_t addr_iter = start; addr_iter < end; addr_iter += PAGE_SIZE) {
|
|
||||||
if (addr_iter + PAGE_SIZE <= end && addr_iter < MAX_PHYSICAL_MEMORY) {
|
|
||||||
clear_frame((phys_addr_t)addr_iter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t kstart = (uint32_t)&_kernel_start;
|
|
||||||
uint32_t kend = (uint32_t)&_kernel_end;
|
|
||||||
|
|
||||||
kstart &= ~(PAGE_SIZE - 1);
|
|
||||||
kend = (kend + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
|
|
||||||
|
|
||||||
for (uint32_t i = kstart; i < kend; i += PAGE_SIZE) {
|
|
||||||
set_frame(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark multiboot structure
|
|
||||||
uint32_t mb_start = multiboot_addr;
|
|
||||||
uint32_t mb_end = multiboot_addr + size;
|
|
||||||
mb_start &= ~(PAGE_SIZE - 1);
|
|
||||||
mb_end = (mb_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
|
|
||||||
|
|
||||||
for (uint32_t i = mb_start; i < mb_end; i += PAGE_SIZE) {
|
|
||||||
set_frame(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark first page as used to avoid returning 0 (NULL)
|
|
||||||
set_frame(0);
|
|
||||||
|
|
||||||
offset_print(" PMM: found ");
|
|
||||||
print_hex(regions_found);
|
|
||||||
offset_print(" PMM: available memory regions\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
phys_addr_t pmm_alloc_page(pmm_zone_t zone) {
|
|
||||||
uint32_t start_frame = 0;
|
|
||||||
uint32_t end_frame = BITMAP_SIZE * FRAMES_PER_INDEX;
|
|
||||||
uint32_t frame = (uint32_t)-1;
|
|
||||||
|
|
||||||
// 16MB boundary is at 16*1024*1024 / 4096 = 4096 frames
|
|
||||||
uint32_t dma_limit = 4096;
|
|
||||||
|
|
||||||
if (zone == PMM_ZONE_DMA) {
|
|
||||||
end_frame = dma_limit;
|
|
||||||
frame = first_free_frame(start_frame, end_frame);
|
|
||||||
} else {
|
|
||||||
start_frame = dma_limit;
|
|
||||||
frame = first_free_frame(start_frame, end_frame);
|
|
||||||
|
|
||||||
// Fallback to DMA if NORMAL failed
|
|
||||||
if (frame == (uint32_t)-1) {
|
|
||||||
frame = first_free_frame(0, dma_limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame != (uint32_t)-1) {
|
|
||||||
phys_addr_t addr = frame * PAGE_SIZE;
|
|
||||||
set_frame(addr);
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0; // OOM
|
|
||||||
}
|
|
||||||
|
|
||||||
void pmm_free_page(phys_addr_t addr) {
|
|
||||||
clear_frame(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t pmm_get_memory_size(void) {
|
|
||||||
return memory_size;
|
|
||||||
}
|
|
||||||
42
src/pmm.h
42
src/pmm.h
@@ -1,42 +0,0 @@
|
|||||||
#ifndef PMM_H
|
|
||||||
#define PMM_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
#define PAGE_SIZE 4096
|
|
||||||
|
|
||||||
typedef uintptr_t phys_addr_t;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
PMM_ZONE_DMA, // < 16 MB
|
|
||||||
PMM_ZONE_NORMAL, // Any other memory
|
|
||||||
PMM_ZONE_HIGHMEM // > 4 GB (if 64-bit or PAE, but we are 32-bit for now) - actually sticking to DMA and NORMAL is enough for 32-bit typically
|
|
||||||
} pmm_zone_t;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize the physical memory manager.
|
|
||||||
* @param multiboot_addr Address of the multiboot info structure
|
|
||||||
*/
|
|
||||||
void init_pmm(uint32_t multiboot_addr);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Allocate a physical page.
|
|
||||||
* @param zone The preferred zone to allocate from.
|
|
||||||
* @return Physical address of the allocated page, or 0 if out of memory.
|
|
||||||
*/
|
|
||||||
phys_addr_t pmm_alloc_page(pmm_zone_t zone);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Free a physical page.
|
|
||||||
* @param addr Physical address of the page to free.
|
|
||||||
*/
|
|
||||||
void pmm_free_page(phys_addr_t addr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the total detected upper memory in KiB.
|
|
||||||
* @return Upper memory size in KiB as reported by Multiboot.
|
|
||||||
*/
|
|
||||||
uint32_t pmm_get_memory_size(void);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#ifndef PORT_IO_H
|
|
||||||
#define PORT_IO_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
static inline void outb(uint16_t port, uint8_t val)
|
|
||||||
{
|
|
||||||
asm volatile ( "outb %b0, %w1" : : "a"(val), "Nd"(port) );
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint8_t inb(uint16_t port)
|
|
||||||
{
|
|
||||||
uint8_t ret;
|
|
||||||
asm volatile ( "inb %w1, %b0" : "=a"(ret) : "Nd"(port) );
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void io_wait(void)
|
|
||||||
{
|
|
||||||
/* Port 0x80 is used for 'checkpoints' during POST. */
|
|
||||||
/* The Linux kernel seems to think it is free for use :-/ */
|
|
||||||
asm volatile ( "outb %%al, $0x80" : : "a"(0) );
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
336
src/process.c
336
src/process.c
@@ -1,336 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file process.c
|
|
||||||
* @brief Process management subsystem implementation.
|
|
||||||
*
|
|
||||||
* Manages process creation, context switching, and scheduling.
|
|
||||||
* Each process has its own page directory and kernel stack.
|
|
||||||
* Context switching is done by modifying the interrupt frame registers
|
|
||||||
* on the kernel stack, so the iret restores the next process's state.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "process.h"
|
|
||||||
#include "tss.h"
|
|
||||||
#include "paging.h"
|
|
||||||
#include "pmm.h"
|
|
||||||
#include "kmalloc.h"
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/* Debug print helpers defined in kernel.c */
|
|
||||||
extern void offset_print(const char *str);
|
|
||||||
extern void print_hex(uint32_t val);
|
|
||||||
|
|
||||||
/** Assembly helper: enter user mode for the first process. */
|
|
||||||
extern void enter_usermode(uint32_t eip, uint32_t esp);
|
|
||||||
|
|
||||||
/** Process table. */
|
|
||||||
static process_t process_table[MAX_PROCESSES];
|
|
||||||
|
|
||||||
/** Currently running process, or NULL if none. */
|
|
||||||
static process_t *current_process = NULL;
|
|
||||||
|
|
||||||
/** Next PID to assign. */
|
|
||||||
static uint32_t next_pid = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a free slot in the process table.
|
|
||||||
*
|
|
||||||
* @return Index of a free slot, or -1 if full.
|
|
||||||
*/
|
|
||||||
static int find_free_slot(void) {
|
|
||||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
|
||||||
if (process_table[i].state == PROCESS_UNUSED) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_process(void) {
|
|
||||||
memset(process_table, 0, sizeof(process_table));
|
|
||||||
current_process = NULL;
|
|
||||||
next_pid = 1;
|
|
||||||
offset_print(" PROCESS: subsystem initialized\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t process_create(const char *name, const void *code, uint32_t size) {
|
|
||||||
int slot = find_free_slot();
|
|
||||||
if (slot < 0) {
|
|
||||||
offset_print(" PROCESS: no free slots\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
process_t *proc = &process_table[slot];
|
|
||||||
memset(proc, 0, sizeof(process_t));
|
|
||||||
|
|
||||||
proc->pid = next_pid++;
|
|
||||||
proc->state = PROCESS_READY;
|
|
||||||
|
|
||||||
/* Copy name */
|
|
||||||
uint32_t nlen = strlen(name);
|
|
||||||
if (nlen > 31) nlen = 31;
|
|
||||||
memcpy(proc->name, name, nlen);
|
|
||||||
proc->name[nlen] = '\0';
|
|
||||||
|
|
||||||
/* Allocate kernel stack (full page, not from kmalloc which has header overhead) */
|
|
||||||
void *kstack = paging_alloc_page();
|
|
||||||
if (!kstack) {
|
|
||||||
offset_print(" PROCESS: cannot allocate kernel stack\n");
|
|
||||||
proc->state = PROCESS_UNUSED;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
proc->kernel_stack = (uint32_t)kstack;
|
|
||||||
proc->kernel_stack_top = proc->kernel_stack + 4096;
|
|
||||||
|
|
||||||
/* Clone the kernel page directory */
|
|
||||||
proc->page_directory = paging_clone_directory();
|
|
||||||
if (!proc->page_directory) {
|
|
||||||
offset_print(" PROCESS: cannot clone page directory\n");
|
|
||||||
paging_free_page((void *)proc->kernel_stack);
|
|
||||||
proc->state = PROCESS_UNUSED;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t *pd = (uint32_t *)proc->page_directory;
|
|
||||||
|
|
||||||
/* Map user code pages */
|
|
||||||
uint32_t code_pages = (size + 4095) / 4096;
|
|
||||||
for (uint32_t i = 0; i < code_pages; i++) {
|
|
||||||
phys_addr_t phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
|
||||||
if (phys == 0) {
|
|
||||||
offset_print(" PROCESS: cannot allocate code page\n");
|
|
||||||
/* TODO: clean up already allocated pages */
|
|
||||||
proc->state = PROCESS_UNUSED;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t vaddr = USER_CODE_START + i * 4096;
|
|
||||||
paging_map_page_in(pd, vaddr, phys,
|
|
||||||
PAGE_PRESENT | PAGE_WRITE | PAGE_USER);
|
|
||||||
|
|
||||||
/* Copy code to the physical page (identity-mapped, so phys == virt) */
|
|
||||||
uint32_t offset = i * 4096;
|
|
||||||
uint32_t bytes = size - offset;
|
|
||||||
if (bytes > 4096) bytes = 4096;
|
|
||||||
memcpy((void *)phys, (const uint8_t *)code + offset, bytes);
|
|
||||||
if (bytes < 4096) {
|
|
||||||
memset((void *)(phys + bytes), 0, 4096 - bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Map user stack pages */
|
|
||||||
uint32_t stack_base = USER_STACK_TOP - USER_STACK_PAGES * 4096;
|
|
||||||
for (uint32_t i = 0; i < USER_STACK_PAGES; i++) {
|
|
||||||
phys_addr_t phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
|
||||||
if (phys == 0) {
|
|
||||||
offset_print(" PROCESS: cannot allocate stack page\n");
|
|
||||||
proc->state = PROCESS_UNUSED;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t vaddr = stack_base + i * 4096;
|
|
||||||
paging_map_page_in(pd, vaddr, phys,
|
|
||||||
PAGE_PRESENT | PAGE_WRITE | PAGE_USER);
|
|
||||||
|
|
||||||
/* Zero the stack page */
|
|
||||||
memset((void *)phys, 0, 4096);
|
|
||||||
}
|
|
||||||
|
|
||||||
proc->user_stack = USER_STACK_TOP;
|
|
||||||
proc->entry_point = USER_CODE_START;
|
|
||||||
|
|
||||||
/* Set up saved registers for the first context switch.
|
|
||||||
* When the scheduler loads these into regs on the stack, the
|
|
||||||
* iret will enter user mode at the entry point. */
|
|
||||||
memset(&proc->saved_regs, 0, sizeof(registers_t));
|
|
||||||
proc->saved_regs.ds = 0x23; /* User data segment */
|
|
||||||
proc->saved_regs.ss = 0x23; /* User stack segment */
|
|
||||||
proc->saved_regs.cs = 0x1B; /* User code segment */
|
|
||||||
proc->saved_regs.eip = USER_CODE_START;
|
|
||||||
proc->saved_regs.useresp = USER_STACK_TOP;
|
|
||||||
proc->saved_regs.eflags = 0x202; /* IF=1 (enable interrupts) */
|
|
||||||
proc->saved_regs.esp = USER_STACK_TOP; /* For pusha's ESP */
|
|
||||||
|
|
||||||
offset_print(" PROCESS: created '");
|
|
||||||
offset_print(proc->name);
|
|
||||||
offset_print("' pid=");
|
|
||||||
print_hex(proc->pid);
|
|
||||||
|
|
||||||
return (int32_t)proc->pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void schedule_tick(registers_t *regs) {
|
|
||||||
if (!current_process && next_pid <= 1) {
|
|
||||||
return; /* No processes created yet */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save current process state */
|
|
||||||
if (current_process) {
|
|
||||||
current_process->saved_regs = *regs;
|
|
||||||
if (current_process->state == PROCESS_RUNNING) {
|
|
||||||
current_process->state = PROCESS_READY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find next ready process (round-robin) */
|
|
||||||
uint32_t start_idx = 0;
|
|
||||||
if (current_process) {
|
|
||||||
/* Find current process's index in the table */
|
|
||||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
|
||||||
if (&process_table[i] == current_process) {
|
|
||||||
start_idx = (uint32_t)i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
process_t *next = NULL;
|
|
||||||
for (int i = 1; i <= MAX_PROCESSES; i++) {
|
|
||||||
uint32_t idx = (start_idx + (uint32_t)i) % MAX_PROCESSES;
|
|
||||||
if (process_table[idx].state == PROCESS_READY) {
|
|
||||||
next = &process_table[idx];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!next) {
|
|
||||||
/* No other process ready */
|
|
||||||
if (current_process && current_process->state == PROCESS_READY) {
|
|
||||||
current_process->state = PROCESS_RUNNING;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Switch to next process */
|
|
||||||
current_process = next;
|
|
||||||
current_process->state = PROCESS_RUNNING;
|
|
||||||
|
|
||||||
/* Update TSS kernel stack for ring transitions */
|
|
||||||
tss_set_kernel_stack(current_process->kernel_stack_top);
|
|
||||||
|
|
||||||
/* Switch page directory */
|
|
||||||
paging_switch_directory(current_process->page_directory);
|
|
||||||
|
|
||||||
/* Restore next process's registers into the interrupt frame */
|
|
||||||
*regs = current_process->saved_regs;
|
|
||||||
}
|
|
||||||
|
|
||||||
void schedule(void) {
|
|
||||||
/* Trigger a yield via software interrupt.
|
|
||||||
* This is a simplified version for voluntary preemption from kernel code. */
|
|
||||||
__asm__ volatile("int $0x80" : : "a"(5)); /* SYS_YIELD = 5 */
|
|
||||||
}
|
|
||||||
|
|
||||||
void process_exit(int32_t code) {
|
|
||||||
if (!current_process) {
|
|
||||||
offset_print(" PROCESS: exit with no current process\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset_print(" PROCESS: pid ");
|
|
||||||
print_hex(current_process->pid);
|
|
||||||
offset_print(" PROCESS: exited with code ");
|
|
||||||
print_hex((uint32_t)code);
|
|
||||||
|
|
||||||
current_process->state = PROCESS_ZOMBIE;
|
|
||||||
current_process->exit_code = code;
|
|
||||||
|
|
||||||
/* Find another process to run.
|
|
||||||
* We construct a minimal register frame to pass to schedule_tick.
|
|
||||||
* Since the process is zombie, schedule_tick won't save its state. */
|
|
||||||
registers_t dummy;
|
|
||||||
memset(&dummy, 0, sizeof(dummy));
|
|
||||||
schedule_tick(&dummy);
|
|
||||||
|
|
||||||
/* If we get here, no other process was ready. Halt. */
|
|
||||||
offset_print(" PROCESS: no processes remaining, halting\n");
|
|
||||||
for (;;) {
|
|
||||||
__asm__ volatile("hlt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
process_t *process_current(void) {
|
|
||||||
return current_process;
|
|
||||||
}
|
|
||||||
|
|
||||||
process_t *process_get(uint32_t pid) {
|
|
||||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
|
||||||
if (process_table[i].state != PROCESS_UNUSED &&
|
|
||||||
process_table[i].pid == pid) {
|
|
||||||
return &process_table[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t process_fork(void) {
|
|
||||||
if (!current_process) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int slot = find_free_slot();
|
|
||||||
if (slot < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
process_t *child = &process_table[slot];
|
|
||||||
memcpy(child, current_process, sizeof(process_t));
|
|
||||||
|
|
||||||
child->pid = next_pid++;
|
|
||||||
child->state = PROCESS_READY;
|
|
||||||
child->parent_pid = current_process->pid;
|
|
||||||
|
|
||||||
/* Allocate a separate kernel stack for the child */
|
|
||||||
void *child_kstack = paging_alloc_page();
|
|
||||||
if (!child_kstack) {
|
|
||||||
child->state = PROCESS_UNUSED;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
child->kernel_stack = (uint32_t)child_kstack;
|
|
||||||
child->kernel_stack_top = child->kernel_stack + 4096;
|
|
||||||
|
|
||||||
/* Clone the page directory */
|
|
||||||
child->page_directory = paging_clone_directory();
|
|
||||||
if (!child->page_directory) {
|
|
||||||
kfree((void *)child->kernel_stack);
|
|
||||||
child->state = PROCESS_UNUSED;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Child's return value is 0 (in EAX) */
|
|
||||||
child->saved_regs.eax = 0;
|
|
||||||
|
|
||||||
/* Parent's return value is child's PID */
|
|
||||||
return (int32_t)child->pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void process_run_first(void) {
|
|
||||||
/* Find the first ready process */
|
|
||||||
process_t *first = NULL;
|
|
||||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
|
||||||
if (process_table[i].state == PROCESS_READY) {
|
|
||||||
first = &process_table[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!first) {
|
|
||||||
offset_print(" PROCESS: no process to run\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
current_process = first;
|
|
||||||
first->state = PROCESS_RUNNING;
|
|
||||||
|
|
||||||
/* Set up TSS for this process */
|
|
||||||
tss_set_kernel_stack(first->kernel_stack_top);
|
|
||||||
|
|
||||||
/* Switch to the process's page directory */
|
|
||||||
paging_switch_directory(first->page_directory);
|
|
||||||
|
|
||||||
offset_print(" PROCESS: entering user mode for '");
|
|
||||||
offset_print(first->name);
|
|
||||||
offset_print("'\n");
|
|
||||||
|
|
||||||
/* Jump to user mode - does not return */
|
|
||||||
enter_usermode(first->entry_point, first->user_stack);
|
|
||||||
}
|
|
||||||
127
src/process.h
127
src/process.h
@@ -1,127 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file process.h
|
|
||||||
* @brief Process management subsystem.
|
|
||||||
*
|
|
||||||
* Manages process creation, scheduling, and context switching.
|
|
||||||
* Supports both kernel-mode and user-mode (Ring 3) processes.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PROCESS_H
|
|
||||||
#define PROCESS_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include "isr.h"
|
|
||||||
|
|
||||||
/** Maximum number of concurrent processes. */
|
|
||||||
#define MAX_PROCESSES 64
|
|
||||||
|
|
||||||
/** Per-process kernel stack size (4 KiB). */
|
|
||||||
#define KERNEL_STACK_SIZE 4096
|
|
||||||
|
|
||||||
/** User-mode stack virtual address (top of user space). */
|
|
||||||
#define USER_STACK_TOP 0xBFFFF000
|
|
||||||
/** User-mode stack size (8 KiB = 2 pages). */
|
|
||||||
#define USER_STACK_PAGES 2
|
|
||||||
|
|
||||||
/** User-mode code start virtual address. */
|
|
||||||
#define USER_CODE_START 0x08048000
|
|
||||||
|
|
||||||
/** Process states. */
|
|
||||||
typedef enum {
|
|
||||||
PROCESS_UNUSED = 0, /**< Slot is free. */
|
|
||||||
PROCESS_READY, /**< Ready to run. */
|
|
||||||
PROCESS_RUNNING, /**< Currently executing. */
|
|
||||||
PROCESS_BLOCKED, /**< Waiting for I/O or event. */
|
|
||||||
PROCESS_ZOMBIE, /**< Finished, waiting for parent to reap. */
|
|
||||||
} process_state_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saved CPU context for context switching.
|
|
||||||
* Uses the full interrupt frame (registers_t from isr.h) so that
|
|
||||||
* saving/restoring context works directly with the ISR stub's stack layout.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process control block (PCB).
|
|
||||||
*/
|
|
||||||
typedef struct process {
|
|
||||||
uint32_t pid; /**< Process ID. */
|
|
||||||
process_state_t state; /**< Current state. */
|
|
||||||
registers_t saved_regs; /**< Saved interrupt frame for context switch. */
|
|
||||||
uint32_t kernel_stack; /**< Base of kernel stack (virtual). */
|
|
||||||
uint32_t kernel_stack_top; /**< Kernel stack top for TSS. */
|
|
||||||
uint32_t page_directory; /**< Physical address of page directory. */
|
|
||||||
uint32_t user_stack; /**< Virtual address of user stack top. */
|
|
||||||
uint32_t entry_point; /**< User-mode entry point. */
|
|
||||||
int32_t exit_code; /**< Exit code (if ZOMBIE). */
|
|
||||||
uint32_t parent_pid; /**< Parent process ID. */
|
|
||||||
char name[32]; /**< Process name (for debugging). */
|
|
||||||
} process_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the process subsystem.
|
|
||||||
* Must be called after paging and kmalloc are initialized.
|
|
||||||
*/
|
|
||||||
void init_process(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new user-mode process from a memory image.
|
|
||||||
*
|
|
||||||
* @param name Process name (for debugging).
|
|
||||||
* @param code Pointer to the code to load.
|
|
||||||
* @param size Size of the code in bytes.
|
|
||||||
* @return PID of the new process, or -1 on failure.
|
|
||||||
*/
|
|
||||||
int32_t process_create(const char *name, const void *code, uint32_t size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Yield the current process to the scheduler.
|
|
||||||
* Called from timer interrupt or voluntarily via SYS_YIELD.
|
|
||||||
* Modifies the registers on the stack to switch context.
|
|
||||||
*
|
|
||||||
* @param regs Pointer to the interrupt frame registers on the kernel stack.
|
|
||||||
*/
|
|
||||||
void schedule_tick(registers_t *regs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Voluntary yield wrapper (triggers schedule via current context).
|
|
||||||
*/
|
|
||||||
void schedule(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exit the current process with the given exit code.
|
|
||||||
*
|
|
||||||
* @param code Exit code.
|
|
||||||
*/
|
|
||||||
void process_exit(int32_t code);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the currently running process.
|
|
||||||
*
|
|
||||||
* @return Pointer to the current process PCB, or NULL if none.
|
|
||||||
*/
|
|
||||||
process_t *process_current(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a process by PID.
|
|
||||||
*
|
|
||||||
* @param pid Process ID.
|
|
||||||
* @return Pointer to the process PCB, or NULL if not found.
|
|
||||||
*/
|
|
||||||
process_t *process_get(uint32_t pid);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fork the current process.
|
|
||||||
*
|
|
||||||
* @return PID of the child in the parent, 0 in the child, -1 on error.
|
|
||||||
*/
|
|
||||||
int32_t process_fork(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the first user-mode process. Does not return if a process is ready.
|
|
||||||
* Should be called after creating at least one process.
|
|
||||||
*/
|
|
||||||
void process_run_first(void);
|
|
||||||
|
|
||||||
#endif /* PROCESS_H */
|
|
||||||
172
src/string.c
172
src/string.c
@@ -1,172 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file string.c
|
|
||||||
* @brief Minimal C string/memory functions for the freestanding kernel.
|
|
||||||
*
|
|
||||||
* These implementations replace the standard library versions since the
|
|
||||||
* kernel is compiled with -ffreestanding and does not link against libc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fill a region of memory with a byte value.
|
|
||||||
*
|
|
||||||
* @param s Destination pointer.
|
|
||||||
* @param c Byte value to fill (only low 8 bits used).
|
|
||||||
* @param n Number of bytes to fill.
|
|
||||||
* @return The destination pointer.
|
|
||||||
*/
|
|
||||||
void *memset(void *s, int c, size_t n) {
|
|
||||||
unsigned char *p = (unsigned char *)s;
|
|
||||||
while (n--) {
|
|
||||||
*p++ = (unsigned char)c;
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy a region of memory (non-overlapping).
|
|
||||||
*
|
|
||||||
* @param dest Destination pointer.
|
|
||||||
* @param src Source pointer.
|
|
||||||
* @param n Number of bytes to copy.
|
|
||||||
* @return The destination pointer.
|
|
||||||
*/
|
|
||||||
void *memcpy(void *dest, const void *src, size_t n) {
|
|
||||||
unsigned char *d = (unsigned char *)dest;
|
|
||||||
const unsigned char *s = (const unsigned char *)src;
|
|
||||||
while (n--) {
|
|
||||||
*d++ = *s++;
|
|
||||||
}
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy a region of memory, handling overlaps correctly.
|
|
||||||
*
|
|
||||||
* @param dest Destination pointer.
|
|
||||||
* @param src Source pointer.
|
|
||||||
* @param n Number of bytes to copy.
|
|
||||||
* @return The destination pointer.
|
|
||||||
*/
|
|
||||||
void *memmove(void *dest, const void *src, size_t n) {
|
|
||||||
unsigned char *d = (unsigned char *)dest;
|
|
||||||
const unsigned char *s = (const unsigned char *)src;
|
|
||||||
|
|
||||||
if (d < s) {
|
|
||||||
while (n--) {
|
|
||||||
*d++ = *s++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
d += n;
|
|
||||||
s += n;
|
|
||||||
while (n--) {
|
|
||||||
*--d = *--s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare two regions of memory.
|
|
||||||
*
|
|
||||||
* @param s1 First memory region.
|
|
||||||
* @param s2 Second memory region.
|
|
||||||
* @param n Number of bytes to compare.
|
|
||||||
* @return 0 if equal, negative if s1 < s2, positive if s1 > s2.
|
|
||||||
*/
|
|
||||||
int memcmp(const void *s1, const void *s2, size_t n) {
|
|
||||||
const unsigned char *a = (const unsigned char *)s1;
|
|
||||||
const unsigned char *b = (const unsigned char *)s2;
|
|
||||||
while (n--) {
|
|
||||||
if (*a != *b) {
|
|
||||||
return *a - *b;
|
|
||||||
}
|
|
||||||
a++;
|
|
||||||
b++;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the length of a null-terminated string.
|
|
||||||
*
|
|
||||||
* @param s The string.
|
|
||||||
* @return Number of characters before the null terminator.
|
|
||||||
*/
|
|
||||||
size_t strlen(const char *s) {
|
|
||||||
size_t len = 0;
|
|
||||||
while (*s++) {
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare two null-terminated strings.
|
|
||||||
*
|
|
||||||
* @param s1 First string.
|
|
||||||
* @param s2 Second string.
|
|
||||||
* @return 0 if equal, negative if s1 < s2, positive if s1 > s2.
|
|
||||||
*/
|
|
||||||
int strcmp(const char *s1, const char *s2) {
|
|
||||||
while (*s1 && *s1 == *s2) {
|
|
||||||
s1++;
|
|
||||||
s2++;
|
|
||||||
}
|
|
||||||
return *(unsigned char *)s1 - *(unsigned char *)s2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare at most n characters of two strings.
|
|
||||||
*
|
|
||||||
* @param s1 First string.
|
|
||||||
* @param s2 Second string.
|
|
||||||
* @param n Maximum number of characters to compare.
|
|
||||||
* @return 0 if equal, negative if s1 < s2, positive if s1 > s2.
|
|
||||||
*/
|
|
||||||
int strncmp(const char *s1, const char *s2, size_t n) {
|
|
||||||
while (n && *s1 && *s1 == *s2) {
|
|
||||||
s1++;
|
|
||||||
s2++;
|
|
||||||
n--;
|
|
||||||
}
|
|
||||||
if (n == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return *(unsigned char *)s1 - *(unsigned char *)s2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy a null-terminated string.
|
|
||||||
*
|
|
||||||
* @param dest Destination buffer (must be large enough).
|
|
||||||
* @param src Source string.
|
|
||||||
* @return The destination pointer.
|
|
||||||
*/
|
|
||||||
char *strcpy(char *dest, const char *src) {
|
|
||||||
char *d = dest;
|
|
||||||
while ((*d++ = *src++))
|
|
||||||
;
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy at most n characters of a string, null-padding if shorter.
|
|
||||||
*
|
|
||||||
* @param dest Destination buffer.
|
|
||||||
* @param src Source string.
|
|
||||||
* @param n Maximum characters to copy.
|
|
||||||
* @return The destination pointer.
|
|
||||||
*/
|
|
||||||
char *strncpy(char *dest, const char *src, size_t n) {
|
|
||||||
char *d = dest;
|
|
||||||
while (n && (*d++ = *src++)) {
|
|
||||||
n--;
|
|
||||||
}
|
|
||||||
while (n--) {
|
|
||||||
*d++ = '\0';
|
|
||||||
}
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
151
src/syscall.c
151
src/syscall.c
@@ -1,151 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file syscall.c
|
|
||||||
* @brief System call handler implementation.
|
|
||||||
*
|
|
||||||
* Dispatches INT 0x80 system calls to the appropriate kernel function.
|
|
||||||
* System call number is in EAX, arguments in EBX, ECX, EDX, ESI, EDI.
|
|
||||||
* Return value is placed in EAX.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "syscall.h"
|
|
||||||
#include "process.h"
|
|
||||||
#include "port_io.h"
|
|
||||||
#include "vga.h"
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
/* Debug print helpers defined in kernel.c */
|
|
||||||
extern void offset_print(const char *str);
|
|
||||||
extern void print_hex(uint32_t val);
|
|
||||||
|
|
||||||
/** IDT gate setup (from idt.c) */
|
|
||||||
extern void set_idt_gate_from_c(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags);
|
|
||||||
|
|
||||||
/** INT 0x80 assembly stub */
|
|
||||||
extern void isr128(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle SYS_EXIT: terminate the current process.
|
|
||||||
*/
|
|
||||||
static int32_t sys_exit(registers_t *regs) {
|
|
||||||
process_exit((int32_t)regs->ebx);
|
|
||||||
/* Never returns */
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle SYS_WRITE: write bytes to a file descriptor.
|
|
||||||
* Currently only supports fd=1 (stdout) -> debug port + VGA.
|
|
||||||
*/
|
|
||||||
static int32_t sys_write(registers_t *regs) {
|
|
||||||
int fd = (int)regs->ebx;
|
|
||||||
const char *buf = (const char *)regs->ecx;
|
|
||||||
uint32_t len = regs->edx;
|
|
||||||
|
|
||||||
if (fd == 1 || fd == 2) {
|
|
||||||
/* stdout or stderr: write to debug port and VGA */
|
|
||||||
for (uint32_t i = 0; i < len; i++) {
|
|
||||||
outb(0xE9, buf[i]);
|
|
||||||
vga_putchar(buf[i]);
|
|
||||||
}
|
|
||||||
return (int32_t)len;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1; /* Invalid fd */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle SYS_READ: read bytes from a file descriptor.
|
|
||||||
* Stub for now.
|
|
||||||
*/
|
|
||||||
static int32_t sys_read(registers_t *regs) {
|
|
||||||
(void)regs;
|
|
||||||
return -1; /* Not implemented */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle SYS_FORK: fork the current process.
|
|
||||||
*/
|
|
||||||
static int32_t sys_fork(registers_t *regs) {
|
|
||||||
(void)regs;
|
|
||||||
return process_fork();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle SYS_GETPID: return the current process ID.
|
|
||||||
*/
|
|
||||||
static int32_t sys_getpid(registers_t *regs) {
|
|
||||||
(void)regs;
|
|
||||||
process_t *cur = process_current();
|
|
||||||
return cur ? (int32_t)cur->pid : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle SYS_YIELD: voluntarily yield the CPU.
|
|
||||||
*/
|
|
||||||
static int32_t sys_yield(registers_t *regs) {
|
|
||||||
(void)regs;
|
|
||||||
schedule();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle SYS_WAITPID: wait for a child to exit.
|
|
||||||
*/
|
|
||||||
static int32_t sys_waitpid(registers_t *regs) {
|
|
||||||
uint32_t pid = regs->ebx;
|
|
||||||
process_t *child = process_get(pid);
|
|
||||||
if (!child) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Busy-wait until child is zombie */
|
|
||||||
while (child->state != PROCESS_ZOMBIE) {
|
|
||||||
schedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t code = child->exit_code;
|
|
||||||
child->state = PROCESS_UNUSED;
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle SYS_EXEC: placeholder.
|
|
||||||
*/
|
|
||||||
static int32_t sys_exec(registers_t *regs) {
|
|
||||||
(void)regs;
|
|
||||||
return -1; /* Not implemented yet */
|
|
||||||
}
|
|
||||||
|
|
||||||
/** System call dispatch table. */
|
|
||||||
typedef int32_t (*syscall_fn)(registers_t *);
|
|
||||||
static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
|
||||||
[SYS_EXIT] = sys_exit,
|
|
||||||
[SYS_WRITE] = sys_write,
|
|
||||||
[SYS_READ] = sys_read,
|
|
||||||
[SYS_FORK] = sys_fork,
|
|
||||||
[SYS_GETPID] = sys_getpid,
|
|
||||||
[SYS_YIELD] = sys_yield,
|
|
||||||
[SYS_WAITPID] = sys_waitpid,
|
|
||||||
[SYS_EXEC] = sys_exec,
|
|
||||||
};
|
|
||||||
|
|
||||||
void syscall_handler(registers_t *regs) {
|
|
||||||
uint32_t num = regs->eax;
|
|
||||||
|
|
||||||
if (num >= NUM_SYSCALLS || !syscall_table[num]) {
|
|
||||||
offset_print(" SYSCALL: invalid syscall ");
|
|
||||||
print_hex(num);
|
|
||||||
regs->eax = (uint32_t)-1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t ret = syscall_table[num](regs);
|
|
||||||
regs->eax = (uint32_t)ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_syscalls(void) {
|
|
||||||
/* Install INT 0x80 as a user-callable interrupt gate.
|
|
||||||
* Flags: 0xEE = Present(1) DPL(11) 0 Type(1110) = 32-bit Interrupt Gate, Ring 3 callable */
|
|
||||||
set_idt_gate_from_c(0x80, (uint32_t)isr128, 0x08, 0xEE);
|
|
||||||
offset_print(" SYSCALL: INT 0x80 installed\n");
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file syscall.h
|
|
||||||
* @brief System call interface.
|
|
||||||
*
|
|
||||||
* Defines system call numbers and the kernel-side handler. User-mode
|
|
||||||
* processes invoke system calls via INT 0x80 with the call number in EAX
|
|
||||||
* and arguments in EBX, ECX, EDX, ESI, EDI.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef SYSCALL_H
|
|
||||||
#define SYSCALL_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "isr.h"
|
|
||||||
|
|
||||||
/** System call numbers. */
|
|
||||||
#define SYS_EXIT 0 /**< Exit the process. Arg: exit code in EBX. */
|
|
||||||
#define SYS_WRITE 1 /**< Write to a file descriptor. fd=EBX, buf=ECX, len=EDX. */
|
|
||||||
#define SYS_READ 2 /**< Read from a file descriptor. fd=EBX, buf=ECX, len=EDX. */
|
|
||||||
#define SYS_FORK 3 /**< Fork the current process. */
|
|
||||||
#define SYS_GETPID 4 /**< Get current process ID. */
|
|
||||||
#define SYS_YIELD 5 /**< Yield the CPU. */
|
|
||||||
#define SYS_WAITPID 6 /**< Wait for a child process. pid=EBX. */
|
|
||||||
#define SYS_EXEC 7 /**< Execute a program. path=EBX, argv=ECX. */
|
|
||||||
|
|
||||||
/** Total number of system calls. */
|
|
||||||
#define NUM_SYSCALLS 8
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the system call handler.
|
|
||||||
* Installs INT 0x80 in the IDT.
|
|
||||||
*/
|
|
||||||
void init_syscalls(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* System call dispatcher (called from the INT 0x80 handler).
|
|
||||||
*
|
|
||||||
* @param regs Register state at the time of the interrupt.
|
|
||||||
*/
|
|
||||||
void syscall_handler(registers_t *regs);
|
|
||||||
|
|
||||||
#endif /* SYSCALL_H */
|
|
||||||
47
src/tss.c
47
src/tss.c
@@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file tss.c
|
|
||||||
* @brief Task State Segment initialization and management.
|
|
||||||
*
|
|
||||||
* Sets up the TSS for ring 3 -> ring 0 transitions. The TSS is installed
|
|
||||||
* as GDT entry 5 (selector 0x28). The GDT must be expanded to 6 entries
|
|
||||||
* to accommodate the TSS descriptor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "tss.h"
|
|
||||||
#include "gdt.h"
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/** The TSS instance. */
|
|
||||||
static tss_entry_t tss;
|
|
||||||
|
|
||||||
/** Assembly function to load the TSS register. */
|
|
||||||
extern void tss_flush(void);
|
|
||||||
|
|
||||||
void init_tss(void) {
|
|
||||||
uint32_t base = (uint32_t)&tss;
|
|
||||||
uint32_t limit = sizeof(tss) - 1;
|
|
||||||
|
|
||||||
/* Clear the TSS */
|
|
||||||
memset(&tss, 0, sizeof(tss));
|
|
||||||
|
|
||||||
/* Set kernel stack segment and pointer.
|
|
||||||
* SS0 = kernel data segment (0x10).
|
|
||||||
* ESP0 will be set per-process during context switches. */
|
|
||||||
tss.ss0 = 0x10;
|
|
||||||
tss.esp0 = 0; /* Will be set before entering user mode */
|
|
||||||
|
|
||||||
/* Set the I/O map base to the size of the TSS, meaning no I/O bitmap. */
|
|
||||||
tss.iomap_base = sizeof(tss);
|
|
||||||
|
|
||||||
/* Install the TSS descriptor in GDT entry 5.
|
|
||||||
* Access byte: 0xE9 = Present(1) DPL(11) 0 Type(1001) = 32-bit TSS (Available)
|
|
||||||
* Granularity: 0x00 = byte granularity, 16-bit */
|
|
||||||
gdt_set_gate(5, base, limit, 0xE9, 0x00);
|
|
||||||
|
|
||||||
/* Load the TSS register */
|
|
||||||
tss_flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
void tss_set_kernel_stack(uint32_t esp0) {
|
|
||||||
tss.esp0 = esp0;
|
|
||||||
}
|
|
||||||
63
src/tss.h
63
src/tss.h
@@ -1,63 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file tss.h
|
|
||||||
* @brief Task State Segment (TSS) definitions.
|
|
||||||
*
|
|
||||||
* The TSS is required by x86 for ring transitions. When a user-mode process
|
|
||||||
* triggers an interrupt, the CPU loads the kernel stack pointer (SS0:ESP0)
|
|
||||||
* from the TSS before pushing the interrupt frame.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef TSS_H
|
|
||||||
#define TSS_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* x86 Task State Segment structure.
|
|
||||||
* Only SS0 and ESP0 are actively used for ring 3 -> ring 0 transitions.
|
|
||||||
*/
|
|
||||||
typedef struct tss_entry {
|
|
||||||
uint32_t prev_tss;
|
|
||||||
uint32_t esp0; /**< Kernel stack pointer (loaded on ring transition). */
|
|
||||||
uint32_t ss0; /**< Kernel stack segment (loaded on ring transition). */
|
|
||||||
uint32_t esp1;
|
|
||||||
uint32_t ss1;
|
|
||||||
uint32_t esp2;
|
|
||||||
uint32_t ss2;
|
|
||||||
uint32_t cr3;
|
|
||||||
uint32_t eip;
|
|
||||||
uint32_t eflags;
|
|
||||||
uint32_t eax;
|
|
||||||
uint32_t ecx;
|
|
||||||
uint32_t edx;
|
|
||||||
uint32_t ebx;
|
|
||||||
uint32_t esp;
|
|
||||||
uint32_t ebp;
|
|
||||||
uint32_t esi;
|
|
||||||
uint32_t edi;
|
|
||||||
uint32_t es;
|
|
||||||
uint32_t cs;
|
|
||||||
uint32_t ss;
|
|
||||||
uint32_t ds;
|
|
||||||
uint32_t fs;
|
|
||||||
uint32_t gs;
|
|
||||||
uint32_t ldt;
|
|
||||||
uint16_t trap;
|
|
||||||
uint16_t iomap_base;
|
|
||||||
} __attribute__((packed)) tss_entry_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the TSS and install it as GDT entry 5 (selector 0x28).
|
|
||||||
* Must be called after init_gdt().
|
|
||||||
*/
|
|
||||||
void init_tss(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the kernel stack pointer in the TSS.
|
|
||||||
* Called during context switches to set the stack for the next process.
|
|
||||||
*
|
|
||||||
* @param esp0 The new kernel stack pointer.
|
|
||||||
*/
|
|
||||||
void tss_set_kernel_stack(uint32_t esp0);
|
|
||||||
|
|
||||||
#endif /* TSS_H */
|
|
||||||
230
src/vga.c
230
src/vga.c
@@ -1,230 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file vga.c
|
|
||||||
* @brief VGA text-mode driver implementation.
|
|
||||||
*
|
|
||||||
* Drives the standard VGA text-mode framebuffer at 0xB8000. The buffer
|
|
||||||
* is an array of 80×25 16-bit values, where the low byte is the ASCII
|
|
||||||
* character and the high byte encodes foreground and background color.
|
|
||||||
*
|
|
||||||
* This driver registers itself via the REGISTER_DRIVER macro and is
|
|
||||||
* automatically discovered during boot.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "vga.h"
|
|
||||||
#include "driver.h"
|
|
||||||
#include "port_io.h"
|
|
||||||
#include "pmm.h"
|
|
||||||
|
|
||||||
/** Base address of the VGA text-mode framebuffer. */
|
|
||||||
#define VGA_BUFFER 0xB8000
|
|
||||||
|
|
||||||
/** Pointer to the VGA framebuffer, treated as an array of uint16_t. */
|
|
||||||
static uint16_t *vga_buffer = (uint16_t *)VGA_BUFFER;
|
|
||||||
|
|
||||||
/** Current cursor row (0-based). */
|
|
||||||
static uint8_t cursor_row = 0;
|
|
||||||
|
|
||||||
/** Current cursor column (0-based). */
|
|
||||||
static uint8_t cursor_col = 0;
|
|
||||||
|
|
||||||
/** Current text attribute byte (foreground | background << 4). */
|
|
||||||
static uint8_t text_attr = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a VGA entry (character + attribute) for the framebuffer.
|
|
||||||
*
|
|
||||||
* @param c ASCII character.
|
|
||||||
* @param attr Color attribute byte.
|
|
||||||
* @return 16-bit VGA entry.
|
|
||||||
*/
|
|
||||||
static inline uint16_t vga_entry(char c, uint8_t attr) {
|
|
||||||
return (uint16_t)c | ((uint16_t)attr << 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a color attribute byte from foreground and background colors.
|
|
||||||
*
|
|
||||||
* @param fg Foreground color (0–15).
|
|
||||||
* @param bg Background color (0–15).
|
|
||||||
* @return Attribute byte.
|
|
||||||
*/
|
|
||||||
static inline uint8_t vga_color(vga_color_t fg, vga_color_t bg) {
|
|
||||||
return (uint8_t)fg | ((uint8_t)bg << 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the hardware cursor position via VGA I/O ports.
|
|
||||||
*/
|
|
||||||
static void update_cursor(void) {
|
|
||||||
uint16_t pos = cursor_row * VGA_WIDTH + cursor_col;
|
|
||||||
|
|
||||||
outb(0x3D4, 0x0F);
|
|
||||||
outb(0x3D5, (uint8_t)(pos & 0xFF));
|
|
||||||
outb(0x3D4, 0x0E);
|
|
||||||
outb(0x3D5, (uint8_t)((pos >> 8) & 0xFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scroll the screen up by one line.
|
|
||||||
*
|
|
||||||
* The top line is discarded, all other lines move up, and the bottom
|
|
||||||
* line is filled with spaces.
|
|
||||||
*/
|
|
||||||
static void scroll(void) {
|
|
||||||
/* Move all lines up by one */
|
|
||||||
for (int i = 0; i < (VGA_HEIGHT - 1) * VGA_WIDTH; i++) {
|
|
||||||
vga_buffer[i] = vga_buffer[i + VGA_WIDTH];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Clear the last line */
|
|
||||||
uint16_t blank = vga_entry(' ', text_attr);
|
|
||||||
for (int i = (VGA_HEIGHT - 1) * VGA_WIDTH; i < VGA_HEIGHT * VGA_WIDTH; i++) {
|
|
||||||
vga_buffer[i] = blank;
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor_row = VGA_HEIGHT - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void vga_clear(void) {
|
|
||||||
uint16_t blank = vga_entry(' ', text_attr);
|
|
||||||
for (int i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++) {
|
|
||||||
vga_buffer[i] = blank;
|
|
||||||
}
|
|
||||||
cursor_row = 0;
|
|
||||||
cursor_col = 0;
|
|
||||||
update_cursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
void vga_set_color(vga_color_t fg, vga_color_t bg) {
|
|
||||||
text_attr = vga_color(fg, bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vga_putchar(char c) {
|
|
||||||
if (c == '\n') {
|
|
||||||
cursor_col = 0;
|
|
||||||
cursor_row++;
|
|
||||||
} else if (c == '\r') {
|
|
||||||
cursor_col = 0;
|
|
||||||
} else if (c == '\t') {
|
|
||||||
cursor_col = (cursor_col + 8) & ~7;
|
|
||||||
if (cursor_col >= VGA_WIDTH) {
|
|
||||||
cursor_col = 0;
|
|
||||||
cursor_row++;
|
|
||||||
}
|
|
||||||
} else if (c == '\b') {
|
|
||||||
if (cursor_col > 0) {
|
|
||||||
cursor_col--;
|
|
||||||
vga_buffer[cursor_row * VGA_WIDTH + cursor_col] = vga_entry(' ', text_attr);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vga_buffer[cursor_row * VGA_WIDTH + cursor_col] = vga_entry(c, text_attr);
|
|
||||||
cursor_col++;
|
|
||||||
if (cursor_col >= VGA_WIDTH) {
|
|
||||||
cursor_col = 0;
|
|
||||||
cursor_row++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cursor_row >= VGA_HEIGHT) {
|
|
||||||
scroll();
|
|
||||||
}
|
|
||||||
|
|
||||||
update_cursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
void vga_puts(const char *str) {
|
|
||||||
while (*str) {
|
|
||||||
vga_putchar(*str);
|
|
||||||
str++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void vga_put_hex(uint32_t val) {
|
|
||||||
const char *hex = "0123456789ABCDEF";
|
|
||||||
vga_putchar('0');
|
|
||||||
vga_putchar('x');
|
|
||||||
for (int i = 28; i >= 0; i -= 4) {
|
|
||||||
vga_putchar(hex[(val >> i) & 0xF]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void vga_put_dec(uint32_t val) {
|
|
||||||
if (val == 0) {
|
|
||||||
vga_putchar('0');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char buf[12];
|
|
||||||
int pos = 0;
|
|
||||||
while (val > 0) {
|
|
||||||
buf[pos++] = '0' + (val % 10);
|
|
||||||
val /= 10;
|
|
||||||
}
|
|
||||||
/* Print in reverse */
|
|
||||||
while (pos > 0) {
|
|
||||||
vga_putchar(buf[--pos]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void vga_show_mem_stats(void) {
|
|
||||||
uint32_t mem_kb = pmm_get_memory_size() + 1024; /* total including lower */
|
|
||||||
|
|
||||||
vga_set_color(VGA_LIGHT_CYAN, VGA_BLACK);
|
|
||||||
vga_puts("=== ClaudeOS Memory Statistics ===\n");
|
|
||||||
|
|
||||||
vga_set_color(VGA_WHITE, VGA_BLACK);
|
|
||||||
vga_puts(" Total RAM: ");
|
|
||||||
vga_put_dec(mem_kb);
|
|
||||||
vga_puts(" KiB (");
|
|
||||||
vga_put_dec(mem_kb / 1024);
|
|
||||||
vga_puts(" MiB)\n");
|
|
||||||
|
|
||||||
vga_puts(" Kernel start: ");
|
|
||||||
extern uint32_t _kernel_start;
|
|
||||||
vga_put_hex((uint32_t)&_kernel_start);
|
|
||||||
vga_puts("\n");
|
|
||||||
|
|
||||||
vga_puts(" Kernel end: ");
|
|
||||||
extern uint32_t _kernel_end;
|
|
||||||
vga_put_hex((uint32_t)&_kernel_end);
|
|
||||||
vga_puts("\n");
|
|
||||||
|
|
||||||
uint32_t kernel_size = (uint32_t)&_kernel_end - (uint32_t)&_kernel_start;
|
|
||||||
vga_puts(" Kernel size: ");
|
|
||||||
vga_put_dec(kernel_size / 1024);
|
|
||||||
vga_puts(" KiB\n");
|
|
||||||
|
|
||||||
vga_set_color(VGA_LIGHT_CYAN, VGA_BLACK);
|
|
||||||
vga_puts("==================================\n");
|
|
||||||
vga_set_color(VGA_LIGHT_GREY, VGA_BLACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Driver registration --- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* VGA probe: always succeeds since VGA text mode is always available
|
|
||||||
* on the target platform (i386).
|
|
||||||
*/
|
|
||||||
static driver_probe_result_t vga_probe(void) {
|
|
||||||
return DRIVER_PROBE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
int vga_init(void) {
|
|
||||||
text_attr = vga_color(VGA_LIGHT_GREY, VGA_BLACK);
|
|
||||||
vga_clear();
|
|
||||||
|
|
||||||
vga_set_color(VGA_LIGHT_GREEN, VGA_BLACK);
|
|
||||||
vga_puts("ClaudeOS v0.1 booting...\n\n");
|
|
||||||
vga_set_color(VGA_LIGHT_GREY, VGA_BLACK);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** VGA driver descriptor. */
|
|
||||||
static const driver_t vga_driver = {
|
|
||||||
.name = "vga",
|
|
||||||
.probe = vga_probe,
|
|
||||||
.init = vga_init,
|
|
||||||
};
|
|
||||||
|
|
||||||
REGISTER_DRIVER(vga_driver);
|
|
||||||
97
src/vga.h
97
src/vga.h
@@ -1,97 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file vga.h
|
|
||||||
* @brief VGA text-mode driver interface.
|
|
||||||
*
|
|
||||||
* Provides functions to write text to the VGA text-mode framebuffer
|
|
||||||
* (typically at 0xB8000). Supports an 80x25 character display with
|
|
||||||
* 16 foreground and 16 background colors.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef VGA_H
|
|
||||||
#define VGA_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
/** VGA color constants. */
|
|
||||||
typedef enum {
|
|
||||||
VGA_BLACK = 0,
|
|
||||||
VGA_BLUE = 1,
|
|
||||||
VGA_GREEN = 2,
|
|
||||||
VGA_CYAN = 3,
|
|
||||||
VGA_RED = 4,
|
|
||||||
VGA_MAGENTA = 5,
|
|
||||||
VGA_BROWN = 6,
|
|
||||||
VGA_LIGHT_GREY = 7,
|
|
||||||
VGA_DARK_GREY = 8,
|
|
||||||
VGA_LIGHT_BLUE = 9,
|
|
||||||
VGA_LIGHT_GREEN = 10,
|
|
||||||
VGA_LIGHT_CYAN = 11,
|
|
||||||
VGA_LIGHT_RED = 12,
|
|
||||||
VGA_LIGHT_MAGENTA = 13,
|
|
||||||
VGA_YELLOW = 14,
|
|
||||||
VGA_WHITE = 15,
|
|
||||||
} vga_color_t;
|
|
||||||
|
|
||||||
/** VGA screen dimensions. */
|
|
||||||
#define VGA_WIDTH 80
|
|
||||||
#define VGA_HEIGHT 25
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the VGA driver.
|
|
||||||
*
|
|
||||||
* Clears the screen and sets the default colors.
|
|
||||||
*
|
|
||||||
* @return 0 on success.
|
|
||||||
*/
|
|
||||||
int vga_init(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the VGA screen.
|
|
||||||
*/
|
|
||||||
void vga_clear(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the foreground and background color for subsequent writes.
|
|
||||||
*
|
|
||||||
* @param fg Foreground color.
|
|
||||||
* @param bg Background color.
|
|
||||||
*/
|
|
||||||
void vga_set_color(vga_color_t fg, vga_color_t bg);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a single character at the current cursor position.
|
|
||||||
*
|
|
||||||
* @param c Character to write.
|
|
||||||
*/
|
|
||||||
void vga_putchar(char c);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a null-terminated string at the current cursor position.
|
|
||||||
*
|
|
||||||
* @param str String to write.
|
|
||||||
*/
|
|
||||||
void vga_puts(const char *str);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a 32-bit value in hexadecimal to the VGA display.
|
|
||||||
*
|
|
||||||
* @param val Value to display.
|
|
||||||
*/
|
|
||||||
void vga_put_hex(uint32_t val);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a 32-bit value in decimal to the VGA display.
|
|
||||||
*
|
|
||||||
* @param val Value to display.
|
|
||||||
*/
|
|
||||||
void vga_put_dec(uint32_t val);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display boot memory statistics on VGA.
|
|
||||||
*
|
|
||||||
* Shows detected memory, kernel size, and free pages.
|
|
||||||
*/
|
|
||||||
void vga_show_mem_stats(void);
|
|
||||||
|
|
||||||
#endif /* VGA_H */
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Build directory
|
|
||||||
BUILD_DIR=build
|
|
||||||
BIN_DIR=$BUILD_DIR/bin
|
|
||||||
RELEASE_DIR=release
|
|
||||||
|
|
||||||
# Check if images exist
|
|
||||||
if [ ! -f "$RELEASE_DIR/claude-os.iso" ]; then
|
|
||||||
echo "Error: claude-os.iso not found!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Testing ISO image..."
|
|
||||||
timeout 5s qemu-system-i386 -cdrom "$RELEASE_DIR/claude-os.iso" -debugcon file:iso_output.txt -display none -no-reboot || true
|
|
||||||
if grep -q "Hello, world" iso_output.txt; then
|
|
||||||
echo "ISO Test Passed!"
|
|
||||||
else
|
|
||||||
echo "ISO Test Failed!"
|
|
||||||
cat iso_output.txt
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "All tests passed!"
|
|
||||||
0
vendor/.gitkeep
vendored
Normal file
0
vendor/.gitkeep
vendored
Normal file
417
vendor/multiboot2.h
vendored
417
vendor/multiboot2.h
vendored
@@ -1,417 +0,0 @@
|
|||||||
/* multiboot2.h - Multiboot 2 header file. */
|
|
||||||
/* Copyright (C) 1999,2003,2007,2008,2009,2010 Free Software Foundation, Inc.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to
|
|
||||||
* deal in the Software without restriction, including without limitation the
|
|
||||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
* sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ANY
|
|
||||||
* DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
||||||
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef MULTIBOOT_HEADER
|
|
||||||
#define MULTIBOOT_HEADER 1
|
|
||||||
|
|
||||||
/* 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
|
|
||||||
|
|
||||||
/* Flags set in the 'flags' member of the multiboot header. */
|
|
||||||
|
|
||||||
#define MULTIBOOT_TAG_ALIGN 8
|
|
||||||
#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
|
|
||||||
#define MULTIBOOT_TAG_TYPE_ELF_SECTIONS 9
|
|
||||||
#define MULTIBOOT_TAG_TYPE_APM 10
|
|
||||||
#define MULTIBOOT_TAG_TYPE_EFI32 11
|
|
||||||
#define MULTIBOOT_TAG_TYPE_EFI64 12
|
|
||||||
#define MULTIBOOT_TAG_TYPE_SMBIOS 13
|
|
||||||
#define MULTIBOOT_TAG_TYPE_ACPI_OLD 14
|
|
||||||
#define MULTIBOOT_TAG_TYPE_ACPI_NEW 15
|
|
||||||
#define MULTIBOOT_TAG_TYPE_NETWORK 16
|
|
||||||
#define MULTIBOOT_TAG_TYPE_EFI_MMAP 17
|
|
||||||
#define MULTIBOOT_TAG_TYPE_EFI_BS 18
|
|
||||||
#define MULTIBOOT_TAG_TYPE_EFI32_IH 19
|
|
||||||
#define MULTIBOOT_TAG_TYPE_EFI64_IH 20
|
|
||||||
#define MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR 21
|
|
||||||
|
|
||||||
#define MULTIBOOT_HEADER_TAG_END 0
|
|
||||||
#define MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST 1
|
|
||||||
#define MULTIBOOT_HEADER_TAG_ADDRESS 2
|
|
||||||
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS 3
|
|
||||||
#define MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS 4
|
|
||||||
#define MULTIBOOT_HEADER_TAG_FRAMEBUFFER 5
|
|
||||||
#define MULTIBOOT_HEADER_TAG_MODULE_ALIGN 6
|
|
||||||
#define MULTIBOOT_HEADER_TAG_EFI_BS 7
|
|
||||||
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI32 8
|
|
||||||
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64 9
|
|
||||||
#define MULTIBOOT_HEADER_TAG_RELOCATABLE 10
|
|
||||||
|
|
||||||
#define MULTIBOOT_ARCHITECTURE_I386 0
|
|
||||||
#define MULTIBOOT_ARCHITECTURE_MIPS32 4
|
|
||||||
#define MULTIBOOT_HEADER_TAG_OPTIONAL 1
|
|
||||||
|
|
||||||
#define MULTIBOOT_LOAD_PREFERENCE_NONE 0
|
|
||||||
#define MULTIBOOT_LOAD_PREFERENCE_LOW 1
|
|
||||||
#define MULTIBOOT_LOAD_PREFERENCE_HIGH 2
|
|
||||||
|
|
||||||
#define MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED 1
|
|
||||||
#define MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED 2
|
|
||||||
|
|
||||||
#ifndef ASM_FILE
|
|
||||||
|
|
||||||
typedef unsigned char multiboot_uint8_t;
|
|
||||||
typedef unsigned short multiboot_uint16_t;
|
|
||||||
typedef unsigned int multiboot_uint32_t;
|
|
||||||
typedef unsigned long long multiboot_uint64_t;
|
|
||||||
|
|
||||||
struct multiboot_header
|
|
||||||
{
|
|
||||||
/* Must be MULTIBOOT_MAGIC - see above. */
|
|
||||||
multiboot_uint32_t magic;
|
|
||||||
|
|
||||||
/* ISA */
|
|
||||||
multiboot_uint32_t architecture;
|
|
||||||
|
|
||||||
/* Total header length. */
|
|
||||||
multiboot_uint32_t header_length;
|
|
||||||
|
|
||||||
/* The above fields plus this one must equal 0 mod 2^32. */
|
|
||||||
multiboot_uint32_t checksum;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_header_tag
|
|
||||||
{
|
|
||||||
multiboot_uint16_t type;
|
|
||||||
multiboot_uint16_t flags;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_header_tag_information_request
|
|
||||||
{
|
|
||||||
multiboot_uint16_t type;
|
|
||||||
multiboot_uint16_t flags;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t requests[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_header_tag_address
|
|
||||||
{
|
|
||||||
multiboot_uint16_t type;
|
|
||||||
multiboot_uint16_t flags;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t header_addr;
|
|
||||||
multiboot_uint32_t load_addr;
|
|
||||||
multiboot_uint32_t load_end_addr;
|
|
||||||
multiboot_uint32_t bss_end_addr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_header_tag_entry_address
|
|
||||||
{
|
|
||||||
multiboot_uint16_t type;
|
|
||||||
multiboot_uint16_t flags;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t entry_addr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_header_tag_console_flags
|
|
||||||
{
|
|
||||||
multiboot_uint16_t type;
|
|
||||||
multiboot_uint16_t flags;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t console_flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_header_tag_framebuffer
|
|
||||||
{
|
|
||||||
multiboot_uint16_t type;
|
|
||||||
multiboot_uint16_t flags;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t width;
|
|
||||||
multiboot_uint32_t height;
|
|
||||||
multiboot_uint32_t depth;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_header_tag_module_align
|
|
||||||
{
|
|
||||||
multiboot_uint16_t type;
|
|
||||||
multiboot_uint16_t flags;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_header_tag_relocatable
|
|
||||||
{
|
|
||||||
multiboot_uint16_t type;
|
|
||||||
multiboot_uint16_t flags;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t min_addr;
|
|
||||||
multiboot_uint32_t max_addr;
|
|
||||||
multiboot_uint32_t align;
|
|
||||||
multiboot_uint32_t preference;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_color
|
|
||||||
{
|
|
||||||
multiboot_uint8_t red;
|
|
||||||
multiboot_uint8_t green;
|
|
||||||
multiboot_uint8_t blue;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_mmap_entry
|
|
||||||
{
|
|
||||||
multiboot_uint64_t addr;
|
|
||||||
multiboot_uint64_t len;
|
|
||||||
#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
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t zero;
|
|
||||||
};
|
|
||||||
typedef struct multiboot_mmap_entry multiboot_memory_map_t;
|
|
||||||
|
|
||||||
struct multiboot_tag
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_string
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
char string[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_module
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t mod_start;
|
|
||||||
multiboot_uint32_t mod_end;
|
|
||||||
char cmdline[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_basic_meminfo
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t mem_lower;
|
|
||||||
multiboot_uint32_t mem_upper;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_bootdev
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t biosdev;
|
|
||||||
multiboot_uint32_t slice;
|
|
||||||
multiboot_uint32_t part;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_mmap
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t entry_size;
|
|
||||||
multiboot_uint32_t entry_version;
|
|
||||||
struct multiboot_mmap_entry entries[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_vbe_info_block
|
|
||||||
{
|
|
||||||
multiboot_uint8_t external_specification[512];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_vbe_mode_info_block
|
|
||||||
{
|
|
||||||
multiboot_uint8_t external_specification[256];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_vbe
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
|
|
||||||
multiboot_uint16_t vbe_mode;
|
|
||||||
multiboot_uint16_t vbe_interface_seg;
|
|
||||||
multiboot_uint16_t vbe_interface_off;
|
|
||||||
multiboot_uint16_t vbe_interface_len;
|
|
||||||
|
|
||||||
struct multiboot_vbe_info_block vbe_control_info;
|
|
||||||
struct multiboot_vbe_mode_info_block vbe_mode_info;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_framebuffer_common
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
|
|
||||||
multiboot_uint64_t framebuffer_addr;
|
|
||||||
multiboot_uint32_t framebuffer_pitch;
|
|
||||||
multiboot_uint32_t framebuffer_width;
|
|
||||||
multiboot_uint32_t framebuffer_height;
|
|
||||||
multiboot_uint8_t framebuffer_bpp;
|
|
||||||
#define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0
|
|
||||||
#define MULTIBOOT_FRAMEBUFFER_TYPE_RGB 1
|
|
||||||
#define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT 2
|
|
||||||
multiboot_uint8_t framebuffer_type;
|
|
||||||
multiboot_uint16_t reserved;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_framebuffer
|
|
||||||
{
|
|
||||||
struct multiboot_tag_framebuffer_common common;
|
|
||||||
|
|
||||||
union
|
|
||||||
{
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
multiboot_uint16_t framebuffer_palette_num_colors;
|
|
||||||
struct multiboot_color framebuffer_palette[0];
|
|
||||||
};
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
multiboot_uint8_t framebuffer_red_field_position;
|
|
||||||
multiboot_uint8_t framebuffer_red_mask_size;
|
|
||||||
multiboot_uint8_t framebuffer_green_field_position;
|
|
||||||
multiboot_uint8_t framebuffer_green_mask_size;
|
|
||||||
multiboot_uint8_t framebuffer_blue_field_position;
|
|
||||||
multiboot_uint8_t framebuffer_blue_mask_size;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_elf_sections
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t num;
|
|
||||||
multiboot_uint32_t entsize;
|
|
||||||
multiboot_uint32_t shndx;
|
|
||||||
char sections[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_apm
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint16_t version;
|
|
||||||
multiboot_uint16_t cseg;
|
|
||||||
multiboot_uint32_t offset;
|
|
||||||
multiboot_uint16_t cseg_16;
|
|
||||||
multiboot_uint16_t dseg;
|
|
||||||
multiboot_uint16_t flags;
|
|
||||||
multiboot_uint16_t cseg_len;
|
|
||||||
multiboot_uint16_t cseg_16_len;
|
|
||||||
multiboot_uint16_t dseg_len;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_efi32
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t pointer;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_efi64
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint64_t pointer;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_smbios
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint8_t major;
|
|
||||||
multiboot_uint8_t minor;
|
|
||||||
multiboot_uint8_t reserved[6];
|
|
||||||
multiboot_uint8_t tables[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_old_acpi
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint8_t rsdp[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_new_acpi
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint8_t rsdp[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_network
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint8_t dhcpack[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_efi_mmap
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t descr_size;
|
|
||||||
multiboot_uint32_t descr_vers;
|
|
||||||
multiboot_uint8_t efi_mmap[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_efi32_ih
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t pointer;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_efi64_ih
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint64_t pointer;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct multiboot_tag_load_base_addr
|
|
||||||
{
|
|
||||||
multiboot_uint32_t type;
|
|
||||||
multiboot_uint32_t size;
|
|
||||||
multiboot_uint32_t load_base_addr;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* ! ASM_FILE */
|
|
||||||
|
|
||||||
#endif /* ! MULTIBOOT_HEADER */
|
|
||||||
Reference in New Issue
Block a user