Compare commits
8 Commits
6910deae7c
...
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/
|
||||
release/
|
||||
*.img
|
||||
*.iso
|
||||
debug_grub/
|
||||
*_output.txt
|
||||
snippet.*
|
||||
qemu.log
|
||||
iso_output.txt
|
||||
*.img
|
||||
|
||||
@@ -1,58 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(ClaudeOS C ASM)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
|
||||
# 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)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
# Kernel
|
||||
add_subdirectory(src)
|
||||
|
||||
# Create output directories
|
||||
file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/release)
|
||||
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/isodir/boot/grub)
|
||||
|
||||
# Build user-mode applications as flat binaries.
|
||||
set(APPS_BIN_DIR ${CMAKE_BINARY_DIR}/apps_bin)
|
||||
file(MAKE_DIRECTORY ${APPS_BIN_DIR})
|
||||
add_custom_target(apps
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/scripts/build_apps.sh ${CMAKE_SOURCE_DIR}/apps ${APPS_BIN_DIR}
|
||||
COMMENT "Building user-mode applications"
|
||||
)
|
||||
|
||||
# Generate CPIO initial ramdisk from built app binaries.
|
||||
set(INITRD_FILE ${CMAKE_BINARY_DIR}/isodir/boot/initrd.cpio)
|
||||
add_custom_command(
|
||||
OUTPUT ${INITRD_FILE}
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/scripts/gen_initrd.sh ${APPS_BIN_DIR} ${INITRD_FILE}
|
||||
DEPENDS apps
|
||||
COMMENT "Generating CPIO initial ramdisk"
|
||||
)
|
||||
add_custom_target(initrd DEPENDS ${INITRD_FILE})
|
||||
|
||||
# Create grub.cfg for ISO - includes module2 for the initrd
|
||||
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\n module2 /boot/initrd.cpio }")
|
||||
|
||||
|
||||
# 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 initrd
|
||||
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.
|
||||
34
README.md
34
README.md
@@ -37,24 +37,22 @@ 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.
|
||||
Once a task is completed, it should be checked off.
|
||||
|
||||
- [x] Create directory structure
|
||||
- [x] Create initial build system
|
||||
- [x] 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)
|
||||
- [x] Update the kernel to correctly setup the GDT
|
||||
- [x] Create an interrupt handler.
|
||||
- [x] Implement a PIC handler
|
||||
- [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.
|
||||
- [x] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
|
||||
- [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.
|
||||
- [x] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
|
||||
- [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.
|
||||
- [x] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
|
||||
- [x] Write a VFS subsystem.
|
||||
- [x] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer.
|
||||
- [x] Create a `hello-world` app. It should print `Hello, World` to its own stdout. The kernel should route this to Qemu and to the VGA dispaly. Ensure this work.
|
||||
- [x] Implement the fork system call.
|
||||
- [ ] Create directory structure
|
||||
- [ ] Create initial build system
|
||||
- [ ] Setup a simple kernel that writes `Hello, world` to Qemu debug port
|
||||
- [ ] Update the build system to create both ISO and Floppy images. Verify these work using a test script.
|
||||
- [ ] Update the kernel to correctly setup the GDT
|
||||
- [ ] 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 paging subsystem. It should allow drivers to allocate and deallocate pages at will.
|
||||
- [ ] 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 an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
|
||||
- [ ] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
|
||||
- [ ] 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.
|
||||
- [ ] Write a VFS subsystem.
|
||||
- [ ] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer.
|
||||
- [ ] Create a `hello-world` app. It should print `Hello, World` to its own stdout. The kernel should route this to Qemu and to the VGA dispaly. Ensure this work.
|
||||
- [ ] Implement the fork system call.
|
||||
- [ ] Implement environment variables. Apps should be able to modify this, and it should be copied to new forks of an app.
|
||||
- [ ] Create a basic shell program `sh`. This shell must be able to start the hello-world app. It must include `cd` as a built-in to change the current working directory.
|
||||
- [ ] Create an `ls` app. It should list the contents of the current working directory, via the environment variable.
|
||||
|
||||
0
apps/.gitkeep
Normal file
0
apps/.gitkeep
Normal file
@@ -1 +0,0 @@
|
||||
This is the ClaudeOS initial ramdisk.
|
||||
@@ -1,79 +0,0 @@
|
||||
#
|
||||
# fork-test: Tests the fork system call.
|
||||
#
|
||||
# 1. Calls SYS_FORK
|
||||
# 2. Parent prints "Parent: pid=<pid>\n" and waits for child
|
||||
# 3. Child prints "Child: pid=0\n" and exits with code 7
|
||||
# 4. Parent exits with code 0
|
||||
#
|
||||
|
||||
.section .text
|
||||
.global _start
|
||||
|
||||
# System call numbers
|
||||
.equ SYS_EXIT, 0
|
||||
.equ SYS_WRITE, 1
|
||||
.equ SYS_FORK, 3
|
||||
.equ SYS_GETPID, 4
|
||||
.equ SYS_WAITPID, 6
|
||||
|
||||
_start:
|
||||
# Fork
|
||||
movl $SYS_FORK, %eax
|
||||
int $0x80
|
||||
|
||||
# EAX = 0 in child, child PID in parent
|
||||
testl %eax, %eax
|
||||
jz .child
|
||||
|
||||
.parent:
|
||||
# Save child PID on the stack
|
||||
pushl %eax
|
||||
|
||||
# Print "Parent\n"
|
||||
movl $SYS_WRITE, %eax
|
||||
movl $1, %ebx # fd = stdout
|
||||
movl $parent_msg, %ecx
|
||||
movl $parent_msg_len, %edx
|
||||
int $0x80
|
||||
|
||||
# Waitpid for child
|
||||
popl %ebx # child PID
|
||||
movl $SYS_WAITPID, %eax
|
||||
int $0x80
|
||||
# EAX now has child's exit code (should be 7)
|
||||
|
||||
# Print "Reaped\n"
|
||||
pushl %eax # save exit code
|
||||
movl $SYS_WRITE, %eax
|
||||
movl $1, %ebx
|
||||
movl $reaped_msg, %ecx
|
||||
movl $reaped_msg_len, %edx
|
||||
int $0x80
|
||||
popl %ebx # exit code (unused, exit with 0)
|
||||
|
||||
# Exit with code 0
|
||||
movl $SYS_EXIT, %eax
|
||||
movl $0, %ebx
|
||||
int $0x80
|
||||
|
||||
.child:
|
||||
# Print "Child\n"
|
||||
movl $SYS_WRITE, %eax
|
||||
movl $1, %ebx # fd = stdout
|
||||
movl $child_msg, %ecx
|
||||
movl $child_msg_len, %edx
|
||||
int $0x80
|
||||
|
||||
# Exit with code 7
|
||||
movl $SYS_EXIT, %eax
|
||||
movl $7, %ebx
|
||||
int $0x80
|
||||
|
||||
.section .rodata
|
||||
parent_msg: .ascii "Parent\n"
|
||||
.equ parent_msg_len, . - parent_msg
|
||||
child_msg: .ascii "Child\n"
|
||||
.equ child_msg_len, . - child_msg
|
||||
reaped_msg: .ascii "Reaped\n"
|
||||
.equ reaped_msg_len, . - reaped_msg
|
||||
@@ -1,28 +0,0 @@
|
||||
# hello-world.S - ClaudeOS user-mode hello world application
|
||||
# Assembled as flat binary, loaded at USER_CODE_START (0x08048000)
|
||||
#
|
||||
# Uses INT 0x80 system calls:
|
||||
# SYS_WRITE(1): EAX=1, EBX=fd, ECX=buf, EDX=len
|
||||
# SYS_EXIT(0): EAX=0, EBX=code
|
||||
|
||||
.code32
|
||||
.section .text
|
||||
.globl _start
|
||||
_start:
|
||||
/* SYS_WRITE(stdout, "Hello, World\n", 13) */
|
||||
movl $1, %eax /* SYS_WRITE */
|
||||
movl $1, %ebx /* fd = stdout */
|
||||
movl $msg, %ecx /* buf = absolute address of message */
|
||||
movl $13, %edx /* len = 13 */
|
||||
int $0x80
|
||||
|
||||
/* SYS_EXIT(0) */
|
||||
movl $0, %eax /* SYS_EXIT */
|
||||
movl $0, %ebx /* exit code = 0 */
|
||||
int $0x80
|
||||
|
||||
/* Safety: infinite loop (should never reach here) */
|
||||
jmp .
|
||||
|
||||
msg:
|
||||
.ascii "Hello, World\n"
|
||||
25
apps/user.ld
25
apps/user.ld
@@ -1,25 +0,0 @@
|
||||
/* Linker script for ClaudeOS user-mode flat binary.
|
||||
* Applications are loaded at 0x08048000 (USER_CODE_START).
|
||||
* This produces a flat binary (no ELF headers). */
|
||||
|
||||
ENTRY(_start)
|
||||
|
||||
SECTIONS {
|
||||
. = 0x08048000;
|
||||
|
||||
.text : {
|
||||
*(.text)
|
||||
}
|
||||
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
|
||||
.data : {
|
||||
*(.data)
|
||||
}
|
||||
|
||||
.bss : {
|
||||
*(.bss)
|
||||
}
|
||||
}
|
||||
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
81
docs/cpio.md
81
docs/cpio.md
@@ -1,81 +0,0 @@
|
||||
# CPIO Initial Ramdisk
|
||||
|
||||
## Overview
|
||||
|
||||
The initial ramdisk (initrd) provides files to the kernel at boot time before
|
||||
any filesystem drivers are available. It is a CPIO archive in SVR4/newc format,
|
||||
loaded by GRUB as a Multiboot2 module.
|
||||
|
||||
## Build Process
|
||||
|
||||
During the build, the script `scripts/gen_initrd.sh` packs all files from the
|
||||
`apps/` directory into a CPIO archive:
|
||||
|
||||
```
|
||||
apps/
|
||||
├── README (placeholder)
|
||||
├── hello-world (future: flat binary)
|
||||
└── sh (future: shell binary)
|
||||
```
|
||||
|
||||
The archive is placed at `build/isodir/boot/initrd.cpio` and included in the
|
||||
ISO image. GRUB loads it as a module via:
|
||||
|
||||
```
|
||||
module2 /boot/initrd.cpio
|
||||
```
|
||||
|
||||
## CPIO Format
|
||||
|
||||
The newc (SVR4) CPIO format uses 110-byte headers with hex ASCII fields:
|
||||
|
||||
```
|
||||
Offset Size Field
|
||||
0 6 Magic ("070701")
|
||||
6 8 Inode
|
||||
14 8 Mode
|
||||
22 8 UID
|
||||
30 8 GID
|
||||
38 8 Nlink
|
||||
46 8 Mtime
|
||||
54 8 Filesize
|
||||
62 8 Devmajor
|
||||
70 8 Devminor
|
||||
78 8 Rdevmajor
|
||||
86 8 Rdevminor
|
||||
94 8 Namesize
|
||||
102 8 Check
|
||||
```
|
||||
|
||||
After the header: filename (namesize bytes, padded to 4-byte boundary),
|
||||
then file data (filesize bytes, padded to 4-byte boundary). The archive
|
||||
ends with a `TRAILER!!!` entry.
|
||||
|
||||
## Kernel Interface
|
||||
|
||||
The kernel finds the initrd by scanning Multiboot2 boot information for
|
||||
`MULTIBOOT_TAG_TYPE_MODULE` (type 3). The module's physical memory range
|
||||
is identity-mapped, so it can be read directly.
|
||||
|
||||
```c
|
||||
#include "cpio.h"
|
||||
|
||||
// Find a file
|
||||
cpio_entry_t entry;
|
||||
if (cpio_find("hello-world", &entry) == 0) {
|
||||
// entry.data = pointer to file contents
|
||||
// entry.datasize = file size
|
||||
}
|
||||
|
||||
// Iterate all files
|
||||
uint32_t offset = 0;
|
||||
while (cpio_next(&offset, &entry) == 0) {
|
||||
// entry.name, entry.data, entry.datasize
|
||||
}
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
- `scripts/gen_initrd.sh` — Build script to generate CPIO archive
|
||||
- `src/cpio.h` / `src/cpio.c` — CPIO parser (find, iterate, count)
|
||||
- `CMakeLists.txt` — `initrd` target and `module2` in grub.cfg
|
||||
83
docs/fork.md
83
docs/fork.md
@@ -1,83 +0,0 @@
|
||||
# Fork System Call
|
||||
|
||||
## Overview
|
||||
|
||||
The `fork()` system call duplicates the calling process, creating a new child
|
||||
process with an independent copy of the parent's address space.
|
||||
|
||||
## System Call Interface
|
||||
|
||||
- **Number**: `SYS_FORK` (3)
|
||||
- **Arguments**: None
|
||||
- **Returns**: Child PID in the parent, 0 in the child, -1 on error
|
||||
|
||||
## Implementation
|
||||
|
||||
### Address Space Cloning
|
||||
|
||||
`paging_clone_directory_from(src_pd_phys)` performs a deep copy of a process's
|
||||
page directory:
|
||||
|
||||
1. **Kernel-space entries** (no `PAGE_USER` flag): shared directly between
|
||||
parent and child. Both processes see the same kernel mappings.
|
||||
|
||||
2. **User-space entries** (`PAGE_USER` flag set): fully deep-copied. For each
|
||||
user-space page directory entry:
|
||||
- A new page table is allocated
|
||||
- Each present user page has a new physical page allocated and the content
|
||||
copied byte-for-byte
|
||||
- This ensures parent and child have completely independent memory
|
||||
|
||||
### Register State
|
||||
|
||||
The child receives a copy of the parent's register state at the time of the
|
||||
`INT 0x80` syscall, with `EAX` set to 0. This means the child resumes execution
|
||||
at the instruction immediately following the `INT 0x80` that triggered fork.
|
||||
|
||||
### Process Exit and Waitpid
|
||||
|
||||
`process_exit()` was refactored to support multi-process scenarios:
|
||||
|
||||
- When a process exits, it scans for any process blocked on `waitpid()` for
|
||||
its PID and unblocks it, setting the waiter's saved `EAX` to the exit code.
|
||||
- If another process is ready, `process_switch_to_user()` is called to
|
||||
directly context-switch via an assembly stub that loads the full register
|
||||
set and performs `iret`.
|
||||
- If no processes remain, the system halts.
|
||||
|
||||
`sys_waitpid()` supports blocking:
|
||||
|
||||
- If the child is already a zombie, it reaps immediately
|
||||
- Otherwise, the caller is marked `PROCESS_BLOCKED` and the scheduler is
|
||||
invoked to switch to another process
|
||||
- When the child exits, the parent is unblocked with the exit code
|
||||
|
||||
### Assembly Support
|
||||
|
||||
`process_switch_to_user` in `interrupts.S` loads a full `registers_t` struct
|
||||
and performs `iret` to enter user mode. This is used when `process_exit()`
|
||||
needs to context-switch outside the normal ISR return path.
|
||||
|
||||
## Syscall Flow
|
||||
|
||||
```
|
||||
User: INT 0x80 (EAX=SYS_FORK)
|
||||
→ ISR stub pushes registers
|
||||
→ isr_handler → syscall_handler → sys_fork(regs)
|
||||
→ process_fork(regs)
|
||||
→ Clone page directory with deep user-page copy
|
||||
→ Copy current interrupt frame to child (EAX=0)
|
||||
→ Return child PID to parent (via EAX)
|
||||
→ ISR stub pops registers, iret
|
||||
→ Parent continues with EAX=child_pid
|
||||
→ [Timer interrupt] → scheduler picks child
|
||||
→ Child starts with EAX=0
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The `fork-test` application validates fork by:
|
||||
1. Calling `SYS_FORK`
|
||||
2. Parent prints "Parent" and calls `SYS_WAITPID`
|
||||
3. Child prints "Child" and exits with code 7
|
||||
4. Parent reaps child, prints "Reaped", exits with code 0
|
||||
@@ -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`
|
||||
78
docs/vfs.md
78
docs/vfs.md
@@ -1,78 +0,0 @@
|
||||
# Virtual Filesystem (VFS)
|
||||
|
||||
## Overview
|
||||
|
||||
The VFS provides a unified interface for file and directory operations across
|
||||
different filesystem implementations. Filesystem drivers register ops structs
|
||||
and are mounted at specific paths. Path resolution finds the longest-matching
|
||||
mount point and delegates to that filesystem.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
User/Kernel Code
|
||||
│
|
||||
▼
|
||||
vfs_open("/initrd/hello-world", 0)
|
||||
│
|
||||
▼
|
||||
VFS: find_mount("/initrd/hello-world")
|
||||
│ → mount "/initrd", rel_path = "hello-world"
|
||||
▼
|
||||
resolve_path → initrd_finddir("hello-world")
|
||||
│
|
||||
▼
|
||||
vfs_read(fd, buf, size)
|
||||
│ → initrd_read(node, offset, size, buf)
|
||||
▼
|
||||
Returns file data from CPIO archive
|
||||
```
|
||||
|
||||
## Mount Points
|
||||
|
||||
Filesystems are mounted at absolute paths. The VFS supports up to 16
|
||||
simultaneous mounts. Path resolution uses longest-prefix matching:
|
||||
|
||||
```
|
||||
Mount: "/initrd" → handles /initrd/*
|
||||
Mount: "/sys" → handles /sys/*
|
||||
Mount: "/dev" → handles /dev/*
|
||||
```
|
||||
|
||||
## File Operations
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `vfs_open(path, flags)` | Open a file, returns fd |
|
||||
| `vfs_close(fd)` | Close a file descriptor |
|
||||
| `vfs_read(fd, buf, size)` | Read bytes, advances offset |
|
||||
| `vfs_write(fd, buf, size)` | Write bytes, advances offset |
|
||||
| `vfs_seek(fd, offset, whence)` | Seek within file |
|
||||
| `vfs_readdir(path, idx, out)` | Read directory entry |
|
||||
| `vfs_stat(path, out)` | Get file info |
|
||||
|
||||
## Filesystem Driver Interface
|
||||
|
||||
Each filesystem provides a `vfs_fs_ops_t` struct:
|
||||
|
||||
```c
|
||||
typedef struct vfs_fs_ops {
|
||||
int (*open)(vfs_node_t *node, uint32_t flags);
|
||||
void (*close)(vfs_node_t *node);
|
||||
int32_t (*read)(vfs_node_t *node, uint32_t offset, uint32_t size, void *buf);
|
||||
int32_t (*write)(vfs_node_t *node, uint32_t offset, uint32_t size, const void *buf);
|
||||
int (*readdir)(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out);
|
||||
int (*finddir)(vfs_node_t *dir, const char *name, vfs_node_t *out);
|
||||
} vfs_fs_ops_t;
|
||||
```
|
||||
|
||||
## Initrd Filesystem Driver
|
||||
|
||||
The initrd filesystem (`initrd_fs.c`) provides read-only access to the CPIO
|
||||
ramdisk. It is automatically mounted at `/initrd` during boot. Files are
|
||||
accessed via zero-copy reads directly from the CPIO archive in memory.
|
||||
|
||||
## Files
|
||||
|
||||
- `src/vfs.h` / `src/vfs.c` — VFS core: mount table, fd table, path resolution
|
||||
- `src/initrd_fs.h` / `src/initrd_fs.c` — CPIO ramdisk VFS driver
|
||||
0
libs/.gitkeep
Normal file
0
libs/.gitkeep
Normal file
@@ -1,49 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Build all user-mode applications as flat binaries.
|
||||
# Usage: build_apps.sh <apps_dir> <output_dir>
|
||||
# Each app directory in <apps_dir>/ gets compiled and its flat binary
|
||||
# is placed in <output_dir>/.
|
||||
set -e
|
||||
|
||||
APPS_DIR="$1"
|
||||
OUTPUT_DIR="$2"
|
||||
LINKER_SCRIPT="$APPS_DIR/user.ld"
|
||||
|
||||
CC="${CC:-clang}"
|
||||
OBJCOPY="${OBJCOPY:-objcopy}"
|
||||
CFLAGS="-ffreestanding -m32 -fno-pie -fno-pic -fno-builtin -fno-stack-protector -mno-sse -mno-mmx -O2 -Wall"
|
||||
LDFLAGS="-m32 -nostdlib -no-pie -Wl,--no-dynamic-linker"
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
for app_dir in "$APPS_DIR"/*/; do
|
||||
[ -d "$app_dir" ] || continue
|
||||
app_name=$(basename "$app_dir")
|
||||
|
||||
echo "Building app: $app_name"
|
||||
|
||||
# Collect source files
|
||||
OBJ_FILES=""
|
||||
for src in "$app_dir"*.S "$app_dir"*.c; do
|
||||
[ -f "$src" ] || continue
|
||||
obj="$OUTPUT_DIR/${app_name}_$(basename "${src%.*}").o"
|
||||
$CC $CFLAGS -c "$src" -o "$obj"
|
||||
OBJ_FILES="$OBJ_FILES $obj"
|
||||
done
|
||||
|
||||
if [ -z "$OBJ_FILES" ]; then
|
||||
echo " No sources found, skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Link into ELF
|
||||
elf="$OUTPUT_DIR/$app_name.elf"
|
||||
$CC $LDFLAGS -T "$LINKER_SCRIPT" $OBJ_FILES -o "$elf"
|
||||
|
||||
# Convert to flat binary (strip non-code sections)
|
||||
bin="$OUTPUT_DIR/$app_name"
|
||||
$OBJCOPY -O binary --only-section=.text --only-section=.rodata --only-section=.data "$elf" "$bin"
|
||||
|
||||
size=$(wc -c < "$bin")
|
||||
echo " Built: $bin ($size bytes)"
|
||||
done
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Generate CPIO initial ramdisk from built application binaries.
|
||||
# Usage: gen_initrd.sh <binaries_dir> <output_file>
|
||||
# Packs all files in <binaries_dir> into a newc-format CPIO archive.
|
||||
set -e
|
||||
|
||||
BIN_DIR="$1"
|
||||
OUTPUT="$2"
|
||||
|
||||
# Ensure output directory exists
|
||||
mkdir -p "$(dirname "$OUTPUT")"
|
||||
|
||||
cd "$BIN_DIR"
|
||||
# Only pack actual binary files (no .o, .elf intermediates)
|
||||
find . -maxdepth 1 -type f ! -name '*.o' ! -name '*.elf' | cpio -o -H newc > "$OUTPUT" 2>/dev/null
|
||||
echo "Generated initrd: $(wc -c < "$OUTPUT") bytes"
|
||||
0
src/.gitkeep
Normal file
0
src/.gitkeep
Normal file
@@ -1,32 +1,33 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
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
|
||||
cpio.c
|
||||
vfs.c
|
||||
initrd_fs.c
|
||||
interrupts.S
|
||||
set(KERNEL_SOURCES
|
||||
boot/boot.s
|
||||
kernel.c
|
||||
debugport.c
|
||||
)
|
||||
|
||||
# Use our custom linker script
|
||||
target_link_options(kernel PRIVATE -T ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld)
|
||||
add_executable(kernel ${KERNEL_SOURCES})
|
||||
|
||||
target_include_directories(kernel PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/vendor
|
||||
${CMAKE_SOURCE_DIR}/include # If created later
|
||||
target_include_directories(kernel PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
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
|
||||
179
src/cpio.c
179
src/cpio.c
@@ -1,179 +0,0 @@
|
||||
/**
|
||||
* @file cpio.c
|
||||
* @brief CPIO newc archive parser implementation.
|
||||
*
|
||||
* Parses CPIO archives in the SVR4/newc format. The archive is expected
|
||||
* to be loaded into memory by GRUB as a Multiboot2 module.
|
||||
*/
|
||||
|
||||
#include "cpio.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);
|
||||
|
||||
/** Pointer to the CPIO archive in memory. */
|
||||
static const uint8_t *archive = NULL;
|
||||
|
||||
/** Size of the archive (0 if unknown). */
|
||||
static uint32_t archive_len = 0;
|
||||
|
||||
/**
|
||||
* Parse an N-character hexadecimal ASCII string to uint32_t.
|
||||
*
|
||||
* @param s Pointer to hex string.
|
||||
* @param n Number of characters to parse.
|
||||
* @return Parsed value.
|
||||
*/
|
||||
static uint32_t parse_hex(const char *s, int n) {
|
||||
uint32_t val = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
char c = s[i];
|
||||
uint32_t digit;
|
||||
if (c >= '0' && c <= '9') {
|
||||
digit = (uint32_t)(c - '0');
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
digit = (uint32_t)(c - 'a' + 10);
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
digit = (uint32_t)(c - 'A' + 10);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
val = (val << 4) | digit;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Round up to 4-byte boundary.
|
||||
*/
|
||||
static inline uint32_t align4(uint32_t v) {
|
||||
return (v + 3) & ~3u;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a CPIO entry at the given offset.
|
||||
*
|
||||
* @param offset Byte offset into the archive.
|
||||
* @param entry Output entry information.
|
||||
* @return Offset of the next entry, or 0 on error/end.
|
||||
*/
|
||||
static uint32_t parse_entry(uint32_t offset, cpio_entry_t *entry) {
|
||||
if (!archive) return 0;
|
||||
|
||||
const cpio_newc_header_t *hdr = (const cpio_newc_header_t *)(archive + offset);
|
||||
|
||||
/* Verify magic */
|
||||
if (memcmp(hdr->magic, CPIO_MAGIC, 6) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t namesize = parse_hex(hdr->namesize, 8);
|
||||
uint32_t filesize = parse_hex(hdr->filesize, 8);
|
||||
uint32_t mode = parse_hex(hdr->mode, 8);
|
||||
|
||||
/* Filename starts right after the header */
|
||||
const char *name = (const char *)(archive + offset + CPIO_HEADER_SIZE);
|
||||
|
||||
/* Data starts after header + name, aligned to 4 bytes */
|
||||
uint32_t data_offset = align4(offset + CPIO_HEADER_SIZE + namesize);
|
||||
const void *data = archive + data_offset;
|
||||
|
||||
/* Next entry starts after data, aligned to 4 bytes */
|
||||
uint32_t next_offset = align4(data_offset + filesize);
|
||||
|
||||
entry->name = name;
|
||||
entry->namesize = namesize;
|
||||
entry->data = data;
|
||||
entry->datasize = filesize;
|
||||
entry->mode = mode;
|
||||
|
||||
return next_offset;
|
||||
}
|
||||
|
||||
void cpio_init(const void *archive_start, uint32_t archive_size) {
|
||||
archive = (const uint8_t *)archive_start;
|
||||
archive_len = archive_size;
|
||||
|
||||
offset_print(" CPIO: archive at ");
|
||||
print_hex((uint32_t)archive_start);
|
||||
offset_print(" CPIO: size = ");
|
||||
print_hex(archive_size);
|
||||
|
||||
/* Count and list entries */
|
||||
uint32_t count = 0;
|
||||
uint32_t off = 0;
|
||||
cpio_entry_t entry;
|
||||
while (1) {
|
||||
uint32_t next = parse_entry(off, &entry);
|
||||
if (next == 0) break;
|
||||
if (strcmp(entry.name, CPIO_TRAILER) == 0) break;
|
||||
|
||||
offset_print(" CPIO: [");
|
||||
offset_print(entry.name);
|
||||
offset_print("] size=");
|
||||
print_hex(entry.datasize);
|
||||
|
||||
count++;
|
||||
off = next;
|
||||
}
|
||||
|
||||
offset_print(" CPIO: ");
|
||||
print_hex(count);
|
||||
offset_print(" CPIO: files found\n");
|
||||
}
|
||||
|
||||
int cpio_find(const char *name, cpio_entry_t *entry) {
|
||||
if (!archive) return -1;
|
||||
|
||||
uint32_t off = 0;
|
||||
while (1) {
|
||||
uint32_t next = parse_entry(off, entry);
|
||||
if (next == 0) return -1;
|
||||
if (strcmp(entry->name, CPIO_TRAILER) == 0) return -1;
|
||||
|
||||
/* Match by name. CPIO entries often have "./" prefix, try both. */
|
||||
if (strcmp(entry->name, name) == 0) return 0;
|
||||
|
||||
/* Try matching without "./" prefix */
|
||||
if (entry->name[0] == '.' && entry->name[1] == '/' &&
|
||||
strcmp(entry->name + 2, name) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Try matching with "./" prefix */
|
||||
if (name[0] != '.' && entry->namesize > 2) {
|
||||
/* Already handled above */
|
||||
}
|
||||
|
||||
off = next;
|
||||
}
|
||||
}
|
||||
|
||||
int cpio_next(uint32_t *offset, cpio_entry_t *entry) {
|
||||
if (!archive) return -1;
|
||||
|
||||
uint32_t next = parse_entry(*offset, entry);
|
||||
if (next == 0) return -1;
|
||||
if (strcmp(entry->name, CPIO_TRAILER) == 0) return -1;
|
||||
|
||||
*offset = next;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t cpio_count(void) {
|
||||
if (!archive) return 0;
|
||||
|
||||
uint32_t count = 0;
|
||||
uint32_t off = 0;
|
||||
cpio_entry_t entry;
|
||||
while (1) {
|
||||
uint32_t next = parse_entry(off, &entry);
|
||||
if (next == 0) break;
|
||||
if (strcmp(entry.name, CPIO_TRAILER) == 0) break;
|
||||
count++;
|
||||
off = next;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
92
src/cpio.h
92
src/cpio.h
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* @file cpio.h
|
||||
* @brief CPIO newc archive parser.
|
||||
*
|
||||
* Parses CPIO archives in the SVR4/newc format (magic "070701").
|
||||
* Used to read files from the initial ramdisk loaded by GRUB.
|
||||
*/
|
||||
|
||||
#ifndef CPIO_H
|
||||
#define CPIO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* CPIO newc header (110 bytes).
|
||||
* All fields are 8-character hexadecimal ASCII strings.
|
||||
*/
|
||||
typedef struct cpio_newc_header {
|
||||
char magic[6]; /**< Must be "070701". */
|
||||
char ino[8];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
char nlink[8];
|
||||
char mtime[8];
|
||||
char filesize[8];
|
||||
char devmajor[8];
|
||||
char devminor[8];
|
||||
char rdevmajor[8];
|
||||
char rdevminor[8];
|
||||
char namesize[8];
|
||||
char check[8];
|
||||
} cpio_newc_header_t;
|
||||
|
||||
/** Size of the CPIO newc header in bytes. */
|
||||
#define CPIO_HEADER_SIZE 110
|
||||
|
||||
/** CPIO newc magic string. */
|
||||
#define CPIO_MAGIC "070701"
|
||||
|
||||
/** Trailer entry name that marks end of archive. */
|
||||
#define CPIO_TRAILER "TRAILER!!!"
|
||||
|
||||
/**
|
||||
* CPIO file entry (result of iteration or lookup).
|
||||
*/
|
||||
typedef struct cpio_entry {
|
||||
const char *name; /**< Filename (pointer into archive). */
|
||||
uint32_t namesize; /**< Length of filename including NUL. */
|
||||
const void *data; /**< Pointer to file data within archive. */
|
||||
uint32_t datasize; /**< Size of file data in bytes. */
|
||||
uint32_t mode; /**< File mode/permissions. */
|
||||
} cpio_entry_t;
|
||||
|
||||
/**
|
||||
* Initialize the CPIO parser with the archive location.
|
||||
*
|
||||
* @param archive_start Pointer to the start of the CPIO archive in memory.
|
||||
* @param archive_size Size of the archive in bytes (0 if unknown).
|
||||
*/
|
||||
void cpio_init(const void *archive_start, uint32_t archive_size);
|
||||
|
||||
/**
|
||||
* Find a file in the CPIO archive by name.
|
||||
*
|
||||
* @param name Filename to search for (without leading "./").
|
||||
* @param entry Output: filled with file information if found.
|
||||
* @return 0 on success, -1 if not found.
|
||||
*/
|
||||
int cpio_find(const char *name, cpio_entry_t *entry);
|
||||
|
||||
/**
|
||||
* Iterate through all entries in the CPIO archive.
|
||||
*
|
||||
* Call with *offset = 0 to start. Returns 0 on success, -1 when no
|
||||
* more entries exist or the TRAILER is reached.
|
||||
*
|
||||
* @param offset In/out: current position in the archive.
|
||||
* @param entry Output: filled with the next entry's information.
|
||||
* @return 0 on success, -1 at end of archive.
|
||||
*/
|
||||
int cpio_next(uint32_t *offset, cpio_entry_t *entry);
|
||||
|
||||
/**
|
||||
* Get the number of files in the CPIO archive (excluding TRAILER).
|
||||
*
|
||||
* @return Number of files.
|
||||
*/
|
||||
uint32_t cpio_count(void);
|
||||
|
||||
#endif /* CPIO_H */
|
||||
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;
|
||||
}
|
||||
123
src/initrd_fs.c
123
src/initrd_fs.c
@@ -1,123 +0,0 @@
|
||||
/**
|
||||
* @file initrd_fs.c
|
||||
* @brief CPIO initial ramdisk VFS driver implementation.
|
||||
*
|
||||
* Provides a read-only VFS interface to the CPIO archive loaded at boot.
|
||||
* Files are accessed directly from the archive memory (zero-copy reads).
|
||||
*/
|
||||
|
||||
#include "initrd_fs.h"
|
||||
#include "vfs.h"
|
||||
#include "cpio.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);
|
||||
|
||||
/**
|
||||
* Read from a file in the initrd.
|
||||
* Data is read directly from the CPIO archive (memory-mapped).
|
||||
*/
|
||||
static int32_t initrd_read(vfs_node_t *node, uint32_t offset,
|
||||
uint32_t size, void *buf) {
|
||||
if (!node || !node->fs_data || !buf) return -1;
|
||||
|
||||
/* fs_data points to the file's data within the CPIO archive */
|
||||
const uint8_t *data = (const uint8_t *)node->fs_data;
|
||||
uint32_t file_size = node->size;
|
||||
|
||||
if (offset >= file_size) return 0;
|
||||
|
||||
uint32_t remaining = file_size - offset;
|
||||
if (size > remaining) size = remaining;
|
||||
|
||||
memcpy(buf, data + offset, size);
|
||||
return (int32_t)size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a directory entry from the initrd root.
|
||||
* The initrd is a flat archive — all files are at the root level.
|
||||
*/
|
||||
static int initrd_readdir(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out) {
|
||||
(void)dir;
|
||||
|
||||
uint32_t off = 0;
|
||||
uint32_t current = 0;
|
||||
cpio_entry_t entry;
|
||||
|
||||
while (cpio_next(&off, &entry) == 0) {
|
||||
/* Skip the "." directory entry if present */
|
||||
if (entry.name[0] == '.' && entry.name[1] == '\0') continue;
|
||||
|
||||
/* Strip "./" prefix */
|
||||
const char *name = entry.name;
|
||||
if (name[0] == '.' && name[1] == '/') name += 2;
|
||||
|
||||
/* Skip empty names */
|
||||
if (*name == '\0') continue;
|
||||
|
||||
if (current == idx) {
|
||||
memset(out, 0, sizeof(vfs_dirent_t));
|
||||
strncpy(out->name, name, VFS_MAX_NAME - 1);
|
||||
out->inode = current;
|
||||
out->type = VFS_FILE;
|
||||
return 0;
|
||||
}
|
||||
current++;
|
||||
}
|
||||
|
||||
return -1; /* No more entries */
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a file by name within the initrd.
|
||||
*/
|
||||
static int initrd_finddir(vfs_node_t *dir, const char *name, vfs_node_t *out) {
|
||||
(void)dir;
|
||||
|
||||
cpio_entry_t entry;
|
||||
if (cpio_find(name, &entry) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(out, 0, sizeof(vfs_node_t));
|
||||
/* Strip "./" prefix for the node name */
|
||||
const char *display_name = entry.name;
|
||||
if (display_name[0] == '.' && display_name[1] == '/') {
|
||||
display_name += 2;
|
||||
}
|
||||
strncpy(out->name, display_name, VFS_MAX_NAME - 1);
|
||||
out->type = VFS_FILE;
|
||||
out->size = entry.datasize;
|
||||
out->mode = entry.mode;
|
||||
out->fs_data = (void *)entry.data; /* Direct pointer into CPIO archive */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Filesystem operations for the initrd. */
|
||||
static vfs_fs_ops_t initrd_ops = {
|
||||
.open = NULL, /* No special open needed */
|
||||
.close = NULL, /* No special close needed */
|
||||
.read = initrd_read,
|
||||
.write = NULL, /* Read-only */
|
||||
.readdir = initrd_readdir,
|
||||
.finddir = initrd_finddir,
|
||||
};
|
||||
|
||||
int init_initrd_fs(void) {
|
||||
if (cpio_count() == 0) {
|
||||
offset_print(" INITRD_FS: no files in ramdisk\n");
|
||||
}
|
||||
|
||||
int ret = vfs_mount("/initrd", &initrd_ops, NULL);
|
||||
if (ret != 0) {
|
||||
offset_print(" INITRD_FS: failed to mount\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset_print(" INITRD_FS: mounted at /initrd\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @file initrd_fs.h
|
||||
* @brief CPIO initial ramdisk VFS driver.
|
||||
*
|
||||
* Provides a read-only filesystem backed by the CPIO initial ramdisk.
|
||||
* Mounted at "/initrd" to expose the contents of the ramdisk via the VFS.
|
||||
*/
|
||||
|
||||
#ifndef INITRD_FS_H
|
||||
#define INITRD_FS_H
|
||||
|
||||
/**
|
||||
* Initialize the initrd filesystem driver and mount it at "/initrd".
|
||||
* Must be called after init_vfs() and cpio_init().
|
||||
*
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
int init_initrd_fs(void);
|
||||
|
||||
#endif /* INITRD_FS_H */
|
||||
201
src/interrupts.S
201
src/interrupts.S
@@ -1,201 +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
|
||||
|
||||
/*
|
||||
* process_switch_to_user - Restore full register state and iret to user mode.
|
||||
* void process_switch_to_user(registers_t *regs);
|
||||
*
|
||||
* Used by process_exit to context-switch to the next process when the normal
|
||||
* interrupt-return path isn't available (because we're not returning through
|
||||
* an ISR stub). Loads all registers from the registers_t struct and performs
|
||||
* iret to enter user mode.
|
||||
*/
|
||||
.global process_switch_to_user
|
||||
.type process_switch_to_user, @function
|
||||
process_switch_to_user:
|
||||
movl 4(%esp), %esp /* Point ESP to the registers_t struct */
|
||||
|
||||
/* Restore segment register (ds → all data segments) */
|
||||
pop %eax
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
|
||||
popa /* Restore EAX-EDI */
|
||||
addl $8, %esp /* Skip int_no and err_code */
|
||||
iret /* Pops EIP, CS, EFLAGS, UserESP, SS */
|
||||
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
222
src/kernel.c
222
src/kernel.c
@@ -1,187 +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 <stddef.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"
|
||||
#include "cpio.h"
|
||||
#include "vfs.h"
|
||||
#include "initrd_fs.h"
|
||||
#include <debugport.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) {
|
||||
outb(0xE9, *str);
|
||||
str++;
|
||||
(void)multiboot_info;
|
||||
|
||||
/* 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");
|
||||
|
||||
/* Scan Multiboot2 tags for the initrd module */
|
||||
uint32_t initrd_start = 0, initrd_end = 0;
|
||||
{
|
||||
struct multiboot_tag *tag;
|
||||
for (tag = (struct multiboot_tag *)(addr + 8);
|
||||
tag->type != MULTIBOOT_TAG_TYPE_END;
|
||||
tag = (struct multiboot_tag *)((uint8_t *)tag + ((tag->size + 7) & ~7u))) {
|
||||
if (tag->type == MULTIBOOT_TAG_TYPE_MODULE) {
|
||||
struct multiboot_tag_module *mod = (struct multiboot_tag_module *)tag;
|
||||
initrd_start = mod->mod_start;
|
||||
initrd_end = mod->mod_end;
|
||||
offset_print("Initrd module at ");
|
||||
print_hex(initrd_start);
|
||||
offset_print(" to ");
|
||||
print_hex(initrd_end);
|
||||
break; /* Use first module */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
/* Initialize CPIO ramdisk if module was loaded */
|
||||
if (initrd_start != 0) {
|
||||
cpio_init((const void *)initrd_start, initrd_end - initrd_start);
|
||||
offset_print("CPIO ramdisk initialized\n");
|
||||
} else {
|
||||
offset_print("No initrd module found\n");
|
||||
}
|
||||
|
||||
init_vfs();
|
||||
offset_print("VFS initialized\n");
|
||||
|
||||
if (initrd_start != 0) {
|
||||
init_initrd_fs();
|
||||
offset_print("Initrd filesystem mounted\n");
|
||||
|
||||
/* Test VFS: read a file from the initrd */
|
||||
int fd = vfs_open("/initrd/README", 0);
|
||||
if (fd >= 0) {
|
||||
char buf[64];
|
||||
int32_t n = vfs_read(fd, buf, sizeof(buf) - 1);
|
||||
if (n > 0) {
|
||||
buf[n] = '\0';
|
||||
offset_print("VFS read /initrd/README: ");
|
||||
offset_print(buf);
|
||||
}
|
||||
vfs_close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
/* Load hello-world from the initrd and run it as a user process */
|
||||
cpio_entry_t app_entry;
|
||||
if (cpio_find("hello-world", &app_entry) == 0) {
|
||||
offset_print("Found hello-world in initrd (");
|
||||
print_hex(app_entry.datasize);
|
||||
offset_print(" bytes)\n");
|
||||
|
||||
int32_t pid = process_create("hello-world",
|
||||
app_entry.data,
|
||||
app_entry.datasize);
|
||||
if (pid > 0) {
|
||||
offset_print("Created hello-world 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 hello-world process\n");
|
||||
}
|
||||
} else {
|
||||
offset_print("hello-world not found in initrd\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)
|
||||
|
||||
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)
|
||||
{
|
||||
KEEP(*(.multiboot))
|
||||
*(.text)
|
||||
}
|
||||
.text ALIGN(4096) :
|
||||
{
|
||||
*(.text*)
|
||||
}
|
||||
|
||||
.rodata BLOCK(4K) : ALIGN(4K)
|
||||
{
|
||||
*(.rodata)
|
||||
*(.rodata.*)
|
||||
}
|
||||
.rodata ALIGN(4096) :
|
||||
{
|
||||
*(.rodata*)
|
||||
}
|
||||
|
||||
.drivers BLOCK(4K) : ALIGN(4K)
|
||||
{
|
||||
__drivers_start = .;
|
||||
KEEP(*(.drivers))
|
||||
__drivers_end = .;
|
||||
}
|
||||
.data ALIGN(4096) :
|
||||
{
|
||||
*(.data*)
|
||||
}
|
||||
|
||||
.data BLOCK(4K) : ALIGN(4K)
|
||||
{
|
||||
*(.data)
|
||||
}
|
||||
|
||||
.bss BLOCK(4K) : ALIGN(4K)
|
||||
{
|
||||
*(COMMON)
|
||||
*(.bss)
|
||||
}
|
||||
.bss ALIGN(4096) :
|
||||
{
|
||||
_bss_start = .;
|
||||
*(COMMON)
|
||||
*(.bss*)
|
||||
_bss_end = .;
|
||||
}
|
||||
|
||||
_kernel_end = .;
|
||||
}
|
||||
|
||||
384
src/paging.c
384
src/paging.c
@@ -1,384 +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;
|
||||
}
|
||||
|
||||
uint32_t paging_clone_directory_from(uint32_t src_pd_phys) {
|
||||
uint32_t *src_pd = (uint32_t *)src_pd_phys;
|
||||
|
||||
/* Allocate a new page directory */
|
||||
phys_addr_t new_pd_phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
||||
if (new_pd_phys == 0) {
|
||||
offset_print(" PAGING: cannot allocate page directory for fork\n");
|
||||
return 0;
|
||||
}
|
||||
uint32_t *new_pd = (uint32_t *)new_pd_phys;
|
||||
|
||||
/* Copy all page directory entries (shares kernel mappings) */
|
||||
memcpy(new_pd, src_pd, 4096);
|
||||
|
||||
/* Deep-copy user-space page tables (those with PAGE_USER set) */
|
||||
for (uint32_t i = 0; i < PAGE_ENTRIES; i++) {
|
||||
if (!(src_pd[i] & PAGE_PRESENT)) continue;
|
||||
if (!(src_pd[i] & PAGE_USER)) continue; /* kernel entry, shared */
|
||||
|
||||
uint32_t *src_pt = (uint32_t *)(src_pd[i] & 0xFFFFF000);
|
||||
|
||||
/* Allocate a new page table */
|
||||
phys_addr_t new_pt_phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
||||
if (new_pt_phys == 0) {
|
||||
offset_print(" PAGING: fork: cannot allocate page table\n");
|
||||
return 0; /* TODO: free partially allocated pages */
|
||||
}
|
||||
uint32_t *new_pt = (uint32_t *)new_pt_phys;
|
||||
|
||||
/* Deep-copy each page in the page table */
|
||||
for (uint32_t j = 0; j < PAGE_ENTRIES; j++) {
|
||||
if (!(src_pt[j] & PAGE_PRESENT)) {
|
||||
new_pt[j] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (src_pt[j] & PAGE_USER) {
|
||||
/* User page: allocate new physical page and copy content */
|
||||
phys_addr_t old_phys = src_pt[j] & 0xFFFFF000;
|
||||
phys_addr_t new_phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
||||
if (new_phys == 0) {
|
||||
offset_print(" PAGING: fork: cannot allocate page\n");
|
||||
return 0;
|
||||
}
|
||||
memcpy((void *)new_phys, (void *)old_phys, 4096);
|
||||
new_pt[j] = new_phys | (src_pt[j] & 0xFFF);
|
||||
} else {
|
||||
/* Kernel page within a user page table: share directly */
|
||||
new_pt[j] = src_pt[j];
|
||||
}
|
||||
}
|
||||
|
||||
new_pd[i] = new_pt_phys | (src_pd[i] & 0xFFF);
|
||||
}
|
||||
|
||||
return new_pd_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");
|
||||
}
|
||||
129
src/paging.h
129
src/paging.h
@@ -1,129 +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);
|
||||
|
||||
/**
|
||||
* Clone a page directory, deep-copying all user-space pages.
|
||||
* Kernel-space entries are shared (same page tables). User-space page
|
||||
* tables and their physical pages are duplicated so the clone is fully
|
||||
* independent.
|
||||
*
|
||||
* @param src_pd_phys Physical address of the source page directory.
|
||||
* @return Physical address of the new page directory, or 0 on failure.
|
||||
*/
|
||||
uint32_t paging_clone_directory_from(uint32_t src_pd_phys);
|
||||
|
||||
/**
|
||||
* 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
|
||||
372
src/process.c
372
src/process.c
@@ -1,372 +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;
|
||||
|
||||
/* Wake any process blocked on waitpid for this PID */
|
||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||
if (process_table[i].state == PROCESS_BLOCKED &&
|
||||
process_table[i].waiting_for_pid == current_process->pid) {
|
||||
process_table[i].state = PROCESS_READY;
|
||||
process_table[i].saved_regs.eax = (uint32_t)code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find next ready process to switch to */
|
||||
process_t *next = NULL;
|
||||
for (int i = 0; i < MAX_PROCESSES; i++) {
|
||||
if (process_table[i].state == PROCESS_READY) {
|
||||
next = &process_table[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!next) {
|
||||
offset_print(" PROCESS: no processes remaining, halting\n");
|
||||
for (;;) {
|
||||
__asm__ volatile("cli; hlt");
|
||||
}
|
||||
}
|
||||
|
||||
/* Context switch to the next process via assembly stub */
|
||||
current_process = next;
|
||||
next->state = PROCESS_RUNNING;
|
||||
tss_set_kernel_stack(next->kernel_stack_top);
|
||||
paging_switch_directory(next->page_directory);
|
||||
|
||||
extern void process_switch_to_user(registers_t *regs);
|
||||
process_switch_to_user(&next->saved_regs);
|
||||
|
||||
/* Should never reach here */
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
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(registers_t *regs) {
|
||||
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;
|
||||
child->waiting_for_pid = 0;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* Deep-clone the parent's page directory (copies all user-space pages) */
|
||||
child->page_directory = paging_clone_directory_from(current_process->page_directory);
|
||||
if (!child->page_directory) {
|
||||
paging_free_page((void *)child->kernel_stack);
|
||||
child->state = PROCESS_UNUSED;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Copy the current syscall registers to the child.
|
||||
* This ensures the child resumes at the same point as the parent
|
||||
* (right after the INT 0x80 instruction). */
|
||||
child->saved_regs = *regs;
|
||||
|
||||
/* Child's return value is 0 (in EAX) */
|
||||
child->saved_regs.eax = 0;
|
||||
|
||||
offset_print(" PROCESS: forked pid ");
|
||||
print_hex(current_process->pid);
|
||||
offset_print(" PROCESS: -> child pid ");
|
||||
print_hex(child->pid);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
130
src/process.h
130
src/process.h
@@ -1,130 +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. */
|
||||
uint32_t waiting_for_pid; /**< PID we are blocked waiting for (if BLOCKED). */
|
||||
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.
|
||||
* Clones the current process's address space and register state.
|
||||
*
|
||||
* @param regs Pointer to the current interrupt frame (syscall registers).
|
||||
* @return PID of the child in the parent, 0 in the child, -1 on error.
|
||||
*/
|
||||
int32_t process_fork(registers_t *regs);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
176
src/syscall.c
176
src/syscall.c
@@ -1,176 +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>
|
||||
|
||||
/** Magic return value indicating the syscall blocked and switched processes.
|
||||
* syscall_handler must NOT overwrite regs->eax in this case. */
|
||||
#define SYSCALL_SWITCHED 0x7FFFFFFF
|
||||
|
||||
/* 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) {
|
||||
return process_fork(regs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* If the child is already a zombie, reaps immediately and returns the code.
|
||||
* Otherwise, blocks the current process and switches to the next one.
|
||||
* When the child exits, process_exit() will unblock the waiting parent
|
||||
* and set its saved_regs.eax to the exit code.
|
||||
*/
|
||||
static int32_t sys_waitpid(registers_t *regs) {
|
||||
uint32_t pid = regs->ebx;
|
||||
process_t *child = process_get(pid);
|
||||
if (!child) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If child already exited, reap immediately */
|
||||
if (child->state == PROCESS_ZOMBIE) {
|
||||
int32_t code = child->exit_code;
|
||||
child->state = PROCESS_UNUSED;
|
||||
return code;
|
||||
}
|
||||
|
||||
/* Block the current process until the child exits */
|
||||
process_t *cur = process_current();
|
||||
cur->state = PROCESS_BLOCKED;
|
||||
cur->waiting_for_pid = pid;
|
||||
|
||||
/* Save the current syscall registers so we resume here when unblocked.
|
||||
* The return value (eax) will be set by process_exit when the child dies. */
|
||||
cur->saved_regs = *regs;
|
||||
|
||||
/* Schedule the next process. This modifies *regs to the next process's
|
||||
* saved state, so when the ISR stub does iret, it enters the next process. */
|
||||
schedule_tick(regs);
|
||||
|
||||
/* Tell syscall_handler not to overwrite regs->eax, since regs now
|
||||
* points to the next process's registers on the kernel stack. */
|
||||
return SYSCALL_SWITCHED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if (ret != SYSCALL_SWITCHED) {
|
||||
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 */
|
||||
295
src/vfs.c
295
src/vfs.c
@@ -1,295 +0,0 @@
|
||||
/**
|
||||
* @file vfs.c
|
||||
* @brief Virtual Filesystem (VFS) subsystem implementation.
|
||||
*
|
||||
* Manages mount points and routes file operations to the appropriate
|
||||
* filesystem driver. Path resolution walks the mount table to find the
|
||||
* longest-matching mount point, then delegates to that fs's operations.
|
||||
*/
|
||||
|
||||
#include "vfs.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);
|
||||
|
||||
/** Mount table. */
|
||||
static vfs_mount_t mounts[VFS_MAX_MOUNTS];
|
||||
|
||||
/** Open file descriptor table. */
|
||||
static vfs_fd_t fd_table[VFS_MAX_OPEN_FILES];
|
||||
|
||||
/**
|
||||
* Find the mount point that best matches a given path.
|
||||
* Returns the index of the longest matching mount, or -1 if none.
|
||||
*
|
||||
* @param path The absolute path to resolve.
|
||||
* @param rel_path Output: pointer within `path` past the mount prefix.
|
||||
* @return Mount index, or -1.
|
||||
*/
|
||||
static int find_mount(const char *path, const char **rel_path) {
|
||||
int best = -1;
|
||||
uint32_t best_len = 0;
|
||||
|
||||
for (int i = 0; i < VFS_MAX_MOUNTS; i++) {
|
||||
if (!mounts[i].active) continue;
|
||||
|
||||
uint32_t mlen = strlen(mounts[i].path);
|
||||
|
||||
/* Check if path starts with this mount point */
|
||||
if (strncmp(path, mounts[i].path, mlen) != 0) continue;
|
||||
|
||||
/* Must match at a directory boundary */
|
||||
if (mlen > 1 && path[mlen] != '\0' && path[mlen] != '/') continue;
|
||||
|
||||
if (mlen > best_len) {
|
||||
best = i;
|
||||
best_len = mlen;
|
||||
}
|
||||
}
|
||||
|
||||
if (best >= 0 && rel_path) {
|
||||
const char *r = path + best_len;
|
||||
/* Skip leading slash in relative path */
|
||||
while (*r == '/') r++;
|
||||
*rel_path = r;
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a path to a VFS node by finding the appropriate mount
|
||||
* and asking the filesystem driver.
|
||||
*
|
||||
* @param path Full absolute path.
|
||||
* @param out Output node.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
static int resolve_path(const char *path, vfs_node_t *out) {
|
||||
const char *rel_path = NULL;
|
||||
int mount_idx = find_mount(path, &rel_path);
|
||||
|
||||
if (mount_idx < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
vfs_mount_t *mnt = &mounts[mount_idx];
|
||||
|
||||
/* If rel_path is empty, we're looking at the mount root */
|
||||
if (*rel_path == '\0') {
|
||||
/* Return a directory node for the mount root */
|
||||
memset(out, 0, sizeof(vfs_node_t));
|
||||
strncpy(out->name, mnt->path, VFS_MAX_NAME - 1);
|
||||
out->type = VFS_DIRECTORY;
|
||||
out->ops = mnt->ops;
|
||||
out->fs_data = mnt->fs_data;
|
||||
out->mount_idx = mount_idx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Walk path components through the filesystem */
|
||||
vfs_node_t current;
|
||||
memset(¤t, 0, sizeof(vfs_node_t));
|
||||
current.type = VFS_DIRECTORY;
|
||||
current.ops = mnt->ops;
|
||||
current.fs_data = mnt->fs_data;
|
||||
current.mount_idx = mount_idx;
|
||||
|
||||
/* Parse path components */
|
||||
char component[VFS_MAX_NAME];
|
||||
const char *p = rel_path;
|
||||
|
||||
while (*p) {
|
||||
/* Skip slashes */
|
||||
while (*p == '/') p++;
|
||||
if (*p == '\0') break;
|
||||
|
||||
/* Extract component */
|
||||
const char *end = p;
|
||||
while (*end && *end != '/') end++;
|
||||
|
||||
uint32_t clen = (uint32_t)(end - p);
|
||||
if (clen >= VFS_MAX_NAME) clen = VFS_MAX_NAME - 1;
|
||||
memcpy(component, p, clen);
|
||||
component[clen] = '\0';
|
||||
|
||||
/* Look up this component in the current directory */
|
||||
if (!current.ops || !current.ops->finddir) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
vfs_node_t child;
|
||||
if (current.ops->finddir(¤t, component, &child) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
child.ops = mnt->ops;
|
||||
/* Preserve fs_data set by finddir; only use mount fs_data if not set */
|
||||
if (!child.fs_data) {
|
||||
child.fs_data = mnt->fs_data;
|
||||
}
|
||||
child.mount_idx = mount_idx;
|
||||
current = child;
|
||||
p = end;
|
||||
}
|
||||
|
||||
*out = current;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a free file descriptor slot.
|
||||
* @return Index (>= 0), or -1 if all slots are used.
|
||||
*/
|
||||
static int alloc_fd(void) {
|
||||
/* Skip fds 0,1,2 (stdin, stdout, stderr) for user processes */
|
||||
for (int i = 3; i < VFS_MAX_OPEN_FILES; i++) {
|
||||
if (!fd_table[i].active) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void init_vfs(void) {
|
||||
memset(mounts, 0, sizeof(mounts));
|
||||
memset(fd_table, 0, sizeof(fd_table));
|
||||
offset_print(" VFS: initialized\n");
|
||||
}
|
||||
|
||||
int vfs_mount(const char *path, vfs_fs_ops_t *ops, void *fs_data) {
|
||||
/* Find a free mount slot */
|
||||
int slot = -1;
|
||||
for (int i = 0; i < VFS_MAX_MOUNTS; i++) {
|
||||
if (!mounts[i].active) {
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot < 0) {
|
||||
offset_print(" VFS: no free mount slots\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
strncpy(mounts[slot].path, path, VFS_MAX_PATH - 1);
|
||||
mounts[slot].ops = ops;
|
||||
mounts[slot].fs_data = fs_data;
|
||||
mounts[slot].active = 1;
|
||||
|
||||
offset_print(" VFS: mounted at ");
|
||||
offset_print(path);
|
||||
offset_print("\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfs_open(const char *path, uint32_t flags) {
|
||||
vfs_node_t node;
|
||||
if (resolve_path(path, &node) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fd = alloc_fd();
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Call filesystem's open if available */
|
||||
if (node.ops && node.ops->open) {
|
||||
if (node.ops->open(&node, flags) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
fd_table[fd].node = node;
|
||||
fd_table[fd].offset = 0;
|
||||
fd_table[fd].flags = flags;
|
||||
fd_table[fd].active = 1;
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
void vfs_close(int fd) {
|
||||
if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return;
|
||||
if (!fd_table[fd].active) return;
|
||||
|
||||
vfs_fd_t *f = &fd_table[fd];
|
||||
if (f->node.ops && f->node.ops->close) {
|
||||
f->node.ops->close(&f->node);
|
||||
}
|
||||
|
||||
f->active = 0;
|
||||
}
|
||||
|
||||
int32_t vfs_read(int fd, void *buf, uint32_t size) {
|
||||
if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return -1;
|
||||
if (!fd_table[fd].active) return -1;
|
||||
|
||||
vfs_fd_t *f = &fd_table[fd];
|
||||
if (!f->node.ops || !f->node.ops->read) return -1;
|
||||
|
||||
int32_t bytes = f->node.ops->read(&f->node, f->offset, size, buf);
|
||||
if (bytes > 0) {
|
||||
f->offset += (uint32_t)bytes;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
int32_t vfs_write(int fd, const void *buf, uint32_t size) {
|
||||
if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return -1;
|
||||
if (!fd_table[fd].active) return -1;
|
||||
|
||||
vfs_fd_t *f = &fd_table[fd];
|
||||
if (!f->node.ops || !f->node.ops->write) return -1;
|
||||
|
||||
int32_t bytes = f->node.ops->write(&f->node, f->offset, size, buf);
|
||||
if (bytes > 0) {
|
||||
f->offset += (uint32_t)bytes;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
int32_t vfs_seek(int fd, int32_t offset, int whence) {
|
||||
if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return -1;
|
||||
if (!fd_table[fd].active) return -1;
|
||||
|
||||
vfs_fd_t *f = &fd_table[fd];
|
||||
int32_t new_offset;
|
||||
|
||||
switch (whence) {
|
||||
case VFS_SEEK_SET:
|
||||
new_offset = offset;
|
||||
break;
|
||||
case VFS_SEEK_CUR:
|
||||
new_offset = (int32_t)f->offset + offset;
|
||||
break;
|
||||
case VFS_SEEK_END:
|
||||
new_offset = (int32_t)f->node.size + offset;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (new_offset < 0) return -1;
|
||||
f->offset = (uint32_t)new_offset;
|
||||
return new_offset;
|
||||
}
|
||||
|
||||
int vfs_readdir(const char *path, uint32_t idx, vfs_dirent_t *out) {
|
||||
vfs_node_t node;
|
||||
if (resolve_path(path, &node) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (node.type != VFS_DIRECTORY) return -1;
|
||||
if (!node.ops || !node.ops->readdir) return -1;
|
||||
|
||||
return node.ops->readdir(&node, idx, out);
|
||||
}
|
||||
|
||||
int vfs_stat(const char *path, vfs_node_t *out) {
|
||||
return resolve_path(path, out);
|
||||
}
|
||||
197
src/vfs.h
197
src/vfs.h
@@ -1,197 +0,0 @@
|
||||
/**
|
||||
* @file vfs.h
|
||||
* @brief Virtual Filesystem (VFS) subsystem.
|
||||
*
|
||||
* Provides a unified interface for file and directory operations across
|
||||
* different filesystem implementations. Filesystem drivers register
|
||||
* themselves and can be mounted at specific paths.
|
||||
*/
|
||||
|
||||
#ifndef VFS_H
|
||||
#define VFS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/** Maximum number of open files across all processes. */
|
||||
#define VFS_MAX_OPEN_FILES 256
|
||||
|
||||
/** Maximum number of mounted filesystems. */
|
||||
#define VFS_MAX_MOUNTS 16
|
||||
|
||||
/** Maximum path length. */
|
||||
#define VFS_MAX_PATH 256
|
||||
|
||||
/** Maximum filename length. */
|
||||
#define VFS_MAX_NAME 128
|
||||
|
||||
/** File types. */
|
||||
#define VFS_FILE 0x01
|
||||
#define VFS_DIRECTORY 0x02
|
||||
#define VFS_CHARDEV 0x03
|
||||
#define VFS_BLOCKDEV 0x04
|
||||
#define VFS_SYMLINK 0x06
|
||||
|
||||
/** Seek origins. */
|
||||
#define VFS_SEEK_SET 0
|
||||
#define VFS_SEEK_CUR 1
|
||||
#define VFS_SEEK_END 2
|
||||
|
||||
/** Forward declarations. */
|
||||
struct vfs_node;
|
||||
struct vfs_dirent;
|
||||
struct vfs_fs_ops;
|
||||
|
||||
/**
|
||||
* Directory entry, returned by readdir.
|
||||
*/
|
||||
typedef struct vfs_dirent {
|
||||
char name[VFS_MAX_NAME]; /**< Entry name. */
|
||||
uint32_t inode; /**< Inode number (fs-specific). */
|
||||
uint8_t type; /**< VFS_FILE, VFS_DIRECTORY, etc. */
|
||||
} vfs_dirent_t;
|
||||
|
||||
/**
|
||||
* VFS node representing a file or directory.
|
||||
*/
|
||||
typedef struct vfs_node {
|
||||
char name[VFS_MAX_NAME]; /**< Node name. */
|
||||
uint8_t type; /**< VFS_FILE, VFS_DIRECTORY, etc. */
|
||||
uint32_t size; /**< File size in bytes. */
|
||||
uint32_t inode; /**< Inode number (fs-specific). */
|
||||
uint32_t mode; /**< Permissions/mode. */
|
||||
|
||||
/** Filesystem-specific operations. */
|
||||
struct vfs_fs_ops *ops;
|
||||
|
||||
/** Opaque pointer for the filesystem driver. */
|
||||
void *fs_data;
|
||||
|
||||
/** Mount index (which mount this node belongs to). */
|
||||
int mount_idx;
|
||||
} vfs_node_t;
|
||||
|
||||
/**
|
||||
* Filesystem operations provided by each filesystem driver.
|
||||
*/
|
||||
typedef struct vfs_fs_ops {
|
||||
/** Open a file. Returns 0 on success. */
|
||||
int (*open)(vfs_node_t *node, uint32_t flags);
|
||||
|
||||
/** Close a file. */
|
||||
void (*close)(vfs_node_t *node);
|
||||
|
||||
/** Read up to `size` bytes at `offset`. Returns bytes read, or -1. */
|
||||
int32_t (*read)(vfs_node_t *node, uint32_t offset, uint32_t size, void *buf);
|
||||
|
||||
/** Write up to `size` bytes at `offset`. Returns bytes written, or -1. */
|
||||
int32_t (*write)(vfs_node_t *node, uint32_t offset, uint32_t size, const void *buf);
|
||||
|
||||
/** Read directory entry at index `idx`. Returns 0 on success, -1 at end. */
|
||||
int (*readdir)(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out);
|
||||
|
||||
/** Look up a child by name within a directory. Returns 0 on success. */
|
||||
int (*finddir)(vfs_node_t *dir, const char *name, vfs_node_t *out);
|
||||
} vfs_fs_ops_t;
|
||||
|
||||
/**
|
||||
* Mount point entry.
|
||||
*/
|
||||
typedef struct vfs_mount {
|
||||
char path[VFS_MAX_PATH]; /**< Mount path (e.g., "/initrd"). */
|
||||
vfs_fs_ops_t *ops; /**< Filesystem operations. */
|
||||
void *fs_data; /**< Filesystem-specific data. */
|
||||
int active; /**< 1 if mounted, 0 if free. */
|
||||
} vfs_mount_t;
|
||||
|
||||
/**
|
||||
* Open file descriptor.
|
||||
*/
|
||||
typedef struct vfs_fd {
|
||||
vfs_node_t node; /**< The file node. */
|
||||
uint32_t offset; /**< Current read/write offset. */
|
||||
uint32_t flags; /**< Open flags. */
|
||||
int active; /**< 1 if in use, 0 if free. */
|
||||
} vfs_fd_t;
|
||||
|
||||
/**
|
||||
* Initialize the VFS subsystem.
|
||||
*/
|
||||
void init_vfs(void);
|
||||
|
||||
/**
|
||||
* Mount a filesystem at the given path.
|
||||
*
|
||||
* @param path Mount point path (e.g., "/initrd").
|
||||
* @param ops Filesystem operations.
|
||||
* @param fs_data Filesystem-specific data pointer.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
int vfs_mount(const char *path, vfs_fs_ops_t *ops, void *fs_data);
|
||||
|
||||
/**
|
||||
* Open a file by path.
|
||||
*
|
||||
* @param path Absolute path to the file.
|
||||
* @param flags Open flags (currently unused).
|
||||
* @return File descriptor (>= 0), or -1 on failure.
|
||||
*/
|
||||
int vfs_open(const char *path, uint32_t flags);
|
||||
|
||||
/**
|
||||
* Close an open file descriptor.
|
||||
*
|
||||
* @param fd File descriptor.
|
||||
*/
|
||||
void vfs_close(int fd);
|
||||
|
||||
/**
|
||||
* Read from an open file.
|
||||
*
|
||||
* @param fd File descriptor.
|
||||
* @param buf Buffer to read into.
|
||||
* @param size Maximum bytes to read.
|
||||
* @return Bytes read, or -1 on error.
|
||||
*/
|
||||
int32_t vfs_read(int fd, void *buf, uint32_t size);
|
||||
|
||||
/**
|
||||
* Write to an open file.
|
||||
*
|
||||
* @param fd File descriptor.
|
||||
* @param buf Buffer to write from.
|
||||
* @param size Bytes to write.
|
||||
* @return Bytes written, or -1 on error.
|
||||
*/
|
||||
int32_t vfs_write(int fd, const void *buf, uint32_t size);
|
||||
|
||||
/**
|
||||
* Seek within an open file.
|
||||
*
|
||||
* @param fd File descriptor.
|
||||
* @param offset Offset to seek to.
|
||||
* @param whence VFS_SEEK_SET, VFS_SEEK_CUR, or VFS_SEEK_END.
|
||||
* @return New offset, or -1 on error.
|
||||
*/
|
||||
int32_t vfs_seek(int fd, int32_t offset, int whence);
|
||||
|
||||
/**
|
||||
* Read a directory entry.
|
||||
*
|
||||
* @param path Path to the directory.
|
||||
* @param idx Entry index (0-based).
|
||||
* @param out Output directory entry.
|
||||
* @return 0 on success, -1 at end or on error.
|
||||
*/
|
||||
int vfs_readdir(const char *path, uint32_t idx, vfs_dirent_t *out);
|
||||
|
||||
/**
|
||||
* Stat a file (get its node info).
|
||||
*
|
||||
* @param path Path to the file.
|
||||
* @param out Output node.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
int vfs_stat(const char *path, vfs_node_t *out);
|
||||
|
||||
#endif /* VFS_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