30 Commits

Author SHA1 Message Date
AI
d1bf69ce0d Add sysfs VFS driver, SYS_OPEN/CLOSE syscalls, cat app
Sysfs:
- New VFS driver mounted at /sys that lets kernel drivers expose
  virtual text files via namespace registration
- Drivers call sysfs_register(name, ops, ctx) with list/read/write
  callbacks for their namespace
- IDE driver registers 'ide' namespace exposing per-device attributes:
  model, type, channel, drive, sectors, sector_size
- Tested: ls /sys -> ide, ls /sys/ide -> hdd1 cd1,
  cat /sys/ide/hdd1/model -> QEMU HARDDISK

Syscalls:
- Added SYS_OPEN (11) and SYS_CLOSE (12) for file I/O from userspace
- Extended SYS_READ/SYS_WRITE to handle VFS file descriptors (fd >= 3)
- Updated userspace syscalls.h with open()/close() wrappers

Apps:
- New 'cat' app: reads and displays file contents via open/read/close
- Updated 'ls' to accept path argument via ARG1 env var
- Updated shell to pass ARG1 env var to external commands
2026-02-23 14:26:52 +00:00
AI
d064e67a8f Add MBR partition driver
- Scan sector 0 of all hdd devices for MBR signature (0xAA55)
- Parse 4 partition entries, register non-empty ones as sub-devices
- Sub-devices named hddNmbrY (e.g., hdd1mbr1) via devicefs
- LBA translation: sub-device reads/writes offset by partition start LBA
- Partition type constants for FAT12/16/32, Linux, NTFS, etc.
- IDE improvements: floating bus detection (0xFF), channel presence check
- Tested: detects FAT32 LBA partition (type=0x0C) on test disk
- Check off devicefs, IDE, MBR in README
2026-02-23 14:08:10 +00:00
AI
c07ec030a7 Add IDE/ATA disk driver with devicefs integration
Implement PIO-mode IDE driver that scans primary and secondary channels
for ATA hard drives and ATAPI CD/DVD drives using IDENTIFY commands.

Features:
- Scans 4 possible devices (2 channels x 2 drives each)
- ATA IDENTIFY DEVICE for hard drives
- ATAPI IDENTIFY PACKET DEVICE for CD/DVD drives
- PIO-mode 28-bit LBA sector read/write for ATA drives
- Model string extraction and sector count parsing
- Registers as kernel driver via REGISTER_DRIVER macro
- Registers devices with devicefs: ATA → hdd class, ATAPI → cd class
- Added inw/outw to port_io.h for 16-bit I/O

Tested: QEMU detects hdd1 (QEMU HARDDISK) and cd1 (QEMU DVD-ROM).
2026-02-23 13:57:00 +00:00
AI
c12d49dea0 Add devicefs VFS driver mounted at /dev
Implement device filesystem subsystem that provides a VFS interface at
/dev for exposing block and character devices. Drivers register devices
via devicefs_register_block() or devicefs_register_char(), and the
devicefs assigns sequential numbers per device class (e.g., hdd1, hdd2).

Features:
- Block device ops: read_sectors, write_sectors, sector_size, sector_count
- Character device ops: read, write
- VFS integration: readdir lists devices, finddir looks up by name
- Byte-offset to sector translation for block device reads/writes
- Auto-numbering: devices named classN where N starts at 1 per class

Also checks off 'ls' task in README.
2026-02-23 13:53:31 +00:00
AI
3512e937ae Fix keyboard init: add timeout to controller flush loop
The PS/2 keyboard controller flush loop could hang infinitely on some
VMs (UTM) where the controller keeps reporting data. Add a 1024-byte
timeout to prevent the kernel from hanging during keyboard_init().

Also add debug prints showing flush progress for diagnostics.
2026-02-23 13:48:33 +00:00
AI
c3c01049bf Force text mode when GRUB reports RGB but display is text mode
On UTM/Mac, GRUB may report a RGB framebuffer while the actual display
is in VGA text mode (0xB8000). The early VGA canary confirms text mode
works. Override fb_info to EGA_TEXT before init_drivers() so vga_init()
uses the correct text buffer instead of writing to an invisible pixel
framebuffer.

Also add EARLY_PRINT markers for SYSCALL, KBD, PROC, DRV stages.
2026-02-23 13:44:54 +00:00
AI
d3ab5a5b55 Add early VGA boot diagnostics for UTM debugging
Write boot milestones directly to VGA text buffer (0xB8000) at each
init stage so the user can see exactly where the kernel stops on
platforms where the VGA driver may not initialize.

Milestones: Magic OK, GDT, IDT, PIC, PMM, PAGING, HEAP, CPIO, VFS,
INITRD, TSS. Each appears as white-on-blue text.

If the multiboot2 magic check fails, display the received magic value
in red-on-white on screen and halt (instead of silently returning).

This helps diagnose if GRUB matched the multiboot1 header instead.
2026-02-23 13:42:25 +00:00
AI
e3d011da2f Fix UTM display: remove required framebuffer tag, add serial output, ls app
Display fixes for UTM/Mac black screen issue:
- Remove MULTIBOOT_HEADER_TAG_FRAMEBUFFER from multiboot2 header (was required,
  requesting text mode depth=0 which confused GRUB on some platforms)
- Add COM1 serial port (0x3F8) output alongside debugcon for UTM serial capture
- Change VGA background from black to dark blue for diagnostics
- Add early canary write to 0xB8000 ('COS' in magenta) before subsystem init
- print_hex now outputs to both debugcon and COM1

New ls command and SYS_READDIR syscall:
- SYS_READDIR (10): reads directory entries via VFS
- VFS root listing: vfs_readdir handles '/' by iterating mount table
- apps/ls: lists CWD contents, appends '/' for directories
- apps/libc/syscalls.h: readdir() wrapper
2026-02-23 13:36:34 +00:00
AI
993cf05712 feat: graphical framebuffer support for VGA driver
When GRUB is configured with multiboot2, it may provide a graphical
(RGB) framebuffer instead of legacy VGA text mode. This happens on
UTM/QEMU on macOS and other configurations where the bootloader
switches to a pixel-based display.

- Parse multiboot2 framebuffer tag in kernel_main to detect display mode
- Identity-map the framebuffer address (often at 0xFD000000+) after
  paging is enabled, with write-through caching
- Add framebuffer.h describing the boot-time display info
- Embed 8x16 VGA bitmap font (font8x16.h) for pixel-mode rendering
- Rewrite VGA driver to support both text mode and pixel mode:
  - Text mode: unchanged behavior writing to 0xB8000
  - Pixel mode: renders characters using the bitmap font to the
    GRUB-provided framebuffer, with proper VGA color palette mapping
  - Auto-detects mode from fb_info at init time
- Multiboot2 header now requests text mode via framebuffer tag, but
  gracefully falls back to pixel rendering if GRUB provides RGB
- Reverted grub.cfg gfxpayload=text (caused display issues on UTM)

Tested: boots in both text mode and graphical framebuffer mode.
2026-02-23 13:25:42 +00:00
AI
000d53e2f3 fix: request text mode in multiboot2 header for VGA output
When using multiboot2, GRUB may set a graphical framebuffer instead
of text mode, causing VGA text writes to 0xB8000 to be invisible.

- Add MULTIBOOT_HEADER_TAG_FRAMEBUFFER to multiboot2 header requesting
  80x25 text mode (depth=0)
- Add 'set gfxpayload=text' to grub.cfg as additional safeguard

This fixes blank display when running under UTM/QEMU with certain
VGA device configurations.
2026-02-23 13:14:56 +00:00
AI
c25ba1fccd feat: shell (sh) with keyboard driver, SYS_READ, SYS_EXEC
- PS/2 keyboard driver: IRQ1 handler, scancode set 1 translation,
  ring buffer, shift key support
- SYS_READ (fd=0): non-blocking keyboard read for stdin
- SYS_YIELD: calls schedule_tick directly (no nested INT 0x80)
- SYS_EXEC: loads binary from CPIO initrd, replaces process image
- User-space libc: crt0.S (C runtime startup), syscalls.h (inline
  syscall wrappers, basic string functions)
- Shell app (sh): readline with echo/backspace, builtins (cd, env,
  help, exit), fork+exec for external commands
- Updated build_apps.sh: C app support with crt0 linking, libc
  include path, .bss section in objcopy

Tested: shell boots, keyboard input works, hello-world runs via
fork+exec, env shows CWD, exit cleanly terminates.
2026-02-23 13:08:06 +00:00
AI
e9b66cd60e docs: check off environment variables task in README 2026-02-23 12:46:26 +00:00
AI
9cef025687 feat: implement per-process environment variables (AI)
- Created env.c/h: key=value store with ENV_MAX_VARS=32 entries per
  process. Supports get, set, unset, and copy operations.
- Added env_block_t and cwd[128] fields to process_t struct.
- process_create() initializes CWD=/ by default.
- Fork inherits environment via memcpy of the entire process_t.
- Added SYS_GETENV (8) and SYS_SETENV (9) system calls.
- Created env-test user app that verifies: CWD default, set/get,
  and inheritance across fork.
- Updated kernel.c to launch 'sh' as init process (for next task).
- Updated README.md to check off environment variables task.

Tested: env-test reads CWD=/, sets TEST=hello, reads it back,
forks child which inherits TEST=hello. All verified via QEMU.
2026-02-23 12:46:12 +00:00
AI
6910deae7c docs: check off fork system call task in README 2026-02-23 12:42:14 +00:00
AI
42328ead0b feat: implement fork system call with deep address space cloning (AI)
- Added paging_clone_directory_from(): deep-copies user-space pages so
  parent and child have independent memory. Kernel pages are shared.
- Fixed process_fork() to accept registers_t* for accurate child state,
  and to clone from the parent's page directory (not the kernel's).
- Refactored process_exit() to properly context-switch to next process
  using new process_switch_to_user assembly stub (loads full registers_t
  and performs iret), instead of halting unconditionally.
- Fixed sys_waitpid() to use proper blocking: marks process BLOCKED,
  invokes scheduler, and resumes with exit code when child dies.
- Added SYSCALL_SWITCHED mechanism to prevent syscall_handler from
  clobbering the next process's EAX after a context switch.
- Created fork-test user app that validates fork + waitpid.
- Added docs/fork.md with architecture documentation.

Tested: fork-test creates child, both print messages, parent waits for
child exit (code 7), parent reaps and exits (code 0). hello-world also
verified to still work correctly after the process_exit refactor.
2026-02-23 12:42:02 +00:00
AI
f1de5b6da6 docs: check off hello-world app task in README 2026-02-23 12:31:00 +00:00
AI
a6e6e3d8ca feat: hello-world user-mode app loaded from initrd via VFS
- Created apps/hello-world/hello-world.S: AT&T assembly user program that
  calls SYS_WRITE to print 'Hello, World' then SYS_EXIT(0)
- Created apps/user.ld: linker script for user apps at 0x08048000
- Created scripts/build_apps.sh: builds each app dir into flat binary
- Updated CMakeLists.txt: added apps build target in ISO pipeline
- Updated gen_initrd.sh: packs built binaries from build/apps_bin/
- Updated kernel.c: replaced inline machine code with VFS-based loading
  of hello-world from /initrd/hello-world via cpio_find()

Verified: hello-world binary (49 bytes) loads from CPIO initrd,
prints 'Hello, World', and exits with code 0.
2026-02-23 12:30:36 +00:00
AI
0c5aa72fd3 Add VFS subsystem and initrd filesystem driver (AI)
VFS subsystem (vfs.c/h):
- Mount table with up to 16 mount points, longest-prefix path matching.
- File descriptor table (256 entries, fds 0-2 reserved for std streams).
- Path resolution walks mount table then delegates to filesystem's
  finddir() for each path component.
