Compare commits
35 Commits
master
...
3512e937ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3512e937ae | ||
|
|
c3c01049bf | ||
|
|
d3ab5a5b55 | ||
|
|
e3d011da2f | ||
|
|
993cf05712 | ||
|
|
000d53e2f3 | ||
|
|
c25ba1fccd | ||
|
|
e9b66cd60e | ||
|
|
9cef025687 | ||
|
|
6910deae7c | ||
|
|
42328ead0b | ||
|
|
f1de5b6da6 | ||
|
|
a6e6e3d8ca | ||
|
|
0c5aa72fd3 | ||
|
|
3d5fb4c267 | ||
|
|
71e2ae482a | ||
|
|
313aeb5872 | ||
|
|
bb09de6a6d | ||
|
|
f63cd9eb3f | ||
|
|
fb61ab7c15 | ||
|
|
f2e7d6c5d7 | ||
|
|
cf3059747a | ||
|
|
f1923fdbcf | ||
| 3909a1f581 | |||
| 7c45406c23 | |||
|
|
e4cc638a8d | ||
| c7e9833819 | |||
|
|
6b00cf3154 | ||
|
|
f2e84924f5 | ||
| 6d91edc897 | |||
|
|
7068176d91 | ||
|
|
436aeceb10 | ||
|
|
aa954045c1 | ||
|
|
a048764a3d | ||
|
|
34382babb3 |
1
.github/copilot-instructions.md
vendored
Normal file
1
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
See `README.md` for a general overview on how to work and for a list of tasks to perform.
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1 +1,9 @@
|
|||||||
/build/
|
build/
|
||||||
|
release/
|
||||||
|
*.img
|
||||||
|
*.iso
|
||||||
|
debug_grub/
|
||||||
|
*_output.txt
|
||||||
|
snippet.*
|
||||||
|
qemu.log
|
||||||
|
iso_output.txt
|
||||||
|
|||||||
58
CMakeLists.txt
Normal file
58
CMakeLists.txt
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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\" {\n multiboot2 /boot/kernel.bin\n module2 /boot/initrd.cpio\n}")
|
||||||
|
|
||||||
|
|
||||||
|
# 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}
|
||||||
|
)
|
||||||
38
README.md
38
README.md
@@ -37,24 +37,26 @@ The git commit should describe what is done and why certain choices were made.
|
|||||||
The title of the commit should always end with (AI), to indicate that it was written by an AI.
|
The title of the commit should always end with (AI), to indicate that it was written by an AI.
|
||||||
Once a task is completed, it should be checked off.
|
Once a task is completed, it should be checked off.
|
||||||
|
|
||||||
- [ ] Create directory structure
|
- [x] Create directory structure
|
||||||
- [ ] Create initial build system
|
- [x] Create initial build system
|
||||||
- [ ] Setup a simple kernel that writes `Hello, world` to Qemu debug port
|
- [x] 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. The standard CMake build target should automatically generate both images.
|
- [x] Update the build system to create both ISO and Floppy images. Verify these work using a test script. The standard CMake build target should automatically generate both images. (Only ISO supported for now)
|
||||||
- [ ] Update the kernel to correctly setup the GDT
|
- [x] 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.
|
- [x] Create an interrupt handler.
|
||||||
- [ ] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
|
- [x] Implement a PIC handler
|
||||||
- [ ] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them.
|
- [x] Create a 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 an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
|
- [x] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
|
||||||
- [ ] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
|
- [x] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them.
|
||||||
- [ ] Create subsystem for loading new processes in Ring 3.
|
- [x] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
|
||||||
- [ ] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
|
- [x] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
|
||||||
- [ ] Write a VFS subsystem.
|
- [x] Create subsystem for loading new processes in Ring 3.
|
||||||
- [ ] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer.
|
- [x] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
|
||||||
- [ ] 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] Write a VFS subsystem.
|
||||||
- [ ] Implement the fork system call.
|
- [x] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer.
|
||||||
- [ ] Implement environment variables. Apps should be able to modify this, and it should be copied to new forks of an app.
|
- [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.
|
||||||
- [ ] 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.
|
- [x] Implement the fork system call.
|
||||||
|
- [x] Implement environment variables. Apps should be able to modify this, and it should be copied to new forks of an app.
|
||||||
|
- [x] 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.
|
- [ ] Create an `ls` app. It should list the contents of the current working directory, via the environment variable.
|
||||||
- [ ] Create a devicefs vfs driver. The devicefs subsystem should allow drivers to create new block devices devices.
|
- [ ] Create a devicefs vfs driver. The devicefs subsystem should allow drivers to create new block devices devices.
|
||||||
- [ ] Create an IDE driver. It should enumerate all IDE devices, and expose them to the vfs driver as `hddN` or `cdN`, where N is a number that starts at 1 and increases for each new device. The `N` value should be calucated by the devicefs subsystem, not the IDE driver.
|
- [ ] Create an IDE driver. It should enumerate all IDE devices, and expose them to the vfs driver as `hddN` or `cdN`, where N is a number that starts at 1 and increases for each new device. The `N` value should be calucated by the devicefs subsystem, not the IDE driver.
|
||||||
|
|||||||
1
apps/README
Normal file
1
apps/README
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This is the ClaudeOS initial ramdisk.
|
||||||
123
apps/env-test/env-test.S
Normal file
123
apps/env-test/env-test.S
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#
|
||||||
|
# env-test: Tests environment variable syscalls.
|
||||||
|
#
|
||||||
|
# 1. Gets CWD (should be "/")
|
||||||
|
# 2. Sets TEST=hello
|
||||||
|
# 3. Gets TEST (should be "hello")
|
||||||
|
# 4. Forks - child gets TEST, verifies it was copied
|
||||||
|
# 5. Both print results and exit
|
||||||
|
#
|
||||||
|
|
||||||
|
.section .text
|
||||||
|
.global _start
|
||||||
|
|
||||||
|
.equ SYS_EXIT, 0
|
||||||
|
.equ SYS_WRITE, 1
|
||||||
|
.equ SYS_FORK, 3
|
||||||
|
.equ SYS_WAITPID, 6
|
||||||
|
.equ SYS_GETENV, 8
|
||||||
|
.equ SYS_SETENV, 9
|
||||||
|
|
||||||
|
# Helper: write a string (addr in %ecx, len in %edx) to stdout
|
||||||
|
.macro WRITE_STR addr, len
|
||||||
|
movl $SYS_WRITE, %eax
|
||||||
|
movl $1, %ebx
|
||||||
|
movl \addr, %ecx
|
||||||
|
movl \len, %edx
|
||||||
|
int $0x80
|
||||||
|
.endm
|
||||||
|
|
||||||
|
_start:
|
||||||
|
# 1. Get CWD env var
|
||||||
|
movl $SYS_GETENV, %eax
|
||||||
|
movl $key_cwd, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
movl $128, %edx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
# Print "CWD="
|
||||||
|
WRITE_STR $label_cwd, $4
|
||||||
|
# Print the value (length returned in eax)
|
||||||
|
movl %eax, %edx # length
|
||||||
|
movl $SYS_WRITE, %eax
|
||||||
|
movl $1, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
int $0x80
|
||||||
|
WRITE_STR $newline, $1
|
||||||
|
|
||||||
|
# 2. Set TEST=hello
|
||||||
|
movl $SYS_SETENV, %eax
|
||||||
|
movl $key_test, %ebx
|
||||||
|
movl $val_hello, %ecx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
# 3. Get TEST
|
||||||
|
movl $SYS_GETENV, %eax
|
||||||
|
movl $key_test, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
movl $128, %edx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
# Print "TEST="
|
||||||
|
WRITE_STR $label_test, $5
|
||||||
|
movl %eax, %edx
|
||||||
|
movl $SYS_WRITE, %eax
|
||||||
|
movl $1, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
int $0x80
|
||||||
|
WRITE_STR $newline, $1
|
||||||
|
|
||||||
|
# 4. Fork
|
||||||
|
movl $SYS_FORK, %eax
|
||||||
|
int $0x80
|
||||||
|
testl %eax, %eax
|
||||||
|
jz .child
|
||||||
|
|
||||||
|
.parent:
|
||||||
|
pushl %eax # save child PID
|
||||||
|
WRITE_STR $msg_parent, $15
|
||||||
|
|
||||||
|
# Wait for child
|
||||||
|
popl %ebx
|
||||||
|
movl $SYS_WAITPID, %eax
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
# Exit
|
||||||
|
movl $SYS_EXIT, %eax
|
||||||
|
movl $0, %ebx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
.child:
|
||||||
|
# Child: get TEST to verify it was inherited
|
||||||
|
movl $SYS_GETENV, %eax
|
||||||
|
movl $key_test, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
movl $128, %edx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
# Print "Child TEST="
|
||||||
|
WRITE_STR $label_child_test, $11
|
||||||
|
movl %eax, %edx
|
||||||
|
movl $SYS_WRITE, %eax
|
||||||
|
movl $1, %ebx
|
||||||
|
movl $buf, %ecx
|
||||||
|
int $0x80
|
||||||
|
WRITE_STR $newline, $1
|
||||||
|
|
||||||
|
# Exit
|
||||||
|
movl $SYS_EXIT, %eax
|
||||||
|
movl $0, %ebx
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
.section .rodata
|
||||||
|
key_cwd: .asciz "CWD"
|
||||||
|
key_test: .asciz "TEST"
|
||||||
|
val_hello: .asciz "hello"
|
||||||
|
label_cwd: .ascii "CWD="
|
||||||
|
label_test: .ascii "TEST="
|
||||||
|
label_child_test: .ascii "Child TEST="
|
||||||
|
msg_parent: .ascii "Parent done!\n\0\0"
|
||||||
|
newline: .ascii "\n"
|
||||||
|
|
||||||
|
.section .bss
|
||||||
|
buf: .space 128
|
||||||
79
apps/fork-test/fork-test.S
Normal file
79
apps/fork-test/fork-test.S
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#
|
||||||
|
# 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
|
||||||
28
apps/hello-world/hello-world.S
Normal file
28
apps/hello-world/hello-world.S
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# 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"
|
||||||
21
apps/libc/crt0.S
Normal file
21
apps/libc/crt0.S
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @file crt0.S
|
||||||
|
* @brief C runtime startup for user-mode applications.
|
||||||
|
*
|
||||||
|
* Calls the C main() function and then exits with the return value.
|
||||||
|
*/
|
||||||
|
.section .text
|
||||||
|
.global _start
|
||||||
|
.extern main
|
||||||
|
|
||||||
|
_start:
|
||||||
|
/* Call main() */
|
||||||
|
call main
|
||||||
|
|
||||||
|
/* Exit with main's return value (in EAX) */
|
||||||
|
movl %eax, %ebx /* exit code = return value */
|
||||||
|
movl $0, %eax /* SYS_EXIT = 0 */
|
||||||
|
int $0x80
|
||||||
|
|
||||||
|
/* Should never reach here */
|
||||||
|
1: jmp 1b
|
||||||
159
apps/libc/syscalls.h
Normal file
159
apps/libc/syscalls.h
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
/**
|
||||||
|
* @file syscalls.h
|
||||||
|
* @brief User-space system call wrappers for ClaudeOS.
|
||||||
|
*
|
||||||
|
* Provides inline functions that invoke INT 0x80 with the appropriate
|
||||||
|
* system call numbers and arguments.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef USERSPACE_SYSCALLS_H
|
||||||
|
#define USERSPACE_SYSCALLS_H
|
||||||
|
|
||||||
|
typedef unsigned int uint32_t;
|
||||||
|
typedef int int32_t;
|
||||||
|
|
||||||
|
/* System call numbers (must match kernel's syscall.h) */
|
||||||
|
#define SYS_EXIT 0
|
||||||
|
#define SYS_WRITE 1
|
||||||
|
#define SYS_READ 2
|
||||||
|
#define SYS_FORK 3
|
||||||
|
#define SYS_GETPID 4
|
||||||
|
#define SYS_YIELD 5
|
||||||
|
#define SYS_WAITPID 6
|
||||||
|
#define SYS_EXEC 7
|
||||||
|
#define SYS_GETENV 8
|
||||||
|
#define SYS_SETENV 9
|
||||||
|
#define SYS_READDIR 10
|
||||||
|
|
||||||
|
static inline int32_t syscall0(int num) {
|
||||||
|
int32_t ret;
|
||||||
|
__asm__ volatile("int $0x80" : "=a"(ret) : "a"(num));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t syscall1(int num, uint32_t arg1) {
|
||||||
|
int32_t ret;
|
||||||
|
__asm__ volatile("int $0x80" : "=a"(ret) : "a"(num), "b"(arg1));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t syscall2(int num, uint32_t arg1, uint32_t arg2) {
|
||||||
|
int32_t ret;
|
||||||
|
__asm__ volatile("int $0x80" : "=a"(ret)
|
||||||
|
: "a"(num), "b"(arg1), "c"(arg2));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t syscall3(int num, uint32_t arg1, uint32_t arg2, uint32_t arg3) {
|
||||||
|
int32_t ret;
|
||||||
|
__asm__ volatile("int $0x80" : "=a"(ret)
|
||||||
|
: "a"(num), "b"(arg1), "c"(arg2), "d"(arg3));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void exit(int code) {
|
||||||
|
syscall1(SYS_EXIT, (uint32_t)code);
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t write(int fd, const void *buf, uint32_t len) {
|
||||||
|
return syscall3(SYS_WRITE, (uint32_t)fd, (uint32_t)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t read(int fd, void *buf, uint32_t len) {
|
||||||
|
return syscall3(SYS_READ, (uint32_t)fd, (uint32_t)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t fork(void) {
|
||||||
|
return syscall0(SYS_FORK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t getpid(void) {
|
||||||
|
return syscall0(SYS_GETPID);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void yield(void) {
|
||||||
|
syscall0(SYS_YIELD);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t waitpid(int32_t pid) {
|
||||||
|
return syscall1(SYS_WAITPID, (uint32_t)pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t exec(const char *path) {
|
||||||
|
return syscall1(SYS_EXEC, (uint32_t)path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t getenv(const char *key, char *buf, uint32_t bufsize) {
|
||||||
|
return syscall3(SYS_GETENV, (uint32_t)key, (uint32_t)buf, bufsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t setenv(const char *key, const char *value) {
|
||||||
|
return syscall2(SYS_SETENV, (uint32_t)key, (uint32_t)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a directory entry.
|
||||||
|
* @param path Directory path.
|
||||||
|
* @param idx Entry index (0-based).
|
||||||
|
* @param name Buffer for entry name (128 bytes min).
|
||||||
|
* @return Entry type (1=file, 2=dir) on success, -1 at end.
|
||||||
|
*/
|
||||||
|
static inline int32_t readdir(const char *path, uint32_t idx, char *name) {
|
||||||
|
return syscall3(SYS_READDIR, (uint32_t)path, idx, (uint32_t)name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Basic string operations for user-space */
|
||||||
|
static inline uint32_t strlen(const char *s) {
|
||||||
|
uint32_t len = 0;
|
||||||
|
while (s[len]) len++;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int strcmp(const char *a, const char *b) {
|
||||||
|
while (*a && *a == *b) { a++; b++; }
|
||||||
|
return (unsigned char)*a - (unsigned char)*b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int strncmp(const char *a, const char *b, uint32_t n) {
|
||||||
|
while (n && *a && *a == *b) { a++; b++; n--; }
|
||||||
|
return n ? ((unsigned char)*a - (unsigned char)*b) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char *strcpy(char *dst, const char *src) {
|
||||||
|
char *d = dst;
|
||||||
|
while ((*d++ = *src++));
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char *strncpy(char *dst, const char *src, uint32_t n) {
|
||||||
|
char *d = dst;
|
||||||
|
while (n && (*d++ = *src++)) n--;
|
||||||
|
while (n--) *d++ = '\0';
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void *memset(void *s, int c, uint32_t n) {
|
||||||
|
unsigned char *p = (unsigned char *)s;
|
||||||
|
while (n--) *p++ = (unsigned char)c;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void *memcpy(void *dst, const void *src, uint32_t n) {
|
||||||
|
unsigned char *d = (unsigned char *)dst;
|
||||||
|
const unsigned char *s = (const unsigned char *)src;
|
||||||
|
while (n--) *d++ = *s++;
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Print a string to stdout. */
|
||||||
|
static inline void puts(const char *s) {
|
||||||
|
write(1, s, strlen(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Print a single character to stdout. */
|
||||||
|
static inline void putchar(char c) {
|
||||||
|
write(1, &c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* USERSPACE_SYSCALLS_H */
|
||||||
36
apps/ls/ls.c
Normal file
36
apps/ls/ls.c
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* @file ls.c
|
||||||
|
* @brief List directory contents.
|
||||||
|
*
|
||||||
|
* Lists entries in the current working directory (from the CWD
|
||||||
|
* environment variable). Uses the SYS_READDIR syscall to enumerate
|
||||||
|
* directory entries.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "syscalls.h"
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
/* Get the current working directory */
|
||||||
|
char cwd[128];
|
||||||
|
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
|
||||||
|
/* Default to root if CWD not set */
|
||||||
|
cwd[0] = '/';
|
||||||
|
cwd[1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char name[128];
|
||||||
|
uint32_t idx = 0;
|
||||||
|
int32_t type;
|
||||||
|
|
||||||
|
while ((type = readdir(cwd, idx, name)) >= 0) {
|
||||||
|
puts(name);
|
||||||
|
if (type == 2) {
|
||||||
|
/* Directory: append / indicator */
|
||||||
|
putchar('/');
|
||||||
|
}
|
||||||
|
putchar('\n');
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
180
apps/sh/sh.c
Normal file
180
apps/sh/sh.c
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/**
|
||||||
|
* @file sh.c
|
||||||
|
* @brief ClaudeOS shell.
|
||||||
|
*
|
||||||
|
* A simple interactive shell that reads commands from stdin,
|
||||||
|
* supports built-in commands (cd, exit, help, env), and
|
||||||
|
* executes external programs via fork+exec.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "syscalls.h"
|
||||||
|
|
||||||
|
/** Maximum command line length. */
|
||||||
|
#define CMD_MAX 256
|
||||||
|
|
||||||
|
/** Read a line from stdin with echo and basic line editing.
|
||||||
|
* Returns length of the line (excluding newline). */
|
||||||
|
static int readline(char *buf, int maxlen) {
|
||||||
|
int pos = 0;
|
||||||
|
while (pos < maxlen - 1) {
|
||||||
|
char c;
|
||||||
|
int n = read(0, &c, 1);
|
||||||
|
if (n <= 0) {
|
||||||
|
/* No data yet: yield CPU and retry */
|
||||||
|
yield();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '\n' || c == '\r') {
|
||||||
|
putchar('\n');
|
||||||
|
break;
|
||||||
|
} else if (c == '\b' || c == 127) {
|
||||||
|
/* Backspace */
|
||||||
|
if (pos > 0) {
|
||||||
|
pos--;
|
||||||
|
puts("\b \b"); /* Move back, overwrite with space, move back */
|
||||||
|
}
|
||||||
|
} else if (c >= 32) {
|
||||||
|
/* Printable character */
|
||||||
|
buf[pos++] = c;
|
||||||
|
putchar(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf[pos] = '\0';
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Skip leading whitespace, return pointer to first non-space. */
|
||||||
|
static char *skip_spaces(char *s) {
|
||||||
|
while (*s == ' ' || *s == '\t') s++;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find the next space or end of string. */
|
||||||
|
static char *find_space(char *s) {
|
||||||
|
while (*s && *s != ' ' && *s != '\t') s++;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Built-in: cd <path> */
|
||||||
|
static void builtin_cd(char *arg) {
|
||||||
|
if (!arg || !*arg) {
|
||||||
|
arg = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update CWD environment variable */
|
||||||
|
setenv("CWD", arg);
|
||||||
|
/* Verify it was set */
|
||||||
|
char cwd[128];
|
||||||
|
if (getenv("CWD", cwd, sizeof(cwd)) >= 0) {
|
||||||
|
puts("cd: ");
|
||||||
|
puts(cwd);
|
||||||
|
putchar('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Built-in: env - print all known env vars. */
|
||||||
|
static void builtin_env(void) {
|
||||||
|
char buf[128];
|
||||||
|
if (getenv("CWD", buf, sizeof(buf)) >= 0) {
|
||||||
|
puts("CWD=");
|
||||||
|
puts(buf);
|
||||||
|
putchar('\n');
|
||||||
|
}
|
||||||
|
if (getenv("PATH", buf, sizeof(buf)) >= 0) {
|
||||||
|
puts("PATH=");
|
||||||
|
puts(buf);
|
||||||
|
putchar('\n');
|
||||||
|
}
|
||||||
|
if (getenv("TEST", buf, sizeof(buf)) >= 0) {
|
||||||
|
puts("TEST=");
|
||||||
|
puts(buf);
|
||||||
|
putchar('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Built-in: help */
|
||||||
|
static void builtin_help(void) {
|
||||||
|
puts("ClaudeOS Shell\n");
|
||||||
|
puts("Built-in commands:\n");
|
||||||
|
puts(" cd <path> - change working directory\n");
|
||||||
|
puts(" env - show environment variables\n");
|
||||||
|
puts(" help - show this message\n");
|
||||||
|
puts(" exit - exit the shell\n");
|
||||||
|
puts("External commands are loaded from initrd.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Execute an external command via fork+exec. */
|
||||||
|
static void run_command(const char *cmd) {
|
||||||
|
int32_t pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
puts("sh: fork failed\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid == 0) {
|
||||||
|
/* Child: exec the command */
|
||||||
|
int32_t ret = exec(cmd);
|
||||||
|
if (ret < 0) {
|
||||||
|
puts("sh: ");
|
||||||
|
puts(cmd);
|
||||||
|
puts(": not found\n");
|
||||||
|
exit(127);
|
||||||
|
}
|
||||||
|
/* exec doesn't return on success */
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parent: wait for child */
|
||||||
|
waitpid(pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
puts("ClaudeOS Shell v0.1\n");
|
||||||
|
puts("Type 'help' for available commands.\n\n");
|
||||||
|
|
||||||
|
char cmd[CMD_MAX];
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
/* Print prompt with CWD */
|
||||||
|
char cwd[128];
|
||||||
|
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
|
||||||
|
strcpy(cwd, "/");
|
||||||
|
}
|
||||||
|
puts(cwd);
|
||||||
|
puts("$ ");
|
||||||
|
|
||||||
|
/* Read command */
|
||||||
|
int len = readline(cmd, CMD_MAX);
|
||||||
|
if (len == 0) continue;
|
||||||
|
|
||||||
|
/* Parse command and arguments */
|
||||||
|
char *line = skip_spaces(cmd);
|
||||||
|
if (*line == '\0') continue;
|
||||||
|
|
||||||
|
char *arg_start = find_space(line);
|
||||||
|
char *arg = (char *)0;
|
||||||
|
if (*arg_start) {
|
||||||
|
*arg_start = '\0';
|
||||||
|
arg = skip_spaces(arg_start + 1);
|
||||||
|
if (*arg == '\0') arg = (char *)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check built-in commands */
|
||||||
|
if (strcmp(line, "exit") == 0) {
|
||||||
|
puts("Goodbye!\n");
|
||||||
|
exit(0);
|
||||||
|
} else if (strcmp(line, "cd") == 0) {
|
||||||
|
builtin_cd(arg);
|
||||||
|
} else if (strcmp(line, "env") == 0) {
|
||||||
|
builtin_env();
|
||||||
|
} else if (strcmp(line, "help") == 0) {
|
||||||
|
builtin_help();
|
||||||
|
} else {
|
||||||
|
/* External command */
|
||||||
|
run_command(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
25
apps/user.ld
Normal file
25
apps/user.ld
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
81
docs/cpio.md
Normal file
81
docs/cpio.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# 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
Normal file
83
docs/fork.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# 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
|
||||||
62
docs/interrupts.md
Normal file
62
docs/interrupts.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# 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`.
|
||||||
61
docs/kmalloc.md
Normal file
61
docs/kmalloc.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# 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()`.
|
||||||
75
docs/paging.md
Normal file
75
docs/paging.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# 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
Normal file
63
docs/pmm.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# 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
Normal file
109
docs/process.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# 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
Normal file
78
docs/vfs.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# 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
|
||||||
68
scripts/build_apps.sh
Executable file
68
scripts/build_apps.sh
Executable file
@@ -0,0 +1,68 @@
|
|||||||
|
#!/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 -I$APPS_DIR/libc"
|
||||||
|
LDFLAGS="-m32 -nostdlib -no-pie -Wl,--no-dynamic-linker"
|
||||||
|
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
|
# Build crt0 if it exists
|
||||||
|
CRT0_OBJ=""
|
||||||
|
if [ -f "$APPS_DIR/libc/crt0.S" ]; then
|
||||||
|
CRT0_OBJ="$OUTPUT_DIR/_crt0.o"
|
||||||
|
$CC $CFLAGS -c "$APPS_DIR/libc/crt0.S" -o "$CRT0_OBJ"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for app_dir in "$APPS_DIR"/*/; do
|
||||||
|
[ -d "$app_dir" ] || continue
|
||||||
|
app_name=$(basename "$app_dir")
|
||||||
|
|
||||||
|
# Skip the libc directory (shared library, not an app)
|
||||||
|
[ "$app_name" = "libc" ] && continue
|
||||||
|
|
||||||
|
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 (include crt0 if app has .c files and doesn't have its own _start)
|
||||||
|
elf="$OUTPUT_DIR/$app_name.elf"
|
||||||
|
HAS_C_FILES=""
|
||||||
|
for src in "$app_dir"*.c; do
|
||||||
|
[ -f "$src" ] && HAS_C_FILES="yes"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$HAS_C_FILES" ] && [ -n "$CRT0_OBJ" ]; then
|
||||||
|
$CC $LDFLAGS -T "$LINKER_SCRIPT" "$CRT0_OBJ" $OBJ_FILES -o "$elf"
|
||||||
|
else
|
||||||
|
$CC $LDFLAGS -T "$LINKER_SCRIPT" $OBJ_FILES -o "$elf"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Convert to flat binary (include .bss for zero-initialized data)
|
||||||
|
bin="$OUTPUT_DIR/$app_name"
|
||||||
|
$OBJCOPY -O binary --only-section=.text --only-section=.rodata --only-section=.data --only-section=.bss "$elf" "$bin"
|
||||||
|
|
||||||
|
size=$(wc -c < "$bin")
|
||||||
|
echo " Built: $bin ($size bytes)"
|
||||||
|
done
|
||||||
16
scripts/gen_initrd.sh
Executable file
16
scripts/gen_initrd.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/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"
|
||||||
34
src/CMakeLists.txt
Normal file
34
src/CMakeLists.txt
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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
|
||||||
|
env.c
|
||||||
|
keyboard.c
|
||||||
|
interrupts.S
|
||||||
|
kernel.c
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use our custom linker script
|
||||||
|
target_link_options(kernel PRIVATE -T ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld)
|
||||||
|
|
||||||
|
target_include_directories(kernel PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/vendor
|
||||||
|
${CMAKE_SOURCE_DIR}/include # If created later
|
||||||
|
)
|
||||||
64
src/boot.S
Normal file
64
src/boot.S
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#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))
|
||||||
|
|
||||||
|
/* End tag */
|
||||||
|
.align 8
|
||||||
|
.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
|
||||||
179
src/cpio.c
Normal file
179
src/cpio.c
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
/**
|
||||||
|
* @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
Normal file
92
src/cpio.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* @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 */
|
||||||
135
src/devicefs.h
Normal file
135
src/devicefs.h
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/**
|
||||||
|
* @file devicefs.h
|
||||||
|
* @brief Device filesystem (devicefs) subsystem.
|
||||||
|
*
|
||||||
|
* Provides a VFS interface at /dev for exposing block and character devices.
|
||||||
|
* Drivers register devices through the devicefs API, and each device is
|
||||||
|
* assigned a sequential number by device class (e.g., hdd1, hdd2, cd1).
|
||||||
|
*
|
||||||
|
* The devicefs owns device naming — drivers specify a class name (e.g., "hdd")
|
||||||
|
* and the devicefs appends a sequential number.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DEVICEFS_H
|
||||||
|
#define DEVICEFS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/** Maximum number of registered devices. */
|
||||||
|
#define DEVICEFS_MAX_DEVICES 32
|
||||||
|
|
||||||
|
/** Maximum length of a device class name (e.g., "hdd", "cd", "floppy"). */
|
||||||
|
#define DEVICEFS_MAX_CLASS_NAME 16
|
||||||
|
|
||||||
|
/** Maximum length of a full device name (class + number, e.g., "hdd1"). */
|
||||||
|
#define DEVICEFS_MAX_DEV_NAME 32
|
||||||
|
|
||||||
|
/** Device types. */
|
||||||
|
#define DEVICEFS_BLOCK 0x01 /**< Block device (e.g., hard drives, CDs). */
|
||||||
|
#define DEVICEFS_CHAR 0x02 /**< Character device (e.g., serial ports). */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block device operations.
|
||||||
|
*
|
||||||
|
* Block devices transfer data in fixed-size sectors.
|
||||||
|
*/
|
||||||
|
typedef struct devicefs_block_ops {
|
||||||
|
/** Read `count` sectors starting at `lba` into `buf`. Returns 0 on success. */
|
||||||
|
int (*read_sectors)(void *dev_data, uint32_t lba, uint32_t count, void *buf);
|
||||||
|
|
||||||
|
/** Write `count` sectors from `buf` starting at `lba`. Returns 0 on success. */
|
||||||
|
int (*write_sectors)(void *dev_data, uint32_t lba, uint32_t count, const void *buf);
|
||||||
|
|
||||||
|
/** Get the sector size in bytes. */
|
||||||
|
uint32_t (*sector_size)(void *dev_data);
|
||||||
|
|
||||||
|
/** Get total number of sectors. */
|
||||||
|
uint32_t (*sector_count)(void *dev_data);
|
||||||
|
} devicefs_block_ops_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Character device operations.
|
||||||
|
*
|
||||||
|
* Character devices transfer data as byte streams.
|
||||||
|
*/
|
||||||
|
typedef struct devicefs_char_ops {
|
||||||
|
/** Read up to `size` bytes into `buf`. Returns bytes read, or -1. */
|
||||||
|
int32_t (*read)(void *dev_data, uint32_t size, void *buf);
|
||||||
|
|
||||||
|
/** Write `size` bytes from `buf`. Returns bytes written, or -1. */
|
||||||
|
int32_t (*write)(void *dev_data, uint32_t size, const void *buf);
|
||||||
|
} devicefs_char_ops_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registered device entry.
|
||||||
|
*/
|
||||||
|
typedef struct devicefs_device {
|
||||||
|
char name[DEVICEFS_MAX_DEV_NAME]; /**< Full device name (e.g., "hdd1"). */
|
||||||
|
char class_name[DEVICEFS_MAX_CLASS_NAME]; /**< Device class (e.g., "hdd"). */
|
||||||
|
uint8_t type; /**< DEVICEFS_BLOCK or DEVICEFS_CHAR. */
|
||||||
|
uint32_t number; /**< Assigned device number within class. */
|
||||||
|
int active; /**< 1 if registered, 0 if free. */
|
||||||
|
|
||||||
|
/** Device-specific operations (union of block/char). */
|
||||||
|
union {
|
||||||
|
devicefs_block_ops_t *block_ops;
|
||||||
|
devicefs_char_ops_t *char_ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Opaque driver-specific data passed to operation callbacks. */
|
||||||
|
void *dev_data;
|
||||||
|
} devicefs_device_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the devicefs subsystem and mount at /dev.
|
||||||
|
*
|
||||||
|
* @return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
int init_devicefs(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new block device.
|
||||||
|
*
|
||||||
|
* The devicefs assigns a sequential number within the class. For example,
|
||||||
|
* registering class "hdd" twice yields "hdd1" and "hdd2".
|
||||||
|
*
|
||||||
|
* @param class_name Device class name (e.g., "hdd", "cd").
|
||||||
|
* @param ops Block device operations.
|
||||||
|
* @param dev_data Opaque data passed to operation callbacks.
|
||||||
|
* @return Pointer to the device entry, or NULL on failure.
|
||||||
|
*/
|
||||||
|
devicefs_device_t *devicefs_register_block(const char *class_name,
|
||||||
|
devicefs_block_ops_t *ops,
|
||||||
|
void *dev_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new character device.
|
||||||
|
*
|
||||||
|
* @param class_name Device class name (e.g., "tty", "serial").
|
||||||
|
* @param ops Character device operations.
|
||||||
|
* @param dev_data Opaque data passed to operation callbacks.
|
||||||
|
* @return Pointer to the device entry, or NULL on failure.
|
||||||
|
*/
|
||||||
|
devicefs_device_t *devicefs_register_char(const char *class_name,
|
||||||
|
devicefs_char_ops_t *ops,
|
||||||
|
void *dev_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a device by its full name (e.g., "hdd1").
|
||||||
|
*
|
||||||
|
* @param name Device name.
|
||||||
|
* @return Pointer to the device entry, or NULL if not found.
|
||||||
|
*/
|
||||||
|
devicefs_device_t *devicefs_find(const char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next device number for a given class.
|
||||||
|
* This is called internally but may be useful for drivers.
|
||||||
|
*
|
||||||
|
* @param class_name Device class name.
|
||||||
|
* @return Next sequential number (starting from 1).
|
||||||
|
*/
|
||||||
|
uint32_t devicefs_next_number(const char *class_name);
|
||||||
|
|
||||||
|
#endif /* DEVICEFS_H */
|
||||||
72
src/driver.c
Normal file
72
src/driver.c
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* @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
Normal file
56
src/driver.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* @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 */
|
||||||
104
src/env.c
Normal file
104
src/env.c
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* @file env.c
|
||||||
|
* @brief Per-process environment variable implementation.
|
||||||
|
*
|
||||||
|
* Provides a simple key=value store per process. Each process has a fixed
|
||||||
|
* array of ENV_MAX_VARS entries. Empty key strings indicate unused slots.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "env.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void env_init(env_block_t *env) {
|
||||||
|
memset(env, 0, sizeof(env_block_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the slot index for a given key.
|
||||||
|
*
|
||||||
|
* @param env Pointer to the environment block.
|
||||||
|
* @param key Variable name to search for.
|
||||||
|
* @return Slot index, or -1 if not found.
|
||||||
|
*/
|
||||||
|
static int env_find(const env_block_t *env, const char *key) {
|
||||||
|
for (int i = 0; i < ENV_MAX_VARS; i++) {
|
||||||
|
if (env->vars[i].key[0] != '\0' &&
|
||||||
|
strcmp(env->vars[i].key, key) == 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an empty slot in the environment block.
|
||||||
|
*
|
||||||
|
* @param env Pointer to the environment block.
|
||||||
|
* @return Slot index, or -1 if full.
|
||||||
|
*/
|
||||||
|
static int env_find_free(const env_block_t *env) {
|
||||||
|
for (int i = 0; i < ENV_MAX_VARS; i++) {
|
||||||
|
if (env->vars[i].key[0] == '\0') {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t env_get(const env_block_t *env, const char *key, char *buf, uint32_t bufsize) {
|
||||||
|
int idx = env_find(env, key);
|
||||||
|
if (idx < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t vlen = strlen(env->vars[idx].value);
|
||||||
|
if (buf && bufsize > 0) {
|
||||||
|
uint32_t copy_len = vlen;
|
||||||
|
if (copy_len >= bufsize) {
|
||||||
|
copy_len = bufsize - 1;
|
||||||
|
}
|
||||||
|
memcpy(buf, env->vars[idx].value, copy_len);
|
||||||
|
buf[copy_len] = '\0';
|
||||||
|
}
|
||||||
|
return (int32_t)vlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t env_set(env_block_t *env, const char *key, const char *value) {
|
||||||
|
if (!key || key[0] == '\0') {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If value is NULL or empty, unset the variable */
|
||||||
|
if (!value || value[0] == '\0') {
|
||||||
|
int idx = env_find(env, key);
|
||||||
|
if (idx >= 0) {
|
||||||
|
env->vars[idx].key[0] = '\0';
|
||||||
|
env->vars[idx].value[0] = '\0';
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if key already exists */
|
||||||
|
int idx = env_find(env, key);
|
||||||
|
if (idx < 0) {
|
||||||
|
/* Need a new slot */
|
||||||
|
idx = env_find_free(env);
|
||||||
|
if (idx < 0) {
|
||||||
|
return -1; /* Environment full */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy key */
|
||||||
|
strncpy(env->vars[idx].key, key, ENV_MAX_KEY - 1);
|
||||||
|
env->vars[idx].key[ENV_MAX_KEY - 1] = '\0';
|
||||||
|
|
||||||
|
/* Copy value */
|
||||||
|
strncpy(env->vars[idx].value, value, ENV_MAX_VALUE - 1);
|
||||||
|
env->vars[idx].value[ENV_MAX_VALUE - 1] = '\0';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void env_copy(env_block_t *dst, const env_block_t *src) {
|
||||||
|
memcpy(dst, src, sizeof(env_block_t));
|
||||||
|
}
|
||||||
75
src/env.h
Normal file
75
src/env.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* @file env.h
|
||||||
|
* @brief Per-process environment variable support.
|
||||||
|
*
|
||||||
|
* Each process has a fixed-size environment table storing key=value pairs.
|
||||||
|
* Environment variables are copied to child processes during fork().
|
||||||
|
* User-mode programs interact with the environment via SYS_GETENV and
|
||||||
|
* SYS_SETENV system calls.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ENV_H
|
||||||
|
#define ENV_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** Maximum number of environment variables per process. */
|
||||||
|
#define ENV_MAX_VARS 32
|
||||||
|
/** Maximum length of an environment variable key (including NUL). */
|
||||||
|
#define ENV_MAX_KEY 64
|
||||||
|
/** Maximum length of an environment variable value (including NUL). */
|
||||||
|
#define ENV_MAX_VALUE 128
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single environment variable entry.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
char key[ENV_MAX_KEY]; /**< Variable name (empty string = unused). */
|
||||||
|
char value[ENV_MAX_VALUE]; /**< Variable value. */
|
||||||
|
} env_var_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment block for a process.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
env_var_t vars[ENV_MAX_VARS];
|
||||||
|
} env_block_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize an environment block (all entries empty).
|
||||||
|
*
|
||||||
|
* @param env Pointer to the environment block to initialize.
|
||||||
|
*/
|
||||||
|
void env_init(env_block_t *env);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an environment variable.
|
||||||
|
*
|
||||||
|
* @param env Pointer to the environment block.
|
||||||
|
* @param key Variable name to look up.
|
||||||
|
* @param buf Buffer to write the value into.
|
||||||
|
* @param bufsize Size of the buffer.
|
||||||
|
* @return Length of the value (excluding NUL), or -1 if not found.
|
||||||
|
* If bufsize is too small, the value is truncated.
|
||||||
|
*/
|
||||||
|
int32_t env_get(const env_block_t *env, const char *key, char *buf, uint32_t bufsize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an environment variable. Creates it if it doesn't exist.
|
||||||
|
*
|
||||||
|
* @param env Pointer to the environment block.
|
||||||
|
* @param key Variable name (must not be empty).
|
||||||
|
* @param value Variable value (NULL or empty string to unset).
|
||||||
|
* @return 0 on success, -1 if the environment is full.
|
||||||
|
*/
|
||||||
|
int32_t env_set(env_block_t *env, const char *key, const char *value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy an environment block.
|
||||||
|
*
|
||||||
|
* @param dst Destination environment block.
|
||||||
|
* @param src Source environment block.
|
||||||
|
*/
|
||||||
|
void env_copy(env_block_t *dst, const env_block_t *src);
|
||||||
|
|
||||||
|
#endif /* ENV_H */
|
||||||
215
src/font8x16.h
Normal file
215
src/font8x16.h
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
/**
|
||||||
|
* @file font8x16.h
|
||||||
|
* @brief Embedded 8x16 VGA bitmap font for graphical framebuffer rendering.
|
||||||
|
*
|
||||||
|
* Each character is 16 bytes: one byte per scanline, MSB is leftmost pixel.
|
||||||
|
* Covers ASCII 32 (space) through 126 (~). Characters outside this range
|
||||||
|
* render as a filled block.
|
||||||
|
*
|
||||||
|
* This is the standard VGA 8x16 font data, in the public domain.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FONT8X16_H
|
||||||
|
#define FONT8X16_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define FONT_WIDTH 8
|
||||||
|
#define FONT_HEIGHT 16
|
||||||
|
#define FONT_FIRST 32
|
||||||
|
#define FONT_LAST 126
|
||||||
|
|
||||||
|
static const uint8_t font8x16_data[][16] = {
|
||||||
|
/* 32: space */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||||
|
/* 33: ! */
|
||||||
|
{0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00},
|
||||||
|
/* 34: " */
|
||||||
|
{0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||||
|
/* 35: # */
|
||||||
|
{0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C,0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 36: $ */
|
||||||
|
{0x18,0x18,0x7C,0xC6,0xC2,0xC0,0x7C,0x06,0x06,0x86,0xC6,0x7C,0x18,0x18,0x00,0x00},
|
||||||
|
/* 37: % */
|
||||||
|
{0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18,0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00},
|
||||||
|
/* 38: & */
|
||||||
|
{0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
|
||||||
|
/* 39: ' */
|
||||||
|
{0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||||
|
/* 40: ( */
|
||||||
|
{0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30,0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 41: ) */
|
||||||
|
{0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00},
|
||||||
|
/* 42: * */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||||
|
/* 43: + */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||||
|
/* 44: , */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00},
|
||||||
|
/* 45: - */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||||
|
/* 46: . */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00},
|
||||||
|
/* 47: / */
|
||||||
|
{0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00},
|
||||||
|
/* 48: 0 */
|
||||||
|
{0x00,0x00,0x3C,0x66,0xC3,0xC3,0xDB,0xDB,0xC3,0xC3,0x66,0x3C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 49: 1 */
|
||||||
|
{0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00},
|
||||||
|
/* 50: 2 */
|
||||||
|
{0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30,0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00},
|
||||||
|
/* 51: 3 */
|
||||||
|
{0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 52: 4 */
|
||||||
|
{0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00},
|
||||||
|
/* 53: 5 */
|
||||||
|
{0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 54: 6 */
|
||||||
|
{0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 55: 7 */
|
||||||
|
{0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00},
|
||||||
|
/* 56: 8 */
|
||||||
|
{0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 57: 9 */
|
||||||
|
{0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06,0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00},
|
||||||
|
/* 58: : */
|
||||||
|
{0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00},
|
||||||
|
/* 59: ; */
|
||||||
|
{0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00},
|
||||||
|
/* 60: < */
|
||||||
|
{0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00},
|
||||||
|
/* 61: = */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||||
|
/* 62: > */
|
||||||
|
{0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00},
|
||||||
|
/* 63: ? */
|
||||||
|
{0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00},
|
||||||
|
/* 64: @ */
|
||||||
|
{0x00,0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE,0xDE,0xDC,0xC0,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 65: A */
|
||||||
|
{0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
|
||||||
|
/* 66: B */
|
||||||
|
{0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66,0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00},
|
||||||
|
/* 67: C */
|
||||||
|
{0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0,0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 68: D */
|
||||||
|
{0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00},
|
||||||
|
/* 69: E */
|
||||||
|
{0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00},
|
||||||
|
/* 70: F */
|
||||||
|
{0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00},
|
||||||
|
/* 71: G */
|
||||||
|
{0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE,0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00},
|
||||||
|
/* 72: H */
|
||||||
|
{0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
|
||||||
|
/* 73: I */
|
||||||
|
{0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 74: J */
|
||||||
|
{0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00},
|
||||||
|
/* 75: K */
|
||||||
|
{0x00,0x00,0xE6,0x66,0x66,0x6C,0x78,0x78,0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00},
|
||||||
|
/* 76: L */
|
||||||
|
{0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00},
|
||||||
|
/* 77: M */
|
||||||
|
{0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
|
||||||
|
/* 78: N */
|
||||||
|
{0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
|
||||||
|
/* 79: O */
|
||||||
|
{0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 80: P */
|
||||||
|
{0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00},
|
||||||
|
/* 81: Q */
|
||||||
|
{0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00},
|
||||||
|
/* 82: R */
|
||||||
|
{0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00},
|
||||||
|
/* 83: S */
|
||||||
|
{0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C,0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 84: T */
|
||||||
|
{0x00,0x00,0xFF,0xDB,0x99,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 85: U */
|
||||||
|
{0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 86: V */
|
||||||
|
{0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00},
|
||||||
|
/* 87: W */
|
||||||
|
{0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0xEE,0x6C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 88: X */
|
||||||
|
{0x00,0x00,0xC6,0xC6,0x6C,0x7C,0x38,0x38,0x7C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00},
|
||||||
|
/* 89: Y */
|
||||||
|
{0x00,0x00,0xC3,0xC3,0x66,0x3C,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 90: Z */
|
||||||
|
{0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30,0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00},
|
||||||
|
/* 91: [ */
|
||||||
|
{0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 92: \ */
|
||||||
|
{0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38,0x1C,0x0E,0x06,0x02,0x00,0x00,0x00,0x00},
|
||||||
|
/* 93: ] */
|
||||||
|
{0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 94: ^ */
|
||||||
|
{0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||||
|
/* 95: _ */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00},
|
||||||
|
/* 96: ` */
|
||||||
|
{0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||||
|
/* 97: a */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
|
||||||
|
/* 98: b */
|
||||||
|
{0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66,0x66,0x66,0x66,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 99: c */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 100: d */
|
||||||
|
{0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
|
||||||
|
/* 101: e */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 102: f */
|
||||||
|
{0x00,0x00,0x1C,0x36,0x32,0x30,0x78,0x30,0x30,0x30,0x30,0x78,0x00,0x00,0x00,0x00},
|
||||||
|
/* 103: g */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00,0x00},
|
||||||
|
/* 104: h */
|
||||||
|
{0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00},
|
||||||
|
/* 105: i */
|
||||||
|
{0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 106: j */
|
||||||
|
{0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06,0x06,0x06,0x06,0x06,0x66,0x3C,0x00,0x00},
|
||||||
|
/* 107: k */
|
||||||
|
{0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78,0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00},
|
||||||
|
/* 108: l */
|
||||||
|
{0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 109: m */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0xE6,0xFF,0xDB,0xDB,0xDB,0xDB,0xDB,0x00,0x00,0x00,0x00},
|
||||||
|
/* 110: n */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00},
|
||||||
|
/* 111: o */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 112: p */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00,0x00},
|
||||||
|
/* 113: q */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00,0x00},
|
||||||
|
/* 114: r */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x66,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00},
|
||||||
|
/* 115: s */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60,0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 116: t */
|
||||||
|
{0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30,0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 117: u */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
|
||||||
|
/* 118: v */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0xC3,0xC3,0xC3,0xC3,0x66,0x3C,0x18,0x00,0x00,0x00,0x00},
|
||||||
|
/* 119: w */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00},
|
||||||
|
/* 120: x */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38,0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00},
|
||||||
|
/* 121: y */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00,0x00},
|
||||||
|
/* 122: z */
|
||||||
|
{0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18,0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00},
|
||||||
|
/* 123: { */
|
||||||
|
{0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18,0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00},
|
||||||
|
/* 124: | */
|
||||||
|
{0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00},
|
||||||
|
/* 125: } */
|
||||||
|
{0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18,0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00},
|
||||||
|
/* 126: ~ */
|
||||||
|
{0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* FONT8X16_H */
|
||||||
44
src/framebuffer.h
Normal file
44
src/framebuffer.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* @file framebuffer.h
|
||||||
|
* @brief Framebuffer information from the bootloader.
|
||||||
|
*
|
||||||
|
* Stores the display mode and framebuffer address provided by GRUB
|
||||||
|
* via the multiboot2 framebuffer tag. The VGA driver uses this to
|
||||||
|
* decide between text-mode writes (0xB8000) and pixel rendering.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FRAMEBUFFER_H
|
||||||
|
#define FRAMEBUFFER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** Framebuffer types (matches multiboot2 definitions). */
|
||||||
|
#define FB_TYPE_INDEXED 0
|
||||||
|
#define FB_TYPE_RGB 1
|
||||||
|
#define FB_TYPE_EGA_TEXT 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Framebuffer information structure.
|
||||||
|
* Populated during boot from the multiboot2 framebuffer tag.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t addr; /**< Physical address of the framebuffer. */
|
||||||
|
uint32_t pitch; /**< Bytes per scanline. */
|
||||||
|
uint32_t width; /**< Width in pixels (or columns for text). */
|
||||||
|
uint32_t height; /**< Height in pixels (or rows for text). */
|
||||||
|
uint8_t bpp; /**< Bits per pixel. */
|
||||||
|
uint8_t type; /**< FB_TYPE_RGB, FB_TYPE_EGA_TEXT, etc. */
|
||||||
|
|
||||||
|
/* RGB field positions (only valid when type == FB_TYPE_RGB). */
|
||||||
|
uint8_t red_pos;
|
||||||
|
uint8_t red_size;
|
||||||
|
uint8_t green_pos;
|
||||||
|
uint8_t green_size;
|
||||||
|
uint8_t blue_pos;
|
||||||
|
uint8_t blue_size;
|
||||||
|
} framebuffer_info_t;
|
||||||
|
|
||||||
|
/** Global framebuffer info, filled by kernel_main. */
|
||||||
|
extern framebuffer_info_t fb_info;
|
||||||
|
|
||||||
|
#endif /* FRAMEBUFFER_H */
|
||||||
68
src/gdt.c
Normal file
68
src/gdt.c
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#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
Normal file
28
src/gdt.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#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
|
||||||
18
src/gdt_flush.S
Normal file
18
src/gdt_flush.S
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
.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
Normal file
141
src/idt.c
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#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
Normal file
33
src/idt.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#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
|
||||||
123
src/initrd_fs.c
Normal file
123
src/initrd_fs.c
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
20
src/initrd_fs.h
Normal file
20
src/initrd_fs.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @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
Normal file
201
src/interrupts.S
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
.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 */
|
||||||
88
src/isr.c
Normal file
88
src/isr.c
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#include "isr.h"
|
||||||
|
#include "pic.h"
|
||||||
|
#include "process.h"
|
||||||
|
#include "syscall.h"
|
||||||
|
#include "keyboard.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 IRQ */
|
||||||
|
keyboard_irq(regs);
|
||||||
|
}
|
||||||
|
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
Normal file
18
src/isr.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#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
|
||||||
339
src/kernel.c
Normal file
339
src/kernel.c
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
#include <multiboot2.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.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 "keyboard.h"
|
||||||
|
#include "framebuffer.h"
|
||||||
|
|
||||||
|
/* Global framebuffer info, parsed from multiboot2 tags. */
|
||||||
|
framebuffer_info_t fb_info;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize COM1 serial port for debug output.
|
||||||
|
* Baud rate: 115200, 8N1.
|
||||||
|
*/
|
||||||
|
static void serial_init(void) {
|
||||||
|
outb(0x3F8 + 1, 0x00); /* Disable interrupts */
|
||||||
|
outb(0x3F8 + 3, 0x80); /* Enable DLAB */
|
||||||
|
outb(0x3F8 + 0, 0x01); /* Divisor low: 115200 baud */
|
||||||
|
outb(0x3F8 + 1, 0x00); /* Divisor high */
|
||||||
|
outb(0x3F8 + 3, 0x03); /* 8 bits, no parity, 1 stop */
|
||||||
|
outb(0x3F8 + 2, 0xC7); /* Enable FIFO */
|
||||||
|
outb(0x3F8 + 4, 0x03); /* RTS/DSR set */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void serial_putc(char c) {
|
||||||
|
/* Wait for transmit buffer empty */
|
||||||
|
while (!(inb(0x3F8 + 5) & 0x20));
|
||||||
|
outb(0x3F8, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void offset_print(const char *str)
|
||||||
|
{
|
||||||
|
while (*str) {
|
||||||
|
outb(0xE9, *str); /* debugcon */
|
||||||
|
serial_putc(*str); /* COM1 serial */
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_hex(uint32_t val)
|
||||||
|
{
|
||||||
|
const char *hex = "0123456789ABCDEF";
|
||||||
|
outb(0xE9, '0'); serial_putc('0');
|
||||||
|
outb(0xE9, 'x'); serial_putc('x');
|
||||||
|
for (int i = 28; i >= 0; i -= 4) {
|
||||||
|
char c = hex[(val >> i) & 0xF];
|
||||||
|
outb(0xE9, c);
|
||||||
|
serial_putc(c);
|
||||||
|
}
|
||||||
|
outb(0xE9, '\n'); serial_putc('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
void kernel_main(uint32_t magic, uint32_t addr) {
|
||||||
|
/* Initialize serial port first so all debug output goes to COM1 too */
|
||||||
|
serial_init();
|
||||||
|
|
||||||
|
/* Early VGA: write directly to the text buffer at 0xB8000 for boot
|
||||||
|
* progress that is visible even without the VGA driver initialized.
|
||||||
|
* Attribute 0x1F = white on blue. */
|
||||||
|
volatile uint16_t *early_vga = (volatile uint16_t *)0xB8000;
|
||||||
|
int early_pos = 0;
|
||||||
|
|
||||||
|
/* Helper macro: write a short string to early VGA */
|
||||||
|
#define EARLY_PRINT(s) do { \
|
||||||
|
const char *_p = (s); \
|
||||||
|
while (*_p) { \
|
||||||
|
early_vga[early_pos++] = (uint16_t)(unsigned char)*_p | (0x1F << 8); \
|
||||||
|
_p++; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
/* Clear screen to blue */
|
||||||
|
for (int i = 0; i < 80 * 25; i++)
|
||||||
|
early_vga[i] = (uint16_t)' ' | (0x1F << 8);
|
||||||
|
|
||||||
|
EARLY_PRINT("ClaudeOS early boot");
|
||||||
|
early_pos = 80; /* Move to line 2 */
|
||||||
|
|
||||||
|
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) {
|
||||||
|
EARLY_PRINT("ERROR: Bad magic=0x");
|
||||||
|
/* Print magic in hex */
|
||||||
|
const char *hex = "0123456789ABCDEF";
|
||||||
|
for (int i = 28; i >= 0; i -= 4)
|
||||||
|
early_vga[early_pos++] = (uint16_t)(unsigned char)hex[(magic >> i) & 0xF] | (0x4F << 8);
|
||||||
|
early_pos = 80 * 3;
|
||||||
|
EARLY_PRINT("Expected 0x36D76289 (Multiboot2)");
|
||||||
|
early_pos = 80 * 4;
|
||||||
|
EARLY_PRINT("Got MB1 magic? Checking grub.cfg...");
|
||||||
|
|
||||||
|
offset_print("Invalid magic number: ");
|
||||||
|
print_hex(magic);
|
||||||
|
|
||||||
|
/* Hang with interrupts disabled so user can see the message */
|
||||||
|
for (;;) __asm__ volatile("hlt");
|
||||||
|
}
|
||||||
|
|
||||||
|
EARLY_PRINT("Magic OK ");
|
||||||
|
offset_print("Booting...\n");
|
||||||
|
|
||||||
|
init_gdt();
|
||||||
|
EARLY_PRINT("GDT ");
|
||||||
|
offset_print("GDT initialized\n");
|
||||||
|
|
||||||
|
init_idt();
|
||||||
|
EARLY_PRINT("IDT ");
|
||||||
|
offset_print("IDT initialized\n");
|
||||||
|
|
||||||
|
init_pic();
|
||||||
|
/* Unmask timer IRQ (IRQ0) explicitly */
|
||||||
|
pic_clear_mask(0);
|
||||||
|
EARLY_PRINT("PIC ");
|
||||||
|
offset_print("PIC initialized\n");
|
||||||
|
|
||||||
|
init_pmm(addr);
|
||||||
|
EARLY_PRINT("PMM ");
|
||||||
|
offset_print("PMM initialized\n");
|
||||||
|
|
||||||
|
/* Scan Multiboot2 tags for the initrd module and framebuffer info */
|
||||||
|
uint32_t initrd_start = 0, initrd_end = 0;
|
||||||
|
memset(&fb_info, 0, sizeof(fb_info));
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if (tag->type == MULTIBOOT_TAG_TYPE_FRAMEBUFFER) {
|
||||||
|
struct multiboot_tag_framebuffer *fbt =
|
||||||
|
(struct multiboot_tag_framebuffer *)tag;
|
||||||
|
fb_info.addr = (uint32_t)fbt->common.framebuffer_addr;
|
||||||
|
fb_info.pitch = fbt->common.framebuffer_pitch;
|
||||||
|
fb_info.width = fbt->common.framebuffer_width;
|
||||||
|
fb_info.height = fbt->common.framebuffer_height;
|
||||||
|
fb_info.bpp = fbt->common.framebuffer_bpp;
|
||||||
|
fb_info.type = fbt->common.framebuffer_type;
|
||||||
|
|
||||||
|
if (fb_info.type == FB_TYPE_RGB) {
|
||||||
|
fb_info.red_pos = fbt->framebuffer_red_field_position;
|
||||||
|
fb_info.red_size = fbt->framebuffer_red_mask_size;
|
||||||
|
fb_info.green_pos = fbt->framebuffer_green_field_position;
|
||||||
|
fb_info.green_size = fbt->framebuffer_green_mask_size;
|
||||||
|
fb_info.blue_pos = fbt->framebuffer_blue_field_position;
|
||||||
|
fb_info.blue_size = fbt->framebuffer_blue_mask_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print("Framebuffer: type=");
|
||||||
|
print_hex(fb_info.type);
|
||||||
|
offset_print(" addr=");
|
||||||
|
print_hex(fb_info.addr);
|
||||||
|
offset_print(" ");
|
||||||
|
print_hex(fb_info.width);
|
||||||
|
offset_print(" x ");
|
||||||
|
print_hex(fb_info.height);
|
||||||
|
offset_print(" bpp=");
|
||||||
|
print_hex(fb_info.bpp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init_paging();
|
||||||
|
EARLY_PRINT("PAGING ");
|
||||||
|
offset_print("Paging initialized\n");
|
||||||
|
|
||||||
|
/* If GRUB provided a graphical framebuffer, identity-map it so
|
||||||
|
* the VGA driver can write pixels to it after paging is enabled. */
|
||||||
|
if (fb_info.addr != 0 && fb_info.type == FB_TYPE_RGB) {
|
||||||
|
uint32_t fb_size = fb_info.pitch * fb_info.height;
|
||||||
|
uint32_t fb_base = fb_info.addr & ~0xFFFu; /* page-align down */
|
||||||
|
uint32_t fb_end = (fb_info.addr + fb_size + 0xFFF) & ~0xFFFu;
|
||||||
|
offset_print(" Mapping framebuffer ");
|
||||||
|
print_hex(fb_base);
|
||||||
|
offset_print(" to ");
|
||||||
|
print_hex(fb_end);
|
||||||
|
for (uint32_t pa = fb_base; pa < fb_end; pa += 4096) {
|
||||||
|
paging_map_page(pa, pa, PAGE_PRESENT | PAGE_WRITE | PAGE_WRITETHROUGH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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();
|
||||||
|
EARLY_PRINT("HEAP ");
|
||||||
|
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);
|
||||||
|
EARLY_PRINT("CPIO ");
|
||||||
|
offset_print("CPIO ramdisk initialized\n");
|
||||||
|
} else {
|
||||||
|
offset_print("No initrd module found\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
init_vfs();
|
||||||
|
EARLY_PRINT("VFS ");
|
||||||
|
offset_print("VFS initialized\n");
|
||||||
|
|
||||||
|
if (initrd_start != 0) {
|
||||||
|
init_initrd_fs();
|
||||||
|
EARLY_PRINT("INITRD ");
|
||||||
|
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();
|
||||||
|
EARLY_PRINT("TSS ");
|
||||||
|
offset_print("TSS initialized\n");
|
||||||
|
|
||||||
|
init_syscalls();
|
||||||
|
EARLY_PRINT("SYSCALL ");
|
||||||
|
offset_print("Syscalls initialized\n");
|
||||||
|
|
||||||
|
keyboard_init();
|
||||||
|
EARLY_PRINT("KBD ");
|
||||||
|
offset_print("Keyboard initialized\n");
|
||||||
|
|
||||||
|
init_process();
|
||||||
|
EARLY_PRINT("PROC ");
|
||||||
|
offset_print("Process subsystem initialized\n");
|
||||||
|
|
||||||
|
/* If the early VGA canary at 0xB8000 was visible, the display is
|
||||||
|
* definitely in text mode, regardless of what the GRUB framebuffer
|
||||||
|
* tag says. Force text mode so vga_init doesn't try to use a
|
||||||
|
* pixel framebuffer that isn't actually being displayed. */
|
||||||
|
if (fb_info.type == FB_TYPE_RGB) {
|
||||||
|
offset_print(" Overriding fb type from RGB to EGA_TEXT (early VGA visible)\n");
|
||||||
|
fb_info.type = FB_TYPE_EGA_TEXT;
|
||||||
|
fb_info.addr = 0x000B8000;
|
||||||
|
fb_info.width = 80;
|
||||||
|
fb_info.height = 25;
|
||||||
|
fb_info.bpp = 16;
|
||||||
|
fb_info.pitch = 80 * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_drivers();
|
||||||
|
EARLY_PRINT("DRV ");
|
||||||
|
offset_print("Drivers initialized\n");
|
||||||
|
|
||||||
|
/* At this point the VGA driver has been initialized and taken over
|
||||||
|
* the display. The early VGA text is no longer visible. */
|
||||||
|
|
||||||
|
/* 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 the initial program from the initrd and run it */
|
||||||
|
cpio_entry_t app_entry;
|
||||||
|
const char *init_app = "sh";
|
||||||
|
if (cpio_find(init_app, &app_entry) == 0) {
|
||||||
|
offset_print("Found ");
|
||||||
|
offset_print(init_app);
|
||||||
|
offset_print(" in initrd (");
|
||||||
|
print_hex(app_entry.datasize);
|
||||||
|
offset_print(" bytes)\n");
|
||||||
|
|
||||||
|
int32_t pid = process_create(init_app,
|
||||||
|
app_entry.data,
|
||||||
|
app_entry.datasize);
|
||||||
|
if (pid > 0) {
|
||||||
|
offset_print("Created init process, pid=");
|
||||||
|
print_hex((uint32_t)pid);
|
||||||
|
|
||||||
|
/* Enable interrupts before entering user mode */
|
||||||
|
asm volatile("sti");
|
||||||
|
offset_print("Interrupts enabled\n");
|
||||||
|
|
||||||
|
/* Enter user mode - does not return */
|
||||||
|
process_run_first();
|
||||||
|
} else {
|
||||||
|
offset_print("FAILED to create init process\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offset_print(init_app);
|
||||||
|
offset_print(" not found in initrd\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enable interrupts */
|
||||||
|
asm volatile("sti");
|
||||||
|
offset_print("Interrupts enabled\n");
|
||||||
|
|
||||||
|
offset_print("Hello, world\n");
|
||||||
|
}
|
||||||
198
src/keyboard.c
Normal file
198
src/keyboard.c
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
/**
|
||||||
|
* @file keyboard.c
|
||||||
|
* @brief PS/2 keyboard driver implementation.
|
||||||
|
*
|
||||||
|
* Reads scancodes from I/O port 0x60 on IRQ1, translates scancode set 1
|
||||||
|
* to ASCII using a simple lookup table, stores characters in a ring buffer,
|
||||||
|
* and wakes any process blocked waiting for keyboard input.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "port_io.h"
|
||||||
|
#include "pic.h"
|
||||||
|
#include "process.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);
|
||||||
|
|
||||||
|
/** PS/2 keyboard data port. */
|
||||||
|
#define KB_DATA_PORT 0x60
|
||||||
|
|
||||||
|
/** Ring buffer for keyboard input. */
|
||||||
|
static char kb_buffer[KB_BUFFER_SIZE];
|
||||||
|
static volatile uint32_t kb_head = 0;
|
||||||
|
static volatile uint32_t kb_tail = 0;
|
||||||
|
|
||||||
|
/** Process waiting for keyboard input (PID, or 0 if none). */
|
||||||
|
static volatile uint32_t kb_waiting_pid = 0;
|
||||||
|
|
||||||
|
/** Shift key state. */
|
||||||
|
static int shift_pressed = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scancode set 1 to ASCII lookup table (unshifted).
|
||||||
|
* Index = scancode, value = ASCII character (0 = unmapped).
|
||||||
|
*/
|
||||||
|
static const char scancode_ascii[128] = {
|
||||||
|
0, 27, '1', '2', '3', '4', '5', '6', /* 0x00 - 0x07 */
|
||||||
|
'7', '8', '9', '0', '-', '=', '\b', '\t', /* 0x08 - 0x0F */
|
||||||
|
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', /* 0x10 - 0x17 */
|
||||||
|
'o', 'p', '[', ']', '\n', 0, 'a', 's', /* 0x18 - 0x1F */
|
||||||
|
'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', /* 0x20 - 0x27 */
|
||||||
|
'\'', '`', 0, '\\', 'z', 'x', 'c', 'v', /* 0x28 - 0x2F */
|
||||||
|
'b', 'n', 'm', ',', '.', '/', 0, '*', /* 0x30 - 0x37 */
|
||||||
|
0, ' ', 0, 0, 0, 0, 0, 0, /* 0x38 - 0x3F */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x47 */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x48 - 0x4F */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x57 */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x58 - 0x5F */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x67 */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x68 - 0x6F */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x77 */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x78 - 0x7F */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scancode set 1 to ASCII lookup table (shifted).
|
||||||
|
*/
|
||||||
|
static const char scancode_ascii_shift[128] = {
|
||||||
|
0, 27, '!', '@', '#', '$', '%', '^', /* 0x00 - 0x07 */
|
||||||
|
'&', '*', '(', ')', '_', '+', '\b', '\t', /* 0x08 - 0x0F */
|
||||||
|
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', /* 0x10 - 0x17 */
|
||||||
|
'O', 'P', '{', '}', '\n', 0, 'A', 'S', /* 0x18 - 0x1F */
|
||||||
|
'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', /* 0x20 - 0x27 */
|
||||||
|
'"', '~', 0, '|', 'Z', 'X', 'C', 'V', /* 0x28 - 0x2F */
|
||||||
|
'B', 'N', 'M', '<', '>', '?', 0, '*', /* 0x30 - 0x37 */
|
||||||
|
0, ' ', 0, 0, 0, 0, 0, 0, /* 0x38 - 0x3F */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x47 */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x48 - 0x4F */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x57 */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x58 - 0x5F */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x67 */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x68 - 0x6F */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x77 */
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, /* 0x78 - 0x7F */
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Left/right shift scancodes. */
|
||||||
|
#define SC_LSHIFT_PRESS 0x2A
|
||||||
|
#define SC_LSHIFT_RELEASE 0xAA
|
||||||
|
#define SC_RSHIFT_PRESS 0x36
|
||||||
|
#define SC_RSHIFT_RELEASE 0xB6
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a character into the ring buffer.
|
||||||
|
*/
|
||||||
|
static void kb_buffer_put(char c) {
|
||||||
|
uint32_t next = (kb_head + 1) % KB_BUFFER_SIZE;
|
||||||
|
if (next == kb_tail) {
|
||||||
|
return; /* Buffer full, drop character */
|
||||||
|
}
|
||||||
|
kb_buffer[kb_head] = c;
|
||||||
|
kb_head = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
void keyboard_init(void) {
|
||||||
|
kb_head = 0;
|
||||||
|
kb_tail = 0;
|
||||||
|
kb_waiting_pid = 0;
|
||||||
|
shift_pressed = 0;
|
||||||
|
|
||||||
|
offset_print(" KEYBOARD: flushing controller...\n");
|
||||||
|
|
||||||
|
/* Flush any pending data from the keyboard controller.
|
||||||
|
* Use a timeout to avoid hanging if the controller keeps reporting data
|
||||||
|
* (some emulators/VMs behave differently). */
|
||||||
|
int flush_count = 0;
|
||||||
|
while ((inb(0x64) & 0x01) && flush_count < 1024) {
|
||||||
|
inb(KB_DATA_PORT);
|
||||||
|
flush_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print(" KEYBOARD: flushed ");
|
||||||
|
print_hex((uint32_t)flush_count);
|
||||||
|
offset_print(" KEYBOARD: bytes, unmasking IRQ1...\n");
|
||||||
|
|
||||||
|
/* Unmask IRQ1 (keyboard) in the PIC */
|
||||||
|
pic_clear_mask(1);
|
||||||
|
|
||||||
|
offset_print(" KEYBOARD: initialized\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void keyboard_irq(registers_t *regs) {
|
||||||
|
(void)regs;
|
||||||
|
uint8_t scancode = inb(KB_DATA_PORT);
|
||||||
|
|
||||||
|
/* Handle shift keys */
|
||||||
|
if (scancode == SC_LSHIFT_PRESS || scancode == SC_RSHIFT_PRESS) {
|
||||||
|
shift_pressed = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (scancode == SC_LSHIFT_RELEASE || scancode == SC_RSHIFT_RELEASE) {
|
||||||
|
shift_pressed = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ignore key releases (bit 7 set) */
|
||||||
|
if (scancode & 0x80) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Translate scancode to ASCII */
|
||||||
|
char c;
|
||||||
|
if (shift_pressed) {
|
||||||
|
c = scancode_ascii_shift[scancode];
|
||||||
|
} else {
|
||||||
|
c = scancode_ascii[scancode];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == 0) {
|
||||||
|
return; /* Unmapped key */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Put character in buffer */
|
||||||
|
kb_buffer_put(c);
|
||||||
|
|
||||||
|
/* Wake any process waiting for keyboard input */
|
||||||
|
if (kb_waiting_pid != 0) {
|
||||||
|
process_t *waiter = process_get(kb_waiting_pid);
|
||||||
|
if (waiter && waiter->state == PROCESS_BLOCKED) {
|
||||||
|
waiter->state = PROCESS_READY;
|
||||||
|
}
|
||||||
|
kb_waiting_pid = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t keyboard_read(char *buf, uint32_t count) {
|
||||||
|
uint32_t n = 0;
|
||||||
|
while (n < count && kb_tail != kb_head) {
|
||||||
|
buf[n++] = kb_buffer[kb_tail];
|
||||||
|
kb_tail = (kb_tail + 1) % KB_BUFFER_SIZE;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int keyboard_has_data(void) {
|
||||||
|
return kb_head != kb_tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
void keyboard_block_for_input(registers_t *regs) {
|
||||||
|
process_t *cur = process_current();
|
||||||
|
if (!cur) return;
|
||||||
|
|
||||||
|
cur->state = PROCESS_BLOCKED;
|
||||||
|
cur->saved_regs = *regs;
|
||||||
|
|
||||||
|
/* Rewind EIP by 2 bytes so that when the process is unblocked and
|
||||||
|
* scheduled, the CPU re-executes the INT 0x80 instruction. At that
|
||||||
|
* point keyboard_has_data() will return true and the read succeeds. */
|
||||||
|
cur->saved_regs.eip -= 2;
|
||||||
|
|
||||||
|
kb_waiting_pid = cur->pid;
|
||||||
|
|
||||||
|
/* Schedule next process */
|
||||||
|
schedule_tick(regs);
|
||||||
|
}
|
||||||
56
src/keyboard.h
Normal file
56
src/keyboard.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* @file keyboard.h
|
||||||
|
* @brief PS/2 keyboard driver.
|
||||||
|
*
|
||||||
|
* Handles IRQ1 keyboard interrupts, translates scancodes to ASCII,
|
||||||
|
* and provides a ring buffer for user-space reading via SYS_READ.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KEYBOARD_H
|
||||||
|
#define KEYBOARD_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "isr.h"
|
||||||
|
|
||||||
|
/** Keyboard input buffer size. */
|
||||||
|
#define KB_BUFFER_SIZE 256
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the keyboard driver.
|
||||||
|
*/
|
||||||
|
void keyboard_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a keyboard IRQ (called from isr_handler for IRQ1).
|
||||||
|
*
|
||||||
|
* @param regs Register state (may be used to wake blocked processes).
|
||||||
|
*/
|
||||||
|
void keyboard_irq(registers_t *regs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read characters from the keyboard buffer.
|
||||||
|
* Non-blocking: returns whatever is available, 0 if empty.
|
||||||
|
*
|
||||||
|
* @param buf Destination buffer.
|
||||||
|
* @param count Maximum bytes to read.
|
||||||
|
* @return Number of bytes read.
|
||||||
|
*/
|
||||||
|
uint32_t keyboard_read(char *buf, uint32_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there is data available in the keyboard buffer.
|
||||||
|
*
|
||||||
|
* @return Non-zero if data is available.
|
||||||
|
*/
|
||||||
|
int keyboard_has_data(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block the given process until keyboard data is available.
|
||||||
|
* Sets the process to BLOCKED state and records it as waiting for keyboard.
|
||||||
|
* When data arrives, the process will be unblocked.
|
||||||
|
*
|
||||||
|
* @param regs Current interrupt frame (for saving process state).
|
||||||
|
*/
|
||||||
|
void keyboard_block_for_input(registers_t *regs);
|
||||||
|
|
||||||
|
#endif /* KEYBOARD_H */
|
||||||
253
src/kmalloc.c
Normal file
253
src/kmalloc.c
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
46
src/kmalloc.h
Normal file
46
src/kmalloc.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* @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 */
|
||||||
40
src/linker.ld
Normal file
40
src/linker.ld
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
ENTRY(_start)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
. = 1M;
|
||||||
|
|
||||||
|
_kernel_start = .;
|
||||||
|
|
||||||
|
.text BLOCK(4K) : ALIGN(4K)
|
||||||
|
{
|
||||||
|
KEEP(*(.multiboot))
|
||||||
|
*(.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
.rodata BLOCK(4K) : ALIGN(4K)
|
||||||
|
{
|
||||||
|
*(.rodata)
|
||||||
|
*(.rodata.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
.drivers BLOCK(4K) : ALIGN(4K)
|
||||||
|
{
|
||||||
|
__drivers_start = .;
|
||||||
|
KEEP(*(.drivers))
|
||||||
|
__drivers_end = .;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data BLOCK(4K) : ALIGN(4K)
|
||||||
|
{
|
||||||
|
*(.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
.bss BLOCK(4K) : ALIGN(4K)
|
||||||
|
{
|
||||||
|
*(COMMON)
|
||||||
|
*(.bss)
|
||||||
|
}
|
||||||
|
|
||||||
|
_kernel_end = .;
|
||||||
|
}
|
||||||
384
src/paging.c
Normal file
384
src/paging.c
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
/**
|
||||||
|
* @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
Normal file
129
src/paging.h
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* @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
Normal file
95
src/pic.c
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#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
Normal file
11
src/pic.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#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
Normal file
165
src/pmm.c
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#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
Normal file
42
src/pmm.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#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
|
||||||
25
src/port_io.h
Normal file
25
src/port_io.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#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
|
||||||
377
src/process.c
Normal file
377
src/process.c
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
/**
|
||||||
|
* @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';
|
||||||
|
|
||||||
|
/* Initialize environment and working directory */
|
||||||
|
env_init(&proc->env);
|
||||||
|
strcpy(proc->cwd, "/");
|
||||||
|
env_set(&proc->env, "CWD", "/");
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
133
src/process.h
Normal file
133
src/process.h
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* @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"
|
||||||
|
#include "env.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). */
|
||||||
|
char cwd[128]; /**< Current working directory. */
|
||||||
|
env_block_t env; /**< Per-process environment variables. */
|
||||||
|
} 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
Normal file
172
src/string.c
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
346
src/syscall.c
Normal file
346
src/syscall.c
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
/**
|
||||||
|
* @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 "env.h"
|
||||||
|
#include "port_io.h"
|
||||||
|
#include "vga.h"
|
||||||
|
#include "vfs.h"
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "cpio.h"
|
||||||
|
#include "paging.h"
|
||||||
|
#include "pmm.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.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.
|
||||||
|
* fd=0 (stdin) reads from the keyboard buffer (non-blocking).
|
||||||
|
* Returns 0 if no data available; caller should yield and retry.
|
||||||
|
*/
|
||||||
|
static int32_t sys_read(registers_t *regs) {
|
||||||
|
int fd = (int)regs->ebx;
|
||||||
|
char *buf = (char *)regs->ecx;
|
||||||
|
uint32_t len = regs->edx;
|
||||||
|
|
||||||
|
if (fd == 0) {
|
||||||
|
/* stdin: non-blocking read from keyboard */
|
||||||
|
if (keyboard_has_data()) {
|
||||||
|
uint32_t n = keyboard_read(buf, len);
|
||||||
|
return (int32_t)n;
|
||||||
|
}
|
||||||
|
return 0; /* No data available */
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1; /* Invalid fd */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* Calls schedule_tick directly to potentially switch to another process.
|
||||||
|
*/
|
||||||
|
static int32_t sys_yield(registers_t *regs) {
|
||||||
|
schedule_tick(regs);
|
||||||
|
return SYSCALL_SWITCHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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: replace the current process image with a new program.
|
||||||
|
* EBX = path to binary (C string), e.g. "hello-world".
|
||||||
|
* Loads the binary from the initrd and replaces the current process's
|
||||||
|
* code and stack. Does not return on success.
|
||||||
|
*/
|
||||||
|
static int32_t sys_exec(registers_t *regs) {
|
||||||
|
const char *path = (const char *)regs->ebx;
|
||||||
|
if (!path) return -1;
|
||||||
|
|
||||||
|
process_t *cur = process_current();
|
||||||
|
if (!cur) return -1;
|
||||||
|
|
||||||
|
/* Look up the binary in the initrd */
|
||||||
|
cpio_entry_t entry;
|
||||||
|
if (cpio_find(path, &entry) != 0) {
|
||||||
|
return -1; /* Not found */
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t *pd = (uint32_t *)cur->page_directory;
|
||||||
|
|
||||||
|
/* Unmap and free old user code pages (0x08048000 region).
|
||||||
|
* We don't know exactly how many pages were mapped, so scan a
|
||||||
|
* reasonable range. */
|
||||||
|
for (uint32_t vaddr = USER_CODE_START;
|
||||||
|
vaddr < USER_CODE_START + 0x100000; /* up to 1 MiB of code */
|
||||||
|
vaddr += 4096) {
|
||||||
|
uint32_t pd_idx = vaddr >> 22;
|
||||||
|
uint32_t pt_idx = (vaddr >> 12) & 0x3FF;
|
||||||
|
if (!(pd[pd_idx] & 0x001)) break; /* No page table */
|
||||||
|
uint32_t *pt = (uint32_t *)(pd[pd_idx] & 0xFFFFF000);
|
||||||
|
if (!(pt[pt_idx] & 0x001)) break; /* No page */
|
||||||
|
phys_addr_t old_phys = pt[pt_idx] & 0xFFFFF000;
|
||||||
|
pt[pt_idx] = 0;
|
||||||
|
pmm_free_page(old_phys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map new code pages */
|
||||||
|
uint32_t code_pages = (entry.datasize + 4095) / 4096;
|
||||||
|
for (uint32_t i = 0; i < code_pages; i++) {
|
||||||
|
phys_addr_t phys = pmm_alloc_page(PMM_ZONE_NORMAL);
|
||||||
|
if (phys == 0) return -1;
|
||||||
|
|
||||||
|
uint32_t vaddr = USER_CODE_START + i * 4096;
|
||||||
|
paging_map_page_in(pd, vaddr, phys,
|
||||||
|
PAGE_PRESENT | PAGE_WRITE | PAGE_USER);
|
||||||
|
|
||||||
|
uint32_t offset = i * 4096;
|
||||||
|
uint32_t bytes = entry.datasize - offset;
|
||||||
|
if (bytes > 4096) bytes = 4096;
|
||||||
|
memcpy((void *)phys, (const uint8_t *)entry.data + offset, bytes);
|
||||||
|
if (bytes < 4096) {
|
||||||
|
memset((void *)(phys + bytes), 0, 4096 - bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zero the user stack pages (reuse existing stack mappings) */
|
||||||
|
uint32_t stack_base = USER_STACK_TOP - USER_STACK_PAGES * 4096;
|
||||||
|
for (uint32_t i = 0; i < USER_STACK_PAGES; i++) {
|
||||||
|
uint32_t vaddr = stack_base + i * 4096;
|
||||||
|
uint32_t pd_idx = vaddr >> 22;
|
||||||
|
uint32_t pt_idx = (vaddr >> 12) & 0x3FF;
|
||||||
|
if ((pd[pd_idx] & 0x001)) {
|
||||||
|
uint32_t *pt = (uint32_t *)(pd[pd_idx] & 0xFFFFF000);
|
||||||
|
if ((pt[pt_idx] & 0x001)) {
|
||||||
|
phys_addr_t phys = pt[pt_idx] & 0xFFFFF000;
|
||||||
|
memset((void *)phys, 0, 4096);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flush TLB */
|
||||||
|
paging_switch_directory(cur->page_directory);
|
||||||
|
|
||||||
|
/* Update process name */
|
||||||
|
uint32_t nlen = strlen(path);
|
||||||
|
if (nlen > 31) nlen = 31;
|
||||||
|
memcpy(cur->name, path, nlen);
|
||||||
|
cur->name[nlen] = '\0';
|
||||||
|
|
||||||
|
/* Set up registers for the new program */
|
||||||
|
regs->eip = USER_CODE_START;
|
||||||
|
regs->useresp = USER_STACK_TOP;
|
||||||
|
regs->esp = USER_STACK_TOP;
|
||||||
|
regs->eax = 0;
|
||||||
|
regs->ebx = 0;
|
||||||
|
regs->ecx = 0;
|
||||||
|
regs->edx = 0;
|
||||||
|
regs->esi = 0;
|
||||||
|
regs->edi = 0;
|
||||||
|
regs->ebp = 0;
|
||||||
|
regs->cs = 0x1B;
|
||||||
|
regs->ds = 0x23;
|
||||||
|
regs->ss = 0x23;
|
||||||
|
regs->eflags = 0x202; /* IF=1 */
|
||||||
|
|
||||||
|
/* Return SYSCALL_SWITCHED so syscall_handler doesn't overwrite regs */
|
||||||
|
return SYSCALL_SWITCHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_GETENV: get an environment variable.
|
||||||
|
* EBX = key pointer, ECX = value buffer pointer, EDX = buffer size.
|
||||||
|
* Returns length of value, or -1 if not found.
|
||||||
|
*/
|
||||||
|
static int32_t sys_getenv(registers_t *regs) {
|
||||||
|
const char *key = (const char *)regs->ebx;
|
||||||
|
char *buf = (char *)regs->ecx;
|
||||||
|
uint32_t bufsize = regs->edx;
|
||||||
|
|
||||||
|
process_t *cur = process_current();
|
||||||
|
if (!cur) return -1;
|
||||||
|
|
||||||
|
return env_get(&cur->env, key, buf, bufsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_SETENV: set an environment variable.
|
||||||
|
* EBX = key pointer, ECX = value pointer (NULL to unset).
|
||||||
|
* Returns 0 on success, -1 on error.
|
||||||
|
*/
|
||||||
|
static int32_t sys_setenv(registers_t *regs) {
|
||||||
|
const char *key = (const char *)regs->ebx;
|
||||||
|
const char *value = (const char *)regs->ecx;
|
||||||
|
|
||||||
|
process_t *cur = process_current();
|
||||||
|
if (!cur) return -1;
|
||||||
|
|
||||||
|
return env_set(&cur->env, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_READDIR: read a directory entry.
|
||||||
|
* EBX = path, ECX = index, EDX = name buffer (128 bytes min).
|
||||||
|
* Returns entry type (VFS_FILE=1, VFS_DIRECTORY=2, ...) on success, -1 at end.
|
||||||
|
*/
|
||||||
|
static int32_t sys_readdir(registers_t *regs) {
|
||||||
|
const char *path = (const char *)regs->ebx;
|
||||||
|
uint32_t idx = regs->ecx;
|
||||||
|
char *name_buf = (char *)regs->edx;
|
||||||
|
|
||||||
|
vfs_dirent_t entry;
|
||||||
|
if (vfs_readdir(path, idx, &entry) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy entry name to user buffer */
|
||||||
|
uint32_t len = strlen(entry.name);
|
||||||
|
if (len >= 128) len = 127;
|
||||||
|
memcpy(name_buf, entry.name, len);
|
||||||
|
name_buf[len] = '\0';
|
||||||
|
|
||||||
|
return (int32_t)entry.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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,
|
||||||
|
[SYS_GETENV] = sys_getenv,
|
||||||
|
[SYS_SETENV] = sys_setenv,
|
||||||
|
[SYS_READDIR] = sys_readdir,
|
||||||
|
};
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
45
src/syscall.h
Normal file
45
src/syscall.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* @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. */
|
||||||
|
#define SYS_GETENV 8 /**< Get environment variable. key=EBX, buf=ECX, bufsize=EDX. */
|
||||||
|
#define SYS_SETENV 9 /**< Set environment variable. key=EBX, value=ECX. */
|
||||||
|
#define SYS_READDIR 10 /**< Read directory entry. path=EBX, idx=ECX, buf=EDX. Returns type or -1. */
|
||||||
|
|
||||||
|
/** Total number of system calls. */
|
||||||
|
#define NUM_SYSCALLS 11
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
Normal file
47
src/tss.c
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* @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
Normal file
63
src/tss.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* @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 */
|
||||||
314
src/vfs.c
Normal file
314
src/vfs.c
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
/**
|
||||||
|
* @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) {
|
||||||
|
/* Special case: root directory lists mount points */
|
||||||
|
if (path[0] == '/' && path[1] == '\0') {
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (int i = 0; i < VFS_MAX_MOUNTS; i++) {
|
||||||
|
if (!mounts[i].active) continue;
|
||||||
|
if (count == idx) {
|
||||||
|
memset(out, 0, sizeof(vfs_dirent_t));
|
||||||
|
/* Extract top-level name from mount path (skip leading /) */
|
||||||
|
const char *name = mounts[i].path + 1;
|
||||||
|
strncpy(out->name, name, VFS_MAX_NAME - 1);
|
||||||
|
out->type = VFS_DIRECTORY;
|
||||||
|
out->inode = (uint32_t)i;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return -1; /* No more entries */
|
||||||
|
}
|
||||||
|
|
||||||
|
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
Normal file
197
src/vfs.h
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/**
|
||||||
|
* @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 */
|
||||||
465
src/vga.c
Normal file
465
src/vga.c
Normal file
@@ -0,0 +1,465 @@
|
|||||||
|
/**
|
||||||
|
* @file vga.c
|
||||||
|
* @brief Display driver supporting both VGA text mode and graphical framebuffer.
|
||||||
|
*
|
||||||
|
* Supports two modes depending on what GRUB provides:
|
||||||
|
* - EGA text mode: writes character+attribute pairs to the text buffer
|
||||||
|
* - Graphical (RGB) framebuffer: renders an embedded 8x16 bitmap font
|
||||||
|
* to the pixel framebuffer provided by GRUB
|
||||||
|
*
|
||||||
|
* The mode is detected at init time from the global fb_info structure,
|
||||||
|
* which kernel_main populates from the multiboot2 framebuffer tag.
|
||||||
|
*
|
||||||
|
* 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"
|
||||||
|
#include "framebuffer.h"
|
||||||
|
#include "font8x16.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Debug helpers defined in kernel.c */
|
||||||
|
extern void offset_print(const char *str);
|
||||||
|
extern void print_hex(uint32_t val);
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Common state
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** Current text cursor position. */
|
||||||
|
static uint32_t cursor_row = 0;
|
||||||
|
static uint32_t cursor_col = 0;
|
||||||
|
|
||||||
|
/** Columns and rows of the text grid. */
|
||||||
|
static uint32_t text_cols = 80;
|
||||||
|
static uint32_t text_rows = 25;
|
||||||
|
|
||||||
|
/** Current color attribute (foreground | background << 4). */
|
||||||
|
static uint8_t text_attr = 0x07;
|
||||||
|
|
||||||
|
/** Display mode: 0 = text, 1 = pixel. */
|
||||||
|
static int display_mode = 0;
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Text mode (EGA) internals
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** VGA text-mode framebuffer default base. */
|
||||||
|
#define VGA_TEXT_BUFFER 0xB8000
|
||||||
|
static uint16_t *text_buffer = (uint16_t *)VGA_TEXT_BUFFER;
|
||||||
|
|
||||||
|
static inline uint16_t vga_entry(char c, uint8_t attr) {
|
||||||
|
return (uint16_t)c | ((uint16_t)attr << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void text_update_cursor(void) {
|
||||||
|
uint16_t pos = (uint16_t)(cursor_row * text_cols + cursor_col);
|
||||||
|
outb(0x3D4, 0x0F);
|
||||||
|
outb(0x3D5, (uint8_t)(pos & 0xFF));
|
||||||
|
outb(0x3D4, 0x0E);
|
||||||
|
outb(0x3D5, (uint8_t)((pos >> 8) & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void text_scroll(void) {
|
||||||
|
for (uint32_t i = 0; i < (text_rows - 1) * text_cols; i++) {
|
||||||
|
text_buffer[i] = text_buffer[i + text_cols];
|
||||||
|
}
|
||||||
|
uint16_t blank = vga_entry(' ', text_attr);
|
||||||
|
for (uint32_t i = (text_rows - 1) * text_cols; i < text_rows * text_cols; i++) {
|
||||||
|
text_buffer[i] = blank;
|
||||||
|
}
|
||||||
|
cursor_row = text_rows - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void text_clear(void) {
|
||||||
|
uint16_t blank = vga_entry(' ', text_attr);
|
||||||
|
for (uint32_t i = 0; i < text_cols * text_rows; i++) {
|
||||||
|
text_buffer[i] = blank;
|
||||||
|
}
|
||||||
|
cursor_row = 0;
|
||||||
|
cursor_col = 0;
|
||||||
|
text_update_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void text_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) & ~7u;
|
||||||
|
if (cursor_col >= text_cols) {
|
||||||
|
cursor_col = 0;
|
||||||
|
cursor_row++;
|
||||||
|
}
|
||||||
|
} else if (c == '\b') {
|
||||||
|
if (cursor_col > 0) {
|
||||||
|
cursor_col--;
|
||||||
|
text_buffer[cursor_row * text_cols + cursor_col] = vga_entry(' ', text_attr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text_buffer[cursor_row * text_cols + cursor_col] = vga_entry(c, text_attr);
|
||||||
|
cursor_col++;
|
||||||
|
if (cursor_col >= text_cols) {
|
||||||
|
cursor_col = 0;
|
||||||
|
cursor_row++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cursor_row >= text_rows) {
|
||||||
|
text_scroll();
|
||||||
|
}
|
||||||
|
text_update_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Pixel mode (graphical framebuffer) internals
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** Pointer to the pixel framebuffer. */
|
||||||
|
static uint8_t *pixel_fb = (uint8_t *)0;
|
||||||
|
|
||||||
|
/** Framebuffer parameters. */
|
||||||
|
static uint32_t fb_pitch = 0;
|
||||||
|
static uint32_t fb_width = 0;
|
||||||
|
static uint32_t fb_height = 0;
|
||||||
|
static uint32_t fb_bpp = 0;
|
||||||
|
|
||||||
|
/** RGB field info. */
|
||||||
|
static uint8_t fb_red_pos = 16, fb_red_size = 8;
|
||||||
|
static uint8_t fb_green_pos = 8, fb_green_size = 8;
|
||||||
|
static uint8_t fb_blue_pos = 0, fb_blue_size = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pack an RGB color into the framebuffer's native pixel format.
|
||||||
|
*/
|
||||||
|
static inline uint32_t pack_color(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
(void)fb_red_size; (void)fb_green_size; (void)fb_blue_size;
|
||||||
|
return ((uint32_t)r << fb_red_pos) |
|
||||||
|
((uint32_t)g << fb_green_pos) |
|
||||||
|
((uint32_t)b << fb_blue_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a single pixel in the framebuffer.
|
||||||
|
*/
|
||||||
|
static inline void pixel_set(uint32_t x, uint32_t y, uint32_t color) {
|
||||||
|
if (x >= fb_width || y >= fb_height) return;
|
||||||
|
uint32_t offset = y * fb_pitch + x * (fb_bpp / 8);
|
||||||
|
uint32_t bytes = fb_bpp / 8;
|
||||||
|
|
||||||
|
if (bytes == 4) {
|
||||||
|
*(volatile uint32_t *)(pixel_fb + offset) = color;
|
||||||
|
} else if (bytes == 3) {
|
||||||
|
pixel_fb[offset] = (uint8_t)(color & 0xFF);
|
||||||
|
pixel_fb[offset + 1] = (uint8_t)((color >> 8) & 0xFF);
|
||||||
|
pixel_fb[offset + 2] = (uint8_t)((color >> 16) & 0xFF);
|
||||||
|
} else if (bytes == 2) {
|
||||||
|
*(volatile uint16_t *)(pixel_fb + offset) = (uint16_t)color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VGA color index to 24-bit RGB mapping.
|
||||||
|
*/
|
||||||
|
static const uint32_t vga_palette[16] = {
|
||||||
|
0x000000, /* 0 black */
|
||||||
|
0x0000AA, /* 1 blue */
|
||||||
|
0x00AA00, /* 2 green */
|
||||||
|
0x00AAAA, /* 3 cyan */
|
||||||
|
0xAA0000, /* 4 red */
|
||||||
|
0xAA00AA, /* 5 magenta */
|
||||||
|
0xAA5500, /* 6 brown */
|
||||||
|
0xAAAAAA, /* 7 light grey */
|
||||||
|
0x555555, /* 8 dark grey */
|
||||||
|
0x5555FF, /* 9 light blue */
|
||||||
|
0x55FF55, /* 10 light green */
|
||||||
|
0x55FFFF, /* 11 light cyan */
|
||||||
|
0xFF5555, /* 12 light red */
|
||||||
|
0xFF55FF, /* 13 light magenta */
|
||||||
|
0xFFFF55, /* 14 yellow */
|
||||||
|
0xFFFFFF, /* 15 white */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get packed foreground/background colors from text_attr.
|
||||||
|
*/
|
||||||
|
static void attr_to_colors(uint32_t *fg_out, uint32_t *bg_out) {
|
||||||
|
uint8_t fg_idx = text_attr & 0x0F;
|
||||||
|
uint8_t bg_idx = (text_attr >> 4) & 0x0F;
|
||||||
|
uint32_t fg_rgb = vga_palette[fg_idx];
|
||||||
|
uint32_t bg_rgb = vga_palette[bg_idx];
|
||||||
|
*fg_out = pack_color((fg_rgb >> 16) & 0xFF, (fg_rgb >> 8) & 0xFF, fg_rgb & 0xFF);
|
||||||
|
*bg_out = pack_color((bg_rgb >> 16) & 0xFF, (bg_rgb >> 8) & 0xFF, bg_rgb & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a single glyph at character grid position (col, row).
|
||||||
|
*/
|
||||||
|
static void pixel_render_char(uint32_t col, uint32_t row, char c) {
|
||||||
|
uint32_t fg, bg;
|
||||||
|
attr_to_colors(&fg, &bg);
|
||||||
|
|
||||||
|
const uint8_t *glyph;
|
||||||
|
if (c >= FONT_FIRST && c <= FONT_LAST) {
|
||||||
|
glyph = font8x16_data[c - FONT_FIRST];
|
||||||
|
} else {
|
||||||
|
glyph = 0; /* NULL = solid block for unknown chars */
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t px = col * FONT_WIDTH;
|
||||||
|
uint32_t py = row * FONT_HEIGHT;
|
||||||
|
|
||||||
|
for (uint32_t y = 0; y < FONT_HEIGHT; y++) {
|
||||||
|
uint8_t bits = glyph ? glyph[y] : 0xFF;
|
||||||
|
for (uint32_t x = 0; x < FONT_WIDTH; x++) {
|
||||||
|
uint32_t color = (bits & (0x80 >> x)) ? fg : bg;
|
||||||
|
pixel_set(px + x, py + y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll the pixel framebuffer up by one text row (FONT_HEIGHT pixels).
|
||||||
|
*/
|
||||||
|
static void pixel_scroll(void) {
|
||||||
|
uint32_t row_bytes = FONT_HEIGHT * fb_pitch;
|
||||||
|
uint32_t total_text_bytes = text_rows * row_bytes;
|
||||||
|
|
||||||
|
/* Move all rows up by one */
|
||||||
|
memcpy(pixel_fb, pixel_fb + row_bytes, total_text_bytes - row_bytes);
|
||||||
|
|
||||||
|
/* Clear the last text row */
|
||||||
|
uint32_t dummy, bg;
|
||||||
|
attr_to_colors(&dummy, &bg);
|
||||||
|
uint32_t last_row_y = (text_rows - 1) * FONT_HEIGHT;
|
||||||
|
for (uint32_t y = last_row_y; y < last_row_y + FONT_HEIGHT; y++) {
|
||||||
|
for (uint32_t x = 0; x < text_cols * FONT_WIDTH; x++) {
|
||||||
|
pixel_set(x, y, bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor_row = text_rows - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pixel_clear(void) {
|
||||||
|
uint32_t dummy, bg;
|
||||||
|
attr_to_colors(&dummy, &bg);
|
||||||
|
for (uint32_t y = 0; y < fb_height; y++) {
|
||||||
|
for (uint32_t x = 0; x < fb_width; x++) {
|
||||||
|
pixel_set(x, y, bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor_row = 0;
|
||||||
|
cursor_col = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pixel_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) & ~7u;
|
||||||
|
if (cursor_col >= text_cols) {
|
||||||
|
cursor_col = 0;
|
||||||
|
cursor_row++;
|
||||||
|
}
|
||||||
|
} else if (c == '\b') {
|
||||||
|
if (cursor_col > 0) {
|
||||||
|
cursor_col--;
|
||||||
|
pixel_render_char(cursor_col, cursor_row, ' ');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pixel_render_char(cursor_col, cursor_row, c);
|
||||||
|
cursor_col++;
|
||||||
|
if (cursor_col >= text_cols) {
|
||||||
|
cursor_col = 0;
|
||||||
|
cursor_row++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cursor_row >= text_rows) {
|
||||||
|
pixel_scroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Public interface
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
void vga_clear(void) {
|
||||||
|
if (display_mode == 0)
|
||||||
|
text_clear();
|
||||||
|
else
|
||||||
|
pixel_clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void vga_set_color(vga_color_t fg, vga_color_t bg) {
|
||||||
|
text_attr = (uint8_t)fg | ((uint8_t)bg << 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vga_putchar(char c) {
|
||||||
|
if (display_mode == 0)
|
||||||
|
text_putchar(c);
|
||||||
|
else
|
||||||
|
pixel_putchar(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
while (pos > 0) {
|
||||||
|
vga_putchar(buf[--pos]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vga_show_mem_stats(void) {
|
||||||
|
uint32_t mem_kb = pmm_get_memory_size() + 1024;
|
||||||
|
|
||||||
|
vga_set_color(VGA_LIGHT_CYAN, VGA_BLUE);
|
||||||
|
vga_puts("=== ClaudeOS Memory Statistics ===\n");
|
||||||
|
|
||||||
|
vga_set_color(VGA_WHITE, VGA_BLUE);
|
||||||
|
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_BLUE);
|
||||||
|
vga_puts("==================================\n");
|
||||||
|
vga_set_color(VGA_LIGHT_GREY, VGA_BLUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Driver registration
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static driver_probe_result_t vga_probe(void) {
|
||||||
|
return DRIVER_PROBE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vga_init(void) {
|
||||||
|
text_attr = (uint8_t)VGA_LIGHT_GREY | ((uint8_t)VGA_BLACK << 4);
|
||||||
|
|
||||||
|
if (fb_info.type == FB_TYPE_EGA_TEXT || fb_info.addr == 0) {
|
||||||
|
/* Text mode (or no framebuffer tag — assume legacy text mode) */
|
||||||
|
display_mode = 0;
|
||||||
|
text_cols = 80;
|
||||||
|
text_rows = 25;
|
||||||
|
|
||||||
|
if (fb_info.addr != 0) {
|
||||||
|
text_buffer = (uint16_t *)(uint32_t)fb_info.addr;
|
||||||
|
text_cols = fb_info.width;
|
||||||
|
text_rows = fb_info.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print(" VGA: text mode ");
|
||||||
|
print_hex(text_cols);
|
||||||
|
offset_print(" VGA: x ");
|
||||||
|
print_hex(text_rows);
|
||||||
|
} else if (fb_info.type == FB_TYPE_RGB) {
|
||||||
|
/* Graphical framebuffer — render with bitmap font */
|
||||||
|
display_mode = 1;
|
||||||
|
pixel_fb = (uint8_t *)(uint32_t)fb_info.addr;
|
||||||
|
fb_pitch = fb_info.pitch;
|
||||||
|
fb_width = fb_info.width;
|
||||||
|
fb_height = fb_info.height;
|
||||||
|
fb_bpp = fb_info.bpp;
|
||||||
|
|
||||||
|
fb_red_pos = fb_info.red_pos;
|
||||||
|
fb_red_size = fb_info.red_size;
|
||||||
|
fb_green_pos = fb_info.green_pos;
|
||||||
|
fb_green_size = fb_info.green_size;
|
||||||
|
fb_blue_pos = fb_info.blue_pos;
|
||||||
|
fb_blue_size = fb_info.blue_size;
|
||||||
|
|
||||||
|
/* Calculate text grid from pixel dimensions */
|
||||||
|
text_cols = fb_width / FONT_WIDTH;
|
||||||
|
text_rows = fb_height / FONT_HEIGHT;
|
||||||
|
if (text_cols == 0) text_cols = 1;
|
||||||
|
if (text_rows == 0) text_rows = 1;
|
||||||
|
|
||||||
|
offset_print(" VGA: pixel mode ");
|
||||||
|
print_hex(fb_width);
|
||||||
|
offset_print(" VGA: x ");
|
||||||
|
print_hex(fb_height);
|
||||||
|
offset_print(" VGA: bpp=");
|
||||||
|
print_hex(fb_bpp);
|
||||||
|
offset_print(" VGA: text grid ");
|
||||||
|
print_hex(text_cols);
|
||||||
|
offset_print(" VGA: x ");
|
||||||
|
print_hex(text_rows);
|
||||||
|
offset_print(" VGA: addr=");
|
||||||
|
print_hex((uint32_t)pixel_fb);
|
||||||
|
} else {
|
||||||
|
/* Indexed or unknown — fall back to text mode */
|
||||||
|
display_mode = 0;
|
||||||
|
text_cols = 80;
|
||||||
|
text_rows = 25;
|
||||||
|
offset_print(" VGA: unknown fb type, assuming text mode\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use dark blue background so user can distinguish "rendering works
|
||||||
|
* but text invisible" from "framebuffer not working at all". */
|
||||||
|
vga_set_color(VGA_LIGHT_GREY, VGA_BLUE);
|
||||||
|
vga_clear();
|
||||||
|
|
||||||
|
vga_set_color(VGA_LIGHT_GREEN, VGA_BLUE);
|
||||||
|
vga_puts("ClaudeOS v0.1 booting...\n\n");
|
||||||
|
vga_set_color(VGA_LIGHT_GREY, VGA_BLUE);
|
||||||
|
|
||||||
|
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
Normal file
97
src/vga.h
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* @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 */
|
||||||
25
test_images.sh
Executable file
25
test_images.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/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!"
|
||||||
Reference in New Issue
Block a user