├── catten ├── src │ ├── cpu │ │ ├── isa │ │ │ ├── interface │ │ │ │ ├── timers │ │ │ │ │ └── mod.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── io │ │ │ │ │ └── mod.rs │ │ │ │ ├── system_info │ │ │ │ │ └── mod.rs │ │ │ │ ├── init │ │ │ │ │ └── mod.rs │ │ │ │ ├── interrupts │ │ │ │ │ └── mod.rs │ │ │ │ ├── lp │ │ │ │ │ └── mod.rs │ │ │ │ └── memory │ │ │ │ │ ├── address.rs │ │ │ │ │ └── mod.rs │ │ │ ├── common │ │ │ │ ├── mod.rs │ │ │ │ └── memory │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── address │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── paddr.rs │ │ │ │ │ └── vaddr.rs │ │ │ ├── x86_64 │ │ │ │ ├── interrupts │ │ │ │ │ ├── context_switch │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── ipis │ │ │ │ │ │ ├── ipis.asm │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── x2apic │ │ │ │ │ │ ├── id.rs │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── idt.rs │ │ │ │ │ └── exceptions │ │ │ │ │ │ └── mod.rs │ │ │ │ ├── constants │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── interrupt_vectors.rs │ │ │ │ │ └── msrs.rs │ │ │ │ ├── timers │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── apic_timer.rs │ │ │ │ │ ├── i8254.rs │ │ │ │ │ └── tsc.rs │ │ │ │ ├── lp │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── per_lp.rs │ │ │ │ │ ├── ops.rs │ │ │ │ │ └── thread_context.rs │ │ │ │ ├── memory │ │ │ │ │ ├── address │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── tlb.rs │ │ │ │ │ └── paging │ │ │ │ │ │ ├── pte.rs │ │ │ │ │ │ └── mod.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── init │ │ │ │ │ ├── bsp.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── ap.rs │ │ │ │ ├── io │ │ │ │ │ └── mod.rs │ │ │ │ ├── asm_macros │ │ │ │ │ └── context_switch.asm │ │ │ │ └── system_info │ │ │ │ │ └── mod.rs │ │ │ ├── aarch64 │ │ │ │ ├── memory │ │ │ │ │ ├── paging │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── address │ │ │ │ │ │ └── mod.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── lp │ │ │ │ │ └── mod.rs │ │ │ │ ├── interrupts │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── ivt.asm │ │ │ │ ├── io │ │ │ │ │ └── mod.rs │ │ │ │ ├── init │ │ │ │ │ └── mod.rs │ │ │ │ └── system_info │ │ │ │ │ ├── isa_extensions.rs │ │ │ │ │ └── mod.rs │ │ │ └── mod.rs │ │ ├── scheduler │ │ │ ├── sync │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ ├── threads │ │ │ │ └── mod.rs │ │ │ ├── lp_schedulers │ │ │ │ └── mod.rs │ │ │ └── system_scheduler │ │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── multiprocessor │ │ │ └── mod.rs │ ├── environment │ │ ├── uefi_rt │ │ │ └── mod.rs │ │ ├── acpi │ │ │ ├── mod.rs │ │ │ └── uacpi_kernel.rs │ │ ├── arm_smc │ │ │ └── mod.rs │ │ ├── devicetree │ │ │ └── mod.rs │ │ ├── boot_protocol │ │ │ ├── mod.rs │ │ │ └── limine.rs │ │ └── mod.rs │ ├── common │ │ ├── collections │ │ │ ├── mod.rs │ │ │ └── id_table.rs │ │ ├── constants.rs │ │ ├── integer.rs │ │ ├── mod.rs │ │ ├── size.rs │ │ ├── io.rs │ │ └── bitwise.rs │ ├── drivers │ │ ├── input │ │ │ ├── hid.rs │ │ │ ├── mod.rs │ │ │ └── i8042.rs │ │ ├── mod.rs │ │ ├── display │ │ │ ├── mod.rs │ │ │ └── limine_fb.rs │ │ └── uart │ │ │ ├── mod.rs │ │ │ └── ns16550 │ │ │ ├── legacy_ports.rs │ │ │ └── mod.rs │ ├── self_test │ │ ├── memory │ │ │ ├── mod.rs │ │ │ ├── pmem.rs │ │ │ ├── vmem.rs │ │ │ └── allocator.rs │ │ └── mod.rs │ ├── memory │ │ ├── allocators │ │ │ ├── mod.rs │ │ │ ├── global_allocator.rs │ │ │ ├── memory.rs │ │ │ └── stack_allocator.rs │ │ ├── linear │ │ │ ├── mod.rs │ │ │ └── address_map.rs │ │ └── mod.rs │ ├── framebuffer │ │ ├── mod.rs │ │ └── colors.rs │ ├── panic.rs │ ├── log │ │ └── mod.rs │ ├── event │ │ └── mod.rs │ ├── init │ │ └── mod.rs │ └── main.rs ├── .cargo │ └── config.toml ├── rust-toolchain.toml ├── rustfmt.toml ├── Cargo.toml ├── build.rs └── linker │ ├── x86_64.ld │ ├── aarch64.ld │ └── riscv64.ld ├── package.json ├── limine.conf ├── .gitignore ├── REUSE.toml ├── Documents ├── SETUP.md └── TARGET_POLICY.md ├── LICENSE_NOTE.md ├── tools └── setup_dev_env.bash ├── .github └── copilot-instructions.md └── README.md /catten/src/cpu/isa/interface/timers/mod.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /catten/src/cpu/scheduler/sync/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /catten/src/environment/uefi_rt/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod memory; 2 | -------------------------------------------------------------------------------- /catten/src/common/collections/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod id_table; 2 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/common/memory/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod address; 2 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/interrupts/context_switch/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /catten/src/environment/acpi/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod uacpi_kernel; 2 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/aarch64/memory/paging/mod.rs: -------------------------------------------------------------------------------- 1 | pub type HwAsid = u16; 2 | -------------------------------------------------------------------------------- /catten/src/drivers/input/hid.rs: -------------------------------------------------------------------------------- 1 | //! # Human Interface Device (USB or I2C) 2 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/common/memory/address/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod paddr; 2 | pub mod vaddr; 3 | -------------------------------------------------------------------------------- /catten/src/environment/arm_smc/mod.rs: -------------------------------------------------------------------------------- 1 | //! ARM64 Secure Monitor Call (SMC) Interface 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "prettier": "3.6.2" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/constants/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod interrupt_vectors; 2 | pub mod msrs; 3 | -------------------------------------------------------------------------------- /catten/src/self_test/memory/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod allocator; 2 | pub mod pmem; 3 | pub mod vmem; 4 | -------------------------------------------------------------------------------- /catten/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = ["x86_64-unknown-none", "aarch64-unknown-none"] 3 | -------------------------------------------------------------------------------- /catten/src/common/constants.rs: -------------------------------------------------------------------------------- 1 | //! # Named Constants 2 | 3 | pub const BITS_PER_BYTE: usize = 8; 4 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/timers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod apic_timer; 2 | pub mod i8254; 3 | pub mod tsc; 4 | -------------------------------------------------------------------------------- /catten/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | targets = ["x86_64-unknown-none"] 4 | -------------------------------------------------------------------------------- /catten/src/drivers/input/mod.rs: -------------------------------------------------------------------------------- 1 | //! Input Controller Drivers 2 | 3 | pub mod hid; 4 | pub mod i8042; 5 | -------------------------------------------------------------------------------- /catten/src/drivers/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Device Drivers 2 | 3 | pub mod display; 4 | pub mod input; 5 | pub mod uart; 6 | -------------------------------------------------------------------------------- /catten/src/memory/allocators/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod global_allocator; 2 | mod memory; 3 | pub mod stack_allocator; 4 | -------------------------------------------------------------------------------- /catten/src/drivers/display/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Display Adapter Drivers 2 | #[cfg(feature = "limine")] 3 | pub mod limine_fb; 4 | -------------------------------------------------------------------------------- /catten/src/cpu/scheduler/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod lp_schedulers; 2 | pub mod sync; 3 | pub mod system_scheduler; 4 | pub mod threads; 5 | -------------------------------------------------------------------------------- /catten/src/cpu/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Central Processor Subsystem 2 | 3 | pub mod isa; 4 | pub mod multiprocessor; 5 | pub mod scheduler; 6 | -------------------------------------------------------------------------------- /limine.conf: -------------------------------------------------------------------------------- 1 | TIMEOUT: 0 2 | SERIAL: no 3 | 4 | /Catten 5 | PROTOCOL: limine 6 | KERNEL_PATH: boot():/catten 7 | KASLR: no 8 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/lp/mod.rs: -------------------------------------------------------------------------------- 1 | // x86_64 Logical Processor Operations 2 | pub mod ops; 3 | pub mod thread_context; 4 | 5 | pub type LpId = u32; 6 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/interface/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod init; 2 | pub mod interrupts; 3 | pub mod io; 4 | pub mod lp; 5 | pub mod memory; 6 | pub mod system_info; 7 | -------------------------------------------------------------------------------- /catten/src/framebuffer/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Simple Framebuffer Renderer 2 | 3 | pub mod chars; 4 | pub mod colors; 5 | pub mod console; 6 | pub mod framebuffer; 7 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/interface/io/mod.rs: -------------------------------------------------------------------------------- 1 | pub trait IReg8Ifce { 2 | unsafe fn read(&self) -> u8; 3 | } 4 | 5 | pub trait OReg8Ifce { 6 | unsafe fn write(&self, value: u8); 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iso 2 | ovmf-aarch64 3 | ovmf-riscv64 4 | ovmf-x86_64 5 | catten/target/** 6 | Catten-*.iso 7 | catten-*.iso 8 | log.txt 9 | Limine 10 | .vscode/** 11 | log_*.txt 12 | target/** 13 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/constants/interrupt_vectors.rs: -------------------------------------------------------------------------------- 1 | pub const UNICAST_IPI_VECTOR: u8 = 32; 2 | pub const MULTICAST_IPI_VECTOR: u8 = 33; 3 | pub const BROADCAST_IPI_VECTOR: u8 = 34; 4 | pub const TIMER_INTERRUPT_VECTOR: u8 = 35; 5 | -------------------------------------------------------------------------------- /catten/src/environment/devicetree/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Devicetree 2 | //! 3 | //! This module provides facilities for parsing flattened device trees (FDTs) and abstracts them to 4 | //! the common interface expected by the rest of the kernel. 5 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[annotations]] 4 | path = ["./"] 5 | precedence = "closest" 6 | SPDX-FileCopyrightText = "2025 Mohit D. Patel and Contributors " 7 | SPDX-License-Identifier = "AGPL-3.0-or-later" 8 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/interrupts/ipis/ipis.asm: -------------------------------------------------------------------------------- 1 | .code64 2 | 3 | .section .text 4 | .global isr_interprocessor_interrupt 5 | isr_interprocessor_interrupt: 6 | mov rdi, gs:[rip + GS_OFFSET_IPI_QUEUE] 7 | call ih_interprocessor_interrupt 8 | iretq -------------------------------------------------------------------------------- /catten/src/panic.rs: -------------------------------------------------------------------------------- 1 | //! # Rust Panic Handler 2 | 3 | use core::panic::PanicInfo; 4 | 5 | use crate::cpu::isa::lp::ops::halt; 6 | use crate::logln; 7 | 8 | #[panic_handler] 9 | fn panic(_info: &PanicInfo) -> ! { 10 | logln!("{}", _info); 11 | halt!() 12 | } 13 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/aarch64/memory/address/mod.rs: -------------------------------------------------------------------------------- 1 | // Re-export the common 64-bit address modules for AArch64 2 | pub use crate::cpu::isa::common::memory::address::*; 3 | /* Note: The x86-64 canonical address rules should work on aarch64 systems if they are configured 4 | * correctly. Let's endeavor to do this to every extent possible for portability. */ 5 | -------------------------------------------------------------------------------- /catten/src/drivers/uart/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Universal Asynchronous Receiver/Transmitter (UART) Drivers 2 | 3 | pub mod ns16550; 4 | 5 | use core::fmt::Write; 6 | use core::marker::Sized; 7 | 8 | use crate::cpu::isa::io::IoReg8; 9 | 10 | pub trait Uart: Write + Sized { 11 | type Error: Sized; 12 | fn try_new(base: IoReg8) -> Result; 13 | } 14 | -------------------------------------------------------------------------------- /catten/src/common/integer.rs: -------------------------------------------------------------------------------- 1 | pub fn nearest_multiple_of(value: T, multiple: T) -> T 2 | where 3 | T: From + Into + Copy, 4 | { 5 | let value: u64 = value.into(); 6 | let multiple: u64 = multiple.into(); 7 | if multiple == 0 { 8 | T::from(value) 9 | } else { 10 | T::from(((value + multiple / 2) / multiple) * multiple) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/lp/per_lp.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::scheduler::local_scheduler::LocalScheduler; 2 | use crate::cpu::scheduler::threads::Thread; 3 | 4 | pub static LP_DATA_TABLE: Lazy> = Lazy::new(IdTable::new); 5 | 6 | pub struct PerLpDataSegment { 7 | pub curr_tcb: Arc>, 8 | pub ipi_mailbox: *const ipis::IpiRpc, 9 | idt: Idt, 10 | local_scheduler: Mutex, 11 | } 12 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/interface/system_info/mod.rs: -------------------------------------------------------------------------------- 1 | pub enum Error { 2 | UnsupportedByIsa, 3 | UnableToDetermine, 4 | } 5 | 6 | pub trait CpuInfoIfce { 7 | type IsaExtension; 8 | type Vendor; 9 | type Model; 10 | 11 | fn get_vendor() -> Self::Vendor; 12 | fn get_model() -> Self::Model; 13 | fn get_vaddr_sig_bits() -> u8; 14 | fn get_paddr_sig_bits() -> u8; 15 | fn is_extension_supported(extension: Self::IsaExtension) -> bool; 16 | } 17 | -------------------------------------------------------------------------------- /catten/src/drivers/display/limine_fb.rs: -------------------------------------------------------------------------------- 1 | //! # Limine Framebuffer Driver 2 | //! 3 | //! Stub driver that presents a standard interface to the Limine framebuffer for use by this 4 | //! kernel's rendering subsystem. If the Limine boot protocol is not used to boot the kernel, or 5 | //! if no framebuffers are provided by Limine, this driver will not enumerate anything when queried 6 | //! by the device manager. Ideally on builds where Limine is not used, this driver should be 7 | //! configured out. 8 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/interface/init/mod.rs: -------------------------------------------------------------------------------- 1 | ///! # Interface for ISA specific initialization 2 | 3 | pub trait InitInterface { 4 | type Error: core::fmt::Debug; 5 | /// Perform ISA specific processor and system initialization 6 | fn init_bsp() -> Result<(), Self::Error>; 7 | /// Perform ISA specific application processor initialization 8 | fn init_ap() -> Result<(), Self::Error>; 9 | /// Perform ISA specific deinitialization 10 | fn deinit() -> Result<(), Self::Error>; 11 | } 12 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/aarch64/mod.rs: -------------------------------------------------------------------------------- 1 | //! Aarch64 Instruction Set Architecture 2 | //! 3 | //! This module contains all aarch64-specific code. The main reference documentation 4 | //! for this ISA is the 5 | //! [ARM Architecture Reference Manual, for A-profile architecture](https://developer.arm.com/documentation/ddi0487/latest/) 6 | //! which we generally refer to as the "ARM ARM". 7 | 8 | pub mod init; 9 | pub mod interrupts; 10 | pub mod io; 11 | pub mod lp; 12 | pub mod memory; 13 | pub mod system_info; 14 | -------------------------------------------------------------------------------- /catten/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Common Utilities 2 | //! 3 | //! This module can essentially be thought of as an extension to the Rust core library. It provides 4 | //! standard library like functionality for the Catten kernel. This module should not duplicate 5 | //! functionality that is already provided by core or any other high quality no_std library unless 6 | //! there is a compelling reason to do so. 7 | pub mod bitwise; 8 | pub mod collections; 9 | pub mod constants; 10 | pub mod integer; 11 | pub mod io; 12 | pub mod size; 13 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/memory/address/mod.rs: -------------------------------------------------------------------------------- 1 | use spin::Lazy; 2 | 3 | pub use crate::cpu::isa::common::memory::address::*; 4 | use crate::cpu::isa::interface::system_info::CpuInfoIfce; 5 | use crate::cpu::isa::x86_64::system_info::CpuInfo; 6 | 7 | pub static PADDR_SIG_BITS: Lazy = Lazy::new(CpuInfo::get_paddr_sig_bits); 8 | pub static PADDR_MASK: Lazy = Lazy::new(|| (1 << *PADDR_SIG_BITS as usize) - 1); 9 | pub static VADDR_SIG_BITS: Lazy = Lazy::new(CpuInfo::get_vaddr_sig_bits); 10 | pub static VADDR_MASK: Lazy = Lazy::new(|| (1 << *VADDR_SIG_BITS as usize) - 1); 11 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/aarch64/lp/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Logical Processor Control Interface for AArch64 2 | use crate::cpu::isa::interface::lp; 3 | use crate::cpu::isa::system_info::{CpuInfo, CpuInfoIfce}; 4 | 5 | pub type LpId = u32; 6 | 7 | macro_rules! halt { 8 | () => { 9 | unsafe { core::arch::asm!("wfe", options(nomem, nostack, preserves_flags)) } 10 | }; 11 | } 12 | macro_rules! mask_interrupts { 13 | () => { 14 | unsafe { core::arch::asm!("msr daifset, 0b1111", options(nomem, nostack)) } 15 | }; 16 | } 17 | macro_rules! unmask_interrupts { 18 | () => { 19 | unsafe { core::arch::asm!("msr daifclr, 0b1111", options(nomem, nostack)) } 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /catten/src/common/size.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for Working with Memory Sizes 2 | 3 | pub const fn kibibytes(n: usize) -> usize { 4 | n * 1024 5 | } 6 | pub const fn mebibytes(n: usize) -> usize { 7 | n * 1024 * 1024 8 | } 9 | pub const fn gibibytes(n: usize) -> usize { 10 | n * 1024 * 1024 * 1024 11 | } 12 | pub const fn tebibytes(n: usize) -> usize { 13 | n * 1024 * 1024 * 1024 * 1024 14 | } 15 | pub const fn pebibytes(n: usize) -> usize { 16 | n * 1024 * 1024 * 1024 * 1024 * 1024 17 | } 18 | pub const fn exbibytes(n: usize) -> usize { 19 | n * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 20 | } 21 | pub const fn zebibytes(n: usize) -> usize { 22 | n * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 23 | } 24 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/mod.rs: -------------------------------------------------------------------------------- 1 | //! The x86-64 Instruction Set Architecture 2 | //! 3 | //! This module contains all x86-64-specific code. The two main reference documents 4 | //! for this ISA are the 5 | //! [AMD64 Architecture Programmer's Manual](https://docs.amd.com/v/u/en-US/40332-PUB_4.08) 6 | //! which we refer to as the "AAPM" 7 | //! and the 8 | //! [Intel64 and IA-32 Architectures Software Developer's Manual](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html) 9 | //! which we generally refer to as the "ISDM". 10 | 11 | pub mod constants; 12 | pub mod init; 13 | pub mod interrupts; 14 | pub mod io; 15 | pub mod lp; 16 | pub mod memory; 17 | pub mod system_info; 18 | pub mod timers; 19 | -------------------------------------------------------------------------------- /catten/rustfmt.toml: -------------------------------------------------------------------------------- 1 | combine_control_expr = false 2 | wrap_comments = true 3 | comment_width = 100 4 | force_explicit_abi = true 5 | hex_literal_case = "Lower" 6 | max_width = 100 7 | merge_derives = true 8 | imports_layout = "HorizontalVertical" 9 | imports_granularity = "Module" 10 | group_imports = "StdExternalCrate" 11 | reorder_imports = true 12 | newline_style = "Unix" 13 | reorder_impl_items = true 14 | struct_field_align_threshold = 1 15 | tab_spaces = 4 16 | unstable_features = true 17 | use_try_shorthand = true 18 | skip_macro_invocations = ["asm", "naked_asm", "macro_rules", "global_asm"] 19 | use_small_heuristics = "Off" 20 | format_strings = true 21 | format_macro_matchers = true 22 | format_code_in_doc_comments = true 23 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/aarch64/interrupts/mod.rs: -------------------------------------------------------------------------------- 1 | use core::arch::{asm, global_asm}; 2 | 3 | // Include the interrupt vector table assembly 4 | global_asm!(include_str!("ivt.asm")); 5 | 6 | #[inline(always)] 7 | pub fn load_ivt() { 8 | // Load the interrupt vector table 9 | unsafe { 10 | // Load the interrupt vector table 11 | asm!( 12 | "ldr x0, =ivt", 13 | "msr vbar_el1, x0", 14 | out("x0") _, 15 | ); 16 | } 17 | } 18 | #[unsafe(no_mangle)] 19 | pub extern "C" fn sync_dispatcher() {} 20 | #[unsafe(no_mangle)] 21 | pub extern "C" fn irq_dispatcher() {} 22 | #[unsafe(no_mangle)] 23 | pub extern "C" fn fiq_dispatcher() {} 24 | #[unsafe(no_mangle)] 25 | pub extern "C" fn serr_dispatcher() {} 26 | -------------------------------------------------------------------------------- /catten/src/self_test/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Kernel Self-Test Subsystem 2 | //! 3 | //! This subsystem contains diagnostic tests meant to test the kernel itself and aid in development 4 | //! and troubleshooting. Almost all subsystems with the exception of drivers should have at least 5 | //! some tests in this module. In software engineering terminology the tests in this module should 6 | //! be whitebox integration tests that can be run after Catten initializes itself. 7 | 8 | pub mod memory; 9 | 10 | use crate::logln; 11 | 12 | pub fn run_self_tests() { 13 | logln!("Running self tests..."); 14 | memory::pmem::test_pmem(); 15 | memory::vmem::test_vmem(); 16 | memory::allocator::test_allocator(); 17 | logln!("Testing Complete. All Tests Passed!"); 18 | } 19 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/aarch64/io/mod.rs: -------------------------------------------------------------------------------- 1 | use core::ops::Add; 2 | 3 | use crate::cpu::isa::interface::io::{IReg8Ifce, OReg8Ifce}; 4 | 5 | #[derive(Copy, Clone, Debug)] 6 | pub struct IoReg8 { 7 | address: *mut u8, 8 | } 9 | 10 | impl IReg8Ifce for IoReg8 { 11 | fn read(&self) -> u8 { 12 | unsafe { core::ptr::read_volatile(self.address) } 13 | } 14 | } 15 | 16 | impl OReg8Ifce for IoReg8 { 17 | fn write(&self, value: u8) { 18 | unsafe { core::ptr::write_volatile(self.address, value) } 19 | } 20 | } 21 | 22 | impl Add for IoReg8 { 23 | type Output = IoReg8; 24 | 25 | fn add(self, rhs: u16) -> Self::Output { 26 | IoReg8 { 27 | address: unsafe { (self.address as *mut u8).add(rhs as usize) as *mut u8 }, 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /catten/src/drivers/uart/ns16550/legacy_ports.rs: -------------------------------------------------------------------------------- 1 | //! Legacy PC COM Port I/O port bases 2 | /* 3 | * These won't work on modern hardware as it no longer uses the ISA bus however they are useful 4 | * for logging when running under hypervisors like QEMU or Bochs which emulate ISA hardware. 5 | * Typically modern hardware exposes UARTs via MMIO or PCIe. 6 | */ 7 | #[allow(unused)] 8 | pub static COM1: u16 = 0x3f8; 9 | #[allow(unused)] 10 | pub static COM2: u16 = 0x2f8; 11 | #[allow(unused)] 12 | pub static COM3: u16 = 0x3e8; 13 | #[allow(unused)] 14 | pub static COM4: u16 = 0x2e8; 15 | #[allow(unused)] 16 | pub static COM5: u16 = 0x5f8; 17 | #[allow(unused)] 18 | pub static COM6: u16 = 0x4f8; 19 | #[allow(unused)] 20 | pub static COM7: u16 = 0x5e8; 21 | #[allow(unused)] 22 | pub static COM8: u16 = 0x4e8; 23 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/interface/interrupts/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::isa::lp::LpId; 2 | pub trait InterruptManagerIfce { 3 | type Error; 4 | type IsrDesc; 5 | type LocalIntCtlr: LocalIntCtlr; 6 | /// Initialize interrupt structures (IDT, IVT, etc.) 7 | fn init_interrupt_structures() -> Result<(), Self::Error>; 8 | /// Register an interrupt handler using an ISA specific descriptor for where to install it and 9 | /// with what attributes 10 | fn register_interrupt_handler(isrd: &Self::IsrDesc) -> Result<(), Self::Error>; 11 | } 12 | 13 | /// # Local Interrupt Controller Interface 14 | pub trait LocalIntCtlr { 15 | type Error; 16 | /// Send an inter-processor interrupt to the specified logical processor 17 | fn send_unicast_ipi(target_lp: LpId) -> Result<(), Self::Error>; 18 | } 19 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/interface/lp/mod.rs: -------------------------------------------------------------------------------- 1 | //! # x86_64 Logical Processor Control Interface 2 | 3 | pub trait CoreStateIfce { 4 | extern "C" fn save(&mut self); 5 | extern "C" fn load(&self); 6 | } 7 | 8 | /* 9 | * The following macros are used to logical processor operations in assembly and 10 | * must be defined in each architecture module. 11 | * 12 | * halt!() halts the current logical processor. 13 | * mask_interrupts!() disables interrupts on the current logical processor. 14 | * unmask_interrupts!() enables interrupts on the current logical processor. 15 | * curr_lic_id!() evaluates to the ID of the current local interrupt controller. 16 | * curr_lp_id!() evaluates to the ID of the current logical processor. 17 | * The following type aliases must also be defined: 18 | * LpId: The type used for logical processor IDs. 19 | * 20 | * See the x86_64 implementation for examples. 21 | */ 22 | -------------------------------------------------------------------------------- /catten/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "catten" 3 | version = "0.3.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | [[bin]] 8 | name = "catten" 9 | path = "src/main.rs" 10 | test = false 11 | bench = false 12 | 13 | [features] 14 | limine = [] 15 | default = ["limine"] 16 | 17 | [dependencies] 18 | hashbrown = ">=0.16.0" 19 | limine = ">=0.5.0" 20 | os-terminal = ">=0.6.11" 21 | spin = { version = ">=0.10.0", features = ["ticket_mutex", "lock_api"] } 22 | talc = { version = ">=4.4.3" } 23 | uacpi-raw = {git = "https://codeberg.org/CharlotteOS/uacpi-raw.git", branch = "main"} 24 | 25 | 26 | [profile.dev] 27 | debug = "full" 28 | debug-assertions = true 29 | panic = "abort" 30 | lto = false 31 | opt-level = 0 32 | 33 | [profile.release] 34 | debug = "none" 35 | debug-assertions = false 36 | panic = "abort" 37 | lto = false 38 | opt-level = 3 39 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/timers/apic_timer.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::isa::x86_64::constants::*; 2 | 3 | pub const APIC_TIMER_LVTR_TMM_BIT: u64 = 1 << 17; 4 | pub const APIC_TIMER_LVTR_MASK_BIT: u64 = 1 << 16; 5 | pub const APIC_TIMER_LVTR_VECTOR_VALUE: u64 = 255; 6 | pub const APIC_TIMER_DIVIDER_VALUE_1: u64 = 0b1011; 7 | 8 | pub fn init() { 9 | unsafe { 10 | // Set the divider register to indicate a divisor of 1 to use the maximum frequency 11 | // This is always 200 MHz on supported AMD processors and the crystal oscillator frequency on Intel processors 12 | // Ref: AMD APM 16.4.1 13 | msrs::write(msrs::APIC_TIMER_DIVIDE_CONFIGURATION, APIC_TIMER_DIVIDER_VALUE_1); 14 | // Clear the mode, mask, and delivery status bits and set the vector 15 | msrs::write(msrs::APIC_TIMER_LVTR, APIC_TIMER_LVTR_VECTOR_VALUE); 16 | // The APIC timer is now initialized and ready to use with x2APIC mode 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /catten/src/cpu/scheduler/threads/mod.rs: -------------------------------------------------------------------------------- 1 | use spin::Lazy; 2 | 3 | use crate::common::collections::id_table::IdTable; 4 | use crate::cpu::isa::lp::LpId; 5 | use crate::cpu::isa::lp::thread_context::ThreadContext; 6 | use crate::memory::{AddressSpaceId, VAddr}; 7 | 8 | static mut THREAD_TABLE: Lazy = Lazy::new(ThreadTable::new); 9 | type ThreadTable = IdTable; 10 | 11 | const LP_AFFINITY_COUNT: usize = 8; 12 | 13 | pub type ThreadId = usize; 14 | 15 | pub struct Thread { 16 | is_user: bool, 17 | context: ThreadContext, 18 | asid: AddressSpaceId, 19 | lp_affinity: [LpId; LP_AFFINITY_COUNT], 20 | } 21 | 22 | impl Thread { 23 | pub fn new(is_user: bool, asid: AddressSpaceId, entry_point: VAddr) -> Self { 24 | Thread { 25 | is_user, 26 | context: ThreadContext::new(asid, entry_point).expect(""), 27 | asid, 28 | lp_affinity: [LpId::default(); LP_AFFINITY_COUNT], 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/init/bsp.rs: -------------------------------------------------------------------------------- 1 | use spin::Lazy; 2 | 3 | use super::INTERRUPT_STACK_SIZE; 4 | use super::gdt::*; 5 | use crate::cpu::isa::interrupts::idt::Idt; 6 | use crate::cpu::isa::interrupts::register_fixed_isr_gates; 7 | use crate::logln; 8 | 9 | static mut BSP_INTERRUPT_STACK: [u8; INTERRUPT_STACK_SIZE] = [0u8; INTERRUPT_STACK_SIZE]; 10 | static mut BSP_DF_STACK: [u8; INTERRUPT_STACK_SIZE] = [0u8; INTERRUPT_STACK_SIZE]; 11 | pub static BSP_TSS: Lazy = Lazy::new(|| { 12 | Tss::new((&raw const BSP_INTERRUPT_STACK) as u64, (&raw const BSP_DF_STACK) as u64) 13 | }); 14 | static BSP_GDT: Lazy = Lazy::new(|| Gdt::new(&BSP_TSS)); 15 | pub static BSP_IDT: Lazy = Lazy::new(|| { 16 | let mut idt = Idt::new(); 17 | register_fixed_isr_gates(&mut idt); 18 | idt 19 | }); 20 | 21 | pub fn init_bsp() { 22 | BSP_GDT.load(); 23 | unsafe { 24 | reload_segment_regs(); 25 | } 26 | BSP_IDT.load(); 27 | logln!("BSP: x86-64 logical processor initialization complete"); 28 | } 29 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Instruction Set Architecture (ISA) Interface 2 | //! 3 | //! This module provides a set of interfaces that commonize the ISA specific 4 | //! functionality needed by the kernel: 5 | //! - [`Initialization`](init): ISA specific system initialization and deinitialization 6 | //! - [`Interrupts`](interrupts): wrappers over ISA specific interrupt management structures 7 | //! - [`Input/Output`](io): wrappers over MMIO and Port IO 8 | //! - [`Logical Processor Control`](lp): logical processor operating state control 9 | //! - [`Memory`](memory): wrappers over ISA specific memory management structures 10 | //! - [`System Information`](system_info): ISA specific system information 11 | 12 | #[cfg(target_arch = "aarch64")] 13 | mod aarch64; 14 | // Contains components common to some though not necessarily all ISAs 15 | mod common; 16 | pub mod interface; 17 | #[cfg(target_arch = "x86_64")] 18 | mod x86_64; 19 | #[cfg(target_arch = "aarch64")] 20 | pub use aarch64::*; 21 | #[cfg(target_arch = "x86_64")] 22 | pub use x86_64::*; 23 | -------------------------------------------------------------------------------- /catten/src/environment/boot_protocol/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Boot Protocol 2 | //! 3 | //! This module exists to abstract the boot protocol used to boot the kernel. As of now and for the 4 | //! foreseeable future this kernel will only support the Limine boot protocol on top of UEFI or the 5 | //! subset of UEFI required by the EBBR standard. This is in line with the system requirements for 6 | //! ARM64 systems including compliance with the requirements of ARM SystemReady program. 7 | //! 8 | //! Systems that do not meet the aforementioned requirements are not supported and will never be 9 | //! supported in line with the philosophy of this project to exclusively support and actively 10 | //! encourage the proliferation of standardized systems. Please do not open issues or PRs requesting 11 | //! or implementing support for non-standardized systems. They will be rejected. If you would like 12 | //! support on a given device please contact the manufacturer and request that they provide support 13 | //! for ARM SystemReady compliant firmware. 14 | 15 | pub mod limine; 16 | -------------------------------------------------------------------------------- /Documents/SETUP.md: -------------------------------------------------------------------------------- 1 | # Development Environment Setup 2 | 3 | The following software must be installed on a Unix like system in order to build and develop `catten`. On Windows WSL2 can be used. 4 | The preferred developement operating system is Fedora Workstation and all documentation and helper scripts will assume that it is the development OS used. 5 | 6 | - curl 7 | - git 8 | - make 9 | - qemu-system 10 | - GNU coreutils 11 | - clang 12 | - llvm 13 | - lldb 14 | - rustup 15 | - rust toolchain (nightly) 16 | - xorriso 17 | - Balena Etcher (to test on real hardware) 18 | 19 | On Fedora systems the [automatic set up shell script](./tools/setup_dev_env.bash) can be used to install all of the necessary software simply by running the following command from the directory in which the current file is located: 20 | 21 | ```bash 22 | sudo chmod 777 ./tools/setup_dev_env.bash && ./tools/setup_dev_env.bash 23 | ``` 24 | 25 | On other systems please refer to your operating system's documentation to determine how to install all of the aforementioned software programs. 26 | -------------------------------------------------------------------------------- /catten/src/common/io.rs: -------------------------------------------------------------------------------- 1 | //! # Input/Output Traits and Blanket Implementations 2 | 3 | pub trait Read { 4 | // Required 5 | fn read(&mut self, buf: &mut [u8]) -> usize; 6 | // Provided 7 | #[inline] 8 | fn read_byte(&mut self) -> u8 { 9 | let mut buf = [0; 1]; 10 | self.read(&mut buf); 11 | buf[0] 12 | } 13 | #[inline(always)] 14 | fn read_line(&mut self, buf: &mut [u8]) -> usize { 15 | self.read_until(buf, b'\n') 16 | } 17 | #[inline] 18 | fn read_until(&mut self, buf: &mut [u8], delim: u8) -> usize { 19 | let n_bytes = buf.len(); 20 | for i in 0..n_bytes { 21 | let c = self.read_byte(); 22 | if c == delim { 23 | return i; 24 | } 25 | buf[i] = c; 26 | } 27 | n_bytes 28 | } 29 | #[inline] 30 | fn read_n(&mut self, buf: &mut [u8], n: usize) -> usize { 31 | let n_bytes = core::cmp::max(n, buf.len()); 32 | for i in 0..n_bytes { 33 | buf[i] = self.read_byte(); 34 | } 35 | n_bytes 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/aarch64/init/mod.rs: -------------------------------------------------------------------------------- 1 | use super::interrupts::load_ivt; 2 | use crate::cpu::isa::interface::init::InitInterface; 3 | use crate::logln; 4 | 5 | pub struct IsaInitializer; 6 | 7 | #[derive(Debug)] 8 | pub enum Error { 9 | // Error type for the aarch64 architecture 10 | } 11 | 12 | impl InitInterface for IsaInitializer { 13 | type Error = Error; 14 | 15 | fn init_bsp() -> Result<(), Self::Error> { 16 | init_ap() 17 | } 18 | 19 | fn init_ap() -> Result<(), Self::Error> { 20 | // Initialization code for the aarch64 architecture 21 | logln!("Performing Aarch64 ISA specific initialization..."); 22 | // Setup the interrupt vector table 23 | logln!("Loading the interrupt vector table on the AP"); 24 | load_ivt(); 25 | logln!("Interrupt vector table loaded on the AP"); 26 | 27 | logln!("Aarch64 ISA specific initialization complete!"); 28 | Ok(()) 29 | } 30 | 31 | fn deinit() -> Result<(), Self::Error> { 32 | // Deinitialization code for the aarch64 architecture 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /catten/src/environment/boot_protocol/limine.rs: -------------------------------------------------------------------------------- 1 | use limine::BaseRevision; 2 | use limine::request::{ 3 | ExecutableAddressRequest, 4 | FramebufferRequest, 5 | HhdmRequest, 6 | MemoryMapRequest, 7 | MpRequest, 8 | RsdpRequest, 9 | StackSizeRequest, 10 | }; 11 | 12 | use crate::cpu::isa::memory::MemoryInterfaceImpl; 13 | use crate::memory::MemoryInterface as _; 14 | 15 | pub static BASE_REVISION: BaseRevision = BaseRevision::new(); 16 | pub static MEMORY_MAP_REQUEST: MemoryMapRequest = MemoryMapRequest::new(); 17 | pub static HHDM_REQUEST: HhdmRequest = HhdmRequest::new(); 18 | pub static EXECUTABLE_ADDRESS_REQUEST: ExecutableAddressRequest = ExecutableAddressRequest::new(); 19 | pub static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new(); 20 | pub static SMP_REQUEST: MpRequest = MpRequest::new(); 21 | pub static RSDP_REQUEST: RsdpRequest = RsdpRequest::new(); 22 | pub static STACK_SIZE: StackSizeRequest = 23 | StackSizeRequest::new().with_size((MemoryInterfaceImpl::PAGE_SIZE * 4) as u64); 24 | pub static MP: MpRequest = MpRequest::new().with_flags(limine::mp::RequestFlags::X2APIC); 25 | -------------------------------------------------------------------------------- /catten/src/framebuffer/colors.rs: -------------------------------------------------------------------------------- 1 | /// Define as a non_exhaustive struct to behave as an "enum" with constant values 2 | /// Doing it this way avoids explicit type casting with Rust enums 3 | #[non_exhaustive] 4 | pub struct Color; 5 | 6 | impl Color { 7 | pub const BLACK: u32 = 0x00000000; 8 | pub const BLUE: u32 = 0x000000ff; 9 | pub const CYAN: u32 = 0x0000ffff; 10 | pub const GREEN: u32 = 0x0000ff00; 11 | pub const MAGENTA: u32 = 0x00ff00ff; 12 | pub const RED: u32 = 0x00ff0000; 13 | pub const WHITE: u32 = 0xffffffff; 14 | pub const YELLOW: u32 = 0x00ffff00; 15 | } 16 | 17 | #[allow(unused)] 18 | pub fn blend_colors(foreground: u32, background: u32, blend_factor: u8) -> u32 { 19 | let fg_ratio = blend_factor as u32; 20 | let bg_ratio = 255 - fg_ratio as u32; 21 | 22 | let r = (((foreground >> 16) & 0xff) * fg_ratio + ((background >> 16) & 0xff) * bg_ratio) / 255; 23 | let g = (((foreground >> 8) & 0xff) * fg_ratio + ((background >> 8) & 0xff) * bg_ratio) / 255; 24 | let b = ((foreground & 0xff) * fg_ratio + (background & 0xff) * bg_ratio) / 255; 25 | 26 | (r << 16) | (g << 8) | b 27 | } 28 | -------------------------------------------------------------------------------- /catten/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); 5 | 6 | match arch.as_str() { 7 | "x86_64" => { 8 | // Tell cargo to pass the linker script to the linker... 9 | println!("cargo:rustc-link-arg=-Tlinker/x86_64.ld"); 10 | // ...and to re-run if it changes. 11 | println!("cargo:rerun-if-changed=linker/x86_64.ld"); 12 | } 13 | "aarch64" => { 14 | // Tell cargo to pass the linker script to the linker... 15 | println!("cargo:rustc-link-arg=-Tlinker/aarch64.ld"); 16 | // ...and to re-run if it changes. 17 | println!("cargo:rerun-if-changed=linker/aarch64.ld"); 18 | } 19 | "riscv64" => { 20 | // Tell cargo to pass the linker script to the linker... 21 | println!("cargo:rustc-link-arg=-Tlinker/riscv64.ld"); 22 | // ...and to re-run if it changes. 23 | println!("cargo:rerun-if-changed=linker/riscv64.ld"); 24 | } 25 | _ => panic!("Invalid ISA"), 26 | } 27 | 28 | println!("cargo:rerun-if-changed=asm"); 29 | } 30 | -------------------------------------------------------------------------------- /catten/src/cpu/scheduler/lp_schedulers/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Logical Processor Local Schedulers 2 | use alloc::collections::btree_map::BTreeMap; 3 | use alloc::vec::Vec; 4 | 5 | use crate::cpu::isa::memory::paging::HwAsid; 6 | use crate::cpu::scheduler::threads::ThreadId; 7 | use crate::memory::AddressSpaceId; 8 | 9 | pub struct LocalScheduler { 10 | run_queue: BTreeMap>, 11 | index: (AddressSpaceId, usize), 12 | advance: extern "C" fn(&mut Self) -> Status, 13 | } 14 | 15 | #[repr(u8)] 16 | pub enum Status { 17 | QueueFull, 18 | ThreadNotFound, 19 | AsNotFound, 20 | } 21 | 22 | impl LocalScheduler { 23 | pub fn add_thread(&mut self, thread: ThreadId) -> Status { 24 | todo!() 25 | } 26 | 27 | pub fn remove_threads(&mut self, thread_ids: Vec) { 28 | todo!() 29 | } 30 | 31 | pub fn remove_as(&mut self, asid: AddressSpaceId) { 32 | todo!() 33 | } 34 | 35 | pub fn is_idle(&self) -> bool { 36 | self.run_queue.is_empty() 37 | } 38 | 39 | pub fn asid_to_hwasid(&self, asid: AddressSpaceId) -> Option { 40 | todo!() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/interrupts/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod context_switch; 2 | pub mod exceptions; 3 | pub mod idt; 4 | pub mod ipis; 5 | pub mod x2apic; 6 | 7 | use idt::*; 8 | use spin::{Lazy, Mutex}; 9 | 10 | use crate::cpu::isa::init::gdt; 11 | use crate::cpu::isa::interface::interrupts::InterruptManagerIfce; 12 | use crate::cpu::isa::lp::LpId; 13 | use crate::memory::IdTable; 14 | 15 | pub static BSP_IDT: Mutex = Mutex::new(Idt::new()); 16 | pub static IDT_TABLE: Lazy>> = Lazy::new(IdTable::new); 17 | 18 | pub fn register_fixed_isr_gates(idt: &mut Idt) { 19 | exceptions::load_exceptions(idt); 20 | } 21 | 22 | pub struct IsrDesc { 23 | pub target_lp: LpId, 24 | pub vector: u8, 25 | pub handler: extern "C" fn(), 26 | } 27 | 28 | pub struct InterruptManager; 29 | 30 | pub enum Error {} 31 | 32 | impl InterruptManagerIfce for InterruptManager { 33 | type Error = Error; 34 | type IsrDesc = IsrDesc; 35 | type LocalIntCtlr = x2apic::X2Apic; 36 | 37 | fn init_interrupt_structures() -> Result<(), Self::Error> { 38 | todo!() 39 | } 40 | 41 | fn register_interrupt_handler(isrd: &Self::IsrDesc) -> Result<(), Self::Error> { 42 | todo!() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/interface/memory/address.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | 3 | pub trait Address: 4 | Copy 5 | + Clone 6 | + Debug 7 | + PartialEq 8 | + Eq 9 | + PartialOrd 10 | + Ord 11 | + Into 12 | + Default 13 | + core::ops::Add 14 | + core::ops::Sub 15 | + core::ops::Add 16 | + core::ops::Sub 17 | { 18 | const MIN: Self; 19 | const MAX: Self; 20 | const NULL: Self; 21 | 22 | fn is_aligned_to(&self, alignment: usize) -> bool; 23 | fn next_aligned_to(&self, alignment: usize) -> Self; 24 | fn prev_aligned_to(&self, alignment: usize) -> Self; 25 | fn is_valid(value: usize) -> bool; 26 | fn is_null(&self) -> bool; 27 | 28 | unsafe fn from_unchecked(addr: usize) -> Self; 29 | } 30 | 31 | pub trait VirtualAddress: Address { 32 | fn from_ptr(ptr: *const T) -> Self; 33 | fn from_mut(ptr: *mut T) -> Self; 34 | fn into_ptr(self) -> *const T; 35 | fn into_mut(self) -> *mut T; 36 | } 37 | 38 | pub trait PhysicalAddress: Address { 39 | unsafe fn into_hhdm_ptr(self) -> *const T; 40 | unsafe fn into_hhdm_mut(self) -> *mut T; 41 | } 42 | -------------------------------------------------------------------------------- /catten/src/log/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Kernel Logging Macros 2 | //! 3 | //! This module provides convenient macros for logging messages to the kernel 4 | //! log. They will be updated as the kernel develops to provide more 5 | //! functionality and use an actual kernel log that will reside in memory and be 6 | //! stored in a file. For now they print to the COM1 serial port on x86_64 7 | //! systems only. 8 | 9 | #[macro_export] 10 | macro_rules! log { 11 | ($text:expr $(, $arg:tt)*) => ({ 12 | if cfg!(all(target_arch = "x86_64", debug_assertions)) { 13 | use core::fmt::Write; 14 | use crate::drivers::uart::ns16550::LOG_PORT; 15 | let _ = write!(LOG_PORT.lock(), $text $(, $arg)*); 16 | } 17 | use crate::print; 18 | print!($text $(, $arg)*); 19 | }) 20 | } 21 | #[macro_export] 22 | macro_rules! logln { 23 | ($text:expr $(, $arg:tt)*) => ({ 24 | if cfg!(all(target_arch = "x86_64", debug_assertions)) { 25 | use core::fmt::Write; 26 | use crate::drivers::uart::ns16550::LOG_PORT; 27 | let _ = writeln!(LOG_PORT.lock(), $text $(, $arg)*); 28 | } 29 | use crate::println; 30 | println!($text $(, $arg)*); 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/init/mod.rs: -------------------------------------------------------------------------------- 1 | mod ap; 2 | mod bsp; 3 | pub mod gdt; 4 | 5 | use crate::cpu::isa::interface::init::InitInterface; 6 | use crate::cpu::isa::lp::ops::get_lp_id; 7 | use crate::cpu::isa::memory::paging::PAGE_SIZE; 8 | use crate::logln; 9 | 10 | const INTERRUPT_STACK_SIZE: usize = PAGE_SIZE * 4; 11 | 12 | pub struct IsaInitializer; 13 | 14 | impl InitInterface for IsaInitializer { 15 | type Error = core::convert::Infallible; 16 | 17 | fn init_bsp() -> Result<(), Self::Error> { 18 | let lp_id = get_lp_id!(); 19 | logln!("LP{}: Starting x86-64 bootstrap processor initialization", lp_id); 20 | // Initialize TSS, GDT, and IDT 21 | bsp::init_bsp(); 22 | logln!("LP{}: x86-64 bootstrap processor initialization complete", lp_id); 23 | // return success 24 | Ok(()) 25 | } 26 | 27 | fn init_ap() -> Result<(), Self::Error> { 28 | let lp_id = get_lp_id!(); 29 | logln!("LP{}: Starting x86-64 application processor initialization", lp_id); 30 | // Initialize TSS, GDT, and IDT 31 | ap::init_ap(); 32 | logln!("LP{}: x86-64 logical processor initialization complete", lp_id); 33 | Ok(()) 34 | } 35 | 36 | fn deinit() -> Result<(), Self::Error> { 37 | // Nothing to do here yet 38 | Ok(()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/memory/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod address; 2 | pub mod paging; 3 | pub mod tlb; 4 | 5 | pub use crate::cpu::isa::interface::memory::MemoryInterface; 6 | use crate::cpu::isa::memory::address::paddr::PAddrError; 7 | use crate::memory::linear::Error as VMemError; 8 | use crate::memory::physical::Error as PMemError; 9 | 10 | #[derive(Debug, Clone, Copy)] 11 | pub enum Error { 12 | Unmapped, 13 | AlreadyMapped, 14 | NullVAddrNotAllowed, 15 | VAddrNotPageAligned, 16 | NoRequestedVAddrRegionAvailable, 17 | PMemError(PMemError), 18 | VMemError(VMemError), 19 | } 20 | 21 | impl From for Error { 22 | fn from(err: PMemError) -> Self { 23 | Error::PMemError(err) 24 | } 25 | } 26 | 27 | impl From for Error { 28 | fn from(err: PAddrError) -> Self { 29 | Error::PMemError(PMemError::PAddrError(err)) 30 | } 31 | } 32 | 33 | impl From for Error { 34 | fn from(err: VMemError) -> Self { 35 | Error::VMemError(err) 36 | } 37 | } 38 | pub struct MemoryInterfaceImpl; 39 | 40 | impl MemoryInterface for MemoryInterfaceImpl { 41 | type AddressSpace = paging::AddressSpace; 42 | type Error = Error; 43 | type PAddr = address::paddr::PAddr; 44 | type VAddr = address::vaddr::VAddr; 45 | 46 | const PAGE_SIZE: usize = paging::PAGE_SIZE; 47 | } 48 | -------------------------------------------------------------------------------- /catten/src/common/bitwise.rs: -------------------------------------------------------------------------------- 1 | /// Obtain a bitfield from a larger word size. 2 | pub fn mask_shift_read(val: T, mask: T, shift: u8) -> T 3 | where 4 | T: core::ops::BitAnd + core::ops::Shr, 5 | { 6 | (val & mask) >> shift 7 | } 8 | /// Used to compare against bitfields embedded in larger word sizes. 9 | pub fn mask_shift_cmp(val: T, mask: T, shift: u8, cmp: T) -> bool 10 | where 11 | T: core::ops::BitAnd + core::ops::Shr + core::cmp::PartialEq + Copy, 12 | { 13 | (val & mask) >> shift == cmp 14 | } 15 | pub fn mask_from_shift_len(shift: u8, len: u8) -> T 16 | where 17 | T: core::ops::Shl 18 | + core::ops::Sub 19 | + core::ops::BitOr 20 | + From, 21 | { 22 | ((T::from(1) << len) - T::from(1)) << shift 23 | } 24 | /// write a bitfield into a larger word size. 25 | pub fn splice_into(dest: &mut T, val: T, mask: T, shift: u8) -> Result 26 | where 27 | T: core::ops::Not 28 | + core::ops::Shl 29 | + core::ops::BitOrAssign 30 | + core::ops::BitAndAssign 31 | + core::ops::BitAnd 32 | + Copy, 33 | { 34 | dest.bitand_assign(!mask); 35 | // set the bits in dest 36 | dest.bitor_assign((val << shift) & mask); 37 | Ok(*dest) 38 | } 39 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/constants/msrs.rs: -------------------------------------------------------------------------------- 1 | //! # x86-64 Model Specific Registers (MSRs) 2 | //! 3 | //! Make sure to check any necessary CPUID features before using these MSRs as not all of them are 4 | //! architectural. 5 | #[inline(always)] 6 | pub unsafe fn read(msr: u32) -> u64 { 7 | let low: u32; 8 | let high: u32; 9 | 10 | unsafe { 11 | core::arch::asm!( 12 | "rdmsr", 13 | out("eax") low, 14 | out("edx") high, 15 | in("ecx") msr, 16 | options(nomem, nostack) 17 | ); 18 | } 19 | 20 | ((high as u64) << 32) | (low as u64) 21 | } 22 | 23 | #[inline(always)] 24 | pub unsafe fn write(msr: u32, value: u64) { 25 | let low = value as u32; 26 | let high = (value >> 32) as u32; 27 | 28 | unsafe { 29 | core::arch::asm!( 30 | "wrmsr", 31 | in("eax") low, 32 | in("edx") high, 33 | in("ecx") msr, 34 | options(nomem, nostack) 35 | ); 36 | } 37 | } 38 | 39 | /// # x2APIC MSRs 40 | /// Ref: AMD APM 16.11.1 and Intel SDM Vol.3 12.12.1.2 41 | pub const LAPIC_ID: u32 = 0x802; 42 | pub const INTERRUPT_COMMAND_REGISTER: u32 = 0x830; 43 | pub const APIC_TIMER_LVTR: u32 = 0x832; 44 | pub const APIC_TIMER_INITIAL_COUNT: u32 = 0x838; 45 | pub const APIC_TIMER_CURRENT_COUNT: u32 = 0x839; 46 | pub const APIC_TIMER_DIVIDE_CONFIGURATION: u32 = 0x83e; 47 | /// # TSC_AUX MSR 48 | pub const TSC_AUX: u32 = 0xc000_0103; 49 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/interface/memory/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod address; 2 | 3 | use crate::cpu::isa::memory::MemoryInterfaceImpl; 4 | use crate::cpu::isa::memory::address::paddr::PAddr; 5 | use crate::cpu::isa::memory::address::vaddr::VAddr; 6 | pub use crate::memory::linear::{MemoryMapping, PageType}; 7 | 8 | pub trait MemoryInterface { 9 | type VAddr: address::VirtualAddress; 10 | type PAddr: address::PhysicalAddress; 11 | type Error; 12 | type AddressSpace: AddressSpaceInterface; 13 | 14 | const PAGE_SIZE: usize; 15 | } 16 | 17 | pub trait AddressSpaceInterface { 18 | fn get_current() -> Self; 19 | fn load(&self) -> Result<(), ::Error>; 20 | fn find_free_region( 21 | &mut self, 22 | n_pages: usize, 23 | range: (VAddr, VAddr), 24 | ) -> Result::Error>; 25 | fn map_page( 26 | &mut self, 27 | mapping: MemoryMapping, 28 | ) -> Result<(), ::Error>; 29 | fn unmap_page( 30 | &mut self, 31 | vaddr: VAddr, 32 | ) -> Result::Error>; 33 | fn is_mapped( 34 | &mut self, 35 | vaddr: VAddr, 36 | ) -> Result::Error>; 37 | fn translate_address( 38 | &mut self, 39 | vaddr: VAddr, 40 | ) -> Result::Error>; 41 | } 42 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/interrupts/x2apic/id.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::btree_map::BTreeMap; 2 | 3 | use crate::cpu::isa::lp::LpId; 4 | 5 | pub(super) static mut X2APIC_ID_TABLE: BTreeMap = BTreeMap::new(); 6 | 7 | /// x2APIC MSR space docs: AAPM 16.11.1 and ISDM 12.12.1.2 8 | pub static X2APIC_ID_REG: u32 = 0x802; 9 | pub static X2APIC_LOGICAL_DEST_REG: u32 = 0x80d; 10 | 11 | #[derive(Clone, Copy, Debug)] 12 | #[repr(C, packed)] 13 | pub struct LapicId { 14 | pub physical: PhysicalLapicId, 15 | pub logical: LogicalLapicId, 16 | } 17 | 18 | impl LapicId { 19 | pub fn get_local() -> Self { 20 | let physical: PhysicalLapicId; 21 | let logical: u32; 22 | unsafe { 23 | core::arch::asm! { 24 | "mov ecx, X2APIC_ID_REG", // x2APIC ID Register 25 | "rdmsr", 26 | "mov [{phys:e}], eax", 27 | "mov ecx, X2APIC_LOGICAL_DEST_REG", // x2APIC Logical Destination Register 28 | "rdmsr", 29 | "mov [{log:e}], eax", 30 | phys = out(reg) physical, 31 | log = out(reg) logical, 32 | } 33 | } 34 | LapicId { 35 | physical, 36 | logical: unsafe { core::mem::transmute(logical) }, 37 | } 38 | } 39 | } 40 | pub(super) type PhysicalLapicId = u32; 41 | #[derive(Clone, Copy, Debug)] 42 | #[repr(C, packed)] 43 | pub(super) struct LogicalLapicId { 44 | cluster_id: u16, 45 | apic_bitmask: u16, 46 | } 47 | -------------------------------------------------------------------------------- /catten/src/event/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Event Subsystem 2 | 3 | pub trait Event { 4 | fn register_observer(&mut self, observer: &dyn Observer); 5 | } 6 | 7 | pub trait Observer { 8 | fn notify(&mut self); 9 | } 10 | 11 | pub struct Completion { 12 | completed: bool, 13 | callback: Option, 14 | } 15 | 16 | impl Completion { 17 | pub fn new(callback: Option) -> Self { 18 | Completion { 19 | completed: false, 20 | callback, 21 | } 22 | } 23 | 24 | pub fn poll(&self) -> bool { 25 | self.completed 26 | } 27 | 28 | pub fn register_callback(&mut self, callback: fn()) { 29 | self.callback.replace(callback); 30 | } 31 | } 32 | 33 | impl Observer for Completion { 34 | fn notify(&mut self) { 35 | self.completed = true; 36 | if let Some(cb) = self.callback { 37 | cb(); 38 | } 39 | } 40 | } 41 | 42 | pub struct Sentinel { 43 | times_notified: u64, 44 | callback: Option, 45 | } 46 | 47 | impl Sentinel { 48 | pub fn new(callback: Option) -> Self { 49 | Sentinel { 50 | times_notified: 0, 51 | callback, 52 | } 53 | } 54 | 55 | pub fn get_times_notified(&self) -> u64 { 56 | self.times_notified 57 | } 58 | 59 | pub fn register_callback(&mut self, callback: fn()) { 60 | self.callback.replace(callback); 61 | } 62 | } 63 | 64 | impl Observer for Sentinel { 65 | fn notify(&mut self) { 66 | self.times_notified += 1; 67 | if let Some(cb) = self.callback { 68 | cb(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /catten/src/common/collections/id_table.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | use alloc::vec::Vec; 3 | use core::fmt::Debug; 4 | 5 | use hashbrown::HashMap; 6 | use spin::{Mutex, RwLock}; 7 | 8 | pub struct IdTable 9 | where 10 | I: TryFrom + Copy + core::cmp::Eq + core::hash::Hash, 11 | { 12 | map: HashMap>>, 13 | available_ids: Mutex>, 14 | } 15 | 16 | impl IdTable 17 | where 18 | I: TryFrom + Copy + core::cmp::Eq + core::hash::Hash, 19 | >::Error: Debug, 20 | { 21 | pub fn new() -> Self { 22 | IdTable { 23 | map: HashMap::new(), 24 | available_ids: Mutex::new(Vec::new()), 25 | } 26 | } 27 | 28 | pub fn add_element(&mut self, element: T) -> I { 29 | let element_id = { 30 | if let Some(id) = self.available_ids.lock().pop() { 31 | id 32 | } else { 33 | self.map.len().try_into().unwrap() 34 | } 35 | }; 36 | self.map.insert(element_id, Arc::new(RwLock::new(element))); 37 | element_id 38 | } 39 | 40 | pub fn try_get_element_arc(&self, element_id: I) -> Option>> { 41 | if let Some(lock_ptr) = self.map.get(&element_id) { 42 | Some(lock_ptr.clone()) 43 | } else { 44 | None 45 | } 46 | } 47 | 48 | pub fn remove_element(&mut self, element_id: I) { 49 | self.map.remove(&element_id); 50 | self.available_ids.lock().push(element_id); 51 | } 52 | } 53 | 54 | unsafe impl Send for IdTable 55 | where 56 | I: TryFrom + Copy + core::cmp::Eq + core::hash::Hash, 57 | T: Send, 58 | { 59 | } 60 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/io/mod.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | use core::ops::Add; 3 | 4 | pub use crate::cpu::isa::interface::io::{IReg8Ifce, OReg8Ifce}; 5 | use crate::memory::PAddr; 6 | use crate::memory::physical::PhysicalAddress; 7 | 8 | #[derive(Copy, Clone, Debug)] 9 | pub enum IoReg8 { 10 | IoPort(u16), 11 | Mmio(PAddr), 12 | } 13 | 14 | impl IReg8Ifce for IoReg8 { 15 | unsafe fn read(&self) -> u8 { 16 | match self { 17 | IoReg8::IoPort(port) => { 18 | let value: u8; 19 | unsafe { 20 | asm!( 21 | "in al, dx", 22 | in("dx") *port, 23 | out("al") value, 24 | ); 25 | } 26 | value 27 | } 28 | IoReg8::Mmio(address) => unsafe { core::ptr::read_volatile(address.into_hhdm_ptr()) }, 29 | } 30 | } 31 | } 32 | 33 | impl OReg8Ifce for IoReg8 { 34 | unsafe fn write(&self, value: u8) { 35 | match self { 36 | IoReg8::IoPort(port) => unsafe { 37 | asm!( 38 | "out dx, al", 39 | in("dx") *port, 40 | in("al") value, 41 | ); 42 | }, 43 | IoReg8::Mmio(address) => unsafe { 44 | core::ptr::write_volatile(address.into_hhdm_mut(), value) 45 | }, 46 | } 47 | } 48 | } 49 | 50 | impl Add for IoReg8 { 51 | type Output = IoReg8; 52 | 53 | fn add(self, rhs: u16) -> Self::Output { 54 | match self { 55 | IoReg8::IoPort(port) => IoReg8::IoPort(port + rhs), 56 | IoReg8::Mmio(address) => IoReg8::Mmio(address + rhs as usize), 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/asm_macros/context_switch.asm: -------------------------------------------------------------------------------- 1 | .macro ctx_save_m 2 | // Save the general purpose registers to the interrupt stack 3 | push r15 4 | push r14 5 | push r13 6 | push r12 7 | push r11 8 | push r10 9 | push r9 10 | push r8 11 | push rsi 12 | push rdi 13 | push rbp 14 | push rdx 15 | push rcx 16 | push rbx 17 | push rax 18 | // compute the address of the thread context in the logical processor's local data segment 19 | mov rax, LP_LOCAL_THREAD_OFFSET 20 | add rax, THREAD_TC_OFFSET 21 | // save the stack pointer for CPL0 into the thread context for later restoration 22 | mov gs:[rip + rax + TC_RSP_CPL0_OFFSET], rsp 23 | // Save the current page table hierarchy base into the thread context 24 | mov rdx, cr3 25 | mov gs:[rip + rax + TC_CR3_OFFSET], rdx 26 | .endm 27 | 28 | .macro ctx_load_m 29 | mov rax, LP_LOCAL_THREAD_OFFSET 30 | add rax, THREAD_TC_OFFSET 31 | // Load the correct page table hierarchy into the page table base control register, CR3 32 | mov rdx, gs:[rip + rax + TC_CR3_OFFSET] 33 | mov cr3, rdx 34 | // Load the stack pointer for CPL0 from the thread context to restore kernel stack 35 | // with the register state and interrupt return frame 36 | mov rsp, gs:[rip + rax + TC_RSP_CPL0_OFFSET] 37 | // write rsp for CPL=0 as it will be at the time of iretq to the TSS 38 | mov rdi, rsp + 19 * 8 // 15 general purpose registers + 4 quadwords of iretq frame 39 | call write_rsp0 40 | // Restore the general purpose registers 41 | pop rax 42 | pop rbx 43 | pop rcx 44 | pop rdx 45 | pop rbp 46 | pop rdi 47 | pop rsi 48 | pop r8 49 | pop r9 50 | pop r10 51 | pop r11 52 | pop r12 53 | pop r13 54 | pop r14 55 | pop r15 56 | .endm -------------------------------------------------------------------------------- /catten/src/init/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Initialization Module 2 | 3 | use crate::cpu::isa::init::IsaInitializer; 4 | use crate::cpu::isa::interface::init::InitInterface; 5 | use crate::cpu::isa::lp; 6 | use crate::logln; 7 | use crate::memory::PHYSICAL_FRAME_ALLOCATOR; 8 | use crate::memory::allocators::global_allocator::init_primary_allocator; 9 | 10 | pub fn bsp_init() { 11 | logln!("Performing ISA specific initialization..."); 12 | match IsaInitializer::init_bsp() { 13 | Ok(_) => logln!("ISA specific initialization complete."), 14 | Err(e) => { 15 | // initialization failure is irrecoverable 16 | panic!("ISA specific initialization failed: {:?}", e); 17 | } 18 | } 19 | logln!("Performing ISA independent initialization..."); 20 | logln!("Initializing physical memory..."); 21 | match PHYSICAL_FRAME_ALLOCATOR.try_lock() { 22 | Some(pfa) => { 23 | logln!("PhysicalFrameAllocator: {:?}", pfa); 24 | } 25 | None => { 26 | panic!("Failed to acquire lock on PhysicalFrameAllocator."); 27 | } 28 | } 29 | logln!("Initializing kernel allocator..."); 30 | init_primary_allocator(); 31 | logln!("Intialized kernel allocator."); 32 | logln!("ISA independent initialization complete."); 33 | logln!("BSP initialization complete."); 34 | } 35 | 36 | pub fn ap_init() { 37 | let lp_id = lp::ops::get_lp_id!(); 38 | logln!("Initializing LP {}...", lp_id); 39 | logln!("LP {}: Performing ISA specific initialization...", lp_id); 40 | match IsaInitializer::init_ap() { 41 | Ok(_) => logln!("LP {}: ISA specific initialization complete.", lp_id), 42 | Err(e) => { 43 | // initialization failure is irrecoverable 44 | panic!("LP {}: ISA specific initialization failed: {:?}", lp_id, e); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /catten/src/self_test/memory/pmem.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::isa::interface::memory::address::PhysicalAddress; 2 | use crate::logln; 3 | use crate::memory::PHYSICAL_FRAME_ALLOCATOR; 4 | 5 | pub fn test_pmem() { 6 | logln!("Starting physical memory subsystem tests..."); 7 | logln!("Attempting to allocate a physical memory frame."); 8 | 9 | let mut pfa_lock = PHYSICAL_FRAME_ALLOCATOR.lock(); 10 | 11 | match pfa_lock.allocate_frame() { 12 | Ok(ref frame) => { 13 | logln!("Allocated a frame at {:?}.", frame); 14 | let magic_number = 0xcafebabeu32; 15 | logln!("Writing magic number 0x{:X} to the beginning of the frame.", magic_number); 16 | unsafe { 17 | let frame_ptr = frame.into_hhdm_mut::(); 18 | frame_ptr.write(magic_number); 19 | logln!("Reading back magic number from the frame: {:X}", (frame_ptr.read())); 20 | } 21 | match pfa_lock.deallocate_frame(*frame) { 22 | Ok(()) => { 23 | logln!("Successfully deallocated frame."); 24 | } 25 | Err(e) => { 26 | logln!("Failed to deallocate frame!"); 27 | panic!( 28 | "Self-test failure: Failed to deallocate a physical memory frame at \ 29 | address {:?}. Error: {:?}", 30 | frame, e 31 | ); 32 | } 33 | } 34 | } 35 | Err(e) => { 36 | logln!("Failed to allocate a frame!"); 37 | panic!( 38 | "Self-test failure: Failed to allocate a frame from the physical frame allocator. \ 39 | Error: {:?}", 40 | e 41 | ); 42 | } 43 | } 44 | logln!("All physical memory subsystem tests passed."); 45 | } 46 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/timers/i8254.rs: -------------------------------------------------------------------------------- 1 | //! # Intel 8254 Compatible Programmable Interval Timer 2 | //! 3 | //! At present this timer is only used to measure the TSC frequency. 4 | //! It is a legacy timer present in all x86-64 systems however it should not be used 5 | //! for any purpose other than TSC calibration as it is obsolete and this kernel 6 | //! uses the TSC and APIC timers exclusively on the x86-64 architecture. 7 | //! 8 | //! Ref: https://osdev.wiki/wiki/Programmable_Interval_Timer 9 | 10 | use crate::cpu::isa::io::{IReg8Ifce, IoReg8, OReg8Ifce}; 11 | 12 | static PIT_CH0_DATA_REG: IoReg8 = IoReg8::IoPort(0x40); 13 | static PIT_CH1_DATA_REG: IoReg8 = IoReg8::IoPort(0x41); 14 | static PIT_CH2_DATA_REG: IoReg8 = IoReg8::IoPort(0x42); 15 | static PIT_MODE_COMMAND_REG: IoReg8 = IoReg8::IoPort(0x43); 16 | static PIT_CH2_GATE_AND_OUTPUT_REG: IoReg8 = IoReg8::IoPort(0x61); 17 | const CH2_OUTPUT_BIT: u8 = 5; 18 | const CH2_GATE_INPUT_BIT: u8 = 0; 19 | const CH2_SPEAKER_BIT: u8 = 1; 20 | 21 | pub const PIT_FREQUENCY_HZ: u64 = 1_193_182; 22 | 23 | fn make_command_byte(channel: u8, access_mode: u8, operating_mode: u8) -> u8 { 24 | ((channel & 0b11) << 6) | ((access_mode & 0b11) << 4) | ((operating_mode & 0b111) << 1) 25 | } 26 | 27 | pub fn set_interrupt_on_terminal_count(count: u16) { 28 | let cb = make_command_byte(2, 0b11, 0b000); 29 | let low_byte = (count & 0xff) as u8; 30 | let high_byte = ((count >> 8) & 0xff) as u8; 31 | unsafe { 32 | let mut pgo = PIT_CH2_GATE_AND_OUTPUT_REG.read(); 33 | pgo |= 1 << CH2_GATE_INPUT_BIT; 34 | pgo &= !(1 << CH2_SPEAKER_BIT); 35 | PIT_CH2_GATE_AND_OUTPUT_REG.write(pgo); // Enable gate input 36 | PIT_MODE_COMMAND_REG.write(cb); 37 | PIT_CH2_DATA_REG.write(low_byte); 38 | PIT_CH2_DATA_REG.write(high_byte); 39 | } 40 | } 41 | 42 | pub fn read_channel_2_output() -> bool { 43 | unsafe { PIT_CH2_GATE_AND_OUTPUT_REG.read() & (1 << CH2_OUTPUT_BIT) != 0 } 44 | } 45 | -------------------------------------------------------------------------------- /catten/src/cpu/multiprocessor/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Multi-Processor Management 2 | use spin::{Lazy, RwLock}; 3 | 4 | use crate::environment::boot_protocol::limine::MP; 5 | use crate::{ap_main, logln}; 6 | 7 | static LP_COUNT: RwLock> = RwLock::new(Lazy::new(|| { 8 | if let Some(mp_res) = MP.get_response() { 9 | mp_res.cpus().len() as u32 10 | } else { 11 | panic!("Limine was not able to start the secondary logical processors!") 12 | } 13 | })); 14 | 15 | pub fn get_lp_count() -> u32 { 16 | **LP_COUNT.read() 17 | } 18 | 19 | #[derive(Debug)] 20 | pub enum MpError { 21 | SecondaryLpStartupFailed, 22 | } 23 | 24 | pub fn start_secondary_lps() -> Result<(), MpError> { 25 | logln!("Starting Secondary LPs..."); 26 | if let Some(res) = MP.get_response() { 27 | logln!("Obtained multiprocessor response from Limine"); 28 | if cfg!(target_arch = "x86_64") { 29 | if res.flags().contains(limine::mp::ResponseFlags::X2APIC) { 30 | logln!("Limine has set all LAPICs to x2APIC mode.") 31 | } else { 32 | panic!("Processor not supported: x2APIC mode is not available."); 33 | } 34 | } 35 | let lps = res.cpus(); 36 | for lp in lps { 37 | logln!("Writing entry point address for LP{}", (lp.id)); 38 | lp.goto_address.write(ap_main); 39 | } 40 | Ok(()) 41 | } else { 42 | Err(MpError::SecondaryLpStartupFailed) 43 | } 44 | } 45 | 46 | use core::sync::atomic::{AtomicU32, Ordering}; 47 | 48 | use crate::cpu::isa::lp::ops::*; 49 | 50 | pub static ID_COUNTER: AtomicU32 = AtomicU32::new(0); 51 | 52 | pub unsafe fn assign_id() { 53 | let lp_id = ID_COUNTER.fetch_add(1, Ordering::SeqCst); 54 | store_lp_id(lp_id); 55 | logln!( 56 | "Logical Processor with local interrupt controller ID = {} has been designated LP{}.", 57 | (get_lic_id!()), 58 | (get_lp_id!()) 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /catten/src/environment/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Firmware Abstraction Layer 2 | //! 3 | //! This module provides an abstraction layer over the myriad firmware interfaces that are provided 4 | //! by modern hardware platforms. It is intended to provide a common interface for interacting with 5 | //! device firmware and/or boot system provided system description structures. Boot time firmware 6 | //! interactions are expected to be handled by the bootloader and this module only provides a common 7 | //! interface over the supported boot protocols. 8 | //! 9 | //! - The Limine boot protocol is used on all supported systems. Accordingly all supported systems 10 | //! are required to provide at least a minimal EFI boot environment that can load a Limine boot 11 | //! protocol compatible bootloader such as Limine itself. 12 | //! - PC like systems including all servers are expected to provide the UEFI and ACPI firmware 13 | //! interfaces in a manner that conforms to the specifications and does not require OS specific 14 | //! hacks. 15 | //! - Embedded systems are expected to at least adhere to the Embedded Base Boot Requirements (EBBR) 16 | //! specification and provide a reduced subset of UEFI boot services and either ACPI tables or a 17 | //! Flattened Device Tree (FDT) if they do not provide full UEFI and ACPI conformant firmware. 18 | //! - All ARM64 systems are expected to provide an ARM Trusted Firmware (ATF) and consequently a 19 | //! Secure Monitor Call (SMC) interface. 20 | //! - x86_64 systems tend to provide firmware operating in System Management Mode (SMM) however the 21 | //! interface to SMM interrupt calls is not standardized and thus must be accessed through ACPI. 22 | //! As such we do not provide a separate module for SMM calls. 23 | 24 | // Advanced Configuration and Power Interface (ACPI) 25 | mod acpi; 26 | pub mod boot_protocol; 27 | // Device Tree 28 | #[cfg(not(target_arch = "x86_64"))] 29 | mod devicetree; 30 | // ARM Secure Monitor Call (SMC) Interface 31 | #[cfg(target_arch = "aarch64")] 32 | mod arm_smc; 33 | // Unified Extensible Firmware Interface (UEFI) Runtime Services 34 | mod uefi_rt; 35 | -------------------------------------------------------------------------------- /catten/linker/x86_64.ld: -------------------------------------------------------------------------------- 1 | /* Tell the linker that we want an x86_64 ELF64 output file */ 2 | OUTPUT_FORMAT(elf64-x86-64) 3 | OUTPUT_ARCH(i386:x86-64) 4 | 5 | /* We want this symbol to be our entry point */ 6 | ENTRY(bsp_main) 7 | 8 | /* Define the program headers we want so the bootloader gives us the right */ 9 | /* MMU permissions */ 10 | PHDRS 11 | { 12 | text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */ 13 | rodata PT_LOAD FLAGS((1 << 2)) ; /* Read only */ 14 | data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */ 15 | dynamic PT_DYNAMIC FLAGS((1 << 1) | (1 << 2)) ; /* Dynamic PHDR for relocations */ 16 | } 17 | 18 | SECTIONS 19 | { 20 | /* We wanna be placed in the topmost 2GiB of the address space, for optimisations */ 21 | /* and because that is what the Limine spec mandates. */ 22 | /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ 23 | /* that is the beginning of the region. */ 24 | . = 0xffffffff80000000; 25 | 26 | .text : { 27 | *(.text .text.*) 28 | } :text 29 | 30 | /* Move to the next memory page for .rodata */ 31 | . += CONSTANT(MAXPAGESIZE); 32 | 33 | .rodata : { 34 | *(.rodata .rodata.*) 35 | } :rodata 36 | 37 | /* Move to the next memory page for .data */ 38 | . += CONSTANT(MAXPAGESIZE); 39 | 40 | .data : { 41 | *(.data .data.*) 42 | } :data 43 | 44 | /* Dynamic section for relocations, both in its own PHDR and inside data PHDR */ 45 | .dynamic : { 46 | *(.dynamic) 47 | } :data :dynamic 48 | 49 | /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ 50 | /* unnecessary zeros will be written to the binary. */ 51 | /* If you need, for example, .init_array and .fini_array, those should be placed */ 52 | /* above this. */ 53 | .bss : { 54 | *(.bss .bss.*) 55 | *(COMMON) 56 | } :data 57 | 58 | /* Discard .note.* and .eh_frame since they may cause issues on some hosts. */ 59 | /DISCARD/ : { 60 | *(.eh_frame) 61 | *(.note .note.*) 62 | } 63 | } -------------------------------------------------------------------------------- /catten/linker/aarch64.ld: -------------------------------------------------------------------------------- 1 | /* Tell the linker that we want an aarch64 ELF64 output file */ 2 | OUTPUT_FORMAT(elf64-littleaarch64) 3 | OUTPUT_ARCH(aarch64) 4 | 5 | /* We want this symbol to be our entry point */ 6 | ENTRY(bsp_main) 7 | 8 | /* Define the program headers we want so the bootloader gives us the right */ 9 | /* MMU permissions */ 10 | PHDRS 11 | { 12 | text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */ 13 | rodata PT_LOAD FLAGS((1 << 2)) ; /* Read only */ 14 | data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */ 15 | dynamic PT_DYNAMIC FLAGS((1 << 1) | (1 << 2)) ; /* Dynamic PHDR for relocations */ 16 | } 17 | 18 | SECTIONS 19 | { 20 | /* We wanna be placed in the topmost 2GiB of the address space, for optimisations */ 21 | /* and because that is what the Limine spec mandates. */ 22 | /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ 23 | /* that is the beginning of the region. */ 24 | . = 0xffffffff80000000; 25 | 26 | .text : { 27 | *(.text .text.*) 28 | } :text 29 | 30 | /* Move to the next memory page for .rodata */ 31 | . += CONSTANT(MAXPAGESIZE); 32 | 33 | .rodata : { 34 | *(.rodata .rodata.*) 35 | } :rodata 36 | 37 | /* Move to the next memory page for .data */ 38 | . += CONSTANT(MAXPAGESIZE); 39 | 40 | .data : { 41 | *(.data .data.*) 42 | } :data 43 | 44 | /* Dynamic section for relocations, both in its own PHDR and inside data PHDR */ 45 | .dynamic : { 46 | *(.dynamic) 47 | } :data :dynamic 48 | 49 | /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ 50 | /* unnecessary zeros will be written to the binary. */ 51 | /* If you need, for example, .init_array and .fini_array, those should be placed */ 52 | /* above this. */ 53 | .bss : { 54 | *(.bss .bss.*) 55 | *(COMMON) 56 | } :data 57 | 58 | /* Discard .note.* and .eh_frame since they may cause issues on some hosts. */ 59 | /DISCARD/ : { 60 | *(.eh_frame) 61 | *(.note .note.*) 62 | } 63 | } -------------------------------------------------------------------------------- /catten/src/memory/linear/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod address_map; 2 | 3 | pub use crate::cpu::isa::memory::address::paddr::PAddr; 4 | pub use crate::cpu::isa::memory::address::vaddr::VAddr; 5 | 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 7 | pub enum Error { 8 | InvalidPageAttributes, 9 | } 10 | 11 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 12 | pub enum PageType { 13 | NotPresent, 14 | KernelCode, //read, execute 15 | KernelData, //read, write 16 | KernelRoData, //read only 17 | UserCode, //user, read, execute 18 | UserData, //user, read, write 19 | UserRoData, //user, read only 20 | Mmio, //read, write, no caching 21 | DirectMemoryAccess, //read, write, no caching 22 | Framebuffer, //read, write, write combining 23 | } 24 | 25 | impl PageType { 26 | pub fn is_user_accessible(&self) -> bool { 27 | match *self { 28 | PageType::UserCode | PageType::UserData | PageType::UserRoData => true, 29 | _ => false, 30 | } 31 | } 32 | 33 | pub fn is_writable(&self) -> bool { 34 | match *self { 35 | PageType::KernelData 36 | | PageType::UserData 37 | | PageType::Mmio 38 | | PageType::DirectMemoryAccess 39 | | PageType::Framebuffer => true, 40 | _ => false, 41 | } 42 | } 43 | 44 | pub fn is_no_execute(&self) -> bool { 45 | match *self { 46 | PageType::KernelCode | PageType::UserCode => false, 47 | _ => true, 48 | } 49 | } 50 | 51 | pub fn is_uncacheable(&self) -> bool { 52 | match *self { 53 | PageType::Mmio | PageType::DirectMemoryAccess | PageType::Framebuffer => true, 54 | _ => false, 55 | } 56 | } 57 | 58 | pub fn should_combine_writes(&self) -> bool { 59 | if *self == PageType::Framebuffer { 60 | true 61 | } else { 62 | false 63 | } 64 | } 65 | } 66 | #[derive(Debug, Clone)] 67 | pub struct MemoryMapping { 68 | pub vaddr: VAddr, 69 | pub paddr: PAddr, 70 | pub page_type: PageType, 71 | } 72 | -------------------------------------------------------------------------------- /catten/linker/riscv64.ld: -------------------------------------------------------------------------------- 1 | /* Tell the linker that we want a riscv64 ELF64 output file */ 2 | OUTPUT_FORMAT(elf64-littleriscv) 3 | OUTPUT_ARCH(riscv:rv64) 4 | 5 | /* We want this symbol to be our entry point */ 6 | ENTRY(bsp_main) 7 | 8 | /* Define the program headers we want so the bootloader gives us the right */ 9 | /* MMU permissions */ 10 | PHDRS 11 | { 12 | text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */ 13 | rodata PT_LOAD FLAGS((1 << 2)) ; /* Read only */ 14 | data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */ 15 | dynamic PT_DYNAMIC FLAGS((1 << 1) | (1 << 2)) ; /* Dynamic PHDR for relocations */ 16 | } 17 | 18 | SECTIONS 19 | { 20 | /* We wanna be placed in the topmost 2GiB of the address space, for optimisations */ 21 | /* and because that is what the Limine spec mandates. */ 22 | /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ 23 | /* that is the beginning of the region. */ 24 | . = 0xffffffff80000000; 25 | 26 | .text : { 27 | *(.text .text.*) 28 | } :text 29 | 30 | /* Move to the next memory page for .rodata */ 31 | . += CONSTANT(MAXPAGESIZE); 32 | 33 | .rodata : { 34 | *(.rodata .rodata.*) 35 | } :rodata 36 | 37 | /* Move to the next memory page for .data */ 38 | . += CONSTANT(MAXPAGESIZE); 39 | 40 | .data : { 41 | *(.data .data.*) 42 | *(.sdata .sdata.*) 43 | } :data 44 | 45 | /* Dynamic section for relocations, both in its own PHDR and inside data PHDR */ 46 | .dynamic : { 47 | *(.dynamic) 48 | } :data :dynamic 49 | 50 | /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ 51 | /* unnecessary zeros will be written to the binary. */ 52 | /* If you need, for example, .init_array and .fini_array, those should be placed */ 53 | /* above this. */ 54 | .bss : { 55 | *(.sbss .sbss.*) 56 | *(.bss .bss.*) 57 | *(COMMON) 58 | } :data 59 | 60 | /* Discard .note.* and .eh_frame since they may cause issues on some hosts. */ 61 | /DISCARD/ : { 62 | *(.eh_frame) 63 | *(.note .note.*) 64 | } 65 | } -------------------------------------------------------------------------------- /catten/src/self_test/memory/vmem.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::isa::interface::memory::AddressSpaceInterface; 2 | use crate::cpu::isa::interface::memory::address::VirtualAddress; 3 | use crate::cpu::isa::memory::paging::AddressSpace; 4 | use crate::logln; 5 | use crate::memory::PHYSICAL_FRAME_ALLOCATOR; 6 | use crate::memory::linear::{MemoryMapping, PageType, VAddr}; 7 | 8 | pub fn test_vmem() { 9 | logln!("Entering Virtual Memory Subsystem Self Test"); 10 | logln!("Allocating physical frame"); 11 | let frame = PHYSICAL_FRAME_ALLOCATOR.lock().allocate_frame().unwrap(); 12 | logln!("Physical frame allocated"); 13 | logln!("Obtaining current address space"); 14 | let mut current_as = AddressSpace::get_current(); 15 | logln!("Obtained current address space."); 16 | logln!("Creating MemoryMapping struct."); 17 | let higher_half_start: VAddr = VAddr::from(0xffff_ffff_ffff_f000usize); 18 | let mapping = MemoryMapping { 19 | vaddr: higher_half_start, 20 | paddr: frame, 21 | page_type: PageType::KernelData, 22 | }; 23 | logln!( 24 | "Created MemoryMapping struct.\nMapping the allocated frame to the beginning of the \ 25 | higher half." 26 | ); 27 | match current_as.map_page(mapping) { 28 | Ok(_) => logln!("Page mapped successfully."), 29 | Err(e) => panic!("Error mapping page: {:?}", e), 30 | } 31 | let addr: *mut u32 = higher_half_start.into_mut(); 32 | const MAGIC_NUMBER: u32 = 0xcafebabe; 33 | unsafe { 34 | logln!( 35 | "Writing magic number {:x?}_16 to virtual address {:?}", 36 | MAGIC_NUMBER, 37 | higher_half_start 38 | ); 39 | addr.write(MAGIC_NUMBER); 40 | logln!("Reading magic number back from {:?}", higher_half_start); 41 | let read_value = addr.read(); 42 | assert_eq!(read_value, MAGIC_NUMBER); 43 | logln!("Magic number matches."); 44 | logln!("Test completed successfully."); 45 | logln!("Unmapping test page."); 46 | current_as.unmap_page(higher_half_start).expect("Error unmapping page."); 47 | logln!("Test page successfully unmapped."); 48 | logln!("All virtual memory tests passed!"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/lp/ops.rs: -------------------------------------------------------------------------------- 1 | #[rustfmt::skip] 2 | #[macro_export] 3 | macro_rules! halt { 4 | () => { 5 | loop { 6 | unsafe { 7 | core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); 8 | } 9 | } 10 | }; 11 | } 12 | #[rustfmt::skip] 13 | pub use halt; 14 | 15 | #[rustfmt::skip] 16 | #[macro_export] 17 | macro_rules! mask_interrupts { 18 | () => { 19 | unsafe { 20 | asm!("cli", options(nomem, nostack)); 21 | } 22 | }; 23 | } 24 | #[rustfmt::skip] 25 | pub use mask_interrupts; 26 | 27 | #[rustfmt::skip] 28 | #[macro_export] 29 | macro_rules! unmask_interrupts { 30 | () => { 31 | unsafe { 32 | asm!("sti", options(nomem, nostack)); 33 | } 34 | }; 35 | } 36 | #[rustfmt::skip] 37 | pub use unmask_interrupts; 38 | 39 | #[rustfmt::skip] 40 | #[macro_export] 41 | macro_rules! get_lic_id { 42 | () => {{ 43 | let apic_id: u32; 44 | use crate::cpu::isa::constants::*; 45 | unsafe { 46 | core::arch::asm!( 47 | "rdmsr", 48 | inlateout("ecx") msrs::LAPIC_ID => _, 49 | lateout("eax") apic_id, 50 | lateout("edx") _, 51 | options(nostack, preserves_flags) 52 | ); 53 | } 54 | apic_id 55 | }}; 56 | } 57 | #[rustfmt::skip] 58 | pub use get_lic_id; 59 | 60 | use core::arch::asm; 61 | 62 | use super::LpId; 63 | use crate::cpu::isa::constants::*; 64 | 65 | pub fn store_lp_id(id: LpId) { 66 | let id_upper = ((id as u64) >> 32) as u32; 67 | let id_lower = ((id as u64) & (1 << 32) - 1) as u32; 68 | unsafe { 69 | asm!( 70 | "wrmsr", 71 | in("eax") id_lower, 72 | in("edx") id_upper, 73 | in("ecx") msrs::TSC_AUX, 74 | options(nostack, preserves_flags) 75 | ); 76 | } 77 | } 78 | #[macro_export] 79 | macro_rules! get_lp_id { 80 | () => {{ 81 | let mut id: u32; 82 | unsafe { 83 | core::arch::asm!( 84 | "rdpid rax", 85 | out("eax") id, 86 | ); 87 | } 88 | id as crate::cpu::isa::lp::LpId 89 | }}; 90 | } 91 | pub use get_lp_id; 92 | -------------------------------------------------------------------------------- /catten/src/cpu/scheduler/system_scheduler/mod.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::btree_map::BTreeMap; 2 | use alloc::sync::Arc; 3 | use alloc::vec::Vec; 4 | 5 | use spin::Mutex; 6 | 7 | use super::lp_schedulers::LocalScheduler; 8 | use crate::cpu::isa::lp::LpId; 9 | use crate::cpu::isa::lp::ops::get_lp_id; 10 | use crate::cpu::scheduler::threads::ThreadId; 11 | use crate::event::Event; 12 | use crate::memory::AddressSpaceId; 13 | 14 | pub static SYSTEM_SCHEDULER: SystemScheduler = SystemScheduler::new(); 15 | 16 | pub enum Error { 17 | InvalidThread, 18 | } 19 | 20 | /// The system-wide thread scheduler 21 | pub struct SystemScheduler { 22 | lp_schedulers: BTreeMap>>, 23 | } 24 | 25 | impl SystemScheduler { 26 | pub const fn new() -> Self { 27 | Self { 28 | lp_schedulers: BTreeMap::new(), 29 | } 30 | } 31 | 32 | pub fn get_local_scheduler(&self) -> Arc> { 33 | self.lp_schedulers[&get_lp_id!()].clone() 34 | } 35 | 36 | pub fn submit_ready_thread(&self, tid: ThreadId) -> Result { 37 | todo!() 38 | } 39 | 40 | /// Yield the current LP's execution to the scheduler 41 | /// This differs from blocking in that the processor state on entry is discarded 42 | pub unsafe fn yield_lp(&self) { 43 | todo!() 44 | } 45 | 46 | /// Block the specified thread at least until the given event notifies its observers 47 | pub fn block_tid(&self, tid: ThreadId, event: &dyn Event) -> Result<(), Error> { 48 | /* Crate a completion object registered with event and push it to the back of the blocker 49 | queue for the specified thread. If the tid doesn't point to any thread structure then 50 | return Error::InvalidThread. If the thread is not already blocked then send a broadcast 51 | over the kernel IPI-RPC protocol with the EvictThread command. */ 52 | todo!() 53 | } 54 | 55 | pub fn terminate_threads(&self, tids: Vec) { 56 | todo!() 57 | } 58 | 59 | pub fn abort_threads(&self, tids: Vec) { 60 | todo!() 61 | } 62 | 63 | pub fn abort_as_threads(&self, asid: AddressSpaceId) { 64 | todo!() 65 | } 66 | } 67 | 68 | unsafe impl Sync for SystemScheduler {} 69 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/memory/tlb.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | 3 | use crate::cpu::isa::memory::paging::PAGE_SIZE; 4 | use crate::cpu::scheduler::system_scheduler::SYSTEM_SCHEDULER; 5 | use crate::memory::{AddressSpaceId, VAddr}; 6 | 7 | pub fn inval_range_user(asid: AddressSpaceId, base: VAddr, size: usize) { 8 | // SAFETY: This is safe because we are executing in an interrupt context where 9 | // preemption is disabled, and we are not modifying any data structures that 10 | // could be accessed by other threads. 11 | if let Some(pcid) = SYSTEM_SCHEDULER.get_local_scheduler().lock().asid_to_hwasid(asid) { 12 | let raw_base = >::into(base); 13 | for page in (raw_base..raw_base + size * PAGE_SIZE).step_by(PAGE_SIZE) { 14 | let descriptor: [u64; 2] = [page as u64, pcid.get_inner() as u64]; 15 | unsafe { 16 | asm!( 17 | "invpcid {mode:r}, [{desc_ptr}]", 18 | mode = in(reg) 0, 19 | desc_ptr = in(reg) &descriptor, 20 | options(nostack, preserves_flags), 21 | ); 22 | } 23 | } 24 | } 25 | } 26 | 27 | pub fn inval_asid(asid: AddressSpaceId) { 28 | // SAFETY: This is safe because we are executing in an interrupt context where 29 | // preemption is disabled, and we are not modifying any data structures that 30 | // could be accessed by other threads. 31 | if let Some(pcid) = SYSTEM_SCHEDULER.get_local_scheduler().lock().asid_to_hwasid(asid) { 32 | let descriptor: [u64; 2] = [0, pcid.get_inner() as u64]; 33 | unsafe { 34 | asm!( 35 | "invpcid {mode:r}, [{desc_ptr}]", 36 | mode = in(reg) 1, 37 | desc_ptr = in(reg) &descriptor, 38 | options(nostack, preserves_flags), 39 | ); 40 | } 41 | } 42 | } 43 | 44 | pub fn inval_range_kernel(base: VAddr, num_pages: usize) { 45 | let raw_base = >::into(base); 46 | let len_bytes = num_pages * PAGE_SIZE; 47 | for page in (raw_base..raw_base + len_bytes).step_by(PAGE_SIZE) { 48 | unsafe { 49 | asm!( 50 | "invlpg [{page}]", 51 | page = in(reg) page, 52 | options(nostack, preserves_flags), 53 | ); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /catten/src/memory/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Memory Management Subsystem 2 | 3 | pub mod allocators; 4 | pub mod linear; 5 | pub mod physical; 6 | 7 | pub use linear::VAddr; 8 | pub use physical::{MemoryInterface, PAddr, PhysicalFrameAllocator}; 9 | pub use spin::{Lazy, Mutex, RwLock}; 10 | 11 | pub use crate::common::collections::id_table::IdTable; 12 | pub use crate::cpu::isa::interface::memory::AddressSpaceInterface; 13 | pub use crate::cpu::isa::memory::paging::AddressSpace; 14 | use crate::environment::boot_protocol::limine::{HHDM_REQUEST, MEMORY_MAP_REQUEST}; 15 | 16 | pub type AddressSpaceId = usize; 17 | 18 | /*The kernel address space is always ASID 0 and it is handled differently from userspace address 19 | * spaces because it needs to be initialized and accessible before the kernel allocator is 20 | * constructed and initialized. 21 | */ 22 | /// The kernel address space ID. 23 | pub const KERNEL_ASID: AddressSpaceId = 0; 24 | /// The kernel address space. It is initialized to the current address space when this static is 25 | /// first accessed. Which should happen during the BSP init process. 26 | pub static KERNEL_AS: Lazy> = 27 | Lazy::new(|| Mutex::new(AddressSpace::get_current())); 28 | /// Holds all userspace address spaces, indexed by their kernel assigned AddressSpaceId. 29 | type AddressSpaceTable = IdTable; 30 | pub static ADDRESS_SPACE_TABLE: Lazy = Lazy::new(|| AddressSpaceTable::new()); 31 | /// The starting virtual address of the higher half direct mapping region created by the bootloader. 32 | /// This should be remapped by the VMM during BSP init to be placed at the address specified by the 33 | /// kernel virtual memory map at which point this address should be updated to reflect the new 34 | /// location. 35 | pub static HHDM_BASE: Lazy = Lazy::new(|| { 36 | VAddr::from( 37 | HHDM_REQUEST 38 | .get_response() 39 | .expect("Limine failed to provide a higher half direct mapping region.") 40 | .offset() as usize, 41 | ) 42 | }); 43 | /// The physical frame allocator instance used by the kernel. 44 | pub static PHYSICAL_FRAME_ALLOCATOR: Lazy> = Lazy::new(|| { 45 | Mutex::new(PhysicalFrameAllocator::from( 46 | MEMORY_MAP_REQUEST.get_response().expect("Limine failed to provide a memory map."), 47 | )) 48 | }); 49 | -------------------------------------------------------------------------------- /catten/src/self_test/memory/allocator.rs: -------------------------------------------------------------------------------- 1 | use alloc::alloc::{Allocator, Layout}; 2 | 3 | use crate::logln; 4 | use crate::memory::allocators::global_allocator::PRIMARY_ALLOCATOR; 5 | 6 | pub fn test_allocator() { 7 | logln!("Starting the kernel allocator self-test..."); 8 | logln!("Kernel allocator self-test: Allocating 1024 bytes..."); 9 | let ptr = PRIMARY_ALLOCATOR 10 | .allocate(Layout::from_size_align(1050, 64).unwrap()) 11 | .unwrap() 12 | .as_non_null_ptr(); 13 | logln!("Kernel allocator self-test: Allocated 1024 bytes at {:p}", ptr); 14 | logln!("Kernel allocator self-test: Writing to allocated memory..."); 15 | for i in 0..1050 { 16 | unsafe { 17 | ptr.add(i).write(i as u8); 18 | } 19 | } 20 | logln!("Kernel allocator self-test: Write complete."); 21 | logln!("Kernel allocator self-test: Reading from allocated memory..."); 22 | for i in 0..1050 { 23 | assert_eq!(unsafe { ptr.offset(i).read() }, i as u8); 24 | } 25 | logln!("Kernel allocator self-test: Read complete."); 26 | logln!("Kernel allocator self-test: Deallocating allocated memory..."); 27 | unsafe { 28 | PRIMARY_ALLOCATOR.deallocate(ptr, Layout::from_size_align(1050, 64).unwrap()); 29 | } 30 | logln!("Kernel allocator self-test: Deallocation complete."); 31 | logln!("Kernel allocator self-test: Allocating 8 KiB..."); 32 | let ptr = { 33 | PRIMARY_ALLOCATOR 34 | .allocate(Layout::from_size_align(8192, 8).unwrap()) 35 | .unwrap() 36 | .as_non_null_ptr() 37 | }; 38 | logln!("Kernel allocator self-test: Allocated 8 KiB at {:p}", ptr); 39 | logln!("Kernel allocator self-test: Writing to allocated memory..."); 40 | for i in 0..8192 { 41 | unsafe { 42 | ptr.add(i).write(i as u8); 43 | } 44 | } 45 | logln!("Kernel allocator self-test: Write complete."); 46 | logln!("Kernel allocator self-test: Reading from allocated memory..."); 47 | for i in 0..8192 { 48 | assert_eq!(unsafe { ptr.offset(i).read() }, i as u8); 49 | } 50 | logln!("Kernel allocator self-test: Read complete."); 51 | logln!("Kernel allocator self-test: Deallocating allocated memory..."); 52 | unsafe { 53 | PRIMARY_ALLOCATOR.deallocate(ptr, Layout::from_size_align(8192, 8).unwrap()); 54 | } 55 | logln!("Kernel allocator self-test: Deallocation complete."); 56 | 57 | logln!("Kernel allocator self-test: PASSED"); 58 | } 59 | -------------------------------------------------------------------------------- /LICENSE_NOTE.md: -------------------------------------------------------------------------------- 1 | # CharlotteOS License Clarification: Closed Binary Driver Linking for Private Use 2 | 3 | This note documents the interpretation of the **GNU General Public License, version 3 (or any later version)**, as agreed upon by all copyright 4 | holders of this project with respect to linking closed-source binary drivers. 5 | 6 | --- 7 | 8 | ## Statement of Interpretation 9 | 10 | We interpret the GNU GPLv3 to require source code disclosure **only when a covered work is _conveyed_ to a third party**, as defined in Section 0 of the license. 11 | **Private use** — including modifications, combinations, and compilations — is not regulated by the GPL unless and until the resulting work is conveyed. 12 | 13 | Accordingly: 14 | 15 | - Users **may link this kernel with closed-source binary drivers**, including static libraries, **for personal, internal, or evaluation use** 16 | without being required to disclose the source code of the proprietary driver. 17 | 18 | - This applies whether linking is performed manually or by automated build systems, package managers, or similar tools, **provided that the resulting binaries are not conveyed to others**. 19 | 20 | - If the combined work is ever conveyed (e.g., shared, sold, or distributed), **all GPLv3 obligations** — including source code disclosure for the entire combined work — **apply in full**, as specified by the license. 21 | 22 | --- 23 | 24 | ## Scope and Purpose 25 | 26 | This is **not an exception** or a modification to the terms of the GPLv3. 27 | It is a statement of how we, the copyright holders, 28 | **interpret the license’s scope with regard to private, non-conveyed use**. 29 | 30 | This clarification is provided to reduce uncertainty for users and developers working with closed binary drivers in non-distributed contexts. 31 | 32 | --- 33 | 34 | ## Contributor Acknowledgment 35 | 36 | By contributing to this project, **you affirm that you agree with this interpretation of the GPLv3 license** as stated above. 37 | All contributors are assumed to consent to this interpretation as a condition of their participation. 38 | 39 | If you **do not agree** with this interpretation, **please do not contribute** to this project. 40 | 41 | This ensures a consistent and transparent legal foundation for the project, its users, and its community. 42 | 43 | --- 44 | 45 | ## Effective Scope 46 | 47 | This interpretation applies to **all components of the kernel** covered under this project’s **GPLv3-or-later license**, 48 | and represents the **unanimous position of all contributors** as to how the license should be understood in this context. 49 | -------------------------------------------------------------------------------- /catten/src/memory/allocators/global_allocator.rs: -------------------------------------------------------------------------------- 1 | use core::mem::MaybeUninit; 2 | 3 | use spin::Mutex; 4 | use talc::*; 5 | 6 | use crate::common::size::mebibytes; 7 | use crate::cpu::isa::interface::memory::address::VirtualAddress; 8 | use crate::cpu::isa::memory::paging::PAGE_SIZE; 9 | use crate::memory::allocators::memory::try_allocate_and_map_range; 10 | use crate::memory::linear::VAddr; 11 | use crate::memory::linear::address_map::LA_MAP; 12 | use crate::memory::linear::address_map::RegionType::KernelStackArena; 13 | 14 | const INITIAL_HEAP_SIZE: usize = mebibytes(2); 15 | #[global_allocator] 16 | pub static PRIMARY_ALLOCATOR: Talck, ExtendOnOom> = 17 | Talck::new(Talc::new(ExtendOnOom::new())); 18 | 19 | pub fn init_primary_allocator() { 20 | let base = LA_MAP.get_region(KernelStackArena).base; 21 | try_allocate_and_map_range(base, INITIAL_HEAP_SIZE / PAGE_SIZE) 22 | .expect("Failed to allocate and map initial kernel heap memory"); 23 | unsafe { 24 | let mut pa_lock = PRIMARY_ALLOCATOR.lock(); 25 | let span = Span::new(base.into_mut(), (base + INITIAL_HEAP_SIZE).into_mut()); 26 | let returned_span = 27 | pa_lock.claim(span).expect("Talc failed to claim the initial kernel heap"); 28 | pa_lock.oom_handler.heap_span.write(returned_span); 29 | } 30 | } 31 | 32 | pub struct ExtendOnOom { 33 | heap_span: MaybeUninit, 34 | } 35 | 36 | impl ExtendOnOom { 37 | const fn new() -> Self { 38 | ExtendOnOom { 39 | heap_span: MaybeUninit::uninit(), 40 | } 41 | } 42 | } 43 | 44 | impl OomHandler for ExtendOnOom { 45 | fn handle_oom(talc: &mut Talc, _layout: core::alloc::Layout) -> Result<(), ()> { 46 | let raw_span = 47 | unsafe { talc.oom_handler.heap_span.assume_init_ref() }.get_base_acme().unwrap(); 48 | let (base, acme) = (VAddr::from_ptr(raw_span.0), VAddr::from_ptr(raw_span.1)); 49 | let current_size = acme - base; 50 | let new_acme = core::cmp::min( 51 | acme + current_size, 52 | LA_MAP.get_region(KernelStackArena).base + LA_MAP.get_region(KernelStackArena).length, 53 | ); 54 | let new_span = Span::new(base.into_mut(), new_acme.into_mut()); 55 | if let Ok(_) = try_allocate_and_map_range(acme, current_size as usize / PAGE_SIZE) { 56 | unsafe { 57 | *(talc.oom_handler.heap_span.assume_init_mut()) = new_span; 58 | } 59 | unsafe { talc.extend(Span::new(base.into_mut(), acme.into_mut()), new_span) }; 60 | Ok(()) 61 | } else { 62 | Err(()) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/timers/tsc.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | use core::arch::x86_64::__cpuid_count; 3 | 4 | use spin::Lazy; 5 | 6 | use crate::common::integer::nearest_multiple_of; 7 | use crate::cpu::isa::interface::system_info::CpuInfoIfce; 8 | use crate::cpu::isa::system_info::{CpuInfo, IsaExtension}; 9 | use crate::cpu::isa::timers::i8254; 10 | 11 | pub static IS_TSC_INVARIANT: Lazy = 12 | Lazy::new(|| CpuInfo::is_extension_supported(IsaExtension::InvariantTsc)); 13 | pub static TSC_FREQUENCY_HZ: Lazy = Lazy::new(get_tsc_freq); 14 | 15 | pub fn rdtsc() -> u64 { 16 | //! # Read the timestamp counter with proper serialization 17 | let tsc_low: u32; 18 | let tsc_high: u32; 19 | unsafe { 20 | asm! { 21 | "rdtscp", 22 | out("eax") tsc_low, 23 | out("edx") tsc_high, 24 | out("ecx") _ 25 | } 26 | } 27 | ((tsc_high as u64) << 32) | tsc_low as u64 28 | } 29 | 30 | fn get_tsc_freq() -> u64 { 31 | //! # Get the timestamp counter frequency in Hz. 32 | // Determining the TSC frequency differs by vendor 33 | match CpuInfo::get_vendor().as_str() { 34 | "GenuineIntel" => get_tsc_freq_intel(), 35 | _ => get_tsc_freq_common(), 36 | } 37 | } 38 | 39 | fn get_tsc_freq_common() -> u64 { 40 | //! # Measure the TSC frequency using the legacy i8254 41 | use crate::cpu::isa::x86_64::timers::i8254::*; 42 | 43 | const N_SAMPLES: u64 = 8; 44 | let mut samples = [0u64; N_SAMPLES as usize]; 45 | crate::cpu::isa::lp::ops::mask_interrupts!(); 46 | for sample in samples.iter_mut() { 47 | i8254::set_interrupt_on_terminal_count((PIT_FREQUENCY_HZ / 25) as u16); // Set PIT to 40 ms 48 | while read_channel_2_output() {} // Wait for counting to start 49 | let start_tsc = rdtsc(); 50 | while !read_channel_2_output() {} // Wait for counting to complete 51 | let end_tsc = rdtsc(); 52 | *sample = end_tsc - start_tsc; 53 | } 54 | crate::cpu::isa::lp::ops::unmask_interrupts!(); 55 | let mean_tsc_cycles = (samples.iter().sum::() / N_SAMPLES) * 25; 56 | // Round to the nearest MHz since CPU clocks are near universally multiples of that 57 | nearest_multiple_of(mean_tsc_cycles, 1_000_000) 58 | } 59 | 60 | fn get_tsc_freq_intel() -> u64 { 61 | //! # Use the CPUID instruction to determine the frequency of the TSC 62 | //! On Intel processors CPUID leaf 0x15 can be used to determine the frequency of the TSC. 63 | let cpuid_15 = unsafe { __cpuid_count(0x15, 0) }; 64 | if cpuid_15.ecx != 0 && cpuid_15.eax != 0 && cpuid_15.ebx != 0 { 65 | cpuid_15.ecx as u64 * (cpuid_15.eax as u64 / cpuid_15.ebx as u64) 66 | } else { 67 | get_tsc_freq_common() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/aarch64/interrupts/ivt.asm: -------------------------------------------------------------------------------- 1 | // Define the optimized macros 2 | .macro push_volatile_regs 3 | stp x0, x1, [sp, #-16]! 4 | stp x2, x3, [sp, #-16]! 5 | stp x4, x5, [sp, #-16]! 6 | stp x6, x7, [sp, #-16]! 7 | stp x8, x9, [sp, #-16]! 8 | stp x10, x11, [sp, #-16]! 9 | stp x12, x13, [sp, #-16]! 10 | stp x14, x15, [sp, #-16]! 11 | stp x16, x17, [sp, #-16]! 12 | str x18, [sp, #-8]! 13 | .endm 14 | 15 | .macro pop_volatile_regs 16 | ldr x18, [sp], #8 17 | ldp x16, x17, [sp], #16 18 | ldp x14, x15, [sp], #16 19 | ldp x12, x13, [sp], #16 20 | ldp x10, x11, [sp], #16 21 | ldp x8, x9, [sp], #16 22 | ldp x6, x7, [sp], #16 23 | ldp x4, x5, [sp], #16 24 | ldp x2, x3, [sp], #16 25 | ldp x0, x1, [sp], #16 26 | .endm 27 | 28 | .text 29 | .extern sync_dispatcher 30 | .extern irq_dispatcher 31 | .extern fiq_dispatcher 32 | .extern serr_dispatcher 33 | // Interrupt Vector Table 34 | // Given the scheme we empoloy each used IVT entry is 22 instructions exactly while the ISA requires 32 instructions 35 | // This means that we have 10 instructions of padding for each IVT entry 36 | .balign 128 37 | .global ivt 38 | ivt: 39 | // Exception from current EL while using SP_EL0 40 | // Unused because we don't use SP_EL0 as the stack pointer in kernelspace 41 | nop 42 | nop 43 | nop 44 | nop 45 | nop 46 | nop 47 | nop 48 | nop 49 | nop 50 | nop 51 | nop 52 | nop 53 | nop 54 | nop 55 | nop 56 | nop 57 | nop 58 | nop 59 | nop 60 | nop 61 | nop 62 | nop 63 | nop 64 | nop 65 | nop 66 | nop 67 | nop 68 | nop 69 | nop 70 | nop 71 | nop 72 | nop 73 | nop 74 | // Exception from current EL using SP_ELx 75 | .balign 128 76 | push_volatile_regs 77 | bl sync_dispatcher 78 | pop_volatile_regs 79 | eret 80 | 81 | .balign 128 82 | push_volatile_regs 83 | bl irq_dispatcher 84 | pop_volatile_regs 85 | eret 86 | .balign 128 87 | push_volatile_regs 88 | bl fiq_dispatcher 89 | pop_volatile_regs 90 | eret 91 | .balign 128 92 | push_volatile_regs 93 | bl serr_dispatcher 94 | pop_volatile_regs 95 | eret 96 | // Exception from a lower EL and at least one lower EL is AArch64 97 | .balign 128 98 | push_volatile_regs 99 | bl sync_dispatcher 100 | pop_volatile_regs 101 | eret 102 | .balign 128 103 | push_volatile_regs 104 | bl irq_dispatcher 105 | pop_volatile_regs 106 | eret 107 | .balign 128 108 | push_volatile_regs 109 | bl fiq_dispatcher 110 | pop_volatile_regs 111 | eret 112 | .balign 128 113 | push_volatile_regs 114 | bl serr_dispatcher 115 | pop_volatile_regs 116 | eret 117 | // Exception from a lower EL and all lower ELs are AArch32 118 | // Unused because we don't support AArch32 119 | .balign 128 120 | nop 121 | nop 122 | nop 123 | nop 124 | nop 125 | nop 126 | nop 127 | nop 128 | nop 129 | nop 130 | nop 131 | nop 132 | nop 133 | nop 134 | nop 135 | nop 136 | nop 137 | nop 138 | nop 139 | nop 140 | nop 141 | nop 142 | nop 143 | nop 144 | nop 145 | nop 146 | nop 147 | nop 148 | nop 149 | nop 150 | nop 151 | nop 152 | nop 153 | // End of IVT -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/interrupts/ipis/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Inter-Processor Interrupts (IPIs) on the x86_64 Architecture 2 | //! 3 | //! The Catten IPI protocol is designed to work using remote procedure calls (RPCs). 4 | //! This allows for a flexible and extensible way to send IPIs between processors. 5 | //! The protocol supports both unicast (single target) and multicast (multiple targets) IPIs. 6 | //! The implementation is kept as similar as possible across different architectures within reason. 7 | //! 8 | //! Each logical processor (LP) has its own IPI mailbox, which holds a pointer to the the requested 9 | //! RPC and its arguments. To send an IPI create the RPC type instance and attempt to write its 10 | //! address into each target LP's mailbox but only if it is currenlty null using an atomic 11 | //! compare-and-swap (CAS) operation. If you fail to write even one of the the target LPs' 12 | //! mailboxes, revert the ones you did write back to null. When writing to multiple mailboxes you 13 | //! MUST do so in order of ascending LP ID to avoid deadlocks. If you are able to write to all 14 | //! target mailboxes, then immediately send an IPI interrupt to each target LP to trigger the IPI 15 | //! ISR and have your RPC be executed. At the end of the IPI ISR the target LP MUST set its 16 | //! mailbox back to null to indicate it is ready to receive another IPI. 17 | 18 | use alloc::collections::vec_deque::VecDeque; 19 | use alloc::vec::Vec; 20 | use core::arch::global_asm; 21 | 22 | use spin::Mutex; 23 | 24 | use crate::cpu::isa::memory::tlb; 25 | use crate::cpu::scheduler::system_scheduler::SYSTEM_SCHEDULER; 26 | use crate::cpu::scheduler::threads::ThreadId; 27 | use crate::memory::linear::VAddr; 28 | use crate::memory::{AddressSpaceId, KERNEL_ASID}; 29 | 30 | #[unsafe(no_mangle)] 31 | pub static GS_OFFSET_IPI_MAILBOX: usize = 16; 32 | 33 | global_asm!(include_str!("ipis.asm")); 34 | 35 | unsafe extern "C" { 36 | pub fn isr_interprocessor_interrupt(); 37 | } 38 | 39 | #[derive(Clone, Debug)] 40 | pub enum IpiRpc { 41 | VMemInval(AddressSpaceId, VAddr, usize), 42 | AsidInval(AddressSpaceId), 43 | TerminateThreads(Vec), 44 | AbortThreads(Vec), 45 | AbortAsThreads(AddressSpaceId), 46 | } 47 | 48 | #[unsafe(no_mangle)] 49 | pub extern "C" fn ih_interprocessor_interrupt(ipi_queue: &'static mut Mutex>) { 50 | while let Some(ipi) = ipi_queue.lock().pop_front() { 51 | match ipi { 52 | IpiRpc::VMemInval(asid, base, size) => { 53 | if asid == KERNEL_ASID { 54 | tlb::inval_range_kernel(base, size); 55 | } else { 56 | tlb::inval_range_user(asid, base, size); 57 | } 58 | } 59 | IpiRpc::AsidInval(asid) => tlb::inval_asid(asid), 60 | IpiRpc::TerminateThreads(tids) => SYSTEM_SCHEDULER.terminate_threads(tids), 61 | IpiRpc::AbortThreads(tids) => SYSTEM_SCHEDULER.abort_threads(tids), 62 | IpiRpc::AbortAsThreads(asid) => SYSTEM_SCHEDULER.abort_as_threads(asid), 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/aarch64/system_info/isa_extensions.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum IsaExtension { 3 | // 128-bit page table entries for stage 1 translations 4 | FeatD128, 5 | // Large Physical Address Extension for the 64KB granule 6 | FeatLPA, 7 | // Large Physical Address Extension for 4KB and 16KB granules 8 | FeatLPA2, 9 | // Large Virtual Address Extension 52-bit 10 | FeatLVA, 11 | // Large Virtual Address Extension 56-bit 12 | FeatLVA3, 13 | // Non-Maskable Interrupts 14 | FeatNMI, 15 | } 16 | 17 | mod check_feat { 18 | use crate::common::bitwise::*; 19 | 20 | // ARM ARM D24.2.85 21 | pub fn d128() -> bool { 22 | let mut id_aa64mmfr1_el1: u64; 23 | unsafe { 24 | core::arch::asm!("mrs {}, id_aa64mmfr1_el1", out(reg) id_aa64mmfr1_el1); 25 | } 26 | const ID_AA64MMFR1_EL1_D128_SHIFT: u64 = 32; 27 | const ID_AA64MMFR1_EL1_D128_MASK: u64 = 0b1111 << ID_AA64MMFR1_EL1_D128_SHIFT; 28 | const ID_AA64MMFR1_EL1_D128_VAL: u64 = 0b0001; 29 | mask_shift_cmp( 30 | id_aa64mmfr1_el1, 31 | ID_AA64MMFR1_EL1_D128_MASK, 32 | ID_AA64MMFR1_EL1_D128_SHIFT, 33 | ID_AA64MMFR1_EL1_D128_VAL, 34 | ) 35 | } 36 | // ARM ARM D24.2.82 37 | pub fn lpa() -> bool { 38 | let mut id_aa64mmfr0_el1 = 0u64; 39 | unsafe { 40 | core::arch::asm!("mrs {}, id_aa64mmfr0_el1", out(reg) id_aa64mmfr0_el1); 41 | } 42 | const ID_AA64MMFR0_EL1_LPA_SHIFT: u64 = 0; 43 | const ID_AA64MMFR0_EL1_LPA_MASK: u64 = 0b1111 << ID_AA64MMFR0_EL1_LPA_SHIFT; 44 | const ID_AA64MMFR0_EL1_LPA_VAL: u64 = 0b0110; 45 | mask_shift_cmp( 46 | id_aa64mmfr0_el1, 47 | ID_AA64MMFR0_EL1_LPA_MASK, 48 | ID_AA64MMFR0_EL1_LPA_SHIFT, 49 | ID_AA64MMFR0_EL1_LPA_VAL, 50 | ) 51 | } 52 | // ARM ARM D24.2.82 53 | pub fn lpa2() -> bool { 54 | let mut id_aa64mmfr0_el1 = 0u64; 55 | unsafe { 56 | core::arch::asm!("mrs {}, id_aa64mmfr0_el1", out(reg) id_aa64mmfr0_el1); 57 | } 58 | const ID_AA64MMFR0_EL1_LPA2_SHIFT: u64 = 28; 59 | const ID_AA64MMFR0_EL1_LPA2_MASK: u64 = 0b1111 << ID_AA64MMFR0_EL1_LPA2; 60 | const ID_AA64MMFR0_EL1_LPA2_VAL: u64 = 0b0001; 61 | mask_shift_cmp( 62 | id_aa64mmfr0_el1, 63 | ID_AA64MMFR0_EL1_LPA2_MASK, 64 | ID_AA64MMFR0_EL1_LPA2_SHIFT, 65 | ID_AA64MMFR0_EL1_LPA2_VAL, 66 | ) 67 | } 68 | 69 | pub fn nmi() -> bool { 70 | let mut id_aa64pfr1_el1 = 0u64; 71 | unsafe { 72 | core::arch::asm!("mrs {}, id_aa64pfr1_el1", out(reg) id_aa64pfr0_el1); 73 | } 74 | // ARM ARM D24.2.80 75 | const ID_AA64PFR0_EL1_NMI_SHIFT: u64 = 36; 76 | const ID_AA64PFR0_EL1_NMI_MASK: u64 = 0b1111 << ID_AA64PFR0_EL1_NMI_SHIFT; 77 | const ID_AA64PFR0_EL1_NMI_VAL: u64 = 0b0001; 78 | mask_shift_cmp( 79 | id_aa64pfr0_el1, 80 | ID_AA64PFR0_EL1_NMI_MASK, 81 | ID_AA64PFR0_EL1_NMI_SHIFT, 82 | ID_AA64PFR0_EL1_NMI_VAL, 83 | ) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /catten/src/memory/allocators/memory.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::isa::interface::memory::AddressSpaceInterface; 2 | use crate::cpu::isa::memory::paging::PAGE_SIZE; 3 | use crate::logln; 4 | use crate::memory::linear::{MemoryMapping, PageType, VAddr}; 5 | use crate::memory::physical::*; 6 | use crate::memory::{KERNEL_AS, PHYSICAL_FRAME_ALLOCATOR, physical}; 7 | 8 | #[derive(Debug)] 9 | pub enum Error { 10 | PfaError(physical::Error), 11 | IsaMemoryError(crate::cpu::isa::memory::Error), 12 | } 13 | 14 | impl From for Error { 15 | fn from(err: physical::Error) -> Self { 16 | Error::PfaError(err) 17 | } 18 | } 19 | 20 | impl From for Error { 21 | fn from(err: crate::cpu::isa::memory::Error) -> Self { 22 | Error::IsaMemoryError(err) 23 | } 24 | } 25 | 26 | pub fn try_allocate_and_map_range(base: VAddr, num_pages: usize) -> Result<(), Error> { 27 | // lock the kernel address space for writing 28 | let mut kas = KERNEL_AS.lock(); 29 | let mut mapping = MemoryMapping { 30 | vaddr: VAddr::default(), 31 | paddr: PAddr::default(), 32 | page_type: PageType::KernelData, 33 | }; 34 | // allocate and map the pages 35 | // if mapping fails, deallocate and unmap the frames that were allocated 36 | for page_idx in 0..num_pages { 37 | let frame = match PHYSICAL_FRAME_ALLOCATOR.lock().allocate_frame() { 38 | Ok(f) => f, 39 | Err(err) => { 40 | // release the lock so the unmap_and_deallocate_range function can acquire it 41 | drop(kas); 42 | unmap_and_deallocate_range(base, page_idx); 43 | return Err(Error::PfaError(err)); 44 | } 45 | }; 46 | let vaddr = base + (page_idx * PAGE_SIZE) as isize; 47 | mapping.vaddr = vaddr; 48 | mapping.paddr = frame; 49 | if let Err(err) = kas.map_page(mapping.clone()) { 50 | // release the lock so the unmap_and_deallocate_range function can acquire it 51 | drop(kas); 52 | // deallocate and unmap the frames that were allocated 53 | unmap_and_deallocate_range(base, page_idx + 1); 54 | // deallocate the frame that was just allocated 55 | if let Err(err) = PHYSICAL_FRAME_ALLOCATOR.lock().deallocate_frame(frame) { 56 | logln!("Error deallocating frame at {frame:?} during cleanup: {err:?}"); 57 | } 58 | return Err(Error::IsaMemoryError(err)); 59 | } 60 | } 61 | Ok(()) 62 | } 63 | 64 | pub fn unmap_and_deallocate_range(base: VAddr, num_pages: usize) { 65 | let mut kas = KERNEL_AS.lock(); 66 | for page_idx in 0..num_pages { 67 | let vaddr = base + (page_idx * PAGE_SIZE) as isize; 68 | if let Ok(paddr) = kas.translate_address(vaddr) { 69 | if let Err(err) = PHYSICAL_FRAME_ALLOCATOR.lock().deallocate_frame(paddr) { 70 | logln!("Error deallocating frame at {paddr:?} during cleanup: {err:?}"); 71 | } 72 | if let Err(err) = kas.unmap_page(vaddr) { 73 | logln!("Error unmapping vaddr {vaddr:?} during cleanup: {err:?}"); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/interrupts/idt.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | use core::mem::MaybeUninit; 3 | 4 | static mut IDTR: MaybeUninit = MaybeUninit::uninit(); 5 | 6 | const N_INTERRUPT_VECTORS: usize = 256; 7 | 8 | #[derive(Debug)] 9 | #[repr(C, align(16))] 10 | pub struct Idt { 11 | pub gates: [InterruptGate; 256], 12 | } 13 | 14 | impl Idt { 15 | pub const fn new() -> Self { 16 | Idt { 17 | gates: [InterruptGate::new(); 256], 18 | } 19 | } 20 | 21 | pub fn set_gate( 22 | &mut self, 23 | index: usize, 24 | isr_ptr: unsafe extern "C" fn(), 25 | segment_selector: u16, 26 | is_trap: bool, 27 | is_present: bool, 28 | ) { 29 | let gate = &mut self.gates[index]; 30 | let isr_addr = isr_ptr as u64; 31 | 32 | gate.addr0 = u16::try_from(isr_addr & 0xffff).unwrap(); 33 | gate.segment_selector = segment_selector; 34 | gate.reserved_ist_index = 0u8; // the IST is not used 35 | gate.flags = if is_trap { 36 | 0b1111u8 37 | } else { 38 | 0b1110u8 39 | }; //gate type 40 | gate.flags &= !(0b1u8 << 4); //reserved bit 41 | gate.flags &= !(0b11u8 << 5); //privilege ring required to use gate 42 | if is_present { 43 | gate.flags |= 0b1u8 << 7; 44 | } else { 45 | gate.flags &= !(0b1u8 << 7); 46 | } 47 | gate.addr1 = ((isr_addr & (0xffff << 16)) >> 16) as u16; 48 | gate.addr2 = ((isr_addr & (0xffffffff << 32)) >> 32) as u32; 49 | gate.reserved = 0u32; 50 | } 51 | 52 | #[allow(unused)] 53 | pub fn set_present(&mut self, index: usize) { 54 | if index < 256 { 55 | self.gates[index].flags |= 0b1u8 << 7; 56 | } 57 | } 58 | 59 | #[allow(unused)] 60 | pub fn clear_present(&mut self, index: usize) { 61 | if index < 256 { 62 | self.gates[index].flags &= !(0b1u8 << 7); 63 | } 64 | } 65 | 66 | pub fn load(&self) { 67 | unsafe { 68 | IDTR.write(Idtr::new( 69 | size_of::() as u16 * N_INTERRUPT_VECTORS as u16 - 1u16, 70 | self as *const Idt as u64, 71 | )); 72 | asm_load_idt(IDTR.as_ptr()); 73 | } 74 | } 75 | } 76 | #[derive(Clone, Copy, Debug)] 77 | #[repr(C, packed(1))] 78 | pub struct InterruptGate { 79 | addr0: u16, 80 | segment_selector: u16, 81 | reserved_ist_index: u8, 82 | flags: u8, 83 | addr1: u16, 84 | addr2: u32, 85 | reserved: u32, 86 | } 87 | 88 | impl InterruptGate { 89 | const fn new() -> Self { 90 | InterruptGate { 91 | addr0: 0u16, 92 | segment_selector: 0u16, 93 | reserved_ist_index: 0u8, 94 | flags: 0u8, 95 | addr1: 0u16, 96 | addr2: 0u32, 97 | reserved: 0u32, 98 | } 99 | } 100 | } 101 | 102 | #[repr(C, packed)] 103 | struct Idtr { 104 | size: u16, 105 | base: u64, 106 | } 107 | 108 | impl Idtr { 109 | fn new(size: u16, base: u64) -> Self { 110 | Idtr { 111 | size, 112 | base, 113 | } 114 | } 115 | } 116 | 117 | #[inline(always)] 118 | unsafe fn asm_load_idt(idtr: *const Idtr) { 119 | unsafe { 120 | asm!("lidt [rdi]", in("rdi") idtr); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/system_info/mod.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use alloc::vec::Vec; 3 | use core::arch::x86_64::__cpuid_count; 4 | use core::mem::transmute; 5 | 6 | use crate::cpu::isa::interface::system_info::CpuInfoIfce; 7 | 8 | pub enum IsaExtension { 9 | /* indicates support for 5-level paging i.e. 57 bit linear addresses */ 10 | La57, 11 | /* indicates support for `invlpgb` (Invalidate Page with Broadcast) and `tlbsync` 12 | * (TLB shootdown synchronization after `invlpgb`) */ 13 | Invlpgb, 14 | InvariantTsc, 15 | } 16 | 17 | pub struct CpuInfo; 18 | 19 | impl CpuInfoIfce for CpuInfo { 20 | type IsaExtension = IsaExtension; 21 | type Model = String; 22 | type Vendor = String; 23 | 24 | fn get_vendor() -> Self::Vendor { 25 | unsafe { 26 | let vendor_string_raw = __cpuid_count(0, 0); 27 | let utf8 = Vec::from(transmute::<[u32; 3], [u8; 12]>([ 28 | vendor_string_raw.ebx, 29 | vendor_string_raw.edx, 30 | vendor_string_raw.ecx, 31 | ])); 32 | String::from_utf8(utf8).unwrap() 33 | } 34 | } 35 | 36 | fn get_model() -> Self::Model { 37 | unsafe { 38 | let cpuid_results = [ 39 | __cpuid_count(0x80000002, 0), 40 | __cpuid_count(0x80000003, 0), 41 | __cpuid_count(0x80000004, 0), 42 | ]; 43 | let utf8 = Vec::from(transmute::<[u32; 12], [u8; 48]>([ 44 | cpuid_results[0].eax, 45 | cpuid_results[0].ebx, 46 | cpuid_results[0].ecx, 47 | cpuid_results[0].edx, 48 | cpuid_results[1].eax, 49 | cpuid_results[1].ebx, 50 | cpuid_results[1].ecx, 51 | cpuid_results[1].edx, 52 | cpuid_results[2].eax, 53 | cpuid_results[2].ebx, 54 | cpuid_results[2].ecx, 55 | cpuid_results[2].edx, 56 | ])); 57 | // Convert the byte vector to a String, assuming it is valid UTF-8 58 | // Note: This is safe because the cpuid results are guaranteed to be valid UTF-8 59 | // as per the AMD64 Architecture Programmer's Manual. 60 | String::from_utf8(utf8).unwrap().trim_end_matches("\0").into() 61 | } 62 | } 63 | 64 | fn get_paddr_sig_bits() -> u8 { 65 | unsafe { 66 | let cpuid_result = __cpuid_count(0x80000008, 0); 67 | cpuid_result.eax as u8 68 | } 69 | } 70 | 71 | fn get_vaddr_sig_bits() -> u8 { 72 | unsafe { 73 | let cpuid_result = __cpuid_count(0x80000008, 0); 74 | ((cpuid_result.eax >> 8) & 0xff) as u8 75 | } 76 | } 77 | 78 | fn is_extension_supported(extension: Self::IsaExtension) -> bool { 79 | match extension { 80 | IsaExtension::La57 => unsafe { 81 | let cpuid_result = __cpuid_count(0x0000_0007, 0); 82 | (cpuid_result.ecx & 1 << 16) != 0 83 | }, 84 | IsaExtension::Invlpgb => unsafe { 85 | let cpuid_result = __cpuid_count(0x8000_0008, 0); 86 | (cpuid_result.ebx & 1 << 5) != 0 87 | }, 88 | IsaExtension::InvariantTsc => unsafe { 89 | let feat_ext = __cpuid_count(0x80000007, 0); 90 | (feat_ext.edx & (1 << 8)) != 0 91 | }, 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/interrupts/x2apic/mod.rs: -------------------------------------------------------------------------------- 1 | //! # x2APIC Local Advanced Programmable Interrupt Controller 2 | mod id; 3 | 4 | use core::arch::asm; 5 | 6 | use super::super::constants::interrupt_vectors::*; 7 | use crate::cpu::isa::constants::msrs::INTERRUPT_COMMAND_REGISTER; 8 | use crate::cpu::isa::interface::interrupts::LocalIntCtlr; 9 | use crate::cpu::isa::lp::LpId; 10 | use crate::get_lp_id; 11 | 12 | pub enum Error { 13 | InvalidLpId, 14 | } 15 | 16 | /// # Interrupt Command Register Delivery Mode 17 | #[repr(u32)] 18 | enum IcrDeliveryMode { 19 | Fixed = 0b000, 20 | Smi = 0b010, 21 | Nmi = 0b100, 22 | Init = 0b101, 23 | Startup = 0b110, 24 | } 25 | 26 | /// # Interrupt Command Register Destination Shorthand 27 | #[repr(u32)] 28 | enum IcrDestShorthand { 29 | NoShorthand = 0b00, 30 | OnlySelf = 0b01, 31 | AllIncludingSelf = 0b10, 32 | AllExcludingSelf = 0b11, 33 | } 34 | pub struct X2Apic; 35 | 36 | impl X2Apic { 37 | pub fn record_id() { 38 | unsafe { 39 | id::X2APIC_ID_TABLE.insert(get_lp_id!(), id::LapicId::get_local()); 40 | } 41 | } 42 | 43 | fn translate_lp_id(lp_id: LpId) -> Option { 44 | unsafe { id::X2APIC_ID_TABLE.get(&lp_id).cloned() } 45 | } 46 | 47 | fn make_icr_low( 48 | vector: u8, 49 | delivery_mode: IcrDeliveryMode, 50 | is_dest_logical: bool, 51 | level: bool, 52 | is_level_triggered: bool, 53 | dest_shorthand: IcrDestShorthand, 54 | ) -> u32 { 55 | const DELIVERY_MODE_SHIFT: u32 = 8; 56 | const IS_DEST_LOGICAL_SHIFT: u32 = 11; 57 | const LEVEL_SHIFT: u32 = 14; 58 | const IS_LEVEL_TRIGGERED_SHIFT: u32 = 15; 59 | const DEST_SHORTHAND_SHIFT: u32 = 18; 60 | 61 | vector as u32 62 | | ((delivery_mode as u32) << DELIVERY_MODE_SHIFT) 63 | | ((is_dest_logical as u32) << IS_DEST_LOGICAL_SHIFT) 64 | | ((level as u32) << LEVEL_SHIFT) 65 | | ((is_level_triggered as u32) << IS_LEVEL_TRIGGERED_SHIFT) 66 | | ((dest_shorthand as u32) << DEST_SHORTHAND_SHIFT) 67 | } 68 | } 69 | 70 | impl LocalIntCtlr for X2Apic { 71 | type Error = Error; 72 | 73 | /// # Send a unicast IPI to the target logical processor 74 | /// 75 | /// Ref: Intel SDM Vol.3 12.12.10.1 76 | fn send_unicast_ipi(target_lp: LpId) -> Result<(), Error> { 77 | if let Some(apic_id) = Self::translate_lp_id(target_lp) { 78 | // Get the physical APIC ID for the target LP 79 | let dest = apic_id.physical; 80 | // Construct the ICR low dword 81 | let icr_low = Self::make_icr_low( 82 | UNICAST_IPI_VECTOR, 83 | IcrDeliveryMode::Fixed, 84 | false, 85 | true, 86 | false, 87 | IcrDestShorthand::NoShorthand, 88 | ); 89 | // Write to the Interrupt Command Register MSR to send the IPI 90 | unsafe { 91 | asm!{ 92 | "wrmsr", 93 | in("ecx") INTERRUPT_COMMAND_REGISTER, 94 | in("eax") icr_low, 95 | in("edx") dest, 96 | options(nomem, nostack, preserves_flags), 97 | } 98 | } 99 | // Success 100 | Ok(()) 101 | } else { 102 | Err(Error::InvalidLpId) 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /catten/src/drivers/uart/ns16550/mod.rs: -------------------------------------------------------------------------------- 1 | //! # National Semiconductor 16550 Compatible UART Driver 2 | mod legacy_ports; 3 | 4 | use core::fmt::{self, Write}; 5 | use core::result::Result; 6 | 7 | use spin::{Lazy, Mutex}; 8 | 9 | use crate::common::io::Read; 10 | use crate::cpu::isa::interface::io::{IReg8Ifce, OReg8Ifce}; 11 | use crate::cpu::isa::io::{self, IoReg8}; 12 | use crate::drivers::uart::Uart; 13 | 14 | #[cfg(target_arch = "x86_64")] 15 | pub static LOG_PORT: Lazy> = 16 | Lazy::new(|| Mutex::new(Uart16550::try_new(io::IoReg8::IoPort(legacy_ports::COM1)).unwrap())); 17 | 18 | #[derive(Copy, Clone, Debug)] 19 | #[repr(transparent)] 20 | pub struct Uart16550 { 21 | base: IoReg8, 22 | } 23 | #[derive(Debug, Clone, Copy)] 24 | pub enum Error { 25 | FailedSelfTest, 26 | } 27 | 28 | impl Uart16550 { 29 | fn is_transmit_empty(&self) -> i32 { 30 | (unsafe { (self.base + 5).read() } & 0x20).into() 31 | } 32 | 33 | fn received(&self) -> bool { 34 | (unsafe { (self.base + 5).read() } & 1) != 0 35 | } 36 | 37 | fn read_char(&self) -> char { 38 | while !self.received() {} 39 | unsafe { (self.base).read() as char } 40 | } 41 | } 42 | 43 | impl Uart for Uart16550 { 44 | type Error = Error; 45 | 46 | fn try_new(base: IoReg8) -> Result { 47 | let port = Uart16550 { 48 | base: base, 49 | }; 50 | unsafe { 51 | (port.base + 1).write(0x00); // Disable all interrupts 52 | (port.base + 3).write(0x80); // Enable DLAB (set baud rate divisor) 53 | (port.base + 0).write(0x01); // Set divisor to 1 (lo byte) 115200 baud 54 | (port.base + 1).write(0x00); // (hi byte) 55 | (port.base + 3).write(0x03); // 8 bits, no parity, one stop bit 56 | (port.base + 2).write(0xc7); // Enable FIFO, clear them, with 14-byte threshold 57 | (port.base + 4).write(0x0b); // IRQs enabled, RTS/DSR set 58 | (port.base + 4).write(0x1e); // Set in loopback mode, test the serial chip 59 | (port.base + 0).write(0xae); // Test serial chip (send byte 0xAE and check if serial returns same byte) 60 | 61 | if port.base.read() != 0xae { 62 | Err(Error::FailedSelfTest) 63 | } else { 64 | (port.base + 4).write(0x0f); 65 | Ok(port) 66 | } 67 | } 68 | } 69 | } 70 | 71 | impl Write for Uart16550 { 72 | fn write_str(&mut self, s: &str) -> fmt::Result { 73 | for c in s.chars() { 74 | self.write_char(c)? 75 | } 76 | Ok(()) 77 | } 78 | 79 | fn write_char(&mut self, c: char) -> fmt::Result { 80 | while self.is_transmit_empty() == 0 {} 81 | if c.is_ascii() { 82 | if c == '\n' { 83 | unsafe { 84 | (self.base).write('\r' as u8); 85 | (self.base).write('\n' as u8); 86 | } 87 | } else { 88 | unsafe { 89 | (self.base).write(c as u8); 90 | } 91 | } 92 | Ok(()) 93 | } else { 94 | Err(fmt::Error) 95 | } 96 | } 97 | } 98 | 99 | impl Read for Uart16550 { 100 | fn read(&mut self, buf: &mut [u8]) -> usize { 101 | for i in 0..buf.len() { 102 | buf[i] = self.read_char() as u8; 103 | } 104 | buf.len() 105 | } 106 | } 107 | 108 | unsafe impl Send for Uart16550 {} 109 | -------------------------------------------------------------------------------- /catten/src/memory/allocators/stack_allocator.rs: -------------------------------------------------------------------------------- 1 | //! # Kernel Stack Allocator 2 | //! 3 | //! This module provides a an allocator for kernel thread stacks. 4 | //! 5 | //! Please note that on all supported architectures, the stack grows towards lower addresses so this 6 | //! is the highest address of the stack. Also be aware that stacks allocated using this allocator 7 | //! are mapped into the kernel stack arena in the higher half which means it is only suitable for 8 | //! allocating stacks for kernel threads. Stacks are surrounded on both sides by guard pages to 9 | //! allow for safe stack overflow detection and when enabled for the owning thread, transparent 10 | //! reallocation such that from that thread's perspective it is as if the stack overflow never 11 | //! happened. 12 | 13 | use alloc::collections::BTreeSet; 14 | use core::ops::Bound::{Excluded, Unbounded}; 15 | 16 | use spin::{Lazy, RwLock}; 17 | 18 | use super::memory; 19 | use crate::cpu::isa::memory::paging::PAGE_SIZE; 20 | use crate::cpu::isa::memory::{MemoryInterface, MemoryInterfaceImpl}; 21 | use crate::memory::linear::VAddr; 22 | use crate::memory::linear::address_map::LA_MAP; 23 | use crate::memory::{AddressSpaceInterface, KERNEL_AS}; 24 | 25 | static KERNEL_GUARD_PAGE_SET: Lazy>> = 26 | Lazy::new(|| RwLock::new(BTreeSet::new())); 27 | #[derive(Debug)] 28 | pub enum Error { 29 | IsaMemoryIfce(::Error), 30 | AllocatorsMemory(memory::Error), 31 | InvalidStack, 32 | } 33 | 34 | impl From<::Error> for Error { 35 | fn from(err: ::Error) -> Self { 36 | Error::IsaMemoryIfce(err) 37 | } 38 | } 39 | 40 | impl From for Error { 41 | fn from(err: memory::Error) -> Self { 42 | Error::AllocatorsMemory(err) 43 | } 44 | } 45 | 46 | /// Allocate a kernel stack with `n_pages` being the number of usable pages. 47 | /// 48 | /// The address returned by this function is the base address of the stack and it is 49 | /// aligned to the page size and suitable for placing directly into the stack pointer register. 50 | /// This is guaranteed to be the case under all supported architectures. 51 | pub fn allocate_stack(n_pages: usize) -> Result { 52 | const NUM_GUARD_PAGES: usize = 2; 53 | // find a suitable range in the kernel stack arena 54 | let stack_buf_base = KERNEL_AS.lock().find_free_region( 55 | n_pages + NUM_GUARD_PAGES, 56 | (*LA_MAP.get_region(crate::memory::linear::address_map::RegionType::KernelStackArena)) 57 | .clone() 58 | .into(), 59 | )?; 60 | memory::try_allocate_and_map_range(stack_buf_base + PAGE_SIZE, n_pages)?; 61 | Ok(stack_buf_base + PAGE_SIZE * (n_pages + 1)) 62 | } 63 | 64 | /// Deallocate a kernel stack previously allocated by `allocate_stack`. 65 | pub fn deallocate_stack(stack_end: VAddr) -> Result<(), Error> { 66 | let n_pages = validate_stack(stack_end)?; 67 | memory::unmap_and_deallocate_range(stack_end - PAGE_SIZE * (n_pages + 1), n_pages); 68 | Ok(()) 69 | } 70 | 71 | fn validate_stack(stack_end: VAddr) -> Result { 72 | let stack_buf_base = stack_end - PAGE_SIZE; 73 | let guard_set = KERNEL_GUARD_PAGE_SET.read(); 74 | if guard_set.contains(&stack_buf_base) { 75 | let next_guard = guard_set 76 | .range((Excluded(&stack_buf_base), Unbounded)) 77 | .next() 78 | .copied() 79 | .ok_or(Error::InvalidStack)?; 80 | 81 | Ok((next_guard - stack_buf_base) as usize / PAGE_SIZE) 82 | } else { 83 | Err(Error::InvalidStack) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /catten/src/environment/acpi/uacpi_kernel.rs: -------------------------------------------------------------------------------- 1 | //! # uACPI Kernel Interface 2 | //! 3 | //! This module provides kernel hooks for the uACPI library, enabling it to make use of this 4 | //! kernel's functionality. uACPI is a C library which we use via a thin Rust wrapper made with 5 | //! bindgen and cc. As such everything we provide to interact with it uses the C ABI. 6 | 7 | use alloc::ffi::CString; 8 | use core::ffi::*; 9 | 10 | use uacpi_raw::*; 11 | 12 | use crate::cpu::isa::interface::memory::address::VirtualAddress; 13 | use crate::cpu::isa::memory::paging::PAGE_SIZE; 14 | use crate::environment::boot_protocol::limine::RSDP_REQUEST; 15 | use crate::log; 16 | use crate::memory::linear::address_map::{LA_MAP, RegionType}; 17 | use crate::memory::linear::{MemoryMapping, PageType}; 18 | use crate::memory::{AddressSpaceInterface, KERNEL_AS, PAddr, VAddr}; 19 | 20 | #[allow(unused)] 21 | #[unsafe(no_mangle)] 22 | pub extern "C" fn uacpi_kernel_get_rsdp(out_rsdp_address: *mut uacpi_phys_addr) -> uacpi_status { 23 | let rsdp = RSDP_REQUEST.get_response().expect("Limine failed to provide an RSDP").address(); 24 | unsafe { 25 | out_rsdp_address.write(rsdp as u64); 26 | } 27 | uacpi_status_UACPI_STATUS_OK 28 | } 29 | 30 | #[allow(unused)] 31 | #[unsafe(no_mangle)] 32 | pub extern "C" fn uacpi_kernel_map(addr: uacpi_phys_addr, len: uacpi_size) -> *mut c_void { 33 | let corrected_phys_addr = addr & !(0xfff); 34 | let mut corrected_len = len + (addr & 0xfff) as uacpi_size; 35 | if corrected_len % PAGE_SIZE != 0 { 36 | corrected_len += PAGE_SIZE - (corrected_len % PAGE_SIZE); 37 | } 38 | let kernel_dyn_region = LA_MAP.get_region(RegionType::KernelAllocatorArena); 39 | let mut kas = KERNEL_AS.lock(); 40 | let mapping_addr = match kas.find_free_region( 41 | corrected_len / PAGE_SIZE, 42 | (kernel_dyn_region.base, kernel_dyn_region.base + kernel_dyn_region.length), 43 | ) { 44 | Ok(addr) => addr, 45 | Err(_) => return core::ptr::null_mut(), 46 | }; 47 | for offset in (0..corrected_len).step_by(PAGE_SIZE) { 48 | kas.map_page(MemoryMapping { 49 | vaddr: mapping_addr + offset, 50 | paddr: PAddr::from(corrected_phys_addr) + offset, 51 | page_type: PageType::KernelData, 52 | }); 53 | } 54 | // Preserve the offset within the first page 55 | (mapping_addr + (addr & 0xfff) as usize).into_mut() 56 | } 57 | 58 | #[allow(unused)] 59 | #[unsafe(no_mangle)] 60 | pub extern "C" fn uacpi_kernel_unmap(mapped_addr: *mut c_void, len: uacpi_size) { 61 | let corrected_lin_addr = VAddr::from(mapped_addr as usize & !(0xfff)); 62 | let mut corrected_len = len + (mapped_addr as usize & 0xfff) as uacpi_size; 63 | if corrected_len % PAGE_SIZE != 0 { 64 | corrected_len += PAGE_SIZE - (corrected_len % PAGE_SIZE); 65 | } 66 | let mut kas = KERNEL_AS.lock(); 67 | 68 | for va in (corrected_lin_addr..corrected_lin_addr + corrected_len).step_by(PAGE_SIZE) { 69 | kas.unmap_page(va); 70 | } 71 | } 72 | 73 | #[allow(unused)] 74 | #[unsafe(no_mangle)] 75 | pub extern "C" fn uacpi_kernel_log(ll: uacpi_log_level, cstr: *const uacpi_char) { 76 | let string = unsafe { CString::from_raw(cstr as *mut c_char) }; 77 | let rust_str = string.to_str().unwrap_or("Invalid UTF-8 string passed by UACPI."); 78 | let prefix = match ll { 79 | uacpi_log_level_UACPI_LOG_LEVEL_DEBUG => "[DEBUG]", 80 | uacpi_log_level_UACPI_LOG_LEVEL_INFO => "[INFO]", 81 | uacpi_log_level_UACPI_LOG_LEVEL_WARN => "[WARN]", 82 | uacpi_log_level_UACPI_LOG_LEVEL_ERROR => "[ERROR]", 83 | uacpi_log_level_UACPI_LOG_TRACE => "[TRACE]", 84 | _ => "[NONE]", 85 | }; 86 | log!("{} UACPI: {}", prefix, rust_str); 87 | } 88 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/aarch64/memory/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod address; 2 | pub mod paging; 3 | 4 | use core::arch::asm; 5 | 6 | use address::paddr::PAddr; 7 | use address::vaddr::VAddr; 8 | 9 | use crate::cpu::isa::interface::memory::address::{Address, PhysicalAddress, VirtualAddress}; 10 | use crate::cpu::isa::interface::memory::{AddressSpaceInterface, MemoryInterface, MemoryMapping}; 11 | 12 | pub struct MemoryInterfaceImpl; 13 | 14 | impl MemoryInterface for MemoryInterfaceImpl { 15 | type AddressSpace = AddressSpace; 16 | type Error = Error; 17 | type PAddr = address::paddr::PAddr; 18 | type VAddr = address::vaddr::VAddr; 19 | } 20 | 21 | pub enum Error {} 22 | 23 | pub struct AddressSpace { 24 | /// user space translation table base register 25 | ttbr0_el1: u64, 26 | /// kernel space translation table base register 27 | ttbr1_el1: u64, 28 | } 29 | 30 | impl AddressSpaceInterface for AddressSpace { 31 | fn get_current() -> Self { 32 | let ttbr0_el1: u64; 33 | let ttbr1_el1: u64; 34 | unsafe { 35 | asm!("mrs {}, ttbr0_el1", out(reg) ttbr0_el1); 36 | asm!("mrs {}, ttbr1_el1", out(reg) ttbr1_el1); 37 | } 38 | AddressSpace { 39 | ttbr0_el1, 40 | ttbr1_el1, 41 | } 42 | } 43 | 44 | fn load(&self) -> Result<(), ::Error> { 45 | unsafe { 46 | asm!("msr ttbr0_el1, {}", in(reg) self.ttbr0_el1); 47 | asm!("msr ttbr1_el1, {}", in(reg) self.ttbr1_el1); 48 | } 49 | Ok(()) 50 | } 51 | 52 | fn find_free_region( 53 | &mut self, 54 | n_pages: usize, 55 | range: ( 56 | ::VAddr, 57 | ::VAddr, 58 | ), 59 | ) -> Result< 60 | ::VAddr, 61 | ::Error, 62 | > { 63 | // Use n_pages and range to implement the logic 64 | todo!() 65 | } 66 | 67 | fn map_page( 68 | &mut self, 69 | mapping: MemoryMapping, 70 | ) -> Result<(), ::Error> { 71 | todo!() 72 | } 73 | 74 | fn unmap_page( 75 | &mut self, 76 | vaddr: ::VAddr, 77 | ) -> Result::Error> { 78 | todo!() 79 | } 80 | 81 | fn is_mapped( 82 | &mut self, 83 | vaddr: ::VAddr, 84 | ) -> Result::Error> { 85 | todo!() 86 | } 87 | 88 | fn translate_address( 89 | &mut self, 90 | vaddr: VAddr, 91 | ) -> Result::Error> { 92 | let mut par_el1 = (0u64, 0u64); 93 | unsafe { 94 | asm!( 95 | // Address translation stage 1 at EL1 without permission check 96 | "at s1e1a, {}", 97 | "isb", // Weakly ordered ISA is weakly ordered lol 98 | "mrrs x0, x1, par_el1", 99 | vaddr = in(reg) vaddr, 100 | lateout("x0") par_el1.0, 101 | lateout("x1") par_el1.1, 102 | ); 103 | } 104 | if par_el1.0 & 1 == 1 { 105 | // Check F bit 106 | Err(Error {}) 107 | } else { 108 | Ok(PAddr( 109 | if is_d128_set(par_el1) { 110 | par_el1.1 111 | } else { 112 | par_el1.0 113 | } & PAR_EL1_PADDR_MASK, 114 | )) 115 | } 116 | } 117 | } 118 | 119 | const PAR_EL1_PADDR_MASK: u64 = 0x0000fffffffff000; 120 | 121 | fn is_d128_set(par_el1: (u64, u64)) -> bool { 122 | par_el1.1 & 1 == 1 123 | } 124 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/lp/thread_context.rs: -------------------------------------------------------------------------------- 1 | use core::mem::offset_of; 2 | 3 | const INIT_KERNEL_STACK_PAGES: usize = 16; 4 | 5 | use crate::cpu::isa::init::gdt::{ 6 | KERNEL_CODE_SELECTOR, 7 | KERNEL_DATA_SELECTOR, 8 | USER_CODE_SELECTOR, 9 | USER_DATA_SELECTOR, 10 | }; 11 | use crate::cpu::isa::interface::memory::address::VirtualAddress; 12 | use crate::cpu::isa::memory::paging::PAGE_SIZE; 13 | use crate::memory::allocators::stack_allocator::allocate_stack; 14 | use crate::memory::{ADDRESS_SPACE_TABLE, AddressSpaceId, KERNEL_ASID, VAddr}; 15 | 16 | /// # Interrupt stack frame structure for x86_64 architecture 17 | /// Note: must be 16 byte aligned as per `AMD APM 8.9.3` 18 | #[repr(C, packed)] 19 | struct InterruptStackFrame { 20 | gprs: [u64; 15], 21 | rip: u64, 22 | cs: u64, 23 | rflags: u64, 24 | rsp: u64, 25 | ss: u64, 26 | } 27 | 28 | impl InterruptStackFrame { 29 | fn new(is_user: bool, entry_point: VAddr, return_rsp: VAddr, flags: u64) -> Self { 30 | InterruptStackFrame { 31 | gprs: [0u64; 15], 32 | rip: >::into(entry_point), 33 | cs: if is_user { 34 | USER_CODE_SELECTOR 35 | } else { 36 | KERNEL_CODE_SELECTOR 37 | } as u64, 38 | rflags: flags, 39 | rsp: >::into(return_rsp), 40 | ss: if is_user { 41 | USER_DATA_SELECTOR 42 | } else { 43 | KERNEL_DATA_SELECTOR 44 | } as u64, 45 | } 46 | } 47 | 48 | fn push_to_stack(rsp: VAddr, isf: InterruptStackFrame) -> VAddr { 49 | let new_rsp = rsp - core::mem::size_of::(); 50 | unsafe { 51 | let isf_ptr = new_rsp.into_mut::(); 52 | isf_ptr.write(isf); 53 | } 54 | new_rsp 55 | } 56 | } 57 | 58 | #[derive(Debug, Clone, Default)] 59 | pub struct ThreadContext { 60 | rsp_cpl0: u64, 61 | cr3: u64, 62 | kernel_stack_buf: VAddr, 63 | user_stack_buf: Option, 64 | } 65 | #[derive(Debug)] 66 | pub enum Error { 67 | AddressSpaceNotFound, 68 | StackAllocError(crate::memory::allocators::stack_allocator::Error), 69 | } 70 | 71 | impl From for Error { 72 | fn from(err: crate::memory::allocators::stack_allocator::Error) -> Self { 73 | Error::StackAllocError(err) 74 | } 75 | } 76 | 77 | impl ThreadContext { 78 | pub fn new(asid: AddressSpaceId, entry_point: VAddr) -> Result { 79 | let mut tctx = ThreadContext { 80 | rsp_cpl0: 0, 81 | cr3: ADDRESS_SPACE_TABLE 82 | .try_get_element_arc(asid) 83 | .ok_or(Error::AddressSpaceNotFound)? 84 | .read() 85 | .get_cr3(), 86 | kernel_stack_buf: allocate_stack(INIT_KERNEL_STACK_PAGES)?, 87 | user_stack_buf: if asid != KERNEL_ASID { 88 | Some(allocate_stack(INIT_KERNEL_STACK_PAGES)?) 89 | } else { 90 | None 91 | }, 92 | }; 93 | let isf = InterruptStackFrame::new( 94 | asid != KERNEL_ASID, 95 | entry_point, 96 | if asid != KERNEL_ASID { 97 | tctx.user_stack_buf.unwrap() + INIT_KERNEL_STACK_PAGES * PAGE_SIZE 98 | } else { 99 | tctx.kernel_stack_buf + INIT_KERNEL_STACK_PAGES * PAGE_SIZE 100 | }, 101 | 0x202, // IF=1 102 | ); 103 | tctx.rsp_cpl0 = >::into(InterruptStackFrame::push_to_stack( 104 | tctx.kernel_stack_buf + INIT_KERNEL_STACK_PAGES * PAGE_SIZE, 105 | isf, 106 | )); 107 | Ok(tctx) 108 | } 109 | } 110 | 111 | #[unsafe(no_mangle)] 112 | pub static TC_RSP_CPL0_OFFSET: usize = offset_of!(ThreadContext, rsp_cpl0); 113 | #[unsafe(no_mangle)] 114 | pub static TC_CR3_OFFSET: usize = offset_of!(ThreadContext, cr3); 115 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/init/ap.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use alloc::vec::Vec; 3 | 4 | use spin::Mutex; 5 | use spin::lazy::Lazy; 6 | 7 | use super::{INTERRUPT_STACK_SIZE, gdt}; 8 | use crate::cpu::isa::init::gdt::Gdt; 9 | use crate::cpu::isa::lp::ops::get_lp_id; 10 | use crate::cpu::multiprocessor::get_lp_count; 11 | use crate::logln; 12 | 13 | static AP_INTERRUPT_STACKS: Lazy> = Lazy::new(|| { 14 | logln!("LP{}: Computing the number of AP interrupt stacks to allocate.", (get_lp_id!())); 15 | let num_aps = get_lp_count() - 1; // Exclude BSP 16 | logln!("LP{}: Allocating {} AP interrupt stacks.", (get_lp_id!()), num_aps); 17 | let mut ret = Vec::<[u8; INTERRUPT_STACK_SIZE]>::with_capacity(num_aps as usize); 18 | for _ in 0..num_aps { 19 | ret.push(*(Box::new([0u8; INTERRUPT_STACK_SIZE]))); 20 | } 21 | logln!("LP{}: AP interrupt stacks allocated.", (get_lp_id!())); 22 | ret 23 | }); 24 | 25 | static AP_DF_STACKS: Lazy> = Lazy::new(|| { 26 | logln!("LP{}: Computing the number of AP double fault stacks to allocate.", (get_lp_id!())); 27 | let num_aps = get_lp_count() - 1; // Exclude BSP 28 | logln!("LP{}: Allocating {} AP df stacks.", (get_lp_id!()), num_aps); 29 | let mut ret = Vec::<[u8; INTERRUPT_STACK_SIZE]>::with_capacity(num_aps as usize); 30 | for _ in 0..num_aps { 31 | ret.push(*(Box::new([0u8; INTERRUPT_STACK_SIZE]))); 32 | } 33 | logln!("LP{}: AP df stacks allocated.", (get_lp_id!())); 34 | ret 35 | }); 36 | 37 | pub static AP_TSS: Lazy> = Lazy::new(|| { 38 | logln!("LP{}: Creating the TSS vector.", (get_lp_id!())); 39 | let mut tsses = Vec::new(); 40 | logln!("LP{}: Allocating {} TSS entries.", (get_lp_id!()), (get_lp_count() - 1)); 41 | for i in 0..(get_lp_count() - 1) { 42 | tsses.push(super::gdt::Tss::new( 43 | unsafe { (&raw const AP_INTERRUPT_STACKS[i as usize]).byte_add(INTERRUPT_STACK_SIZE) } 44 | as u64, 45 | unsafe { (&raw const AP_DF_STACKS[i as usize]).byte_add(INTERRUPT_STACK_SIZE) } as u64, 46 | )); 47 | } 48 | logln!("LP{}: TSS vector initialized.", (get_lp_id!())); 49 | tsses 50 | }); 51 | 52 | static AP_GDTS: Lazy> = Lazy::new(|| { 53 | logln!("LP{}: Creating the GDT vector.", (get_lp_id!())); 54 | let mut gdts = Vec::new(); 55 | logln!("LP{}: Allocating {} GDT entries.", (get_lp_id!()), (get_lp_count() - 1)); 56 | for tss in AP_TSS.iter() { 57 | logln!("LP{}: Constructing and allocating a GDT", (get_lp_id!())); 58 | gdts.push(super::gdt::Gdt::new(tss)); 59 | } 60 | logln!("LP{}: GDT vector initialized.", (get_lp_id!())); 61 | gdts 62 | }); 63 | 64 | pub static AP_IDTS: Lazy> = Lazy::new(|| { 65 | logln!("LP{}: Creating the IDT vector.", (get_lp_id!())); 66 | let mut idts = Vec::new(); 67 | logln!("LP{}: Allocating {} IDT entries.", (get_lp_id!()), (get_lp_count() - 1)); 68 | for _ in 0..(get_lp_count() - 1) { 69 | logln!("LP{}: Constructing and allocating an IDT", (get_lp_id!())); 70 | let mut idt = crate::cpu::isa::interrupts::idt::Idt::new(); 71 | logln!("LP{}: Registering fixed interrupt gates.", (get_lp_id!())); 72 | crate::cpu::isa::interrupts::register_fixed_isr_gates(&mut idt); 73 | logln!("LP{}: Pushing the initialized IDT to the vector.", (get_lp_id!())); 74 | idts.push(idt); 75 | } 76 | logln!("LP{}: IDT vector initialized.", (get_lp_id!())); 77 | idts 78 | }); 79 | 80 | pub fn init_ap() { 81 | let lp_id = crate::cpu::isa::lp::ops::get_lp_id!(); 82 | logln!("LP{}: Init mutex acquired.", lp_id); 83 | logln!("LP{}: Computing LP index.", lp_id); 84 | let ap_index = (lp_id - 1) as usize; // APs start from LP1 85 | logln!("LP{}: LP index is {}.", lp_id, ap_index); 86 | crate::logln!("LP{}: Initializing TSS, GDT, and IDT", lp_id); 87 | AP_GDTS[ap_index].load(); 88 | unsafe { 89 | gdt::reload_segment_regs(); 90 | } 91 | AP_IDTS[ap_index].load(); 92 | crate::logln!("AP{}: x86-64 logical processor initialization complete", lp_id); 93 | } 94 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/common/memory/address/paddr.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Add, Sub}; 2 | 3 | use crate::cpu::isa::interface::memory::address::{Address, PhysicalAddress, VirtualAddress}; 4 | use crate::cpu::isa::memory::address::PADDR_MASK; 5 | use crate::memory::HHDM_BASE; 6 | 7 | #[derive(Debug, Clone, Copy)] 8 | pub enum PAddrError { 9 | OutOfCpuSupportedRange(usize), 10 | } 11 | 12 | #[repr(transparent)] 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 14 | pub struct PAddr { 15 | raw: usize, 16 | } 17 | 18 | impl Address for PAddr { 19 | const MAX: Self = PAddr { 20 | raw: usize::MAX, 21 | }; 22 | const MIN: Self = PAddr { 23 | raw: 0, 24 | }; 25 | const NULL: Self = PAddr { 26 | raw: 0, 27 | }; 28 | 29 | fn is_aligned_to(&self, alignment: usize) -> bool { 30 | self.raw % alignment == 0 31 | } 32 | 33 | fn is_valid(value: usize) -> bool { 34 | value & *PADDR_MASK == value 35 | } 36 | 37 | fn is_null(&self) -> bool { 38 | self.raw == 0 39 | } 40 | 41 | fn next_aligned_to(&self, alignment: usize) -> Self { 42 | unsafe { PAddr::from_unchecked((self.raw + alignment - 1) & !(alignment - 1)) } 43 | } 44 | 45 | fn prev_aligned_to(&self, alignment: usize) -> Self { 46 | PAddr { 47 | raw: if alignment % 2 == 0 { 48 | self.raw & !(alignment - 1) 49 | } else { 50 | self.raw - (self.raw % alignment) 51 | }, 52 | } 53 | } 54 | 55 | unsafe fn from_unchecked(raw: usize) -> Self { 56 | PAddr { 57 | raw, 58 | } 59 | } 60 | } 61 | 62 | impl PhysicalAddress for PAddr { 63 | unsafe fn into_hhdm_ptr(self) -> *const T { 64 | unsafe { (*HHDM_BASE).into_ptr::().byte_offset(self.raw as isize) } 65 | } 66 | 67 | unsafe fn into_hhdm_mut(self) -> *mut T { 68 | unsafe { (*HHDM_BASE).into_mut::().byte_offset(self.raw as isize) } 69 | } 70 | } 71 | 72 | impl Into<*const T> for PAddr { 73 | fn into(self) -> *const T { 74 | unsafe { (*HHDM_BASE).into_ptr::().byte_offset(self.raw as isize) } 75 | } 76 | } 77 | 78 | impl Into<*mut T> for PAddr { 79 | fn into(self) -> *mut T { 80 | unsafe { (*HHDM_BASE).into_mut::().byte_offset(self.raw as isize) } 81 | } 82 | } 83 | 84 | impl TryFrom for PAddr { 85 | type Error = PAddrError; 86 | 87 | fn try_from(value: usize) -> Result { 88 | if value & !*PADDR_MASK != 0 { 89 | Err(PAddrError::OutOfCpuSupportedRange(value)) 90 | } else { 91 | Ok(PAddr { 92 | raw: value, 93 | }) 94 | } 95 | } 96 | } 97 | 98 | impl Into for PAddr { 99 | fn into(self) -> usize { 100 | self.raw 101 | } 102 | } 103 | 104 | impl From for PAddr { 105 | fn from(value: u64) -> Self { 106 | PAddr { 107 | raw: value as usize & *PADDR_MASK, 108 | } 109 | } 110 | } 111 | 112 | impl Into for PAddr { 113 | fn into(self) -> u64 { 114 | self.raw as u64 115 | } 116 | } 117 | 118 | impl Add for PAddr { 119 | type Output = PAddr; 120 | 121 | fn add(self, rhs: isize) -> Self::Output { 122 | PAddr::try_from(self.raw.wrapping_add(rhs as usize)).unwrap() 123 | } 124 | } 125 | 126 | impl Sub for PAddr { 127 | type Output = PAddr; 128 | 129 | fn sub(self, rhs: isize) -> Self::Output { 130 | PAddr::try_from(self.raw.wrapping_sub(rhs as usize)).unwrap() 131 | } 132 | } 133 | 134 | impl Add for PAddr { 135 | type Output = PAddr; 136 | 137 | fn add(self, rhs: usize) -> Self::Output { 138 | PAddr::try_from(self.raw.wrapping_add(rhs)).unwrap() 139 | } 140 | } 141 | 142 | impl Sub for PAddr { 143 | type Output = PAddr; 144 | 145 | fn sub(self, rhs: usize) -> Self::Output { 146 | PAddr::try_from(self.raw.wrapping_sub(rhs)).unwrap() 147 | } 148 | } 149 | 150 | impl Default for PAddr { 151 | fn default() -> Self { 152 | PAddr { 153 | raw: 0, 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /catten/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(abi_custom)] 4 | #![feature(allocator_api)] 5 | #![feature(atomic_try_update)] 6 | #![feature(exclusive_wrapper)] 7 | #![feature(iter_advance_by)] 8 | #![feature(likely_unlikely)] 9 | #![feature(ptr_as_ref_unchecked)] 10 | #![feature(slice_ptr_get)] 11 | #![feature(step_trait)] 12 | #![allow(static_mut_refs)] 13 | #![allow(named_asm_labels)] 14 | 15 | //! # Catten 16 | //! 17 | //! Catten is an operating system kernel developed as a component of CharlotteOS, an 18 | //! experimental modern operating system.This kernel is responsible for initializing the hardware, 19 | //! providing commonizing abstractions for all hardware resources, and managing the execution of 20 | //! user-space applications and the environment in which they run. It is a crucial part of the 21 | //! operating system, as it provides the foundation on which the rest of the system is built and it 22 | //! touches every hardware and software component of the system on which it is used. While it is 23 | //! developed as a component of CharlotteOS, it is designed to be modular and flexible, and thus 24 | //! useful in other operating systems, embedded firmware, and other types of software distributions 25 | //! as well. 26 | 27 | extern crate alloc; 28 | 29 | pub mod common; 30 | pub mod cpu; 31 | pub mod drivers; 32 | pub mod environment; 33 | pub mod event; 34 | pub mod framebuffer; 35 | pub mod init; 36 | pub mod log; 37 | pub mod memory; 38 | pub mod panic; 39 | pub mod self_test; 40 | 41 | use cpu::isa::interface::system_info::CpuInfoIfce; 42 | use cpu::isa::system_info::CpuInfo; 43 | use cpu::multiprocessor; 44 | use limine::mp::Cpu; 45 | use spin::{Barrier, Lazy}; 46 | 47 | use crate::cpu::isa::timers::tsc::{IS_TSC_INVARIANT, TSC_FREQUENCY_HZ}; 48 | use crate::cpu::multiprocessor::get_lp_count; 49 | 50 | const KERNEL_VERSION: (u64, u64, u64) = (0, 3, 0); 51 | static INIT_BARRIER: Lazy = Lazy::new(|| Barrier::new(get_lp_count() as usize)); 52 | /// This is the bootstrap processor's entry point into the kernel. The `bsp_main` function is 53 | /// called by the bootloader after setting up the environment. It is made C ABI compatible so 54 | /// that it can be called by Limine or any other Limine Boot Protocol compliant bootloader. 55 | #[unsafe(no_mangle)] 56 | pub extern "C" fn bsp_main() -> ! { 57 | logln!( 58 | "Catten Kernel Version {}.{}.{}", 59 | (KERNEL_VERSION.0), 60 | (KERNEL_VERSION.1), 61 | (KERNEL_VERSION.2) 62 | ); 63 | logln!("========================================================================"); 64 | logln!("Initializing the system using the bootstrap processor..."); 65 | unsafe { 66 | multiprocessor::assign_id(); 67 | } 68 | init::bsp_init(); 69 | logln!("System initialized."); 70 | logln!("Starting secondary LPs..."); 71 | multiprocessor::start_secondary_lps().expect("Failed to start secondary LPs"); 72 | INIT_BARRIER.wait(); 73 | self_test::run_self_tests(); 74 | if cfg!(target_arch = "x86_64") { 75 | if *IS_TSC_INVARIANT { 76 | logln!("The x86-64 Timestamp Counter IS invariant."); 77 | } else { 78 | logln!("The x86-64 Timestamp Counter is NOT invariant."); 79 | } 80 | logln!( 81 | "The x86-64 Timestamp Counter frequency is {} MHz.", 82 | (*TSC_FREQUENCY_HZ / 1_000_000) 83 | ); 84 | } 85 | logln!("System Information:"); 86 | logln!("CPU Vendor: {}", (CpuInfo::get_vendor())); 87 | logln!("CPU Model: {}", (CpuInfo::get_model())); 88 | logln!("Physical Address bits implemented: {}", (CpuInfo::get_paddr_sig_bits())); 89 | logln!("Virtual Address bits implemented: {}", (CpuInfo::get_vaddr_sig_bits())); 90 | logln!("Nothing left to do. Waiting for interrupts..."); 91 | halt!() 92 | } 93 | /// This is the application processor's entry point into the kernel. The `ap_main` function is 94 | /// called by each application processor upon entering the kernel. It initializes the processor and 95 | /// then hands it off to the scheduler. It is made C ABI compatible so that it can work with the 96 | /// Limine Boot Protocol MP feature. Other boot protocols may require alternate implementations of 97 | /// `ap_main`. 98 | #[unsafe(no_mangle)] 99 | pub unsafe extern "C" fn ap_main(_cpuinfo: &Cpu) -> ! { 100 | unsafe { 101 | multiprocessor::assign_id(); 102 | } 103 | init::ap_init(); 104 | INIT_BARRIER.wait(); 105 | logln!("LP{}: Nothing left to do. Waiting for interrupts...", (get_lp_id!())); 106 | halt!() 107 | } 108 | -------------------------------------------------------------------------------- /tools/setup_dev_env.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script sets up packages for each distro. 4 | 5 | # What we need to install is: 6 | # curl, 7 | # edk2-ovmf, edk2-aarch64, edk2-riscv64 8 | # git, 9 | # llvm, 10 | # clang, 11 | # lld, 12 | # lldb, 13 | # make, 14 | # xorriso, 15 | # qemu, 16 | # rust nightly. 17 | 18 | # To add a new distro: 19 | # Add a new entry in pkgm. 20 | # Make a new distro_name_setup function. 21 | # Call it in the case statement. 22 | 23 | # We want to check if the file even exists. 24 | if [ ! -e /etc/os-release ]; then 25 | echo "The file /etc/os-release was not found. This bash script is linux-only." 26 | exit 1 27 | fi 28 | 29 | # os-release defines distro ID as ID="distro". 30 | DISTRO=$(grep '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"') 31 | 32 | # Echo the distro that we found. 33 | echo "Distro found: $DISTRO" 34 | 35 | # Each distro has their package manager. 36 | declare -A pkgm=( 37 | ["arch"]="pacman" 38 | ) 39 | 40 | # Check if the package manager is supported. 41 | if [[ ! -v pkgm[$DISTRO] ]]; then 42 | echo "The package manager for your distro is not yet supported." 43 | exit 1 44 | fi 45 | 46 | PKGMAN=${pkgm[$DISTRO]} 47 | 48 | # Echo the package manager. 49 | echo "Package manager: $PKGMAN" 50 | 51 | # Does the user want to proceed? 52 | read -p "Do you want to continue? [Y/n] " answer 53 | 54 | # User's answer. 55 | answer=${answer:-Y} 56 | 57 | # Check if the user declined. 58 | if [[ ! "$answer" =~ ^[Yy]$ ]]; then 59 | echo "Aborting..." 60 | exit 1 61 | fi 62 | 63 | # Continue... 64 | 65 | ROOT_PERM=sudo 66 | 67 | # Set up for arch linux 68 | arch_setup(){ 69 | echo "Setting up for $DISTRO using $PKGMAN..." 70 | # Check for packages first! 71 | echo "Checking packages..." 72 | 73 | # Packages, rust last! 74 | local -a arch_pkgs=("curl" "git" "llvm" "clang" "lld" "lldb" "make" "xorriso" "qemu") 75 | # Missing packages list. 76 | local -a missing_pkgs=() 77 | 78 | # Check each package 79 | for pkg in "${arch_pkgs[@]}"; do 80 | if ! $PKGMAN -Q "$pkg" &>/dev/null; then 81 | missing_pkgs+=("$pkg") 82 | fi 83 | done 84 | 85 | # report missing packages 86 | if (( ${#missing_pkgs[@]} )); then 87 | echo "These packages are missing:" 88 | for mpkg in "${missing_pkgs[@]}"; do 89 | echo " - $mpkg" 90 | done 91 | 92 | read -p "Do you want to install them? [Y/n] " answer 93 | answer=${answer:-Y} 94 | 95 | if [[ "$answer" =~ ^[Yy]$ ]]; then 96 | echo "Installing missing packages..." 97 | if ! command -v $ROOT_PERM >/dev/null; then 98 | echo "Sudo not found..." 99 | echo "Checking doas..." 100 | fi 101 | if command -v doas >/dev/null; then 102 | ROOT_PERM=doas 103 | else 104 | echo "Sudo and doas not found. aborting..." 105 | exit 1 106 | fi 107 | $ROOT_PERM $PKGMAN -S --noconfirm "${missing_pkgs[@]}" 108 | echo "Installation complete." 109 | else 110 | echo "Skipping installation of missing packages." 111 | fi 112 | else 113 | # all packages are present 114 | echo "All base packages installed!" 115 | fi 116 | 117 | # Check for rust separately 118 | # On arch pacman rust and rustup are not recommended together. 119 | echo "Checking for rust..." 120 | if $PKGMAN -Q rust &>/dev/null; then 121 | echo "Please uninstall pacman rust first." 122 | exit 1 123 | else 124 | echo "No pacman rust found. Proceeding to rustup..." 125 | fi 126 | 127 | if command -v rustup >/dev/null 2>&1; then 128 | echo "Rustup already exists! Make sure nightly is present." 129 | else 130 | echo "Installing rustup..." 131 | if ! command -v curl >/dev/null 2>&1; then 132 | echo "curl isn't present, cancelling install..." 133 | exit 1 134 | fi 135 | 136 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly 137 | fi 138 | } 139 | 140 | # This is a case statement for different distros, add as you will. 141 | case "$DISTRO" in 142 | arch) arch_setup ;; 143 | # To add a distro, you'd define a package manager first, then a function for it. 144 | # Then you would add a new case like 145 | # distro_name) distro_name_setup ;; 146 | *) 147 | echo "Unsupported distro: $DISTRO" 148 | exit 1 149 | ;; 150 | esac -------------------------------------------------------------------------------- /catten/src/cpu/isa/aarch64/system_info/mod.rs: -------------------------------------------------------------------------------- 1 | mod feature_check; 2 | 3 | use feature_check::*; 4 | 5 | use crate::cpu::isa::interface::system_info::CpuInfoIfce; 6 | 7 | #[derive(Debug)] 8 | pub enum Vendor { 9 | SwReserved = 0x00, 10 | Arm = 0x41, 11 | Broadcom = 0x42, 12 | Cavium = 0x43, 13 | Dec = 0x44, 14 | Fujitsu = 0x46, 15 | Infineon = 0x49, 16 | MotorolaFreescale = 0x4d, 17 | Nvidia = 0x4e, 18 | Amcc = 0x50, 19 | Qualcomm = 0x51, 20 | Marvell = 0x56, 21 | Intel = 0x69, 22 | Ampere = 0xc0, 23 | Unrecognized, 24 | } 25 | 26 | impl From for Vendor { 27 | fn from(vendor_id: u8) -> Self { 28 | match vendor_id { 29 | 0x00 => Vendor::SwReserved, 30 | 0x41 => Vendor::Arm, 31 | 0x42 => Vendor::Broadcom, 32 | 0x43 => Vendor::Cavium, 33 | 0x44 => Vendor::Dec, 34 | 0x46 => Vendor::Fujitsu, 35 | 0x49 => Vendor::Infineon, 36 | 0x4d => Vendor::MotorolaFreescale, 37 | 0x4e => Vendor::Nvidia, 38 | 0x50 => Vendor::Amcc, 39 | 0x51 => Vendor::Qualcomm, 40 | 0x56 => Vendor::Marvell, 41 | 0x69 => Vendor::Intel, 42 | 0xc0 => Vendor::Ampere, 43 | _ => Vendor::Unrecognized, 44 | } 45 | } 46 | } 47 | 48 | #[derive(Debug)] 49 | pub struct Model { 50 | part_num: u16, 51 | revision: u8, 52 | variant: u8, 53 | architecture: u8, 54 | } 55 | 56 | #[derive(Debug)] 57 | pub struct CpuInfo; 58 | 59 | impl CpuInfoIfce for CpuInfo { 60 | type IsaExtension = IsaExtension; 61 | type Model = Model; 62 | type Vendor = Vendor; 63 | 64 | fn get_vendor() -> Self::Vendor { 65 | let mut midr: u64; 66 | unsafe { 67 | core::arch::asm!("mrs {}, midr_el1", out(reg) midr); 68 | } 69 | let vendor_id = (midr >> 24) as u8; 70 | 71 | vendor_id.into() 72 | } 73 | 74 | fn get_model() -> Self::Model { 75 | let mut midr: u64; 76 | unsafe { 77 | core::arch::asm!("mrs {}, midr_el1", out(reg) midr); 78 | } 79 | let part_num = (midr & 0xfff) as u16; 80 | let revision = ((midr >> 16) & 0xf) as u8; 81 | let variant = ((midr >> 20) & 0xf) as u8; 82 | let architecture = ((midr >> 4) & 0xf) as u8; 83 | 84 | Model { 85 | part_num, 86 | revision, 87 | variant, 88 | architecture, 89 | } 90 | } 91 | 92 | fn get_vaddr_sig_bits() -> u8 { 93 | let mut id_aa64mmfr2_el1: u64; 94 | unsafe { 95 | core::arch::asm!("mrs {}, id_aa64mmfr2_el1", out(reg) id_aa64mmfr2_el1); 96 | } 97 | let vaddr_range = ((id_aa64mmfr2_el1 >> 16) & 0xf) as u8; 98 | 99 | match vaddr_range { 100 | // 48-bit VA; always implemented on AArch64 101 | 0b0000 => 48, 102 | // FEAT_LVA: 52-bit VA when using the 64KB granule 103 | 0b0001 => 52, 104 | // FEAT_LVA3: 56-bit VA available and FEAT_D128 is guaranteed to be implemented 105 | 0b0010 => 56, 106 | _ => panic!("aarch64 systeminfo: Unrecognized virtual address range value!"), 107 | } 108 | } 109 | 110 | fn get_paddr_sig_bits() -> u8 { 111 | let mut id_aa64mmfr0_el1: u64; 112 | unsafe { 113 | core::arch::asm!("mrs {}, id_aa64mmfr0_el1", out(reg) id_aa64mmfr0_el1); 114 | } 115 | let paddr_range = (id_aa64mmfr0_el1 & 0xf) as u8; 116 | 117 | match paddr_range { 118 | // 32-bit PA, 4GiB 119 | 0b0000 => 32, 120 | // 36-bit PA, 64GiB 121 | 0b0001 => 36, 122 | // 40-bit PA, 1TiB 123 | 0b0010 => 40, 124 | // 42-bit PA, 4TiB 125 | 0b0011 => 42, 126 | // 44-bit PA, 16TiB 127 | 0b0100 => 44, 128 | // 48-bit PA, 256TiB 129 | 0b0101 => 48, 130 | /* The following numbers of significant bits require FEAT_LPA in order to be used 131 | with the 64 KiB granule and additionally FEAT_LPA2 in order to be used 132 | with the 4 KiB and 16 KiB granules */ 133 | // 52-bit PA, 4PiB 134 | 0b0110 => 52, 135 | /* 52-bit PA, 64PiB when FEAT_D128 is implemented and using the 64KB granule */ 136 | 0b0111 => 56, 137 | _ => panic!("aarch64 systeminfo: Unrecognized physical address range value!"), 138 | } 139 | } 140 | 141 | fn is_extension_supported(extension: Self::IsaExtension) -> bool { 142 | match extension { 143 | IsaExtension::FeatD128 => check_feat::d128(), 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Documents/TARGET_POLICY.md: -------------------------------------------------------------------------------- 1 | # CharlotteOS Target Platform Policy 2 | 3 | This document defines the official platform support policy for CharlotteOS. It provides a clear, concise overview of which platforms the kernel supports, why they are supported, and what standards or documentation are required for any architecture or board to qualify. 4 | 5 | CharlotteOS ships **one unified kernel**, fully maintained in-tree. Platforms that do not meet these requirements may still be used via **external static libraries**, but cannot be merged into mainline. 6 | 7 | --- 8 | 9 | ## 1. Core Principles 10 | 11 | ### **Open Documentation** 12 | CharlotteOS only supports platforms with complete, public hardware documentation. Vendor documentation is preferred, but **reverse‑engineered documentation is fully acceptable** if it is public, complete, and stable. 13 | 14 | ### **Standards Compliance** 15 | Supported platforms must use: 16 | 17 | - **UEFI** for boot 18 | - **ACPI** *or* a **DTSpec‑conforming Devicetree** 19 | - **SBI** on RISC‑V 20 | - Fully documented devices and initialization sequences 21 | 22 | The discoverability format (ACPI vs DT) does not matter; correctness and documentation do. 23 | 24 | ### **Maintainability** 25 | All supported platforms must be maintainable by the core team without per‑board quirks or vendor‑specific workarounds. 26 | 27 | --- 28 | 29 | ## 2. Tier 1 — Officially Supported Platforms 30 | 31 | Tier 1 platforms receive full mainline support and represent the intended environment for CharlotteOS. 32 | 33 | ### **2.1 x86‑64 (Primary ISA)** 34 | 35 | Requirements: 36 | - UEFI boot 37 | - ACPI 6.x static tables 38 | - Invariant TSC 39 | - x2APIC 40 | - Fully documented hardware 41 | 42 | x86‑64 is the primary launch platform for CharlotteOS. 43 | 44 | --- 45 | 46 | ### **2.2 RISC‑V (Interoperable Platforms — BRS‑I, RVA22+)** 47 | 48 | Tier 1 RISC‑V must meet full interoperable‑class standards. 49 | 50 | Requirements: 51 | - **RVA22 or later** 52 | - UEFI boot 53 | - ACPI 6.6+ *or* DTSpec‑conforming FDT 54 | - SBI-compliant firmware 55 | - All peripherals documented (vendor documentation or fully reverse‑engineered) 56 | - PCIe, interrupts, and memory behavior following published specifications 57 | - No undocumented initialization sequences 58 | 59 | **Initial Tier 1 target:** Milk‑V Titan featuring the **UltraRISC UR‑CP100** CPU (pending evaluation). 60 | 61 | --- 62 | 63 | ### **2.3 Virtualized Environments** 64 | 65 | Virtualization is essential for data center use and fully supported. 66 | 67 | Supported hypervisors: 68 | - QEMU 69 | - KVM 70 | - Xen (HVM/PVH) 71 | - Hyper‑V 72 | - VMware ESXi 73 | - Any VMM providing UEFI, ACPI, and virtio‑class documented virtual devices 74 | 75 | Virtual environments appear last in ordering but have full Tier 1 status. 76 | 77 | --- 78 | 79 | ## 3. Tier 2 — Embedded RISC‑V Platforms (RVB + BRS‑B) 80 | 81 | Tier 2 includes embedded RISC‑V boards that meet the following: 82 | - **RVB** hardware profile 83 | - **BRS‑B** firmware profile 84 | - DTSpec‑compliant FDT 85 | - All hardware blocks fully documented (vendor or reverse‑engineered) 86 | - No vendor‑specific Linux assumptions 87 | - No undocumented boot or power‑up sequences 88 | 89 | Tier 2 platforms must be maintainable without per‑board quirks. 90 | 91 | --- 92 | 93 | ## 4. Unsupported Hardware 94 | 95 | A platform is unsupported if it: 96 | - Lacks documentation for **core system components** required for OS bring-up (CPU, interrupt controller, timers, memory controllers, PCIe root complex, USB/XHCI, UARTs, etc.) 97 | - Uses incomplete or Linux-tailored ACPI/DT for **critical subsystems** 98 | - Requires undocumented vendor-specific initialization sequences for essential hardware 99 | - Prevents running modified software without vendor keys 100 | 101 | **Important:** A platform is *not* rejected simply because it includes some undocumented peripherals (e.g., GPUs, Wi-Fi NICs, accelerators). These devices will simply remain unsupported by CharlotteOS until proper documentation exists. The presence of such devices does *not* disqualify the rest of the system. 102 | 103 | Unsupported platforms may still be used via out-of-tree static libraries, but cannot be merged into mainline. 104 | 105 | This preserves a single, unified kernel while avoiding unnecessary platform exclusion. 106 | 107 | --- 108 | 109 | ## 5. RISC‑V Support Roadmap 110 | 111 | ### **Phase 1 — QEMU Bring‑Up** 112 | - Validate paging, traps, interrupts 113 | - Validate UEFI boot 114 | - Validate ACPI/FDT parsing 115 | - Ensure architecture abstraction layer stays clean 116 | 117 | ### **Phase 2 — Hardware Validation (UR‑CP100 / Titan)** 118 | Criteria: 119 | - Fully functional UEFI 120 | - ACPI or compliant FDT 121 | - Documented device controllers 122 | - Standard PCIe behavior 123 | - No opaque or proprietary firmware 124 | 125 | ### **Phase 3 — Tier 1 Promotion** 126 | Upon meeting all criteria, the RISC‑V platform becomes Tier 1. 127 | 128 | ### **Phase 4 — Expand Tier 2** 129 | Additional RVB + BRS‑B platforms may be added if fully documented and predictable. 130 | 131 | --- 132 | 133 | ## 6. Summary 134 | 135 | **Tier 1** 136 | - x86‑64 137 | - RISC‑V (RVA22+, BRS‑I, documented devices such as UR‑CP100/Titan) 138 | - Virtualized environments (QEMU, KVM, Xen, Hyper‑V, VMware) 139 | 140 | **Tier 2** 141 | - Embedded RISC‑V following RVB + BRS‑B with full public documentation 142 | 143 | **Unsupported** 144 | - Hardware lacking documentation 145 | - Platforms requiring vendor‑specific hacks 146 | - Systems without UEFI/ACPI/DTSpec 147 | - May still be targeted via out‑of‑tree static libraries 148 | 149 | CharlotteOS supports platforms that uphold open standards, predictable behavior, and long‑term sustainability for a unified kernel. 150 | 151 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/memory/paging/pte.rs: -------------------------------------------------------------------------------- 1 | //! # Page Table Entry 2 | 3 | use spin::Lazy; 4 | 5 | use crate::cpu::isa::x86_64::memory::address::paddr::PAddr; 6 | 7 | /// PTE component indexes and masks 8 | const PRESENT_BIT_INDEX: u64 = 0; 9 | const WRITABLE_BIT_INDEX: u64 = 1; 10 | const USER_ACCESSIBLE_BIT_INDEX: u64 = 2; 11 | const PAT_INDEX_0: u64 = 3; 12 | const PAT_INDEX_1: u64 = 4; 13 | const PAT_INDEX_2_STANDARD: u64 = 7; // only for PTEs pointing to a 4 KiB page 14 | //const PAT_INDEX_2_LARGE_HUGE: u64 = 12; // only for PTEs pointing to a 2 MiB or 1 GiB page 15 | const ACCESSED_BIT_INDEX: u64 = 5; 16 | const DIRTY_BIT_INDEX: u64 = 6; 17 | const PAGE_SIZE_BIT_INDEX: u64 = 7; // only for PTEs pointing to a 2 MiB or 1 GiB page 18 | const GLOBAL_BIT_INDEX: u64 = 8; 19 | 20 | static FRAME_ADDR_MASK: Lazy = 21 | Lazy::new(|| 0xfffffffffffff000 & *super::super::address::PADDR_MASK as u64); 22 | const EXECUTE_DISABLE_BIT_INDEX: u64 = 63; 23 | 24 | /// The page table entry structure 25 | #[repr(transparent)] 26 | pub struct PageTableEntry(u64); 27 | 28 | impl PageTableEntry { 29 | pub fn new( 30 | present: bool, 31 | writable: bool, 32 | user_accessible: bool, 33 | pat_index: u8, 34 | global: bool, 35 | frame_addr: PAddr, 36 | ) -> Self { 37 | let mut pte = Self(0); 38 | pte.set_present(present) 39 | .set_writable(writable) 40 | .set_user_accessible(user_accessible) 41 | .set_pat_index_bits(pat_index) 42 | .set_global(global) 43 | .set_frame(frame_addr); 44 | pte 45 | } 46 | 47 | pub fn is_present(&self) -> bool { 48 | self.0 & (1 << PRESENT_BIT_INDEX) != 0 49 | } 50 | 51 | pub fn set_present(&mut self, present: bool) -> &mut Self { 52 | if present { 53 | self.0 |= 1 << PRESENT_BIT_INDEX; 54 | } else { 55 | self.0 &= !(1 << PRESENT_BIT_INDEX); 56 | } 57 | self 58 | } 59 | 60 | pub fn is_writable(&self) -> bool { 61 | self.0 & (1 << WRITABLE_BIT_INDEX) != 0 62 | } 63 | 64 | pub fn set_writable(&mut self, writable: bool) -> &mut Self { 65 | if writable { 66 | self.0 |= 1 << WRITABLE_BIT_INDEX; 67 | } else { 68 | self.0 &= !(1 << WRITABLE_BIT_INDEX); 69 | } 70 | self 71 | } 72 | 73 | pub fn is_user_accessible(&self) -> bool { 74 | self.0 & (1 << USER_ACCESSIBLE_BIT_INDEX) != 0 75 | } 76 | 77 | pub fn set_user_accessible(&mut self, user_accessible: bool) -> &mut Self { 78 | if user_accessible { 79 | self.0 |= 1 << USER_ACCESSIBLE_BIT_INDEX; 80 | } else { 81 | self.0 &= !(1 << USER_ACCESSIBLE_BIT_INDEX); 82 | } 83 | self 84 | } 85 | 86 | pub fn get_pat_index(&self) -> u8 { 87 | let mut pat_index = 0u8; 88 | pat_index |= ((self.0 & (1 << PAT_INDEX_0)) >> PAT_INDEX_0) as u8; 89 | pat_index |= ((self.0 & (1 << PAT_INDEX_1)) >> PAT_INDEX_1 - 1) as u8; 90 | pat_index |= ((self.0 & (1 << PAT_INDEX_2_STANDARD)) >> PAT_INDEX_2_STANDARD - 2) as u8; 91 | pat_index 92 | } 93 | 94 | pub fn set_pat_index_bits(&mut self, pat_index: u8) -> &mut Self { 95 | self.0 |= ((pat_index & 1) << PAT_INDEX_0) as u64; 96 | self.0 |= ((pat_index & 1 << 1) << PAT_INDEX_1 - 1) as u64; 97 | self.0 |= ((pat_index & 1 << 2) << PAT_INDEX_2_STANDARD - 2) as u64; 98 | self 99 | } 100 | 101 | pub fn is_accessed(&self) -> bool { 102 | self.0 & (1 << ACCESSED_BIT_INDEX) != 0 103 | } 104 | 105 | pub fn set_accessed(&mut self, accessed: bool) -> &mut Self { 106 | if accessed { 107 | self.0 |= 1 << ACCESSED_BIT_INDEX; 108 | } else { 109 | self.0 &= !(1 << ACCESSED_BIT_INDEX); 110 | } 111 | self 112 | } 113 | 114 | pub fn is_dirty(&self) -> bool { 115 | self.0 & (1 << DIRTY_BIT_INDEX) != 0 116 | } 117 | 118 | pub fn set_dirty(&mut self, dirty: bool) -> &mut Self { 119 | if dirty { 120 | self.0 |= 1 << DIRTY_BIT_INDEX; 121 | } else { 122 | self.0 &= !(1 << DIRTY_BIT_INDEX); 123 | } 124 | self 125 | } 126 | 127 | pub fn get_page_size(&self) -> bool { 128 | self.0 & (1 << PAGE_SIZE_BIT_INDEX) != 0 129 | } 130 | 131 | pub fn set_page_size(&mut self, page_size: bool) -> &mut Self { 132 | if page_size { 133 | self.0 |= 1 << PAGE_SIZE_BIT_INDEX; 134 | } else { 135 | self.0 &= !(1 << PAGE_SIZE_BIT_INDEX); 136 | } 137 | self 138 | } 139 | 140 | pub fn is_global(&self) -> bool { 141 | self.0 & (1 << GLOBAL_BIT_INDEX) != 0 142 | } 143 | 144 | pub fn set_global(&mut self, global: bool) -> &mut Self { 145 | if global { 146 | self.0 |= 1 << GLOBAL_BIT_INDEX; 147 | } else { 148 | self.0 &= !(1 << GLOBAL_BIT_INDEX); 149 | } 150 | self 151 | } 152 | 153 | pub fn try_get_frame(&self) -> Result { 154 | Ok(PAddr::try_from((self.0 & *FRAME_ADDR_MASK) as usize)?) 155 | } 156 | 157 | pub fn set_frame(&mut self, frame: PAddr) -> &mut Self { 158 | self.0 = 159 | (self.0 & !*FRAME_ADDR_MASK) | ((>::into(frame)) & *FRAME_ADDR_MASK); 160 | self 161 | } 162 | 163 | pub fn is_execute_disabled(&self) -> bool { 164 | self.0 & (1 << EXECUTE_DISABLE_BIT_INDEX) != 0 165 | } 166 | 167 | pub fn set_execute_disabled(&mut self, execute_disabled: bool) -> &mut Self { 168 | if execute_disabled { 169 | self.0 |= 1 << EXECUTE_DISABLE_BIT_INDEX; 170 | } else { 171 | self.0 &= !(1 << EXECUTE_DISABLE_BIT_INDEX); 172 | } 173 | self 174 | } 175 | 176 | pub fn is_uncached(&self) -> bool { 177 | self.0 & (0b11 << PAT_INDEX_0) == 0 178 | } 179 | 180 | pub fn is_write_combining(&self) -> bool { 181 | (self.0 & (0b11 << PAT_INDEX_0) >> PAT_INDEX_0) == 0b01 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/memory/paging/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod pte; 2 | pub mod pth_walker; 3 | 4 | use alloc::collections::btree_map::BTreeMap; 5 | use core::arch::asm; 6 | use core::iter::Iterator; 7 | use core::ptr::NonNull; 8 | 9 | use spin::Mutex; 10 | 11 | use super::MemoryInterfaceImpl; 12 | use super::address::vaddr::VAddr; 13 | use crate::cpu::isa::interface::memory::{AddressSpaceInterface, MemoryInterface, MemoryMapping}; 14 | use crate::logln; 15 | use crate::memory::{AddressSpaceId, PAddr}; 16 | 17 | #[repr(transparent)] 18 | pub struct HwAsid(u16); 19 | 20 | impl HwAsid { 21 | pub unsafe fn get_inner_unchecked(&self) -> u16 { 22 | self.0 23 | } 24 | 25 | pub extern "C" fn get_inner(&self) -> u16 { 26 | self.0 & 0xfff 27 | } 28 | } 29 | 30 | impl TryFrom for HwAsid { 31 | type Error = (); 32 | 33 | fn try_from(value: u16) -> Result { 34 | if value & !0xfff == 0 { 35 | Ok(HwAsid(value)) 36 | } else { 37 | Err(()) 38 | } 39 | } 40 | } 41 | 42 | pub const PAGE_SIZE: usize = 4096; 43 | pub const N_PAGE_TABLE_ENTRIES: usize = 512; 44 | pub type PageTable = [pte::PageTableEntry; N_PAGE_TABLE_ENTRIES]; 45 | 46 | pub fn is_pagetable_unused(table_ptr: NonNull) -> bool { 47 | unsafe { 48 | for i in 0..N_PAGE_TABLE_ENTRIES { 49 | if (table_ptr.as_ref())[i].is_present() { 50 | return false; 51 | } 52 | } 53 | } 54 | true 55 | } 56 | 57 | #[repr(transparent)] 58 | pub struct AddressSpace { 59 | // control register 3 i.e. top level page table base register 60 | cr3: u64, 61 | } 62 | 63 | impl AddressSpace { 64 | pub fn get_cr3(&self) -> u64 { 65 | self.cr3 66 | } 67 | } 68 | 69 | impl AddressSpaceInterface for AddressSpace { 70 | fn get_current() -> Self { 71 | let cr3: u64; 72 | unsafe { 73 | asm!("mov {}, cr3", out(reg) cr3); 74 | } 75 | AddressSpace { 76 | cr3: cr3, 77 | } 78 | } 79 | 80 | fn load(&self) -> Result<(), ::Error> { 81 | unsafe { 82 | // Set the top level page table base register 83 | asm!("mov cr3, {}", in(reg) self.cr3); 84 | } 85 | Ok(()) 86 | } 87 | 88 | fn find_free_region( 89 | &mut self, 90 | n_pages: usize, 91 | range: (VAddr, VAddr), 92 | ) -> Result< 93 | ::VAddr, 94 | ::Error, 95 | > { 96 | logln!("Finding free region of {} pages in range {:?}...", n_pages, range); 97 | let mut page_iter = (range.0..range.1).step_by(PAGE_SIZE); 98 | while let Some(base) = page_iter.next() { 99 | logln!("Checking base address: {:?}", base); 100 | for nth_page in 0..n_pages { 101 | let curr_page = base + ((nth_page * PAGE_SIZE) as isize); 102 | //logln!("Checking page: {:?}", curr_page); 103 | if range.1 - curr_page < (n_pages * PAGE_SIZE) as isize { 104 | return Err(::Error::NoRequestedVAddrRegionAvailable); 105 | } 106 | if self.is_mapped(curr_page)? { 107 | match page_iter.advance_by(nth_page) { 108 | Ok(_) => { 109 | logln!("Page {:?} is already mapped, skipping to next base address.", curr_page); 110 | break; 111 | } 112 | Err(_) => return Err(::Error::NoRequestedVAddrRegionAvailable), 113 | } 114 | } 115 | if nth_page == n_pages - 1 { 116 | logln!("Found free region starting at: {:?}", base); 117 | return Ok(base); 118 | } 119 | } 120 | } 121 | Err(::Error::NoRequestedVAddrRegionAvailable) 122 | } 123 | 124 | fn map_page( 125 | &mut self, 126 | mapping: MemoryMapping, 127 | ) -> Result<(), ::Error> { 128 | let mut walker = pth_walker::PthWalker::new(self, mapping.vaddr); 129 | walker.map_page( 130 | mapping.paddr, 131 | mapping.page_type.is_writable(), 132 | mapping.page_type.is_user_accessible(), 133 | mapping.page_type.is_no_execute(), 134 | )?; 135 | Ok(()) 136 | } 137 | 138 | fn unmap_page( 139 | &mut self, 140 | vaddr: ::VAddr, 141 | ) -> Result::Error> { 142 | if >::into(vaddr) == 0 { 143 | return Err(::Error::NullVAddrNotAllowed); 144 | } 145 | if vaddr.page_offset() != 0 { 146 | return Err(::Error::VAddrNotPageAligned); 147 | } 148 | let mut walker = pth_walker::PthWalker::new(self, vaddr); 149 | walker.unmap_page() 150 | } 151 | 152 | fn is_mapped( 153 | &mut self, 154 | vaddr: ::VAddr, 155 | ) -> Result::Error> { 156 | let mut walker = pth_walker::PthWalker::new(self, vaddr); 157 | match walker.walk() { 158 | Ok(_) => Ok(true), 159 | Err(::Error::Unmapped) => Ok(false), 160 | Err(e) => Err(e), 161 | } 162 | } 163 | 164 | fn translate_address( 165 | &mut self, 166 | vaddr: super::address::vaddr::VAddr, 167 | ) -> Result::Error> { 168 | let mut walker = pth_walker::PthWalker::new(self, vaddr); 169 | walker.walk()?; 170 | let paddr = unsafe { (*(walker.pt_ptr))[vaddr.pt_index()].try_get_frame()?.into() }; 171 | Ok(paddr) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # Copilot / AI Agent Project Instructions 2 | 3 | Concise, action-focused context to make high-quality changes rapidly. Keep responses specific to this repository; avoid generic OS dev boilerplate unless directly relevant. 4 | 5 | ## 1. Project Snapshot 6 | - Repository: `Catten` – a monolithic, modular Rust kernel (early stage) for multiple ISAs (currently x86_64 primary; aarch64 & riscv64 scaffolding present). 7 | - Boot flow: Limine Boot Protocol → `bsp_main` (`src/main.rs`) → `init::bsp_init()` → start secondary logical processors (`multiprocessing::start_secondary_lps`) → optional self tests → idle (HLT). 8 | - Secondary CPUs enter at `ap_main` (currently minimal) and halt. 9 | - Kernel is `#![no_std]`, uses `alloc` + custom global allocator built atop physical + virtual memory layers. 10 | 11 | ## 2. Key Subsystem Layout 12 | - `isa/` – Architectural abstraction. `isa::interface/*` defines traits (memory, lp_control, system_info, io, interrupts). Concrete per-arch modules live under `isa/x86_64`, `isa/aarch64`, etc. Use `isa::target` for arch-selected reexports. 13 | - `memory/` – Layered memory mgmt: `pmem` (physical frame allocator), `vmem` (address space + paging abstraction), `allocator.rs` (heap init using Talc). Kernel heap region is dynamically found in higher half; page size & significant VA bits come from ISA module. 14 | - `drivers/` – Early basic drivers (keyboard ps/2, uart ns16550). Pattern: each driver in its own submodule, minimal global state guarded by spin or custom raw mutex. 15 | - `multiprocessing/` – Brings up secondary logical processors (x2APIC requirement noted in README). Uses Limine SMP info. 16 | - `scheduler/` – Early scaffolding (global/local schedulers, simple RR in `local/simple_rr.rs`). Not fully integrated yet—treat as experimental. 17 | - `framebuffer/` – Text/console rendering over UEFI GOP-provided framebuffer; `console.rs` likely central abstraction (scan before extending output paths). 18 | - `log/` – Logging macro(s) (e.g., `logln!`) used pervasively; prefer integrating with existing logging rather than ad-hoc UART writes. 19 | - `panic.rs` – Minimal panic handler logs and halts via `LpControl::halt()`. 20 | 21 | ## 3. Conventions & Patterns 22 | - Use Intel syntax for x86_64 assembly (per README); keep arch-specific asm within the respective ISA tree. 23 | - Avoid introducing non-Rust dependencies unless justified (only Rust/C/asm allowed; prefer Rust crates like existing Talc allocator usage). 24 | - Global resources: guard with `spin::Mutex` or custom `klib::raw_mutex::RawMutex` (wraps `lock_api`). Follow existing allocator initialization style. 25 | - Memory mapping: use `AddressSpaceInterface::map_page` with a `MemoryMapping { vaddr, paddr, page_type }`. Do NOT handcraft page tables in new code; route through the ISA abstraction. 26 | - Architecture selection: gate new ISA code with `#[cfg(target_arch = "x86_64")]` etc and expose via `target` reexport for call sites. 27 | - Logging & panics: always prefer `logln!` before halting. Panic path must remain minimal (no allocations assumed safe). 28 | 29 | ## 4. Build & Run Workflows (Do These, Don’t Recreate) 30 | - Build debug x86_64 ISO: `make build-x86_64-debug` (generates `Catten-x86_64-debug.iso`). 31 | - Run (KVM accel, serial on stdio): `make run-x86_64-debug`. 32 | - Multiprocessor / NUMA test variants: `make run-x86_64-debug-multicore`, `make run-x86_64-debug-numa`. 33 | - Release build: `make run-x86_64-release` (includes build step). 34 | - Other ISAs: `make run-aarch64-debug`, `make run-riscv64-debug` (scaffolding; feature completeness may lag x86_64). 35 | - Clean: `make clean` / `make distclean` (also removes bootloader clone & OVMF blobs). 36 | - Cargo alone: always pass explicit target triple (e.g., `cargo build --target x86_64-unknown-none`). Don’t assume host std env. 37 | 38 | ## 5. Allocator & Memory Gotchas 39 | - Global allocator (`Talc`) is installed in `memory/allocator.rs`; call `init_allocator()` only once after paging + frame allocator ready. 40 | - Heap size constant: `KERNEL_HEAP_PAGE_COUNT` (currently 4 MiB). Expanding it requires ensuring contiguous virtual region available and enough physical frames. 41 | - HIGHER HALF layout is computed, not hardcoded; respect `HIGHER_HALF_START/END` when reserving regions. 42 | 43 | ## 6. Adding New Code Safely 44 | - New driver: create submodule under `drivers/` and expose through `drivers/mod.rs`. Provide a small init function invoked from the architecture-specific init path (search existing init calls first). 45 | - New memory-related feature: extend trait in `isa/interface/memory/*` then implement per-ISA; keep trait minimal—avoid leaking arch-specific types. 46 | - Scheduler changes: coordinate with existing `simple_rr` prototype—avoid breaking current placeholder unless also updating call sites. 47 | - Cross-ISA additions: mirror directory structure (`init/`, `interrupts/`, `memory/`, etc.) for consistency. 48 | 49 | ## 7. Error Handling & Halting 50 | - Kernel frequently halts on unrecoverable errors via `LpControl::halt()`; maintainers prefer explicit expect messages for now (early stage). Provide clear context in `expect()` strings. 51 | 52 | ## 8. Style & Stability Constraints 53 | - Nightly Rust features already enabled; before adding more `#![feature]` gates, justify necessity in PR description. 54 | - Avoid allocations in early init paths before allocator init (self-tests run after allocator & SMP startup). 55 | - Keep public interfaces small, trait-based, and architecture-agnostic. 56 | 57 | ## 9. Quick Reference (When Unsure) 58 | - Entry points: `bsp_main`, `ap_main` in `src/main.rs`. 59 | - Logging macro: search `logln!` usage for patterns. 60 | - Memory traits: `isa/interface/memory/mod.rs`. 61 | - Heap init pattern: `memory/allocator.rs::init_allocator`. 62 | - SMP startup: `multiprocessing::start_secondary_lps()`. 63 | 64 | ## 10. What NOT to Do 65 | - Don’t introduce `std` or host OS assumptions. 66 | - Don’t bypass ISA abstractions for page tables or CPU control registers in shared code. 67 | - Don’t add dependencies outside Rust/C/asm domain. 68 | - Don’t silently unwrap in low-level critical paths—use `expect` with rationale. 69 | 70 | --- 71 | If a task touches initialization order, SMP, or memory mapping, double‑check sequencing against `init::` and `memory::` modules before proceeding. Ask for clarification if a required trait implementation for a non-primary ISA is missing. 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CharlotteOS – Catten 2 | 3 | `Catten` is the kernel of CharlotteOS, designed as a robust, general-purpose monolithic kernel with a strong emphasis on clarity, safety, and architectural flexibility. While developed as part of CharlotteOS, the kernel is structured so that it may also serve as a foundation for other systems. 4 | 5 | Catten draws inspiration from exokernels, Fuchsia, and capability-based microkernel designs while remaining monolithic for performance and simplicity. Its low-level system call interface is intentionally minimal, enabling a wide range of higher-level runtimes and user environments to be layered on top. 6 | 7 | Catten also provides a typed, capability-secured system namespace with URI-like paths, inspired by concepts from Plan 9 and Fuchsia but extended for stronger typing, clean access semantics, and the ability to access another host’s namespace without mounting. Namespace operations are governed by granular capabilities and a persistent mandatory access control policy. 8 | 9 | The kernel is still early in development. Core subsystems are under active construction, and contributions are welcome. Join our discussions on Discord, Matrix, or via the issue tracker if you’re interested in participating. 10 | 11 | --- 12 | 13 | ## Programming Languages 14 | 15 | - Catten is written primarily in Rust, with architecture-specific assembly where required. 16 | - x86-64 assembly uses Intel syntax as implemented by `rustc`/`llvm-mc`. 17 | - Minimal C is permitted for vetted components where a high-quality Rust alternative does not exist. 18 | 19 | --- 20 | 21 | ## External Dependencies 22 | 23 | - Rust, C, and assembly are the only allowed implementation languages. 24 | - Any C dependency must be approved and justified. 25 | - Non-Rust/C/ASM dependencies are not permitted. 26 | - Prefer high-quality Rust crates unless native C is unavoidable due to specification or interoperability constraints. 27 | 28 | --- 29 | 30 | ## Platform & Firmware Requirements 31 | 32 | CharlotteOS aims to support platforms that offer **standardized, documented, and interoperable hardware interfaces**. The focus is on systems where the OS can rely on well-defined firmware and discoverability mechanisms, without requiring vendor-specific hacks or opaque initialization sequences. 33 | 34 | ### Supported Architectures 35 | 36 | #### x86-64 (Primary ISA) 37 | 38 | - Invariant Timestamp Counter 39 | - Local APIC with x2APIC mode 40 | - Full UEFI and ACPI firmware environment 41 | 42 | #### Aarch64 (Secondary ISA) 43 | 44 | - ARMv8.2A or later 45 | - GICv3 or later 46 | - Secure Monitor Call interface with PSCI 47 | - SystemReady Compliant firmware - Full or DT band 48 | 49 | #### RISC-V64 (Secondary ISA) 50 | 51 | - RVA23 or later 52 | - V extension not required 53 | - Supervisor Binary Interface 54 | - BRS-I or BRS-B compliant firmware 55 | 56 | --- 57 | 58 | ## Firmware Model 59 | 60 | Catten supports both ACPI and Devicetree, with equal weight. The format is not the determining factor—**documentation and correctness are.** 61 | 62 | ### UEFI 63 | 64 | - Required for PC/server-class systems on all architectures. 65 | - Provides boot services, memory descriptors, and framebuffer/console access. 66 | 67 | ### ACPI 68 | 69 | - Expected on PC/server-class machines across ISAs and all x86-64 systems. 70 | - ACPI tables must be complete and spec-compliant enough to allow device discovery without vendor-specific workarounds or drivers. 71 | 72 | ### Flattened Devicetree (FDT) 73 | 74 | - Fully supported for SoC-style platforms. 75 | - FDT must conform to DTSpec and accurately describe hardware resources. 76 | - All `compatible` strings must map to publicly documented hardware blocks or IP cores. 77 | 78 | ### Documentation Requirement 79 | 80 | Whether via ACPI or DT: 81 | 82 | - Devices must be identifiable. 83 | - Devices must be documented. 84 | - “Unknown peripheral at address 0xXXXX” is not acceptable without vendor documentation. 85 | 86 | This ensures that Catten can operate without relying on undocumented Linux driver behavior, hard-coded quirks, or vendor-specific hacks. 87 | 88 | --- 89 | 90 | ## Supported Hardware 91 | 92 | ### Memory[^1] 93 | 94 | Embedded: 95 | 96 | - Recommended: ≥ 1 GiB 97 | - Minimum: 128 MiB 98 | 99 | PC and Server: 100 | 101 | - Recommended: ≥ 8 GiB 102 | - Minimum: 4 GiB 103 | 104 | ### Storage[^1] 105 | 106 | - Recommended: ≥ 64 GiB 107 | - Minimum: 4 GiB 108 | - Supported device classes: 109 | - NVMe (PCIe) 110 | - USB Mass Storage (MSC) 111 | 112 | ### Display 113 | 114 | - Linear framebuffer exposed via: 115 | - UEFI GOP 116 | - FDT `simplefb` node 117 | 118 | ### Input Devices 119 | 120 | - Keyboards: 121 | - i8042 PS/2 122 | - USB HID 123 | - I²C HID (documented ACPI/FDT only) 124 | - Pointers: 125 | - USB HID 126 | 127 | ### Serial Console 128 | 129 | - NS16550-compatible UART 130 | - USB CDC-ACM (virtual serial) 131 | 132 | ### Networking 133 | 134 | - USB CDC-NCM (Ethernet over USB) 135 | 136 | --- 137 | 138 | ## Contributing 139 | 140 | We welcome contributions of all forms—code, design proposals, documentation, and testing. 141 | Please join our Discord or Matrix communities if you’d like to get involved. 142 | 143 | Community contributions for new hardware support will only be accepted when they include inline documentation comments with references to publicly available hardware documentation which may include community reverse engineered documentation along with clean, maintainable code. 144 | 145 | --- 146 | 147 | ## Licensing 148 | 149 | Catten is licensed under the GNU Affero General Public License version 3.0 (or any later version). By contributing, you agree that your work may be distributed under the AGPL version 3.0 or later. Your work will also be included in possible commercial licensing arrangements should the project maintainers and community agree to offer those in the future as a source of funding and ecosystem growth. If you object to the latter use then please state that fact in the relevant commit messages and pull requests or send an email with the subject "Commercial Licensing Opt-Out" with all relevant commit hashes and proof that you are their current copyright holder to . 150 | 151 | --- 152 | 153 | ## Community 154 | 155 | Find us on: 156 | 157 | - **Discord:** 158 | - **Matrix:** 159 | - **Reddit** 160 | - **E-Mail** 161 | 162 | [^1]: These requirements are estimates that may change during the course of development. 163 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/common/memory/address/vaddr.rs: -------------------------------------------------------------------------------- 1 | use core::iter::Step; 2 | use core::ops::{Add, Sub}; 3 | 4 | use crate::cpu::isa::interface::memory::address::{Address, VirtualAddress}; 5 | use crate::cpu::isa::memory::address::VADDR_SIG_BITS; 6 | 7 | #[repr(transparent)] 8 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 9 | pub struct VAddr { 10 | raw: usize, 11 | } 12 | 13 | impl core::fmt::Debug for VAddr { 14 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 15 | write!(f, "VAddr({:#x})", self.raw) 16 | } 17 | } 18 | 19 | /// VAddr component indexes and masks 20 | const PAGE_TABLE_INDEX_MASK: usize = 0x1ff; 21 | const PML4_INDEX_SHIFT: usize = 39; 22 | const PML4_INDEX_MASK: usize = PAGE_TABLE_INDEX_MASK << PML4_INDEX_SHIFT; 23 | const PDPT_INDEX_SHIFT: usize = 30; 24 | const PDPT_INDEX_MASK: usize = PAGE_TABLE_INDEX_MASK << PDPT_INDEX_SHIFT; 25 | const PD_INDEX_SHIFT: usize = 21; 26 | const PD_INDEX_MASK: usize = PAGE_TABLE_INDEX_MASK << PD_INDEX_SHIFT; 27 | const PT_INDEX_SHIFT: usize = 12; 28 | const PT_INDEX_MASK: usize = PAGE_TABLE_INDEX_MASK << PT_INDEX_SHIFT; 29 | const OFFSET_MASK: usize = 0xfff; 30 | 31 | impl VAddr { 32 | /// Convenience functions to get the index for each level of the page table hierarchy 33 | 34 | pub fn pml4_index(&self) -> usize { 35 | (self.raw & PML4_INDEX_MASK) >> PML4_INDEX_SHIFT 36 | } 37 | 38 | pub fn pdpt_index(&self) -> usize { 39 | (self.raw & PDPT_INDEX_MASK) >> PDPT_INDEX_SHIFT 40 | } 41 | 42 | pub fn pd_index(&self) -> usize { 43 | (self.raw & PD_INDEX_MASK) >> PD_INDEX_SHIFT 44 | } 45 | 46 | pub fn pt_index(&self) -> usize { 47 | (self.raw & PT_INDEX_MASK) >> PT_INDEX_SHIFT 48 | } 49 | 50 | pub fn page_offset(&self) -> usize { 51 | self.raw & OFFSET_MASK 52 | } 53 | 54 | /// Safety: The address must be in canonical form 55 | pub const unsafe fn from_raw_unchecked(raw: usize) -> Self { 56 | VAddr { 57 | raw, 58 | } 59 | } 60 | } 61 | 62 | impl Address for VAddr { 63 | const MAX: Self = VAddr { 64 | raw: usize::MAX, 65 | }; 66 | const MIN: Self = VAddr { 67 | raw: 0, 68 | }; 69 | const NULL: Self = VAddr { 70 | raw: 0, 71 | }; 72 | 73 | fn is_aligned_to(&self, alignment: usize) -> bool { 74 | self.raw % alignment == 0 75 | } 76 | 77 | fn next_aligned_to(&self, alignment: usize) -> Self { 78 | let mask = alignment - 1; 79 | let aligned = (>::into(*self) + mask) & !mask; 80 | VAddr::from(aligned) 81 | } 82 | 83 | fn prev_aligned_to(&self, alignment: usize) -> Self { 84 | VAddr { 85 | raw: if alignment % 2 == 0 { 86 | self.raw & !(alignment - 1) 87 | } else { 88 | self.raw - (self.raw % alignment) 89 | }, 90 | } 91 | } 92 | 93 | fn is_valid(value: usize) -> bool { 94 | value != 0 95 | } 96 | 97 | fn is_null(&self) -> bool { 98 | self.raw == 0 99 | } 100 | 101 | unsafe fn from_unchecked(addr: usize) -> Self { 102 | VAddr { 103 | raw: addr, 104 | } 105 | } 106 | } 107 | 108 | impl VirtualAddress for VAddr { 109 | fn from_ptr(ptr: *const T) -> Self { 110 | VAddr { 111 | raw: ptr as usize, 112 | } 113 | } 114 | 115 | fn from_mut(ptr: *mut T) -> Self { 116 | VAddr { 117 | raw: ptr as usize, 118 | } 119 | } 120 | 121 | fn into_ptr(self) -> *const T { 122 | self.raw as *const T 123 | } 124 | 125 | fn into_mut(self) -> *mut T { 126 | self.raw as *mut T 127 | } 128 | } 129 | 130 | impl From for VAddr { 131 | fn from(value: usize) -> Self { 132 | let mask = (1 << *VADDR_SIG_BITS) - 1; 133 | let sign_extended = if value & (1 << (*VADDR_SIG_BITS - 1)) != 0 { 134 | value | (!mask) 135 | } else { 136 | value & mask 137 | }; 138 | VAddr { 139 | raw: sign_extended, 140 | } 141 | } 142 | } 143 | 144 | impl Into for VAddr { 145 | fn into(self) -> usize { 146 | self.raw 147 | } 148 | } 149 | 150 | impl From for VAddr { 151 | fn from(value: u64) -> Self { 152 | VAddr::from(value as usize) 153 | } 154 | } 155 | impl Into for VAddr { 156 | fn into(self) -> u64 { 157 | self.raw as u64 158 | } 159 | } 160 | 161 | impl Sub for VAddr { 162 | type Output = isize; 163 | 164 | fn sub(self, other: Self) -> Self::Output { 165 | self.raw as isize - other.raw as isize 166 | } 167 | } 168 | 169 | impl Add for VAddr { 170 | type Output = VAddr; 171 | 172 | fn add(self, other: isize) -> Self::Output { 173 | VAddr { 174 | raw: (self.raw as isize + other) as usize, 175 | } 176 | } 177 | } 178 | 179 | impl Sub for VAddr { 180 | type Output = VAddr; 181 | 182 | fn sub(self, other: isize) -> Self::Output { 183 | VAddr::from(self.raw - other as usize) 184 | } 185 | } 186 | 187 | impl Add for VAddr { 188 | type Output = VAddr; 189 | 190 | fn add(self, other: usize) -> Self::Output { 191 | VAddr::from(self.raw + other) 192 | } 193 | } 194 | 195 | impl Sub for VAddr { 196 | type Output = VAddr; 197 | 198 | fn sub(self, other: usize) -> Self::Output { 199 | VAddr::from(self.raw - other) 200 | } 201 | } 202 | 203 | impl Step for VAddr { 204 | fn steps_between(start: &Self, end: &Self) -> (usize, Option) { 205 | if start > end { 206 | (0, None) 207 | } else { 208 | (end.raw - start.raw, Some(end.raw - start.raw)) 209 | } 210 | } 211 | 212 | fn forward_checked(start: Self, count: usize) -> Option { 213 | if start.raw.saturating_add(count) < usize::MAX { 214 | Some(VAddr { 215 | raw: start.raw + count, 216 | }) 217 | } else { 218 | None 219 | } 220 | } 221 | 222 | fn backward_checked(start: Self, count: usize) -> Option { 223 | if start.raw.saturating_sub(count) > usize::MIN { 224 | Some(VAddr { 225 | raw: start.raw - count, 226 | }) 227 | } else { 228 | None 229 | } 230 | } 231 | } 232 | 233 | impl Default for VAddr { 234 | fn default() -> Self { 235 | VAddr { 236 | raw: 0, 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /catten/src/memory/linear/address_map.rs: -------------------------------------------------------------------------------- 1 | //! # Linear Address Map 2 | 3 | use spin::Lazy; 4 | 5 | use super::VAddr; 6 | use crate::common::size::*; 7 | use crate::cpu::isa::memory::address::VADDR_SIG_BITS; 8 | 9 | /// The rest of the kernel only sees the correct linear address map for the system it is running on 10 | pub static LA_MAP: Lazy<&'static LinearAddressMap> = Lazy::new(|| match *VADDR_SIG_BITS { 11 | 39 => &LA_MAP_39BIT, 12 | 48 => &LA_MAP_48BIT, 13 | 57 => &LA_MAP_57BIT, 14 | _ => panic!("Unsupported virtual address size"), 15 | }); 16 | 17 | static LA_MAP_39BIT: Lazy = Lazy::new(|| LinearAddressMap { 18 | null_page: LinearMemoryRegion { 19 | base: VAddr::from(0x0000_0000_0000_0000usize), 20 | length: kibibytes(4), 21 | }, 22 | application: LinearMemoryRegion { 23 | base: VAddr::from(0x0000000000001000usize), 24 | length: gibibytes(512), 25 | }, 26 | direct_mapping: LinearMemoryRegion { 27 | base: VAddr::from(0xffffff8000000000usize), 28 | length: gibibytes(512), 29 | }, 30 | kernel_stack_arena: LinearMemoryRegion { 31 | base: VAddr::from(0xffffff0000000000usize), 32 | length: gibibytes(4), 33 | }, 34 | kernel_mmio: LinearMemoryRegion { 35 | base: VAddr::from(0xffffff0800000000usize), 36 | length: gibibytes(4), 37 | }, 38 | kenrnel_allocator_arena: LinearMemoryRegion { 39 | base: VAddr::from(0xffffff1000000000usize), 40 | length: gibibytes(988), 41 | }, 42 | kernel_image: LinearMemoryRegion { 43 | base: VAddr::from(0xffffffff80000000usize), 44 | length: gibibytes(2), 45 | }, 46 | }); 47 | 48 | static LA_MAP_48BIT: Lazy = Lazy::new(|| LinearAddressMap { 49 | null_page: LinearMemoryRegion { 50 | base: VAddr::from(0x0000_0000_0000_0000usize), 51 | length: kibibytes(4), 52 | }, 53 | application: LinearMemoryRegion { 54 | base: VAddr::from(0x0000000000001000usize), 55 | length: tebibytes(256), 56 | }, 57 | direct_mapping: LinearMemoryRegion { 58 | base: VAddr::from(0xffffff8000000000usize), 59 | length: tebibytes(256), 60 | }, 61 | kernel_stack_arena: LinearMemoryRegion { 62 | base: VAddr::from(0xffff800000000000usize), 63 | length: tebibytes(2), 64 | }, 65 | kernel_mmio: LinearMemoryRegion { 66 | base: VAddr::from(0xffff820000000000usize), 67 | length: tebibytes(2), 68 | }, 69 | kenrnel_allocator_arena: LinearMemoryRegion { 70 | base: VAddr::from(0xffff840000000000usize), 71 | length: tebibytes(506), 72 | }, 73 | kernel_image: LinearMemoryRegion { 74 | base: VAddr::from(0xffffffff80000000usize), 75 | length: gibibytes(2), 76 | }, 77 | }); 78 | 79 | static LA_MAP_57BIT: Lazy = Lazy::new(|| LinearAddressMap { 80 | null_page: LinearMemoryRegion { 81 | base: VAddr::from(0x0000_0000_0000_0000usize), 82 | length: kibibytes(4), 83 | }, 84 | application: LinearMemoryRegion { 85 | base: VAddr::from(0x0000000000001000usize), 86 | length: pebibytes(128), 87 | }, 88 | direct_mapping: LinearMemoryRegion { 89 | base: VAddr::from(0xffffff8000000000usize), 90 | length: pebibytes(128), 91 | }, 92 | kernel_stack_arena: LinearMemoryRegion { 93 | base: VAddr::from(0xff80000000000000usize), 94 | length: pebibytes(1), 95 | }, 96 | kernel_mmio: LinearMemoryRegion { 97 | base: VAddr::from(0xff88000000000000usize), 98 | length: pebibytes(1), 99 | }, 100 | kenrnel_allocator_arena: LinearMemoryRegion { 101 | base: VAddr::from(0xff90000000000000usize), 102 | length: pebibytes(253), 103 | }, 104 | kernel_image: LinearMemoryRegion { 105 | base: VAddr::from(0xffffffff80000000usize), 106 | length: gibibytes(2), 107 | }, 108 | }); 109 | 110 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 111 | pub enum RegionType { 112 | NullPage, 113 | Application, 114 | DirectMapping, 115 | KernelStackArena, 116 | KernelMmio, 117 | KernelAllocatorArena, 118 | KernelImage, 119 | } 120 | 121 | pub struct LinearAddressMap { 122 | null_page: LinearMemoryRegion, 123 | application: LinearMemoryRegion, 124 | direct_mapping: LinearMemoryRegion, 125 | kernel_stack_arena: LinearMemoryRegion, 126 | kernel_mmio: LinearMemoryRegion, 127 | kenrnel_allocator_arena: LinearMemoryRegion, 128 | kernel_image: LinearMemoryRegion, 129 | } 130 | 131 | impl LinearAddressMap { 132 | pub fn region_type(&self, addr: VAddr) -> RegionType { 133 | if self.null_page.contains(addr) { 134 | RegionType::NullPage 135 | } else if self.application.contains(addr) { 136 | RegionType::Application 137 | } else if self.direct_mapping.contains(addr) { 138 | RegionType::DirectMapping 139 | } else if self.kernel_stack_arena.contains(addr) { 140 | RegionType::KernelStackArena 141 | } else if self.kernel_mmio.contains(addr) { 142 | RegionType::KernelMmio 143 | } else if self.kenrnel_allocator_arena.contains(addr) { 144 | RegionType::KernelAllocatorArena 145 | } else if self.kernel_image.contains(addr) { 146 | RegionType::KernelImage 147 | } else { 148 | unreachable!( 149 | "This should be unreachable because the entire address space is tightly mapped \ 150 | and the VAddr type guarantees a valid address." 151 | ); 152 | } 153 | } 154 | 155 | pub fn get_region(&self, region: RegionType) -> &LinearMemoryRegion { 156 | match region { 157 | RegionType::NullPage => &self.null_page, 158 | RegionType::Application => &self.application, 159 | RegionType::DirectMapping => &self.direct_mapping, 160 | RegionType::KernelStackArena => &self.kernel_stack_arena, 161 | RegionType::KernelMmio => &self.kernel_mmio, 162 | RegionType::KernelAllocatorArena => &self.kenrnel_allocator_arena, 163 | RegionType::KernelImage => &self.kernel_image, 164 | } 165 | } 166 | } 167 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 168 | pub struct LinearMemoryRegion { 169 | pub base: VAddr, 170 | pub length: usize, 171 | } 172 | 173 | impl LinearMemoryRegion { 174 | pub fn contains(&self, addr: VAddr) -> bool { 175 | addr >= self.base && addr < (self.base + (self.length as isize)) 176 | } 177 | } 178 | 179 | impl Into<(VAddr, VAddr)> for LinearMemoryRegion { 180 | fn into(self) -> (VAddr, VAddr) { 181 | (self.base, self.base + self.length as isize) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /catten/src/drivers/input/i8042.rs: -------------------------------------------------------------------------------- 1 | //! # Intel 8042 Compatible Input Controller Driver 2 | //! 3 | //! This is the driver for the I8042 PS/2 Controller. 4 | //! This uses interrupts for I/O so make sure these are handled. 5 | 6 | use crate::cpu::isa::io::{IReg8Ifce, IoReg8, OReg8Ifce}; 7 | 8 | // I/O Ports. 9 | const DATA_PORT: u16 = 0x60; 10 | const STATUS_PORT: u16 = 0x64; 11 | 12 | // Status bits. 13 | const STATUS_OUTPUT_BUFFER: u8 = 1 << 0; 14 | const STATUS_INPUT_BUFFER: u8 = 1 << 1; 15 | 16 | // Commands. 17 | const CMD_DISABLE_KBD: u8 = 0xad; 18 | const CMD_ENABLE_KBD: u8 = 0xae; 19 | const CMD_DISABLE_MOUSE: u8 = 0xa7; 20 | const CMD_ENABLE_MOUSE: u8 = 0xa8; 21 | 22 | // Tests. 23 | const CMD_CONTROLLER_TEST: u8 = 0xaa; 24 | const CONTROLLER_TEST_OK: u8 = 0x55; 25 | const CMD_KBD_INTERFACE_TEST: u8 = 0xab; 26 | const CMD_MOUSE_INTERFACE_TEST: u8 = 0xa9; 27 | const INTERFACE_TEST_OK: u8 = 0x00; 28 | 29 | const CMD_ACK: u8 = 0xfa; 30 | 31 | // Device commands. 32 | const KBD_RESET: u8 = 0xff; 33 | 34 | const KBD_ENABLE_SCANNING: u8 = 0xf4; 35 | const KBD_DISABLE_SCANNING: u8 = 0xf5; 36 | 37 | const MOUSE_ENABLE_REPORTING: u8 = 0xf4; 38 | const MOUSE_DISABLE_REPORTING: u8 = 0xf5; 39 | 40 | pub struct I8042 { 41 | data: IoReg8, 42 | status: IoReg8, 43 | } 44 | 45 | pub struct Ps2Status { 46 | pub keyboard_ok: bool, 47 | pub mouse_ok: bool, 48 | } 49 | 50 | impl I8042 { 51 | fn wait_input_empty(&self) { 52 | unsafe { while self.status.read() & STATUS_INPUT_BUFFER != 0 {} } 53 | } 54 | 55 | fn wait_output_full(&self) { 56 | unsafe { while self.status.read() & STATUS_OUTPUT_BUFFER == 0 {} } 57 | } 58 | 59 | unsafe fn send_keyboard_command(&self, cmd: u8) -> bool { 60 | unsafe { 61 | self.wait_input_empty(); 62 | self.data.write(cmd); 63 | self.wait_output_full(); 64 | return self.data.read() == CMD_ACK; 65 | } 66 | } 67 | 68 | unsafe fn send_mouse_command(&self, cmd: u8) -> bool { 69 | unsafe { 70 | self.wait_input_empty(); 71 | self.status.write(0xd4); 72 | 73 | self.wait_input_empty(); 74 | self.data.write(cmd); 75 | 76 | self.wait_output_full(); 77 | return self.data.read() == CMD_ACK; 78 | } 79 | } 80 | 81 | ///# Main initialize function for the `I8042` driver. 82 | /// Initialize the PS/2 controller and device. 83 | /// Returns `Ok(I8042)` if both keyboard and mouse are successfully initialized. 84 | /// Else returns `Err(Ps2Status)` and states which devices failed. 85 | pub fn try_new() -> Result { 86 | // The status. 87 | let mut status = Ps2Status { 88 | keyboard_ok: false, 89 | mouse_ok: false, 90 | }; 91 | 92 | let driver = I8042 { 93 | data: IoReg8::IoPort(DATA_PORT), 94 | status: IoReg8::IoPort(STATUS_PORT), 95 | }; 96 | 97 | unsafe { 98 | // Disable first. 99 | driver.wait_input_empty(); 100 | driver.status.write(CMD_DISABLE_KBD); 101 | driver.wait_input_empty(); 102 | driver.status.write(CMD_DISABLE_MOUSE); 103 | 104 | // Flush. 105 | while driver.status.read() & STATUS_OUTPUT_BUFFER != 0 { 106 | let _nothing = driver.data.read(); 107 | } 108 | 109 | driver.wait_input_empty(); 110 | driver.status.write(CMD_CONTROLLER_TEST); 111 | driver.wait_output_full(); 112 | if driver.data.read() != CONTROLLER_TEST_OK { 113 | return Err(Ps2Status { 114 | keyboard_ok: false, 115 | mouse_ok: false, 116 | }); 117 | } 118 | 119 | // Read CCB. 120 | driver.wait_input_empty(); 121 | driver.status.write(0x20); 122 | driver.wait_output_full(); 123 | let mut ccb = driver.data.read(); 124 | 125 | // CCB Disable interrupts and disable translation. 126 | ccb &= !0b01000011; 127 | 128 | // Write CCB. 129 | driver.wait_input_empty(); 130 | driver.status.write(0x60); 131 | driver.wait_input_empty(); 132 | driver.data.write(ccb); 133 | 134 | driver.wait_input_empty(); 135 | driver.status.write(CMD_KBD_INTERFACE_TEST); 136 | driver.wait_output_full(); 137 | if driver.data.read() != INTERFACE_TEST_OK { 138 | // Test failed. TODO: Add something here. 139 | } 140 | 141 | driver.wait_input_empty(); 142 | driver.status.write(CMD_MOUSE_INTERFACE_TEST); 143 | driver.wait_output_full(); 144 | if driver.data.read() != INTERFACE_TEST_OK { 145 | // Mouse port interface failed. TODO: Same here. 146 | } 147 | 148 | // Keyboard. 149 | driver.wait_input_empty(); 150 | driver.status.write(CMD_ENABLE_KBD); 151 | if driver.send_keyboard_command(KBD_RESET) { 152 | if driver.send_keyboard_command(KBD_ENABLE_SCANNING) { 153 | status.keyboard_ok = true; 154 | } 155 | } 156 | 157 | // Mouse. 158 | driver.wait_input_empty(); 159 | driver.status.write(CMD_ENABLE_MOUSE); 160 | if driver.send_mouse_command(MOUSE_ENABLE_REPORTING) { 161 | status.mouse_ok = true; 162 | } 163 | 164 | driver.wait_input_empty(); 165 | driver.status.write(0x20); // CCB 166 | driver.wait_output_full(); 167 | let mut final_ccb = driver.data.read(); 168 | 169 | if status.keyboard_ok { 170 | final_ccb |= 0b00000001; 171 | } 172 | if status.mouse_ok { 173 | final_ccb |= 0b00000010; 174 | } 175 | 176 | driver.wait_input_empty(); 177 | driver.status.write(0x60); 178 | driver.wait_input_empty(); 179 | driver.data.write(final_ccb); 180 | } 181 | 182 | if status.keyboard_ok && status.mouse_ok { 183 | return Ok(driver); 184 | } else { 185 | return Err(status); 186 | } 187 | } 188 | 189 | /// Enable mouse data reporting. 190 | pub fn enable_mouse_packets(&self) -> bool { 191 | unsafe { 192 | return self.send_mouse_command(MOUSE_ENABLE_REPORTING); 193 | } 194 | } 195 | 196 | /// Disable mouse data reporting. 197 | pub fn disable_mouse_packets(&self) -> bool { 198 | unsafe { 199 | return self.send_mouse_command(MOUSE_DISABLE_REPORTING); 200 | } 201 | } 202 | 203 | /// Enable keyboard scanning (start sending key scancodes). 204 | pub fn enable_keyboard(&self) -> bool { 205 | unsafe { 206 | return self.send_keyboard_command(KBD_ENABLE_SCANNING); 207 | } 208 | } 209 | 210 | /// Disable keyboard scanning (stop sending key scancodes). 211 | pub fn disable_keyboard(&self) -> bool { 212 | unsafe { return self.send_keyboard_command(KBD_DISABLE_SCANNING) } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /catten/src/cpu/isa/x86_64/interrupts/exceptions/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::isa::init::gdt; 2 | use crate::cpu::isa::interrupts::idt::Idt; 3 | use crate::logln; 4 | 5 | pub fn load_exceptions(idt: &mut Idt) { 6 | idt.set_gate(0, isr_divide_by_zero, gdt::KERNEL_CODE_SELECTOR, true, true); 7 | idt.set_gate(1, isr_debug, gdt::KERNEL_CODE_SELECTOR, true, false); 8 | idt.set_gate(2, isr_non_maskable_interrupt, gdt::KERNEL_CODE_SELECTOR, true, false); 9 | idt.set_gate(3, isr_breakpoint, gdt::KERNEL_CODE_SELECTOR, true, false); 10 | idt.set_gate(4, isr_overflow, gdt::KERNEL_CODE_SELECTOR, true, false); 11 | idt.set_gate(5, isr_bound_range_exceeded, gdt::KERNEL_CODE_SELECTOR, true, false); 12 | idt.set_gate(6, isr_invalid_opcode, gdt::KERNEL_CODE_SELECTOR, true, false); 13 | idt.set_gate(7, isr_device_not_available, gdt::KERNEL_CODE_SELECTOR, true, false); 14 | idt.set_gate(8, isr_double_fault, gdt::KERNEL_CODE_SELECTOR, true, true); 15 | idt.set_gate(10, isr_invalid_tss, gdt::KERNEL_CODE_SELECTOR, true, false); 16 | idt.set_gate(11, isr_segment_not_present, gdt::KERNEL_CODE_SELECTOR, true, true); 17 | idt.set_gate(12, isr_stack_segment_fault, gdt::KERNEL_CODE_SELECTOR, true, false); 18 | idt.set_gate(13, isr_general_protection_fault, gdt::KERNEL_CODE_SELECTOR, true, true); 19 | idt.set_gate(14, isr_page_fault, gdt::KERNEL_CODE_SELECTOR, true, true); 20 | idt.set_gate(16, isr_x87_floating_point, gdt::KERNEL_CODE_SELECTOR, true, false); 21 | idt.set_gate(17, isr_alignment_check, gdt::KERNEL_CODE_SELECTOR, true, false); 22 | idt.set_gate(18, isr_machine_check, gdt::KERNEL_CODE_SELECTOR, true, true); 23 | idt.set_gate(19, isr_simd_floating_point, gdt::KERNEL_CODE_SELECTOR, true, false); 24 | idt.set_gate(20, isr_virtualization, gdt::KERNEL_CODE_SELECTOR, true, false); 25 | idt.set_gate(21, isr_control_protection, gdt::KERNEL_CODE_SELECTOR, true, false); 26 | idt.set_gate(28, isr_hypervisor_injection, gdt::KERNEL_CODE_SELECTOR, true, false); 27 | idt.set_gate(29, isr_vmm_communication, gdt::KERNEL_CODE_SELECTOR, true, false); 28 | idt.set_gate(30, isr_security_exception, gdt::KERNEL_CODE_SELECTOR, true, false); 29 | } 30 | 31 | core::arch::global_asm! { 32 | include_str!("exceptions.asm"), 33 | } 34 | unsafe extern "C" { 35 | fn isr_divide_by_zero(); 36 | fn isr_debug(); 37 | fn isr_non_maskable_interrupt(); 38 | fn isr_breakpoint(); 39 | fn isr_overflow(); 40 | fn isr_bound_range_exceeded(); 41 | fn isr_invalid_opcode(); 42 | fn isr_device_not_available(); 43 | fn isr_double_fault(); 44 | fn isr_invalid_tss(); 45 | fn isr_stack_segment_fault(); 46 | fn isr_general_protection_fault(); 47 | fn isr_segment_not_present(); 48 | fn isr_page_fault(); 49 | fn isr_x87_floating_point(); 50 | fn isr_alignment_check(); 51 | fn isr_machine_check(); 52 | fn isr_simd_floating_point(); 53 | fn isr_virtualization(); 54 | fn isr_control_protection(); 55 | fn isr_hypervisor_injection(); 56 | fn isr_vmm_communication(); 57 | fn isr_security_exception(); 58 | } 59 | 60 | #[unsafe(no_mangle)] 61 | extern "C" fn ih_double_fault(_error_code: u64) { 62 | logln!("A double fault has occurred in kernelspace! Panicking!"); 63 | panic!("Double fault"); 64 | } 65 | 66 | #[unsafe(no_mangle)] 67 | extern "C" fn ih_divide_by_zero() { 68 | logln!("Divide by zero exception occurred!"); 69 | panic!("Divide by zero"); 70 | } 71 | 72 | #[unsafe(no_mangle)] 73 | extern "C" fn ih_debug() { 74 | logln!("Debug exception occurred!"); 75 | panic!("Debug exception"); 76 | } 77 | 78 | #[unsafe(no_mangle)] 79 | extern "C" fn ih_non_maskable_interrupt() { 80 | logln!("Non-maskable interrupt occurred!"); 81 | panic!("Non-maskable interrupt"); 82 | } 83 | 84 | #[unsafe(no_mangle)] 85 | extern "C" fn ih_breakpoint() { 86 | logln!("Breakpoint exception occurred!"); 87 | panic!("Breakpoint exception"); 88 | } 89 | 90 | #[unsafe(no_mangle)] 91 | extern "C" fn ih_overflow() { 92 | logln!("Overflow exception occurred!"); 93 | panic!("Overflow exception"); 94 | } 95 | 96 | #[unsafe(no_mangle)] 97 | extern "C" fn ih_bound_range_exceeded() { 98 | logln!("Bound range exceeded exception occurred!"); 99 | panic!("Bound range exceeded"); 100 | } 101 | 102 | #[unsafe(no_mangle)] 103 | extern "C" fn ih_invalid_opcode() { 104 | logln!("Invalid opcode exception occurred!"); 105 | panic!("Invalid opcode"); 106 | } 107 | 108 | #[unsafe(no_mangle)] 109 | extern "C" fn ih_device_not_available() { 110 | logln!("Device not available exception occurred!"); 111 | panic!("Device not available"); 112 | } 113 | 114 | #[unsafe(no_mangle)] 115 | extern "C" fn ih_invalid_tss() { 116 | logln!("Invalid TSS exception occurred!"); 117 | panic!("Invalid TSS"); 118 | } 119 | 120 | #[unsafe(no_mangle)] 121 | extern "C" fn ih_segment_not_present() { 122 | logln!("Segment not present exception occurred!"); 123 | panic!("Segment not present"); 124 | } 125 | 126 | #[unsafe(no_mangle)] 127 | extern "C" fn ih_stack_segment_fault() { 128 | logln!("Stack segment fault occurred!"); 129 | panic!("Stack segment fault"); 130 | } 131 | 132 | #[unsafe(no_mangle)] 133 | extern "C" fn ih_general_protection_fault(_error_code: u64) { 134 | logln!("General protection fault occurred!"); 135 | panic!("General protection fault"); 136 | } 137 | 138 | #[unsafe(no_mangle)] 139 | extern "C" fn ih_page_fault(error_code: u64) { 140 | logln!("Page fault occurred with error code {:X}!", error_code); 141 | let pf_addr: u64; 142 | unsafe { 143 | core::arch::asm!("mov {0}, cr2", out(reg) pf_addr); 144 | } 145 | logln!("Page fault address: {:x}", pf_addr); 146 | panic!("Page fault"); 147 | } 148 | 149 | #[unsafe(no_mangle)] 150 | extern "C" fn ih_x87_floating_point() { 151 | logln!("x87 floating point exception occurred!"); 152 | panic!("x87 floating point exception"); 153 | } 154 | 155 | #[unsafe(no_mangle)] 156 | extern "C" fn ih_alignment_check() { 157 | logln!("Alignment check exception occurred!"); 158 | panic!("Alignment check"); 159 | } 160 | 161 | #[unsafe(no_mangle)] 162 | extern "C" fn ih_machine_check() { 163 | logln!("Machine check exception occurred!"); 164 | panic!("Machine check"); 165 | } 166 | 167 | #[unsafe(no_mangle)] 168 | extern "C" fn ih_simd_floating_point() { 169 | logln!("SIMD floating point exception occurred!"); 170 | panic!("SIMD floating point exception"); 171 | } 172 | 173 | #[unsafe(no_mangle)] 174 | extern "C" fn ih_virtualization() { 175 | logln!("Virtualization exception occurred!"); 176 | panic!("Virtualization exception"); 177 | } 178 | 179 | #[unsafe(no_mangle)] 180 | extern "C" fn ih_control_protection() { 181 | logln!("Control protection exception occurred!"); 182 | panic!("Control protection exception"); 183 | } 184 | 185 | #[unsafe(no_mangle)] 186 | extern "C" fn ih_hypervisor_injection() { 187 | logln!("Hypervisor injection exception occurred!"); 188 | panic!("Hypervisor injection"); 189 | } 190 | 191 | #[unsafe(no_mangle)] 192 | extern "C" fn ih_vmm_communication() { 193 | logln!("VMM communication exception occurred!"); 194 | panic!("VMM communication"); 195 | } 196 | 197 | #[unsafe(no_mangle)] 198 | extern "C" fn ih_security_exception() { 199 | logln!("Security exception occurred!"); 200 | panic!("Security exception"); 201 | } 202 | --------------------------------------------------------------------------------