- Operations: open, close, read, write, seek, readdir, stat.
- Each filesystem driver provides a vfs_fs_ops_t with callbacks.

Initrd filesystem driver (initrd_fs.c/h):
- Read-only VFS driver backed by the CPIO ramdisk.
- Mounted at '/initrd' during boot.
- Zero-copy reads: file data points directly into the CPIO archive
  memory, no allocation or copying needed.
- Supports readdir (flat iteration) and finddir (name lookup).

Bug fix: resolve_path was overwriting file-specific fs_data (set by
finddir, e.g. pointer to CPIO file data) with the mount's fs_data
(NULL). Fixed to preserve fs_data from finddir.

Verified in QEMU: kernel reads /initrd/README via VFS and prints its
contents. Ring 3 user process continues to work.
2026-02-23 12:23:32 +00:00
AI
3d5fb4c267 Add CPIO initial ramdisk with build infrastructure and parser (AI)
Build system changes:
- scripts/gen_initrd.sh packs all files from apps/ into a newc-format
  CPIO archive at build/isodir/boot/initrd.cpio.
- CMakeLists.txt adds 'initrd' target as ISO dependency. GRUB loads the
  archive as a Multiboot2 module via 'module2 /boot/initrd.cpio'.
- apps/README added as placeholder file for initial ramdisk content.

Kernel changes:
- kernel.c scans Multiboot2 tags for MULTIBOOT_TAG_TYPE_MODULE to find
  the initrd's physical address range, then passes it to cpio_init().
- cpio.c/h implements a parser for the SVR4/newc CPIO format:
  - cpio_init(): lists archive contents on startup
  - cpio_find(): look up a file by name (handles ./ prefix)
  - cpio_next(): iterate through all entries
  - cpio_count(): count files in archive
- The initrd lives in identity-mapped physical memory, so no additional
  mapping is needed to access it.

Verified in QEMU: GRUB loads the module at 0x0014A000, CPIO parser
finds the README file (38 bytes). All existing functionality (Ring 3
processes, syscalls) continues to work.
2026-02-23 12:16:24 +00:00
AI
71e2ae482a Implement Ring 3 process subsystem with syscalls and context switching (AI)
Add complete user-mode process support:

- TSS (tss.c/h): Task State Segment for Ring 3->0 transitions, installed
  as GDT entry 5 (selector 0x28). ESP0 updated per-process for kernel
  stack switching.

- Process management (process.c/h): Process table with up to 64 processes.
  process_create() clones kernel page directory, maps user code at
  0x08048000 and user stack at 0xBFFFF000, copies flat binary code.
  Round-robin scheduler via schedule_tick() modifies the interrupt frame
  in-place for zero-copy context switching.

- System calls (syscall.c/h): INT 0x80 dispatcher with 8 syscalls:
  SYS_EXIT, SYS_WRITE (to debug port + VGA), SYS_READ, SYS_FORK,
  SYS_GETPID, SYS_YIELD, SYS_WAITPID, SYS_EXEC. IDT gate at 0x80
  uses DPL=3 (flags 0xEE) so user code can invoke it.

- Assembly stubs (interrupts.S): isr128 for INT 0x80, tss_flush for
  loading the Task Register, enter_usermode for initial iret to Ring 3.

- Paging extensions (paging.c/h): paging_clone_directory() to create
  per-process page directories, paging_map_page_in() for mapping into
  non-active directories, paging_switch_directory() for CR3 switching.

- GDT expanded from 5 to 6 entries to accommodate TSS descriptor.
  gdt_set_gate() exposed in header for TSS initialization.

- ISR handler routes timer IRQ (32) to scheduler and INT 0x80 to
  syscall dispatcher. Exception handler now prints EIP/CS/ERR for
  debugging.

- Kernel boots a test user program that writes 'Hello from Ring 3!'
  via SYS_WRITE and exits with code 42 via SYS_EXIT. Verified working
  in QEMU.

Context switching approach: Timer/syscall interrupts save all registers
via the ISR stub. schedule_tick() copies saved_regs between PCBs and
overwrites the interrupt frame, so the existing iret restores the next
process's state without separate switch assembly.
2026-02-23 12:10:46 +00:00
AI
313aeb5872 Implement VGA text-mode driver with memory statistics display (AI)
- Created VGA driver that writes to the 0xB8000 text-mode framebuffer.
  Supports 80x25 display with 16 foreground/background colors, scrolling,
  hardware cursor updates, and special characters (\n, \r, \t, \b).
- Provides vga_puts, vga_putchar, vga_put_hex, vga_put_dec, vga_set_color.
- Displays boot banner ("ClaudeOS v0.1 booting...") on screen clear.
- vga_show_mem_stats() prints total RAM, kernel start/end addresses, and
  kernel size on the VGA display during boot.
- Registered as the first driver using REGISTER_DRIVER, proving the
  driver framework works end-to-end (probe -> init lifecycle).

Tested: driver loads successfully, debug port confirms vga probe/init.
2026-02-23 11:10:48 +00:00
AI
bb09de6a6d Implement driver architecture with linker-section registration (AI)
- Created driver framework with probe/init lifecycle. Drivers register via
  REGISTER_DRIVER macro which places pointers in a .drivers linker section.
- During boot, init_drivers() iterates the section, probes each driver
  (checking if hardware is present), and initializes those that respond OK.
- Added .drivers section to linker.ld with __drivers_start/__drivers_end
  symbols for iteration.
- Also added .rodata.* pattern to the .rodata section for string literals
  placed in sub-sections by the compiler.
- No drivers are registered yet; the VGA driver will be the first.

Tested: boots cleanly with driver scan completing (0 registered, 0 loaded).
2026-02-23 11:08:59 +00:00
AI
f63cd9eb3f Implement kernel memory allocator (kmalloc/kfree) and freestanding string library (AI)
- Added first-fit free-list allocator with block splitting and coalescing.
  Provides kmalloc(), kfree(), and kcalloc() for kernel-space dynamic memory.
- Each block carries an inline header with a magic value (0xCAFEBABE) for
  heap corruption detection, plus double-free checking.
- Memory is obtained from the paging subsystem in 4 KiB page increments.
  All allocations are 8-byte aligned with a 16-byte minimum block size.
- Created freestanding string.c with memset, memcpy, memmove, memcmp,
  strlen, strcmp, strncmp, strcpy, strncpy — replacing the unavailable
  libc implementations.
- Added documentation in docs/kmalloc.md.

Tested: kmalloc(64) returns 0xD0001010 (in kernel heap) and kfree succeeds.
Works with both 4 MiB and 128 MiB RAM.
2026-02-23 11:06:52 +00:00
AI
fb61ab7c15 Implement paging subsystem with identity mapping and kernel heap (AI)
- Created two-level x86 paging (page directory + page tables) with 4 KiB pages.
- Identity maps all detected physical memory in two phases:
  1) Static: first 16 MiB using 4 BSS-allocated page tables (avoids
     chicken-and-egg with PMM bitmap in BSS).
  2) Dynamic: memory above 16 MiB using PMM-allocated page tables,
     created before paging is enabled so physical addresses still work.
- Provides kernel heap at 0xD0000000–0xF0000000 for virtual page allocation.
- API: paging_map_page, paging_unmap_page, paging_alloc_page, paging_free_page,
  paging_get_physical.
- Added pmm_get_memory_size() to expose detected RAM for paging init.
- Kernel tests paging by allocating a virtual page, writing 0xDEADBEEF, and
  reading it back, then freeing it.
- Added documentation in docs/paging.md.

Tested: boots and passes paging test with both 4 MiB and 128 MiB RAM in QEMU.
2026-02-23 11:03:27 +00:00
AI
f2e7d6c5d7 Fix PMM: switch to Multiboot2 boot protocol and add documentation (AI)
- Changed grub.cfg from 'multiboot' to 'multiboot2' command. The PMM parses
  Multiboot2 tag structures, but GRUB was booting with Multiboot1 protocol,
  causing the memory map parsing to silently fail (all memory stayed marked
  as used, leading to OOM on every allocation).
- Fixed BITMAP_SIZE calculation to properly round up instead of truncating,
  ensuring the last few pages of the address space are covered.
- Fixed sign comparison warning in bitmap init loop.
- Added debug output to PMM init (mem_upper, region count) for diagnostics.
- Removed stale Multiboot1 magic constant and (void)addr cast from kernel.c.
- Added documentation for the interrupt subsystem and PMM in docs/.
- Checked off 'Implement a PIC handler' and 'Create a physical memory
  allocator' in the task list.

Tested: kernel boots in QEMU with both 4MB and 128MB RAM, PMM correctly
allocates from NORMAL zone (0x01000000) and DMA zone (0x00001000).
2026-02-23 10:57:56 +00:00
AI
cf3059747a Implement physical memory allocator with zone support (AI)
- Added bitmap-based physical memory manager (PMM) that parses the Multiboot2
  memory map to discover available RAM regions.
- Supports two allocation zones: PMM_ZONE_DMA (below 16MB) and PMM_ZONE_NORMAL
  (above 16MB), with automatic fallback from NORMAL to DMA when the preferred
  zone is exhausted.
- Marks kernel memory region and multiboot info structure as reserved using
  _kernel_start/_kernel_end linker symbols.
- Page 0 is always marked as used to prevent returning NULL as a valid address.
- Added linker script symbols (_kernel_start, _kernel_end) to track kernel
  memory boundaries for the allocator.
- Kernel now initializes PMM after PIC and performs test allocations to verify
  the subsystem works.
2026-02-23 10:52:06 +00:00
AI
f1923fdbcf Implement ISR stubs and PIC driver for hardware interrupt handling (AI)
- Reworked IDT initialization to register all 32 CPU exception handlers (ISR 0-31)
  and 16 hardware interrupt handlers (IRQ 0-15, mapped to IDT entries 32-47).
- Created assembly stubs in interrupts.S using macros for ISRs with and without
  error codes, plus IRQ stubs. All route through a common stub that saves
  registers, loads kernel data segment, and calls the C handler.
- Added isr.c with a unified interrupt dispatcher that handles both exceptions
  (halts on fault) and hardware IRQs (sends EOI via PIC).
- Implemented PIC (8259) driver in pic.c with full initialization sequence that
  remaps IRQ 0-7 to IDT 32-39 and IRQ 8-15 to IDT 40-47. Includes mask/unmask
  and EOI support.
- Extracted port I/O primitives (inb, outb, io_wait) into port_io.h header for
  reuse across drivers.
- Kernel now initializes PIC after IDT and enables interrupts with STI.
2026-02-23 10:51:45 +00:00
3909a1f581 Add copilot instructions (human) 2026-02-23 11:48:40 +01:00
7c45406c23 Add PIC handling (human) 2026-02-23 11:05:41 +01:00
AI
e4cc638a8d Implement GDT and basic IDT setup
This commit adds GDT initialization with proper code/data segments and reloads CS.

It also adds the initial IDT structure and loads an empty IDT.

Build configuration updated to disable SSE/MMX to prevent compiler generation of unsupported instructions in early boot.
2026-02-23 10:05:17 +00:00
77 changed files with 9039 additions and 40 deletions

1
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1 @@
See `README.md` for a general overview on how to work and for a list of tasks to perform.

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@ release/
debug_grub/
*_output.txt
snippet.*
qemu.log
iso_output.txt

View File

