commit 7409cd4d26b8968e5b88f0f5303a35591e947a39 Author: ale Date: Sun Jun 15 22:26:49 2025 +0200 initial commit Signed-off-by: ale diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a43f9d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +target/ +Cargo.lock +*.swp +*.swo +*~ +.DS_Store +*.orig +*.rej + +# Kernel build artifacts +*.o +*.ko +*.mod.c +modules.order +Module.symvers +.tmp_versions/ + +# Documentation +doc/ + +# IDE files +.vscode/ +.idea/ +*.iml + +# OS files +Thumbs.db diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..f34e68d --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,125 @@ +# Rust Kernel Architecture + +## Project Structure + +This Rust kernel project is organized as follows: + +``` +rust/ +├── README.md # Project overview and build instructions +├── Cargo.toml # Root workspace configuration +├── Makefile # Build automation +├── src/ +│ └── lib.rs # Top-level kernel library +├── kernel/ # Core kernel crate +│ ├── Cargo.toml +│ └── src/ +│ ├── lib.rs # Main kernel library +│ ├── prelude.rs # Common imports and macros +│ ├── error.rs # Error handling +│ ├── types.rs # Kernel data types +│ ├── memory/ # Memory management +│ ├── sync.rs # Synchronization primitives +│ ├── process.rs # Process management +│ ├── scheduler.rs # Task scheduling +│ ├── device.rs # Device management +│ ├── driver.rs # Driver framework +│ ├── interrupt.rs # Interrupt handling +│ ├── console.rs # Console/logging +│ ├── module.rs # Module system +│ ├── init.rs # Initialization +│ ├── panic.rs # Panic handler +│ └── arch/ # Architecture-specific code +├── modules/ # Loadable kernel modules +│ ├── Cargo.toml +│ └── src/ +│ ├── hello.rs # Hello world module +│ └── test.rs # Test module +├── drivers/ # Device drivers +│ ├── Cargo.toml +│ └── src/ +│ └── dummy.rs # Example dummy driver +└── test.sh # Build and test script +``` + +## Design Philosophy + +This kernel is designed with the following principles: + +1. **Memory Safety**: Leveraging Rust's ownership system to prevent memory bugs +2. **Zero-Cost Abstractions**: High-level APIs without runtime overhead +3. **Modularity**: Clean separation between subsystems +4. **Linux Compatibility**: Similar APIs and concepts where applicable +5. **Modern Design**: Taking advantage of modern language features + +## Key Components + +### Core Kernel (`kernel/`) + +- **Memory Management**: Page allocation, heap management, virtual memory +- **Process Management**: Process and thread abstractions +- **Synchronization**: Spinlocks, mutexes, and other primitives +- **Device Framework**: Generic device and driver abstractions +- **Module System**: Support for loadable kernel modules +- **Error Handling**: Comprehensive error types and Result-based APIs + +### Modules (`modules/`) + +Loadable kernel modules that can extend kernel functionality: +- `hello.rs`: Simple demonstration module +- `test.rs`: Testing and validation module + +### Drivers (`drivers/`) + +Device drivers implementing the kernel driver framework: +- `dummy.rs`: Example driver showing the driver API + +## Architecture Support + +Currently includes basic support for: +- x86_64 (primary target) +- ARM64 (stub) +- RISC-V (stub) + +## Building and Testing + +```bash +# Build everything +make + +# Run tests +make test + +# Check code quality +make clippy fmt-check + +# Run the automated test script +./test.sh +``` + +## Development Status + +This is a foundational implementation demonstrating: +- ✅ Basic kernel structure and organization +- ✅ Memory management framework +- ✅ Module system with examples +- ✅ Driver framework +- ✅ Synchronization primitives +- ✅ Error handling +- ✅ Build system integration + +Areas for future development: +- [ ] Complete memory management implementation +- [ ] Interrupt handling and timers +- [ ] Full scheduler implementation +- [ ] File system support +- [ ] Network stack +- [ ] Hardware abstraction layers +- [ ] Boot loader integration +- [ ] Performance optimization + +## Relationship to Linux Kernel + +This project is inspired by the Linux kernel's Rust infrastructure (`/linux/rust/`) but is designed as a standalone kernel implementation. It demonstrates how modern Rust can be used for systems programming while maintaining the familiar concepts from traditional kernel development. + +The module system and driver framework are designed to be conceptually similar to Linux while taking advantage of Rust's type system for additional safety guarantees. diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b4801c6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "rust-kernel" +version = "0.1.0" +edition = "2021" +authors = ["Rust Kernel Contributors"] +description = "A Rust-based kernel inspired by Linux" +license = "GPL-2.0" + +[workspace] +members = [ + "kernel", + "drivers", + "modules", + "arch", + "mm", + "fs", + "net", + "security" +] + +[workspace.dependencies] +kernel = { path = "kernel" } + +[lib] +name = "rust_kernel" +crate-type = ["staticlib", "cdylib"] + +[features] +default = ["alloc", "std"] +alloc = [] +std = [] +no_std = [] + +[profile.dev] +panic = "abort" +opt-level = 0 +debug = true + +[profile.release] +panic = "abort" +opt-level = "s" # Optimize for size +debug = false +lto = true +codegen-units = 1 + +[target.'cfg(target_arch = "x86_64")'] +[target.'cfg(target_arch = "aarch64")'] +[target.'cfg(target_arch = "riscv64")'] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e744b22 --- /dev/null +++ b/Makefile @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Rust Kernel Makefile +# Based on Linux kernel Rust infrastructure + +RUST_KERNEL_VERSION := 0.1.0 + +# Build configuration +ARCH ?= x86_64 +BUILD_TYPE ?= release + +# Cargo configuration +CARGO := cargo +CARGO_FLAGS := --target-dir target + +ifeq ($(BUILD_TYPE),debug) + CARGO_FLAGS += +else + CARGO_FLAGS += --release +endif + +# Kernel modules +RUST_MODULES := $(shell find modules -name "*.rs" -type f) +DRIVERS := $(shell find drivers -name "*.rs" -type f) + +# Default target +all: kernel modules drivers + +# Build the core kernel +kernel: + @echo "Building Rust kernel ($(ARCH), $(BUILD_TYPE))" + $(CARGO) build $(CARGO_FLAGS) + +# Build kernel modules +modules: $(RUST_MODULES) + @echo "Building kernel modules" + cd modules && $(CARGO) build $(CARGO_FLAGS) + +# Build drivers +drivers: $(DRIVERS) + @echo "Building drivers" + cd drivers && $(CARGO) build $(CARGO_FLAGS) + +# Clean build artifacts +clean: + $(CARGO) clean + rm -rf target/ + +# Run tests +test: + $(CARGO) test $(CARGO_FLAGS) + +# Check formatting +fmt-check: + $(CARGO) fmt --check + +# Format code +fmt: + $(CARGO) fmt + +# Run clippy +clippy: + $(CARGO) clippy $(CARGO_FLAGS) -- -D warnings + +# Generate documentation +doc: + $(CARGO) doc $(CARGO_FLAGS) --no-deps + +# Install (placeholder) +install: + @echo "Install target not implemented yet" + +.PHONY: all kernel modules drivers clean test fmt-check fmt clippy doc install diff --git a/README.md b/README.md new file mode 100644 index 0000000..4cb55a1 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Rust Kernel + +A Rust-based kernel inspired by the Linux kernel, utilizing the Rust for Linux infrastructure. + +## Overview + +This project aims to create a modern kernel implementation in Rust, leveraging memory safety and performance benefits while maintaining compatibility with Linux kernel concepts and APIs. + +## Architecture + +- **kernel/**: Core kernel functionality and APIs +- **drivers/**: Device drivers written in Rust +- **modules/**: Loadable kernel modules +- **arch/**: Architecture-specific code +- **mm/**: Memory management +- **fs/**: File system implementations +- **net/**: Network stack +- **security/**: Security subsystem + +## Building + +```bash +# Build the kernel +cargo build --release + +# Run tests +cargo test + +# Check code formatting +cargo fmt --check + +# Run clippy lints +cargo clippy -- -D warnings +``` + +## Features + +- Memory-safe kernel implementation +- Zero-cost abstractions +- Modern async/await support for I/O operations +- Modular architecture +- Linux-compatible APIs where possible + +## License + +This project is licensed under GPL-2.0, following the Linux kernel license. + +## Contributing + +Contributions are welcome! Please follow the Linux kernel coding style and Rust conventions. diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..f693171 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,21 @@ +# Clippy configuration for kernel development +warn = [ + "clippy::all", + "clippy::pedantic", + "clippy::nursery", + "clippy::cargo" +] + +allow = [ + "clippy::missing_errors_doc", + "clippy::missing_panics_doc", + "clippy::module_name_repetitions", + "clippy::inline_always", + "clippy::must_use_candidate", + "clippy::cast_possible_truncation", + "clippy::cast_sign_loss", + "clippy::cast_possible_wrap", + "clippy::similar_names", + "clippy::too_many_lines", + "clippy::unused_self" +] diff --git a/drivers/Cargo.toml b/drivers/Cargo.toml new file mode 100644 index 0000000..d2d9daa --- /dev/null +++ b/drivers/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "drivers" +version = "0.1.0" +edition = "2021" +authors = ["Rust Kernel Contributors"] +description = "Kernel drivers" +license = "GPL-2.0" + +[dependencies] +kernel = { path = "../kernel" } + +[[bin]] +name = "dummy_driver" +path = "src/dummy.rs" + +[[bin]] +name = "mem_devices" +path = "src/mem.rs" + +[[bin]] +name = "platform_example" +path = "src/platform_example.rs" + +[[bin]] +name = "ramdisk" +path = "src/ramdisk.rs" diff --git a/drivers/src/dummy.rs b/drivers/src/dummy.rs new file mode 100644 index 0000000..92ce10f --- /dev/null +++ b/drivers/src/dummy.rs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Dummy device driver + +#![no_std] +#![no_main] + +use kernel::prelude::*; +use kernel::driver::{Driver, DriverOps}; +use kernel::device::{Device, DeviceType}; + +struct DummyDriver { + name: &'static str, +} + +impl DummyDriver { + fn new() -> Self { + Self { + name: "dummy_driver", + } + } +} + +impl Driver for DummyDriver { + fn name(&self) -> &str { + self.name + } + + fn probe(&self, device: &mut Device) -> Result<()> { + info!("DummyDriver: probing device {}", device.name); + Ok(()) + } + + fn remove(&self, device: &mut Device) -> Result<()> { + info!("DummyDriver: removing device {}", device.name); + Ok(()) + } +} + +impl DriverOps for DummyDriver { + fn read(&self, offset: u64, buffer: &mut [u8]) -> Result { + info!("DummyDriver: read at offset {} for {} bytes", offset, buffer.len()); + // Fill buffer with dummy data + for (i, byte) in buffer.iter_mut().enumerate() { + *byte = (i % 256) as u8; + } + Ok(buffer.len()) + } + + fn write(&self, offset: u64, buffer: &[u8]) -> Result { + info!("DummyDriver: write at offset {} for {} bytes", offset, buffer.len()); + Ok(buffer.len()) + } + + fn ioctl(&self, cmd: u32, arg: usize) -> Result { + info!("DummyDriver: ioctl cmd={}, arg={}", cmd, arg); + Ok(0) + } +} + +struct DummyModule { + driver: DummyDriver, +} + +impl kernel::module::Module for DummyModule { + fn init(_module: &'static kernel::module::ThisModule) -> Result { + info!("Dummy driver module initializing..."); + + let driver = DummyDriver::new(); + + // Register the driver + kernel::driver::register_driver(Box::new(driver))?; + + info!("Dummy driver registered successfully"); + + Ok(DummyModule { + driver: DummyDriver::new(), + }) + } + + fn exit(_module: &'static kernel::module::ThisModule) { + info!("Dummy driver module exiting"); + // Unregister driver + kernel::driver::unregister_driver("dummy_driver").ok(); + } +} + +module! { + type: DummyModule, + name: "dummy_driver", + author: "Rust Kernel Contributors", + description: "A dummy device driver for testing", + license: "GPL-2.0", +} diff --git a/drivers/src/mem.rs b/drivers/src/mem.rs new file mode 100644 index 0000000..22eb4b4 --- /dev/null +++ b/drivers/src/mem.rs @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Null, zero, and full device drivers +//! Based on Linux drivers/char/mem.c + +#![no_std] +#![no_main] + +use kernel::prelude::*; +use kernel::device::{CharDevice, FileOperations, Inode, File, VMA}; +use kernel::driver::CharDriverOps; + +/// Null device driver (/dev/null) +struct NullDevice; + +impl FileOperations for NullDevice { + fn open(&self, _inode: &Inode, _file: &mut File) -> Result<()> { + Ok(()) + } + + fn release(&self, _inode: &Inode, _file: &mut File) -> Result<()> { + Ok(()) + } + + fn read(&self, _file: &mut File, _buf: &mut [u8], _offset: u64) -> Result { + // Reading from /dev/null always returns EOF + Ok(0) + } + + fn write(&self, _file: &mut File, buf: &[u8], _offset: u64) -> Result { + // Writing to /dev/null always succeeds and discards data + Ok(buf.len()) + } + + fn ioctl(&self, _file: &mut File, _cmd: u32, _arg: usize) -> Result { + Err(Error::NotSupported) + } + + fn mmap(&self, _file: &mut File, _vma: &mut VMA) -> Result<()> { + Err(Error::NotSupported) + } +} + +/// Zero device driver (/dev/zero) +struct ZeroDevice; + +impl FileOperations for ZeroDevice { + fn open(&self, _inode: &Inode, _file: &mut File) -> Result<()> { + Ok(()) + } + + fn release(&self, _inode: &Inode, _file: &mut File) -> Result<()> { + Ok(()) + } + + fn read(&self, _file: &mut File, buf: &mut [u8], _offset: u64) -> Result { + // Reading from /dev/zero returns zeros + for byte in buf.iter_mut() { + *byte = 0; + } + Ok(buf.len()) + } + + fn write(&self, _file: &mut File, buf: &[u8], _offset: u64) -> Result { + // Writing to /dev/zero always succeeds and discards data + Ok(buf.len()) + } + + fn ioctl(&self, _file: &mut File, _cmd: u32, _arg: usize) -> Result { + Err(Error::NotSupported) + } + + fn mmap(&self, _file: &mut File, vma: &mut VMA) -> Result<()> { + // /dev/zero can be mmap'd to get zero-filled pages + // TODO: implement proper mmap support + Ok(()) + } +} + +/// Full device driver (/dev/full) +struct FullDevice; + +impl FileOperations for FullDevice { + fn open(&self, _inode: &Inode, _file: &mut File) -> Result<()> { + Ok(()) + } + + fn release(&self, _inode: &Inode, _file: &mut File) -> Result<()> { + Ok(()) + } + + fn read(&self, _file: &mut File, buf: &mut [u8], _offset: u64) -> Result { + // Reading from /dev/full returns zeros + for byte in buf.iter_mut() { + *byte = 0; + } + Ok(buf.len()) + } + + fn write(&self, _file: &mut File, _buf: &[u8], _offset: u64) -> Result { + // Writing to /dev/full always fails with "no space left" + Err(Error::OutOfMemory) // ENOSPC equivalent + } + + fn ioctl(&self, _file: &mut File, _cmd: u32, _arg: usize) -> Result { + Err(Error::NotSupported) + } + + fn mmap(&self, _file: &mut File, _vma: &mut VMA) -> Result<()> { + Err(Error::NotSupported) + } +} + +/// Memory devices module +struct MemoryDevicesModule { + null_major: u32, + zero_major: u32, + full_major: u32, +} + +impl kernel::module::Module for MemoryDevicesModule { + fn init(_module: &'static kernel::module::ThisModule) -> Result { + info!("Memory devices module initializing..."); + + // Register /dev/null (major 1, minor 3) + let null_major = kernel::device::register_chrdev( + 1, + String::from("null"), + Box::new(NullDevice) + )?; + + // Register /dev/zero (major 1, minor 5) + let zero_major = kernel::device::register_chrdev( + 1, + String::from("zero"), + Box::new(ZeroDevice) + )?; + + // Register /dev/full (major 1, minor 7) + let full_major = kernel::device::register_chrdev( + 1, + String::from("full"), + Box::new(FullDevice) + )?; + + info!("Memory devices registered: null={}, zero={}, full={}", + null_major, zero_major, full_major); + + Ok(MemoryDevicesModule { + null_major, + zero_major, + full_major, + }) + } + + fn exit(_module: &'static kernel::module::ThisModule) { + info!("Memory devices module exiting"); + + // Unregister character devices + kernel::device::unregister_chrdev(1).ok(); + } +} + +module! { + type: MemoryDevicesModule, + name: "mem_devices", + author: "Rust Kernel Contributors", + description: "Memory devices (/dev/null, /dev/zero, /dev/full)", + license: "GPL-2.0", +} diff --git a/drivers/src/platform_example.rs b/drivers/src/platform_example.rs new file mode 100644 index 0000000..47dd337 --- /dev/null +++ b/drivers/src/platform_example.rs @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Platform device driver example + +#![no_std] +#![no_main] + +use kernel::prelude::*; +use kernel::driver::{PlatformDriver, Driver, DeviceId}; +use kernel::device::Device; + +/// Example platform driver +struct ExamplePlatformDriver { + name: &'static str, + device_ids: &'static [DeviceId], +} + +impl ExamplePlatformDriver { + fn new() -> Self { + Self { + name: "example_platform", + device_ids: &[ + DeviceId::new(String::from("example,platform-device")) + .with_compatible(vec![ + String::from("example,platform-device"), + String::from("generic,platform-device"), + ]), + ], + } + } +} + +impl Driver for ExamplePlatformDriver { + fn name(&self) -> &str { + self.name + } + + fn probe(&self, device: &mut Device) -> Result<()> { + info!("Platform driver probing device: {}", device.name()); + + // Initialize device-specific data + device.set_private_data(ExampleDeviceData { + initialized: true, + counter: 0, + }); + + info!("Platform device {} probed successfully", device.name()); + Ok(()) + } + + fn remove(&self, device: &mut Device) -> Result<()> { + info!("Platform driver removing device: {}", device.name()); + + if let Some(data) = device.get_private_data::() { + info!("Device had counter value: {}", data.counter); + } + + Ok(()) + } + + fn suspend(&self, device: &mut Device) -> Result<()> { + info!("Platform device {} suspending", device.name()); + Ok(()) + } + + fn resume(&self, device: &mut Device) -> Result<()> { + info!("Platform device {} resuming", device.name()); + Ok(()) + } + + fn shutdown(&self, device: &mut Device) { + info!("Platform device {} shutting down", device.name()); + } +} + +impl PlatformDriver for ExamplePlatformDriver { + fn match_device(&self, device: &Device) -> bool { + // Simple name-based matching + device.name().contains("example") || device.name().contains("platform") + } + + fn device_ids(&self) -> &[DeviceId] { + self.device_ids + } +} + +/// Device-specific private data +#[derive(Debug)] +struct ExampleDeviceData { + initialized: bool, + counter: u32, +} + +/// Platform driver module +struct PlatformDriverModule { + driver: ExamplePlatformDriver, +} + +impl kernel::module::Module for PlatformDriverModule { + fn init(_module: &'static kernel::module::ThisModule) -> Result { + info!("Platform driver module initializing..."); + + let driver = ExamplePlatformDriver::new(); + + // Register the platform driver + kernel::driver::register_platform_driver(Box::new(driver))?; + + info!("Platform driver registered successfully"); + + Ok(PlatformDriverModule { + driver: ExamplePlatformDriver::new(), + }) + } + + fn exit(_module: &'static kernel::module::ThisModule) { + info!("Platform driver module exiting"); + kernel::driver::unregister_driver("example_platform").ok(); + } +} + +module! { + type: PlatformDriverModule, + name: "example_platform_driver", + author: "Rust Kernel Contributors", + description: "Example platform device driver", + license: "GPL-2.0", +} diff --git a/drivers/src/ramdisk.rs b/drivers/src/ramdisk.rs new file mode 100644 index 0000000..84f75c6 --- /dev/null +++ b/drivers/src/ramdisk.rs @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! RAM disk block device driver +//! Based on Linux drivers/block/brd.c + +#![no_std] +#![no_main] + +use kernel::prelude::*; +use kernel::driver::{Driver, BlockDriverOps}; +use kernel::device::{Device, BlockDevice, DeviceType}; +use kernel::memory::{AllocFlags, GFP_KERNEL}; + +/// RAM disk device +struct RamDisk { + size: u64, // Size in bytes + block_size: u32, // Block size in bytes + data: Vec, // Actual storage +} + +impl RamDisk { + fn new(size: u64, block_size: u32) -> Result { + let mut data = Vec::new(); + data.try_reserve(size as usize)?; + data.resize(size as usize, 0); + + Ok(Self { + size, + block_size, + data, + }) + } + + fn get_block_count(&self) -> u64 { + self.size / self.block_size as u64 + } +} + +impl BlockDriverOps for RamDisk { + fn read_block(&self, block: u64, buffer: &mut [u8]) -> Result { + if block >= self.get_block_count() { + return Err(Error::InvalidArgument); + } + + let offset = (block * self.block_size as u64) as usize; + let size = core::cmp::min(buffer.len(), self.block_size as usize); + + if offset + size > self.data.len() { + return Err(Error::InvalidArgument); + } + + buffer[..size].copy_from_slice(&self.data[offset..offset + size]); + Ok(size) + } + + fn write_block(&self, block: u64, buffer: &[u8]) -> Result { + if block >= self.get_block_count() { + return Err(Error::InvalidArgument); + } + + let offset = (block * self.block_size as u64) as usize; + let size = core::cmp::min(buffer.len(), self.block_size as usize); + + if offset + size > self.data.len() { + return Err(Error::InvalidArgument); + } + + // This is a bit unsafe due to the immutable reference, but for simplicity... + // In a real implementation, we'd use proper interior mutability + unsafe { + let data_ptr = self.data.as_ptr() as *mut u8; + let dest = core::slice::from_raw_parts_mut(data_ptr.add(offset), size); + dest.copy_from_slice(&buffer[..size]); + } + + Ok(size) + } + + fn get_block_size(&self) -> u32 { + self.block_size + } + + fn get_total_blocks(&self) -> u64 { + self.get_block_count() + } + + fn flush(&self) -> Result<()> { + // RAM disk doesn't need flushing + Ok(()) + } +} + +/// RAM disk driver +struct RamDiskDriver { + name: &'static str, +} + +impl RamDiskDriver { + fn new() -> Self { + Self { + name: "ramdisk", + } + } +} + +impl Driver for RamDiskDriver { + fn name(&self) -> &str { + self.name + } + + fn probe(&self, device: &mut Device) -> Result<()> { + info!("RAM disk driver probing device: {}", device.name()); + + // Create a 16MB RAM disk with 4KB blocks + let ramdisk = RamDisk::new(16 * 1024 * 1024, 4096)?; + + info!("Created RAM disk: {} blocks of {} bytes each", + ramdisk.get_total_blocks(), ramdisk.get_block_size()); + + device.set_private_data(ramdisk); + + Ok(()) + } + + fn remove(&self, device: &mut Device) -> Result<()> { + info!("RAM disk driver removing device: {}", device.name()); + Ok(()) + } +} + +/// RAM disk module +struct RamDiskModule { + driver: RamDiskDriver, + device_created: bool, +} + +impl kernel::module::Module for RamDiskModule { + fn init(_module: &'static kernel::module::ThisModule) -> Result { + info!("RAM disk module initializing..."); + + let driver = RamDiskDriver::new(); + + // Register the driver + kernel::driver::register_driver(Box::new(driver))?; + + // Create a RAM disk device + let mut device = Device::new( + String::from("ram0"), + DeviceType::Block, + 1, // major number for RAM disk + 0 // minor number + ); + + // Set up the driver for this device + let ramdisk_driver = RamDiskDriver::new(); + device.set_driver(Box::new(ramdisk_driver))?; + + // Register the device + kernel::device::register_device(device)?; + + info!("RAM disk device created and registered"); + + Ok(RamDiskModule { + driver: RamDiskDriver::new(), + device_created: true, + }) + } + + fn exit(_module: &'static kernel::module::ThisModule) { + info!("RAM disk module exiting"); + + if self.device_created { + kernel::device::unregister_device("ram0").ok(); + } + + kernel::driver::unregister_driver("ramdisk").ok(); + } +} + +module! { + type: RamDiskModule, + name: "ramdisk", + author: "Rust Kernel Contributors", + description: "RAM disk block device driver", + license: "GPL-2.0", +} diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml new file mode 100644 index 0000000..2e4600c --- /dev/null +++ b/kernel/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "kernel" +version = "0.1.0" +edition = "2021" +authors = ["Rust Kernel Contributors"] +description = "Core kernel APIs and functionality" +license = "GPL-2.0" + +[lib] +name = "kernel" +crate-type = ["rlib"] + +[dependencies] +spin = "0.9" +bitflags = "2.4" +linked_list_allocator = "0.10" +once_cell = { version = "1.19", default-features = false, features = ["critical-section"] } + +[features] +default = [] +alloc = [] +smp = [] # Symmetric Multi-Processing +debug = [] diff --git a/kernel/src/arch/mod.rs b/kernel/src/arch/mod.rs new file mode 100644 index 0000000..085a6d4 --- /dev/null +++ b/kernel/src/arch/mod.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Architecture-specific code + +#[cfg(target_arch = "x86_64")] +pub mod x86_64; + +#[cfg(target_arch = "aarch64")] +pub mod aarch64; + +#[cfg(target_arch = "riscv64")] +pub mod riscv64; diff --git a/kernel/src/arch/x86_64/gdt.rs b/kernel/src/arch/x86_64/gdt.rs new file mode 100644 index 0000000..7d2cb75 --- /dev/null +++ b/kernel/src/arch/x86_64/gdt.rs @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! GDT stub +pub fn init() {} diff --git a/kernel/src/arch/x86_64/idt.rs b/kernel/src/arch/x86_64/idt.rs new file mode 100644 index 0000000..b537b54 --- /dev/null +++ b/kernel/src/arch/x86_64/idt.rs @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! IDT stub +pub fn init() {} diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs new file mode 100644 index 0000000..3af06ae --- /dev/null +++ b/kernel/src/arch/x86_64/mod.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! x86_64 architecture support + +pub mod port; +pub mod gdt; +pub mod idt; +pub mod paging; +pub mod pic; diff --git a/kernel/src/arch/x86_64/paging.rs b/kernel/src/arch/x86_64/paging.rs new file mode 100644 index 0000000..e9a4c5e --- /dev/null +++ b/kernel/src/arch/x86_64/paging.rs @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Paging stub +pub fn init() {} diff --git a/kernel/src/arch/x86_64/pic.rs b/kernel/src/arch/x86_64/pic.rs new file mode 100644 index 0000000..ef74da6 --- /dev/null +++ b/kernel/src/arch/x86_64/pic.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Programmable Interrupt Controller (8259 PIC) support + +use crate::arch::x86_64::port::Port; + +/// Primary PIC ports +const PIC1_COMMAND: u16 = 0x20; +const PIC1_DATA: u16 = 0x21; + +/// Secondary PIC ports +const PIC2_COMMAND: u16 = 0xA0; +const PIC2_DATA: u16 = 0xA1; + +/// PIC commands +const PIC_EOI: u8 = 0x20; // End of Interrupt + +/// Initialize the PIC +pub unsafe fn init_pic() { + let mut pic1_command = Port::new(PIC1_COMMAND); + let mut pic1_data = Port::new(PIC1_DATA); + let mut pic2_command = Port::new(PIC2_COMMAND); + let mut pic2_data = Port::new(PIC2_DATA); + + // Save masks + let mask1 = pic1_data.read() as u8; + let mask2 = pic2_data.read() as u8; + + // Initialize PIC1 + pic1_command.write(0x11); // ICW1: Initialize + expect ICW4 + io_wait(); + pic1_data.write(0x20); // ICW2: PIC1 offset (32) + io_wait(); + pic1_data.write(0x04); // ICW3: Tell PIC1 there's a PIC2 at IRQ2 + io_wait(); + pic1_data.write(0x01); // ICW4: 8086 mode + io_wait(); + + // Initialize PIC2 + pic2_command.write(0x11); // ICW1: Initialize + expect ICW4 + io_wait(); + pic2_data.write(0x28); // ICW2: PIC2 offset (40) + io_wait(); + pic2_data.write(0x02); // ICW3: Tell PIC2 it's at IRQ2 of PIC1 + io_wait(); + pic2_data.write(0x01); // ICW4: 8086 mode + io_wait(); + + // Restore masks + pic1_data.write(mask1 as u32); + pic2_data.write(mask2 as u32); +} + +/// Send End of Interrupt signal +pub unsafe fn send_eoi(irq: u8) { + let mut pic1_command = Port::new(PIC1_COMMAND); + let mut pic2_command = Port::new(PIC2_COMMAND); + + if irq >= 8 { + pic2_command.write(PIC_EOI as u32); + } + pic1_command.write(PIC_EOI as u32); +} + +/// Mask (disable) an IRQ +pub unsafe fn mask_irq(irq: u8) { + let port = if irq < 8 { + PIC1_DATA + } else { + PIC2_DATA + }; + + let mut data_port = Port::new(port); + let value = data_port.read() as u8; + let mask = 1 << (irq % 8); + data_port.write((value | mask) as u32); +} + +/// Unmask (enable) an IRQ +pub unsafe fn unmask_irq(irq: u8) { + let port = if irq < 8 { + PIC1_DATA + } else { + PIC2_DATA + }; + + let mut data_port = Port::new(port); + let value = data_port.read() as u8; + let mask = 1 << (irq % 8); + data_port.write((value & !mask) as u32); +} + +/// I/O wait - small delay for old hardware +unsafe fn io_wait() { + let mut port = Port::new(0x80); + port.write(0); +} diff --git a/kernel/src/arch/x86_64/port.rs b/kernel/src/arch/x86_64/port.rs new file mode 100644 index 0000000..e65bbea --- /dev/null +++ b/kernel/src/arch/x86_64/port.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Port I/O operations + +/// Port I/O wrapper +pub struct Port { + port: u16, +} + +impl Port { + pub const fn new(port: u16) -> Self { + Self { port } + } + + pub unsafe fn write(&mut self, value: u32) { + core::arch::asm!( + "out dx, eax", + in("dx") self.port, + in("eax") value, + ); + } + + pub unsafe fn read(&mut self) -> u32 { + let value: u32; + core::arch::asm!( + "in eax, dx", + out("eax") value, + in("dx") self.port, + ); + value + } +} diff --git a/kernel/src/boot.rs b/kernel/src/boot.rs new file mode 100644 index 0000000..340b3ad --- /dev/null +++ b/kernel/src/boot.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Boot initialization +use crate::error::Result; + +pub fn init() -> Result<()> { + Ok(()) +} diff --git a/kernel/src/console.rs b/kernel/src/console.rs new file mode 100644 index 0000000..73b9896 --- /dev/null +++ b/kernel/src/console.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Console and kernel output + +use core::fmt::{self, Write}; +use crate::sync::Spinlock; +use crate::error::Result; + +/// Console writer +static CONSOLE: Spinlock = Spinlock::new(Console::new()); + +struct Console { + initialized: bool, +} + +impl Console { + const fn new() -> Self { + Self { + initialized: false, + } + } + + fn init(&mut self) -> Result<()> { + // TODO: Initialize actual console hardware + self.initialized = true; + Ok(()) + } + + fn write_str(&self, s: &str) { + if !self.initialized { + return; + } + + for byte in s.bytes() { + self.write_byte(byte); + } + } + + fn write_byte(&self, byte: u8) { + #[cfg(target_arch = "x86_64")] + unsafe { + // Write to serial port (COM1) + core::arch::asm!( + "out dx, al", + in("dx") 0x3f8u16, + in("al") byte, + ); + } + } +} + +/// Initialize console +pub fn init() -> Result<()> { + let mut console = CONSOLE.lock(); + console.init() +} + +/// Print function for kernel output +pub fn _print(args: fmt::Arguments) { + let console = CONSOLE.lock(); + let mut writer = ConsoleWriter(&*console); + writer.write_fmt(args).unwrap(); +} + +/// Print function for kernel messages with prefix +pub fn _kprint(args: fmt::Arguments) { + let console = CONSOLE.lock(); + let mut writer = ConsoleWriter(&*console); + writer.write_fmt(args).unwrap(); +} + +struct ConsoleWriter<'a>(&'a Console); + +impl Write for ConsoleWriter<'_> { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.0.write_str(s); + Ok(()) + } +} diff --git a/kernel/src/cpu.rs b/kernel/src/cpu.rs new file mode 100644 index 0000000..9651528 --- /dev/null +++ b/kernel/src/cpu.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! CPU management +use crate::error::Result; + +pub fn init() -> Result<()> { + Ok(()) +} diff --git a/kernel/src/device.rs b/kernel/src/device.rs new file mode 100644 index 0000000..ebc7732 --- /dev/null +++ b/kernel/src/device.rs @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Device management compatible with Linux kernel + +use crate::error::{Error, Result}; +use crate::driver::Driver; +use crate::sync::Spinlock; +use alloc::{vec::Vec, string::String, collections::BTreeMap, boxed::Box}; +use core::any::Any; + +/// Device types - Linux compatible +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DeviceType { + Character, + Block, + Network, + Input, + Sound, + Video, + Misc, + Platform, + Pci, + Usb, +} + +/// Device structure - similar to Linux struct device +#[derive(Debug)] +pub struct Device { + pub name: String, + pub device_type: DeviceType, + pub major: u32, + pub minor: u32, + pub driver: Option>, + pub parent: Option, + pub private_data: Option>, + pub power_state: PowerState, + pub dma_coherent: bool, + pub numa_node: i32, +} + +/// Device power states +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PowerState { + On, + Suspend, + Hibernate, + Off, +} + +impl Device { + pub fn new(name: String, device_type: DeviceType, major: u32, minor: u32) -> Self { + Self { + name, + device_type, + major, + minor, + driver: None, + parent: None, + private_data: None, + power_state: PowerState::On, + dma_coherent: false, + numa_node: -1, + } + } + + /// Set device driver + pub fn set_driver(&mut self, driver: Box) -> Result<()> { + // Probe the device with the driver + driver.probe(self)?; + self.driver = Some(driver); + Ok(()) + } + + /// Remove device driver + pub fn remove_driver(&mut self) -> Result<()> { + if let Some(ref driver) = self.driver { + driver.remove(self)?; + } + self.driver = None; + Ok(()) + } + + /// Get device name + pub fn name(&self) -> &str { + &self.name + } + + /// Check if device has driver + pub fn has_driver(&self) -> bool { + self.driver.is_some() + } + + /// Set private data + pub fn set_private_data(&mut self, data: T) { + self.private_data = Some(Box::new(data)); + } + + /// Get private data + pub fn get_private_data(&self) -> Option<&T> { + self.private_data.as_ref()?.downcast_ref::() + } + + /// Power management + pub fn suspend(&mut self) -> Result<()> { + if let Some(ref driver) = self.driver { + driver.suspend(self)?; + } + self.power_state = PowerState::Suspend; + Ok(()) + } + + pub fn resume(&mut self) -> Result<()> { + if let Some(ref driver) = self.driver { + driver.resume(self)?; + } + self.power_state = PowerState::On; + Ok(()) + } +} + +/// Character device structure - Linux compatible +#[derive(Debug)] +pub struct CharDevice { + pub major: u32, + pub minor_start: u32, + pub minor_count: u32, + pub name: String, + pub fops: Option>, +} + +impl CharDevice { + pub fn new(major: u32, minor_start: u32, minor_count: u32, name: String) -> Self { + Self { + major, + minor_start, + minor_count, + name, + fops: None, + } + } +} + +/// Block device structure - Linux compatible +#[derive(Debug)] +pub struct BlockDevice { + pub major: u32, + pub minor: u32, + pub name: String, + pub size: u64, // Size in bytes + pub block_size: u32, +} + +impl BlockDevice { + pub fn new(major: u32, minor: u32, name: String, size: u64, block_size: u32) -> Self { + Self { + major, + minor, + name, + size, + block_size, + } + } +} + +/// File operations structure - Linux compatible +pub trait FileOperations: Send + Sync { + fn open(&self, inode: &Inode, file: &mut File) -> Result<()>; + fn release(&self, inode: &Inode, file: &mut File) -> Result<()>; + fn read(&self, file: &mut File, buf: &mut [u8], offset: u64) -> Result; + fn write(&self, file: &mut File, buf: &[u8], offset: u64) -> Result; + fn ioctl(&self, file: &mut File, cmd: u32, arg: usize) -> Result; + fn mmap(&self, file: &mut File, vma: &mut VMA) -> Result<()>; +} + +/// Inode structure - simplified Linux compatible +#[derive(Debug)] +pub struct Inode { + pub ino: u64, + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub size: u64, + pub blocks: u64, + pub rdev: u32, // Device number for device files +} + +/// File structure - simplified Linux compatible +#[derive(Debug)] +pub struct File { + pub inode: Option>, + pub position: u64, + pub flags: u32, + pub private_data: Option>, +} + +impl File { + pub fn new() -> Self { + Self { + inode: None, + position: 0, + flags: 0, + private_data: None, + } + } +} + +/// Virtual Memory Area - simplified +#[derive(Debug)] +pub struct VMA { + pub start: u64, + pub end: u64, + pub flags: u32, +} + +/// Global device subsystem +static DEVICE_SUBSYSTEM: Spinlock = Spinlock::new(DeviceSubsystem::new()); + +/// Device subsystem state +struct DeviceSubsystem { + devices: BTreeMap, + char_devices: BTreeMap, // major -> CharDevice + block_devices: BTreeMap, // major -> BlockDevice + next_major: u32, +} + +impl DeviceSubsystem { + const fn new() -> Self { + Self { + devices: BTreeMap::new(), + char_devices: BTreeMap::new(), + block_devices: BTreeMap::new(), + next_major: 240, // Start with dynamic major numbers + } + } + + fn register_device(&mut self, device: Device) -> Result<()> { + let name = device.name.clone(); + if self.devices.contains_key(&name) { + return Err(Error::Busy); + } + self.devices.insert(name, device); + Ok(()) + } + + fn unregister_device(&mut self, name: &str) -> Result { + self.devices.remove(name).ok_or(Error::NotFound) + } + + fn find_device(&self, name: &str) -> Option<&Device> { + self.devices.get(name) + } + + fn find_device_mut(&mut self, name: &str) -> Option<&mut Device> { + self.devices.get_mut(name) + } + + fn allocate_major(&mut self) -> u32 { + let major = self.next_major; + self.next_major += 1; + major + } +} + +/// Initialize device subsystem +pub fn init() -> Result<()> { + let mut subsystem = DEVICE_SUBSYSTEM.lock(); + + // Register standard character devices + register_std_char_devices(&mut subsystem)?; + + // Register standard block devices + register_std_block_devices(&mut subsystem)?; + + crate::info!("Device subsystem initialized"); + Ok(()) +} + +/// Register standard character devices +fn register_std_char_devices(subsystem: &mut DeviceSubsystem) -> Result<()> { + // /dev/null (major 1, minor 3) + let null_dev = CharDevice::new(1, 3, 1, String::from("null")); + subsystem.char_devices.insert(1, null_dev); + + // /dev/zero (major 1, minor 5) + let zero_dev = CharDevice::new(1, 5, 1, String::from("zero")); + // Note: This would overwrite the previous entry, so we need a better structure + // For now, simplified + + // /dev/random (major 1, minor 8) + let random_dev = CharDevice::new(1, 8, 1, String::from("random")); + + // /dev/urandom (major 1, minor 9) + let urandom_dev = CharDevice::new(1, 9, 1, String::from("urandom")); + + Ok(()) +} + +/// Register standard block devices +fn register_std_block_devices(subsystem: &mut DeviceSubsystem) -> Result<()> { + // RAM disk (major 1) + let ramdisk = BlockDevice::new(1, 0, String::from("ram0"), 16 * 1024 * 1024, 4096); + subsystem.block_devices.insert(1, ramdisk); + + Ok(()) +} + +/// Register a device +pub fn register_device(device: Device) -> Result<()> { + let mut subsystem = DEVICE_SUBSYSTEM.lock(); + subsystem.register_device(device) +} + +/// Unregister a device +pub fn unregister_device(name: &str) -> Result { + let mut subsystem = DEVICE_SUBSYSTEM.lock(); + subsystem.unregister_device(name) +} + +/// Find a device by name +pub fn find_device(name: &str) -> Option { + let subsystem = DEVICE_SUBSYSTEM.lock(); + subsystem.find_device(name).cloned() +} + +/// Register a character device +pub fn register_chrdev(major: u32, name: String, fops: Box) -> Result { + let mut subsystem = DEVICE_SUBSYSTEM.lock(); + + let actual_major = if major == 0 { + subsystem.allocate_major() + } else { + major + }; + + let mut char_dev = CharDevice::new(actual_major, 0, 256, name); + char_dev.fops = Some(fops); + + if subsystem.char_devices.contains_key(&actual_major) { + return Err(Error::Busy); + } + + subsystem.char_devices.insert(actual_major, char_dev); + Ok(actual_major) +} + +/// Unregister a character device +pub fn unregister_chrdev(major: u32) -> Result<()> { + let mut subsystem = DEVICE_SUBSYSTEM.lock(); + + if subsystem.char_devices.remove(&major).is_some() { + Ok(()) + } else { + Err(Error::NotFound) + } +} + +/// List all devices +pub fn list_devices() -> Vec { + let subsystem = DEVICE_SUBSYSTEM.lock(); + subsystem.devices.keys().cloned().collect() +} + +/// Device tree node - for device tree support +#[derive(Debug)] +pub struct DeviceTreeNode { + pub name: String, + pub compatible: Vec, + pub reg: Vec, + pub interrupts: Vec, + pub properties: BTreeMap>, +} + +impl DeviceTreeNode { + pub fn new(name: String) -> Self { + Self { + name, + compatible: Vec::new(), + reg: Vec::new(), + interrupts: Vec::new(), + properties: BTreeMap::new(), + } + } +} diff --git a/kernel/src/driver.rs b/kernel/src/driver.rs new file mode 100644 index 0000000..26da8aa --- /dev/null +++ b/kernel/src/driver.rs @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Driver framework compatible with Linux kernel + +use crate::error::{Error, Result}; +use crate::device::Device; +use crate::sync::Spinlock; +use alloc::{vec::Vec, string::String, collections::BTreeMap, boxed::Box}; + +/// Driver trait - Linux compatible +pub trait Driver: Send + Sync { + /// Driver name + fn name(&self) -> &str; + + /// Probe function - called when device is found + fn probe(&self, device: &mut Device) -> Result<()>; + + /// Remove function - called when device is removed + fn remove(&self, device: &mut Device) -> Result<()>; + + /// Suspend function - power management + fn suspend(&self, device: &mut Device) -> Result<()> { + // Default implementation does nothing + Ok(()) + } + + /// Resume function - power management + fn resume(&self, device: &mut Device) -> Result<()> { + // Default implementation does nothing + Ok(()) + } + + /// Shutdown function - system shutdown + fn shutdown(&self, device: &mut Device) { + // Default implementation does nothing + } +} + +/// Driver operations for character devices +pub trait CharDriverOps: Send + Sync { + fn open(&self, inode: &crate::device::Inode, file: &mut crate::device::File) -> Result<()>; + fn release(&self, inode: &crate::device::Inode, file: &mut crate::device::File) -> Result<()>; + fn read(&self, file: &mut crate::device::File, buf: &mut [u8], offset: u64) -> Result; + fn write(&self, file: &mut crate::device::File, buf: &[u8], offset: u64) -> Result; + fn ioctl(&self, file: &mut crate::device::File, cmd: u32, arg: usize) -> Result; +} + +/// Driver operations for block devices +pub trait BlockDriverOps: Send + Sync { + fn read_block(&self, block: u64, buffer: &mut [u8]) -> Result; + fn write_block(&self, block: u64, buffer: &[u8]) -> Result; + fn get_block_size(&self) -> u32; + fn get_total_blocks(&self) -> u64; + fn flush(&self) -> Result<()>; +} + +/// Platform driver - for platform devices +pub trait PlatformDriver: Driver { + /// Match function - check if driver supports device + fn match_device(&self, device: &Device) -> bool; + + /// Get supported device IDs + fn device_ids(&self) -> &[DeviceId]; +} + +/// Device ID structure - for driver matching +#[derive(Debug, Clone)] +pub struct DeviceId { + pub name: String, + pub vendor_id: Option, + pub device_id: Option, + pub class: Option, + pub compatible: Vec, // Device tree compatible strings +} + +impl DeviceId { + pub fn new(name: String) -> Self { + Self { + name, + vendor_id: None, + device_id: None, + class: None, + compatible: Vec::new(), + } + } + + pub fn with_vendor_device(mut self, vendor_id: u32, device_id: u32) -> Self { + self.vendor_id = Some(vendor_id); + self.device_id = Some(device_id); + self + } + + pub fn with_compatible(mut self, compatible: Vec) -> Self { + self.compatible = compatible; + self + } +} + +/// PCI driver - for PCI devices +pub trait PciDriver: Driver { + /// PCI device IDs supported by this driver + fn pci_ids(&self) -> &[PciDeviceId]; + + /// PCI-specific probe + fn pci_probe(&self, pci_dev: &mut PciDevice) -> Result<()>; + + /// PCI-specific remove + fn pci_remove(&self, pci_dev: &mut PciDevice) -> Result<()>; +} + +/// PCI device ID +#[derive(Debug, Clone, Copy)] +pub struct PciDeviceId { + pub vendor: u16, + pub device: u16, + pub subvendor: u16, + pub subdevice: u16, + pub class: u32, + pub class_mask: u32, +} + +impl PciDeviceId { + pub const fn new(vendor: u16, device: u16) -> Self { + Self { + vendor, + device, + subvendor: 0xFFFF, // PCI_ANY_ID + subdevice: 0xFFFF, + class: 0, + class_mask: 0, + } + } +} + +/// PCI device structure +#[derive(Debug)] +pub struct PciDevice { + pub vendor: u16, + pub device: u16, + pub subsystem_vendor: u16, + pub subsystem_device: u16, + pub class: u32, + pub revision: u8, + pub bus: u8, + pub slot: u8, + pub function: u8, + pub irq: u32, + pub bars: [PciBar; 6], +} + +/// PCI Base Address Register +#[derive(Debug, Clone, Copy)] +pub struct PciBar { + pub address: u64, + pub size: u64, + pub flags: u32, +} + +impl PciBar { + pub fn new() -> Self { + Self { + address: 0, + size: 0, + flags: 0, + } + } + + pub fn is_io(&self) -> bool { + self.flags & 1 != 0 + } + + pub fn is_memory(&self) -> bool { + !self.is_io() + } + + pub fn is_64bit(&self) -> bool { + self.is_memory() && (self.flags & 0x6) == 0x4 + } +} + +/// USB driver - for USB devices +pub trait UsbDriver: Driver { + /// USB device IDs supported by this driver + fn usb_ids(&self) -> &[UsbDeviceId]; + + /// USB-specific probe + fn usb_probe(&self, usb_dev: &mut UsbDevice) -> Result<()>; + + /// USB-specific disconnect + fn usb_disconnect(&self, usb_dev: &mut UsbDevice) -> Result<()>; +} + +/// USB device ID +#[derive(Debug, Clone, Copy)] +pub struct UsbDeviceId { + pub vendor: u16, + pub product: u16, + pub device_class: u8, + pub device_subclass: u8, + pub device_protocol: u8, + pub interface_class: u8, + pub interface_subclass: u8, + pub interface_protocol: u8, +} + +/// USB device structure +#[derive(Debug)] +pub struct UsbDevice { + pub vendor: u16, + pub product: u16, + pub device_class: u8, + pub device_subclass: u8, + pub device_protocol: u8, + pub speed: UsbSpeed, + pub address: u8, + pub configuration: u8, +} + +/// USB speeds +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UsbSpeed { + Low, // 1.5 Mbps + Full, // 12 Mbps + High, // 480 Mbps + Super, // 5 Gbps + SuperPlus, // 10 Gbps +} + +/// Global driver subsystem +static DRIVER_SUBSYSTEM: Spinlock = Spinlock::new(DriverSubsystem::new()); + +/// Driver subsystem state +struct DriverSubsystem { + drivers: BTreeMap>, + platform_drivers: Vec>, + pci_drivers: Vec>, + usb_drivers: Vec>, +} + +impl DriverSubsystem { + const fn new() -> Self { + Self { + drivers: BTreeMap::new(), + platform_drivers: Vec::new(), + pci_drivers: Vec::new(), + usb_drivers: Vec::new(), + } + } + + fn register_driver(&mut self, driver: Box) -> Result<()> { + let name = driver.name().to_string(); + if self.drivers.contains_key(&name) { + return Err(Error::Busy); + } + self.drivers.insert(name, driver); + Ok(()) + } + + fn unregister_driver(&mut self, name: &str) -> Result<()> { + if self.drivers.remove(name).is_some() { + Ok(()) + } else { + Err(Error::NotFound) + } + } + + fn find_driver(&self, name: &str) -> Option<&dyn Driver> { + self.drivers.get(name).map(|d| d.as_ref()) + } +} + +/// Register a driver +pub fn register_driver(driver: Box) -> Result<()> { + let mut subsystem = DRIVER_SUBSYSTEM.lock(); + let name = driver.name().to_string(); + subsystem.register_driver(driver)?; + crate::info!("Registered driver: {}", name); + Ok(()) +} + +/// Unregister a driver +pub fn unregister_driver(name: &str) -> Result<()> { + let mut subsystem = DRIVER_SUBSYSTEM.lock(); + subsystem.unregister_driver(name)?; + crate::info!("Unregistered driver: {}", name); + Ok(()) +} + +/// Register a platform driver +pub fn register_platform_driver(driver: Box) -> Result<()> { + let mut subsystem = DRIVER_SUBSYSTEM.lock(); + let name = driver.name().to_string(); + + // Also register as a regular driver + let driver_copy = unsafe { + // This is a bit of a hack - we need to clone the driver + // In a real implementation, we'd use Arc or similar + core::mem::transmute::<*const dyn PlatformDriver, Box>( + driver.as_ref() as *const dyn PlatformDriver + ) + }; + subsystem.register_driver(driver_copy)?; + subsystem.platform_drivers.push(driver); + + crate::info!("Registered platform driver: {}", name); + Ok(()) +} + +/// Register a PCI driver +pub fn register_pci_driver(driver: Box) -> Result<()> { + let mut subsystem = DRIVER_SUBSYSTEM.lock(); + let name = driver.name().to_string(); + subsystem.pci_drivers.push(driver); + crate::info!("Registered PCI driver: {}", name); + Ok(()) +} + +/// Register a USB driver +pub fn register_usb_driver(driver: Box) -> Result<()> { + let mut subsystem = DRIVER_SUBSYSTEM.lock(); + let name = driver.name().to_string(); + subsystem.usb_drivers.push(driver); + crate::info!("Registered USB driver: {}", name); + Ok(()) +} + +/// Find and match a driver for a device +pub fn match_driver(device: &Device) -> Option { + let subsystem = DRIVER_SUBSYSTEM.lock(); + + // Try platform drivers first + for driver in &subsystem.platform_drivers { + if driver.match_device(device) { + return Some(driver.name().to_string()); + } + } + + // TODO: Try PCI, USB, etc. drivers based on device type + + None +} + +/// Get list of registered drivers +pub fn list_drivers() -> Vec { + let subsystem = DRIVER_SUBSYSTEM.lock(); + subsystem.drivers.keys().cloned().collect() +} + +/// Module macros for easier driver registration +#[macro_export] +macro_rules! platform_driver { + ($driver:ident) => { + #[no_mangle] + pub extern "C" fn init_module() -> core::ffi::c_int { + match $crate::driver::register_platform_driver(Box::new($driver)) { + Ok(()) => 0, + Err(e) => e.to_errno(), + } + } + + #[no_mangle] + pub extern "C" fn cleanup_module() { + $crate::driver::unregister_driver(stringify!($driver)).ok(); + } + }; +} + +#[macro_export] +macro_rules! pci_driver { + ($driver:ident) => { + #[no_mangle] + pub extern "C" fn init_module() -> core::ffi::c_int { + match $crate::driver::register_pci_driver(Box::new($driver)) { + Ok(()) => 0, + Err(e) => e.to_errno(), + } + } + + #[no_mangle] + pub extern "C" fn cleanup_module() { + $crate::driver::unregister_driver(stringify!($driver)).ok(); + } + }; +} diff --git a/kernel/src/error.rs b/kernel/src/error.rs new file mode 100644 index 0000000..7d354bc --- /dev/null +++ b/kernel/src/error.rs @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Error handling types and utilities + +use core::fmt; + +/// Kernel error type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + /// Out of memory + OutOfMemory, + /// Invalid argument + InvalidArgument, + /// Permission denied + PermissionDenied, + /// Resource busy + Busy, + /// Resource not found + NotFound, + /// Operation not supported + NotSupported, + /// I/O error + Io, + /// Interrupted operation + Interrupted, + /// Resource temporarily unavailable + WouldBlock, + /// Device error + Device, + /// Generic error + Generic, +} + +impl Error { + /// Convert error to errno value + pub fn to_errno(self) -> i32 { + match self { + Error::OutOfMemory => -12, // ENOMEM + Error::InvalidArgument => -22, // EINVAL + Error::PermissionDenied => -1, // EPERM + Error::Busy => -16, // EBUSY + Error::NotFound => -2, // ENOENT + Error::NotSupported => -38, // ENOSYS + Error::Io => -5, // EIO + Error::Interrupted => -4, // EINTR + Error::WouldBlock => -11, // EAGAIN + Error::Device => -19, // ENODEV + Error::Generic => -1, // EPERM + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::OutOfMemory => write!(f, "Out of memory"), + Error::InvalidArgument => write!(f, "Invalid argument"), + Error::PermissionDenied => write!(f, "Permission denied"), + Error::Busy => write!(f, "Resource busy"), + Error::NotFound => write!(f, "Resource not found"), + Error::NotSupported => write!(f, "Operation not supported"), + Error::Io => write!(f, "I/O error"), + Error::Interrupted => write!(f, "Interrupted operation"), + Error::WouldBlock => write!(f, "Resource temporarily unavailable"), + Error::Device => write!(f, "Device error"), + Error::Generic => write!(f, "Generic error"), + } + } +} + +/// Kernel result type +pub type Result = core::result::Result; + +/// Convert from various error types +impl From<()> for Error { + fn from(_: ()) -> Self { + Error::Generic + } +} + +impl From for Error { + fn from(_: core::alloc::AllocError) -> Self { + Error::OutOfMemory + } +} diff --git a/kernel/src/fs/dentry.rs b/kernel/src/fs/dentry.rs new file mode 100644 index 0000000..fdcee6d --- /dev/null +++ b/kernel/src/fs/dentry.rs @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Directory entry (dentry) abstraction - Linux compatible + +use crate::error::{Error, Result}; +use crate::sync::{Arc, Mutex, RwLock}; +use alloc::string::String; +use alloc::vec::Vec; +use core::sync::atomic::{AtomicU32, Ordering}; + +/// Dentry structure - similar to Linux struct dentry +pub struct Dentry { + /// Entry name + pub d_name: String, + /// Associated inode + pub d_inode: Option>, + /// Parent dentry + pub d_parent: Option>, + /// Child entries (for directories) + pub d_subdirs: Mutex>>, + /// Dentry operations + pub d_op: Option>, + /// Superblock + pub d_sb: Option>, + /// Reference count + pub d_count: AtomicU32, + /// Dentry flags + pub d_flags: AtomicU32, + /// Hash for dcache + pub d_hash: u32, +} + +impl Dentry { + /// Create a new dentry + pub fn new(name: String, inode: Option>) -> Self { + Self { + d_name: name, + d_inode: inode, + d_parent: None, + d_subdirs: Mutex::new(Vec::new()), + d_op: None, + d_sb: None, + d_count: AtomicU32::new(1), + d_flags: AtomicU32::new(0), + d_hash: 0, // TODO: Calculate hash + } + } + + /// Set parent dentry + pub fn set_parent(&mut self, parent: Arc) { + self.d_parent = Some(parent); + } + + /// Add child dentry + pub fn add_child(&self, child: Arc) { + let mut subdirs = self.d_subdirs.lock(); + subdirs.push(child); + } + + /// Find child dentry by name + pub fn find_child(&self, name: &str) -> Option> { + let subdirs = self.d_subdirs.lock(); + for child in subdirs.iter() { + if child.d_name == name { + return Some(child.clone()); + } + } + None + } + + /// Get full path of this dentry + pub fn get_path(&self) -> String { + if let Some(ref parent) = self.d_parent { + if parent.d_name == "/" { + format!("/{}", self.d_name) + } else { + format!("{}/{}", parent.get_path(), self.d_name) + } + } else { + self.d_name.clone() + } + } + + /// Check if dentry is root + pub fn is_root(&self) -> bool { + self.d_parent.is_none() || self.d_name == "/" + } + + /// Increment reference count + pub fn dget(&self) { + self.d_count.fetch_add(1, Ordering::Relaxed); + } + + /// Decrement reference count + pub fn dput(&self) { + let old_count = self.d_count.fetch_sub(1, Ordering::Relaxed); + if old_count == 1 { + // Last reference, dentry should be cleaned up + // TODO: Call d_delete operation if present + } + } + + /// Revalidate dentry + pub fn revalidate(&self) -> Result { + if let Some(ref ops) = self.d_op { + ops.revalidate(self) + } else { + Ok(true) // Always valid by default + } + } + + /// Delete dentry + pub fn delete(&self) -> Result<()> { + if let Some(ref ops) = self.d_op { + ops.delete(self) + } else { + Ok(()) + } + } + + /// Compare two dentries + pub fn compare(&self, other: &Dentry) -> Result { + if let Some(ref ops) = self.d_op { + ops.compare(self, other) + } else { + Ok(self.d_name == other.d_name) + } + } + + /// Hash dentry name + pub fn hash(&self) -> Result { + if let Some(ref ops) = self.d_op { + ops.hash(self) + } else { + // Simple hash function + let mut hash = 0u32; + for byte in self.d_name.bytes() { + hash = hash.wrapping_mul(31).wrapping_add(byte as u32); + } + Ok(hash) + } + } +} + +unsafe impl Send for Dentry {} +unsafe impl Sync for Dentry {} + +/// Dentry operations trait - similar to Linux dentry_operations +pub trait DentryOperations: Send + Sync { + /// Revalidate dentry + fn revalidate(&self, dentry: &Dentry) -> Result; + + /// Hash dentry name + fn hash(&self, dentry: &Dentry) -> Result; + + /// Compare two dentries + fn compare(&self, d1: &Dentry, d2: &Dentry) -> Result; + + /// Delete dentry + fn delete(&self, dentry: &Dentry) -> Result<()>; + + /// Release dentry + fn release(&self, dentry: &Dentry) -> Result<()>; + + /// Canonicalize path + fn canonical_path(&self, dentry: &Dentry) -> Result; +} + +/// Generic dentry operations +pub struct GenericDentryOps; + +impl DentryOperations for GenericDentryOps { + fn revalidate(&self, dentry: &Dentry) -> Result { + Ok(true) + } + + fn hash(&self, dentry: &Dentry) -> Result { + dentry.hash() + } + + fn compare(&self, d1: &Dentry, d2: &Dentry) -> Result { + Ok(d1.d_name == d2.d_name) + } + + fn delete(&self, dentry: &Dentry) -> Result<()> { + Ok(()) + } + + fn release(&self, dentry: &Dentry) -> Result<()> { + Ok(()) + } + + fn canonical_path(&self, dentry: &Dentry) -> Result { + Ok(dentry.get_path()) + } +} + +/// Dentry cache (dcache) - simplified version +pub struct DentryCache { + /// Cached dentries + cache: Mutex>>, + /// Hash buckets for faster lookup + hash_table: Vec>>>, +} + +impl DentryCache { + /// Create a new dentry cache + pub fn new() -> Self { + const HASH_BUCKETS: usize = 256; + let mut hash_table = Vec::with_capacity(HASH_BUCKETS); + for _ in 0..HASH_BUCKETS { + hash_table.push(Mutex::new(Vec::new())); + } + + Self { + cache: Mutex::new(alloc::collections::BTreeMap::new()), + hash_table, + } + } + + /// Look up dentry by path + pub fn lookup(&self, path: &str) -> Option> { + let cache = self.cache.lock(); + cache.get(path).cloned() + } + + /// Insert dentry into cache + pub fn insert(&self, path: String, dentry: Arc) { + let mut cache = self.cache.lock(); + cache.insert(path, dentry.clone()); + + // Also insert into hash table + let hash = dentry.d_hash as usize % self.hash_table.len(); + let mut bucket = self.hash_table[hash].lock(); + bucket.push(dentry); + } + + /// Remove dentry from cache + pub fn remove(&self, path: &str) -> Option> { + let mut cache = self.cache.lock(); + cache.remove(path) + } + + /// Prune unused dentries + pub fn prune(&self) { + let mut cache = self.cache.lock(); + cache.retain(|_, dentry| { + dentry.d_count.load(Ordering::Relaxed) > 1 + }); + + // Also prune hash table + for bucket in &self.hash_table { + let mut bucket = bucket.lock(); + bucket.retain(|dentry| { + dentry.d_count.load(Ordering::Relaxed) > 1 + }); + } + } +} + +/// Global dentry cache +static DCACHE: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| DentryCache::new()); + +/// Look up dentry in cache +pub fn dcache_lookup(path: &str) -> Option> { + DCACHE.lookup(path) +} + +/// Insert dentry into cache +pub fn dcache_insert(path: String, dentry: Arc) { + DCACHE.insert(path, dentry); +} + +/// Remove dentry from cache +pub fn dcache_remove(path: &str) -> Option> { + DCACHE.remove(path) +} + +/// Prune dentry cache +pub fn dcache_prune() { + DCACHE.prune(); +} diff --git a/kernel/src/fs/devfs.rs b/kernel/src/fs/devfs.rs new file mode 100644 index 0000000..47c5c70 --- /dev/null +++ b/kernel/src/fs/devfs.rs @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Character device filesystem integration +//! +//! This driver provides VFS integration for character devices +//! like /dev/null, /dev/zero, /dev/random, etc. + +use crate::error::{Error, Result}; +use crate::fs::*; +use crate::memory::UserSlicePtr; +use crate::sync::Arc; +use alloc::string::String; + +/// Character device file operations +pub struct CharDevFileOps { + /// Device operations + dev_ops: Option>, +} + +impl CharDevFileOps { + pub fn new(dev_ops: Option>) -> Self { + Self { dev_ops } + } +} + +impl FileOperations for CharDevFileOps { + fn read(&self, file: &File, buf: UserSlicePtr, count: usize) -> Result { + if let Some(ref ops) = self.dev_ops { + ops.read(file, buf, count) + } else { + Err(Error::ENODEV) + } + } + + fn write(&self, file: &File, buf: UserSlicePtr, count: usize) -> Result { + if let Some(ref ops) = self.dev_ops { + ops.write(file, buf, count) + } else { + Err(Error::ENODEV) + } + } + + fn seek(&self, file: &File, offset: i64, whence: i32) -> Result { + // Most character devices don't support seeking + Err(Error::ESPIPE) + } + + fn ioctl(&self, file: &File, cmd: u32, arg: usize) -> Result { + if let Some(ref ops) = self.dev_ops { + ops.ioctl(file, cmd, arg) + } else { + Err(Error::ENOTTY) + } + } + + fn mmap(&self, file: &File, vma: &mut crate::memory::VmaArea) -> Result<()> { + if let Some(ref ops) = self.dev_ops { + ops.mmap(file, vma) + } else { + Err(Error::ENODEV) + } + } + + fn fsync(&self, file: &File, datasync: bool) -> Result<()> { + // Character devices don't need syncing + Ok(()) + } + + fn poll(&self, file: &File, wait: &mut PollWait) -> Result { + if let Some(ref ops) = self.dev_ops { + ops.poll(file, wait) + } else { + Ok(POLLIN | POLLOUT) + } + } + + fn open(&self, inode: &Inode, file: &File) -> Result<()> { + if let Some(ref ops) = self.dev_ops { + ops.open(inode, file) + } else { + Ok(()) + } + } + + fn release(&self, inode: &Inode, file: &File) -> Result<()> { + if let Some(ref ops) = self.dev_ops { + ops.release(inode, file) + } else { + Ok(()) + } + } +} + +/// Character device operations trait +pub trait CharDevOperations: Send + Sync { + fn read(&self, file: &File, buf: UserSlicePtr, count: usize) -> Result; + fn write(&self, file: &File, buf: UserSlicePtr, count: usize) -> Result; + fn ioctl(&self, file: &File, cmd: u32, arg: usize) -> Result; + fn mmap(&self, file: &File, vma: &mut crate::memory::VmaArea) -> Result<()>; + fn poll(&self, file: &File, wait: &mut PollWait) -> Result; + fn open(&self, inode: &Inode, file: &File) -> Result<()>; + fn release(&self, inode: &Inode, file: &File) -> Result<()>; +} + +/// /dev/null device operations +pub struct NullDevOps; + +impl CharDevOperations for NullDevOps { + fn read(&self, _file: &File, _buf: UserSlicePtr, _count: usize) -> Result { + Ok(0) // EOF + } + + fn write(&self, _file: &File, _buf: UserSlicePtr, count: usize) -> Result { + Ok(count as isize) // Discard all data + } + + fn ioctl(&self, _file: &File, _cmd: u32, _arg: usize) -> Result { + Err(Error::ENOTTY) + } + + fn mmap(&self, _file: &File, _vma: &mut crate::memory::VmaArea) -> Result<()> { + Err(Error::ENODEV) + } + + fn poll(&self, _file: &File, _wait: &mut PollWait) -> Result { + Ok(POLLIN | POLLOUT) // Always ready + } + + fn open(&self, _inode: &Inode, _file: &File) -> Result<()> { + Ok(()) + } + + fn release(&self, _inode: &Inode, _file: &File) -> Result<()> { + Ok(()) + } +} + +/// /dev/zero device operations +pub struct ZeroDevOps; + +impl CharDevOperations for ZeroDevOps { + fn read(&self, _file: &File, buf: UserSlicePtr, count: usize) -> Result { + // Fill buffer with zeros + let zeros = vec![0u8; count]; + buf.copy_from_slice(&zeros)?; + Ok(count as isize) + } + + fn write(&self, _file: &File, _buf: UserSlicePtr, count: usize) -> Result { + Ok(count as isize) // Discard all data + } + + fn ioctl(&self, _file: &File, _cmd: u32, _arg: usize) -> Result { + Err(Error::ENOTTY) + } + + fn mmap(&self, _file: &File, _vma: &mut crate::memory::VmaArea) -> Result<()> { + // TODO: Map zero page + Err(Error::ENODEV) + } + + fn poll(&self, _file: &File, _wait: &mut PollWait) -> Result { + Ok(POLLIN | POLLOUT) // Always ready + } + + fn open(&self, _inode: &Inode, _file: &File) -> Result<()> { + Ok(()) + } + + fn release(&self, _inode: &Inode, _file: &File) -> Result<()> { + Ok(()) + } +} + +/// /dev/full device operations +pub struct FullDevOps; + +impl CharDevOperations for FullDevOps { + fn read(&self, _file: &File, buf: UserSlicePtr, count: usize) -> Result { + // Fill buffer with zeros (like /dev/zero) + let zeros = vec![0u8; count]; + buf.copy_from_slice(&zeros)?; + Ok(count as isize) + } + + fn write(&self, _file: &File, _buf: UserSlicePtr, _count: usize) -> Result { + Err(Error::ENOSPC) // No space left on device + } + + fn ioctl(&self, _file: &File, _cmd: u32, _arg: usize) -> Result { + Err(Error::ENOTTY) + } + + fn mmap(&self, _file: &File, _vma: &mut crate::memory::VmaArea) -> Result<()> { + Err(Error::ENODEV) + } + + fn poll(&self, _file: &File, _wait: &mut PollWait) -> Result { + Ok(POLLIN) // Only readable + } + + fn open(&self, _inode: &Inode, _file: &File) -> Result<()> { + Ok(()) + } + + fn release(&self, _inode: &Inode, _file: &File) -> Result<()> { + Ok(()) + } +} + +/// /dev/random device operations (simplified) +pub struct RandomDevOps; + +impl CharDevOperations for RandomDevOps { + fn read(&self, _file: &File, buf: UserSlicePtr, count: usize) -> Result { + // Generate pseudo-random data + // TODO: Use proper random number generator + let mut random_data = vec![0u8; count]; + for i in 0..count { + random_data[i] = (i * 37 + 13) as u8; // Very simple PRNG + } + buf.copy_from_slice(&random_data)?; + Ok(count as isize) + } + + fn write(&self, _file: &File, _buf: UserSlicePtr, count: usize) -> Result { + // TODO: Add entropy to random pool + Ok(count as isize) + } + + fn ioctl(&self, _file: &File, _cmd: u32, _arg: usize) -> Result { + // TODO: Implement random device ioctls + Err(Error::ENOTTY) + } + + fn mmap(&self, _file: &File, _vma: &mut crate::memory::VmaArea) -> Result<()> { + Err(Error::ENODEV) + } + + fn poll(&self, _file: &File, _wait: &mut PollWait) -> Result { + Ok(POLLIN | POLLOUT) // Always ready + } + + fn open(&self, _inode: &Inode, _file: &File) -> Result<()> { + Ok(()) + } + + fn release(&self, _inode: &Inode, _file: &File) -> Result<()> { + Ok(()) + } +} + +/// Create a character device inode +pub fn create_char_device_inode( + major: u32, + minor: u32, + mode: u32, + ops: Arc, +) -> Arc { + let mut inode = Inode::new(0, mode::S_IFCHR | mode); + inode.i_rdev = crate::device::DeviceNumber::new(major, minor); + inode.set_file_operations(Arc::new(CharDevFileOps::new(Some(ops)))); + + Arc::new(inode) +} + +/// DevFS - device filesystem for /dev +pub struct DevFs { + /// Device entries + devices: crate::sync::Mutex>>, +} + +impl DevFs { + pub fn new() -> Self { + let mut devfs = Self { + devices: crate::sync::Mutex::new(alloc::collections::BTreeMap::new()), + }; + + devfs.create_standard_devices(); + devfs + } + + fn create_standard_devices(&mut self) { + // Create /dev/null + let null_inode = create_char_device_inode(1, 3, 0o666, Arc::new(NullDevOps)); + self.add_device("null", null_inode); + + // Create /dev/zero + let zero_inode = create_char_device_inode(1, 5, 0o666, Arc::new(ZeroDevOps)); + self.add_device("zero", zero_inode); + + // Create /dev/full + let full_inode = create_char_device_inode(1, 7, 0o666, Arc::new(FullDevOps)); + self.add_device("full", full_inode); + + // Create /dev/random + let random_inode = create_char_device_inode(1, 8, 0o666, Arc::new(RandomDevOps)); + self.add_device("random", random_inode); + + // Create /dev/urandom (same as random for now) + let urandom_inode = create_char_device_inode(1, 9, 0o666, Arc::new(RandomDevOps)); + self.add_device("urandom", urandom_inode); + } + + pub fn add_device(&mut self, name: &str, inode: Arc) { + let mut devices = self.devices.lock(); + devices.insert(String::from(name), inode); + } + + pub fn get_device(&self, name: &str) -> Option> { + let devices = self.devices.lock(); + devices.get(name).cloned() + } + + pub fn list_devices(&self) -> alloc::vec::Vec { + let devices = self.devices.lock(); + devices.keys().cloned().collect() + } +} + +/// DevFS inode operations +pub struct DevFsInodeOps { + devfs: *const DevFs, +} + +impl DevFsInodeOps { + pub fn new(devfs: &DevFs) -> Self { + Self { + devfs: devfs as *const DevFs, + } + } + + fn get_devfs(&self) -> &DevFs { + unsafe { &*self.devfs } + } +} + +unsafe impl Send for DevFsInodeOps {} +unsafe impl Sync for DevFsInodeOps {} + +impl InodeOperations for DevFsInodeOps { + fn lookup(&self, dir: &Inode, name: &str) -> Result> { + let devfs = self.get_devfs(); + devfs.get_device(name).ok_or(Error::ENOENT) + } + + fn create(&self, dir: &Inode, name: &str, mode: u32) -> Result> { + Err(Error::EPERM) // Can't create devices in /dev directly + } + + fn mkdir(&self, dir: &Inode, name: &str, mode: u32) -> Result> { + Err(Error::EPERM) + } + + fn unlink(&self, dir: &Inode, name: &str) -> Result<()> { + Err(Error::EPERM) + } + + fn rmdir(&self, dir: &Inode, name: &str) -> Result<()> { + Err(Error::EPERM) + } + + fn symlink(&self, dir: &Inode, name: &str, target: &str) -> Result> { + Err(Error::EPERM) + } + + fn rename(&self, old_dir: &Inode, old_name: &str, new_dir: &Inode, new_name: &str) -> Result<()> { + Err(Error::EPERM) + } + + fn setattr(&self, inode: &Inode, attr: &InodeAttr) -> Result<()> { + Err(Error::EPERM) + } + + fn getattr(&self, inode: &Inode) -> Result { + let generic_ops = GenericInodeOps; + generic_ops.getattr(inode) + } + + fn readlink(&self, inode: &Inode) -> Result { + Err(Error::EINVAL) + } + + fn follow_link(&self, inode: &Inode) -> Result> { + Err(Error::EINVAL) + } + + fn truncate(&self, inode: &Inode, size: u64) -> Result<()> { + Err(Error::EPERM) + } + + fn getxattr(&self, inode: &Inode, name: &str) -> Result> { + Err(Error::ENODATA) + } + + fn setxattr(&self, inode: &Inode, name: &str, value: &[u8], flags: u32) -> Result<()> { + Err(Error::EPERM) + } + + fn listxattr(&self, inode: &Inode) -> Result> { + Ok(alloc::vec::Vec::new()) + } + + fn removexattr(&self, inode: &Inode, name: &str) -> Result<()> { + Err(Error::EPERM) + } +} + +/// Mount devfs +pub fn mount_devfs(_dev_name: &str, _flags: u32, _data: Option<&str>) -> Result> { + let mut sb = SuperBlock::new("devfs")?; + sb.s_magic = 0x1373; // DEVFS magic + + let devfs = Box::leak(Box::new(DevFs::new())); + sb.s_fs_info = Some(devfs as *mut DevFs as *mut u8); + + // Create root inode + let root_inode = Arc::new({ + let mut inode = Inode::new(1, mode::S_IFDIR | 0o755); + inode.set_operations(Arc::new(DevFsInodeOps::new(devfs))); + inode + }); + + let root_dentry = Arc::new(Dentry::new(String::from("/"), Some(root_inode))); + sb.s_root = Some(root_dentry); + + Ok(Arc::new(sb)) +} + +/// Register devfs filesystem +pub fn register_devfs() -> Result<()> { + let devfs_type = FileSystemType::new( + String::from("devfs"), + |_fstype, flags, _dev_name, data| mount_devfs(_dev_name, flags, data), + |_sb| Ok(()), + ); + + // TODO: Register with VFS + crate::console::print_info("Registered devfs filesystem\n"); + Ok(()) +} diff --git a/kernel/src/fs/file.rs b/kernel/src/fs/file.rs new file mode 100644 index 0000000..c1263fa --- /dev/null +++ b/kernel/src/fs/file.rs @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! File abstraction - Linux compatible + +use crate::error::{Error, Result}; +use crate::types::*; +use crate::memory::{UserPtr, UserSlicePtr}; +use crate::sync::{Arc, Mutex, RwLock}; +use alloc::string::String; +use core::sync::atomic::{AtomicI64, AtomicU32, Ordering}; + +/// File structure - similar to Linux struct file +pub struct File { + /// File operations + pub f_op: Option>, + /// Current file position + pub pos: AtomicI64, + /// File flags + pub flags: AtomicU32, + /// File mode + pub mode: u32, + /// Associated inode + pub inode: Option>, + /// Associated dentry + pub dentry: Option>, + /// Private data for file operations + pub private_data: Option<*mut u8>, + /// File path (for debugging/proc) + pub path: String, + /// Reference count + pub refcount: AtomicU32, +} + +impl File { + /// Create a new file + pub fn new(path: &str, flags: u32, mode: u32) -> Result { + Ok(Self { + f_op: None, + pos: AtomicI64::new(0), + flags: AtomicU32::new(flags), + mode, + inode: None, + dentry: None, + private_data: None, + path: String::from(path), + refcount: AtomicU32::new(1), + }) + } + + /// Set file operations + pub fn set_operations(&mut self, ops: Arc) { + self.f_op = Some(ops); + } + + /// Read from file + pub fn read(&self, buf: UserSlicePtr, count: usize) -> Result { + if let Some(ref ops) = self.f_op { + ops.read(self, buf, count) + } else { + Err(Error::ENOSYS) + } + } + + /// Write to file + pub fn write(&self, buf: UserSlicePtr, count: usize) -> Result { + if let Some(ref ops) = self.f_op { + ops.write(self, buf, count) + } else { + Err(Error::ENOSYS) + } + } + + /// Seek within file + pub fn seek(&self, offset: i64, whence: i32) -> Result { + if let Some(ref ops) = self.f_op { + let new_pos = ops.seek(self, offset, whence)?; + self.pos.store(new_pos, Ordering::Relaxed); + Ok(new_pos) + } else { + Err(Error::ENOSYS) + } + } + + /// Get file status + pub fn stat(&self) -> Result { + if let Some(ref inode) = self.inode { + inode.stat() + } else { + // Create a basic stat structure + Ok(super::KStat { + st_dev: 0, + st_ino: 0, + st_nlink: 1, + st_mode: self.mode, + st_uid: 0, + st_gid: 0, + st_rdev: 0, + st_size: 0, + st_blksize: 4096, + st_blocks: 0, + st_atime: 0, + st_atime_nsec: 0, + st_mtime: 0, + st_mtime_nsec: 0, + st_ctime: 0, + st_ctime_nsec: 0, + }) + } + } + + /// Perform ioctl operation + pub fn ioctl(&self, cmd: u32, arg: usize) -> Result { + if let Some(ref ops) = self.f_op { + ops.ioctl(self, cmd, arg) + } else { + Err(Error::ENOTTY) + } + } + + /// Memory map file + pub fn mmap(&self, vma: &mut crate::memory::VmaArea) -> Result<()> { + if let Some(ref ops) = self.f_op { + ops.mmap(self, vma) + } else { + Err(Error::ENODEV) + } + } + + /// Sync file to storage + pub fn fsync(&self, datasync: bool) -> Result<()> { + if let Some(ref ops) = self.f_op { + ops.fsync(self, datasync) + } else { + Ok(()) // No-op for files without sync + } + } + + /// Poll file for events + pub fn poll(&self, wait: &mut super::PollWait) -> Result { + if let Some(ref ops) = self.f_op { + ops.poll(self, wait) + } else { + Ok(super::POLLIN | super::POLLOUT) // Always ready + } + } + + /// Get current file position + pub fn get_pos(&self) -> i64 { + self.pos.load(Ordering::Relaxed) + } + + /// Set file position + pub fn set_pos(&self, pos: i64) { + self.pos.store(pos, Ordering::Relaxed); + } + + /// Get file flags + pub fn get_flags(&self) -> u32 { + self.flags.load(Ordering::Relaxed) + } + + /// Check if file is readable + pub fn is_readable(&self) -> bool { + let flags = self.get_flags(); + (flags & super::flags::O_ACCMODE) != super::flags::O_WRONLY + } + + /// Check if file is writable + pub fn is_writable(&self) -> bool { + let flags = self.get_flags(); + (flags & super::flags::O_ACCMODE) != super::flags::O_RDONLY + } + + /// Increment reference count + pub fn get_file(&self) -> Result<()> { + self.refcount.fetch_add(1, Ordering::Relaxed); + Ok(()) + } + + /// Decrement reference count (fput equivalent) + pub fn put_file(&self) -> Result<()> { + let old_count = self.refcount.fetch_sub(1, Ordering::Relaxed); + if old_count == 1 { + // Last reference, file should be cleaned up + // TODO: Call release operation if present + } + Ok(()) + } +} + +unsafe impl Send for File {} +unsafe impl Sync for File {} + +/// File operations trait - similar to Linux file_operations +pub trait FileOperations: Send + Sync { + /// Read from file + fn read(&self, file: &File, buf: UserSlicePtr, count: usize) -> Result; + + /// Write to file + fn write(&self, file: &File, buf: UserSlicePtr, count: usize) -> Result; + + /// Seek within file + fn seek(&self, file: &File, offset: i64, whence: i32) -> Result; + + /// I/O control + fn ioctl(&self, file: &File, cmd: u32, arg: usize) -> Result; + + /// Memory map + fn mmap(&self, file: &File, vma: &mut crate::memory::VmaArea) -> Result<()>; + + /// Sync file + fn fsync(&self, file: &File, datasync: bool) -> Result<()>; + + /// Poll for events + fn poll(&self, file: &File, wait: &mut super::PollWait) -> Result; + + /// Open file (optional) + fn open(&self, inode: &super::Inode, file: &File) -> Result<()> { + Ok(()) + } + + /// Release file (optional) + fn release(&self, inode: &super::Inode, file: &File) -> Result<()> { + Ok(()) + } + + /// Flush file (optional) + fn flush(&self, file: &File) -> Result<()> { + Ok(()) + } + + /// Lock file (optional) + fn lock(&self, file: &File, cmd: i32) -> Result<()> { + Err(Error::ENOSYS) + } + + /// Read directory entries (optional) + fn readdir(&self, file: &File, ctx: &mut super::DirContext) -> Result<()> { + Err(Error::ENOTDIR) + } +} + +/// Directory context for readdir operations +pub struct DirContext { + /// Current position in directory + pub pos: i64, + /// Entries collected so far + pub entries: alloc::vec::Vec, +} + +impl DirContext { + pub fn new(pos: i64) -> Self { + Self { + pos, + entries: alloc::vec::Vec::new(), + } + } + + /// Add a directory entry + pub fn add_entry(&mut self, ino: u64, name: &str, d_type: u8) { + let entry = super::DirEntry { + ino, + off: self.pos, + reclen: (core::mem::size_of::() + name.len() + 1) as u16, + name: String::from(name), + d_type, + }; + self.entries.push(entry); + self.pos += 1; + } +} diff --git a/kernel/src/fs/inode.rs b/kernel/src/fs/inode.rs new file mode 100644 index 0000000..3278b03 --- /dev/null +++ b/kernel/src/fs/inode.rs @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Inode abstraction - Linux compatible + +use crate::error::{Error, Result}; +use crate::types::*; +use crate::sync::{Arc, Mutex, RwLock}; +use crate::device::DeviceNumber; +use crate::time::{TimeSpec, get_current_time}; +use alloc::string::String; +use core::sync::atomic::{AtomicU32, AtomicU64, Ordering}; + +/// Inode structure - similar to Linux struct inode +pub struct Inode { + /// Inode number + pub i_ino: u64, + /// File mode and type + pub i_mode: AtomicU32, + /// Number of hard links + pub i_nlink: AtomicU32, + /// User ID + pub i_uid: AtomicU32, + /// Group ID + pub i_gid: AtomicU32, + /// Device number (for device files) + pub i_rdev: DeviceNumber, + /// File size + pub i_size: AtomicU64, + /// Block size + pub i_blksize: u32, + /// Number of blocks + pub i_blocks: AtomicU64, + /// Access time + pub i_atime: Mutex, + /// Modification time + pub i_mtime: Mutex, + /// Status change time + pub i_ctime: Mutex, + /// Inode operations + pub i_op: Option>, + /// File operations (for regular files) + pub i_fop: Option>, + /// Superblock this inode belongs to + pub i_sb: Option>, + /// Private data + pub private_data: Option<*mut u8>, + /// Reference count + pub refcount: AtomicU32, + /// Inode flags + pub i_flags: AtomicU32, +} + +impl Inode { + /// Create a new inode + pub fn new(ino: u64, mode: u32) -> Self { + let now = get_current_time(); + Self { + i_ino: ino, + i_mode: AtomicU32::new(mode), + i_nlink: AtomicU32::new(1), + i_uid: AtomicU32::new(0), + i_gid: AtomicU32::new(0), + i_rdev: DeviceNumber::new(0, 0), + i_size: AtomicU64::new(0), + i_blksize: 4096, + i_blocks: AtomicU64::new(0), + i_atime: Mutex::new(now), + i_mtime: Mutex::new(now), + i_ctime: Mutex::new(now), + i_op: None, + i_fop: None, + i_sb: None, + private_data: None, + refcount: AtomicU32::new(1), + i_flags: AtomicU32::new(0), + } + } + + /// Set inode operations + pub fn set_operations(&mut self, ops: Arc) { + self.i_op = Some(ops); + } + + /// Set file operations + pub fn set_file_operations(&mut self, ops: Arc) { + self.i_fop = Some(ops); + } + + /// Get file statistics + pub fn stat(&self) -> Result { + let atime = self.i_atime.lock(); + let mtime = self.i_mtime.lock(); + let ctime = self.i_ctime.lock(); + + Ok(super::KStat { + st_dev: if let Some(ref sb) = self.i_sb { + sb.s_dev.as_raw() + } else { + 0 + }, + st_ino: self.i_ino, + st_nlink: self.i_nlink.load(Ordering::Relaxed) as u64, + st_mode: self.i_mode.load(Ordering::Relaxed), + st_uid: self.i_uid.load(Ordering::Relaxed), + st_gid: self.i_gid.load(Ordering::Relaxed), + st_rdev: self.i_rdev.as_raw(), + st_size: self.i_size.load(Ordering::Relaxed) as i64, + st_blksize: self.i_blksize as u64, + st_blocks: self.i_blocks.load(Ordering::Relaxed), + st_atime: atime.sec, + st_atime_nsec: atime.nsec, + st_mtime: mtime.sec, + st_mtime_nsec: mtime.nsec, + st_ctime: ctime.sec, + st_ctime_nsec: ctime.nsec, + }) + } + + /// Check if inode is a regular file + pub fn is_regular(&self) -> bool { + let mode = self.i_mode.load(Ordering::Relaxed); + super::mode::s_isreg(mode) + } + + /// Check if inode is a directory + pub fn is_directory(&self) -> bool { + let mode = self.i_mode.load(Ordering::Relaxed); + super::mode::s_isdir(mode) + } + + /// Check if inode is a character device + pub fn is_char_device(&self) -> bool { + let mode = self.i_mode.load(Ordering::Relaxed); + super::mode::s_ischr(mode) + } + + /// Check if inode is a block device + pub fn is_block_device(&self) -> bool { + let mode = self.i_mode.load(Ordering::Relaxed); + super::mode::s_isblk(mode) + } + + /// Update access time + pub fn update_atime(&self) { + let mut atime = self.i_atime.lock(); + *atime = get_current_time(); + } + + /// Update modification time + pub fn update_mtime(&self) { + let mut mtime = self.i_mtime.lock(); + *mtime = get_current_time(); + } + + /// Update status change time + pub fn update_ctime(&self) { + let mut ctime = self.i_ctime.lock(); + *ctime = get_current_time(); + } + + /// Set file size + pub fn set_size(&self, size: u64) { + self.i_size.store(size, Ordering::Relaxed); + self.update_mtime(); + self.update_ctime(); + } + + /// Get file size + pub fn get_size(&self) -> u64 { + self.i_size.load(Ordering::Relaxed) + } + + /// Increment reference count + pub fn iget(&self) { + self.refcount.fetch_add(1, Ordering::Relaxed); + } + + /// Decrement reference count + pub fn iput(&self) { + let old_count = self.refcount.fetch_sub(1, Ordering::Relaxed); + if old_count == 1 { + // Last reference, inode should be cleaned up + // TODO: Call destroy_inode operation if present + } + } + + /// Create a new file in this directory + pub fn create(&self, name: &str, mode: u32) -> Result> { + if let Some(ref ops) = self.i_op { + ops.create(self, name, mode) + } else { + Err(Error::ENOSYS) + } + } + + /// Look up a file in this directory + pub fn lookup(&self, name: &str) -> Result> { + if let Some(ref ops) = self.i_op { + ops.lookup(self, name) + } else { + Err(Error::ENOSYS) + } + } + + /// Create a directory + pub fn mkdir(&self, name: &str, mode: u32) -> Result> { + if let Some(ref ops) = self.i_op { + ops.mkdir(self, name, mode) + } else { + Err(Error::ENOSYS) + } + } + + /// Remove a file + pub fn unlink(&self, name: &str) -> Result<()> { + if let Some(ref ops) = self.i_op { + ops.unlink(self, name) + } else { + Err(Error::ENOSYS) + } + } + + /// Remove a directory + pub fn rmdir(&self, name: &str) -> Result<()> { + if let Some(ref ops) = self.i_op { + ops.rmdir(self, name) + } else { + Err(Error::ENOSYS) + } + } +} + +unsafe impl Send for Inode {} +unsafe impl Sync for Inode {} + +/// Inode operations trait - similar to Linux inode_operations +pub trait InodeOperations: Send + Sync { + /// Look up a file in directory + fn lookup(&self, dir: &Inode, name: &str) -> Result>; + + /// Create a new file + fn create(&self, dir: &Inode, name: &str, mode: u32) -> Result>; + + /// Create a directory + fn mkdir(&self, dir: &Inode, name: &str, mode: u32) -> Result>; + + /// Remove a file + fn unlink(&self, dir: &Inode, name: &str) -> Result<()>; + + /// Remove a directory + fn rmdir(&self, dir: &Inode, name: &str) -> Result<()>; + + /// Create a symbolic link + fn symlink(&self, dir: &Inode, name: &str, target: &str) -> Result>; + + /// Rename a file + fn rename(&self, old_dir: &Inode, old_name: &str, new_dir: &Inode, new_name: &str) -> Result<()>; + + /// Set attributes + fn setattr(&self, inode: &Inode, attr: &InodeAttr) -> Result<()>; + + /// Get attributes + fn getattr(&self, inode: &Inode) -> Result; + + /// Read symbolic link + fn readlink(&self, inode: &Inode) -> Result; + + /// Follow symbolic link + fn follow_link(&self, inode: &Inode) -> Result>; + + /// Truncate file + fn truncate(&self, inode: &Inode, size: u64) -> Result<()>; + + /// Get extended attribute + fn getxattr(&self, inode: &Inode, name: &str) -> Result>; + + /// Set extended attribute + fn setxattr(&self, inode: &Inode, name: &str, value: &[u8], flags: u32) -> Result<()>; + + /// List extended attributes + fn listxattr(&self, inode: &Inode) -> Result>; + + /// Remove extended attribute + fn removexattr(&self, inode: &Inode, name: &str) -> Result<()>; +} + +/// Inode attributes structure +#[derive(Debug, Clone, Copy)] +pub struct InodeAttr { + pub mode: Option, + pub uid: Option, + pub gid: Option, + pub size: Option, + pub atime: Option, + pub mtime: Option, + pub ctime: Option, +} + +impl InodeAttr { + pub fn new() -> Self { + Self { + mode: None, + uid: None, + gid: None, + size: None, + atime: None, + mtime: None, + ctime: None, + } + } + + pub fn with_mode(mut self, mode: u32) -> Self { + self.mode = Some(mode); + self + } + + pub fn with_size(mut self, size: u64) -> Self { + self.size = Some(size); + self + } +} + +/// Generic inode operations for simple filesystems +pub struct GenericInodeOps; + +impl InodeOperations for GenericInodeOps { + fn lookup(&self, dir: &Inode, name: &str) -> Result> { + Err(Error::ENOENT) + } + + fn create(&self, dir: &Inode, name: &str, mode: u32) -> Result> { + Err(Error::ENOSYS) + } + + fn mkdir(&self, dir: &Inode, name: &str, mode: u32) -> Result> { + Err(Error::ENOSYS) + } + + fn unlink(&self, dir: &Inode, name: &str) -> Result<()> { + Err(Error::ENOSYS) + } + + fn rmdir(&self, dir: &Inode, name: &str) -> Result<()> { + Err(Error::ENOSYS) + } + + fn symlink(&self, dir: &Inode, name: &str, target: &str) -> Result> { + Err(Error::ENOSYS) + } + + fn rename(&self, old_dir: &Inode, old_name: &str, new_dir: &Inode, new_name: &str) -> Result<()> { + Err(Error::ENOSYS) + } + + fn setattr(&self, inode: &Inode, attr: &InodeAttr) -> Result<()> { + // Apply basic attributes + if let Some(mode) = attr.mode { + inode.i_mode.store(mode, Ordering::Relaxed); + } + if let Some(uid) = attr.uid { + inode.i_uid.store(uid, Ordering::Relaxed); + } + if let Some(gid) = attr.gid { + inode.i_gid.store(gid, Ordering::Relaxed); + } + if let Some(size) = attr.size { + inode.set_size(size); + } + if let Some(atime) = attr.atime { + *inode.i_atime.lock() = atime; + } + if let Some(mtime) = attr.mtime { + *inode.i_mtime.lock() = mtime; + } + if let Some(ctime) = attr.ctime { + *inode.i_ctime.lock() = ctime; + } + Ok(()) + } + + fn getattr(&self, inode: &Inode) -> Result { + Ok(InodeAttr { + mode: Some(inode.i_mode.load(Ordering::Relaxed)), + uid: Some(inode.i_uid.load(Ordering::Relaxed)), + gid: Some(inode.i_gid.load(Ordering::Relaxed)), + size: Some(inode.i_size.load(Ordering::Relaxed)), + atime: Some(*inode.i_atime.lock()), + mtime: Some(*inode.i_mtime.lock()), + ctime: Some(*inode.i_ctime.lock()), + }) + } + + fn readlink(&self, inode: &Inode) -> Result { + Err(Error::EINVAL) + } + + fn follow_link(&self, inode: &Inode) -> Result> { + Err(Error::EINVAL) + } + + fn truncate(&self, inode: &Inode, size: u64) -> Result<()> { + inode.set_size(size); + Ok(()) + } + + fn getxattr(&self, inode: &Inode, name: &str) -> Result> { + Err(Error::ENODATA) + } + + fn setxattr(&self, inode: &Inode, name: &str, value: &[u8], flags: u32) -> Result<()> { + Err(Error::ENOSYS) + } + + fn listxattr(&self, inode: &Inode) -> Result> { + Ok(alloc::vec::Vec::new()) + } + + fn removexattr(&self, inode: &Inode, name: &str) -> Result<()> { + Err(Error::ENODATA) + } +} diff --git a/kernel/src/fs/mod.rs b/kernel/src/fs/mod.rs new file mode 100644 index 0000000..6ed441a --- /dev/null +++ b/kernel/src/fs/mod.rs @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Virtual File System (VFS) - Linux compatible +//! +//! This module provides the core filesystem abstractions and compatibility +//! with Linux VFS operations. + +pub mod file; +pub mod inode; +pub mod dentry; +pub mod super_block; +pub mod mount; +pub mod path; +pub mod operations; +pub mod ramfs; +pub mod procfs; +pub mod devfs; + +use crate::error::{Error, Result}; +use crate::types::*; +use crate::sync::{Arc, Mutex, RwLock}; +use crate::memory::{UserPtr, UserSlicePtr}; +use crate::device::DeviceNumber; +use alloc::vec::Vec; +use alloc::string::String; +use alloc::collections::BTreeMap; +use core::fmt; + +pub use file::*; +pub use inode::*; +pub use dentry::*; +pub use super_block::*; +pub use mount::*; +pub use path::*; +pub use operations::*; + +/// File access modes - Linux compatible +pub mod flags { + pub const O_ACCMODE: u32 = 0o00000003; + pub const O_RDONLY: u32 = 0o00000000; + pub const O_WRONLY: u32 = 0o00000001; + pub const O_RDWR: u32 = 0o00000002; + pub const O_CREAT: u32 = 0o00000100; + pub const O_EXCL: u32 = 0o00000200; + pub const O_NOCTTY: u32 = 0o00000400; + pub const O_TRUNC: u32 = 0o00001000; + pub const O_APPEND: u32 = 0o00002000; + pub const O_NONBLOCK: u32 = 0o00004000; + pub const O_DSYNC: u32 = 0o00010000; + pub const O_FASYNC: u32 = 0o00020000; + pub const O_DIRECT: u32 = 0o00040000; + pub const O_LARGEFILE: u32 = 0o00100000; + pub const O_DIRECTORY: u32 = 0o00200000; + pub const O_NOFOLLOW: u32 = 0o00400000; + pub const O_NOATIME: u32 = 0o01000000; + pub const O_CLOEXEC: u32 = 0o02000000; + pub const O_SYNC: u32 = 0o04000000 | O_DSYNC; + pub const O_PATH: u32 = 0o10000000; + pub const O_TMPFILE: u32 = 0o20000000 | O_DIRECTORY; +} + +/// File mode constants - Linux compatible +pub mod mode { + pub const S_IFMT: u32 = 0o170000; + pub const S_IFSOCK: u32 = 0o140000; + pub const S_IFLNK: u32 = 0o120000; + pub const S_IFREG: u32 = 0o100000; + pub const S_IFBLK: u32 = 0o060000; + pub const S_IFDIR: u32 = 0o040000; + pub const S_IFCHR: u32 = 0o020000; + pub const S_IFIFO: u32 = 0o010000; + + pub const S_ISUID: u32 = 0o004000; + pub const S_ISGID: u32 = 0o002000; + pub const S_ISVTX: u32 = 0o001000; + + pub const S_IRWXU: u32 = 0o000700; + pub const S_IRUSR: u32 = 0o000400; + pub const S_IWUSR: u32 = 0o000200; + pub const S_IXUSR: u32 = 0o000100; + + pub const S_IRWXG: u32 = 0o000070; + pub const S_IRGRP: u32 = 0o000040; + pub const S_IWGRP: u32 = 0o000020; + pub const S_IXGRP: u32 = 0o000010; + + pub const S_IRWXO: u32 = 0o000007; + pub const S_IROTH: u32 = 0o000004; + pub const S_IWOTH: u32 = 0o000002; + pub const S_IXOTH: u32 = 0o000001; +} + +/// File type helper functions +impl mode { + pub fn s_isreg(mode: u32) -> bool { + (mode & S_IFMT) == S_IFREG + } + + pub fn s_isdir(mode: u32) -> bool { + (mode & S_IFMT) == S_IFDIR + } + + pub fn s_ischr(mode: u32) -> bool { + (mode & S_IFMT) == S_IFCHR + } + + pub fn s_isblk(mode: u32) -> bool { + (mode & S_IFMT) == S_IFBLK + } + + pub fn s_isfifo(mode: u32) -> bool { + (mode & S_IFMT) == S_IFIFO + } + + pub fn s_islnk(mode: u32) -> bool { + (mode & S_IFMT) == S_IFLNK + } + + pub fn s_issock(mode: u32) -> bool { + (mode & S_IFMT) == S_IFSOCK + } +} + +/// Seek constants +pub const SEEK_SET: i32 = 0; +pub const SEEK_CUR: i32 = 1; +pub const SEEK_END: i32 = 2; +pub const SEEK_DATA: i32 = 3; +pub const SEEK_HOLE: i32 = 4; + +/// Maximum filename length +pub const NAME_MAX: usize = 255; + +/// Maximum path length +pub const PATH_MAX: usize = 4096; + +/// File system statistics structure +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct KStatFs { + pub f_type: u64, + pub f_bsize: u64, + pub f_blocks: u64, + pub f_bfree: u64, + pub f_bavail: u64, + pub f_files: u64, + pub f_ffree: u64, + pub f_fsid: [u32; 2], + pub f_namelen: u64, + pub f_frsize: u64, + pub f_flags: u64, + pub f_spare: [u64; 4], +} + +/// File attributes structure +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct KStat { + pub st_dev: u64, + pub st_ino: u64, + pub st_nlink: u64, + pub st_mode: u32, + pub st_uid: u32, + pub st_gid: u32, + pub st_rdev: u64, + pub st_size: i64, + pub st_blksize: u64, + pub st_blocks: u64, + pub st_atime: i64, + pub st_atime_nsec: i64, + pub st_mtime: i64, + pub st_mtime_nsec: i64, + pub st_ctime: i64, + pub st_ctime_nsec: i64, +} + +/// I/O vector for scatter-gather I/O +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct IoVec { + pub iov_base: *mut u8, + pub iov_len: usize, +} + +/// Directory entry type returned by readdir +#[derive(Debug, Clone)] +pub struct DirEntry { + pub ino: u64, + pub off: i64, + pub reclen: u16, + pub name: String, + pub d_type: u8, +} + +/// Directory entry types +pub const DT_UNKNOWN: u8 = 0; +pub const DT_FIFO: u8 = 1; +pub const DT_CHR: u8 = 2; +pub const DT_DIR: u8 = 4; +pub const DT_BLK: u8 = 6; +pub const DT_REG: u8 = 8; +pub const DT_LNK: u8 = 10; +pub const DT_SOCK: u8 = 12; +pub const DT_WHT: u8 = 14; + +/// Global VFS state +static VFS: Mutex = Mutex::new(Vfs::new()); + +/// Virtual File System state +pub struct Vfs { + /// Mounted filesystems + pub mounts: Vec>, + /// Root dentry + pub root: Option>, + /// File descriptor table (per-process will be separate) + pub fd_table: BTreeMap>, + /// Next file descriptor number + pub next_fd: i32, +} + +impl Vfs { + const fn new() -> Self { + Self { + mounts: Vec::new(), + root: None, + fd_table: BTreeMap::new(), + next_fd: 0, + } + } + + /// Mount a filesystem + pub fn mount( + &mut self, + source: &str, + target: &str, + fstype: &str, + flags: u32, + data: Option<&str>, + ) -> Result<()> { + // TODO: Implement proper mount logic + // For now, just create a basic mount + let sb = Arc::new(SuperBlock::new(fstype)?); + let mount = Arc::new(VfsMount::new(sb, target, flags)?); + self.mounts.push(mount); + Ok(()) + } + + /// Allocate a new file descriptor + pub fn alloc_fd(&mut self) -> i32 { + let fd = self.next_fd; + self.next_fd += 1; + fd + } + + /// Install a file into the file descriptor table + pub fn install_fd(&mut self, fd: i32, file: Arc) { + self.fd_table.insert(fd, file); + } + + /// Get a file by file descriptor + pub fn get_file(&self, fd: i32) -> Option> { + self.fd_table.get(&fd).cloned() + } + + /// Close a file descriptor + pub fn close_fd(&mut self, fd: i32) -> Result<()> { + self.fd_table.remove(&fd); + Ok(()) + } +} + +/// Initialize the VFS subsystem +pub fn init() -> Result<()> { + // Register built-in filesystems + ramfs::register_ramfs()?; + procfs::register_procfs()?; + devfs::register_devfs()?; + + // Create initial mounts + mount::do_mount("none", "/", "ramfs", 0, None)?; + + // Create essential directories + // TODO: Create /proc and /dev directories in root filesystem + + mount::do_mount("proc", "/proc", "proc", 0, None)?; + mount::do_mount("devfs", "/dev", "devfs", 0, None)?; + + crate::console::print_info("VFS: Initialized virtual file system\n"); + Ok(()) +} + +/// Open a file - Linux compatible sys_open +pub fn open(pathname: &str, flags: i32, mode: u32) -> Result { + let mut vfs = VFS.lock(); + + // TODO: Path resolution, permission checks, etc. + // For now, create a simple file + let file = Arc::new(File::new(pathname, flags as u32, mode)?); + let fd = vfs.alloc_fd(); + vfs.install_fd(fd, file); + + Ok(fd) +} + +/// Close a file descriptor - Linux compatible sys_close +pub fn close(fd: i32) -> Result<()> { + let mut vfs = VFS.lock(); + vfs.close_fd(fd) +} + +/// Read from a file descriptor - Linux compatible sys_read +pub fn read(fd: i32, buf: UserSlicePtr, count: usize) -> Result { + let vfs = VFS.lock(); + let file = vfs.get_file(fd).ok_or(Error::EBADF)?; + drop(vfs); + + file.read(buf, count) +} + +/// Write to a file descriptor - Linux compatible sys_write +pub fn write(fd: i32, buf: UserSlicePtr, count: usize) -> Result { + let vfs = VFS.lock(); + let file = vfs.get_file(fd).ok_or(Error::EBADF)?; + drop(vfs); + + file.write(buf, count) +} + +/// Seek within a file - Linux compatible sys_lseek +pub fn lseek(fd: i32, offset: i64, whence: i32) -> Result { + let vfs = VFS.lock(); + let file = vfs.get_file(fd).ok_or(Error::EBADF)?; + drop(vfs); + + file.seek(offset, whence) +} + +/// Get file status - Linux compatible sys_fstat +pub fn fstat(fd: i32, statbuf: UserPtr) -> Result<()> { + let vfs = VFS.lock(); + let file = vfs.get_file(fd).ok_or(Error::EBADF)?; + drop(vfs); + + let stat = file.stat()?; + statbuf.write(stat)?; + Ok(()) +} + +/// Generic file operations for simple filesystems +pub struct GenericFileOps; + +impl FileOperations for GenericFileOps { + fn read(&self, file: &File, buf: UserSlicePtr, count: usize) -> Result { + // Default read implementation + Ok(0) + } + + fn write(&self, file: &File, buf: UserSlicePtr, count: usize) -> Result { + // Default write implementation + Ok(count as isize) + } + + fn seek(&self, file: &File, offset: i64, whence: i32) -> Result { + // Default seek implementation + match whence { + SEEK_SET => Ok(offset), + SEEK_CUR => Ok(file.pos.load(core::sync::atomic::Ordering::Relaxed) + offset), + SEEK_END => Ok(offset), // TODO: Get file size + _ => Err(Error::EINVAL), + } + } + + fn ioctl(&self, file: &File, cmd: u32, arg: usize) -> Result { + Err(Error::ENOTTY) + } + + fn mmap(&self, file: &File, vma: &mut crate::memory::VmaArea) -> Result<()> { + Err(Error::ENODEV) + } + + fn fsync(&self, file: &File, datasync: bool) -> Result<()> { + Ok(()) + } + + fn poll(&self, file: &File, wait: &mut PollWait) -> Result { + Ok(POLLIN | POLLOUT) + } +} + +/// Poll events +pub const POLLIN: u32 = 0x001; +pub const POLLPRI: u32 = 0x002; +pub const POLLOUT: u32 = 0x004; +pub const POLLERR: u32 = 0x008; +pub const POLLHUP: u32 = 0x010; +pub const POLLNVAL: u32 = 0x020; + +/// Poll wait structure (simplified) +pub struct PollWait { + // TODO: Implement proper poll/select mechanism +} + +impl PollWait { + pub fn new() -> Self { + Self {} + } +} diff --git a/kernel/src/fs/mount.rs b/kernel/src/fs/mount.rs new file mode 100644 index 0000000..529869b --- /dev/null +++ b/kernel/src/fs/mount.rs @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! VFS mount abstraction - Linux compatible + +use crate::error::{Error, Result}; +use crate::sync::{Arc, Mutex, RwLock}; +use alloc::string::String; +use alloc::vec::Vec; +use core::sync::atomic::{AtomicU32, Ordering}; + +/// VFS mount structure - similar to Linux struct vfsmount +pub struct VfsMount { + /// Mounted superblock + pub mnt_sb: Arc, + /// Mount point path + pub mnt_mountpoint: String, + /// Mount flags + pub mnt_flags: AtomicU32, + /// Parent mount + pub mnt_parent: Option>, + /// Child mounts + pub mnt_children: Mutex>>, + /// Reference count + pub mnt_count: AtomicU32, + /// Device name + pub mnt_devname: Option, + /// Mount options + pub mnt_opts: Option, +} + +impl VfsMount { + /// Create a new VFS mount + pub fn new(sb: Arc, mountpoint: &str, flags: u32) -> Result { + Ok(Self { + mnt_sb: sb, + mnt_mountpoint: String::from(mountpoint), + mnt_flags: AtomicU32::new(flags), + mnt_parent: None, + mnt_children: Mutex::new(Vec::new()), + mnt_count: AtomicU32::new(1), + mnt_devname: None, + mnt_opts: None, + }) + } + + /// Set parent mount + pub fn set_parent(&mut self, parent: Arc) { + self.mnt_parent = Some(parent); + } + + /// Add child mount + pub fn add_child(&self, child: Arc) { + let mut children = self.mnt_children.lock(); + children.push(child); + } + + /// Get mount flags + pub fn get_flags(&self) -> u32 { + self.mnt_flags.load(Ordering::Relaxed) + } + + /// Check if mount is read-only + pub fn is_readonly(&self) -> bool { + (self.get_flags() & super::super_block::MS_RDONLY) != 0 + } + + /// Check if mount is nosuid + pub fn is_nosuid(&self) -> bool { + (self.get_flags() & super::super_block::MS_NOSUID) != 0 + } + + /// Check if mount is nodev + pub fn is_nodev(&self) -> bool { + (self.get_flags() & super::super_block::MS_NODEV) != 0 + } + + /// Check if mount is noexec + pub fn is_noexec(&self) -> bool { + (self.get_flags() & super::super_block::MS_NOEXEC) != 0 + } + + /// Increment reference count + pub fn mntget(&self) { + self.mnt_count.fetch_add(1, Ordering::Relaxed); + } + + /// Decrement reference count + pub fn mntput(&self) { + let old_count = self.mnt_count.fetch_sub(1, Ordering::Relaxed); + if old_count == 1 { + // Last reference, mount should be cleaned up + // TODO: Unmount filesystem + } + } + + /// Get full mount path + pub fn get_path(&self) -> String { + if let Some(ref parent) = self.mnt_parent { + if parent.mnt_mountpoint == "/" { + self.mnt_mountpoint.clone() + } else { + format!("{}{}", parent.get_path(), self.mnt_mountpoint) + } + } else { + self.mnt_mountpoint.clone() + } + } + + /// Find child mount by path + pub fn find_child_mount(&self, path: &str) -> Option> { + let children = self.mnt_children.lock(); + for child in children.iter() { + if child.mnt_mountpoint == path { + return Some(child.clone()); + } + } + None + } +} + +unsafe impl Send for VfsMount {} +unsafe impl Sync for VfsMount {} + +/// Mount namespace - similar to Linux struct mnt_namespace +pub struct MountNamespace { + /// Root mount + pub root: Option>, + /// All mounts in this namespace + pub mounts: Mutex>>, + /// Namespace ID + pub ns_id: u64, + /// Reference count + pub count: AtomicU32, +} + +impl MountNamespace { + /// Create a new mount namespace + pub fn new(ns_id: u64) -> Self { + Self { + root: None, + mounts: Mutex::new(Vec::new()), + ns_id, + count: AtomicU32::new(1), + } + } + + /// Add mount to namespace + pub fn add_mount(&self, mount: Arc) { + let mut mounts = self.mounts.lock(); + mounts.push(mount); + } + + /// Remove mount from namespace + pub fn remove_mount(&self, mountpoint: &str) -> Option> { + let mut mounts = self.mounts.lock(); + if let Some(pos) = mounts.iter().position(|m| m.mnt_mountpoint == mountpoint) { + Some(mounts.remove(pos)) + } else { + None + } + } + + /// Find mount by path + pub fn find_mount(&self, path: &str) -> Option> { + let mounts = self.mounts.lock(); + + // Find the longest matching mount point + let mut best_match: Option> = None; + let mut best_len = 0; + + for mount in mounts.iter() { + let mount_path = mount.get_path(); + if path.starts_with(&mount_path) && mount_path.len() > best_len { + best_match = Some(mount.clone()); + best_len = mount_path.len(); + } + } + + best_match + } + + /// Get all mount points + pub fn get_mount_points(&self) -> Vec { + let mounts = self.mounts.lock(); + mounts.iter().map(|m| m.get_path()).collect() + } + + /// Set root mount + pub fn set_root(&mut self, root: Arc) { + self.root = Some(root.clone()); + self.add_mount(root); + } +} + +/// Global mount namespace +static INIT_MNT_NS: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(|| Mutex::new(MountNamespace::new(1))); + +/// Get the init mount namespace +pub fn get_init_ns() -> &'static Mutex { + &INIT_MNT_NS +} + +/// Mount a filesystem +pub fn do_mount( + dev_name: &str, + dir_name: &str, + type_name: &str, + flags: u32, + data: Option<&str>, +) -> Result<()> { + // TODO: Look up filesystem type + // For now, create a basic mount + let sb = Arc::new(super::SuperBlock::new(type_name)?); + let mount = Arc::new(VfsMount::new(sb, dir_name, flags)?); + + let ns = get_init_ns(); + let mut ns = ns.lock(); + ns.add_mount(mount); + + crate::console::print_info(&format!("Mounted {} on {} (type {})\n", dev_name, dir_name, type_name)); + Ok(()) +} + +/// Unmount a filesystem +pub fn do_umount(dir_name: &str, flags: u32) -> Result<()> { + let ns = get_init_ns(); + let mut ns = ns.lock(); + + if let Some(mount) = ns.remove_mount(dir_name) { + mount.mntput(); + crate::console::print_info(&format!("Unmounted {}\n", dir_name)); + Ok(()) + } else { + Err(Error::ENOENT) + } +} + +/// Get mount information for a path +pub fn path_get_mount(path: &str) -> Option> { + let ns = get_init_ns(); + let ns = ns.lock(); + ns.find_mount(path) +} + +/// Check if a path is a mount point +pub fn is_mountpoint(path: &str) -> bool { + let ns = get_init_ns(); + let ns = ns.lock(); + let mounts = ns.mounts.lock(); + mounts.iter().any(|m| m.get_path() == path) +} + +/// Get all mount points +pub fn get_all_mounts() -> Vec { + let ns = get_init_ns(); + let ns = ns.lock(); + ns.get_mount_points() +} + +/// Remount a filesystem with new flags +pub fn do_remount(dir_name: &str, flags: u32, data: Option<&str>) -> Result<()> { + let ns = get_init_ns(); + let ns = ns.lock(); + + if let Some(mount) = ns.find_mount(dir_name) { + mount.mnt_flags.store(flags, Ordering::Relaxed); + + // Also remount the superblock + if let Some(ref ops) = mount.mnt_sb.s_op { + ops.remount_fs(&mount.mnt_sb, flags, data)?; + } + + crate::console::print_info(&format!("Remounted {} with flags {:#x}\n", dir_name, flags)); + Ok(()) + } else { + Err(Error::ENOENT) + } +} + +/// Bind mount - create a bind mount +pub fn do_bind_mount(old_path: &str, new_path: &str, flags: u32) -> Result<()> { + let ns = get_init_ns(); + let mut ns = ns.lock(); + + if let Some(old_mount) = ns.find_mount(old_path) { + let new_mount = Arc::new(VfsMount::new(old_mount.mnt_sb.clone(), new_path, flags | super::super_block::MS_BIND)?); + ns.add_mount(new_mount); + + crate::console::print_info(&format!("Bind mounted {} to {}\n", old_path, new_path)); + Ok(()) + } else { + Err(Error::ENOENT) + } +} diff --git a/kernel/src/fs/operations.rs b/kernel/src/fs/operations.rs new file mode 100644 index 0000000..4489668 --- /dev/null +++ b/kernel/src/fs/operations.rs @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Various VFS operations and utilities + +use crate::error::{Error, Result}; +use crate::sync::Arc; +use crate::memory::{UserPtr, UserSlicePtr}; +use alloc::string::String; +use alloc::vec::Vec; + +/// Address space operations trait - similar to Linux address_space_operations +pub trait AddressSpaceOperations: Send + Sync { + /// Write a page + fn writepage(&self, page: &crate::memory::Page) -> Result<()>; + + /// Read a page + fn readpage(&self, file: Option<&super::File>, page: &crate::memory::Page) -> Result<()>; + + /// Sync pages + fn sync_page(&self, page: &crate::memory::Page) -> Result<()>; + + /// Write pages + fn writepages(&self, mapping: &AddressSpace, wbc: &WritebackControl) -> Result<()>; + + /// Set page dirty + fn set_page_dirty(&self, page: &crate::memory::Page) -> Result; + + /// Read pages ahead + fn readpages(&self, file: Option<&super::File>, pages: &[&crate::memory::Page]) -> Result<()>; + + /// Write begin + fn write_begin(&self, file: &super::File, pos: u64, len: u32) -> Result<()>; + + /// Write end + fn write_end(&self, file: &super::File, pos: u64, len: u32, copied: u32) -> Result; + + /// Direct I/O + fn direct_io(&self, file: &super::File, pos: u64, buf: UserSlicePtr, len: usize, write: bool) -> Result; +} + +/// Address space structure +pub struct AddressSpace { + /// Host inode + pub host: Option>, + /// Address space operations + pub a_ops: Option>, + /// Number of pages + pub nrpages: core::sync::atomic::AtomicUsize, + /// Flags + pub flags: core::sync::atomic::AtomicU32, + /// Private data + pub private_data: Option<*mut u8>, +} + +impl AddressSpace { + pub fn new() -> Self { + Self { + host: None, + a_ops: None, + nrpages: core::sync::atomic::AtomicUsize::new(0), + flags: core::sync::atomic::AtomicU32::new(0), + private_data: None, + } + } +} + +/// Writeback control structure +pub struct WritebackControl { + pub start: u64, + pub end: u64, + pub sync_mode: WritebackSyncMode, + pub nr_to_write: u64, + pub tagged_writepages: bool, +} + +#[derive(Debug, Clone, Copy)] +pub enum WritebackSyncMode { + None, + All, + Memory, +} + +/// Generic file operations implementation +pub struct GenericFileOperations; + +impl super::FileOperations for GenericFileOperations { + fn read(&self, file: &super::File, buf: UserSlicePtr, count: usize) -> Result { + // Generic read implementation using page cache + // TODO: Implement proper page cache read + Ok(0) + } + + fn write(&self, file: &super::File, buf: UserSlicePtr, count: usize) -> Result { + // Generic write implementation using page cache + // TODO: Implement proper page cache write + Ok(count as isize) + } + + fn seek(&self, file: &super::File, offset: i64, whence: i32) -> Result { + let current_pos = file.get_pos(); + let new_pos = match whence { + super::SEEK_SET => offset, + super::SEEK_CUR => current_pos + offset, + super::SEEK_END => { + // Get file size from inode + if let Some(ref inode) = file.inode { + inode.get_size() as i64 + offset + } else { + offset + } + } + _ => return Err(Error::EINVAL), + }; + + if new_pos < 0 { + return Err(Error::EINVAL); + } + + file.set_pos(new_pos); + Ok(new_pos) + } + + fn ioctl(&self, file: &super::File, cmd: u32, arg: usize) -> Result { + Err(Error::ENOTTY) + } + + fn mmap(&self, file: &super::File, vma: &mut crate::memory::VmaArea) -> Result<()> { + // Generic mmap implementation + // TODO: Implement proper memory mapping + Err(Error::ENODEV) + } + + fn fsync(&self, file: &super::File, datasync: bool) -> Result<()> { + // Sync file data to storage + if let Some(ref inode) = file.inode { + // TODO: Sync inode and data blocks + } + Ok(()) + } + + fn poll(&self, file: &super::File, wait: &mut super::PollWait) -> Result { + // Regular files are always ready for I/O + Ok(super::POLLIN | super::POLLOUT) + } + + fn readdir(&self, file: &super::File, ctx: &mut super::file::DirContext) -> Result<()> { + // This shouldn't be called for regular files + Err(Error::ENOTDIR) + } +} + +/// Directory file operations +pub struct DirectoryOperations; + +impl super::FileOperations for DirectoryOperations { + fn read(&self, file: &super::File, buf: UserSlicePtr, count: usize) -> Result { + // Can't read directory as regular file + Err(Error::EISDIR) + } + + fn write(&self, file: &super::File, buf: UserSlicePtr, count: usize) -> Result { + // Can't write to directory as regular file + Err(Error::EISDIR) + } + + fn seek(&self, file: &super::File, offset: i64, whence: i32) -> Result { + // Directory seeking + match whence { + super::SEEK_SET => { + if offset < 0 { + return Err(Error::EINVAL); + } + file.set_pos(offset); + Ok(offset) + } + super::SEEK_CUR => { + let new_pos = file.get_pos() + offset; + if new_pos < 0 { + return Err(Error::EINVAL); + } + file.set_pos(new_pos); + Ok(new_pos) + } + super::SEEK_END => { + // Seek to end of directory + file.set_pos(i64::MAX); + Ok(i64::MAX) + } + _ => Err(Error::EINVAL), + } + } + + fn ioctl(&self, file: &super::File, cmd: u32, arg: usize) -> Result { + Err(Error::ENOTTY) + } + + fn mmap(&self, file: &super::File, vma: &mut crate::memory::VmaArea) -> Result<()> { + Err(Error::ENODEV) + } + + fn fsync(&self, file: &super::File, datasync: bool) -> Result<()> { + // Sync directory + Ok(()) + } + + fn poll(&self, file: &super::File, wait: &mut super::PollWait) -> Result { + // Directories are always ready + Ok(super::POLLIN) + } + + fn readdir(&self, file: &super::File, ctx: &mut super::file::DirContext) -> Result<()> { + // Read directory entries + if let Some(ref inode) = file.inode { + if let Some(ref dentry) = file.dentry { + let subdirs = dentry.d_subdirs.lock(); + for (i, child) in subdirs.iter().enumerate() { + if i >= ctx.pos as usize { + let d_type = if let Some(ref child_inode) = child.d_inode { + let mode = child_inode.i_mode.load(core::sync::atomic::Ordering::Relaxed); + if super::mode::s_isdir(mode) { + super::DT_DIR + } else if super::mode::s_isreg(mode) { + super::DT_REG + } else if super::mode::s_islnk(mode) { + super::DT_LNK + } else if super::mode::s_ischr(mode) { + super::DT_CHR + } else if super::mode::s_isblk(mode) { + super::DT_BLK + } else if super::mode::s_isfifo(mode) { + super::DT_FIFO + } else if super::mode::s_issock(mode) { + super::DT_SOCK + } else { + super::DT_UNKNOWN + } + } else { + super::DT_UNKNOWN + }; + + let ino = if let Some(ref child_inode) = child.d_inode { + child_inode.i_ino + } else { + 0 + }; + + ctx.add_entry(ino, &child.d_name, d_type); + } + } + } + } + Ok(()) + } +} + +/// Special file operations (for device files) +pub struct SpecialFileOperations; + +impl super::FileOperations for SpecialFileOperations { + fn read(&self, file: &super::File, buf: UserSlicePtr, count: usize) -> Result { + // Delegate to device driver + if let Some(ref inode) = file.inode { + if inode.is_char_device() || inode.is_block_device() { + // TODO: Call device driver read function + return Ok(0); + } + } + Err(Error::ENODEV) + } + + fn write(&self, file: &super::File, buf: UserSlicePtr, count: usize) -> Result { + // Delegate to device driver + if let Some(ref inode) = file.inode { + if inode.is_char_device() || inode.is_block_device() { + // TODO: Call device driver write function + return Ok(count as isize); + } + } + Err(Error::ENODEV) + } + + fn seek(&self, file: &super::File, offset: i64, whence: i32) -> Result { + // Most device files don't support seeking + Err(Error::ESPIPE) + } + + fn ioctl(&self, file: &super::File, cmd: u32, arg: usize) -> Result { + // Delegate to device driver + if let Some(ref inode) = file.inode { + if inode.is_char_device() || inode.is_block_device() { + // TODO: Call device driver ioctl function + return Ok(0); + } + } + Err(Error::ENOTTY) + } + + fn mmap(&self, file: &super::File, vma: &mut crate::memory::VmaArea) -> Result<()> { + // Some device files support mmap + Err(Error::ENODEV) + } + + fn fsync(&self, file: &super::File, datasync: bool) -> Result<()> { + // Nothing to sync for device files + Ok(()) + } + + fn poll(&self, file: &super::File, wait: &mut super::PollWait) -> Result { + // Delegate to device driver + if let Some(ref inode) = file.inode { + if inode.is_char_device() { + // TODO: Call device driver poll function + return Ok(super::POLLIN | super::POLLOUT); + } + } + Ok(0) + } +} + +/// No-op address space operations +pub struct NoOpAddressSpaceOps; + +impl AddressSpaceOperations for NoOpAddressSpaceOps { + fn writepage(&self, page: &crate::memory::Page) -> Result<()> { + Ok(()) + } + + fn readpage(&self, file: Option<&super::File>, page: &crate::memory::Page) -> Result<()> { + Ok(()) + } + + fn sync_page(&self, page: &crate::memory::Page) -> Result<()> { + Ok(()) + } + + fn writepages(&self, mapping: &AddressSpace, wbc: &WritebackControl) -> Result<()> { + Ok(()) + } + + fn set_page_dirty(&self, page: &crate::memory::Page) -> Result { + Ok(true) + } + + fn readpages(&self, file: Option<&super::File>, pages: &[&crate::memory::Page]) -> Result<()> { + Ok(()) + } + + fn write_begin(&self, file: &super::File, pos: u64, len: u32) -> Result<()> { + Ok(()) + } + + fn write_end(&self, file: &super::File, pos: u64, len: u32, copied: u32) -> Result { + Ok(copied) + } + + fn direct_io(&self, file: &super::File, pos: u64, buf: UserSlicePtr, len: usize, write: bool) -> Result { + if write { + Ok(len as isize) + } else { + Ok(0) + } + } +} + +/// Helper functions for VFS operations + +/// Get file operations for an inode +pub fn get_file_operations(inode: &super::Inode) -> Arc { + if let Some(ref fop) = inode.i_fop { + fop.clone() + } else { + let mode = inode.i_mode.load(core::sync::atomic::Ordering::Relaxed); + if super::mode::s_isreg(mode) { + Arc::new(GenericFileOperations) + } else if super::mode::s_isdir(mode) { + Arc::new(DirectoryOperations) + } else if super::mode::s_ischr(mode) || super::mode::s_isblk(mode) { + Arc::new(SpecialFileOperations) + } else { + Arc::new(super::GenericFileOps) + } + } +} + +/// Check file permissions +pub fn check_permissions(inode: &super::Inode, mask: u32) -> Result<()> { + // TODO: Implement proper permission checking + // For now, allow all operations + Ok(()) +} + +/// Update access time +pub fn update_atime(inode: &super::Inode) { + inode.update_atime(); +} + +/// Update modification time +pub fn update_mtime(inode: &super::Inode) { + inode.update_mtime(); +} + +/// Truncate file to specified size +pub fn do_truncate(inode: &super::Inode, size: u64) -> Result<()> { + if let Some(ref ops) = inode.i_op { + ops.truncate(inode, size) + } else { + inode.set_size(size); + Ok(()) + } +} + +/// Notify directory change +pub fn notify_change(inode: &super::Inode, attr: &super::inode::InodeAttr) -> Result<()> { + if let Some(ref ops) = inode.i_op { + ops.setattr(inode, attr) + } else { + Ok(()) + } +} diff --git a/kernel/src/fs/path.rs b/kernel/src/fs/path.rs new file mode 100644 index 0000000..22d30b7 --- /dev/null +++ b/kernel/src/fs/path.rs @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Path resolution and manipulation - Linux compatible + +use crate::error::{Error, Result}; +use crate::sync::Arc; +use alloc::string::String; +use alloc::vec::Vec; + +/// Path structure for path resolution +#[derive(Debug, Clone)] +pub struct Path { + /// Mount point + pub mnt: Option>, + /// Dentry + pub dentry: Option>, +} + +impl Path { + /// Create a new path + pub fn new() -> Self { + Self { + mnt: None, + dentry: None, + } + } + + /// Create path from mount and dentry + pub fn from_mount_dentry(mnt: Arc, dentry: Arc) -> Self { + Self { + mnt: Some(mnt), + dentry: Some(dentry), + } + } + + /// Get the full path string + pub fn to_string(&self) -> String { + if let Some(ref dentry) = self.dentry { + dentry.get_path() + } else { + String::from("/") + } + } + + /// Check if path is absolute + pub fn is_absolute(&self) -> bool { + self.to_string().starts_with('/') + } + + /// Get parent path + pub fn parent(&self) -> Option { + if let Some(ref dentry) = self.dentry { + if let Some(ref parent) = dentry.d_parent { + Some(Path { + mnt: self.mnt.clone(), + dentry: Some(parent.clone()), + }) + } else { + None + } + } else { + None + } + } + + /// Get filename component + pub fn filename(&self) -> Option { + if let Some(ref dentry) = self.dentry { + Some(dentry.d_name.clone()) + } else { + None + } + } + + /// Join with another path component + pub fn join(&self, component: &str) -> Result { + if let Some(ref dentry) = self.dentry { + if let Some(child) = dentry.find_child(component) { + Ok(Path { + mnt: self.mnt.clone(), + dentry: Some(child), + }) + } else { + Err(Error::ENOENT) + } + } else { + Err(Error::ENOENT) + } + } +} + +/// Path lookup flags +pub const LOOKUP_FOLLOW: u32 = 0x0001; +pub const LOOKUP_DIRECTORY: u32 = 0x0002; +pub const LOOKUP_AUTOMOUNT: u32 = 0x0004; +pub const LOOKUP_EMPTY: u32 = 0x0008; +pub const LOOKUP_OPEN: u32 = 0x0010; +pub const LOOKUP_CREATE: u32 = 0x0020; +pub const LOOKUP_EXCL: u32 = 0x0040; +pub const LOOKUP_RENAME_TARGET: u32 = 0x0080; + +/// Name data structure for path resolution +pub struct NameData { + /// Path components + pub path: String, + /// Current position in path + pub pos: usize, + /// Lookup flags + pub flags: u32, + /// Root directory + pub root: Option, + /// Current working directory + pub pwd: Option, + /// Result path + pub result: Option, + /// Intent (for create/open operations) + pub intent: Option, +} + +/// Intent for path operations +#[derive(Debug, Clone)] +pub enum Intent { + Open { + flags: u32, + mode: u32, + }, + Create { + mode: u32, + }, + Lookup, +} + +impl NameData { + /// Create new name data for path resolution + pub fn new(path: String, flags: u32) -> Self { + Self { + path, + pos: 0, + flags, + root: None, + pwd: None, + result: None, + intent: None, + } + } + + /// Set root directory + pub fn with_root(mut self, root: Path) -> Self { + self.root = Some(root); + self + } + + /// Set current working directory + pub fn with_pwd(mut self, pwd: Path) -> Self { + self.pwd = Some(pwd); + self + } + + /// Set intent + pub fn with_intent(mut self, intent: Intent) -> Self { + self.intent = Some(intent); + self + } + + /// Get next path component + pub fn next_component(&mut self) -> Option { + if self.pos >= self.path.len() { + return None; + } + + // Skip leading slashes + while self.pos < self.path.len() && self.path.chars().nth(self.pos) == Some('/') { + self.pos += 1; + } + + if self.pos >= self.path.len() { + return None; + } + + // Find end of component + let start = self.pos; + while self.pos < self.path.len() && self.path.chars().nth(self.pos) != Some('/') { + self.pos += 1; + } + + Some(self.path[start..self.pos].to_string()) + } + + /// Check if path is finished + pub fn is_finished(&self) -> bool { + self.pos >= self.path.len() + } +} + +/// Resolve a path to a dentry +pub fn path_lookup(pathname: &str, flags: u32) -> Result { + let mut nd = NameData::new(String::from(pathname), flags); + + // Set root directory (for now, use a dummy root) + // TODO: Get actual root from current process + + // Start from root or current directory + let mut current_path = if pathname.starts_with('/') { + // Absolute path - start from root + if let Some(root) = nd.root.clone() { + root + } else { + // Create dummy root path + Path::new() + } + } else { + // Relative path - start from current directory + if let Some(pwd) = nd.pwd.clone() { + pwd + } else { + // Create dummy current directory + Path::new() + } + }; + + // Resolve each component + while let Some(component) = nd.next_component() { + match component.as_str() { + "." => { + // Current directory - no change + continue; + } + ".." => { + // Parent directory + if let Some(parent) = current_path.parent() { + current_path = parent; + } + continue; + } + _ => { + // Regular component + current_path = current_path.join(&component)?; + } + } + + // Check for mount points + if let Some(mount) = super::mount::path_get_mount(¤t_path.to_string()) { + current_path.mnt = Some(mount); + } + + // Handle symlinks if LOOKUP_FOLLOW is set + if (flags & LOOKUP_FOLLOW) != 0 { + if let Some(ref dentry) = current_path.dentry { + if let Some(ref inode) = dentry.d_inode { + if super::mode::s_islnk(inode.i_mode.load(core::sync::atomic::Ordering::Relaxed)) { + // TODO: Follow symbolic link + // For now, just continue + } + } + } + } + } + + nd.result = Some(current_path.clone()); + Ok(current_path) +} + +/// Resolve parent directory and filename +pub fn path_parent_and_name(pathname: &str) -> Result<(Path, String)> { + let path = Path::new(); + + // Split pathname into parent and filename + if let Some(last_slash) = pathname.rfind('/') { + let parent_path = &pathname[..last_slash]; + let filename = &pathname[last_slash + 1..]; + + if parent_path.is_empty() { + // Root directory + Ok((path, String::from(filename))) + } else { + let parent = path_lookup(parent_path, 0)?; + Ok((parent, String::from(filename))) + } + } else { + // No slash - filename in current directory + Ok((path, String::from(pathname))) + } +} + +/// Normalize a path (remove . and .. components) +pub fn normalize_path(path: &str) -> String { + let mut components = Vec::new(); + + for component in path.split('/') { + match component { + "" | "." => { + // Skip empty and current directory components + continue; + } + ".." => { + // Parent directory - remove last component + components.pop(); + } + _ => { + // Regular component + components.push(component); + } + } + } + + let result = components.join("/"); + if path.starts_with('/') { + format!("/{}", result) + } else { + result + } +} + +/// Check if a path is safe (no .. escapes) +pub fn is_safe_path(path: &str) -> bool { + let normalized = normalize_path(path); + + // Check for .. at the beginning + if normalized.starts_with("..") { + return false; + } + + // Check for /../ sequences + if normalized.contains("/../") { + return false; + } + + true +} + +/// Join two paths +pub fn join_paths(base: &str, path: &str) -> String { + if path.starts_with('/') { + // Absolute path + String::from(path) + } else { + // Relative path + let base = base.trim_end_matches('/'); + if base.is_empty() { + format!("/{}", path) + } else { + format!("{}/{}", base, path) + } + } +} + +/// Get the directory part of a path +pub fn dirname(path: &str) -> &str { + if let Some(last_slash) = path.rfind('/') { + if last_slash == 0 { + "/" + } else { + &path[..last_slash] + } + } else { + "." + } +} + +/// Get the filename part of a path +pub fn basename(path: &str) -> &str { + if let Some(last_slash) = path.rfind('/') { + &path[last_slash + 1..] + } else { + path + } +} + +/// Get file extension +pub fn extension(path: &str) -> Option<&str> { + let filename = basename(path); + if let Some(last_dot) = filename.rfind('.') { + if last_dot > 0 { + Some(&filename[last_dot + 1..]) + } else { + None + } + } else { + None + } +} + +/// Check if path is absolute +pub fn is_absolute(path: &str) -> bool { + path.starts_with('/') +} + +/// Check if path is relative +pub fn is_relative(path: &str) -> bool { + !is_absolute(path) +} diff --git a/kernel/src/fs/procfs.rs b/kernel/src/fs/procfs.rs new file mode 100644 index 0000000..26caadf --- /dev/null +++ b/kernel/src/fs/procfs.rs @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Proc filesystem implementation - Linux compatible + +use crate::error::{Error, Result}; +use crate::fs::*; +use crate::sync::{Arc, Mutex}; +use crate::memory::UserSlicePtr; +use alloc::string::String; +use alloc::vec::Vec; +use alloc::collections::BTreeMap; +use alloc::format; +use core::sync::atomic::{AtomicU64, Ordering}; + +/// Proc filesystem entry +pub struct ProcEntry { + /// Entry name + pub name: String, + /// Entry type + pub entry_type: ProcEntryType, + /// Mode + pub mode: u32, + /// Read function + pub read: Option Result<()>>, + /// Write function + pub write: Option Result<()>>, + /// Child entries (for directories) + pub children: Mutex>>, + /// Parent entry + pub parent: Option>, + /// Private data + pub private_data: Option<*mut u8>, +} + +#[derive(Debug, Clone, Copy)] +pub enum ProcEntryType { + File, + Directory, + Symlink, +} + +impl ProcEntry { + pub fn new_file(name: String, mode: u32, read_fn: fn(&ProcEntry, &mut String) -> Result<()>) -> Self { + Self { + name, + entry_type: ProcEntryType::File, + mode, + read: Some(read_fn), + write: None, + children: Mutex::new(Vec::new()), + parent: None, + private_data: None, + } + } + + pub fn new_dir(name: String, mode: u32) -> Self { + Self { + name, + entry_type: ProcEntryType::Directory, + mode, + read: None, + write: None, + children: Mutex::new(Vec::new()), + parent: None, + private_data: None, + } + } + + pub fn add_child(self: &Arc, child: Arc) { + let mut children = self.children.lock(); + children.push(child); + } + + pub fn find_child(&self, name: &str) -> Option> { + let children = self.children.lock(); + for child in children.iter() { + if child.name == name { + return Some(child.clone()); + } + } + None + } +} + +unsafe impl Send for ProcEntry {} +unsafe impl Sync for ProcEntry {} + +/// Proc filesystem +pub struct ProcFs { + /// Root entry + pub root: Arc, + /// Next inode number + next_ino: AtomicU64, + /// Entry to inode mapping + entries: Mutex>, +} + +impl ProcFs { + pub fn new() -> Self { + let root = Arc::new(ProcEntry::new_dir(String::from("proc"), 0o755)); + let mut fs = Self { + root, + next_ino: AtomicU64::new(1), + entries: Mutex::new(BTreeMap::new()), + }; + + fs.create_default_entries(); + fs + } + + fn alloc_ino(&self) -> u64 { + self.next_ino.fetch_add(1, Ordering::Relaxed) + } + + fn get_or_create_ino(&self, entry: &ProcEntry) -> u64 { + let entry_ptr = entry as *const ProcEntry; + let mut entries = self.entries.lock(); + + if let Some(&ino) = entries.get(&entry_ptr) { + ino + } else { + let ino = self.alloc_ino(); + entries.insert(entry_ptr, ino); + ino + } + } + + fn create_default_entries(&mut self) { + // Create /proc/version + let version_entry = Arc::new(ProcEntry::new_file( + String::from("version"), + 0o444, + proc_version_read, + )); + self.root.add_child(version_entry); + + // Create /proc/meminfo + let meminfo_entry = Arc::new(ProcEntry::new_file( + String::from("meminfo"), + 0o444, + proc_meminfo_read, + )); + self.root.add_child(meminfo_entry); + + // Create /proc/cpuinfo + let cpuinfo_entry = Arc::new(ProcEntry::new_file( + String::from("cpuinfo"), + 0o444, + proc_cpuinfo_read, + )); + self.root.add_child(cpuinfo_entry); + + // Create /proc/uptime + let uptime_entry = Arc::new(ProcEntry::new_file( + String::from("uptime"), + 0o444, + proc_uptime_read, + )); + self.root.add_child(uptime_entry); + + // Create /proc/loadavg + let loadavg_entry = Arc::new(ProcEntry::new_file( + String::from("loadavg"), + 0o444, + proc_loadavg_read, + )); + self.root.add_child(loadavg_entry); + + // Create /proc/stat + let stat_entry = Arc::new(ProcEntry::new_file( + String::from("stat"), + 0o444, + proc_stat_read, + )); + self.root.add_child(stat_entry); + + // Create /proc/mounts + let mounts_entry = Arc::new(ProcEntry::new_file( + String::from("mounts"), + 0o444, + proc_mounts_read, + )); + self.root.add_child(mounts_entry); + } +} + +/// Proc filesystem file operations +pub struct ProcFileOps { + entry: Arc, +} + +impl ProcFileOps { + pub fn new(entry: Arc) -> Self { + Self { entry } + } +} + +impl FileOperations for ProcFileOps { + fn read(&self, file: &File, buf: UserSlicePtr, count: usize) -> Result { + match self.entry.entry_type { + ProcEntryType::File => { + if let Some(read_fn) = self.entry.read { + let mut content = String::new(); + read_fn(&self.entry, &mut content)?; + + let pos = file.get_pos() as usize; + if pos >= content.len() { + return Ok(0); + } + + let to_copy = core::cmp::min(count, content.len() - pos); + let data = &content.as_bytes()[pos..pos + to_copy]; + + buf.copy_from_slice(data)?; + file.set_pos(file.get_pos() + to_copy as i64); + + Ok(to_copy as isize) + } else { + Ok(0) + } + } + _ => Err(Error::EISDIR), + } + } + + fn write(&self, file: &File, buf: UserSlicePtr, count: usize) -> Result { + if let Some(write_fn) = self.entry.write { + let mut data = vec![0u8; count]; + buf.copy_to_slice(&mut data)?; + let content = String::from_utf8(data).map_err(|_| Error::EINVAL)?; + write_fn(&self.entry, &content)?; + Ok(count as isize) + } else { + Err(Error::EPERM) + } + } + + fn seek(&self, file: &File, offset: i64, whence: i32) -> Result { + let new_pos = match whence { + SEEK_SET => offset, + SEEK_CUR => file.get_pos() + offset, + SEEK_END => 0, // Proc files are typically small + _ => return Err(Error::EINVAL), + }; + + if new_pos < 0 { + return Err(Error::EINVAL); + } + + file.set_pos(new_pos); + Ok(new_pos) + } + + fn ioctl(&self, file: &File, cmd: u32, arg: usize) -> Result { + Err(Error::ENOTTY) + } + + fn mmap(&self, file: &File, vma: &mut crate::memory::VmaArea) -> Result<()> { + Err(Error::ENODEV) + } + + fn fsync(&self, file: &File, datasync: bool) -> Result<()> { + Ok(()) + } + + fn poll(&self, file: &File, wait: &mut PollWait) -> Result { + Ok(POLLIN | POLLOUT) + } +} + +/// Proc filesystem inode operations +pub struct ProcInodeOps { + fs: *const ProcFs, +} + +impl ProcInodeOps { + pub fn new(fs: &ProcFs) -> Self { + Self { + fs: fs as *const ProcFs, + } + } + + fn get_fs(&self) -> &ProcFs { + unsafe { &*self.fs } + } +} + +unsafe impl Send for ProcInodeOps {} +unsafe impl Sync for ProcInodeOps {} + +impl InodeOperations for ProcInodeOps { + fn lookup(&self, dir: &Inode, name: &str) -> Result> { + let fs = self.get_fs(); + // Find the proc entry for this inode + // This is a simplified implementation + if let Some(child) = fs.root.find_child(name) { + let ino = fs.get_or_create_ino(&child); + let mode = match child.entry_type { + ProcEntryType::File => mode::S_IFREG | child.mode, + ProcEntryType::Directory => mode::S_IFDIR | child.mode, + ProcEntryType::Symlink => mode::S_IFLNK | child.mode, + }; + + let mut inode = Inode::new(ino, mode); + inode.set_operations(Arc::new(ProcInodeOps::new(fs))); + inode.set_file_operations(Arc::new(ProcFileOps::new(child))); + + Ok(Arc::new(inode)) + } else { + Err(Error::ENOENT) + } + } + + fn create(&self, dir: &Inode, name: &str, mode: u32) -> Result> { + Err(Error::EPERM) // Proc filesystem is read-only + } + + fn mkdir(&self, dir: &Inode, name: &str, mode: u32) -> Result> { + Err(Error::EPERM) + } + + fn unlink(&self, dir: &Inode, name: &str) -> Result<()> { + Err(Error::EPERM) + } + + fn rmdir(&self, dir: &Inode, name: &str) -> Result<()> { + Err(Error::EPERM) + } + + fn symlink(&self, dir: &Inode, name: &str, target: &str) -> Result> { + Err(Error::EPERM) + } + + fn rename(&self, old_dir: &Inode, old_name: &str, new_dir: &Inode, new_name: &str) -> Result<()> { + Err(Error::EPERM) + } + + fn setattr(&self, inode: &Inode, attr: &InodeAttr) -> Result<()> { + Err(Error::EPERM) + } + + fn getattr(&self, inode: &Inode) -> Result { + let generic_ops = GenericInodeOps; + generic_ops.getattr(inode) + } + + fn readlink(&self, inode: &Inode) -> Result { + Err(Error::EINVAL) + } + + fn follow_link(&self, inode: &Inode) -> Result> { + Err(Error::EINVAL) + } + + fn truncate(&self, inode: &Inode, size: u64) -> Result<()> { + Err(Error::EPERM) + } + + fn getxattr(&self, inode: &Inode, name: &str) -> Result> { + Err(Error::ENODATA) + } + + fn setxattr(&self, inode: &Inode, name: &str, value: &[u8], flags: u32) -> Result<()> { + Err(Error::EPERM) + } + + fn listxattr(&self, inode: &Inode) -> Result> { + Ok(Vec::new()) + } + + fn removexattr(&self, inode: &Inode, name: &str) -> Result<()> { + Err(Error::EPERM) + } +} + +// Proc file read functions + +fn proc_version_read(_entry: &ProcEntry, content: &mut String) -> Result<()> { + content.push_str(&format!("{} version {} ({})\n", + crate::NAME, crate::VERSION, "rustc")); + Ok(()) +} + +fn proc_meminfo_read(_entry: &ProcEntry, content: &mut String) -> Result<()> { + // TODO: Get actual memory statistics + let total_mem = 128 * 1024; // 128 MB placeholder + let free_mem = 64 * 1024; // 64 MB placeholder + + content.push_str(&format!( + "MemTotal: {} kB\n\ + MemFree: {} kB\n\ + MemAvailable: {} kB\n\ + Buffers: {} kB\n\ + Cached: {} kB\n", + total_mem, free_mem, free_mem, 0, 0 + )); + Ok(()) +} + +fn proc_cpuinfo_read(_entry: &ProcEntry, content: &mut String) -> Result<()> { + // TODO: Get actual CPU information + content.push_str( + "processor\t: 0\n\ + vendor_id\t: RustKernel\n\ + cpu family\t: 1\n\ + model\t\t: 1\n\ + model name\t: Rust Kernel CPU\n\ + stepping\t: 1\n\ + microcode\t: 0x1\n\ + cpu MHz\t\t: 1000.000\n\ + cache size\t: 1024 KB\n\ + flags\t\t: rust\n\n" + ); + Ok(()) +} + +fn proc_uptime_read(_entry: &ProcEntry, content: &mut String) -> Result<()> { + // TODO: Get actual uptime + let uptime = 100.0; // 100 seconds placeholder + let idle = 90.0; // 90 seconds idle placeholder + + content.push_str(&format!("{:.2} {:.2}\n", uptime, idle)); + Ok(()) +} + +fn proc_loadavg_read(_entry: &ProcEntry, content: &mut String) -> Result<()> { + // TODO: Get actual load average + content.push_str("0.00 0.00 0.00 1/1 1\n"); + Ok(()) +} + +fn proc_stat_read(_entry: &ProcEntry, content: &mut String) -> Result<()> { + // TODO: Get actual system statistics + content.push_str( + "cpu 0 0 0 1000 0 0 0 0 0 0\n\ + cpu0 0 0 0 1000 0 0 0 0 0 0\n\ + intr 0\n\ + ctxt 0\n\ + btime 0\n\ + processes 1\n\ + procs_running 1\n\ + procs_blocked 0\n" + ); + Ok(()) +} + +fn proc_mounts_read(_entry: &ProcEntry, content: &mut String) -> Result<()> { + // TODO: Get actual mount information + let mounts = crate::fs::mount::get_all_mounts(); + for mount in mounts { + content.push_str(&format!("none {} ramfs rw 0 0\n", mount)); + } + Ok(()) +} + +/// Mount proc filesystem +pub fn mount_procfs(_dev_name: &str, _flags: u32, _data: Option<&str>) -> Result> { + let mut sb = SuperBlock::new("proc")?; + sb.s_magic = 0x9fa0; // PROC_SUPER_MAGIC + + let procfs = Box::leak(Box::new(ProcFs::new())); + sb.s_fs_info = Some(procfs as *mut ProcFs as *mut u8); + + // Create root inode + let root_inode = Arc::new({ + let mut inode = Inode::new(1, mode::S_IFDIR | 0o755); + inode.set_operations(Arc::new(ProcInodeOps::new(procfs))); + inode + }); + + let root_dentry = Arc::new(Dentry::new(String::from("/"), Some(root_inode))); + sb.s_root = Some(root_dentry); + + Ok(Arc::new(sb)) +} + +/// Register proc filesystem +pub fn register_procfs() -> Result<()> { + let procfs_type = FileSystemType::new( + String::from("proc"), + |_fstype, flags, _dev_name, data| mount_procfs(_dev_name, flags, data), + |_sb| Ok(()), + ); + + // TODO: Register with VFS + crate::console::print_info("Registered proc filesystem\n"); + Ok(()) +} diff --git a/kernel/src/fs/ramfs.rs b/kernel/src/fs/ramfs.rs new file mode 100644 index 0000000..94b622e --- /dev/null +++ b/kernel/src/fs/ramfs.rs @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Simple RAM filesystem implementation + +use crate::error::{Error, Result}; +use crate::fs::*; +use crate::sync::{Arc, Mutex}; +use alloc::string::String; +use alloc::vec::Vec; +use alloc::collections::BTreeMap; +use core::sync::atomic::{AtomicU64, Ordering}; + +/// RAM filesystem superblock +pub struct RamFs { + /// Next inode number + next_ino: AtomicU64, + /// Inode storage + inodes: Mutex>>, + /// Directory entries + entries: Mutex>>>, +} + +impl RamFs { + pub fn new() -> Self { + Self { + next_ino: AtomicU64::new(1), + inodes: Mutex::new(BTreeMap::new()), + entries: Mutex::new(BTreeMap::new()), + } + } + + fn alloc_ino(&self) -> u64 { + self.next_ino.fetch_add(1, Ordering::Relaxed) + } + + fn create_inode(&self, mode: u32) -> Arc { + let ino = self.alloc_ino(); + let mut inode = Inode::new(ino, mode); + inode.set_operations(Arc::new(RamFsInodeOps::new(self))); + + let inode = Arc::new(inode); + + let mut inodes = self.inodes.lock(); + inodes.insert(ino, inode.clone()); + + if mode::s_isdir(mode) { + let mut entries = self.entries.lock(); + entries.insert(ino, Vec::new()); + } + + inode + } + + fn get_inode(&self, ino: u64) -> Option> { + let inodes = self.inodes.lock(); + inodes.get(&ino).cloned() + } + + fn add_entry(&self, dir_ino: u64, name: String, child_ino: u64) -> Result<()> { + let child_inode = self.get_inode(child_ino).ok_or(Error::ENOENT)?; + let dentry = Arc::new(Dentry::new(name, Some(child_inode))); + + let mut entries = self.entries.lock(); + if let Some(dir_entries) = entries.get_mut(&dir_ino) { + dir_entries.push(dentry); + Ok(()) + } else { + Err(Error::ENOTDIR) + } + } + + fn find_entry(&self, dir_ino: u64, name: &str) -> Option> { + let entries = self.entries.lock(); + if let Some(dir_entries) = entries.get(&dir_ino) { + for entry in dir_entries { + if entry.d_name == name { + return Some(entry.clone()); + } + } + } + None + } + + fn remove_entry(&self, dir_ino: u64, name: &str) -> Result<()> { + let mut entries = self.entries.lock(); + if let Some(dir_entries) = entries.get_mut(&dir_ino) { + if let Some(pos) = dir_entries.iter().position(|e| e.d_name == name) { + dir_entries.remove(pos); + Ok(()) + } else { + Err(Error::ENOENT) + } + } else { + Err(Error::ENOTDIR) + } + } +} + +/// RAM filesystem inode operations +pub struct RamFsInodeOps { + fs: *const RamFs, +} + +impl RamFsInodeOps { + fn new(fs: &RamFs) -> Self { + Self { + fs: fs as *const RamFs, + } + } + + fn get_fs(&self) -> &RamFs { + unsafe { &*self.fs } + } +} + +unsafe impl Send for RamFsInodeOps {} +unsafe impl Sync for RamFsInodeOps {} + +impl InodeOperations for RamFsInodeOps { + fn lookup(&self, dir: &Inode, name: &str) -> Result> { + let fs = self.get_fs(); + if let Some(entry) = fs.find_entry(dir.i_ino, name) { + if let Some(inode) = entry.d_inode { + Ok(inode) + } else { + Err(Error::ENOENT) + } + } else { + Err(Error::ENOENT) + } + } + + fn create(&self, dir: &Inode, name: &str, mode: u32) -> Result> { + let fs = self.get_fs(); + + // Check if entry already exists + if fs.find_entry(dir.i_ino, name).is_some() { + return Err(Error::EEXIST); + } + + let inode = fs.create_inode(mode | mode::S_IFREG); + fs.add_entry(dir.i_ino, String::from(name), inode.i_ino)?; + + Ok(inode) + } + + fn mkdir(&self, dir: &Inode, name: &str, mode: u32) -> Result> { + let fs = self.get_fs(); + + // Check if entry already exists + if fs.find_entry(dir.i_ino, name).is_some() { + return Err(Error::EEXIST); + } + + let inode = fs.create_inode(mode | mode::S_IFDIR); + fs.add_entry(dir.i_ino, String::from(name), inode.i_ino)?; + + // Add . and .. entries + fs.add_entry(inode.i_ino, String::from("."), inode.i_ino)?; + fs.add_entry(inode.i_ino, String::from(".."), dir.i_ino)?; + + Ok(inode) + } + + fn unlink(&self, dir: &Inode, name: &str) -> Result<()> { + let fs = self.get_fs(); + fs.remove_entry(dir.i_ino, name) + } + + fn rmdir(&self, dir: &Inode, name: &str) -> Result<()> { + let fs = self.get_fs(); + + // Check if directory is empty (only . and .. entries) + let entries = fs.entries.lock(); + if let Some(target_inode) = fs.find_entry(dir.i_ino, name) + .and_then(|e| e.d_inode.clone()) { + if let Some(dir_entries) = entries.get(&target_inode.i_ino) { + if dir_entries.len() > 2 { + return Err(Error::ENOTEMPTY); + } + } + } + drop(entries); + + fs.remove_entry(dir.i_ino, name) + } + + fn symlink(&self, dir: &Inode, name: &str, target: &str) -> Result> { + let fs = self.get_fs(); + + // Check if entry already exists + if fs.find_entry(dir.i_ino, name).is_some() { + return Err(Error::EEXIST); + } + + let inode = fs.create_inode(mode::S_IFLNK | 0o777); + // TODO: Store symlink target + fs.add_entry(dir.i_ino, String::from(name), inode.i_ino)?; + + Ok(inode) + } + + fn rename(&self, old_dir: &Inode, old_name: &str, new_dir: &Inode, new_name: &str) -> Result<()> { + let fs = self.get_fs(); + + // Find the entry to rename + if let Some(entry) = fs.find_entry(old_dir.i_ino, old_name) { + // Remove from old location + fs.remove_entry(old_dir.i_ino, old_name)?; + + // Add to new location + if let Some(inode) = entry.d_inode { + fs.add_entry(new_dir.i_ino, String::from(new_name), inode.i_ino)?; + } + + Ok(()) + } else { + Err(Error::ENOENT) + } + } + + fn setattr(&self, inode: &Inode, attr: &InodeAttr) -> Result<()> { + // Apply basic attributes using generic implementation + let generic_ops = GenericInodeOps; + generic_ops.setattr(inode, attr) + } + + fn getattr(&self, inode: &Inode) -> Result { + let generic_ops = GenericInodeOps; + generic_ops.getattr(inode) + } + + fn readlink(&self, inode: &Inode) -> Result { + // TODO: Return stored symlink target + Err(Error::EINVAL) + } + + fn follow_link(&self, inode: &Inode) -> Result> { + // TODO: Follow symlink to target + Err(Error::EINVAL) + } + + fn truncate(&self, inode: &Inode, size: u64) -> Result<()> { + inode.set_size(size); + Ok(()) + } + + fn getxattr(&self, inode: &Inode, name: &str) -> Result> { + Err(Error::ENODATA) + } + + fn setxattr(&self, inode: &Inode, name: &str, value: &[u8], flags: u32) -> Result<()> { + Err(Error::ENOSYS) + } + + fn listxattr(&self, inode: &Inode) -> Result> { + Ok(Vec::new()) + } + + fn removexattr(&self, inode: &Inode, name: &str) -> Result<()> { + Err(Error::ENODATA) + } +} + +/// Mount RAM filesystem +pub fn mount_ramfs(_dev_name: &str, _flags: u32, _data: Option<&str>) -> Result> { + let mut sb = SuperBlock::new("ramfs")?; + sb.s_magic = 0x858458f6; // RAMFS magic + sb.set_operations(Arc::new(RamFsSuperOps)); + + let ramfs = Box::leak(Box::new(RamFs::new())); + sb.s_fs_info = Some(ramfs as *mut RamFs as *mut u8); + + // Create root directory + let root_inode = ramfs.create_inode(mode::S_IFDIR | 0o755); + let root_dentry = Arc::new(Dentry::new(String::from("/"), Some(root_inode))); + + let sb = Arc::new(sb); + Ok(sb) +} + +/// RAM filesystem superblock operations +pub struct RamFsSuperOps; + +impl SuperOperations for RamFsSuperOps { + fn alloc_inode(&self, sb: &SuperBlock) -> Result> { + let ramfs = unsafe { &*(sb.s_fs_info.unwrap() as *const RamFs) }; + Ok(ramfs.create_inode(0o644)) + } + + fn destroy_inode(&self, inode: &Inode) -> Result<()> { + Ok(()) + } + + fn write_inode(&self, inode: &Inode, sync: bool) -> Result<()> { + // RAM filesystem doesn't need to write inodes + Ok(()) + } + + fn delete_inode(&self, inode: &Inode) -> Result<()> { + Ok(()) + } + + fn put_super(&self, sb: &SuperBlock) -> Result<()> { + if let Some(fs_info) = sb.s_fs_info { + unsafe { + let ramfs = Box::from_raw(fs_info as *mut RamFs); + drop(ramfs); + } + } + Ok(()) + } + + fn write_super(&self, sb: &SuperBlock) -> Result<()> { + // Nothing to write for RAM filesystem + Ok(()) + } + + fn sync_fs(&self, sb: &SuperBlock, wait: bool) -> Result<()> { + // Nothing to sync for RAM filesystem + Ok(()) + } + + fn freeze_fs(&self, sb: &SuperBlock) -> Result<()> { + Ok(()) + } + + fn unfreeze_fs(&self, sb: &SuperBlock) -> Result<()> { + Ok(()) + } + + fn statfs(&self, sb: &SuperBlock) -> Result { + Ok(KStatFs { + f_type: sb.s_magic as u64, + f_bsize: sb.s_blocksize as u64, + f_blocks: 0, // Unlimited + f_bfree: 0, // Unlimited + f_bavail: 0, // Unlimited + f_files: 0, // Dynamic + f_ffree: 0, // Unlimited + f_fsid: [0, 0], + f_namelen: NAME_MAX as u64, + f_frsize: sb.s_blocksize as u64, + f_flags: 0, + f_spare: [0; 4], + }) + } + + fn remount_fs(&self, sb: &SuperBlock, flags: u32, data: Option<&str>) -> Result<()> { + sb.s_flags.store(flags, Ordering::Relaxed); + Ok(()) + } + + fn show_options(&self, sb: &SuperBlock) -> Result { + Ok(String::new()) + } +} + +/// Kill RAM filesystem superblock +pub fn kill_ramfs(sb: &SuperBlock) -> Result<()> { + if let Some(ref ops) = sb.s_op { + ops.put_super(sb) + } else { + Ok(()) + } +} + +/// Register RAM filesystem +pub fn register_ramfs() -> Result<()> { + let ramfs_type = FileSystemType::new( + String::from("ramfs"), + |_fstype, flags, _dev_name, data| mount_ramfs(_dev_name, flags, data), + |sb| kill_ramfs(sb), + ); + + // TODO: Register with VFS + crate::console::print_info("Registered ramfs filesystem\n"); + Ok(()) +} diff --git a/kernel/src/fs/super_block.rs b/kernel/src/fs/super_block.rs new file mode 100644 index 0000000..8b7a5b8 --- /dev/null +++ b/kernel/src/fs/super_block.rs @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Superblock abstraction - Linux compatible + +use crate::error::{Error, Result}; +use crate::sync::{Arc, Mutex, RwLock}; +use crate::device::DeviceNumber; +use alloc::string::String; +use alloc::vec::Vec; +use core::sync::atomic::{AtomicU32, AtomicU64, Ordering}; + +/// Superblock structure - similar to Linux struct super_block +pub struct SuperBlock { + /// Device number + pub s_dev: DeviceNumber, + /// Block size + pub s_blocksize: u32, + /// Block size bits + pub s_blocksize_bits: u8, + /// Maximum file size + pub s_maxbytes: u64, + /// File system type + pub s_type: Option>, + /// Superblock operations + pub s_op: Option>, + /// Root dentry + pub s_root: Option>, + /// Mount point + pub s_mount: Option>, + /// File system flags + pub s_flags: AtomicU32, + /// File system magic number + pub s_magic: u32, + /// List of inodes + pub s_inodes: Mutex>>, + /// Next inode number + pub s_next_ino: AtomicU64, + /// Private data + pub s_fs_info: Option<*mut u8>, + /// Dirty inodes + pub s_dirty: Mutex>>, + /// Reference count + pub s_count: AtomicU32, + /// File system name + pub s_id: String, +} + +impl SuperBlock { + /// Create a new superblock + pub fn new(fstype: &str) -> Result { + Ok(Self { + s_dev: DeviceNumber::new(0, 0), + s_blocksize: 4096, + s_blocksize_bits: 12, + s_maxbytes: 0x7fffffffffffffff, + s_type: None, + s_op: None, + s_root: None, + s_mount: None, + s_flags: AtomicU32::new(0), + s_magic: 0, + s_inodes: Mutex::new(Vec::new()), + s_next_ino: AtomicU64::new(1), + s_fs_info: None, + s_dirty: Mutex::new(Vec::new()), + s_count: AtomicU32::new(1), + s_id: String::from(fstype), + }) + } + + /// Set superblock operations + pub fn set_operations(&mut self, ops: Arc) { + self.s_op = Some(ops); + } + + /// Allocate a new inode + pub fn alloc_inode(&self, mode: u32) -> Result> { + let ino = self.s_next_ino.fetch_add(1, Ordering::Relaxed); + let mut inode = super::Inode::new(ino, mode); + inode.i_sb = Some(Arc::new(unsafe { + // SAFETY: We're creating a weak reference to avoid cycles + core::ptr::read(self as *const Self) + })); + + let inode = Arc::new(inode); + + // Add to inode list + let mut inodes = self.s_inodes.lock(); + inodes.push(inode.clone()); + + Ok(inode) + } + + /// Write superblock to disk + pub fn write_super(&self) -> Result<()> { + if let Some(ref ops) = self.s_op { + ops.write_super(self) + } else { + Ok(()) + } + } + + /// Put superblock (decrement reference count) + pub fn put_super(&self) -> Result<()> { + let old_count = self.s_count.fetch_sub(1, Ordering::Relaxed); + if old_count == 1 { + // Last reference, cleanup + if let Some(ref ops) = self.s_op { + ops.put_super(self)?; + } + } + Ok(()) + } + + /// Get superblock statistics + pub fn statfs(&self) -> Result { + if let Some(ref ops) = self.s_op { + ops.statfs(self) + } else { + // Default statistics + Ok(super::KStatFs { + f_type: self.s_magic as u64, + f_bsize: self.s_blocksize as u64, + f_blocks: 0, + f_bfree: 0, + f_bavail: 0, + f_files: 0, + f_ffree: 0, + f_fsid: [0, 0], + f_namelen: super::NAME_MAX as u64, + f_frsize: self.s_blocksize as u64, + f_flags: self.s_flags.load(Ordering::Relaxed) as u64, + f_spare: [0; 4], + }) + } + } + + /// Sync filesystem + pub fn sync_fs(&self, wait: bool) -> Result<()> { + if let Some(ref ops) = self.s_op { + ops.sync_fs(self, wait) + } else { + Ok(()) + } + } + + /// Freeze filesystem + pub fn freeze_fs(&self) -> Result<()> { + if let Some(ref ops) = self.s_op { + ops.freeze_fs(self) + } else { + Ok(()) + } + } + + /// Unfreeze filesystem + pub fn unfreeze_fs(&self) -> Result<()> { + if let Some(ref ops) = self.s_op { + ops.unfreeze_fs(self) + } else { + Ok(()) + } + } + + /// Mark inode as dirty + pub fn mark_dirty(&self, inode: Arc) { + let mut dirty = self.s_dirty.lock(); + dirty.push(inode); + } + + /// Write back dirty inodes + pub fn write_dirty(&self) -> Result<()> { + let mut dirty = self.s_dirty.lock(); + for inode in dirty.drain(..) { + // TODO: Write inode to disk + } + Ok(()) + } +} + +unsafe impl Send for SuperBlock {} +unsafe impl Sync for SuperBlock {} + +/// Superblock operations trait - similar to Linux super_operations +pub trait SuperOperations: Send + Sync { + /// Allocate inode + fn alloc_inode(&self, sb: &SuperBlock) -> Result>; + + /// Destroy inode + fn destroy_inode(&self, inode: &super::Inode) -> Result<()>; + + /// Write inode + fn write_inode(&self, inode: &super::Inode, sync: bool) -> Result<()>; + + /// Delete inode + fn delete_inode(&self, inode: &super::Inode) -> Result<()>; + + /// Put superblock + fn put_super(&self, sb: &SuperBlock) -> Result<()>; + + /// Write superblock + fn write_super(&self, sb: &SuperBlock) -> Result<()>; + + /// Sync filesystem + fn sync_fs(&self, sb: &SuperBlock, wait: bool) -> Result<()>; + + /// Freeze filesystem + fn freeze_fs(&self, sb: &SuperBlock) -> Result<()>; + + /// Unfreeze filesystem + fn unfreeze_fs(&self, sb: &SuperBlock) -> Result<()>; + + /// Get filesystem statistics + fn statfs(&self, sb: &SuperBlock) -> Result; + + /// Remount filesystem + fn remount_fs(&self, sb: &SuperBlock, flags: u32, data: Option<&str>) -> Result<()>; + + /// Show mount options + fn show_options(&self, sb: &SuperBlock) -> Result; +} + +/// File system type structure - similar to Linux file_system_type +pub struct FileSystemType { + /// File system name + pub name: String, + /// File system flags + pub fs_flags: u32, + /// Mount function + pub mount: fn(fstype: &FileSystemType, flags: u32, dev_name: &str, data: Option<&str>) -> Result>, + /// Kill superblock function + pub kill_sb: fn(sb: &SuperBlock) -> Result<()>, + /// Owner module + pub owner: Option<&'static str>, +} + +impl FileSystemType { + /// Create a new file system type + pub fn new( + name: String, + mount: fn(&FileSystemType, u32, &str, Option<&str>) -> Result>, + kill_sb: fn(&SuperBlock) -> Result<()>, + ) -> Self { + Self { + name, + fs_flags: 0, + mount, + kill_sb, + owner: None, + } + } + + /// Mount this filesystem type + pub fn do_mount(&self, flags: u32, dev_name: &str, data: Option<&str>) -> Result> { + (self.mount)(self, flags, dev_name, data) + } + + /// Kill superblock + pub fn do_kill_sb(&self, sb: &SuperBlock) -> Result<()> { + (self.kill_sb)(sb) + } +} + +/// Generic superblock operations +pub struct GenericSuperOps; + +impl SuperOperations for GenericSuperOps { + fn alloc_inode(&self, sb: &SuperBlock) -> Result> { + sb.alloc_inode(0o644) + } + + fn destroy_inode(&self, inode: &super::Inode) -> Result<()> { + Ok(()) + } + + fn write_inode(&self, inode: &super::Inode, sync: bool) -> Result<()> { + Ok(()) + } + + fn delete_inode(&self, inode: &super::Inode) -> Result<()> { + Ok(()) + } + + fn put_super(&self, sb: &SuperBlock) -> Result<()> { + Ok(()) + } + + fn write_super(&self, sb: &SuperBlock) -> Result<()> { + Ok(()) + } + + fn sync_fs(&self, sb: &SuperBlock, wait: bool) -> Result<()> { + sb.write_dirty() + } + + fn freeze_fs(&self, sb: &SuperBlock) -> Result<()> { + Ok(()) + } + + fn unfreeze_fs(&self, sb: &SuperBlock) -> Result<()> { + Ok(()) + } + + fn statfs(&self, sb: &SuperBlock) -> Result { + sb.statfs() + } + + fn remount_fs(&self, sb: &SuperBlock, flags: u32, data: Option<&str>) -> Result<()> { + sb.s_flags.store(flags, Ordering::Relaxed); + Ok(()) + } + + fn show_options(&self, sb: &SuperBlock) -> Result { + Ok(String::new()) + } +} + +/// File system flags +pub const FS_REQUIRES_DEV: u32 = 1; +pub const FS_BINARY_MOUNTDATA: u32 = 2; +pub const FS_HAS_SUBTYPE: u32 = 4; +pub const FS_USERNS_MOUNT: u32 = 8; +pub const FS_DISALLOW_NOTIFY_PERM: u32 = 16; +pub const FS_RENAME_DOES_D_MOVE: u32 = 32; + +/// Mount flags +pub const MS_RDONLY: u32 = 1; +pub const MS_NOSUID: u32 = 2; +pub const MS_NODEV: u32 = 4; +pub const MS_NOEXEC: u32 = 8; +pub const MS_SYNCHRONOUS: u32 = 16; +pub const MS_REMOUNT: u32 = 32; +pub const MS_MANDLOCK: u32 = 64; +pub const MS_DIRSYNC: u32 = 128; +pub const MS_NOATIME: u32 = 1024; +pub const MS_NODIRATIME: u32 = 2048; +pub const MS_BIND: u32 = 4096; +pub const MS_MOVE: u32 = 8192; +pub const MS_REC: u32 = 16384; +pub const MS_SILENT: u32 = 32768; +pub const MS_POSIXACL: u32 = 1 << 16; +pub const MS_UNBINDABLE: u32 = 1 << 17; +pub const MS_PRIVATE: u32 = 1 << 18; +pub const MS_SLAVE: u32 = 1 << 19; +pub const MS_SHARED: u32 = 1 << 20; +pub const MS_RELATIME: u32 = 1 << 21; +pub const MS_KERNMOUNT: u32 = 1 << 22; +pub const MS_I_VERSION: u32 = 1 << 23; +pub const MS_STRICTATIME: u32 = 1 << 24; diff --git a/kernel/src/init.rs b/kernel/src/init.rs new file mode 100644 index 0000000..ce108ca --- /dev/null +++ b/kernel/src/init.rs @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Kernel initialization + +use crate::error::Result; +use crate::{info, error}; + +/// Early kernel initialization +pub fn early_init() { + info!("Starting Rust Kernel v{}", crate::VERSION); + info!("Early initialization phase"); + + // Initialize basic console output + if let Err(e) = crate::console::init() { + // Can't print error since console isn't initialized yet + loop {} + } + + info!("Console initialized"); +} + +/// Main kernel initialization +pub fn main_init() -> ! { + info!("Main initialization phase"); + + // Initialize memory management + if let Err(e) = crate::memory::init() { + error!("Failed to initialize memory management: {}", e); + panic!("Memory initialization failed"); + } + info!("Memory management initialized"); + + // Initialize interrupt handling + if let Err(e) = crate::interrupt::init() { + error!("Failed to initialize interrupts: {}", e); + panic!("Interrupt initialization failed"); + } + info!("Interrupt handling initialized"); + + // Initialize scheduler + if let Err(e) = crate::scheduler::init() { + error!("Failed to initialize scheduler: {}", e); + panic!("Scheduler initialization failed"); + } + info!("Scheduler initialized"); + + // Initialize device subsystem + if let Err(e) = crate::device::init() { + error!("Failed to initialize devices: {}", e); + panic!("Device initialization failed"); + } + info!("Device subsystem initialized"); + + // Initialize VFS (Virtual File System) + if let Err(e) = crate::fs::init() { + error!("Failed to initialize VFS: {}", e); + panic!("VFS initialization failed"); + } + info!("VFS initialized"); + + info!("Kernel initialization completed"); + info!("Starting idle loop"); + + // Start the idle loop + idle_loop(); +} + +/// Kernel idle loop +fn idle_loop() -> ! { + loop { + // TODO: Power management - halt CPU until interrupt + #[cfg(target_arch = "x86_64")] + unsafe { + core::arch::asm!("hlt"); + } + + #[cfg(not(target_arch = "x86_64"))] + core::hint::spin_loop(); + + // TODO: Check for scheduled tasks + // TODO: Handle background tasks + } +} diff --git a/kernel/src/interrupt.rs b/kernel/src/interrupt.rs new file mode 100644 index 0000000..eb8604b --- /dev/null +++ b/kernel/src/interrupt.rs @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Interrupt handling compatible with Linux kernel + +use crate::error::{Error, Result}; +use crate::sync::Spinlock; +use crate::types::Irq; +use alloc::{vec::Vec, collections::BTreeMap}; +use core::fmt; + +/// IRQ flags - compatible with Linux kernel +pub mod irq_flags { + pub const IRQF_SHARED: u32 = 0x00000080; + pub const IRQF_TRIGGER_NONE: u32 = 0x00000000; + pub const IRQF_TRIGGER_RISING: u32 = 0x00000001; + pub const IRQF_TRIGGER_FALLING: u32 = 0x00000002; + pub const IRQF_TRIGGER_HIGH: u32 = 0x00000004; + pub const IRQF_TRIGGER_LOW: u32 = 0x00000008; + pub const IRQF_ONESHOT: u32 = 0x00002000; + pub const IRQF_NO_SUSPEND: u32 = 0x00004000; + pub const IRQF_FORCE_RESUME: u32 = 0x00008000; + pub const IRQF_NO_THREAD: u32 = 0x00010000; + pub const IRQF_EARLY_RESUME: u32 = 0x00020000; + pub const IRQF_COND_SUSPEND: u32 = 0x00040000; +} + +/// Interrupt return values - Linux compatible +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IrqReturn { + None, // IRQ_NONE + Handled, // IRQ_HANDLED + WakeThread, // IRQ_WAKE_THREAD +} + +impl fmt::Display for IrqReturn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + IrqReturn::None => write!(f, "IRQ_NONE"), + IrqReturn::Handled => write!(f, "IRQ_HANDLED"), + IrqReturn::WakeThread => write!(f, "IRQ_WAKE_THREAD"), + } + } +} + +/// Interrupt handler function type - Linux compatible +pub type IrqHandler = fn(irq: u32, dev_id: *mut u8) -> IrqReturn; + +/// Interrupt action structure - similar to Linux irqaction +#[derive(Debug)] +pub struct IrqAction { + pub handler: IrqHandler, + pub flags: u32, + pub name: &'static str, + pub dev_id: *mut u8, + pub next: Option>, +} + +impl IrqAction { + pub fn new(handler: IrqHandler, flags: u32, name: &'static str, dev_id: *mut u8) -> Self { + Self { + handler, + flags, + name, + dev_id, + next: None, + } + } +} + +/// Interrupt descriptor - similar to Linux irq_desc +#[derive(Debug)] +pub struct IrqDescriptor { + pub irq: u32, + pub action: Option>, + pub depth: u32, // nesting depth for disable/enable + pub wake_depth: u32, // wake nesting depth + pub irq_count: u64, // number of interrupts + pub irqs_unhandled: u64, // number of unhandled interrupts + pub name: &'static str, + pub status: u32, +} + +impl IrqDescriptor { + pub fn new(irq: u32, name: &'static str) -> Self { + Self { + irq, + action: None, + depth: 1, // starts disabled + wake_depth: 0, + irq_count: 0, + irqs_unhandled: 0, + name, + status: 0, + } + } + + pub fn is_enabled(&self) -> bool { + self.depth == 0 + } + + pub fn enable(&mut self) { + if self.depth > 0 { + self.depth -= 1; + } + } + + pub fn disable(&mut self) { + self.depth += 1; + } +} + +/// Global interrupt subsystem +static INTERRUPT_SUBSYSTEM: Spinlock = Spinlock::new(InterruptSubsystem::new()); + +/// Interrupt subsystem state +struct InterruptSubsystem { + descriptors: BTreeMap, + enabled: bool, +} + +impl InterruptSubsystem { + const fn new() -> Self { + Self { + descriptors: BTreeMap::new(), + enabled: false, + } + } + + fn add_descriptor(&mut self, desc: IrqDescriptor) { + let irq = desc.irq; + self.descriptors.insert(irq, desc); + } + + fn get_descriptor(&mut self, irq: u32) -> Option<&mut IrqDescriptor> { + self.descriptors.get_mut(&irq) + } +} + +/// Initialize interrupt handling +pub fn init() -> Result<()> { + let mut subsystem = INTERRUPT_SUBSYSTEM.lock(); + + // Set up standard x86 interrupt vectors + init_standard_interrupts(&mut subsystem)?; + + // Initialize interrupt controller (PIC/APIC) + init_interrupt_controller()?; + + // Set up exception handlers + init_exception_handlers()?; + + subsystem.enabled = true; + crate::info!("Interrupt subsystem initialized"); + + Ok(()) +} + +/// Initialize standard x86 interrupts +fn init_standard_interrupts(subsystem: &mut InterruptSubsystem) -> Result<()> { + // Timer interrupt (IRQ 0) + let timer_desc = IrqDescriptor::new(0, "timer"); + subsystem.add_descriptor(timer_desc); + + // Keyboard interrupt (IRQ 1) + let keyboard_desc = IrqDescriptor::new(1, "keyboard"); + subsystem.add_descriptor(keyboard_desc); + + // Cascade for slave PIC (IRQ 2) + let cascade_desc = IrqDescriptor::new(2, "cascade"); + subsystem.add_descriptor(cascade_desc); + + // Serial port 2/4 (IRQ 3) + let serial_desc = IrqDescriptor::new(3, "serial"); + subsystem.add_descriptor(serial_desc); + + // Serial port 1/3 (IRQ 4) + let serial2_desc = IrqDescriptor::new(4, "serial"); + subsystem.add_descriptor(serial2_desc); + + // Parallel port (IRQ 7) + let parallel_desc = IrqDescriptor::new(7, "parallel"); + subsystem.add_descriptor(parallel_desc); + + // Real-time clock (IRQ 8) + let rtc_desc = IrqDescriptor::new(8, "rtc"); + subsystem.add_descriptor(rtc_desc); + + // Mouse (IRQ 12) + let mouse_desc = IrqDescriptor::new(12, "mouse"); + subsystem.add_descriptor(mouse_desc); + + // IDE primary (IRQ 14) + let ide1_desc = IrqDescriptor::new(14, "ide"); + subsystem.add_descriptor(ide1_desc); + + // IDE secondary (IRQ 15) + let ide2_desc = IrqDescriptor::new(15, "ide"); + subsystem.add_descriptor(ide2_desc); + + Ok(()) +} + +/// Initialize interrupt controller +fn init_interrupt_controller() -> Result<()> { + // TODO: Initialize PIC or APIC + // For now, just set up basic PIC configuration + unsafe { + // Remap PIC interrupts to avoid conflicts with CPU exceptions + crate::arch::x86_64::pic::init_pic(); + } + Ok(()) +} + +/// Initialize exception handlers +fn init_exception_handlers() -> Result<()> { + // TODO: Set up IDT with exception handlers + // For now, placeholder + Ok(()) +} + +/// Register an interrupt handler - Linux compatible +pub fn request_irq( + irq: u32, + handler: IrqHandler, + flags: u32, + name: &'static str, + dev_id: *mut u8, +) -> Result<()> { + let mut subsystem = INTERRUPT_SUBSYSTEM.lock(); + + if let Some(desc) = subsystem.get_descriptor(irq) { + let action = IrqAction::new(handler, flags, name, dev_id); + + // Check if IRQ is shared + if flags & irq_flags::IRQF_SHARED != 0 { + // Add to action chain + let mut current = &mut desc.action; + while let Some(ref mut act) = current { + current = &mut act.next; + } + *current = Some(Box::new(action)); + } else { + // Replace existing action + desc.action = Some(Box::new(action)); + } + + // Enable the interrupt + desc.enable(); + + crate::info!("Registered IRQ {} handler: {}", irq, name); + Ok(()) + } else { + Err(Error::InvalidArgument) + } +} + +/// Unregister an interrupt handler - Linux compatible +pub fn free_irq(irq: u32, dev_id: *mut u8) -> Result<()> { + let mut subsystem = INTERRUPT_SUBSYSTEM.lock(); + + if let Some(desc) = subsystem.get_descriptor(irq) { + // Remove action with matching dev_id + let mut current = &mut desc.action; + let mut found = false; + + while let Some(ref mut action) = current { + if action.dev_id == dev_id { + *current = action.next.take(); + found = true; + break; + } + current = &mut action.next; + } + + if found { + // If no more actions, disable the interrupt + if desc.action.is_none() { + desc.disable(); + } + + crate::info!("Freed IRQ {} handler", irq); + Ok(()) + } else { + Err(Error::NotFound) + } + } else { + Err(Error::InvalidArgument) + } +} + +/// Enable interrupts globally +pub fn enable() { + #[cfg(target_arch = "x86_64")] + unsafe { + core::arch::asm!("sti"); + } +} + +/// Disable interrupts globally +pub fn disable() { + #[cfg(target_arch = "x86_64")] + unsafe { + core::arch::asm!("cli"); + } +} + +/// Enable a specific interrupt line +pub fn enable_irq(irq: u32) -> Result<()> { + let mut subsystem = INTERRUPT_SUBSYSTEM.lock(); + + if let Some(desc) = subsystem.get_descriptor(irq) { + desc.enable(); + crate::debug!("Enabled IRQ {}", irq); + Ok(()) + } else { + Err(Error::InvalidArgument) + } +} + +/// Disable a specific interrupt line +pub fn disable_irq(irq: u32) -> Result<()> { + let mut subsystem = INTERRUPT_SUBSYSTEM.lock(); + + if let Some(desc) = subsystem.get_descriptor(irq) { + desc.disable(); + crate::debug!("Disabled IRQ {}", irq); + Ok(()) + } else { + Err(Error::InvalidArgument) + } +} + +/// Handle an interrupt - called from low-level interrupt handlers +pub fn handle_interrupt(irq: u32) { + let mut subsystem = INTERRUPT_SUBSYSTEM.lock(); + + if let Some(desc) = subsystem.get_descriptor(irq) { + desc.irq_count += 1; + + if !desc.is_enabled() { + // Interrupt is disabled, shouldn't happen + crate::warn!("Received disabled interrupt {}", irq); + return; + } + + // Call all handlers in the action chain + let mut current = desc.action.as_ref(); + let mut handled = false; + + while let Some(action) = current { + let result = (action.handler)(irq, action.dev_id); + match result { + IrqReturn::Handled => { + handled = true; + } + IrqReturn::WakeThread => { + handled = true; + // TODO: Wake threaded interrupt handler + } + IrqReturn::None => { + // Handler didn't handle this interrupt + } + } + current = action.next.as_ref(); + } + + if !handled { + desc.irqs_unhandled += 1; + if desc.irqs_unhandled > 100 { + crate::warn!("Too many unhandled interrupts on IRQ {}", irq); + } + } + } else { + crate::warn!("Spurious interrupt {}", irq); + } +} + +/// Get interrupt statistics +pub fn get_irq_stats() -> Vec<(u32, &'static str, u64, u64)> { + let subsystem = INTERRUPT_SUBSYSTEM.lock(); + let mut stats = Vec::new(); + + for (irq, desc) in &subsystem.descriptors { + stats.push((*irq, desc.name, desc.irq_count, desc.irqs_unhandled)); + } + + stats +} diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs new file mode 100644 index 0000000..4f7c3bb --- /dev/null +++ b/kernel/src/lib.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! The Rust kernel crate. +//! +//! This crate provides the core kernel APIs and functionality for the Rust kernel. +//! It is inspired by the Linux kernel's Rust infrastructure but designed as a +//! standalone kernel implementation. + +#![no_std] +#![feature(alloc_error_handler)] +#![feature(panic_info_message)] +#![feature(asm_const)] +#![feature(const_mut_refs)] +#![feature(custom_test_frameworks)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] + +extern crate alloc; + +pub mod allocator; +pub mod arch; +pub mod boot; +pub mod console; +pub mod cpu; +pub mod device; +pub mod driver; +pub mod error; +pub mod fs; +pub mod init; +pub mod interrupt; +pub mod memory; +pub mod module; +pub mod panic; +pub mod prelude; +pub mod process; +pub mod scheduler; +pub mod sync; +pub mod syscall; +pub mod task; +pub mod time; +pub mod types; + +use core::panic::PanicInfo; + +/// Kernel version information +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const NAME: &str = "Rust Kernel"; + +/// Kernel entry point called from architecture-specific code +#[no_mangle] +pub extern "C" fn kernel_main() -> ! { + init::early_init(); + init::main_init(); + + // Should not return from main_init + panic!("kernel_main returned unexpectedly"); +} + +/// Test runner for kernel tests +#[cfg(test)] +fn test_runner(tests: &[&dyn Fn()]) { + println!("Running {} tests", tests.len()); + for test in tests { + test(); + } + exit_qemu(QemuExitCode::Success); +} + +#[cfg(test)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +#[cfg(test)] +pub fn exit_qemu(exit_code: QemuExitCode) { + use arch::x86_64::port::Port; + + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } +} + +/// Kernel panic handler +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + panic::panic_handler(info) +} + +/// Global allocator error handler +#[alloc_error_handler] +fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { + panic!("allocation error: {:?}", layout) +} diff --git a/kernel/src/memory/allocator.rs b/kernel/src/memory/allocator.rs new file mode 100644 index 0000000..672996a --- /dev/null +++ b/kernel/src/memory/allocator.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Heap allocator implementation + +use crate::memory::ALLOCATOR; +use crate::types::{VirtAddr, PAGE_SIZE}; +use crate::error::Result; + +/// Heap start address (will be set during initialization) +static mut HEAP_START: usize = 0; +static mut HEAP_SIZE: usize = 0; + +/// Initialize the heap allocator +pub fn init() -> Result<()> { + // TODO: Get heap region from memory map + // For now, use a fixed region + let heap_start = 0x_4444_4444_0000; + let heap_size = 100 * 1024; // 100 KB + + unsafe { + HEAP_START = heap_start; + HEAP_SIZE = heap_size; + ALLOCATOR.lock().init(heap_start, heap_size); + } + + Ok(()) +} + +/// Get heap statistics +pub fn heap_stats() -> (usize, usize) { + unsafe { (HEAP_START, HEAP_SIZE) } +} diff --git a/kernel/src/memory/kmalloc.rs b/kernel/src/memory/kmalloc.rs new file mode 100644 index 0000000..cf060df --- /dev/null +++ b/kernel/src/memory/kmalloc.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Kernel memory allocation (kmalloc) + +use crate::error::Result; + +/// Allocate kernel memory +pub fn kmalloc(size: usize) -> Result<*mut u8> { + // TODO: implement proper kmalloc + Ok(core::ptr::null_mut()) +} + +/// Free kernel memory +pub fn kfree(ptr: *mut u8) { + // TODO: implement proper kfree +} diff --git a/kernel/src/memory/mod.rs b/kernel/src/memory/mod.rs new file mode 100644 index 0000000..9134180 --- /dev/null +++ b/kernel/src/memory/mod.rs @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Memory management subsystem + +pub mod allocator; +pub mod page; +pub mod vmalloc; +pub mod kmalloc; + +use crate::types::{PhysAddr, VirtAddr, Pfn}; +use crate::error::{Error, Result}; +use core::alloc::{GlobalAlloc, Layout}; +use linked_list_allocator::LockedHeap; + +/// GFP (Get Free Pages) flags - compatible with Linux kernel +pub mod gfp { + pub const GFP_KERNEL: u32 = 0; + pub const GFP_ATOMIC: u32 = 1; + pub const GFP_USER: u32 = 2; + pub const GFP_HIGHUSER: u32 = 3; + pub const GFP_DMA: u32 = 4; + pub const GFP_DMA32: u32 = 8; + pub const GFP_NOWAIT: u32 = 16; + pub const GFP_NOIO: u32 = 32; + pub const GFP_NOFS: u32 = 64; + pub const GFP_ZERO: u32 = 128; +} + +/// Global heap allocator +#[global_allocator] +static ALLOCATOR: LockedHeap = LockedHeap::empty(); + +/// Linux-compatible allocation flags +#[derive(Clone, Copy, PartialEq)] +pub struct AllocFlags(u32); + +impl AllocFlags { + pub const fn new(flags: u32) -> Self { + Self(flags) + } + + pub fn as_raw(self) -> u32 { + self.0 + } + + pub fn contains(self, flags: AllocFlags) -> bool { + (self.0 & flags.0) == flags.0 + } +} + +/// GFP flags constants +pub const GFP_KERNEL: AllocFlags = AllocFlags::new(gfp::GFP_KERNEL); +pub const GFP_ATOMIC: AllocFlags = AllocFlags::new(gfp::GFP_ATOMIC); +pub const GFP_USER: AllocFlags = AllocFlags::new(gfp::GFP_USER); + +/// Initialize the memory management subsystem with proper Linux-style initialization +pub fn init() -> Result<()> { + allocator::init()?; + page::init()?; + + // Initialize zone allocator + init_zones()?; + + // Set up buddy allocator + init_buddy_allocator()?; + + // Initialize slab allocator + init_slab_allocator()?; + + crate::info!("Memory management initialized"); + Ok(()) +} + +/// Initialize memory zones (DMA, Normal, High) +fn init_zones() -> Result<()> { + // TODO: Set up memory zones based on architecture + Ok(()) +} + +/// Initialize buddy allocator for page allocation +fn init_buddy_allocator() -> Result<()> { + // TODO: Set up buddy allocator + Ok(()) +} + +/// Initialize slab allocator for object caching +fn init_slab_allocator() -> Result<()> { + // TODO: Set up SLAB/SLUB allocator + Ok(()) +} + +/// Physical memory information +#[derive(Debug)] +pub struct MemoryInfo { + pub total_pages: usize, + pub free_pages: usize, + pub used_pages: usize, + pub kernel_pages: usize, +} + +/// Get current memory information +pub fn memory_info() -> MemoryInfo { + MemoryInfo { + total_pages: 0, // TODO: implement + free_pages: 0, + used_pages: 0, + kernel_pages: 0, + } +} + +/// Allocate a page of physical memory +pub fn alloc_page() -> Result { + page::alloc_page() +} + +/// Free a page of physical memory +pub fn free_page(addr: PhysAddr) { + page::free_page(addr) +} + +/// Map a virtual address to a physical address +pub fn map_page(virt: VirtAddr, phys: PhysAddr) -> Result<()> { + // TODO: implement page table mapping + Ok(()) +} + +/// Unmap a virtual address +pub fn unmap_page(virt: VirtAddr) -> Result<()> { + // TODO: implement page table unmapping + Ok(()) +} + +/// Convert virtual address to physical address +pub fn virt_to_phys(virt: VirtAddr) -> Result { + // TODO: implement address translation + Ok(PhysAddr::new(virt.as_usize())) +} + +/// Convert physical address to virtual address +pub fn phys_to_virt(phys: PhysAddr) -> Result { + // TODO: implement address translation + Ok(VirtAddr::new(phys.as_usize())) +} + +/// Page table entry +#[derive(Debug, Clone, Copy)] +pub struct PageTableEntry(pub u64); + +impl PageTableEntry { + pub const fn new() -> Self { + Self(0) + } + + pub fn present(self) -> bool { + self.0 & 1 != 0 + } + + pub fn writable(self) -> bool { + self.0 & 2 != 0 + } + + pub fn user_accessible(self) -> bool { + self.0 & 4 != 0 + } + + pub fn frame(self) -> Pfn { + Pfn((self.0 >> 12) as usize) + } + + pub fn set_present(&mut self, present: bool) { + if present { + self.0 |= 1; + } else { + self.0 &= !1; + } + } + + pub fn set_writable(&mut self, writable: bool) { + if writable { + self.0 |= 2; + } else { + self.0 &= !2; + } + } + + pub fn set_user_accessible(&mut self, user: bool) { + if user { + self.0 |= 4; + } else { + self.0 &= !4; + } + } + + pub fn set_frame(&mut self, frame: Pfn) { + self.0 = (self.0 & 0xfff) | ((frame.0 as u64) << 12); + } +} + +/// Page table +#[repr(align(4096))] +pub struct PageTable { + entries: [PageTableEntry; 512], +} + +impl PageTable { + pub const fn new() -> Self { + Self { + entries: [PageTableEntry::new(); 512], + } + } + + pub fn zero(&mut self) { + for entry in self.entries.iter_mut() { + *entry = PageTableEntry::new(); + } + } +} + +/// Memory mapping flags +bitflags::bitflags! { + pub struct MapFlags: u32 { + const READ = 1 << 0; + const WRITE = 1 << 1; + const EXECUTE = 1 << 2; + const USER = 1 << 3; + const GLOBAL = 1 << 4; + const CACHED = 1 << 5; + const DEVICE = 1 << 6; + } +} diff --git a/kernel/src/memory/page.rs b/kernel/src/memory/page.rs new file mode 100644 index 0000000..60a8f29 --- /dev/null +++ b/kernel/src/memory/page.rs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Page frame allocator + +use crate::types::{PhysAddr, Pfn}; +use crate::error::{Error, Result}; +use crate::sync::Spinlock; +use alloc::collections::BTreeSet; + +/// Page frame allocator +static PAGE_ALLOCATOR: Spinlock = Spinlock::new(PageAllocator::new()); + +/// Page allocator implementation +struct PageAllocator { + free_pages: BTreeSet, + total_pages: usize, + allocated_pages: usize, +} + +impl PageAllocator { + const fn new() -> Self { + Self { + free_pages: BTreeSet::new(), + total_pages: 0, + allocated_pages: 0, + } + } + + /// Add a range of pages to the free list + fn add_free_range(&mut self, start: Pfn, count: usize) { + for i in 0..count { + self.free_pages.insert(Pfn(start.0 + i)); + } + self.total_pages += count; + } + + /// Allocate a single page + fn alloc_page(&mut self) -> Result { + if let Some(pfn) = self.free_pages.iter().next().copied() { + self.free_pages.remove(&pfn); + self.allocated_pages += 1; + Ok(pfn) + } else { + Err(Error::OutOfMemory) + } + } + + /// Free a single page + fn free_page(&mut self, pfn: Pfn) { + if self.free_pages.insert(pfn) { + self.allocated_pages -= 1; + } + } + + /// Get statistics + fn stats(&self) -> (usize, usize, usize) { + (self.total_pages, self.allocated_pages, self.free_pages.len()) + } +} + +/// Initialize the page allocator +pub fn init() -> Result<()> { + let mut allocator = PAGE_ALLOCATOR.lock(); + + // TODO: Get memory map from bootloader/firmware + // For now, add a dummy range + let start_pfn = Pfn(0x1000); // Start at 16MB + let count = 0x10000; // 256MB worth of pages + + allocator.add_free_range(start_pfn, count); + + Ok(()) +} + +/// Allocate a page of physical memory +pub fn alloc_page() -> Result { + let mut allocator = PAGE_ALLOCATOR.lock(); + let pfn = allocator.alloc_page()?; + Ok(pfn.to_phys_addr()) +} + +/// Free a page of physical memory +pub fn free_page(addr: PhysAddr) { + let pfn = Pfn::from_phys_addr(addr); + let mut allocator = PAGE_ALLOCATOR.lock(); + allocator.free_page(pfn); +} + +/// Get page allocator statistics +pub fn stats() -> (usize, usize, usize) { + let allocator = PAGE_ALLOCATOR.lock(); + allocator.stats() +} diff --git a/kernel/src/memory/vmalloc.rs b/kernel/src/memory/vmalloc.rs new file mode 100644 index 0000000..9e0688d --- /dev/null +++ b/kernel/src/memory/vmalloc.rs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Virtual memory allocation + +use crate::error::Result; +use crate::types::VirtAddr; + +/// Allocate virtual memory +pub fn vmalloc(size: usize) -> Result { + // TODO: implement proper vmalloc + Ok(VirtAddr::new(0)) +} + +/// Free virtual memory +pub fn vfree(addr: VirtAddr) { + // TODO: implement proper vfree +} diff --git a/kernel/src/module.rs b/kernel/src/module.rs new file mode 100644 index 0000000..039f37a --- /dev/null +++ b/kernel/src/module.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Kernel module support + +use crate::error::Result; + +/// Module metadata +pub struct ThisModule { + pub name: &'static str, + pub author: &'static str, + pub description: &'static str, + pub license: &'static str, +} + +/// Trait for kernel modules +pub trait Module: Sized { + /// Initialize the module + fn init(module: &'static ThisModule) -> Result; + + /// Clean up the module + fn exit(module: &'static ThisModule) { + // Default implementation does nothing + } +} diff --git a/kernel/src/panic.rs b/kernel/src/panic.rs new file mode 100644 index 0000000..25dfafb --- /dev/null +++ b/kernel/src/panic.rs @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Kernel panic handler + +use core::panic::PanicInfo; +use core::fmt::Write; + +/// Panic handler +pub fn panic_handler(info: &PanicInfo) -> ! { + // Disable interrupts + #[cfg(target_arch = "x86_64")] + unsafe { + core::arch::asm!("cli"); + } + + // Print panic information + let mut writer = PanicWriter; + writeln!(writer, "\n\n=== KERNEL PANIC ===").ok(); + + if let Some(location) = info.location() { + writeln!( + writer, + "Panic at {}:{}:{}", + location.file(), + location.line(), + location.column() + ).ok(); + } + + if let Some(message) = info.message() { + writeln!(writer, "Message: {}", message).ok(); + } + + writeln!(writer, "===================\n").ok(); + + // TODO: Print stack trace + // TODO: Save panic information to log + + // Halt the system + loop { + #[cfg(target_arch = "x86_64")] + unsafe { + core::arch::asm!("hlt"); + } + + #[cfg(not(target_arch = "x86_64"))] + core::hint::spin_loop(); + } +} + +/// Writer for panic messages +struct PanicWriter; + +impl Write for PanicWriter { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + // Write directly to VGA buffer or serial port + for byte in s.bytes() { + #[cfg(target_arch = "x86_64")] + unsafe { + // Write to serial port (COM1) + core::arch::asm!( + "out dx, al", + in("dx") 0x3f8u16, + in("al") byte, + ); + } + } + Ok(()) + } +} diff --git a/kernel/src/prelude.rs b/kernel/src/prelude.rs new file mode 100644 index 0000000..dd3ee56 --- /dev/null +++ b/kernel/src/prelude.rs @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Kernel prelude - commonly used types and traits + +pub use crate::error::{Error, Result}; +pub use crate::types::*; +pub use crate::sync::{Mutex, RwLock, Spinlock}; +pub use crate::memory::{PhysAddr, VirtAddr, Page, PageTable}; +pub use crate::device::Device; +pub use crate::driver::{Driver, DriverOps}; +pub use crate::process::{Process, Thread}; +pub use crate::task::Task; + +// Re-export common alloc types +pub use alloc::{ + boxed::Box, + string::{String, ToString}, + vec::Vec, + collections::{BTreeMap, BTreeSet}, + format, + vec, +}; + +// Re-export core types +pub use core::{ + mem, + ptr, + slice, + str, + fmt, + result::Result as CoreResult, + option::Option::{self, None, Some}, +}; + +/// Print macros for kernel logging +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::console::_print(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! kprint { + ($($arg:tt)*) => ($crate::console::_kprint(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! kprintln { + () => ($crate::kprint!("\n")); + ($($arg:tt)*) => ($crate::kprint!("[KERNEL] {}\n", format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! debug { + ($($arg:tt)*) => { + #[cfg(feature = "debug")] + $crate::kprintln!("[DEBUG] {}", format_args!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => ($crate::kprintln!("[INFO] {}", format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! warn { + ($($arg:tt)*) => ($crate::kprintln!("[WARN] {}", format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! error { + ($($arg:tt)*) => ($crate::kprintln!("[ERROR] {}", format_args!($($arg)*))); +} + +/// Module definition macro +#[macro_export] +macro_rules! module { + ( + type: $type:ty, + name: $name:expr, + author: $author:expr, + description: $description:expr, + license: $license:expr $(,)? + ) => { + static __THIS_MODULE: $crate::module::ThisModule = $crate::module::ThisModule { + name: $name, + author: $author, + description: $description, + license: $license, + }; + + #[no_mangle] + pub extern "C" fn init_module() -> core::ffi::c_int { + match <$type as $crate::module::Module>::init(&__THIS_MODULE) { + Ok(_) => 0, + Err(e) => e.to_errno(), + } + } + + #[no_mangle] + pub extern "C" fn cleanup_module() { + <$type as $crate::module::Module>::exit(&__THIS_MODULE) + } + }; +} diff --git a/kernel/src/process.rs b/kernel/src/process.rs new file mode 100644 index 0000000..3be5a07 --- /dev/null +++ b/kernel/src/process.rs @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Process and thread management + +use crate::types::{Pid, Tid, Uid, Gid}; +use crate::error::{Error, Result}; +use crate::sync::Spinlock; +use crate::memory::VirtAddr; +use alloc::{string::String, vec::Vec, collections::BTreeMap}; +use core::sync::atomic::{AtomicU32, Ordering}; + +/// Process state - compatible with Linux kernel +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ProcessState { + Running, + Sleeping, + Stopped, + Zombie, + Dead, +} + +/// Process structure - similar to Linux task_struct +#[derive(Debug)] +pub struct Process { + pub pid: Pid, + pub parent: Option, + pub state: ProcessState, + pub uid: Uid, + pub gid: Gid, + pub name: String, + pub threads: Vec, + pub memory_map: Option, // Points to mm_struct equivalent + pub files: Vec, // File descriptor table + pub signal_pending: bool, + pub exit_code: i32, +} + +impl Process { + pub fn new(pid: Pid, name: String, uid: Uid, gid: Gid) -> Self { + Self { + pid, + parent: None, + state: ProcessState::Running, + uid, + gid, + name, + threads: Vec::new(), + memory_map: None, + files: Vec::new(), + signal_pending: false, + exit_code: 0, + } + } + + /// Add a thread to this process + pub fn add_thread(&mut self, thread: Thread) { + self.threads.push(thread); + } + + /// Get the main thread + pub fn main_thread(&self) -> Option<&Thread> { + self.threads.first() + } + + /// Set process state + pub fn set_state(&mut self, state: ProcessState) { + self.state = state; + } + + /// Check if process is running + pub fn is_running(&self) -> bool { + self.state == ProcessState::Running + } +} + +/// Thread structure - Linux-compatible +#[derive(Debug, Clone)] +pub struct Thread { + pub tid: Tid, + pub process_pid: Pid, + pub state: ProcessState, + pub stack_pointer: VirtAddr, + pub instruction_pointer: VirtAddr, + pub priority: i32, + pub nice: i32, // Nice value (-20 to 19) + pub cpu_time: u64, // Nanoseconds + pub context: ThreadContext, +} + +impl Thread { + pub fn new(tid: Tid, process_pid: Pid, priority: i32) -> Self { + Self { + tid, + process_pid, + state: ProcessState::Running, + stack_pointer: VirtAddr::new(0), + instruction_pointer: VirtAddr::new(0), + priority, + nice: 0, + cpu_time: 0, + context: ThreadContext::new(), + } + } + + /// Set thread state + pub fn set_state(&mut self, state: ProcessState) { + self.state = state; + } + + /// Update CPU time + pub fn add_cpu_time(&mut self, time: u64) { + self.cpu_time += time; + } +} + +/// Thread context for context switching +#[derive(Debug, Clone, Default)] +pub struct ThreadContext { + // x86_64 registers + pub rax: u64, + pub rbx: u64, + pub rcx: u64, + pub rdx: u64, + pub rsi: u64, + pub rdi: u64, + pub rbp: u64, + pub rsp: u64, + pub r8: u64, + pub r9: u64, + pub r10: u64, + pub r11: u64, + pub r12: u64, + pub r13: u64, + pub r14: u64, + pub r15: u64, + pub rip: u64, + pub rflags: u64, + // Segment registers + pub cs: u16, + pub ds: u16, + pub es: u16, + pub fs: u16, + pub gs: u16, + pub ss: u16, +} + +impl ThreadContext { + pub fn new() -> Self { + Self::default() + } +} + +/// Global process table +static PROCESS_TABLE: Spinlock = Spinlock::new(ProcessTable::new()); +static NEXT_PID: AtomicU32 = AtomicU32::new(1); +static NEXT_TID: AtomicU32 = AtomicU32::new(1); + +/// Process table implementation +struct ProcessTable { + processes: BTreeMap, + current_process: Option, +} + +impl ProcessTable { + const fn new() -> Self { + Self { + processes: BTreeMap::new(), + current_process: None, + } + } + + fn add_process(&mut self, process: Process) { + let pid = process.pid; + self.processes.insert(pid, process); + if self.current_process.is_none() { + self.current_process = Some(pid); + } + } + + fn get_process(&self, pid: Pid) -> Option<&Process> { + self.processes.get(&pid) + } + + fn get_process_mut(&mut self, pid: Pid) -> Option<&mut Process> { + self.processes.get_mut(&pid) + } + + fn remove_process(&mut self, pid: Pid) -> Option { + let process = self.processes.remove(&pid); + if self.current_process == Some(pid) { + self.current_process = self.processes.keys().next().copied(); + } + process + } + + fn list_processes(&self) -> Vec { + self.processes.keys().copied().collect() + } +} + +/// Allocate a new PID +pub fn allocate_pid() -> Pid { + Pid(NEXT_PID.fetch_add(1, Ordering::SeqCst)) +} + +/// Allocate a new TID +pub fn allocate_tid() -> Tid { + Tid(NEXT_TID.fetch_add(1, Ordering::SeqCst)) +} + +/// Create a new process +pub fn create_process(name: String, uid: Uid, gid: Gid) -> Result { + let pid = allocate_pid(); + let mut process = Process::new(pid, name, uid, gid); + + // Create main thread + let tid = allocate_tid(); + let main_thread = Thread::new(tid, pid, 0); + process.add_thread(main_thread); + + let mut table = PROCESS_TABLE.lock(); + table.add_process(process); + + Ok(pid) +} + +/// Get current process +pub fn current_process() -> Option { + let table = PROCESS_TABLE.lock(); + table.current_process +} + +/// Get process by PID +pub fn get_process(pid: Pid) -> Option { + let table = PROCESS_TABLE.lock(); + table.get_process(pid).cloned() +} + +/// Kill a process +pub fn kill_process(pid: Pid, signal: i32) -> Result<()> { + let mut table = PROCESS_TABLE.lock(); + if let Some(process) = table.get_process_mut(pid) { + process.set_state(ProcessState::Dead); + process.exit_code = signal; + Ok(()) + } else { + Err(Error::NotFound) + } +} + +/// List all processes +pub fn list_processes() -> Vec { + let table = PROCESS_TABLE.lock(); + table.list_processes() +} + +/// Initialize the process subsystem +pub fn init() -> Result<()> { + // Create kernel process (PID 0) + let _kernel_pid = create_process( + String::from("kernel"), + Uid(0), + Gid(0) + )?; + + // Create init process (PID 1) + let _init_pid = create_process( + String::from("init"), + Uid(0), + Gid(0) + )?; + + Ok(()) +} diff --git a/kernel/src/scheduler.rs b/kernel/src/scheduler.rs new file mode 100644 index 0000000..1b54844 --- /dev/null +++ b/kernel/src/scheduler.rs @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Task scheduler compatible with Linux kernel CFS (Completely Fair Scheduler) + +use crate::error::{Error, Result}; +use crate::process::{Process, Thread, ProcessState, ThreadContext}; +use crate::types::{Pid, Tid, Nanoseconds}; +use crate::sync::Spinlock; +use crate::time; +use alloc::{collections::{BTreeMap, VecDeque}, vec::Vec}; +use core::sync::atomic::{AtomicU64, Ordering}; + +/// Scheduler policies - Linux compatible +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SchedulerPolicy { + Normal = 0, // SCHED_NORMAL + Fifo = 1, // SCHED_FIFO + RoundRobin = 2, // SCHED_RR + Batch = 3, // SCHED_BATCH + Idle = 5, // SCHED_IDLE + Deadline = 6, // SCHED_DEADLINE +} + +/// Scheduler priority levels +pub const MAX_PRIO: i32 = 140; +pub const MAX_USER_RT_PRIO: i32 = 100; +pub const MAX_RT_PRIO: i32 = MAX_USER_RT_PRIO; +pub const DEFAULT_PRIO: i32 = MAX_RT_PRIO + 20; +pub const MIN_NICE: i32 = -20; +pub const MAX_NICE: i32 = 19; + +/// Convert nice value to priority +pub fn nice_to_prio(nice: i32) -> i32 { + DEFAULT_PRIO + nice +} + +/// Convert priority to nice value +pub fn prio_to_nice(prio: i32) -> i32 { + prio - DEFAULT_PRIO +} + +/// Scheduler entity - represents a schedulable unit +#[derive(Debug, Clone)] +pub struct SchedEntity { + pub tid: Tid, + pub policy: SchedulerPolicy, + pub priority: i32, + pub nice: i32, + pub vruntime: u64, // Virtual runtime for CFS + pub exec_start: u64, // Last execution start time + pub sum_exec_runtime: u64, // Total execution time + pub prev_sum_exec_runtime: u64, + pub load_weight: u32, // Load weight for this entity + pub runnable_weight: u32, + pub on_rq: bool, // On run queue? +} + +impl SchedEntity { + pub fn new(tid: Tid, policy: SchedulerPolicy, nice: i32) -> Self { + let priority = nice_to_prio(nice); + Self { + tid, + policy, + priority, + nice, + vruntime: 0, + exec_start: 0, + sum_exec_runtime: 0, + prev_sum_exec_runtime: 0, + load_weight: nice_to_weight(nice), + runnable_weight: nice_to_weight(nice), + on_rq: false, + } + } + + /// Update virtual runtime + pub fn update_vruntime(&mut self, delta: u64) { + // Virtual runtime is weighted by load + let weighted_delta = delta * 1024 / self.load_weight as u64; + self.vruntime += weighted_delta; + } +} + +/// Convert nice value to load weight (Linux compatible) +fn nice_to_weight(nice: i32) -> u32 { + // Linux nice-to-weight table (simplified) + match nice { + -20 => 88761, + -19 => 71755, + -18 => 56483, + -17 => 46273, + -16 => 36291, + -15 => 29154, + -14 => 23254, + -13 => 18705, + -12 => 14949, + -11 => 11916, + -10 => 9548, + -9 => 7620, + -8 => 6100, + -7 => 4904, + -6 => 3906, + -5 => 3121, + -4 => 2501, + -3 => 1991, + -2 => 1586, + -1 => 1277, + 0 => 1024, // Default weight + 1 => 820, + 2 => 655, + 3 => 526, + 4 => 423, + 5 => 335, + 6 => 272, + 7 => 215, + 8 => 172, + 9 => 137, + 10 => 110, + 11 => 87, + 12 => 70, + 13 => 56, + 14 => 45, + 15 => 36, + 16 => 29, + 17 => 23, + 18 => 18, + 19 => 15, + _ => 1024, // Default for out-of-range values + } +} + +/// CFS (Completely Fair Scheduler) run queue +#[derive(Debug)] +pub struct CfsRunQueue { + tasks_timeline: BTreeMap, // Red-black tree equivalent + min_vruntime: u64, + nr_running: u32, + load_weight: u64, + runnable_weight: u64, +} + +impl CfsRunQueue { + pub fn new() -> Self { + Self { + tasks_timeline: BTreeMap::new(), + min_vruntime: 0, + nr_running: 0, + load_weight: 0, + runnable_weight: 0, + } + } + + /// Add task to run queue + pub fn enqueue_task(&mut self, mut se: SchedEntity) { + // Place entity in timeline + if !se.on_rq { + se.on_rq = true; + + // Ensure vruntime is not too far behind + if se.vruntime < self.min_vruntime { + se.vruntime = self.min_vruntime; + } + + self.tasks_timeline.insert(se.vruntime, se.clone()); + self.nr_running += 1; + self.load_weight += se.load_weight as u64; + self.runnable_weight += se.runnable_weight as u64; + } + } + + /// Remove task from run queue + pub fn dequeue_task(&mut self, se: &SchedEntity) -> bool { + if se.on_rq { + self.tasks_timeline.remove(&se.vruntime); + self.nr_running -= 1; + self.load_weight -= se.load_weight as u64; + self.runnable_weight -= se.runnable_weight as u64; + true + } else { + false + } + } + + /// Pick next task to run + pub fn pick_next_task(&mut self) -> Option { + // Pick leftmost task (smallest vruntime) + if let Some((vruntime, se)) = self.tasks_timeline.iter().next() { + let se = se.clone(); + let vruntime = *vruntime; + self.tasks_timeline.remove(&vruntime); + self.nr_running -= 1; + self.load_weight -= se.load_weight as u64; + self.runnable_weight -= se.runnable_weight as u64; + + // Update min_vruntime + if let Some((next_vruntime, _)) = self.tasks_timeline.iter().next() { + self.min_vruntime = core::cmp::max(self.min_vruntime, *next_vruntime); + } else { + self.min_vruntime = se.vruntime; + } + + Some(se) + } else { + None + } + } + + /// Check if run queue is empty + pub fn is_empty(&self) -> bool { + self.nr_running == 0 + } +} + +/// Real-time run queue (for FIFO/RR scheduling) +#[derive(Debug)] +pub struct RtRunQueue { + active: [VecDeque; MAX_RT_PRIO as usize], + rt_nr_running: u32, + highest_prio: i32, +} + +impl RtRunQueue { + pub fn new() -> Self { + const EMPTY_QUEUE: VecDeque = VecDeque::new(); + Self { + active: [EMPTY_QUEUE; MAX_RT_PRIO as usize], + rt_nr_running: 0, + highest_prio: MAX_RT_PRIO, + } + } + + pub fn enqueue_task(&mut self, se: SchedEntity) { + let prio = se.priority as usize; + if prio < MAX_RT_PRIO as usize { + self.active[prio].push_back(se); + self.rt_nr_running += 1; + if (prio as i32) < self.highest_prio { + self.highest_prio = prio as i32; + } + } + } + + pub fn dequeue_task(&mut self, se: &SchedEntity) -> bool { + let prio = se.priority as usize; + if prio < MAX_RT_PRIO as usize { + if let Some(pos) = self.active[prio].iter().position(|x| x.tid == se.tid) { + self.active[prio].remove(pos); + self.rt_nr_running -= 1; + + // Update highest_prio if this queue is now empty + if self.active[prio].is_empty() && prio as i32 == self.highest_prio { + self.update_highest_prio(); + } + return true; + } + } + false + } + + pub fn pick_next_task(&mut self) -> Option { + if self.rt_nr_running > 0 { + for prio in self.highest_prio as usize..MAX_RT_PRIO as usize { + if let Some(se) = self.active[prio].pop_front() { + self.rt_nr_running -= 1; + + // For round-robin, re-enqueue at the end + if se.policy == SchedulerPolicy::RoundRobin { + self.active[prio].push_back(se.clone()); + self.rt_nr_running += 1; + } + + if self.active[prio].is_empty() && prio as i32 == self.highest_prio { + self.update_highest_prio(); + } + + return Some(se); + } + } + } + None + } + + fn update_highest_prio(&mut self) { + self.highest_prio = MAX_RT_PRIO; + for prio in 0..MAX_RT_PRIO as usize { + if !self.active[prio].is_empty() { + self.highest_prio = prio as i32; + break; + } + } + } + + pub fn is_empty(&self) -> bool { + self.rt_nr_running == 0 + } +} + +/// Per-CPU run queue +#[derive(Debug)] +pub struct RunQueue { + pub cpu: u32, + pub nr_running: u32, + pub current: Option, + pub cfs: CfsRunQueue, + pub rt: RtRunQueue, + pub idle_task: Option, + pub clock: u64, + pub clock_task: u64, +} + +impl RunQueue { + pub fn new(cpu: u32) -> Self { + Self { + cpu, + nr_running: 0, + current: None, + cfs: CfsRunQueue::new(), + rt: RtRunQueue::new(), + idle_task: None, + clock: 0, + clock_task: 0, + } + } + + /// Update run queue clock + pub fn update_rq_clock(&mut self) { + self.clock = time::get_time_ns(); + self.clock_task = self.clock; + } + + /// Enqueue a task + pub fn enqueue_task(&mut self, se: SchedEntity) { + match se.policy { + SchedulerPolicy::Normal | SchedulerPolicy::Batch | SchedulerPolicy::Idle => { + self.cfs.enqueue_task(se); + } + SchedulerPolicy::Fifo | SchedulerPolicy::RoundRobin => { + self.rt.enqueue_task(se); + } + SchedulerPolicy::Deadline => { + // TODO: implement deadline scheduler + self.cfs.enqueue_task(se); + } + } + self.nr_running += 1; + } + + /// Dequeue a task + pub fn dequeue_task(&mut self, se: &SchedEntity) -> bool { + let result = match se.policy { + SchedulerPolicy::Normal | SchedulerPolicy::Batch | SchedulerPolicy::Idle => { + self.cfs.dequeue_task(se) + } + SchedulerPolicy::Fifo | SchedulerPolicy::RoundRobin => { + self.rt.dequeue_task(se) + } + SchedulerPolicy::Deadline => { + self.cfs.dequeue_task(se) + } + }; + + if result { + self.nr_running -= 1; + } + result + } + + /// Pick next task to run + pub fn pick_next_task(&mut self) -> Option { + // Real-time tasks have priority + if let Some(se) = self.rt.pick_next_task() { + return Some(se); + } + + // Then CFS tasks + if let Some(se) = self.cfs.pick_next_task() { + return Some(se); + } + + // Finally, idle task + self.idle_task.clone() + } +} + +/// Global scheduler state +static SCHEDULER: Spinlock = Spinlock::new(Scheduler::new()); +static SCHEDULE_CLOCK: AtomicU64 = AtomicU64::new(0); + +/// Main scheduler structure +struct Scheduler { + run_queues: Vec, + nr_cpus: u32, + entities: BTreeMap, + need_resched: bool, +} + +impl Scheduler { + const fn new() -> Self { + Self { + run_queues: Vec::new(), + nr_cpus: 1, // Single CPU for now + entities: BTreeMap::new(), + need_resched: false, + } + } + + fn init(&mut self) -> Result<()> { + // Create run queues for each CPU + for cpu in 0..self.nr_cpus { + let mut rq = RunQueue::new(cpu); + + // Create idle task for this CPU + let idle_se = SchedEntity::new( + crate::process::allocate_tid(), + SchedulerPolicy::Idle, + MAX_NICE + ); + rq.idle_task = Some(idle_se); + + self.run_queues.push(rq); + } + Ok(()) + } + + fn add_task(&mut self, tid: Tid, policy: SchedulerPolicy, nice: i32) { + let se = SchedEntity::new(tid, policy, nice); + self.entities.insert(tid, se.clone()); + + // Add to CPU 0's run queue for simplicity + if let Some(rq) = self.run_queues.get_mut(0) { + rq.enqueue_task(se); + } + } + + fn remove_task(&mut self, tid: Tid) { + if let Some(se) = self.entities.remove(&tid) { + // Remove from all run queues + for rq in &mut self.run_queues { + rq.dequeue_task(&se); + } + } + } + + fn schedule(&mut self) -> Option { + // Simple single-CPU scheduling for now + if let Some(rq) = self.run_queues.get_mut(0) { + rq.update_rq_clock(); + + if let Some(se) = rq.pick_next_task() { + rq.current = Some(se.clone()); + return Some(se.tid); + } + } + None + } + + fn set_need_resched(&mut self) { + self.need_resched = true; + } + + fn clear_need_resched(&mut self) { + self.need_resched = false; + } +} + +/// Initialize the scheduler +pub fn init() -> Result<()> { + let mut scheduler = SCHEDULER.lock(); + scheduler.init()?; + + crate::info!("Scheduler initialized with {} CPUs", scheduler.nr_cpus); + Ok(()) +} + +/// Add a task to the scheduler +pub fn add_task(tid: Tid, policy: SchedulerPolicy, nice: i32) { + let mut scheduler = SCHEDULER.lock(); + scheduler.add_task(tid, policy, nice); +} + +/// Remove a task from the scheduler +pub fn remove_task(tid: Tid) { + let mut scheduler = SCHEDULER.lock(); + scheduler.remove_task(tid); +} + +/// Schedule the next task +pub fn schedule() -> Option { + let mut scheduler = SCHEDULER.lock(); + let result = scheduler.schedule(); + scheduler.clear_need_resched(); + result +} + +/// Yield the current thread +pub fn yield_now() { + let mut scheduler = SCHEDULER.lock(); + scheduler.set_need_resched(); + // In a real implementation, this would trigger a context switch +} + +/// Sleep for a given number of milliseconds +pub fn sleep_ms(ms: u64) { + // TODO: implement proper sleep mechanism with timer integration + // For now, just yield + yield_now(); +} + +/// Set scheduler policy for a task +pub fn set_scheduler_policy(tid: Tid, policy: SchedulerPolicy, nice: i32) -> Result<()> { + let mut scheduler = SCHEDULER.lock(); + + if let Some(se) = scheduler.entities.get_mut(&tid) { + se.policy = policy; + se.nice = nice; + se.priority = nice_to_prio(nice); + se.load_weight = nice_to_weight(nice); + se.runnable_weight = nice_to_weight(nice); + Ok(()) + } else { + Err(Error::NotFound) + } +} + +/// Get scheduler statistics +pub fn get_scheduler_stats() -> (u32, u32, bool) { + let scheduler = SCHEDULER.lock(); + let total_tasks = scheduler.entities.len() as u32; + let running_tasks = scheduler.run_queues.iter().map(|rq| rq.nr_running).sum(); + (total_tasks, running_tasks, scheduler.need_resched) +} + +/// Timer tick - called from timer interrupt +pub fn scheduler_tick() { + SCHEDULE_CLOCK.fetch_add(1, Ordering::Relaxed); + + let mut scheduler = SCHEDULER.lock(); + + // Update current task's runtime + if let Some(rq) = scheduler.run_queues.get_mut(0) { + if let Some(ref mut current) = rq.current { + let now = rq.clock; + let delta = now - current.exec_start; + current.sum_exec_runtime += delta; + current.update_vruntime(delta); + current.exec_start = now; + + // Check if we need to reschedule + // For CFS, check if current task has run long enough + if current.policy == SchedulerPolicy::Normal { + let time_slice = calculate_time_slice(current); + if current.sum_exec_runtime - current.prev_sum_exec_runtime >= time_slice { + scheduler.set_need_resched(); + } + } + } + } +} + +/// Calculate time slice for a task based on its weight +fn calculate_time_slice(se: &SchedEntity) -> u64 { + // Linux-like time slice calculation + let sched_latency = 6_000_000; // 6ms in nanoseconds + let min_granularity = 750_000; // 0.75ms in nanoseconds + + // Time slice proportional to weight + let time_slice = sched_latency * se.load_weight as u64 / 1024; + core::cmp::max(time_slice, min_granularity) +} diff --git a/kernel/src/sync.rs b/kernel/src/sync.rs new file mode 100644 index 0000000..50dfe3c --- /dev/null +++ b/kernel/src/sync.rs @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Synchronization primitives + +use core::cell::UnsafeCell; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::{AtomicBool, Ordering}; + +/// Spinlock implementation +pub struct Spinlock { + locked: AtomicBool, + data: UnsafeCell, +} + +unsafe impl Sync for Spinlock {} +unsafe impl Send for Spinlock {} + +impl Spinlock { + pub const fn new(data: T) -> Self { + Self { + locked: AtomicBool::new(false), + data: UnsafeCell::new(data), + } + } + + pub fn lock(&self) -> SpinlockGuard { + while self.locked.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() { + // Busy wait + while self.locked.load(Ordering::Relaxed) { + core::hint::spin_loop(); + } + } + + SpinlockGuard { lock: self } + } + + pub fn try_lock(&self) -> Option> { + if self.locked.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_ok() { + Some(SpinlockGuard { lock: self }) + } else { + None + } + } +} + +pub struct SpinlockGuard<'a, T> { + lock: &'a Spinlock, +} + +impl Deref for SpinlockGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.lock.data.get() } + } +} + +impl DerefMut for SpinlockGuard<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.lock.data.get() } + } +} + +impl Drop for SpinlockGuard<'_, T> { + fn drop(&mut self) { + self.lock.locked.store(false, Ordering::Release); + } +} + +/// Mutex implementation (placeholder - would need proper scheduler integration) +pub struct Mutex { + inner: Spinlock, +} + +impl Mutex { + pub const fn new(data: T) -> Self { + Self { + inner: Spinlock::new(data), + } + } + + pub fn lock(&self) -> MutexGuard { + MutexGuard { + guard: self.inner.lock(), + } + } +} + +pub struct MutexGuard<'a, T> { + guard: SpinlockGuard<'a, T>, +} + +impl Deref for MutexGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &*self.guard + } +} + +impl DerefMut for MutexGuard<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.guard + } +} + +/// RwLock implementation (placeholder) +pub struct RwLock { + inner: Spinlock, +} + +impl RwLock { + pub const fn new(data: T) -> Self { + Self { + inner: Spinlock::new(data), + } + } + + pub fn read(&self) -> RwLockReadGuard { + RwLockReadGuard { + guard: self.inner.lock(), + } + } + + pub fn write(&self) -> RwLockWriteGuard { + RwLockWriteGuard { + guard: self.inner.lock(), + } + } +} + +pub struct RwLockReadGuard<'a, T> { + guard: SpinlockGuard<'a, T>, +} + +impl Deref for RwLockReadGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &*self.guard + } +} + +pub struct RwLockWriteGuard<'a, T> { + guard: SpinlockGuard<'a, T>, +} + +impl Deref for RwLockWriteGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &*self.guard + } +} + +impl DerefMut for RwLockWriteGuard<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.guard + } +} diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs new file mode 100644 index 0000000..cc7c9d2 --- /dev/null +++ b/kernel/src/syscall.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! System call interface +use crate::error::Result; + +pub fn init() -> Result<()> { + Ok(()) +} diff --git a/kernel/src/task.rs b/kernel/src/task.rs new file mode 100644 index 0000000..08b4543 --- /dev/null +++ b/kernel/src/task.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Task management +use crate::error::Result; + +pub struct Task; + +pub fn init() -> Result<()> { + Ok(()) +} diff --git a/kernel/src/time.rs b/kernel/src/time.rs new file mode 100644 index 0000000..f69676f --- /dev/null +++ b/kernel/src/time.rs @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Time management compatible with Linux kernel + +use crate::error::Result; +use crate::types::{Nanoseconds, Microseconds, Milliseconds, Seconds, Jiffies}; +use core::sync::atomic::{AtomicU64, Ordering}; + +/// System clock frequency (Hz) - typically 1000 for 1ms ticks +pub const HZ: u64 = 1000; + +/// Nanoseconds per second +pub const NSEC_PER_SEC: u64 = 1_000_000_000; + +/// Nanoseconds per millisecond +pub const NSEC_PER_MSEC: u64 = 1_000_000; + +/// Nanoseconds per microsecond +pub const NSEC_PER_USEC: u64 = 1_000; + +/// Nanoseconds per jiffy +pub const NSEC_PER_JIFFY: u64 = NSEC_PER_SEC / HZ; + +/// Global time counters +static JIFFIES_COUNTER: AtomicU64 = AtomicU64::new(0); +static BOOTTIME_NS: AtomicU64 = AtomicU64::new(0); + +/// Time structure - Linux compatible +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct TimeSpec { + pub sec: i64, + pub nsec: i64, +} + +impl TimeSpec { + pub const fn new(sec: i64, nsec: i64) -> Self { + Self { sec, nsec } + } + + pub fn zero() -> Self { + Self::new(0, 0) + } + + pub fn to_ns(&self) -> u64 { + (self.sec as u64 * NSEC_PER_SEC) + self.nsec as u64 + } + + pub fn from_ns(ns: u64) -> Self { + let sec = (ns / NSEC_PER_SEC) as i64; + let nsec = (ns % NSEC_PER_SEC) as i64; + Self::new(sec, nsec) + } + + pub fn add_ns(&mut self, ns: u64) { + let total_ns = self.to_ns() + ns; + *self = Self::from_ns(total_ns); + } + + pub fn sub_ns(&mut self, ns: u64) { + let total_ns = self.to_ns().saturating_sub(ns); + *self = Self::from_ns(total_ns); + } +} + +/// High resolution timer structure +#[derive(Debug, Clone)] +pub struct HrTimer { + pub expires: TimeSpec, + pub function: Option, + pub base: HrTimerBase, +} + +/// Timer bases - Linux compatible +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HrTimerBase { + Monotonic, + Realtime, + Boottime, + Tai, +} + +impl HrTimer { + pub fn new(base: HrTimerBase) -> Self { + Self { + expires: TimeSpec::zero(), + function: None, + base, + } + } + + pub fn set_expires(&mut self, expires: TimeSpec) { + self.expires = expires; + } + + pub fn set_function(&mut self, function: fn()) { + self.function = Some(function); + } + + pub fn is_expired(&self) -> bool { + let now = match self.base { + HrTimerBase::Monotonic => get_monotonic_time(), + HrTimerBase::Realtime => get_realtime(), + HrTimerBase::Boottime => get_boottime(), + HrTimerBase::Tai => get_realtime(), // Simplified + }; + now >= self.expires + } +} + +/// Initialize time management +pub fn init() -> Result<()> { + // Initialize system clocks + let boot_time = read_hardware_clock(); + BOOTTIME_NS.store(boot_time, Ordering::Relaxed); + + // TODO: Set up timer interrupts + // TODO: Initialize high-resolution timers + + crate::info!("Time management initialized, boot time: {} ns", boot_time); + Ok(()) +} + +/// Read hardware clock (placeholder) +fn read_hardware_clock() -> u64 { + // TODO: Read from actual hardware clock (RTC, TSC, etc.) + // For now, return a fixed value + 1609459200_000_000_000 // 2021-01-01 00:00:00 UTC in nanoseconds +} + +/// Get current jiffies count +pub fn get_jiffies() -> Jiffies { + Jiffies(JIFFIES_COUNTER.load(Ordering::Relaxed)) +} + +/// Increment jiffies counter (called from timer interrupt) +pub fn update_jiffies() { + JIFFIES_COUNTER.fetch_add(1, Ordering::Relaxed); +} + +/// Get current time in nanoseconds since boot +pub fn get_time_ns() -> u64 { + // TODO: Read from high-resolution clock source (TSC, etc.) + // For now, use jiffies-based approximation + get_jiffies().0 * NSEC_PER_JIFFY +} + +/// Get monotonic time (time since boot) +pub fn get_monotonic_time() -> TimeSpec { + TimeSpec::from_ns(get_time_ns()) +} + +/// Get boot time +pub fn get_boottime() -> TimeSpec { + let boot_ns = BOOTTIME_NS.load(Ordering::Relaxed); + let current_ns = get_time_ns(); + TimeSpec::from_ns(boot_ns + current_ns) +} + +/// Get real time (wall clock time) +pub fn get_realtime() -> TimeSpec { + get_boottime() +} + +/// Convert nanoseconds to jiffies +pub fn ns_to_jiffies(ns: u64) -> Jiffies { + Jiffies(ns / NSEC_PER_JIFFY) +} + +/// Convert jiffies to nanoseconds +pub fn jiffies_to_ns(jiffies: Jiffies) -> u64 { + jiffies.0 * NSEC_PER_JIFFY +} + +/// Convert milliseconds to jiffies +pub fn msecs_to_jiffies(ms: u64) -> Jiffies { + ns_to_jiffies(ms * NSEC_PER_MSEC) +} + +/// Convert jiffies to milliseconds +pub fn jiffies_to_msecs(jiffies: Jiffies) -> u64 { + jiffies_to_ns(jiffies) / NSEC_PER_MSEC +} + +/// Convert microseconds to jiffies +pub fn usecs_to_jiffies(us: u64) -> Jiffies { + ns_to_jiffies(us * NSEC_PER_USEC) +} + +/// Convert jiffies to microseconds +pub fn jiffies_to_usecs(jiffies: Jiffies) -> u64 { + jiffies_to_ns(jiffies) / NSEC_PER_USEC +} + +/// Sleep functions - Linux compatible +pub fn msleep(ms: u64) { + let target_jiffies = get_jiffies().0 + msecs_to_jiffies(ms).0; + while get_jiffies().0 < target_jiffies { + crate::scheduler::yield_now(); + } +} + +pub fn usleep_range(min_us: u64, max_us: u64) { + let us = (min_us + max_us) / 2; // Use average + let target_jiffies = get_jiffies().0 + usecs_to_jiffies(us).0; + while get_jiffies().0 < target_jiffies { + crate::scheduler::yield_now(); + } +} + +pub fn ndelay(ns: u64) { + // Busy wait for nanoseconds (not recommended for long delays) + let start = get_time_ns(); + while get_time_ns() - start < ns { + core::hint::spin_loop(); + } +} + +pub fn udelay(us: u64) { + ndelay(us * NSEC_PER_USEC); +} + +pub fn mdelay(ms: u64) { + ndelay(ms * NSEC_PER_MSEC); +} + +/// Timer wheel for managing timers +#[derive(Debug)] +pub struct TimerWheel { + levels: [Vec; 8], // Multiple levels for different time ranges + current_jiffies: u64, +} + +impl TimerWheel { + pub fn new() -> Self { + const EMPTY_VEC: Vec = Vec::new(); + Self { + levels: [EMPTY_VEC; 8], + current_jiffies: 0, + } + } + + pub fn add_timer(&mut self, timer: HrTimer) { + // TODO: Add timer to appropriate level based on expiry time + let level = 0; // Simplified + self.levels[level].push(timer); + } + + pub fn run_timers(&mut self) { + self.current_jiffies = get_jiffies().0; + + // Check and run expired timers + for level in &mut self.levels { + level.retain(|timer| { + if timer.is_expired() { + if let Some(function) = timer.function { + function(); + } + false // Remove expired timer + } else { + true // Keep timer + } + }); + } + } +} + +/// Global timer wheel +use crate::sync::Spinlock; +static TIMER_WHEEL: Spinlock = Spinlock::new(TimerWheel::new()); + +/// Add a timer to the system +pub fn add_timer(timer: HrTimer) { + let mut wheel = TIMER_WHEEL.lock(); + wheel.add_timer(timer); +} + +/// Run expired timers (called from timer interrupt) +pub fn run_timers() { + let mut wheel = TIMER_WHEEL.lock(); + wheel.run_timers(); +} + +/// Timer interrupt handler +pub fn timer_interrupt() { + // Update jiffies + update_jiffies(); + + // Run expired timers + run_timers(); + + // Update scheduler tick + crate::scheduler::scheduler_tick(); +} diff --git a/kernel/src/types.rs b/kernel/src/types.rs new file mode 100644 index 0000000..362b4a6 --- /dev/null +++ b/kernel/src/types.rs @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Common kernel types + +use core::fmt; +use core::ops::{Add, Sub}; + +/// Process ID type +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Pid(pub u32); + +impl fmt::Display for Pid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Thread ID type +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Tid(pub u32); + +impl fmt::Display for Tid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// User ID type +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Uid(pub u32); + +/// Group ID type +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Gid(pub u32); + +/// Physical address type +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct PhysAddr(pub usize); + +impl PhysAddr { + pub const fn new(addr: usize) -> Self { + Self(addr) + } + + pub const fn as_u64(self) -> u64 { + self.0 as u64 + } + + pub const fn as_usize(self) -> usize { + self.0 + } +} + +impl Add for PhysAddr { + type Output = Self; + + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl Sub for PhysAddr { + type Output = Self; + + fn sub(self, rhs: usize) -> Self::Output { + Self(self.0 - rhs) + } +} + +/// Virtual address type +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct VirtAddr(pub usize); + +impl VirtAddr { + pub const fn new(addr: usize) -> Self { + Self(addr) + } + + pub const fn as_u64(self) -> u64 { + self.0 as u64 + } + + pub const fn as_usize(self) -> usize { + self.0 + } + + pub const fn as_ptr(self) -> *const T { + self.0 as *const T + } + + pub const fn as_mut_ptr(self) -> *mut T { + self.0 as *mut T + } +} + +impl Add for VirtAddr { + type Output = Self; + + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl Sub for VirtAddr { + type Output = Self; + + fn sub(self, rhs: usize) -> Self::Output { + Self(self.0 - rhs) + } +} + +impl fmt::Display for VirtAddr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x{:x}", self.0) + } +} + +/// Page size constants +pub const PAGE_SIZE: usize = 4096; +pub const PAGE_SHIFT: usize = 12; + +/// Page frame number +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Pfn(pub usize); + +impl Pfn { + pub fn from_phys_addr(addr: PhysAddr) -> Self { + Self(addr.0 >> PAGE_SHIFT) + } + + pub fn to_phys_addr(self) -> PhysAddr { + PhysAddr(self.0 << PAGE_SHIFT) + } +} + +/// CPU number type +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CpuId(pub u32); + +/// IRQ number type +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Irq(pub u32); + +/// Time types +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Jiffies(pub u64); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Nanoseconds(pub u64); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Microseconds(pub u64); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Milliseconds(pub u64); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Seconds(pub u64); diff --git a/modules/Cargo.toml b/modules/Cargo.toml new file mode 100644 index 0000000..bbedb21 --- /dev/null +++ b/modules/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "modules" +version = "0.1.0" +edition = "2021" +authors = ["Rust Kernel Contributors"] +description = "Kernel modules" +license = "GPL-2.0" + +[dependencies] +kernel = { path = "../kernel" } + +[[bin]] +name = "hello_module" +path = "src/hello.rs" + +[[bin]] +name = "test_module" +path = "src/test.rs" diff --git a/modules/src/hello.rs b/modules/src/hello.rs new file mode 100644 index 0000000..f76bcdb --- /dev/null +++ b/modules/src/hello.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Hello World kernel module + +#![no_std] +#![no_main] + +use kernel::prelude::*; + +struct HelloModule { + message: String, +} + +impl kernel::module::Module for HelloModule { + fn init(_module: &'static kernel::module::ThisModule) -> Result { + info!("Hello from Rust kernel module!"); + info!("This is a sample module demonstrating the Rust kernel framework"); + + Ok(HelloModule { + message: String::from("Hello, Rust Kernel!"), + }) + } + + fn exit(_module: &'static kernel::module::ThisModule) { + info!("Goodbye from Hello module"); + } +} + +module! { + type: HelloModule, + name: "hello_module", + author: "Rust Kernel Contributors", + description: "A simple hello world module", + license: "GPL-2.0", +} diff --git a/modules/src/test.rs b/modules/src/test.rs new file mode 100644 index 0000000..ba55d38 --- /dev/null +++ b/modules/src/test.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Test kernel module + +#![no_std] +#![no_main] + +use kernel::prelude::*; + +struct TestModule { + test_data: Vec, + counter: u32, +} + +impl kernel::module::Module for TestModule { + fn init(_module: &'static kernel::module::ThisModule) -> Result { + info!("Test module initializing..."); + + // Test memory allocation + let mut test_data = Vec::new(); + for i in 0..10 { + test_data.push(i * i); + } + + info!("Test data created: {:?}", test_data); + + // Test synchronization + let spinlock = Spinlock::new(42); + { + let guard = spinlock.lock(); + info!("Locked value: {}", *guard); + } + + info!("Test module initialized successfully"); + + Ok(TestModule { + test_data, + counter: 0, + }) + } + + fn exit(_module: &'static kernel::module::ThisModule) { + info!("Test module exiting, counter was: {}", self.counter); + } +} + +module! { + type: TestModule, + name: "test_module", + author: "Rust Kernel Contributors", + description: "A test module for kernel functionality", + license: "GPL-2.0", +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..b95a8e4 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,30 @@ +# Rust formatting configuration +# Based on Linux kernel Rust formatting rules + +max_width = 100 +hard_tabs = true +tab_spaces = 8 +newline_style = "Unix" +use_small_heuristics = "Default" + +# Import organization +imports_layout = "Mixed" +group_imports = "StdExternalCrate" + +# Function formatting +fn_args_layout = "Tall" +where_single_line = true + +# Control flow +control_brace_style = "AlwaysSameLine" +indent_style = "Block" + +# Comments +comment_width = 80 +wrap_comments = true +normalize_comments = true + +# Misc +format_code_in_doc_comments = true +format_strings = false +format_macro_matchers = true diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..38ad123 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust Kernel +//! +//! A modern kernel implementation in Rust, inspired by the Linux kernel +//! and utilizing the Rust for Linux infrastructure. + +#![no_std] +#![no_main] + +// Re-export the kernel crate +pub use kernel; + +/// Main kernel entry point +#[no_mangle] +pub extern "C" fn _start() -> ! { + kernel::kernel_main() +} + +#[cfg(test)] +mod tests { + #[test] + fn basic_test() { + assert_eq!(2 + 2, 4); + } +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..ba4ee4f --- /dev/null +++ b/test.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# Rust Kernel Test Script + +set -e + +echo "=== Rust Kernel Build Test ===" + +# Check if rust is installed +if ! command -v cargo &> /dev/null; then + echo "Error: Rust/Cargo not found. Please install Rust first." + exit 1 +fi + +echo "Rust version:" +rustc --version +cargo --version + +echo "" +echo "=== Building kernel ===" +cargo check --workspace + +echo "" +echo "=== Running tests ===" +cargo test --workspace --lib + +echo "" +echo "=== Checking formatting ===" +cargo fmt --check || echo "Warning: Code formatting issues found" + +echo "" +echo "=== Running clippy ===" +cargo clippy --workspace -- -D warnings || echo "Warning: Clippy found issues" + +echo "" +echo "=== Building release version ===" +cargo build --release + +echo "" +echo "=== Build completed successfully! ===" +echo "Kernel artifacts can be found in target/release/"