@@ -4,7 +4,7 @@ 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 -g -O2 -Wall -Wextra")
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")
@@ -19,15 +19,33 @@ add_subdirectory(src)
file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/release)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/isodir/boot/grub)
# Create grub.cfg for ISO
file(WRITE ${CMAKE_BINARY_DIR}/isodir/boot/grub/grub.cfg "set timeout=0\nset default=0\nsearch --set=root --file /boot/kernel.bin\nmenuentry \"ClaudeOS\" { multiboot /boot/kernel.bin }")
# 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
DEPENDS kernel initrd
COMMENT "Generating bootable ISO image"
)

View File

@@ -41,25 +41,27 @@ Once a task is completed, it should be checked off.
- [x] Create initial build system
- [x] Setup a simple kernel that writes `Hello, world` to Qemu debug port
- [x] Update the build system to create both ISO and Floppy images. Verify these work using a test script. The standard CMake build target should automatically generate both images. (Only ISO supported for now)
- [ ] Update the kernel to correctly setup the GDT
- [ ] Create a physical memory allocator and mapper. The kernel should live in the upper last gigabyte of virtual memory. It should support different zones (e.g.: `SUB_16M`, `DEFAULT`, ...) These zones describe the region of memory that memory should be allocated in. If it is not possible to allocate in that region (because it is full, or has 0 capacity to begin with), it should fallback to another zone.
- [ ] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
- [ ] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them.
- [ ] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
- [ ] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
- [ ] Create subsystem for loading new processes in Ring 3.
- [ ] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
- [ ] Write a VFS subsystem.
- [ ] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer.
- [ ] Create a `hello-world` app. It should print `Hello, World` to its own stdout. The kernel should route this to Qemu and to the VGA dispaly. Ensure this work.
- [ ] Implement the fork system call.
- [ ] Implement environment variables. Apps should be able to modify this, and it should be copied to new forks of an app.
- [ ] Create a basic shell program `sh`. This shell must be able to start the hello-world app. It must include `cd` as a built-in to change the current working directory.
- [ ] Create an `ls` app. It should list the contents of the current working directory, via the environment variable.
- [ ] 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 MBR driver. It should be able to automatically detect when new hard drives are detected, and automatically scan them for MBR partitions. Each MBR partition found should be listed as `hddNmbrY`, where Y is a number determined by the devicefs subsystem.
- [ ] Create a `sysfs` vfs driver. It should allow drivers to expose text/config files to the VFS. Each driver can request a namespace in the sysfs. E.g.: the IDE driver could request `ide`. During this registration, the drive must provide a struct containing function callbacks. The callbacks must contain the function `list`, `read`, and `write`. These are executed when the user lists a directory, reads a file, or writes a file. It is expected that the contents of these files are extremely small and can simply be stored on the stack. It should be very easy for a driver to expose new information.
- [x] Update the kernel to correctly setup the GDT
- [x] Create an interrupt handler.
- [x] Implement a PIC handler
- [x] Create a physical memory allocator and mapper. The kernel should live in the upper last gigabyte of virtual memory. It should support different zones (e.g.: `SUB_16M`, `DEFAULT`, ...) These zones describe the region of memory that memory should be allocated in. If it is not possible to allocate in that region (because it is full, or has 0 capacity to begin with), it should fallback to another zone.
- [x] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
- [x] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them.
- [x] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
- [x] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
- [x] Create subsystem for loading new processes in Ring 3.
- [x] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
- [x] Write a VFS subsystem.
- [x] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer.
- [x] Create a `hello-world` app. It should print `Hello, World` to its own stdout. The kernel should route this to Qemu and to the VGA dispaly. Ensure this work.
- [x] Implement the fork system call.
- [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.
- [x] Create an `ls` app. It should list the contents of the current working directory, via the environment variable.
- [x] Create a devicefs vfs driver. The devicefs subsystem should allow drivers to create new block devices devices.
- [x] 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.
- [x] Create an MBR driver. It should be able to automatically detect when new hard drives are detected, and automatically scan them for MBR partitions. Each MBR partition found should be listed as `hddNmbrY`, where Y is a number determined by the devicefs subsystem.
- [x] Create a `sysfs` vfs driver. It should allow drivers to expose text/config files to the VFS. Each driver can request a namespace in the sysfs. E.g.: the IDE driver could request `ide`. During this registration, the drive must provide a struct containing function callbacks. The callbacks must contain the function `list`, `read`, and `write`. These are executed when the user lists a directory, reads a file, or writes a file. It is expected that the contents of these files are extremely small and can simply be stored on the stack. It should be very easy for a driver to expose new information.
- [ ] Create a FAT32 driver. It should allow reading and writing to and from a block device.
- [ ] Create the `mount` app. It should allow on to mount a block device using the fat32 driver. Internally, it should use sysfs (which should be mounted automatically by the kernel to `/sys`) to setup a new mount.
- [ ] Create a floppy driver. Each floppy device should be exposed as `/dev/floppyN`.

1
apps/README Normal file
View File

@@ -0,0 +1 @@
This is the ClaudeOS initial ramdisk.

41
apps/cat/cat.c Normal file
View File

@@ -0,0 +1,41 @@
/**
* @file cat.c
* @brief Display file contents.
*
* Reads a file specified as the first argument (from shell)
* and prints its contents to stdout. If no argument is given,
* prints usage.
*
* Usage: cat <filepath>
*/
#include "syscalls.h"
int main(void) {
/* Get the file path from the ARGS environment variable.
* The shell sets ARGS to the arguments after the command name. */
char path[128];
if (getenv("ARG1", path, sizeof(path)) < 0) {
puts("Usage: cat <file>\n");
return 1;
}
/* Open the file */
int32_t fd = open(path, 0);
if (fd < 0) {
puts("cat: ");
puts(path);
puts(": open failed\n");
return 1;
}
/* Read and print in chunks */
char buf[256];
int32_t n;
while ((n = read(fd, buf, sizeof(buf))) > 0) {
write(1, buf, (uint32_t)n);
}
close(fd);
return 0;
}

123
apps/env-test/env-test.S Normal file
View 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

View 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

View 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
View 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

180
apps/libc/syscalls.h Normal file
View File

@@ -0,0 +1,180 @@
/**
* @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
#define SYS_OPEN 11
#define SYS_CLOSE 12
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);
}
/**
* Open a file by path.
* @param path File path.
* @param flags Open flags (currently unused, pass 0).
* @return File descriptor (>= 3) on success, -1 on failure.
*/
static inline int32_t open(const char *path, uint32_t flags) {
return syscall2(SYS_OPEN, (uint32_t)path, flags);
}
/**
* Close a file descriptor.
* @param fd File descriptor.
* @return 0 on success, -1 on failure.
*/
static inline int32_t close(int32_t fd) {
return syscall1(SYS_CLOSE, (uint32_t)fd);
}
/* 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 */

38
apps/ls/ls.c Normal file
View File

@@ -0,0 +1,38 @@
/**
* @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) {
/* Check for an explicit path argument first */
char cwd[128];
if (getenv("ARG1", cwd, sizeof(cwd)) < 0 || cwd[0] == '\0') {
/* No argument; use the current working directory */
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
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;
}

187
apps/sh/sh.c Normal file
View File

@@ -0,0 +1,187 @@
/**
* @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, const char *arg) {
int32_t pid = fork();
if (pid < 0) {
puts("sh: fork failed\n");
return;
}
if (pid == 0) {
/* Child: set ARG1 if there's an argument */
if (arg && *arg) {
setenv("ARG1", arg);
} else {
setenv("ARG1", "");
}
/* 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, arg);
}
}
return 0;
}

25
apps/user.ld Normal file
View 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
View 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
View 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
View 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 0255 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 |
|---|---|
| 031 | CPU exceptions (Division by Zero, Page Fault, GPF, etc.) |
| 3247 | Hardware IRQs (remapped from default 015) |
| 48255 | 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 3247), `isr_handler` sends an End-of-Interrupt (EOI) to the PIC.
6. For CPU exceptions (vectors 031), 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 07 mapped to vectors 815, which collide with CPU exceptions. During initialization, we remap:
- **Master PIC** (IRQ 07) → vectors 3239
- **Slave PIC** (IRQ 815) → vectors 4047
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 031 and IRQs 015.
- `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
View 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
View 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
View 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
View 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 03. 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
View 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
View 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
View 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"

View File

@@ -2,6 +2,30 @@ 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
devicefs.c
sysfs.c
ide.c
mbr.c
env.c
keyboard.c
interrupts.S
kernel.c
)

View File

@@ -26,9 +26,8 @@ multiboot_header:
/* checksum */
.long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
/* Tags here */
/* End tag */
.align 8
.short MULTIBOOT_HEADER_TAG_END
.short 0
.long 8

179
src/cpio.c Normal file
View 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
View 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 */

350
src/devicefs.c Normal file
View File

@@ -0,0 +1,350 @@
/**
* @file devicefs.c
* @brief Device filesystem (devicefs) implementation.
*
* Provides a VFS driver mounted at /dev that exposes block and character
* devices. Kernel drivers register devices via devicefs_register_block()
* or devicefs_register_char(), and the devicefs assigns sequential numbers
* per device class (e.g., hdd1, hdd2, cd1).
*
* The VFS interface supports:
* - readdir: lists all registered devices
* - finddir: looks up a device by name
* - read/write: delegates to the device's block or char operations
*/
#include "devicefs.h"
#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);
/** Device table. */
static devicefs_device_t devices[DEVICEFS_MAX_DEVICES];
/** Number of active devices. */
static uint32_t device_count = 0;
/**
* Find a free slot in the device table.
* @return Index of free slot, or -1 if full.
*/
static int find_free_slot(void) {
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
if (!devices[i].active) {
return i;
}
}
return -1;
}
/* ================================================================
* VFS operations for /dev
* ================================================================ */
/**
* Read from a device file.
*
* For block devices, translates byte offset/size to sector reads.
* For character devices, delegates directly to the char read op.
*/
static int32_t devfs_read(vfs_node_t *node, uint32_t offset,
uint32_t size, void *buf) {
if (!node || !node->fs_data) return -1;
devicefs_device_t *dev = (devicefs_device_t *)node->fs_data;
if (dev->type == DEVICEFS_BLOCK && dev->block_ops) {
uint32_t sec_size = 512;
if (dev->block_ops->sector_size) {
sec_size = dev->block_ops->sector_size(dev->dev_data);
}
if (sec_size == 0) return -1;
uint32_t start_lba = offset / sec_size;
uint32_t end_byte = offset + size;
uint32_t end_lba = (end_byte + sec_size - 1) / sec_size;
uint32_t num_sectors = end_lba - start_lba;
/* For simplicity, require aligned reads for now */
if (offset % sec_size != 0 || size % sec_size != 0) {
/* Unaligned read: read full sectors, copy partial */
/* TODO: implement unaligned block reads with temp buffer */
return -1;
}
if (dev->block_ops->read_sectors) {
int ret = dev->block_ops->read_sectors(dev->dev_data,
start_lba,
num_sectors,
buf);
return (ret == 0) ? (int32_t)size : -1;
}
return -1;
}
if (dev->type == DEVICEFS_CHAR && dev->char_ops) {
if (dev->char_ops->read) {
return dev->char_ops->read(dev->dev_data, size, buf);
}
return -1;
}
return -1;
}
/**
* Write to a device file.
*
* For block devices, translates byte offset/size to sector writes.
* For character devices, delegates directly to the char write op.
*/
static int32_t devfs_write(vfs_node_t *node, uint32_t offset,
uint32_t size, const void *buf) {
if (!node || !node->fs_data) return -1;
devicefs_device_t *dev = (devicefs_device_t *)node->fs_data;
if (dev->type == DEVICEFS_BLOCK && dev->block_ops) {
uint32_t sec_size = 512;
if (dev->block_ops->sector_size) {
sec_size = dev->block_ops->sector_size(dev->dev_data);
}
if (sec_size == 0) return -1;
if (offset % sec_size != 0 || size % sec_size != 0) {
return -1; /* Require aligned writes */
}
uint32_t start_lba = offset / sec_size;
uint32_t num_sectors = size / sec_size;
if (dev->block_ops->write_sectors) {
int ret = dev->block_ops->write_sectors(dev->dev_data,
start_lba,
num_sectors,
buf);
return (ret == 0) ? (int32_t)size : -1;
}
return -1;
}
if (dev->type == DEVICEFS_CHAR && dev->char_ops) {
if (dev->char_ops->write) {
return dev->char_ops->write(dev->dev_data, size, buf);
}
return -1;
}
return -1;
}
/**
* Read a directory entry from /dev.
* Lists all registered devices.
*/
static int devfs_readdir(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out) {
(void)dir;
uint32_t count = 0;
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
if (!devices[i].active) continue;
if (count == idx) {
memset(out, 0, sizeof(vfs_dirent_t));
strncpy(out->name, devices[i].name, VFS_MAX_NAME - 1);
out->inode = (uint32_t)i;
out->type = (devices[i].type == DEVICEFS_BLOCK) ?
VFS_BLOCKDEV : VFS_CHARDEV;
return 0;
}
count++;
}
return -1; /* No more entries */
}
/**
* Find a device by name within /dev.
*/
static int devfs_finddir(vfs_node_t *dir, const char *name, vfs_node_t *out) {
(void)dir;
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
if (!devices[i].active) continue;
if (strcmp(devices[i].name, name) == 0) {
memset(out, 0, sizeof(vfs_node_t));
strncpy(out->name, devices[i].name, VFS_MAX_NAME - 1);
out->type = (devices[i].type == DEVICEFS_BLOCK) ?
VFS_BLOCKDEV : VFS_CHARDEV;
out->inode = (uint32_t)i;
out->fs_data = &devices[i];
/* For block devices, compute size from sector count */
if (devices[i].type == DEVICEFS_BLOCK && devices[i].block_ops) {
uint32_t sec_size = 512;
uint32_t sec_count = 0;
if (devices[i].block_ops->sector_size) {
sec_size = devices[i].block_ops->sector_size(devices[i].dev_data);
}
if (devices[i].block_ops->sector_count) {
sec_count = devices[i].block_ops->sector_count(devices[i].dev_data);
}
out->size = sec_count * sec_size;
}
return 0;
}
}
return -1;
}
/** Filesystem operations for /dev. */
static vfs_fs_ops_t devfs_ops = {
.open = NULL,
.close = NULL,
.read = devfs_read,
.write = devfs_write,
.readdir = devfs_readdir,
.finddir = devfs_finddir,
};
/* ================================================================
* Public API
* ================================================================ */
uint32_t devicefs_next_number(const char *class_name) {
uint32_t max_num = 0;
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
if (!devices[i].active) continue;
if (strcmp(devices[i].class_name, class_name) == 0) {
if (devices[i].number >= max_num) {
max_num = devices[i].number + 1;
}
}
}
/* Numbers start at 1 */
return (max_num == 0) ? 1 : max_num;
}
/**
* Format a uint32 as a decimal string into buf.
* Returns pointer to the start of the number within buf.
*/
static char *uint_to_str(uint32_t val, char *buf, int buf_size) {
buf[buf_size - 1] = '\0';
int pos = buf_size - 2;
if (val == 0) {
buf[pos] = '0';
return &buf[pos];
}
while (val > 0 && pos >= 0) {
buf[pos--] = (char)('0' + (val % 10));
val /= 10;
}
return &buf[pos + 1];
}
devicefs_device_t *devicefs_register_block(const char *class_name,
devicefs_block_ops_t *ops,
void *dev_data) {
int slot = find_free_slot();
if (slot < 0) {
offset_print(" DEVICEFS: no free device slots\n");
return NULL;
}
devicefs_device_t *dev = &devices[slot];
memset(dev, 0, sizeof(devicefs_device_t));
/* Copy class name */
strncpy(dev->class_name, class_name, DEVICEFS_MAX_CLASS_NAME - 1);
/* Assign sequential number */
dev->number = devicefs_next_number(class_name);
/* Build full device name: class_name + number */
char num_buf[12];
char *num_str = uint_to_str(dev->number, num_buf, sizeof(num_buf));
strncpy(dev->name, class_name, DEVICEFS_MAX_DEV_NAME - 12);
/* Append number string */
uint32_t nlen = strlen(dev->name);
uint32_t slen = strlen(num_str);
if (nlen + slen < DEVICEFS_MAX_DEV_NAME) {
memcpy(dev->name + nlen, num_str, slen + 1);
}
dev->type = DEVICEFS_BLOCK;
dev->block_ops = ops;
dev->dev_data = dev_data;
dev->active = 1;
device_count++;
offset_print(" DEVICEFS: registered block device /dev/");
offset_print(dev->name);
offset_print("\n");
return dev;
}
devicefs_device_t *devicefs_register_char(const char *class_name,
devicefs_char_ops_t *ops,
void *dev_data) {
int slot = find_free_slot();
if (slot < 0) {
offset_print(" DEVICEFS: no free device slots\n");
return NULL;
}
devicefs_device_t *dev = &devices[slot];
memset(dev, 0, sizeof(devicefs_device_t));
strncpy(dev->class_name, class_name, DEVICEFS_MAX_CLASS_NAME - 1);
dev->number = devicefs_next_number(class_name);
char num_buf[12];
char *num_str = uint_to_str(dev->number, num_buf, sizeof(num_buf));
strncpy(dev->name, class_name, DEVICEFS_MAX_DEV_NAME - 12);
/* Append number string */
uint32_t nlen2 = strlen(dev->name);
uint32_t slen2 = strlen(num_str);
if (nlen2 + slen2 < DEVICEFS_MAX_DEV_NAME) {
memcpy(dev->name + nlen2, num_str, slen2 + 1);
}
dev->type = DEVICEFS_CHAR;
dev->char_ops = ops;
dev->dev_data = dev_data;
dev->active = 1;
device_count++;
offset_print(" DEVICEFS: registered char device /dev/");
offset_print(dev->name);
offset_print("\n");
return dev;
}
devicefs_device_t *devicefs_find(const char *name) {
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
if (!devices[i].active) continue;
if (strcmp(devices[i].name, name) == 0) {
return &devices[i];
}
}
return NULL;
}
int init_devicefs(void) {
memset(devices, 0, sizeof(devices));
device_count = 0;
int ret = vfs_mount("/dev", &devfs_ops, NULL);
if (ret != 0) {
offset_print(" DEVICEFS: failed to mount at /dev\n");
return -1;
}
offset_print(" DEVICEFS: mounted at /dev\n");
return 0;
}

135
src/devicefs.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

616
src/ide.c Normal file
View File

@@ -0,0 +1,616 @@
/**
* @file ide.c
* @brief IDE/ATA disk driver implementation.
*
* Probes the primary and secondary IDE channels for ATA hard drives and
* ATAPI CD/DVD drives using PIO-mode IDENTIFY commands. Detected devices
* are registered with the devicefs subsystem as block devices:
* - ATA drives → "hdd" class (hdd1, hdd2, ...)
* - ATAPI drives → "cd" class (cd1, cd2, ...)
*
* Supports PIO-mode sector reads and writes using 28-bit LBA addressing,
* which covers drives up to 128 GiB.
*/
#include "ide.h"
#include "port_io.h"
#include "devicefs.h"
#include "sysfs.h"
#include "driver.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);
/** All detected IDE devices. */
static ide_device_t ide_devices[IDE_MAX_DEVICES];
/* ================================================================
* Low-level IDE I/O helpers
* ================================================================ */
/**
* Wait for the BSY flag to clear on the given channel.
* Returns the final status byte, or 0xFF on timeout.
*
* @param io_base Channel I/O base port.
* @return Status byte.
*/
static uint8_t ide_wait(uint16_t io_base) {
uint8_t status;
int timeout = 500000;
do {
status = inb(io_base + IDE_REG_STATUS);
if (status == 0xFF) return 0xFF; /* Floating bus */
if (--timeout == 0) return 0xFF;
} while (status & IDE_STATUS_BSY);
return status;
}
/**
* Wait for BSY to clear and DRQ to set (data ready).
* Returns 0 on success, -1 on timeout or error.
*
* @param io_base Channel I/O base port.
* @return 0 on success, -1 on error/timeout.
*/
static int ide_wait_drq(uint16_t io_base) {
uint8_t status;
int timeout = 500000;
do {
status = inb(io_base + IDE_REG_STATUS);
if (status == 0xFF) return -1;
if (status & (IDE_STATUS_ERR | IDE_STATUS_DF)) return -1;
if (--timeout == 0) return -1;
} while ((status & (IDE_STATUS_BSY | IDE_STATUS_DRQ)) != IDE_STATUS_DRQ);
return 0;
}
/**
* Read 256 16-bit words (512 bytes) from the data register.
*
* @param io_base Channel I/O base port.
* @param buf Destination buffer (must be at least 512 bytes).
*/
static void ide_read_buffer(uint16_t io_base, uint16_t *buf) {
for (int i = 0; i < 256; i++) {
buf[i] = inw(io_base + IDE_REG_DATA);
}
}
/**
* Write 256 16-bit words (512 bytes) to the data register.
*
* @param io_base Channel I/O base port.
* @param buf Source buffer (must be at least 512 bytes).
*/
static void ide_write_buffer(uint16_t io_base, const uint16_t *buf) {
for (int i = 0; i < 256; i++) {
outw(io_base + IDE_REG_DATA, buf[i]);
}
}
/**
* Perform a software reset on an IDE channel.
*
* @param ctrl_base Channel control port.
*/
static void ide_soft_reset(uint16_t ctrl_base) {
outb(ctrl_base, 0x04); /* Set SRST bit */
/* Wait ~5 µs (several I/O reads) */
for (int i = 0; i < 4; i++) inb(ctrl_base);
outb(ctrl_base, 0x00); /* Clear SRST bit */
/* Wait for BSY to clear */
for (int i = 0; i < 4; i++) inb(ctrl_base);
}
/**
* Select a drive on a channel (master=0, slave=1).
*
* @param io_base Channel I/O base port.
* @param drive 0 for master, 1 for slave.
*/
static void ide_select_drive(uint16_t io_base, uint8_t drive) {
outb(io_base + IDE_REG_DRIVE_HEAD, 0xA0 | (drive << 4));
/* Wait ~400 ns by reading status 4 times */
for (int i = 0; i < 4; i++) inb(io_base + IDE_REG_STATUS);
}
/* ================================================================
* IDENTIFY command
* ================================================================ */
/**
* Send IDENTIFY (or IDENTIFY PACKET) to a drive and read the result.
*
* @param dev Pointer to the device descriptor to fill.
* @return 0 on success, -1 if no device or error.
*/
static int ide_identify(ide_device_t *dev) {
uint16_t io = dev->io_base;
/* Select the drive */
ide_select_drive(io, dev->drive);
/* Check for floating bus (no device) — read status, if 0xFF, no drive */
uint8_t check = inb(io + IDE_REG_STATUS);
if (check == 0xFF) return -1;
/* Clear sector count and LBA registers */
outb(io + IDE_REG_SECCOUNT, 0);
outb(io + IDE_REG_LBA_LO, 0);
outb(io + IDE_REG_LBA_MID, 0);
outb(io + IDE_REG_LBA_HI, 0);
/* Send IDENTIFY command */
outb(io + IDE_REG_COMMAND, IDE_CMD_IDENTIFY);
/* Read status — if 0 or 0xFF, no device */
uint8_t status = inb(io + IDE_REG_STATUS);
if (status == 0 || status == 0xFF) return -1;
/* Wait for BSY to clear */
status = ide_wait(io);
if (status == 0xFF) return -1;
/* Check if this is an ATAPI device (LBA_MID/HI will be non-zero) */
uint8_t lba_mid = inb(io + IDE_REG_LBA_MID);
uint8_t lba_hi = inb(io + IDE_REG_LBA_HI);
if (lba_mid == 0x14 && lba_hi == 0xEB) {
/* ATAPI device — re-identify with IDENTIFY PACKET DEVICE */
dev->type = IDE_TYPE_ATAPI;
outb(io + IDE_REG_COMMAND, IDE_CMD_IDENTIFY_PKT);
status = ide_wait(io);
if (status == 0xFF) return -1;
} else if (lba_mid == 0 && lba_hi == 0) {
dev->type = IDE_TYPE_ATA;
} else {
/* Unknown device type */
return -1;
}
/* Wait for DRQ */
if (ide_wait_drq(io) != 0) return -1;
/* Read 256 words of identification data */
uint16_t identify_buf[256];
ide_read_buffer(io, identify_buf);
/* Parse model string (words 27-46, each word is big-endian) */
for (int i = 0; i < 20; i++) {
dev->model[i * 2] = (char)(identify_buf[27 + i] >> 8);
dev->model[i * 2 + 1] = (char)(identify_buf[27 + i] & 0xFF);
}
dev->model[40] = '\0';
/* Trim trailing spaces */
for (int i = 39; i >= 0; i--) {
if (dev->model[i] == ' ') dev->model[i] = '\0';
else break;
}
/* Parse sector count (28-bit LBA: words 60-61) */
if (dev->type == IDE_TYPE_ATA) {
dev->sector_count = (uint32_t)identify_buf[60] |
((uint32_t)identify_buf[61] << 16);
dev->sector_size = 512;
} else {
/* ATAPI: sector count from READ CAPACITY, default to 0 */
dev->sector_count = 0;
dev->sector_size = 2048; /* Standard CD sector size */
}
dev->present = 1;
return 0;
}
/* ================================================================
* Block device operations (for devicefs)
* ================================================================ */
/**
* Read sectors from an ATA drive using PIO.
*/
static int ide_block_read(void *dev_data, uint32_t lba,
uint32_t count, void *buf) {
ide_device_t *dev = (ide_device_t *)dev_data;
if (!dev || dev->type != IDE_TYPE_ATA) return -1;
uint16_t io = dev->io_base;
uint8_t *dest = (uint8_t *)buf;
for (uint32_t s = 0; s < count; s++) {
uint32_t cur_lba = lba + s;
/* Select drive with LBA mode and top 4 LBA bits */
outb(io + IDE_REG_DRIVE_HEAD,
0xE0 | (dev->drive << 4) | ((cur_lba >> 24) & 0x0F));
/* Set sector count = 1 and LBA */
outb(io + IDE_REG_SECCOUNT, 1);
outb(io + IDE_REG_LBA_LO, cur_lba & 0xFF);
outb(io + IDE_REG_LBA_MID, (cur_lba >> 8) & 0xFF);
outb(io + IDE_REG_LBA_HI, (cur_lba >> 16) & 0xFF);
/* Send READ SECTORS command */
outb(io + IDE_REG_COMMAND, IDE_CMD_READ_PIO);
/* Wait for data */
if (ide_wait_drq(io) != 0) return -1;
/* Read 256 words (512 bytes) */
ide_read_buffer(io, (uint16_t *)(dest + s * 512));
}
return 0;
}
/**
* Write sectors to an ATA drive using PIO.
*/
static int ide_block_write(void *dev_data, uint32_t lba,
uint32_t count, const void *buf) {
ide_device_t *dev = (ide_device_t *)dev_data;
if (!dev || dev->type != IDE_TYPE_ATA) return -1;
uint16_t io = dev->io_base;
const uint8_t *src = (const uint8_t *)buf;
for (uint32_t s = 0; s < count; s++) {
uint32_t cur_lba = lba + s;
outb(io + IDE_REG_DRIVE_HEAD,
0xE0 | (dev->drive << 4) | ((cur_lba >> 24) & 0x0F));
outb(io + IDE_REG_SECCOUNT, 1);
outb(io + IDE_REG_LBA_LO, cur_lba & 0xFF);
outb(io + IDE_REG_LBA_MID, (cur_lba >> 8) & 0xFF);
outb(io + IDE_REG_LBA_HI, (cur_lba >> 16) & 0xFF);
outb(io + IDE_REG_COMMAND, IDE_CMD_WRITE_PIO);
if (ide_wait_drq(io) != 0) return -1;
ide_write_buffer(io, (const uint16_t *)(src + s * 512));
/* Flush cache: wait for BSY to clear after write */
ide_wait(io);
}
return 0;
}
/**
* Return sector size for a device.
*/
static uint32_t ide_block_sector_size(void *dev_data) {
ide_device_t *dev = (ide_device_t *)dev_data;
return dev ? dev->sector_size : 512;
}
/**
* Return total sector count for a device.
*/
static uint32_t ide_block_sector_count(void *dev_data) {
ide_device_t *dev = (ide_device_t *)dev_data;
return dev ? dev->sector_count : 0;
}
/** Block operations for ATA/ATAPI devices. */
static devicefs_block_ops_t ide_block_ops = {
.read_sectors = ide_block_read,
.write_sectors = ide_block_write,
.sector_size = ide_block_sector_size,
.sector_count = ide_block_sector_count,
};
/* ================================================================
* Sysfs interface for /sys/ide
* ================================================================
*
* Exposes per-device info:
* /sys/ide/ → lists device names (hdd1, cd1, ...)
* /sys/ide/hdd1/ → lists attributes (model, type, channel, drive,
* sectors, sector_size)
* /sys/ide/hdd1/model → "QEMU HARDDISK\n"
* /sys/ide/hdd1/sectors → "0x00003800\n"
*/
/** Integer to hex string helper for sysfs output. */
static int ide_sysfs_hex(uint32_t val, char *buf, uint32_t buf_size) {
static const char hex[] = "0123456789ABCDEF";
if (buf_size < 12) return -1; /* "0x" + 8 hex + \n + \0 */
buf[0] = '0'; buf[1] = 'x';
for (int i = 7; i >= 0; i--) {
buf[2 + (7 - i)] = hex[(val >> (i * 4)) & 0xF];
}
buf[10] = '\n'; buf[11] = '\0';
return 11;
}
/**
* Split a path string into first component and remainder.
* "hdd1/model" → first="hdd1", rest="model"
*/
static void ide_split_path(const char *path, char *first, uint32_t fsize,
const char **rest) {
while (*path == '/') path++;
const char *s = path;
while (*s && *s != '/') s++;
uint32_t len = (uint32_t)(s - path);
if (len >= fsize) len = fsize - 1;
memcpy(first, path, len);
first[len] = '\0';
if (*s == '/') s++;
*rest = s;
}
/** Attribute names exposed for each device. */
static const char *ide_sysfs_attrs[] = {
"model", "type", "channel", "drive", "sectors", "sector_size"
};
#define IDE_SYSFS_NUM_ATTRS 6
/**
* Find an IDE device by its devicefs name (hdd1, cd1, etc.).
* Returns the device pointer, or NULL.
*/
static ide_device_t *ide_find_by_name(const char *name) {
for (int i = 0; i < IDE_MAX_DEVICES; i++) {
if (!ide_devices[i].present) continue;
/* Reconstruct the devicefs name for this device */
const char *cls = (ide_devices[i].type == IDE_TYPE_ATA) ? "hdd" : "cd";
uint32_t cls_len = strlen(cls);
if (strncmp(name, cls, cls_len) != 0) continue;
/* Parse the number suffix */
const char *num_str = name + cls_len;
uint32_t num = 0;
while (*num_str >= '0' && *num_str <= '9') {
num = num * 10 + (*num_str - '0');
num_str++;
}
if (*num_str != '\0' || num == 0) continue;
/* Count how many devices of this class precede idx i */
uint32_t count = 0;
for (int j = 0; j <= i; j++) {
if (!ide_devices[j].present) continue;
const char *jcls = (ide_devices[j].type == IDE_TYPE_ATA) ? "hdd" : "cd";
if (strcmp(cls, jcls) == 0) count++;
}
if (count == num) return &ide_devices[i];
}
return NULL;
}
/**
* Sysfs list callback.
* path="" → list device names (hdd1, cd1, ...)
* path="hdd1" → list attributes
*/
static int ide_sysfs_list(void *ctx, const char *path, uint32_t idx,
sysfs_entry_t *out) {
(void)ctx;
if (path[0] == '\0') {
/* List all present devices */
uint32_t count = 0;
for (int i = 0; i < IDE_MAX_DEVICES; i++) {
if (!ide_devices[i].present) continue;
if (count == idx) {
memset(out, 0, sizeof(sysfs_entry_t));
/* Build device name */
const char *cls = (ide_devices[i].type == IDE_TYPE_ATA)
? "hdd" : "cd";
/* Count devices of this class up to and including i */
uint32_t cls_count = 0;
for (int j = 0; j <= i; j++) {
if (!ide_devices[j].present) continue;
const char *jcls = (ide_devices[j].type == IDE_TYPE_ATA)
? "hdd" : "cd";
if (strcmp(cls, jcls) == 0) cls_count++;
}
uint32_t clen = strlen(cls);
memcpy(out->name, cls, clen);
/* Append number as ASCII (max 1 digit for 4 devs) */
out->name[clen] = '0' + (char)cls_count;
out->name[clen + 1] = '\0';
out->is_dir = 1;
return 0;
}
count++;
}
return -1;
}
/* List attributes for a specific device */
ide_device_t *dev = ide_find_by_name(path);
if (!dev) return -1;
if (idx >= IDE_SYSFS_NUM_ATTRS) return -1;
memset(out, 0, sizeof(sysfs_entry_t));
strncpy(out->name, ide_sysfs_attrs[idx], SYSFS_MAX_NAME - 1);
out->is_dir = 0;
return 0;
}
/**
* Sysfs read callback.
* path="hdd1/model" → device model string
* path="hdd1/sectors" → hex sector count
*/
static int ide_sysfs_read(void *ctx, const char *path, char *buf,
uint32_t buf_size) {
(void)ctx;
/* Split path into device name and attribute name */
char dev_name[SYSFS_MAX_NAME];
const char *attr;
ide_split_path(path, dev_name, sizeof(dev_name), &attr);
ide_device_t *dev = ide_find_by_name(dev_name);
if (!dev) return -1;
if (attr[0] == '\0') return -1; /* no attribute specified */
if (strcmp(attr, "model") == 0) {
uint32_t mlen = strlen(dev->model);
if (mlen + 2 > buf_size) return -1;
memcpy(buf, dev->model, mlen);
buf[mlen] = '\n';
buf[mlen + 1] = '\0';
return (int)(mlen + 1);
}
if (strcmp(attr, "type") == 0) {
const char *t = (dev->type == IDE_TYPE_ATA) ? "ATA\n" :
(dev->type == IDE_TYPE_ATAPI) ? "ATAPI\n" : "unknown\n";
uint32_t tlen = strlen(t);
if (tlen + 1 > buf_size) return -1;
memcpy(buf, t, tlen + 1);
return (int)tlen;
}
if (strcmp(attr, "channel") == 0) {
const char *c = (dev->channel == 0) ? "primary\n" : "secondary\n";
uint32_t clen = strlen(c);
if (clen + 1 > buf_size) return -1;
memcpy(buf, c, clen + 1);
return (int)clen;
}
if (strcmp(attr, "drive") == 0) {
const char *d = (dev->drive == 0) ? "master\n" : "slave\n";
uint32_t dlen = strlen(d);
if (dlen + 1 > buf_size) return -1;
memcpy(buf, d, dlen + 1);
return (int)dlen;
}
if (strcmp(attr, "sectors") == 0) {
return ide_sysfs_hex(dev->sector_count, buf, buf_size);
}
if (strcmp(attr, "sector_size") == 0) {
return ide_sysfs_hex(dev->sector_size, buf, buf_size);
}
return -1;
}
/**
* Sysfs write callback — IDE is read-only for now.
*/
static int ide_sysfs_write(void *ctx, const char *path, const char *buf,
uint32_t size) {
(void)ctx; (void)path; (void)buf; (void)size;
return -1; /* Read-only */
}
/** Sysfs operations for the IDE namespace. */
static sysfs_ops_t ide_sysfs_ops = {
.list = ide_sysfs_list,
.read = ide_sysfs_read,
.write = ide_sysfs_write,
};
/* ================================================================
* Driver probe and init
* ================================================================ */
/**
* Probe: always return OK since IDE ports are standard.
*/
static driver_probe_result_t ide_probe(void) {
return DRIVER_PROBE_OK;
}
/**
* Initialize the IDE driver: scan channels and register devices.
*/
static int ide_driver_init(void) {
memset(ide_devices, 0, sizeof(ide_devices));
/* Channel definitions: primary (0x1F0, 0x3F6), secondary (0x170, 0x376) */
static const uint16_t io_bases[2] = { IDE_PRIMARY_IO, IDE_SECONDARY_IO };
static const uint16_t ctrl_bases[2] = { IDE_PRIMARY_CTRL, IDE_SECONDARY_CTRL };
int found = 0;
for (int ch = 0; ch < 2; ch++) {
/* Check if channel exists by reading status — 0xFF = floating bus */
uint8_t ch_status = inb(io_bases[ch] + IDE_REG_STATUS);
if (ch_status == 0xFF) {
offset_print(" IDE: ");
offset_print(ch == 0 ? "primary" : "secondary");
offset_print(" channel not present\n");
continue;
}
/* Software reset the channel */
ide_soft_reset(ctrl_bases[ch]);
for (int drv = 0; drv < 2; drv++) {
int idx = ch * 2 + drv;
ide_devices[idx].channel = (uint8_t)ch;
ide_devices[idx].drive = (uint8_t)drv;
ide_devices[idx].io_base = io_bases[ch];
ide_devices[idx].ctrl_base = ctrl_bases[ch];
ide_devices[idx].present = 0;
if (ide_identify(&ide_devices[idx]) == 0) {
found++;
const char *class_name = (ide_devices[idx].type == IDE_TYPE_ATA)
? "hdd" : "cd";
const char *type_str = (ide_devices[idx].type == IDE_TYPE_ATA)
? "ATA" : "ATAPI";
offset_print(" IDE: ");
offset_print(type_str);
offset_print(" device on ");
offset_print(ch == 0 ? "primary" : "secondary");
offset_print(drv == 0 ? " master" : " slave");
offset_print(": ");
offset_print(ide_devices[idx].model);
offset_print(" (");
print_hex(ide_devices[idx].sector_count);
offset_print(" sectors)\n");
/* Register with devicefs */
devicefs_register_block(class_name, &ide_block_ops,
&ide_devices[idx]);
}
}
}
if (found == 0) {
offset_print(" IDE: no devices found\n");
} else {
offset_print(" IDE: ");
print_hex((uint32_t)found);
offset_print(" device(s) found\n");
}
/* Register sysfs namespace for IDE information */
sysfs_register("ide", &ide_sysfs_ops, NULL);
return 0;
}
int ide_init(void) {
return ide_driver_init();
}
ide_device_t *ide_get_device(int index) {
if (index < 0 || index >= IDE_MAX_DEVICES) return NULL;
if (!ide_devices[index].present) return NULL;
return &ide_devices[index];
}
/** IDE driver descriptor. */
static const driver_t ide_driver = {
.name = "ide",
.probe = ide_probe,
.init = ide_driver_init,
};
REGISTER_DRIVER(ide_driver);

88
src/ide.h Normal file
View File

@@ -0,0 +1,88 @@
/**
* @file ide.h
* @brief IDE/ATA disk driver.
*
* Enumerates IDE devices on the primary and secondary channels, identifies
* ATA hard drives and ATAPI CD-ROMs, and registers them with the devicefs
* subsystem as block devices (hddN / cdN).
*/
#ifndef IDE_H
#define IDE_H
#include <stdint.h>
/** Maximum number of IDE devices (2 channels × 2 drives). */
#define IDE_MAX_DEVICES 4
/** IDE channel I/O port bases. */
#define IDE_PRIMARY_IO 0x1F0
#define IDE_PRIMARY_CTRL 0x3F6
#define IDE_SECONDARY_IO 0x170
#define IDE_SECONDARY_CTRL 0x376
/** IDE register offsets from I/O base. */
#define IDE_REG_DATA 0x00
#define IDE_REG_ERROR 0x01
#define IDE_REG_FEATURES 0x01
#define IDE_REG_SECCOUNT 0x02
#define IDE_REG_LBA_LO 0x03
#define IDE_REG_LBA_MID 0x04
#define IDE_REG_LBA_HI 0x05
#define IDE_REG_DRIVE_HEAD 0x06
#define IDE_REG_STATUS 0x07
#define IDE_REG_COMMAND 0x07
/** IDE status register bits. */
#define IDE_STATUS_ERR 0x01 /**< Error occurred. */
#define IDE_STATUS_DRQ 0x08 /**< Data request ready. */
#define IDE_STATUS_SRV 0x10 /**< Overlapped mode service request. */
#define IDE_STATUS_DF 0x20 /**< Drive fault. */
#define IDE_STATUS_DRDY 0x40 /**< Drive ready. */
#define IDE_STATUS_BSY 0x80 /**< Drive busy. */
/** IDE commands. */
#define IDE_CMD_IDENTIFY 0xEC /**< ATA IDENTIFY DEVICE. */
#define IDE_CMD_IDENTIFY_PKT 0xA1 /**< ATAPI IDENTIFY PACKET DEVICE. */
#define IDE_CMD_READ_PIO 0x20 /**< Read sectors (PIO, 28-bit LBA). */
#define IDE_CMD_WRITE_PIO 0x30 /**< Write sectors (PIO, 28-bit LBA). */
/** IDE device types. */
#define IDE_TYPE_NONE 0 /**< No device present. */
#define IDE_TYPE_ATA 1 /**< ATA hard disk. */
#define IDE_TYPE_ATAPI 2 /**< ATAPI CD/DVD drive. */
/**
* IDE device descriptor.
*/
typedef struct ide_device {
uint8_t present; /**< 1 if device is present. */
uint8_t type; /**< IDE_TYPE_ATA or IDE_TYPE_ATAPI. */
uint8_t channel; /**< 0 = primary, 1 = secondary. */
uint8_t drive; /**< 0 = master, 1 = slave. */
uint16_t io_base; /**< I/O base port for this channel. */
uint16_t ctrl_base; /**< Control port for this channel. */
uint32_t sector_count; /**< Total sectors (28-bit LBA max). */
uint32_t sector_size; /**< Sector size in bytes (usually 512). */
char model[41]; /**< Model string from IDENTIFY. */
} ide_device_t;
/**
* Initialize the IDE driver.
*
* Scans primary and secondary channels for ATA/ATAPI devices,
* reads their IDENTIFY data, and registers them with devicefs.
*
* @return Number of devices found.
*/
int ide_init(void);
/**
* Get an IDE device by index (03).
*
* @param index Device index.
* @return Pointer to the device descriptor, or NULL if invalid/not present.
*/
ide_device_t *ide_get_device(int index);
#endif /* IDE_H */

141
src/idt.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View File

@@ -1,31 +1,354 @@
#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 "devicefs.h"
#include "sysfs.h"
#include "mbr.h"
#include "keyboard.h"
#include "framebuffer.h"
static inline void outb(uint16_t port, uint8_t val)
{
asm volatile ( "outb %b0, %w1" : : "a"(val), "Nd"(port) );
/* 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);
outb(0xE9, *str); /* debugcon */
serial_putc(*str); /* COM1 serial */
str++;
}
}
#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002
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) {
(void)addr; // Unused for now
/* 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...");
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC && magic != MULTIBOOT_BOOTLOADER_MAGIC) {
offset_print("Invalid magic number: ");
// I don't have hex print yet, but I can print something generic
offset_print("Unknown\n");
return;
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_devicefs();
EARLY_PRINT("DEV ");
offset_print("Devicefs initialized\n");
init_sysfs();
EARLY_PRINT("SYS ");
offset_print("Sysfs initialized\n");
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");
/* Scan for MBR partitions on detected hard drives */
init_mbr();
offset_print("MBR scan complete\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
View 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
View 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
View 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
View 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 */

View File

@@ -4,6 +4,8 @@ SECTIONS
{
. = 1M;
_kernel_start = .;
.text BLOCK(4K) : ALIGN(4K)
{
KEEP(*(.multiboot))
@@ -13,6 +15,14 @@ SECTIONS
.rodata BLOCK(4K) : ALIGN(4K)
{
*(.rodata)
*(.rodata.*)
}
.drivers BLOCK(4K) : ALIGN(4K)
{
__drivers_start = .;
KEEP(*(.drivers))
__drivers_end = .;
}
.data BLOCK(4K) : ALIGN(4K)
@@ -25,4 +35,6 @@ SECTIONS
*(COMMON)
*(.bss)
}
_kernel_end = .;
}

270
src/mbr.c Normal file
View File

@@ -0,0 +1,270 @@
/**
* @file mbr.c
* @brief MBR partition table driver implementation.
*
* Reads sector 0 from hard drives registered with the devicefs, parses
* the MBR partition table, and registers each partition as a sub-device.
* Partition devices are named using the parent device name + "mbr" class
* prefix, with the number assigned by the devicefs (e.g., hdd1mbr1).
*
* Each partition sub-device translates LBA offsets relative to its own
* start into absolute LBA addresses on the parent device.
*/
#include "mbr.h"
#include "devicefs.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);
/** Maximum number of MBR partition sub-devices. */
#define MBR_MAX_SUBS 16
/**
* Per-partition context for sub-device read/write operations.
*/
typedef struct mbr_sub_device {
devicefs_device_t *parent; /**< Parent block device. */
uint32_t lba_start; /**< Partition start LBA on parent. */
uint32_t sector_count; /**< Partition size in sectors. */
uint8_t type; /**< MBR partition type code. */
int active; /**< 1 if in use. */
} mbr_sub_device_t;
/** Pool of sub-device contexts. */
static mbr_sub_device_t sub_devices[MBR_MAX_SUBS];
/**
* Allocate a sub-device context.
* @return Pointer to a free sub-device, or NULL if exhausted.
*/
static mbr_sub_device_t *alloc_sub(void) {
for (int i = 0; i < MBR_MAX_SUBS; i++) {
if (!sub_devices[i].active) {
sub_devices[i].active = 1;
return &sub_devices[i];
}
}
return NULL;
}
/* ================================================================
* Block device ops for MBR partition sub-devices
* ================================================================ */
/**
* Read sectors from a partition (translates LBA to parent device).
*/
static int mbr_sub_read(void *dev_data, uint32_t lba,
uint32_t count, void *buf) {
mbr_sub_device_t *sub = (mbr_sub_device_t *)dev_data;
if (!sub || !sub->parent) return -1;
/* Bounds check */
if (lba + count > sub->sector_count) return -1;
/* Translate to absolute LBA on parent device */
uint32_t abs_lba = sub->lba_start + lba;
/* Read from parent device */
if (!sub->parent->block_ops || !sub->parent->block_ops->read_sectors) {
return -1;
}
return sub->parent->block_ops->read_sectors(sub->parent->dev_data,
abs_lba, count, buf);
}
/**
* Write sectors to a partition (translates LBA to parent device).
*/
static int mbr_sub_write(void *dev_data, uint32_t lba,
uint32_t count, const void *buf) {
mbr_sub_device_t *sub = (mbr_sub_device_t *)dev_data;
if (!sub || !sub->parent) return -1;
if (lba + count > sub->sector_count) return -1;
uint32_t abs_lba = sub->lba_start + lba;
if (!sub->parent->block_ops || !sub->parent->block_ops->write_sectors) {
return -1;
}
return sub->parent->block_ops->write_sectors(sub->parent->dev_data,
abs_lba, count, buf);
}
/**
* Get sector size (same as parent device).
*/
static uint32_t mbr_sub_sector_size(void *dev_data) {
mbr_sub_device_t *sub = (mbr_sub_device_t *)dev_data;
if (!sub || !sub->parent || !sub->parent->block_ops) return 512;
if (sub->parent->block_ops->sector_size) {
return sub->parent->block_ops->sector_size(sub->parent->dev_data);
}
return 512;
}
/**
* Get total sector count of the partition.
*/
static uint32_t mbr_sub_sector_count(void *dev_data) {
mbr_sub_device_t *sub = (mbr_sub_device_t *)dev_data;
return sub ? sub->sector_count : 0;
}
/** Block operations for MBR partition sub-devices. */
static devicefs_block_ops_t mbr_sub_ops = {
.read_sectors = mbr_sub_read,
.write_sectors = mbr_sub_write,
.sector_size = mbr_sub_sector_size,
.sector_count = mbr_sub_sector_count,
};
/* ================================================================
* MBR scanning
* ================================================================ */
int mbr_scan(const char *parent_name) {
devicefs_device_t *parent = devicefs_find(parent_name);
if (!parent) {
offset_print(" MBR: device not found: ");
offset_print(parent_name);
offset_print("\n");
return -1;
}
if (parent->type != DEVICEFS_BLOCK || !parent->block_ops) {
offset_print(" MBR: not a block device: ");
offset_print(parent_name);
offset_print("\n");
return -1;
}
if (!parent->block_ops->read_sectors) {
offset_print(" MBR: device has no read_sectors op\n");
return -1;
}
/* Read sector 0 (MBR) */
uint8_t sector[512];
if (parent->block_ops->read_sectors(parent->dev_data, 0, 1, sector) != 0) {
offset_print(" MBR: failed to read sector 0 of ");
offset_print(parent_name);
offset_print("\n");
return -1;
}
/* Check MBR signature */
uint16_t sig = (uint16_t)sector[510] | ((uint16_t)sector[511] << 8);
if (sig != MBR_SIGNATURE) {
offset_print(" MBR: no valid signature on ");
offset_print(parent_name);
offset_print(" (sig=");
print_hex(sig);
offset_print(")\n");
return 0; /* Not an error, just no MBR */
}
/* Build the class name for partition sub-devices: parentName + "mbr" */
char class_name[DEVICEFS_MAX_DEV_NAME];
memset(class_name, 0, sizeof(class_name));
strncpy(class_name, parent_name, DEVICEFS_MAX_DEV_NAME - 4);
/* Append "mbr" */
uint32_t clen = strlen(class_name);
if (clen + 3 < DEVICEFS_MAX_DEV_NAME) {
class_name[clen] = 'm';
class_name[clen + 1] = 'b';
class_name[clen + 2] = 'r';
class_name[clen + 3] = '\0';
}
/* Parse the 4 partition table entries (at offset 446) */
int found = 0;
for (int i = 0; i < MBR_MAX_PARTITIONS; i++) {
mbr_partition_entry_t *entry =
(mbr_partition_entry_t *)(sector + 446 + i * 16);
/* Skip empty partitions */
if (entry->type == MBR_TYPE_EMPTY) continue;
if (entry->sector_count == 0) continue;
/* Skip extended partitions for now */
if (entry->type == MBR_TYPE_EXTENDED) {
offset_print(" MBR: skipping extended partition on ");
offset_print(parent_name);
offset_print("\n");
continue;
}
mbr_sub_device_t *sub = alloc_sub();
if (!sub) {
offset_print(" MBR: no free sub-device slots\n");
break;
}
sub->parent = parent;
sub->lba_start = entry->lba_start;
sub->sector_count = entry->sector_count;
sub->type = entry->type;
/* Register with devicefs */
devicefs_device_t *dev = devicefs_register_block(class_name,
&mbr_sub_ops,
sub);
if (dev) {
offset_print(" MBR: ");
offset_print(dev->name);
offset_print(" type=");
print_hex(entry->type);
offset_print(" LBA=");
print_hex(entry->lba_start);
offset_print(" size=");
print_hex(entry->sector_count);
offset_print(" sectors\n");
found++;
}
}
if (found == 0) {
offset_print(" MBR: no partitions on ");
offset_print(parent_name);
offset_print("\n");
}
return found;
}
int init_mbr(void) {
memset(sub_devices, 0, sizeof(sub_devices));
/* Scan all "hdd" class devices for MBR partitions */
int total_partitions = 0;
for (uint32_t i = 1; i <= 16; i++) {
/* Build device name: hdd1, hdd2, ... */
char name[DEVICEFS_MAX_DEV_NAME];
memset(name, 0, sizeof(name));
name[0] = 'h'; name[1] = 'd'; name[2] = 'd';
/* Append number */
if (i < 10) {
name[3] = (char)('0' + i);
} else {
name[3] = (char)('0' + i / 10);
name[4] = (char)('0' + i % 10);
}
devicefs_device_t *dev = devicefs_find(name);
if (!dev) continue;
int n = mbr_scan(name);
if (n > 0) total_partitions += n;
}
offset_print(" MBR: ");
print_hex((uint32_t)total_partitions);
offset_print(" partition(s) found\n");
return 0;
}

67
src/mbr.h Normal file
View File

@@ -0,0 +1,67 @@
/**
* @file mbr.h
* @brief MBR (Master Boot Record) partition table driver.
*
* Scans block devices for MBR partition tables and registers each
* discovered partition as a new block device in the devicefs.
* For example, hdd1 with two partitions becomes hdd1mbr1 and hdd1mbr2.
*
* The partition number (Y in hddNmbrY) is assigned by the devicefs
* subsystem using the device class "hddNmbr".
*/
#ifndef MBR_H
#define MBR_H
#include <stdint.h>
/** MBR signature bytes (offset 510-511). */
#define MBR_SIGNATURE 0xAA55
/** Maximum partitions in a standard MBR. */
#define MBR_MAX_PARTITIONS 4
/** MBR partition table entry (16 bytes each). */
typedef struct __attribute__((packed)) mbr_partition_entry {
uint8_t status; /**< 0x80 = bootable, 0x00 = not bootable. */
uint8_t chs_first[3]; /**< CHS address of first sector. */
uint8_t type; /**< Partition type code. */
uint8_t chs_last[3]; /**< CHS address of last sector. */
uint32_t lba_start; /**< LBA of first sector. */
uint32_t sector_count; /**< Number of sectors. */
} mbr_partition_entry_t;
/** Well-known MBR partition types. */
#define MBR_TYPE_EMPTY 0x00
#define MBR_TYPE_FAT12 0x01
#define MBR_TYPE_FAT16_SM 0x04 /**< FAT16 < 32 MiB. */
#define MBR_TYPE_EXTENDED 0x05
#define MBR_TYPE_FAT16_LG 0x06 /**< FAT16 >= 32 MiB. */
#define MBR_TYPE_FAT32 0x0B
#define MBR_TYPE_FAT32_LBA 0x0C
#define MBR_TYPE_FAT16_LBA 0x0E
#define MBR_TYPE_LINUX 0x83
#define MBR_TYPE_LINUX_SWAP 0x82
/**
* Scan a block device for MBR partitions.
*
* Reads sector 0 of the given device, validates the MBR signature,
* and registers each non-empty partition entry with the devicefs
* as a sub-device.
*
* @param parent_name Name of the parent device (e.g., "hdd1").
* @return Number of partitions found, or -1 on error.
*/
int mbr_scan(const char *parent_name);
/**
* Initialize the MBR subsystem.
*
* Scans all currently registered "hdd" class devices for MBR partitions.
*
* @return 0 on success.
*/
int init_mbr(void);
#endif /* MBR_H */

384
src/paging.c Normal file
View 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 (01023).
* @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
View 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
View 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
View 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
View 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
View 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

37
src/port_io.h Normal file
View File

@@ -0,0 +1,37 @@
#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 outw(uint16_t port, uint16_t val)
{
asm volatile ( "outw %w0, %w1" : : "a"(val), "Nd"(port) );
}
static inline uint16_t inw(uint16_t port)
{
uint16_t ret;
asm volatile ( "inw %w1, %w0" : "=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
View 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
View 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
View 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;
}

381
src/syscall.c Normal file
View File

@@ -0,0 +1,381 @@
/**
* @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;
}
/* VFS file descriptors (fd >= 3) */
if (fd >= 3) {
return vfs_write(fd, buf, 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 */
}
/* VFS file descriptors (fd >= 3) */
if (fd >= 3) {
return vfs_read(fd, buf, len);
}
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_OPEN: open a file by path.
* EBX = path string, ECX = flags.
* Returns file descriptor (>= 3) on success, -1 on failure.
*/
static int32_t sys_open(registers_t *regs) {
const char *path = (const char *)regs->ebx;
uint32_t flags = regs->ecx;
return (int32_t)vfs_open(path, flags);
}
/**
* Handle SYS_CLOSE: close a file descriptor.
* EBX = fd.
* Returns 0 on success, -1 on failure.
*/
static int32_t sys_close(registers_t *regs) {
int fd = (int)regs->ebx;
if (fd < 3) return -1; /* Don't close stdin/stdout/stderr */
vfs_close(fd);
return 0;
}
/**
* 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,
[SYS_OPEN] = sys_open,
[SYS_CLOSE] = sys_close,
};
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");
}

47
src/syscall.h Normal file
View File

@@ -0,0 +1,47 @@
/**
* @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. */
#define SYS_OPEN 11 /**< Open a file. path=EBX, flags=ECX. Returns fd or -1. */
#define SYS_CLOSE 12 /**< Close a file descriptor. fd=EBX. Returns 0 or -1. */
/** Total number of system calls. */
#define NUM_SYSCALLS 13
/**
* 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 */

294
src/sysfs.c Normal file
View File

@@ -0,0 +1,294 @@
/**
* @file sysfs.c
* @brief System filesystem (sysfs) implementation.
*
* A VFS driver mounted at /sys that lets kernel drivers expose virtual
* text files. Drivers register namespaces (e.g., "ide", "mem") and
* provide callbacks for listing entries and reading/writing file content.
*
* Path structure:
* /sys/<namespace>/<subpath...>
*
* The VFS routes requests through sysfs_readdir, sysfs_finddir, and
* sysfs_read/write, which decompose the path and forward it to the
* appropriate driver's callbacks.
*/
#include "sysfs.h"
#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);
/* ================================================================
* Namespace registry
* ================================================================ */
/** A registered sysfs namespace. */
typedef struct sysfs_ns {
char name[SYSFS_MAX_NAME]; /**< Namespace name. */
sysfs_ops_t *ops; /**< Driver callbacks. */
void *ctx; /**< Driver context. */
int active; /**< 1 if registered. */
} sysfs_ns_t;
/** Namespace table. */
static sysfs_ns_t namespaces[SYSFS_MAX_NAMESPACES];
/**
* Find a namespace by name.
* @return Pointer to the namespace, or NULL.
*/
static sysfs_ns_t *find_ns(const char *name) {
for (int i = 0; i < SYSFS_MAX_NAMESPACES; i++) {
if (namespaces[i].active && strcmp(namespaces[i].name, name) == 0) {
return &namespaces[i];
}
}
return NULL;
}
/* ================================================================
* Path helpers
* ================================================================ */
/**
* Extract the first path component from a relative path.
*
* Given "ide/hdd1/model", sets component="ide" and rest="hdd1/model".
* Given "ide", sets component="ide" and rest="".
*
* @param path Input relative path.
* @param component Output buffer for the first component.
* @param comp_size Size of component buffer.
* @param rest Output pointer to the remainder of the path.
*/
static void split_first(const char *path, char *component,
uint32_t comp_size, const char **rest) {
/* Skip leading slashes */
while (*path == '/') path++;
const char *slash = path;
while (*slash && *slash != '/') slash++;
uint32_t len = (uint32_t)(slash - path);
if (len >= comp_size) len = comp_size - 1;
memcpy(component, path, len);
component[len] = '\0';
/* Skip the slash */
if (*slash == '/') slash++;
*rest = slash;
}
/* ================================================================
* VFS callbacks for /sys
* ================================================================ */
/**
* Read from a sysfs virtual file.
*
* The node's fs_data points to the sysfs_ns_t.
* The node's name encodes the sub-path (set by finddir).
*
* We store the full sub-path (relative to namespace) in node->name.
*/
static int32_t sysfs_vfs_read(vfs_node_t *node, uint32_t offset,
uint32_t size, void *buf) {
if (!node || !node->fs_data) return -1;
sysfs_ns_t *ns = (sysfs_ns_t *)node->fs_data;
if (!ns->ops || !ns->ops->read) return -1;
/* Read the full content into a stack buffer */
char content[SYSFS_MAX_CONTENT];
int total = ns->ops->read(ns->ctx, node->name, content,
sizeof(content));
if (total < 0) return -1;
/* Apply offset */
if (offset >= (uint32_t)total) return 0;
uint32_t avail = (uint32_t)total - offset;
if (size > avail) size = avail;
memcpy(buf, content + offset, size);
return (int32_t)size;
}
/**
* Write to a sysfs virtual file.
*/
static int32_t sysfs_vfs_write(vfs_node_t *node, uint32_t offset,
uint32_t size, const void *buf) {
if (!node || !node->fs_data) return -1;
sysfs_ns_t *ns = (sysfs_ns_t *)node->fs_data;
if (!ns->ops || !ns->ops->write) return -1;
return ns->ops->write(ns->ctx, node->name, (const char *)buf, size);
}
/**
* Read directory entries under /sys or /sys/<namespace>/...
*
* When the directory node has no fs_data, we're at /sys root → list
* namespaces. When fs_data is a namespace, delegate to its list callback.
*/
static int sysfs_vfs_readdir(vfs_node_t *dir, uint32_t idx,
vfs_dirent_t *out) {
if (!dir) return -1;
/* Root of /sys → list registered namespaces */
if (dir->fs_data == NULL) {
uint32_t count = 0;
for (int i = 0; i < SYSFS_MAX_NAMESPACES; i++) {
if (!namespaces[i].active) continue;
if (count == idx) {
memset(out, 0, sizeof(vfs_dirent_t));
strncpy(out->name, namespaces[i].name,
VFS_MAX_NAME - 1);
out->type = VFS_DIRECTORY;
out->inode = (uint32_t)i;
return 0;
}
count++;
}
return -1;
}
/* Subdirectory within a namespace */
sysfs_ns_t *ns = (sysfs_ns_t *)dir->fs_data;
if (!ns->ops || !ns->ops->list) return -1;
sysfs_entry_t entry;
int ret = ns->ops->list(ns->ctx, dir->name, idx, &entry);
if (ret != 0) return -1;
memset(out, 0, sizeof(vfs_dirent_t));
strncpy(out->name, entry.name, VFS_MAX_NAME - 1);
out->type = entry.is_dir ? VFS_DIRECTORY : VFS_FILE;
out->inode = idx;
return 0;
}
/**
* Look up a child by name inside a sysfs directory.
*
* At the /sys root (fs_data == NULL), we look up namespace names.
* Inside a namespace, we ask the driver's list callback to check
* whether the child is a file or directory.
*/
static int sysfs_vfs_finddir(vfs_node_t *dir, const char *name,
vfs_node_t *out) {
if (!dir) return -1;
memset(out, 0, sizeof(vfs_node_t));
/* Root of /sys → find a namespace */
if (dir->fs_data == NULL) {
sysfs_ns_t *ns = find_ns(name);
if (!ns) return -1;
strncpy(out->name, "", VFS_MAX_NAME - 1); /* sub-path is "" */
out->type = VFS_DIRECTORY;
out->fs_data = ns;
return 0;
}
/* Inside a namespace: build sub-path */
sysfs_ns_t *ns = (sysfs_ns_t *)dir->fs_data;
if (!ns->ops || !ns->ops->list) return -1;
/* The dir->name contains the current sub-path (relative to namespace).
* The child path is dir->name + "/" + name (or just name if dir->name
* is empty). */
char child_path[VFS_MAX_NAME];
if (dir->name[0] == '\0') {
strncpy(child_path, name, VFS_MAX_NAME - 1);
child_path[VFS_MAX_NAME - 1] = '\0';
} else {
/* Manually concatenate dir->name + "/" + name */
uint32_t dlen = strlen(dir->name);
uint32_t nlen = strlen(name);
if (dlen + 1 + nlen >= VFS_MAX_NAME) return -1;
memcpy(child_path, dir->name, dlen);
child_path[dlen] = '/';
memcpy(child_path + dlen + 1, name, nlen);
child_path[dlen + 1 + nlen] = '\0';
}
/* Iterate the list callback to see if this child exists */
sysfs_entry_t entry;
for (uint32_t i = 0; ; i++) {
int ret = ns->ops->list(ns->ctx, dir->name, i, &entry);
if (ret != 0) break; /* no more entries */
if (strcmp(entry.name, name) == 0) {
strncpy(out->name, child_path, VFS_MAX_NAME - 1);
out->type = entry.is_dir ? VFS_DIRECTORY : VFS_FILE;
out->fs_data = ns;
/* For files, get the content size */
if (!entry.is_dir && ns->ops->read) {
char tmp[SYSFS_MAX_CONTENT];
int sz = ns->ops->read(ns->ctx, child_path, tmp, sizeof(tmp));
out->size = (sz > 0) ? (uint32_t)sz : 0;
}
return 0;
}
}
return -1; /* child not found */
}
/** VFS operations for /sys. */
static vfs_fs_ops_t sysfs_vfs_ops = {
.open = NULL,
.close = NULL,
.read = sysfs_vfs_read,
.write = sysfs_vfs_write,
.readdir = sysfs_vfs_readdir,
.finddir = sysfs_vfs_finddir,
};
/* ================================================================
* Public API
* ================================================================ */
void init_sysfs(void) {
memset(namespaces, 0, sizeof(namespaces));
vfs_mount("/sys", &sysfs_vfs_ops, NULL);
offset_print(" Sysfs mounted at /sys\n");
}
int sysfs_register(const char *name, sysfs_ops_t *ops, void *ctx) {
if (!name || !ops) return -1;
/* Check for duplicate */
if (find_ns(name)) {
offset_print(" Sysfs: namespace '");
offset_print(name);
offset_print("' already exists\n");
return -1;
}
/* Find a free slot */
for (int i = 0; i < SYSFS_MAX_NAMESPACES; i++) {
if (!namespaces[i].active) {
strncpy(namespaces[i].name, name, SYSFS_MAX_NAME - 1);
namespaces[i].name[SYSFS_MAX_NAME - 1] = '\0';
namespaces[i].ops = ops;
namespaces[i].ctx = ctx;
namespaces[i].active = 1;
offset_print(" Sysfs: registered namespace '");
offset_print(name);
offset_print("'\n");
return 0;
}
}
offset_print(" Sysfs: no free namespace slots\n");
return -1;
}

100
src/sysfs.h Normal file
View File

@@ -0,0 +1,100 @@
/**
* @file sysfs.h
* @brief System filesystem (sysfs) interface.
*
* Allows kernel drivers to expose text/config files to userspace via the
* VFS at /sys. Each driver registers a namespace (e.g., "ide") and
* provides callbacks for listing, reading, and writing virtual files.
*
* Usage:
* 1. Define a sysfs_ops_t with list/read/write callbacks.
* 2. Call sysfs_register("ide", &my_ops, my_context).
* 3. Users can then `ls /sys/ide`, `cat /sys/ide/model`, etc.
*
* File contents are small text strings stored on the stack; the read
* callback fills a caller-provided buffer.
*/
#ifndef SYSFS_H
#define SYSFS_H
#include <stdint.h>
#include <stddef.h>
/** Maximum number of sysfs namespaces. */
#define SYSFS_MAX_NAMESPACES 32
/** Maximum namespace name length. */
#define SYSFS_MAX_NAME 64
/** Maximum size for sysfs file content (text buffer). */
#define SYSFS_MAX_CONTENT 512
/**
* Sysfs file entry returned by the list callback.
*/
typedef struct sysfs_entry {
char name[SYSFS_MAX_NAME]; /**< File or directory name. */
uint8_t is_dir; /**< 1 if directory, 0 if file. */
} sysfs_entry_t;
/**
* Callbacks provided by a driver to expose files under its namespace.
*
* All paths are relative to the namespace root. For a namespace "ide",
* a path of "hdd1/model" means the file "model" inside subdirectory "hdd1".
*/
typedef struct sysfs_ops {
/**
* List entries in a directory.
*
* @param ctx Driver context pointer (from registration).
* @param path Relative path within the namespace ("" for root).
* @param idx Entry index (0-based).
* @param out Output entry.
* @return 0 on success, -1 when no more entries.
*/
int (*list)(void *ctx, const char *path, uint32_t idx,
sysfs_entry_t *out);
/**
* Read a file's content as a text string.
*
* @param ctx Driver context pointer.
* @param path Relative path to the file within the namespace.
* @param buf Output buffer for file content.
* @param buf_size Buffer size.
* @return Number of bytes written to buf, or -1 on error.
*/
int (*read)(void *ctx, const char *path, char *buf, uint32_t buf_size);
/**
* Write data to a file.
*
* @param ctx Driver context pointer.
* @param path Relative path to the file within the namespace.
* @param buf Input data.
* @param size Number of bytes to write.
* @return Number of bytes consumed, or -1 on error.
*/
int (*write)(void *ctx, const char *path, const char *buf, uint32_t size);
} sysfs_ops_t;
/**
* Initialize the sysfs and mount it at /sys.
*/
void init_sysfs(void);
/**
* Register a driver namespace in sysfs.
*
* After registration, files are accessible under /sys/<name>/.
*
* @param name Namespace name (e.g., "ide").
* @param ops Callback operations.
* @param ctx Opaque driver context passed to all callbacks.
* @return 0 on success, -1 if the table is full or name is invalid.
*/
int sysfs_register(const char *name, sysfs_ops_t *ops, void *ctx);
#endif /* SYSFS_H */

47
src/tss.c Normal file
View 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
View 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
View 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(&current, 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(&current, 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
View 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
View 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
View 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 */

View File

@@ -12,11 +12,6 @@ if [ ! -f "$RELEASE_DIR/claude-os.iso" ]; then
exit 1
fi
if [ ! -f "$RELEASE_DIR/claude-os.img" ]; then
echo "Error: claude-os.img 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